diff --git a/src/BaseRepository.php b/src/BaseRepository.php index 64a0d1c51..abec4c119 100644 --- a/src/BaseRepository.php +++ b/src/BaseRepository.php @@ -109,26 +109,22 @@ abstract class BaseRepository extends BaseFactory */ public function selectByBoundaries(array $condition = [], array $params = [], int $max_id = null, int $since_id = null, int $limit = self::LIMIT) { - $condition = DBA::collapseCondition($condition); + $totalCount = DBA::count(static::$table_name, $condition); $boundCondition = $condition; if (isset($max_id)) { - $boundCondition[0] .= " AND `id` < ?"; - $boundCondition[] = $max_id; + $boundCondition = DBA::mergeConditions($boundCondition, ['`id` < ?', $max_id]); } if (isset($since_id)) { - $boundCondition[0] .= " AND `id` > ?"; - $boundCondition[] = $since_id; + $boundCondition = DBA::mergeConditions($boundCondition, ['`id` > ?', $since_id]); } $params['limit'] = $limit; $models = $this->selectModels($boundCondition, $params); - $totalCount = DBA::count(static::$table_name, $condition); - return new static::$collection_class($models, $totalCount); } diff --git a/src/Database/DBA.php b/src/Database/DBA.php index f3edf52be..46bd871b4 100644 --- a/src/Database/DBA.php +++ b/src/Database/DBA.php @@ -539,7 +539,7 @@ class DBA * Returns the SQL condition string built from the provided condition array * * This function operates with two modes. - * - Supplied with a filed/value associative array, it builds simple strict + * - Supplied with a field/value associative array, it builds simple strict * equality conditions linked by AND. * - Supplied with a flat list, the first element is the condition string and * the following arguments are the values to be interpolated @@ -645,6 +645,34 @@ class DBA return $condition; } + /** + * Merges the provided conditions into a single collapsed one + * + * @param array ...$conditions One or more condition arrays + * @return array A collapsed condition + * @see DBA::collapseCondition() for the condition array formats + */ + public static function mergeConditions(array ...$conditions) + { + $conditionStrings = []; + $result = []; + + foreach ($conditions as $key => $condition) { + $condition = self::collapseCondition($condition); + + $conditionStrings[] = array_shift($condition); + // The result array holds the eventual parameter values + $result = array_merge($result, $condition); + } + + if (count($conditionStrings)) { + // We prepend the condition string at the end to form a collapsed condition array again + array_unshift($result, implode(' AND ', $conditionStrings)); + } + + return $result; + } + /** * Returns the SQL parameter string built from the provided parameter array * diff --git a/src/Model/Contact.php b/src/Model/Contact.php index 1adf24275..12ec06012 100644 --- a/src/Model/Contact.php +++ b/src/Model/Contact.php @@ -2124,7 +2124,7 @@ class Contact return false; } - if (ContactRelation::isDiscoverable($ret['url'])) { + if (Contact\Relation::isDiscoverable($ret['url'])) { Worker::add(PRIORITY_LOW, 'ContactDiscovery', $ret['url']); } diff --git a/src/Model/Contact/Relation.php b/src/Model/Contact/Relation.php new file mode 100644 index 000000000..a98f2bfd9 --- /dev/null +++ b/src/Model/Contact/Relation.php @@ -0,0 +1,538 @@ +. + * + */ + +namespace Friendica\Model\Contact; + +use Exception; +use Friendica\Core\Logger; +use Friendica\Core\Protocol; +use Friendica\Database\DBA; +use Friendica\DI; +use Friendica\Protocol\ActivityPub; +use Friendica\Util\DateTimeFormat; +use Friendica\Util\Strings; + +/** + * This class provides relationship information based on the `contact-relation` table. + * This table is directional (cid = source, relation-cid = target), references public contacts (with uid=0) and records both + * follows and the last interaction (likes/comments) on public posts. + */ +class Relation +{ + /** + * No discovery of followers/followings + */ + const DISCOVERY_NONE = 0; + /** + * Discover followers/followings of local contacts + */ + const DISCOVERY_LOCAL = 1; + /** + * Discover followers/followings of local contacts and contacts that visibly interacted on the system + */ + const DISCOVERY_INTERACTOR = 2; + /** + * Discover followers/followings of all contacts + */ + const DISCOVERY_ALL = 3; + + public static function store(int $target, int $actor, string $interaction_date) + { + if ($actor == $target) { + return; + } + + DBA::update('contact-relation', ['last-interaction' => $interaction_date], ['cid' => $target, 'relation-cid' => $actor], true); + } + + /** + * Fetches the followers of a given profile and adds them + * + * @param string $url URL of a profile + * @return void + */ + public static function discoverByUrl(string $url) + { + $contact = Contact::getByURL($url); + if (empty($contact)) { + return; + } + + if (!self::isDiscoverable($url, $contact)) { + return; + } + + $apcontact = APContact::getByURL($url, false); + + 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)) { + DBA::update('contact', ['last-discovery' => DateTimeFormat::utcNow()], ['id' => $contact['id']]); + Logger::info('The contact does not offer discoverable data', ['id' => $contact['id'], 'url' => $url, 'network' => $contact['network']]); + return; + } + + $target = $contact['id']; + + if (!empty($followers)) { + // Clear the follower list, since it will be recreated in the next step + DBA::update('contact-relation', ['follows' => false], ['cid' => $target]); + } + + $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); + + $follower_counter = 0; + $following_counter = 0; + + Logger::info('Discover contacts', ['id' => $target, 'url' => $url, 'contacts' => count($contacts)]); + foreach ($contacts as $contact) { + $actor = Contact::getIdForURL($contact); + if (!empty($actor)) { + if (in_array($contact, $followers)) { + $fields = ['cid' => $target, 'relation-cid' => $actor]; + DBA::update('contact-relation', ['follows' => true, 'follow-updated' => DateTimeFormat::utcNow()], $fields, true); + $follower_counter++; + } + + if (in_array($contact, $followings)) { + $fields = ['cid' => $actor, 'relation-cid' => $target]; + DBA::update('contact-relation', ['follows' => true, 'follow-updated' => DateTimeFormat::utcNow()], $fields, true); + $following_counter++; + } + } + } + + if (!empty($followers)) { + // Delete all followers that aren't followers anymore (and aren't interacting) + DBA::delete('contact-relation', ['cid' => $target, 'follows' => false, 'last-interaction' => DBA::NULL_DATETIME]); + } + + DBA::update('contact', ['last-discovery' => DateTimeFormat::utcNow()], ['id' => $target]); + Logger::info('Contacts discovery finished', ['id' => $target, 'url' => $url, 'follower' => $follower_counter, 'following' => $following_counter]); + return; + } + + /** + * Tests if a given contact url is discoverable + * + * @param string $url Contact url + * @param array $contact Contact array + * @return boolean True if contact is discoverable + */ + public static function isDiscoverable(string $url, array $contact = []) + { + $contact_discovery = DI::config()->get('system', 'contact_discovery'); + + if ($contact_discovery == self::DISCOVERY_NONE) { + return false; + } + + if (empty($contact)) { + $contact = Contact::getByURL($url); + } + + if (empty($contact)) { + return false; + } + + if ($contact['last-discovery'] > DateTimeFormat::utc('now - 1 month')) { + Logger::info('No discovery - Last was less than a month ago.', ['id' => $contact['id'], 'url' => $url, 'discovery' => $contact['last-discovery']]); + return false; + } + + if ($contact_discovery != self::DISCOVERY_ALL) { + $local = DBA::exists('contact', ["`nurl` = ? AND `uid` != ?", Strings::normaliseLink($url), 0]); + if (($contact_discovery == self::DISCOVERY_LOCAL) && !$local) { + Logger::info('No discovery - This contact is not followed/following locally.', ['id' => $contact['id'], 'url' => $url]); + return false; + } + + if ($contact_discovery == self::DISCOVERY_INTERACTOR) { + $interactor = DBA::exists('contact-relation', ["`relation-cid` = ? AND `last-interaction` > ?", $contact['id'], DBA::NULL_DATETIME]); + if (!$local && !$interactor) { + Logger::info('No discovery - This contact is not interacting locally.', ['id' => $contact['id'], 'url' => $url]); + return false; + } + } + } elseif ($contact['created'] > DateTimeFormat::utc('now - 1 day')) { + // Newly created contacts are not discovered to avoid DDoS attacks + Logger::info('No discovery - Contact record is less than a day old.', ['id' => $contact['id'], 'url' => $url, 'discovery' => $contact['created']]); + return false; + } + + if (!in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS])) { + $apcontact = APContact::getByURL($url, false); + if (empty($apcontact)) { + Logger::info('No discovery - The contact does not seem to speak ActivityPub.', ['id' => $contact['id'], 'url' => $url, 'network' => $contact['network']]); + return false; + } + } + + return true; + } + + /** + * Counts all the known follows of the provided public contact + * + * @param int $cid Public contact id + * @param array $condition Additional condition on the contact table + * @return int + * @throws Exception + */ + public static function countFollows(int $cid, array $condition = []) + { + $condition = DBA::mergeConditions($condition, + ['`id` IN ( + SELECT `relation-cid` + FROM `contact-relation` + WHERE `cid` = ? + AND `follows` +)', $cid] + ); + + return DI::dba()->count('contact', $condition); + } + + /** + * Returns a paginated list of contacts that are followed the provided public contact. + * + * @param int $cid Public contact id + * @param array $condition Additional condition on the contact table + * @param int $count + * @param int $offset + * @param bool $shuffle + * @return array + * @throws Exception + */ + public static function listFollows(int $cid, array $condition = [], int $count = 30, int $offset = 0, bool $shuffle = false) + { + $condition = DBA::mergeConditions($condition, + ['`id` IN ( + SELECT `relation-cid` + FROM `contact-relation` + WHERE `cid` = ? + AND `follows` +)', $cid] + ); + + $follows = DI::dba()->selectToArray( + 'contact', + $condition, + [ + 'limit' => [$offset, $count], + 'order' => [$shuffle ? 'RAND()' : 'name'] + ] + ); + + return $follows; + } + + /** + * Counts all the known followers of the provided public contact + * + * @param int $cid Public contact id + * @param array $condition Additional condition on the contact table + * @return int + * @throws Exception + */ + public static function countFollowers(int $cid, array $condition = []) + { + $condition = DBA::mergeConditions($condition, + ['`id` IN ( + SELECT `cid` + FROM `contact-relation` + WHERE `relation-cid` = ? + AND `follows` +)', $cid] + ); + + return DI::dba()->count('contact', $condition); + } + + /** + * Returns a paginated list of contacts that follow the provided public contact. + * + * @param int $cid Public contact id + * @param array $condition Additional condition on the contact table + * @param int $count + * @param int $offset + * @param bool $shuffle + * @return array + * @throws Exception + */ + public static function listFollowers(int $cid, array $condition = [], int $count = 30, int $offset = 0, bool $shuffle = false) + { + $condition = DBA::mergeConditions($condition, + ['`id` IN ( + SELECT `cid` + FROM `contact-relation` + WHERE `relation-cid` = ? + AND `follows` +)', $cid] + ); + + $followers = DI::dba()->selectToArray( + 'contact', + $condition, + [ + 'limit' => [$offset, $count], + 'order' => [$shuffle ? 'RAND()' : 'name'] + ] + ); + + return $followers; + } + + /** + * Counts the number of contacts that both provided public contacts have interacted with at least once. + * Interactions include follows and likes and comments on public posts. + * + * @param int $sourceId Public contact id + * @param int $targetId Public contact id + * @param array $condition Additional condition array on the contact table + * @return int + * @throws Exception + */ + public static function countCommon(int $sourceId, int $targetId, array $condition = []) + { + $condition = DBA::mergeConditions($condition, + ['`id` IN ( + SELECT `relation-cid` + FROM `contact-relation` + WHERE `cid` = ? +) + AND `id` IN ( + SELECT `relation-cid` + FROM `contact-relation` + WHERE `cid` = ? +)', $sourceId, $targetId] + ); + + $total = DI::dba()->count('contact', $condition); + + return $total; + } + + /** + * Returns a paginated list of contacts that both provided public contacts have interacted with at least once. + * Interactions include follows and likes and comments on public posts. + * + * @param int $sourceId Public contact id + * @param int $targetId Public contact id + * @param array $condition Additional condition on the contact table + * @param int $count + * @param int $offset + * @param bool $shuffle + * @return array + * @throws Exception + */ + public static function listCommon(int $sourceId, int $targetId, array $condition = [], int $count = 30, int $offset = 0, bool $shuffle = false) + { + $condition = DBA::mergeConditions($condition, + ["`id` IN ( + SELECT `relation-cid` + FROM `contact-relation` + WHERE `cid` = ? + AND `follows` +) + AND `id` IN ( + SELECT `relation-cid` + FROM `contact-relation` + WHERE `cid` = ? + AND `follows` +)", $sourceId, $targetId] + ); + + $contacts = DI::dba()->selectToArray( + 'contact', + $condition, + [ + 'limit' => [$offset, $count], + 'order' => [$shuffle ? 'name' : 'RAND()'], + ] + ); + + return $contacts; + } + + + /** + * Counts the number of contacts that are followed by both provided public contacts. + * + * @param int $sourceId Public contact id + * @param int $targetId Public contact id + * @param array $condition Additional condition array on the contact table + * @return int + * @throws Exception + */ + public static function countCommonFollows(int $sourceId, int $targetId, array $condition = []) + { + $condition = DBA::mergeConditions($condition, + ['`id` IN ( + SELECT `relation-cid` + FROM `contact-relation` + WHERE `cid` = ? + AND `follows` +) + AND `id` IN ( + SELECT `relation-cid` + FROM `contact-relation` + WHERE `cid` = ? + AND `follows` +)', $sourceId, $targetId] + ); + + $total = DI::dba()->count('contact', $condition); + + return $total; + } + + /** + * Returns a paginated list of contacts that are followed by both provided public contacts. + * + * @param int $sourceId Public contact id + * @param int $targetId Public contact id + * @param array $condition Additional condition array on the contact table + * @param int $count + * @param int $offset + * @param bool $shuffle + * @return array + * @throws Exception + */ + public static function listCommonFollows(int $sourceId, int $targetId, array $condition = [], int $count = 30, int $offset = 0, bool $shuffle = false) + { + $condition = DBA::mergeConditions($condition, + ["`id` IN ( + SELECT `relation-cid` + FROM `contact-relation` + WHERE `cid` = ? + AND `follows` +) + AND `id` IN ( + SELECT `relation-cid` + FROM `contact-relation` + WHERE `cid` = ? + AND `follows` +)", $sourceId, $targetId] + ); + + $contacts = DI::dba()->selectToArray( + 'contact', + $condition, + [ + 'limit' => [$offset, $count], + 'order' => [$shuffle ? 'name' : 'RAND()'], + ] + ); + + return $contacts; + } + + /** + * Counts the number of contacts that follow both provided public contacts. + * + * @param int $sourceId Public contact id + * @param int $targetId Public contact id + * @param array $condition Additional condition on the contact table + * @return int + * @throws Exception + */ + public static function countCommonFollowers(int $sourceId, int $targetId, array $condition = []) + { + $condition = DBA::mergeConditions($condition, + ['`id` IN ( + SELECT `cid` + FROM `contact-relation` + WHERE `relation-cid` = ? + AND `follows` +) + AND `id` IN ( + SELECT `cid` + FROM `contact-relation` + WHERE `relation-cid` = ? + AND `follows` +)', $sourceId, $targetId] + ); + + $total = DI::dba()->count('contact', $condition); + + return $total; + } + + /** + * Returns a paginated list of contacts that follow both provided public contacts. + * + * @param int $sourceId Public contact id + * @param int $targetId Public contact id + * @param array $condition Additional condition on the contact table + * @param int $count + * @param int $offset + * @param bool $shuffle + * @return array + * @throws Exception + */ + public static function listCommonFollowers(int $sourceId, int $targetId, array $condition = [], int $count = 30, int $offset = 0, bool $shuffle = false) + { + $condition = DBA::mergeConditions($condition, + ["`id` IN ( + SELECT `cid` + FROM `contact-relation` + WHERE `relation-cid` = ? + AND `follows` +) + AND `id` IN ( + SELECT `cid` + FROM `contact-relation` + WHERE `relation-cid` = ? + AND `follows` +)", $sourceId, $targetId] + ); + + $contacts = DI::dba()->selectToArray( + 'contact', + $condition, + [ + 'limit' => [$offset, $count], + 'order' => [$shuffle ? 'name' : 'RAND()'], + ] + ); + + return $contacts; + } +} diff --git a/src/Model/ContactRelation.php b/src/Model/ContactRelation.php deleted file mode 100644 index e8b0f81f0..000000000 --- a/src/Model/ContactRelation.php +++ /dev/null @@ -1,203 +0,0 @@ -. - * - */ - -namespace Friendica\Model; - -use Friendica\Core\Logger; -use Friendica\Core\Protocol; -use Friendica\Database\DBA; -use Friendica\DI; -use Friendica\Protocol\ActivityPub; -use Friendica\Util\DateTimeFormat; -use Friendica\Util\Strings; - -class ContactRelation -{ - /** - * No discovery of followers/followings - */ - const DISCOVERY_NONE = 0; - /** - * Discover followers/followings of local contacts - */ - const DISCOVERY_LOCAL = 1; - /** - * Discover followers/followings of local contacts and contacts that visibly interacted on the system - */ - const DISCOVERY_INTERACTOR = 2; - /** - * Discover followers/followings of all contacts - */ - const DISCOVERY_ALL = 3; - - public static function store(int $target, int $actor, string $interaction_date) - { - if ($actor == $target) { - return; - } - - DBA::update('contact-relation', ['last-interaction' => $interaction_date], ['cid' => $target, 'relation-cid' => $actor], true); - } - - /** - * Fetches the followers of a given profile and adds them - * - * @param string $url URL of a profile - * @return void - */ - public static function discoverByUrl(string $url) - { - $contact = Contact::getByURL($url); - if (empty($contact)) { - return; - } - - if (!self::isDiscoverable($url, $contact)) { - return; - } - - $apcontact = APContact::getByURL($url, false); - - 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)) { - DBA::update('contact', ['last-discovery' => DateTimeFormat::utcNow()], ['id' => $contact['id']]); - Logger::info('The contact does not offer discoverable data', ['id' => $contact['id'], 'url' => $url, 'network' => $contact['network']]); - return; - } - - $target = $contact['id']; - - if (!empty($followers)) { - // Clear the follower list, since it will be recreated in the next step - DBA::update('contact-relation', ['follows' => false], ['cid' => $target]); - } - - $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); - - $follower_counter = 0; - $following_counter = 0; - - Logger::info('Discover contacts', ['id' => $target, 'url' => $url, 'contacts' => count($contacts)]); - foreach ($contacts as $contact) { - $actor = Contact::getIdForURL($contact); - if (!empty($actor)) { - if (in_array($contact, $followers)) { - $fields = ['cid' => $target, 'relation-cid' => $actor]; - DBA::update('contact-relation', ['follows' => true, 'follow-updated' => DateTimeFormat::utcNow()], $fields, true); - $follower_counter++; - } - - if (in_array($contact, $followings)) { - $fields = ['cid' => $actor, 'relation-cid' => $target]; - DBA::update('contact-relation', ['follows' => true, 'follow-updated' => DateTimeFormat::utcNow()], $fields, true); - $following_counter++; - } - } - } - - if (!empty($followers)) { - // Delete all followers that aren't followers anymore (and aren't interacting) - DBA::delete('contact-relation', ['cid' => $target, 'follows' => false, 'last-interaction' => DBA::NULL_DATETIME]); - } - - DBA::update('contact', ['last-discovery' => DateTimeFormat::utcNow()], ['id' => $target]); - Logger::info('Contacts discovery finished', ['id' => $target, 'url' => $url, 'follower' => $follower_counter, 'following' => $following_counter]); - return; - } - - /** - * Tests if a given contact url is discoverable - * - * @param string $url Contact url - * @param array $contact Contact array - * @return boolean True if contact is discoverable - */ - public static function isDiscoverable(string $url, array $contact = []) - { - $contact_discovery = DI::config()->get('system', 'contact_discovery'); - - if ($contact_discovery == self::DISCOVERY_NONE) { - return false; - } - - if (empty($contact)) { - $contact = Contact::getByURL($url); - } - - if (empty($contact)) { - return false; - } - - if ($contact['last-discovery'] > DateTimeFormat::utc('now - 1 month')) { - Logger::info('No discovery - Last was less than a month ago.', ['id' => $contact['id'], 'url' => $url, 'discovery' => $contact['last-discovery']]); - return false; - } - - if ($contact_discovery != self::DISCOVERY_ALL) { - $local = DBA::exists('contact', ["`nurl` = ? AND `uid` != ?", Strings::normaliseLink($url), 0]); - if (($contact_discovery == self::DISCOVERY_LOCAL) && !$local) { - Logger::info('No discovery - This contact is not followed/following locally.', ['id' => $contact['id'], 'url' => $url]); - return false; - } - - if ($contact_discovery == self::DISCOVERY_INTERACTOR) { - $interactor = DBA::exists('contact-relation', ["`relation-cid` = ? AND `last-interaction` > ?", $contact['id'], DBA::NULL_DATETIME]); - if (!$local && !$interactor) { - Logger::info('No discovery - This contact is not interacting locally.', ['id' => $contact['id'], 'url' => $url]); - return false; - } - } - } elseif ($contact['created'] > DateTimeFormat::utc('now - 1 day')) { - // Newly created contacts are not discovered to avoid DDoS attacks - Logger::info('No discovery - Contact record is less than a day old.', ['id' => $contact['id'], 'url' => $url, 'discovery' => $contact['created']]); - return false; - } - - if (!in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS])) { - $apcontact = APContact::getByURL($url, false); - if (empty($apcontact)) { - Logger::info('No discovery - The contact does not seem to speak ActivityPub.', ['id' => $contact['id'], 'url' => $url, 'network' => $contact['network']]); - return false; - } - } - - return true; - } -} diff --git a/src/Model/GContact.php b/src/Model/GContact.php index 1650123c7..78583bcc5 100644 --- a/src/Model/GContact.php +++ b/src/Model/GContact.php @@ -23,6 +23,8 @@ namespace Friendica\Model; use Exception; use Friendica\Database\DBA; +use Friendica\DI; +use Friendica\Util\DateTimeFormat; /** * This class handles GlobalContact related functions @@ -37,22 +39,16 @@ class GContact */ public static function countCommonFriends($uid, $cid) { - $r = q( - "SELECT count(*) as `total` - FROM `glink` INNER JOIN `gcontact` on `glink`.`gcid` = `gcontact`.`id` - WHERE `glink`.`cid` = %d AND `glink`.`uid` = %d AND - NOT `gcontact`.`failed` - AND `gcontact`.`nurl` IN (select nurl from contact where uid = %d and self = 0 and blocked = 0 and hidden = 0 and id != %d) ", - intval($cid), - intval($uid), - intval($uid), - intval($cid) - ); + $sourceId = Contact::getPublicIdByUserId($uid); - if (DBA::isResult($r)) { - return $r[0]['total']; - } - return 0; + $targetIds = Contact::getPublicAndUserContacID($cid, $uid); + + $condition = [ + 'NOT `self` AND NOT `blocked` AND NOT `hidden` AND `id` != ?', + $sourceId, + ]; + + return Contact\Relation::countCommonFollows($sourceId, $targetIds['public'] ?? 0, $condition); } /** @@ -63,91 +59,111 @@ class GContact */ public static function countCommonFriendsZcid($uid, $zcid) { - $r = q( - "SELECT count(*) as `total` - FROM `glink` INNER JOIN `gcontact` on `glink`.`gcid` = `gcontact`.`id` - where `glink`.`zcid` = %d - and `gcontact`.`nurl` in (select nurl from contact where uid = %d and self = 0 and blocked = 0 and hidden = 0) ", - intval($zcid), - intval($uid) + $sourceId = Contact::getPublicIdByUserId($uid); + + $targetPublicContact = DI::dba()->fetchFirst(" +SELECT `id` +FROM `contact` c +JOIN `gcontact` z ON z.`nurl` = c.`nurl` +AND z.`id` = ? +AND c.`uid` = 0 +LIMIT 1", + $zcid ); - if (DBA::isResult($r)) { - return $r[0]['total']; - } + $condition = [ + 'NOT `self` AND NOT `blocked` AND NOT `hidden` AND `id` != ?', + $sourceId, + ]; - return 0; + return Contact\Relation::countCommonFollowers($sourceId, $targetPublicContact['id'] ?? 0, $condition); } /** - * @param integer $uid user - * @param integer $cid cid + * Returns the cross-section between the local user contacts and one of their contact's own relationships + * as known by the local node. + * + * @param integer $uid local user id + * @param integer $cid user contact id to compare friends with * @param integer $start optional, default 0 * @param integer $limit optional, default 9999 * @param boolean $shuffle optional, default false - * @return object + * @return array * @throws Exception */ public static function commonFriends($uid, $cid, $start = 0, $limit = 9999, $shuffle = false) { - if ($shuffle) { - $sql_extra = " order by rand() "; - } else { - $sql_extra = " order by `gcontact`.`name` asc "; - } + $sourceId = Contact::getPublicIdByUserId($uid); - $r = q( - "SELECT `gcontact`.*, `contact`.`id` AS `cid` - FROM `glink` - INNER JOIN `gcontact` ON `glink`.`gcid` = `gcontact`.`id` - INNER JOIN `contact` ON `gcontact`.`nurl` = `contact`.`nurl` - WHERE `glink`.`cid` = %d and `glink`.`uid` = %d - AND `contact`.`uid` = %d AND `contact`.`self` = 0 AND `contact`.`blocked` = 0 - AND `contact`.`hidden` = 0 AND `contact`.`id` != %d - AND NOT `gcontact`.`failed` - $sql_extra LIMIT %d, %d", - intval($cid), - intval($uid), - intval($uid), - intval($cid), - intval($start), - intval($limit) - ); + $targetIds = Contact::getPublicAndUserContacID($cid, $uid); - /// @TODO Check all calling-findings of this function if they properly use DBA::isResult() - return $r; + $condition = [ + 'NOT `self` AND NOT `blocked` AND NOT `hidden` AND `id` != ?', + $sourceId, + ]; + + return Contact\Relation::listCommonFollows($sourceId, $targetIds['public'] ?? 0, $condition, $limit, $start, $shuffle); } /** - * @param integer $uid user - * @param integer $zcid zcid + * Returns the cross-section between a local user and a remote visitor contact's own relationships + * as known by the local node. + * + * @param integer $uid local user id + * @param integer $zcid remote visitor contact zcid * @param integer $start optional, default 0 * @param integer $limit optional, default 9999 * @param boolean $shuffle optional, default false - * @return object + * @return array * @throws Exception */ public static function commonFriendsZcid($uid, $zcid, $start = 0, $limit = 9999, $shuffle = false) { - if ($shuffle) { - $sql_extra = " order by rand() "; - } else { - $sql_extra = " order by `gcontact`.`name` asc "; - } + $sourceId = Contact::getPublicIdByUserId($uid); - $r = q( - "SELECT `gcontact`.* - FROM `glink` INNER JOIN `gcontact` on `glink`.`gcid` = `gcontact`.`id` - where `glink`.`zcid` = %d - and `gcontact`.`nurl` in (select nurl from contact where uid = %d and self = 0 and blocked = 0 and hidden = 0) - $sql_extra limit %d, %d", - intval($zcid), - intval($uid), - intval($start), - intval($limit) + $targetPublicContact = DI::dba()->fetchFirst(" +SELECT c.`id` +FROM `contact` c +JOIN `gcontact` z ON z.`nurl` = c.`nurl` +AND z.`id` = ? +AND c.`uid` = 0 +LIMIT 1", + $zcid ); - /// @TODO Check all calling-findings of this function if they properly use DBA::isResult() - return $r; + $condition = [ + 'NOT `self` AND NOT `blocked` AND NOT `hidden` AND `id` != ?', + $sourceId, + ]; + + return Contact\Relation::listCommonFollows($sourceId, $targetPublicContact['id'] ?? 0, $condition, $limit, $start, $shuffle); + } + + /** + * @param integer $uid user + * @param integer $cid cid + * @return integer + * @throws Exception + */ + public static function countAllFriends($uid, $cid) + { + $cids = Contact::getPublicAndUserContacID($cid, $uid); + + return Contact\Relation::countFollows($cids['public'] ?? 0); + } + + /** + * @param integer $uid user + * @param integer $cid cid + * @param integer $start optional, default 0 + * @param integer $limit optional, default 80 + * @return array + * @throws Exception + */ + public static function allFriends($uid, $cid, $start = 0, $limit = 80) + { + $cids = Contact::getPublicAndUserContacID($cid, $uid); + + return Contact\Relation::listFollows($cids['public'] ?? 0, [], $limit, $start); } } diff --git a/src/Model/Item.php b/src/Model/Item.php index b6f8ffa3d..54cd4a5db 100644 --- a/src/Model/Item.php +++ b/src/Model/Item.php @@ -1554,7 +1554,7 @@ class Item } // Update the contact relations - ContactRelation::store($parent['author-id'], $item['author-id'], $item['created']); + Contact\Relation::store($parent['author-id'], $item['author-id'], $item['created']); } return $item; diff --git a/src/Module/Admin/Site.php b/src/Module/Admin/Site.php index 7b298cd93..50566b349 100644 --- a/src/Module/Admin/Site.php +++ b/src/Module/Admin/Site.php @@ -28,7 +28,7 @@ use Friendica\Core\Theme; use Friendica\Core\Worker; use Friendica\Database\DBA; use Friendica\DI; -use Friendica\Model\ContactRelation; +use Friendica\Model\Contact; use Friendica\Module\BaseAdmin; use Friendica\Module\Register; use Friendica\Util\BasePath; @@ -175,7 +175,7 @@ class Site extends BaseAdmin $min_memory = (!empty($_POST['min_memory']) ? intval(trim($_POST['min_memory'])) : 0); $optimize_max_tablesize = (!empty($_POST['optimize_max_tablesize']) ? intval(trim($_POST['optimize_max_tablesize'])) : 100); $optimize_fragmentation = (!empty($_POST['optimize_fragmentation']) ? intval(trim($_POST['optimize_fragmentation'])) : 30); - $contact_discovery = (!empty($_POST['contact_discovery']) ? intval(trim($_POST['contact_discovery'])) : ContactRelation::DISCOVERY_NONE); + $contact_discovery = (!empty($_POST['contact_discovery']) ? intval(trim($_POST['contact_discovery'])) : Contact\Relation::DISCOVERY_NONE); $synchronize_directory = (!empty($_POST['synchronize_directory']) ? intval(trim($_POST['synchronize_directory'])) : false); $poco_requery_days = (!empty($_POST['poco_requery_days']) ? intval(trim($_POST['poco_requery_days'])) : 7); $poco_discovery = (!empty($_POST['poco_discovery']) ? intval(trim($_POST['poco_discovery'])) : false); @@ -532,9 +532,9 @@ class Site extends BaseAdmin ]; $discovery_choices = [ - ContactRelation::DISCOVERY_NONE => DI::l10n()->t('none'), - ContactRelation::DISCOVERY_LOCAL => DI::l10n()->t('Local contacts'), - ContactRelation::DISCOVERY_INTERACTOR => DI::l10n()->t('Interactors'), + Contact\Relation::DISCOVERY_NONE => DI::l10n()->t('none'), + Contact\Relation::DISCOVERY_LOCAL => DI::l10n()->t('Local contacts'), + Contact\Relation::DISCOVERY_INTERACTOR => DI::l10n()->t('Interactors'), // "All" is deactivated until we are sure not to put too much stress on the fediverse with this // ContactRelation::DISCOVERY_ALL => DI::l10n()->t('All'), ]; diff --git a/src/Module/Api/Twitter/ContactEndpoint.php b/src/Module/Api/Twitter/ContactEndpoint.php index 116f8eea2..f34101971 100644 --- a/src/Module/Api/Twitter/ContactEndpoint.php +++ b/src/Module/Api/Twitter/ContactEndpoint.php @@ -143,7 +143,7 @@ abstract class ContactEndpoint extends BaseApi $previous_cursor = 0; $total_count = 0; if (!$hide_friends) { - $condition = DBA::collapseCondition([ + $condition = [ 'rel' => $rel, 'uid' => $uid, 'self' => false, @@ -151,17 +151,15 @@ abstract class ContactEndpoint extends BaseApi 'hidden' => false, 'archive' => false, 'pending' => false - ]); + ]; $total_count = DBA::count('contact', $condition); if ($cursor !== -1) { if ($cursor > 0) { - $condition[0] .= " AND `id` > ?"; - $condition[] = $cursor; + $condition = DBA::mergeConditions($condition, ['`id` > ?', $cursor]); } else { - $condition[0] .= " AND `id` < ?"; - $condition[] = -$cursor; + $condition = DBA::mergeConditions($condition, ['`id` < ?', -$cursor]); } } diff --git a/src/Worker/ContactDiscovery.php b/src/Worker/ContactDiscovery.php index 34d4a7da0..5dac17322 100644 --- a/src/Worker/ContactDiscovery.php +++ b/src/Worker/ContactDiscovery.php @@ -21,7 +21,7 @@ namespace Friendica\Worker; -use Friendica\Model\ContactRelation; +use Friendica\Model\Contact; class ContactDiscovery { @@ -31,6 +31,6 @@ class ContactDiscovery */ public static function execute(string $url) { - ContactRelation::discoverByUrl($url); + Contact\Relation::discoverByUrl($url); } } diff --git a/src/Worker/UpdateServerDirectory.php b/src/Worker/UpdateServerDirectory.php index dd94aa52c..1244e2c2c 100644 --- a/src/Worker/UpdateServerDirectory.php +++ b/src/Worker/UpdateServerDirectory.php @@ -60,8 +60,8 @@ class UpdateServerDirectory Logger::info('PoCo discovery started', ['poco' => $gserver['poco']]); $urls = []; - foreach ($contacts['entry'] as $entry) { - foreach ($entry['urls'] as $url_entry) { + foreach (array_column($contacts['entry'], 'urls') as $urls) { + foreach ($urls as $url_entry) { if (empty($url_entry['type']) || empty($url_entry['value'])) { continue; }