diff --git a/app/App/HomeController.php b/app/App/HomeController.php index f5a3c7ed6..6a4cb0176 100644 --- a/app/App/HomeController.php +++ b/app/App/HomeController.php @@ -40,7 +40,7 @@ class HomeController extends Controller $recentFactor = count($draftPages) > 0 ? 0.5 : 1; $recents = $this->isSignedIn() ? (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); $recentlyUpdatedPages = $this->queries->pages->visibleForList() ->where('draft', false) diff --git a/app/Entities/Controllers/BookApiController.php b/app/Entities/Controllers/BookApiController.php index aa21aea47..955bd707b 100644 --- a/app/Entities/Controllers/BookApiController.php +++ b/app/Entities/Controllers/BookApiController.php @@ -6,6 +6,7 @@ use BookStack\Api\ApiEntityListFormatter; use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Entity; +use BookStack\Entities\Queries\BookQueries; use BookStack\Entities\Repos\BookRepo; use BookStack\Entities\Tools\BookContents; use BookStack\Http\ApiController; @@ -15,7 +16,8 @@ use Illuminate\Validation\ValidationException; class BookApiController extends ApiController { public function __construct( - protected BookRepo $bookRepo + protected BookRepo $bookRepo, + protected BookQueries $queries, ) { } @@ -24,7 +26,7 @@ class BookApiController extends ApiController */ public function list() { - $books = Book::visible(); + $books = $this->queries->visibleForList(); return $this->apiListingResponse($books, [ '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) { - $book = Book::visible()->findOrFail($id); + $book = $this->queries->findVisibleByIdOrFail(intval($id)); $book = $this->forJsonDisplay($book); $book->load(['createdBy', 'updatedBy', 'ownedBy']); @@ -83,7 +85,7 @@ class BookApiController extends ApiController */ public function update(Request $request, string $id) { - $book = Book::visible()->findOrFail($id); + $book = $this->queries->findVisibleByIdOrFail(intval($id)); $this->checkOwnablePermission('book-update', $book); $requestData = $this->validate($request, $this->rules()['update']); @@ -100,7 +102,7 @@ class BookApiController extends ApiController */ public function delete(string $id) { - $book = Book::visible()->findOrFail($id); + $book = $this->queries->findVisibleByIdOrFail(intval($id)); $this->checkOwnablePermission('book-delete', $book); $this->bookRepo->destroy($book); diff --git a/app/Entities/Controllers/BookExportApiController.php b/app/Entities/Controllers/BookExportApiController.php index 5b6826c19..1161ddb88 100644 --- a/app/Entities/Controllers/BookExportApiController.php +++ b/app/Entities/Controllers/BookExportApiController.php @@ -2,18 +2,17 @@ namespace BookStack\Entities\Controllers; -use BookStack\Entities\Models\Book; +use BookStack\Entities\Queries\BookQueries; use BookStack\Entities\Tools\ExportFormatter; use BookStack\Http\ApiController; use Throwable; class BookExportApiController extends ApiController { - protected $exportFormatter; - - public function __construct(ExportFormatter $exportFormatter) - { - $this->exportFormatter = $exportFormatter; + public function __construct( + protected ExportFormatter $exportFormatter, + protected BookQueries $queries, + ) { $this->middleware('can:content-export'); } @@ -24,7 +23,7 @@ class BookExportApiController extends ApiController */ public function exportPdf(int $id) { - $book = Book::visible()->findOrFail($id); + $book = $this->queries->findVisibleByIdOrFail($id); $pdfContent = $this->exportFormatter->bookToPdf($book); return $this->download()->directly($pdfContent, $book->slug . '.pdf'); @@ -37,7 +36,7 @@ class BookExportApiController extends ApiController */ public function exportHtml(int $id) { - $book = Book::visible()->findOrFail($id); + $book = $this->queries->findVisibleByIdOrFail($id); $htmlContent = $this->exportFormatter->bookToContainedHtml($book); return $this->download()->directly($htmlContent, $book->slug . '.html'); @@ -48,7 +47,7 @@ class BookExportApiController extends ApiController */ public function exportPlainText(int $id) { - $book = Book::visible()->findOrFail($id); + $book = $this->queries->findVisibleByIdOrFail($id); $textContent = $this->exportFormatter->bookToPlainText($book); return $this->download()->directly($textContent, $book->slug . '.txt'); @@ -59,7 +58,7 @@ class BookExportApiController extends ApiController */ public function exportMarkdown(int $id) { - $book = Book::visible()->findOrFail($id); + $book = $this->queries->findVisibleByIdOrFail($id); $markdown = $this->exportFormatter->bookToMarkdown($book); return $this->download()->directly($markdown, $book->slug . '.md'); diff --git a/app/Entities/Controllers/BookshelfController.php b/app/Entities/Controllers/BookshelfController.php index 3118325da..6cedd23e7 100644 --- a/app/Entities/Controllers/BookshelfController.php +++ b/app/Entities/Controllers/BookshelfController.php @@ -4,7 +4,7 @@ namespace BookStack\Entities\Controllers; use BookStack\Activity\ActivityQueries; use BookStack\Activity\Models\View; -use BookStack\Entities\Models\Book; +use BookStack\Entities\Queries\BookQueries; use BookStack\Entities\Queries\BookshelfQueries; use BookStack\Entities\Repos\BookshelfRepo; use BookStack\Entities\Tools\ShelfContext; @@ -22,6 +22,7 @@ class BookshelfController extends Controller public function __construct( protected BookshelfRepo $shelfRepo, protected BookshelfQueries $queries, + protected BookQueries $bookQueries, protected ShelfContext $shelfContext, protected ReferenceFetcher $referenceFetcher, ) { @@ -68,7 +69,7 @@ class BookshelfController extends Controller public function create() { $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')); return view('shelves.create', ['books' => $books]); @@ -145,7 +146,10 @@ class BookshelfController extends Controller $this->checkOwnablePermission('bookshelf-update', $shelf); $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()])); diff --git a/app/Entities/Controllers/ChapterApiController.php b/app/Entities/Controllers/ChapterApiController.php index 3fbe85222..fb484b85d 100644 --- a/app/Entities/Controllers/ChapterApiController.php +++ b/app/Entities/Controllers/ChapterApiController.php @@ -2,8 +2,9 @@ namespace BookStack\Entities\Controllers; -use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Chapter; +use BookStack\Entities\Queries\BookQueries; +use BookStack\Entities\Queries\ChapterQueries; use BookStack\Entities\Repos\ChapterRepo; use BookStack\Exceptions\PermissionsException; use BookStack\Http\ApiController; @@ -35,7 +36,9 @@ class ChapterApiController extends ApiController ]; 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() { - $chapters = Chapter::visible(); + $chapters = $this->queries->visibleForList(); return $this->apiListingResponse($chapters, [ 'id', 'book_id', 'name', 'slug', 'description', 'priority', @@ -60,7 +63,7 @@ class ChapterApiController extends ApiController $requestData = $this->validate($request, $this->rules['create']); $bookId = $request->get('book_id'); - $book = Book::visible()->findOrFail($bookId); + $book = $this->bookQueries->findVisibleByIdOrFail(intval($bookId)); $this->checkOwnablePermission('chapter-create', $book); $chapter = $this->chapterRepo->create($requestData, $book); @@ -73,7 +76,7 @@ class ChapterApiController extends ApiController */ public function read(string $id) { - $chapter = Chapter::visible()->findOrFail($id); + $chapter = $this->queries->findVisibleByIdOrFail(intval($id)); $chapter = $this->forJsonDisplay($chapter); $chapter->load([ @@ -94,7 +97,7 @@ class ChapterApiController extends ApiController public function update(Request $request, string $id) { $requestData = $this->validate($request, $this->rules()['update']); - $chapter = Chapter::visible()->findOrFail($id); + $chapter = $this->queries->findVisibleByIdOrFail(intval($id)); $this->checkOwnablePermission('chapter-update', $chapter); 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) { - $chapter = Chapter::visible()->findOrFail($id); + $chapter = $this->queries->findVisibleByIdOrFail(intval($id)); $this->checkOwnablePermission('chapter-delete', $chapter); $this->chapterRepo->destroy($chapter); diff --git a/app/Entities/Controllers/PageApiController.php b/app/Entities/Controllers/PageApiController.php index 6e3880aed..d2a5a3ee3 100644 --- a/app/Entities/Controllers/PageApiController.php +++ b/app/Entities/Controllers/PageApiController.php @@ -2,9 +2,7 @@ namespace BookStack\Entities\Controllers; -use BookStack\Entities\Models\Book; -use BookStack\Entities\Models\Chapter; -use BookStack\Entities\Models\Page; +use BookStack\Entities\Queries\EntityQueries; use BookStack\Entities\Queries\PageQueries; use BookStack\Entities\Repos\PageRepo; use BookStack\Exceptions\PermissionsException; @@ -38,6 +36,7 @@ class PageApiController extends ApiController public function __construct( protected PageRepo $pageRepo, protected PageQueries $queries, + protected EntityQueries $entityQueries, ) { } @@ -46,7 +45,7 @@ class PageApiController extends ApiController */ public function list() { - $pages = Page::visible(); + $pages = $this->queries->visibleForList(); return $this->apiListingResponse($pages, [ 'id', 'book_id', 'chapter_id', 'name', 'slug', 'priority', @@ -72,9 +71,9 @@ class PageApiController extends ApiController $this->validate($request, $this->rules['create']); if ($request->has('chapter_id')) { - $parent = Chapter::visible()->findOrFail($request->get('chapter_id')); + $parent = $this->entityQueries->chapters->findVisibleByIdOrFail(intval($request->get('chapter_id'))); } else { - $parent = Book::visible()->findOrFail($request->get('book_id')); + $parent = $this->entityQueries->books->findVisibleByIdOrFail(intval($request->get('book_id'))); } $this->checkOwnablePermission('page-create', $parent); @@ -120,9 +119,9 @@ class PageApiController extends ApiController $parent = null; 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')) { - $parent = Book::visible()->findOrFail($request->get('book_id')); + $parent = $this->entityQueries->books->findVisibleByIdOrFail(intval($request->get('book_id'))); } if ($parent && !$parent->matches($page->getParent())) { diff --git a/app/Entities/Controllers/PageController.php b/app/Entities/Controllers/PageController.php index 3a5bdbd0b..471df8184 100644 --- a/app/Entities/Controllers/PageController.php +++ b/app/Entities/Controllers/PageController.php @@ -276,8 +276,8 @@ class PageController extends Controller $this->checkOwnablePermission('page-delete', $page); $this->setPageTitle(trans('entities.pages_delete_named', ['pageName' => $page->getShortName()])); $usedAsTemplate = - Book::query()->where('default_template_id', '=', $page->id)->count() > 0 || - Chapter::query()->where('default_template_id', '=', $page->id)->count() > 0; + $this->entityQueries->books->start()->where('default_template_id', '=', $page->id)->count() > 0 || + $this->entityQueries->chapters->start()->where('default_template_id', '=', $page->id)->count() > 0; return view('pages.delete', [ 'book' => $page->book, @@ -298,8 +298,8 @@ class PageController extends Controller $this->checkOwnablePermission('page-update', $page); $this->setPageTitle(trans('entities.pages_delete_draft_named', ['pageName' => $page->getShortName()])); $usedAsTemplate = - Book::query()->where('default_template_id', '=', $page->id)->count() > 0 || - Chapter::query()->where('default_template_id', '=', $page->id)->count() > 0; + $this->entityQueries->books->start()->where('default_template_id', '=', $page->id)->count() > 0 || + $this->entityQueries->chapters->start()->where('default_template_id', '=', $page->id)->count() > 0; return view('pages.delete', [ 'book' => $page->book, diff --git a/app/Entities/Controllers/RecycleBinController.php b/app/Entities/Controllers/RecycleBinController.php index 78f86a5ae..d11dde4dd 100644 --- a/app/Entities/Controllers/RecycleBinController.php +++ b/app/Entities/Controllers/RecycleBinController.php @@ -116,9 +116,9 @@ class RecycleBinController extends Controller * * @throws \Exception */ - public function empty() + public function empty(TrashCan $trash) { - $deleteCount = (new TrashCan())->empty(); + $deleteCount = $trash->empty(); $this->logActivity(ActivityType::RECYCLE_BIN_EMPTY); $this->showSuccessNotification(trans('settings.recycle_bin_destroy_notification', ['count' => $deleteCount])); diff --git a/app/Entities/Models/Book.php b/app/Entities/Models/Book.php index 14cb790c5..c1644dcf5 100644 --- a/app/Entities/Models/Book.php +++ b/app/Entities/Models/Book.php @@ -117,20 +117,11 @@ class Book extends Entity implements HasCoverImage /** * Get the direct child items within this book. */ - public function getDirectChildren(): Collection + public function getDirectVisibleChildren(): Collection { $pages = $this->directPages()->scopes('visible')->get(); $chapters = $this->chapters()->scopes('visible')->get(); 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(); - } } diff --git a/app/Entities/Queries/BookQueries.php b/app/Entities/Queries/BookQueries.php index 3d7474b3d..2ffff5eca 100644 --- a/app/Entities/Queries/BookQueries.php +++ b/app/Entities/Queries/BookQueries.php @@ -18,6 +18,11 @@ class BookQueries implements ProvidesEntityQueries 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 { /** @var ?Book $book */ diff --git a/app/Entities/Queries/ChapterQueries.php b/app/Entities/Queries/ChapterQueries.php index 200514932..31193c7ea 100644 --- a/app/Entities/Queries/ChapterQueries.php +++ b/app/Entities/Queries/ChapterQueries.php @@ -24,10 +24,17 @@ class ChapterQueries implements ProvidesEntityQueries 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 { /** @var ?Chapter $chapter */ - $chapter = $this->start()->with('book') + $chapter = $this->start() + ->scopes('visible') + ->with('book') ->whereHas('book', function (Builder $query) use ($bookSlug) { $query->where('slug', '=', $bookSlug); }) @@ -41,9 +48,19 @@ class ChapterQueries implements ProvidesEntityQueries 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 { return $this->start() + ->scopes('visible') ->select(array_merge(static::$listAttributes, ['book_slug' => function ($builder) { $builder->select('slug') ->from('books') diff --git a/app/Entities/Queries/PageQueries.php b/app/Entities/Queries/PageQueries.php index 1640dc2db..e22769c3a 100644 --- a/app/Entities/Queries/PageQueries.php +++ b/app/Entities/Queries/PageQueries.php @@ -33,6 +33,7 @@ class PageQueries implements ProvidesEntityQueries { /** @var ?Page $page */ $page = $this->start()->with('book') + ->scopes('visible') ->whereHas('book', function (Builder $query) use ($bookSlug) { $query->where('slug', '=', $bookSlug); }) @@ -46,9 +47,19 @@ class PageQueries implements ProvidesEntityQueries 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 { return $this->start() + ->scopes('visible') ->select(array_merge(Page::$listAttributes, ['book_slug' => function ($builder) { $builder->select('slug') ->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 { return $this->visibleForList() diff --git a/app/Entities/Repos/BookRepo.php b/app/Entities/Repos/BookRepo.php index 26b9414fb..19d159eb1 100644 --- a/app/Entities/Repos/BookRepo.php +++ b/app/Entities/Repos/BookRepo.php @@ -17,7 +17,8 @@ class BookRepo public function __construct( protected BaseRepo $baseRepo, protected TagRepo $tagRepo, - protected ImageRepo $imageRepo + protected ImageRepo $imageRepo, + protected TrashCan $trashCan, ) { } @@ -73,10 +74,9 @@ class BookRepo */ public function destroy(Book $book) { - $trashCan = new TrashCan(); - $trashCan->softDestroyBook($book); + $this->trashCan->softDestroyBook($book); Activity::add(ActivityType::BOOK_DELETE, $book); - $trashCan->autoClearOld(); + $this->trashCan->autoClearOld(); } } diff --git a/app/Entities/Repos/BookshelfRepo.php b/app/Entities/Repos/BookshelfRepo.php index 479e6178f..a00349ef1 100644 --- a/app/Entities/Repos/BookshelfRepo.php +++ b/app/Entities/Repos/BookshelfRepo.php @@ -3,8 +3,8 @@ namespace BookStack\Entities\Repos; use BookStack\Activity\ActivityType; -use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Bookshelf; +use BookStack\Entities\Queries\BookQueries; use BookStack\Entities\Tools\TrashCan; use BookStack\Facades\Activity; use Exception; @@ -13,6 +13,8 @@ class BookshelfRepo { public function __construct( protected BaseRepo $baseRepo, + protected BookQueries $bookQueries, + protected TrashCan $trashCan, ) { } @@ -60,7 +62,7 @@ class BookshelfRepo return intval($id); }); - $syncData = Book::visible() + $syncData = $this->bookQueries->visibleForList() ->whereIn('id', $bookIds) ->pluck('id') ->mapWithKeys(function ($bookId) use ($numericIDs) { @@ -77,9 +79,8 @@ class BookshelfRepo */ public function destroy(Bookshelf $shelf) { - $trashCan = new TrashCan(); - $trashCan->softDestroyShelf($shelf); + $this->trashCan->softDestroyShelf($shelf); Activity::add(ActivityType::BOOKSHELF_DELETE, $shelf); - $trashCan->autoClearOld(); + $this->trashCan->autoClearOld(); } } diff --git a/app/Entities/Repos/ChapterRepo.php b/app/Entities/Repos/ChapterRepo.php index cacca93f6..17cbccd41 100644 --- a/app/Entities/Repos/ChapterRepo.php +++ b/app/Entities/Repos/ChapterRepo.php @@ -18,6 +18,7 @@ class ChapterRepo public function __construct( protected BaseRepo $baseRepo, protected EntityQueries $entityQueries, + protected TrashCan $trashCan, ) { } @@ -59,10 +60,9 @@ class ChapterRepo */ public function destroy(Chapter $chapter) { - $trashCan = new TrashCan(); - $trashCan->softDestroyChapter($chapter); + $this->trashCan->softDestroyChapter($chapter); Activity::add(ActivityType::CHAPTER_DELETE, $chapter); - $trashCan->autoClearOld(); + $this->trashCan->autoClearOld(); } /** diff --git a/app/Entities/Repos/PageRepo.php b/app/Entities/Repos/PageRepo.php index 74aed072a..2526b6c44 100644 --- a/app/Entities/Repos/PageRepo.php +++ b/app/Entities/Repos/PageRepo.php @@ -27,7 +27,8 @@ class PageRepo protected RevisionRepo $revisionRepo, protected EntityQueries $entityQueries, protected ReferenceStore $referenceStore, - protected ReferenceUpdater $referenceUpdater + protected ReferenceUpdater $referenceUpdater, + protected TrashCan $trashCan, ) { } @@ -184,10 +185,9 @@ class PageRepo */ public function destroy(Page $page) { - $trashCan = new TrashCan(); - $trashCan->softDestroyPage($page); + $this->trashCan->softDestroyPage($page); Activity::add(ActivityType::PAGE_DELETE, $page); - $trashCan->autoClearOld(); + $this->trashCan->autoClearOld(); } /** diff --git a/app/Entities/Tools/BookContents.php b/app/Entities/Tools/BookContents.php index f45bdfcc1..7fa2134b7 100644 --- a/app/Entities/Tools/BookContents.php +++ b/app/Entities/Tools/BookContents.php @@ -7,15 +7,17 @@ use BookStack\Entities\Models\BookChild; use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\Page; +use BookStack\Entities\Queries\EntityQueries; use Illuminate\Support\Collection; class BookContents { - protected Book $book; + protected EntityQueries $queries; - public function __construct(Book $book) - { - $this->book = $book; + public function __construct( + protected Book $book, + ) { + $this->queries = app()->make(EntityQueries::class); } /** @@ -23,10 +25,12 @@ class BookContents */ public function getLastPriority(): int { - $maxPage = Page::visible()->where('book_id', '=', $this->book->id) + $maxPage = $this->book->pages() ->where('draft', '=', false) - ->where('chapter_id', '=', 0)->max('priority'); - $maxChapter = Chapter::visible()->where('book_id', '=', $this->book->id) + ->where('chapter_id', '=', 0) + ->max('priority'); + + $maxChapter = $this->book->chapters() ->max('priority'); return max($maxChapter, $maxPage, 1); @@ -38,7 +42,7 @@ class BookContents public function getTree(bool $showDrafts = false, bool $renderPages = false): Collection { $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); $chapterMap = $chapters->keyBy('id'); $lonePages = collect(); @@ -87,15 +91,17 @@ class BookContents */ protected function getPages(bool $showDrafts = false, bool $getPageContent = false): Collection { - $query = Page::visible() - ->select($getPageContent ? Page::$contentAttributes : Page::$listAttributes) - ->where('book_id', '=', $this->book->id); + if ($getPageContent) { + $query = $this->queries->pages->visibleWithContents(); + } else { + $query = $this->queries->pages->visibleForList(); + } if (!$showDrafts) { $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 */ $booksInvolved = array_values(array_filter($modelMap, function (string $key) { - return strpos($key, 'book:') === 0; + return str_starts_with($key, 'book:'); }, ARRAY_FILTER_USE_KEY)); // 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 */ foreach ($pages as $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 */ foreach ($chapters as $chapter) { $modelMap['chapter:' . $chapter->id] = $chapter; $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 */ foreach ($books as $book) { $modelMap['book:' . $book->id] = $book; diff --git a/app/Entities/Tools/Cloner.php b/app/Entities/Tools/Cloner.php index f7ed4b72d..2030b050c 100644 --- a/app/Entities/Tools/Cloner.php +++ b/app/Entities/Tools/Cloner.php @@ -77,7 +77,7 @@ class Cloner $copyBook = $this->bookRepo->create($bookDetails); // Clone contents - $directChildren = $original->getDirectChildren(); + $directChildren = $original->getDirectVisibleChildren(); foreach ($directChildren as $child) { if ($child instanceof Chapter && userCan('chapter-create', $copyBook)) { $this->cloneChapter($child, $copyBook, $child->name); diff --git a/app/Entities/Tools/SiblingFetcher.php b/app/Entities/Tools/SiblingFetcher.php index 617ef4a62..7b8ac37ad 100644 --- a/app/Entities/Tools/SiblingFetcher.php +++ b/app/Entities/Tools/SiblingFetcher.php @@ -7,10 +7,16 @@ use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Bookshelf; use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Page; +use BookStack\Entities\Queries\EntityQueries; use Illuminate\Support\Collection; class SiblingFetcher { + public function __construct( + protected EntityQueries $queries, + ) { + } + /** * Search among the siblings of the entity of given type and id. */ @@ -26,7 +32,7 @@ class SiblingFetcher // Page in book or chapter if (($entity instanceof Page && !$entity->chapter) || $entity instanceof Chapter) { - $entities = $entity->book->getDirectChildren(); + $entities = $entity->book->getDirectVisibleChildren(); } // Book @@ -36,13 +42,13 @@ class SiblingFetcher if ($contextShelf) { $entities = $contextShelf->visibleBooks()->get(); } else { - $entities = Book::visible()->get(); + $entities = $this->queries->books->visibleForList()->get(); } } // Shelf if ($entity instanceof Bookshelf) { - $entities = Bookshelf::visible()->get(); + $entities = $this->queries->shelves->visibleForList()->get(); } return $entities; diff --git a/app/Entities/Tools/TrashCan.php b/app/Entities/Tools/TrashCan.php index 8e9f010df..39c982cdc 100644 --- a/app/Entities/Tools/TrashCan.php +++ b/app/Entities/Tools/TrashCan.php @@ -10,6 +10,7 @@ use BookStack\Entities\Models\Deletion; use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\HasCoverImage; use BookStack\Entities\Models\Page; +use BookStack\Entities\Queries\EntityQueries; use BookStack\Exceptions\NotifyException; use BookStack\Facades\Activity; use BookStack\Uploads\AttachmentService; @@ -20,6 +21,11 @@ use Illuminate\Support\Carbon; class TrashCan { + public function __construct( + protected EntityQueries $queries, + ) { + } + /** * Send a shelf to the recycle bin. * @@ -203,11 +209,13 @@ class TrashCan } // 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]); // 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]); $page->forceDelete(); diff --git a/app/Permissions/JointPermissionBuilder.php b/app/Permissions/JointPermissionBuilder.php index 8c961fb13..49eaf0774 100644 --- a/app/Permissions/JointPermissionBuilder.php +++ b/app/Permissions/JointPermissionBuilder.php @@ -8,6 +8,7 @@ use BookStack\Entities\Models\Bookshelf; use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\Page; +use BookStack\Entities\Queries\EntityQueries; use BookStack\Permissions\Models\JointPermission; use BookStack\Users\Models\Role; use Illuminate\Database\Eloquent\Builder; @@ -20,6 +21,12 @@ use Illuminate\Support\Facades\DB; */ class JointPermissionBuilder { + public function __construct( + protected EntityQueries $queries, + ) { + } + + /** * Re-generate all entity permission from scratch. */ @@ -36,7 +43,7 @@ class JointPermissionBuilder }); // 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) { $this->createManyJointPermissions($shelves->all(), $roles); }); @@ -88,7 +95,7 @@ class JointPermissionBuilder }); // Chunk through all bookshelves - Bookshelf::query()->select(['id', 'owned_by']) + $this->queries->shelves->start()->select(['id', 'owned_by']) ->chunk(100, function ($shelves) use ($roles) { $this->createManyJointPermissions($shelves->all(), $roles); }); @@ -99,7 +106,7 @@ class JointPermissionBuilder */ protected function bookFetchQuery(): Builder { - return Book::query()->withTrashed() + return $this->queries->books->start()->withTrashed() ->select(['id', 'owned_by'])->with([ 'chapters' => function ($query) { $query->withTrashed()->select(['id', 'owned_by', 'book_id']); diff --git a/app/Permissions/PermissionsController.php b/app/Permissions/PermissionsController.php index f2014ea73..5d2035870 100644 --- a/app/Permissions/PermissionsController.php +++ b/app/Permissions/PermissionsController.php @@ -2,10 +2,7 @@ namespace BookStack\Permissions; -use BookStack\Entities\Models\Book; -use BookStack\Entities\Models\Bookshelf; -use BookStack\Entities\Models\Chapter; -use BookStack\Entities\Models\Page; +use BookStack\Entities\Queries\EntityQueries; use BookStack\Entities\Tools\PermissionsUpdater; use BookStack\Http\Controller; use BookStack\Permissions\Models\EntityPermission; @@ -14,19 +11,18 @@ use Illuminate\Http\Request; class PermissionsController extends Controller { - protected PermissionsUpdater $permissionsUpdater; - - public function __construct(PermissionsUpdater $permissionsUpdater) - { - $this->permissionsUpdater = $permissionsUpdater; + public function __construct( + protected PermissionsUpdater $permissionsUpdater, + protected EntityQueries $queries, + ) { } /** - * Show the Permissions view for a page. + * Show the permissions view for a page. */ 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->setPageTitle(trans('entities.pages_permissions')); @@ -41,7 +37,7 @@ class PermissionsController extends Controller */ 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->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) { - $chapter = Chapter::getBySlugs($bookSlug, $chapterSlug); + $chapter = $this->queries->chapters->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); $this->checkOwnablePermission('restrictions-manage', $chapter); $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) { - $chapter = Chapter::getBySlugs($bookSlug, $chapterSlug); + $chapter = $this->queries->chapters->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); $this->checkOwnablePermission('restrictions-manage', $chapter); $this->permissionsUpdater->updateFromPermissionsForm($chapter, $request); @@ -86,7 +82,7 @@ class PermissionsController extends Controller */ public function showForBook(string $slug) { - $book = Book::getBySlug($slug); + $book = $this->queries->books->findVisibleBySlugOrFail($slug); $this->checkOwnablePermission('restrictions-manage', $book); $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) { - $book = Book::getBySlug($slug); + $book = $this->queries->books->findVisibleBySlugOrFail($slug); $this->checkOwnablePermission('restrictions-manage', $book); $this->permissionsUpdater->updateFromPermissionsForm($book, $request); @@ -116,7 +112,7 @@ class PermissionsController extends Controller */ public function showForShelf(string $slug) { - $shelf = Bookshelf::getBySlug($slug); + $shelf = $this->queries->shelves->findVisibleBySlugOrFail($slug); $this->checkOwnablePermission('restrictions-manage', $shelf); $this->setPageTitle(trans('entities.shelves_permissions')); @@ -131,7 +127,7 @@ class PermissionsController extends Controller */ public function updateForShelf(Request $request, string $slug) { - $shelf = Bookshelf::getBySlug($slug); + $shelf = $this->queries->shelves->findVisibleBySlugOrFail($slug); $this->checkOwnablePermission('restrictions-manage', $shelf); $this->permissionsUpdater->updateFromPermissionsForm($shelf, $request); @@ -146,7 +142,7 @@ class PermissionsController extends Controller */ public function copyShelfPermissionsToBooks(string $slug) { - $shelf = Bookshelf::getBySlug($slug); + $shelf = $this->queries->shelves->findVisibleBySlugOrFail($slug); $this->checkOwnablePermission('restrictions-manage', $shelf); $updateCount = $this->permissionsUpdater->updateBookPermissionsFromShelf($shelf); diff --git a/app/References/CrossLinkParser.php b/app/References/CrossLinkParser.php index b9c3ad205..1fd4c1b3e 100644 --- a/app/References/CrossLinkParser.php +++ b/app/References/CrossLinkParser.php @@ -3,6 +3,7 @@ namespace BookStack\References; use BookStack\App\Model; +use BookStack\Entities\Queries\EntityQueries; use BookStack\References\ModelResolvers\BookLinkModelResolver; use BookStack\References\ModelResolvers\BookshelfLinkModelResolver; use BookStack\References\ModelResolvers\ChapterLinkModelResolver; @@ -85,12 +86,14 @@ class CrossLinkParser */ public static function createWithEntityResolvers(): self { + $queries = app()->make(EntityQueries::class); + return new self([ - new PagePermalinkModelResolver(), - new PageLinkModelResolver(), - new ChapterLinkModelResolver(), - new BookLinkModelResolver(), - new BookshelfLinkModelResolver(), + new PagePermalinkModelResolver($queries->pages), + new PageLinkModelResolver($queries->pages), + new ChapterLinkModelResolver($queries->chapters), + new BookLinkModelResolver($queries->books), + new BookshelfLinkModelResolver($queries->shelves), ]); } } diff --git a/app/References/ModelResolvers/BookLinkModelResolver.php b/app/References/ModelResolvers/BookLinkModelResolver.php index d404fe2fd..a671fbf57 100644 --- a/app/References/ModelResolvers/BookLinkModelResolver.php +++ b/app/References/ModelResolvers/BookLinkModelResolver.php @@ -4,9 +4,15 @@ namespace BookStack\References\ModelResolvers; use BookStack\App\Model; use BookStack\Entities\Models\Book; +use BookStack\Entities\Queries\BookQueries; class BookLinkModelResolver implements CrossLinkModelResolver { + public function __construct( + protected BookQueries $queries + ) { + } + public function resolve(string $link): ?Model { $pattern = '/^' . preg_quote(url('/books'), '/') . '\/([\w-]+)' . '([#?\/]|$)/'; @@ -19,7 +25,7 @@ class BookLinkModelResolver implements CrossLinkModelResolver $bookSlug = $matches[1]; /** @var ?Book $model */ - $model = Book::query()->where('slug', '=', $bookSlug)->first(['id']); + $model = $this->queries->start()->where('slug', '=', $bookSlug)->first(['id']); return $model; } diff --git a/app/References/ModelResolvers/BookshelfLinkModelResolver.php b/app/References/ModelResolvers/BookshelfLinkModelResolver.php index cc9df034e..d79c760e0 100644 --- a/app/References/ModelResolvers/BookshelfLinkModelResolver.php +++ b/app/References/ModelResolvers/BookshelfLinkModelResolver.php @@ -4,9 +4,14 @@ namespace BookStack\References\ModelResolvers; use BookStack\App\Model; use BookStack\Entities\Models\Bookshelf; +use BookStack\Entities\Queries\BookshelfQueries; class BookshelfLinkModelResolver implements CrossLinkModelResolver { + public function __construct( + protected BookshelfQueries $queries + ) { + } public function resolve(string $link): ?Model { $pattern = '/^' . preg_quote(url('/shelves'), '/') . '\/([\w-]+)' . '([#?\/]|$)/'; @@ -19,7 +24,7 @@ class BookshelfLinkModelResolver implements CrossLinkModelResolver $shelfSlug = $matches[1]; /** @var ?Bookshelf $model */ - $model = Bookshelf::query()->where('slug', '=', $shelfSlug)->first(['id']); + $model = $this->queries->start()->where('slug', '=', $shelfSlug)->first(['id']); return $model; } diff --git a/app/References/ModelResolvers/ChapterLinkModelResolver.php b/app/References/ModelResolvers/ChapterLinkModelResolver.php index 4733c68ee..318b5ed35 100644 --- a/app/References/ModelResolvers/ChapterLinkModelResolver.php +++ b/app/References/ModelResolvers/ChapterLinkModelResolver.php @@ -4,9 +4,15 @@ namespace BookStack\References\ModelResolvers; use BookStack\App\Model; use BookStack\Entities\Models\Chapter; +use BookStack\Entities\Queries\ChapterQueries; class ChapterLinkModelResolver implements CrossLinkModelResolver { + public function __construct( + protected ChapterQueries $queries + ) { + } + public function resolve(string $link): ?Model { $pattern = '/^' . preg_quote(url('/books'), '/') . '\/([\w-]+)' . '\/chapter\/' . '([\w-]+)' . '([#?\/]|$)/'; @@ -20,7 +26,7 @@ class ChapterLinkModelResolver implements CrossLinkModelResolver $chapterSlug = $matches[2]; /** @var ?Chapter $model */ - $model = Chapter::query()->whereSlugs($bookSlug, $chapterSlug)->first(['id']); + $model = $this->queries->usingSlugs($bookSlug, $chapterSlug)->first(['id']); return $model; } diff --git a/app/References/ModelResolvers/PageLinkModelResolver.php b/app/References/ModelResolvers/PageLinkModelResolver.php index 736382bec..9a01416a4 100644 --- a/app/References/ModelResolvers/PageLinkModelResolver.php +++ b/app/References/ModelResolvers/PageLinkModelResolver.php @@ -4,9 +4,15 @@ namespace BookStack\References\ModelResolvers; use BookStack\App\Model; use BookStack\Entities\Models\Page; +use BookStack\Entities\Queries\PageQueries; class PageLinkModelResolver implements CrossLinkModelResolver { + public function __construct( + protected PageQueries $queries + ) { + } + public function resolve(string $link): ?Model { $pattern = '/^' . preg_quote(url('/books'), '/') . '\/([\w-]+)' . '\/page\/' . '([\w-]+)' . '([#?\/]|$)/'; @@ -20,7 +26,7 @@ class PageLinkModelResolver implements CrossLinkModelResolver $pageSlug = $matches[2]; /** @var ?Page $model */ - $model = Page::query()->whereSlugs($bookSlug, $pageSlug)->first(['id']); + $model = $this->queries->usingSlugs($bookSlug, $pageSlug)->first(['id']); return $model; } diff --git a/app/References/ModelResolvers/PagePermalinkModelResolver.php b/app/References/ModelResolvers/PagePermalinkModelResolver.php index 9ed00b36d..59d509f33 100644 --- a/app/References/ModelResolvers/PagePermalinkModelResolver.php +++ b/app/References/ModelResolvers/PagePermalinkModelResolver.php @@ -4,9 +4,15 @@ namespace BookStack\References\ModelResolvers; use BookStack\App\Model; use BookStack\Entities\Models\Page; +use BookStack\Entities\Queries\PageQueries; class PagePermalinkModelResolver implements CrossLinkModelResolver { + public function __construct( + protected PageQueries $queries + ) { + } + public function resolve(string $link): ?Model { $pattern = '/^' . preg_quote(url('/link'), '/') . '\/(\d+)/'; @@ -18,7 +24,7 @@ class PagePermalinkModelResolver implements CrossLinkModelResolver $id = intval($matches[1]); /** @var ?Page $model */ - $model = Page::query()->find($id, ['id']); + $model = $this->queries->start()->find($id, ['id']); return $model; } diff --git a/app/References/ReferenceController.php b/app/References/ReferenceController.php index 991f47225..57dbcce3c 100644 --- a/app/References/ReferenceController.php +++ b/app/References/ReferenceController.php @@ -6,12 +6,14 @@ use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Bookshelf; use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Page; +use BookStack\Entities\Queries\EntityQueries; use BookStack\Http\Controller; class ReferenceController extends Controller { 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) { - $page = Page::getBySlugs($bookSlug, $pageSlug); + $page = $this->queries->pages->findVisibleBySlugsOrFail($bookSlug, $pageSlug); $references = $this->referenceFetcher->getReferencesToEntity($page); return view('pages.references', [ @@ -34,7 +36,7 @@ class ReferenceController extends Controller */ public function chapter(string $bookSlug, string $chapterSlug) { - $chapter = Chapter::getBySlugs($bookSlug, $chapterSlug); + $chapter = $this->queries->chapters->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); $references = $this->referenceFetcher->getReferencesToEntity($chapter); return view('chapters.references', [ @@ -48,7 +50,7 @@ class ReferenceController extends Controller */ public function book(string $slug) { - $book = Book::getBySlug($slug); + $book = $this->queries->books->findVisibleBySlugOrFail($slug); $references = $this->referenceFetcher->getReferencesToEntity($book); return view('books.references', [ @@ -62,7 +64,7 @@ class ReferenceController extends Controller */ public function shelf(string $slug) { - $shelf = Bookshelf::getBySlug($slug); + $shelf = $this->queries->shelves->findVisibleBySlugOrFail($slug); $references = $this->referenceFetcher->getReferencesToEntity($shelf); return view('shelves.references', [ diff --git a/app/Search/SearchController.php b/app/Search/SearchController.php index 6cf12a579..bc3f2ddb4 100644 --- a/app/Search/SearchController.php +++ b/app/Search/SearchController.php @@ -130,12 +130,12 @@ class SearchController extends Controller /** * Search siblings items in the system. */ - public function searchSiblings(Request $request) + public function searchSiblings(Request $request, SiblingFetcher $siblingFetcher) { $type = $request->get('entity_type', 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']); } diff --git a/app/Settings/MaintenanceController.php b/app/Settings/MaintenanceController.php index 62eeecf39..0382ae08a 100644 --- a/app/Settings/MaintenanceController.php +++ b/app/Settings/MaintenanceController.php @@ -14,7 +14,7 @@ class MaintenanceController extends Controller /** * Show the page for application maintenance. */ - public function index() + public function index(TrashCan $trashCan) { $this->checkPermission('settings-manage'); $this->setPageTitle(trans('settings.maint')); @@ -23,7 +23,7 @@ class MaintenanceController extends Controller $version = trim(file_get_contents(base_path('version'))); // Recycle bin details - $recycleStats = (new TrashCan())->getTrashedCounts(); + $recycleStats = $trashCan->getTrashedCounts(); return view('settings.maintenance', [ 'version' => $version, diff --git a/app/Uploads/ImageService.php b/app/Uploads/ImageService.php index 1655a4cc3..ee2cb8a35 100644 --- a/app/Uploads/ImageService.php +++ b/app/Uploads/ImageService.php @@ -5,6 +5,7 @@ namespace BookStack\Uploads; use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Bookshelf; use BookStack\Entities\Models\Page; +use BookStack\Entities\Queries\EntityQueries; use BookStack\Exceptions\ImageUploadException; use Exception; use Illuminate\Support\Facades\DB; @@ -20,6 +21,7 @@ class ImageService public function __construct( protected ImageStorage $storage, protected ImageResizer $resizer, + protected EntityQueries $queries, ) { } @@ -278,15 +280,15 @@ class ImageService } 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') { - return Book::visible()->where('id', '=', $image->uploaded_to)->exists(); + return $this->queries->books->visibleForList()->where('id', '=', $image->uploaded_to)->exists(); } 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; diff --git a/app/Users/Controllers/UserProfileController.php b/app/Users/Controllers/UserProfileController.php index bdf268260..963d69a62 100644 --- a/app/Users/Controllers/UserProfileController.php +++ b/app/Users/Controllers/UserProfileController.php @@ -10,16 +10,25 @@ use BookStack\Users\UserRepo; class UserProfileController extends Controller { + public function __construct( + protected UserRepo $userRepo, + protected ActivityQueries $activityQueries, + protected UserContentCounts $contentCounts, + protected UserRecentlyCreatedContent $recentlyCreatedContent + ) { + } + + /** * 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); - $recentlyCreated = (new UserRecentlyCreatedContent())->run($user, 5); - $assetCounts = (new UserContentCounts())->run($user); + $userActivity = $this->activityQueries->userActivity($user); + $recentlyCreated = $this->recentlyCreatedContent->run($user, 5); + $assetCounts = $this->contentCounts->run($user); $this->setPageTitle($user->name); diff --git a/app/Users/Queries/UserContentCounts.php b/app/Users/Queries/UserContentCounts.php index 178d8536b..af38bfa7e 100644 --- a/app/Users/Queries/UserContentCounts.php +++ b/app/Users/Queries/UserContentCounts.php @@ -2,10 +2,7 @@ namespace BookStack\Users\Queries; -use BookStack\Entities\Models\Book; -use BookStack\Entities\Models\Bookshelf; -use BookStack\Entities\Models\Chapter; -use BookStack\Entities\Models\Page; +use BookStack\Entities\Queries\EntityQueries; use BookStack\Users\Models\User; /** @@ -13,6 +10,12 @@ use BookStack\Users\Models\User; */ class UserContentCounts { + public function __construct( + protected EntityQueries $queries, + ) { + } + + /** * @return array{pages: int, chapters: int, books: int, shelves: int} */ @@ -21,10 +24,10 @@ class UserContentCounts $createdBy = ['created_by' => $user->id]; return [ - 'pages' => Page::visible()->where($createdBy)->count(), - 'chapters' => Chapter::visible()->where($createdBy)->count(), - 'books' => Book::visible()->where($createdBy)->count(), - 'shelves' => Bookshelf::visible()->where($createdBy)->count(), + 'pages' => $this->queries->pages->visibleForList()->where($createdBy)->count(), + 'chapters' => $this->queries->chapters->visibleForList()->where($createdBy)->count(), + 'books' => $this->queries->books->visibleForList()->where($createdBy)->count(), + 'shelves' => $this->queries->shelves->visibleForList()->where($createdBy)->count(), ]; } } diff --git a/app/Users/Queries/UserRecentlyCreatedContent.php b/app/Users/Queries/UserRecentlyCreatedContent.php index 23db2c1f1..23850e072 100644 --- a/app/Users/Queries/UserRecentlyCreatedContent.php +++ b/app/Users/Queries/UserRecentlyCreatedContent.php @@ -2,10 +2,7 @@ namespace BookStack\Users\Queries; -use BookStack\Entities\Models\Book; -use BookStack\Entities\Models\Bookshelf; -use BookStack\Entities\Models\Chapter; -use BookStack\Entities\Models\Page; +use BookStack\Entities\Queries\EntityQueries; use BookStack\Users\Models\User; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; @@ -15,6 +12,11 @@ use Illuminate\Database\Eloquent\Collection; */ class UserRecentlyCreatedContent { + public function __construct( + protected EntityQueries $queries, + ) { + } + /** * @return array{pages: Collection, chapters: Collection, books: Collection, shelves: Collection} */ @@ -28,10 +30,10 @@ class UserRecentlyCreatedContent }; return [ - 'pages' => $query(Page::visible()->where('draft', '=', false)), - 'chapters' => $query(Chapter::visible()), - 'books' => $query(Book::visible()), - 'shelves' => $query(Bookshelf::visible()), + 'pages' => $query($this->queries->pages->visibleForList()->where('draft', '=', false)), + 'chapters' => $query($this->queries->chapters->visibleForList()), + 'books' => $query($this->queries->books->visibleForList()), + 'shelves' => $query($this->queries->shelves->visibleForList()), ]; } } diff --git a/tests/Entity/BookTest.php b/tests/Entity/BookTest.php index 374089246..04dff293f 100644 --- a/tests/Entity/BookTest.php +++ b/tests/Entity/BookTest.php @@ -317,7 +317,7 @@ class BookTest extends TestCase $copy = Book::query()->where('name', '=', 'My copy book')->first(); $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); } @@ -329,7 +329,7 @@ class BookTest extends TestCase // Hide child content /** @var BookChild $page */ - foreach ($book->getDirectChildren() as $child) { + foreach ($book->getDirectVisibleChildren() as $child) { $this->permissions->setEntityPermissions($child, [], []); } @@ -337,7 +337,7 @@ class BookTest extends TestCase /** @var Book $copy */ $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() diff --git a/tests/Entity/EntitySearchTest.php b/tests/Entity/EntitySearchTest.php index 9b77a32ab..dcc062044 100644 --- a/tests/Entity/EntitySearchTest.php +++ b/tests/Entity/EntitySearchTest.php @@ -303,7 +303,7 @@ class EntitySearchTest extends TestCase public function test_sibling_search_for_pages_without_chapter() { $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'); $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() { $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'); $search = $this->actingAs($this->users->viewer())->get("/search/entity/siblings?entity_id={$chapter->id}&entity_type=chapter");