2021-06-26 23:23:15 +08:00
< ? php
2022-08-16 18:27:22 +08:00
namespace BookStack\Search ;
2018-09-25 19:30:50 +08:00
2020-11-22 08:17:45 +08:00
use BookStack\Entities\EntityProvider ;
use BookStack\Entities\Models\Entity ;
2021-11-08 19:41:14 +08:00
use BookStack\Entities\Models\Page ;
2024-02-08 06:41:45 +08:00
use BookStack\Entities\Queries\EntityQueries ;
2023-05-18 00:56:55 +08:00
use BookStack\Permissions\PermissionApplicator ;
2024-10-04 02:27:03 +08:00
use BookStack\Search\Options\TagSearchOption ;
2023-05-18 00:56:55 +08:00
use BookStack\Users\Models\User ;
2021-11-20 22:03:56 +08:00
use Illuminate\Database\Connection ;
2018-09-26 01:00:40 +08:00
use Illuminate\Database\Eloquent\Builder as EloquentBuilder ;
2021-11-08 19:04:27 +08:00
use Illuminate\Database\Eloquent\Collection as EloquentCollection ;
2021-11-08 23:24:49 +08:00
use Illuminate\Database\Eloquent\Relations\BelongsTo ;
2017-03-27 18:57:33 +08:00
use Illuminate\Database\Query\Builder ;
2017-04-16 02:16:07 +08:00
use Illuminate\Support\Collection ;
2021-11-08 19:04:27 +08:00
use Illuminate\Support\Facades\DB ;
2019-09-14 06:58:40 +08:00
use Illuminate\Support\Str ;
2024-10-04 02:27:03 +08:00
use WeakMap ;
2017-03-19 20:48:44 +08:00
2020-11-22 08:17:45 +08:00
class SearchRunner
2017-03-19 20:48:44 +08:00
{
2021-11-08 22:12:40 +08:00
/**
* Retain a cache of score adjusted terms for specific search options .
*/
2024-10-04 02:27:03 +08:00
protected WeakMap $termAdjustmentCache ;
2021-11-08 22:12:40 +08:00
2024-02-08 06:41:45 +08:00
public function __construct (
protected EntityProvider $entityProvider ,
protected PermissionApplicator $permissions ,
protected EntityQueries $entityQueries ,
) {
2024-10-04 02:27:03 +08:00
$this -> termAdjustmentCache = new WeakMap ();
2017-03-19 20:48:44 +08:00
}
2017-03-27 18:57:33 +08:00
/**
* Search all entities in the system .
2020-06-27 20:29:00 +08:00
* The provided count is for each entity to search ,
2021-11-06 08:32:01 +08:00
* Total returned could be larger and not guaranteed .
2021-11-15 00:28:01 +08:00
*
2022-10-24 19:12:48 +08:00
* @ return array { total : int , count : int , has_more : bool , results : Collection < Entity > }
2017-03-27 18:57:33 +08:00
*/
2022-07-13 22:23:03 +08:00
public function searchEntities ( SearchOptions $searchOpts , string $entityType = 'all' , int $page = 1 , int $count = 20 ) : array
2017-03-19 20:48:44 +08:00
{
2018-09-26 01:00:40 +08:00
$entityTypes = array_keys ( $this -> entityProvider -> all ());
2017-04-10 03:59:57 +08:00
$entityTypesToSearch = $entityTypes ;
2024-10-03 00:31:45 +08:00
$filterMap = $searchOpts -> filters -> toValueMap ();
2017-04-10 03:59:57 +08:00
if ( $entityType !== 'all' ) {
2023-12-11 23:55:43 +08:00
$entityTypesToSearch = [ $entityType ];
2024-10-03 00:31:45 +08:00
} elseif ( isset ( $filterMap [ 'type' ])) {
$entityTypesToSearch = explode ( '|' , $filterMap [ 'type' ]);
2017-04-10 03:59:57 +08:00
}
2018-03-25 02:46:31 +08:00
$results = collect ();
2017-04-15 22:04:30 +08:00
$total = 0 ;
2018-03-25 03:04:18 +08:00
$hasMore = false ;
2017-04-15 22:04:30 +08:00
2017-04-10 03:59:57 +08:00
foreach ( $entityTypesToSearch as $entityType ) {
2018-01-29 00:58:52 +08:00
if ( ! in_array ( $entityType , $entityTypes )) {
continue ;
}
2021-11-06 08:32:01 +08:00
2024-02-08 06:41:45 +08:00
$searchQuery = $this -> buildQuery ( $searchOpts , $entityType );
2021-11-08 19:04:27 +08:00
$entityTotal = $searchQuery -> count ();
2024-02-08 06:41:45 +08:00
$searchResults = $this -> getPageOfDataFromQuery ( $searchQuery , $entityType , $page , $count );
2021-11-06 08:32:01 +08:00
if ( $entityTotal > ( $page * $count )) {
2018-03-25 03:04:18 +08:00
$hasMore = true ;
}
2021-11-06 08:32:01 +08:00
2018-03-25 03:04:18 +08:00
$total += $entityTotal ;
2021-11-08 19:04:27 +08:00
$results = $results -> merge ( $searchResults );
2017-04-10 03:59:57 +08:00
}
2017-03-27 02:24:57 +08:00
2017-04-15 22:04:30 +08:00
return [
2021-06-26 23:23:15 +08:00
'total' => $total ,
'count' => count ( $results ),
2018-03-25 03:04:18 +08:00
'has_more' => $hasMore ,
2021-06-26 23:23:15 +08:00
'results' => $results -> sortByDesc ( 'score' ) -> values (),
2017-04-15 22:04:30 +08:00
];
2017-03-27 02:24:57 +08:00
}
2017-04-16 02:16:07 +08:00
/**
2021-06-26 23:23:15 +08:00
* Search a book for entities .
2017-04-16 02:16:07 +08:00
*/
2020-06-27 20:29:00 +08:00
public function searchBook ( int $bookId , string $searchString ) : Collection
2017-04-16 02:16:07 +08:00
{
2020-06-27 20:29:00 +08:00
$opts = SearchOptions :: fromString ( $searchString );
2017-04-16 02:31:11 +08:00
$entityTypes = [ 'page' , 'chapter' ];
2024-10-03 00:31:45 +08:00
$filterMap = $opts -> filters -> toValueMap ();
$entityTypesToSearch = isset ( $filterMap [ 'type' ]) ? explode ( '|' , $filterMap [ 'type' ]) : $entityTypes ;
2017-04-16 02:31:11 +08:00
2017-04-16 02:16:07 +08:00
$results = collect ();
2017-04-16 02:31:11 +08:00
foreach ( $entityTypesToSearch as $entityType ) {
2018-01-29 00:58:52 +08:00
if ( ! in_array ( $entityType , $entityTypes )) {
continue ;
}
2021-11-08 23:24:49 +08:00
2024-02-08 06:41:45 +08:00
$search = $this -> buildQuery ( $opts , $entityType ) -> where ( 'book_id' , '=' , $bookId ) -> take ( 20 ) -> get ();
2017-04-16 02:31:11 +08:00
$results = $results -> merge ( $search );
}
2020-11-22 08:17:45 +08:00
2017-04-16 02:31:11 +08:00
return $results -> sortByDesc ( 'score' ) -> take ( 20 );
2017-04-16 02:16:07 +08:00
}
/**
2021-06-26 23:23:15 +08:00
* Search a chapter for entities .
2017-04-16 02:16:07 +08:00
*/
2020-06-27 20:29:00 +08:00
public function searchChapter ( int $chapterId , string $searchString ) : Collection
2017-04-16 02:16:07 +08:00
{
2020-06-27 20:29:00 +08:00
$opts = SearchOptions :: fromString ( $searchString );
2024-02-08 06:41:45 +08:00
$pages = $this -> buildQuery ( $opts , 'page' ) -> where ( 'chapter_id' , '=' , $chapterId ) -> take ( 20 ) -> get ();
2021-06-26 23:23:15 +08:00
2017-04-16 02:16:07 +08:00
return $pages -> sortByDesc ( 'score' );
}
2017-03-27 18:57:33 +08:00
/**
2021-11-08 19:04:27 +08:00
* Get a page of result data from the given query based on the provided page parameters .
2017-03-27 18:57:33 +08:00
*/
2024-02-08 06:41:45 +08:00
protected function getPageOfDataFromQuery ( EloquentBuilder $query , string $entityType , int $page = 1 , int $count = 20 ) : EloquentCollection
2017-04-16 02:16:07 +08:00
{
2021-11-08 23:24:49 +08:00
$relations = [ 'tags' ];
2024-02-08 06:41:45 +08:00
if ( $entityType === 'page' || $entityType === 'chapter' ) {
2021-11-13 21:28:17 +08:00
$relations [ 'book' ] = function ( BelongsTo $query ) {
2021-11-23 07:33:55 +08:00
$query -> scopes ( 'visible' );
2021-11-08 23:24:49 +08:00
};
}
2024-02-08 06:41:45 +08:00
if ( $entityType === 'page' ) {
2021-11-13 21:28:17 +08:00
$relations [ 'chapter' ] = function ( BelongsTo $query ) {
2021-11-23 07:33:55 +08:00
$query -> scopes ( 'visible' );
2021-11-08 23:24:49 +08:00
};
}
2021-11-08 19:04:27 +08:00
return $query -> clone ()
2021-11-08 23:24:49 +08:00
-> with ( array_filter ( $relations ))
2021-11-08 19:04:27 +08:00
-> skip (( $page - 1 ) * $count )
-> take ( $count )
-> get ();
2017-04-16 02:16:07 +08:00
}
/**
2021-06-26 23:23:15 +08:00
* Create a search query for an entity .
2017-04-16 02:16:07 +08:00
*/
2024-02-08 06:41:45 +08:00
protected function buildQuery ( SearchOptions $searchOpts , string $entityType ) : EloquentBuilder
2017-03-27 02:24:57 +08:00
{
2024-02-08 06:41:45 +08:00
$entityModelInstance = $this -> entityProvider -> get ( $entityType );
$entityQuery = $this -> entityQueries -> visibleForList ( $entityType );
2021-11-08 19:41:14 +08:00
2017-03-27 18:57:33 +08:00
// Handle normal search terms
2024-02-08 06:41:45 +08:00
$this -> applyTermSearch ( $entityQuery , $searchOpts , $entityType );
2017-03-27 18:57:33 +08:00
// Handle exact term matching
2024-10-04 02:27:03 +08:00
foreach ( $searchOpts -> exacts -> all () as $exact ) {
$filter = function ( EloquentBuilder $query ) use ( $exact , $entityModelInstance ) {
$inputTerm = str_replace ( '\\' , '\\\\' , $exact -> value );
2021-06-26 23:23:15 +08:00
$query -> where ( 'name' , 'like' , '%' . $inputTerm . '%' )
2021-11-08 23:24:49 +08:00
-> orWhere ( $entityModelInstance -> textField , 'like' , '%' . $inputTerm . '%' );
2024-10-04 02:27:03 +08:00
};
$exact -> negated ? $entityQuery -> whereNot ( $filter ) : $entityQuery -> where ( $filter );
2017-03-27 18:57:33 +08:00
}
2017-03-28 01:05:34 +08:00
// Handle tag searches
2024-10-04 02:27:03 +08:00
foreach ( $searchOpts -> tags -> all () as $tagOption ) {
$this -> applyTagSearch ( $entityQuery , $tagOption );
2017-03-28 01:05:34 +08:00
}
// Handle filters
2024-10-04 02:27:03 +08:00
foreach ( $searchOpts -> filters -> all () as $filterOption ) {
$functionName = Str :: camel ( 'filter_' . $filterOption -> getKey ());
2018-01-29 00:58:52 +08:00
if ( method_exists ( $this , $functionName )) {
2024-10-04 02:27:03 +08:00
$this -> $functionName ( $entityQuery , $entityModelInstance , $filterOption -> value , $filterOption -> negated );
2018-01-29 00:58:52 +08:00
}
2017-03-28 01:05:34 +08:00
}
2022-07-17 02:54:25 +08:00
return $entityQuery ;
2021-11-08 19:29:25 +08:00
}
/**
* For the given search query , apply the queries for handling the regular search terms .
*/
2024-02-08 06:41:45 +08:00
protected function applyTermSearch ( EloquentBuilder $entityQuery , SearchOptions $options , string $entityType ) : void
2021-11-08 19:29:25 +08:00
{
2024-10-03 00:31:45 +08:00
$terms = $options -> searches -> toValueArray ();
2021-11-08 19:29:25 +08:00
if ( count ( $terms ) === 0 ) {
return ;
}
2021-11-08 22:12:40 +08:00
$scoredTerms = $this -> getTermAdjustments ( $options );
$scoreSelect = $this -> selectForScoredTerms ( $scoredTerms );
2021-11-08 19:29:25 +08:00
$subQuery = DB :: table ( 'search_terms' ) -> select ([
'entity_id' ,
'entity_type' ,
2021-11-08 22:12:40 +08:00
DB :: raw ( $scoreSelect [ 'statement' ]),
2021-11-08 19:29:25 +08:00
]);
2021-11-08 22:12:40 +08:00
$subQuery -> addBinding ( $scoreSelect [ 'bindings' ], 'select' );
2021-11-08 19:41:14 +08:00
2024-02-08 06:41:45 +08:00
$subQuery -> where ( 'entity_type' , '=' , $entityType );
2021-11-08 19:29:25 +08:00
$subQuery -> where ( function ( Builder $query ) use ( $terms ) {
foreach ( $terms as $inputTerm ) {
2024-10-03 00:31:45 +08:00
$escapedTerm = str_replace ( '\\' , '\\\\' , $inputTerm );
$query -> orWhere ( 'term' , 'like' , $escapedTerm . '%' );
2021-11-08 19:29:25 +08:00
}
2021-11-08 22:12:40 +08:00
});
$subQuery -> groupBy ( 'entity_type' , 'entity_id' );
$entityQuery -> joinSub ( $subQuery , 's' , 'id' , '=' , 'entity_id' );
$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 23:00:47 +08:00
*
2021-11-09 23:13:15 +08:00
* @ param array < string , float > $scoredTerms
*
2021-11-08 22:12:40 +08: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 23:00:47 +08:00
$ifChain = 'IF(term like ?, score * ' . ( float ) $score . ', ' . $ifChain . ')' ;
2021-11-08 22:12:40 +08:00
$bindings [] = $term . '%' ;
}
return [
'statement' => 'SUM(' . $ifChain . ') as score' ,
2021-11-08 23:00:47 +08:00
'bindings' => array_reverse ( $bindings ),
2021-11-08 22:12:40 +08:00
];
}
2021-11-09 23:13:15 +08: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 22:12:40 +08:00
protected function getTermAdjustments ( SearchOptions $options ) : array
{
if ( isset ( $this -> termAdjustmentCache [ $options ])) {
return $this -> termAdjustmentCache [ $options ];
}
$termQuery = SearchTerm :: query () -> toBase ();
$whenStatements = [];
$whenBindings = [];
2024-10-03 00:31:45 +08:00
foreach ( $options -> searches -> toValueArray () as $term ) {
2021-11-08 22:12:40 +08:00
$whenStatements [] = 'WHEN term LIKE ? THEN ?' ;
$whenBindings [] = $term . '%' ;
$whenBindings [] = $term ;
$termQuery -> orWhere ( 'term' , 'like' , $term . '%' );
}
$case = 'CASE ' . implode ( ' ' , $whenStatements ) . ' END' ;
2021-11-08 23:00:47 +08:00
$termQuery -> selectRaw ( $case . ' as term' , $whenBindings );
2021-11-08 22:12:40 +08:00
$termQuery -> selectRaw ( 'COUNT(*) as count' );
$termQuery -> groupByRaw ( $case , $whenBindings );
2021-11-08 19:41:14 +08:00
2021-11-08 23:00:47 +08:00
$termCounts = $termQuery -> pluck ( 'count' , 'term' ) -> toArray ();
2021-11-08 22:12:40 +08:00
$adjusted = $this -> rawTermCountsToAdjustments ( $termCounts );
$this -> termAdjustmentCache [ $options ] = $adjusted ;
2021-11-08 23:00:47 +08:00
2021-11-08 22:12:40 +08:00
return $this -> termAdjustmentCache [ $options ];
}
/**
* Convert counts of terms into a relative - count normalised multiplier .
2021-11-08 23:00:47 +08:00
*
2021-11-08 22:12:40 +08:00
* @ param array < string , int > $termCounts
2021-11-08 23:00:47 +08:00
*
2021-11-08 22:12:40 +08:00
* @ return array < string , int >
*/
protected function rawTermCountsToAdjustments ( array $termCounts ) : array
{
2021-11-08 23:00:47 +08:00
if ( empty ( $termCounts )) {
return [];
}
2021-11-13 21:28:17 +08:00
2021-11-08 22:12:40 +08: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 19:41:14 +08:00
2021-11-08 22:12:40 +08:00
return $multipliers ;
2017-03-27 02:24:57 +08:00
}
2017-03-28 01:05:34 +08:00
/**
2024-10-04 02:27:03 +08:00
* Apply a tag search term onto an entity query .
2017-03-28 01:05:34 +08:00
*/
2024-10-04 02:27:03 +08:00
protected function applyTagSearch ( EloquentBuilder $query , TagSearchOption $option ) : void
2017-03-28 01:05:34 +08:00
{
2024-10-04 02:27:03 +08: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 23:23:15 +08:00
2024-10-04 02:27:03 +08:00
if ( ! empty ( $tagParts [ 'name' ])) {
$query -> where ( 'name' , '=' , $tagParts [ 'name' ]);
}
2017-03-28 01:05:34 +08:00
2024-10-04 02:27:03 +08: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-28 01:05:34 +08:00
} else {
2024-10-04 02:27:03 +08:00
$query -> where ( 'value' , $tagParts [ 'operator' ], $tagParts [ 'value' ]);
2017-03-28 01:05:34 +08:00
}
2024-10-04 02:27:03 +08:00
};
2021-06-26 23:23:15 +08:00
2024-10-04 02:27:03 +08:00
$option -> negated ? $query -> whereDoesntHave ( 'tags' , $filter ) : $query -> whereHas ( 'tags' , $filter );
}
protected function applyNegatableWhere ( EloquentBuilder $query , bool $negated , string $column , string $operator , mixed $value ) : void
{
if ( $negated ) {
$query -> whereNot ( $column , $operator , $value );
} else {
$query -> where ( $column , $operator , $value );
}
2017-03-28 01:05:34 +08:00
}
/**
2021-06-26 23:23:15 +08:00
* Custom entity search filters .
2017-03-28 01:05:34 +08:00
*/
2024-10-04 02:27:03 +08:00
protected function filterUpdatedAfter ( EloquentBuilder $query , Entity $model , string $input , bool $negated ) : void
2017-03-28 01:05:34 +08:00
{
2024-10-04 02:27:03 +08:00
$date = date_create ( $input );
$this -> applyNegatableWhere ( $query , $negated , 'updated_at' , '>=' , $date );
2017-03-28 01:05:34 +08:00
}
2024-10-04 02:27:03 +08:00
protected function filterUpdatedBefore ( EloquentBuilder $query , Entity $model , string $input , bool $negated ) : void
2017-03-28 01:05:34 +08:00
{
2024-10-04 02:27:03 +08:00
$date = date_create ( $input );
$this -> applyNegatableWhere ( $query , $negated , 'updated_at' , '<' , $date );
2017-03-28 01:05:34 +08:00
}
2024-10-04 02:27:03 +08:00
protected function filterCreatedAfter ( EloquentBuilder $query , Entity $model , string $input , bool $negated ) : void
2017-03-28 01:05:34 +08:00
{
2024-10-04 02:27:03 +08:00
$date = date_create ( $input );
$this -> applyNegatableWhere ( $query , $negated , 'created_at' , '>=' , $date );
2017-03-28 01:05:34 +08:00
}
2024-10-04 02:27:03 +08:00
protected function filterCreatedBefore ( EloquentBuilder $query , Entity $model , string $input , bool $negated )
2017-03-28 01:05:34 +08:00
{
2024-10-04 02:27:03 +08:00
$date = date_create ( $input );
$this -> applyNegatableWhere ( $query , $negated , 'created_at' , '<' , $date );
2017-03-28 01:05:34 +08:00
}
2024-10-04 02:27:03 +08:00
protected function filterCreatedBy ( EloquentBuilder $query , Entity $model , string $input , bool $negated )
2017-03-28 01:05:34 +08:00
{
2021-03-11 06:51:18 +08:00
$userSlug = $input === 'me' ? user () -> slug : trim ( $input );
$user = User :: query () -> where ( 'slug' , '=' , $userSlug ) -> first ([ 'id' ]);
if ( $user ) {
2024-10-04 02:27:03 +08:00
$this -> applyNegatableWhere ( $query , $negated , 'created_by' , '=' , $user -> id );
2018-01-29 00:58:52 +08:00
}
2017-03-28 01:05:34 +08:00
}
2024-10-04 02:27:03 +08:00
protected function filterUpdatedBy ( EloquentBuilder $query , Entity $model , string $input , bool $negated )
2017-03-28 01:05:34 +08:00
{
2021-03-11 06:51:18 +08:00
$userSlug = $input === 'me' ? user () -> slug : trim ( $input );
$user = User :: query () -> where ( 'slug' , '=' , $userSlug ) -> first ([ 'id' ]);
if ( $user ) {
2024-10-04 02:27:03 +08:00
$this -> applyNegatableWhere ( $query , $negated , 'updated_by' , '=' , $user -> id );
2018-01-29 00:58:52 +08:00
}
2017-03-28 01:05:34 +08:00
}
2024-10-04 02:27:03 +08:00
protected function filterOwnedBy ( EloquentBuilder $query , Entity $model , string $input , bool $negated )
2021-02-14 18:39:18 +08:00
{
2021-03-16 02:27:03 +08:00
$userSlug = $input === 'me' ? user () -> slug : trim ( $input );
$user = User :: query () -> where ( 'slug' , '=' , $userSlug ) -> first ([ 'id' ]);
if ( $user ) {
2024-10-04 02:27:03 +08:00
$this -> applyNegatableWhere ( $query , $negated , 'owned_by' , '=' , $user -> id );
2021-02-14 18:39:18 +08:00
}
}
2024-10-04 02:27:03 +08:00
protected function filterInName ( EloquentBuilder $query , Entity $model , string $input , bool $negated )
2017-03-28 01:05:34 +08:00
{
2024-10-04 02:27:03 +08:00
$this -> applyNegatableWhere ( $query , $negated , 'name' , 'like' , '%' . $input . '%' );
2017-03-28 01:05:34 +08:00
}
2024-10-04 02:27:03 +08:00
protected function filterInTitle ( EloquentBuilder $query , Entity $model , string $input , bool $negated )
2018-01-29 00:58:52 +08:00
{
2024-10-04 02:27:03 +08:00
$this -> filterInName ( $query , $model , $input , $negated );
2018-01-29 00:58:52 +08:00
}
2017-03-28 01:05:34 +08:00
2024-10-04 02:27:03 +08:00
protected function filterInBody ( EloquentBuilder $query , Entity $model , string $input , bool $negated )
2017-03-28 01:05:34 +08:00
{
2024-10-04 02:27:03 +08:00
$this -> applyNegatableWhere ( $query , $negated , $model -> textField , 'like' , '%' . $input . '%' );
2017-03-28 01:05:34 +08:00
}
2024-10-04 02:27:03 +08:00
protected function filterIsRestricted ( EloquentBuilder $query , Entity $model , string $input , bool $negated )
2017-03-28 01:05:34 +08:00
{
2024-10-04 02:27:03 +08:00
$negated ? $query -> whereDoesntHave ( 'permissions' ) : $query -> whereHas ( 'permissions' );
2017-03-28 01:05:34 +08:00
}
2024-10-04 02:27:03 +08:00
protected function filterViewedByMe ( EloquentBuilder $query , Entity $model , string $input , bool $negated )
2017-03-28 01:05:34 +08:00
{
2024-10-04 02:27:03 +08:00
$filter = function ( $query ) {
2017-03-28 01:05:34 +08:00
$query -> where ( 'user_id' , '=' , user () -> id );
2024-10-04 02:27:03 +08:00
};
$negated ? $query -> whereDoesntHave ( 'views' , $filter ) : $query -> whereHas ( 'views' , $filter );
2017-03-28 01:05:34 +08:00
}
2024-10-04 02:27:03 +08:00
protected function filterNotViewedByMe ( EloquentBuilder $query , Entity $model , string $input , bool $negated )
2017-03-28 01:05:34 +08:00
{
2024-10-04 02:27:03 +08:00
$filter = function ( $query ) {
2017-03-28 01:05:34 +08:00
$query -> where ( 'user_id' , '=' , user () -> id );
2024-10-04 02:27:03 +08:00
};
$negated ? $query -> whereHas ( 'views' , $filter ) : $query -> whereDoesntHave ( 'views' , $filter );
2017-03-28 01:05:34 +08:00
}
2024-10-04 02:27:03 +08:00
protected function filterIsTemplate ( EloquentBuilder $query , Entity $model , string $input , bool $negated )
2023-12-11 23:55:43 +08:00
{
if ( $model instanceof Page ) {
2024-10-04 02:27:03 +08:00
$this -> applyNegatableWhere ( $query , $negated , 'template' , '=' , true );
2023-12-11 23:55:43 +08:00
}
}
2024-10-04 02:27:03 +08:00
protected function filterSortBy ( EloquentBuilder $query , Entity $model , string $input , bool $negated )
2017-10-01 18:24:13 +08:00
{
2019-09-14 06:58:40 +08:00
$functionName = Str :: camel ( 'sort_by_' . $input );
2018-01-29 00:58:52 +08:00
if ( method_exists ( $this , $functionName )) {
2024-10-04 02:27:03 +08:00
$this -> $functionName ( $query , $model , $negated );
2018-01-29 00:58:52 +08:00
}
2017-10-01 18:24:13 +08:00
}
/**
2021-06-26 23:23:15 +08:00
* Sorting filter options .
2017-10-01 18:24:13 +08:00
*/
2024-10-04 02:27:03 +08:00
protected function sortByLastCommented ( EloquentBuilder $query , Entity $model , bool $negated )
2017-10-01 18:24:13 +08:00
{
2021-11-08 19:04:27 +08:00
$commentsTable = DB :: getTablePrefix () . 'comments' ;
2017-10-01 18:24:13 +08:00
$morphClass = str_replace ( '\\' , '\\\\' , $model -> getMorphClass ());
2021-11-08 19:04:27 +08:00
$commentQuery = DB :: raw ( '(SELECT c1.entity_id, c1.entity_type, c1.created_at as last_commented FROM ' . $commentsTable . ' c1 LEFT JOIN ' . $commentsTable . ' c2 ON (c1.entity_id = c2.entity_id AND c1.entity_type = c2.entity_type AND c1.created_at < c2.created_at) WHERE c1.entity_type = \'' . $morphClass . '\' AND c2.created_at IS NULL) as comments' );
2017-10-01 18:24:13 +08:00
2024-10-05 22:20:04 +08:00
$query -> join ( $commentQuery , $model -> getTable () . '.id' , '=' , DB :: raw ( 'comments.entity_id' ))
2024-10-04 02:27:03 +08:00
-> orderBy ( 'last_commented' , $negated ? 'asc' : 'desc' );
2017-10-01 18:24:13 +08:00
}
2018-01-29 00:58:52 +08:00
}