]> git.mxchange.org Git - friendica.git/blobdiff - src/Protocol/ActivityPub/Transmitter.php
Merge pull request #13580 from annando/fetch-async
[friendica.git] / src / Protocol / ActivityPub / Transmitter.php
index ec61eb2eb34668e8c24ddb402f3e3157d8e3ee26..9e19724fa61b9512c1167f035374ec29e804a794 100644 (file)
@@ -195,7 +195,7 @@ class Transmitter
                }
 
                // When we hide our friends we will only show the pure number but don't allow more.
-               $show_contacts = empty($owner['hide-friends']);
+               $show_contacts = ActivityPub::isAcceptedRequester($owner['uid']) && empty($owner['hide-friends']);
 
                // Allow fetching the contact list when the requester is part of the list.
                if (($owner['page-flags'] == User::PAGE_FLAGS_PRVGROUP) && !empty($requester)) {
@@ -337,16 +337,17 @@ class Transmitter
        /**
         * Return the ActivityPub profile of the given user
         *
-        * @param int $uid User ID
+        * @param int  $uid  User ID
+        * @param bool $full If not full, only the basic information is returned
         * @return array with profile data
         * @throws HTTPException\NotFoundException
         * @throws HTTPException\InternalServerErrorException
         */
-       public static function getProfile(int $uid): array
+       public static function getProfile(int $uid, bool $full = true): array
        {
                $owner = User::getOwnerDataById($uid);
                if (!isset($owner['id'])) {
-                       DI::logger()->error('Unable to find owner data for uid', ['uid' => $uid, 'callstack' => System::callstack(20)]);
+                       DI::logger()->error('Unable to find owner data for uid', ['uid' => $uid]);
                        throw new HTTPException\NotFoundException('User not found.');
                }
 
@@ -372,16 +373,16 @@ class Transmitter
                $data['preferredUsername'] = $owner['nick'];
                $data['name'] = $owner['name'];
 
-               if (!empty($owner['country-name'] . $owner['region'] . $owner['locality'])) {
+               if (!$full && !empty($owner['country-name'] . $owner['region'] . $owner['locality'])) {
                        $data['vcard:hasAddress'] = ['@type' => 'vcard:Home', 'vcard:country-name' => $owner['country-name'],
                                'vcard:region' => $owner['region'], 'vcard:locality' => $owner['locality']];
                }
 
-               if (!empty($owner['about'])) {
+               if ($full && !empty($owner['about'])) {
                        $data['summary'] = BBCode::convertForUriId($owner['uri-id'] ?? 0, $owner['about'], BBCode::EXTERNAL);
                }
 
-               if (!empty($owner['xmpp']) || !empty($owner['matrix'])) {
+               if ($full && (!empty($owner['xmpp']) || !empty($owner['matrix']))) {
                        $data['vcard:hasInstantMessage'] = [];
 
                        if (!empty($owner['xmpp'])) {
@@ -399,7 +400,7 @@ class Transmitter
                        'owner' => $owner['url'],
                        'publicKeyPem' => $owner['pubkey']];
                $data['endpoints'] = ['sharedInbox' => DI::baseUrl() . '/inbox'];
-               if ($uid != 0) {
+               if ($full && $uid != 0) {
                        $data['icon'] = ['type' => 'Image', 'url' => User::getAvatarUrl($owner)];
 
                        $resourceid = Photo::ridFromURI($owner['photo']);
@@ -497,7 +498,7 @@ class Transmitter
         * @throws \Friendica\Network\HTTPException\InternalServerErrorException
         * @throws \ImagickException
         */
-       private static function fetchPermissionBlockFromThreadParent(array $item): array
+       private static function fetchPermissionBlockFromThreadParent(array $item, bool $is_group_thread): array
        {
                if (empty($item['thr-parent-id'])) {
                        return [];
@@ -528,7 +529,9 @@ class Transmitter
                $type = [Tag::TO => 'to', Tag::CC => 'cc', Tag::BTO => 'bto', Tag::BCC => 'bcc', Tag::AUDIENCE => 'audience'];
                foreach (Tag::getByURIId($item['thr-parent-id'], [Tag::TO, Tag::CC, Tag::BTO, Tag::BCC, Tag::AUDIENCE]) as $receiver) {
                        if (!empty($parent_profile['followers']) && $receiver['url'] == $parent_profile['followers'] && !empty($item_profile['followers'])) {
-                               $permissions[$type[$receiver['type']]][] = $item_profile['followers'];
+                               if (!$is_group_thread) {
+                                       $permissions[$type[$receiver['type']]][] = $item_profile['followers'];
+                               }
                        } elseif (!in_array($receiver['url'], $exclude)) {
                                $permissions[$type[$receiver['type']]][] = $receiver['url'];
                        }
@@ -573,13 +576,16 @@ class Transmitter
                $always_bcc = false;
                $is_group   = false;
                $follower   = '';
+               $exclusive  = false;
+               $mention    = false;
+               $audience   = [];
 
                // Check if we should always deliver our stuff via BCC
                if (!empty($item['uid'])) {
                        $owner = User::getOwnerDataById($item['uid']);
                        if (!empty($owner)) {
                                $always_bcc = $owner['hide-friends'];
-                               $is_group   = ($owner['account-type'] == User::ACCOUNT_TYPE_COMMUNITY) && $owner['manually-approve'];
+                               $is_group   = ($owner['account-type'] == User::ACCOUNT_TYPE_COMMUNITY);
 
                                $profile  = APContact::getByURL($owner['url'], false);
                                $follower = $profile['followers'] ?? '';
@@ -598,40 +604,43 @@ class Transmitter
                        $is_group_thread = false;
                }
 
-               $exclusive = false;
-               $mention   = false;
-
-               $parent_tags = Tag::getByURIId($item['parent-uri-id'], [Tag::AUDIENCE, Tag::MENTION]);
-               if (!empty($parent_tags)) {
-                       $is_group_thread = false;
-                       foreach ($parent_tags as $tag) {
-                               if ($tag['type'] != Tag::AUDIENCE) {
-                                       continue;
-                               }
-                               $profile = APContact::getByURL($tag['url'], false);
-                               if (!empty($profile) && ($profile['type'] == 'Group')) {
-                                       $is_group_thread = true;
-                               }
-                       }
-                       if ($is_group_thread) {
+               if (!$is_group) {
+                       $parent_tags = Tag::getByURIId($item['parent-uri-id'], [Tag::AUDIENCE, Tag::MENTION]);
+                       if (!empty($parent_tags)) {
+                               $is_group_thread = false;
                                foreach ($parent_tags as $tag) {
-                                       if (($tag['type'] == Tag::MENTION) && ($tag['url'] == $profile['url'])) {
-                                               $mention = false;
+                                       if ($tag['type'] != Tag::AUDIENCE) {
+                                               continue;
+                                       }
+                                       $profile = APContact::getByURL($tag['url'], false);
+                                       if (!empty($profile) && ($profile['type'] == 'Group')) {
+                                               $audience[] = $tag['url'];
+                                               $is_group_thread = true;
                                        }
                                }
-                               $exclusive = !$mention;
-                       }
-               } elseif ($is_group_thread) {
-                       foreach (Tag::getByURIId($item['parent-uri-id'], [Tag::MENTION, Tag::EXCLUSIVE_MENTION]) as $term) {
-                               $profile = APContact::getByURL($term['url'], false);
-                               if (!empty($profile) && ($profile['type'] == 'Group')) {
-                                       if ($term['type'] == Tag::EXCLUSIVE_MENTION) {
-                                               $exclusive = true;
-                                       } elseif ($term['type'] == Tag::MENTION) {
-                                               $mention = true;
+                               if ($is_group_thread) {
+                                       foreach ($parent_tags as $tag) {
+                                               if (($tag['type'] == Tag::MENTION) && in_array($tag['url'], $audience)) {
+                                                       $mention = true;
+                                               }
+                                       }
+                                       $exclusive = !$mention;
+                               }
+                       } elseif ($is_group_thread) {
+                               foreach (Tag::getByURIId($item['parent-uri-id'], [Tag::MENTION, Tag::EXCLUSIVE_MENTION]) as $term) {
+                                       $profile = APContact::getByURL($term['url'], false);
+                                       if (!empty($profile) && ($profile['type'] == 'Group')) {
+                                               if ($term['type'] == Tag::EXCLUSIVE_MENTION) {
+                                                       $audience[] = $term['url'];
+                                                       $exclusive  = true;
+                                               } elseif ($term['type'] == Tag::MENTION) {
+                                                       $mention = true;
+                                               }
                                        }
                                }
                        }
+               } else {
+                       $audience[] = $owner['url'];
                }
 
                if (self::isAnnounce($item) || self::isAPPost($last_id)) {
@@ -642,7 +651,7 @@ class Transmitter
                        $networks = [Protocol::ACTIVITYPUB, Protocol::OSTATUS];
                }
 
-               $data = ['to' => [], 'cc' => [], 'bcc' => [] , 'audience' => []];
+               $data = ['to' => [], 'cc' => [], 'bcc' => [] , 'audience' => $audience];
 
                if ($item['gravity'] == Item::GRAVITY_PARENT) {
                        $actor_profile = APContact::getByURL($item['owner-link']);
@@ -650,8 +659,7 @@ class Transmitter
                        $actor_profile = APContact::getByURL($item['author-link']);
                }
 
-
-               $terms = Tag::getByURIId($item['uri-id'], [Tag::MENTION, Tag::IMPLICIT_MENTION, Tag::EXCLUSIVE_MENTION]);
+               $terms = Tag::getByURIId($item['uri-id'], [Tag::MENTION, Tag::IMPLICIT_MENTION, Tag::EXCLUSIVE_MENTION, Tag::AUDIENCE]);
 
                if ($item['private'] != Item::PRIVATE) {
                        // Directly mention the original author upon a quoted reshare.
@@ -663,8 +671,8 @@ class Transmitter
                                $data['cc'][] = $announce['actor']['url'];
                        }
 
-                       if (!$is_group_thread) {
-                               $data = array_merge($data, self::fetchPermissionBlockFromThreadParent($item));
+                       if (!$exclusive) {
+                               $data = array_merge_recursive($data, self::fetchPermissionBlockFromThreadParent($item, $is_group_thread));
                        }
 
                        // Check if the item is completely public or unlisted
@@ -677,6 +685,9 @@ class Transmitter
                        foreach ($terms as $term) {
                                $profile = APContact::getByURL($term['url'], false);
                                if (!empty($profile)) {
+                                       if (($term['type'] == Tag::AUDIENCE) && ($profile['type'] == 'Group')) {
+                                               $data['audience'][] = $profile['url'];
+                                       }
                                        if ($term['type'] == Tag::EXCLUSIVE_MENTION) {
                                                $exclusive = true;
                                                if (!empty($profile['followers']) && ($profile['type'] == 'Group')) {
@@ -705,6 +716,9 @@ class Transmitter
 
                                        $profile = APContact::getByURL($term['url'], false);
                                        if (!empty($profile)) {
+                                               if (($term['type'] == Tag::AUDIENCE) && ($profile['type'] == 'Group')) {
+                                                       $data['audience'][] = $profile['url'];
+                                               }
                                                if ($term['type'] == Tag::EXCLUSIVE_MENTION) {
                                                        $exclusive = true;
                                                        if (!empty($profile['followers']) && ($profile['type'] == 'Group')) {
@@ -748,7 +762,7 @@ class Transmitter
                        }
                }
 
-               if (!empty($item['parent']) && (!$is_group_thread || ($item['private'] == Item::PRIVATE))) {
+               if (!empty($item['parent']) && (!$exclusive || ($item['private'] == Item::PRIVATE))) {
                        if ($item['private'] == Item::PRIVATE) {
                                $condition = ['parent' => $item['parent'], 'uri-id' => $item['thr-parent-id']];
                        } else {
@@ -815,10 +829,6 @@ class Transmitter
                        unset($data['bcc'][$key]);
                }
 
-               if (($key = array_search($item['author-link'], $data['audience'])) !== false) {
-                       unset($data['audience'][$key]);
-               }
-
                foreach ($data['to'] as $to) {
                        if (($key = array_search($to, $data['cc'])) !== false) {
                                unset($data['cc'][$key]);
@@ -1009,7 +1019,7 @@ class Transmitter
                                }
 
                                if ($item_profile && ($receiver == $item_profile['followers']) && ($uid == $profile_uid)) {
-                                       $inboxes = array_merge($inboxes, self::fetchTargetInboxesforUser($uid, $personal, self::isAPPost($last_id)));
+                                       $inboxes = array_merge_recursive($inboxes, self::fetchTargetInboxesforUser($uid, $personal, self::isAPPost($last_id)));
                                } else {
                                        $profile = APContact::getByURL($receiver, false);
                                        if (!empty($profile)) {
@@ -1195,14 +1205,16 @@ class Transmitter
        /**
         * Creates the activity or fetches it from the cache
         *
-        * @param integer $item_id Item id
-        * @param boolean $force Force new cache entry
+        * @param integer $item_id           Item id
+        * @param boolean $force             Force new cache entry
+        * @param boolean $object_mode       true = Create the object, false = create the activity with the object
+        * @param boolean $announce_activity true = the announced object is the activity, false = we announce the object link
         * @return array|false activity or false on failure
         * @throws \Exception
         */
-       public static function createCachedActivityFromItem(int $item_id, bool $force = false, bool $object_mode = false)
+       public static function createCachedActivityFromItem(int $item_id, bool $force = false, bool $object_mode = false, $announce_activity = false)
        {
-               $cachekey = 'APDelivery:createActivity:' . $item_id . ':' . (int)$object_mode;
+               $cachekey = 'APDelivery:createActivity:' . $item_id . ':' . (int)$object_mode . ':' . (int)$announce_activity;
 
                if (!$force) {
                        $data = DI::cache()->get($cachekey);
@@ -1211,7 +1223,7 @@ class Transmitter
                        }
                }
 
-               $data = self::createActivityFromItem($item_id, $object_mode);
+               $data = self::createActivityFromItem($item_id, $object_mode, false, $announce_activity);
 
                DI::cache()->set($cachekey, $data, Duration::QUARTER_HOUR);
                return $data;
@@ -1221,12 +1233,13 @@ class Transmitter
         * Creates an activity array for a given item id
         *
         * @param integer $item_id
-        * @param boolean $object_mode Is the activity item is used inside another object?
-        * @param boolean $api_mode    "true" if used for the API
+        * @param boolean $object_mode       true = Create the object, false = create the activity with the object
+        * @param boolean $api_mode          true = used for the API
+        * @param boolean $announce_activity true = the announced object is the activity, false = we announce the object link
         * @return false|array
         * @throws \Exception
         */
-       public static function createActivityFromItem(int $item_id, bool $object_mode = false, $api_mode = false)
+       public static function createActivityFromItem(int $item_id, bool $object_mode = false, $api_mode = false, $announce_activity = false)
        {
                $condition = ['id' => $item_id];
                if (!$api_mode) {
@@ -1237,7 +1250,7 @@ class Transmitter
                if (!DBA::isResult($item)) {
                        return false;
                }
-               return self::createActivityFromArray($item, $object_mode, $api_mode);
+               return self::createActivityFromArray($item, $object_mode, $api_mode, $announce_activity);
        }
 
        /**
@@ -1245,12 +1258,13 @@ class Transmitter
         *
         * @param integer $uri_id
         * @param integer $uid
-        * @param boolean $object_mode Is the activity item is used inside another object?
-        * @param boolean $api_mode    "true" if used for the API
+        * @param boolean $object_mode       true = Create the object, false = create the activity with the object
+        * @param boolean $api_mode          true = used for the API
+        * @param boolean $announce_activity true = the announced object is the activity, false = we announce the object link
         * @return false|array
         * @throws \Exception
         */
-       public static function createActivityFromUriId(int $uri_id, int $uid, bool $object_mode = false, $api_mode = false)
+       public static function createActivityFromUriId(int $uri_id, int $uid, bool $object_mode = false, $api_mode = false, $announce_activity = false)
        {
                $condition = ['uri-id' => $uri_id, 'uid' => [0, $uid]];
                if (!$api_mode) {
@@ -1262,19 +1276,20 @@ class Transmitter
                        return false;
                }
 
-               return self::createActivityFromArray($item, $object_mode, $api_mode);
+               return self::createActivityFromArray($item, $object_mode, $api_mode, $announce_activity);
        }
 
        /**
         * Creates an activity array for a given item id
         *
         * @param integer $item_id
-        * @param boolean $object_mode Is the activity item is used inside another object?
-        * @param boolean $api_mode    "true" if used for the API
+        * @param boolean $object_mode       true = Create the object, false = create the activity with the object
+        * @param boolean $api_mode          true = used for the API
+        * @param boolean $announce_activity true = the announced object is the activity, false = we announce the object link
         * @return false|array
         * @throws \Exception
         */
-       private static function createActivityFromArray(array $item, bool $object_mode = false, $api_mode = false)
+       private static function createActivityFromArray(array $item, bool $object_mode = false, $api_mode = false, $announce_activity = false)
        {
                if (!$api_mode && !$item['deleted'] && $item['network'] == Protocol::ACTIVITYPUB) {
                        $data = Post\Activity::getByURIId($item['uri-id']);
@@ -1344,7 +1359,13 @@ class Transmitter
                        $data = self::createAddTag($item, $data);
                } elseif ($data['type'] == 'Announce') {
                        if ($item['verb'] == ACTIVITY::ANNOUNCE) {
-                               $data['object'] = $item['thr-parent'];
+                               if ($announce_activity) {
+                                       $anounced_item = Post::selectFirst(['uid'], ['uri-id' => $item['thr-parent-id'], 'origin' => true]);
+                                       $data['object'] = self::createActivityFromUriId($item['thr-parent-id'], $anounced_item['uid'] ?? 0);
+                                       unset($data['object']['@context']);
+                               } else {
+                                       $data['object'] = $item['thr-parent'];
+                               }
                        } else {
                                $data = self::createAnnounce($item, $data, $api_mode);
                        }
@@ -1731,7 +1752,7 @@ class Transmitter
 
                        $body = BBCode::setMentionsToNicknames($body);
 
-                       if (!empty($item['quote-uri-id'])) {
+                       if (!empty($item['quote-uri-id']) && ($item['quote-uri-id'] != $item['uri-id'])) {
                                if (Post::exists(['uri-id' => $item['quote-uri-id'], 'network' => [Protocol::ACTIVITYPUB, Protocol::DFRN]])) {
                                        $real_quote = true;
                                        $data['quoteUrl'] = $item['quote-uri'];
@@ -1751,7 +1772,7 @@ class Transmitter
                if (!empty($language)) {
                        $richbody = BBCode::setMentionsToNicknames($item['body'] ?? '');
                        $richbody = Post\Media::removeFromEndOfBody($richbody);
-                       if (!empty($item['quote-uri-id'])) {
+                       if (!empty($item['quote-uri-id']) && ($item['quote-uri-id'] != $item['uri-id'])) {
                                if ($real_quote) {
                                        $richbody = DI::contentItem()->addShareLink($richbody, $item['quote-uri-id']);
                                } else {
@@ -1763,7 +1784,7 @@ class Transmitter
                        $data['contentMap'][$language] = BBCode::convertForUriId($item['uri-id'], $richbody, BBCode::EXTERNAL);
                }
 
-               if (!empty($item['quote-uri-id'])) {
+               if (!empty($item['quote-uri-id']) && ($item['quote-uri-id'] != $item['uri-id'])) {
                        $source = DI::contentItem()->addSharedPost($item, $item['body']);
                } else {
                        $source = $item['body'];