X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FProtocol%2FDiaspora.php;h=14c8df1a7815c8867e71373771e77f669c2d3317;hb=890d1057d2d962f477aa6d1ddacab9b9963ce0fd;hp=592f76ce734f165dd62fcefff81f27e2bd3eaca2;hpb=e26d73393e0c65d2c9544763210739dbe6614582;p=friendica.git diff --git a/src/Protocol/Diaspora.php b/src/Protocol/Diaspora.php index 592f76ce73..14c8df1a78 100644 --- a/src/Protocol/Diaspora.php +++ b/src/Protocol/Diaspora.php @@ -56,196 +56,6 @@ use SimpleXMLElement; */ class Diaspora { - /** - * Mark the relay contact of the given contact for archival - * This is called whenever there is a communication issue with the server. - * It avoids sending stuff to servers who don't exist anymore. - * The relay contact is a technical contact entry that exists once per server. - * - * @param array $contact of the relay contact - */ - public static function markRelayForArchival(array $contact) - { - if (!empty($contact['contact-type']) && ($contact['contact-type'] == Contact::TYPE_RELAY)) { - // This is already the relay contact, we don't need to fetch it - $relay_contact = $contact; - } elseif (empty($contact['baseurl'])) { - if (!empty($contact['batch'])) { - $condition = ['uid' => 0, 'network' => Protocol::FEDERATED, 'batch' => $contact['batch'], 'contact-type' => Contact::TYPE_RELAY]; - $relay_contact = DBA::selectFirst('contact', [], $condition); - } else { - return; - } - } else { - $relay_contact = self::getRelayContact($contact['baseurl'], []); - } - - if (!empty($relay_contact)) { - Logger::info('Relay contact will be marked for archival', ['id' => $relay_contact['id'], 'url' => $relay_contact['url']]); - Contact::markForArchival($relay_contact); - } - } - - /** - * Return a list of relay servers - * - * The list contains not only the official relays but also servers that we serve directly - * - * @param integer $item_id The id of the item that is sent - * @param array $contacts The previously fetched contacts - * - * @return array of relay servers - * @throws \Friendica\Network\HTTPException\InternalServerErrorException - */ - public static function relayList($item_id, array $contacts = []) - { - $serverlist = []; - - // Fetching relay servers - $serverdata = DI::config()->get("system", "relay_server"); - - if (!empty($serverdata)) { - $servers = explode(",", $serverdata); - foreach ($servers as $server) { - $serverlist[$server] = trim($server); - } - } - - 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(['uri-id'], ['id' => $item_id]); - if (!DBA::isResult($parent)) { - return; - } - - // Servers that want to get all content - $servers = DBA::select('gserver', ['url'], ['relay-subscribe' => true, 'relay-scope' => 'all']); - while ($server = DBA::fetch($servers)) { - $serverlist[$server['url']] = $server['url']; - } - DBA::close($servers); - - // All tags of the current post - $tags = DBA::select('tag-view', ['name'], ['uri-id' => $parent['uri-id'], 'type' => Tag::HASHTAG]); - $taglist = []; - while ($tag = DBA::fetch($tags)) { - $taglist[] = $tag['name']; - } - DBA::close($tags); - - // All servers who wants content with this tag - $tagserverlist = []; - if (!empty($taglist)) { - $tagserver = DBA::select('gserver-tag', ['gserver-id'], ['tag' => $taglist]); - while ($server = DBA::fetch($tagserver)) { - $tagserverlist[] = $server['gserver-id']; - } - DBA::close($tagserver); - } - - // All adresses with the given id - if (!empty($tagserverlist)) { - $servers = DBA::select('gserver', ['url'], ['relay-subscribe' => true, 'relay-scope' => 'tags', 'id' => $tagserverlist]); - while ($server = DBA::fetch($servers)) { - $serverlist[$server['url']] = $server['url']; - } - DBA::close($servers); - } - } - - // Now we are collecting all relay contacts - foreach ($serverlist as $server_url) { - // We don't send messages to ourselves - if (Strings::compareLink($server_url, DI::baseUrl())) { - continue; - } - $contact = self::getRelayContact($server_url); - if (is_bool($contact)) { - continue; - } - - $exists = false; - foreach ($contacts as $entry) { - if ($entry['batch'] == $contact['batch']) { - $exists = true; - } - } - - if (!$exists) { - $contacts[] = $contact; - } - } - - return $contacts; - } - - /** - * Return a contact for a given server address or creates a dummy entry - * - * @param string $server_url The url of the server - * @param array $fields Fieldlist - * @return array with the contact - * @throws \Exception - */ - private static function getRelayContact(string $server_url, array $fields = ['batch', 'id', 'url', 'name', 'network', 'protocol', 'archive', 'blocked']) - { - // Fetch the relay contact - $condition = ['uid' => 0, 'nurl' => Strings::normaliseLink($server_url), - 'contact-type' => Contact::TYPE_RELAY]; - $contact = DBA::selectFirst('contact', $fields, $condition); - - if (DBA::isResult($contact)) { - if ($contact['archive'] || $contact['blocked']) { - return false; - } - return $contact; - } else { - self::setRelayContact($server_url); - - $contact = DBA::selectFirst('contact', $fields, $condition); - if (DBA::isResult($contact)) { - return $contact; - } - } - - // It should never happen that we arrive here - return []; - } - - /** - * Update or insert a relay contact - * - * @param string $server_url The url of the server - * @param array $network_fields Optional network specific fields - * @throws \Exception - */ - public static function setRelayContact($server_url, array $network_fields = []) - { - $fields = ['created' => DateTimeFormat::utcNow(), - 'name' => 'relay', 'nick' => 'relay', 'url' => $server_url, - 'nurl' => Strings::normaliseLink($server_url), - 'network' => Protocol::DIASPORA, 'uid' => 0, - 'batch' => $server_url . '/receive/public', - 'rel' => Contact::FOLLOWER, 'blocked' => false, - 'pending' => false, 'writable' => true, - 'baseurl' => $server_url, 'contact-type' => Contact::TYPE_RELAY]; - - $fields = array_merge($fields, $network_fields); - - $condition = ['uid' => 0, 'nurl' => Strings::normaliseLink($server_url)]; - $old = DBA::selectFirst('contact', [], $condition); - if (DBA::isResult($old)) { - unset($fields['created']); - $condition = ['id' => $old['id']]; - - Logger::info('Update relay contact', ['fields' => $fields, 'condition' => $condition]); - DBA::update('contact', $fields, $condition, $old); - } else { - Logger::info('Create relay contact', ['fields' => $fields]); - Contact::insert($fields); - } - } - /** * Return a list of participating contacts for a thread * @@ -266,9 +76,9 @@ class Diaspora return $contacts; } - $items = Item::select(['author-id', 'author-link', 'parent-author-link', 'parent-guid', 'guid'], + $items = Post::select(['author-id', 'author-link', 'parent-author-link', 'parent-guid', 'guid'], ['parent' => $item['parent'], 'gravity' => [GRAVITY_COMMENT, GRAVITY_ACTIVITY]]); - while ($item = DBA::fetch($items)) { + while ($item = Post::fetch($items)) { $contact = DBA::selectFirst('contact', ['id', 'url', 'name', 'protocol', 'batch', 'network'], ['id' => $item['author-id']]); if (!DBA::isResult($contact) || empty($contact['batch']) || @@ -727,7 +537,7 @@ class Diaspora return self::receiveConversation($importer, $msg, $fields); case "like": - return self::receiveLike($importer, $sender, $fields); + return self::receiveLike($importer, $sender, $fields, $fetched); case "message": if (!$private) { @@ -741,7 +551,7 @@ class Diaspora Logger::log('Message with type ' . $type . ' is not private, quitting.'); return false; } - return self::receiveParticipation($importer, $fields); + return self::receiveParticipation($importer, $fields, $fetched); case "photo": // Not implemented return self::receivePhoto($importer, $fields); @@ -757,7 +567,7 @@ class Diaspora return self::receiveProfile($importer, $fields); case "reshare": - return self::receiveReshare($importer, $fields, $msg["message"]); + return self::receiveReshare($importer, $fields, $msg["message"], $fetched); case "retraction": return self::receiveRetraction($importer, $sender, $fields); @@ -1121,7 +931,7 @@ class Diaspora */ private static function messageExists($uid, $guid) { - $item = Item::selectFirst(['id'], ['uid' => $uid, 'guid' => $guid]); + $item = Post::selectFirst(['id'], ['uid' => $uid, 'guid' => $guid]); if (DBA::isResult($item)) { Logger::log("message ".$guid." already exists for user ".$uid); return $item["id"]; @@ -1341,7 +1151,7 @@ class Diaspora $guid = urldecode($matches[2]); - $item = Item::selectFirst(['id'], ['guid' => $guid, 'uid' => $uid]); + $item = Post::selectFirst(['id'], ['guid' => $guid, 'uid' => $uid]); if (DBA::isResult($item)) { Logger::info('Found', ['id' => $item['id']]); return $item['id']; @@ -1351,7 +1161,7 @@ class Diaspora $ret = self::storeByGuid($guid, $matches[1], $uid); Logger::info('Result', ['ret' => $ret]); - $item = Item::selectFirst(['id'], ['guid' => $guid, 'uid' => $uid]); + $item = Post::selectFirst(['id'], ['guid' => $guid, 'uid' => $uid]); if (DBA::isResult($item)) { Logger::info('Found', ['id' => $item['id']]); return $item['id']; @@ -1378,7 +1188,7 @@ class Diaspora 'author-name', 'author-link', 'author-avatar', 'gravity', 'owner-name', 'owner-link', 'owner-avatar']; $condition = ['uid' => $uid, 'guid' => $guid]; - $item = Item::selectFirst($fields, $condition); + $item = Post::selectFirst($fields, $condition); if (!DBA::isResult($item)) { $person = FContact::getByURL($author); @@ -1392,7 +1202,7 @@ class Diaspora if ($result) { Logger::log("Fetched missing item ".$guid." - result: ".$result, Logger::DEBUG); - $item = Item::selectFirst($fields, $condition); + $item = Post::selectFirst($fields, $condition); } } @@ -1455,7 +1265,7 @@ class Diaspora * @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \ImagickException */ - private static function plink($addr, $guid, $parent_guid = '') + private static function plink(string $addr, string $guid, string $parent_guid = '') { $contact = Contact::getByURL($addr); if (empty($contact)) { @@ -1600,7 +1410,7 @@ class Diaspora */ private static function getUriFromGuid($author, $guid, $onlyfound = false) { - $item = Item::selectFirst(['uri'], ['guid' => $guid]); + $item = Post::selectFirst(['uri'], ['guid' => $guid]); if (DBA::isResult($item)) { return $item["uri"]; } elseif (!$onlyfound) { @@ -1616,25 +1426,6 @@ class Diaspora return ""; } - /** - * Fetch the guid from our database with a given uri - * - * @param string $uri Message uri - * @param string $uid Author handle - * - * @return string The post guid - * @throws \Exception - */ - private static function getGuidFromUri($uri, $uid) - { - $item = Item::selectFirst(['guid'], ['uri' => $uri, 'uid' => $uid]); - if (DBA::isResult($item)) { - return $item["guid"]; - } else { - return false; - } - } - /** * Store the mentions in the tag table * @@ -1697,9 +1488,9 @@ class Diaspora if (isset($data->thread_parent_guid)) { $thread_parent_guid = Strings::escapeTags(XML::unescape($data->thread_parent_guid)); - $thr_uri = self::getUriFromGuid("", $thread_parent_guid, true); + $thr_parent = self::getUriFromGuid("", $thread_parent_guid, true); } else { - $thr_uri = ""; + $thr_parent = ""; } $contact = self::allowedContactByHandle($importer, $sender, true); @@ -1712,8 +1503,8 @@ class Diaspora return true; } - $parent_item = self::parentItem($importer["uid"], $parent_guid, $author, $contact); - if (!$parent_item) { + $toplevel_parent_item = self::parentItem($importer["uid"], $parent_guid, $author, $contact); + if (!$toplevel_parent_item) { return false; } @@ -1754,20 +1545,17 @@ class Diaspora $datarray["verb"] = Activity::POST; $datarray["gravity"] = GRAVITY_COMMENT; - if ($thr_uri != "") { - $datarray["parent-uri"] = $thr_uri; - } else { - $datarray["parent-uri"] = $parent_item["uri"]; - } + $datarray['thr-parent'] = $thr_parent ?: $toplevel_parent_item['uri']; $datarray["object-type"] = Activity\ObjectType::COMMENT; $datarray["protocol"] = Conversation::PARCEL_DIASPORA; $datarray["source"] = $xml; + $datarray["direction"] = $fetched ? Conversation::PULL : Conversation::PUSH; $datarray["changed"] = $datarray["created"] = $datarray["edited"] = $created_at; - $datarray["plink"] = self::plink($author, $guid, $parent_item['guid']); + $datarray["plink"] = self::plink($author, $guid, $toplevel_parent_item['guid']); $body = Markdown::toBBCode($text); $datarray["body"] = self::replacePeopleGuid($body, $person["url"]); @@ -1779,10 +1567,15 @@ class Diaspora // If we are the origin of the parent we store the original data. // We notify our followers during the item storage. - if ($parent_item["origin"]) { + if ($toplevel_parent_item["origin"]) { $datarray['diaspora_signed_text'] = json_encode($data); } + if (Item::isTooOld($datarray)) { + Logger::info('Comment is too old', ['created' => $datarray['created'], 'uid' => $datarray['uid'], 'guid' => $datarray['guid']]); + return false; + } + $message_id = Item::insert($datarray); if ($message_id <= 0) { @@ -1929,7 +1722,7 @@ class Diaspora * @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \ImagickException */ - private static function receiveLike(array $importer, $sender, $data) + private static function receiveLike(array $importer, $sender, $data, bool $fetched) { $author = Strings::escapeTags(XML::unescape($data->author)); $guid = Strings::escapeTags(XML::unescape($data->guid)); @@ -1953,8 +1746,8 @@ class Diaspora return true; } - $parent_item = self::parentItem($importer["uid"], $parent_guid, $author, $contact); - if (!$parent_item) { + $toplevel_parent_item = self::parentItem($importer["uid"], $parent_guid, $author, $contact); + if (!$toplevel_parent_item) { return false; } @@ -1978,6 +1771,7 @@ class Diaspora $datarray = []; $datarray["protocol"] = Conversation::PARCEL_DIASPORA; + $datarray["direction"] = $fetched ? Conversation::PULL : Conversation::PUSH; $datarray["uid"] = $importer["uid"]; $datarray["contact-id"] = $author_contact["cid"]; @@ -1991,7 +1785,7 @@ class Diaspora $datarray["verb"] = $verb; $datarray["gravity"] = GRAVITY_ACTIVITY; - $datarray["parent-uri"] = $parent_item["uri"]; + $datarray['thr-parent'] = $toplevel_parent_item['uri']; $datarray["object-type"] = Activity\ObjectType::NOTE; @@ -2001,11 +1795,11 @@ 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['gravity'] != GRAVITY_PARENT) { - $toplevel = Item::selectFirst(['origin'], ['id' => $parent_item['parent']]); + if ($toplevel_parent_item['gravity'] != GRAVITY_PARENT) { + $toplevel = Post::selectFirst(['origin'], ['id' => $toplevel_parent_item['parent']]); $origin = $toplevel["origin"]; } else { - $origin = $parent_item["origin"]; + $origin = $toplevel_parent_item["origin"]; } // If we are the origin of the parent we store the original data. @@ -2014,6 +1808,11 @@ class Diaspora $datarray['diaspora_signed_text'] = json_encode($data); } + if (Item::isTooOld($datarray)) { + Logger::info('Like is too old', ['created' => $datarray['created'], 'uid' => $datarray['uid'], 'guid' => $datarray['guid']]); + return false; + } + $message_id = Item::insert($datarray); if ($message_id <= 0) { @@ -2101,7 +1900,7 @@ class Diaspora * @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \ImagickException */ - private static function receiveParticipation(array $importer, $data) + private static function receiveParticipation(array $importer, $data, bool $fetched) { $author = strtolower(Strings::escapeTags(XML::unescape($data->author))); $guid = Strings::escapeTags(XML::unescape($data->guid)); @@ -2116,16 +1915,16 @@ class Diaspora return true; } - $parent_item = self::parentItem($importer["uid"], $parent_guid, $author, $contact); - if (!$parent_item) { + $toplevel_parent_item = self::parentItem($importer["uid"], $parent_guid, $author, $contact); + if (!$toplevel_parent_item) { return false; } - if (!$parent_item['origin']) { + if (!$toplevel_parent_item['origin']) { Logger::info('Not our origin. Participation is ignored', ['parent_guid' => $parent_guid, 'guid' => $guid, 'author' => $author]); } - if (!in_array($parent_item['private'], [Item::PUBLIC, Item::UNLISTED])) { + if (!in_array($toplevel_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; } @@ -2142,6 +1941,7 @@ class Diaspora $datarray = []; $datarray["protocol"] = Conversation::PARCEL_DIASPORA; + $datarray["direction"] = $fetched ? Conversation::PULL : Conversation::PUSH; $datarray["uid"] = $importer["uid"]; $datarray["contact-id"] = $author_contact["cid"]; @@ -2155,7 +1955,7 @@ class Diaspora $datarray["verb"] = Activity::FOLLOW; $datarray["gravity"] = GRAVITY_ACTIVITY; - $datarray["parent-uri"] = $parent_item["uri"]; + $datarray['thr-parent'] = $toplevel_parent_item['uri']; $datarray["object-type"] = Activity\ObjectType::NOTE; @@ -2164,14 +1964,19 @@ class Diaspora // Diaspora doesn't provide a date for a participation $datarray["changed"] = $datarray["created"] = $datarray["edited"] = DateTimeFormat::utcNow(); + if (Item::isTooOld($datarray)) { + Logger::info('Participation is too old', ['created' => $datarray['created'], 'uid' => $datarray['uid'], 'guid' => $datarray['guid']]); + return false; + } + $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', 'uri-id', 'parent-author-network', 'author-network', 'verb'], - ['parent' => $parent_item['id'], 'gravity' => [GRAVITY_COMMENT, GRAVITY_ACTIVITY]]); - while ($comment = Item::fetch($comments)) { + $comments = Post::select(['id', 'uri-id', 'parent-author-network', 'author-network', 'verb'], + ['parent' => $toplevel_parent_item['id'], 'gravity' => [GRAVITY_COMMENT, GRAVITY_ACTIVITY]]); + while ($comment = Post::fetch($comments)) { if (in_array($comment['verb'], [Activity::FOLLOW, Activity::TAG])) { Logger::info('participation messages are not relayed', ['item' => $comment['id']]); continue; @@ -2458,10 +2263,10 @@ class Diaspora } // Do we already have this item? - $fields = ['body', 'title', 'attach', 'app', 'created', 'object-type', 'uri', 'guid', - 'author-name', 'author-link', 'author-avatar', 'plink']; + $fields = ['body', 'title', 'app', 'created', 'object-type', 'uri', 'guid', + 'author-name', 'author-link', 'author-avatar', 'plink', 'uri-id']; $condition = ['guid' => $guid, 'visible' => true, 'deleted' => false, 'private' => [Item::PUBLIC, Item::UNLISTED]]; - $item = Item::selectFirst($fields, $condition); + $item = Post::selectFirst($fields, $condition); if (DBA::isResult($item)) { Logger::log("reshared message ".$guid." already exists on system."); @@ -2502,10 +2307,10 @@ class Diaspora } if ($stored) { - $fields = ['body', 'title', 'attach', 'app', 'created', 'object-type', 'uri', 'guid', - 'author-name', 'author-link', 'author-avatar', 'plink']; + $fields = ['body', 'title', 'app', 'created', 'object-type', 'uri', 'guid', + 'author-name', 'author-link', 'author-avatar', 'plink', 'uri-id']; $condition = ['guid' => $guid, 'visible' => true, 'deleted' => false, 'private' => [Item::PUBLIC, Item::UNLISTED]]; - $item = Item::selectFirst($fields, $condition); + $item = Post::selectFirst($fields, $condition); if (DBA::isResult($item)) { // If it is a reshared post from another network then reformat to avoid display problems with two share elements @@ -2531,7 +2336,7 @@ class Diaspora */ private static function addReshareActivity($item, $parent_message_id, $guid, $author) { - $parent = Item::selectFirst(['uri', 'guid'], ['id' => $parent_message_id]); + $parent = Post::selectFirst(['uri', 'guid'], ['id' => $parent_message_id]); $datarray = []; @@ -2547,18 +2352,25 @@ class Diaspora $datarray['guid'] = $parent['guid'] . '-' . $guid; $datarray['uri'] = self::getUriFromGuid($author, $datarray['guid']); - $datarray['parent-uri'] = $parent['uri']; + $datarray['thr-parent'] = $parent['uri']; $datarray['verb'] = $datarray['body'] = Activity::ANNOUNCE; $datarray['gravity'] = GRAVITY_ACTIVITY; $datarray['object-type'] = Activity\ObjectType::NOTE; $datarray['protocol'] = $item['protocol']; + $datarray['source'] = $item['source']; + $datarray['direction'] = $item['direction']; $datarray['plink'] = self::plink($author, $datarray['guid']); $datarray['private'] = $item['private']; $datarray['changed'] = $datarray['created'] = $datarray['edited'] = $item['created']; + if (Item::isTooOld($datarray)) { + Logger::info('Reshare activity is too old', ['created' => $datarray['created'], 'uid' => $datarray['uid'], 'guid' => $datarray['guid']]); + return false; + } + $message_id = Item::insert($datarray); if ($message_id) { @@ -2580,7 +2392,7 @@ class Diaspora * @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \ImagickException */ - private static function receiveReshare(array $importer, $data, $xml) + private static function receiveReshare(array $importer, $data, $xml, bool $fetched) { $author = Strings::escapeTags(XML::unescape($data->author)); $guid = Strings::escapeTags(XML::unescape($data->guid)); @@ -2605,6 +2417,10 @@ class Diaspora return false; } + if (empty($original_item['plink'])) { + $original_item['plink'] = self::plink($root_author, $root_guid); + } + $datarray = []; $datarray["uid"] = $importer["uid"]; @@ -2618,7 +2434,7 @@ class Diaspora $datarray["owner-id"] = $datarray["author-id"]; $datarray["guid"] = $guid; - $datarray["uri"] = $datarray["parent-uri"] = self::getUriFromGuid($author, $guid); + $datarray["uri"] = $datarray["thr-parent"] = self::getUriFromGuid($author, $guid); $datarray['uri-id'] = ItemURI::insert(['uri' => $datarray['uri'], 'guid' => $datarray['guid']]); $datarray["verb"] = Activity::POST; @@ -2626,6 +2442,7 @@ class Diaspora $datarray["protocol"] = Conversation::PARCEL_DIASPORA; $datarray["source"] = $xml; + $datarray["direction"] = $fetched ? Conversation::PULL : Conversation::PUSH; /// @todo Copy tag data from original post @@ -2646,7 +2463,7 @@ class Diaspora Tag::storeFromBody($datarray['uri-id'], $datarray["body"]); - $datarray["attach"] = $original_item["attach"]; + Post\Media::copy($original_item['uri-id'], $datarray['uri-id']); $datarray["app"] = $original_item["app"]; $datarray["plink"] = self::plink($author, $guid); @@ -2656,6 +2473,12 @@ class Diaspora $datarray["object-type"] = $original_item["object-type"]; self::fetchGuid($datarray); + + if (Item::isTooOld($datarray)) { + Logger::info('Reshare is too old', ['created' => $datarray['created'], 'uid' => $datarray['uid'], 'guid' => $datarray['guid']]); + return false; + } + $message_id = Item::insert($datarray); self::sendParticipation($contact, $datarray); @@ -2703,7 +2526,7 @@ class Diaspora } // Fetch items that are about to be deleted - $fields = ['uid', 'id', 'parent', 'parent-uri', 'author-link', 'file']; + $fields = ['uid', 'id', 'parent', 'author-link', 'uri-id']; // When we receive a public retraction, we delete every item that we find. if ($importer['uid'] == 0) { @@ -2712,20 +2535,20 @@ class Diaspora $condition = ['guid' => $target_guid, 'deleted' => false, 'uid' => $importer['uid']]; } - $r = Item::select($fields, $condition); + $r = Post::select($fields, $condition); if (!DBA::isResult($r)) { Logger::log("Target guid ".$target_guid." was not found on this system for user ".$importer['uid']."."); return false; } - while ($item = Item::fetch($r)) { - if (strstr($item['file'], '[')) { + while ($item = Post::fetch($r)) { + if (DBA::exists('post-category', ['uri-id' => $item['uri-id'], 'uid' => $item['uid'], 'type' => Post\Category::FILE])) { Logger::log("Target guid " . $target_guid . " for user " . $item['uid'] . " is filed. So it won't be deleted.", Logger::DEBUG); continue; } // Fetch the parent item - $parent = Item::selectFirst(['author-link'], ['id' => $item['parent']]); + $parent = Post::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"])) { @@ -2737,6 +2560,7 @@ class Diaspora Logger::log("Deleted target ".$target_guid." (".$item["id"].") from user ".$item["uid"]." parent: ".$item['parent'], Logger::DEBUG); } + DBA::close($r); return true; } @@ -2807,7 +2631,27 @@ class Diaspora $taglist = Tag::getByURIId($uriid, [Tag::HASHTAG]); $tags = array_column($taglist, 'name'); - return Relay::isSolicitedPost($tags, $body, $url, Protocol::DIASPORA); + return Relay::isSolicitedPost($tags, $body, $contact['id'], $url, Protocol::DIASPORA); + } + + /** + * Store an attached photo in the post-media table + * + * @param int $uriid + * @param object $photo + * @return void + */ + private static function storePhotoAsMedia(int $uriid, $photo) + { + $data = []; + $data['uri-id'] = $uriid; + $data['type'] = Post\Media::IMAGE; + $data['url'] = XML::unescape($photo->remote_photo_path) . XML::unescape($photo->remote_photo_name); + $data['height'] = (int)XML::unescape($photo->height ?? 0); + $data['width'] = (int)XML::unescape($photo->width ?? 0); + $data['description'] = XML::unescape($photo->text ?? ''); + + Post\Media::insert($data); } /** @@ -2847,13 +2691,18 @@ class Diaspora } } - $body = Markdown::toBBCode($text); + $raw_body = $body = Markdown::toBBCode($text); $datarray = []; + $datarray["guid"] = $guid; + $datarray["uri"] = $datarray["thr-parent"] = self::getUriFromGuid($author, $guid); + $datarray['uri-id'] = ItemURI::insert(['uri' => $datarray['uri'], 'guid' => $datarray['guid']]); + // Attach embedded pictures to the body if ($data->photo) { foreach ($data->photo as $photo) { + self::storePhotoAsMedia($datarray['uri-id'], $photo); $body = "[img]".XML::unescape($photo->remote_photo_path). XML::unescape($photo->remote_photo_name)."[/img]\n".$body; } @@ -2887,15 +2736,12 @@ class Diaspora $datarray["owner-link"] = $datarray["author-link"]; $datarray["owner-id"] = $datarray["author-id"]; - $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; $datarray["protocol"] = Conversation::PARCEL_DIASPORA; $datarray["source"] = $xml; + $datarray["direction"] = $fetched ? Conversation::PULL : Conversation::PUSH; if ($fetched) { $datarray["post-type"] = Item::PT_FETCHED; @@ -2904,12 +2750,13 @@ class Diaspora } $datarray["body"] = self::replacePeopleGuid($body, $contact["url"]); + $datarray["raw-body"] = self::replacePeopleGuid($raw_body, $contact["url"]); self::storeMentions($datarray['uri-id'], $text); Tag::storeRawTagsFromBody($datarray['uri-id'], $datarray["body"]); if (!$fetched && !self::isSolicitedMessage($datarray["uri"], $datarray['uri-id'], $author, $body)) { - DBA::delete('uri-id', ['uri' => $datarray['uri']]); + DBA::delete('item-uri', ['uri' => $datarray['uri']]); return false; } @@ -2930,6 +2777,12 @@ class Diaspora } self::fetchGuid($datarray); + + if (Item::isTooOld($datarray)) { + Logger::info('Status is too old', ['created' => $datarray['created'], 'uid' => $datarray['uid'], 'guid' => $datarray['guid']]); + return false; + } + $message_id = Item::insert($datarray); self::sendParticipation($contact, $datarray); @@ -3203,7 +3056,18 @@ class Diaspora $owner['uprvkey'] = $owner['prvkey']; } - $envelope = self::buildMessage($msg, $owner, $contact, $owner['uprvkey'], $contact['pubkey'], $public_batch); + // When sending content to Friendica contacts using the Diaspora protocol + // we have to fetch the public key from the fcontact. + // This is due to the fact that legacy DFRN had unique keys for every contact. + $pubkey = $contact['pubkey']; + if (!empty($contact['addr'])) { + $fcontact = FContact::getByURL($contact['addr']); + if (!empty($fcontact)) { + $pubkey = $fcontact['pubkey']; + } + } + + $envelope = self::buildMessage($msg, $owner, $contact, $owner['uprvkey'], $pubkey, $public_batch); $return_code = self::transmit($owner, $contact, $envelope, $public_batch, $guid); @@ -3381,7 +3245,7 @@ class Diaspora if (!empty($reshared['guid']) && $complete) { $condition = ['guid' => $reshared['guid'], 'network' => [Protocol::DFRN, Protocol::DIASPORA]]; - $item = Item::selectFirst(['contact-id'], $condition); + $item = Post::selectFirst(['contact-id'], $condition); if (DBA::isResult($item)) { $ret = []; $ret["root_handle"] = self::handleFromContact($item["contact-id"]); @@ -3543,7 +3407,6 @@ class Diaspora } if ($item['author-link'] != $item['owner-link']) { - require_once 'mod/share.php'; $body = BBCode::getShareOpeningTag($item['author-name'], $item['author-link'], $item['author-avatar'], $item['plink'], $item['created']) . $body . '[/share]'; } @@ -3556,13 +3419,11 @@ class Diaspora $body = "### ".html_entity_decode($title)."\n\n".$body; } - if ($item["attach"]) { - $cnt = preg_match_all('/href=\"(.*?)\"(.*?)title=\"(.*?)\"/ism', $item["attach"], $matches, PREG_SET_ORDER); - if ($cnt) { - $body .= "\n".DI::l10n()->t("Attachments:")."\n"; - foreach ($matches as $mtch) { - $body .= "[".$mtch[3]."](".$mtch[1].")\n"; - } + $attachments = Post\Media::getByURIId($item['uri-id'], [Post\Media::DOCUMENT, Post\Media::TORRENT, Post\Media::UNKNOWN]); + if (!empty($attachments)) { + $body .= "\n".DI::l10n()->t("Attachments:")."\n"; + foreach ($attachments as $attachment) { + $body .= "[" . $attachment['description'] . "](" . $attachment['url'] . ")\n"; } } @@ -3661,12 +3522,12 @@ class Diaspora */ private static function constructLike(array $item, array $owner) { - $parent = Item::selectFirst(['guid', 'uri', 'parent-uri'], ['uri' => $item["thr-parent"]]); + $parent = Post::selectFirst(['guid', 'uri', 'thr-parent'], ['uri' => $item["thr-parent"]]); if (!DBA::isResult($parent)) { return false; } - $target_type = ($parent["uri"] === $parent["parent-uri"] ? "Post" : "Comment"); + $target_type = ($parent["uri"] === $parent["thr-parent"] ? "Post" : "Comment"); $positive = null; if ($item['verb'] === Activity::LIKE) { $positive = "true"; @@ -3693,7 +3554,7 @@ class Diaspora */ private static function constructAttend(array $item, array $owner) { - $parent = Item::selectFirst(['guid', 'uri', 'parent-uri'], ['uri' => $item["thr-parent"]]); + $parent = Post::selectFirst(['guid'], ['uri' => $item['thr-parent']]); if (!DBA::isResult($parent)) { return false; } @@ -3738,7 +3599,7 @@ class Diaspora return $result; } - $toplevel_item = Item::selectFirst(['guid', 'author-id', 'author-link'], ['id' => $item['parent'], 'parent' => $item['parent']]); + $toplevel_item = Post::selectFirst(['guid', 'author-id', 'author-link', 'gravity'], ['id' => $item['parent'], 'parent' => $item['parent']]); if (!DBA::isResult($toplevel_item)) { Logger::error('Missing parent conversation item', ['parent' => $item['parent']]); return false; @@ -3746,7 +3607,7 @@ class Diaspora $thread_parent_item = $toplevel_item; if ($item['thr-parent'] != $item['parent-uri']) { - $thread_parent_item = Item::selectFirst(['guid', 'author-id', 'author-link'], ['uri' => $item['thr-parent'], 'uid' => $item['uid']]); + $thread_parent_item = Post::selectFirst(['guid', 'author-id', 'author-link', 'gravity'], ['uri' => $item['thr-parent'], 'uid' => $item['uid']]); } $body = $item["body"]; @@ -3757,6 +3618,7 @@ class Diaspora // - Implicit mentions are enabled if ( $item['author-id'] != $thread_parent_item['author-id'] + && ($thread_parent_item['gravity'] != GRAVITY_PARENT) && (empty($item['uid']) || !Feature::isEnabled($item['uid'], 'explicit_mentions')) && !DI::config()->get('system', 'disable_implicit_mentions') ) { @@ -3821,51 +3683,6 @@ class Diaspora return self::buildAndTransmit($owner, $contact, $type, $message, $public_batch, $item["guid"]); } - /** - * Creates a message from a signature record entry - * - * @param array $item The item that will be exported - * @return array The message - */ - private static function messageFromSignature(array $item) - { - // Split the signed text - $signed_parts = explode(";", $item['signed_text']); - - if ($item["deleted"]) { - $message = ["author" => $item['signer'], - "target_guid" => $signed_parts[0], - "target_type" => $signed_parts[1]]; - } elseif (in_array($item["verb"], [Activity::LIKE, Activity::DISLIKE])) { - $message = ["author" => $signed_parts[4], - "guid" => $signed_parts[1], - "parent_guid" => $signed_parts[3], - "parent_type" => $signed_parts[2], - "positive" => $signed_parts[0], - "author_signature" => $item['signature'], - "parent_author_signature" => ""]; - } else { - // Remove the comment guid - $guid = array_shift($signed_parts); - - // Remove the parent guid - $parent_guid = array_shift($signed_parts); - - // Remove the handle - $handle = array_pop($signed_parts); - - $message = [ - "author" => $handle, - "guid" => $guid, - "parent_guid" => $parent_guid, - "text" => implode(";", $signed_parts), - "author_signature" => $item['signature'], - "parent_author_signature" => "" - ]; - } - return $message; - } - /** * Relays messages (like, comment, retraction) to other servers if we are the thread owner * @@ -4222,10 +4039,7 @@ class Diaspora return false; } - // This is a workaround for the behaviour of the "insert" function, see mod/item.php - $item['thr-parent'] = $item['parent-uri']; - - $parent = Item::selectFirst(['parent-uri'], ['uri' => $item['parent-uri']]); + $parent = Post::selectFirst(['parent-uri'], ['uri' => $item['thr-parent']]); if (!DBA::isResult($parent)) { return; }