]> git.mxchange.org Git - friendica.git/blobdiff - src/Protocol/ActivityPub/Transmitter.php
Merge pull request #10230 from annando/oauth-login
[friendica.git] / src / Protocol / ActivityPub / Transmitter.php
index cf0b0170d700c611038723f1b2e097c6a56a0aa2..9773a5a48bbd1aa43ee7b511d2aefe6d263e1fbf 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
- * @copyright Copyright (C) 2020, Friendica
+ * @copyright Copyright (C) 2010-2021, the Friendica project
  *
  * @license GNU AGPL version 3 or any later version
  *
@@ -33,6 +33,7 @@ use Friendica\DI;
 use Friendica\Model\APContact;
 use Friendica\Model\Contact;
 use Friendica\Model\Conversation;
+use Friendica\Model\GServer;
 use Friendica\Model\Item;
 use Friendica\Model\ItemURI;
 use Friendica\Model\Profile;
@@ -51,9 +52,6 @@ use Friendica\Util\Map;
 use Friendica\Util\Network;
 use Friendica\Util\XML;
 
-require_once 'include/api.php';
-require_once 'mod/share.php';
-
 /**
  * ActivityPub Transmitter Protocol class
  *
@@ -71,8 +69,8 @@ class Transmitter
        public static function addRelayServerInboxes(array $inboxes = [])
        {
                $contacts = DBA::select('apcontact', ['inbox'],
-                       ["`type` = ? AND `url` IN (SELECT `url` FROM `contact` WHERE `uid` = ? AND `rel` IN (?, ?))",
-                               'Application', 0, Contact::FOLLOWER, Contact::FRIEND]);
+                       ["`type` = ? AND `url` IN (SELECT `url` FROM `contact` WHERE `uid` = ? AND `rel` = ?)",
+                               'Application', 0, Contact::FRIEND]);
                while ($contact = DBA::fetch($contacts)) {
                        $inboxes[$contact['inbox']] = $contact['inbox'];
                }
@@ -89,7 +87,7 @@ class Transmitter
         */
        public static function addRelayServerInboxesForItem(int $item_id, array $inboxes = [])
        {
-               $item = Item::selectFirst(['uid'], ['id' => $item_id]);
+               $item = Post::selectFirst(['uid'], ['id' => $item_id]);
                if (empty($item)) {
                        return $inboxes;
                }
@@ -122,8 +120,7 @@ class Transmitter
                $activity_id = ActivityPub\Transmitter::activityIDFromContact($contact['id']);
                $success = ActivityPub\Transmitter::sendActivity('Follow', $url, 0, $activity_id);
                if ($success) {
-                       $rel = $contact['rel'] == Contact::SHARING ? Contact::FRIEND : Contact::FOLLOWER;
-                       DBA::update('contact', ['rel' => $rel], ['id' => $contact['id']]);
+                       DBA::update('contact', ['rel' => Contact::FRIEND], ['id' => $contact['id']]);
                }
 
                return $success;
@@ -145,8 +142,7 @@ class Transmitter
 
                $success = self::sendContactUndo($url, $contact['id'], 0);
                if ($success || $force) {
-                       $rel = $contact['rel'] == Contact::FRIEND ? Contact::SHARING : Contact::NOTHING;
-                       DBA::update('contact', ['rel' => $rel], ['id' => $contact['id']]);
+                       DBA::update('contact', ['rel' => Contact::NOTHING], ['id' => $contact['id']]);
                }
 
                return $success;
@@ -262,9 +258,9 @@ class Transmitter
                $condition = array_merge($condition,
                        ['author-id' => $public_contact,
                        'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT],
-                       'deleted' => false, 'visible' => true, 'moderated' => false]);
+                       'deleted' => false, 'visible' => true]);
 
-               $count = DBA::count('item', $condition);
+               $count = Post::count($condition);
 
                $data = ['@context' => ActivityPub::CONTEXT];
                $data['id'] = DI::baseUrl() . '/outbox/' . $owner['nickname'];
@@ -279,8 +275,8 @@ class Transmitter
 
                        $condition['parent-network'] = Protocol::NATIVE_SUPPORT;
 
-                       $items = Item::select(['id'], $condition, ['limit' => [($page - 1) * 20, 20], 'order' => ['created' => true]]);
-                       while ($item = Item::fetch($items)) {
+                       $items = Post::select(['id'], $condition, ['limit' => [($page - 1) * 20, 20], 'order' => ['created' => true]]);
+                       while ($item = Post::fetch($items)) {
                                $activity = self::createActivityFromItem($item['id'], true);
                                $activity['type'] = $activity['type'] == 'Update' ? 'Create' : $activity['type'];
 
@@ -289,6 +285,7 @@ class Transmitter
                                        $list[] = $activity['object'];
                                }
                        }
+                       DBA::close($items);
 
                        if (!empty($list)) {
                                $data['next'] = DI::baseUrl() . '/outbox/' . $owner['nickname'] . '?page=' . ($page + 1);
@@ -485,7 +482,7 @@ class Transmitter
                        return false;
                }
 
-               return Item::exists(['id' => $item_id, 'network' => Protocol::ACTIVITYPUB]);
+               return Post::exists(['id' => $item_id, 'network' => Protocol::ACTIVITYPUB]);
        }
 
        /**
@@ -569,8 +566,8 @@ class Transmitter
                        foreach ($terms as $term) {
                                $cid = Contact::getIdForURL($term['url'], $item['uid']);
                                if (!empty($cid) && in_array($cid, $receiver_list)) {
-                                       $contact = DBA::selectFirst('contact', ['url', 'network', 'protocol'], ['id' => $cid]);
-                                       if (!DBA::isResult($contact) || (!in_array($contact['network'], $networks) && ($contact['protocol'] != Protocol::ACTIVITYPUB))) {
+                                       $contact = DBA::selectFirst('contact', ['url', 'network', 'protocol', 'gsid'], ['id' => $cid, 'network' => Protocol::FEDERATED]);
+                                       if (!DBA::isResult($contact) || !self::isAPContact($contact, $networks)) {
                                                continue;
                                        }
 
@@ -581,8 +578,8 @@ class Transmitter
                        }
 
                        foreach ($receiver_list as $receiver) {
-                               $contact = DBA::selectFirst('contact', ['url', 'hidden', 'network', 'protocol'], ['id' => $receiver]);
-                               if (!DBA::isResult($contact) || (!in_array($contact['network'], $networks) && ($contact['protocol'] != Protocol::ACTIVITYPUB))) {
+                               $contact = DBA::selectFirst('contact', ['url', 'hidden', 'network', 'protocol', 'gsid'], ['id' => $receiver, 'network' => Protocol::FEDERATED]);
+                               if (!DBA::isResult($contact) || !self::isAPContact($contact, $networks)) {
                                        continue;
                                }
 
@@ -597,8 +594,8 @@ class Transmitter
                }
 
                if (!empty($item['parent'])) {
-                       $parents = Item::select(['id', 'author-link', 'owner-link', 'gravity', 'uri'], ['parent' => $item['parent']]);
-                       while ($parent = Item::fetch($parents)) {
+                       $parents = Post::select(['id', 'author-link', 'owner-link', 'gravity', 'uri'], ['parent' => $item['parent']]);
+                       while ($parent = Post::fetch($parents)) {
                                if ($parent['gravity'] == GRAVITY_PARENT) {
                                        $profile = APContact::getByURL($parent['owner-link'], false);
                                        if (!empty($profile)) {
@@ -694,6 +691,23 @@ class Transmitter
                return DBA::exists('inbox-status', ['url' => $url, 'archive' => true]);
        }
 
+       /**
+        * Check if a given contact should be delivered via AP
+        *
+        * @param array $contact 
+        * @param array $networks 
+        * @return bool 
+        * @throws Exception 
+        */
+       private static function isAPContact(array $contact, array $networks)
+       {
+               if (in_array($contact['network'], $networks) || ($contact['protocol'] == Protocol::ACTIVITYPUB)) {
+                       return true;
+               }
+
+               return GServer::getProtocol($contact['gsid'] ?? 0) == Post\DeliveryData::ACTIVITYPUB;
+       }
+
        /**
         * Fetches a list of inboxes of followers of a given user
         *
@@ -726,19 +740,19 @@ class Transmitter
                        $networks = [Protocol::ACTIVITYPUB, Protocol::OSTATUS];
                }
 
-               $condition = ['uid' => $uid, 'archive' => false, 'pending' => false, 'blocked' => false];
+               $condition = ['uid' => $uid, 'archive' => false, 'pending' => false, 'blocked' => false, 'network' => Protocol::FEDERATED];
 
                if (!empty($uid)) {
                        $condition['rel'] = [Contact::FOLLOWER, Contact::FRIEND];
                }
 
-               $contacts = DBA::select('contact', ['id', 'url', 'network', 'protocol'], $condition);
+               $contacts = DBA::select('contact', ['id', 'url', 'network', 'protocol', 'gsid'], $condition);
                while ($contact = DBA::fetch($contacts)) {
                        if (Contact::isLocal($contact['url'])) {
                                continue;
                        }
 
-                       if (!in_array($contact['network'], $networks) && ($contact['protocol'] != Protocol::ACTIVITYPUB)) {
+                       if (!self::isAPContact($contact, $networks)) {
                                continue;
                        }
 
@@ -1031,19 +1045,19 @@ class Transmitter
        public static function createActivityFromItem(int $item_id, bool $object_mode = false)
        {
                Logger::info('Fetching activity', ['item' => $item_id]);
-               $item = Item::selectFirst([], ['id' => $item_id, 'parent-network' => Protocol::NATIVE_SUPPORT]);
+               $item = Post::selectFirst(Item::DELIVER_FIELDLIST, ['id' => $item_id, 'parent-network' => Protocol::NATIVE_SUPPORT]);
                if (!DBA::isResult($item)) {
                        return false;
                }
 
                // In case of a forum post ensure to return the original post if author and forum are on the same machine
-               if (!empty($item['forum_mode'])) {
+               if (($item['gravity'] == GRAVITY_PARENT) && !empty($item['forum_mode'])) {
                        $author = Contact::getById($item['author-id'], ['nurl']);
                        if (!empty($author['nurl'])) {
                                $self = Contact::selectFirst(['uid'], ['nurl' => $author['nurl'], 'self' => true]);
                                if (!empty($self['uid'])) {
-                                       $forum_item = Item::selectFirst([], ['uri-id' => $item['uri-id'], 'uid' => $self['uid']]);
-                                       if (DBA::isResult($item)) {
+                                       $forum_item = Post::selectFirst(Item::DELIVER_FIELDLIST, ['uri-id' => $item['uri-id'], 'uid' => $self['uid']]);
+                                       if (DBA::isResult($forum_item)) {
                                                $item = $forum_item; 
                                        }
                                }
@@ -1055,25 +1069,27 @@ class Transmitter
                        return false;
                }
 
-               $condition = ['item-uri' => $item['uri'], 'protocol' => Conversation::PARCEL_ACTIVITYPUB];
-               $conversation = DBA::selectFirst('conversation', ['source'], $condition);
-               if (!$item['origin'] && DBA::isResult($conversation)) {
-                       $data = json_decode($conversation['source'], true);
-                       if (!empty($data['type'])) {
-                               if (in_array($data['type'], ['Create', 'Update'])) {
-                                       if ($object_mode) {
-                                               unset($data['@context']);
-                                               unset($data['signature']);
-                                       }
-                                       Logger::info('Return stored conversation', ['item' => $item_id]);
-                                       return $data;
-                               } elseif (in_array('as:' . $data['type'], Receiver::CONTENT_TYPES)) {
-                                       if (!empty($data['@context'])) {
-                                               $context = $data['@context'];
-                                               unset($data['@context']);
+               if (!$item['deleted']) {
+                       $condition = ['item-uri' => $item['uri'], 'protocol' => Conversation::PARCEL_ACTIVITYPUB];
+                       $conversation = DBA::selectFirst('conversation', ['source'], $condition);
+                       if (!$item['origin'] && DBA::isResult($conversation)) {
+                               $data = json_decode($conversation['source'], true);
+                               if (!empty($data['type'])) {
+                                       if (in_array($data['type'], ['Create', 'Update'])) {
+                                               if ($object_mode) {
+                                                       unset($data['@context']);
+                                                       unset($data['signature']);
+                                               }
+                                               Logger::info('Return stored conversation', ['item' => $item_id]);
+                                               return $data;
+                                       } elseif (in_array('as:' . $data['type'], Receiver::CONTENT_TYPES)) {
+                                               if (!empty($data['@context'])) {
+                                                       $context = $data['@context'];
+                                                       unset($data['@context']);
+                                               }
+                                               unset($data['actor']);
+                                               $object = $data;
                                        }
-                                       unset($data['actor']);
-                                       $object = $data;
                                }
                        }
                }
@@ -1092,7 +1108,9 @@ class Transmitter
                        $data = [];
                }
 
-               if (($item['gravity'] == GRAVITY_ACTIVITY) && ($type != 'Undo')) {
+               if ($type == 'Delete') {
+                       $data['id'] = Item::newURI($item['uid'], $item['guid']) . '/' . $type;;
+               } elseif (($item['gravity'] == GRAVITY_ACTIVITY) && ($type != 'Undo')) {
                        $data['id'] = $item['uri'];
                } else {
                        $data['id'] = $item['uri'] . '/' . $type;
@@ -1242,53 +1260,60 @@ class Transmitter
        {
                $attachments = [];
 
-               // Currently deactivated, since it creates side effects on Mastodon and Pleroma.
-               // It will be reactivated, once this cleared.
-               /*
-               $attach_data = BBCode::getAttachmentData($item['body']);
-               if (!empty($attach_data['url'])) {
-                       $attachment = ['type' => 'Page',
-                               'mediaType' => 'text/html',
-                               'url' => $attach_data['url']];
-
-                       if (!empty($attach_data['title'])) {
-                               $attachment['name'] = $attach_data['title'];
-                       }
-
-                       if (!empty($attach_data['description'])) {
-                               $attachment['summary'] = $attach_data['description'];
+               $uriids = [$item['uri-id']];
+               $shared = BBCode::fetchShareAttributes($item['body']);
+               if (!empty($shared['guid'])) {
+                       $shared_item = Post::selectFirst(['uri-id'], ['guid' => $shared['guid']]);
+                       if (!empty($shared_item['uri-id'])) {
+                               $uriids[] = $shared_item['uri-id'];
                        }
+               }
 
-                       if (!empty($attach_data['image'])) {
-                               $imgdata = Images::getInfoFromURLCached($attach_data['image']);
-                               if ($imgdata) {
-                                       $attachment['icon'] = ['type' => 'Image',
-                                               'mediaType' => $imgdata['mime'],
-                                               'width' => $imgdata[0],
-                                               'height' => $imgdata[1],
-                                               'url' => $attach_data['image']];
+               $urls = [];
+               foreach ($uriids as $uriid) {
+                       foreach (Post\Media::getByURIId($uriid, [Post\Media::DOCUMENT, Post\Media::TORRENT]) as $attachment) {
+                               if (in_array($attachment['url'], $urls)) {
+                                       continue;
                                }
-                       }
+                               $urls[] = $attachment['url'];
 
-                       $attachments[] = $attachment;
-               }
-               */
-               foreach (Post\Media::getByURIId($item['uri-id'], [Post\Media::DOCUMENT, Post\Media::TORRENT, Post\Media::UNKNOWN]) as $attachment) {
-                       $attachments[] = ['type' => 'Document',
-                               'mediaType' => $attachment['mimetype'],
-                               'url' => $attachment['url'],
-                               'name' => $attachment['description']];
+                               $attachments[] = ['type' => 'Document',
+                                       'mediaType' => $attachment['mimetype'],
+                                       'url' => $attachment['url'],
+                                       'name' => $attachment['description']];
+                       }
                }
 
                if ($type != 'Note') {
                        return $attachments;
                }
 
-               foreach (Post\Media::getByURIId($item['uri-id'], [Post\Media::AUDIO, Post\Media::IMAGE, Post\Media::VIDEO]) as $attachment) {
-                       $attachments[] = ['type' => 'Document',
-                               'mediaType' => $attachment['mimetype'],
-                               'url' => $attachment['url'],
-                               'name' => $attachment['description']];
+               foreach ($uriids as $uriid) {
+                       foreach (Post\Media::getByURIId($uriid, [Post\Media::AUDIO, Post\Media::IMAGE, Post\Media::VIDEO]) as $attachment) {
+                               if (in_array($attachment['url'], $urls)) {
+                                       continue;
+                               }
+                               $urls[] = $attachment['url'];
+
+                               $attachments[] = ['type' => 'Document',
+                                       'mediaType' => $attachment['mimetype'],
+                                       'url' => $attachment['url'],
+                                       'name' => $attachment['description']];
+                       }
+                       // Currently deactivated, since it creates side effects on Mastodon and Pleroma.
+                       // It will be activated, once this cleared.
+                       /*
+                       foreach (Post\Media::getByURIId($uriid, [Post\Media::HTML]) as $attachment) {
+                               if (in_array($attachment['url'], $urls)) {
+                                       continue;
+                               }
+                               $urls[] = $attachment['url'];
+
+                               $attachments[] = ['type' => 'Page',
+                                       'mediaType' => $attachment['mimetype'],
+                                       'url' => $attachment['url'],
+                                       'name' => $attachment['description']];
+                       }*/
                }
 
                return $attachments;
@@ -1312,7 +1337,28 @@ class Transmitter
                        return $match[0];
                }
 
-               return '[url=' . ($data['alias'] ?: $data['url']) . ']@' . $data['nick'] . '[/url]';
+               return '[url=' . $data['url'] . ']@' . $data['nick'] . '[/url]';
+       }
+
+       /**
+        * Callback function to replace a Friendica style mention in a mention for a summary
+        *
+        * @param array $match Matching values for the callback
+        * @return string Replaced mention
+        * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+        */
+       private static function mentionAddrCallback($match)
+       {
+               if (empty($match[1])) {
+                       return '';
+               }
+
+               $data = Contact::getByURL($match[1], false, ['addr']);
+               if (empty($data['addr'])) {
+                       return $match[0];
+               }
+
+               return '@' . $data['addr'];
        }
 
        /**
@@ -1390,7 +1436,7 @@ class Transmitter
         * @return array with the event data
         * @throws \Friendica\Network\HTTPException\InternalServerErrorException
         */
-       public static function createEvent($item)
+       private static function createEvent($item)
        {
                $event = [];
                $event['name'] = $item['event-summary'];
@@ -1406,6 +1452,8 @@ class Transmitter
                        $event['location'] = self::createLocation($item);
                }
 
+               $event['dfrn:adjust'] = (bool)$item['event-adjust'];
+
                return $event;
        }
 
@@ -1475,7 +1523,9 @@ class Transmitter
                if ($type == 'Note') {
                        $body = $item['raw-body'] ?? self::removePictures($body);
                } elseif (($type == 'Article') && empty($data['summary'])) {
-                       $data['summary'] = BBCode::toPlaintext(Plaintext::shorten(self::removePictures($body), 1000));
+                       $regexp = "/[@!]\[url\=([^\[\]]*)\].*?\[\/url\]/ism";
+                       $summary = preg_replace_callback($regexp, ['self', 'mentionAddrCallback'], $body);
+                       $data['summary'] = BBCode::toPlaintext(Plaintext::shorten(self::removePictures($summary), 1000));
                }
 
                if (empty($item['uid']) || !Feature::isEnabled($item['uid'], 'explicit_mentions')) {
@@ -1500,7 +1550,7 @@ class Transmitter
                        $richbody = preg_replace_callback($regexp, ['self', 'mentionCallback'], $item['body']);
                        $richbody = BBCode::removeAttachment($richbody);
 
-                       $data['contentMap'][$language] = BBCode::convert($richbody, false);
+                       $data['contentMap'][$language] = BBCode::convert($richbody, false, BBCode::EXTERNAL);
                }
 
                $data['source'] = ['content' => $item['body'], 'mediaType' => "text/bbcode"];
@@ -1631,7 +1681,7 @@ class Transmitter
                        return [];
                }
 
-               $reshared_item = Item::selectFirst([], ['guid' => $reshared['guid']]);
+               $reshared_item = Post::selectFirst(Item::DELIVER_FIELDLIST, ['guid' => $reshared['guid']]);
                if (!DBA::isResult($reshared_item)) {
                        return [];
                }
@@ -1890,7 +1940,7 @@ class Transmitter
 
                $condition = ['verb' => Activity::FOLLOW, 'uid' => 0, 'parent-uri' => $object,
                        'author-id' => Contact::getPublicIdByUserId($uid)];
-               if (Item::exists($condition)) {
+               if (Post::exists($condition)) {
                        Logger::log('Follow for ' . $object . ' for user ' . $uid . ' does already exist.', Logger::DEBUG);
                        return false;
                }