2023-09-09 09:14:36 +00:00
< ? php
/**
* @ copyright Copyright ( C ) 2010 - 2023 , the Friendica project
*
* @ 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 />.
*
*/
namespace Friendica\Module\Conversation ;
use Friendica\App ;
use Friendica\App\Mode ;
use Friendica\BaseModule ;
use Friendica\Content\Conversation\Collection\Timelines ;
use Friendica\Content\Conversation\Entity\Timeline as TimelineEntity ;
2023-09-19 09:05:28 +00:00
use Friendica\Content\Conversation\Repository\Channel ;
2023-09-09 09:14:36 +00:00
use Friendica\Core\Cache\Capability\ICanCache ;
use Friendica\Core\Cache\Enum\Duration ;
use Friendica\Core\Config\Capability\IManageConfigValues ;
use Friendica\Core\L10n ;
use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues ;
use Friendica\Core\Renderer ;
use Friendica\Core\Session\Capability\IHandleUserSessions ;
use Friendica\Model\Contact ;
use Friendica\Model\User ;
use Friendica\Database\Database ;
2023-09-09 12:00:22 +00:00
use Friendica\Database\DBA ;
2023-09-09 09:14:36 +00:00
use Friendica\Model\Item ;
use Friendica\Model\Post ;
use Friendica\Module\Response ;
use Friendica\Util\DateTimeFormat ;
use Friendica\Util\Profiler ;
use Psr\Log\LoggerInterface ;
class Timeline extends BaseModule
{
/** @var string */
2023-09-09 12:48:51 +00:00
protected $selectedTab ;
2023-09-09 09:14:36 +00:00
/** @var mixed */
2023-09-09 13:02:20 +00:00
protected $minId ;
2023-09-09 09:14:36 +00:00
/** @var mixed */
2023-09-09 13:02:20 +00:00
protected $maxId ;
2023-09-09 09:14:36 +00:00
/** @var string */
2023-09-09 12:48:51 +00:00
protected $accountTypeString ;
2023-09-09 09:14:36 +00:00
/** @var int */
2023-09-09 12:48:51 +00:00
protected $accountType ;
2023-09-09 09:14:36 +00:00
/** @var int */
2023-09-09 12:48:51 +00:00
protected $itemUriId ;
2023-09-09 09:14:36 +00:00
/** @var int */
2023-09-09 12:48:51 +00:00
protected $itemsPerPage ;
2023-09-09 09:14:36 +00:00
/** @var bool */
2023-09-09 12:48:51 +00:00
protected $noSharer ;
2023-09-16 04:21:59 +00:00
/** @var bool */
protected $force ;
2023-09-17 08:17:31 +00:00
/** @var bool */
protected $update ;
2023-09-09 09:14:36 +00:00
/** @var App\Mode $mode */
protected $mode ;
2023-09-09 14:13:58 +00:00
/** @var IHandleUserSessions */
2023-09-09 09:14:36 +00:00
protected $session ;
/** @var Database */
protected $database ;
/** @var IManagePersonalConfigValues */
protected $pConfig ;
/** @var IManageConfigValues The config */
protected $config ;
/** @var ICanCache */
protected $cache ;
2023-09-19 09:05:28 +00:00
/** @var Channel */
protected $channel ;
2023-09-09 09:14:36 +00:00
2023-09-19 09:05:28 +00:00
public function __construct ( Channel $channel , Mode $mode , IHandleUserSessions $session , Database $database , IManagePersonalConfigValues $pConfig , IManageConfigValues $config , ICanCache $cache , L10n $l10n , App\BaseURL $baseUrl , App\Arguments $args , LoggerInterface $logger , Profiler $profiler , Response $response , array $server , array $parameters = [])
2023-09-09 09:14:36 +00:00
{
parent :: __construct ( $l10n , $baseUrl , $args , $logger , $profiler , $response , $server , $parameters );
2023-09-19 09:05:28 +00:00
$this -> channel = $channel ;
2023-09-09 09:14:36 +00:00
$this -> mode = $mode ;
$this -> session = $session ;
$this -> database = $database ;
$this -> pConfig = $pConfig ;
$this -> config = $config ;
$this -> cache = $cache ;
}
/**
* Computes module parameters from the request and local configuration
*
* @ throws HTTPException\BadRequestException
* @ throws HTTPException\ForbiddenException
*/
protected function parseRequest ( array $request )
{
2023-09-09 12:00:22 +00:00
$this -> logger -> debug ( 'Got request' , $request );
2023-09-09 17:38:09 +00:00
$this -> selectedTab = $this -> parameters [ 'content' ] ? ? $request [ 'channel' ] ? ? '' ;
2023-09-09 09:14:36 +00:00
2023-09-09 12:48:51 +00:00
$this -> accountTypeString = $request [ 'accounttype' ] ? ? $this -> parameters [ 'accounttype' ] ? ? '' ;
$this -> accountType = User :: getAccountTypeByString ( $this -> accountTypeString );
2023-09-09 09:14:36 +00:00
if ( $this -> mode -> isMobile ()) {
2023-09-09 12:48:51 +00:00
$this -> itemsPerPage = $this -> pConfig -> get (
2023-09-09 09:14:36 +00:00
$this -> session -> getLocalUserId (),
'system' ,
'itemspage_mobile_network' ,
$this -> config -> get ( 'system' , 'itemspage_network_mobile' )
);
} else {
2023-09-09 12:48:51 +00:00
$this -> itemsPerPage = $this -> pConfig -> get (
2023-09-09 09:14:36 +00:00
$this -> session -> getLocalUserId (),
'system' ,
'itemspage_network' ,
$this -> config -> get ( 'system' , 'itemspage_network' )
);
}
2023-09-09 12:00:22 +00:00
if ( ! empty ( $request [ 'item' ])) {
2023-09-09 13:02:20 +00:00
$item = Post :: selectFirst ([ 'parent' , 'parent-uri-id' ], [ 'id' => $request [ 'item' ]]);
2023-09-09 12:48:51 +00:00
$this -> itemUriId = $item [ 'parent-uri-id' ] ? ? 0 ;
2023-09-09 12:00:22 +00:00
} else {
2023-09-09 12:48:51 +00:00
$this -> itemUriId = 0 ;
2023-09-09 12:00:22 +00:00
}
2023-09-09 13:02:20 +00:00
$this -> minId = $request [ 'min_id' ] ? ? null ;
$this -> maxId = $request [ 'max_id' ] ? ? null ;
2023-09-09 09:14:36 +00:00
2023-09-09 12:48:51 +00:00
$this -> noSharer = ! empty ( $request [ 'no_sharer' ]);
2023-09-17 08:17:31 +00:00
$this -> force = ! empty ( $request [ 'force' ]) && ! empty ( $request [ 'item' ]);
$this -> update = ! empty ( $request [ 'force' ]) && ! empty ( $request [ 'first_received' ]) && ! empty ( $request [ 'first_created' ]) && ! empty ( $request [ 'first_uriid' ]) && ! empty ( $request [ 'first_commented' ]);
2023-09-09 09:14:36 +00:00
}
protected function getNoSharerWidget ( string $base ) : string
{
2023-09-09 12:48:51 +00:00
$path = $this -> selectedTab ;
if ( ! empty ( $this -> accountTypeString )) {
$path .= '/' . $this -> accountTypeString ;
2023-09-09 09:14:36 +00:00
}
$query_parameters = [];
2023-09-09 13:02:20 +00:00
if ( ! empty ( $this -> minId )) {
$query_parameters [ 'min_id' ] = $this -> minId ;
2023-09-09 09:14:36 +00:00
}
2023-09-09 13:02:20 +00:00
if ( ! empty ( $this -> maxId )) {
$query_parameters [ 'max_id' ] = $this -> maxId ;
2023-09-09 09:14:36 +00:00
}
2023-09-09 09:30:55 +00:00
$path_all = $path . ( ! empty ( $query_parameters ) ? '?' . http_build_query ( $query_parameters ) : '' );
2023-09-09 09:14:36 +00:00
$path_no_sharer = $path . '?' . http_build_query ( array_merge ( $query_parameters , [ 'no_sharer' => true ]));
return Renderer :: replaceMacros ( Renderer :: getMarkupTemplate ( 'widget/community_sharer.tpl' ), [
'$title' => $this -> l10n -> t ( 'Own Contacts' ),
'$path_all' => $path_all ,
'$path_no_sharer' => $path_no_sharer ,
2023-09-09 12:48:51 +00:00
'$no_sharer' => $this -> noSharer ,
2023-09-09 09:14:36 +00:00
'$all' => $this -> l10n -> t ( 'Include' ),
'$no_sharer_label' => $this -> l10n -> t ( 'Hide' ),
'$base' => $base ,
]);
}
2023-09-09 17:38:09 +00:00
protected function getTabArray ( Timelines $timelines , string $prefix , string $parameter = '' ) : array
2023-09-09 09:14:36 +00:00
{
$tabs = [];
foreach ( $timelines as $tab ) {
2023-09-09 17:38:09 +00:00
if ( is_null ( $tab -> path ) && ! empty ( $parameter )) {
$path = $prefix . '?' . http_build_query ([ $parameter => $tab -> code ]);
} else {
$path = $tab -> path ? ? $prefix . '/' . $tab -> code ;
}
$tabs [ $tab -> code ] = [
2023-09-19 09:05:28 +00:00
'code' => $tab -> code ,
2023-09-09 09:14:36 +00:00
'label' => $tab -> label ,
2023-09-09 17:38:09 +00:00
'url' => $path ,
2023-09-09 12:48:51 +00:00
'sel' => $this -> selectedTab == $tab -> code ? 'active' : '' ,
2023-09-09 09:14:36 +00:00
'title' => $tab -> description ,
'id' => $prefix . '-' . $tab -> code . '-tab' ,
'accesskey' => $tab -> accessKey ,
];
}
return $tabs ;
}
/**
* Database query for the channel page
*
* @ return array
* @ throws \Exception
*/
protected function getChannelItems ()
2023-09-12 10:55:33 +00:00
{
$items = $this -> getRawChannelItems ();
2023-09-16 04:21:59 +00:00
$contacts = $this -> database -> selectToArray ( 'user-contact' , [ 'cid' ], [ 'channel-frequency' => Contact\User :: FREQUENCY_REDUCED , 'cid' => array_column ( $items , 'owner-id' )]);
2023-09-12 10:55:33 +00:00
$reduced = array_column ( $contacts , 'cid' );
$maxpostperauthor = $this -> config -> get ( 'channel' , 'max_posts_per_author' );
if ( $maxpostperauthor != 0 ) {
$count = 1 ;
2023-09-16 04:21:59 +00:00
$owner_posts = [];
2023-09-12 10:55:33 +00:00
$selected_items = [];
while ( count ( $selected_items ) < $this -> itemsPerPage && ++ $count < 50 && count ( $items ) > 0 ) {
2023-09-16 04:21:59 +00:00
$maxposts = round (( count ( $items ) / $this -> itemsPerPage ) * $maxpostperauthor );
$minId = $items [ array_key_first ( $items )][ 'created' ];
$maxId = $items [ array_key_last ( $items )][ 'created' ];
2023-09-12 10:55:33 +00:00
foreach ( $items as $item ) {
2023-09-16 04:21:59 +00:00
if ( ! in_array ( $item [ 'owner-id' ], $reduced )) {
continue ;
2023-09-12 10:55:33 +00:00
}
2023-09-16 04:21:59 +00:00
$owner_posts [ $item [ 'owner-id' ]][ $item [ 'uri-id' ]] = (( $item [ 'comments' ] * 100 ) + $item [ 'activities' ]);
2023-09-12 10:55:33 +00:00
}
2023-09-16 04:21:59 +00:00
foreach ( $owner_posts as $posts ) {
if ( count ( $posts ) <= $maxposts ) {
continue ;
}
asort ( $posts );
while ( count ( $posts ) > $maxposts ) {
$uri_id = array_key_first ( $posts );
unset ( $posts [ $uri_id ]);
unset ( $items [ $uri_id ]);
}
}
$selected_items = array_merge ( $selected_items , $items );
2023-09-12 10:55:33 +00:00
// If we're looking at a "previous page", the lookup continues forward in time because the list is
// sorted in chronologically decreasing order
2023-09-16 04:21:59 +00:00
if ( ! empty ( $this -> minId )) {
$this -> minId = $minId ;
2023-09-12 10:55:33 +00:00
} else {
// In any other case, the lookup continues backwards in time
2023-09-16 04:21:59 +00:00
$this -> maxId = $maxId ;
2023-09-12 10:55:33 +00:00
}
if ( count ( $selected_items ) < $this -> itemsPerPage ) {
$items = $this -> getRawChannelItems ();
}
}
} else {
$selected_items = $items ;
}
$condition = [ 'unseen' => true , 'uid' => $this -> session -> getLocalUserId (), 'parent-uri-id' => array_column ( $selected_items , 'uri-id' )];
$this -> setItemsSeenByCondition ( $condition );
return $selected_items ;
}
/**
* Database query for the channel page
*
* @ return array
* @ throws \Exception
*/
private function getRawChannelItems ()
2023-09-09 09:14:36 +00:00
{
$uid = $this -> session -> getLocalUserId ();
2023-09-09 12:48:51 +00:00
if ( $this -> selectedTab == TimelineEntity :: WHATSHOT ) {
if ( ! is_null ( $this -> accountType )) {
2023-09-12 10:55:33 +00:00
$condition = [ " (`comments` > ? OR `activities` > ?) AND `contact-type` = ? " , $this -> getMedianComments ( $uid , 4 ), $this -> getMedianActivities ( $uid , 4 ), $this -> accountType ];
2023-09-09 09:14:36 +00:00
} else {
2023-09-12 10:55:33 +00:00
$condition = [ " (`comments` > ? OR `activities` > ?) AND `contact-type` != ? " , $this -> getMedianComments ( $uid , 4 ), $this -> getMedianActivities ( $uid , 4 ), Contact :: TYPE_COMMUNITY ];
2023-09-09 09:14:36 +00:00
}
2023-09-09 12:48:51 +00:00
} elseif ( $this -> selectedTab == TimelineEntity :: FORYOU ) {
2023-09-09 09:14:36 +00:00
$cid = Contact :: getPublicIdByUserId ( $uid );
2023-09-09 09:30:55 +00:00
2023-09-09 09:14:36 +00:00
$condition = [
" (`owner-id` IN (SELECT `cid` FROM `contact-relation` WHERE `relation-cid` = ? AND `relation-thread-score` > ?) OR
(( `comments` >= ? OR `activities` >= ? ) AND `owner-id` IN ( SELECT `cid` FROM `contact-relation` WHERE `follows` AND `relation-cid` = ? )) OR
2023-09-16 04:21:59 +00:00
( `owner-id` IN ( SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND ( `notify_new_posts` OR `channel-frequency` = ? )))) " ,
2023-09-09 09:14:36 +00:00
$cid , $this -> getMedianRelationThreadScore ( $cid , 4 ), $this -> getMedianComments ( $uid , 4 ), $this -> getMedianActivities ( $uid , 4 ), $cid ,
2023-09-16 04:21:59 +00:00
$uid , Contact\User :: FREQUENCY_ALWAYS
2023-09-09 09:14:36 +00:00
];
2023-09-09 12:48:51 +00:00
} elseif ( $this -> selectedTab == TimelineEntity :: FOLLOWERS ) {
2023-09-09 09:14:36 +00:00
$condition = [ " `owner-id` IN (SELECT `pid` FROM `account-user-view` WHERE `uid` = ? AND `rel` = ?) " , $uid , Contact :: FOLLOWER ];
2023-09-09 12:48:51 +00:00
} elseif ( $this -> selectedTab == TimelineEntity :: SHARERSOFSHARERS ) {
2023-09-09 09:14:36 +00:00
$cid = Contact :: getPublicIdByUserId ( $uid );
// @todo Suggest posts from contacts that are followed most by our followers
$condition = [
" `owner-id` IN (SELECT `cid` FROM `contact-relation` WHERE `follows` AND `last-interaction` > ?
AND `relation-cid` IN ( SELECT `cid` FROM `contact-relation` WHERE `follows` AND `relation-cid` = ? AND `relation-thread-score` >= ? )
AND NOT `cid` IN ( SELECT `cid` FROM `contact-relation` WHERE `follows` AND `relation-cid` = ? )) " ,
DateTimeFormat :: utc ( 'now - ' . $this -> config -> get ( 'channel' , 'sharer_interaction_days' ) . ' day' ), $cid , $this -> getMedianRelationThreadScore ( $cid , 4 ), $cid
];
2023-09-09 12:48:51 +00:00
} elseif ( $this -> selectedTab == TimelineEntity :: IMAGE ) {
2023-09-09 09:14:36 +00:00
$condition = [ " `media-type` & ? " , 1 ];
2023-09-09 12:48:51 +00:00
} elseif ( $this -> selectedTab == TimelineEntity :: VIDEO ) {
2023-09-09 09:14:36 +00:00
$condition = [ " `media-type` & ? " , 2 ];
2023-09-09 12:48:51 +00:00
} elseif ( $this -> selectedTab == TimelineEntity :: AUDIO ) {
2023-09-09 09:14:36 +00:00
$condition = [ " `media-type` & ? " , 4 ];
2023-09-09 12:48:51 +00:00
} elseif ( $this -> selectedTab == TimelineEntity :: LANGUAGE ) {
2023-09-09 09:14:36 +00:00
$condition = [ " JSON_EXTRACT(JSON_KEYS(language), ' $ [0]') = ? " , $this -> l10n -> convertCodeForLanguageDetection ( User :: getLanguageCode ( $uid ))];
2023-09-19 09:05:28 +00:00
} elseif ( is_numeric ( $this -> selectedTab )) {
$condition = $this -> getUserChannelConditions ( $this -> selectedTab , $this -> session -> getLocalUserId ());
2023-09-09 09:14:36 +00:00
}
2023-09-09 12:48:51 +00:00
if ( $this -> selectedTab != TimelineEntity :: LANGUAGE ) {
2023-09-09 09:14:36 +00:00
$condition = $this -> addLanguageCondition ( $uid , $condition );
}
2023-09-17 19:28:38 +00:00
$condition = DBA :: mergeConditions ( $condition , [ " (NOT `restricted` OR EXISTS(SELECT `id` FROM `post-user` WHERE `uid` = ? AND `uri-id` = `post-engagement`.`uri-id`)) " , $uid ]);
2023-09-16 04:21:59 +00:00
$condition = DBA :: mergeConditions ( $condition , [ " NOT EXISTS(SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `cid` = `post-engagement`.`owner-id` AND (`ignored` OR `blocked` OR `collapsed` OR `is-blocked` OR `channel-frequency` = ?)) " , $uid , Contact\User :: FREQUENCY_NEVER ]);
2023-09-09 09:14:36 +00:00
2023-09-09 12:48:51 +00:00
if (( $this -> selectedTab != TimelineEntity :: WHATSHOT ) && ! is_null ( $this -> accountType )) {
$condition = DBA :: mergeConditions ( $condition , [ 'contact-type' => $this -> accountType ]);
2023-09-09 09:14:36 +00:00
}
2023-09-09 12:48:51 +00:00
$params = [ 'order' => [ 'created' => true ], 'limit' => $this -> itemsPerPage ];
2023-09-09 09:14:36 +00:00
2023-09-09 12:48:51 +00:00
if ( ! empty ( $this -> itemUriId )) {
$condition = DBA :: mergeConditions ( $condition , [ 'uri-id' => $this -> itemUriId ]);
2023-09-09 09:14:36 +00:00
} else {
2023-09-09 12:48:51 +00:00
if ( $this -> noSharer ) {
2023-09-09 12:00:22 +00:00
$condition = DBA :: mergeConditions ( $condition , [ " NOT `uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE `post-user`.`uid` = ? AND `post-user`.`uri-id` = `post-engagement`.`uri-id`) " , $this -> session -> getLocalUserId ()]);
2023-09-09 09:14:36 +00:00
}
2023-09-09 13:02:20 +00:00
if ( isset ( $this -> maxId )) {
$condition = DBA :: mergeConditions ( $condition , [ " `created` < ? " , $this -> maxId ]);
2023-09-09 09:14:36 +00:00
}
2023-09-09 13:02:20 +00:00
if ( isset ( $this -> minId )) {
$condition = DBA :: mergeConditions ( $condition , [ " `created` > ? " , $this -> minId ]);
2023-09-09 09:14:36 +00:00
// Previous page case: we want the items closest to min_id but for that we need to reverse the query order
2023-09-09 13:02:20 +00:00
if ( ! isset ( $this -> maxId )) {
2023-09-09 09:14:36 +00:00
$params [ 'order' ][ 'created' ] = false ;
}
}
}
2023-09-16 04:21:59 +00:00
$items = [];
$result = $this -> database -> select ( 'post-engagement' , [ 'uri-id' , 'created' , 'owner-id' , 'comments' , 'activities' ], $condition , $params );
while ( $item = $this -> database -> fetch ( $result )) {
$items [ $item [ 'uri-id' ]] = $item ;
}
$this -> database -> close ( $result );
2023-09-09 09:14:36 +00:00
if ( empty ( $items )) {
return [];
}
// Previous page case: once we get the relevant items closest to min_id, we need to restore the expected display order
2023-09-09 13:02:20 +00:00
if ( empty ( $this -> itemUriId ) && isset ( $this -> minId ) && ! isset ( $this -> maxId )) {
2023-09-16 04:21:59 +00:00
$items = array_reverse ( $items , true );
2023-09-09 09:14:36 +00:00
}
2023-09-09 14:13:58 +00:00
$condition = [ 'unseen' => true , 'uid' => $uid , 'parent-uri-id' => array_column ( $items , 'uri-id' )];
$this -> setItemsSeenByCondition ( $condition );
2023-09-09 09:14:36 +00:00
return $items ;
}
2023-09-19 09:05:28 +00:00
private function getUserChannelConditions ( int $id , int $uid ) : array
{
$channel = $this -> channel -> selectById ( $id , $uid );
if ( empty ( $channel )) {
return [];
}
$condition = [];
if ( ! empty ( $channel -> fullTextSearch )) {
$first = $this -> database -> selectFirst ( 'post-engagement' , [ 'uri-id' ]);
$condition = DBA :: mergeConditions ( $condition , [ " `uri-id` IN (SELECT `uri-id` FROM `post-content` WHERE `uri-id` >= ? AND MATCH (`title`, `content-warning`, `body`) AGAINST (? IN BOOLEAN MODE)) " , $first [ 'uri-id' ], $channel -> fullTextSearch ]);
}
if ( ! empty ( $channel -> includeTags )) {
$search = explode ( ',' , mb_strtolower ( $channel -> includeTags ));
$placeholders = substr ( str_repeat ( " ?, " , count ( $search )), 0 , - 2 );
$condition = DBA :: mergeConditions ( $condition , array_merge ([ " `uri-id` IN (SELECT `uri-id` FROM `post-tag` INNER JOIN `tag` ON `tag`.`id` = `post-tag`.`tid` WHERE `post-tag`.`type` = 1 AND `name` IN ( " . $placeholders . " )) " ], $search ));
}
if ( ! empty ( $channel -> excludeTags )) {
$search = explode ( ',' , mb_strtolower ( $channel -> excludeTags ));
$placeholders = substr ( str_repeat ( " ?, " , count ( $search )), 0 , - 2 );
$condition = DBA :: mergeConditions ( $condition , array_merge ([ " NOT `uri-id` IN (SELECT `uri-id` FROM `post-tag` INNER JOIN `tag` ON `tag`.`id` = `post-tag`.`tid` WHERE `post-tag`.`type` = 1 AND `name` IN ( " . $placeholders . " )) " ], $search ));
}
if ( ! empty ( $channel -> mediaType )) {
$condition = DBA :: mergeConditions ( $condition , [ " `media-type` & ? " , $channel -> mediaType ]);
}
return $condition ;
}
2023-09-09 09:14:36 +00:00
private function addLanguageCondition ( int $uid , array $condition ) : array
{
$conditions = [];
$languages = $this -> pConfig -> get ( $uid , 'channel' , 'languages' , [ User :: getLanguageCode ( $uid )]);
$languages = $this -> l10n -> convertForLanguageDetection ( $languages );
foreach ( $languages as $language ) {
$conditions [] = " JSON_EXTRACT(JSON_KEYS(language), ' $ [0]') = ? " ;
$condition [] = $language ;
}
if ( ! empty ( $conditions )) {
$condition [ 0 ] .= " AND (`language` IS NULL OR " . implode ( ' OR ' , $conditions ) . " ) " ;
}
return $condition ;
}
private function getMedianComments ( int $uid , int $divider ) : int
{
$languages = $this -> pConfig -> get ( $uid , 'channel' , 'languages' , [ User :: getLanguageCode ( $uid )]);
$cache_key = 'Channel:getMedianComments:' . $divider . ':' . implode ( ':' , $languages );
$comments = $this -> cache -> get ( $cache_key );
if ( ! empty ( $comments )) {
return $comments ;
}
2023-09-17 19:28:38 +00:00
$condition = [ " `contact-type` != ? AND `comments` > ? AND NOT `restricted` " , Contact :: TYPE_COMMUNITY , 0 ];
2023-09-09 09:14:36 +00:00
$condition = $this -> addLanguageCondition ( $uid , $condition );
$limit = $this -> database -> count ( 'post-engagement' , $condition ) / $divider ;
$post = $this -> database -> selectToArray ( 'post-engagement' , [ 'comments' ], $condition , [ 'order' => [ 'comments' => true ], 'limit' => [ $limit , 1 ]]);
$comments = $post [ 0 ][ 'comments' ] ? ? 0 ;
if ( empty ( $comments )) {
return 0 ;
}
$this -> cache -> set ( $cache_key , $comments , Duration :: HALF_HOUR );
$this -> logger -> debug ( 'Calculated median comments' , [ 'divider' => $divider , 'languages' => $languages , 'median' => $comments ]);
return $comments ;
}
private function getMedianActivities ( int $uid , int $divider ) : int
{
$languages = $this -> pConfig -> get ( $uid , 'channel' , 'languages' , [ User :: getLanguageCode ( $uid )]);
$cache_key = 'Channel:getMedianActivities:' . $divider . ':' . implode ( ':' , $languages );
$activities = $this -> cache -> get ( $cache_key );
if ( ! empty ( $activities )) {
return $activities ;
}
2023-09-17 19:28:38 +00:00
$condition = [ " `contact-type` != ? AND `activities` > ? AND NOT `restricted` " , Contact :: TYPE_COMMUNITY , 0 ];
2023-09-09 09:14:36 +00:00
$condition = $this -> addLanguageCondition ( $uid , $condition );
$limit = $this -> database -> count ( 'post-engagement' , $condition ) / $divider ;
$post = $this -> database -> selectToArray ( 'post-engagement' , [ 'activities' ], $condition , [ 'order' => [ 'activities' => true ], 'limit' => [ $limit , 1 ]]);
$activities = $post [ 0 ][ 'activities' ] ? ? 0 ;
if ( empty ( $activities )) {
return 0 ;
}
$this -> cache -> set ( $cache_key , $activities , Duration :: HALF_HOUR );
$this -> logger -> debug ( 'Calculated median activities' , [ 'divider' => $divider , 'languages' => $languages , 'median' => $activities ]);
return $activities ;
}
private function getMedianRelationThreadScore ( int $cid , int $divider ) : int
{
$cache_key = 'Channel:getThreadScore:' . $cid . ':' . $divider ;
$score = $this -> cache -> get ( $cache_key );
if ( ! empty ( $score )) {
return $score ;
}
$condition = [ " `relation-cid` = ? AND `relation-thread-score` > ? " , $cid , 0 ];
$limit = $this -> database -> count ( 'contact-relation' , $condition ) / $divider ;
$relation = $this -> database -> selectToArray ( 'contact-relation' , [ 'relation-thread-score' ], $condition , [ 'order' => [ 'relation-thread-score' => true ], 'limit' => [ $limit , 1 ]]);
$score = $relation [ 0 ][ 'relation-thread-score' ] ? ? 0 ;
if ( empty ( $score )) {
return 0 ;
}
$this -> cache -> set ( $cache_key , $score , Duration :: HALF_HOUR );
$this -> logger -> debug ( 'Calculated median score' , [ 'cid' => $cid , 'divider' => $divider , 'median' => $score ]);
return $score ;
}
/**
* Computes the displayed items .
*
* Community pages have a restriction on how many successive posts by the same author can show on any given page ,
* so we may have to retrieve more content beyond the first query
*
* @ return array
* @ throws \Exception
*/
protected function getCommunityItems ()
{
$items = $this -> selectItems ();
$maxpostperauthor = ( int ) $this -> config -> get ( 'system' , 'max_author_posts_community_page' );
2023-09-09 12:48:51 +00:00
if ( $maxpostperauthor != 0 && $this -> selectedTab == 'local' ) {
2023-09-09 09:30:55 +00:00
$count = 1 ;
2023-09-09 09:14:36 +00:00
$previousauthor = '' ;
2023-09-09 09:30:55 +00:00
$numposts = 0 ;
2023-09-09 09:14:36 +00:00
$selected_items = [];
2023-09-09 12:48:51 +00:00
while ( count ( $selected_items ) < $this -> itemsPerPage && ++ $count < 50 && count ( $items ) > 0 ) {
2023-09-09 09:14:36 +00:00
foreach ( $items as $item ) {
if ( $previousauthor == $item [ " author-link " ]) {
++ $numposts ;
} else {
$numposts = 0 ;
}
$previousauthor = $item [ " author-link " ];
2023-09-09 12:48:51 +00:00
if (( $numposts < $maxpostperauthor ) && ( count ( $selected_items ) < $this -> itemsPerPage )) {
2023-09-09 09:14:36 +00:00
$selected_items [] = $item ;
}
}
// If we're looking at a "previous page", the lookup continues forward in time because the list is
// sorted in chronologically decreasing order
2023-09-09 13:02:20 +00:00
if ( isset ( $this -> minId )) {
2023-09-16 04:21:59 +00:00
$this -> minId = $items [ 0 ][ 'received' ];
2023-09-09 09:14:36 +00:00
} else {
// In any other case, the lookup continues backwards in time
2023-09-16 04:21:59 +00:00
$this -> maxId = $items [ count ( $items ) - 1 ][ 'received' ];
2023-09-09 09:14:36 +00:00
}
$items = $this -> selectItems ();
}
} else {
$selected_items = $items ;
}
2023-09-09 14:13:58 +00:00
$condition = [ 'unseen' => true , 'uid' => $this -> session -> getLocalUserId (), 'parent-uri-id' => array_column ( $selected_items , 'uri-id' )];
$this -> setItemsSeenByCondition ( $condition );
2023-09-09 09:14:36 +00:00
return $selected_items ;
}
/**
* Database query for the community page
*
* @ return array
* @ throws \Exception
* @ TODO Move to repository / factory
*/
private function selectItems ()
{
2023-09-09 12:48:51 +00:00
if ( $this -> selectedTab == 'local' ) {
2023-09-16 04:21:59 +00:00
$condition = [ " `wall` AND `origin` AND `private` = ? " , Item :: PUBLIC ];
2023-09-09 12:48:51 +00:00
} elseif ( $this -> selectedTab == 'global' ) {
2023-09-16 04:21:59 +00:00
$condition = [ " `uid` = ? AND `private` = ? " , 0 , Item :: PUBLIC ];
2023-09-09 09:14:36 +00:00
} else {
return [];
}
2023-09-16 04:21:59 +00:00
if ( ! is_null ( $this -> accountType )) {
$condition = DBA :: mergeConditions ( $condition , [ 'owner-contact-type' => $this -> accountType ]);
}
$params = [ 'order' => [ 'received' => true ], 'limit' => $this -> itemsPerPage ];
2023-09-09 09:14:36 +00:00
2023-09-09 12:48:51 +00:00
if ( ! empty ( $this -> itemUriId )) {
$condition = DBA :: mergeConditions ( $condition , [ 'uri-id' => $this -> itemUriId ]);
2023-09-09 09:14:36 +00:00
} else {
2023-09-09 12:48:51 +00:00
if ( $this -> session -> getLocalUserId () && $this -> noSharer ) {
2023-09-09 12:00:22 +00:00
$condition = DBA :: mergeConditions ( $condition , [ " NOT `uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE `post-user`.`uid` = ? AND `post-user`.`uri-id` = `post-thread-user-view`.`uri-id`) " , $this -> session -> getLocalUserId ()]);
2023-09-09 09:14:36 +00:00
}
2023-09-09 13:02:20 +00:00
if ( isset ( $this -> maxId )) {
2023-09-16 04:21:59 +00:00
$condition = DBA :: mergeConditions ( $condition , [ " `received` < ? " , $this -> maxId ]);
2023-09-09 09:14:36 +00:00
}
2023-09-09 13:02:20 +00:00
if ( isset ( $this -> minId )) {
2023-09-16 04:21:59 +00:00
$condition = DBA :: mergeConditions ( $condition , [ " `received` > ? " , $this -> minId ]);
2023-09-09 09:14:36 +00:00
// Previous page case: we want the items closest to min_id but for that we need to reverse the query order
2023-09-09 13:02:20 +00:00
if ( ! isset ( $this -> maxId )) {
2023-09-16 04:21:59 +00:00
$params [ 'order' ][ 'received' ] = false ;
2023-09-09 09:14:36 +00:00
}
}
}
2023-09-16 04:21:59 +00:00
$r = Post :: selectThreadForUser ( $this -> session -> getLocalUserId () ? : 0 , [ 'uri-id' , 'received' , 'author-link' ], $condition , $params );
2023-09-09 09:14:36 +00:00
$items = Post :: toArray ( $r );
if ( empty ( $items )) {
return [];
}
// Previous page case: once we get the relevant items closest to min_id, we need to restore the expected display order
2023-09-09 13:02:20 +00:00
if ( empty ( $this -> itemUriId ) && isset ( $this -> minId ) && ! isset ( $this -> maxId )) {
2023-09-09 09:14:36 +00:00
$items = array_reverse ( $items );
}
return $items ;
}
2023-09-09 14:13:58 +00:00
/**
* Sets items as seen
*
* @ param array $condition The array with the SQL condition
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
protected function setItemsSeenByCondition ( array $condition )
{
if ( empty ( $condition )) {
return ;
}
$unseen = Post :: exists ( $condition );
if ( $unseen ) {
/// @todo handle huge "unseen" updates in the background to avoid timeout errors
Item :: update ([ 'unseen' => false ], $condition );
}
}
2023-09-09 09:14:36 +00:00
}