Skip to content

Commit 751934c

Browse files
committed
Search: Tested changes to single-table search
Updated filters to use single table where needed.
1 parent 3fd25bd commit 751934c

File tree

6 files changed

+61
-11
lines changed

6 files changed

+61
-11
lines changed

app/Entities/Models/EntityTable.php

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@
22

33
namespace BookStack\Entities\Models;
44

5+
use BookStack\Activity\Models\Tag;
6+
use BookStack\Activity\Models\View;
57
use BookStack\App\Model;
8+
use BookStack\Permissions\Models\EntityPermission;
69
use BookStack\Permissions\Models\JointPermission;
710
use BookStack\Permissions\PermissionApplicator;
811
use Illuminate\Database\Eloquent\Builder;
912
use Illuminate\Database\Eloquent\Relations\HasMany;
13+
use Illuminate\Database\Eloquent\Relations\MorphMany;
1014
use Illuminate\Database\Eloquent\SoftDeletes;
1115

1216
/**
@@ -32,6 +36,34 @@ public function scopeVisible(Builder $query): Builder
3236
*/
3337
public function jointPermissions(): HasMany
3438
{
35-
return $this->hasMany(JointPermission::class, 'entity_id')->whereColumn('entity_type', '=', 'entities.type');
39+
return $this->hasMany(JointPermission::class, 'entity_id')
40+
->whereColumn('entity_type', '=', 'entities.type');
41+
}
42+
43+
/**
44+
* Get the Tags that have been assigned to entities.
45+
*/
46+
public function tags(): HasMany
47+
{
48+
return $this->hasMany(Tag::class, 'entity_id')
49+
->whereColumn('entity_type', '=', 'entities.type');
50+
}
51+
52+
/**
53+
* Get the assigned permissions.
54+
*/
55+
public function permissions(): HasMany
56+
{
57+
return $this->hasMany(EntityPermission::class, 'entity_id')
58+
->whereColumn('entity_type', '=', 'entities.type');
59+
}
60+
61+
/**
62+
* Get View objects for this entity.
63+
*/
64+
public function views(): HasMany
65+
{
66+
return $this->hasMany(View::class, 'viewable_id')
67+
->whereColumn('viewable_type', '=', 'entities.type');
3668
}
3769
}

app/Entities/Queries/EntityQueries.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use BookStack\Entities\Models\Entity;
66
use BookStack\Entities\Models\EntityTable;
77
use Illuminate\Database\Eloquent\Builder;
8+
use Illuminate\Database\Query\Builder as QueryBuilder;
89
use Illuminate\Database\Query\JoinClause;
910
use Illuminate\Support\Facades\DB;
1011
use InvalidArgumentException;
@@ -43,8 +44,14 @@ public function findVisibleByStringIdentifier(string $identifier): ?Entity
4344
public function visibleForList(): Builder
4445
{
4546
$rawDescriptionField = DB::raw('COALESCE(description, text) as description');
47+
$bookSlugSelect = function (QueryBuilder $query) {
48+
return $query->select('slug')->from('entities as books')
49+
->whereColumn('books.id', '=', 'entities.book_id')
50+
->where('type', '=', 'book');
51+
};
52+
4653
return EntityTable::query()->scopes('visible')
47-
->select(['id', 'type', 'name', 'slug', 'book_id', 'chapter_id', 'created_at', 'updated_at', 'draft', $rawDescriptionField])
54+
->select(['id', 'type', 'name', 'slug', 'book_id', 'chapter_id', 'created_at', 'updated_at', 'draft', 'book_slug' => $bookSlugSelect, $rawDescriptionField])
4855
->leftJoin('entity_container_data', function (JoinClause $join) {
4956
$join->on('entity_container_data.entity_id', '=', 'entities.id')
5057
->on('entity_container_data.entity_type', '=', 'entities.type');

app/Entities/Tools/EntityHydrator.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,11 @@ protected function loadParentsIntoModels(array $entities): void
129129
foreach ($entities as $entity) {
130130
if ($entity instanceof Page || $entity instanceof Chapter) {
131131
$key = 'book:' . $entity->getRawAttribute('book_id');
132-
$entity->setAttribute('book', $parentMap[$key] ?? null);
132+
$entity->setRelation('book', $parentMap[$key] ?? null);
133133
}
134134
if ($entity instanceof Page) {
135135
$key = 'chapter:' . $entity->getRawAttribute('chapter_id');
136-
$entity->setAttribute('chapter', $parentMap[$key] ?? null);
136+
$entity->setRelation('chapter', $parentMap[$key] ?? null);
137137
}
138138
}
139139
}

app/Search/SearchRunner.php

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,9 @@ public function searchBook(int $bookId, string $searchString): Collection
7272
$entityTypesToSearch = isset($filterMap['type']) ? explode('|', $filterMap['type']) : $entityTypes;
7373

7474
$filteredTypes = array_intersect($entityTypesToSearch, $entityTypes);
75-
$results = $this->buildQuery($opts, $filteredTypes)->where('book_id', '=', $bookId)->take(20)->get();
75+
$query = $this->buildQuery($opts, $filteredTypes)->where('book_id', '=', $bookId);
7676

77-
return $results->sortByDesc('score')->take(20);
77+
return $this->getPageOfDataFromQuery($query, 1, 20)->sortByDesc('score');
7878
}
7979

8080
/**
@@ -83,9 +83,9 @@ public function searchBook(int $bookId, string $searchString): Collection
8383
public function searchChapter(int $chapterId, string $searchString): Collection
8484
{
8585
$opts = SearchOptions::fromString($searchString);
86-
$pages = $this->buildQuery($opts, ['page'])->where('chapter_id', '=', $chapterId)->take(20)->get();
86+
$query = $this->buildQuery($opts, ['page'])->where('chapter_id', '=', $chapterId);
8787

88-
return $pages->sortByDesc('score');
88+
return $this->getPageOfDataFromQuery($query, 1, 20)->sortByDesc('score');
8989
}
9090

9191
/**
@@ -120,7 +120,8 @@ protected function buildQuery(SearchOptions $searchOpts, array $entityTypes): El
120120
$filter = function (EloquentBuilder $query) use ($exact) {
121121
$inputTerm = str_replace('\\', '\\\\', $exact->value);
122122
$query->where('name', 'like', '%' . $inputTerm . '%')
123-
->orWhere('description', 'like', '%' . $inputTerm . '%');
123+
->orWhere('description', 'like', '%' . $inputTerm . '%')
124+
->orWhere('text', 'like', '%' . $inputTerm . '%');
124125
};
125126

126127
$exact->negated ? $entityQuery->whereNot($filter) : $entityQuery->where($filter);
@@ -301,7 +302,7 @@ protected function applyTagSearch(EloquentBuilder $query, TagSearchOption $optio
301302
$option->negated ? $query->whereDoesntHave('tags', $filter) : $query->whereHas('tags', $filter);
302303
}
303304

304-
protected function applyNegatableWhere(EloquentBuilder $query, bool $negated, string $column, string $operator, mixed $value): void
305+
protected function applyNegatableWhere(EloquentBuilder $query, bool $negated, string|callable $column, string|null $operator, mixed $value): void
305306
{
306307
if ($negated) {
307308
$query->whereNot($column, $operator, $value);
@@ -376,7 +377,10 @@ protected function filterInTitle(EloquentBuilder $query, string $input, bool $ne
376377

377378
protected function filterInBody(EloquentBuilder $query, string $input, bool $negated)
378379
{
379-
$this->applyNegatableWhere($query, $negated, 'description', 'like', '%' . $input . '%');
380+
$this->applyNegatableWhere($query, $negated, function (EloquentBuilder $query) use ($input) {
381+
$query->where('description', 'like', '%' . $input . '%')
382+
->orWhere('text', 'like', '%' . $input . '%');
383+
}, null, null);
380384
}
381385

382386
protected function filterIsRestricted(EloquentBuilder $query, string $input, bool $negated)

tests/Api/SearchApiTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ public function test_all_endpoint_includes_parent_details_where_visible()
113113
$this->permissions->disableEntityInheritedPermissions($book);
114114

115115
$resp = $this->getJson($this->baseEndpoint . '?query=superextrauniquevalue');
116+
$resp->assertOk();
116117
$resp->assertJsonPath('data.0.id', $page->id);
117118
$resp->assertJsonMissingPath('data.0.book.name');
118119
}

tests/Search/EntitySearchTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ public function test_bookshelf_search()
2727
$search->assertSeeText($shelf->name, true);
2828
}
2929

30+
public function test_search_shows_pagination()
31+
{
32+
$search = $this->asEditor()->get('/search?term=a');
33+
$this->withHtml($search)->assertLinkExists('/search?term=a&page=2', '2');
34+
}
35+
3036
public function test_invalid_page_search()
3137
{
3238
$resp = $this->asEditor()->get('/search?term=' . urlencode('<p>test</p>'));

0 commit comments

Comments
 (0)