]> git.mxchange.org Git - friendica.git/blobdiff - src/Protocol/Diaspora.php
Merge pull request #8698 from MrPetovan/bug/8685-frio-compose-disable-asynchronous
[friendica.git] / src / Protocol / Diaspora.php
index 8bb407ebd8ddb3308af1804e1f0ee196c9b46f4d..c4e7d9fb87fcf5f16adfb456de50515c9876c458 100644 (file)
@@ -35,10 +35,10 @@ use Friendica\Model\Contact;
 use Friendica\Model\Conversation;
 use Friendica\Model\GContact;
 use Friendica\Model\Item;
-use Friendica\Model\ItemDeliveryData;
+use Friendica\Model\ItemURI;
 use Friendica\Model\Mail;
-use Friendica\Model\Profile;
-use Friendica\Model\Term;
+use Friendica\Model\Post;
+use Friendica\Model\Tag;
 use Friendica\Model\User;
 use Friendica\Network\Probe;
 use Friendica\Util\Crypto;
@@ -112,7 +112,7 @@ class Diaspora
 
                if (DI::config()->get("system", "relay_directly", false)) {
                        // We distribute our stuff based on the parent to ensure that the thread will be complete
-                       $parent = Item::selectFirst(['parent'], ['id' => $item_id]);
+                       $parent = Item::selectFirst(['uri-id'], ['id' => $item_id]);
                        if (!DBA::isResult($parent)) {
                                return;
                        }
@@ -122,14 +122,15 @@ class Diaspora
                        while ($server = DBA::fetch($servers)) {
                                $serverlist[$server['url']] = $server['url'];
                        }
+                       DBA::close($servers);
 
                        // All tags of the current post
-                       $condition = ['otype' => Term::OBJECT_TYPE_POST, 'type' => Term::HASHTAG, 'oid' => $parent['parent']];
-                       $tags = DBA::select('term', ['term'], $condition);
+                       $tags = DBA::select('tag-view', ['name'], ['uri-id' => $parent['uri-id'], 'type' => Tag::HASHTAG]);
                        $taglist = [];
                        while ($tag = DBA::fetch($tags)) {
-                               $taglist[] = $tag['term'];
+                               $taglist[] = $tag['name'];
                        }
+                       DBA::close($tags);
 
                        // All servers who wants content with this tag
                        $tagserverlist = [];
@@ -138,6 +139,7 @@ class Diaspora
                                while ($server = DBA::fetch($tagserver)) {
                                        $tagserverlist[] = $server['gserver-id'];
                                }
+                               DBA::close($tagserver);
                        }
 
                        // All adresses with the given id
@@ -146,6 +148,7 @@ class Diaspora
                                while ($server = DBA::fetch($servers)) {
                                        $serverlist[$server['url']] = $server['url'];
                                }
+                               DBA::close($servers);
                        }
                }
 
@@ -249,34 +252,29 @@ class Diaspora
         * One of the parameters is a contact array.
         * This is done to avoid duplicates.
         *
-        * @param integer $thread   The id of the thread
-        * @param array   $contacts The previously fetched contacts
+        * @param array $item     Item that is about to be delivered
+        * @param array $contacts The previously fetched contacts
         *
         * @return array of relay servers
         * @throws \Exception
         */
-       public static function participantsForThread($thread, array $contacts)
+       public static function participantsForThread(array $item, array $contacts)
        {
-               $r = DBA::p("SELECT `contact`.`batch`, `contact`.`id`, `contact`.`url`, `contact`.`name`, `contact`.`network`, `contact`.`protocol`,
-                               `fcontact`.`batch` AS `fbatch`, `fcontact`.`network` AS `fnetwork` FROM `participation`
-                               INNER JOIN `contact` ON `contact`.`id` = `participation`.`cid`
-                               INNER JOIN `fcontact` ON `fcontact`.`id` = `participation`.`fid`
-                               WHERE `participation`.`iid` = ? AND NOT `contact`.`archive`", $thread);
-
-               while ($contact = DBA::fetch($r)) {
-                       if (!empty($contact['fnetwork'])) {
-                               $contact['network'] = $contact['fnetwork'];
-                       }
-                       unset($contact['fnetwork']);
-
-                       if (empty($contact['protocol'])) {
-                               $contact['protocol'] = $contact['network'];
-                       }
-
-                       if (empty($contact['batch']) && !empty($contact['fbatch'])) {
-                               $contact['batch'] = $contact['fbatch'];
+               if (!in_array($item['private'], [Item::PUBLIC, Item::UNLISTED]) || in_array($item["verb"], [Activity::FOLLOW, Activity::TAG])) {
+                       Logger::info('Item is private or a participation request. It will not be relayed', ['guid' => $item['guid'], 'private' => $item['private'], 'verb' => $item['verb']]);
+                       return $contacts;
+               }
+
+               $items = Item::select(['author-id', 'author-link', 'parent-author-link', 'parent-guid', 'guid'],
+                       ['parent' => $item['parent'], 'gravity' => [GRAVITY_COMMENT, GRAVITY_ACTIVITY]]);
+               while ($item = DBA::fetch($items)) {
+                       $contact = DBA::selectFirst('contact', ['id', 'url', 'name', 'protocol', 'batch', 'network'],
+                               ['id' => $item['author-id']]);
+                       if (!DBA::isResult($contact) || empty($contact['batch']) ||
+                               ($contact['network'] != Protocol::DIASPORA) ||
+                               Strings::compareLink($item['parent-author-link'], $item['author-link'])) {
+                               continue;
                        }
-                       unset($contact['fbatch']);
 
                        $exists = false;
                        foreach ($contacts as $entry) {
@@ -286,10 +284,11 @@ class Diaspora
                        }
 
                        if (!$exists) {
+                               Logger::info('Add participant to receiver list', ['parent' => $item['parent-guid'], 'item' => $item['guid'], 'participant' => $contact['url']]);
                                $contacts[] = $contact;
                        }
                }
-               DBA::close($r);
+               DBA::close($items);
 
                return $contacts;
        }
@@ -336,7 +335,7 @@ class Diaspora
         */
        private static function verifyMagicEnvelope($envelope)
        {
-               $basedom = XML::parseString($envelope);
+               $basedom = XML::parseString($envelope, true);
 
                if (!is_object($basedom)) {
                        Logger::log("Envelope is no XML file");
@@ -462,7 +461,7 @@ class Diaspora
                        $xml = $raw;
                }
 
-               $basedom = XML::parseString($xml);
+               $basedom = XML::parseString($xml, true);
 
                if (!is_object($basedom)) {
                        Logger::log('Received data does not seem to be an XML. Discarding. '.$xml);
@@ -1518,7 +1517,7 @@ class Diaspora
        private static function parentItem($uid, $guid, $author, array $contact)
        {
                $fields = ['id', 'parent', 'body', 'wall', 'uri', 'guid', 'private', 'origin',
-                       'author-name', 'author-link', 'author-avatar',
+                       'author-name', 'author-link', 'author-avatar', 'gravity',
                        'owner-name', 'owner-link', 'owner-avatar'];
                $condition = ['uid' => $uid, 'guid' => $guid];
                $item = Item::selectFirst($fields, $condition);
@@ -1731,6 +1730,7 @@ class Diaspora
                while ($contact = DBA::fetch($contacts)) {
                        Contact::remove($contact["id"]);
                }
+               DBA::close($contacts);
 
                DBA::delete('gcontact', ['addr' => $author]);
 
@@ -1808,6 +1808,40 @@ class Diaspora
                return false;
        }
 
+       /**
+        * Store the mentions in the tag table
+        *
+        * @param integer $uriid
+        * @param string $text
+        */
+       private static function storeMentions(int $uriid, string $text)
+       {
+               preg_match_all('/([@!]){(?:([^}]+?); ?)?([^} ]+)}/', $text, $matches, PREG_SET_ORDER);
+               if (empty($matches)) {
+                       return;
+               }
+
+               /*
+                * Matching values for the preg match
+                * [1] = mention type (@ or !)
+                * [2] = name (optional)
+                * [3] = profile URL
+                */
+
+               foreach ($matches as $match) {
+                       if (empty($match)) {
+                               continue;
+                       }
+
+                       $person = self::personByHandle($match[3]);
+                       if (empty($person)) {
+                               continue;
+                       }
+
+                       Tag::storeByHash($uriid, $match[1], $person['name'] ?: $person['nick'], $person['url']);
+               }
+       }
+
        /**
         * Processes an incoming comment
         *
@@ -1878,6 +1912,7 @@ class Diaspora
 
                $datarray["guid"] = $guid;
                $datarray["uri"] = self::getUriFromGuid($author, $guid);
+               $datarray['uri-id'] = ItemURI::insert(['uri' => $datarray['uri'], 'guid' => $datarray['guid']]);
 
                $datarray["verb"] = Activity::POST;
                $datarray["gravity"] = GRAVITY_COMMENT;
@@ -1900,6 +1935,9 @@ class Diaspora
 
                $datarray["body"] = self::replacePeopleGuid($body, $person["url"]);
 
+               self::storeMentions($datarray['uri-id'], $text);
+               Tag::storeRawTagsFromBody($datarray['uri-id'], $datarray["body"]);
+
                self::fetchGuid($datarray);
 
                // If we are the origin of the parent we store the original data.
@@ -2126,8 +2164,8 @@ class Diaspora
                $datarray["changed"] = $datarray["created"] = $datarray["edited"] = DateTimeFormat::utcNow();
 
                // like on comments have the comment as parent. So we need to fetch the toplevel parent
-               if ($parent_item["id"] != $parent_item["parent"]) {
-                       $toplevel = Item::selectFirst(['origin'], ['id' => $parent_item["parent"]]);
+               if ($parent_item['gravity'] != GRAVITY_PARENT) {
+                       $toplevel = Item::selectFirst(['origin'], ['id' => $parent_item['parent']]);
                        $origin = $toplevel["origin"];
                } else {
                        $origin = $parent_item["origin"];
@@ -2222,57 +2260,88 @@ class Diaspora
         * @param array  $importer Array of the importer user
         * @param object $data     The message object
         *
-        * @return bool always true
+        * @return bool success
         * @throws \Friendica\Network\HTTPException\InternalServerErrorException
         * @throws \ImagickException
         */
        private static function receiveParticipation(array $importer, $data)
        {
                $author = strtolower(Strings::escapeTags(XML::unescape($data->author)));
+               $guid = Strings::escapeTags(XML::unescape($data->guid));
                $parent_guid = Strings::escapeTags(XML::unescape($data->parent_guid));
 
-               $contact_id = Contact::getIdForURL($author);
-               if (!$contact_id) {
-                       Logger::log('Contact not found: '.$author);
+               $contact = self::allowedContactByHandle($importer, $author, true);
+               if (!$contact) {
                        return false;
                }
 
-               $person = self::personByHandle($author);
-               if (!is_array($person)) {
-                       Logger::log("Person not found: ".$author);
-                       return false;
+               if (self::messageExists($importer["uid"], $guid)) {
+                       return true;
                }
 
-               $item = Item::selectFirst(['id'], ['guid' => $parent_guid, 'origin' => true, 'private' => [Item::PUBLIC, Item::UNLISTED]]);
-               if (!DBA::isResult($item)) {
-                       Logger::log('Item not found, no origin or private: '.$parent_guid);
+               $parent_item = self::parentItem($importer["uid"], $parent_guid, $author, $contact);
+               if (!$parent_item) {
                        return false;
                }
 
-               $author_parts = explode('@', $author);
-               if (isset($author_parts[1])) {
-                       $server = $author_parts[1];
-               } else {
-                       // Should never happen
-                       $server = $author;
+               if (!$parent_item['origin']) {
+                       Logger::info('Not our origin. Participation is ignored', ['parent_guid' => $parent_guid, 'guid' => $guid, 'author' => $author]);
                }
 
-               Logger::log('Received participation for ID: '.$item['id'].' - Contact: '.$contact_id.' - Server: '.$server, Logger::DEBUG);
+               if (!in_array($parent_item['private'], [Item::PUBLIC, Item::UNLISTED])) {
+                       Logger::info('Item is not public, participation is ignored', ['parent_guid' => $parent_guid, 'guid' => $guid, 'author' => $author]);
+                       return false;
+               }
 
-               if (!DBA::exists('participation', ['iid' => $item['id'], 'server' => $server])) {
-                       DBA::insert('participation', ['iid' => $item['id'], 'cid' => $contact_id, 'fid' => $person['id'], 'server' => $server]);
+               $person = self::personByHandle($author);
+               if (!is_array($person)) {
+                       Logger::log("Person not found: ".$author);
+                       return false;
                }
 
+               $author_contact = self::authorContactByUrl($contact, $person, $importer["uid"]);
+
+               // Store participation
+               $datarray = [];
+
+               $datarray["protocol"] = Conversation::PARCEL_DIASPORA;
+
+               $datarray["uid"] = $importer["uid"];
+               $datarray["contact-id"] = $author_contact["cid"];
+               $datarray["network"]  = $author_contact["network"];
+
+               $datarray["owner-link"] = $datarray["author-link"] = $person["url"];
+               $datarray["owner-id"] = $datarray["author-id"] = Contact::getIdForURL($person["url"], 0);
+
+               $datarray["guid"] = $guid;
+               $datarray["uri"] = self::getUriFromGuid($author, $guid);
+
+               $datarray["verb"] = Activity::FOLLOW;
+               $datarray["gravity"] = GRAVITY_ACTIVITY;
+               $datarray["parent-uri"] = $parent_item["uri"];
+
+               $datarray["object-type"] = Activity\ObjectType::NOTE;
+
+               $datarray["body"] = Activity::FOLLOW;
+
+               // Diaspora doesn't provide a date for a participation
+               $datarray["changed"] = $datarray["created"] = $datarray["edited"] = DateTimeFormat::utcNow();
+
+               $message_id = Item::insert($datarray);
+
+               Logger::info('Participation stored', ['id' => $message_id, 'guid' => $guid, 'parent_guid' => $parent_guid, 'author' => $author]);
+
                // Send all existing comments and likes to the requesting server
-               $comments = Item::select(['id', 'parent', 'verb', 'self'], ['parent' => $item['id']]);
+               $comments = Item::select(['id', 'uri-id', 'parent', 'verb'], ['parent' => $parent_item['id'], 'gravity' => [GRAVITY_COMMENT, GRAVITY_ACTIVITY]]);
                while ($comment = Item::fetch($comments)) {
-                       if ($comment['id'] == $comment['parent']) {
+                       if (in_array($comment["verb"], [Activity::FOLLOW, Activity::TAG])) {
+                               Logger::info('participation messages are not relayed', ['item' => $comment['id']]);
                                continue;
                        }
 
-                       Logger::info('Deliver participation', ['item' => $comment['id'], 'contact' => $contact_id]);
-                       if (Worker::add(PRIORITY_HIGH, 'Delivery', Delivery::POST, $comment['id'], $contact_id)) {
-                               ItemDeliveryData::incrementQueueCount($comment['id'], 1);
+                       Logger::info('Deliver participation', ['item' => $comment['id'], 'contact' => $author_contact["cid"]]);
+                       if (Worker::add(PRIORITY_HIGH, 'Delivery', Delivery::POST, $comment['id'], $author_contact["cid"])) {
+                               Post\DeliveryData::incrementQueueCount($comment['uri-id'], 1);
                        }
                }
                DBA::close($comments);
@@ -2553,7 +2622,7 @@ class Diaspora
                }
 
                // Do we already have this item?
-               $fields = ['body', 'title', 'attach', 'tag', 'app', 'created', 'object-type', 'uri', 'guid',
+               $fields = ['body', 'title', 'attach', 'app', 'created', 'object-type', 'uri', 'guid',
                        'author-name', 'author-link', 'author-avatar'];
                $condition = ['guid' => $guid, 'visible' => true, 'deleted' => false, 'private' => [Item::PUBLIC, Item::UNLISTED]];
                $item = Item::selectFirst($fields, $condition);
@@ -2597,7 +2666,7 @@ class Diaspora
                        }
 
                        if ($stored) {
-                               $fields = ['body', 'title', 'attach', 'tag', 'app', 'created', 'object-type', 'uri', 'guid',
+                               $fields = ['body', 'title', 'attach', 'app', 'created', 'object-type', 'uri', 'guid',
                                        'author-name', 'author-link', 'author-avatar'];
                                $condition = ['guid' => $guid, 'visible' => true, 'deleted' => false, 'private' => [Item::PUBLIC, Item::UNLISTED]];
                                $item = Item::selectFirst($fields, $condition);
@@ -2716,6 +2785,7 @@ class Diaspora
 
                $datarray["guid"] = $guid;
                $datarray["uri"] = $datarray["parent-uri"] = self::getUriFromGuid($author, $guid);
+               $datarray['uri-id'] = ItemURI::insert(['uri' => $datarray['uri'], 'guid' => $datarray['guid']]);
 
                $datarray["verb"] = Activity::POST;
                $datarray["gravity"] = GRAVITY_PARENT;
@@ -2723,6 +2793,8 @@ class Diaspora
                $datarray["protocol"] = Conversation::PARCEL_DIASPORA;
                $datarray["source"] = $xml;
 
+               /// @todo Copy tag data from original post
+
                $prefix = share_header(
                        $original_item["author-name"],
                        $original_item["author-link"],
@@ -2738,7 +2810,8 @@ class Diaspora
 
                $datarray["body"] = $prefix.$original_item["body"]."[/share]";
 
-               $datarray["tag"] = $original_item["tag"];
+               Tag::storeFromBody($datarray['uri-id'], $datarray["body"]);
+
                $datarray["attach"] = $original_item["attach"];
                $datarray["app"]  = $original_item["app"];
 
@@ -2818,7 +2891,7 @@ class Diaspora
                        }
 
                        // Fetch the parent item
-                       $parent = Item::selectFirst(['author-link'], ['id' => $item["parent"]]);
+                       $parent = Item::selectFirst(['author-link'], ['id' => $item['parent']]);
 
                        // Only delete it if the parent author really fits
                        if (!Strings::compareLink($parent["author-link"], $contact["url"]) && !Strings::compareLink($item["author-link"], $contact["url"])) {
@@ -2828,7 +2901,7 @@ class Diaspora
 
                        Item::markForDeletion(['id' => $item['id']]);
 
-                       Logger::log("Deleted target ".$target_guid." (".$item["id"].") from user ".$item["uid"]." parent: ".$item["parent"], Logger::DEBUG);
+                       Logger::log("Deleted target ".$target_guid." (".$item["id"].") from user ".$item["uid"]." parent: ".$item['parent'], Logger::DEBUG);
                }
 
                return true;
@@ -2959,6 +3032,7 @@ class Diaspora
 
                $datarray["guid"] = $guid;
                $datarray["uri"] = $datarray["parent-uri"] = self::getUriFromGuid($author, $guid);
+               $datarray['uri-id'] = ItemURI::insert(['uri' => $datarray['uri'], 'guid' => $datarray['guid']]);
 
                $datarray["verb"] = Activity::POST;
                $datarray["gravity"] = GRAVITY_PARENT;
@@ -2968,6 +3042,9 @@ class Diaspora
 
                $datarray["body"] = self::replacePeopleGuid($body, $contact["url"]);
 
+               self::storeMentions($datarray['uri-id'], $text);
+               Tag::storeRawTagsFromBody($datarray['uri-id'], $datarray["body"]);
+
                if ($provider_display_name != "") {
                        $datarray["app"] = $provider_display_name;
                }
@@ -3062,7 +3139,9 @@ class Diaspora
                $json = json_encode(["iv" => $b_iv, "key" => $b_aes_key]);
 
                $encrypted_key_bundle = "";
-               openssl_public_encrypt($json, $encrypted_key_bundle, $pubkey);
+               if (!@openssl_public_encrypt($json, $encrypted_key_bundle, $pubkey)) {
+                       return false;
+               }
 
                $json_object = json_encode(
                        ["aes_key" => base64_encode($encrypted_key_bundle),
@@ -3791,9 +3870,9 @@ class Diaspora
                        return $result;
                }
 
-               $toplevel_item = Item::selectFirst(['guid', 'author-id', 'author-link'], ['id' => $item["parent"], 'parent' => $item["parent"]]);
+               $toplevel_item = Item::selectFirst(['guid', 'author-id', 'author-link'], ['id' => $item['parent'], 'parent' => $item['parent']]);
                if (!DBA::isResult($toplevel_item)) {
-                       Logger::error('Missing parent conversation item', ['parent' => $item["parent"]]);
+                       Logger::error('Missing parent conversation item', ['parent' => $item['parent']]);
                        return false;
                }
 
@@ -3942,30 +4021,24 @@ class Diaspora
 
                Logger::log("Got relayable data ".$type." for item ".$item["guid"]." (".$item["id"].")", Logger::DEBUG);
 
-               // Old way - is used by the internal Friendica functions
-               /// @todo Change all signatur storing functions to the new format
-               if ($item['signed_text'] && $item['signature'] && $item['signer']) {
-                       $message = self::messageFromSignature($item);
-               } else {// New way
-                       $msg = json_decode($item['signed_text'], true);
-
-                       $message = [];
-                       if (is_array($msg)) {
-                               foreach ($msg as $field => $data) {
-                                       if (!$item["deleted"]) {
-                                               if ($field == "diaspora_handle") {
-                                                       $field = "author";
-                                               }
-                                               if ($field == "target_type") {
-                                                       $field = "parent_type";
-                                               }
-                                       }
+               $msg = json_decode($item['signed_text'], true);
 
-                                       $message[$field] = $data;
+               $message = [];
+               if (is_array($msg)) {
+                       foreach ($msg as $field => $data) {
+                               if (!$item["deleted"]) {
+                                       if ($field == "diaspora_handle") {
+                                               $field = "author";
+                                       }
+                                       if ($field == "target_type") {
+                                               $field = "parent_type";
+                                       }
                                }
-                       } else {
-                               Logger::log("Signature text for item ".$item["guid"]." (".$item["id"].") couldn't be extracted: ".$item['signed_text'], Logger::DEBUG);
+
+                               $message[$field] = $data;
                        }
+               } else {
+                       Logger::log("Signature text for item ".$item["guid"]." (".$item["id"].") couldn't be extracted: ".$item['signed_text'], Logger::DEBUG);
                }
 
                $message["parent_author_signature"] = self::signature($owner, $message);
@@ -3993,7 +4066,7 @@ class Diaspora
 
                $msg_type = "retraction";
 
-               if ($item['id'] == $item['parent']) {
+               if ($item['gravity'] == GRAVITY_PARENT) {
                        $target_type = "Post";
                } elseif (in_array($item["verb"], [Activity::LIKE, Activity::DISLIKE])) {
                        $target_type = "Like";
@@ -4127,20 +4200,11 @@ class Diaspora
         */
        private static function createProfileData($uid)
        {
-               $r = q(
-                       "SELECT `profile`.`uid` AS `profile_uid`, `profile`.* , `user`.*, `user`.`prvkey` AS `uprvkey`, `contact`.`addr`
-                       FROM `profile`
-                       INNER JOIN `user` ON `profile`.`uid` = `user`.`uid`
-                       INNER JOIN `contact` ON `profile`.`uid` = `contact`.`uid`
-                       WHERE `user`.`uid` = %d AND `contact`.`self` LIMIT 1",
-                       intval($uid)
-               );
-
-               if (!$r) {
+               $profile = DBA::selectFirst('owner-view', ['uid', 'addr', 'name', 'location', 'net-publish', 'dob', 'about', 'pub_keywords'], ['uid' => $uid]);
+               if (!DBA::isResult($profile)) {
                        return [];
                }
 
-               $profile = $r[0];
                $handle = $profile["addr"];
 
                $split_name = self::splitName($profile['name']);
@@ -4169,7 +4233,7 @@ class Diaspora
 
                        $about = BBCode::toMarkdown($profile['about']);
 
-                       $location = Profile::formatLocation($profile);
+                       $location = $profile['location'];
                        $tags = '';
                        if ($profile['pub_keywords']) {
                                $kw = str_replace(',', ' ', $profile['pub_keywords']);