Skip to content

Commit 3fd25bd

Browse files
committed
Search: Added pagination, updated other search uses
Also updated hydrator to be created via injection.
1 parent f0303de commit 3fd25bd

File tree

5 files changed

+31
-59
lines changed

5 files changed

+31
-59
lines changed

app/Entities/Tools/EntityHydrator.php

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,31 +12,22 @@
1212

1313
class EntityHydrator
1414
{
15-
/**
16-
* @var EntityTable[] $entities
17-
*/
18-
protected array $entities;
19-
20-
protected bool $loadTags = false;
21-
protected bool $loadParents = false;
22-
23-
public function __construct(array $entities, bool $loadTags = false, bool $loadParents = false)
24-
{
25-
$this->entities = $entities;
26-
$this->loadTags = $loadTags;
27-
$this->loadParents = $loadParents;
15+
public function __construct(
16+
protected EntityQueries $entityQueries,
17+
) {
2818
}
2919

3020
/**
3121
* Hydrate the entities of this hydrator to return a list of entities represented
3222
* in their original intended models.
23+
* @param EntityTable[] $entities
3324
* @return Entity[]
3425
*/
35-
public function hydrate(): array
26+
public function hydrate(array $entities, bool $loadTags = false, bool $loadParents = false): array
3627
{
3728
$hydrated = [];
3829

39-
foreach ($this->entities as $entity) {
30+
foreach ($entities as $entity) {
4031
$data = $entity->getRawOriginal();
4132
$instance = Entity::instanceFromType($entity->type);
4233

@@ -49,11 +40,11 @@ public function hydrate(): array
4940
$hydrated[] = $instance;
5041
}
5142

52-
if ($this->loadTags) {
43+
if ($loadTags) {
5344
$this->loadTagsIntoModels($hydrated);
5445
}
5546

56-
if ($this->loadParents) {
47+
if ($loadParents) {
5748
$this->loadParentsIntoModels($hydrated);
5849
}
5950

@@ -115,10 +106,7 @@ protected function loadParentsIntoModels(array $entities): void
115106
}
116107
}
117108

118-
// TODO - Inject in?
119-
$queries = app()->make(EntityQueries::class);
120-
121-
$parentQuery = $queries->visibleForList();
109+
$parentQuery = $this->entityQueries->visibleForList();
122110
$filtered = count($parentsByType['book']) > 0 || count($parentsByType['chapter']) > 0;
123111
$parentQuery = $parentQuery->where(function ($query) use ($parentsByType) {
124112
foreach ($parentsByType as $type => $ids) {
@@ -132,7 +120,7 @@ protected function loadParentsIntoModels(array $entities): void
132120
});
133121

134122
$parentModels = $filtered ? $parentQuery->get()->all() : [];
135-
$parents = (new EntityHydrator($parentModels))->hydrate();
123+
$parents = $this->hydrate($parentModels);
136124
$parentMap = [];
137125
foreach ($parents as $parent) {
138126
$parentMap[$parent->type . ':' . $parent->id] = $parent;

app/Search/SearchApiController.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
<?php
22

3+
declare(strict_types=1);
4+
35
namespace BookStack\Search;
46

57
use BookStack\Api\ApiEntityListFormatter;
68
use BookStack\Entities\Models\Entity;
79
use BookStack\Http\ApiController;
10+
use Illuminate\Http\JsonResponse;
811
use Illuminate\Http\Request;
912

1013
class SearchApiController extends ApiController
@@ -31,11 +34,9 @@ public function __construct(
3134
* between: bookshelf, book, chapter & page.
3235
*
3336
* The paging parameters and response format emulates a standard listing endpoint
34-
* but standard sorting and filtering cannot be done on this endpoint. If a count value
35-
* is provided this will only be taken as a suggestion. The results in the response
36-
* may currently be up to 4x this value.
37+
* but standard sorting and filtering cannot be done on this endpoint.
3738
*/
38-
public function all(Request $request)
39+
public function all(Request $request): JsonResponse
3940
{
4041
$this->validate($request, $this->rules['all']);
4142

app/Search/SearchController.php

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use BookStack\Entities\Tools\SiblingFetcher;
88
use BookStack\Http\Controller;
99
use Illuminate\Http\Request;
10+
use Illuminate\Pagination\LengthAwarePaginator;
1011

1112
class SearchController extends Controller
1213
{
@@ -23,20 +24,21 @@ public function search(Request $request, SearchResultsFormatter $formatter)
2324
{
2425
$searchOpts = SearchOptions::fromRequest($request);
2526
$fullSearchString = $searchOpts->toString();
26-
$this->setPageTitle(trans('entities.search_for_term', ['term' => $fullSearchString]));
27-
2827
$page = intval($request->get('page', '0')) ?: 1;
29-
$nextPageLink = url('/search?term=' . urlencode($fullSearchString) . '&page=' . ($page + 1));
3028

3129
$results = $this->searchRunner->searchEntities($searchOpts, 'all', $page, 20);
3230
$formatter->format($results['results']->all(), $searchOpts);
31+
$paginator = new LengthAwarePaginator($results['results'], $results['total'], 20, $page);
32+
$paginator->setPath('/search');
33+
$paginator->appends($request->except('page'));
34+
35+
$this->setPageTitle(trans('entities.search_for_term', ['term' => $fullSearchString]));
3336

3437
return view('search.all', [
3538
'entities' => $results['results'],
3639
'totalResults' => $results['total'],
40+
'paginator' => $paginator,
3741
'searchTerm' => $fullSearchString,
38-
'hasNextPage' => $results['has_more'],
39-
'nextPageLink' => $nextPageLink,
4042
'options' => $searchOpts,
4143
]);
4244
}
@@ -128,7 +130,7 @@ public function searchSuggestions(Request $request)
128130
}
129131

130132
/**
131-
* Search siblings items in the system.
133+
* Search sibling items in the system.
132134
*/
133135
public function searchSiblings(Request $request, SiblingFetcher $siblingFetcher)
134136
{

app/Search/SearchRunner.php

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
use BookStack\Users\Models\User;
1212
use Illuminate\Database\Connection;
1313
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
14-
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
1514
use Illuminate\Database\Query\Builder;
1615
use Illuminate\Database\Query\JoinClause;
1716
use Illuminate\Support\Collection;
@@ -30,17 +29,15 @@ public function __construct(
3029
protected EntityProvider $entityProvider,
3130
protected PermissionApplicator $permissions,
3231
protected EntityQueries $entityQueries,
32+
protected EntityHydrator $entityHydrator,
3333
) {
3434
$this->termAdjustmentCache = new WeakMap();
3535
}
3636

3737
/**
3838
* Search all entities in the system.
39-
* The provided count is for each entity to search,
40-
* Total returned could be larger and not guaranteed.
41-
* // TODO - Update this comment
4239
*
43-
* @return array{total: int, count: int, has_more: bool, results: Collection<Entity>}
40+
* @return array{total: int, results: Collection<Entity>}
4441
*/
4542
public function searchEntities(SearchOptions $searchOpts, string $entityType = 'all', int $page = 1, int $count = 20): array
4643
{
@@ -58,14 +55,9 @@ public function searchEntities(SearchOptions $searchOpts, string $entityType = '
5855
$total = $searchQuery->count();
5956
$results = $this->getPageOfDataFromQuery($searchQuery, $page, $count);
6057

61-
// TODO - Pagination?
62-
$hasMore = ($total > ($page * $count));
63-
6458
return [
6559
'total' => $total,
66-
'count' => count($results),
67-
'has_more' => $hasMore,
68-
'results' => $results->sortByDesc('score')->values(),
60+
'results' => $results->values(),
6961
];
7062
}
7163

@@ -79,15 +71,8 @@ public function searchBook(int $bookId, string $searchString): Collection
7971
$filterMap = $opts->filters->toValueMap();
8072
$entityTypesToSearch = isset($filterMap['type']) ? explode('|', $filterMap['type']) : $entityTypes;
8173

82-
$results = collect();
83-
foreach ($entityTypesToSearch as $entityType) {
84-
if (!in_array($entityType, $entityTypes)) {
85-
continue;
86-
}
87-
88-
$search = $this->buildQuery($opts, $entityType)->where('book_id', '=', $bookId)->take(20)->get();
89-
$results = $results->merge($search);
90-
}
74+
$filteredTypes = array_intersect($entityTypesToSearch, $entityTypes);
75+
$results = $this->buildQuery($opts, $filteredTypes)->where('book_id', '=', $bookId)->take(20)->get();
9176

9277
return $results->sortByDesc('score')->take(20);
9378
}
@@ -98,7 +83,7 @@ public function searchBook(int $bookId, string $searchString): Collection
9883
public function searchChapter(int $chapterId, string $searchString): Collection
9984
{
10085
$opts = SearchOptions::fromString($searchString);
101-
$pages = $this->buildQuery($opts, 'page')->where('chapter_id', '=', $chapterId)->take(20)->get();
86+
$pages = $this->buildQuery($opts, ['page'])->where('chapter_id', '=', $chapterId)->take(20)->get();
10287

10388
return $pages->sortByDesc('score');
10489
}
@@ -113,7 +98,7 @@ protected function getPageOfDataFromQuery(EloquentBuilder $query, int $page, int
11398
->take($count)
11499
->get();
115100

116-
$hydrated = (new EntityHydrator($entities->all(), true, true))->hydrate();
101+
$hydrated = $this->entityHydrator->hydrate($entities->all(), true, true);
117102

118103
return collect($hydrated);
119104
}

resources/views/search/all.blade.php

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,7 @@
8787
@include('entities.list', ['entities' => $entities, 'showPath' => true, 'showTags' => true])
8888
</div>
8989

90-
@if($hasNextPage)
91-
<div class="text-right mt-m">
92-
<a href="{{ $nextPageLink }}" class="button outline">{{ trans('entities.search_more') }}</a>
93-
</div>
94-
@endif
90+
{{ $paginator->render() }}
9591
</div>
9692
</div>
9793
</div>

0 commit comments

Comments
 (0)