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 ;
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 ;
2021-11-08 22:12:40 +08:00
use SplObjectStorage ;
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
{
2017-03-28 01:05:34 +08:00
/**
2021-06-26 23:23:15 +08:00
* Acceptable operators to be used in a query .
*
2022-07-13 03:15:41 +08:00
* @ var string []
2017-03-28 01:05:34 +08:00
*/
2022-08-16 18:27:22 +08:00
protected array $queryOperators = [ '<=' , '>=' , '=' , '<' , '>' , 'like' , '!=' ];
2017-03-28 01:05:34 +08:00
2021-11-08 22:12:40 +08:00
/**
* Retain a cache of score adjusted terms for specific search options .
* From PHP >= 8 this can be made into a WeakMap instead .
*
* @ var SplObjectStorage
*/
protected $termAdjustmentCache ;
2024-02-08 06:41:45 +08:00
public function __construct (
protected EntityProvider $entityProvider ,
protected PermissionApplicator $permissions ,
protected EntityQueries $entityQueries ,
) {
2021-11-08 22:12:40 +08:00
$this -> termAdjustmentCache = new SplObjectStorage ();
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 ;
if ( $entityType !== 'all' ) {
2023-12-11 23:55:43 +08:00
$entityTypesToSearch = [ $entityType ];
2021-06-26 23:23:15 +08:00
} elseif ( isset ( $searchOpts -> filters [ 'type' ])) {
2020-06-27 20:29:00 +08:00
$entityTypesToSearch = explode ( '|' , $searchOpts -> filters [ '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' ];
2020-06-27 20:29:00 +08:00
$entityTypesToSearch = isset ( $opts -> filters [ 'type' ]) ? explode ( '|' , $opts -> filters [ '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
2020-11-22 08:17:45 +08:00
foreach ( $searchOpts -> exacts as $inputTerm ) {
2021-11-08 23:24:49 +08:00
$entityQuery -> where ( function ( EloquentBuilder $query ) use ( $inputTerm , $entityModelInstance ) {
2023-04-27 23:33:24 +08:00
$inputTerm = str_replace ( '\\' , '\\\\' , $inputTerm );
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 . '%' );
2017-03-27 18:57:33 +08:00
});
}
2017-03-28 01:05:34 +08:00
// Handle tag searches
2020-06-27 20:29:00 +08:00
foreach ( $searchOpts -> tags as $inputTerm ) {
2021-11-08 19:29:25 +08:00
$this -> applyTagSearch ( $entityQuery , $inputTerm );
2017-03-28 01:05:34 +08:00
}
// Handle filters
2020-06-27 20:29:00 +08:00
foreach ( $searchOpts -> filters as $filterTerm => $filterValue ) {
2019-09-14 06:58:40 +08:00
$functionName = Str :: camel ( 'filter_' . $filterTerm );
2018-01-29 00:58:52 +08:00
if ( method_exists ( $this , $functionName )) {
2021-11-08 23:24:49 +08:00
$this -> $functionName ( $entityQuery , $entityModelInstance , $filterValue );
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
{
2021-11-08 22:12:40 +08:00
$terms = $options -> searches ;
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 ) {
2023-04-27 23:33:24 +08:00
$inputTerm = str_replace ( '\\' , '\\\\' , $inputTerm );
2021-11-08 19:29:25 +08:00
$query -> orWhere ( 'term' , 'like' , $inputTerm . '%' );
}
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 = [];
foreach ( $options -> searches as $term ) {
$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
/**
* Get the available query operators as a regex escaped list .
*/
2020-06-27 20:29:00 +08:00
protected function getRegexEscapedOperators () : string
2017-03-28 01:05:34 +08:00
{
$escapedOperators = [];
foreach ( $this -> queryOperators as $operator ) {
$escapedOperators [] = preg_quote ( $operator );
}
2021-06-26 23:23:15 +08:00
2021-10-27 05:04:18 +08:00
return implode ( '|' , $escapedOperators );
2017-03-28 01:05:34 +08:00
}
/**
* Apply a tag search term onto a entity query .
*/
2020-06-27 20:29:00 +08:00
protected function applyTagSearch ( EloquentBuilder $query , string $tagTerm ) : EloquentBuilder
2018-01-29 00:58:52 +08:00
{
2021-06-26 23:23:15 +08:00
preg_match ( '/^(.*?)((' . $this -> getRegexEscapedOperators () . ')(.*?))?$/' , $tagTerm , $tagSplit );
2018-09-26 01:00:40 +08:00
$query -> whereHas ( 'tags' , function ( EloquentBuilder $query ) use ( $tagSplit ) {
2017-03-28 01:05:34 +08:00
$tagName = $tagSplit [ 1 ];
$tagOperator = count ( $tagSplit ) > 2 ? $tagSplit [ 3 ] : '' ;
$tagValue = count ( $tagSplit ) > 3 ? $tagSplit [ 4 ] : '' ;
$validOperator = in_array ( $tagOperator , $this -> queryOperators );
if ( ! empty ( $tagOperator ) && ! empty ( $tagValue ) && $validOperator ) {
2018-01-29 00:58:52 +08:00
if ( ! empty ( $tagName )) {
$query -> where ( 'name' , '=' , $tagName );
}
2017-03-28 01:05:34 +08:00
if ( is_numeric ( $tagValue ) && $tagOperator !== '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.
2021-11-20 22:03:56 +08:00
/** @var Connection $connection */
$connection = $query -> getConnection ();
$tagValue = ( float ) trim ( $connection -> getPdo () -> quote ( $tagValue ), " ' " );
2022-05-05 16:33:25 +08:00
$query -> whereRaw ( " value { $tagOperator } { $tagValue } " );
2017-03-28 01:05:34 +08:00
} else {
2023-04-27 23:33:24 +08:00
if ( $tagOperator === 'like' ) {
$tagValue = str_replace ( '\\' , '\\\\' , $tagValue );
}
2017-03-28 01:05:34 +08:00
$query -> where ( 'value' , $tagOperator , $tagValue );
}
} else {
$query -> where ( 'name' , '=' , $tagName );
}
});
2021-06-26 23:23:15 +08:00
2017-03-28 01:05:34 +08:00
return $query ;
}
/**
2021-06-26 23:23:15 +08:00
* Custom entity search filters .
2017-03-28 01:05:34 +08:00
*/
2021-11-08 19:04:27 +08:00
protected function filterUpdatedAfter ( EloquentBuilder $query , Entity $model , $input ) : void
2017-03-28 01:05:34 +08:00
{
2018-01-29 00:58:52 +08:00
try {
$date = date_create ( $input );
2021-11-08 19:04:27 +08:00
$query -> where ( 'updated_at' , '>=' , $date );
2021-11-08 23:00:47 +08:00
} catch ( \Exception $e ) {
}
2017-03-28 01:05:34 +08:00
}
2021-11-08 19:04:27 +08:00
protected function filterUpdatedBefore ( EloquentBuilder $query , Entity $model , $input ) : void
2017-03-28 01:05:34 +08:00
{
2018-01-29 00:58:52 +08:00
try {
$date = date_create ( $input );
2021-11-08 19:04:27 +08:00
$query -> where ( 'updated_at' , '<' , $date );
2021-11-08 23:00:47 +08:00
} catch ( \Exception $e ) {
}
2017-03-28 01:05:34 +08:00
}
2021-11-08 19:04:27 +08:00
protected function filterCreatedAfter ( EloquentBuilder $query , Entity $model , $input ) : void
2017-03-28 01:05:34 +08:00
{
2018-01-29 00:58:52 +08:00
try {
$date = date_create ( $input );
2021-11-08 19:04:27 +08:00
$query -> where ( 'created_at' , '>=' , $date );
2021-11-08 23:00:47 +08:00
} catch ( \Exception $e ) {
}
2017-03-28 01:05:34 +08:00
}
2018-09-26 01:00:40 +08:00
protected function filterCreatedBefore ( EloquentBuilder $query , Entity $model , $input )
2017-03-28 01:05:34 +08:00
{
2018-01-29 00:58:52 +08:00
try {
$date = date_create ( $input );
2021-11-08 19:04:27 +08:00
$query -> where ( 'created_at' , '<' , $date );
2021-11-08 23:00:47 +08:00
} catch ( \Exception $e ) {
}
2017-03-28 01:05:34 +08:00
}
2018-09-26 01:00:40 +08:00
protected function filterCreatedBy ( EloquentBuilder $query , Entity $model , $input )
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 ) {
$query -> where ( 'created_by' , '=' , $user -> id );
2018-01-29 00:58:52 +08:00
}
2017-03-28 01:05:34 +08:00
}
2018-09-26 01:00:40 +08:00
protected function filterUpdatedBy ( EloquentBuilder $query , Entity $model , $input )
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 ) {
$query -> where ( 'updated_by' , '=' , $user -> id );
2018-01-29 00:58:52 +08:00
}
2017-03-28 01:05:34 +08:00
}
2021-02-14 18:39:18 +08:00
protected function filterOwnedBy ( EloquentBuilder $query , Entity $model , $input )
{
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 ) {
$query -> where ( 'owned_by' , '=' , $user -> id );
2021-02-14 18:39:18 +08:00
}
}
2018-09-26 01:00:40 +08:00
protected function filterInName ( EloquentBuilder $query , Entity $model , $input )
2017-03-28 01:05:34 +08:00
{
2021-06-26 23:23:15 +08:00
$query -> where ( 'name' , 'like' , '%' . $input . '%' );
2017-03-28 01:05:34 +08:00
}
2018-09-26 01:00:40 +08:00
protected function filterInTitle ( EloquentBuilder $query , Entity $model , $input )
2018-01-29 00:58:52 +08:00
{
$this -> filterInName ( $query , $model , $input );
}
2017-03-28 01:05:34 +08:00
2018-09-26 01:00:40 +08:00
protected function filterInBody ( EloquentBuilder $query , Entity $model , $input )
2017-03-28 01:05:34 +08:00
{
2021-06-26 23:23:15 +08:00
$query -> where ( $model -> textField , 'like' , '%' . $input . '%' );
2017-03-28 01:05:34 +08:00
}
2018-09-26 01:00:40 +08:00
protected function filterIsRestricted ( EloquentBuilder $query , Entity $model , $input )
2017-03-28 01:05:34 +08:00
{
2022-10-10 23:22:51 +08:00
$query -> whereHas ( 'permissions' );
2017-03-28 01:05:34 +08:00
}
2018-09-26 01:00:40 +08:00
protected function filterViewedByMe ( EloquentBuilder $query , Entity $model , $input )
2017-03-28 01:05:34 +08:00
{
2018-01-29 00:58:52 +08:00
$query -> whereHas ( 'views' , function ( $query ) {
2017-03-28 01:05:34 +08:00
$query -> where ( 'user_id' , '=' , user () -> id );
});
}
2018-09-26 01:00:40 +08:00
protected function filterNotViewedByMe ( EloquentBuilder $query , Entity $model , $input )
2017-03-28 01:05:34 +08:00
{
2018-01-29 00:58:52 +08:00
$query -> whereDoesntHave ( 'views' , function ( $query ) {
2017-03-28 01:05:34 +08:00
$query -> where ( 'user_id' , '=' , user () -> id );
});
}
2023-12-11 23:55:43 +08:00
protected function filterIsTemplate ( EloquentBuilder $query , Entity $model , $input )
{
if ( $model instanceof Page ) {
$query -> where ( 'template' , '=' , true );
}
}
2018-09-26 01:00:40 +08:00
protected function filterSortBy ( EloquentBuilder $query , Entity $model , $input )
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 )) {
$this -> $functionName ( $query , $model );
}
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
*/
2018-09-26 01:00:40 +08:00
protected function sortByLastCommented ( EloquentBuilder $query , Entity $model )
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
$query -> join ( $commentQuery , $model -> getTable () . '.id' , '=' , 'comments.entity_id' ) -> orderBy ( 'last_commented' , 'desc' );
}
2018-01-29 00:58:52 +08:00
}