Also updated hydrator to be created via injection.
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);
$hydrated[] = $instance;
}
- if ($this->loadTags) {
+ if ($loadTags) {
$this->loadTagsIntoModels($hydrated);
}
- if ($this->loadParents) {
+ if ($loadParents) {
$this->loadParentsIntoModels($hydrated);
}
}
}
- // 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) {
});
$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;
<?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
* 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']);
use BookStack\Entities\Tools\SiblingFetcher;
use BookStack\Http\Controller;
use Illuminate\Http\Request;
+use Illuminate\Pagination\LengthAwarePaginator;
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,
]);
}
}
/**
- * Search siblings items in the system.
+ * Search sibling items in the system.
*/
public function searchSiblings(Request $request, SiblingFetcher $siblingFetcher)
{
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;
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
{
$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(),
];
}
$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);
}
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');
}
->take($count)
->get();
- $hydrated = (new EntityHydrator($entities->all(), true, true))->hydrate();
+ $hydrated = $this->entityHydrator->hydrate($entities->all(), true, true);
return collect($hydrated);
}
@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>