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;
$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)

View file

@ -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);

View file

@ -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');

View file

@ -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()]));

View file

@ -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);

View file

@ -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())) {

View file

@ -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,

View file

@ -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]));

View file

@ -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();
}
}

View file

@ -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 */

View file

@ -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')

View file

@ -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()

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
/**

View file

@ -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();
}
/**

View file

@ -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;

View file

@ -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);

View file

@ -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;

View file

@ -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();

View file

@ -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']);

View file

@ -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);

View file

@ -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),
]);
}
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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', [

View file

@ -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']);
}

View file

@ -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,

View file

@ -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;

View file

@ -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);

View file

@ -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(),
];
}
}

View file

@ -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()),
];
}
}

View file

@ -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()

View file

@ -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");