From adb660f1ad53abb0d8cbfa18db476747c9005dfe Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 24 Oct 2020 13:11:44 +0000 Subject: [PATCH 01/23] Issue 9451: We now do store the tab in a config again --- src/Module/Conversation/Network.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Module/Conversation/Network.php b/src/Module/Conversation/Network.php index abe836f27a..ebccc0a3a2 100644 --- a/src/Module/Conversation/Network.php +++ b/src/Module/Conversation/Network.php @@ -283,7 +283,7 @@ class Network extends BaseModule self::$forumContactId = $parameters['contact_id'] ?? 0; - self::$selectedTab = Session::get('network-tab', ''); + self::$selectedTab = Session::get('network-tab', DI::pConfig()->get(local_user(), 'network.view', 'selected_tab', '')); if (!empty($get['star'])) { self::$selectedTab = 'star'; @@ -297,8 +297,6 @@ class Network extends BaseModule self::$selectedTab = $get['order']; } - Session::set('network-tab', self::$selectedTab); - self::$star = intval($get['star'] ?? 0); self::$mention = intval($get['mention'] ?? 0); self::$order = $get['order'] ?? Session::get('network-order', 'commented'); @@ -306,7 +304,7 @@ class Network extends BaseModule self::$selectedTab = self::$selectedTab ?? self::$order; Session::set('network-tab', self::$selectedTab); - Session::set('network-order', self::$order); + DI::pConfig()->set(local_user(), 'network.view', 'selected_tab', self::$selectedTab); self::$accountTypeString = $get['accounttype'] ?? $parameters['accounttype'] ?? ''; self::$accountType = User::getAccountTypeByString(self::$accountTypeString); @@ -340,7 +338,12 @@ class Network extends BaseModule case 'uriid': self::$max_id = $get['last_uriid'] ?? self::$max_id; break; + default: + self::$order = 'commented'; + self::$max_id = $get['last_commented'] ?? self::$max_id; } + + Session::set('network-order', self::$order); } protected static function getItems(string $table, array $params, array $conditionFields = []) From 403c9e456e87b15d312991dc53fd4bc8b75f7b4b Mon Sep 17 00:00:00 2001 From: Hypolite Petovan Date: Sat, 24 Oct 2020 09:12:26 -0400 Subject: [PATCH 02/23] [Actions] Downgrade Composer to v1 - The current fxp/composer-asset-plugin version we use is incompatible with Composer v2 --- .github/workflows/php.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index cf1ad13670..91a8b60747 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -37,7 +37,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-versions }} - tools: pecl + tools: pecl, composer:v1 extensions: pdo_mysql, gd, zip, opcache, ctype, pcntl, ldap, apcu, memcached, redis, imagick, memcache coverage: xdebug ini-values: apc.enabled=1, apc.enable_cli=1 From 8bd45ab857e174382432fac0849111c8eb28fa46 Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 24 Oct 2020 13:44:31 +0000 Subject: [PATCH 03/23] "commented" is the default --- src/Module/Conversation/Network.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Module/Conversation/Network.php b/src/Module/Conversation/Network.php index ebccc0a3a2..b19f409025 100644 --- a/src/Module/Conversation/Network.php +++ b/src/Module/Conversation/Network.php @@ -329,9 +329,6 @@ class Network extends BaseModule case 'received': self::$max_id = $get['last_received'] ?? self::$max_id; break; - case 'commented': - self::$max_id = $get['last_commented'] ?? self::$max_id; - break; case 'created': self::$max_id = $get['last_created'] ?? self::$max_id; break; From fd0e5cfe48c38135adb816c77cd85bd1a33c97c9 Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 24 Oct 2020 18:40:15 +0000 Subject: [PATCH 04/23] Added logging for executing child processes --- src/Core/Process.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Core/Process.php b/src/Core/Process.php index f8958f102c..06653cbe31 100644 --- a/src/Core/Process.php +++ b/src/Core/Process.php @@ -225,6 +225,7 @@ class Process public function run($command, $args) { if (!function_exists('proc_open')) { + $this->logger->notice('"proc_open" not available - quitting'); return; } @@ -242,6 +243,7 @@ class Process } if ($this->isMinMemoryReached()) { + $this->logger->notice('Memory limit reached - quitting'); return; } @@ -251,9 +253,11 @@ class Process $resource = proc_open($cmdline . ' &', [], $foo, $this->basePath); } if (!is_resource($resource)) { - $this->logger->debug('We got no resource for command.', ['cmd' => $cmdline]); + $this->logger->notice('We got no resource for command.', ['command' => $cmdline]); return; } proc_close($resource); + + $this->logger->info('Executed "proc_open"', ['command' => $cmdline, 'callstack' => System::callstack(10)]); } } From d639912f3869105aa644b7c620f6266fb43710f9 Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 24 Oct 2020 19:33:38 +0000 Subject: [PATCH 05/23] Disable the "proc_open" option when it isn't available --- src/Module/Admin/Site.php | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Module/Admin/Site.php b/src/Module/Admin/Site.php index ea2b5604e3..a5ca196214 100644 --- a/src/Module/Admin/Site.php +++ b/src/Module/Admin/Site.php @@ -417,7 +417,11 @@ class Site extends BaseAdmin DI::config()->set('system', 'only_tag_search' , $only_tag_search); DI::config()->set('system', 'worker_queues' , $worker_queues); - DI::config()->set('system', 'worker_dont_fork' , $worker_dont_fork); + + if (function_exists('proc_open')) { + DI::config()->set('system', 'worker_dont_fork', $worker_dont_fork); + } + DI::config()->set('system', 'worker_fastlane' , $worker_fastlane); DI::config()->set('system', 'frontend_worker' , $worker_frontend); @@ -578,6 +582,14 @@ class Site extends BaseAdmin } } + if (function_exists('proc_open')) { + $worker_dont_fork = DI::config()->get('system', 'worker_dont_fork'); + $worker_dont_fork_disabled = ''; + } else { + $worker_dont_fork = true; + $worker_dont_fork_disabled = 'disabled'; + } + $t = Renderer::getMarkupTemplate('admin/site.tpl'); return Renderer::replaceMacros($t, [ '$title' => DI::l10n()->t('Administration'), @@ -689,7 +701,7 @@ class Site extends BaseAdmin '$rino' => ['rino', DI::l10n()->t('RINO Encryption'), intval(DI::config()->get('system', 'rino_encrypt')), DI::l10n()->t('Encryption layer between nodes.'), [0 => DI::l10n()->t('Disabled'), 1 => DI::l10n()->t('Enabled')]], '$worker_queues' => ['worker_queues', DI::l10n()->t('Maximum number of parallel workers'), DI::config()->get('system', 'worker_queues'), DI::l10n()->t('On shared hosters set this to %d. On larger systems, values of %d are great. Default value is %d.', 5, 20, 10)], - '$worker_dont_fork' => ['worker_dont_fork', DI::l10n()->t('Don\'t use "proc_open" with the worker'), DI::config()->get('system', 'worker_dont_fork'), DI::l10n()->t('Enable this if your system doesn\'t allow the use of "proc_open". This can happen on shared hosters. If this is enabled you should increase the frequency of worker calls in your crontab.')], + '$worker_dont_fork' => ['worker_dont_fork', DI::l10n()->t('Don\'t use "proc_open" with the worker'), $worker_dont_fork, DI::l10n()->t('Enable this if your system doesn\'t allow the use of "proc_open". This can happen on shared hosters. If this is enabled you should increase the frequency of worker calls in your crontab.'), $worker_dont_fork_disabled], '$worker_fastlane' => ['worker_fastlane', DI::l10n()->t('Enable fastlane'), DI::config()->get('system', 'worker_fastlane'), DI::l10n()->t('When enabed, the fastlane mechanism starts an additional worker if processes with higher priority are blocked by processes of lower priority.')], '$worker_frontend' => ['worker_frontend', DI::l10n()->t('Enable frontend worker'), DI::config()->get('system', 'frontend_worker'), DI::l10n()->t('When enabled the Worker process is triggered when backend access is performed (e.g. messages being delivered). On smaller sites you might want to call %s/worker on a regular basis via an external cron job. You should only enable this option if you cannot utilize cron/scheduled jobs on your server.', DI::baseUrl()->get())], From 89509f02e43bff4ab445201b3d76597ce97207d7 Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 24 Oct 2020 21:42:49 +0000 Subject: [PATCH 06/23] Move "remote self" to the contact settings --- doc/Settings.md | 2 +- doc/de/Settings.md | 2 +- src/Module/Contact.php | 25 +++++++++++++++++-- src/Module/Contact/Advanced.php | 22 ---------------- view/templates/contact/advanced.tpl | 6 ----- view/templates/contact_edit.tpl | 4 +++ .../theme/frio/templates/contact/advanced.tpl | 6 ----- view/theme/frio/templates/contact_edit.tpl | 4 +++ view/theme/vier/templates/contact_edit.tpl | 4 +++ 9 files changed, 37 insertions(+), 38 deletions(-) diff --git a/doc/Settings.md b/doc/Settings.md index effba6a81e..67a5af832e 100644 --- a/doc/Settings.md +++ b/doc/Settings.md @@ -182,7 +182,7 @@ By default, any (valid) email address is allowed in registrations. #### Allow Users to set remote_self -If you enable the `Allow Users to set remote_self` users can select Atom feeds from their contact list being their *remote self* in the advanced contact settings. +If you enable the `Allow Users to set remote_self` users can select Atom feeds from their contact list being their *remote self* in the contact settings. Which means that postings by the remote self are automatically reposted by Friendica in their names. This feature can be used to let the user mirror e.g. blog postings into their Friendica postings. diff --git a/doc/de/Settings.md b/doc/de/Settings.md index abb3817570..33023fd712 100644 --- a/doc/de/Settings.md +++ b/doc/de/Settings.md @@ -172,7 +172,7 @@ Wildcards werden akzeptiert Standardmäßig sind alle gültigen Email-Adressen e #### Nutzern erlauben das remote_self Flag zu setzen -Webb du die Option `Nutzern erlauben das remote_self Flag zu setzen` aktivierst, können alle Nutzer Atom Feeds in den erweiterten Einstellungen des Kontakts als "Entferntes Konto" markieren. +Webb du die Option `Nutzern erlauben das remote_self Flag zu setzen` aktivierst, können alle Nutzer Atom Feeds in den Kontakteinstellungen als "Entferntes Konto" markieren. Dadurch werden automatisch alle Beiträge dieser Feeds für diesen Nutzer gespiegelt und an die Kontakte bei Friendica verteilt. Dieses Feature kann z.B. dafür genutzt werden Blogbeiträge zu spiegeln. diff --git a/src/Module/Contact.php b/src/Module/Contact.php index 87fff3e963..17b598a98b 100644 --- a/src/Module/Contact.php +++ b/src/Module/Contact.php @@ -21,7 +21,6 @@ namespace Friendica\Module; -use Friendica\App; use Friendica\BaseModule; use Friendica\Content\ContactSelector; use Friendica\Content\Nav; @@ -132,6 +131,8 @@ class Contact extends BaseModule $fetch_further_information = intval($_POST['fetch_further_information'] ?? 0); + $remote_self = $_POST['remote_self'] ?? false; + $ffi_keyword_denylist = Strings::escapeHtml(trim($_POST['ffi_keyword_denylist'] ?? '')); $priority = intval($_POST['poll'] ?? 0); @@ -147,6 +148,7 @@ class Contact extends BaseModule 'hidden' => $hidden, 'notify_new_posts' => $notify, 'fetch_further_information' => $fetch_further_information, + 'remote_self' => $remote_self, 'ffi_keyword_denylist' => $ffi_keyword_denylist], ['id' => $contact_id, 'uid' => local_user()] ); @@ -555,6 +557,18 @@ class Contact extends BaseModule ]; } + // Disable remote self for everything except feeds. + // There is an issue when you repeat an item from maybe twitter and you got comments from friendica and twitter + // Problem is, you couldn't reply to both networks. + $allow_remote_self = in_array($contact['network'], [Protocol::FEED, Protocol::DFRN, Protocol::DIASPORA, Protocol::TWITTER]) + && DI::config()->get('system', 'allow_users_remote_self'); + + if ($contact['network'] == Protocol::FEED) { + $remote_self_options = ['0' => DI::l10n()->t('No mirroring'), '1' => DI::l10n()->t('Mirror as forwarded posting'), '2' => DI::l10n()->t('Mirror as my own posting')]; + } else { + $remote_self_options = ['0' => DI::l10n()->t('No mirroring'), '2' => DI::l10n()->t('Mirror as my own posting')]; + } + $poll_interval = null; if ((($contact['network'] == Protocol::FEED) && !DI::config()->get('system', 'adjust_poll_frequency')) || ($contact['network']== Protocol::MAIL)) { $poll_interval = ContactSelector::pollInterval($contact['priority'], !$poll_enabled); @@ -629,6 +643,13 @@ class Contact extends BaseModule '$contact_status' => DI::l10n()->t('Status'), '$contact_settings_label' => $contact_settings_label, '$contact_profile_label' => DI::l10n()->t('Profile'), + '$allow_remote_self' => $allow_remote_self, + '$remote_self' => ['remote_self', + DI::l10n()->t('Mirror postings from this contact'), + $contact['remote_self'], + DI::l10n()->t('Mark this contact as remote_self, this will cause friendica to repost new entries from this contact.'), + $remote_self_options + ], ]); $arr = ['contact' => $contact, 'output' => $o]; @@ -916,7 +937,7 @@ class Contact extends BaseModule ], ]; - if ($cid != $pcid) { + if (in_array($contact['network'], [Protocol::FEED, Protocol::MAIL]) && ($cid != $pcid)) { $tabs[] = ['label' => DI::l10n()->t('Advanced'), 'url' => 'contact/' . $cid . '/advanced/', 'sel' => (($active_tab == self::TAB_ADVANCED) ? 'active' : ''), diff --git a/src/Module/Contact/Advanced.php b/src/Module/Contact/Advanced.php index 1c2595dc24..91536a8889 100644 --- a/src/Module/Contact/Advanced.php +++ b/src/Module/Contact/Advanced.php @@ -63,7 +63,6 @@ class Advanced extends BaseModule $poll = $_POST['poll'] ?? ''; $attag = $_POST['attag'] ?? ''; $photo = $_POST['photo'] ?? ''; - $remote_self = $_POST['remote_self'] ?? false; $nurl = Strings::normaliseLink($url); $r = DI::dba()->update( @@ -79,7 +78,6 @@ class Advanced extends BaseModule 'notify' => $notify, 'poll' => $poll, 'attag' => $attag, - 'remote_self' => $remote_self, ], ['id' => $contact['id'], 'uid' => local_user()] ); @@ -113,18 +111,6 @@ class Advanced extends BaseModule $returnaddr = "contact/$cid"; - // Disable remote self for everything except feeds. - // There is an issue when you repeat an item from maybe twitter and you got comments from friendica and twitter - // Problem is, you couldn't reply to both networks. - $allow_remote_self = in_array($contact['network'], [Protocol::FEED, Protocol::DFRN, Protocol::DIASPORA, Protocol::TWITTER]) - && DI::config()->get('system', 'allow_users_remote_self'); - - if ($contact['network'] == Protocol::FEED) { - $remote_self_options = ['0' => DI::l10n()->t('No mirroring'), '1' => DI::l10n()->t('Mirror as forwarded posting'), '2' => DI::l10n()->t('Mirror as my own posting')]; - } else { - $remote_self_options = ['0' => DI::l10n()->t('No mirroring'), '2' => DI::l10n()->t('Mirror as my own posting')]; - } - // This data is fetched automatically for most networks. // Editing does only makes sense for mail and feed contacts. if (!in_array($contact['network'], [Protocol::FEED, Protocol::MAIL])) { @@ -146,14 +132,6 @@ class Advanced extends BaseModule '$udprofilenow' => DI::l10n()->t('Refetch contact data'), '$contact_id' => $contact['id'], '$lbl_submit' => DI::l10n()->t('Submit'), - '$label_remote_self' => DI::l10n()->t('Remote Self'), - '$allow_remote_self' => $allow_remote_self, - '$remote_self' => ['remote_self', - DI::l10n()->t('Mirror postings from this contact'), - $contact['remote_self'], - DI::l10n()->t('Mark this contact as remote_self, this will cause friendica to repost new entries from this contact.'), - $remote_self_options - ], '$name' => ['name', DI::l10n()->t('Name'), $contact['name'], '', '', $readonly], '$nick' => ['nick', DI::l10n()->t('Account Nickname'), $contact['nick'], '', '', $readonly], diff --git a/view/templates/contact/advanced.tpl b/view/templates/contact/advanced.tpl index 6c6065f1cc..487f27a0eb 100644 --- a/view/templates/contact/advanced.tpl +++ b/view/templates/contact/advanced.tpl @@ -39,12 +39,6 @@ {{include file="field_input.tpl" field=$photo}} - - {{if $allow_remote_self eq 1}} -

{{$label_remote_self}}

- {{include file="field_select.tpl" field=$remote_self}} - {{/if}} - diff --git a/view/templates/contact_edit.tpl b/view/templates/contact_edit.tpl index 1dc9e531f9..9d93494a1b 100644 --- a/view/templates/contact_edit.tpl +++ b/view/templates/contact_edit.tpl @@ -69,6 +69,10 @@ {{include file="field_select.tpl" field=$fetch_further_information}} {{if $fetch_further_information.2 == 2 || $fetch_further_information.2 == 3}} {{include file="field_textarea.tpl" field=$ffi_keyword_denylist}} {{/if}} {{/if}} + {{if $allow_remote_self}} + {{include file="field_select.tpl" field=$remote_self}} + {{/if}} + {{include file="field_checkbox.tpl" field=$hidden}}
diff --git a/view/theme/frio/templates/contact/advanced.tpl b/view/theme/frio/templates/contact/advanced.tpl index 30a2c4bea0..6dff44a755 100644 --- a/view/theme/frio/templates/contact/advanced.tpl +++ b/view/theme/frio/templates/contact/advanced.tpl @@ -41,12 +41,6 @@ {{include file="field_input.tpl" field=$photo}} - - {{if $allow_remote_self eq 1}} -

{{$label_remote_self}}

- {{include file="field_select.tpl" field=$remote_self}} - {{/if}} -
diff --git a/view/theme/frio/templates/contact_edit.tpl b/view/theme/frio/templates/contact_edit.tpl index 68ca758a23..776e8f8aff 100644 --- a/view/theme/frio/templates/contact_edit.tpl +++ b/view/theme/frio/templates/contact_edit.tpl @@ -135,6 +135,10 @@ {{include file="field_select.tpl" field=$fetch_further_information}} {{if $fetch_further_information.2 == 2 || $fetch_further_information.2 == 3}} {{include file="field_textarea.tpl" field=$ffi_keyword_denylist}} {{/if}} {{/if}} + {{if $allow_remote_self}} + {{include file="field_select.tpl" field=$remote_self}} + {{/if}} + {{include file="field_checkbox.tpl" field=$hidden}}
diff --git a/view/theme/vier/templates/contact_edit.tpl b/view/theme/vier/templates/contact_edit.tpl index 3f8315c835..e4e9ec44fb 100644 --- a/view/theme/vier/templates/contact_edit.tpl +++ b/view/theme/vier/templates/contact_edit.tpl @@ -70,6 +70,10 @@ {{include file="field_select.tpl" field=$fetch_further_information}} {{if $fetch_further_information.2 == 2 || $fetch_further_information.2 == 3}} {{include file="field_textarea.tpl" field=$ffi_keyword_denylist}} {{/if}} {{/if}} + {{if $allow_remote_self}} + {{include file="field_select.tpl" field=$remote_self}} + {{/if}} + {{include file="field_checkbox.tpl" field=$hidden}}
From d9452588bdb5d14a56d6fe7f16244a12f6411038 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 25 Oct 2020 07:41:01 +0000 Subject: [PATCH 07/23] Avoid deleting newly create item-uri entries --- src/Worker/CleanItemUri.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Worker/CleanItemUri.php b/src/Worker/CleanItemUri.php index 0f24e549f7..d7a99153f6 100644 --- a/src/Worker/CleanItemUri.php +++ b/src/Worker/CleanItemUri.php @@ -31,9 +31,19 @@ class CleanItemUri */ public static function execute() { - $ret = DBA::e("DELETE FROM `item-uri` WHERE NOT `id` IN (SELECT `uri-id` FROM `item`) + // We have to avoid deleting newly created "item-uri" entries. + // So we fetch a post that had been stored yesterday and only delete older ones. + $item = DBA::selectFirst('item', ['uri-id'], ["`uid` = ? AND `received` < UTC_TIMESTAMP() - INTERVAL ? DAY", 0, 1], + ['order' => ['received' => true]]); + if (empty($item['uri-id'])) { + Logger::warning('No item with uri-id found - we better quit here'); + return; + } + Logger::notice('Start deleting orphaned URI-ID', ['last-id' => $item['uri-id']]); + $ret = DBA::e("DELETE FROM `item-uri` WHERE `id` < ? + AND NOT `id` IN (SELECT `uri-id` FROM `item`) AND NOT `id` IN (SELECT `parent-uri-id` FROM `item`) - AND NOT `id` IN (SELECT `thr-parent-id` FROM `item`)"); + AND NOT `id` IN (SELECT `thr-parent-id` FROM `item`)", $item['uri-id']); Logger::notice('Orphaned URI-ID entries removed', ['result' => $ret, 'rows' => DBA::affectedRows()]); } } From a643888aebd195ffa3e8c9639e38bf0df412aad1 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 25 Oct 2020 16:13:31 +0000 Subject: [PATCH 08/23] More data for the Mastodon status class --- src/DI.php | 16 +++++ src/Factory/Api/Mastodon/Mention.php | 67 +++++++++++++++++++ src/Factory/Api/Mastodon/Status.php | 19 +++++- src/Factory/Api/Mastodon/Tag.php | 65 ++++++++++++++++++ src/Object/Api/Mastodon/Card.php | 62 +++++++++++++++++ src/Object/Api/Mastodon/Mention.php | 66 ++++++++++++++++++ src/Object/Api/Mastodon/Status.php | 31 +++++---- .../Api/Mastodon/Status/UserAttributes.php | 64 ++++++++++++++++++ src/Object/Api/Mastodon/Tag.php | 51 ++++++++++++++ 9 files changed, 426 insertions(+), 15 deletions(-) create mode 100644 src/Factory/Api/Mastodon/Mention.php create mode 100644 src/Factory/Api/Mastodon/Tag.php create mode 100644 src/Object/Api/Mastodon/Card.php create mode 100644 src/Object/Api/Mastodon/Mention.php create mode 100644 src/Object/Api/Mastodon/Status/UserAttributes.php create mode 100644 src/Object/Api/Mastodon/Tag.php diff --git a/src/DI.php b/src/DI.php index 73f9b7811d..e259c271a0 100644 --- a/src/DI.php +++ b/src/DI.php @@ -279,6 +279,22 @@ abstract class DI return self::$dice->create(Factory\Api\Mastodon\Status::class); } + /** + * @return Factory\Api\Mastodon\Mention + */ + public static function mstdnMention() + { + return self::$dice->create(Factory\Api\Mastodon\Mention::class); + } + + /** + * @return Factory\Api\Mastodon\Tag + */ + public static function mstdnTag() + { + return self::$dice->create(Factory\Api\Mastodon\Tag::class); + } + /** * @return Factory\Api\Twitter\User */ diff --git a/src/Factory/Api/Mastodon/Mention.php b/src/Factory/Api/Mastodon/Mention.php new file mode 100644 index 0000000000..5ab82d710f --- /dev/null +++ b/src/Factory/Api/Mastodon/Mention.php @@ -0,0 +1,67 @@ +. + * + */ + +namespace Friendica\Factory\Api\Mastodon; + +use Friendica\App\BaseURL; +use Friendica\BaseFactory; +use Friendica\Model\Contact; +use Friendica\Model\Tag; +use Friendica\Network\HTTPException; +use Friendica\Repository\ProfileField; +use Psr\Log\LoggerInterface; + +class Mention extends BaseFactory +{ + /** @var BaseURL */ + protected $baseUrl; + /** @var ProfileField */ + protected $profileField; + /** @var Field */ + protected $mstdnField; + + public function __construct(LoggerInterface $logger, BaseURL $baseURL, ProfileField $profileField, Field $mstdnField) + { + parent::__construct($logger); + + $this->baseUrl = $baseURL; + $this->profileField = $profileField; + $this->mstdnField = $mstdnField; + } + + /** + * @param int $uriId Uri-ID of the item + * @return array + * @throws HTTPException\InternalServerErrorException + * @throws \ImagickException + */ + public function createFromUriId(int $uriId) + { + $mentions = []; + $tags = Tag::getByURIId($uriId, [Tag::MENTION, Tag::EXCLUSIVE_MENTION, Tag::IMPLICIT_MENTION]); + foreach ($tags as $tag) { + $contact = Contact::getByURL($tag['url'], false); + $mention = new \Friendica\Object\Api\Mastodon\Mention($this->baseUrl, $tag, $contact); + $mentions[] = $mention->toArray(); + } + return $mentions; + } +} diff --git a/src/Factory/Api/Mastodon/Status.php b/src/Factory/Api/Mastodon/Status.php index c31c211a59..0069c91b32 100644 --- a/src/Factory/Api/Mastodon/Status.php +++ b/src/Factory/Api/Mastodon/Status.php @@ -23,6 +23,7 @@ namespace Friendica\Factory\Api\Mastodon; use Friendica\App\BaseURL; use Friendica\BaseFactory; +use Friendica\Content\Text\BBCode; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\Item; @@ -68,6 +69,22 @@ class Status extends BaseFactory DBA::count('item', ['thr-parent-id' => $uriId, 'uid' => $uid, 'gravity' => GRAVITY_ACTIVITY, 'vid' => Verb::getID(Activity::LIKE)]) ); - return new \Friendica\Object\Api\Mastodon\Status($item, $account, $counts); + $userAttributes = new \Friendica\Object\Api\Mastodon\Status\UserAttributes( + DBA::exists('item', ['thr-parent-id' => $uriId, 'uid' => $uid, 'origin' => true, 'gravity' => GRAVITY_ACTIVITY, 'vid' => Verb::getID(Activity::LIKE)]), + DBA::exists('item', ['thr-parent-id' => $uriId, 'uid' => $uid, 'origin' => true, 'gravity' => GRAVITY_ACTIVITY, 'vid' => Verb::getID(Activity::ANNOUNCE)]), + DBA::exists('thread', ['iid' => $item['id'], 'uid' => $item['uid'], 'ignored' => true]), + (bool)$item['starred'], + DBA::exists('user-item', ['iid' => $item['id'], 'uid' => $item['uid'], 'pinned' => true]) + ); + + $sensitive = DBA::exists('tag-view', ['uri-id' => $uriId, 'name' => 'nsfw']); + $application = new \Friendica\Object\Api\Mastodon\Application($item['app']); + $mentions = DI::mstdnMention()->createFromUriId($uriId); + $tags = DI::mstdnTag()->createFromUriId($uriId); + + $attachment = BBCode::getAttachmentData($item['body']); + $card = new \Friendica\Object\Api\Mastodon\Card($attachment); + + return new \Friendica\Object\Api\Mastodon\Status($item, $account, $counts, $userAttributes, $sensitive, $application, $mentions, $tags, $card); } } diff --git a/src/Factory/Api/Mastodon/Tag.php b/src/Factory/Api/Mastodon/Tag.php new file mode 100644 index 0000000000..9b81e6d691 --- /dev/null +++ b/src/Factory/Api/Mastodon/Tag.php @@ -0,0 +1,65 @@ +. + * + */ + +namespace Friendica\Factory\Api\Mastodon; + +use Friendica\App\BaseURL; +use Friendica\BaseFactory; +use Friendica\Model\Tag as TagModel; +use Friendica\Network\HTTPException; +use Friendica\Repository\ProfileField; +use Psr\Log\LoggerInterface; + +class Tag extends BaseFactory +{ + /** @var BaseURL */ + protected $baseUrl; + /** @var ProfileField */ + protected $profileField; + /** @var Field */ + protected $mstdnField; + + public function __construct(LoggerInterface $logger, BaseURL $baseURL, ProfileField $profileField, Field $mstdnField) + { + parent::__construct($logger); + + $this->baseUrl = $baseURL; + $this->profileField = $profileField; + $this->mstdnField = $mstdnField; + } + + /** + * @param int $uriId Uri-ID of the item + * @return array + * @throws HTTPException\InternalServerErrorException + * @throws \ImagickException + */ + public function createFromUriId(int $uriId) + { + $hashtags = []; + $tags = TagModel::getByURIId($uriId, [TagModel::HASHTAG]); + foreach ($tags as $tag) { + $hashtag = new \Friendica\Object\Api\Mastodon\Tag($this->baseUrl, $tag); + $hashtags[] = $hashtag->toArray(); + } + return $hashtags; + } +} diff --git a/src/Object/Api/Mastodon/Card.php b/src/Object/Api/Mastodon/Card.php new file mode 100644 index 0000000000..990f6a6bfa --- /dev/null +++ b/src/Object/Api/Mastodon/Card.php @@ -0,0 +1,62 @@ +. + * + */ + +namespace Friendica\Object\Api\Mastodon; + +use Friendica\BaseEntity; +use Friendica\Content\Text\BBCode; +use Friendica\Object\Api\Mastodon\Status\Counts; +use Friendica\Object\Api\Mastodon\Status\UserAttributes; +use Friendica\Util\DateTimeFormat; + +/** + * Class Card + * + * @see https://docs.joinmastodon.org/entities/card + */ +class Card extends BaseEntity +{ + /** @var string */ + protected $url; + /** @var string */ + protected $title; + /** @var string */ + protected $description; + /** @var string */ + protected $type; + /** @var string */ + protected $image; + + /** + * Creates a status record from an item record. + * + * @param array $attachment Attachment record + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + public function __construct(array $attachment) + { + $this->url = $attachment['url'] ?? ''; + $this->title = $attachment['title'] ?? ''; + $this->description = $attachment['description'] ?? ''; + $this->type = $attachment['type'] ?? ''; + $this->image = $attachment['image'] ?? ''; + } +} diff --git a/src/Object/Api/Mastodon/Mention.php b/src/Object/Api/Mastodon/Mention.php new file mode 100644 index 0000000000..22e623e604 --- /dev/null +++ b/src/Object/Api/Mastodon/Mention.php @@ -0,0 +1,66 @@ +. + * + */ + +namespace Friendica\Object\Api\Mastodon; + +use Friendica\App\BaseURL; +use Friendica\BaseEntity; + +/** + * Class Mention + * + * @see https://docs.joinmastodon.org/entities/mention + */ +class Mention extends BaseEntity +{ + /** @var string */ + protected $id; + /** @var string */ + protected $username; + /** @var string */ + protected $url = null; + /** @var string */ + protected $acct = null; + + /** + * Creates a mention record from an tag-view record. + * + * @param BaseURL $baseUrl + * @param array $tag tag-view record + * @param array $contact contact table record + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + public function __construct(BaseURL $baseUrl, array $tag, array $contact) + { + $this->id = $contact['id'] ?? 0; + $this->username = $tag['name']; + $this->url = $tag['url']; + + if (!empty($contact)) { + $this->acct = + strpos($contact['url'], $baseUrl->get() . '/') === 0 ? + $contact['nick'] : + $contact['addr']; + } else { + $this->acct = ''; + } + } +} diff --git a/src/Object/Api/Mastodon/Status.php b/src/Object/Api/Mastodon/Status.php index aa26fa1986..cc9108fc8c 100644 --- a/src/Object/Api/Mastodon/Status.php +++ b/src/Object/Api/Mastodon/Status.php @@ -24,6 +24,7 @@ namespace Friendica\Object\Api\Mastodon; use Friendica\BaseEntity; use Friendica\Content\Text\BBCode; use Friendica\Object\Api\Mastodon\Status\Counts; +use Friendica\Object\Api\Mastodon\Status\UserAttributes; use Friendica\Util\DateTimeFormat; /** @@ -96,7 +97,7 @@ class Status extends BaseEntity * @param array $item * @throws \Friendica\Network\HTTPException\InternalServerErrorException */ - public function __construct(array $item, Account $account, Counts $counts) + public function __construct(array $item, Account $account, Counts $counts, UserAttributes $userAttributes, bool $sensitive, Application $application, array $mentions, array $tags, Card $card) { $this->id = (string)$item['uri-id']; $this->created_at = DateTimeFormat::utc($item['created'], DateTimeFormat::ATOM); @@ -106,32 +107,34 @@ class Status extends BaseEntity $this->in_reply_to_account_id = (string)$item['parent-author-id']; } - $this->sensitive = false; + $this->sensitive = $sensitive; $this->spoiler_text = $item['title']; $visibility = ['public', 'private', 'unlisted']; $this->visibility = $visibility[$item['private']]; - $this->language = null; + $languages = json_decode($item['language'], true); + $this->language = is_array($languages) ? array_key_first($languages) : null; + $this->uri = $item['uri']; $this->url = $item['plink'] ?? null; $this->replies_count = $counts->replies; $this->reblogs_count = $counts->reblogs; $this->favourites_count = $counts->favourites; - $this->favourited = false; - $this->reblogged = false; - $this->muted = false; - $this->bookmarked = false; - $this->pinned = false; + $this->favourited = $userAttributes->favourited; + $this->reblogged = $userAttributes->reblogged; + $this->muted = $userAttributes->muted; + $this->bookmarked = $userAttributes->bookmarked; + $this->pinned = $userAttributes->pinned; $this->content = BBCode::convert($item['body'], false); - $this->reblog = null; - $this->application = null; + $this->reblog = null; /// @todo + $this->application = $application->toArray(); $this->account = $account->toArray(); - $this->media_attachments = []; - $this->mentions = []; - $this->tags = []; + $this->media_attachments = []; /// @todo + $this->mentions = $mentions; + $this->tags = $tags; $this->emojis = []; - $this->card = null; + $this->card = $card->toArray(); $this->poll = null; } } diff --git a/src/Object/Api/Mastodon/Status/UserAttributes.php b/src/Object/Api/Mastodon/Status/UserAttributes.php new file mode 100644 index 0000000000..f33cc51171 --- /dev/null +++ b/src/Object/Api/Mastodon/Status/UserAttributes.php @@ -0,0 +1,64 @@ +. + * + */ + +namespace Friendica\Object\Api\Mastodon\Status; + +/** + * Class UserAttributes + * + * @see https://docs.joinmastodon.org/entities/status + */ +class UserAttributes +{ + /** @var bool */ + protected $favourited; + /** @var bool */ + protected $reblogged; + /** @var bool */ + protected $muted; + /** @var bool */ + protected $bookmarked; + /** @var bool */ + protected $pinned; + + /** + * Creates a authorized user attributes object + * + * @param bool $favourited + * @param bool $reblogged + * @param bool $muted + * @param bool $bookmarked + * @param bool $pinned + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + public function __construct(bool $favourited, bool $reblogged, bool $muted, bool $bookmarked, bool $pinned) + { + $this->favourited = $favourited; + $this->reblogged = $reblogged; + $this->muted = $muted; + $this->bookmarked = $bookmarked; + $this->pinned = $pinned; + } + + public function __get($name) { + return $this->$name; + } +} diff --git a/src/Object/Api/Mastodon/Tag.php b/src/Object/Api/Mastodon/Tag.php new file mode 100644 index 0000000000..0cb9107039 --- /dev/null +++ b/src/Object/Api/Mastodon/Tag.php @@ -0,0 +1,51 @@ +. + * + */ + +namespace Friendica\Object\Api\Mastodon; + +use Friendica\App\BaseURL; +use Friendica\BaseEntity; + +/** + * Class Tag + * + * @see https://docs.joinmastodon.org/entities/tag + */ +class Tag extends BaseEntity +{ + /** @var string */ + protected $name; + /** @var string */ + protected $url = null; + + /** + * Creates a hashtag record from an tag-view record. + * + * @param BaseURL $baseUrl + * @param array $tag tag-view record + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + public function __construct(BaseURL $baseUrl, array $tag) + { + $this->name = $tag['name']; + $this->url = $baseUrl . '/search?tag=' . urlencode($tag['name']); + } +} From efcd76d3a71e8c7fada5ba25a2ddc52b0c8a7213 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 25 Oct 2020 16:26:47 +0000 Subject: [PATCH 09/23] Spaces to tabs --- src/Object/Api/Mastodon/Status/Counts.php | 4 ++-- src/Object/Api/Mastodon/Status/UserAttributes.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Object/Api/Mastodon/Status/Counts.php b/src/Object/Api/Mastodon/Status/Counts.php index 2c446c36c7..a0af517dfb 100644 --- a/src/Object/Api/Mastodon/Status/Counts.php +++ b/src/Object/Api/Mastodon/Status/Counts.php @@ -51,6 +51,6 @@ class Counts } public function __get($name) { - return $this->$name; - } + return $this->$name; + } } diff --git a/src/Object/Api/Mastodon/Status/UserAttributes.php b/src/Object/Api/Mastodon/Status/UserAttributes.php index f33cc51171..c1201c931f 100644 --- a/src/Object/Api/Mastodon/Status/UserAttributes.php +++ b/src/Object/Api/Mastodon/Status/UserAttributes.php @@ -59,6 +59,6 @@ class UserAttributes } public function __get($name) { - return $this->$name; - } + return $this->$name; + } } From 13b14bff91734bcf3578570d9f464aa3056e55cd Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 25 Oct 2020 16:55:05 +0000 Subject: [PATCH 10/23] Return 'null' on empty card --- src/Object/Api/Mastodon/Card.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Object/Api/Mastodon/Card.php b/src/Object/Api/Mastodon/Card.php index 990f6a6bfa..46aea3c280 100644 --- a/src/Object/Api/Mastodon/Card.php +++ b/src/Object/Api/Mastodon/Card.php @@ -59,4 +59,18 @@ class Card extends BaseEntity $this->type = $attachment['type'] ?? ''; $this->image = $attachment['image'] ?? ''; } + + /** + * Returns the current entity as an array + * + * @return array + */ + public function toArray() + { + if (empty($this->url)) { + return null; + } + + return parent::toArray(); + } } From 72efcc8169ef7d7b78cfb9846cfe3de2c61c875a Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 25 Oct 2020 16:59:38 +0000 Subject: [PATCH 11/23] "locked" is boolean --- src/Object/Api/Mastodon/Account.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Object/Api/Mastodon/Account.php b/src/Object/Api/Mastodon/Account.php index 5bd2743b9e..f6e66941ac 100644 --- a/src/Object/Api/Mastodon/Account.php +++ b/src/Object/Api/Mastodon/Account.php @@ -99,7 +99,7 @@ class Account extends BaseEntity $publicContact['nick'] : $publicContact['addr']; $this->display_name = $publicContact['name']; - $this->locked = $publicContact['manually-approve'] ?? !empty($apcontact['manually-approve']); + $this->locked = (bool)$publicContact['manually-approve'] ?? !empty($apcontact['manually-approve']); $this->bot = ($publicContact['contact-type'] == Contact::TYPE_NEWS); $this->discoverable = !$publicContact['unsearchable']; $this->group = ($publicContact['contact-type'] == Contact::TYPE_COMMUNITY); From 825189e8e908a027c4292f4f8dd5d2e84f9ac810 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 25 Oct 2020 17:42:42 +0000 Subject: [PATCH 12/23] Don't publish some fields --- src/Object/Api/Mastodon/Account.php | 16 ++++++++++++++++ src/Object/Api/Mastodon/Status.php | 20 ++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/Object/Api/Mastodon/Account.php b/src/Object/Api/Mastodon/Account.php index f6e66941ac..587c6ce6d4 100644 --- a/src/Object/Api/Mastodon/Account.php +++ b/src/Object/Api/Mastodon/Account.php @@ -132,4 +132,20 @@ class Account extends BaseEntity $this->fields = $fields->getArrayCopy(); } + + /** + * Returns the current entity as an array + * + * @return array + */ + public function toArray() + { + $account = parent::toArray(); + + if (empty($account['moved'])) { + unset($account['moved']); + } + + return $account; + } } diff --git a/src/Object/Api/Mastodon/Status.php b/src/Object/Api/Mastodon/Status.php index cc9108fc8c..2d2beb583f 100644 --- a/src/Object/Api/Mastodon/Status.php +++ b/src/Object/Api/Mastodon/Status.php @@ -137,4 +137,24 @@ class Status extends BaseEntity $this->card = $card->toArray(); $this->poll = null; } + + /** + * Returns the current entity as an array + * + * @return array + */ + public function toArray() + { + $status = parent::toArray(); + + if (!$status['pinned']) { + unset($status['pinned']); + } + + if (empty($status['application']['name'])) { + unset($status['application']); + } + + return $status; + } } From 463f8ee3f32f724b233316adb79c49e8225daa5d Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 25 Oct 2020 17:59:28 +0000 Subject: [PATCH 13/23] Use lowercase for tags --- src/Object/Api/Mastodon/Tag.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Object/Api/Mastodon/Tag.php b/src/Object/Api/Mastodon/Tag.php index 0cb9107039..1e74eae00f 100644 --- a/src/Object/Api/Mastodon/Tag.php +++ b/src/Object/Api/Mastodon/Tag.php @@ -45,7 +45,7 @@ class Tag extends BaseEntity */ public function __construct(BaseURL $baseUrl, array $tag) { - $this->name = $tag['name']; - $this->url = $baseUrl . '/search?tag=' . urlencode($tag['name']); + $this->name = strtolower($tag['name']); + $this->url = $baseUrl . '/search?tag=' . urlencode($this->name); } } From 319ceeda3ba994fe76d22262fef1f40ec8d779af Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 25 Oct 2020 18:21:18 +0000 Subject: [PATCH 14/23] Publish the profile fields --- src/Factory/Api/Mastodon/Account.php | 5 ++++- src/Factory/Api/Mastodon/Field.php | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Factory/Api/Mastodon/Account.php b/src/Factory/Api/Mastodon/Account.php index b0c31c09b4..a7aa642450 100644 --- a/src/Factory/Api/Mastodon/Account.php +++ b/src/Factory/Api/Mastodon/Account.php @@ -69,7 +69,10 @@ class Account extends BaseFactory $apcontact = APContact::getByURL($publicContact['url'], false); - return new \Friendica\Object\Api\Mastodon\Account($this->baseUrl, $publicContact, new Fields(), $apcontact, $userContact); + $profileFields = $this->profileField->select(['uid' => $uid, 'psid' => PermissionSet::PUBLIC]); + $fields = $this->mstdnField->createFromProfileFields($profileFields); + + return new \Friendica\Object\Api\Mastodon\Account($this->baseUrl, $publicContact, $fields, $apcontact, $userContact); } /** diff --git a/src/Factory/Api/Mastodon/Field.php b/src/Factory/Api/Mastodon/Field.php index fdf0a4ef6d..d357ee2fa5 100644 --- a/src/Factory/Api/Mastodon/Field.php +++ b/src/Factory/Api/Mastodon/Field.php @@ -37,7 +37,7 @@ class Field extends BaseFactory */ public function createFromProfileField(ProfileField $profileField) { - return new \Friendica\Api\Entity\Mastodon\Field($profileField->label, BBCode::convert($profileField->value, false, BBCode::ACTIVITYPUB)); + return new \Friendica\Object\Api\Mastodon\Field($profileField->label, BBCode::convert($profileField->value, false, BBCode::ACTIVITYPUB)); } /** From d8d96e6e61325abab94f11eefce93185ff0605d2 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 25 Oct 2020 20:32:45 +0000 Subject: [PATCH 15/23] Display the user fields for the admin account --- src/Factory/Api/Mastodon/Account.php | 8 ++++++-- src/Object/Api/Mastodon/Instance.php | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Factory/Api/Mastodon/Account.php b/src/Factory/Api/Mastodon/Account.php index a7aa642450..d7e94819e9 100644 --- a/src/Factory/Api/Mastodon/Account.php +++ b/src/Factory/Api/Mastodon/Account.php @@ -69,8 +69,12 @@ class Account extends BaseFactory $apcontact = APContact::getByURL($publicContact['url'], false); - $profileFields = $this->profileField->select(['uid' => $uid, 'psid' => PermissionSet::PUBLIC]); - $fields = $this->mstdnField->createFromProfileFields($profileFields); + if (!empty($userContact['self'])) { + $profileFields = $this->profileField->select(['uid' => $uid, 'psid' => PermissionSet::PUBLIC]); + $fields = $this->mstdnField->createFromProfileFields($profileFields); + } else { + $fields = new Fields(); + } return new \Friendica\Object\Api\Mastodon\Account($this->baseUrl, $publicContact, $fields, $apcontact, $userContact); } diff --git a/src/Object/Api/Mastodon/Instance.php b/src/Object/Api/Mastodon/Instance.php index 6105a8bee1..c52e59f8ab 100644 --- a/src/Object/Api/Mastodon/Instance.php +++ b/src/Object/Api/Mastodon/Instance.php @@ -93,8 +93,8 @@ class Instance extends BaseEntity $adminList = explode(',', str_replace(' ', '', DI::config()->get('config', 'admin_email'))); $administrator = User::getByEmail($adminList[0], ['nickname']); if (!empty($administrator)) { - $adminContact = DBA::selectFirst('contact', ['id'], ['nick' => $administrator['nickname'], 'self' => true]); - $instance->contact_account = DI::mstdnAccount()->createFromContactId($adminContact['id']); + $adminContact = DBA::selectFirst('contact', ['id', 'uid'], ['nick' => $administrator['nickname'], 'self' => true]); + $instance->contact_account = DI::mstdnAccount()->createFromContactId($adminContact['id'], $adminContact['uid']); } } From 44a4b66539da5a701f0da50a0d5f9da6192ab5a1 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 25 Oct 2020 20:40:25 +0000 Subject: [PATCH 16/23] Improved self contact detection --- src/Factory/Api/Mastodon/Account.php | 5 +++-- src/Object/Api/Mastodon/Instance.php | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Factory/Api/Mastodon/Account.php b/src/Factory/Api/Mastodon/Account.php index d7e94819e9..a2bd550a73 100644 --- a/src/Factory/Api/Mastodon/Account.php +++ b/src/Factory/Api/Mastodon/Account.php @@ -69,8 +69,9 @@ class Account extends BaseFactory $apcontact = APContact::getByURL($publicContact['url'], false); - if (!empty($userContact['self'])) { - $profileFields = $this->profileField->select(['uid' => $uid, 'psid' => PermissionSet::PUBLIC]); + $self_contact = Contact::selectFirst(['uid'], ['nurl' => $publicContact['nurl'], 'self' => true]); + if (!empty($self_contact['uid'])) { + $profileFields = $this->profileField->select(['uid' => $self_contact['uid'], 'psid' => PermissionSet::PUBLIC]); $fields = $this->mstdnField->createFromProfileFields($profileFields); } else { $fields = new Fields(); diff --git a/src/Object/Api/Mastodon/Instance.php b/src/Object/Api/Mastodon/Instance.php index c52e59f8ab..6105a8bee1 100644 --- a/src/Object/Api/Mastodon/Instance.php +++ b/src/Object/Api/Mastodon/Instance.php @@ -93,8 +93,8 @@ class Instance extends BaseEntity $adminList = explode(',', str_replace(' ', '', DI::config()->get('config', 'admin_email'))); $administrator = User::getByEmail($adminList[0], ['nickname']); if (!empty($administrator)) { - $adminContact = DBA::selectFirst('contact', ['id', 'uid'], ['nick' => $administrator['nickname'], 'self' => true]); - $instance->contact_account = DI::mstdnAccount()->createFromContactId($adminContact['id'], $adminContact['uid']); + $adminContact = DBA::selectFirst('contact', ['id'], ['nick' => $administrator['nickname'], 'self' => true]); + $instance->contact_account = DI::mstdnAccount()->createFromContactId($adminContact['id']); } } From 2f3918c3a3ebd6c09c64970a5ce19b2c3f596201 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 26 Oct 2020 06:54:10 +0000 Subject: [PATCH 17/23] Issue 9457: Fix network order for starred and mention --- src/Module/Conversation/Network.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Module/Conversation/Network.php b/src/Module/Conversation/Network.php index b19f409025..886ce25931 100644 --- a/src/Module/Conversation/Network.php +++ b/src/Module/Conversation/Network.php @@ -285,21 +285,25 @@ class Network extends BaseModule self::$selectedTab = Session::get('network-tab', DI::pConfig()->get(local_user(), 'network.view', 'selected_tab', '')); + self::$order = 'commented'; + if (!empty($get['star'])) { self::$selectedTab = 'star'; + self::$order = 'received'; } if (!empty($get['mention'])) { self::$selectedTab = 'mention'; + self::$order = 'received'; } if (!empty($get['order'])) { self::$selectedTab = $get['order']; + self::$order = $get['order']; } self::$star = intval($get['star'] ?? 0); self::$mention = intval($get['mention'] ?? 0); - self::$order = $get['order'] ?? Session::get('network-order', 'commented'); self::$selectedTab = self::$selectedTab ?? self::$order; @@ -339,8 +343,6 @@ class Network extends BaseModule self::$order = 'commented'; self::$max_id = $get['last_commented'] ?? self::$max_id; } - - Session::set('network-order', self::$order); } protected static function getItems(string $table, array $params, array $conditionFields = []) From a012234d82a14aadfd253666177f290f8298f514 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 26 Oct 2020 19:40:09 +0000 Subject: [PATCH 18/23] Publish the provider fields in the API --- src/Content/Text/BBCode.php | 26 +++++++++++++++++++------- src/Factory/Api/Mastodon/Status.php | 2 +- src/Object/Api/Mastodon/Card.php | 22 ++++++++++++---------- 3 files changed, 32 insertions(+), 18 deletions(-) diff --git a/src/Content/Text/BBCode.php b/src/Content/Text/BBCode.php index e241abadc8..1229337294 100644 --- a/src/Content/Text/BBCode.php +++ b/src/Content/Text/BBCode.php @@ -146,13 +146,15 @@ class BBCode public static function getAttachmentData($body) { $data = [ - 'type' => '', - 'text' => '', - 'after' => '', - 'image' => null, - 'url' => '', - 'title' => '', - 'description' => '', + 'type' => '', + 'text' => '', + 'after' => '', + 'image' => null, + 'url' => '', + 'provider_name' => '', + 'provider_url' => '', + 'title' => '', + 'description' => '', ]; if (!preg_match("/(.*)\[attachment(.*?)\](.*?)\[\/attachment\](.*)/ism", $body, $match)) { @@ -253,6 +255,16 @@ class BBCode $data['after'] = trim($match[4]); + $parts = parse_url($data['url']); + if (!empty($parts['scheme']) && !empty($parts['host'])) { + $data['provider_name'] = $parts['host']; + $data['provider_url'] = $parts['scheme'] . '://' . $parts['host']; + + if (!empty($parts['port'])) { + $data['provider_url'] .= ':' . $parts['port']; + } + } + return $data; } diff --git a/src/Factory/Api/Mastodon/Status.php b/src/Factory/Api/Mastodon/Status.php index 0069c91b32..b4ef0a0f7f 100644 --- a/src/Factory/Api/Mastodon/Status.php +++ b/src/Factory/Api/Mastodon/Status.php @@ -78,7 +78,7 @@ class Status extends BaseFactory ); $sensitive = DBA::exists('tag-view', ['uri-id' => $uriId, 'name' => 'nsfw']); - $application = new \Friendica\Object\Api\Mastodon\Application($item['app']); + $application = new \Friendica\Object\Api\Mastodon\Application($item['app'] ?? ''); $mentions = DI::mstdnMention()->createFromUriId($uriId); $tags = DI::mstdnTag()->createFromUriId($uriId); diff --git a/src/Object/Api/Mastodon/Card.php b/src/Object/Api/Mastodon/Card.php index 46aea3c280..2f46e4779d 100644 --- a/src/Object/Api/Mastodon/Card.php +++ b/src/Object/Api/Mastodon/Card.php @@ -22,10 +22,6 @@ namespace Friendica\Object\Api\Mastodon; use Friendica\BaseEntity; -use Friendica\Content\Text\BBCode; -use Friendica\Object\Api\Mastodon\Status\Counts; -use Friendica\Object\Api\Mastodon\Status\UserAttributes; -use Friendica\Util\DateTimeFormat; /** * Class Card @@ -43,21 +39,27 @@ class Card extends BaseEntity /** @var string */ protected $type; /** @var string */ + protected $provider_name; + /** @var string */ + protected $provider_url; + /** @var string */ protected $image; /** - * Creates a status record from an item record. + * Creates a card record from an attachment array. * * @param array $attachment Attachment record * @throws \Friendica\Network\HTTPException\InternalServerErrorException */ public function __construct(array $attachment) { - $this->url = $attachment['url'] ?? ''; - $this->title = $attachment['title'] ?? ''; - $this->description = $attachment['description'] ?? ''; - $this->type = $attachment['type'] ?? ''; - $this->image = $attachment['image'] ?? ''; + $this->url = $attachment['url'] ?? ''; + $this->title = $attachment['title'] ?? ''; + $this->description = $attachment['description'] ?? ''; + $this->type = $attachment['type'] ?? ''; + $this->image = $attachment['image'] ?? ''; + $this->provider_name = $attachment['provider_name'] ?? ''; + $this->provider_url = $attachment['provider_url'] ?? ''; } /** From e548d647aaad807894c7e5a85014f94adeda356d Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 29 Oct 2020 05:20:26 +0000 Subject: [PATCH 19/23] New table for attached media files --- database.sql | 26 +++- src/Model/Item.php | 7 +- src/Model/Post/Media.php | 172 +++++++++++++++++++++++++ src/Protocol/ActivityPub/Processor.php | 74 +++++++++-- src/Protocol/ActivityPub/Receiver.php | 20 ++- src/Protocol/Diaspora.php | 32 ++++- static/dbstructure.config.php | 24 +++- 7 files changed, 329 insertions(+), 26 deletions(-) create mode 100644 src/Model/Post/Media.php diff --git a/database.sql b/database.sql index 1e439213d4..d3e75e753e 100644 --- a/database.sql +++ b/database.sql @@ -1,6 +1,6 @@ -- ------------------------------------------ -- Friendica 2020.12-dev (Red Hot Poker) --- DB_UPDATE_VERSION 1370 +-- DB_UPDATE_VERSION 1372 -- ------------------------------------------ @@ -775,6 +775,7 @@ CREATE TABLE IF NOT EXISTS `item-content` ( `title` varchar(255) NOT NULL DEFAULT '' COMMENT 'item title', `content-warning` varchar(255) NOT NULL DEFAULT '' COMMENT '', `body` mediumtext COMMENT 'item body content', + `raw-body` mediumtext COMMENT 'Body without embedded media links', `location` varchar(255) NOT NULL DEFAULT '' COMMENT 'text location where this item originated', `coord` varchar(255) NOT NULL DEFAULT '' COMMENT 'longitude/latitude pair representing location where this item originated', `language` text COMMENT 'Language information about this post', @@ -1064,6 +1065,27 @@ CREATE TABLE IF NOT EXISTS `post-delivery-data` ( FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE ) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Delivery data for items'; +-- +-- TABLE post-media +-- +CREATE TABLE IF NOT EXISTS `post-media` ( + `id` int unsigned NOT NULL auto_increment COMMENT 'sequential ID', + `uri-id` int unsigned NOT NULL COMMENT 'Id of the item-uri table entry that contains the item uri', + `url` varbinary(511) NOT NULL COMMENT 'Media URL', + `type` tinyint unsigned NOT NULL DEFAULT 0 COMMENT 'Media type', + `mimetype` varchar(60) COMMENT '', + `height` smallint unsigned COMMENT 'Height of the media', + `width` smallint unsigned COMMENT 'Width of the media', + `size` int unsigned COMMENT 'Media size', + `preview` varbinary(255) COMMENT 'Preview URL', + `preview-height` smallint unsigned COMMENT 'Height of the preview picture', + `preview-width` smallint unsigned COMMENT 'Width of the preview picture', + `description` text COMMENT '', + PRIMARY KEY(`id`), + UNIQUE INDEX `uri-id-url` (`uri-id`,`url`), + FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE +) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Attached media'; + -- -- TABLE post-tag -- @@ -1390,7 +1412,7 @@ CREATE TABLE IF NOT EXISTS `workerqueue` ( PRIMARY KEY(`id`), INDEX `done_parameter` (`done`,`parameter`(64)), INDEX `done_executed` (`done`,`executed`), - INDEX `done_priority_created` (`done`,`priority`,`created`), + INDEX `done_priority_retrial_created` (`done`,`priority`,`retrial`,`created`), INDEX `done_priority_next_try` (`done`,`priority`,`next_try`), INDEX `done_pid_next_try` (`done`,`pid`,`next_try`), INDEX `done_pid_retrial` (`done`,`pid`,`retrial`), diff --git a/src/Model/Item.php b/src/Model/Item.php index aea428cd72..514163e96f 100644 --- a/src/Model/Item.php +++ b/src/Model/Item.php @@ -104,7 +104,7 @@ class Item 'object-type', 'object', 'target-type', 'target', 'plink']; // Field list for "item-content" table that is not present in the "item" table - const CONTENT_FIELDLIST = ['language']; + const CONTENT_FIELDLIST = ['language', 'raw-body']; // All fields in the item table const ITEM_FIELDLIST = ['id', 'uid', 'parent', 'uri', 'parent-uri', 'thr-parent', @@ -1678,6 +1678,7 @@ class Item $item['deny_gid'] = trim($item['deny_gid'] ?? ''); $item['private'] = intval($item['private'] ?? self::PUBLIC); $item['body'] = trim($item['body'] ?? ''); + $item['raw-body'] = trim($item['raw-body'] ?? $item['body']); $item['attach'] = trim($item['attach'] ?? ''); $item['app'] = trim($item['app'] ?? ''); $item['origin'] = intval($item['origin'] ?? 0); @@ -1816,6 +1817,10 @@ class Item self::setOwnerforResharedItem($item); } + // Remove all media attachments from the body and store them in the post-media table + $item['raw-body'] = Post\Media::addAttachmentsFromBody($item['uri-id'], $item['raw-body']); + $item['raw-body'] = self::setHashtags($item['raw-body']); + // Check for hashtags in the body and repair or add hashtag links $item['body'] = self::setHashtags($item['body']); diff --git a/src/Model/Post/Media.php b/src/Model/Post/Media.php new file mode 100644 index 0000000000..de50f13598 --- /dev/null +++ b/src/Model/Post/Media.php @@ -0,0 +1,172 @@ +. + * + */ + +namespace Friendica\Model\Post; + +use Friendica\Core\Logger; +use Friendica\Core\System; +use Friendica\Database\DBA; +use Friendica\Util\Images; + +/** + * Class Media + * + * This Model class handles media interactions. + * This tables stores medias (images, videos, audio files) related to posts. + */ +class Media +{ + const UNKNOWN = 0; + const IMAGE = 1; + const VIDEO = 2; + const AUDIO = 3; + const TORRENT = 16; + + /** + * Insert a post-media record + * + * @param array $media + * @return void + */ + public static function insert(array $media) + { + if (empty($media['url']) || empty($media['uri-id'])) { + return; + } + + if (DBA::exists('post-media', ['uri-id' => $media['uri-id'], 'url' => $media['url']])) { + Logger::info('Media already exists', ['uri-id' => $media['uri-id'], 'url' => $media['url'], 'callstack' => System::callstack()]); + return; + } + + $fields = ['type', 'mimetype', 'height', 'width', 'size', 'preview', 'preview-height', 'preview-width', 'description']; + foreach ($fields as $field) { + if (empty($media[$field])) { + unset($media[$field]); + } + } + + if ($media['type'] == self::IMAGE) { + $imagedata = Images::getInfoFromURLCached($media['url']); + if (!empty($imagedata)) { + $media['mimetype'] = $imagedata['mime']; + $media['size'] = $imagedata['size']; + $media['width'] = $imagedata[0]; + $media['height'] = $imagedata[1]; + } + if (!empty($media['preview'])) { + $imagedata = Images::getInfoFromURLCached($media['preview']); + if (!empty($imagedata)) { + $media['preview-width'] = $imagedata[0]; + $media['preview-height'] = $imagedata[1]; + } + } + } + + $result = DBA::insert('post-media', $media, true); + Logger::info('Stored media', ['result' => $result, 'media' => $media, 'callstack' => System::callstack()]); + } + + /** + * Tests for path patterns that are usef for picture links in Friendica + * + * @param string $page Link to the image page + * @param string $preview Preview picture + * @return boolean + */ + private static function isPictureLink(string $page, string $preview) + { + return preg_match('#/photos/.*/image/#ism', $page) && preg_match('#/photo/.*-1\.#ism', $preview); + } + + /** + * Add media links and remove them from the body + * + * @param integer $uriid + * @param string $body + * @return string Body without media links + */ + public static function addAttachmentsFromBody(int $uriid, string $body) + { + // Simplify image codes + $body = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $body); + + $attachments = []; + if (preg_match_all("#\[url=([^\]]+?)\]\s*\[img=([^\[\]]*)\]([^\[\]]*)\[\/img\]\s*\[/url\]#ism", $body, $pictures, PREG_SET_ORDER)) { + foreach ($pictures as $picture) { + if (!self::isPictureLink($picture[1], $picture[2])) { + continue; + } + $body = str_replace($picture[0], '', $body); + $image = str_replace('-1.', '-0.', $picture[2]); + $attachments[] = ['uri-id' => $uriid, 'type' => self::IMAGE, 'url' => $image, + 'preview' => $picture[2], 'description' => $picture[3]]; + } + } + + if (preg_match_all("/\[img=([^\[\]]*)\]([^\[\]]*)\[\/img\]/Usi", $body, $pictures, PREG_SET_ORDER)) { + foreach ($pictures as $picture) { + $body = str_replace($picture[0], '', $body); + $attachments[] = ['uri-id' => $uriid, 'type' => self::IMAGE, 'url' => $picture[1], 'description' => $picture[2]]; + } + } + + if (preg_match_all("#\[url=([^\]]+?)\]\s*\[img\]([^\[]+?)\[/img\]\s*\[/url\]#ism", $body, $pictures, PREG_SET_ORDER)) { + foreach ($pictures as $picture) { + if (!self::isPictureLink($picture[1], $picture[2])) { + continue; + } + $body = str_replace($picture[0], '', $body); + $image = str_replace('-1.', '-0.', $picture[2]); + $attachments[] = ['uri-id' => $uriid, 'type' => self::IMAGE, 'url' => $image, + 'preview' => $picture[2], 'description' => null]; + } + } + + if (preg_match_all("/\[img\]([^\[\]]*)\[\/img\]/ism", $body, $pictures, PREG_SET_ORDER)) { + foreach ($pictures as $picture) { + $body = str_replace($picture[0], '', $body); + $attachments[] = ['uri-id' => $uriid, 'type' => self::IMAGE, 'url' => $picture[1]]; + } + } + + /// @todo audio + video + if (preg_match_all("/\[audio\]([^\[\]]*)\[\/audio\]/ism", $body, $audios, PREG_SET_ORDER)) { + foreach ($audios as $audio) { + $body = str_replace($audio[0], '', $body); + $attachments[] = ['uri-id' => $uriid, 'type' => self::AUDIO, 'url' => $audio[1]]; + } + } + + if (preg_match_all("/\[video\]([^\[\]]*)\[\/video\]/ism", $body, $videos, PREG_SET_ORDER)) { + foreach ($videos as $video) { + $body = str_replace($video[0], '', $body); + $attachments[] = ['uri-id' => $uriid, 'type' => self::VIDEO, 'url' => $video[1]]; + } + } + + foreach ($attachments as $attachment) { + self::insert($attachment); + } + + return trim($body); + } +} diff --git a/src/Protocol/ActivityPub/Processor.php b/src/Protocol/ActivityPub/Processor.php index c7310d9eb8..7c8ec33d9d 100644 --- a/src/Protocol/ActivityPub/Processor.php +++ b/src/Protocol/ActivityPub/Processor.php @@ -37,6 +37,7 @@ use Friendica\Model\ItemURI; use Friendica\Model\Mail; use Friendica\Model\Tag; use Friendica\Model\User; +use Friendica\Model\Post; use Friendica\Protocol\Activity; use Friendica\Protocol\ActivityPub; use Friendica\Protocol\Relay; @@ -81,6 +82,45 @@ class Processor return $body; } + /** + * Store attached media files in the post-media table + * + * @param int $uriid + * @param array $attachment + * @return void + */ + private static function storeAttachment(int $uriid, array $attachment) + { + if (empty($attachment['url'])) { + return; + } + + $data = ['uri-id' => $uriid]; + + $filetype = strtolower(substr($attachment['mediaType'], 0, strpos($attachment['mediaType'], '/'))); + if ($filetype == 'image') { + $data['type'] = Post\Media::IMAGE; + } elseif ($filetype == 'video') { + $data['type'] = Post\Media::VIDEO; + } elseif ($filetype == 'audio') { + $data['type'] = Post\Media::AUDIO; + } elseif (in_array($attachment['mediaType'], ['application/x-bittorrent', 'application/x-bittorrent;x-scheme-handler/magnet'])) { + $data['type'] = Post\Media::TORRENT; + } else { + Logger::info('Unknown type', ['attachment' => $attachment]); + return; + } + + $data['url'] = $attachment['url']; + $data['mimetype'] = $attachment['mediaType']; + $data['height'] = $attachment['height'] ?? null; + $data['size'] = $attachment['size'] ?? null; + $data['preview'] = $attachment['image'] ?? null; + $data['description'] = $attachment['name'] ?? null; + + Post\Media::insert($data); + } + /** * Add attachment data to the item array * @@ -95,6 +135,8 @@ class Processor return $item; } + $item['attach'] = ''; + foreach ($activity['attachments'] as $attach) { switch ($attach['type']) { case 'link': @@ -110,6 +152,8 @@ class Processor $item['body'] = PageInfo::appendDataToBody($item['body'], $data); break; default: + self::storeAttachment($item['uri-id'], $attach); + $filetype = strtolower(substr($attach['mediaType'], 0, strpos($attach['mediaType'], '/'))); if ($filetype == 'image') { if (!empty($activity['source']) && strpos($activity['source'], $attach['url'])) { @@ -146,13 +190,13 @@ class Processor $item['body'] .= "\n[video]" . $attach['url'] . '[/video]'; } else { - if (!empty($item["attach"])) { - $item["attach"] .= ','; + if (!empty($item['attach'])) { + $item['attach'] .= ','; } else { - $item["attach"] = ''; + $item['attach'] = ''; } - $item["attach"] .= '[attach]href="' . $attach['url'] . '" length="' . ($attach['length'] ?? '0') . '" type="' . $attach['mediaType'] . '" title="' . ($attach['name'] ?? '') . '"[/attach]'; + $item['attach'] .= '[attach]href="' . $attach['url'] . '" length="' . ($attach['length'] ?? '0') . '" type="' . $attach['mediaType'] . '" title="' . ($attach['name'] ?? '') . '"[/attach]'; } } } @@ -180,6 +224,9 @@ class Processor $item['edited'] = DateTimeFormat::utc($activity['updated']); $item = self::processContent($activity, $item); + + $item = self::constructAttachList($activity, $item); + if (empty($item)) { return; } @@ -403,17 +450,18 @@ class Processor { $item['title'] = HTML::toBBCode($activity['name']); + $content = HTML::toBBCode($activity['content']); + + if (!empty($activity['emojis'])) { + $content = self::replaceEmojis($content, $activity['emojis']); + } + + $content = self::convertMentions($content); + if (!empty($activity['source'])) { $item['body'] = $activity['source']; + $item['raw-body'] = $content; } else { - $content = HTML::toBBCode($activity['content']); - - if (!empty($activity['emojis'])) { - $content = self::replaceEmojis($content, $activity['emojis']); - } - - $content = self::convertMentions($content); - if (empty($activity['directmessage']) && ($item['thr-parent'] != $item['uri']) && ($item['gravity'] == GRAVITY_COMMENT)) { $item_private = !in_array(0, $activity['item_receiver']); $parent = Item::selectFirst(['id', 'uri-id', 'private', 'author-link', 'alias'], ['uri' => $item['thr-parent']]); @@ -429,7 +477,7 @@ class Processor $content = self::removeImplicitMentionsFromBody($content, $parent); } $item['content-warning'] = HTML::toBBCode($activity['summary']); - $item['body'] = $content; + $item['raw-body'] = $item['body'] = $content; } self::storeFromBody($item); diff --git a/src/Protocol/ActivityPub/Receiver.php b/src/Protocol/ActivityPub/Receiver.php index 69d24a7abb..31a2dcb0b8 100644 --- a/src/Protocol/ActivityPub/Receiver.php +++ b/src/Protocol/ActivityPub/Receiver.php @@ -1231,24 +1231,36 @@ class Receiver $filetype = strtolower(substr($mediatype, 0, strpos($mediatype, '/'))); if ($filetype == 'audio') { - $attachments[$filetype] = ['type' => $mediatype, 'url' => $href]; + $attachments[$filetype] = ['type' => $mediatype, 'url' => $href, 'height' => null, 'size' => null]; } elseif ($filetype == 'video') { $height = (int)JsonLD::fetchElement($url, 'as:height', '@value'); + $size = (int)JsonLD::fetchElement($url, 'pt:size', '@value'); - // We save bandwidth by using a moderate height + // We save bandwidth by using a moderate height (alt least 480 pixel height) // Peertube normally uses these heights: 240, 360, 480, 720, 1080 if (!empty($attachments[$filetype]['height']) && - (($height > 480) || $height < $attachments[$filetype]['height'])) { + ($height > $attachments[$filetype]['height']) && ($attachments[$filetype]['height'] >= 480)) { continue; } - $attachments[$filetype] = ['type' => $mediatype, 'url' => $href, 'height' => $height]; + $attachments[$filetype] = ['type' => $mediatype, 'url' => $href, 'height' => $height, 'size' => $size]; + } elseif (in_array($mediatype, ['application/x-bittorrent', 'application/x-bittorrent;x-scheme-handler/magnet'])) { + $height = (int)JsonLD::fetchElement($url, 'as:height', '@value'); + + // For Torrent links we always store the highest resolution + if (!empty($attachments[$mediatype]['height']) && ($height < $attachments[$mediatype]['height'])) { + continue; + } + + $attachments[$mediatype] = ['type' => $mediatype, 'url' => $href, 'height' => $height, 'size' => null]; } } foreach ($attachments as $type => $attachment) { $object_data['attachments'][] = ['type' => $type, 'mediaType' => $attachment['type'], + 'height' => $attachment['height'], + 'size' => $attachment['size'], 'name' => '', 'url' => $attachment['url']]; } diff --git a/src/Protocol/Diaspora.php b/src/Protocol/Diaspora.php index fd668b1f30..d2bbb7a4d0 100644 --- a/src/Protocol/Diaspora.php +++ b/src/Protocol/Diaspora.php @@ -2810,6 +2810,26 @@ class Diaspora return Relay::isSolicitedPost($tags, $body, $contact['id'], $url, Protocol::DIASPORA); } + /** + * Store an attached photo in the post-media table + * + * @param int $uriid + * @param object $photo + * @return void + */ + private static function storePhoto(int $uriid, $photo) + { + $data = []; + $data['uri-id'] = $uriid; + $data['type'] = Post\Media::IMAGE; + $data['url'] = XML::unescape($photo->remote_photo_path) . XML::unescape($photo->remote_photo_name); + $data['height'] = (int)XML::unescape($photo->height ?? 0); + $data['width'] = (int)XML::unescape($photo->width ?? 0); + $data['description'] = XML::unescape($photo->text ?? ''); + + Post\Media::insert($data); + } + /** * Receives status messages * @@ -2847,13 +2867,18 @@ class Diaspora } } - $body = Markdown::toBBCode($text); + $raw_body = $body = Markdown::toBBCode($text); $datarray = []; + $datarray["guid"] = $guid; + $datarray["uri"] = $datarray["parent-uri"] = self::getUriFromGuid($author, $guid); + $datarray['uri-id'] = ItemURI::insert(['uri' => $datarray['uri'], 'guid' => $datarray['guid']]); + // Attach embedded pictures to the body if ($data->photo) { foreach ($data->photo as $photo) { + self::storePhoto($datarray['uri-id'], $photo); $body = "[img]".XML::unescape($photo->remote_photo_path). XML::unescape($photo->remote_photo_name)."[/img]\n".$body; } @@ -2887,10 +2912,6 @@ class Diaspora $datarray["owner-link"] = $datarray["author-link"]; $datarray["owner-id"] = $datarray["author-id"]; - $datarray["guid"] = $guid; - $datarray["uri"] = $datarray["parent-uri"] = self::getUriFromGuid($author, $guid); - $datarray['uri-id'] = ItemURI::insert(['uri' => $datarray['uri'], 'guid' => $datarray['guid']]); - $datarray["verb"] = Activity::POST; $datarray["gravity"] = GRAVITY_PARENT; @@ -2904,6 +2925,7 @@ class Diaspora } $datarray["body"] = self::replacePeopleGuid($body, $contact["url"]); + $datarray["raw-body"] = self::replacePeopleGuid($raw_body, $contact["url"]); self::storeMentions($datarray['uri-id'], $text); Tag::storeRawTagsFromBody($datarray['uri-id'], $datarray["body"]); diff --git a/static/dbstructure.config.php b/static/dbstructure.config.php index 3322616785..a3b94e0b27 100755 --- a/static/dbstructure.config.php +++ b/static/dbstructure.config.php @@ -54,7 +54,7 @@ use Friendica\Database\DBA; if (!defined('DB_UPDATE_VERSION')) { - define('DB_UPDATE_VERSION', 1371); + define('DB_UPDATE_VERSION', 1372); } return [ @@ -843,6 +843,7 @@ return [ "title" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => "item title"], "content-warning" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""], "body" => ["type" => "mediumtext", "comment" => "item body content"], + "raw-body" => ["type" => "mediumtext", "comment" => "Body without embedded media links"], "location" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => "text location where this item originated"], "coord" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => "longitude/latitude pair representing location where this item originated"], "language" => ["type" => "text", "comment" => "Language information about this post"], @@ -1133,6 +1134,27 @@ return [ "PRIMARY" => ["uri-id"], ] ], + "post-media" => [ + "comment" => "Attached media", + "fields" => [ + "id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "comment" => "sequential ID"], + "uri-id" => ["type" => "int unsigned", "not null" => "1", "foreign" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the item uri"], + "url" => ["type" => "varbinary(511)", "not null" => "1", "comment" => "Media URL"], + "type" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => "Media type"], + "mimetype" => ["type" => "varchar(60)", "comment" => ""], + "height" => ["type" => "smallint unsigned", "comment" => "Height of the media"], + "width" => ["type" => "smallint unsigned", "comment" => "Width of the media"], + "size" => ["type" => "int unsigned", "comment" => "Media size"], + "preview" => ["type" => "varbinary(255)", "comment" => "Preview URL"], + "preview-height" => ["type" => "smallint unsigned", "comment" => "Height of the preview picture"], + "preview-width" => ["type" => "smallint unsigned", "comment" => "Width of the preview picture"], + "description" => ["type" => "text", "comment" => ""], + ], + "indexes" => [ + "PRIMARY" => ["id"], + "uri-id-url" => ["UNIQUE", "uri-id", "url"], + ] + ], "post-tag" => [ "comment" => "post relation to tags", "fields" => [ From 5ac73a847d8470546016ec1cb498e0008e0d709b Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 29 Oct 2020 08:48:08 +0000 Subject: [PATCH 20/23] Functions renamed --- src/Model/Item.php | 2 +- src/Model/Post/Media.php | 8 ++++---- src/Protocol/Diaspora.php | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Model/Item.php b/src/Model/Item.php index 514163e96f..a282762135 100644 --- a/src/Model/Item.php +++ b/src/Model/Item.php @@ -1818,7 +1818,7 @@ class Item } // Remove all media attachments from the body and store them in the post-media table - $item['raw-body'] = Post\Media::addAttachmentsFromBody($item['uri-id'], $item['raw-body']); + $item['raw-body'] = Post\Media::insertFromBody($item['uri-id'], $item['raw-body']); $item['raw-body'] = self::setHashtags($item['raw-body']); // Check for hashtags in the body and repair or add hashtag links diff --git a/src/Model/Post/Media.php b/src/Model/Post/Media.php index de50f13598..74af04c8ae 100644 --- a/src/Model/Post/Media.php +++ b/src/Model/Post/Media.php @@ -34,9 +34,9 @@ use Friendica\Util\Images; */ class Media { - const UNKNOWN = 0; - const IMAGE = 1; - const VIDEO = 2; + const UNKNOWN = 0; + const IMAGE = 1; + const VIDEO = 2; const AUDIO = 3; const TORRENT = 16; @@ -104,7 +104,7 @@ class Media * @param string $body * @return string Body without media links */ - public static function addAttachmentsFromBody(int $uriid, string $body) + public static function insertFromBody(int $uriid, string $body) { // Simplify image codes $body = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $body); diff --git a/src/Protocol/Diaspora.php b/src/Protocol/Diaspora.php index d2bbb7a4d0..c9a02a4fce 100644 --- a/src/Protocol/Diaspora.php +++ b/src/Protocol/Diaspora.php @@ -2817,7 +2817,7 @@ class Diaspora * @param object $photo * @return void */ - private static function storePhoto(int $uriid, $photo) + private static function storePhotoAsMedia(int $uriid, $photo) { $data = []; $data['uri-id'] = $uriid; @@ -2878,7 +2878,7 @@ class Diaspora // Attach embedded pictures to the body if ($data->photo) { foreach ($data->photo as $photo) { - self::storePhoto($datarray['uri-id'], $photo); + self::storePhotoAsMedia($datarray['uri-id'], $photo); $body = "[img]".XML::unescape($photo->remote_photo_path). XML::unescape($photo->remote_photo_name)."[/img]\n".$body; } From e3df7eb1310fc1524651dc1005f850f4031059b1 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 29 Oct 2020 08:50:35 +0000 Subject: [PATCH 21/23] Superfluous to-do removed --- src/Model/Post/Media.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Model/Post/Media.php b/src/Model/Post/Media.php index 74af04c8ae..ec3bf967b3 100644 --- a/src/Model/Post/Media.php +++ b/src/Model/Post/Media.php @@ -148,7 +148,6 @@ class Media } } - /// @todo audio + video if (preg_match_all("/\[audio\]([^\[\]]*)\[\/audio\]/ism", $body, $audios, PREG_SET_ORDER)) { foreach ($audios as $audio) { $body = str_replace($audio[0], '', $body); From 56fe1400a748fd351bdee62da65a03586adaeafc Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 29 Oct 2020 09:03:06 +0000 Subject: [PATCH 22/23] Next renamed function --- src/Protocol/ActivityPub/Processor.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Protocol/ActivityPub/Processor.php b/src/Protocol/ActivityPub/Processor.php index 7c8ec33d9d..63732b0e15 100644 --- a/src/Protocol/ActivityPub/Processor.php +++ b/src/Protocol/ActivityPub/Processor.php @@ -89,7 +89,7 @@ class Processor * @param array $attachment * @return void */ - private static function storeAttachment(int $uriid, array $attachment) + private static function storeAttachmentAsMedia(int $uriid, array $attachment) { if (empty($attachment['url'])) { return; @@ -152,7 +152,7 @@ class Processor $item['body'] = PageInfo::appendDataToBody($item['body'], $data); break; default: - self::storeAttachment($item['uri-id'], $attach); + self::storeAttachmentAsMedia($item['uri-id'], $attach); $filetype = strtolower(substr($attach['mediaType'], 0, strpos($attach['mediaType'], '/'))); if ($filetype == 'image') { From 46bd1704df7a6202338cf4a3690599e631c73b8d Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 29 Oct 2020 13:28:27 +0000 Subject: [PATCH 23/23] Fix notice "Undefined index: network" and warning "strpos(): Empty needle" --- src/Content/PageInfo.php | 2 +- src/Module/Contact.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Content/PageInfo.php b/src/Content/PageInfo.php index 39bd35f73a..786385c3ef 100644 --- a/src/Content/PageInfo.php +++ b/src/Content/PageInfo.php @@ -292,7 +292,7 @@ class PageInfo $quotedUrl )$#isx", function ($match) use ($url) { // Stripping URLs with no label - if (!isset($match[1])) { + if (empty($match[1])) { return ''; } diff --git a/src/Module/Contact.php b/src/Module/Contact.php index 17b598a98b..08256bcf56 100644 --- a/src/Module/Contact.php +++ b/src/Module/Contact.php @@ -937,7 +937,7 @@ class Contact extends BaseModule ], ]; - if (in_array($contact['network'], [Protocol::FEED, Protocol::MAIL]) && ($cid != $pcid)) { + if (!empty($contact['network']) && in_array($contact['network'], [Protocol::FEED, Protocol::MAIL]) && ($cid != $pcid)) { $tabs[] = ['label' => DI::l10n()->t('Advanced'), 'url' => 'contact/' . $cid . '/advanced/', 'sel' => (($active_tab == self::TAB_ADVANCED) ? 'active' : ''),