X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FModel%2FContact.php;h=18f0b020e26a426d8a2ea64a75755d20187c0586;hb=187dbc09acb26b4fc0119de5feb63702f7c0bfc4;hp=325183e10b0d0f3c85af70b9daf7119b4f485052;hpb=bb1517a74c6252a89812929e98910d9e293664a9;p=friendica.git diff --git a/src/Model/Contact.php b/src/Model/Contact.php index 325183e10b..18f0b020e2 100644 --- a/src/Model/Contact.php +++ b/src/Model/Contact.php @@ -306,7 +306,7 @@ class Contact */ public static function isFollower($cid, $uid) { - if (self::isBlockedByUser($cid, $uid)) { + if (Contact\User::isBlocked($cid, $uid)) { return false; } @@ -352,7 +352,7 @@ class Contact */ public static function isSharing($cid, $uid) { - if (self::isBlockedByUser($cid, $uid)) { + if (Contact\User::isBlocked($cid, $uid)) { return false; } @@ -537,179 +537,6 @@ class Contact } } - /** - * Block contact id for user id - * - * @param int $cid Either public contact id or user's contact id - * @param int $uid User ID - * @param boolean $blocked Is the contact blocked or unblocked? - * @throws \Exception - */ - public static function setBlockedForUser($cid, $uid, $blocked) - { - $cdata = self::getPublicAndUserContacID($cid, $uid); - if (empty($cdata)) { - return; - } - - if ($cdata['user'] != 0) { - DBA::update('contact', ['blocked' => $blocked], ['id' => $cdata['user'], 'pending' => false]); - } - - DBA::update('user-contact', ['blocked' => $blocked], ['cid' => $cdata['public'], 'uid' => $uid], true); - } - - /** - * Returns "block" state for contact id and user id - * - * @param int $cid Either public contact id or user's contact id - * @param int $uid User ID - * - * @return boolean is the contact id blocked for the given user? - * @throws \Exception - */ - public static function isBlockedByUser($cid, $uid) - { - $cdata = self::getPublicAndUserContacID($cid, $uid); - if (empty($cdata)) { - return; - } - - $public_blocked = false; - - if (!empty($cdata['public'])) { - $public_contact = DBA::selectFirst('user-contact', ['blocked'], ['cid' => $cdata['public'], 'uid' => $uid]); - if (DBA::isResult($public_contact)) { - $public_blocked = $public_contact['blocked']; - } - } - - $user_blocked = $public_blocked; - - if (!empty($cdata['user'])) { - $user_contact = DBA::selectFirst('contact', ['blocked'], ['id' => $cdata['user'], 'pending' => false]); - if (DBA::isResult($user_contact)) { - $user_blocked = $user_contact['blocked']; - } - } - - if ($user_blocked != $public_blocked) { - DBA::update('user-contact', ['blocked' => $user_blocked], ['cid' => $cdata['public'], 'uid' => $uid], true); - } - - return $user_blocked; - } - - /** - * Ignore contact id for user id - * - * @param int $cid Either public contact id or user's contact id - * @param int $uid User ID - * @param boolean $ignored Is the contact ignored or unignored? - * @throws \Exception - */ - public static function setIgnoredForUser($cid, $uid, $ignored) - { - $cdata = self::getPublicAndUserContacID($cid, $uid); - if (empty($cdata)) { - return; - } - - if ($cdata['user'] != 0) { - DBA::update('contact', ['readonly' => $ignored], ['id' => $cdata['user'], 'pending' => false]); - } - - DBA::update('user-contact', ['ignored' => $ignored], ['cid' => $cdata['public'], 'uid' => $uid], true); - } - - /** - * Returns "ignore" state for contact id and user id - * - * @param int $cid Either public contact id or user's contact id - * @param int $uid User ID - * - * @return boolean is the contact id ignored for the given user? - * @throws \Exception - */ - public static function isIgnoredByUser($cid, $uid) - { - $cdata = self::getPublicAndUserContacID($cid, $uid); - if (empty($cdata)) { - return; - } - - $public_ignored = false; - - if (!empty($cdata['public'])) { - $public_contact = DBA::selectFirst('user-contact', ['ignored'], ['cid' => $cdata['public'], 'uid' => $uid]); - if (DBA::isResult($public_contact)) { - $public_ignored = $public_contact['ignored']; - } - } - - $user_ignored = $public_ignored; - - if (!empty($cdata['user'])) { - $user_contact = DBA::selectFirst('contact', ['readonly'], ['id' => $cdata['user'], 'pending' => false]); - if (DBA::isResult($user_contact)) { - $user_ignored = $user_contact['readonly']; - } - } - - if ($user_ignored != $public_ignored) { - DBA::update('user-contact', ['ignored' => $user_ignored], ['cid' => $cdata['public'], 'uid' => $uid], true); - } - - return $user_ignored; - } - - /** - * Set "collapsed" for contact id and user id - * - * @param int $cid Either public contact id or user's contact id - * @param int $uid User ID - * @param boolean $collapsed are the contact's posts collapsed or uncollapsed? - * @throws \Exception - */ - public static function setCollapsedForUser($cid, $uid, $collapsed) - { - $cdata = self::getPublicAndUserContacID($cid, $uid); - if (empty($cdata)) { - return; - } - - DBA::update('user-contact', ['collapsed' => $collapsed], ['cid' => $cdata['public'], 'uid' => $uid], true); - } - - /** - * Returns "collapsed" state for contact id and user id - * - * @param int $cid Either public contact id or user's contact id - * @param int $uid User ID - * - * @return boolean is the contact id blocked for the given user? - * @throws HTTPException\InternalServerErrorException - * @throws \ImagickException - */ - public static function isCollapsedByUser($cid, $uid) - { - $cdata = self::getPublicAndUserContacID($cid, $uid); - if (empty($cdata)) { - return; - } - - $collapsed = false; - - if (!empty($cdata['public'])) { - $public_contact = DBA::selectFirst('user-contact', ['collapsed'], ['cid' => $cdata['public'], 'uid' => $uid]); - if (DBA::isResult($public_contact)) { - $collapsed = $public_contact['collapsed']; - } - } - - return $collapsed; - } - /** * Returns a list of contacts belonging in a group * @@ -1331,13 +1158,12 @@ class Contact * @param integer $uid The user id for the contact (0 = public contact) * @param boolean $update true = always update, false = never update, null = update when not found or outdated * @param array $default Default value for creating the contact when every else fails - * @param boolean $in_loop Internally used variable to prevent an endless loop * * @return integer Contact ID * @throws HTTPException\InternalServerErrorException * @throws \ImagickException */ - public static function getIdForURL($url, $uid = 0, $update = null, $default = [], $in_loop = false) + public static function getIdForURL($url, $uid = 0, $update = null, $default = []) { Logger::info('Get contact data', ['url' => $url, 'user' => $uid]); @@ -1391,11 +1217,11 @@ class Contact } // Take the default values when probing failed - if (!empty($default) && !in_array($data["network"], array_merge(Protocol::NATIVE_SUPPORT, [Protocol::PUMPIO]))) { + if (!empty($default) && (empty($data['network']) || !in_array($data["network"], array_merge(Protocol::NATIVE_SUPPORT, [Protocol::PUMPIO])))) { $data = array_merge($data, $default); } - if (empty($data) || ($data['network'] == Protocol::PHANTOM)) { + if (empty($data['network']) || ($data['network'] == Protocol::PHANTOM)) { Logger::info('No valid network found', ['url' => $url, 'data' => $data, 'callstack' => System::callstack(20)]); return 0; } @@ -1408,8 +1234,16 @@ class Contact $data['gsid'] = GServer::getID($data['baseurl']); } - if (!$contact_id && !empty($data['alias']) && ($data['alias'] != $data['url']) && !$in_loop) { - $contact_id = self::getIdForURL($data["alias"], $uid, false, $default, true); + if (!$contact_id && !empty($data['alias']) && ($data['alias'] != $data['url'])) { + $contact = self::getByURL($data['alias'], false, ['id']); + if (!empty($contact['id'])) { + $contact_id = $contact['id']; + } + } + + if ($uid == 0) { + $data['last-item'] = Probe::getLastUpdate($data); + Logger::info('Fetched last item', ['url' => $url, 'probed_url' => $data['url'], 'last-item' => $data['last-item'], 'callstack' => System::callstack(20)]); } if (!$contact_id) { @@ -1445,6 +1279,10 @@ class Contact 'readonly' => 0, 'pending' => 0]; + if (!empty($data['last-item'])) { + $fields['last-item'] = $data['last-item']; + } + $condition = ['nurl' => Strings::normaliseLink($data["url"]), 'uid' => $uid, 'deleted' => false]; // Before inserting we do check if the entry does exist now. @@ -1481,7 +1319,7 @@ class Contact self::updateFromProbe($contact_id, '', false); } } else { - $fields = ['url', 'nurl', 'addr', 'alias', 'name', 'nick', 'keywords', 'location', 'about', 'avatar-date', 'baseurl', 'gsid']; + $fields = ['url', 'nurl', 'addr', 'alias', 'name', 'nick', 'keywords', 'location', 'about', 'avatar-date', 'baseurl', 'gsid', 'last-item']; $contact = DBA::selectFirst('contact', $fields, ['id' => $contact_id]); // This condition should always be true @@ -1502,6 +1340,10 @@ class Contact $updated[$field] = ($data[$field] ?? '') ?: $contact[$field]; } + if (!empty($data['last-item']) && ($contact['last-item'] < $data['last-item'])) { + $updated['last-item'] = $data['last-item']; + } + if (($updated['addr'] != $contact['addr']) || (!empty($data['alias']) && ($data['alias'] != $contact['alias']))) { $updated['uri-date'] = DateTimeFormat::utcNow(); } @@ -2124,7 +1966,7 @@ class Contact return false; } - if (ContactRelation::isDiscoverable($ret['url'])) { + if (Contact\Relation::isDiscoverable($ret['url'])) { Worker::add(PRIORITY_LOW, 'ContactDiscovery', $ret['url']); } @@ -2588,7 +2430,7 @@ class Contact // Contact is blocked at user-level if (!empty($contact['id']) && !empty($importer['id']) && - self::isBlockedByUser($contact['id'], $importer['id'])) { + Contact\User::isBlocked($contact['id'], $importer['id'])) { return false; } @@ -2952,117 +2794,13 @@ class Contact return $contacts; } - /** - * @param int $uid user - * @param int $start optional, default 0 - * @param int $limit optional, default 80 - * @return array - */ - static public function getSuggestions(int $uid, int $start = 0, int $limit = 80) - { - $cid = self::getPublicIdByUserId($uid); - $totallimit = $start + $limit; - $contacts = []; - - Logger::info('Collecting suggestions', ['uid' => $uid, 'cid' => $cid, 'start' => $start, 'limit' => $limit]); - - $diaspora = DI::config()->get('system', 'diaspora_enabled') ? Protocol::DIASPORA : Protocol::ACTIVITYPUB; - $ostatus = !DI::config()->get('system', 'ostatus_disabled') ? Protocol::OSTATUS : Protocol::ACTIVITYPUB; - - // The query returns contacts where contacts interacted with whom the given user follows. - // Contacts who already are in the user's contact table are ignored. - $results = DBA::select('contact', [], - ["`id` IN (SELECT `cid` FROM `contact-relation` WHERE `relation-cid` IN - (SELECT `cid` FROM `contact-relation` WHERE `relation-cid` = ?) - AND NOT `cid` IN (SELECT `id` FROM `contact` WHERE `uid` = ? AND `nurl` IN - (SELECT `nurl` FROM `contact` WHERE `uid` = ? AND `rel` IN (?, ?)))) - AND NOT `hidden` AND `network` IN (?, ?, ?, ?)", - $cid, 0, $uid, Contact::FRIEND, Contact::SHARING, - Protocol::ACTIVITYPUB, Protocol::DFRN, $diaspora, $ostatus], - ['order' => ['last-item' => true], 'limit' => $totallimit] - ); - - while ($contact = DBA::fetch($results)) { - $contacts[$contact['id']] = $contact; - } - DBA::close($results); - - Logger::info('Contacts of contacts who are followed by the given user', ['uid' => $uid, 'cid' => $cid, 'count' => count($contacts)]); - - if (count($contacts) >= $totallimit) { - return array_slice($contacts, $start, $limit); - } - - // The query returns contacts where contacts interacted with whom also interacted with the given user. - // Contacts who already are in the user's contact table are ignored. - $results = DBA::select('contact', [], - ["`id` IN (SELECT `cid` FROM `contact-relation` WHERE `relation-cid` IN - (SELECT `relation-cid` FROM `contact-relation` WHERE `cid` = ?) - AND NOT `cid` IN (SELECT `id` FROM `contact` WHERE `uid` = ? AND `nurl` IN - (SELECT `nurl` FROM `contact` WHERE `uid` = ? AND `rel` IN (?, ?)))) - AND NOT `hidden` AND `network` IN (?, ?, ?, ?)", - $cid, 0, $uid, Contact::FRIEND, Contact::SHARING, - Protocol::ACTIVITYPUB, Protocol::DFRN, $diaspora, $ostatus], - ['order' => ['last-item' => true], 'limit' => $totallimit] - ); - - while ($contact = DBA::fetch($results)) { - $contacts[$contact['id']] = $contact; - } - DBA::close($results); - - Logger::info('Contacts of contacts who are following the given user', ['uid' => $uid, 'cid' => $cid, 'count' => count($contacts)]); - - if (count($contacts) >= $totallimit) { - return array_slice($contacts, $start, $limit); - } - - // The query returns contacts that follow the given user but aren't followed by that user. - $results = DBA::select('contact', [], - ["`nurl` IN (SELECT `nurl` FROM `contact` WHERE `uid` = ? AND `rel` = ?) - AND NOT `hidden` AND `uid` = ? AND `network` IN (?, ?, ?, ?)", - $uid, Contact::FOLLOWER, 0, - Protocol::ACTIVITYPUB, Protocol::DFRN, $diaspora, $ostatus], - ['order' => ['last-item' => true], 'limit' => $totallimit] - ); - - while ($contact = DBA::fetch($results)) { - $contacts[$contact['id']] = $contact; - } - DBA::close($results); - - Logger::info('Followers that are not followed by the given user', ['uid' => $uid, 'cid' => $cid, 'count' => count($contacts)]); - - if (count($contacts) >= $totallimit) { - return array_slice($contacts, $start, $limit); - } - - // The query returns any contact that isn't followed by that user. - $results = DBA::select('contact', [], - ["NOT `nurl` IN (SELECT `nurl` FROM `contact` WHERE `uid` = ? AND `rel` IN (?, ?)) - AND NOT `hidden` AND `uid` = ? AND `network` IN (?, ?, ?, ?)", - $uid, Contact::FRIEND, Contact::SHARING, 0, - Protocol::ACTIVITYPUB, Protocol::DFRN, $diaspora, $ostatus], - ['order' => ['last-item' => true], 'limit' => $totallimit] - ); - - while ($contact = DBA::fetch($results)) { - $contacts[$contact['id']] = $contact; - } - DBA::close($results); - - Logger::info('Any contact', ['uid' => $uid, 'cid' => $cid, 'count' => count($contacts)]); - - return array_slice($contacts, $start, $limit); - } - /** * Add public contacts from an array * * @param array $urls * @return array result "count", "added" and "updated" */ - public static function addContactsByArray(array $urls) + public static function addByUrls(array $urls) { $added = 0; $updated = 0; @@ -3083,183 +2821,6 @@ class Contact return ['count' => $count, 'added' => $added, 'updated' => $updated]; } - /** - * Set the last date that the contact had posted something - * - * This functionality is currently unused - * - * @param string $data probing result - * @param bool $force force updating - */ - private static function setLastUpdate(array $data, bool $force = false) - { - $contact = self::getByURL($data['url'], false, []); - if (empty($contact)) { - return; - } - if (!$force && !GServer::updateNeeded($contact['created'], $contact['updated'], $contact['last_failure'], $contact['last_contact'])) { - Logger::info("Don't update profile", ['url' => $data['url'], 'updated' => $contact['updated']]); - return; - } - - if (self::updateFromNoScrape($data)) { - return; - } - - if (!empty($data['outbox'])) { - self::updateFromOutbox($data['outbox'], $data); - } elseif (!empty($data['poll']) && ($data['network'] == Protocol::ACTIVITYPUB)) { - self::updateFromOutbox($data['poll'], $data); - } elseif (!empty($data['poll'])) { - self::updateFromFeed($data); - } - } - - /** - * Update a global contact via the "noscrape" endpoint - * - * @param string $data Probing result - * - * @return bool 'true' if update was successful or the server was unreachable - */ - private static function updateFromNoScrape(array $data) - { - // Check the 'noscrape' endpoint when it is a Friendica server - $gserver = DBA::selectFirst('gserver', ['noscrape'], ["`nurl` = ? AND `noscrape` != ''", - Strings::normaliseLink($data['baseurl'])]); - if (!DBA::isResult($gserver)) { - return false; - } - - $curlResult = DI::httpRequest()->get($gserver['noscrape'] . '/' . $data['nick']); - - if ($curlResult->isSuccess() && !empty($curlResult->getBody())) { - $noscrape = json_decode($curlResult->getBody(), true); - if (!empty($noscrape) && !empty($noscrape['updated'])) { - $noscrape['updated'] = DateTimeFormat::utc($noscrape['updated'], DateTimeFormat::MYSQL); - $fields = ['failed' => false, 'last_contact' => DateTimeFormat::utcNow(), 'updated' => $noscrape['updated']]; - DBA::update('contact', $fields, ['nurl' => Strings::normaliseLink($data['url'])]); - return true; - } - } elseif ($curlResult->isTimeout()) { - // On a timeout return the existing value, but mark the contact as failure - $fields = ['failed' => true, 'last_failure' => DateTimeFormat::utcNow()]; - DBA::update('contact', $fields, ['nurl' => Strings::normaliseLink($data['url'])]); - return true; - } - return false; - } - - /** - * Update a global contact via an ActivityPub Outbox - * - * @param string $feed - * @param array $data Probing result - * @throws \Friendica\Network\HTTPException\InternalServerErrorException - */ - private static function updateFromOutbox(string $feed, array $data) - { - $outbox = ActivityPub::fetchContent($feed); - if (empty($outbox)) { - return; - } - - if (!empty($outbox['orderedItems'])) { - $items = $outbox['orderedItems']; - } elseif (!empty($outbox['first']['orderedItems'])) { - $items = $outbox['first']['orderedItems']; - } elseif (!empty($outbox['first']['href']) && ($outbox['first']['href'] != $feed)) { - self::updateFromOutbox($outbox['first']['href'], $data); - return; - } elseif (!empty($outbox['first'])) { - if (is_string($outbox['first']) && ($outbox['first'] != $feed)) { - self::updateFromOutbox($outbox['first'], $data); - } else { - Logger::warning('Unexpected data', ['outbox' => $outbox]); - } - return; - } else { - $items = []; - } - - $last_updated = ''; - foreach ($items as $activity) { - if (!empty($activity['published'])) { - $published = DateTimeFormat::utc($activity['published']); - } elseif (!empty($activity['object']['published'])) { - $published = DateTimeFormat::utc($activity['object']['published']); - } else { - continue; - } - - if ($last_updated < $published) { - $last_updated = $published; - } - } - - if (empty($last_updated)) { - return; - } - - $fields = ['failed' => false, 'last_contact' => DateTimeFormat::utcNow(), 'updated' => $last_updated]; - DBA::update('contact', $fields, ['nurl' => Strings::normaliseLink($data['url'])]); - } - - /** - * Update a global contact via an XML feed - * - * @param string $data Probing result - */ - private static function updateFromFeed(array $data) - { - // Search for the newest entry in the feed - $curlResult = DI::httpRequest()->get($data['poll']); - if (!$curlResult->isSuccess()) { - $fields = ['failed' => true, 'last_failure' => DateTimeFormat::utcNow()]; - DBA::update('contact', $fields, ['nurl' => Strings::normaliseLink($data['url'])]); - - Logger::info("Profile wasn't reachable (no feed)", ['url' => $data['url']]); - return; - } - - $doc = new DOMDocument(); - @$doc->loadXML($curlResult->getBody()); - - $xpath = new DOMXPath($doc); - $xpath->registerNamespace('atom', 'http://www.w3.org/2005/Atom'); - - $entries = $xpath->query('/atom:feed/atom:entry'); - - $last_updated = ''; - - foreach ($entries as $entry) { - $published_item = $xpath->query('atom:published/text()', $entry)->item(0); - $updated_item = $xpath->query('atom:updated/text()' , $entry)->item(0); - $published = !empty($published_item->nodeValue) ? DateTimeFormat::utc($published_item->nodeValue) : null; - $updated = !empty($updated_item->nodeValue) ? DateTimeFormat::utc($updated_item->nodeValue) : null; - - if (empty($published) || empty($updated)) { - Logger::notice('Invalid entry for XPath.', ['entry' => $entry, 'url' => $data['url']]); - continue; - } - - if ($last_updated < $published) { - $last_updated = $published; - } - - if ($last_updated < $updated) { - $last_updated = $updated; - } - } - - if (empty($last_updated)) { - return; - } - - $fields = ['failed' => false, 'last_contact' => DateTimeFormat::utcNow(), 'updated' => $last_updated]; - DBA::update('contact', $fields, ['nurl' => Strings::normaliseLink($data['url'])]); - } - /** * Returns a random, global contact of the current node *