- /**
- * @param int $uid user
- * @param integer $start optional, default 0
- * @param integer $limit optional, default 80
- * @return array
- * @throws \Friendica\Network\HTTPException\InternalServerErrorException
- */
- public static function suggestionQuery($uid, $start = 0, $limit = 80)
- {
- if (!$uid) {
- return [];
- }
-
- $network = [Protocol::DFRN, Protocol::ACTIVITYPUB];
-
- if (DI::config()->get('system', 'diaspora_enabled')) {
- $network[] = Protocol::DIASPORA;
- }
-
- if (!DI::config()->get('system', 'ostatus_disabled')) {
- $network[] = Protocol::OSTATUS;
- }
-
- $sql_network = "'" . implode("', '", $network) . "'";
-
- /// @todo This query is really slow
- // By now we cache the data for five minutes
- $r = q(
- "SELECT count(glink.gcid) as `total`, gcontact.* from gcontact
- INNER JOIN `glink` ON `glink`.`gcid` = `gcontact`.`id`
- where uid = %d and not gcontact.nurl in ( select nurl from contact where uid = %d )
- AND NOT `gcontact`.`name` IN (SELECT `name` FROM `contact` WHERE `uid` = %d)
- AND NOT `gcontact`.`id` IN (SELECT `gcid` FROM `gcign` WHERE `uid` = %d)
- AND `gcontact`.`updated` >= '%s' AND NOT `gcontact`.`hide`
- AND `gcontact`.`last_contact` >= `gcontact`.`last_failure`
- AND `gcontact`.`network` IN (%s)
- GROUP BY `glink`.`gcid` ORDER BY `gcontact`.`updated` DESC,`total` DESC LIMIT %d, %d",
- intval($uid),
- intval($uid),
- intval($uid),
- intval($uid),
- DBA::NULL_DATETIME,
- $sql_network,
- intval($start),
- intval($limit)
- );
-
- if (DBA::isResult($r) && count($r) >= ($limit -1)) {
- return $r;
- }
-
- $r2 = q(
- "SELECT gcontact.* FROM gcontact
- INNER JOIN `glink` ON `glink`.`gcid` = `gcontact`.`id`
- WHERE `glink`.`uid` = 0 AND `glink`.`cid` = 0 AND `glink`.`zcid` = 0 AND NOT `gcontact`.`nurl` IN (SELECT `nurl` FROM `contact` WHERE `uid` = %d)
- AND NOT `gcontact`.`name` IN (SELECT `name` FROM `contact` WHERE `uid` = %d)
- AND NOT `gcontact`.`id` IN (SELECT `gcid` FROM `gcign` WHERE `uid` = %d)
- AND `gcontact`.`updated` >= '%s'
- AND `gcontact`.`last_contact` >= `gcontact`.`last_failure`
- AND `gcontact`.`network` IN (%s)
- ORDER BY rand() LIMIT %d, %d",
- intval($uid),
- intval($uid),
- intval($uid),
- DBA::NULL_DATETIME,
- $sql_network,
- intval($start),
- intval($limit)
- );
-
- $list = [];
- foreach ($r2 as $suggestion) {
- $list[$suggestion['nurl']] = $suggestion;
- }
-
- foreach ($r as $suggestion) {
- $list[$suggestion['nurl']] = $suggestion;
- }
-
- while (sizeof($list) > ($limit)) {
- array_pop($list);
- }
-
- return $list;
- }
-
- /**
- * @return void
- * @throws \Friendica\Network\HTTPException\InternalServerErrorException
- */
- public static function updateSuggestions()
- {
- $done = [];
-
- /// @TODO Check if it is really neccessary to poll the own server
- PortableContact::loadWorker(0, 0, 0, DI::baseUrl() . '/poco');
-
- $done[] = DI::baseUrl() . '/poco';
-
- if (strlen(DI::config()->get('system', 'directory'))) {
- $x = Network::fetchUrl(Search::getGlobalDirectory() . '/pubsites');
- if (!empty($x)) {
- $j = json_decode($x);
- if (!empty($j->entries)) {
- foreach ($j->entries as $entry) {
- GServer::check($entry->url);
-
- $url = $entry->url . '/poco';
- if (!in_array($url, $done)) {
- PortableContact::loadWorker(0, 0, 0, $url);
- $done[] = $url;
- }
- }
- }
- }
- }
-
- // Query your contacts from Friendica and Redmatrix/Hubzilla for their contacts
- $contacts = DBA::p("SELECT DISTINCT(`poco`) AS `poco` FROM `contact` WHERE `network` IN (?, ?)", Protocol::DFRN, Protocol::DIASPORA);
- while ($contact = DBA::fetch($contacts)) {
- $base = substr($contact['poco'], 0, strrpos($contact['poco'], '/'));
- if (!in_array($base, $done)) {
- PortableContact::loadWorker(0, 0, 0, $base);
- }
- }
- }
-
- /**
- * Removes unwanted parts from a contact url
- *
- * @param string $url Contact url
- *
- * @return string Contact url with the wanted parts
- * @throws Exception
- */
- public static function cleanContactUrl($url)
- {
- $parts = parse_url($url);
-
- if (empty($parts['scheme']) || empty($parts['host'])) {
- return $url;
- }
-
- $new_url = $parts['scheme'] . '://' . $parts['host'];
-
- if (!empty($parts['port'])) {
- $new_url .= ':' . $parts['port'];
- }
-
- if (!empty($parts['path'])) {
- $new_url .= $parts['path'];
- }
-
- if ($new_url != $url) {
- Logger::info('Cleaned contact url', ['url' => $url, 'new_url' => $new_url, 'callstack' => System::callstack()]);
- }
-
- return $new_url;
- }
-
- /**
- * Fetch the gcontact id, add an entry if not existed
- *
- * @param array $contact contact array
- *
- * @return bool|int Returns false if not found, integer if contact was found
- * @throws \Friendica\Network\HTTPException\InternalServerErrorException
- * @throws \ImagickException
- */
- public static function getId($contact)
- {
- $gcontact_id = 0;
-
- if (empty($contact['network'])) {
- Logger::notice('Empty network', ['url' => $contact['url'], 'callstack' => System::callstack()]);
- return false;
- }
-
- if (in_array($contact['network'], [Protocol::PHANTOM])) {
- Logger::notice('Invalid network', ['url' => $contact['url'], 'callstack' => System::callstack()]);
- return false;
- }
-
- if ($contact['network'] == Protocol::STATUSNET) {
- $contact['network'] = Protocol::OSTATUS;
- }
-
- // All new contacts are hidden by default
- if (!isset($contact['hide'])) {
- $contact['hide'] = true;
- }
-
- // Remove unwanted parts from the contact url (e.g. '?zrl=...')
- if (in_array($contact['network'], Protocol::FEDERATED)) {
- $contact['url'] = self::cleanContactUrl($contact['url']);
- }
-
- DBA::lock('gcontact');
- $fields = ['id', 'last_contact', 'last_failure', 'network'];
- $gcnt = DBA::selectFirst('gcontact', $fields, ['nurl' => Strings::normaliseLink($contact['url'])]);
- if (DBA::isResult($gcnt)) {
- $gcontact_id = $gcnt['id'];
- } else {
- $contact['location'] = $contact['location'] ?? '';
- $contact['about'] = $contact['about'] ?? '';
- $contact['generation'] = $contact['generation'] ?? 0;
-
- $fields = ['name' => $contact['name'], 'nick' => $contact['nick'] ?? '', 'addr' => $contact['addr'] ?? '', 'network' => $contact['network'],
- 'url' => $contact['url'], 'nurl' => Strings::normaliseLink($contact['url']), 'photo' => $contact['photo'],
- 'created' => DateTimeFormat::utcNow(), 'updated' => DateTimeFormat::utcNow(), 'location' => $contact['location'],
- 'about' => $contact['about'], 'hide' => $contact['hide'], 'generation' => $contact['generation']];
-
- DBA::insert('gcontact', $fields);
-
- $condition = ['nurl' => Strings::normaliseLink($contact['url'])];
- $cnt = DBA::selectFirst('gcontact', ['id', 'network'], $condition, ['order' => ['id']]);
- if (DBA::isResult($cnt)) {
- $gcontact_id = $cnt['id'];
- }
- }
- DBA::unlock();
-
- return $gcontact_id;
- }
-
- /**
- * Updates the gcontact table from a given array
- *
- * @param array $contact contact array
- *
- * @return bool|int Returns false if not found, integer if contact was found
- * @throws \Friendica\Network\HTTPException\InternalServerErrorException
- * @throws \ImagickException
- */
- public static function update($contact)
- {
- // Check for invalid "contact-type" value
- if (isset($contact['contact-type']) && (intval($contact['contact-type']) < 0)) {
- $contact['contact-type'] = 0;
- }
-
- /// @todo update contact table as well
-
- $gcontact_id = self::getId($contact);
-
- if (!$gcontact_id) {
- return false;
- }
-
- $public_contact = DBA::selectFirst('gcontact', [
- 'name', 'nick', 'photo', 'location', 'about', 'addr', 'generation', 'birthday', 'keywords',
- 'contact-type', 'hide', 'nsfw', 'network', 'alias', 'notify', 'server_url', 'connect', 'updated', 'url'
- ], ['id' => $gcontact_id]);
-
- if (!DBA::isResult($public_contact)) {
- return false;
- }
-
- // Get all field names
- $fields = [];
- foreach ($public_contact as $field => $data) {
- $fields[$field] = $data;
- }
-
- unset($fields['url']);
- unset($fields['updated']);
- unset($fields['hide']);
-
- // Bugfix: We had an error in the storing of keywords which lead to the "0"
- // This value is still transmitted via poco.
- if (isset($contact['keywords']) && ($contact['keywords'] == '0')) {
- unset($contact['keywords']);
- }
-
- if (isset($public_contact['keywords']) && ($public_contact['keywords'] == '0')) {
- $public_contact['keywords'] = '';
- }
-
- // assign all unassigned fields from the database entry
- foreach ($fields as $field => $data) {
- if (empty($contact[$field])) {
- $contact[$field] = $public_contact[$field];
- }
- }
-
- if (!isset($contact['hide'])) {
- $contact['hide'] = $public_contact['hide'];
- }
-
- $fields['hide'] = $public_contact['hide'];
-
- if ($contact['network'] == Protocol::STATUSNET) {
- $contact['network'] = Protocol::OSTATUS;
- }
-
- if (!isset($contact['updated'])) {
- $contact['updated'] = DateTimeFormat::utcNow();
- }
-
- if ($contact['network'] == Protocol::TWITTER) {
- $contact['server_url'] = 'http://twitter.com';
- }
-
- if (empty($contact['server_url'])) {
- $data = Probe::uri($contact['url']);
- if ($data['network'] != Protocol::PHANTOM) {
- $contact['server_url'] = $data['baseurl'];
- }
- } else {
- $contact['server_url'] = Strings::normaliseLink($contact['server_url']);
- }
-
- if (empty($contact['addr']) && !empty($contact['server_url']) && !empty($contact['nick'])) {
- $hostname = str_replace('http://', '', $contact['server_url']);
- $contact['addr'] = $contact['nick'] . '@' . $hostname;
- }
-
- // Check if any field changed
- $update = false;
- unset($fields['generation']);
-
- if ((($contact['generation'] > 0) && ($contact['generation'] <= $public_contact['generation'])) || ($public_contact['generation'] == 0)) {
- foreach ($fields as $field => $data) {
- if ($contact[$field] != $public_contact[$field]) {
- Logger::debug('Difference found.', ['contact' => $contact['url'], 'field' => $field, 'new' => $contact[$field], 'old' => $public_contact[$field]]);
- $update = true;
- }
- }
-
- if ($contact['generation'] < $public_contact['generation']) {
- Logger::debug('Difference found.', ['contact' => $contact['url'], 'field' => 'generation', 'new' => $contact['generation'], 'old' => $public_contact['generation']]);
- $update = true;
- }
- }
-
- if ($update) {
- Logger::debug('Update gcontact.', ['contact' => $contact['url']]);
- $condition = ["`nurl` = ? AND (`generation` = 0 OR `generation` >= ?)",
- Strings::normaliseLink($contact['url']), $contact['generation']];
- $contact['updated'] = DateTimeFormat::utc($contact['updated']);
-
- $updated = [
- 'photo' => $contact['photo'], 'name' => $contact['name'],
- 'nick' => $contact['nick'], 'addr' => $contact['addr'],
- 'network' => $contact['network'], 'birthday' => $contact['birthday'],
- 'keywords' => $contact['keywords'],
- 'hide' => $contact['hide'], 'nsfw' => $contact['nsfw'],
- 'contact-type' => $contact['contact-type'], 'alias' => $contact['alias'],
- 'notify' => $contact['notify'], 'url' => $contact['url'],
- 'location' => $contact['location'], 'about' => $contact['about'],
- 'generation' => $contact['generation'], 'updated' => $contact['updated'],
- 'server_url' => $contact['server_url'], 'connect' => $contact['connect']
- ];
-
- DBA::update('gcontact', $updated, $condition, $fields);
- }
-
- return $gcontact_id;
- }
-
- /**
- * Set the last date that the contact had posted something
- *
- * @param string $data Probing result
- * @param bool $force force updating
- */
- public static function setLastUpdate(array $data, bool $force = false)
- {
- // Fetch the global contact
- $gcontact = DBA::selectFirst('gcontact', ['created', 'updated', 'last_contact', 'last_failure'],
- ['nurl' => Strings::normaliseLink($data['url'])]);
- if (!DBA::isResult($gcontact)) {
- return;
- }
-
- if (!$force && !GServer::updateNeeded($gcontact['created'], $gcontact['updated'], $gcontact['last_failure'], $gcontact['last_contact'])) {
- Logger::info("Don't update profile", ['url' => $data['url'], 'updated' => $gcontact['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 = Network::curl($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 = ['last_contact' => DateTimeFormat::utcNow(), 'updated' => $noscrape['updated']];
- DBA::update('gcontact', $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 = ['last_failure' => DateTimeFormat::utcNow()];
- DBA::update('gcontact', $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 = ['last_contact' => DateTimeFormat::utcNow(), 'updated' => $last_updated];
- DBA::update('gcontact', $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 = Network::curl($data['poll']);
- if (!$curlResult->isSuccess()) {
- $fields = ['last_failure' => DateTimeFormat::utcNow()];
- DBA::update('gcontact', $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 = ['last_contact' => DateTimeFormat::utcNow(), 'updated' => $last_updated];
- DBA::update('gcontact', $fields, ['nurl' => Strings::normaliseLink($data['url'])]);
- }
- /**
- * Updates the gcontact entry from a given public contact id
- *
- * @param integer $cid contact id
- * @return void
- * @throws \Friendica\Network\HTTPException\InternalServerErrorException
- * @throws \ImagickException
- */
- public static function updateFromPublicContactID($cid)
- {
- self::updateFromPublicContact(['id' => $cid]);
- }
-
- /**
- * Updates the gcontact entry from a given public contact url
- *
- * @param string $url contact url
- * @return integer gcontact id
- * @throws \Friendica\Network\HTTPException\InternalServerErrorException
- * @throws \ImagickException
- */
- public static function updateFromPublicContactURL($url)
- {
- return self::updateFromPublicContact(['nurl' => Strings::normaliseLink($url)]);
- }
-
- /**
- * Helper function for updateFromPublicContactID and updateFromPublicContactURL
- *
- * @param array $condition contact condition
- * @return integer gcontact id
- * @throws \Friendica\Network\HTTPException\InternalServerErrorException
- * @throws \ImagickException
- */
- private static function updateFromPublicContact($condition)
- {
- $fields = ['name', 'nick', 'url', 'nurl', 'location', 'about', 'keywords',
- 'bd', 'contact-type', 'network', 'addr', 'notify', 'alias', 'archive', 'term-date',
- 'created', 'updated', 'avatar', 'success_update', 'failure_update', 'forum', 'prv',
- 'baseurl', 'sensitive', 'unsearchable'];
-
- $contact = DBA::selectFirst('contact', $fields, array_merge($condition, ['uid' => 0, 'network' => Protocol::FEDERATED]));
- if (!DBA::isResult($contact)) {
- return 0;
- }
-
- $fields = ['name', 'nick', 'url', 'nurl', 'location', 'about', 'keywords', 'generation',
- 'birthday', 'contact-type', 'network', 'addr', 'notify', 'alias', 'archived', 'archive_date',
- 'created', 'updated', 'photo', 'last_contact', 'last_failure', 'community', 'connect',
- 'server_url', 'nsfw', 'hide', 'id'];
-
- $old_gcontact = DBA::selectFirst('gcontact', $fields, ['nurl' => $contact['nurl']]);
- $do_insert = !DBA::isResult($old_gcontact);
- if ($do_insert) {
- $old_gcontact = [];
- }
-
- $gcontact = [];
-
- // These fields are identical in both contact and gcontact
- $fields = ['name', 'nick', 'url', 'nurl', 'location', 'about', 'keywords',
- 'contact-type', 'network', 'addr', 'notify', 'alias', 'created', 'updated'];
-
- foreach ($fields as $field) {
- $gcontact[$field] = $contact[$field];
- }
-
- // These fields are having different names but the same content
- $gcontact['server_url'] = $contact['baseurl'] ?? ''; // "baseurl" can be null, "server_url" not
- $gcontact['nsfw'] = $contact['sensitive'];
- $gcontact['hide'] = $contact['unsearchable'];
- $gcontact['archived'] = $contact['archive'];
- $gcontact['archive_date'] = $contact['term-date'];
- $gcontact['birthday'] = $contact['bd'];
- $gcontact['photo'] = $contact['avatar'];
- $gcontact['last_contact'] = $contact['success_update'];
- $gcontact['last_failure'] = $contact['failure_update'];
- $gcontact['community'] = ($contact['forum'] || $contact['prv']);
-
- foreach (['last_contact', 'last_failure', 'updated'] as $field) {
- if (!empty($old_gcontact[$field]) && ($old_gcontact[$field] >= $gcontact[$field])) {
- unset($gcontact[$field]);
- }
- }
-
- if (!$gcontact['archived']) {
- $gcontact['archive_date'] = DBA::NULL_DATETIME;
- }
-
- if (!empty($old_gcontact['created']) && ($old_gcontact['created'] > DBA::NULL_DATETIME)
- && ($old_gcontact['created'] <= $gcontact['created'])) {
- unset($gcontact['created']);
- }
-
- if (empty($gcontact['birthday']) && ($gcontact['birthday'] <= DBA::NULL_DATETIME)) {
- unset($gcontact['birthday']);
- }
-
- if (empty($old_gcontact['generation']) || ($old_gcontact['generation'] > 2)) {
- $gcontact['generation'] = 2; // We fetched the data directly from the other server
- }
-
- if (!$do_insert) {
- DBA::update('gcontact', $gcontact, ['nurl' => $contact['nurl']], $old_gcontact);
- return $old_gcontact['id'];
- } elseif (!$gcontact['archived']) {
- DBA::insert('gcontact', $gcontact);
- return DBA::lastInsertId();
- }
- }
-
- /**
- * Updates the gcontact entry from probe
- *
- * @param string $url profile link
- * @param boolean $force Optional forcing of network probing (otherwise we use the cached data)
- *
- * @return boolean 'true' when contact had been updated
- *
- * @throws \Friendica\Network\HTTPException\InternalServerErrorException
- * @throws \ImagickException
- */
- public static function updateFromProbe($url, $force = false)
- {
- $data = Probe::uri($url, $force);
-
- if (in_array($data['network'], [Protocol::PHANTOM])) {
- $fields = ['last_failure' => DateTimeFormat::utcNow()];
- DBA::update('gcontact', $fields, ['nurl' => Strings::normaliseLink($url)]);
- Logger::info('Invalid network for contact', ['url' => $data['url'], 'callstack' => System::callstack()]);
- return false;
- }
-
- $data['server_url'] = $data['baseurl'];
-
- self::update($data);
-
- // Set the date of the latest post
- self::setLastUpdate($data, $force);
-
- return true;
- }
-
- /**
- * Update the gcontact entry for a given user id
- *
- * @param int $uid User ID
- * @return bool
- * @throws \Friendica\Network\HTTPException\InternalServerErrorException
- * @throws \ImagickException
- */
- public static function updateForUser($uid)
- {
- $profile = Profile::getByUID($uid);
- if (empty($profile)) {
- Logger::error('Cannot find profile', ['uid' => $uid]);
- return false;
- }
-
- $user = User::getOwnerDataById($uid);
- if (empty($user)) {
- Logger::error('Cannot find user', ['uid' => $uid]);
- return false;
- }
-
- $userdata = array_merge($profile, $user);
-
- $location = Profile::formatLocation(
- ['locality' => $userdata['locality'], 'region' => $userdata['region'], 'country-name' => $userdata['country-name']]
- );
-
- $gcontact = ['name' => $userdata['name'], 'location' => $location, 'about' => $userdata['about'],
- 'keywords' => $userdata['pub_keywords'],
- 'birthday' => $userdata['dob'], 'photo' => $userdata['photo'],
- "notify" => $userdata['notify'], 'url' => $userdata['url'],
- "hide" => !$userdata['net-publish'],
- 'nick' => $userdata['nickname'], 'addr' => $userdata['addr'],
- "connect" => $userdata['addr'], "server_url" => DI::baseUrl(),
- "generation" => 1, 'network' => Protocol::DFRN];
-
- self::update($gcontact);
- }
-
- /**
- * Get the basepath for a given contact link
- *
- * @param string $url The gcontact link
- * @param boolean $dont_update Don't update the contact
- *
- * @return string basepath
- * @throws \Friendica\Network\HTTPException\InternalServerErrorException
- * @throws \ImagickException
- */
- public static function getBasepath($url, $dont_update = false)
- {
- $gcontact = DBA::selectFirst('gcontact', ['server_url'], ['nurl' => Strings::normaliseLink($url)]);
- if (!empty($gcontact['server_url'])) {
- return $gcontact['server_url'];
- } elseif ($dont_update) {
- return '';
- }
-
- self::updateFromProbe($url, true);
-
- // Fetch the result
- $gcontact = DBA::selectFirst('gcontact', ['server_url'], ['nurl' => Strings::normaliseLink($url)]);
- if (empty($gcontact['server_url'])) {
- Logger::info('No baseurl for gcontact', ['url' => $url]);
- return '';
- }
-
- Logger::info('Found baseurl for gcontact', ['url' => $url, 'baseurl' => $gcontact['server_url']]);
- return $gcontact['server_url'];
- }
-
- /**
- * Fetches users of given GNU Social server
- *
- * If the "Statistics" addon is enabled (See http://gstools.org/ for details) we query user data with this.
- *
- * @param string $server Server address
- * @return bool
- * @throws \Friendica\Network\HTTPException\InternalServerErrorException
- * @throws \ImagickException
- */
- public static function fetchGsUsers($server)
- {
- Logger::info('Fetching users from GNU Social server', ['server' => $server]);
-
- $url = $server . '/main/statistics';
-
- $curlResult = Network::curl($url);
- if (!$curlResult->isSuccess()) {
- return false;
- }
-
- $statistics = json_decode($curlResult->getBody());
-
- if (!empty($statistics->config->instance_address)) {
- if (!empty($statistics->config->instance_with_ssl)) {
- $server = 'https://';
- } else {
- $server = 'http://';
- }
-
- $server .= $statistics->config->instance_address;
-
- $hostname = $statistics->config->instance_address;
- } elseif (!empty($statistics->instance_address)) {
- if (!empty($statistics->instance_with_ssl)) {
- $server = 'https://';
- } else {
- $server = 'http://';
- }
-
- $server .= $statistics->instance_address;
-
- $hostname = $statistics->instance_address;
- }
-
- if (!empty($statistics->users)) {
- foreach ($statistics->users as $nick => $user) {
- $profile_url = $server . '/' . $user->nickname;
-
- $contact = ['url' => $profile_url,
- 'name' => $user->fullname,
- 'addr' => $user->nickname . '@' . $hostname,
- 'nick' => $user->nickname,
- "network" => Protocol::OSTATUS,
- 'photo' => DI::baseUrl() . '/images/person-300.jpg'];
-
- if (isset($user->bio)) {
- $contact['about'] = $user->bio;
- }
-
- self::getId($contact);
- }
- }
- }
-
- /**
- * Asking GNU Social server on a regular base for their user data
- *
- * @return void
- * @throws \Friendica\Network\HTTPException\InternalServerErrorException
- * @throws \ImagickException
- */
- public static function discoverGsUsers()
- {
- $requery_days = intval(DI::config()->get('system', 'poco_requery_days'));
-
- $last_update = date("c", time() - (60 * 60 * 24 * $requery_days));
-
- $r = DBA::select('gserver', ['nurl', 'url'], [
- '`network` = ?
- AND `last_contact` >= `last_failure`
- AND `last_poco_query` < ?',
- Protocol::OSTATUS,
- $last_update
- ], [
- 'limit' => 5,
- 'order' => ['RAND()']
- ]);
-
- if (!DBA::isResult($r)) {
- return;
- }
-
- foreach ($r as $server) {
- self::fetchGsUsers($server['url']);
- DBA::update('gserver', ['last_poco_query' => DateTimeFormat::utcNow()], ['nurl' => $server['nurl']]);
- }
- }
-
- /**
- * Fetches the followers of a given profile and adds them
- *
- * @param string $url URL of a profile
- * @return void
- */
- public static function discoverFollowers(string $url)
- {
- $gcontact = DBA::selectFirst('gcontact', ['id', 'last_discovery'], ['nurl' => Strings::normaliseLink(($url))]);
- if (!DBA::isResult($gcontact)) {
- return;
- }
-
- if ($gcontact['last_discovery'] > DateTimeFormat::utc('now - 1 month')) {
- Logger::info('Last discovery was less then a month before.', ['url' => $url, 'discovery' => $gcontact['last_discovery']]);
- return;
- }
-
- $gcid = $gcontact['id'];
-
- $apcontact = APContact::getByURL($url);
-
- if (!empty($apcontact['followers']) && is_string($apcontact['followers'])) {
- $followers = ActivityPub::fetchItems($apcontact['followers']);
- } else {
- $followers = [];
- }
-
- if (!empty($apcontact['following']) && is_string($apcontact['following'])) {
- $followings = ActivityPub::fetchItems($apcontact['following']);
- } else {
- $followings = [];
- }
-
- if (!empty($followers) || !empty($followings)) {
- if (!empty($followers)) {
- // Clear the follower list, since it will be recreated in the next step
- DBA::update('gfollower', ['deleted' => true], ['gcid' => $gcid]);
- }
-
- $contacts = [];
- foreach (array_merge($followers, $followings) as $contact) {
- if (is_string($contact)) {
- $contacts[] = $contact;
- } elseif (!empty($contact['url']) && is_string($contact['url'])) {
- $contacts[] = $contact['url'];
- }
- }
- $contacts = array_unique($contacts);
-
- Logger::info('Discover AP contacts', ['url' => $url, 'contacts' => count($contacts)]);
- foreach ($contacts as $contact) {
- $gcontact = DBA::selectFirst('gcontact', ['id'], ['nurl' => Strings::normaliseLink(($contact))]);
- if (DBA::isResult($gcontact)) {
- $fields = [];
- if (in_array($contact, $followers)) {
- $fields = ['gcid' => $gcid, 'follower-gcid' => $gcontact['id']];
- } elseif (in_array($contact, $followings)) {
- $fields = ['gcid' => $gcontact['id'], 'follower-gcid' => $gcid];
- }
-
- if (!empty($fields)) {
- Logger::info('Set relation between contacts', $fields);
- DBA::update('gfollower', ['deleted' => false], $fields, true);
- continue;
- }
- }
-
- if (!Network::isUrlBlocked($contact)) {
- Logger::info('Discover new AP contact', ['url' => $contact]);
- Worker::add(PRIORITY_LOW, 'UpdateGContact', $contact);
- } else {
- Logger::info('No discovery, the URL is blocked.', ['url' => $contact]);
- }
- }
- if (!empty($followers)) {
- // Delete all followers that aren't undeleted
- DBA::delete('gfollower', ['gcid' => $gcid, 'deleted' => true]);
- }
-
- DBA::update('gcontact', ['last_discovery' => DateTimeFormat::utcNow()], ['id' => $gcid]);
- Logger::info('AP contacts discovery finished, last discovery set', ['url' => $url]);
- return;
- }
-
- $data = Probe::uri($url);
- if (empty($data['poco'])) {
- return;
- }
-
- $curlResult = Network::curl($data['poco']);
- if (!$curlResult->isSuccess()) {
- return;
- }
- $poco = json_decode($curlResult->getBody(), true);
- if (empty($poco['entry'])) {
- return;
- }
-
- Logger::info('PoCo Discovery started', ['url' => $url, 'contacts' => count($poco['entry'])]);
-
- foreach ($poco['entry'] as $entries) {
- if (!empty($entries['urls'])) {
- foreach ($entries['urls'] as $entry) {
- if ($entry['type'] == 'profile') {
- if (DBA::exists('gcontact', ['nurl' => Strings::normaliseLink(($entry['value']))])) {
- continue;
- }
- if (!Network::isUrlBlocked($entry['value'])) {
- Logger::info('Discover new PoCo contact', ['url' => $entry['value']]);
- Worker::add(PRIORITY_LOW, 'UpdateGContact', $entry['value']);
- } else {
- Logger::info('No discovery, the URL is blocked.', ['url' => $entry['value']]);
- }
- }
- }
- }
- }
-
- DBA::update('gcontact', ['last_discovery' => DateTimeFormat::utcNow()], ['id' => $gcid]);
- Logger::info('PoCo Discovery finished', ['url' => $url]);
- }
-