]> git.mxchange.org Git - friendica.git/blobdiff - src/Protocol/ActivityPub/Processor.php
Merge remote-tracking branch 'upstream/develop' into item-notification
[friendica.git] / src / Protocol / ActivityPub / Processor.php
index 8280ccfb78513d67372e2c53a024016b50a6eb42..ad1b9d8bdf10490a2f955f04b251dba834e8a3e6 100644 (file)
@@ -4,19 +4,21 @@
  */
 namespace Friendica\Protocol\ActivityPub;
 
-use Friendica\Database\DBA;
 use Friendica\Content\Text\BBCode;
 use Friendica\Content\Text\HTML;
 use Friendica\Core\Config;
 use Friendica\Core\Logger;
+use Friendica\Core\PConfig;
 use Friendica\Core\Protocol;
-use Friendica\Model\Contact;
+use Friendica\Database\DBA;
 use Friendica\Model\APContact;
-use Friendica\Model\Item;
+use Friendica\Model\Contact;
 use Friendica\Model\Event;
+use Friendica\Model\Item;
+use Friendica\Model\Mail;
 use Friendica\Model\Term;
 use Friendica\Model\User;
-use Friendica\Model\Mail;
+use Friendica\Protocol\Activity;
 use Friendica\Protocol\ActivityPub;
 use Friendica\Util\DateTimeFormat;
 use Friendica\Util\JsonLD;
@@ -74,7 +76,7 @@ class Processor
 
                $tag_text = '';
                foreach ($tags as $tag) {
-                       if (in_array(defaults($tag, 'type', ''), ['Mention', 'Hashtag'])) {
+                       if (in_array($tag['type'] ?? '', ['Mention', 'Hashtag'])) {
                                if (!empty($tag_text)) {
                                        $tag_text .= ',';
                                }
@@ -91,26 +93,29 @@ class Processor
        /**
         * Add attachment data to the item array
         *
-        * @param array   $attachments
+        * @param array   $activity
         * @param array   $item
-        * @param boolean $no_images
         *
         * @return array array
         */
-       private static function constructAttachList($attachments, $item, $no_images)
+       private static function constructAttachList($activity, $item)
        {
-               if (empty($attachments)) {
+               if (empty($activity['attachments'])) {
                        return $item;
                }
 
-               foreach ($attachments as $attach) {
+               foreach ($activity['attachments'] as $attach) {
                        $filetype = strtolower(substr($attach['mediaType'], 0, strpos($attach['mediaType'], '/')));
                        if ($filetype == 'image') {
-                               if ($no_images) {
+                               if (!empty($activity['source']) && strpos($activity['source'], $attach['url'])) {
                                        continue;
                                }
 
-                               $item['body'] .= "\n[img]" . $attach['url'] . '[/img]';
+                               if (empty($attach['name'])) {
+                                       $item['body'] .= "\n[img]" . $attach['url'] . '[/img]';
+                               } else {
+                                       $item['body'] .= "\n[img=" . $attach['url'] . ']' . $attach['name'] . '[/img]';
+                               }
                        } else {
                                if (!empty($item["attach"])) {
                                        $item["attach"] .= ',';
@@ -120,7 +125,7 @@ class Processor
                                if (!isset($attach['length'])) {
                                        $attach['length'] = "0";
                                }
-                               $item["attach"] .= '[attach]href="'.$attach['url'].'" length="'.$attach['length'].'" type="'.$attach['mediaType'].'" title="'.defaults($attach, 'name', '').'"[/attach]';
+                               $item["attach"] .= '[attach]href="'.$attach['url'].'" length="'.$attach['length'].'" type="'.$attach['mediaType'].'" title="'.($attach['name'] ?? '') .'"[/attach]';
                        }
                }
 
@@ -142,7 +147,7 @@ class Processor
                }
 
                $item['changed'] = DateTimeFormat::utcNow();
-               $item['edited'] = $activity['updated'];
+               $item['edited'] = DateTimeFormat::utc($activity['updated']);
 
                $item = self::processContent($activity, $item);
                if (empty($item)) {
@@ -162,15 +167,15 @@ class Processor
        public static function createItem($activity)
        {
                $item = [];
-               $item['verb'] = ACTIVITY_POST;
+               $item['verb'] = Activity::POST;
                $item['thr-parent'] = $activity['reply-to-id'];
 
                if ($activity['reply-to-id'] == $activity['id']) {
                        $item['gravity'] = GRAVITY_PARENT;
-                       $item['object-type'] = ACTIVITY_OBJ_NOTE;
+                       $item['object-type'] = Activity\ObjectType::NOTE;
                } else {
                        $item['gravity'] = GRAVITY_COMMENT;
-                       $item['object-type'] = ACTIVITY_OBJ_COMMENT;
+                       $item['object-type'] = Activity\ObjectType::COMMENT;
                }
 
                if (empty($activity['directmessage']) && ($activity['id'] != $activity['reply-to-id']) && !Item::exists(['uri' => $activity['reply-to-id']])) {
@@ -178,7 +183,7 @@ class Processor
                        self::fetchMissingActivity($activity['reply-to-id'], $activity);
                }
 
-               $item['diaspora_signed_text'] = defaults($activity, 'diaspora:comment', '');
+               $item['diaspora_signed_text'] = $activity['diaspora:comment'] ?? '';
 
                self::postItem($activity, $item);
        }
@@ -198,6 +203,43 @@ class Processor
                Item::delete(['uri' => $activity['object_id'], 'owner-id' => $owner]);
        }
 
+       /**
+        * Prepare the item array for an activity
+        *
+        * @param array $activity Activity array
+        * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+        * @throws \ImagickException
+        */
+       public static function addTag($activity)
+       {
+               if (empty($activity['object_content']) || empty($activity['object_id'])) {
+                       return;
+               }
+
+               foreach ($activity['receiver'] as $receiver) {
+                       $item = Item::selectFirst(['id', 'tag', 'origin', 'author-link'], ['uri' => $activity['target_id'], 'uid' => $receiver]);
+                       if (!DBA::isResult($item)) {
+                               // We don't fetch missing content for this purpose
+                               continue;
+                       }
+
+                       if (($item['author-link'] != $activity['actor']) && !$item['origin']) {
+                               Logger::info('Not origin, not from the author, skipping update', ['id' => $item['id'], 'author' => $item['author-link'], 'actor' => $activity['actor']]);
+                               continue;
+                       }
+
+                       // To-Do:
+                       // - Check if "blocktag" is set
+                       // - Check if actor is a contact
+
+                       if (!stristr($item['tag'], trim($activity['object_content']))) {
+                               $tag = $item['tag'] . (strlen($item['tag']) ? ',' : '') . '#[url=' . $activity['object_id'] . ']'. $activity['object_content'] . '[/url]';
+                               Item::update(['tag' => $tag], ['id' => $item['id']]);
+                               Logger::info('Tagged item', ['id' => $item['id'], 'tag' => $activity['object_content'], 'uri' => $activity['target_id'], 'actor' => $activity['actor']]);
+                       }
+               }
+       }
+
        /**
         * Prepare the item array for an activity
         *
@@ -212,9 +254,9 @@ class Processor
                $item['verb'] = $verb;
                $item['thr-parent'] = $activity['object_id'];
                $item['gravity'] = GRAVITY_ACTIVITY;
-               $item['object-type'] = ACTIVITY_OBJ_NOTE;
+               $item['object-type'] = Activity\ObjectType::NOTE;
 
-               $item['diaspora_signed_text'] = defaults($activity, 'diaspora:like', '');
+               $item['diaspora_signed_text'] = $activity['diaspora:like'] ?? '';
 
                self::postItem($activity, $item);
        }
@@ -333,20 +375,28 @@ class Processor
                $item['private'] = !in_array(0, $activity['receiver']);
                $item['author-link'] = $activity['author'];
                $item['author-id'] = Contact::getIdForURL($activity['author'], 0, true);
+               $item['owner-link'] = $activity['actor'];
+               $item['owner-id'] = Contact::getIdForURL($activity['actor'], 0, true);
 
-               if (empty($activity['thread-completion'])) {
-                       $item['owner-link'] = $activity['actor'];
-                       $item['owner-id'] = Contact::getIdForURL($activity['actor'], 0, true);
-               } else {
-                       Logger::info('Ignoring actor because of thread completion.');
+               $isForum = false;
+
+               if (!empty($activity['thread-completion'])) {
+                       // Store the original actor in the "causer" fields to enable the check for ignored or blocked contacts
+                       $item['causer-link'] = $item['owner-link'];
+                       $item['causer-id'] = $item['owner-id'];
+
+                       Logger::info('Ignoring actor because of thread completion.', ['actor' => $item['owner-link']]);
                        $item['owner-link'] = $item['author-link'];
                        $item['owner-id'] = $item['author-id'];
+               } else {
+                       $actor = APContact::getByURL($item['owner-link'], false);
+                       $isForum = ($actor['type'] == 'Group');
                }
 
                $item['uri'] = $activity['id'];
 
-               $item['created'] = $activity['published'];
-               $item['edited'] = $activity['updated'];
+               $item['created'] = DateTimeFormat::utc($activity['published']);
+               $item['edited'] = DateTimeFormat::utc($activity['updated']);
                $item['guid'] = $activity['diaspora:guid'];
 
                $item = self::processContent($activity, $item);
@@ -354,15 +404,20 @@ class Processor
                        return;
                }
 
-               $item['plink'] = defaults($activity, 'alternate-url', $item['uri']);
+               $item['plink'] = $activity['alternate-url'] ?? $item['uri'];
 
-               $item = self::constructAttachList($activity['attachments'], $item, !empty($activity['source']));
+               $item = self::constructAttachList($activity, $item);
 
                $stored = false;
 
                foreach ($activity['receiver'] as $receiver) {
                        $item['uid'] = $receiver;
-                       $item['contact-id'] = Contact::getIdForURL($activity['author'], $receiver, true);
+
+                       if ($isForum) {
+                               $item['contact-id'] = Contact::getIdForURL($activity['actor'], $receiver, true);
+                       } else {
+                               $item['contact-id'] = Contact::getIdForURL($activity['author'], $receiver, true);
+                       }
 
                        if (($receiver != 0) && empty($item['contact-id'])) {
                                $item['contact-id'] = Contact::getIdForURL($activity['author'], 0, true);
@@ -373,6 +428,21 @@ class Processor
                                continue;
                        }
 
+                       if (PConfig::get($receiver, 'system', 'accept_only_sharer', false) && ($receiver != 0) && ($item['gravity'] == GRAVITY_PARENT)) {
+                               $skip = !Contact::isSharingByURL($activity['author'], $receiver);
+
+                               if ($skip && (($activity['type'] == 'as:Announce') || $isForum)) {
+                                       $skip = !Contact::isSharingByURL($activity['actor'], $receiver);
+                               }
+
+                               if ($skip) {
+                                       Logger::info('Skipping post', ['uid' => $receiver, 'url' => $item['uri']]);
+                                       continue;
+                               }
+
+                               Logger::info('Accepting post', ['uid' => $receiver, 'url' => $item['uri']]);
+                       }
+
                        if ($activity['object_type'] == 'as:Event') {
                                self::createEvent($activity, $item);
                        }
@@ -405,8 +475,8 @@ class Processor
         *
         * @param array $activity Activity data
         * @param array $item     item array
+        * @return int|bool New mail table row id or false on error
         * @throws \Friendica\Network\HTTPException\InternalServerErrorException
-        * @throws \ImagickException
         */
        private static function postMail($activity, $item)
        {
@@ -460,44 +530,64 @@ class Processor
                }
                $msg['body'] = $item['body'];
 
-                Mail::insert($msg);
+               return Mail::insert($msg);
        }
 
        /**
         * Fetches missing posts
         *
-        * @param $url
-        * @param $child
+        * @param string $url message URL
+        * @param array $child activity array with the child of this message
+        * @return boolean success
         * @throws \Friendica\Network\HTTPException\InternalServerErrorException
         */
-       private static function fetchMissingActivity($url, $child)
+       public static function fetchMissingActivity($url, $child = [])
        {
-               if (Config::get('system', 'ostatus_full_threads')) {
-                       return;
+               if (!empty($child['receiver'])) {
+                       $uid = ActivityPub\Receiver::getFirstUserFromReceivers($child['receiver']);
+               } else {
+                       $uid = 0;
                }
 
-               $uid = ActivityPub\Receiver::getFirstUserFromReceivers($child['receiver']);
-
                $object = ActivityPub::fetchContent($url, $uid);
                if (empty($object)) {
                        Logger::log('Activity ' . $url . ' was not fetchable, aborting.');
-                       return;
+                       return false;
                }
 
                if (empty($object['id'])) {
                        Logger::log('Activity ' . $url . ' has got not id, aborting. ' . json_encode($object));
-                       return;
+                       return false;
+               }
+
+               if (!empty($child['author'])) {
+                       $actor = $child['author'];
+               } elseif (!empty($object['actor'])) {
+                       $actor = $object['actor'];
+               } elseif (!empty($object['attributedTo'])) {
+                       $actor = $object['attributedTo'];
+               } else {
+                       // Shouldn't happen
+                       $actor = '';
+               }
+
+               if (!empty($object['published'])) {
+                       $published = $object['published'];
+               } elseif (!empty($child['published'])) {
+                       $published = $child['published'];
+               } else {
+                       $published = DateTimeFormat::utcNow();
                }
 
                $activity = [];
                $activity['@context'] = $object['@context'];
                unset($object['@context']);
                $activity['id'] = $object['id'];
-               $activity['to'] = defaults($object, 'to', []);
-               $activity['cc'] = defaults($object, 'cc', []);
-               $activity['actor'] = $child['author'];
+               $activity['to'] = $object['to'] ?? [];
+               $activity['cc'] = $object['cc'] ?? [];
+               $activity['actor'] = $actor;
                $activity['object'] = $object;
-               $activity['published'] = defaults($object, 'published', $child['published']);
+               $activity['published'] = $published;
                $activity['type'] = 'Create';
 
                $ldactivity = JsonLD::compact($activity);
@@ -506,6 +596,8 @@ class Processor
 
                ActivityPub\Receiver::processActivity($ldactivity);
                Logger::log('Activity ' . $url . ' had been fetched and processed.');
+
+               return true;
        }
 
        /**
@@ -536,16 +628,13 @@ class Processor
                $item = ['author-id' => Contact::getIdForURL($activity['actor']),
                        'author-link' => $activity['actor']];
 
-               $note = Strings::escapeTags(trim(defaults($activity, 'content', '')));
+               $note = Strings::escapeTags(trim($activity['content'] ?? ''));
 
                // Ensure that the contact has got the right network type
                self::switchContact($item['author-id']);
 
                $result = Contact::addRelationship($owner, $contact, $item, false, $note);
-               if ($result === false) {
-                       ActivityPub\Transmitter::sendContactReject($item['author-link'], $item['author-id'], $owner['uid']);
-                       return;
-               }elseif ($result === true) {
+               if ($result === true) {
                        ActivityPub\Transmitter::sendContactAccept($item['author-link'], $item['author-id'], $owner['uid']);
                }
 
@@ -574,7 +663,7 @@ class Processor
                }
 
                Logger::log('Updating profile for ' . $activity['object_id'], Logger::DEBUG);
-               APContact::getByURL($activity['object_id'], true);
+               Contact::updateFromProbeByURL($activity['object_id'], true);
        }
 
        /**
@@ -804,7 +893,7 @@ class Processor
 
                // Extract one prepended mention at a time from the body
                while(preg_match('#^(@\[url=([^\]]+)].*?\[\/url]\s)(.*)#is', $body, $matches)) {
-                       if (!in_array($matches[2], $potential_mentions) ) {
+                       if (!in_array($matches[2], $potential_mentions)) {
                                $kept_mentions[] = $matches[1];
                        }