2020-07-26 03:34:33 -04:00
< ? php
/**
2023-01-01 09:36:24 -05:00
* @ copyright Copyright ( C ) 2010 - 2023 , the Friendica project
2020-07-26 03:34:33 -04:00
*
* @ license GNU AGPL version 3 or any later version
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation , either version 3 of the
* License , or ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
*
* You should have received a copy of the GNU Affero General Public License
* along with this program . If not , see < https :// www . gnu . org / licenses />.
*
*/
2020-08-03 13:10:49 -04:00
namespace Friendica\Model\Contact ;
2020-07-26 03:34:33 -04:00
2020-07-29 11:30:54 -04:00
use Exception ;
2020-07-26 03:34:33 -04:00
use Friendica\Core\Logger ;
2020-07-27 15:36:11 -04:00
use Friendica\Core\Protocol ;
2021-01-19 02:23:01 -05:00
use Friendica\Database\Database ;
2020-07-26 03:34:33 -04:00
use Friendica\Database\DBA ;
use Friendica\DI ;
2020-08-03 15:21:31 -04:00
use Friendica\Model\APContact ;
use Friendica\Model\Contact ;
2020-08-21 17:27:40 -04:00
use Friendica\Model\Profile ;
use Friendica\Model\User ;
2023-08-30 15:17:42 -04:00
use Friendica\Model\Verb ;
use Friendica\Protocol\Activity ;
2020-07-26 03:34:33 -04:00
use Friendica\Protocol\ActivityPub ;
use Friendica\Util\DateTimeFormat ;
use Friendica\Util\Strings ;
2020-07-29 11:30:54 -04:00
/**
* 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 .
*/
2020-08-03 13:10:49 -04:00
class Relation
2020-07-26 03:34:33 -04:00
{
/**
* 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 ;
}
2021-01-19 02:23:01 -05:00
DBA :: insert ( 'contact-relation' , [ 'last-interaction' => $interaction_date , 'cid' => $target , 'relation-cid' => $actor ], Database :: INSERT_UPDATE );
2020-07-26 03:34:33 -04:00
}
2022-12-03 08:49:29 -05:00
/**
* Fetch the followers of a given user
*
* @ param integer $uid User ID
* @ return void
*/
public static function discoverByUser ( int $uid )
{
2022-12-04 08:29:21 -05:00
$contact = Contact :: selectFirst ([ 'id' , 'url' , 'network' ], [ 'uid' => $uid , 'self' => true ]);
2022-12-03 08:49:29 -05:00
if ( empty ( $contact )) {
Logger :: warning ( 'Self contact for user not found' , [ 'uid' => $uid ]);
return ;
}
$followers = self :: getContacts ( $uid , [ Contact :: FOLLOWER , Contact :: FRIEND ]);
$followings = self :: getContacts ( $uid , [ Contact :: SHARING , Contact :: FRIEND ]);
self :: updateFollowersFollowings ( $contact , $followers , $followings );
}
2020-07-26 03:34:33 -04:00
/**
* 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 )) {
2021-05-29 13:24:50 -04:00
Logger :: info ( 'Contact not found' , [ 'url' => $url ]);
2020-07-26 03:34:33 -04:00
return ;
}
2020-07-29 11:39:45 -04:00
if ( ! self :: isDiscoverable ( $url , $contact )) {
2021-05-29 13:24:50 -04:00
Logger :: info ( 'Contact is not discoverable' , [ 'url' => $url ]);
2020-07-26 03:34:33 -04:00
return ;
}
2020-08-21 17:27:40 -04:00
$uid = User :: getIdForURL ( $url );
if ( ! empty ( $uid )) {
2021-05-29 07:11:26 -04:00
Logger :: info ( 'Fetch the followers/followings locally' , [ 'url' => $url ]);
2020-08-21 17:27:40 -04:00
$followers = self :: getContacts ( $uid , [ Contact :: FOLLOWER , Contact :: FRIEND ]);
$followings = self :: getContacts ( $uid , [ Contact :: SHARING , Contact :: FRIEND ]);
2021-05-29 07:11:26 -04:00
} elseif ( ! Contact :: isLocal ( $url )) {
Logger :: info ( 'Fetch the followers/followings by polling the endpoints' , [ 'url' => $url ]);
2020-08-21 17:27:40 -04:00
$apcontact = APContact :: getByURL ( $url , false );
2020-07-26 03:34:33 -04:00
2020-08-21 17:27:40 -04:00
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 = [];
}
2021-05-29 07:11:26 -04:00
} else {
2022-08-30 15:45:30 -04:00
Logger :: warning ( 'Contact seems to be local but could not be found here' , [ 'url' => $url ]);
2021-05-29 07:11:26 -04:00
$followers = [];
$followings = [];
2020-07-26 03:34:33 -04:00
}
2022-12-03 08:49:29 -05:00
self :: updateFollowersFollowings ( $contact , $followers , $followings );
}
/**
* Update followers and followings for the given contact
*
* @ param array $contact
* @ param array $followers
* @ param array $followings
* @ return void
*/
private static function updateFollowersFollowings ( array $contact , array $followers , array $followings )
{
2020-07-26 03:34:33 -04:00
if ( empty ( $followers ) && empty ( $followings )) {
2021-09-10 14:21:19 -04:00
Contact :: update ([ 'last-discovery' => DateTimeFormat :: utcNow ()], [ 'id' => $contact [ 'id' ]]);
2022-12-03 08:49:29 -05:00
Logger :: info ( 'The contact does not offer discoverable data' , [ 'id' => $contact [ 'id' ], 'url' => $contact [ 'url' ], 'network' => $contact [ 'network' ]]);
2020-07-26 03:34:33 -04:00
return ;
}
$target = $contact [ 'id' ];
2022-12-03 08:49:29 -05:00
$url = $contact [ 'url' ];
2020-07-26 03:34:33 -04:00
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 );
2020-07-29 11:39:45 -04:00
$follower_counter = 0 ;
$following_counter = 0 ;
2020-07-26 03:34:33 -04:00
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 )) {
2021-01-19 02:23:01 -05:00
$fields = [ 'cid' => $target , 'relation-cid' => $actor , 'follows' => true , 'follow-updated' => DateTimeFormat :: utcNow ()];
DBA :: insert ( 'contact-relation' , $fields , Database :: INSERT_UPDATE );
2020-07-29 11:39:45 -04:00
$follower_counter ++ ;
2020-07-26 03:34:33 -04:00
}
2020-07-29 11:39:45 -04:00
if ( in_array ( $contact , $followings )) {
2021-01-19 02:23:01 -05:00
$fields = [ 'cid' => $actor , 'relation-cid' => $target , 'follows' => true , 'follow-updated' => DateTimeFormat :: utcNow ()];
DBA :: insert ( 'contact-relation' , $fields , Database :: INSERT_UPDATE );
2020-07-29 11:39:45 -04:00
$following_counter ++ ;
}
2020-07-26 03:34:33 -04:00
}
}
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 ]);
}
2021-09-10 14:21:19 -04:00
Contact :: update ([ 'last-discovery' => DateTimeFormat :: utcNow ()], [ 'id' => $target ]);
2020-07-29 11:39:45 -04:00
Logger :: info ( 'Contacts discovery finished' , [ 'id' => $target , 'url' => $url , 'follower' => $follower_counter , 'following' => $following_counter ]);
2020-07-26 03:34:33 -04:00
return ;
}
2020-07-29 11:39:45 -04:00
2020-08-21 17:27:40 -04:00
/**
2020-08-22 10:36:42 -04:00
* Fetch contact url list from the given local user
2020-08-21 17:27:40 -04:00
*
* @ param integer $uid
* @ param array $rel
2020-08-22 10:36:42 -04:00
* @ return array contact list
2020-08-21 17:27:40 -04:00
*/
2022-06-23 05:39:45 -04:00
private static function getContacts ( int $uid , array $rel ) : array
2020-08-21 17:27:40 -04:00
{
$list = [];
$profile = Profile :: getByUID ( $uid );
if ( ! empty ( $profile [ 'hide-friends' ])) {
return $list ;
}
2022-06-23 05:39:45 -04:00
$condition = [
'rel' => $rel ,
'uid' => $uid ,
'self' => false ,
'deleted' => false ,
'hidden' => false ,
'archive' => false ,
'pending' => false ,
];
2020-08-21 17:27:40 -04:00
$condition = DBA :: mergeConditions ( $condition , [ " `url` IN (SELECT `url` FROM `apcontact`) " ]);
$contacts = DBA :: select ( 'contact' , [ 'url' ], $condition );
while ( $contact = DBA :: fetch ( $contacts )) {
$list [] = $contact [ 'url' ];
}
DBA :: close ( $contacts );
return $list ;
}
2020-07-29 11:39:45 -04:00
/**
* 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
*/
2022-06-23 05:39:45 -04:00
public static function isDiscoverable ( string $url , array $contact = []) : bool
2020-07-29 11:39:45 -04:00
{
$contact_discovery = DI :: config () -> get ( 'system' , 'contact_discovery' );
if ( $contact_discovery == self :: DISCOVERY_NONE ) {
return false ;
}
if ( empty ( $contact )) {
2020-08-17 23:55:27 -04:00
$contact = Contact :: getByURL ( $url , false );
2020-07-29 11:39:45 -04:00
}
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 ;
}
2020-07-29 11:30:54 -04:00
2022-11-30 17:34:50 -05:00
/**
* Check if the cached suggestion is outdated
*
* @ param integer $uid
* @ return boolean
*/
static public function areSuggestionsOutdated ( int $uid ) : bool
{
return DI :: pConfig () -> get ( $uid , 'suggestion' , 'last_update' ) + 3600 < time ();
}
2022-11-30 00:59:27 -05:00
/**
* Update contact suggestions for a given user
*
* @ param integer $uid
* @ return void
*/
2022-11-30 09:33:55 -05:00
static public function updateCachedSuggestions ( int $uid )
2022-11-30 00:59:27 -05:00
{
2022-11-30 17:34:50 -05:00
if ( ! self :: areSuggestionsOutdated ( $uid )) {
2022-11-30 00:59:27 -05:00
return ;
}
DBA :: delete ( 'account-suggestion' , [ 'uid' => $uid , 'ignore' => false ]);
foreach ( self :: getSuggestions ( $uid ) as $contact ) {
DBA :: insert ( 'account-suggestion' , [ 'uri-id' => $contact [ 'uri-id' ], 'uid' => $uid , 'level' => 1 ], Database :: INSERT_IGNORE );
}
DI :: pConfig () -> set ( $uid , 'suggestion' , 'last_update' , time ());
}
/**
* Returns a cached array of suggested contacts for given user id
*
* @ param int $uid User id
* @ param int $start optional , default 0
* @ param int $limit optional , default 80
* @ return array
*/
static public function getCachedSuggestions ( int $uid , int $start = 0 , int $limit = 80 ) : array
{
$condition = [ " `uid` = ? AND `uri-id` IN (SELECT `uri-id` FROM `account-suggestion` WHERE NOT `ignore` AND `uid` = ?) " , 0 , $uid ];
$params = [ 'limit' => [ $start , $limit ]];
$cached = DBA :: selectToArray ( 'contact' , [], $condition , $params );
if ( ! empty ( $cached )) {
return $cached ;
} else {
return self :: getSuggestions ( $uid , $start , $limit );
}
}
2020-08-04 00:47:02 -04:00
/**
2022-06-23 10:03:55 -04:00
* Returns an array of suggested contacts for given user id
2022-06-23 05:39:45 -04:00
*
* @ param int $uid User id
2020-08-04 00:47:02 -04:00
* @ param int $start optional , default 0
* @ param int $limit optional , default 80
* @ return array
*/
2022-06-23 05:39:45 -04:00
static public function getSuggestions ( int $uid , int $start = 0 , int $limit = 80 ) : array
2020-08-04 00:47:02 -04:00
{
2022-12-01 02:07:59 -05:00
if ( $uid == 0 ) {
return [];
}
2020-08-04 00:47:02 -04:00
$cid = Contact :: 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.
2022-06-23 05:39:45 -04:00
$results = DBA :: select ( 'contact' , [], [ " `id` IN (SELECT `cid` FROM `contact-relation` WHERE `relation-cid` IN
2020-08-04 00:47:02 -04:00
( SELECT `cid` FROM `contact-relation` WHERE `relation-cid` = ? )
AND NOT `cid` IN ( SELECT `id` FROM `contact` WHERE `uid` = ? AND `nurl` IN
2022-11-01 02:02:44 -04:00
( SELECT `nurl` FROM `contact` WHERE `uid` = ? AND `rel` IN ( ? , ? ))) AND `id` = `cid` )
2022-11-30 00:59:27 -05:00
AND NOT `hidden` AND `network` IN ( ? , ? , ? , ? )
AND NOT `uri-id` IN ( SELECT `uri-id` FROM `account-suggestion` WHERE `uri-id` = `contact` . `uri-id` AND `uid` = ? ) " ,
2022-06-23 05:39:45 -04:00
$cid ,
0 ,
2022-06-23 10:03:55 -04:00
$uid , Contact :: FRIEND , Contact :: SHARING ,
2022-11-30 00:59:27 -05:00
Protocol :: ACTIVITYPUB , Protocol :: DFRN , $diaspora , $ostatus , $uid
2022-06-23 05:39:45 -04:00
], [
'order' => [ 'last-item' => true ],
'limit' => $totallimit ,
]
2020-08-04 00:47:02 -04:00
);
while ( $contact = DBA :: fetch ( $results )) {
$contacts [ $contact [ 'id' ]] = $contact ;
}
2022-06-23 05:39:45 -04:00
2020-08-04 00:47:02 -04:00
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
2022-11-01 02:02:44 -04:00
( SELECT `nurl` FROM `contact` WHERE `uid` = ? AND `rel` IN ( ? , ? ))) AND `id` = `cid` )
2022-11-30 00:59:27 -05:00
AND NOT `hidden` AND `network` IN ( ? , ? , ? , ? )
AND NOT `uri-id` IN ( SELECT `uri-id` FROM `account-suggestion` WHERE `uri-id` = `contact` . `uri-id` AND `uid` = ? ) " ,
2020-08-04 00:47:02 -04:00
$cid , 0 , $uid , Contact :: FRIEND , Contact :: SHARING ,
2022-11-30 00:59:27 -05:00
Protocol :: ACTIVITYPUB , Protocol :: DFRN , $diaspora , $ostatus , $uid ],
2020-08-04 00:47:02 -04:00
[ '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` = ?)
2022-11-30 00:59:27 -05:00
AND NOT `hidden` AND `uid` = ? AND `network` IN ( ? , ? , ? , ? )
AND NOT `uri-id` IN ( SELECT `uri-id` FROM `account-suggestion` WHERE `uri-id` = `contact` . `uri-id` AND `uid` = ? ) " ,
2023-01-01 09:36:24 -05:00
$uid , Contact :: FOLLOWER , 0 ,
2022-11-30 00:59:27 -05:00
Protocol :: ACTIVITYPUB , Protocol :: DFRN , $diaspora , $ostatus , $uid ],
2020-08-04 00:47:02 -04:00
[ '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' , [],
2022-11-01 02:02:44 -04:00
[ " NOT `nurl` IN (SELECT `nurl` FROM `contact` WHERE `uid` = ? AND `rel` IN (?, ?) AND `nurl` = `nurl`)
2022-11-30 00:59:27 -05:00
AND NOT `hidden` AND `uid` = ? AND `network` IN ( ? , ? , ? , ? )
AND NOT `uri-id` IN ( SELECT `uri-id` FROM `account-suggestion` WHERE `uri-id` = `contact` . `uri-id` AND `uid` = ? ) " ,
2023-01-01 09:36:24 -05:00
$uid , Contact :: FRIEND , Contact :: SHARING , 0 ,
2022-11-30 00:59:27 -05:00
Protocol :: ACTIVITYPUB , Protocol :: DFRN , $diaspora , $ostatus , $uid ],
2020-08-04 00:47:02 -04:00
[ '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 );
}
2020-07-29 11:30:54 -04:00
/**
* 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
*/
2022-06-23 05:39:45 -04:00
public static function countFollows ( int $cid , array $condition = []) : int
2020-07-29 11:30:54 -04:00
{
2022-06-23 05:39:45 -04:00
$condition = DBA :: mergeConditions ( $condition , [
2023-01-01 09:36:24 -05:00
'`id` IN (SELECT `relation-cid` FROM `contact-relation` WHERE `cid` = ? AND `follows`)' ,
2022-06-23 05:39:45 -04:00
$cid ,
]);
2020-07-29 11:30:54 -04:00
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
*/
2020-08-06 10:30:06 -04:00
public static function listFollows ( int $cid , array $condition = [], int $count = 30 , int $offset = 0 , bool $shuffle = false )
2020-07-29 11:30:54 -04:00
{
$condition = DBA :: mergeConditions ( $condition ,
2023-01-01 09:36:24 -05:00
[ '`id` IN (SELECT `relation-cid` FROM `contact-relation` WHERE `cid` = ? AND `follows`)' ,
2020-08-03 15:21:31 -04:00
$cid ]
2020-07-29 11:30:54 -04:00
);
2020-08-06 10:30:06 -04:00
return DI :: dba () -> selectToArray ( 'contact' , [], $condition ,
2020-08-03 15:21:31 -04:00
[ 'limit' => [ $offset , $count ], 'order' => [ $shuffle ? 'RAND()' : 'name' ]]
2020-07-29 11:30:54 -04:00
);
}
/**
* 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 ,
2020-08-03 15:21:31 -04:00
[ '`id` IN (SELECT `cid` FROM `contact-relation` WHERE `relation-cid` = ? AND `follows`)' ,
$cid ]
2020-07-29 11:30:54 -04:00
);
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
*/
2020-08-06 10:30:06 -04:00
public static function listFollowers ( int $cid , array $condition = [], int $count = 30 , int $offset = 0 , bool $shuffle = false )
2020-07-29 11:30:54 -04:00
{
$condition = DBA :: mergeConditions ( $condition ,
2020-08-03 15:21:31 -04:00
[ '`id` IN (SELECT `cid` FROM `contact-relation` WHERE `relation-cid` = ? AND `follows`)' , $cid ]
2020-07-29 11:30:54 -04:00
);
2020-08-06 10:30:06 -04:00
return DI :: dba () -> selectToArray ( 'contact' , [], $condition ,
2020-08-03 15:21:31 -04:00
[ 'limit' => [ $offset , $count ], 'order' => [ $shuffle ? 'RAND()' : 'name' ]]
2020-07-29 11:30:54 -04:00
);
}
2020-08-06 10:30:06 -04:00
/**
* Counts the number of contacts that are known mutuals with the provided public contact .
*
* @ param int $cid Public contact id
* @ param array $condition Additional condition array on the contact table
* @ return int
* @ throws Exception
*/
public static function countMutuals ( int $cid , array $condition = [])
{
$condition = DBA :: mergeConditions ( $condition ,
[ ' `id` IN ( SELECT `relation-cid` FROM `contact-relation` WHERE `cid` = ? AND `follows` )
AND `id` IN ( SELECT `cid` FROM `contact-relation` WHERE `relation-cid` = ? AND `follows` ) ' ,
$cid , $cid ]
);
return DI :: dba () -> count ( 'contact' , $condition );
}
/**
* Returns a paginated list of contacts that are known mutuals with 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 listMutuals ( 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` )
AND `id` IN ( SELECT `cid` FROM `contact-relation` WHERE `relation-cid` = ? AND `follows` ) ' ,
$cid , $cid ]
);
return DI :: dba () -> selectToArray ( 'contact' , [], $condition ,
2020-10-09 15:08:50 -04:00
[ 'limit' => [ $offset , $count ], 'order' => [ $shuffle ? 'RAND()' : 'name' ]]
2020-08-06 10:30:06 -04:00
);
}
/**
* Counts the number of contacts with any relationship with the provided public contact .
*
* @ param int $cid Public contact id
* @ param array $condition Additional condition array on the contact table
* @ return int
* @ throws Exception
*/
public static function countAll ( int $cid , array $condition = [])
{
$condition = DBA :: mergeConditions ( $condition ,
2020-10-09 15:08:50 -04:00
[ ' ( `id` IN ( SELECT `relation-cid` FROM `contact-relation` WHERE `cid` = ? AND `follows` )
OR `id` IN ( SELECT `cid` FROM `contact-relation` WHERE `relation-cid` = ? AND `follows` )) ' ,
2020-08-06 10:30:06 -04:00
$cid , $cid ]
);
return DI :: dba () -> count ( 'contact' , $condition );
}
/**
* Returns a paginated list of contacts with any relationship with 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 listAll ( int $cid , array $condition = [], int $count = 30 , int $offset = 0 , bool $shuffle = false )
{
$condition = DBA :: mergeConditions ( $condition ,
2020-10-09 15:08:50 -04:00
[ ' ( `id` IN ( SELECT `relation-cid` FROM `contact-relation` WHERE `cid` = ? AND `follows` )
OR `id` IN ( SELECT `cid` FROM `contact-relation` WHERE `relation-cid` = ? AND `follows` )) ' ,
2020-08-06 10:30:06 -04:00
$cid , $cid ]
);
return DI :: dba () -> selectToArray ( 'contact' , [], $condition ,
2020-10-09 15:08:50 -04:00
[ 'limit' => [ $offset , $count ], 'order' => [ $shuffle ? 'RAND()' : 'name' ]]
2020-08-06 10:30:06 -04:00
);
}
2020-07-29 11:30:54 -04:00
/**
* 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 ,
2020-08-03 15:21:31 -04:00
[ ' `id` IN ( SELECT `relation-cid` FROM `contact-relation` WHERE `cid` = ? )
AND `id` IN ( SELECT `relation-cid` FROM `contact-relation` WHERE `cid` = ? ) ' ,
$sourceId , $targetId ]
2020-07-29 11:30:54 -04:00
);
2020-08-03 15:38:03 -04:00
return DI :: dba () -> count ( 'contact' , $condition );
2020-07-29 11:30:54 -04:00
}
/**
* 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
2022-06-23 05:39:45 -04:00
* @ return array | bool Array on success , false on failure
2020-07-29 11:30:54 -04:00
* @ throws Exception
*/
2020-08-04 22:44:18 -04:00
public static function listCommon ( int $sourceId , int $targetId , array $condition = [], int $count = 30 , int $offset = 0 , bool $shuffle = false )
2020-07-29 11:30:54 -04:00
{
$condition = DBA :: mergeConditions ( $condition ,
2020-08-06 10:30:06 -04:00
[ " `id` IN (SELECT `relation-cid` FROM `contact-relation` WHERE `cid` = ?)
AND `id` IN ( SELECT `relation-cid` FROM `contact-relation` WHERE `cid` = ? ) " ,
2020-08-03 15:21:31 -04:00
$sourceId , $targetId ]
2020-07-29 11:30:54 -04:00
);
2020-08-04 22:44:18 -04:00
return DI :: dba () -> selectToArray ( 'contact' , [], $condition ,
2020-10-09 15:08:50 -04:00
[ 'limit' => [ $offset , $count ], 'order' => [ $shuffle ? 'RAND()' : 'name' ]]
2020-07-29 11:30:54 -04:00
);
}
/**
* 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
*/
2022-06-23 05:39:45 -04:00
public static function countCommonFollows ( int $sourceId , int $targetId , array $condition = []) : int
2020-07-29 11:30:54 -04:00
{
$condition = DBA :: mergeConditions ( $condition ,
2020-08-03 15:21:31 -04:00
[ ' `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 ]
2020-07-29 11:30:54 -04:00
);
2020-08-03 15:38:03 -04:00
return DI :: dba () -> count ( 'contact' , $condition );
2020-07-29 11:30:54 -04:00
}
/**
* 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
2022-06-23 05:39:45 -04:00
* @ return array | bool Array on success , false on failure
2020-07-29 11:30:54 -04:00
* @ throws Exception
*/
2020-08-04 22:44:18 -04:00
public static function listCommonFollows ( int $sourceId , int $targetId , array $condition = [], int $count = 30 , int $offset = 0 , bool $shuffle = false )
2020-07-29 11:30:54 -04:00
{
$condition = DBA :: mergeConditions ( $condition ,
2020-08-03 15:21:31 -04:00
[ " `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 ]
2020-07-29 11:30:54 -04:00
);
2020-08-04 22:44:18 -04:00
return DI :: dba () -> selectToArray ( 'contact' , [], $condition ,
2020-10-09 15:08:50 -04:00
[ 'limit' => [ $offset , $count ], 'order' => [ $shuffle ? 'RAND()' : 'name' ]]
2020-07-29 11:30:54 -04:00
);
}
/**
* 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
*/
2022-06-23 05:39:45 -04:00
public static function countCommonFollowers ( int $sourceId , int $targetId , array $condition = []) : int
2020-07-29 11:30:54 -04:00
{
$condition = DBA :: mergeConditions ( $condition ,
2020-08-03 15:21:31 -04:00
[ " `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 ]
2020-07-29 11:30:54 -04:00
);
2020-08-03 15:21:31 -04:00
return DI :: dba () -> count ( 'contact' , $condition );
2020-07-29 11:30:54 -04:00
}
/**
* 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
2022-06-23 05:39:45 -04:00
* @ return array | bool Array on success , false on failure
2020-07-29 11:30:54 -04:00
* @ throws Exception
*/
2020-08-04 22:44:18 -04:00
public static function listCommonFollowers ( int $sourceId , int $targetId , array $condition = [], int $count = 30 , int $offset = 0 , bool $shuffle = false )
2020-07-29 11:30:54 -04:00
{
$condition = DBA :: mergeConditions ( $condition ,
2020-08-03 15:21:31 -04:00
[ " `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 ]
2020-07-29 11:30:54 -04:00
);
2020-08-04 22:44:18 -04:00
return DI :: dba () -> selectToArray ( 'contact' , [], $condition ,
2020-10-09 15:08:50 -04:00
[ 'limit' => [ $offset , $count ], 'order' => [ $shuffle ? 'RAND()' : 'name' ]]
2020-07-29 11:30:54 -04:00
);
}
2023-08-30 15:17:42 -04:00
/**
* Calculate the interaction scores for the given user
*
* @ param integer $uid
* @ return void
*/
public static function calculateInteractionScore ( int $uid )
{
2023-09-01 18:28:25 -04:00
$days = DI :: config () -> get ( 'channel' , 'interaction_score_days' );
2023-08-30 15:17:42 -04:00
$contact_id = Contact :: getPublicIdByUserId ( $uid );
Logger :: debug ( 'Calculation - start' , [ 'uid' => $uid , 'cid' => $contact_id , 'days' => $days ]);
$follow = Verb :: getID ( Activity :: FOLLOW );
$view = Verb :: getID ( Activity :: VIEW );
$read = Verb :: getID ( Activity :: READ );
DBA :: update ( 'contact-relation' , [ 'score' => 0 , 'relation-score' => 0 , 'thread-score' => 0 , 'relation-thread-score' => 0 ], [ 'cid' => $contact_id ]);
$total = DBA :: fetchFirst ( " SELECT count(*) AS `activity` FROM `post-user` INNER JOIN `post` ON `post`.`uri-id` = `post-user`.`thr-parent-id` WHERE `post-user`.`author-id` = ? AND `post-user`.`received` >= ? AND `post-user`.`uid` = ? AND `post`.`author-id` != ? AND NOT `post`.`vid` IN (?, ?, ?) " ,
$contact_id , DateTimeFormat :: utc ( 'now - ' . $days . ' day' ), $uid , $contact_id , $follow , $view , $read );
Logger :: debug ( 'Calculate score' , [ 'uid' => $uid , 'total' => $total [ 'activity' ]]);
$interactions = DBA :: p ( " SELECT `post`.`author-id`, count(*) AS `activity` FROM `post-user` INNER JOIN `post` ON `post`.`uri-id` = `post-user`.`thr-parent-id` WHERE `post-user`.`author-id` = ? AND `post-user`.`received` >= ? AND `post-user`.`uid` = ? AND `post`.`author-id` != ? AND NOT `post`.`vid` IN (?, ?, ?) GROUP BY `post`.`author-id` " ,
$contact_id , DateTimeFormat :: utc ( 'now - ' . $days . ' day' ), $uid , $contact_id , $follow , $view , $read );
while ( $interaction = DBA :: fetch ( $interactions )) {
$score = min (( int )(( $interaction [ 'activity' ] / $total [ 'activity' ]) * 65535 ), 65535 );
DBA :: update ( 'contact-relation' , [ 'score' => $score ], [ 'cid' => $contact_id , 'relation-cid' => $interaction [ 'author-id' ]]);
}
DBA :: close ( $interactions );
$total = DBA :: fetchFirst ( " SELECT count(*) AS `activity` FROM `post-user` INNER JOIN `post` ON `post`.`uri-id` = `post-user`.`parent-uri-id` WHERE `post-user`.`author-id` = ? AND `post-user`.`received` >= ? AND `post-user`.`uid` = ? AND `post`.`author-id` != ? AND NOT `post`.`vid` IN (?, ?, ?) " ,
$contact_id , DateTimeFormat :: utc ( 'now - ' . $days . ' day' ), $uid , $contact_id , $follow , $view , $read );
Logger :: debug ( 'Calculate thread-score' , [ 'uid' => $uid , 'total' => $total [ 'activity' ]]);
$interactions = DBA :: p ( " SELECT `post`.`author-id`, count(*) AS `activity` FROM `post-user` INNER JOIN `post` ON `post`.`uri-id` = `post-user`.`parent-uri-id` WHERE `post-user`.`author-id` = ? AND `post-user`.`received` >= ? AND `post-user`.`uid` = ? AND `post`.`author-id` != ? AND NOT `post`.`vid` IN (?, ?, ?) GROUP BY `post`.`author-id` " ,
$contact_id , DateTimeFormat :: utc ( 'now - ' . $days . ' day' ), $uid , $contact_id , $follow , $view , $read );
while ( $interaction = DBA :: fetch ( $interactions )) {
$score = min (( int )(( $interaction [ 'activity' ] / $total [ 'activity' ]) * 65535 ), 65535 );
DBA :: update ( 'contact-relation' , [ 'thread-score' => $score ], [ 'cid' => $contact_id , 'relation-cid' => $interaction [ 'author-id' ]]);
}
DBA :: close ( $interactions );
$total = DBA :: fetchFirst ( " SELECT count(*) AS `activity` FROM `post-user` INNER JOIN `post` ON `post-user`.`uri-id` = `post`.`thr-parent-id` WHERE `post-user`.`author-id` = ? AND `post-user`.`received` >= ? AND `post-user`.`uid` = ? AND `post`.`author-id` != ? AND NOT `post`.`vid` IN (?, ?, ?) " ,
$contact_id , DateTimeFormat :: utc ( 'now - ' . $days . ' day' ), $uid , $contact_id , $follow , $view , $read );
Logger :: debug ( 'Calculate relation-score' , [ 'uid' => $uid , 'total' => $total [ 'activity' ]]);
$interactions = DBA :: p ( " SELECT `post`.`author-id`, count(*) AS `activity` FROM `post-user` INNER JOIN `post` ON `post-user`.`uri-id` = `post`.`thr-parent-id` WHERE `post-user`.`author-id` = ? AND `post-user`.`received` >= ? AND `post-user`.`uid` = ? AND `post`.`author-id` != ? AND NOT `post`.`vid` IN (?, ?, ?) GROUP BY `post`.`author-id` " ,
$contact_id , DateTimeFormat :: utc ( 'now - ' . $days . ' day' ), $uid , $contact_id , $follow , $view , $read );
while ( $interaction = DBA :: fetch ( $interactions )) {
$score = min (( int )(( $interaction [ 'activity' ] / $total [ 'activity' ]) * 65535 ), 65535 );
DBA :: update ( 'contact-relation' , [ 'relation-score' => $score ], [ 'cid' => $contact_id , 'relation-cid' => $interaction [ 'author-id' ]]);
}
DBA :: close ( $interactions );
$total = DBA :: fetchFirst ( " SELECT count(*) AS `activity` FROM `post-user` INNER JOIN `post` ON `post-user`.`uri-id` = `post`.`parent-uri-id` WHERE `post-user`.`author-id` = ? AND `post-user`.`received` >= ? AND `post-user`.`uid` = ? AND `post`.`author-id` != ? AND NOT `post`.`vid` IN (?, ?, ?) " ,
$contact_id , DateTimeFormat :: utc ( 'now - ' . $days . ' day' ), $uid , $contact_id , $follow , $view , $read );
Logger :: debug ( 'Calculate relation-thread-score' , [ 'uid' => $uid , 'total' => $total [ 'activity' ]]);
$interactions = DBA :: p ( " SELECT `post`.`author-id`, count(*) AS `activity` FROM `post-user` INNER JOIN `post` ON `post-user`.`uri-id` = `post`.`parent-uri-id` WHERE `post-user`.`author-id` = ? AND `post-user`.`received` >= ? AND `post-user`.`uid` = ? AND `post`.`author-id` != ? AND NOT `post`.`vid` IN (?, ?, ?) GROUP BY `post`.`author-id` " ,
$contact_id , DateTimeFormat :: utc ( 'now - ' . $days . ' day' ), $uid , $contact_id , $follow , $view , $read );
while ( $interaction = DBA :: fetch ( $interactions )) {
$score = min (( int )(( $interaction [ 'activity' ] / $total [ 'activity' ]) * 65535 ), 65535 );
DBA :: update ( 'contact-relation' , [ 'relation-thread-score' => $score ], [ 'cid' => $contact_id , 'relation-cid' => $interaction [ 'author-id' ]]);
}
DBA :: close ( $interactions );
Logger :: debug ( 'Calculation - end' , [ 'uid' => $uid ]);
}
2020-07-26 03:34:33 -04:00
}