Queries: Updated all app book static query uses

This commit is contained in:
Dan Brown 2024-02-07 16:37:36 +00:00
parent c95f4ca40f
commit 483410749b
No known key found for this signature in database
GPG key ID: 46D9F943C24A2EF9
37 changed files with 278 additions and 162 deletions

View file

@ -40,7 +40,7 @@ class HomeController extends Controller
$recentFactor = count($draftPages) > 0 ? 0.5 : 1; $recentFactor = count($draftPages) > 0 ? 0.5 : 1;
$recents = $this->isSignedIn() ? $recents = $this->isSignedIn() ?
(new RecentlyViewed())->run(12 * $recentFactor, 1) (new RecentlyViewed())->run(12 * $recentFactor, 1)
: Book::visible()->orderBy('created_at', 'desc')->take(12 * $recentFactor)->get(); : $this->queries->books->visibleForList()->orderBy('created_at', 'desc')->take(12 * $recentFactor)->get();
$favourites = (new TopFavourites())->run(6); $favourites = (new TopFavourites())->run(6);
$recentlyUpdatedPages = $this->queries->pages->visibleForList() $recentlyUpdatedPages = $this->queries->pages->visibleForList()
->where('draft', false) ->where('draft', false)

View file

@ -6,6 +6,7 @@ use BookStack\Api\ApiEntityListFormatter;
use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\Entity;
use BookStack\Entities\Queries\BookQueries;
use BookStack\Entities\Repos\BookRepo; use BookStack\Entities\Repos\BookRepo;
use BookStack\Entities\Tools\BookContents; use BookStack\Entities\Tools\BookContents;
use BookStack\Http\ApiController; use BookStack\Http\ApiController;
@ -15,7 +16,8 @@ use Illuminate\Validation\ValidationException;
class BookApiController extends ApiController class BookApiController extends ApiController
{ {
public function __construct( public function __construct(
protected BookRepo $bookRepo protected BookRepo $bookRepo,
protected BookQueries $queries,
) { ) {
} }
@ -24,7 +26,7 @@ class BookApiController extends ApiController
*/ */
public function list() public function list()
{ {
$books = Book::visible(); $books = $this->queries->visibleForList();
return $this->apiListingResponse($books, [ return $this->apiListingResponse($books, [
'id', 'name', 'slug', 'description', 'created_at', 'updated_at', 'created_by', 'updated_by', 'owned_by', 'id', 'name', 'slug', 'description', 'created_at', 'updated_at', 'created_by', 'updated_by', 'owned_by',
@ -56,7 +58,7 @@ class BookApiController extends ApiController
*/ */
public function read(string $id) public function read(string $id)
{ {
$book = Book::visible()->findOrFail($id); $book = $this->queries->findVisibleByIdOrFail(intval($id));
$book = $this->forJsonDisplay($book); $book = $this->forJsonDisplay($book);
$book->load(['createdBy', 'updatedBy', 'ownedBy']); $book->load(['createdBy', 'updatedBy', 'ownedBy']);
@ -83,7 +85,7 @@ class BookApiController extends ApiController
*/ */
public function update(Request $request, string $id) public function update(Request $request, string $id)
{ {
$book = Book::visible()->findOrFail($id); $book = $this->queries->findVisibleByIdOrFail(intval($id));
$this->checkOwnablePermission('book-update', $book); $this->checkOwnablePermission('book-update', $book);
$requestData = $this->validate($request, $this->rules()['update']); $requestData = $this->validate($request, $this->rules()['update']);
@ -100,7 +102,7 @@ class BookApiController extends ApiController
*/ */
public function delete(string $id) public function delete(string $id)
{ {
$book = Book::visible()->findOrFail($id); $book = $this->queries->findVisibleByIdOrFail(intval($id));
$this->checkOwnablePermission('book-delete', $book); $this->checkOwnablePermission('book-delete', $book);
$this->bookRepo->destroy($book); $this->bookRepo->destroy($book);

View file

@ -2,18 +2,17 @@
namespace BookStack\Entities\Controllers; namespace BookStack\Entities\Controllers;
use BookStack\Entities\Models\Book; use BookStack\Entities\Queries\BookQueries;
use BookStack\Entities\Tools\ExportFormatter; use BookStack\Entities\Tools\ExportFormatter;
use BookStack\Http\ApiController; use BookStack\Http\ApiController;
use Throwable; use Throwable;
class BookExportApiController extends ApiController class BookExportApiController extends ApiController
{ {
protected $exportFormatter; public function __construct(
protected ExportFormatter $exportFormatter,
public function __construct(ExportFormatter $exportFormatter) protected BookQueries $queries,
{ ) {
$this->exportFormatter = $exportFormatter;
$this->middleware('can:content-export'); $this->middleware('can:content-export');
} }
@ -24,7 +23,7 @@ class BookExportApiController extends ApiController
*/ */
public function exportPdf(int $id) public function exportPdf(int $id)
{ {
$book = Book::visible()->findOrFail($id); $book = $this->queries->findVisibleByIdOrFail($id);
$pdfContent = $this->exportFormatter->bookToPdf($book); $pdfContent = $this->exportFormatter->bookToPdf($book);
return $this->download()->directly($pdfContent, $book->slug . '.pdf'); return $this->download()->directly($pdfContent, $book->slug . '.pdf');
@ -37,7 +36,7 @@ class BookExportApiController extends ApiController
*/ */
public function exportHtml(int $id) public function exportHtml(int $id)
{ {
$book = Book::visible()->findOrFail($id); $book = $this->queries->findVisibleByIdOrFail($id);
$htmlContent = $this->exportFormatter->bookToContainedHtml($book); $htmlContent = $this->exportFormatter->bookToContainedHtml($book);
return $this->download()->directly($htmlContent, $book->slug . '.html'); return $this->download()->directly($htmlContent, $book->slug . '.html');
@ -48,7 +47,7 @@ class BookExportApiController extends ApiController
*/ */
public function exportPlainText(int $id) public function exportPlainText(int $id)
{ {
$book = Book::visible()->findOrFail($id); $book = $this->queries->findVisibleByIdOrFail($id);
$textContent = $this->exportFormatter->bookToPlainText($book); $textContent = $this->exportFormatter->bookToPlainText($book);
return $this->download()->directly($textContent, $book->slug . '.txt'); return $this->download()->directly($textContent, $book->slug . '.txt');
@ -59,7 +58,7 @@ class BookExportApiController extends ApiController
*/ */
public function exportMarkdown(int $id) public function exportMarkdown(int $id)
{ {
$book = Book::visible()->findOrFail($id); $book = $this->queries->findVisibleByIdOrFail($id);
$markdown = $this->exportFormatter->bookToMarkdown($book); $markdown = $this->exportFormatter->bookToMarkdown($book);
return $this->download()->directly($markdown, $book->slug . '.md'); return $this->download()->directly($markdown, $book->slug . '.md');

View file

@ -4,7 +4,7 @@ namespace BookStack\Entities\Controllers;
use BookStack\Activity\ActivityQueries; use BookStack\Activity\ActivityQueries;
use BookStack\Activity\Models\View; use BookStack\Activity\Models\View;
use BookStack\Entities\Models\Book; use BookStack\Entities\Queries\BookQueries;
use BookStack\Entities\Queries\BookshelfQueries; use BookStack\Entities\Queries\BookshelfQueries;
use BookStack\Entities\Repos\BookshelfRepo; use BookStack\Entities\Repos\BookshelfRepo;
use BookStack\Entities\Tools\ShelfContext; use BookStack\Entities\Tools\ShelfContext;
@ -22,6 +22,7 @@ class BookshelfController extends Controller
public function __construct( public function __construct(
protected BookshelfRepo $shelfRepo, protected BookshelfRepo $shelfRepo,
protected BookshelfQueries $queries, protected BookshelfQueries $queries,
protected BookQueries $bookQueries,
protected ShelfContext $shelfContext, protected ShelfContext $shelfContext,
protected ReferenceFetcher $referenceFetcher, protected ReferenceFetcher $referenceFetcher,
) { ) {
@ -68,7 +69,7 @@ class BookshelfController extends Controller
public function create() public function create()
{ {
$this->checkPermission('bookshelf-create-all'); $this->checkPermission('bookshelf-create-all');
$books = Book::visible()->orderBy('name')->get(['name', 'id', 'slug', 'created_at', 'updated_at']); $books = $this->bookQueries->visibleForList()->orderBy('name')->get(['name', 'id', 'slug', 'created_at', 'updated_at']);
$this->setPageTitle(trans('entities.shelves_create')); $this->setPageTitle(trans('entities.shelves_create'));
return view('shelves.create', ['books' => $books]); return view('shelves.create', ['books' => $books]);
@ -145,7 +146,10 @@ class BookshelfController extends Controller
$this->checkOwnablePermission('bookshelf-update', $shelf); $this->checkOwnablePermission('bookshelf-update', $shelf);
$shelfBookIds = $shelf->books()->get(['id'])->pluck('id'); $shelfBookIds = $shelf->books()->get(['id'])->pluck('id');
$books = Book::visible()->whereNotIn('id', $shelfBookIds)->orderBy('name')->get(['name', 'id', 'slug', 'created_at', 'updated_at']); $books = $this->bookQueries->visibleForList()
->whereNotIn('id', $shelfBookIds)
->orderBy('name')
->get(['name', 'id', 'slug', 'created_at', 'updated_at']);
$this->setPageTitle(trans('entities.shelves_edit_named', ['name' => $shelf->getShortName()])); $this->setPageTitle(trans('entities.shelves_edit_named', ['name' => $shelf->getShortName()]));

View file

@ -2,8 +2,9 @@
namespace BookStack\Entities\Controllers; namespace BookStack\Entities\Controllers;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Queries\BookQueries;
use BookStack\Entities\Queries\ChapterQueries;
use BookStack\Entities\Repos\ChapterRepo; use BookStack\Entities\Repos\ChapterRepo;
use BookStack\Exceptions\PermissionsException; use BookStack\Exceptions\PermissionsException;
use BookStack\Http\ApiController; use BookStack\Http\ApiController;
@ -35,7 +36,9 @@ class ChapterApiController extends ApiController
]; ];
public function __construct( public function __construct(
protected ChapterRepo $chapterRepo protected ChapterRepo $chapterRepo,
protected ChapterQueries $queries,
protected BookQueries $bookQueries,
) { ) {
} }
@ -44,7 +47,7 @@ class ChapterApiController extends ApiController
*/ */
public function list() public function list()
{ {
$chapters = Chapter::visible(); $chapters = $this->queries->visibleForList();
return $this->apiListingResponse($chapters, [ return $this->apiListingResponse($chapters, [
'id', 'book_id', 'name', 'slug', 'description', 'priority', 'id', 'book_id', 'name', 'slug', 'description', 'priority',
@ -60,7 +63,7 @@ class ChapterApiController extends ApiController
$requestData = $this->validate($request, $this->rules['create']); $requestData = $this->validate($request, $this->rules['create']);
$bookId = $request->get('book_id'); $bookId = $request->get('book_id');
$book = Book::visible()->findOrFail($bookId); $book = $this->bookQueries->findVisibleByIdOrFail(intval($bookId));
$this->checkOwnablePermission('chapter-create', $book); $this->checkOwnablePermission('chapter-create', $book);
$chapter = $this->chapterRepo->create($requestData, $book); $chapter = $this->chapterRepo->create($requestData, $book);
@ -73,7 +76,7 @@ class ChapterApiController extends ApiController
*/ */
public function read(string $id) public function read(string $id)
{ {
$chapter = Chapter::visible()->findOrFail($id); $chapter = $this->queries->findVisibleByIdOrFail(intval($id));
$chapter = $this->forJsonDisplay($chapter); $chapter = $this->forJsonDisplay($chapter);
$chapter->load([ $chapter->load([
@ -94,7 +97,7 @@ class ChapterApiController extends ApiController
public function update(Request $request, string $id) public function update(Request $request, string $id)
{ {
$requestData = $this->validate($request, $this->rules()['update']); $requestData = $this->validate($request, $this->rules()['update']);
$chapter = Chapter::visible()->findOrFail($id); $chapter = $this->queries->findVisibleByIdOrFail(intval($id));
$this->checkOwnablePermission('chapter-update', $chapter); $this->checkOwnablePermission('chapter-update', $chapter);
if ($request->has('book_id') && $chapter->book_id !== intval($requestData['book_id'])) { if ($request->has('book_id') && $chapter->book_id !== intval($requestData['book_id'])) {
@ -122,7 +125,7 @@ class ChapterApiController extends ApiController
*/ */
public function delete(string $id) public function delete(string $id)
{ {
$chapter = Chapter::visible()->findOrFail($id); $chapter = $this->queries->findVisibleByIdOrFail(intval($id));
$this->checkOwnablePermission('chapter-delete', $chapter); $this->checkOwnablePermission('chapter-delete', $chapter);
$this->chapterRepo->destroy($chapter); $this->chapterRepo->destroy($chapter);

View file

@ -2,9 +2,7 @@
namespace BookStack\Entities\Controllers; namespace BookStack\Entities\Controllers;
use BookStack\Entities\Models\Book; use BookStack\Entities\Queries\EntityQueries;
use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Page;
use BookStack\Entities\Queries\PageQueries; use BookStack\Entities\Queries\PageQueries;
use BookStack\Entities\Repos\PageRepo; use BookStack\Entities\Repos\PageRepo;
use BookStack\Exceptions\PermissionsException; use BookStack\Exceptions\PermissionsException;
@ -38,6 +36,7 @@ class PageApiController extends ApiController
public function __construct( public function __construct(
protected PageRepo $pageRepo, protected PageRepo $pageRepo,
protected PageQueries $queries, protected PageQueries $queries,
protected EntityQueries $entityQueries,
) { ) {
} }
@ -46,7 +45,7 @@ class PageApiController extends ApiController
*/ */
public function list() public function list()
{ {
$pages = Page::visible(); $pages = $this->queries->visibleForList();
return $this->apiListingResponse($pages, [ return $this->apiListingResponse($pages, [
'id', 'book_id', 'chapter_id', 'name', 'slug', 'priority', 'id', 'book_id', 'chapter_id', 'name', 'slug', 'priority',
@ -72,9 +71,9 @@ class PageApiController extends ApiController
$this->validate($request, $this->rules['create']); $this->validate($request, $this->rules['create']);
if ($request->has('chapter_id')) { if ($request->has('chapter_id')) {
$parent = Chapter::visible()->findOrFail($request->get('chapter_id')); $parent = $this->entityQueries->chapters->findVisibleByIdOrFail(intval($request->get('chapter_id')));
} else { } else {
$parent = Book::visible()->findOrFail($request->get('book_id')); $parent = $this->entityQueries->books->findVisibleByIdOrFail(intval($request->get('book_id')));
} }
$this->checkOwnablePermission('page-create', $parent); $this->checkOwnablePermission('page-create', $parent);
@ -120,9 +119,9 @@ class PageApiController extends ApiController
$parent = null; $parent = null;
if ($request->has('chapter_id')) { if ($request->has('chapter_id')) {
$parent = Chapter::visible()->findOrFail($request->get('chapter_id')); $parent = $this->entityQueries->chapters->findVisibleByIdOrFail(intval($request->get('chapter_id')));
} elseif ($request->has('book_id')) { } elseif ($request->has('book_id')) {
$parent = Book::visible()->findOrFail($request->get('book_id')); $parent = $this->entityQueries->books->findVisibleByIdOrFail(intval($request->get('book_id')));
} }
if ($parent && !$parent->matches($page->getParent())) { if ($parent && !$parent->matches($page->getParent())) {

View file

@ -276,8 +276,8 @@ class PageController extends Controller
$this->checkOwnablePermission('page-delete', $page); $this->checkOwnablePermission('page-delete', $page);
$this->setPageTitle(trans('entities.pages_delete_named', ['pageName' => $page->getShortName()])); $this->setPageTitle(trans('entities.pages_delete_named', ['pageName' => $page->getShortName()]));
$usedAsTemplate = $usedAsTemplate =
Book::query()->where('default_template_id', '=', $page->id)->count() > 0 || $this->entityQueries->books->start()->where('default_template_id', '=', $page->id)->count() > 0 ||
Chapter::query()->where('default_template_id', '=', $page->id)->count() > 0; $this->entityQueries->chapters->start()->where('default_template_id', '=', $page->id)->count() > 0;
return view('pages.delete', [ return view('pages.delete', [
'book' => $page->book, 'book' => $page->book,
@ -298,8 +298,8 @@ class PageController extends Controller
$this->checkOwnablePermission('page-update', $page); $this->checkOwnablePermission('page-update', $page);
$this->setPageTitle(trans('entities.pages_delete_draft_named', ['pageName' => $page->getShortName()])); $this->setPageTitle(trans('entities.pages_delete_draft_named', ['pageName' => $page->getShortName()]));
$usedAsTemplate = $usedAsTemplate =
Book::query()->where('default_template_id', '=', $page->id)->count() > 0 || $this->entityQueries->books->start()->where('default_template_id', '=', $page->id)->count() > 0 ||
Chapter::query()->where('default_template_id', '=', $page->id)->count() > 0; $this->entityQueries->chapters->start()->where('default_template_id', '=', $page->id)->count() > 0;
return view('pages.delete', [ return view('pages.delete', [
'book' => $page->book, 'book' => $page->book,

View file

@ -116,9 +116,9 @@ class RecycleBinController extends Controller
* *
* @throws \Exception * @throws \Exception
*/ */
public function empty() public function empty(TrashCan $trash)
{ {
$deleteCount = (new TrashCan())->empty(); $deleteCount = $trash->empty();
$this->logActivity(ActivityType::RECYCLE_BIN_EMPTY); $this->logActivity(ActivityType::RECYCLE_BIN_EMPTY);
$this->showSuccessNotification(trans('settings.recycle_bin_destroy_notification', ['count' => $deleteCount])); $this->showSuccessNotification(trans('settings.recycle_bin_destroy_notification', ['count' => $deleteCount]));

View file

@ -117,20 +117,11 @@ class Book extends Entity implements HasCoverImage
/** /**
* Get the direct child items within this book. * Get the direct child items within this book.
*/ */
public function getDirectChildren(): Collection public function getDirectVisibleChildren(): Collection
{ {
$pages = $this->directPages()->scopes('visible')->get(); $pages = $this->directPages()->scopes('visible')->get();
$chapters = $this->chapters()->scopes('visible')->get(); $chapters = $this->chapters()->scopes('visible')->get();
return $pages->concat($chapters)->sortBy('priority')->sortByDesc('draft'); return $pages->concat($chapters)->sortBy('priority')->sortByDesc('draft');
} }
/**
* Get a visible book by its slug.
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/
public static function getBySlug(string $slug): self
{
return static::visible()->where('slug', '=', $slug)->firstOrFail();
}
} }

View file

@ -18,6 +18,11 @@ class BookQueries implements ProvidesEntityQueries
return $this->start()->scopes('visible')->find($id); return $this->start()->scopes('visible')->find($id);
} }
public function findVisibleByIdOrFail(int $id): Book
{
return $this->start()->scopes('visible')->findOrFail($id);
}
public function findVisibleBySlugOrFail(string $slug): Book public function findVisibleBySlugOrFail(string $slug): Book
{ {
/** @var ?Book $book */ /** @var ?Book $book */

View file

@ -24,10 +24,17 @@ class ChapterQueries implements ProvidesEntityQueries
return $this->start()->scopes('visible')->find($id); return $this->start()->scopes('visible')->find($id);
} }
public function findVisibleByIdOrFail(int $id): Chapter
{
return $this->start()->scopes('visible')->findOrFail($id);
}
public function findVisibleBySlugsOrFail(string $bookSlug, string $chapterSlug): Chapter public function findVisibleBySlugsOrFail(string $bookSlug, string $chapterSlug): Chapter
{ {
/** @var ?Chapter $chapter */ /** @var ?Chapter $chapter */
$chapter = $this->start()->with('book') $chapter = $this->start()
->scopes('visible')
->with('book')
->whereHas('book', function (Builder $query) use ($bookSlug) { ->whereHas('book', function (Builder $query) use ($bookSlug) {
$query->where('slug', '=', $bookSlug); $query->where('slug', '=', $bookSlug);
}) })
@ -41,9 +48,19 @@ class ChapterQueries implements ProvidesEntityQueries
return $chapter; return $chapter;
} }
public function usingSlugs(string $bookSlug, string $chapterSlug): Builder
{
return $this->start()
->where('slug', '=', $chapterSlug)
->whereHas('book', function (Builder $query) use ($bookSlug) {
$query->where('slug', '=', $bookSlug);
});
}
public function visibleForList(): Builder public function visibleForList(): Builder
{ {
return $this->start() return $this->start()
->scopes('visible')
->select(array_merge(static::$listAttributes, ['book_slug' => function ($builder) { ->select(array_merge(static::$listAttributes, ['book_slug' => function ($builder) {
$builder->select('slug') $builder->select('slug')
->from('books') ->from('books')

View file

@ -33,6 +33,7 @@ class PageQueries implements ProvidesEntityQueries
{ {
/** @var ?Page $page */ /** @var ?Page $page */
$page = $this->start()->with('book') $page = $this->start()->with('book')
->scopes('visible')
->whereHas('book', function (Builder $query) use ($bookSlug) { ->whereHas('book', function (Builder $query) use ($bookSlug) {
$query->where('slug', '=', $bookSlug); $query->where('slug', '=', $bookSlug);
}) })
@ -46,9 +47,19 @@ class PageQueries implements ProvidesEntityQueries
return $page; return $page;
} }
public function usingSlugs(string $bookSlug, string $pageSlug): Builder
{
return $this->start()
->where('slug', '=', $pageSlug)
->whereHas('book', function (Builder $query) use ($bookSlug) {
$query->where('slug', '=', $bookSlug);
});
}
public function visibleForList(): Builder public function visibleForList(): Builder
{ {
return $this->start() return $this->start()
->scopes('visible')
->select(array_merge(Page::$listAttributes, ['book_slug' => function ($builder) { ->select(array_merge(Page::$listAttributes, ['book_slug' => function ($builder) {
$builder->select('slug') $builder->select('slug')
->from('books') ->from('books')
@ -56,6 +67,17 @@ class PageQueries implements ProvidesEntityQueries
}])); }]));
} }
public function visibleWithContents(): Builder
{
return $this->start()
->scopes('visible')
->select(array_merge(Page::$contentAttributes, ['book_slug' => function ($builder) {
$builder->select('slug')
->from('books')
->whereColumn('books.id', '=', 'pages.book_id');
}]));
}
public function currentUserDraftsForList(): Builder public function currentUserDraftsForList(): Builder
{ {
return $this->visibleForList() return $this->visibleForList()

View file

@ -17,7 +17,8 @@ class BookRepo
public function __construct( public function __construct(
protected BaseRepo $baseRepo, protected BaseRepo $baseRepo,
protected TagRepo $tagRepo, protected TagRepo $tagRepo,
protected ImageRepo $imageRepo protected ImageRepo $imageRepo,
protected TrashCan $trashCan,
) { ) {
} }
@ -73,10 +74,9 @@ class BookRepo
*/ */
public function destroy(Book $book) public function destroy(Book $book)
{ {
$trashCan = new TrashCan(); $this->trashCan->softDestroyBook($book);
$trashCan->softDestroyBook($book);
Activity::add(ActivityType::BOOK_DELETE, $book); Activity::add(ActivityType::BOOK_DELETE, $book);
$trashCan->autoClearOld(); $this->trashCan->autoClearOld();
} }
} }

View file

@ -3,8 +3,8 @@
namespace BookStack\Entities\Repos; namespace BookStack\Entities\Repos;
use BookStack\Activity\ActivityType; use BookStack\Activity\ActivityType;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Bookshelf; use BookStack\Entities\Models\Bookshelf;
use BookStack\Entities\Queries\BookQueries;
use BookStack\Entities\Tools\TrashCan; use BookStack\Entities\Tools\TrashCan;
use BookStack\Facades\Activity; use BookStack\Facades\Activity;
use Exception; use Exception;
@ -13,6 +13,8 @@ class BookshelfRepo
{ {
public function __construct( public function __construct(
protected BaseRepo $baseRepo, protected BaseRepo $baseRepo,
protected BookQueries $bookQueries,
protected TrashCan $trashCan,
) { ) {
} }
@ -60,7 +62,7 @@ class BookshelfRepo
return intval($id); return intval($id);
}); });
$syncData = Book::visible() $syncData = $this->bookQueries->visibleForList()
->whereIn('id', $bookIds) ->whereIn('id', $bookIds)
->pluck('id') ->pluck('id')
->mapWithKeys(function ($bookId) use ($numericIDs) { ->mapWithKeys(function ($bookId) use ($numericIDs) {
@ -77,9 +79,8 @@ class BookshelfRepo
*/ */
public function destroy(Bookshelf $shelf) public function destroy(Bookshelf $shelf)
{ {
$trashCan = new TrashCan(); $this->trashCan->softDestroyShelf($shelf);
$trashCan->softDestroyShelf($shelf);
Activity::add(ActivityType::BOOKSHELF_DELETE, $shelf); Activity::add(ActivityType::BOOKSHELF_DELETE, $shelf);
$trashCan->autoClearOld(); $this->trashCan->autoClearOld();
} }
} }

View file

@ -18,6 +18,7 @@ class ChapterRepo
public function __construct( public function __construct(
protected BaseRepo $baseRepo, protected BaseRepo $baseRepo,
protected EntityQueries $entityQueries, protected EntityQueries $entityQueries,
protected TrashCan $trashCan,
) { ) {
} }
@ -59,10 +60,9 @@ class ChapterRepo
*/ */
public function destroy(Chapter $chapter) public function destroy(Chapter $chapter)
{ {
$trashCan = new TrashCan(); $this->trashCan->softDestroyChapter($chapter);
$trashCan->softDestroyChapter($chapter);
Activity::add(ActivityType::CHAPTER_DELETE, $chapter); Activity::add(ActivityType::CHAPTER_DELETE, $chapter);
$trashCan->autoClearOld(); $this->trashCan->autoClearOld();
} }
/** /**

View file

@ -27,7 +27,8 @@ class PageRepo
protected RevisionRepo $revisionRepo, protected RevisionRepo $revisionRepo,
protected EntityQueries $entityQueries, protected EntityQueries $entityQueries,
protected ReferenceStore $referenceStore, protected ReferenceStore $referenceStore,
protected ReferenceUpdater $referenceUpdater protected ReferenceUpdater $referenceUpdater,
protected TrashCan $trashCan,
) { ) {
} }
@ -184,10 +185,9 @@ class PageRepo
*/ */
public function destroy(Page $page) public function destroy(Page $page)
{ {
$trashCan = new TrashCan(); $this->trashCan->softDestroyPage($page);
$trashCan->softDestroyPage($page);
Activity::add(ActivityType::PAGE_DELETE, $page); Activity::add(ActivityType::PAGE_DELETE, $page);
$trashCan->autoClearOld(); $this->trashCan->autoClearOld();
} }
/** /**

View file

@ -7,15 +7,17 @@ use BookStack\Entities\Models\BookChild;
use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\Page; use BookStack\Entities\Models\Page;
use BookStack\Entities\Queries\EntityQueries;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
class BookContents class BookContents
{ {
protected Book $book; protected EntityQueries $queries;
public function __construct(Book $book) public function __construct(
{ protected Book $book,
$this->book = $book; ) {
$this->queries = app()->make(EntityQueries::class);
} }
/** /**
@ -23,10 +25,12 @@ class BookContents
*/ */
public function getLastPriority(): int public function getLastPriority(): int
{ {
$maxPage = Page::visible()->where('book_id', '=', $this->book->id) $maxPage = $this->book->pages()
->where('draft', '=', false) ->where('draft', '=', false)
->where('chapter_id', '=', 0)->max('priority'); ->where('chapter_id', '=', 0)
$maxChapter = Chapter::visible()->where('book_id', '=', $this->book->id) ->max('priority');
$maxChapter = $this->book->chapters()
->max('priority'); ->max('priority');
return max($maxChapter, $maxPage, 1); return max($maxChapter, $maxPage, 1);
@ -38,7 +42,7 @@ class BookContents
public function getTree(bool $showDrafts = false, bool $renderPages = false): Collection public function getTree(bool $showDrafts = false, bool $renderPages = false): Collection
{ {
$pages = $this->getPages($showDrafts, $renderPages); $pages = $this->getPages($showDrafts, $renderPages);
$chapters = Chapter::visible()->where('book_id', '=', $this->book->id)->get(); $chapters = $this->book->chapters()->scopes('visible')->get();
$all = collect()->concat($pages)->concat($chapters); $all = collect()->concat($pages)->concat($chapters);
$chapterMap = $chapters->keyBy('id'); $chapterMap = $chapters->keyBy('id');
$lonePages = collect(); $lonePages = collect();
@ -87,15 +91,17 @@ class BookContents
*/ */
protected function getPages(bool $showDrafts = false, bool $getPageContent = false): Collection protected function getPages(bool $showDrafts = false, bool $getPageContent = false): Collection
{ {
$query = Page::visible() if ($getPageContent) {
->select($getPageContent ? Page::$contentAttributes : Page::$listAttributes) $query = $this->queries->pages->visibleWithContents();
->where('book_id', '=', $this->book->id); } else {
$query = $this->queries->pages->visibleForList();
}
if (!$showDrafts) { if (!$showDrafts) {
$query->where('draft', '=', false); $query->where('draft', '=', false);
} }
return $query->get(); return $query->where('book_id', '=', $this->book->id)->get();
} }
/** /**
@ -126,7 +132,7 @@ class BookContents
/** @var Book[] $booksInvolved */ /** @var Book[] $booksInvolved */
$booksInvolved = array_values(array_filter($modelMap, function (string $key) { $booksInvolved = array_values(array_filter($modelMap, function (string $key) {
return strpos($key, 'book:') === 0; return str_starts_with($key, 'book:');
}, ARRAY_FILTER_USE_KEY)); }, ARRAY_FILTER_USE_KEY));
// Update permissions of books involved // Update permissions of books involved
@ -279,7 +285,7 @@ class BookContents
} }
} }
$pages = Page::visible()->whereIn('id', array_unique($ids['page']))->get(Page::$listAttributes); $pages = $this->queries->pages->visibleForList()->whereIn('id', array_unique($ids['page']))->get();
/** @var Page $page */ /** @var Page $page */
foreach ($pages as $page) { foreach ($pages as $page) {
$modelMap['page:' . $page->id] = $page; $modelMap['page:' . $page->id] = $page;
@ -289,14 +295,14 @@ class BookContents
} }
} }
$chapters = Chapter::visible()->whereIn('id', array_unique($ids['chapter']))->get(); $chapters = $this->queries->chapters->visibleForList()->whereIn('id', array_unique($ids['chapter']))->get();
/** @var Chapter $chapter */ /** @var Chapter $chapter */
foreach ($chapters as $chapter) { foreach ($chapters as $chapter) {
$modelMap['chapter:' . $chapter->id] = $chapter; $modelMap['chapter:' . $chapter->id] = $chapter;
$ids['book'][] = $chapter->book_id; $ids['book'][] = $chapter->book_id;
} }
$books = Book::visible()->whereIn('id', array_unique($ids['book']))->get(); $books = $this->queries->books->visibleForList()->whereIn('id', array_unique($ids['book']))->get();
/** @var Book $book */ /** @var Book $book */
foreach ($books as $book) { foreach ($books as $book) {
$modelMap['book:' . $book->id] = $book; $modelMap['book:' . $book->id] = $book;

View file

@ -77,7 +77,7 @@ class Cloner
$copyBook = $this->bookRepo->create($bookDetails); $copyBook = $this->bookRepo->create($bookDetails);
// Clone contents // Clone contents
$directChildren = $original->getDirectChildren(); $directChildren = $original->getDirectVisibleChildren();
foreach ($directChildren as $child) { foreach ($directChildren as $child) {
if ($child instanceof Chapter && userCan('chapter-create', $copyBook)) { if ($child instanceof Chapter && userCan('chapter-create', $copyBook)) {
$this->cloneChapter($child, $copyBook, $child->name); $this->cloneChapter($child, $copyBook, $child->name);

View file

@ -7,10 +7,16 @@ use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Bookshelf; use BookStack\Entities\Models\Bookshelf;
use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Page; use BookStack\Entities\Models\Page;
use BookStack\Entities\Queries\EntityQueries;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
class SiblingFetcher class SiblingFetcher
{ {
public function __construct(
protected EntityQueries $queries,
) {
}
/** /**
* Search among the siblings of the entity of given type and id. * Search among the siblings of the entity of given type and id.
*/ */
@ -26,7 +32,7 @@ class SiblingFetcher
// Page in book or chapter // Page in book or chapter
if (($entity instanceof Page && !$entity->chapter) || $entity instanceof Chapter) { if (($entity instanceof Page && !$entity->chapter) || $entity instanceof Chapter) {
$entities = $entity->book->getDirectChildren(); $entities = $entity->book->getDirectVisibleChildren();
} }
// Book // Book
@ -36,13 +42,13 @@ class SiblingFetcher
if ($contextShelf) { if ($contextShelf) {
$entities = $contextShelf->visibleBooks()->get(); $entities = $contextShelf->visibleBooks()->get();
} else { } else {
$entities = Book::visible()->get(); $entities = $this->queries->books->visibleForList()->get();
} }
} }
// Shelf // Shelf
if ($entity instanceof Bookshelf) { if ($entity instanceof Bookshelf) {
$entities = Bookshelf::visible()->get(); $entities = $this->queries->shelves->visibleForList()->get();
} }
return $entities; return $entities;

View file

@ -10,6 +10,7 @@ use BookStack\Entities\Models\Deletion;
use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\HasCoverImage; use BookStack\Entities\Models\HasCoverImage;
use BookStack\Entities\Models\Page; use BookStack\Entities\Models\Page;
use BookStack\Entities\Queries\EntityQueries;
use BookStack\Exceptions\NotifyException; use BookStack\Exceptions\NotifyException;
use BookStack\Facades\Activity; use BookStack\Facades\Activity;
use BookStack\Uploads\AttachmentService; use BookStack\Uploads\AttachmentService;
@ -20,6 +21,11 @@ use Illuminate\Support\Carbon;
class TrashCan class TrashCan
{ {
public function __construct(
protected EntityQueries $queries,
) {
}
/** /**
* Send a shelf to the recycle bin. * Send a shelf to the recycle bin.
* *
@ -203,11 +209,13 @@ class TrashCan
} }
// Remove book template usages // Remove book template usages
Book::query()->where('default_template_id', '=', $page->id) $this->queries->books->start()
->where('default_template_id', '=', $page->id)
->update(['default_template_id' => null]); ->update(['default_template_id' => null]);
// Remove chapter template usages // Remove chapter template usages
Chapter::query()->where('default_template_id', '=', $page->id) $this->queries->chapters->start()
->where('default_template_id', '=', $page->id)
->update(['default_template_id' => null]); ->update(['default_template_id' => null]);
$page->forceDelete(); $page->forceDelete();

View file

@ -8,6 +8,7 @@ use BookStack\Entities\Models\Bookshelf;
use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\Page; use BookStack\Entities\Models\Page;
use BookStack\Entities\Queries\EntityQueries;
use BookStack\Permissions\Models\JointPermission; use BookStack\Permissions\Models\JointPermission;
use BookStack\Users\Models\Role; use BookStack\Users\Models\Role;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
@ -20,6 +21,12 @@ use Illuminate\Support\Facades\DB;
*/ */
class JointPermissionBuilder class JointPermissionBuilder
{ {
public function __construct(
protected EntityQueries $queries,
) {
}
/** /**
* Re-generate all entity permission from scratch. * Re-generate all entity permission from scratch.
*/ */
@ -36,7 +43,7 @@ class JointPermissionBuilder
}); });
// Chunk through all bookshelves // Chunk through all bookshelves
Bookshelf::query()->withTrashed()->select(['id', 'owned_by']) $this->queries->shelves->start()->withTrashed()->select(['id', 'owned_by'])
->chunk(50, function (EloquentCollection $shelves) use ($roles) { ->chunk(50, function (EloquentCollection $shelves) use ($roles) {
$this->createManyJointPermissions($shelves->all(), $roles); $this->createManyJointPermissions($shelves->all(), $roles);
}); });
@ -88,7 +95,7 @@ class JointPermissionBuilder
}); });
// Chunk through all bookshelves // Chunk through all bookshelves
Bookshelf::query()->select(['id', 'owned_by']) $this->queries->shelves->start()->select(['id', 'owned_by'])
->chunk(100, function ($shelves) use ($roles) { ->chunk(100, function ($shelves) use ($roles) {
$this->createManyJointPermissions($shelves->all(), $roles); $this->createManyJointPermissions($shelves->all(), $roles);
}); });
@ -99,7 +106,7 @@ class JointPermissionBuilder
*/ */
protected function bookFetchQuery(): Builder protected function bookFetchQuery(): Builder
{ {
return Book::query()->withTrashed() return $this->queries->books->start()->withTrashed()
->select(['id', 'owned_by'])->with([ ->select(['id', 'owned_by'])->with([
'chapters' => function ($query) { 'chapters' => function ($query) {
$query->withTrashed()->select(['id', 'owned_by', 'book_id']); $query->withTrashed()->select(['id', 'owned_by', 'book_id']);

View file

@ -2,10 +2,7 @@
namespace BookStack\Permissions; namespace BookStack\Permissions;
use BookStack\Entities\Models\Book; use BookStack\Entities\Queries\EntityQueries;
use BookStack\Entities\Models\Bookshelf;
use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Page;
use BookStack\Entities\Tools\PermissionsUpdater; use BookStack\Entities\Tools\PermissionsUpdater;
use BookStack\Http\Controller; use BookStack\Http\Controller;
use BookStack\Permissions\Models\EntityPermission; use BookStack\Permissions\Models\EntityPermission;
@ -14,19 +11,18 @@ use Illuminate\Http\Request;
class PermissionsController extends Controller class PermissionsController extends Controller
{ {
protected PermissionsUpdater $permissionsUpdater; public function __construct(
protected PermissionsUpdater $permissionsUpdater,
public function __construct(PermissionsUpdater $permissionsUpdater) protected EntityQueries $queries,
{ ) {
$this->permissionsUpdater = $permissionsUpdater;
} }
/** /**
* Show the Permissions view for a page. * Show the permissions view for a page.
*/ */
public function showForPage(string $bookSlug, string $pageSlug) public function showForPage(string $bookSlug, string $pageSlug)
{ {
$page = Page::getBySlugs($bookSlug, $pageSlug); $page = $this->queries->pages->findVisibleBySlugsOrFail($bookSlug, $pageSlug);
$this->checkOwnablePermission('restrictions-manage', $page); $this->checkOwnablePermission('restrictions-manage', $page);
$this->setPageTitle(trans('entities.pages_permissions')); $this->setPageTitle(trans('entities.pages_permissions'));
@ -41,7 +37,7 @@ class PermissionsController extends Controller
*/ */
public function updateForPage(Request $request, string $bookSlug, string $pageSlug) public function updateForPage(Request $request, string $bookSlug, string $pageSlug)
{ {
$page = Page::getBySlugs($bookSlug, $pageSlug); $page = $this->queries->pages->findVisibleBySlugsOrFail($bookSlug, $pageSlug);
$this->checkOwnablePermission('restrictions-manage', $page); $this->checkOwnablePermission('restrictions-manage', $page);
$this->permissionsUpdater->updateFromPermissionsForm($page, $request); $this->permissionsUpdater->updateFromPermissionsForm($page, $request);
@ -52,11 +48,11 @@ class PermissionsController extends Controller
} }
/** /**
* Show the Restrictions view for a chapter. * Show the permissions view for a chapter.
*/ */
public function showForChapter(string $bookSlug, string $chapterSlug) public function showForChapter(string $bookSlug, string $chapterSlug)
{ {
$chapter = Chapter::getBySlugs($bookSlug, $chapterSlug); $chapter = $this->queries->chapters->findVisibleBySlugsOrFail($bookSlug, $chapterSlug);
$this->checkOwnablePermission('restrictions-manage', $chapter); $this->checkOwnablePermission('restrictions-manage', $chapter);
$this->setPageTitle(trans('entities.chapters_permissions')); $this->setPageTitle(trans('entities.chapters_permissions'));
@ -67,11 +63,11 @@ class PermissionsController extends Controller
} }
/** /**
* Set the restrictions for a chapter. * Set the permissions for a chapter.
*/ */
public function updateForChapter(Request $request, string $bookSlug, string $chapterSlug) public function updateForChapter(Request $request, string $bookSlug, string $chapterSlug)
{ {
$chapter = Chapter::getBySlugs($bookSlug, $chapterSlug); $chapter = $this->queries->chapters->findVisibleBySlugsOrFail($bookSlug, $chapterSlug);
$this->checkOwnablePermission('restrictions-manage', $chapter); $this->checkOwnablePermission('restrictions-manage', $chapter);
$this->permissionsUpdater->updateFromPermissionsForm($chapter, $request); $this->permissionsUpdater->updateFromPermissionsForm($chapter, $request);
@ -86,7 +82,7 @@ class PermissionsController extends Controller
*/ */
public function showForBook(string $slug) public function showForBook(string $slug)
{ {
$book = Book::getBySlug($slug); $book = $this->queries->books->findVisibleBySlugOrFail($slug);
$this->checkOwnablePermission('restrictions-manage', $book); $this->checkOwnablePermission('restrictions-manage', $book);
$this->setPageTitle(trans('entities.books_permissions')); $this->setPageTitle(trans('entities.books_permissions'));
@ -97,11 +93,11 @@ class PermissionsController extends Controller
} }
/** /**
* Set the restrictions for a book. * Set the permissions for a book.
*/ */
public function updateForBook(Request $request, string $slug) public function updateForBook(Request $request, string $slug)
{ {
$book = Book::getBySlug($slug); $book = $this->queries->books->findVisibleBySlugOrFail($slug);
$this->checkOwnablePermission('restrictions-manage', $book); $this->checkOwnablePermission('restrictions-manage', $book);
$this->permissionsUpdater->updateFromPermissionsForm($book, $request); $this->permissionsUpdater->updateFromPermissionsForm($book, $request);
@ -116,7 +112,7 @@ class PermissionsController extends Controller
*/ */
public function showForShelf(string $slug) public function showForShelf(string $slug)
{ {
$shelf = Bookshelf::getBySlug($slug); $shelf = $this->queries->shelves->findVisibleBySlugOrFail($slug);
$this->checkOwnablePermission('restrictions-manage', $shelf); $this->checkOwnablePermission('restrictions-manage', $shelf);
$this->setPageTitle(trans('entities.shelves_permissions')); $this->setPageTitle(trans('entities.shelves_permissions'));
@ -131,7 +127,7 @@ class PermissionsController extends Controller
*/ */
public function updateForShelf(Request $request, string $slug) public function updateForShelf(Request $request, string $slug)
{ {
$shelf = Bookshelf::getBySlug($slug); $shelf = $this->queries->shelves->findVisibleBySlugOrFail($slug);
$this->checkOwnablePermission('restrictions-manage', $shelf); $this->checkOwnablePermission('restrictions-manage', $shelf);
$this->permissionsUpdater->updateFromPermissionsForm($shelf, $request); $this->permissionsUpdater->updateFromPermissionsForm($shelf, $request);
@ -146,7 +142,7 @@ class PermissionsController extends Controller
*/ */
public function copyShelfPermissionsToBooks(string $slug) public function copyShelfPermissionsToBooks(string $slug)
{ {
$shelf = Bookshelf::getBySlug($slug); $shelf = $this->queries->shelves->findVisibleBySlugOrFail($slug);
$this->checkOwnablePermission('restrictions-manage', $shelf); $this->checkOwnablePermission('restrictions-manage', $shelf);
$updateCount = $this->permissionsUpdater->updateBookPermissionsFromShelf($shelf); $updateCount = $this->permissionsUpdater->updateBookPermissionsFromShelf($shelf);

View file

@ -3,6 +3,7 @@
namespace BookStack\References; namespace BookStack\References;
use BookStack\App\Model; use BookStack\App\Model;
use BookStack\Entities\Queries\EntityQueries;
use BookStack\References\ModelResolvers\BookLinkModelResolver; use BookStack\References\ModelResolvers\BookLinkModelResolver;
use BookStack\References\ModelResolvers\BookshelfLinkModelResolver; use BookStack\References\ModelResolvers\BookshelfLinkModelResolver;
use BookStack\References\ModelResolvers\ChapterLinkModelResolver; use BookStack\References\ModelResolvers\ChapterLinkModelResolver;
@ -85,12 +86,14 @@ class CrossLinkParser
*/ */
public static function createWithEntityResolvers(): self public static function createWithEntityResolvers(): self
{ {
$queries = app()->make(EntityQueries::class);
return new self([ return new self([
new PagePermalinkModelResolver(), new PagePermalinkModelResolver($queries->pages),
new PageLinkModelResolver(), new PageLinkModelResolver($queries->pages),
new ChapterLinkModelResolver(), new ChapterLinkModelResolver($queries->chapters),
new BookLinkModelResolver(), new BookLinkModelResolver($queries->books),
new BookshelfLinkModelResolver(), new BookshelfLinkModelResolver($queries->shelves),
]); ]);
} }
} }

View file

@ -4,9 +4,15 @@ namespace BookStack\References\ModelResolvers;
use BookStack\App\Model; use BookStack\App\Model;
use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Book;
use BookStack\Entities\Queries\BookQueries;
class BookLinkModelResolver implements CrossLinkModelResolver class BookLinkModelResolver implements CrossLinkModelResolver
{ {
public function __construct(
protected BookQueries $queries
) {
}
public function resolve(string $link): ?Model public function resolve(string $link): ?Model
{ {
$pattern = '/^' . preg_quote(url('/books'), '/') . '\/([\w-]+)' . '([#?\/]|$)/'; $pattern = '/^' . preg_quote(url('/books'), '/') . '\/([\w-]+)' . '([#?\/]|$)/';
@ -19,7 +25,7 @@ class BookLinkModelResolver implements CrossLinkModelResolver
$bookSlug = $matches[1]; $bookSlug = $matches[1];
/** @var ?Book $model */ /** @var ?Book $model */
$model = Book::query()->where('slug', '=', $bookSlug)->first(['id']); $model = $this->queries->start()->where('slug', '=', $bookSlug)->first(['id']);
return $model; return $model;
} }

View file

@ -4,9 +4,14 @@ namespace BookStack\References\ModelResolvers;
use BookStack\App\Model; use BookStack\App\Model;
use BookStack\Entities\Models\Bookshelf; use BookStack\Entities\Models\Bookshelf;
use BookStack\Entities\Queries\BookshelfQueries;
class BookshelfLinkModelResolver implements CrossLinkModelResolver class BookshelfLinkModelResolver implements CrossLinkModelResolver
{ {
public function __construct(
protected BookshelfQueries $queries
) {
}
public function resolve(string $link): ?Model public function resolve(string $link): ?Model
{ {
$pattern = '/^' . preg_quote(url('/shelves'), '/') . '\/([\w-]+)' . '([#?\/]|$)/'; $pattern = '/^' . preg_quote(url('/shelves'), '/') . '\/([\w-]+)' . '([#?\/]|$)/';
@ -19,7 +24,7 @@ class BookshelfLinkModelResolver implements CrossLinkModelResolver
$shelfSlug = $matches[1]; $shelfSlug = $matches[1];
/** @var ?Bookshelf $model */ /** @var ?Bookshelf $model */
$model = Bookshelf::query()->where('slug', '=', $shelfSlug)->first(['id']); $model = $this->queries->start()->where('slug', '=', $shelfSlug)->first(['id']);
return $model; return $model;
} }

View file

@ -4,9 +4,15 @@ namespace BookStack\References\ModelResolvers;
use BookStack\App\Model; use BookStack\App\Model;
use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Queries\ChapterQueries;
class ChapterLinkModelResolver implements CrossLinkModelResolver class ChapterLinkModelResolver implements CrossLinkModelResolver
{ {
public function __construct(
protected ChapterQueries $queries
) {
}
public function resolve(string $link): ?Model public function resolve(string $link): ?Model
{ {
$pattern = '/^' . preg_quote(url('/books'), '/') . '\/([\w-]+)' . '\/chapter\/' . '([\w-]+)' . '([#?\/]|$)/'; $pattern = '/^' . preg_quote(url('/books'), '/') . '\/([\w-]+)' . '\/chapter\/' . '([\w-]+)' . '([#?\/]|$)/';
@ -20,7 +26,7 @@ class ChapterLinkModelResolver implements CrossLinkModelResolver
$chapterSlug = $matches[2]; $chapterSlug = $matches[2];
/** @var ?Chapter $model */ /** @var ?Chapter $model */
$model = Chapter::query()->whereSlugs($bookSlug, $chapterSlug)->first(['id']); $model = $this->queries->usingSlugs($bookSlug, $chapterSlug)->first(['id']);
return $model; return $model;
} }

View file

@ -4,9 +4,15 @@ namespace BookStack\References\ModelResolvers;
use BookStack\App\Model; use BookStack\App\Model;
use BookStack\Entities\Models\Page; use BookStack\Entities\Models\Page;
use BookStack\Entities\Queries\PageQueries;
class PageLinkModelResolver implements CrossLinkModelResolver class PageLinkModelResolver implements CrossLinkModelResolver
{ {
public function __construct(
protected PageQueries $queries
) {
}
public function resolve(string $link): ?Model public function resolve(string $link): ?Model
{ {
$pattern = '/^' . preg_quote(url('/books'), '/') . '\/([\w-]+)' . '\/page\/' . '([\w-]+)' . '([#?\/]|$)/'; $pattern = '/^' . preg_quote(url('/books'), '/') . '\/([\w-]+)' . '\/page\/' . '([\w-]+)' . '([#?\/]|$)/';
@ -20,7 +26,7 @@ class PageLinkModelResolver implements CrossLinkModelResolver
$pageSlug = $matches[2]; $pageSlug = $matches[2];
/** @var ?Page $model */ /** @var ?Page $model */
$model = Page::query()->whereSlugs($bookSlug, $pageSlug)->first(['id']); $model = $this->queries->usingSlugs($bookSlug, $pageSlug)->first(['id']);
return $model; return $model;
} }

View file

@ -4,9 +4,15 @@ namespace BookStack\References\ModelResolvers;
use BookStack\App\Model; use BookStack\App\Model;
use BookStack\Entities\Models\Page; use BookStack\Entities\Models\Page;
use BookStack\Entities\Queries\PageQueries;
class PagePermalinkModelResolver implements CrossLinkModelResolver class PagePermalinkModelResolver implements CrossLinkModelResolver
{ {
public function __construct(
protected PageQueries $queries
) {
}
public function resolve(string $link): ?Model public function resolve(string $link): ?Model
{ {
$pattern = '/^' . preg_quote(url('/link'), '/') . '\/(\d+)/'; $pattern = '/^' . preg_quote(url('/link'), '/') . '\/(\d+)/';
@ -18,7 +24,7 @@ class PagePermalinkModelResolver implements CrossLinkModelResolver
$id = intval($matches[1]); $id = intval($matches[1]);
/** @var ?Page $model */ /** @var ?Page $model */
$model = Page::query()->find($id, ['id']); $model = $this->queries->start()->find($id, ['id']);
return $model; return $model;
} }

View file

@ -6,12 +6,14 @@ use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Bookshelf; use BookStack\Entities\Models\Bookshelf;
use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Page; use BookStack\Entities\Models\Page;
use BookStack\Entities\Queries\EntityQueries;
use BookStack\Http\Controller; use BookStack\Http\Controller;
class ReferenceController extends Controller class ReferenceController extends Controller
{ {
public function __construct( public function __construct(
protected ReferenceFetcher $referenceFetcher protected ReferenceFetcher $referenceFetcher,
protected EntityQueries $queries,
) { ) {
} }
@ -20,7 +22,7 @@ class ReferenceController extends Controller
*/ */
public function page(string $bookSlug, string $pageSlug) public function page(string $bookSlug, string $pageSlug)
{ {
$page = Page::getBySlugs($bookSlug, $pageSlug); $page = $this->queries->pages->findVisibleBySlugsOrFail($bookSlug, $pageSlug);
$references = $this->referenceFetcher->getReferencesToEntity($page); $references = $this->referenceFetcher->getReferencesToEntity($page);
return view('pages.references', [ return view('pages.references', [
@ -34,7 +36,7 @@ class ReferenceController extends Controller
*/ */
public function chapter(string $bookSlug, string $chapterSlug) public function chapter(string $bookSlug, string $chapterSlug)
{ {
$chapter = Chapter::getBySlugs($bookSlug, $chapterSlug); $chapter = $this->queries->chapters->findVisibleBySlugsOrFail($bookSlug, $chapterSlug);
$references = $this->referenceFetcher->getReferencesToEntity($chapter); $references = $this->referenceFetcher->getReferencesToEntity($chapter);
return view('chapters.references', [ return view('chapters.references', [
@ -48,7 +50,7 @@ class ReferenceController extends Controller
*/ */
public function book(string $slug) public function book(string $slug)
{ {
$book = Book::getBySlug($slug); $book = $this->queries->books->findVisibleBySlugOrFail($slug);
$references = $this->referenceFetcher->getReferencesToEntity($book); $references = $this->referenceFetcher->getReferencesToEntity($book);
return view('books.references', [ return view('books.references', [
@ -62,7 +64,7 @@ class ReferenceController extends Controller
*/ */
public function shelf(string $slug) public function shelf(string $slug)
{ {
$shelf = Bookshelf::getBySlug($slug); $shelf = $this->queries->shelves->findVisibleBySlugOrFail($slug);
$references = $this->referenceFetcher->getReferencesToEntity($shelf); $references = $this->referenceFetcher->getReferencesToEntity($shelf);
return view('shelves.references', [ return view('shelves.references', [

View file

@ -130,12 +130,12 @@ class SearchController extends Controller
/** /**
* Search siblings items in the system. * Search siblings items in the system.
*/ */
public function searchSiblings(Request $request) public function searchSiblings(Request $request, SiblingFetcher $siblingFetcher)
{ {
$type = $request->get('entity_type', null); $type = $request->get('entity_type', null);
$id = $request->get('entity_id', null); $id = $request->get('entity_id', null);
$entities = (new SiblingFetcher())->fetch($type, $id); $entities = $siblingFetcher->fetch($type, $id);
return view('entities.list-basic', ['entities' => $entities, 'style' => 'compact']); return view('entities.list-basic', ['entities' => $entities, 'style' => 'compact']);
} }

View file

@ -14,7 +14,7 @@ class MaintenanceController extends Controller
/** /**
* Show the page for application maintenance. * Show the page for application maintenance.
*/ */
public function index() public function index(TrashCan $trashCan)
{ {
$this->checkPermission('settings-manage'); $this->checkPermission('settings-manage');
$this->setPageTitle(trans('settings.maint')); $this->setPageTitle(trans('settings.maint'));
@ -23,7 +23,7 @@ class MaintenanceController extends Controller
$version = trim(file_get_contents(base_path('version'))); $version = trim(file_get_contents(base_path('version')));
// Recycle bin details // Recycle bin details
$recycleStats = (new TrashCan())->getTrashedCounts(); $recycleStats = $trashCan->getTrashedCounts();
return view('settings.maintenance', [ return view('settings.maintenance', [
'version' => $version, 'version' => $version,

View file

@ -5,6 +5,7 @@ namespace BookStack\Uploads;
use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Bookshelf; use BookStack\Entities\Models\Bookshelf;
use BookStack\Entities\Models\Page; use BookStack\Entities\Models\Page;
use BookStack\Entities\Queries\EntityQueries;
use BookStack\Exceptions\ImageUploadException; use BookStack\Exceptions\ImageUploadException;
use Exception; use Exception;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
@ -20,6 +21,7 @@ class ImageService
public function __construct( public function __construct(
protected ImageStorage $storage, protected ImageStorage $storage,
protected ImageResizer $resizer, protected ImageResizer $resizer,
protected EntityQueries $queries,
) { ) {
} }
@ -278,15 +280,15 @@ class ImageService
} }
if ($imageType === 'gallery' || $imageType === 'drawio') { if ($imageType === 'gallery' || $imageType === 'drawio') {
return Page::visible()->where('id', '=', $image->uploaded_to)->exists(); return $this->queries->pages->visibleForList()->where('id', '=', $image->uploaded_to)->exists();
} }
if ($imageType === 'cover_book') { if ($imageType === 'cover_book') {
return Book::visible()->where('id', '=', $image->uploaded_to)->exists(); return $this->queries->books->visibleForList()->where('id', '=', $image->uploaded_to)->exists();
} }
if ($imageType === 'cover_bookshelf') { if ($imageType === 'cover_bookshelf') {
return Bookshelf::visible()->where('id', '=', $image->uploaded_to)->exists(); return $this->queries->shelves->visibleForList()->where('id', '=', $image->uploaded_to)->exists();
} }
return false; return false;

View file

@ -10,16 +10,25 @@ use BookStack\Users\UserRepo;
class UserProfileController extends Controller class UserProfileController extends Controller
{ {
public function __construct(
protected UserRepo $userRepo,
protected ActivityQueries $activityQueries,
protected UserContentCounts $contentCounts,
protected UserRecentlyCreatedContent $recentlyCreatedContent
) {
}
/** /**
* Show the user profile page. * Show the user profile page.
*/ */
public function show(UserRepo $repo, ActivityQueries $activities, string $slug) public function show(string $slug)
{ {
$user = $repo->getBySlug($slug); $user = $this->userRepo->getBySlug($slug);
$userActivity = $activities->userActivity($user); $userActivity = $this->activityQueries->userActivity($user);
$recentlyCreated = (new UserRecentlyCreatedContent())->run($user, 5); $recentlyCreated = $this->recentlyCreatedContent->run($user, 5);
$assetCounts = (new UserContentCounts())->run($user); $assetCounts = $this->contentCounts->run($user);
$this->setPageTitle($user->name); $this->setPageTitle($user->name);

View file

@ -2,10 +2,7 @@
namespace BookStack\Users\Queries; namespace BookStack\Users\Queries;
use BookStack\Entities\Models\Book; use BookStack\Entities\Queries\EntityQueries;
use BookStack\Entities\Models\Bookshelf;
use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Page;
use BookStack\Users\Models\User; use BookStack\Users\Models\User;
/** /**
@ -13,6 +10,12 @@ use BookStack\Users\Models\User;
*/ */
class UserContentCounts class UserContentCounts
{ {
public function __construct(
protected EntityQueries $queries,
) {
}
/** /**
* @return array{pages: int, chapters: int, books: int, shelves: int} * @return array{pages: int, chapters: int, books: int, shelves: int}
*/ */
@ -21,10 +24,10 @@ class UserContentCounts
$createdBy = ['created_by' => $user->id]; $createdBy = ['created_by' => $user->id];
return [ return [
'pages' => Page::visible()->where($createdBy)->count(), 'pages' => $this->queries->pages->visibleForList()->where($createdBy)->count(),
'chapters' => Chapter::visible()->where($createdBy)->count(), 'chapters' => $this->queries->chapters->visibleForList()->where($createdBy)->count(),
'books' => Book::visible()->where($createdBy)->count(), 'books' => $this->queries->books->visibleForList()->where($createdBy)->count(),
'shelves' => Bookshelf::visible()->where($createdBy)->count(), 'shelves' => $this->queries->shelves->visibleForList()->where($createdBy)->count(),
]; ];
} }
} }

View file

@ -2,10 +2,7 @@
namespace BookStack\Users\Queries; namespace BookStack\Users\Queries;
use BookStack\Entities\Models\Book; use BookStack\Entities\Queries\EntityQueries;
use BookStack\Entities\Models\Bookshelf;
use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Page;
use BookStack\Users\Models\User; use BookStack\Users\Models\User;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
@ -15,6 +12,11 @@ use Illuminate\Database\Eloquent\Collection;
*/ */
class UserRecentlyCreatedContent class UserRecentlyCreatedContent
{ {
public function __construct(
protected EntityQueries $queries,
) {
}
/** /**
* @return array{pages: Collection, chapters: Collection, books: Collection, shelves: Collection} * @return array{pages: Collection, chapters: Collection, books: Collection, shelves: Collection}
*/ */
@ -28,10 +30,10 @@ class UserRecentlyCreatedContent
}; };
return [ return [
'pages' => $query(Page::visible()->where('draft', '=', false)), 'pages' => $query($this->queries->pages->visibleForList()->where('draft', '=', false)),
'chapters' => $query(Chapter::visible()), 'chapters' => $query($this->queries->chapters->visibleForList()),
'books' => $query(Book::visible()), 'books' => $query($this->queries->books->visibleForList()),
'shelves' => $query(Bookshelf::visible()), 'shelves' => $query($this->queries->shelves->visibleForList()),
]; ];
} }
} }

View file

@ -317,7 +317,7 @@ class BookTest extends TestCase
$copy = Book::query()->where('name', '=', 'My copy book')->first(); $copy = Book::query()->where('name', '=', 'My copy book')->first();
$resp->assertRedirect($copy->getUrl()); $resp->assertRedirect($copy->getUrl());
$this->assertEquals($book->getDirectChildren()->count(), $copy->getDirectChildren()->count()); $this->assertEquals($book->getDirectVisibleChildren()->count(), $copy->getDirectVisibleChildren()->count());
$this->get($copy->getUrl())->assertSee($book->description_html, false); $this->get($copy->getUrl())->assertSee($book->description_html, false);
} }
@ -329,7 +329,7 @@ class BookTest extends TestCase
// Hide child content // Hide child content
/** @var BookChild $page */ /** @var BookChild $page */
foreach ($book->getDirectChildren() as $child) { foreach ($book->getDirectVisibleChildren() as $child) {
$this->permissions->setEntityPermissions($child, [], []); $this->permissions->setEntityPermissions($child, [], []);
} }
@ -337,7 +337,7 @@ class BookTest extends TestCase
/** @var Book $copy */ /** @var Book $copy */
$copy = Book::query()->where('name', '=', 'My copy book')->first(); $copy = Book::query()->where('name', '=', 'My copy book')->first();
$this->assertEquals(0, $copy->getDirectChildren()->count()); $this->assertEquals(0, $copy->getDirectVisibleChildren()->count());
} }
public function test_copy_does_not_copy_pages_or_chapters_if_user_cant_create() public function test_copy_does_not_copy_pages_or_chapters_if_user_cant_create()

View file

@ -303,7 +303,7 @@ class EntitySearchTest extends TestCase
public function test_sibling_search_for_pages_without_chapter() public function test_sibling_search_for_pages_without_chapter()
{ {
$page = $this->entities->pageNotWithinChapter(); $page = $this->entities->pageNotWithinChapter();
$bookChildren = $page->book->getDirectChildren(); $bookChildren = $page->book->getDirectVisibleChildren();
$this->assertGreaterThan(2, count($bookChildren), 'Ensure we\'re testing with at least 1 sibling'); $this->assertGreaterThan(2, count($bookChildren), 'Ensure we\'re testing with at least 1 sibling');
$search = $this->actingAs($this->users->viewer())->get("/search/entity/siblings?entity_id={$page->id}&entity_type=page"); $search = $this->actingAs($this->users->viewer())->get("/search/entity/siblings?entity_id={$page->id}&entity_type=page");
@ -318,7 +318,7 @@ class EntitySearchTest extends TestCase
public function test_sibling_search_for_chapters() public function test_sibling_search_for_chapters()
{ {
$chapter = $this->entities->chapter(); $chapter = $this->entities->chapter();
$bookChildren = $chapter->book->getDirectChildren(); $bookChildren = $chapter->book->getDirectVisibleChildren();
$this->assertGreaterThan(2, count($bookChildren), 'Ensure we\'re testing with at least 1 sibling'); $this->assertGreaterThan(2, count($bookChildren), 'Ensure we\'re testing with at least 1 sibling');
$search = $this->actingAs($this->users->viewer())->get("/search/entity/siblings?entity_id={$chapter->id}&entity_type=chapter"); $search = $this->actingAs($this->users->viewer())->get("/search/entity/siblings?entity_id={$chapter->id}&entity_type=chapter");