2023-09-19 11:05:28 +02:00
< ? php
/**
2024-01-02 21:57:26 +01:00
* @ copyright Copyright ( C ) 2010 - 2024 , the Friendica project
2023-09-19 11:05:28 +02: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 />.
*
*/
namespace Friendica\Content\Conversation\Repository ;
use Friendica\BaseCollection ;
2023-10-07 11:44:24 +02:00
use Friendica\Content\Conversation\Collection\UserDefinedChannels ;
use Friendica\Content\Conversation\Entity ;
use Friendica\Content\Conversation\Factory ;
2024-01-06 18:27:42 +01:00
use Friendica\Core\Config\Capability\IManageConfigValues ;
2023-09-19 11:05:28 +02:00
use Friendica\Database\Database ;
2024-01-06 18:27:42 +01:00
use Friendica\Database\DBA ;
2024-01-17 04:29:43 +01:00
use Friendica\Database\DisposableFullTextSearch ;
2024-01-06 18:27:42 +01:00
use Friendica\Model\Contact ;
2023-11-22 00:13:26 +01:00
use Friendica\Model\Post\Engagement ;
2023-11-15 17:19:05 +01:00
use Friendica\Model\User ;
2024-01-06 18:27:42 +01:00
use Friendica\Util\DateTimeFormat ;
2023-09-19 11:05:28 +02:00
use Psr\Log\LoggerInterface ;
2023-10-07 11:44:24 +02:00
class UserDefinedChannel extends \Friendica\BaseRepository
2023-09-19 11:05:28 +02:00
{
protected static $table_name = 'channel' ;
2024-01-17 04:29:43 +01:00
private IManageConfigValues $config ;
2023-11-15 17:19:05 +01:00
2024-01-06 18:27:42 +01:00
public function __construct ( Database $database , LoggerInterface $logger , Factory\UserDefinedChannel $factory , IManageConfigValues $config )
2023-09-19 11:05:28 +02:00
{
parent :: __construct ( $database , $logger , $factory );
2023-11-15 17:19:05 +01:00
2024-01-06 18:27:42 +01:00
$this -> config = $config ;
2023-09-19 11:05:28 +02:00
}
2023-10-07 11:44:24 +02:00
/**
* @ param array $condition
* @ param array $params
* @ return UserDefinedChannels
* @ throws \Exception
*/
protected function _select ( array $condition , array $params = []) : BaseCollection
{
$rows = $this -> db -> selectToArray ( static :: $table_name , [], $condition , $params );
$Entities = new UserDefinedChannels ();
foreach ( $rows as $fields ) {
$Entities [] = $this -> factory -> createFromTableRow ( $fields );
}
return $Entities ;
}
2024-01-07 19:36:47 +01:00
public function select ( array $condition , array $params = []) : UserDefinedChannels
2024-01-06 18:27:42 +01:00
{
return $this -> _select ( $condition , $params );
}
2023-09-19 11:05:28 +02:00
/**
* Fetch a single user channel
2023-09-19 11:09:20 +02:00
*
2023-09-26 07:05:51 +02:00
* @ param int $id The id of the user defined channel
* @ param int $uid The user that this channel belongs to . ( Not part of the primary key )
2023-10-07 11:44:24 +02:00
* @ return Entity\UserDefinedChannel
2023-09-19 11:05:28 +02:00
* @ throws \Friendica\Network\HTTPException\NotFoundException
*/
2023-10-07 11:44:24 +02:00
public function selectById ( int $id , int $uid ) : Entity\UserDefinedChannel
2023-09-19 11:05:28 +02:00
{
return $this -> _selectOne ([ 'id' => $id , 'uid' => $uid ]);
}
/**
* Checks if the provided channel id exists for this user
*
* @ param integer $id
* @ param integer $uid
* @ return boolean
*/
public function existsById ( int $id , int $uid ) : bool
{
return $this -> exists ([ 'id' => $id , 'uid' => $uid ]);
2023-09-20 23:39:05 +02:00
}
/**
* Delete the given channel
*
* @ param integer $id
* @ param integer $uid
* @ return boolean
*/
public function deleteById ( int $id , int $uid ) : bool
{
2023-11-15 17:19:05 +01:00
return $this -> db -> delete ( self :: $table_name , [ 'id' => $id , 'uid' => $uid ]);
2023-09-19 11:05:28 +02:00
}
/**
* Fetch all user channels
*
* @ param integer $uid
2023-10-07 11:44:24 +02:00
* @ return UserDefinedChannels
* @ throws \Exception
2023-09-19 11:05:28 +02:00
*/
2023-10-07 11:44:24 +02:00
public function selectByUid ( int $uid ) : UserDefinedChannels
2023-09-19 11:05:28 +02:00
{
return $this -> _select ([ 'uid' => $uid ]);
}
2023-10-07 11:44:24 +02:00
public function save ( Entity\UserDefinedChannel $Channel ) : Entity\UserDefinedChannel
2023-09-19 11:05:28 +02:00
{
$fields = [
'label' => $Channel -> label ,
'description' => $Channel -> description ,
'access-key' => $Channel -> accessKey ,
'uid' => $Channel -> uid ,
2023-09-24 02:45:07 +02:00
'circle' => $Channel -> circle ,
2023-09-19 11:05:28 +02:00
'include-tags' => $Channel -> includeTags ,
'exclude-tags' => $Channel -> excludeTags ,
2024-01-30 11:05:05 +01:00
'min-size' => $Channel -> minSize ,
'max-size' => $Channel -> maxSize ,
2023-09-19 11:05:28 +02:00
'full-text-search' => $Channel -> fullTextSearch ,
'media-type' => $Channel -> mediaType ,
2024-01-03 20:17:14 +01:00
'languages' => serialize ( $Channel -> languages ),
2024-01-07 19:36:47 +01:00
'publish' => $Channel -> publish ,
2024-01-10 21:17:44 +01:00
'valid' => $this -> isValid ( $Channel -> fullTextSearch ),
2023-09-19 11:05:28 +02:00
];
if ( $Channel -> code ) {
$this -> db -> update ( self :: $table_name , $fields , [ 'uid' => $Channel -> uid , 'id' => $Channel -> code ]);
} else {
$this -> db -> insert ( self :: $table_name , $fields , Database :: INSERT_IGNORE );
$newChannelId = $this -> db -> lastInsertId ();
$Channel = $this -> selectById ( $newChannelId , $Channel -> uid );
}
return $Channel ;
}
2023-11-15 17:19:05 +01:00
2024-01-10 21:17:44 +01:00
private function isValid ( string $searchtext ) : bool
{
if ( $searchtext == '' ) {
return true ;
}
2024-01-17 20:46:22 +01:00
return $this -> db -> select ( 'check-full-text-search' , [], [ " `pid` = ? AND MATCH (`searchtext`) AGAINST (? IN BOOLEAN MODE) " , getmypid (), Engagement :: escapeKeywords ( $searchtext )]) !== false ;
2024-01-10 21:17:44 +01:00
}
2023-11-15 17:19:05 +01:00
/**
2024-01-17 04:29:43 +01:00
* Checks if one of the user - defined channels matches the given language or item text via full - text search
2023-11-15 17:19:05 +01:00
*
2024-01-17 04:29:43 +01:00
* @ param string $haystack
2023-11-15 17:19:05 +01:00
* @ param string $language
* @ return boolean
2024-01-17 04:29:43 +01:00
* @ throws \Exception
2023-11-15 17:19:05 +01:00
*/
2024-01-17 04:29:43 +01:00
public function match ( string $haystack , string $language ) : bool
2024-01-06 18:27:42 +01:00
{
2024-01-07 19:36:47 +01:00
$users = $this -> db -> selectToArray ( 'user' , [ 'uid' ], $this -> getUserCondition ());
2024-01-06 18:27:42 +01:00
if ( empty ( $users )) {
2024-01-17 04:29:43 +01:00
return false ;
2024-01-06 18:27:42 +01:00
}
2024-01-10 11:55:18 +01:00
$uids = array_column ( $users , 'uid' );
2024-01-10 22:17:21 +01:00
$usercondition = [ 'uid' => $uids ];
$condition = DBA :: mergeConditions ( $usercondition , [ " `languages` != ? AND `include-tags` = ? AND `full-text-search` = ? AND `circle` = ? " , '' , '' , '' , 0 ]);
2024-01-10 11:55:18 +01:00
foreach ( $this -> select ( $condition ) as $channel ) {
if ( ! empty ( $channel -> languages ) && in_array ( $language , $channel -> languages )) {
return true ;
}
}
2024-01-10 22:17:21 +01:00
$search = '' ;
$condition = DBA :: mergeConditions ( $usercondition , [ " `full-text-search` != ? AND `circle` = ? AND `valid` " , '' , 0 ]);
foreach ( $this -> select ( $condition ) as $channel ) {
$search .= '(' . $channel -> fullTextSearch . ') ' ;
}
2024-01-17 04:29:43 +01:00
return ( new DisposableFullTextSearch ( $this -> db , $haystack )) -> match ( Engagement :: escapeKeywords ( $search ));
2024-01-06 18:27:42 +01:00
}
/**
2024-01-17 04:29:43 +01:00
* List the IDs of the relay / group users that have matching user - defined channels based on an item details
2024-01-06 18:27:42 +01:00
*
* @ param string $searchtext
* @ param string $language
2024-01-07 02:08:59 +01:00
* @ param array $tags
* @ param int $media_type
* @ param int $owner_id
2024-01-09 23:55:47 +01:00
* @ param int $reshare_id
2024-01-06 18:27:42 +01:00
* @ return array
2024-01-17 04:29:43 +01:00
* @ throws \Exception
2024-01-06 18:27:42 +01:00
*/
2024-01-09 23:55:47 +01:00
public function getMatchingChannelUsers ( string $searchtext , string $language , array $tags , int $media_type , int $owner_id , int $reshare_id ) : array
2024-01-06 18:27:42 +01:00
{
2024-01-07 19:36:47 +01:00
$condition = $this -> getUserCondition ();
$condition = DBA :: mergeConditions ( $condition , [ " `account-type` IN (?, ?) AND `uid` != ? " , User :: ACCOUNT_TYPE_RELAY , User :: ACCOUNT_TYPE_COMMUNITY , 0 ]);
$users = $this -> db -> selectToArray ( 'user' , [ 'uid' ], $condition );
2024-01-06 18:27:42 +01:00
if ( empty ( $users )) {
return [];
}
2023-11-15 17:19:05 +01:00
if ( ! in_array ( $language , User :: getLanguages ())) {
$this -> logger -> debug ( 'Unwanted language found. No matched channel found.' , [ 'language' => $language , 'searchtext' => $searchtext ]);
2024-01-06 18:27:42 +01:00
return [];
2023-11-15 17:19:05 +01:00
}
2024-01-17 04:29:43 +01:00
$disposableFullTextSearch = new DisposableFullTextSearch ( $this -> db , $searchtext );
2024-01-06 18:27:42 +01:00
2024-01-17 04:29:43 +01:00
$filteredChannels = $this -> select ([ 'uid' => array_column ( $users , 'uid' ), 'publish' => true , 'valid' => true ]) -> filter (
function ( Entity\UserDefinedChannel $channel ) use ( $owner_id , $reshare_id , $language , $tags , $media_type , $disposableFullTextSearch , $searchtext ) {
static $uids = [];
2024-01-06 18:27:42 +01:00
2024-01-17 04:29:43 +01:00
// Filter out channels from already picked users
if ( in_array ( $channel -> uid , $uids )) {
return false ;
2024-01-06 18:27:42 +01:00
}
2024-01-17 04:29:43 +01:00
if (
( $channel -> circle ? ? 0 )
&& ! $this -> inCircle ( $channel -> circle , $channel -> uid , $owner_id )
&& ! $this -> inCircle ( $channel -> circle , $channel -> uid , $reshare_id )
) {
return false ;
2024-01-06 18:27:42 +01:00
}
2024-01-17 04:29:43 +01:00
if ( ! in_array ( $language , $channel -> languages ? : User :: getWantedLanguages ( $channel -> uid ))) {
return false ;
2023-11-15 17:19:05 +01:00
}
2024-01-17 04:29:43 +01:00
if ( $channel -> includeTags && ! $this -> inTaglist ( $channel -> includeTags , $tags )) {
return false ;
2024-01-06 18:27:42 +01:00
}
2024-01-17 04:29:43 +01:00
if ( $channel -> excludeTags && $this -> inTaglist ( $channel -> excludeTags , $tags )) {
return false ;
2024-01-06 18:27:42 +01:00
}
2024-01-17 04:29:43 +01:00
if ( $channel -> mediaType && ! ( $channel -> mediaType & $media_type )) {
return false ;
2024-01-06 18:27:42 +01:00
}
2023-11-15 17:19:05 +01:00
2024-01-17 04:29:43 +01:00
if ( $channel -> fullTextSearch && ! $disposableFullTextSearch -> match ( Engagement :: escapeKeywords ( $channel -> fullTextSearch ))) {
return false ;
}
2024-01-07 19:36:47 +01:00
2024-01-17 04:29:43 +01:00
$uids [] = $channel -> uid ;
$this -> logger -> debug ( 'Matching channel found.' , [ 'uid' => $channel -> uid , 'label' => $channel -> label , 'language' => $language , 'tags' => $tags , 'media_type' => $media_type , 'searchtext' => $searchtext ]);
2024-01-15 23:28:42 +01:00
2024-01-17 04:29:43 +01:00
return true ;
}
);
return $filteredChannels -> column ( 'uid' );
2024-01-15 23:28:42 +01:00
}
2024-01-09 23:55:47 +01:00
private function inCircle ( int $circleId , int $uid , int $cid ) : bool
{
if ( $cid == 0 ) {
return false ;
}
$account = Contact :: selectFirstAccountUser ([ 'id' ], [ 'pid' => $cid , 'uid' => $uid ]);
if ( empty ( $account [ 'id' ])) {
return false ;
}
return $this -> db -> exists ( 'group_member' , [ 'gid' => $circleId , 'contact-id' => $account [ 'id' ]]);
}
private function inTaglist ( string $tagList , array $tags ) : bool
{
if ( empty ( $tags )) {
return false ;
}
array_walk ( $tags , function ( & $value ) {
$value = mb_strtolower ( $value );
});
foreach ( explode ( ',' , $tagList ) as $tag ) {
if ( in_array ( $tag , $tags )) {
return true ;
}
}
return false ;
}
2024-01-17 04:29:43 +01:00
private function getUserCondition () : array
2024-01-07 19:36:47 +01:00
{
$condition = [ " `verified` AND NOT `blocked` AND NOT `account_removed` AND NOT `account_expired` AND `user`.`uid` > ? " , 0 ];
$abandon_days = intval ( $this -> config -> get ( 'system' , 'account_abandon_days' ));
if ( ! empty ( $abandon_days )) {
$condition = DBA :: mergeConditions ( $condition , [ " `last-activity` > ? " , DateTimeFormat :: utc ( 'now - ' . $abandon_days . ' days' )]);
}
return $condition ;
}
2023-09-19 11:05:28 +02:00
}