X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/a6633642232efd164d4708967ab59e498fbff896..refs/pull/3012/head:/app/Entities/Tools/SearchRunner.php diff --git a/app/Entities/Tools/SearchRunner.php b/app/Entities/Tools/SearchRunner.php index acfe8d956..df566eb0b 100644 --- a/app/Entities/Tools/SearchRunner.php +++ b/app/Entities/Tools/SearchRunner.php @@ -1,6 +1,9 @@ -=', '=', '<', '>', 'like', '!=']; - public function __construct(EntityProvider $entityProvider, Connection $db, PermissionService $permissionService) { $this->entityProvider = $entityProvider; @@ -55,7 +56,7 @@ class SearchRunner if ($entityType !== 'all') { $entityTypesToSearch = $entityType; - } else if (isset($searchOpts->filters['type'])) { + } elseif (isset($searchOpts->filters['type'])) { $entityTypesToSearch = explode('|', $searchOpts->filters['type']); } @@ -77,16 +78,15 @@ class SearchRunner } return [ - 'total' => $total, - 'count' => count($results), + 'total' => $total, + 'count' => count($results), 'has_more' => $hasMore, - 'results' => $results->sortByDesc('score')->values(), + 'results' => $results->sortByDesc('score')->values(), ]; } - /** - * Search a book for entities + * Search a book for entities. */ public function searchBook(int $bookId, string $searchString): Collection { @@ -107,12 +107,13 @@ class SearchRunner } /** - * Search a chapter for entities + * Search a chapter for entities. */ public function searchChapter(int $chapterId, string $searchString): Collection { $opts = SearchOptions::fromString($searchString); $pages = $this->buildEntitySearchQuery($opts, 'page')->where('chapter_id', '=', $chapterId)->take(20)->get(); + return $pages->sortByDesc('score'); } @@ -120,6 +121,7 @@ class SearchRunner * Search across a particular entity type. * Setting getCount = true will return the total * matching instead of the items themselves. + * * @return \Illuminate\Database\Eloquent\Collection|int|static[] */ protected function searchEntityTable(SearchOptions $searchOpts, string $entityType = 'page', int $page = 1, int $count = 20, string $action = 'view', bool $getCount = false) @@ -129,12 +131,13 @@ class SearchRunner return $query->count(); } - $query = $query->skip(($page-1) * $count)->take($count); + $query = $query->skip(($page - 1) * $count)->take($count); + return $query->get(); } /** - * Create a search query for an entity + * Create a search query for an entity. */ protected function buildEntitySearchQuery(SearchOptions $searchOpts, string $entityType = 'page', string $action = 'view'): EloquentBuilder { @@ -148,20 +151,22 @@ class SearchRunner $subQuery->where('entity_type', '=', $entity->getMorphClass()); $subQuery->where(function (Builder $query) use ($searchOpts) { foreach ($searchOpts->searches as $inputTerm) { - $query->orWhere('term', 'like', $inputTerm .'%'); + $query->orWhere('term', 'like', $inputTerm . '%'); } })->groupBy('entity_type', 'entity_id'); $entitySelect->join($this->db->raw('(' . $subQuery->toSql() . ') as s'), function (JoinClause $join) { $join->on('id', '=', 'entity_id'); - })->selectRaw($entity->getTable().'.*, s.score')->orderBy('score', 'desc'); + })->addSelect($entity->getTable() . '.*') + ->selectRaw('s.score') + ->orderBy('score', 'desc'); $entitySelect->mergeBindings($subQuery); } // Handle exact term matching foreach ($searchOpts->exacts as $inputTerm) { $entitySelect->where(function (EloquentBuilder $query) use ($inputTerm, $entity) { - $query->where('name', 'like', '%'.$inputTerm .'%') - ->orWhere($entity->textField, 'like', '%'.$inputTerm .'%'); + $query->where('name', 'like', '%' . $inputTerm . '%') + ->orWhere($entity->textField, 'like', '%' . $inputTerm . '%'); }); } @@ -178,7 +183,7 @@ class SearchRunner } } - return $this->permissionService->enforceEntityRestrictions($entityType, $entitySelect, $action); + return $this->permissionService->enforceEntityRestrictions($entity, $entitySelect, $action); } /** @@ -190,7 +195,8 @@ class SearchRunner foreach ($this->queryOperators as $operator) { $escapedOperators[] = preg_quote($operator); } - return join('|', $escapedOperators); + + return implode('|', $escapedOperators); } /** @@ -198,7 +204,7 @@ class SearchRunner */ protected function applyTagSearch(EloquentBuilder $query, string $tagTerm): EloquentBuilder { - preg_match("/^(.*?)((".$this->getRegexEscapedOperators().")(.*?))?$/", $tagTerm, $tagSplit); + preg_match('/^(.*?)((' . $this->getRegexEscapedOperators() . ')(.*?))?$/', $tagTerm, $tagSplit); $query->whereHas('tags', function (EloquentBuilder $query) use ($tagSplit) { $tagName = $tagSplit[1]; $tagOperator = count($tagSplit) > 2 ? $tagSplit[3] : ''; @@ -221,13 +227,13 @@ class SearchRunner $query->where('name', '=', $tagName); } }); + return $query; } /** - * Custom entity search filters + * Custom entity search filters. */ - protected function filterUpdatedAfter(EloquentBuilder $query, Entity $model, $input) { try { @@ -270,29 +276,34 @@ class SearchRunner protected function filterCreatedBy(EloquentBuilder $query, Entity $model, $input) { - if (!is_numeric($input) && $input !== 'me') { - return; - } - if ($input === 'me') { - $input = user()->id; + $userSlug = $input === 'me' ? user()->slug : trim($input); + $user = User::query()->where('slug', '=', $userSlug)->first(['id']); + if ($user) { + $query->where('created_by', '=', $user->id); } - $query->where('created_by', '=', $input); } protected function filterUpdatedBy(EloquentBuilder $query, Entity $model, $input) { - if (!is_numeric($input) && $input !== 'me') { - return; + $userSlug = $input === 'me' ? user()->slug : trim($input); + $user = User::query()->where('slug', '=', $userSlug)->first(['id']); + if ($user) { + $query->where('updated_by', '=', $user->id); } - if ($input === 'me') { - $input = user()->id; + } + + protected function filterOwnedBy(EloquentBuilder $query, Entity $model, $input) + { + $userSlug = $input === 'me' ? user()->slug : trim($input); + $user = User::query()->where('slug', '=', $userSlug)->first(['id']); + if ($user) { + $query->where('owned_by', '=', $user->id); } - $query->where('updated_by', '=', $input); } protected function filterInName(EloquentBuilder $query, Entity $model, $input) { - $query->where('name', 'like', '%' .$input. '%'); + $query->where('name', 'like', '%' . $input . '%'); } protected function filterInTitle(EloquentBuilder $query, Entity $model, $input) @@ -302,7 +313,7 @@ class SearchRunner protected function filterInBody(EloquentBuilder $query, Entity $model, $input) { - $query->where($model->textField, 'like', '%' .$input. '%'); + $query->where($model->textField, 'like', '%' . $input . '%'); } protected function filterIsRestricted(EloquentBuilder $query, Entity $model, $input) @@ -332,16 +343,14 @@ class SearchRunner } } - /** - * Sorting filter options + * Sorting filter options. */ - protected function sortByLastCommented(EloquentBuilder $query, Entity $model) { $commentsTable = $this->db->getTablePrefix() . 'comments'; $morphClass = str_replace('\\', '\\\\', $model->getMorphClass()); - $commentQuery = $this->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'); + $commentQuery = $this->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'); $query->join($commentQuery, $model->getTable() . '.id', '=', 'comments.entity_id')->orderBy('last_commented', 'desc'); }