Skip to content

Commit 8e78b4c

Browse files
committed
Queries: Extracted chapter repo queries to class
Updated query classes to align to interface for common aligned operations. Extracted repeated string-identifier-based finding from page/chapter repos to shared higher-level entity queries.
1 parent 3886aed commit 8e78b4c

File tree

12 files changed

+160
-102
lines changed

12 files changed

+160
-102
lines changed

app/Entities/Controllers/ChapterController.php

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
use BookStack\Activity\Models\View;
66
use BookStack\Activity\Tools\UserEntityWatchOptions;
77
use BookStack\Entities\Models\Book;
8+
use BookStack\Entities\Queries\ChapterQueries;
9+
use BookStack\Entities\Queries\EntityQueries;
810
use BookStack\Entities\Repos\ChapterRepo;
911
use BookStack\Entities\Tools\BookContents;
1012
use BookStack\Entities\Tools\Cloner;
@@ -24,7 +26,9 @@ class ChapterController extends Controller
2426
{
2527
public function __construct(
2628
protected ChapterRepo $chapterRepo,
27-
protected ReferenceFetcher $referenceFetcher
29+
protected ChapterQueries $queries,
30+
protected EntityQueries $entityQueries,
31+
protected ReferenceFetcher $referenceFetcher,
2832
) {
2933
}
3034

@@ -33,12 +37,15 @@ public function __construct(
3337
*/
3438
public function create(string $bookSlug)
3539
{
36-
$book = Book::visible()->where('slug', '=', $bookSlug)->firstOrFail();
40+
$book = $this->entityQueries->books->findVisibleBySlug($bookSlug);
3741
$this->checkOwnablePermission('chapter-create', $book);
3842

3943
$this->setPageTitle(trans('entities.chapters_create'));
4044

41-
return view('chapters.create', ['book' => $book, 'current' => $book]);
45+
return view('chapters.create', [
46+
'book' => $book,
47+
'current' => $book,
48+
]);
4249
}
4350

4451
/**
@@ -55,7 +62,7 @@ public function store(Request $request, string $bookSlug)
5562
'default_template_id' => ['nullable', 'integer'],
5663
]);
5764

58-
$book = Book::visible()->where('slug', '=', $bookSlug)->firstOrFail();
65+
$book = $this->entityQueries->books->findVisibleBySlug($bookSlug);
5966
$this->checkOwnablePermission('chapter-create', $book);
6067

6168
$chapter = $this->chapterRepo->create($validated, $book);
@@ -68,7 +75,7 @@ public function store(Request $request, string $bookSlug)
6875
*/
6976
public function show(string $bookSlug, string $chapterSlug)
7077
{
71-
$chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
78+
$chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug);
7279
$this->checkOwnablePermission('chapter-view', $chapter);
7380

7481
$sidebarTree = (new BookContents($chapter->book))->getTree();
@@ -96,7 +103,7 @@ public function show(string $bookSlug, string $chapterSlug)
96103
*/
97104
public function edit(string $bookSlug, string $chapterSlug)
98105
{
99-
$chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
106+
$chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug);
100107
$this->checkOwnablePermission('chapter-update', $chapter);
101108

102109
$this->setPageTitle(trans('entities.chapters_edit_named', ['chapterName' => $chapter->getShortName()]));
@@ -118,7 +125,7 @@ public function update(Request $request, string $bookSlug, string $chapterSlug)
118125
'default_template_id' => ['nullable', 'integer'],
119126
]);
120127

121-
$chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
128+
$chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug);
122129
$this->checkOwnablePermission('chapter-update', $chapter);
123130

124131
$this->chapterRepo->update($chapter, $validated);
@@ -133,7 +140,7 @@ public function update(Request $request, string $bookSlug, string $chapterSlug)
133140
*/
134141
public function showDelete(string $bookSlug, string $chapterSlug)
135142
{
136-
$chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
143+
$chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug);
137144
$this->checkOwnablePermission('chapter-delete', $chapter);
138145

139146
$this->setPageTitle(trans('entities.chapters_delete_named', ['chapterName' => $chapter->getShortName()]));
@@ -149,7 +156,7 @@ public function showDelete(string $bookSlug, string $chapterSlug)
149156
*/
150157
public function destroy(string $bookSlug, string $chapterSlug)
151158
{
152-
$chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
159+
$chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug);
153160
$this->checkOwnablePermission('chapter-delete', $chapter);
154161

155162
$this->chapterRepo->destroy($chapter);
@@ -164,7 +171,7 @@ public function destroy(string $bookSlug, string $chapterSlug)
164171
*/
165172
public function showMove(string $bookSlug, string $chapterSlug)
166173
{
167-
$chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
174+
$chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug);
168175
$this->setPageTitle(trans('entities.chapters_move_named', ['chapterName' => $chapter->getShortName()]));
169176
$this->checkOwnablePermission('chapter-update', $chapter);
170177
$this->checkOwnablePermission('chapter-delete', $chapter);
@@ -182,7 +189,7 @@ public function showMove(string $bookSlug, string $chapterSlug)
182189
*/
183190
public function move(Request $request, string $bookSlug, string $chapterSlug)
184191
{
185-
$chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
192+
$chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug);
186193
$this->checkOwnablePermission('chapter-update', $chapter);
187194
$this->checkOwnablePermission('chapter-delete', $chapter);
188195

@@ -211,7 +218,7 @@ public function move(Request $request, string $bookSlug, string $chapterSlug)
211218
*/
212219
public function showCopy(string $bookSlug, string $chapterSlug)
213220
{
214-
$chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
221+
$chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug);
215222
$this->checkOwnablePermission('chapter-view', $chapter);
216223

217224
session()->flashInput(['name' => $chapter->name]);
@@ -230,13 +237,13 @@ public function showCopy(string $bookSlug, string $chapterSlug)
230237
*/
231238
public function copy(Request $request, Cloner $cloner, string $bookSlug, string $chapterSlug)
232239
{
233-
$chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
240+
$chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug);
234241
$this->checkOwnablePermission('chapter-view', $chapter);
235242

236243
$entitySelection = $request->get('entity_selection') ?: null;
237-
$newParentBook = $entitySelection ? $this->chapterRepo->findParentByIdentifier($entitySelection) : $chapter->getParent();
244+
$newParentBook = $entitySelection ? $this->entityQueries->findVisibleByStringIdentifier($entitySelection) : $chapter->getParent();
238245

239-
if (is_null($newParentBook)) {
246+
if (!$newParentBook instanceof Book) {
240247
$this->showErrorNotification(trans('errors.selected_book_not_found'));
241248

242249
return redirect($chapter->getUrl('/copy'));
@@ -256,7 +263,7 @@ public function copy(Request $request, Cloner $cloner, string $bookSlug, string
256263
*/
257264
public function convertToBook(HierarchyTransformer $transformer, string $bookSlug, string $chapterSlug)
258265
{
259-
$chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
266+
$chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug);
260267
$this->checkOwnablePermission('chapter-update', $chapter);
261268
$this->checkOwnablePermission('chapter-delete', $chapter);
262269
$this->checkPermission('book-create-all');

app/Entities/Controllers/ChapterExportController.php

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,18 @@
22

33
namespace BookStack\Entities\Controllers;
44

5-
use BookStack\Entities\Repos\ChapterRepo;
5+
use BookStack\Entities\Queries\ChapterQueries;
66
use BookStack\Entities\Tools\ExportFormatter;
77
use BookStack\Exceptions\NotFoundException;
88
use BookStack\Http\Controller;
99
use Throwable;
1010

1111
class ChapterExportController extends Controller
1212
{
13-
protected $chapterRepo;
14-
protected $exportFormatter;
15-
16-
/**
17-
* ChapterExportController constructor.
18-
*/
19-
public function __construct(ChapterRepo $chapterRepo, ExportFormatter $exportFormatter)
20-
{
21-
$this->chapterRepo = $chapterRepo;
22-
$this->exportFormatter = $exportFormatter;
13+
public function __construct(
14+
protected ChapterQueries $queries,
15+
protected ExportFormatter $exportFormatter,
16+
) {
2317
$this->middleware('can:content-export');
2418
}
2519

@@ -31,7 +25,7 @@ public function __construct(ChapterRepo $chapterRepo, ExportFormatter $exportFor
3125
*/
3226
public function pdf(string $bookSlug, string $chapterSlug)
3327
{
34-
$chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
28+
$chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug);
3529
$pdfContent = $this->exportFormatter->chapterToPdf($chapter);
3630

3731
return $this->download()->directly($pdfContent, $chapterSlug . '.pdf');
@@ -45,7 +39,7 @@ public function pdf(string $bookSlug, string $chapterSlug)
4539
*/
4640
public function html(string $bookSlug, string $chapterSlug)
4741
{
48-
$chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
42+
$chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug);
4943
$containedHtml = $this->exportFormatter->chapterToContainedHtml($chapter);
5044

5145
return $this->download()->directly($containedHtml, $chapterSlug . '.html');
@@ -58,7 +52,7 @@ public function html(string $bookSlug, string $chapterSlug)
5852
*/
5953
public function plainText(string $bookSlug, string $chapterSlug)
6054
{
61-
$chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
55+
$chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug);
6256
$chapterText = $this->exportFormatter->chapterToPlainText($chapter);
6357

6458
return $this->download()->directly($chapterText, $chapterSlug . '.txt');
@@ -71,7 +65,7 @@ public function plainText(string $bookSlug, string $chapterSlug)
7165
*/
7266
public function markdown(string $bookSlug, string $chapterSlug)
7367
{
74-
$chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
68+
$chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug);
7569
$chapterText = $this->exportFormatter->chapterToMarkdown($chapter);
7670

7771
return $this->download()->directly($chapterText, $chapterSlug . '.md');

app/Entities/Controllers/PageController.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
use BookStack\Entities\Models\Book;
99
use BookStack\Entities\Models\Chapter;
1010
use BookStack\Entities\Models\Page;
11+
use BookStack\Entities\Queries\EntityQueries;
12+
use BookStack\Entities\Queries\PageQueries;
1113
use BookStack\Entities\Repos\PageRepo;
1214
use BookStack\Entities\Tools\BookContents;
1315
use BookStack\Entities\Tools\Cloner;
@@ -29,6 +31,8 @@ class PageController extends Controller
2931
{
3032
public function __construct(
3133
protected PageRepo $pageRepo,
34+
protected PageQueries $pageQueries,
35+
protected EntityQueries $entityQueries,
3236
protected ReferenceFetcher $referenceFetcher
3337
) {
3438
}
@@ -435,9 +439,9 @@ public function copy(Request $request, Cloner $cloner, string $bookSlug, string
435439
$this->checkOwnablePermission('page-view', $page);
436440

437441
$entitySelection = $request->get('entity_selection') ?: null;
438-
$newParent = $entitySelection ? $this->pageRepo->findParentByIdentifier($entitySelection) : $page->getParent();
442+
$newParent = $entitySelection ? $this->entityQueries->findVisibleByStringIdentifier($entitySelection) : $page->getParent();
439443

440-
if (is_null($newParent)) {
444+
if (!$newParent instanceof Book && !$newParent instanceof Chapter) {
441445
$this->showErrorNotification(trans('errors.selected_book_chapter_not_found'));
442446

443447
return redirect($page->getUrl('/copy'));

app/Entities/Models/Chapter.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
* Class Chapter.
1212
*
1313
* @property Collection<Page> $pages
14-
* @property string $description
1514
* @property ?int $default_template_id
1615
* @property ?Page $defaultTemplate
1716
*/

app/Entities/Queries/BookQueries.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,18 @@
66
use BookStack\Exceptions\NotFoundException;
77
use Illuminate\Database\Eloquent\Builder;
88

9-
class BookQueries
9+
class BookQueries implements ProvidesEntityQueries
1010
{
1111
public function start(): Builder
1212
{
1313
return Book::query();
1414
}
1515

16+
public function findVisibleById(int $id): ?Book
17+
{
18+
return $this->start()->scopes('visible')->find($id);
19+
}
20+
1621
public function findVisibleBySlug(string $slug): Book
1722
{
1823
/** @var ?Book $book */

app/Entities/Queries/BookshelfQueries.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,18 @@
66
use BookStack\Exceptions\NotFoundException;
77
use Illuminate\Database\Eloquent\Builder;
88

9-
class BookshelfQueries
9+
class BookshelfQueries implements ProvidesEntityQueries
1010
{
1111
public function start(): Builder
1212
{
1313
return Bookshelf::query();
1414
}
1515

16+
public function findVisibleById(int $id): ?Bookshelf
17+
{
18+
return $this->start()->scopes('visible')->find($id);
19+
}
20+
1621
public function findVisibleBySlug(string $slug): Bookshelf
1722
{
1823
/** @var ?Bookshelf $shelf */
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
namespace BookStack\Entities\Queries;
4+
5+
use BookStack\Entities\Models\Chapter;
6+
use BookStack\Exceptions\NotFoundException;
7+
use Illuminate\Database\Eloquent\Builder;
8+
9+
class ChapterQueries implements ProvidesEntityQueries
10+
{
11+
protected static array $listAttributes = [
12+
'id', 'slug', 'name', 'description', 'priority',
13+
'created_at', 'updated_at',
14+
'created_by', 'updated_by', 'owned_by',
15+
];
16+
17+
public function start(): Builder
18+
{
19+
return Chapter::query();
20+
}
21+
22+
public function findVisibleById(int $id): ?Chapter
23+
{
24+
return $this->start()->scopes('visible')->find($id);
25+
}
26+
27+
public function findVisibleBySlugs(string $bookSlug, string $chapterSlug): Chapter
28+
{
29+
/** @var ?Chapter $chapter */
30+
$chapter = $this->start()->with('book')
31+
->whereHas('book', function (Builder $query) use ($bookSlug) {
32+
$query->where('slug', '=', $bookSlug);
33+
})
34+
->where('slug', '=', $chapterSlug)
35+
->first();
36+
37+
if ($chapter === null) {
38+
throw new NotFoundException(trans('errors.chapter_not_found'));
39+
}
40+
41+
return $chapter;
42+
}
43+
44+
public function visibleForList(): Builder
45+
{
46+
return $this->start()
47+
->select(array_merge(static::$listAttributes, ['book_slug' => function ($builder) {
48+
$builder->select('slug')
49+
->from('books')
50+
->whereColumn('books.id', '=', 'chapters.book_id');
51+
}]));
52+
}
53+
}

app/Entities/Queries/EntityQueries.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,42 @@
22

33
namespace BookStack\Entities\Queries;
44

5+
use BookStack\Entities\Models\Entity;
6+
57
class EntityQueries
68
{
79
public function __construct(
810
public BookshelfQueries $shelves,
911
public BookQueries $books,
12+
public ChapterQueries $chapters,
1013
public PageQueries $pages,
1114
) {
1215
}
16+
17+
/**
18+
* Find an entity via an identifier string in the format:
19+
* {type}:{id}
20+
* Example: (book:5).
21+
*/
22+
public function findVisibleByStringIdentifier(string $identifier): ?Entity
23+
{
24+
$explodedId = explode(':', $identifier);
25+
$entityType = $explodedId[0];
26+
$entityId = intval($explodedId[1]);
27+
28+
/** @var ?ProvidesEntityQueries $queries */
29+
$queries = match ($entityType) {
30+
'page' => $this->pages,
31+
'chapter' => $this->chapters,
32+
'book' => $this->books,
33+
'bookshelf' => $this->shelves,
34+
default => null,
35+
};
36+
37+
if (is_null($queries)) {
38+
return null;
39+
}
40+
41+
return $queries->findVisibleById($entityId);
42+
}
1343
}

0 commit comments

Comments
 (0)