*/
public static function isFollower($cid, $uid)
{
- if (self::isBlockedByUser($cid, $uid)) {
+ if (Contact\User::isBlocked($cid, $uid)) {
return false;
}
*/
public static function isSharing($cid, $uid)
{
- if (self::isBlockedByUser($cid, $uid)) {
+ if (Contact\User::isBlocked($cid, $uid)) {
return false;
}
}
}
- /**
- * 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
*
* @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]);
}
// 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;
}
$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) {
'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.
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
$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();
}
return false;
}
- if (ContactRelation::isDiscoverable($ret['url'])) {
+ if (Contact\Relation::isDiscoverable($ret['url'])) {
Worker::add(PRIORITY_LOW, 'ContactDiscovery', $ret['url']);
}
// 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;
}
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;
}
/**
- * 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
+ * Returns a random, global contact of the current node
*
- * @param string $data Probing result
- *
- * @return bool 'true' if update was successful or the server was unreachable
+ * @return string The profile URL
+ * @throws Exception
*/
- private static function updateFromNoScrape(array $data)
+ public static function getRandomUrl()
{
- // 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']);
+ $r = DBA::selectFirst('contact', ['url'], [
+ "`uid` = ? AND `network` = ? AND NOT `failed` AND `last-item` > ?",
+ 0, Protocol::DFRN, DateTimeFormat::utc('now - 1 month'),
+ ], ['order' => ['RAND()']]);
- 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;
+ if (DBA::isResult($r)) {
+ return $r['url'];
}
- $fields = ['failed' => false, 'last_contact' => DateTimeFormat::utcNow(), 'updated' => $last_updated];
- DBA::update('contact', $fields, ['nurl' => Strings::normaliseLink($data['url'])]);
+ return '';
}
}