X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FProtocol%2FDFRN.php;h=e29cfcc5ab38c7cd29989179c53da31cc0e0489e;hb=d75cd8a00a88a52e6d7bf67c91ba464c81c644c1;hp=7593711e56364289a8e288c0029af53d12c98661;hpb=ff9dc1e2918ab1182ef3a07fd5225306b4f13443;p=friendica.git diff --git a/src/Protocol/DFRN.php b/src/Protocol/DFRN.php index 7593711e56..e29cfcc5ab 100644 --- a/src/Protocol/DFRN.php +++ b/src/Protocol/DFRN.php @@ -25,6 +25,7 @@ use DOMDocument; use DOMElement; use DOMNode; use DOMXPath; +use Friendica\App; use Friendica\Content\Text\BBCode; use Friendica\Core\Logger; use Friendica\Core\Protocol; @@ -33,7 +34,6 @@ use Friendica\DI; use Friendica\Model\Contact; use Friendica\Model\Conversation; use Friendica\Model\Event; -use Friendica\Model\FContact; use Friendica\Model\GServer; use Friendica\Model\Item; use Friendica\Model\ItemURI; @@ -44,6 +44,7 @@ use Friendica\Model\Post; use Friendica\Model\Profile; use Friendica\Model\Tag; use Friendica\Model\User; +use Friendica\Network\HTTPException; use Friendica\Network\Probe; use Friendica\Util\Crypto; use Friendica\Util\DateTimeFormat; @@ -59,9 +60,9 @@ use GuzzleHttp\Psr7\Uri; class DFRN { - const TOP_LEVEL = 0; // Top level posting - const REPLY = 1; // Regular reply that is stored locally - const REPLY_RC = 2; // Reply that will be relayed + const TOP_LEVEL = 0; // Top level posting + const REPLY = 1; // Regular reply that is stored locally + const REPLY_RC = 2; // Reply that will be relayed /** * Generates an array of contact and user for DFRN imports @@ -369,8 +370,8 @@ class DFRN XML::addElement($doc, $root, 'id', DI::baseUrl() . '/profile/' . $owner['nick']); XML::addElement($doc, $root, 'title', $owner['name']); - $attributes = ['uri' => 'https://friendi.ca', 'version' => FRIENDICA_VERSION . '-' . DB_UPDATE_VERSION]; - XML::addElement($doc, $root, 'generator', FRIENDICA_PLATFORM, $attributes); + $attributes = ['uri' => 'https://friendi.ca', 'version' => App::VERSION . '-' . DB_UPDATE_VERSION]; + XML::addElement($doc, $root, 'generator', App::PLATFORM, $attributes); $attributes = ['rel' => 'license', 'href' => 'http://creativecommons.org/licenses/by/3.0/']; XML::addElement($doc, $root, 'link', '', $attributes); @@ -537,7 +538,7 @@ class DFRN XML::addElement($doc, $author, 'poco:utcOffset', DateTimeFormat::timezoneNow($profile['timezone'], 'P')); - if (trim($profile['homepage']) != '') { + if (trim($profile['homepage'])) { $urls = $doc->createElement('poco:urls'); XML::addElement($doc, $urls, 'poco:type', 'homepage'); XML::addElement($doc, $urls, 'poco:value', $profile['homepage']); @@ -545,7 +546,7 @@ class DFRN $author->appendChild($urls); } - if (trim($profile['pub_keywords']) != '') { + if (trim($profile['pub_keywords'] ?? '')) { $keywords = explode(',', $profile['pub_keywords']); foreach ($keywords as $keyword) { @@ -553,7 +554,7 @@ class DFRN } } - if (trim($profile['xmpp']) != '') { + if (trim($profile['xmpp'])) { $ims = $doc->createElement('poco:ims'); XML::addElement($doc, $ims, 'poco:type', 'xmpp'); XML::addElement($doc, $ims, 'poco:value', $profile['xmpp']); @@ -561,7 +562,7 @@ class DFRN $author->appendChild($ims); } - if (trim($profile['locality'] . $profile['region'] . $profile['country-name']) != '') { + if (trim($profile['locality'] . $profile['region'] . $profile['country-name'])) { $element = $doc->createElement('poco:address'); XML::addElement($doc, $element, 'poco:formatted', Profile::formatLocation($profile)); @@ -710,7 +711,7 @@ class DFRN */ private static function getAttachment($doc, $root, array $item) { - foreach (Post\Media::getByURIId($item['uri-id'], [Post\Media::DOCUMENT, Post\Media::TORRENT, Post\Media::UNKNOWN]) as $attachment) { + foreach (Post\Media::getByURIId($item['uri-id'], [Post\Media::DOCUMENT, Post\Media::TORRENT]) as $attachment) { $attributes = ['rel' => 'enclosure', 'href' => $attachment['url'], 'type' => $attachment['mimetype']]; @@ -747,7 +748,7 @@ class DFRN $mentioned = []; if (!$item['parent']) { - Logger::notice('Item without parent found.', ['type' => $type, 'item' => $item]); + Logger::warning('Item without parent found.', ['type' => $type, 'item' => $item]); return null; } @@ -773,7 +774,7 @@ class DFRN $entry->setAttribute("xmlns:statusnet", ActivityNamespace::STATUSNET); } - $body = Post\Media::addAttachmentsToBody($item['uri-id'], $item['body'] ?? ''); + $body = Post\Media::addAttachmentsToBody($item['uri-id'], DI::contentItem()->addSharedPost($item)); if ($item['private'] == Item::PRIVATE) { $body = Item::fixPrivatePhotos($body, $owner['uid'], $item, $cid); @@ -799,7 +800,7 @@ class DFRN $dfrnowner = self::addEntryAuthor($doc, "dfrn:owner", $item["owner-link"], $item); $entry->appendChild($dfrnowner); - if ($item['gravity'] != GRAVITY_PARENT) { + if ($item['gravity'] != Item::GRAVITY_PARENT) { $parent = Post::selectFirst(['guid', 'plink'], ['uri' => $item['thr-parent'], 'uid' => $item['uid']]); if (DBA::isResult($parent)) { $attributes = ["ref" => $item['thr-parent'], "type" => "text/html", @@ -888,7 +889,7 @@ class DFRN if ($item['object-type'] != '') { XML::addElement($doc, $entry, 'activity:object-type', $item['object-type']); - } elseif ($item['gravity'] == GRAVITY_PARENT) { + } elseif ($item['gravity'] == Item::GRAVITY_PARENT) { XML::addElement($doc, $entry, 'activity:object-type', Activity\ObjectType::NOTE); } else { XML::addElement($doc, $entry, 'activity:object-type', Activity\ObjectType::COMMENT); @@ -980,12 +981,12 @@ class DFRN } } - $fcontact = FContact::getByURL($contact['addr']); - if (empty($fcontact)) { + try { + $pubkey = DI::dsprContact()->getByAddr(WebFingerUri::fromString($contact['addr']))->pubKey; + } catch (HTTPException\NotFoundException|\InvalidArgumentException $e) { Logger::notice('Unable to find contact details for ' . $contact['id'] . ' - ' . $contact['addr']); return -22; } - $pubkey = $fcontact['pubkey']; } else { $pubkey = ''; } @@ -998,7 +999,7 @@ class DFRN $path_parts = explode('/', $parts['path']); array_pop($path_parts); $parts['path'] = implode('/', $path_parts); - $contact['batch'] = Uri::fromParts($parts); + $contact['batch'] = (string)Uri::fromParts($parts); } $dest_url = ($public_batch ? $contact['batch'] : $contact['notify']); @@ -1065,8 +1066,8 @@ class DFRN $fields = ['id', 'uid', 'url', 'network', 'avatar-date', 'avatar', 'name-date', 'uri-date', 'addr', 'name', 'nick', 'about', 'location', 'keywords', 'xmpp', 'bdyear', 'bd', 'hidden', 'contact-type']; - $condition = ["`uid` = ? AND `nurl` = ? AND `network` != ? AND NOT `pending` AND NOT `blocked`", - $importer["importer_uid"], Strings::normaliseLink($author["link"]), Protocol::STATUSNET]; + $condition = ["`uid` = ? AND `nurl` = ? AND NOT `pending` AND NOT `blocked`", + $importer["importer_uid"], Strings::normaliseLink($author["link"])]; if ($importer['account-type'] != User::ACCOUNT_TYPE_COMMUNITY) { $condition = DBA::mergeConditions($condition, ['rel' => [Contact::SHARING, Contact::FRIEND]]); @@ -1466,9 +1467,8 @@ class DFRN // update contact $old = Contact::selectFirst(['photo', 'url'], ['id' => $importer['id'], 'uid' => $importer['importer_uid']]); - if (!DBA::isResult($old)) { - Logger::notice("Query failed to execute, no result returned in " . __FUNCTION__); + Logger::warning('Existing contact had not been fetched', ['id' => $importer['id']]); return false; } @@ -1550,85 +1550,15 @@ class DFRN private static function getEntryType(array $importer, array $item): int { if ($item['thr-parent'] != $item['uri']) { - $community = false; - - if ($importer['account-type'] == User::ACCOUNT_TYPE_COMMUNITY) { - $sql_extra = ''; - $community = true; - Logger::notice("possible community action"); - } else { - $sql_extra = " AND `self` AND `wall`"; - } - // was the top-level post for this action written by somebody on this site? // Specifically, the recipient? - $parent = Post::selectFirst(['wall'], - ["`uri` = ? AND `uid` = ?" . $sql_extra, $item['thr-parent'], $importer['importer_uid']]); - - $is_a_remote_action = DBA::isResult($parent); - - if ($is_a_remote_action) { - return DFRN::REPLY_RC; + if (Post::exists(['uri' => $item['thr-parent'], 'uid' => $importer['importer_uid'], 'self' => true, 'wall' => true])) { + return self::REPLY_RC; } else { - return DFRN::REPLY; + return self::REPLY; } } else { - return DFRN::TOP_LEVEL; - } - } - - /** - * Send a "poke" - * - * @param array $item The new item record - * @param array $importer Record of the importer user mixed with contact of the content - * @return void - * @throws \Friendica\Network\HTTPException\InternalServerErrorException - * @todo set proper type-hints (array?) - */ - private static function doPoke(array $item, array $importer) - { - $verb = urldecode(substr($item['verb'], strpos($item['verb'], '#')+1)); - if (!$verb) { - return; - } - $xo = XML::parseString($item['object']); - - if (($xo->type == Activity\ObjectType::PERSON) && ($xo->id)) { - // somebody was poked/prodded. Was it me? - $Blink = ''; - foreach ($xo->link as $l) { - $atts = $l->attributes(); - switch ($atts['rel']) { - case 'alternate': - $Blink = $atts['href']; - break; - - default: - break; - } - } - - if ($Blink && Strings::compareLink($Blink, DI::baseUrl() . '/profile/' . $importer['nickname'])) { - $author = DBA::selectFirst('contact', ['id', 'name', 'thumb', 'url'], ['id' => $item['author-id']]); - - $parent = Post::selectFirst(['id'], ['uri' => $item['thr-parent'], 'uid' => $importer['importer_uid']]); - $item['parent'] = $parent['id']; - - // send a notification - DI::notify()->createFromArray( - [ - 'type' => Notification\Type::POKE, - 'otype' => Notification\ObjectType::PERSON, - 'activity' => $verb, - 'verb' => $item['verb'], - 'uid' => $importer['importer_uid'], - 'cid' => $author['id'], - 'item' => $item, - 'link' => DI::baseUrl() . '/display/' . urlencode($item['guid']), - ] - ); - } + return self::TOP_LEVEL; } } @@ -1638,16 +1568,15 @@ class DFRN * @param int $entrytype Is it a toplevel entry, a comment or a relayed comment? * @param array $importer Record of the importer user mixed with contact of the content * @param array $item the new item record - * @param bool $is_like Is the verb a "like"? * * @return bool Should the processing of the entries be continued? * @throws \Friendica\Network\HTTPException\InternalServerErrorException */ - private static function processVerbs(int $entrytype, array $importer, array &$item, bool &$is_like) + private static function processVerbs(int $entrytype, array $importer, array &$item) { Logger::info('Process verb ' . $item['verb'] . ' and object-type ' . $item['object-type'] . ' for entrytype ' . $entrytype); - if (($entrytype == DFRN::TOP_LEVEL) && !empty($importer['id'])) { + if (($entrytype == self::TOP_LEVEL) && !empty($importer['id'])) { // The filling of the the "contact" variable is done for legcy reasons // The functions below are partly used by ostatus.php as well - where we have this variable $contact = Contact::selectFirst([], ['id' => $importer['id']]); @@ -1684,22 +1613,21 @@ class DFRN || ($item['verb'] == Activity::ATTENDMAYBE) || ($item['verb'] == Activity::ANNOUNCE) ) { - $is_like = true; - $item['gravity'] = GRAVITY_ACTIVITY; + $item['gravity'] = Item::GRAVITY_ACTIVITY; // only one like or dislike per person // split into two queries for performance issues $condition = [ - 'uid' => $item['uid'], - 'author-id' => $item['author-id'], - 'gravity' => GRAVITY_ACTIVITY, - 'verb' => $item['verb'], + 'uid' => $item['uid'], + 'author-id' => $item['author-id'], + 'gravity' => Item::GRAVITY_ACTIVITY, + 'verb' => $item['verb'], 'parent-uri' => $item['thr-parent'], ]; if (Post::exists($condition)) { return false; } - $condition = ['uid' => $item['uid'], 'author-id' => $item['author-id'], 'gravity' => GRAVITY_ACTIVITY, + $condition = ['uid' => $item['uid'], 'author-id' => $item['author-id'], 'gravity' => Item::GRAVITY_ACTIVITY, 'verb' => $item['verb'], 'thr-parent' => $item['thr-parent']]; if (Post::exists($condition)) { return false; @@ -1710,8 +1638,6 @@ class DFRN $item['owner-link'] = $item['author-link']; $item['owner-avatar'] = $item['author-avatar']; $item['owner-id'] = $item['author-id']; - } else { - $is_like = false; } if (($item['verb'] == Activity::TAG) && ($item['object-type'] == Activity\ObjectType::TAGTERM)) { @@ -1720,9 +1646,8 @@ class DFRN if ($xt->type == Activity\ObjectType::NOTE) { $item_tag = Post::selectFirst(['id', 'uri-id'], ['uri' => $xt->id, 'uid' => $importer['importer_uid']]); - if (!DBA::isResult($item_tag)) { - Logger::notice("Query failed to execute, no result returned in " . __FUNCTION__); + Logger::warning('Post had not been fetched', ['uri' => $xt->id, 'uid' => $importer['importer_uid']]); return false; } @@ -1830,8 +1755,6 @@ class DFRN $item = $header; - $item['protocol'] = $protocol; - $item['source'] = $xml; // Get the uri @@ -1915,7 +1838,11 @@ class DFRN $item['uri-id'] = ItemURI::insert(['uri' => $item['uri'], 'guid' => $item['guid']]); - $item['body'] = Item::improveSharedDataInBody($item); + $quote_uri_id = Item::getQuoteUriId($item['body'], $item['uid']); + if (!empty($quote_uri_id)) { + $item['quote-uri-id'] = $quote_uri_id; + $item['body'] = BBCode::removeSharedData($item['body']); + } Tag::storeFromBody($item['uri-id'], $item['body']); @@ -2015,7 +1942,9 @@ class DFRN $entrytype = self::getEntryType($importer, $item); // Now assign the rest of the values that depend on the type of the message - if (in_array($entrytype, [DFRN::REPLY, DFRN::REPLY_RC])) { + if (in_array($entrytype, [self::REPLY, self::REPLY_RC])) { + $item['gravity'] = Item::GRAVITY_COMMENT; + if (!isset($item['object-type'])) { $item['object-type'] = Activity\ObjectType::COMMENT; } @@ -2037,12 +1966,11 @@ class DFRN } } - // Ensure to have the correct share data - $item = Item::addShareDataFromOriginal($item); - - if ($entrytype == DFRN::REPLY_RC) { + if ($entrytype == self::REPLY_RC) { $item['wall'] = 1; - } elseif ($entrytype == DFRN::TOP_LEVEL) { + } elseif ($entrytype == self::TOP_LEVEL) { + $item['gravity'] = Item::GRAVITY_PARENT; + if (!isset($item['object-type'])) { $item['object-type'] = Activity\ObjectType::NOTE; } @@ -2079,16 +2007,13 @@ class DFRN } } - // Need to initialize variable, otherwise E_NOTICE will happen - $is_like = false; - - if (!self::processVerbs($entrytype, $importer, $item, $is_like)) { + if (!self::processVerbs($entrytype, $importer, $item)) { Logger::info("Exiting because 'processVerbs' told us so"); return; } // This check is done here to be able to receive connection requests in "processVerbs" - if (($entrytype == DFRN::TOP_LEVEL) && $owner_unknown) { + if (($entrytype == self::TOP_LEVEL) && $owner_unknown) { Logger::info("Item won't be stored because user " . $importer['importer_uid'] . " doesn't follow " . $item['owner-link'] . "."); return; } @@ -2104,9 +2029,27 @@ class DFRN return; } - if (in_array($entrytype, [DFRN::REPLY, DFRN::REPLY_RC])) { + if (in_array($entrytype, [self::REPLY, self::REPLY_RC])) { + if (($item['uid'] != 0) && !Post::exists(['uid' => $item['uid'], 'uri' => $item['thr-parent']])) { + if (DI::pConfig()->get($item['uid'], 'system', 'accept_only_sharer') == Item::COMPLETION_NONE) { + Logger::info('Completion is set to "none", so we stop here.', ['uid' => $item['uid'], 'owner-id' => $item['owner-id'], 'author-id' => $item['author-id'], 'gravity' => $item['gravity'], 'uri' => $item['uri']]); + return; + } + if (!Contact::isSharing($item['owner-id'], $item['uid']) && !Contact::isSharing($item['author-id'], $item['uid'])) { + Logger::info('Contact is not sharing with the user', ['uid' => $item['uid'], 'owner-id' => $item['owner-id'], 'author-id' => $item['author-id'], 'gravity' => $item['gravity'], 'uri' => $item['uri']]); + return; + } + if (($item['gravity'] == Item::GRAVITY_ACTIVITY) && DI::pConfig()->get($item['uid'], 'system', 'accept_only_sharer') == Item::COMPLETION_COMMENT) { + Logger::info('Completion is set to "comment", but this is an activity. so we stop here.', ['uid' => $item['uid'], 'owner-id' => $item['owner-id'], 'author-id' => $item['author-id'], 'gravity' => $item['gravity'], 'uri' => $item['uri']]); + return; + } + Logger::debug('Post is accepted.', ['uid' => $item['uid'], 'owner-id' => $item['owner-id'], 'author-id' => $item['author-id'], 'gravity' => $item['gravity'], 'uri' => $item['uri']]); + } else { + Logger::debug('Thread parent exists.', ['uid' => $item['uid'], 'owner-id' => $item['owner-id'], 'author-id' => $item['author-id'], 'gravity' => $item['gravity'], 'uri' => $item['uri']]); + } + // Will be overwritten for sharing accounts in Item::insert - if (empty($item['post-reason']) && ($entrytype == DFRN::REPLY)) { + if (empty($item['post-reason']) && ($entrytype == self::REPLY)) { $item['post-reason'] = Item::PR_COMMENT; } @@ -2120,25 +2063,9 @@ class DFRN return true; } - } else { // $entrytype == DFRN::TOP_LEVEL - if (($importer['uid'] == 0) && ($importer['importer_uid'] != 0)) { - Logger::info("Contact " . $importer['id'] . " isn't known to user " . $importer['importer_uid'] . ". The post will be ignored."); - return; - } - if (!Strings::compareLink($item['owner-link'], $importer['url'])) { - /* - * The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery, - * but otherwise there's a possible data mixup on the sender's system. - * the tgroup delivery code called from Item::insert will correct it if it's a forum, - * but we're going to unconditionally correct it here so that the post will always be owned by our contact. - */ - Logger::info('Correcting item owner.'); - $item['owner-link'] = $importer['url']; - $item['owner-id'] = Contact::getIdForURL($importer['url'], 0); - } - - if (($importer['rel'] == Contact::FOLLOWER) && (!self::tgroupCheck($importer['importer_uid'], $item))) { - Logger::info("Contact " . $importer['id'] . " is only follower and tgroup check was negative."); + } else { // $entrytype == self::TOP_LEVEL + if (($item['uid'] != 0) && !Contact::isSharing($item['owner-id'], $item['uid']) && !Contact::isSharing($item['author-id'], $item['uid'])) { + Logger::info('Contact is not sharing with the user', ['uid' => $item['uid'], 'owner-id' => $item['owner-id'], 'author-id' => $item['author-id'], 'gravity' => $item['gravity'], 'uri' => $item['uri']]); return; } @@ -2157,11 +2084,6 @@ class DFRN if ($item['uid'] == 0) { Item::distribute($posted_id); } - - if (stristr($item['verb'], Activity::POKE)) { - $item['id'] = $posted_id; - self::doPoke($item, $importer); - } } } @@ -2202,13 +2124,13 @@ class DFRN } // When it is a starting post it has to belong to the person that wants to delete it - if (($item['gravity'] == GRAVITY_PARENT) && ($item['contact-id'] != $importer['id'])) { + if (($item['gravity'] == Item::GRAVITY_PARENT) && ($item['contact-id'] != $importer['id'])) { Logger::info('Item with URI ' . $uri . ' do not belong to contact ' . $importer['id'] . ' - ignoring deletion.'); return; } // Comments can be deleted by the thread owner or comment owner - if (($item['gravity'] != GRAVITY_PARENT) && ($item['contact-id'] != $importer['id'])) { + if (($item['gravity'] != Item::GRAVITY_PARENT) && ($item['contact-id'] != $importer['id'])) { $condition = ['id' => $item['parent'], 'contact-id' => $importer['id']]; if (!Post::exists($condition)) { Logger::info('Item with URI ' . $uri . ' was not found or must not be deleted by contact ' . $importer['id'] . ' - ignoring deletion.'); @@ -2260,10 +2182,12 @@ class DFRN $header = []; $header['uid'] = $importer['importer_uid']; $header['network'] = Protocol::DFRN; + $header['protocol'] = $protocol; $header['wall'] = 0; $header['origin'] = 0; $header['contact-id'] = $importer['id']; - $header['direction'] = $direction; + + $header = Diaspora::setDirection($header, $direction); if ($direction === Conversation::RELAY) { $header['post-reason'] = Item::PR_RELAY; @@ -2383,47 +2307,6 @@ class DFRN return Activity::POST; } - // @TODO Documentation missing - private static function tgroupCheck(int $uid, array $item): bool - { - $mention = false; - - // check that the message originated elsewhere and is a top-level post - - if ($item['wall'] || $item['origin'] || ($item['uri'] != $item['thr-parent'])) { - return false; - } - - $user = DBA::selectFirst('user', ['account-type', 'nickname'], ['uid' => $uid]); - if (!DBA::isResult($user)) { - return false; - } - - $link = Strings::normaliseLink(DI::baseUrl() . '/profile/' . $user['nickname']); - - /* - * Diaspora uses their own hardwired link URL in @-tags - * instead of the one we supply with webfinger - */ - $dlink = Strings::normaliseLink(DI::baseUrl() . '/u/' . $user['nickname']); - - $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism', $item['body'], $matches, PREG_SET_ORDER); - if ($cnt) { - foreach ($matches as $mtch) { - if (Strings::compareLink($link, $mtch[1]) || Strings::compareLink($dlink, $mtch[1])) { - $mention = true; - Logger::notice('mention found: ' . $mtch[2]); - } - } - } - - if (!$mention) { - return false; - } - - return ($user['account-type'] == User::ACCOUNT_TYPE_COMMUNITY); - } - /** * This function returns true if $update has an edited timestamp newer * than $existing, i.e. $update contains new data which should override