From 0d3aa681b4bec50c72e426c60cc5a22e4736d9e9 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 9 Oct 2022 21:16:36 +0000 Subject: [PATCH] The quote functionality is simplified --- mod/item.php | 9 +- mod/share.php | 5 +- src/Contact/Avatar.php | 2 +- src/Content/Item.php | 112 +++++++++++++++++++++ src/Model/Contact.php | 25 ++++- src/Model/Item.php | 85 ++-------------- src/Protocol/ActivityPub/Processor.php | 37 +------ src/Protocol/ActivityPub/Receiver.php | 16 ++- src/Protocol/DFRN.php | 3 - src/Protocol/Diaspora.php | 133 ++----------------------- 10 files changed, 170 insertions(+), 257 deletions(-) diff --git a/mod/item.php b/mod/item.php index 6c01cdbc18..f3f0fd40d6 100644 --- a/mod/item.php +++ b/mod/item.php @@ -595,14 +595,7 @@ function item_post(App $a) { $datarray['protocol'] = Conversation::PARCEL_DIRECT; $datarray['direction'] = Conversation::PUSH; - if ($orig_post) { - $datarray['edit'] = true; - } else { - // If this was a share, add missing data here - $datarray = Item::addShareDataFromOriginal($datarray); - - $datarray['edit'] = false; - } + $datarray['edit'] = $orig_post; // Check for hashtags in the body and repair or add hashtag links if ($preview || $orig_post) { diff --git a/mod/share.php b/mod/share.php index b4c2bc50dd..de0159df78 100644 --- a/mod/share.php +++ b/mod/share.php @@ -42,11 +42,8 @@ function share_init(App $a) { } $shared = BBCode::fetchShareAttributes($item['body']); - if (!empty($shared['message_id']) || !empty($shared['link'])) { + if (empty($shared['comment']) && (!empty($shared['message_id']) || !empty($shared['link']))) { $content = '[share]' . ($shared['message_id'] ?: $shared['link']) . '[/share]'; - } elseif (strpos($item['body'], '[/share]') !== false) { - $pos = strpos($item['body'], '[share'); - $content = substr($item['body'], $pos); } else { $content = '[share]' . $item['uri'] . '[/share]'; } diff --git a/src/Contact/Avatar.php b/src/Contact/Avatar.php index d4368df22b..0cfc8df345 100644 --- a/src/Contact/Avatar.php +++ b/src/Contact/Avatar.php @@ -161,7 +161,7 @@ class Avatar $dirpath .= $part . '/'; if (!file_exists($dirpath)) { - if (!@mkdir($dirpath, $dir_perm)) { + if (!@mkdir($dirpath, $dir_perm) && !file_exists($dirpath)) { Logger::warning('Directory could not be created', ['directory' => $dirpath]); } } elseif ((($old_perm = fileperms($dirpath) & 0777) != $dir_perm) && !chmod($dirpath, $dir_perm)) { diff --git a/src/Content/Item.php b/src/Content/Item.php index ca60139eaf..d5c33a762c 100644 --- a/src/Content/Item.php +++ b/src/Content/Item.php @@ -22,10 +22,12 @@ namespace Friendica\Content; use Friendica\Content\Text\BBCode; +use Friendica\Content\Text\Markdown; use Friendica\Core\Hook; use Friendica\Core\L10n; use Friendica\Core\Logger; use Friendica\Core\Protocol; +use Friendica\Core\System; use Friendica\Database\DBA; use Friendica\Model\Contact; use Friendica\Model\Group; @@ -34,6 +36,7 @@ use Friendica\Model\Photo; use Friendica\Model\Tag; use Friendica\Model\Post; use Friendica\Protocol\Activity; +use Friendica\Protocol\Diaspora; use Friendica\Util\Profiler; use Friendica\Util\Proxy; use Friendica\Util\XML; @@ -565,4 +568,113 @@ class Item return $owner_thumb; } + + /** + * Add a share block for the given url + * + * @param string $url + * @param integer $uid + * @return string + */ + public function createSharedPostByUrl(string $url, int $uid = 0): string + { + if (!empty($uid)) { + $id = ModelItem::searchByLink($url, $uid); + } + + if (empty($id)) { + $id = ModelItem::fetchByLink($url); + } + + if (!$id) { + Logger::notice('Post could not be fetched.', ['url' => $url, 'uid' => $uid, 'callstack' => System::callstack()]); + return ''; + } + + Logger::debug('Fetched shared post', ['id' => $id, 'url' => $url, 'uid' => $uid, 'callstack' => System::callstack()]); + + $shared_item = Post::selectFirst(['uri-id', 'uri', 'body', 'title', 'author-name', 'author-link', 'author-avatar', 'guid', 'created', 'plink', 'network'], ['id' => $id]); + if (!DBA::isResult($shared_item)) { + Logger::warning('Post does not exist.', ['id' => $id, 'url' => $url, 'uid' => $uid]); + return ''; + } + + return $this->createSharedBlockByArray($shared_item); + } + + /** + * Add a share block for the given uri-id + * + * @param integer $UriId + * @param integer $uid + * @return string + */ + public function createSharedPostByUriId(int $UriId, int $uid = 0): string + { + $fields = ['uri-id', 'uri', 'body', 'title', 'author-name', 'author-link', 'author-avatar', 'guid', 'created', 'plink', 'network']; + $shared_item = Post::selectFirst($fields, ['uri-id' => $UriId, 'uid' => [$uid, 0], 'private' => [ModelItem::PUBLIC, ModelItem::UNLISTED]]); + if (!DBA::isResult($shared_item)) { + Logger::notice('Post does not exist.', ['uri-id' => $UriId, 'uid' => $uid]); + return ''; + } + + return $this->createSharedBlockByArray($shared_item); + } + + /** + * Add a share block for the given guid + * + * @param string $guid + * @param integer $uid + * @return string + */ + public function createSharedPostByGuid(string $guid, int $uid = 0, string $host = ''): string + { + $fields = ['uri-id', 'uri', 'body', 'title', 'author-name', 'author-link', 'author-avatar', 'guid', 'created', 'plink', 'network']; + $shared_item = Post::selectFirst($fields, ['guid' => $guid, 'uid' => [$uid, 0], 'private' => [ModelItem::PUBLIC, ModelItem::UNLISTED]]); + + if (!DBA::isResult($shared_item) && !empty($host) && Diaspora::storeByGuid($guid, $host, true)) { + Logger::debug('Fetched post', ['guid' => $guid, 'host' => $host, 'uid' => $uid]); + $shared_item = Post::selectFirst($fields, ['guid' => $guid, 'uid' => [$uid, 0], 'private' => [ModelItem::PUBLIC, ModelItem::UNLISTED]]); + } elseif (DBA::isResult($shared_item)) { + Logger::debug('Found existing post', ['guid' => $guid, 'host' => $host, 'uid' => $uid]); + } + + if (!DBA::isResult($shared_item)) { + Logger::notice('Post does not exist.', ['guid' => $guid, 'host' => $host, 'uid' => $uid]); + return ''; + } + + return $this->createSharedBlockByArray($shared_item); + } + + /** + * Add a share block for the given item array + * + * @param array $item + * @return string + */ + public function createSharedBlockByArray(array $item): string + { + if (!in_array($item['network'] ?? '', Protocol::FEDERATED)) { + $item['guid'] = ''; + $item['uri'] = ''; + } + + $shared_content = BBCode::getShareOpeningTag($item['author-name'], $item['author-link'], $item['author-avatar'], $item['plink'], $item['created'], $item['guid'], $item['uri']); + + if (!empty($item['title'])) { + $shared_content .= '[h3]' . $item['title'] . "[/h3]\n"; + } + + // If it is a reshared post then reformat it to avoid display problems with two share elements + if (Diaspora::isReshare($item['body'], false)) { + $item['body'] = Markdown::toBBCode(BBCode::toMarkdown($item['body'])); + $item['body'] = Diaspora::replacePeopleGuid($item['body'], $item['author-link']); + } + + $shared_content .= $item['body'] . '[/share]'; + + return $shared_content; + } } diff --git a/src/Model/Contact.php b/src/Model/Contact.php index fcf177d832..2b6cb05bbf 100644 --- a/src/Model/Contact.php +++ b/src/Model/Contact.php @@ -35,7 +35,6 @@ use Friendica\Core\Worker; use Friendica\Database\Database; use Friendica\Database\DBA; use Friendica\DI; -use Friendica\Module\NoScrape; use Friendica\Network\HTTPException; use Friendica\Network\Probe; use Friendica\Protocol\Activity; @@ -138,6 +137,18 @@ class Contact return $contact; } + /** + * @param array $fields Array of selected fields, empty for all + * @param array $condition Array of fields for condition + * @param array $params Array of several parameters + * @return array|bool + * @throws \Exception + */ + public static function selectFirstAccount(array $fields = [], array $condition = [], array $params = []) + { + return DBA::selectFirst('account-view', $fields, $condition, $params); + } + /** * Insert a row into the contact table * Important: You can't use DBA::lastInsertId() after this call since it will be set to 0. @@ -350,6 +361,18 @@ class Contact return $contact; } + /** + * Checks if a contact uses a specific platform + * + * @param string $url + * @param string $platform + * @return boolean + */ + public static function isPlatform(string $url, string $platform): bool + { + return DBA::exists('account-view', ['nurl' => Strings::normaliseLink($url), 'platform' => $platform]); + } + /** * Tests if the given contact is a follower * diff --git a/src/Model/Item.php b/src/Model/Item.php index aa683f3522..3819185355 100644 --- a/src/Model/Item.php +++ b/src/Model/Item.php @@ -3564,63 +3564,6 @@ class Item return BBCode::fetchShareAttributes($item['body']); } - /** - * Fetch item information for shared items from the original items and adds it. - * - * @param array $item - * - * @return array item array with data from the original item - */ - public static function addShareDataFromOriginal(array $item): array - { - $shared = self::getShareArray($item); - if (empty($shared)) { - return $item; - } - - // Real reshares always have got a GUID. - if (empty($shared['guid'])) { - return $item; - } - - $uid = $item['uid'] ?? 0; - - // first try to fetch the item via the GUID. This will work for all reshares that had been created on this system - $shared_item = Post::selectFirst(['title', 'body'], ['guid' => $shared['guid'], 'uid' => [0, $uid]]); - if (!DBA::isResult($shared_item)) { - if (empty($shared['link'])) { - return $item; - } - - // Otherwhise try to find (and possibly fetch) the item via the link. This should work for Diaspora and ActivityPub posts - $id = self::fetchByLink($shared['link'] ?? '', $uid); - if (empty($id)) { - Logger::info('Original item not found', ['url' => $shared['link'] ?? '', 'callstack' => System::callstack()]); - return $item; - } - - $shared_item = Post::selectFirst(['title', 'body'], ['id' => $id]); - if (!DBA::isResult($shared_item)) { - return $item; - } - Logger::info('Got shared data from url', ['url' => $shared['link'], 'callstack' => System::callstack()]); - } else { - Logger::info('Got shared data from guid', ['guid' => $shared['guid'], 'callstack' => System::callstack()]); - } - - if (!empty($shared_item['title'])) { - $body = '[h3]' . $shared_item['title'] . "[/h3]\n" . $shared_item['body']; - unset($shared_item['title']); - } else { - $body = $shared_item['body']; - } - - $item['body'] = preg_replace("/\[share ([^\[\]]*)\].*\[\/share\]/ism", '[share $1]' . str_replace('$', '\$', $body) . '[/share]', $item['body']); - unset($shared_item['body']); - - return array_merge($item, $shared_item); - } - /** * Check a prospective item array against user-level permissions * @@ -3667,39 +3610,23 @@ class Item public static function improveSharedDataInBody(array $item): string { $shared = BBCode::fetchShareAttributes($item['body']); - if (empty($shared['link']) && empty($shared['message_id'])) { + if (empty($shared['guid']) && empty($shared['message_id'])) { return $item['body']; } $link = $shared['link'] ?: $shared['message_id']; - if (!empty($item['uid'])) { - $id = self::searchByLink($link, $item['uid']); + if (empty($shared_content)) { + $shared_content = DI::contentItem()->createSharedPostByUrl($link, $item['uid'] ?? 0); } - if (empty($id)) { - $id = self::fetchByLink($link); - } - Logger::debug('Fetched shared post', ['uri-id' => $item['uri-id'], 'id' => $id, 'author' => $shared['profile'], 'url' => $shared['link'], 'guid' => $shared['guid'], 'uri' => $shared['message_id'], 'callstack' => System::callstack()]); - if (!$id) { + if (empty($shared_content)) { return $item['body']; } - $shared_item = Post::selectFirst(['author-name', 'author-link', 'author-avatar', 'plink', 'created', 'guid', 'uri', 'title', 'body'], ['id' => $id]); - if (!DBA::isResult($shared_item)) { - return $item['body']; - } + $item['body'] = preg_replace("/\[share.*?\](.*)\[\/share\]/ism", $shared_content, $item['body']); - $shared_content = BBCode::getShareOpeningTag($shared_item['author-name'], $shared_item['author-link'], $shared_item['author-avatar'], $shared_item['plink'], $shared_item['created'], $shared_item['guid'], $shared_item['uri']); - - if (!empty($shared_item['title'])) { - $shared_content .= '[h3]'.$shared_item['title'].'[/h3]'."\n"; - } - - $shared_content .= $shared_item['body']; - - $item['body'] = preg_replace("/\[share.*?\](.*)\[\/share\]/ism", $shared_content . '[/share]', $item['body']); - Logger::debug('New shared data', ['uri-id' => $item['uri-id'], 'id' => $id, 'shared_item' => $shared_item]); + Logger::debug('New shared data', ['uri-id' => $item['uri-id'], 'link' => $link, 'guid' => $item['guid']]); return $item['body']; } } diff --git a/src/Protocol/ActivityPub/Processor.php b/src/Protocol/ActivityPub/Processor.php index bfd70f2bab..5183a82c30 100644 --- a/src/Protocol/ActivityPub/Processor.php +++ b/src/Protocol/ActivityPub/Processor.php @@ -850,7 +850,7 @@ class Processor $item['raw-body'] = $item['body'] = $content; if (!empty($activity['quote-url'])) { - $item['body'] .= self::addSharedData($activity['quote-url']); + $item['body'] .= DI::contentItem()->createSharedPostByUrl($activity['quote-url']); } } @@ -870,41 +870,6 @@ class Processor return $item; } - /** - * Add a share block for the given quote link - * - * @param string $url - * @return string - */ - private static function addSharedData(string $url): string - { - $id = Item::fetchByLink($url); - if (empty($id)) { - return ''; - } - - $shared_item = Post::selectFirst(['author-name', 'author-link', 'author-avatar', 'plink', 'created', 'guid', 'uri', 'title', 'body'], ['id' => $id]); - if (!DBA::isResult($shared_item)) { - return ''; - } - - $prefix = BBCode::getShareOpeningTag( - $shared_item['author-name'], - $shared_item['author-link'], - $shared_item['author-avatar'], - $shared_item['plink'], - $shared_item['created'], - $shared_item['guid'], - $shared_item['uri'], - ); - - if (!empty($shared_item['title'])) { - $prefix .= '[h3]' . $shared_item['title'] . "[/h3]\n"; - } - - return $prefix . $shared_item['body'] . '[/share]'; - } - /** * Store hashtags and mentions * diff --git a/src/Protocol/ActivityPub/Receiver.php b/src/Protocol/ActivityPub/Receiver.php index 38ed993cf2..30348c7ade 100644 --- a/src/Protocol/ActivityPub/Receiver.php +++ b/src/Protocol/ActivityPub/Receiver.php @@ -512,7 +512,10 @@ class Receiver // } // } - Logger::info('Processing ' . $object_data['type'] . ' ' . $object_data['object_type'] . ' ' . $object_data['id']); + $account = Contact::selectFirstAccount(['platform'], ['nurl' => Strings::normaliseLink($actor)]); + $platform = $account['platform'] ?? ''; + + Logger::info('Processing', ['type' => $object_data['type'], 'object_type' => $object_data['object_type'], 'id' => $object_data['id'], 'actor' => $actor, 'platform' => $platform]); return $object_data; } @@ -1148,6 +1151,17 @@ class Receiver self::switchContacts($receivers, $actor); + // "birdsitelive" is a service that mirrors tweets into the fediverse + // These posts can be fetched without authentification, but are not marked as public + // We treat them as unlisted posts to be able to handle them. + if (empty($receivers) && $fetch_unlisted && Contact::isPlatform($actor, 'birdsitelive')) { + $receivers[0] = ['uid' => 0, 'type' => self::TARGET_GLOBAL]; + $receivers[-1] = ['uid' => -1, 'type' => self::TARGET_GLOBAL]; + Logger::notice('Post from "birdsitelive" is set to "unlisted"', ['id' => JsonLD::fetchElement($activity, '@id')]); + } elseif (empty($receivers)) { + Logger::notice('Post has got no receivers', ['fetch_unlisted' => $fetch_unlisted, 'actor' => $actor, 'id' => JsonLD::fetchElement($activity, '@id'), 'type' => JsonLD::fetchElement($activity, '@type')]); + } + return $receivers; } diff --git a/src/Protocol/DFRN.php b/src/Protocol/DFRN.php index 7caf8ac634..47637bc2e2 100644 --- a/src/Protocol/DFRN.php +++ b/src/Protocol/DFRN.php @@ -1961,9 +1961,6 @@ class DFRN } } - // Ensure to have the correct share data - $item = Item::addShareDataFromOriginal($item); - if ($entrytype == self::REPLY_RC) { $item['wall'] = 1; } elseif ($entrytype == self::TOP_LEVEL) { diff --git a/src/Protocol/Diaspora.php b/src/Protocol/Diaspora.php index bcf049945f..88665f949a 100644 --- a/src/Protocol/Diaspora.php +++ b/src/Protocol/Diaspora.php @@ -1025,7 +1025,7 @@ class Diaspora * @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \ImagickException */ - private static function storeByGuid(string $guid, string $server, bool $force) + public static function storeByGuid(string $guid, string $server, bool $force) { $serverparts = parse_url($server); @@ -2273,83 +2273,6 @@ class Diaspora return true; } - /** - * Fetches a message with a given guid - * - * @param string $guid message guid - * @param string $orig_author handle of the original post - * @return array|bool The fetched item or false on failure - * @throws \Friendica\Network\HTTPException\InternalServerErrorException - * @throws \ImagickException - */ - public static function originalItem(string $guid, string $orig_author) - { - if (empty($guid)) { - Logger::notice('Empty guid. Quitting.'); - return false; - } - - // Do we already have this item? - $fields = ['body', 'title', 'app', 'created', 'object-type', 'uri', 'guid', - 'author-name', 'author-link', 'author-avatar', 'plink', 'uri-id']; - $condition = ['guid' => $guid, 'visible' => true, 'deleted' => false, 'private' => [Item::PUBLIC, Item::UNLISTED]]; - $item = Post::selectFirst($fields, $condition); - - if (DBA::isResult($item)) { - Logger::notice("reshared message " . $guid . " already exists on system."); - - // Maybe it is already a reshared item? - // Then refetch the content, if it is a reshare from a reshare. - // If it is a reshared post from another network then reformat to avoid display problems with two share elements - if (self::isReshare($item['body'], true)) { - $item = []; - } elseif (self::isReshare($item['body'], false) || strstr($item['body'], '[share')) { - $item['body'] = Markdown::toBBCode(BBCode::toMarkdown($item['body'])); - - $item['body'] = self::replacePeopleGuid($item['body'], $item['author-link']); - - return $item; - } else { - return $item; - } - } - - if (!DBA::isResult($item)) { - if (empty($orig_author)) { - Logger::notice('Empty author for guid ' . $guid . '. Quitting.'); - return false; - } - - $server = 'https://' . substr($orig_author, strpos($orig_author, '@') + 1); - Logger::notice('1st try: reshared message ' . $guid . ' will be fetched via SSL from the server ' . $server); - $stored = self::storeByGuid($guid, $server, true); - - if (!$stored) { - $server = 'http://' . substr($orig_author, strpos($orig_author, '@') + 1); - Logger::notice('2nd try: reshared message ' . $guid . ' will be fetched without SSL from the server ' . $server); - $stored = self::storeByGuid($guid, $server, true); - } - - if ($stored) { - $fields = ['body', 'title', 'app', 'created', 'object-type', 'uri', 'guid', - 'author-name', 'author-link', 'author-avatar', 'plink', 'uri-id']; - $condition = ['guid' => $guid, 'visible' => true, 'deleted' => false, 'private' => [Item::PUBLIC, Item::UNLISTED]]; - $item = Post::selectFirst($fields, $condition); - - if (DBA::isResult($item)) { - // If it is a reshared post from another network then reformat to avoid display problems with two share elements - if (self::isReshare($item['body'], false)) { - $item['body'] = Markdown::toBBCode(BBCode::toMarkdown($item['body'])); - $item['body'] = self::replacePeopleGuid($item['body'], $item['author-link']); - } - - return $item; - } - } - } - return false; - } - /** * Stores a reshare activity * @@ -2442,15 +2365,11 @@ class Diaspora return true; } - $original_item = self::originalItem($root_guid, $root_author); - if (!$original_item) { + $original_person = FContact::getByURL($root_author); + if (!$original_person) { return false; } - if (empty($original_item['plink'])) { - $original_item['plink'] = self::plink($root_author, $root_guid); - } - $datarray = []; $datarray['uid'] = $importer['uid']; @@ -2475,34 +2394,15 @@ class Diaspora $datarray = self::setDirection($datarray, $direction); + $datarray['body'] = DI::contentItem()->createSharedPostByGuid($root_guid, $importer['uid'], $original_person['url']); + /// @todo Copy tag data from original post - - $prefix = BBCode::getShareOpeningTag( - $original_item['author-name'], - $original_item['author-link'], - $original_item['author-avatar'], - $original_item['plink'], - $original_item['created'], - $original_item['guid'], - $original_item['uri'], - ); - - if (!empty($original_item['title'])) { - $prefix .= '[h3]' . $original_item['title'] . "[/h3]\n"; - } - - $datarray['body'] = $prefix.$original_item['body'] . '[/share]'; - Tag::storeFromBody($datarray['uri-id'], $datarray['body']); - $datarray['app'] = $original_item['app']; - $datarray['plink'] = self::plink($author, $guid); $datarray['private'] = (($public == 'false') ? Item::PRIVATE : Item::PUBLIC); $datarray['changed'] = $datarray['created'] = $datarray['edited'] = $created_at; - $datarray['object-type'] = $original_item['object-type']; - self::fetchGuid($datarray); if (Item::isTooOld($datarray)) { @@ -3475,9 +3375,9 @@ class Diaspora } } + // @todo Check if this is obsolete and if we are still using different owners. (Possibly a fragment from the forum functionality) if ($item['author-link'] != $item['owner-link']) { - $body = BBCode::getShareOpeningTag($item['author-name'], $item['author-link'], $item['author-avatar'], - $item['plink'], $item['created']) . $body . '[/share]'; + $body = DI::contentItem()->createSharedBlockByArray($item); } // convert to markdown @@ -4182,26 +4082,11 @@ class Diaspora public static function performReshare(int $UriId, int $uid): int { - $fields = ['uri-id', 'body', 'title', 'author-name', 'author-link', 'author-avatar', 'guid', 'created', 'plink', 'uri']; - $item = Post::selectFirst($fields, ['uri-id' => $UriId, 'uid' => [$uid, 0], 'private' => [Item::PUBLIC, Item::UNLISTED]]); - if (!DBA::isResult($item)) { + $post = DI::contentItem()->createSharedPostByUriId($UriId, $uid); + if (empty($post)) { return 0; } - if (strpos($item['body'], '[/share]') !== false) { - $pos = strpos($item['body'], '[share'); - $post = substr($item['body'], $pos); - } else { - $post = BBCode::getShareOpeningTag($item['author-name'], $item['author-link'], $item['author-avatar'], $item['plink'], $item['created'], $item['guid'], $item['uri']); - - if (!empty($item['title'])) { - $post .= '[h3]' . $item['title'] . "[/h3]\n"; - } - - $post .= $item['body']; - $post .= '[/share]'; - } - $owner = User::getOwnerDataById($uid); $author = Contact::getPublicIdByUserId($uid);