2021-06-26 15:23:15 +00:00
< ? php
2022-08-16 11:27:22 +01:00
namespace BookStack\Search ;
2018-09-25 12:30:50 +01:00
2020-11-22 00:17:45 +00:00
use BookStack\Entities\EntityProvider ;
use BookStack\Entities\Models\Entity ;
2024-02-07 22:41:45 +00:00
use BookStack\Entities\Queries\EntityQueries ;
2025-10-27 17:23:15 +00:00
use BookStack\Entities\Tools\EntityHydrator ;
2023-05-17 17:56:55 +01:00
use BookStack\Permissions\PermissionApplicator ;
2024-10-03 19:27:03 +01:00
use BookStack\Search\Options\TagSearchOption ;
2023-05-17 17:56:55 +01:00
use BookStack\Users\Models\User ;
2021-11-20 14:03:56 +00:00
use Illuminate\Database\Connection ;
2018-09-25 18:00:40 +01:00
use Illuminate\Database\Eloquent\Builder as EloquentBuilder ;
2017-03-27 11:57:33 +01:00
use Illuminate\Database\Query\Builder ;
2025-10-27 17:23:15 +00:00
use Illuminate\Database\Query\JoinClause ;
2017-04-15 19:16:07 +01:00
use Illuminate\Support\Collection ;
2021-11-08 11:04:27 +00:00
use Illuminate\Support\Facades\DB ;
2019-09-13 23:58:40 +01:00
use Illuminate\Support\Str ;
2024-10-03 19:27:03 +01:00
use WeakMap ;
2017-03-19 12:48:44 +00:00
2020-11-22 00:17:45 +00:00
class SearchRunner
2017-03-19 12:48:44 +00:00
{
2021-11-08 14:12:40 +00:00
/**
2025-10-27 17:23:15 +00:00
* Retain a cache of score - adjusted terms for specific search options .
2021-11-08 14:12:40 +00:00
*/
2024-10-03 19:27:03 +01:00
protected WeakMap $termAdjustmentCache ;
2021-11-08 14:12:40 +00:00
2024-02-07 22:41:45 +00:00
public function __construct (
protected EntityProvider $entityProvider ,
protected PermissionApplicator $permissions ,
protected EntityQueries $entityQueries ,
2025-10-28 20:37:41 +00:00
protected EntityHydrator $entityHydrator ,
2024-02-07 22:41:45 +00:00
) {
2024-10-03 19:27:03 +01:00
$this -> termAdjustmentCache = new WeakMap ();
2017-03-19 12:48:44 +00:00
}
2017-03-27 11:57:33 +01:00
/**
* Search all entities in the system .
2021-11-14 16:28:01 +00:00
*
2025-10-28 20:37:41 +00:00
* @ return array { total : int , results : Collection < Entity > }
2017-03-27 11:57:33 +01:00
*/
2022-07-13 15:23:03 +01:00
public function searchEntities ( SearchOptions $searchOpts , string $entityType = 'all' , int $page = 1 , int $count = 20 ) : array
2017-03-19 12:48:44 +00:00
{
2018-09-25 18:00:40 +01:00
$entityTypes = array_keys ( $this -> entityProvider -> all ());
2017-04-09 20:59:57 +01:00
$entityTypesToSearch = $entityTypes ;
2024-10-02 17:31:45 +01:00
$filterMap = $searchOpts -> filters -> toValueMap ();
2017-04-09 20:59:57 +01:00
if ( $entityType !== 'all' ) {
2023-12-11 15:55:43 +00:00
$entityTypesToSearch = [ $entityType ];
2024-10-02 17:31:45 +01:00
} elseif ( isset ( $filterMap [ 'type' ])) {
$entityTypesToSearch = explode ( '|' , $filterMap [ 'type' ]);
2017-04-09 20:59:57 +01:00
}
2025-10-27 17:23:15 +00:00
$searchQuery = $this -> buildQuery ( $searchOpts , $entityTypesToSearch );
$total = $searchQuery -> count ();
$results = $this -> getPageOfDataFromQuery ( $searchQuery , $page , $count );
2021-11-06 00:32:01 +00:00
2017-04-15 15:04:30 +01:00
return [
2021-06-26 15:23:15 +00:00
'total' => $total ,
2025-10-28 20:37:41 +00:00
'results' => $results -> values (),
2017-04-15 15:04:30 +01:00
];
2017-03-26 19:24:57 +01:00
}
2017-04-15 19:16:07 +01:00
/**
2021-06-26 15:23:15 +00:00
* Search a book for entities .
2017-04-15 19:16:07 +01:00
*/
2020-06-27 13:29:00 +01:00
public function searchBook ( int $bookId , string $searchString ) : Collection
2017-04-15 19:16:07 +01:00
{
2020-06-27 13:29:00 +01:00
$opts = SearchOptions :: fromString ( $searchString );
2017-04-15 19:31:11 +01:00
$entityTypes = [ 'page' , 'chapter' ];
2024-10-02 17:31:45 +01:00
$filterMap = $opts -> filters -> toValueMap ();
$entityTypesToSearch = isset ( $filterMap [ 'type' ]) ? explode ( '|' , $filterMap [ 'type' ]) : $entityTypes ;
2017-04-15 19:31:11 +01:00
2025-10-28 20:37:41 +00:00
$filteredTypes = array_intersect ( $entityTypesToSearch , $entityTypes );
2025-10-29 12:59:34 +00:00
$query = $this -> buildQuery ( $opts , $filteredTypes ) -> where ( 'book_id' , '=' , $bookId );
2020-11-22 00:17:45 +00:00
2025-10-29 12:59:34 +00:00
return $this -> getPageOfDataFromQuery ( $query , 1 , 20 ) -> sortByDesc ( 'score' );
2017-04-15 19:16:07 +01:00
}
/**
2021-06-26 15:23:15 +00:00
* Search a chapter for entities .
2017-04-15 19:16:07 +01:00
*/
2020-06-27 13:29:00 +01:00
public function searchChapter ( int $chapterId , string $searchString ) : Collection
2017-04-15 19:16:07 +01:00
{
2020-06-27 13:29:00 +01:00
$opts = SearchOptions :: fromString ( $searchString );
2025-10-29 12:59:34 +00:00
$query = $this -> buildQuery ( $opts , [ 'page' ]) -> where ( 'chapter_id' , '=' , $chapterId );
2021-06-26 15:23:15 +00:00
2025-10-29 12:59:34 +00:00
return $this -> getPageOfDataFromQuery ( $query , 1 , 20 ) -> sortByDesc ( 'score' );
2017-04-15 19:16:07 +01:00
}
2017-03-27 11:57:33 +01:00
/**
2021-11-08 11:04:27 +00:00
* Get a page of result data from the given query based on the provided page parameters .
2017-03-27 11:57:33 +01:00
*/
2025-10-27 17:23:15 +00:00
protected function getPageOfDataFromQuery ( EloquentBuilder $query , int $page , int $count ) : Collection
2017-04-15 19:16:07 +01:00
{
2025-10-27 17:23:15 +00:00
$entities = $query -> clone ()
2021-11-08 11:04:27 +00:00
-> skip (( $page - 1 ) * $count )
-> take ( $count )
-> get ();
2025-10-27 17:23:15 +00:00
2025-10-28 20:37:41 +00:00
$hydrated = $this -> entityHydrator -> hydrate ( $entities -> all (), true , true );
2025-10-27 17:23:15 +00:00
return collect ( $hydrated );
2017-04-15 19:16:07 +01:00
}
/**
2021-06-26 15:23:15 +00:00
* Create a search query for an entity .
2025-10-27 17:23:15 +00:00
* @ param string [] $entityTypes
2017-04-15 19:16:07 +01:00
*/
2025-10-27 17:23:15 +00:00
protected function buildQuery ( SearchOptions $searchOpts , array $entityTypes ) : EloquentBuilder
2017-03-26 19:24:57 +01:00
{
2025-10-27 17:23:15 +00:00
$entityQuery = $this -> entityQueries -> visibleForList ()
-> whereIn ( 'type' , $entityTypes );
2021-11-08 11:41:14 +00:00
2017-03-27 11:57:33 +01:00
// Handle normal search terms
2025-10-27 17:23:15 +00:00
$this -> applyTermSearch ( $entityQuery , $searchOpts , $entityTypes );
2017-03-27 11:57:33 +01:00
// Handle exact term matching
2024-10-03 19:27:03 +01:00
foreach ( $searchOpts -> exacts -> all () as $exact ) {
2025-10-27 17:23:15 +00:00
$filter = function ( EloquentBuilder $query ) use ( $exact ) {
2024-10-03 19:27:03 +01:00
$inputTerm = str_replace ( '\\' , '\\\\' , $exact -> value );
2021-06-26 15:23:15 +00:00
$query -> where ( 'name' , 'like' , '%' . $inputTerm . '%' )
2025-10-29 12:59:34 +00:00
-> orWhere ( 'description' , 'like' , '%' . $inputTerm . '%' )
-> orWhere ( 'text' , 'like' , '%' . $inputTerm . '%' );
2024-10-03 19:27:03 +01:00
};
$exact -> negated ? $entityQuery -> whereNot ( $filter ) : $entityQuery -> where ( $filter );
2017-03-27 11:57:33 +01:00
}
2017-03-27 18:05:34 +01:00
// Handle tag searches
2024-10-03 19:27:03 +01:00
foreach ( $searchOpts -> tags -> all () as $tagOption ) {
$this -> applyTagSearch ( $entityQuery , $tagOption );
2017-03-27 18:05:34 +01:00
}
// Handle filters
2024-10-03 19:27:03 +01:00
foreach ( $searchOpts -> filters -> all () as $filterOption ) {
$functionName = Str :: camel ( 'filter_' . $filterOption -> getKey ());
2018-01-28 16:58:52 +00:00
if ( method_exists ( $this , $functionName )) {
2025-10-27 17:23:15 +00:00
$this -> $functionName ( $entityQuery , $filterOption -> value , $filterOption -> negated );
2018-01-28 16:58:52 +00:00
}
2017-03-27 18:05:34 +01:00
}
2022-07-16 19:54:25 +01:00
return $entityQuery ;
2021-11-08 11:29:25 +00:00
}
/**
* For the given search query , apply the queries for handling the regular search terms .
*/
2025-10-27 17:23:15 +00:00
protected function applyTermSearch ( EloquentBuilder $entityQuery , SearchOptions $options , array $entityTypes ) : void
2021-11-08 11:29:25 +00:00
{
2024-10-02 17:31:45 +01:00
$terms = $options -> searches -> toValueArray ();
2021-11-08 11:29:25 +00:00
if ( count ( $terms ) === 0 ) {
return ;
}
2021-11-08 14:12:40 +00:00
$scoredTerms = $this -> getTermAdjustments ( $options );
$scoreSelect = $this -> selectForScoredTerms ( $scoredTerms );
2021-11-08 11:29:25 +00:00
$subQuery = DB :: table ( 'search_terms' ) -> select ([
'entity_id' ,
'entity_type' ,
2021-11-08 14:12:40 +00:00
DB :: raw ( $scoreSelect [ 'statement' ]),
2021-11-08 11:29:25 +00:00
]);
2021-11-08 14:12:40 +00:00
$subQuery -> addBinding ( $scoreSelect [ 'bindings' ], 'select' );
2021-11-08 11:29:25 +00:00
$subQuery -> where ( function ( Builder $query ) use ( $terms ) {
foreach ( $terms as $inputTerm ) {
2024-10-02 17:31:45 +01:00
$escapedTerm = str_replace ( '\\' , '\\\\' , $inputTerm );
$query -> orWhere ( 'term' , 'like' , $escapedTerm . '%' );
2021-11-08 11:29:25 +00:00
}
2021-11-08 14:12:40 +00:00
});
$subQuery -> groupBy ( 'entity_type' , 'entity_id' );
2025-10-27 17:23:15 +00:00
$entityQuery -> joinSub ( $subQuery , 's' , function ( JoinClause $join ) {
$join -> on ( 's.entity_id' , '=' , 'entities.id' )
-> on ( 's.entity_type' , '=' , 'entities.type' );
});
2021-11-08 14:12:40 +00:00
$entityQuery -> addSelect ( 's.score' );
$entityQuery -> orderBy ( 'score' , 'desc' );
}
/**
* Create a select statement , with prepared bindings , for the given
* set of scored search terms .
2021-11-08 15:00:47 +00:00
*
2021-11-09 15:13:15 +00:00
* @ param array < string , float > $scoredTerms
*
2021-11-08 14:12:40 +00:00
* @ return array { statement : string , bindings : string []}
*/
protected function selectForScoredTerms ( array $scoredTerms ) : array
{
// Within this we walk backwards to create the chain of 'if' statements
// so that each previous statement is used in the 'else' condition of
// the next (earlier) to be built. We start at '0' to have no score
// on no match (Should never actually get to this case).
$ifChain = '0' ;
$bindings = [];
foreach ( $scoredTerms as $term => $score ) {
2021-11-08 15:00:47 +00:00
$ifChain = 'IF(term like ?, score * ' . ( float ) $score . ', ' . $ifChain . ')' ;
2021-11-08 14:12:40 +00:00
$bindings [] = $term . '%' ;
}
return [
'statement' => 'SUM(' . $ifChain . ') as score' ,
2021-11-08 15:00:47 +00:00
'bindings' => array_reverse ( $bindings ),
2021-11-08 14:12:40 +00:00
];
}
2021-11-09 15:13:15 +00:00
/**
* For the terms in the given search options , query their popularity across all
* search terms then provide that back as score adjustment multiplier applicable
* for their rarity . Returns an array of float multipliers , keyed by term .
*
* @ return array < string , float >
*/
2021-11-08 14:12:40 +00:00
protected function getTermAdjustments ( SearchOptions $options ) : array
{
if ( isset ( $this -> termAdjustmentCache [ $options ])) {
return $this -> termAdjustmentCache [ $options ];
}
$termQuery = SearchTerm :: query () -> toBase ();
$whenStatements = [];
$whenBindings = [];
2024-10-02 17:31:45 +01:00
foreach ( $options -> searches -> toValueArray () as $term ) {
2021-11-08 14:12:40 +00:00
$whenStatements [] = 'WHEN term LIKE ? THEN ?' ;
$whenBindings [] = $term . '%' ;
$whenBindings [] = $term ;
$termQuery -> orWhere ( 'term' , 'like' , $term . '%' );
}
$case = 'CASE ' . implode ( ' ' , $whenStatements ) . ' END' ;
2021-11-08 15:00:47 +00:00
$termQuery -> selectRaw ( $case . ' as term' , $whenBindings );
2021-11-08 14:12:40 +00:00
$termQuery -> selectRaw ( 'COUNT(*) as count' );
$termQuery -> groupByRaw ( $case , $whenBindings );
2021-11-08 11:41:14 +00:00
2021-11-08 15:00:47 +00:00
$termCounts = $termQuery -> pluck ( 'count' , 'term' ) -> toArray ();
2021-11-08 14:12:40 +00:00
$adjusted = $this -> rawTermCountsToAdjustments ( $termCounts );
$this -> termAdjustmentCache [ $options ] = $adjusted ;
2021-11-08 15:00:47 +00:00
2021-11-08 14:12:40 +00:00
return $this -> termAdjustmentCache [ $options ];
}
/**
* Convert counts of terms into a relative - count normalised multiplier .
2021-11-08 15:00:47 +00:00
*
2021-11-08 14:12:40 +00:00
* @ param array < string , int > $termCounts
2021-11-08 15:00:47 +00:00
*
2025-09-03 10:47:45 +01:00
* @ return array < string , float >
2021-11-08 14:12:40 +00:00
*/
protected function rawTermCountsToAdjustments ( array $termCounts ) : array
{
2021-11-08 15:00:47 +00:00
if ( empty ( $termCounts )) {
return [];
}
2021-11-13 13:28:17 +00:00
2021-11-08 14:12:40 +00:00
$multipliers = [];
$max = max ( array_values ( $termCounts ));
foreach ( $termCounts as $term => $count ) {
$percent = round ( $count / $max , 5 );
$multipliers [ $term ] = 1.3 - $percent ;
}
2021-11-08 11:41:14 +00:00
2021-11-08 14:12:40 +00:00
return $multipliers ;
2017-03-26 19:24:57 +01:00
}
2017-03-27 18:05:34 +01:00
/**
2024-10-03 19:27:03 +01:00
* Apply a tag search term onto an entity query .
2017-03-27 18:05:34 +01:00
*/
2024-10-03 19:27:03 +01:00
protected function applyTagSearch ( EloquentBuilder $query , TagSearchOption $option ) : void
2017-03-27 18:05:34 +01:00
{
2024-10-03 19:27:03 +01:00
$filter = function ( EloquentBuilder $query ) use ( $option ) : void {
$tagParts = $option -> getParts ();
if ( empty ( $tagParts [ 'operator' ]) || empty ( $tagParts [ 'value' ])) {
$query -> where ( 'name' , '=' , $tagParts [ 'name' ]);
return ;
}
2021-06-26 15:23:15 +00:00
2024-10-03 19:27:03 +01:00
if ( ! empty ( $tagParts [ 'name' ])) {
$query -> where ( 'name' , '=' , $tagParts [ 'name' ]);
}
2017-03-27 18:05:34 +01:00
2024-10-03 19:27:03 +01:00
if ( is_numeric ( $tagParts [ 'value' ]) && $tagParts [ 'operator' ] !== 'like' ) {
// We have to do a raw sql query for this since otherwise PDO will quote the value and MySQL will
// search the value as a string which prevents being able to do number-based operations
// on the tag values. We ensure it has a numeric value and then cast it just to be sure.
/** @var Connection $connection */
$connection = $query -> getConnection ();
$quotedValue = ( float ) trim ( $connection -> getPdo () -> quote ( $tagParts [ 'value' ]), " ' " );
$query -> whereRaw ( " value { $tagParts [ 'operator' ] } { $quotedValue } " );
} else if ( $tagParts [ 'operator' ] === 'like' ) {
$query -> where ( 'value' , $tagParts [ 'operator' ], str_replace ( '\\' , '\\\\' , $tagParts [ 'value' ]));
2017-03-27 18:05:34 +01:00
} else {
2024-10-03 19:27:03 +01:00
$query -> where ( 'value' , $tagParts [ 'operator' ], $tagParts [ 'value' ]);
2017-03-27 18:05:34 +01:00
}
2024-10-03 19:27:03 +01:00
};
2021-06-26 15:23:15 +00:00
2024-10-03 19:27:03 +01:00
$option -> negated ? $query -> whereDoesntHave ( 'tags' , $filter ) : $query -> whereHas ( 'tags' , $filter );
}
2025-10-29 12:59:34 +00:00
protected function applyNegatableWhere ( EloquentBuilder $query , bool $negated , string | callable $column , string | null $operator , mixed $value ) : void
2024-10-03 19:27:03 +01:00
{
if ( $negated ) {
$query -> whereNot ( $column , $operator , $value );
} else {
$query -> where ( $column , $operator , $value );
}
2017-03-27 18:05:34 +01:00
}
/**
2021-06-26 15:23:15 +00:00
* Custom entity search filters .
2017-03-27 18:05:34 +01:00
*/
2025-10-27 17:23:15 +00:00
protected function filterUpdatedAfter ( EloquentBuilder $query , string $input , bool $negated ) : void
2017-03-27 18:05:34 +01:00
{
2024-10-03 19:27:03 +01:00
$date = date_create ( $input );
$this -> applyNegatableWhere ( $query , $negated , 'updated_at' , '>=' , $date );
2017-03-27 18:05:34 +01:00
}
2025-10-27 17:23:15 +00:00
protected function filterUpdatedBefore ( EloquentBuilder $query , string $input , bool $negated ) : void
2017-03-27 18:05:34 +01:00
{
2024-10-03 19:27:03 +01:00
$date = date_create ( $input );
$this -> applyNegatableWhere ( $query , $negated , 'updated_at' , '<' , $date );
2017-03-27 18:05:34 +01:00
}
2025-10-27 17:23:15 +00:00
protected function filterCreatedAfter ( EloquentBuilder $query , string $input , bool $negated ) : void
2017-03-27 18:05:34 +01:00
{
2024-10-03 19:27:03 +01:00
$date = date_create ( $input );
$this -> applyNegatableWhere ( $query , $negated , 'created_at' , '>=' , $date );
2017-03-27 18:05:34 +01:00
}
2025-10-27 17:23:15 +00:00
protected function filterCreatedBefore ( EloquentBuilder $query , string $input , bool $negated )
2017-03-27 18:05:34 +01:00
{
2024-10-03 19:27:03 +01:00
$date = date_create ( $input );
$this -> applyNegatableWhere ( $query , $negated , 'created_at' , '<' , $date );
2017-03-27 18:05:34 +01:00
}
2025-10-27 17:23:15 +00:00
protected function filterCreatedBy ( EloquentBuilder $query , string $input , bool $negated )
2017-03-27 18:05:34 +01:00
{
2021-03-10 22:51:18 +00:00
$userSlug = $input === 'me' ? user () -> slug : trim ( $input );
$user = User :: query () -> where ( 'slug' , '=' , $userSlug ) -> first ([ 'id' ]);
if ( $user ) {
2024-10-03 19:27:03 +01:00
$this -> applyNegatableWhere ( $query , $negated , 'created_by' , '=' , $user -> id );
2018-01-28 16:58:52 +00:00
}
2017-03-27 18:05:34 +01:00
}
2025-10-27 17:23:15 +00:00
protected function filterUpdatedBy ( EloquentBuilder $query , string $input , bool $negated )
2017-03-27 18:05:34 +01:00
{
2021-03-10 22:51:18 +00:00
$userSlug = $input === 'me' ? user () -> slug : trim ( $input );
$user = User :: query () -> where ( 'slug' , '=' , $userSlug ) -> first ([ 'id' ]);
if ( $user ) {
2024-10-03 19:27:03 +01:00
$this -> applyNegatableWhere ( $query , $negated , 'updated_by' , '=' , $user -> id );
2018-01-28 16:58:52 +00:00
}
2017-03-27 18:05:34 +01:00
}
2025-10-27 17:23:15 +00:00
protected function filterOwnedBy ( EloquentBuilder $query , string $input , bool $negated )
2021-02-14 11:39:18 +01:00
{
2021-03-15 18:27:03 +00:00
$userSlug = $input === 'me' ? user () -> slug : trim ( $input );
$user = User :: query () -> where ( 'slug' , '=' , $userSlug ) -> first ([ 'id' ]);
if ( $user ) {
2024-10-03 19:27:03 +01:00
$this -> applyNegatableWhere ( $query , $negated , 'owned_by' , '=' , $user -> id );
2021-02-14 11:39:18 +01:00
}
}
2025-10-27 17:23:15 +00:00
protected function filterInName ( EloquentBuilder $query , string $input , bool $negated )
2017-03-27 18:05:34 +01:00
{
2024-10-03 19:27:03 +01:00
$this -> applyNegatableWhere ( $query , $negated , 'name' , 'like' , '%' . $input . '%' );
2017-03-27 18:05:34 +01:00
}
2025-10-27 17:23:15 +00:00
protected function filterInTitle ( EloquentBuilder $query , string $input , bool $negated )
2018-01-28 16:58:52 +00:00
{
2025-10-27 17:23:15 +00:00
$this -> filterInName ( $query , $input , $negated );
2018-01-28 16:58:52 +00:00
}
2017-03-27 18:05:34 +01:00
2025-10-27 17:23:15 +00:00
protected function filterInBody ( EloquentBuilder $query , string $input , bool $negated )
2017-03-27 18:05:34 +01:00
{
2025-10-29 12:59:34 +00:00
$this -> applyNegatableWhere ( $query , $negated , function ( EloquentBuilder $query ) use ( $input ) {
$query -> where ( 'description' , 'like' , '%' . $input . '%' )
-> orWhere ( 'text' , 'like' , '%' . $input . '%' );
}, null , null );
2017-03-27 18:05:34 +01:00
}
2025-10-27 17:23:15 +00:00
protected function filterIsRestricted ( EloquentBuilder $query , string $input , bool $negated )
2017-03-27 18:05:34 +01:00
{
2024-10-03 19:27:03 +01:00
$negated ? $query -> whereDoesntHave ( 'permissions' ) : $query -> whereHas ( 'permissions' );
2017-03-27 18:05:34 +01:00
}
2025-10-27 17:23:15 +00:00
protected function filterViewedByMe ( EloquentBuilder $query , string $input , bool $negated )
2017-03-27 18:05:34 +01:00
{
2024-10-03 19:27:03 +01:00
$filter = function ( $query ) {
2017-03-27 18:05:34 +01:00
$query -> where ( 'user_id' , '=' , user () -> id );
2024-10-03 19:27:03 +01:00
};
$negated ? $query -> whereDoesntHave ( 'views' , $filter ) : $query -> whereHas ( 'views' , $filter );
2017-03-27 18:05:34 +01:00
}
2025-10-27 17:23:15 +00:00
protected function filterNotViewedByMe ( EloquentBuilder $query , string $input , bool $negated )
2017-03-27 18:05:34 +01:00
{
2024-10-03 19:27:03 +01:00
$filter = function ( $query ) {
2017-03-27 18:05:34 +01:00
$query -> where ( 'user_id' , '=' , user () -> id );
2024-10-03 19:27:03 +01:00
};
$negated ? $query -> whereHas ( 'views' , $filter ) : $query -> whereDoesntHave ( 'views' , $filter );
2017-03-27 18:05:34 +01:00
}
2025-10-27 17:23:15 +00:00
protected function filterIsTemplate ( EloquentBuilder $query , string $input , bool $negated )
2023-12-11 15:55:43 +00:00
{
2025-10-27 17:23:15 +00:00
$this -> applyNegatableWhere ( $query , $negated , 'template' , '=' , true );
2023-12-11 15:55:43 +00:00
}
2025-10-27 17:23:15 +00:00
protected function filterSortBy ( EloquentBuilder $query , string $input , bool $negated )
2017-10-01 11:24:13 +01:00
{
2019-09-13 23:58:40 +01:00
$functionName = Str :: camel ( 'sort_by_' . $input );
2018-01-28 16:58:52 +00:00
if ( method_exists ( $this , $functionName )) {
2025-10-27 17:23:15 +00:00
$this -> $functionName ( $query , $negated );
2018-01-28 16:58:52 +00:00
}
2017-10-01 11:24:13 +01:00
}
/**
2021-06-26 15:23:15 +00:00
* Sorting filter options .
2017-10-01 11:24:13 +01:00
*/
2025-10-27 17:23:15 +00:00
protected function sortByLastCommented ( EloquentBuilder $query , bool $negated )
2017-10-01 11:24:13 +01:00
{
2021-11-08 11:04:27 +00:00
$commentsTable = DB :: getTablePrefix () . 'comments' ;
2025-10-27 17:23:15 +00:00
$commentQuery = DB :: raw ( '(SELECT c1.commentable_id, c1.commentable_type, c1.created_at as last_commented FROM ' . $commentsTable . ' c1 LEFT JOIN ' . $commentsTable . ' c2 ON (c1.commentable_id = c2.commentable_id AND c1.commentable_type = c2.commentable_type AND c1.created_at < c2.created_at) WHERE c2.created_at IS NULL) as comments' );
2017-10-01 11:24:13 +01:00
2025-10-27 17:23:15 +00:00
$query -> join ( $commentQuery , function ( JoinClause $join ) {
$join -> on ( 'entities.id' , '=' , 'comments.commentable_id' )
-> on ( 'entities.type' , '=' , 'comments.commentable_type' );
}) -> orderBy ( 'last_commented' , $negated ? 'asc' : 'desc' );
2017-10-01 11:24:13 +01:00
}
2018-01-28 16:58:52 +00:00
}