mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-11-04 13:31:45 +03:00
Search: Added pagination, updated other search uses
Also updated hydrator to be created via injection.
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BookStack\Search;
|
||||
|
||||
use BookStack\Api\ApiEntityListFormatter;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Http\ApiController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class SearchApiController extends ApiController
|
||||
@@ -31,11 +34,9 @@ class SearchApiController extends ApiController
|
||||
* between: bookshelf, book, chapter & page.
|
||||
*
|
||||
* The paging parameters and response format emulates a standard listing endpoint
|
||||
* but standard sorting and filtering cannot be done on this endpoint. If a count value
|
||||
* is provided this will only be taken as a suggestion. The results in the response
|
||||
* may currently be up to 4x this value.
|
||||
* but standard sorting and filtering cannot be done on this endpoint.
|
||||
*/
|
||||
public function all(Request $request)
|
||||
public function all(Request $request): JsonResponse
|
||||
{
|
||||
$this->validate($request, $this->rules['all']);
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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<Entity>}
|
||||
* @return array{total: int, results: Collection<Entity>}
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -87,11 +87,7 @@
|
||||
@include('entities.list', ['entities' => $entities, 'showPath' => true, 'showTags' => true])
|
||||
</div>
|
||||
|
||||
@if($hasNextPage)
|
||||
<div class="text-right mt-m">
|
||||
<a href="{{ $nextPageLink }}" class="button outline">{{ trans('entities.search_more') }}</a>
|
||||
</div>
|
||||
@endif
|
||||
{{ $paginator->render() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user