From 3fd25bd03e15a965213d61b5887b15ba3d172eef Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 28 Oct 2025 20:37:41 +0000 Subject: [PATCH] Search: Added pagination, updated other search uses Also updated hydrator to be created via injection. --- app/Entities/Tools/EntityHydrator.php | 32 +++++++++------------------ app/Search/SearchApiController.php | 9 ++++---- app/Search/SearchController.php | 14 +++++++----- app/Search/SearchRunner.php | 29 ++++++------------------ resources/views/search/all.blade.php | 6 +---- 5 files changed, 31 insertions(+), 59 deletions(-) diff --git a/app/Entities/Tools/EntityHydrator.php b/app/Entities/Tools/EntityHydrator.php index cb6267423..7cabc7ecb 100644 --- a/app/Entities/Tools/EntityHydrator.php +++ b/app/Entities/Tools/EntityHydrator.php @@ -12,31 +12,22 @@ use Illuminate\Database\Eloquent\Collection; class EntityHydrator { - /** - * @var EntityTable[] $entities - */ - protected array $entities; - - protected bool $loadTags = false; - protected bool $loadParents = false; - - public function __construct(array $entities, bool $loadTags = false, bool $loadParents = false) - { - $this->entities = $entities; - $this->loadTags = $loadTags; - $this->loadParents = $loadParents; + public function __construct( + protected EntityQueries $entityQueries, + ) { } /** * Hydrate the entities of this hydrator to return a list of entities represented * in their original intended models. + * @param EntityTable[] $entities * @return Entity[] */ - public function hydrate(): array + public function hydrate(array $entities, bool $loadTags = false, bool $loadParents = false): array { $hydrated = []; - foreach ($this->entities as $entity) { + foreach ($entities as $entity) { $data = $entity->getRawOriginal(); $instance = Entity::instanceFromType($entity->type); @@ -49,11 +40,11 @@ class EntityHydrator $hydrated[] = $instance; } - if ($this->loadTags) { + if ($loadTags) { $this->loadTagsIntoModels($hydrated); } - if ($this->loadParents) { + if ($loadParents) { $this->loadParentsIntoModels($hydrated); } @@ -115,10 +106,7 @@ class EntityHydrator } } - // TODO - Inject in? - $queries = app()->make(EntityQueries::class); - - $parentQuery = $queries->visibleForList(); + $parentQuery = $this->entityQueries->visibleForList(); $filtered = count($parentsByType['book']) > 0 || count($parentsByType['chapter']) > 0; $parentQuery = $parentQuery->where(function ($query) use ($parentsByType) { foreach ($parentsByType as $type => $ids) { @@ -132,7 +120,7 @@ class EntityHydrator }); $parentModels = $filtered ? $parentQuery->get()->all() : []; - $parents = (new EntityHydrator($parentModels))->hydrate(); + $parents = $this->hydrate($parentModels); $parentMap = []; foreach ($parents as $parent) { $parentMap[$parent->type . ':' . $parent->id] = $parent; diff --git a/app/Search/SearchApiController.php b/app/Search/SearchApiController.php index cd4a14a39..5de7a5110 100644 --- a/app/Search/SearchApiController.php +++ b/app/Search/SearchApiController.php @@ -1,10 +1,13 @@ validate($request, $this->rules['all']); diff --git a/app/Search/SearchController.php b/app/Search/SearchController.php index 2fce6a3d5..9586beffb 100644 --- a/app/Search/SearchController.php +++ b/app/Search/SearchController.php @@ -7,6 +7,7 @@ use BookStack\Entities\Queries\QueryPopular; use BookStack\Entities\Tools\SiblingFetcher; use BookStack\Http\Controller; use Illuminate\Http\Request; +use Illuminate\Pagination\LengthAwarePaginator; class SearchController extends Controller { @@ -23,20 +24,21 @@ class SearchController extends Controller { $searchOpts = SearchOptions::fromRequest($request); $fullSearchString = $searchOpts->toString(); - $this->setPageTitle(trans('entities.search_for_term', ['term' => $fullSearchString])); - $page = intval($request->get('page', '0')) ?: 1; - $nextPageLink = url('/search?term=' . urlencode($fullSearchString) . '&page=' . ($page + 1)); $results = $this->searchRunner->searchEntities($searchOpts, 'all', $page, 20); $formatter->format($results['results']->all(), $searchOpts); + $paginator = new LengthAwarePaginator($results['results'], $results['total'], 20, $page); + $paginator->setPath('/search'); + $paginator->appends($request->except('page')); + + $this->setPageTitle(trans('entities.search_for_term', ['term' => $fullSearchString])); return view('search.all', [ 'entities' => $results['results'], 'totalResults' => $results['total'], + 'paginator' => $paginator, 'searchTerm' => $fullSearchString, - 'hasNextPage' => $results['has_more'], - 'nextPageLink' => $nextPageLink, 'options' => $searchOpts, ]); } @@ -128,7 +130,7 @@ class SearchController extends Controller } /** - * Search siblings items in the system. + * Search sibling items in the system. */ public function searchSiblings(Request $request, SiblingFetcher $siblingFetcher) { diff --git a/app/Search/SearchRunner.php b/app/Search/SearchRunner.php index 4b19aceac..0128e3804 100644 --- a/app/Search/SearchRunner.php +++ b/app/Search/SearchRunner.php @@ -11,7 +11,6 @@ use BookStack\Search\Options\TagSearchOption; use BookStack\Users\Models\User; use Illuminate\Database\Connection; use Illuminate\Database\Eloquent\Builder as EloquentBuilder; -use Illuminate\Database\Eloquent\Collection as EloquentCollection; use Illuminate\Database\Query\Builder; use Illuminate\Database\Query\JoinClause; use Illuminate\Support\Collection; @@ -30,17 +29,15 @@ class SearchRunner protected EntityProvider $entityProvider, protected PermissionApplicator $permissions, protected EntityQueries $entityQueries, + protected EntityHydrator $entityHydrator, ) { $this->termAdjustmentCache = new WeakMap(); } /** * Search all entities in the system. - * The provided count is for each entity to search, - * Total returned could be larger and not guaranteed. - * // TODO - Update this comment * - * @return array{total: int, count: int, has_more: bool, results: Collection} + * @return array{total: int, results: Collection} */ public function searchEntities(SearchOptions $searchOpts, string $entityType = 'all', int $page = 1, int $count = 20): array { @@ -58,14 +55,9 @@ class SearchRunner $total = $searchQuery->count(); $results = $this->getPageOfDataFromQuery($searchQuery, $page, $count); - // TODO - Pagination? - $hasMore = ($total > ($page * $count)); - return [ 'total' => $total, - 'count' => count($results), - 'has_more' => $hasMore, - 'results' => $results->sortByDesc('score')->values(), + 'results' => $results->values(), ]; } @@ -79,15 +71,8 @@ class SearchRunner $filterMap = $opts->filters->toValueMap(); $entityTypesToSearch = isset($filterMap['type']) ? explode('|', $filterMap['type']) : $entityTypes; - $results = collect(); - foreach ($entityTypesToSearch as $entityType) { - if (!in_array($entityType, $entityTypes)) { - continue; - } - - $search = $this->buildQuery($opts, $entityType)->where('book_id', '=', $bookId)->take(20)->get(); - $results = $results->merge($search); - } + $filteredTypes = array_intersect($entityTypesToSearch, $entityTypes); + $results = $this->buildQuery($opts, $filteredTypes)->where('book_id', '=', $bookId)->take(20)->get(); return $results->sortByDesc('score')->take(20); } @@ -98,7 +83,7 @@ class SearchRunner public function searchChapter(int $chapterId, string $searchString): Collection { $opts = SearchOptions::fromString($searchString); - $pages = $this->buildQuery($opts, 'page')->where('chapter_id', '=', $chapterId)->take(20)->get(); + $pages = $this->buildQuery($opts, ['page'])->where('chapter_id', '=', $chapterId)->take(20)->get(); return $pages->sortByDesc('score'); } @@ -113,7 +98,7 @@ class SearchRunner ->take($count) ->get(); - $hydrated = (new EntityHydrator($entities->all(), true, true))->hydrate(); + $hydrated = $this->entityHydrator->hydrate($entities->all(), true, true); return collect($hydrated); } diff --git a/resources/views/search/all.blade.php b/resources/views/search/all.blade.php index ad437604b..48250816f 100644 --- a/resources/views/search/all.blade.php +++ b/resources/views/search/all.blade.php @@ -87,11 +87,7 @@ @include('entities.list', ['entities' => $entities, 'showPath' => true, 'showTags' => true]) - @if($hasNextPage) - - @endif + {{ $paginator->render() }}