From 785f8124ed9300abea533d48be95a483f2cecf18 Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 26 May 2021 09:24:37 +0000 Subject: [PATCH] Issue 10306: Improve local delivery --- src/Model/Contact.php | 2 +- src/Model/Item.php | 89 +++++++++++++++++++----- src/Model/Post.php | 5 +- src/Model/User.php | 2 +- src/Protocol/ActivityPub/Transmitter.php | 12 +--- src/Util/Network.php | 11 +++ src/Worker/Delivery.php | 41 ----------- src/Worker/Notifier.php | 68 +++++++++++------- 8 files changed, 131 insertions(+), 99 deletions(-) diff --git a/src/Model/Contact.php b/src/Model/Contact.php index 4af1d9bb6d..c3c75a8d43 100644 --- a/src/Model/Contact.php +++ b/src/Model/Contact.php @@ -1941,7 +1941,7 @@ class Contact return false; } - if (Contact::isLocal($ret['url'])) { + if (self::isLocal($ret['url'])) { Logger::info('Local contacts are not updated here.'); return true; } diff --git a/src/Model/Item.php b/src/Model/Item.php index 4dbea3eb8b..5f0c4c8ed4 100644 --- a/src/Model/Item.php +++ b/src/Model/Item.php @@ -1018,6 +1018,30 @@ class Item if (empty($item['event-id'])) { unset($item['event-id']); + + $ev = Event::fromBBCode($item['body']); + if ((!empty($ev['desc']) || !empty($ev['summary'])) && !empty($ev['start'])) { + Logger::info('Event found.'); + $ev['cid'] = $item['contact-id']; + $ev['uid'] = $item['uid']; + $ev['uri'] = $item['uri']; + $ev['edited'] = $item['edited']; + $ev['private'] = $item['private']; + $ev['guid'] = $item['guid']; + $ev['plink'] = $item['plink']; + $ev['network'] = $item['network']; + $ev['protocol'] = $item['protocol']; + $ev['direction'] = $item['direction']; + $ev['source'] = $item['source']; + + $event = DBA::selectFirst('event', ['id'], ['uri' => $item['uri'], 'uid' => $item['uid']]); + if (DBA::isResult($event)) { + $ev['id'] = $event['id']; + } + + $item['event-id'] = Event::store($ev); + Logger::info('Event was stored', ['id' => $item['event-id']]); + } } if (empty($item['causer-id'])) { @@ -1322,19 +1346,26 @@ class Item /** * Store a public item defined by their URI-ID for the given users * - * @param integer $uri_id URI-ID of the given item - * @param integer $uid The user that will receive the item entry - * @param array $fields Additional fields to be stored + * @param integer $uri_id URI-ID of the given item + * @param integer $uid The user that will receive the item entry + * @param array $fields Additional fields to be stored + * @param integer $source_uid User id of the source post * @return integer stored item id */ - public static function storeForUserByUriId(int $uri_id, int $uid, array $fields = []) + public static function storeForUserByUriId(int $uri_id, int $uid, array $fields = [], int $source_uid = 0) { - $item = Post::selectFirst(self::ITEM_FIELDLIST, ['uri-id' => $uri_id, 'uid' => 0]); - if (!DBA::isResult($item)) { + if ($uid == $source_uid) { + Logger::warning('target UID must be be equal to the source UID', ['uri-id' => $uri_id, 'uid' => $uid]); return 0; } - if (($item['private'] == self::PRIVATE) || !in_array($item['network'], Protocol::FEDERATED)) { + $item = Post::selectFirst(self::ITEM_FIELDLIST, ['uri-id' => $uri_id, 'uid' => $source_uid]); + if (!DBA::isResult($item)) { + Logger::warning('Item could not be fetched', ['uri-id' => $uri_id, 'uid' => $source_uid]); + return 0; + } + + if (($source_uid == 0) && (($item['private'] == self::PRIVATE) || !in_array($item['network'], Protocol::FEDERATED))) { Logger::notice('Item is private or not from a federated network. It will not be stored for the user.', ['uri-id' => $uri_id, 'uid' => $uid, 'private' => $item['private'], 'network' => $item['network']]); return 0; } @@ -1343,8 +1374,24 @@ class Item $item = array_merge($item, $fields); + $is_reshare = ($item['gravity'] == GRAVITY_ACTIVITY) && ($item['verb'] == Activity::ANNOUNCE); + + if ((($item['gravity'] == GRAVITY_PARENT) || $is_reshare) && + DI::pConfig()->get($uid, 'system', 'accept_only_sharer') && + !Contact::isSharingByURL($item['author-link'], $uid) && + !Contact::isSharingByURL($item['owner-link'], $uid)) { + Logger::info('Contact is not a follower, thread will not be stored', ['author' => $item['author-link'], 'uid' => $uid]); + return 0; + } + + if ((($item['gravity'] == GRAVITY_COMMENT) || $is_reshare) && !Post::exists(['uri-id' => $item['thr-parent-id'], 'uid' => $uid])) { + // Only do an auto complete with the source uid "0" to prevent privavy problems + $result = self::storeForUserByUriId($item['thr-parent-id'], $uid); + Logger::info('Fetched thread parent', ['uri-id' => $item['thr-parent-id'], 'uid' => $uid, 'result' => $result]); + } + $stored = self::storeForUser($item, $uid); - Logger::info('Public item stored for user', ['uri-id' => $item['uri-id'], 'uid' => $uid, 'stored' => $stored]); + Logger::info('Item stored for user', ['uri-id' => $item['uri-id'], 'uid' => $uid, 'source-uid' => $source_uid, 'stored' => $stored]); return $stored; } @@ -1364,11 +1411,21 @@ class Item } unset($item['id']); - unset($item['parent']); unset($item['mention']); unset($item['starred']); unset($item['unseen']); unset($item['psid']); + unset($item['pinned']); + unset($item['ignored']); + unset($item['pubmail']); + unset($item['forum_mode']); + + //unset($item['post-reason']); + //unset($item['protocol']); + unset($item['event-id']); + unset($item['hidden']); + unset($item['notification-type']); + //unset($item['resource-id']); $item['uid'] = $uid; $item['origin'] = 0; @@ -1394,8 +1451,6 @@ class Item $item['contact-id'] = $self['id']; } - /// @todo Handling of "event-id" - $notify = false; if ($item['gravity'] == GRAVITY_PARENT) { $contact = DBA::selectFirst('contact', [], ['id' => $item['contact-id'], 'self' => false]); @@ -1407,9 +1462,9 @@ class Item $distributed = self::insert($item, $notify, true); if (!$distributed) { - Logger::info("Distributed public item wasn't stored", ['uri-id' => $item['uri-id'], 'user' => $uid]); + Logger::info("Distributed item wasn't stored", ['uri-id' => $item['uri-id'], 'user' => $uid]); } else { - Logger::info('Distributed public item was stored', ['uri-id' => $item['uri-id'], 'user' => $uid, 'stored' => $distributed]); + Logger::info('Distributed item was stored', ['uri-id' => $item['uri-id'], 'user' => $uid, 'stored' => $distributed]); } return $distributed; } @@ -3295,18 +3350,18 @@ class Item { $shared = BBCode::fetchShareAttributes($item['body']); if (empty($shared['link'])) { - return $item['body']; + return $item['body']; } - + $id = self::fetchByLink($shared['link']); Logger::info('Fetched shared post', ['uri-id' => $item['uri-id'], 'id' => $id, 'author' => $shared['profile'], 'url' => $shared['link'], 'guid' => $shared['guid'], 'callstack' => System::callstack()]); if (!$id) { - return $item['body']; + return $item['body']; } $shared_item = Post::selectFirst(['author-name', 'author-link', 'author-avatar', 'plink', 'created', 'guid', 'title', 'body'], ['id' => $id]); if (!DBA::isResult($shared_item)) { - return $item['body']; + return $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']); diff --git a/src/Model/Post.php b/src/Model/Post.php index aae847664d..2dc73446be 100644 --- a/src/Model/Post.php +++ b/src/Model/Post.php @@ -164,15 +164,16 @@ class Post * @param array $fields * @param array $condition * @param array $params + * @param bool $user_mode true = post-user-view, false = post-view * @return bool|array * @throws \Exception * @see DBA::select */ - public static function selectFirst(array $fields = [], array $condition = [], $params = []) + public static function selectFirst(array $fields = [], array $condition = [], $params = [], bool $user_mode = true) { $params['limit'] = 1; - $result = self::select($fields, $condition, $params); + $result = self::select($fields, $condition, $params, $user_mode); if (is_bool($result)) { return $result; diff --git a/src/Model/User.php b/src/Model/User.php index 38a22f70f9..0482488e2b 100644 --- a/src/Model/User.php +++ b/src/Model/User.php @@ -313,7 +313,7 @@ class User public static function getIdForURL(string $url) { // Avoid any database requests when the hostname isn't even part of the url. - if (!strpos($url, DI::baseUrl()->getHostname())) { + if (!Contact::isLocal($url)) { return 0; } diff --git a/src/Protocol/ActivityPub/Transmitter.php b/src/Protocol/ActivityPub/Transmitter.php index 83e3d35a97..bfb1287c82 100644 --- a/src/Protocol/ActivityPub/Transmitter.php +++ b/src/Protocol/ActivityPub/Transmitter.php @@ -748,10 +748,6 @@ class Transmitter $contacts = DBA::select('contact', ['id', 'url', 'network', 'protocol', 'gsid'], $condition); while ($contact = DBA::fetch($contacts)) { - if (Contact::isLocal($contact['url'])) { - continue; - } - if (!self::isAPContact($contact, $networks)) { continue; } @@ -766,7 +762,7 @@ class Transmitter $profile = APContact::getByURL($contact['url'], false); if (!empty($profile)) { - if (empty($profile['sharedinbox']) || $personal) { + if (empty($profile['sharedinbox']) || $personal || Contact::isLocal($contact['url'])) { $target = $profile['inbox']; } else { $target = $profile['sharedinbox']; @@ -829,15 +825,11 @@ class Transmitter if ($item_profile && ($receiver == $item_profile['followers']) && ($uid == $profile_uid)) { $inboxes = array_merge($inboxes, self::fetchTargetInboxesforUser($uid, $personal, self::isAPPost($last_id))); } else { - if (Contact::isLocal($receiver)) { - continue; - } - $profile = APContact::getByURL($receiver, false); if (!empty($profile)) { $contact = Contact::getByURLForUser($receiver, $uid, false, ['id']); - if (empty($profile['sharedinbox']) || $personal || $blindcopy) { + if (empty($profile['sharedinbox']) || $personal || $blindcopy || Contact::isLocal($receiver)) { $target = $profile['inbox']; } else { $target = $profile['sharedinbox']; diff --git a/src/Util/Network.php b/src/Util/Network.php index 2c9949ee23..0b10687c9d 100644 --- a/src/Util/Network.php +++ b/src/Util/Network.php @@ -548,4 +548,15 @@ class Network exit; } } + + /** + * Check if the given URL is a local link + * + * @param string $url + * @return bool + */ + public static function isLocalLink(string $url) + { + return (strpos(Strings::normaliseLink($url), Strings::normaliseLink(DI::baseUrl())) !== false); + } } diff --git a/src/Worker/Delivery.php b/src/Worker/Delivery.php index db7b8c26a5..dad2ff7b3d 100644 --- a/src/Worker/Delivery.php +++ b/src/Worker/Delivery.php @@ -30,13 +30,11 @@ use Friendica\Protocol\DFRN; use Friendica\Protocol\Diaspora; use Friendica\Protocol\Email; use Friendica\Protocol\Activity; -use Friendica\Util\Strings; use Friendica\Util\Network; use Friendica\Core\Worker; use Friendica\Model\Conversation; use Friendica\Model\FContact; use Friendica\Model\Item; -use Friendica\Model\Post; use Friendica\Protocol\Relay; class Delivery @@ -216,11 +214,6 @@ class Delivery $contact['network'] = Protocol::DIASPORA; } - // Ensure that local contacts are delivered locally - if (Model\Contact::isLocal($contact['url'])) { - $contact['network'] = Protocol::DFRN; - } - Logger::notice('Delivering', ['cmd' => $cmd, 'uri-id' => $post_uriid, 'followup' => $followup, 'network' => $contact['network']]); switch ($contact['network']) { @@ -316,40 +309,6 @@ class Delivery Logger::debug('Notifier entry: ' . $contact["url"] . ' ' . (($target_item['guid'] ?? '') ?: $target_item['id']) . ' entry: ' . $atom); - // perform local delivery if we are on the same site - if (Model\Contact::isLocal($contact['url'])) { - $condition = ['nurl' => Strings::normaliseLink($contact['url']), 'self' => true]; - $target_self = DBA::selectFirst('contact', ['uid'], $condition); - if (!DBA::isResult($target_self)) { - return; - } - $target_uid = $target_self['uid']; - - // Check if the user has got this contact - $cid = Model\Contact::getIdForURL($owner['url'], $target_uid); - if (!$cid) { - // Otherwise there should be a public contact - $cid = Model\Contact::getIdForURL($owner['url']); - if (!$cid) { - return; - } - } - - $target_importer = DFRN::getImporter($cid, $target_uid); - if (empty($target_importer)) { - // This should never happen - return; - } - - DFRN::import($atom, $target_importer, Conversation::PARCEL_LOCAL_DFRN, Conversation::PUSH); - - if (in_array($cmd, [Delivery::POST, Delivery::POKE])) { - Model\Post\DeliveryData::incrementQueueDone($target_item['uri-id'], Model\Post\DeliveryData::DFRN); - } - - return; - } - $protocol = Model\Post\DeliveryData::DFRN; // We don't have a relationship with contacts on a public post. diff --git a/src/Worker/Notifier.php b/src/Worker/Notifier.php index 60a5572ca5..b53fba05f1 100644 --- a/src/Worker/Notifier.php +++ b/src/Worker/Notifier.php @@ -41,6 +41,7 @@ use Friendica\Protocol\Diaspora; use Friendica\Protocol\OStatus; use Friendica\Protocol\Relay; use Friendica\Protocol\Salmon; +use Friendica\Util\Network; /* * The notifier is typically called with: @@ -313,7 +314,7 @@ class Notifier /// @todo Possibly we should not uplink when the author is the forum itself? if ((intval($parent['forum_mode']) == 1) && !$top_level && ($cmd !== Delivery::UPLINK) - && ($target_item['verb'] != Activity::ANNOUNCE)) { + && ($target_item['verb'] != Activity::ANNOUNCE)) { Worker::add($a->queue['priority'], 'Notifier', Delivery::UPLINK, $post_uriid, $sender_uid); } @@ -494,29 +495,32 @@ class Notifier /** * Deliver the message to the contacts * - * @param string $cmd - * @param int $post_uriid + * @param string $cmd + * @param int $post_uriid * @param int $sender_uid - * @param array $target_item - * @param array $thr_parent - * @param array $owner - * @param bool $batch_delivery - * @param array $contacts - * @param array $ap_contacts - * @param array $conversants - * @return int - * @throws InternalServerErrorException - * @throws Exception + * @param array $target_item + * @param array $thr_parent + * @param array $owner + * @param bool $batch_delivery + * @param array $contacts + * @param array $ap_contacts + * @param array $conversants + * @return int + * @throws InternalServerErrorException + * @throws Exception */ private static function delivery(string $cmd, int $post_uriid, int $sender_uid, array $target_item, array $thr_parent, array $owner, bool $batch_delivery, bool $in_batch, array $contacts, array $ap_contacts, array $conversants = []) { - $a = DI::app(); + $a = DI::app(); $delivery_queue_count = 0; foreach ($contacts as $contact) { - // Ensure that local contacts are delivered via DFRN - if (Contact::isLocal($contact['url'])) { - $contact['network'] = Protocol::DFRN; + // Direct delivery of local contacts + if ($target_uid = User::getIdForURL($contact['url'])) { + Logger::info('Direct delivery', ['uri-id' => $target_item['uri-id'], 'target' => $target_uid]); + $fields = ['protocol' => Conversation::PARCEL_LOCAL_DFRN, 'direction' => Conversation::PUSH]; + Item::storeForUserByUriId($target_item['uri-id'], $target_uid, $fields, $target_item['uid']); + continue; } // Deletions are always sent via DFRN as well. @@ -575,19 +579,19 @@ class Notifier /** * Deliver the message via OStatus * - * @param int $target_id - * @param array $target_item - * @param array $owner - * @param array $url_recipients - * @param bool $public_message - * @param bool $push_notify - * @return int - * @throws InternalServerErrorException - * @throws Exception + * @param int $target_id + * @param array $target_item + * @param array $owner + * @param array $url_recipients + * @param bool $public_message + * @param bool $push_notify + * @return int + * @throws InternalServerErrorException + * @throws Exception */ private static function deliverOStatus(int $target_id, array $target_item, array $owner, array $url_recipients, bool $public_message, bool $push_notify) { - $a = DI::app(); + $a = DI::app(); $delivery_queue_count = 0; $url_recipients = array_filter($url_recipients); @@ -775,6 +779,16 @@ class Notifier foreach ($inboxes as $inbox => $receivers) { $contacts = array_merge($contacts, $receivers); + if ((count($receivers) == 1) && Network::isLocalLink($inbox)) { + $contact = Contact::getById($receivers[0], ['url']); + if ($target_uid = User::getIdForURL($contact['url'])) { + $fields = ['protocol' => Conversation::PARCEL_LOCAL_DFRN, 'direction' => Conversation::PUSH]; + Item::storeForUserByUriId($target_item['uri-id'], $target_uid, $fields, $target_item['uid']); + Logger::info('Delivered locally', ['cmd' => $cmd, 'id' => $target_item['id'], 'inbox' => $inbox]); + continue; + } + } + Logger::info('Delivery via ActivityPub', ['cmd' => $cmd, 'id' => $target_item['id'], 'inbox' => $inbox]); if (Worker::add(['priority' => $priority, 'created' => $created, 'dont_fork' => true],