mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-11-22 11:20:37 +08:00
Merge pull request #4827 from BookStackApp/query_revamp
Update of entity loading to be more efficient and avoid global addSelects
This commit is contained in:
commit
ff8daad22b
|
@ -7,6 +7,7 @@ use BookStack\Entities\Models\Book;
|
|||
use BookStack\Entities\Models\Chapter;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Entities\Tools\MixedEntityListLoader;
|
||||
use BookStack\Permissions\PermissionApplicator;
|
||||
use BookStack\Users\Models\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
@ -14,11 +15,10 @@ use Illuminate\Database\Eloquent\Relations\Relation;
|
|||
|
||||
class ActivityQueries
|
||||
{
|
||||
protected PermissionApplicator $permissions;
|
||||
|
||||
public function __construct(PermissionApplicator $permissions)
|
||||
{
|
||||
$this->permissions = $permissions;
|
||||
public function __construct(
|
||||
protected PermissionApplicator $permissions,
|
||||
protected MixedEntityListLoader $listLoader,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -29,11 +29,13 @@ class ActivityQueries
|
|||
$activityList = $this->permissions
|
||||
->restrictEntityRelationQuery(Activity::query(), 'activities', 'entity_id', 'entity_type')
|
||||
->orderBy('created_at', 'desc')
|
||||
->with(['user', 'entity'])
|
||||
->with(['user'])
|
||||
->skip($count * $page)
|
||||
->take($count)
|
||||
->get();
|
||||
|
||||
$this->listLoader->loadIntoRelations($activityList->all(), 'entity', false);
|
||||
|
||||
return $this->filterSimilar($activityList);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
namespace BookStack\Activity\Controllers;
|
||||
|
||||
use BookStack\Activity\CommentRepo;
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Entities\Queries\PageQueries;
|
||||
use BookStack\Http\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
@ -11,7 +11,8 @@ use Illuminate\Validation\ValidationException;
|
|||
class CommentController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
protected CommentRepo $commentRepo
|
||||
protected CommentRepo $commentRepo,
|
||||
protected PageQueries $pageQueries,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -27,7 +28,7 @@ class CommentController extends Controller
|
|||
'parent_id' => ['nullable', 'integer'],
|
||||
]);
|
||||
|
||||
$page = Page::visible()->find($pageId);
|
||||
$page = $this->pageQueries->findVisibleById($pageId);
|
||||
if ($page === null) {
|
||||
return response('Not found', 404);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace BookStack\Activity\Controllers;
|
||||
|
||||
use BookStack\Entities\Queries\TopFavourites;
|
||||
use BookStack\Entities\Queries\QueryTopFavourites;
|
||||
use BookStack\Entities\Tools\MixedEntityRequestHelper;
|
||||
use BookStack\Http\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
@ -17,11 +17,11 @@ class FavouriteController extends Controller
|
|||
/**
|
||||
* Show a listing of all favourite items for the current user.
|
||||
*/
|
||||
public function index(Request $request)
|
||||
public function index(Request $request, QueryTopFavourites $topFavourites)
|
||||
{
|
||||
$viewCount = 20;
|
||||
$page = intval($request->get('page', 1));
|
||||
$favourites = (new TopFavourites())->run($viewCount + 1, (($page - 1) * $viewCount));
|
||||
$favourites = $topFavourites->run($viewCount + 1, (($page - 1) * $viewCount));
|
||||
|
||||
$hasMoreLink = ($favourites->count() > $viewCount) ? url('/favourites?page=' . ($page + 1)) : null;
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ use Exception;
|
|||
use Illuminate\Contracts\Container\BindingResolutionException;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
|
|
|
@ -3,12 +3,10 @@
|
|||
namespace BookStack\App;
|
||||
|
||||
use BookStack\Activity\ActivityQueries;
|
||||
use BookStack\Entities\Models\Book;
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Entities\Queries\RecentlyViewed;
|
||||
use BookStack\Entities\Queries\TopFavourites;
|
||||
use BookStack\Entities\Repos\BookRepo;
|
||||
use BookStack\Entities\Repos\BookshelfRepo;
|
||||
use BookStack\Entities\Queries\EntityQueries;
|
||||
use BookStack\Entities\Queries\QueryRecentlyViewed;
|
||||
use BookStack\Entities\Queries\QueryTopFavourites;
|
||||
use BookStack\Entities\Tools\PageContent;
|
||||
use BookStack\Http\Controller;
|
||||
use BookStack\Uploads\FaviconHandler;
|
||||
|
@ -17,18 +15,25 @@ use Illuminate\Http\Request;
|
|||
|
||||
class HomeController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
protected EntityQueries $queries,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the homepage.
|
||||
*/
|
||||
public function index(Request $request, ActivityQueries $activities)
|
||||
{
|
||||
public function index(
|
||||
Request $request,
|
||||
ActivityQueries $activities,
|
||||
QueryRecentlyViewed $recentlyViewed,
|
||||
QueryTopFavourites $topFavourites,
|
||||
) {
|
||||
$activity = $activities->latest(10);
|
||||
$draftPages = [];
|
||||
|
||||
if ($this->isSignedIn()) {
|
||||
$draftPages = Page::visible()
|
||||
->where('draft', '=', true)
|
||||
->where('created_by', '=', user()->id)
|
||||
$draftPages = $this->queries->pages->currentUserDraftsForList()
|
||||
->orderBy('updated_at', 'desc')
|
||||
->with('book')
|
||||
->take(6)
|
||||
|
@ -37,14 +42,13 @@ 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();
|
||||
$favourites = (new TopFavourites())->run(6);
|
||||
$recentlyUpdatedPages = Page::visible()->with('book')
|
||||
$recentlyViewed->run(12 * $recentFactor, 1)
|
||||
: $this->queries->books->visibleForList()->orderBy('created_at', 'desc')->take(12 * $recentFactor)->get();
|
||||
$favourites = $topFavourites->run(6);
|
||||
$recentlyUpdatedPages = $this->queries->pages->visibleForList()
|
||||
->where('draft', false)
|
||||
->orderBy('updated_at', 'desc')
|
||||
->take($favourites->count() > 0 ? 5 : 10)
|
||||
->select(Page::$listAttributes)
|
||||
->get();
|
||||
|
||||
$homepageOptions = ['default', 'books', 'bookshelves', 'page'];
|
||||
|
@ -78,14 +82,18 @@ class HomeController extends Controller
|
|||
}
|
||||
|
||||
if ($homepageOption === 'bookshelves') {
|
||||
$shelves = app()->make(BookshelfRepo::class)->getAllPaginated(18, $commonData['listOptions']->getSort(), $commonData['listOptions']->getOrder());
|
||||
$shelves = $this->queries->shelves->visibleForListWithCover()
|
||||
->orderBy($commonData['listOptions']->getSort(), $commonData['listOptions']->getOrder())
|
||||
->paginate(18);
|
||||
$data = array_merge($commonData, ['shelves' => $shelves]);
|
||||
|
||||
return view('home.shelves', $data);
|
||||
}
|
||||
|
||||
if ($homepageOption === 'books') {
|
||||
$books = app()->make(BookRepo::class)->getAllPaginated(18, $commonData['listOptions']->getSort(), $commonData['listOptions']->getOrder());
|
||||
$books = $this->queries->books->visibleForListWithCover()
|
||||
->orderBy($commonData['listOptions']->getSort(), $commonData['listOptions']->getOrder())
|
||||
->paginate(18);
|
||||
$data = array_merge($commonData, ['books' => $books]);
|
||||
|
||||
return view('home.books', $data);
|
||||
|
@ -95,7 +103,7 @@ class HomeController extends Controller
|
|||
$homepageSetting = setting('app-homepage', '0:');
|
||||
$id = intval(explode(':', $homepageSetting)[0]);
|
||||
/** @var Page $customHomepage */
|
||||
$customHomepage = Page::query()->where('draft', '=', false)->findOrFail($id);
|
||||
$customHomepage = $this->queries->pages->start()->where('draft', '=', false)->findOrFail($id);
|
||||
$pageContent = new PageContent($customHomepage);
|
||||
$customHomepage->html = $pageContent->render(false);
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ namespace BookStack\App\Providers;
|
|||
|
||||
use BookStack\Theming\ThemeEvents;
|
||||
use BookStack\Theming\ThemeService;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class ThemeServiceProvider extends ServiceProvider
|
||||
|
|
|
@ -173,6 +173,8 @@ return [
|
|||
|
||||
// List of URIs that should not be collected
|
||||
'except' => [
|
||||
'/uploads/images/.*', // BookStack image requests
|
||||
|
||||
'/horizon/.*', // Laravel Horizon requests
|
||||
'/telescope/.*', // Laravel Telescope requests
|
||||
'/_debugbar/.*', // Laravel DebugBar requests
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace BookStack\Console\Commands;
|
||||
|
||||
use BookStack\Entities\Models\Bookshelf;
|
||||
use BookStack\Entities\Queries\BookshelfQueries;
|
||||
use BookStack\Entities\Tools\PermissionsUpdater;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
|
@ -28,7 +28,7 @@ class CopyShelfPermissionsCommand extends Command
|
|||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(PermissionsUpdater $permissionsUpdater): int
|
||||
public function handle(PermissionsUpdater $permissionsUpdater, BookshelfQueries $queries): int
|
||||
{
|
||||
$shelfSlug = $this->option('slug');
|
||||
$cascadeAll = $this->option('all');
|
||||
|
@ -51,11 +51,11 @@ class CopyShelfPermissionsCommand extends Command
|
|||
return 0;
|
||||
}
|
||||
|
||||
$shelves = Bookshelf::query()->get(['id']);
|
||||
$shelves = $queries->start()->get(['id']);
|
||||
}
|
||||
|
||||
if ($shelfSlug) {
|
||||
$shelves = Bookshelf::query()->where('slug', '=', $shelfSlug)->get(['id']);
|
||||
$shelves = $queries->start()->where('slug', '=', $shelfSlug)->get(['id']);
|
||||
if ($shelves->count() === 0) {
|
||||
$this->info('No shelves found with the given slug.');
|
||||
}
|
||||
|
|
|
@ -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,9 @@ class BookApiController extends ApiController
|
|||
*/
|
||||
public function list()
|
||||
{
|
||||
$books = Book::visible();
|
||||
$books = $this->queries
|
||||
->visibleForList()
|
||||
->addSelect(['created_by', 'updated_by']);
|
||||
|
||||
return $this->apiListingResponse($books, [
|
||||
'id', 'name', 'slug', 'description', 'created_at', 'updated_at', 'created_by', 'updated_by', 'owned_by',
|
||||
|
@ -56,7 +60,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 +87,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 +104,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);
|
||||
|
|
|
@ -6,7 +6,8 @@ use BookStack\Activity\ActivityQueries;
|
|||
use BookStack\Activity\ActivityType;
|
||||
use BookStack\Activity\Models\View;
|
||||
use BookStack\Activity\Tools\UserEntityWatchOptions;
|
||||
use BookStack\Entities\Models\Bookshelf;
|
||||
use BookStack\Entities\Queries\BookQueries;
|
||||
use BookStack\Entities\Queries\BookshelfQueries;
|
||||
use BookStack\Entities\Repos\BookRepo;
|
||||
use BookStack\Entities\Tools\BookContents;
|
||||
use BookStack\Entities\Tools\Cloner;
|
||||
|
@ -27,7 +28,9 @@ class BookController extends Controller
|
|||
public function __construct(
|
||||
protected ShelfContext $shelfContext,
|
||||
protected BookRepo $bookRepo,
|
||||
protected ReferenceFetcher $referenceFetcher
|
||||
protected BookQueries $queries,
|
||||
protected BookshelfQueries $shelfQueries,
|
||||
protected ReferenceFetcher $referenceFetcher,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -43,10 +46,12 @@ class BookController extends Controller
|
|||
'updated_at' => trans('common.sort_updated_at'),
|
||||
]);
|
||||
|
||||
$books = $this->bookRepo->getAllPaginated(18, $listOptions->getSort(), $listOptions->getOrder());
|
||||
$recents = $this->isSignedIn() ? $this->bookRepo->getRecentlyViewed(4) : false;
|
||||
$popular = $this->bookRepo->getPopular(4);
|
||||
$new = $this->bookRepo->getRecentlyCreated(4);
|
||||
$books = $this->queries->visibleForListWithCover()
|
||||
->orderBy($listOptions->getSort(), $listOptions->getOrder())
|
||||
->paginate(18);
|
||||
$recents = $this->isSignedIn() ? $this->queries->recentlyViewedForCurrentUser()->take(4)->get() : false;
|
||||
$popular = $this->queries->popularForList()->take(4)->get();
|
||||
$new = $this->queries->visibleForList()->orderBy('created_at', 'desc')->take(4)->get();
|
||||
|
||||
$this->shelfContext->clearShelfContext();
|
||||
|
||||
|
@ -71,7 +76,7 @@ class BookController extends Controller
|
|||
|
||||
$bookshelf = null;
|
||||
if ($shelfSlug !== null) {
|
||||
$bookshelf = Bookshelf::visible()->where('slug', '=', $shelfSlug)->firstOrFail();
|
||||
$bookshelf = $this->shelfQueries->findVisibleBySlugOrFail($shelfSlug);
|
||||
$this->checkOwnablePermission('bookshelf-update', $bookshelf);
|
||||
}
|
||||
|
||||
|
@ -101,7 +106,7 @@ class BookController extends Controller
|
|||
|
||||
$bookshelf = null;
|
||||
if ($shelfSlug !== null) {
|
||||
$bookshelf = Bookshelf::visible()->where('slug', '=', $shelfSlug)->firstOrFail();
|
||||
$bookshelf = $this->shelfQueries->findVisibleBySlugOrFail($shelfSlug);
|
||||
$this->checkOwnablePermission('bookshelf-update', $bookshelf);
|
||||
}
|
||||
|
||||
|
@ -120,7 +125,7 @@ class BookController extends Controller
|
|||
*/
|
||||
public function show(Request $request, ActivityQueries $activities, string $slug)
|
||||
{
|
||||
$book = $this->bookRepo->getBySlug($slug);
|
||||
$book = $this->queries->findVisibleBySlugOrFail($slug);
|
||||
$bookChildren = (new BookContents($book))->getTree(true);
|
||||
$bookParentShelves = $book->shelves()->scopes('visible')->get();
|
||||
|
||||
|
@ -147,7 +152,7 @@ class BookController extends Controller
|
|||
*/
|
||||
public function edit(string $slug)
|
||||
{
|
||||
$book = $this->bookRepo->getBySlug($slug);
|
||||
$book = $this->queries->findVisibleBySlugOrFail($slug);
|
||||
$this->checkOwnablePermission('book-update', $book);
|
||||
$this->setPageTitle(trans('entities.books_edit_named', ['bookName' => $book->getShortName()]));
|
||||
|
||||
|
@ -163,7 +168,7 @@ class BookController extends Controller
|
|||
*/
|
||||
public function update(Request $request, string $slug)
|
||||
{
|
||||
$book = $this->bookRepo->getBySlug($slug);
|
||||
$book = $this->queries->findVisibleBySlugOrFail($slug);
|
||||
$this->checkOwnablePermission('book-update', $book);
|
||||
|
||||
$validated = $this->validate($request, [
|
||||
|
@ -190,7 +195,7 @@ class BookController extends Controller
|
|||
*/
|
||||
public function showDelete(string $bookSlug)
|
||||
{
|
||||
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||
$book = $this->queries->findVisibleBySlugOrFail($bookSlug);
|
||||
$this->checkOwnablePermission('book-delete', $book);
|
||||
$this->setPageTitle(trans('entities.books_delete_named', ['bookName' => $book->getShortName()]));
|
||||
|
||||
|
@ -204,7 +209,7 @@ class BookController extends Controller
|
|||
*/
|
||||
public function destroy(string $bookSlug)
|
||||
{
|
||||
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||
$book = $this->queries->findVisibleBySlugOrFail($bookSlug);
|
||||
$this->checkOwnablePermission('book-delete', $book);
|
||||
|
||||
$this->bookRepo->destroy($book);
|
||||
|
@ -219,7 +224,7 @@ class BookController extends Controller
|
|||
*/
|
||||
public function showCopy(string $bookSlug)
|
||||
{
|
||||
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||
$book = $this->queries->findVisibleBySlugOrFail($bookSlug);
|
||||
$this->checkOwnablePermission('book-view', $book);
|
||||
|
||||
session()->flashInput(['name' => $book->name]);
|
||||
|
@ -236,7 +241,7 @@ class BookController extends Controller
|
|||
*/
|
||||
public function copy(Request $request, Cloner $cloner, string $bookSlug)
|
||||
{
|
||||
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||
$book = $this->queries->findVisibleBySlugOrFail($bookSlug);
|
||||
$this->checkOwnablePermission('book-view', $book);
|
||||
$this->checkPermission('book-create-all');
|
||||
|
||||
|
@ -252,7 +257,7 @@ class BookController extends Controller
|
|||
*/
|
||||
public function convertToShelf(HierarchyTransformer $transformer, string $bookSlug)
|
||||
{
|
||||
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||
$book = $this->queries->findVisibleBySlugOrFail($bookSlug);
|
||||
$this->checkOwnablePermission('book-update', $book);
|
||||
$this->checkOwnablePermission('book-delete', $book);
|
||||
$this->checkPermission('bookshelf-create-all');
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -2,23 +2,17 @@
|
|||
|
||||
namespace BookStack\Entities\Controllers;
|
||||
|
||||
use BookStack\Entities\Repos\BookRepo;
|
||||
use BookStack\Entities\Queries\BookQueries;
|
||||
use BookStack\Entities\Tools\ExportFormatter;
|
||||
use BookStack\Http\Controller;
|
||||
use Throwable;
|
||||
|
||||
class BookExportController extends Controller
|
||||
{
|
||||
protected $bookRepo;
|
||||
protected $exportFormatter;
|
||||
|
||||
/**
|
||||
* BookExportController constructor.
|
||||
*/
|
||||
public function __construct(BookRepo $bookRepo, ExportFormatter $exportFormatter)
|
||||
{
|
||||
$this->bookRepo = $bookRepo;
|
||||
$this->exportFormatter = $exportFormatter;
|
||||
public function __construct(
|
||||
protected BookQueries $queries,
|
||||
protected ExportFormatter $exportFormatter,
|
||||
) {
|
||||
$this->middleware('can:content-export');
|
||||
}
|
||||
|
||||
|
@ -29,7 +23,7 @@ class BookExportController extends Controller
|
|||
*/
|
||||
public function pdf(string $bookSlug)
|
||||
{
|
||||
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||
$book = $this->queries->findVisibleBySlugOrFail($bookSlug);
|
||||
$pdfContent = $this->exportFormatter->bookToPdf($book);
|
||||
|
||||
return $this->download()->directly($pdfContent, $bookSlug . '.pdf');
|
||||
|
@ -42,7 +36,7 @@ class BookExportController extends Controller
|
|||
*/
|
||||
public function html(string $bookSlug)
|
||||
{
|
||||
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||
$book = $this->queries->findVisibleBySlugOrFail($bookSlug);
|
||||
$htmlContent = $this->exportFormatter->bookToContainedHtml($book);
|
||||
|
||||
return $this->download()->directly($htmlContent, $bookSlug . '.html');
|
||||
|
@ -53,7 +47,7 @@ class BookExportController extends Controller
|
|||
*/
|
||||
public function plainText(string $bookSlug)
|
||||
{
|
||||
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||
$book = $this->queries->findVisibleBySlugOrFail($bookSlug);
|
||||
$textContent = $this->exportFormatter->bookToPlainText($book);
|
||||
|
||||
return $this->download()->directly($textContent, $bookSlug . '.txt');
|
||||
|
@ -64,7 +58,7 @@ class BookExportController extends Controller
|
|||
*/
|
||||
public function markdown(string $bookSlug)
|
||||
{
|
||||
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||
$book = $this->queries->findVisibleBySlugOrFail($bookSlug);
|
||||
$textContent = $this->exportFormatter->bookToMarkdown($book);
|
||||
|
||||
return $this->download()->directly($textContent, $bookSlug . '.md');
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
namespace BookStack\Entities\Controllers;
|
||||
|
||||
use BookStack\Activity\ActivityType;
|
||||
use BookStack\Entities\Repos\BookRepo;
|
||||
use BookStack\Entities\Queries\BookQueries;
|
||||
use BookStack\Entities\Tools\BookContents;
|
||||
use BookStack\Entities\Tools\BookSortMap;
|
||||
use BookStack\Facades\Activity;
|
||||
|
@ -12,11 +12,9 @@ use Illuminate\Http\Request;
|
|||
|
||||
class BookSortController extends Controller
|
||||
{
|
||||
protected $bookRepo;
|
||||
|
||||
public function __construct(BookRepo $bookRepo)
|
||||
{
|
||||
$this->bookRepo = $bookRepo;
|
||||
public function __construct(
|
||||
protected BookQueries $queries,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -24,7 +22,7 @@ class BookSortController extends Controller
|
|||
*/
|
||||
public function show(string $bookSlug)
|
||||
{
|
||||
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||
$book = $this->queries->findVisibleBySlugOrFail($bookSlug);
|
||||
$this->checkOwnablePermission('book-update', $book);
|
||||
|
||||
$bookChildren = (new BookContents($book))->getTree(false);
|
||||
|
@ -40,7 +38,7 @@ class BookSortController extends Controller
|
|||
*/
|
||||
public function showItem(string $bookSlug)
|
||||
{
|
||||
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||
$book = $this->queries->findVisibleBySlugOrFail($bookSlug);
|
||||
$bookChildren = (new BookContents($book))->getTree();
|
||||
|
||||
return view('books.parts.sort-box', ['book' => $book, 'bookChildren' => $bookChildren]);
|
||||
|
@ -51,7 +49,7 @@ class BookSortController extends Controller
|
|||
*/
|
||||
public function update(Request $request, string $bookSlug)
|
||||
{
|
||||
$book = $this->bookRepo->getBySlug($bookSlug);
|
||||
$book = $this->queries->findVisibleBySlugOrFail($bookSlug);
|
||||
$this->checkOwnablePermission('book-update', $book);
|
||||
|
||||
// Return if no map sent
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace BookStack\Entities\Controllers;
|
||||
|
||||
use BookStack\Entities\Models\Bookshelf;
|
||||
use BookStack\Entities\Queries\BookshelfQueries;
|
||||
use BookStack\Entities\Repos\BookshelfRepo;
|
||||
use BookStack\Http\ApiController;
|
||||
use Exception;
|
||||
|
@ -13,7 +14,8 @@ use Illuminate\Validation\ValidationException;
|
|||
class BookshelfApiController extends ApiController
|
||||
{
|
||||
public function __construct(
|
||||
protected BookshelfRepo $bookshelfRepo
|
||||
protected BookshelfRepo $bookshelfRepo,
|
||||
protected BookshelfQueries $queries,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -22,7 +24,9 @@ class BookshelfApiController extends ApiController
|
|||
*/
|
||||
public function list()
|
||||
{
|
||||
$shelves = Bookshelf::visible();
|
||||
$shelves = $this->queries
|
||||
->visibleForList()
|
||||
->addSelect(['created_by', 'updated_by']);
|
||||
|
||||
return $this->apiListingResponse($shelves, [
|
||||
'id', 'name', 'slug', 'description', 'created_at', 'updated_at', 'created_by', 'updated_by', 'owned_by',
|
||||
|
@ -54,7 +58,7 @@ class BookshelfApiController extends ApiController
|
|||
*/
|
||||
public function read(string $id)
|
||||
{
|
||||
$shelf = Bookshelf::visible()->findOrFail($id);
|
||||
$shelf = $this->queries->findVisibleByIdOrFail(intval($id));
|
||||
$shelf = $this->forJsonDisplay($shelf);
|
||||
$shelf->load([
|
||||
'createdBy', 'updatedBy', 'ownedBy',
|
||||
|
@ -78,7 +82,7 @@ class BookshelfApiController extends ApiController
|
|||
*/
|
||||
public function update(Request $request, string $id)
|
||||
{
|
||||
$shelf = Bookshelf::visible()->findOrFail($id);
|
||||
$shelf = $this->queries->findVisibleByIdOrFail(intval($id));
|
||||
$this->checkOwnablePermission('bookshelf-update', $shelf);
|
||||
|
||||
$requestData = $this->validate($request, $this->rules()['update']);
|
||||
|
@ -97,7 +101,7 @@ class BookshelfApiController extends ApiController
|
|||
*/
|
||||
public function delete(string $id)
|
||||
{
|
||||
$shelf = Bookshelf::visible()->findOrFail($id);
|
||||
$shelf = $this->queries->findVisibleByIdOrFail(intval($id));
|
||||
$this->checkOwnablePermission('bookshelf-delete', $shelf);
|
||||
|
||||
$this->bookshelfRepo->destroy($shelf);
|
||||
|
|
|
@ -4,7 +4,8 @@ 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;
|
||||
use BookStack\Exceptions\ImageUploadException;
|
||||
|
@ -20,8 +21,10 @@ class BookshelfController extends Controller
|
|||
{
|
||||
public function __construct(
|
||||
protected BookshelfRepo $shelfRepo,
|
||||
protected BookshelfQueries $queries,
|
||||
protected BookQueries $bookQueries,
|
||||
protected ShelfContext $shelfContext,
|
||||
protected ReferenceFetcher $referenceFetcher
|
||||
protected ReferenceFetcher $referenceFetcher,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -37,10 +40,15 @@ class BookshelfController extends Controller
|
|||
'updated_at' => trans('common.sort_updated_at'),
|
||||
]);
|
||||
|
||||
$shelves = $this->shelfRepo->getAllPaginated(18, $listOptions->getSort(), $listOptions->getOrder());
|
||||
$recents = $this->isSignedIn() ? $this->shelfRepo->getRecentlyViewed(4) : false;
|
||||
$popular = $this->shelfRepo->getPopular(4);
|
||||
$new = $this->shelfRepo->getRecentlyCreated(4);
|
||||
$shelves = $this->queries->visibleForListWithCover()
|
||||
->orderBy($listOptions->getSort(), $listOptions->getOrder())
|
||||
->paginate(18);
|
||||
$recents = $this->isSignedIn() ? $this->queries->recentlyViewedForCurrentUser()->get() : false;
|
||||
$popular = $this->queries->popularForList()->get();
|
||||
$new = $this->queries->visibleForList()
|
||||
->orderBy('created_at', 'desc')
|
||||
->take(4)
|
||||
->get();
|
||||
|
||||
$this->shelfContext->clearShelfContext();
|
||||
$this->setPageTitle(trans('entities.shelves'));
|
||||
|
@ -61,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]);
|
||||
|
@ -96,7 +104,7 @@ class BookshelfController extends Controller
|
|||
*/
|
||||
public function show(Request $request, ActivityQueries $activities, string $slug)
|
||||
{
|
||||
$shelf = $this->shelfRepo->getBySlug($slug);
|
||||
$shelf = $this->queries->findVisibleBySlugOrFail($slug);
|
||||
$this->checkOwnablePermission('bookshelf-view', $shelf);
|
||||
|
||||
$listOptions = SimpleListOptions::fromRequest($request, 'shelf_books')->withSortOptions([
|
||||
|
@ -134,11 +142,14 @@ class BookshelfController extends Controller
|
|||
*/
|
||||
public function edit(string $slug)
|
||||
{
|
||||
$shelf = $this->shelfRepo->getBySlug($slug);
|
||||
$shelf = $this->queries->findVisibleBySlugOrFail($slug);
|
||||
$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()]));
|
||||
|
||||
|
@ -157,7 +168,7 @@ class BookshelfController extends Controller
|
|||
*/
|
||||
public function update(Request $request, string $slug)
|
||||
{
|
||||
$shelf = $this->shelfRepo->getBySlug($slug);
|
||||
$shelf = $this->queries->findVisibleBySlugOrFail($slug);
|
||||
$this->checkOwnablePermission('bookshelf-update', $shelf);
|
||||
$validated = $this->validate($request, [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
|
@ -183,7 +194,7 @@ class BookshelfController extends Controller
|
|||
*/
|
||||
public function showDelete(string $slug)
|
||||
{
|
||||
$shelf = $this->shelfRepo->getBySlug($slug);
|
||||
$shelf = $this->queries->findVisibleBySlugOrFail($slug);
|
||||
$this->checkOwnablePermission('bookshelf-delete', $shelf);
|
||||
|
||||
$this->setPageTitle(trans('entities.shelves_delete_named', ['name' => $shelf->getShortName()]));
|
||||
|
@ -198,7 +209,7 @@ class BookshelfController extends Controller
|
|||
*/
|
||||
public function destroy(string $slug)
|
||||
{
|
||||
$shelf = $this->shelfRepo->getBySlug($slug);
|
||||
$shelf = $this->queries->findVisibleBySlugOrFail($slug);
|
||||
$this->checkOwnablePermission('bookshelf-delete', $shelf);
|
||||
|
||||
$this->shelfRepo->destroy($shelf);
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
|
||||
namespace BookStack\Entities\Controllers;
|
||||
|
||||
use BookStack\Entities\Models\Book;
|
||||
use BookStack\Entities\Models\Chapter;
|
||||
use BookStack\Entities\Queries\ChapterQueries;
|
||||
use BookStack\Entities\Queries\EntityQueries;
|
||||
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 EntityQueries $entityQueries,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -44,7 +47,8 @@ class ChapterApiController extends ApiController
|
|||
*/
|
||||
public function list()
|
||||
{
|
||||
$chapters = Chapter::visible();
|
||||
$chapters = $this->queries->visibleForList()
|
||||
->addSelect(['created_by', 'updated_by']);
|
||||
|
||||
return $this->apiListingResponse($chapters, [
|
||||
'id', 'book_id', 'name', 'slug', 'description', 'priority',
|
||||
|
@ -60,7 +64,7 @@ class ChapterApiController extends ApiController
|
|||
$requestData = $this->validate($request, $this->rules['create']);
|
||||
|
||||
$bookId = $request->get('book_id');
|
||||
$book = Book::visible()->findOrFail($bookId);
|
||||
$book = $this->entityQueries->books->findVisibleByIdOrFail(intval($bookId));
|
||||
$this->checkOwnablePermission('chapter-create', $book);
|
||||
|
||||
$chapter = $this->chapterRepo->create($requestData, $book);
|
||||
|
@ -73,15 +77,17 @@ 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([
|
||||
'createdBy', 'updatedBy', 'ownedBy',
|
||||
'pages' => function (HasMany $query) {
|
||||
$query->scopes('visible')->get(['id', 'name', 'slug']);
|
||||
}
|
||||
]);
|
||||
$chapter->load(['createdBy', 'updatedBy', 'ownedBy']);
|
||||
|
||||
// Note: More fields than usual here, for backwards compatibility,
|
||||
// due to previously accidentally including more fields that desired.
|
||||
$pages = $this->entityQueries->pages->visibleForChapterList($chapter->id)
|
||||
->addSelect(['created_by', 'updated_by', 'revision_count', 'editor'])
|
||||
->get();
|
||||
$chapter->setRelation('pages', $pages);
|
||||
|
||||
return response()->json($chapter);
|
||||
}
|
||||
|
@ -94,7 +100,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 +128,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);
|
||||
|
|
|
@ -5,6 +5,8 @@ namespace BookStack\Entities\Controllers;
|
|||
use BookStack\Activity\Models\View;
|
||||
use BookStack\Activity\Tools\UserEntityWatchOptions;
|
||||
use BookStack\Entities\Models\Book;
|
||||
use BookStack\Entities\Queries\ChapterQueries;
|
||||
use BookStack\Entities\Queries\EntityQueries;
|
||||
use BookStack\Entities\Repos\ChapterRepo;
|
||||
use BookStack\Entities\Tools\BookContents;
|
||||
use BookStack\Entities\Tools\Cloner;
|
||||
|
@ -24,7 +26,9 @@ class ChapterController extends Controller
|
|||
{
|
||||
public function __construct(
|
||||
protected ChapterRepo $chapterRepo,
|
||||
protected ReferenceFetcher $referenceFetcher
|
||||
protected ChapterQueries $queries,
|
||||
protected EntityQueries $entityQueries,
|
||||
protected ReferenceFetcher $referenceFetcher,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -33,12 +37,15 @@ class ChapterController extends Controller
|
|||
*/
|
||||
public function create(string $bookSlug)
|
||||
{
|
||||
$book = Book::visible()->where('slug', '=', $bookSlug)->firstOrFail();
|
||||
$book = $this->entityQueries->books->findVisibleBySlugOrFail($bookSlug);
|
||||
$this->checkOwnablePermission('chapter-create', $book);
|
||||
|
||||
$this->setPageTitle(trans('entities.chapters_create'));
|
||||
|
||||
return view('chapters.create', ['book' => $book, 'current' => $book]);
|
||||
return view('chapters.create', [
|
||||
'book' => $book,
|
||||
'current' => $book,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -55,7 +62,7 @@ class ChapterController extends Controller
|
|||
'default_template_id' => ['nullable', 'integer'],
|
||||
]);
|
||||
|
||||
$book = Book::visible()->where('slug', '=', $bookSlug)->firstOrFail();
|
||||
$book = $this->entityQueries->books->findVisibleBySlugOrFail($bookSlug);
|
||||
$this->checkOwnablePermission('chapter-create', $book);
|
||||
|
||||
$chapter = $this->chapterRepo->create($validated, $book);
|
||||
|
@ -68,11 +75,12 @@ class ChapterController extends Controller
|
|||
*/
|
||||
public function show(string $bookSlug, string $chapterSlug)
|
||||
{
|
||||
$chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
|
||||
$chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug);
|
||||
$this->checkOwnablePermission('chapter-view', $chapter);
|
||||
|
||||
$sidebarTree = (new BookContents($chapter->book))->getTree();
|
||||
$pages = $chapter->getVisiblePages();
|
||||
$pages = $this->entityQueries->pages->visibleForChapterList($chapter->id)->get();
|
||||
|
||||
$nextPreviousLocator = new NextPreviousContentLocator($chapter, $sidebarTree);
|
||||
View::incrementFor($chapter);
|
||||
|
||||
|
@ -96,7 +104,7 @@ class ChapterController extends Controller
|
|||
*/
|
||||
public function edit(string $bookSlug, string $chapterSlug)
|
||||
{
|
||||
$chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
|
||||
$chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug);
|
||||
$this->checkOwnablePermission('chapter-update', $chapter);
|
||||
|
||||
$this->setPageTitle(trans('entities.chapters_edit_named', ['chapterName' => $chapter->getShortName()]));
|
||||
|
@ -118,7 +126,7 @@ class ChapterController extends Controller
|
|||
'default_template_id' => ['nullable', 'integer'],
|
||||
]);
|
||||
|
||||
$chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
|
||||
$chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug);
|
||||
$this->checkOwnablePermission('chapter-update', $chapter);
|
||||
|
||||
$this->chapterRepo->update($chapter, $validated);
|
||||
|
@ -133,7 +141,7 @@ class ChapterController extends Controller
|
|||
*/
|
||||
public function showDelete(string $bookSlug, string $chapterSlug)
|
||||
{
|
||||
$chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
|
||||
$chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug);
|
||||
$this->checkOwnablePermission('chapter-delete', $chapter);
|
||||
|
||||
$this->setPageTitle(trans('entities.chapters_delete_named', ['chapterName' => $chapter->getShortName()]));
|
||||
|
@ -149,7 +157,7 @@ class ChapterController extends Controller
|
|||
*/
|
||||
public function destroy(string $bookSlug, string $chapterSlug)
|
||||
{
|
||||
$chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
|
||||
$chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug);
|
||||
$this->checkOwnablePermission('chapter-delete', $chapter);
|
||||
|
||||
$this->chapterRepo->destroy($chapter);
|
||||
|
@ -164,7 +172,7 @@ class ChapterController extends Controller
|
|||
*/
|
||||
public function showMove(string $bookSlug, string $chapterSlug)
|
||||
{
|
||||
$chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
|
||||
$chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug);
|
||||
$this->setPageTitle(trans('entities.chapters_move_named', ['chapterName' => $chapter->getShortName()]));
|
||||
$this->checkOwnablePermission('chapter-update', $chapter);
|
||||
$this->checkOwnablePermission('chapter-delete', $chapter);
|
||||
|
@ -182,7 +190,7 @@ class ChapterController extends Controller
|
|||
*/
|
||||
public function move(Request $request, string $bookSlug, string $chapterSlug)
|
||||
{
|
||||
$chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
|
||||
$chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug);
|
||||
$this->checkOwnablePermission('chapter-update', $chapter);
|
||||
$this->checkOwnablePermission('chapter-delete', $chapter);
|
||||
|
||||
|
@ -211,7 +219,7 @@ class ChapterController extends Controller
|
|||
*/
|
||||
public function showCopy(string $bookSlug, string $chapterSlug)
|
||||
{
|
||||
$chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
|
||||
$chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug);
|
||||
$this->checkOwnablePermission('chapter-view', $chapter);
|
||||
|
||||
session()->flashInput(['name' => $chapter->name]);
|
||||
|
@ -230,13 +238,13 @@ class ChapterController extends Controller
|
|||
*/
|
||||
public function copy(Request $request, Cloner $cloner, string $bookSlug, string $chapterSlug)
|
||||
{
|
||||
$chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
|
||||
$chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug);
|
||||
$this->checkOwnablePermission('chapter-view', $chapter);
|
||||
|
||||
$entitySelection = $request->get('entity_selection') ?: null;
|
||||
$newParentBook = $entitySelection ? $this->chapterRepo->findParentByIdentifier($entitySelection) : $chapter->getParent();
|
||||
$newParentBook = $entitySelection ? $this->entityQueries->findVisibleByStringIdentifier($entitySelection) : $chapter->getParent();
|
||||
|
||||
if (is_null($newParentBook)) {
|
||||
if (!$newParentBook instanceof Book) {
|
||||
$this->showErrorNotification(trans('errors.selected_book_not_found'));
|
||||
|
||||
return redirect($chapter->getUrl('/copy'));
|
||||
|
@ -256,7 +264,7 @@ class ChapterController extends Controller
|
|||
*/
|
||||
public function convertToBook(HierarchyTransformer $transformer, string $bookSlug, string $chapterSlug)
|
||||
{
|
||||
$chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
|
||||
$chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug);
|
||||
$this->checkOwnablePermission('chapter-update', $chapter);
|
||||
$this->checkOwnablePermission('chapter-delete', $chapter);
|
||||
$this->checkPermission('book-create-all');
|
||||
|
|
|
@ -2,21 +2,17 @@
|
|||
|
||||
namespace BookStack\Entities\Controllers;
|
||||
|
||||
use BookStack\Entities\Models\Chapter;
|
||||
use BookStack\Entities\Queries\ChapterQueries;
|
||||
use BookStack\Entities\Tools\ExportFormatter;
|
||||
use BookStack\Http\ApiController;
|
||||
use Throwable;
|
||||
|
||||
class ChapterExportApiController extends ApiController
|
||||
{
|
||||
protected $exportFormatter;
|
||||
|
||||
/**
|
||||
* ChapterExportController constructor.
|
||||
*/
|
||||
public function __construct(ExportFormatter $exportFormatter)
|
||||
{
|
||||
$this->exportFormatter = $exportFormatter;
|
||||
public function __construct(
|
||||
protected ExportFormatter $exportFormatter,
|
||||
protected ChapterQueries $queries,
|
||||
) {
|
||||
$this->middleware('can:content-export');
|
||||
}
|
||||
|
||||
|
@ -27,7 +23,7 @@ class ChapterExportApiController extends ApiController
|
|||
*/
|
||||
public function exportPdf(int $id)
|
||||
{
|
||||
$chapter = Chapter::visible()->findOrFail($id);
|
||||
$chapter = $this->queries->findVisibleByIdOrFail($id);
|
||||
$pdfContent = $this->exportFormatter->chapterToPdf($chapter);
|
||||
|
||||
return $this->download()->directly($pdfContent, $chapter->slug . '.pdf');
|
||||
|
@ -40,7 +36,7 @@ class ChapterExportApiController extends ApiController
|
|||
*/
|
||||
public function exportHtml(int $id)
|
||||
{
|
||||
$chapter = Chapter::visible()->findOrFail($id);
|
||||
$chapter = $this->queries->findVisibleByIdOrFail($id);
|
||||
$htmlContent = $this->exportFormatter->chapterToContainedHtml($chapter);
|
||||
|
||||
return $this->download()->directly($htmlContent, $chapter->slug . '.html');
|
||||
|
@ -51,7 +47,7 @@ class ChapterExportApiController extends ApiController
|
|||
*/
|
||||
public function exportPlainText(int $id)
|
||||
{
|
||||
$chapter = Chapter::visible()->findOrFail($id);
|
||||
$chapter = $this->queries->findVisibleByIdOrFail($id);
|
||||
$textContent = $this->exportFormatter->chapterToPlainText($chapter);
|
||||
|
||||
return $this->download()->directly($textContent, $chapter->slug . '.txt');
|
||||
|
@ -62,7 +58,7 @@ class ChapterExportApiController extends ApiController
|
|||
*/
|
||||
public function exportMarkdown(int $id)
|
||||
{
|
||||
$chapter = Chapter::visible()->findOrFail($id);
|
||||
$chapter = $this->queries->findVisibleByIdOrFail($id);
|
||||
$markdown = $this->exportFormatter->chapterToMarkdown($chapter);
|
||||
|
||||
return $this->download()->directly($markdown, $chapter->slug . '.md');
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace BookStack\Entities\Controllers;
|
||||
|
||||
use BookStack\Entities\Repos\ChapterRepo;
|
||||
use BookStack\Entities\Queries\ChapterQueries;
|
||||
use BookStack\Entities\Tools\ExportFormatter;
|
||||
use BookStack\Exceptions\NotFoundException;
|
||||
use BookStack\Http\Controller;
|
||||
|
@ -10,16 +10,10 @@ use Throwable;
|
|||
|
||||
class ChapterExportController extends Controller
|
||||
{
|
||||
protected $chapterRepo;
|
||||
protected $exportFormatter;
|
||||
|
||||
/**
|
||||
* ChapterExportController constructor.
|
||||
*/
|
||||
public function __construct(ChapterRepo $chapterRepo, ExportFormatter $exportFormatter)
|
||||
{
|
||||
$this->chapterRepo = $chapterRepo;
|
||||
$this->exportFormatter = $exportFormatter;
|
||||
public function __construct(
|
||||
protected ChapterQueries $queries,
|
||||
protected ExportFormatter $exportFormatter,
|
||||
) {
|
||||
$this->middleware('can:content-export');
|
||||
}
|
||||
|
||||
|
@ -31,7 +25,7 @@ class ChapterExportController extends Controller
|
|||
*/
|
||||
public function pdf(string $bookSlug, string $chapterSlug)
|
||||
{
|
||||
$chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
|
||||
$chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug);
|
||||
$pdfContent = $this->exportFormatter->chapterToPdf($chapter);
|
||||
|
||||
return $this->download()->directly($pdfContent, $chapterSlug . '.pdf');
|
||||
|
@ -45,7 +39,7 @@ class ChapterExportController extends Controller
|
|||
*/
|
||||
public function html(string $bookSlug, string $chapterSlug)
|
||||
{
|
||||
$chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
|
||||
$chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug);
|
||||
$containedHtml = $this->exportFormatter->chapterToContainedHtml($chapter);
|
||||
|
||||
return $this->download()->directly($containedHtml, $chapterSlug . '.html');
|
||||
|
@ -58,7 +52,7 @@ class ChapterExportController extends Controller
|
|||
*/
|
||||
public function plainText(string $bookSlug, string $chapterSlug)
|
||||
{
|
||||
$chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
|
||||
$chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug);
|
||||
$chapterText = $this->exportFormatter->chapterToPlainText($chapter);
|
||||
|
||||
return $this->download()->directly($chapterText, $chapterSlug . '.txt');
|
||||
|
@ -71,7 +65,7 @@ class ChapterExportController extends Controller
|
|||
*/
|
||||
public function markdown(string $bookSlug, string $chapterSlug)
|
||||
{
|
||||
$chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug);
|
||||
$chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug);
|
||||
$chapterText = $this->exportFormatter->chapterToMarkdown($chapter);
|
||||
|
||||
return $this->download()->directly($chapterText, $chapterSlug . '.md');
|
||||
|
|
|
@ -2,9 +2,8 @@
|
|||
|
||||
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;
|
||||
use BookStack\Http\ApiController;
|
||||
|
@ -35,7 +34,9 @@ class PageApiController extends ApiController
|
|||
];
|
||||
|
||||
public function __construct(
|
||||
protected PageRepo $pageRepo
|
||||
protected PageRepo $pageRepo,
|
||||
protected PageQueries $queries,
|
||||
protected EntityQueries $entityQueries,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -44,7 +45,8 @@ class PageApiController extends ApiController
|
|||
*/
|
||||
public function list()
|
||||
{
|
||||
$pages = Page::visible();
|
||||
$pages = $this->queries->visibleForList()
|
||||
->addSelect(['created_by', 'updated_by', 'revision_count', 'editor']);
|
||||
|
||||
return $this->apiListingResponse($pages, [
|
||||
'id', 'book_id', 'chapter_id', 'name', 'slug', 'priority',
|
||||
|
@ -70,9 +72,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);
|
||||
|
||||
|
@ -97,7 +99,7 @@ class PageApiController extends ApiController
|
|||
*/
|
||||
public function read(string $id)
|
||||
{
|
||||
$page = $this->pageRepo->getById($id, []);
|
||||
$page = $this->queries->findVisibleByIdOrFail($id);
|
||||
|
||||
return response()->json($page->forJsonDisplay());
|
||||
}
|
||||
|
@ -113,14 +115,14 @@ class PageApiController extends ApiController
|
|||
{
|
||||
$requestData = $this->validate($request, $this->rules['update']);
|
||||
|
||||
$page = $this->pageRepo->getById($id, []);
|
||||
$page = $this->queries->findVisibleByIdOrFail($id);
|
||||
$this->checkOwnablePermission('page-update', $page);
|
||||
|
||||
$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())) {
|
||||
|
@ -148,7 +150,7 @@ class PageApiController extends ApiController
|
|||
*/
|
||||
public function delete(string $id)
|
||||
{
|
||||
$page = $this->pageRepo->getById($id, []);
|
||||
$page = $this->queries->findVisibleByIdOrFail($id);
|
||||
$this->checkOwnablePermission('page-delete', $page);
|
||||
|
||||
$this->pageRepo->destroy($page);
|
||||
|
|
|
@ -7,7 +7,8 @@ use BookStack\Activity\Tools\CommentTree;
|
|||
use BookStack\Activity\Tools\UserEntityWatchOptions;
|
||||
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\Entities\Tools\BookContents;
|
||||
use BookStack\Entities\Tools\Cloner;
|
||||
|
@ -29,6 +30,8 @@ class PageController extends Controller
|
|||
{
|
||||
public function __construct(
|
||||
protected PageRepo $pageRepo,
|
||||
protected PageQueries $queries,
|
||||
protected EntityQueries $entityQueries,
|
||||
protected ReferenceFetcher $referenceFetcher
|
||||
) {
|
||||
}
|
||||
|
@ -40,7 +43,12 @@ class PageController extends Controller
|
|||
*/
|
||||
public function create(string $bookSlug, string $chapterSlug = null)
|
||||
{
|
||||
$parent = $this->pageRepo->getParentFromSlugs($bookSlug, $chapterSlug);
|
||||
if ($chapterSlug) {
|
||||
$parent = $this->entityQueries->chapters->findVisibleBySlugsOrFail($bookSlug, $chapterSlug);
|
||||
} else {
|
||||
$parent = $this->entityQueries->books->findVisibleBySlugOrFail($bookSlug);
|
||||
}
|
||||
|
||||
$this->checkOwnablePermission('page-create', $parent);
|
||||
|
||||
// Redirect to draft edit screen if signed in
|
||||
|
@ -67,7 +75,12 @@ class PageController extends Controller
|
|||
'name' => ['required', 'string', 'max:255'],
|
||||
]);
|
||||
|
||||
$parent = $this->pageRepo->getParentFromSlugs($bookSlug, $chapterSlug);
|
||||
if ($chapterSlug) {
|
||||
$parent = $this->entityQueries->chapters->findVisibleBySlugsOrFail($bookSlug, $chapterSlug);
|
||||
} else {
|
||||
$parent = $this->entityQueries->books->findVisibleBySlugOrFail($bookSlug);
|
||||
}
|
||||
|
||||
$this->checkOwnablePermission('page-create', $parent);
|
||||
|
||||
$page = $this->pageRepo->getNewDraftPage($parent);
|
||||
|
@ -85,10 +98,10 @@ class PageController extends Controller
|
|||
*/
|
||||
public function editDraft(Request $request, string $bookSlug, int $pageId)
|
||||
{
|
||||
$draft = $this->pageRepo->getById($pageId);
|
||||
$draft = $this->queries->findVisibleByIdOrFail($pageId);
|
||||
$this->checkOwnablePermission('page-create', $draft->getParent());
|
||||
|
||||
$editorData = new PageEditorData($draft, $this->pageRepo, $request->query('editor', ''));
|
||||
$editorData = new PageEditorData($draft, $this->entityQueries, $request->query('editor', ''));
|
||||
$this->setPageTitle(trans('entities.pages_edit_draft'));
|
||||
|
||||
return view('pages.edit', $editorData->getViewData());
|
||||
|
@ -105,7 +118,7 @@ class PageController extends Controller
|
|||
$this->validate($request, [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
]);
|
||||
$draftPage = $this->pageRepo->getById($pageId);
|
||||
$draftPage = $this->queries->findVisibleByIdOrFail($pageId);
|
||||
$this->checkOwnablePermission('page-create', $draftPage->getParent());
|
||||
|
||||
$page = $this->pageRepo->publishDraft($draftPage, $request->all());
|
||||
|
@ -122,11 +135,12 @@ class PageController extends Controller
|
|||
public function show(string $bookSlug, string $pageSlug)
|
||||
{
|
||||
try {
|
||||
$page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
|
||||
$page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug);
|
||||
} catch (NotFoundException $e) {
|
||||
$page = $this->pageRepo->getByOldSlug($bookSlug, $pageSlug);
|
||||
$revision = $this->entityQueries->revisions->findLatestVersionBySlugs($bookSlug, $pageSlug);
|
||||
$page = $revision->page ?? null;
|
||||
|
||||
if ($page === null) {
|
||||
if (is_null($page)) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
|
@ -167,7 +181,7 @@ class PageController extends Controller
|
|||
*/
|
||||
public function getPageAjax(int $pageId)
|
||||
{
|
||||
$page = $this->pageRepo->getById($pageId);
|
||||
$page = $this->queries->findVisibleByIdOrFail($pageId);
|
||||
$page->setHidden(array_diff($page->getHidden(), ['html', 'markdown']));
|
||||
$page->makeHidden(['book']);
|
||||
|
||||
|
@ -181,10 +195,10 @@ class PageController extends Controller
|
|||
*/
|
||||
public function edit(Request $request, string $bookSlug, string $pageSlug)
|
||||
{
|
||||
$page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
|
||||
$page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug);
|
||||
$this->checkOwnablePermission('page-update', $page);
|
||||
|
||||
$editorData = new PageEditorData($page, $this->pageRepo, $request->query('editor', ''));
|
||||
$editorData = new PageEditorData($page, $this->entityQueries, $request->query('editor', ''));
|
||||
if ($editorData->getWarnings()) {
|
||||
$this->showWarningNotification(implode("\n", $editorData->getWarnings()));
|
||||
}
|
||||
|
@ -205,7 +219,7 @@ class PageController extends Controller
|
|||
$this->validate($request, [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
]);
|
||||
$page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
|
||||
$page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug);
|
||||
$this->checkOwnablePermission('page-update', $page);
|
||||
|
||||
$this->pageRepo->update($page, $request->all());
|
||||
|
@ -220,7 +234,7 @@ class PageController extends Controller
|
|||
*/
|
||||
public function saveDraft(Request $request, int $pageId)
|
||||
{
|
||||
$page = $this->pageRepo->getById($pageId);
|
||||
$page = $this->queries->findVisibleByIdOrFail($pageId);
|
||||
$this->checkOwnablePermission('page-update', $page);
|
||||
|
||||
if (!$this->isSignedIn()) {
|
||||
|
@ -245,7 +259,7 @@ class PageController extends Controller
|
|||
*/
|
||||
public function redirectFromLink(int $pageId)
|
||||
{
|
||||
$page = $this->pageRepo->getById($pageId);
|
||||
$page = $this->queries->findVisibleByIdOrFail($pageId);
|
||||
|
||||
return redirect($page->getUrl());
|
||||
}
|
||||
|
@ -257,12 +271,12 @@ class PageController extends Controller
|
|||
*/
|
||||
public function showDelete(string $bookSlug, string $pageSlug)
|
||||
{
|
||||
$page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
|
||||
$page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug);
|
||||
$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,
|
||||
|
@ -279,12 +293,12 @@ class PageController extends Controller
|
|||
*/
|
||||
public function showDeleteDraft(string $bookSlug, int $pageId)
|
||||
{
|
||||
$page = $this->pageRepo->getById($pageId);
|
||||
$page = $this->queries->findVisibleByIdOrFail($pageId);
|
||||
$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,
|
||||
|
@ -302,7 +316,7 @@ class PageController extends Controller
|
|||
*/
|
||||
public function destroy(string $bookSlug, string $pageSlug)
|
||||
{
|
||||
$page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
|
||||
$page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug);
|
||||
$this->checkOwnablePermission('page-delete', $page);
|
||||
$parent = $page->getParent();
|
||||
|
||||
|
@ -319,7 +333,7 @@ class PageController extends Controller
|
|||
*/
|
||||
public function destroyDraft(string $bookSlug, int $pageId)
|
||||
{
|
||||
$page = $this->pageRepo->getById($pageId);
|
||||
$page = $this->queries->findVisibleByIdOrFail($pageId);
|
||||
$book = $page->book;
|
||||
$chapter = $page->chapter;
|
||||
$this->checkOwnablePermission('page-update', $page);
|
||||
|
@ -344,7 +358,9 @@ class PageController extends Controller
|
|||
$query->scopes('visible');
|
||||
};
|
||||
|
||||
$pages = Page::visible()->with(['updatedBy', 'book' => $visibleBelongsScope, 'chapter' => $visibleBelongsScope])
|
||||
$pages = $this->queries->visibleForList()
|
||||
->addSelect('updated_by')
|
||||
->with(['updatedBy', 'book' => $visibleBelongsScope, 'chapter' => $visibleBelongsScope])
|
||||
->orderBy('updated_at', 'desc')
|
||||
->paginate(20)
|
||||
->setPath(url('/pages/recently-updated'));
|
||||
|
@ -366,7 +382,7 @@ class PageController extends Controller
|
|||
*/
|
||||
public function showMove(string $bookSlug, string $pageSlug)
|
||||
{
|
||||
$page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
|
||||
$page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug);
|
||||
$this->checkOwnablePermission('page-update', $page);
|
||||
$this->checkOwnablePermission('page-delete', $page);
|
||||
|
||||
|
@ -384,7 +400,7 @@ class PageController extends Controller
|
|||
*/
|
||||
public function move(Request $request, string $bookSlug, string $pageSlug)
|
||||
{
|
||||
$page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
|
||||
$page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug);
|
||||
$this->checkOwnablePermission('page-update', $page);
|
||||
$this->checkOwnablePermission('page-delete', $page);
|
||||
|
||||
|
@ -413,7 +429,7 @@ class PageController extends Controller
|
|||
*/
|
||||
public function showCopy(string $bookSlug, string $pageSlug)
|
||||
{
|
||||
$page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
|
||||
$page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug);
|
||||
$this->checkOwnablePermission('page-view', $page);
|
||||
session()->flashInput(['name' => $page->name]);
|
||||
|
||||
|
@ -431,13 +447,13 @@ class PageController extends Controller
|
|||
*/
|
||||
public function copy(Request $request, Cloner $cloner, string $bookSlug, string $pageSlug)
|
||||
{
|
||||
$page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
|
||||
$page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug);
|
||||
$this->checkOwnablePermission('page-view', $page);
|
||||
|
||||
$entitySelection = $request->get('entity_selection') ?: null;
|
||||
$newParent = $entitySelection ? $this->pageRepo->findParentByIdentifier($entitySelection) : $page->getParent();
|
||||
$newParent = $entitySelection ? $this->entityQueries->findVisibleByStringIdentifier($entitySelection) : $page->getParent();
|
||||
|
||||
if (is_null($newParent)) {
|
||||
if (!$newParent instanceof Book && !$newParent instanceof Chapter) {
|
||||
$this->showErrorNotification(trans('errors.selected_book_chapter_not_found'));
|
||||
|
||||
return redirect($page->getUrl('/copy'));
|
||||
|
|
|
@ -2,18 +2,17 @@
|
|||
|
||||
namespace BookStack\Entities\Controllers;
|
||||
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Entities\Queries\PageQueries;
|
||||
use BookStack\Entities\Tools\ExportFormatter;
|
||||
use BookStack\Http\ApiController;
|
||||
use Throwable;
|
||||
|
||||
class PageExportApiController extends ApiController
|
||||
{
|
||||
protected $exportFormatter;
|
||||
|
||||
public function __construct(ExportFormatter $exportFormatter)
|
||||
{
|
||||
$this->exportFormatter = $exportFormatter;
|
||||
public function __construct(
|
||||
protected ExportFormatter $exportFormatter,
|
||||
protected PageQueries $queries,
|
||||
) {
|
||||
$this->middleware('can:content-export');
|
||||
}
|
||||
|
||||
|
@ -24,7 +23,7 @@ class PageExportApiController extends ApiController
|
|||
*/
|
||||
public function exportPdf(int $id)
|
||||
{
|
||||
$page = Page::visible()->findOrFail($id);
|
||||
$page = $this->queries->findVisibleByIdOrFail($id);
|
||||
$pdfContent = $this->exportFormatter->pageToPdf($page);
|
||||
|
||||
return $this->download()->directly($pdfContent, $page->slug . '.pdf');
|
||||
|
@ -37,7 +36,7 @@ class PageExportApiController extends ApiController
|
|||
*/
|
||||
public function exportHtml(int $id)
|
||||
{
|
||||
$page = Page::visible()->findOrFail($id);
|
||||
$page = $this->queries->findVisibleByIdOrFail($id);
|
||||
$htmlContent = $this->exportFormatter->pageToContainedHtml($page);
|
||||
|
||||
return $this->download()->directly($htmlContent, $page->slug . '.html');
|
||||
|
@ -48,7 +47,7 @@ class PageExportApiController extends ApiController
|
|||
*/
|
||||
public function exportPlainText(int $id)
|
||||
{
|
||||
$page = Page::visible()->findOrFail($id);
|
||||
$page = $this->queries->findVisibleByIdOrFail($id);
|
||||
$textContent = $this->exportFormatter->pageToPlainText($page);
|
||||
|
||||
return $this->download()->directly($textContent, $page->slug . '.txt');
|
||||
|
@ -59,7 +58,7 @@ class PageExportApiController extends ApiController
|
|||
*/
|
||||
public function exportMarkdown(int $id)
|
||||
{
|
||||
$page = Page::visible()->findOrFail($id);
|
||||
$page = $this->queries->findVisibleByIdOrFail($id);
|
||||
$markdown = $this->exportFormatter->pageToMarkdown($page);
|
||||
|
||||
return $this->download()->directly($markdown, $page->slug . '.md');
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace BookStack\Entities\Controllers;
|
||||
|
||||
use BookStack\Entities\Repos\PageRepo;
|
||||
use BookStack\Entities\Queries\PageQueries;
|
||||
use BookStack\Entities\Tools\ExportFormatter;
|
||||
use BookStack\Entities\Tools\PageContent;
|
||||
use BookStack\Exceptions\NotFoundException;
|
||||
|
@ -11,16 +11,10 @@ use Throwable;
|
|||
|
||||
class PageExportController extends Controller
|
||||
{
|
||||
protected $pageRepo;
|
||||
protected $exportFormatter;
|
||||
|
||||
/**
|
||||
* PageExportController constructor.
|
||||
*/
|
||||
public function __construct(PageRepo $pageRepo, ExportFormatter $exportFormatter)
|
||||
{
|
||||
$this->pageRepo = $pageRepo;
|
||||
$this->exportFormatter = $exportFormatter;
|
||||
public function __construct(
|
||||
protected PageQueries $queries,
|
||||
protected ExportFormatter $exportFormatter,
|
||||
) {
|
||||
$this->middleware('can:content-export');
|
||||
}
|
||||
|
||||
|
@ -33,7 +27,7 @@ class PageExportController extends Controller
|
|||
*/
|
||||
public function pdf(string $bookSlug, string $pageSlug)
|
||||
{
|
||||
$page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
|
||||
$page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug);
|
||||
$page->html = (new PageContent($page))->render();
|
||||
$pdfContent = $this->exportFormatter->pageToPdf($page);
|
||||
|
||||
|
@ -48,7 +42,7 @@ class PageExportController extends Controller
|
|||
*/
|
||||
public function html(string $bookSlug, string $pageSlug)
|
||||
{
|
||||
$page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
|
||||
$page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug);
|
||||
$page->html = (new PageContent($page))->render();
|
||||
$containedHtml = $this->exportFormatter->pageToContainedHtml($page);
|
||||
|
||||
|
@ -62,7 +56,7 @@ class PageExportController extends Controller
|
|||
*/
|
||||
public function plainText(string $bookSlug, string $pageSlug)
|
||||
{
|
||||
$page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
|
||||
$page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug);
|
||||
$pageText = $this->exportFormatter->pageToPlainText($page);
|
||||
|
||||
return $this->download()->directly($pageText, $pageSlug . '.txt');
|
||||
|
@ -75,7 +69,7 @@ class PageExportController extends Controller
|
|||
*/
|
||||
public function markdown(string $bookSlug, string $pageSlug)
|
||||
{
|
||||
$page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
|
||||
$page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug);
|
||||
$pageText = $this->exportFormatter->pageToMarkdown($page);
|
||||
|
||||
return $this->download()->directly($pageText, $pageSlug . '.md');
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace BookStack\Entities\Controllers;
|
|||
|
||||
use BookStack\Activity\ActivityType;
|
||||
use BookStack\Entities\Models\PageRevision;
|
||||
use BookStack\Entities\Queries\PageQueries;
|
||||
use BookStack\Entities\Repos\PageRepo;
|
||||
use BookStack\Entities\Repos\RevisionRepo;
|
||||
use BookStack\Entities\Tools\PageContent;
|
||||
|
@ -18,6 +19,7 @@ class PageRevisionController extends Controller
|
|||
{
|
||||
public function __construct(
|
||||
protected PageRepo $pageRepo,
|
||||
protected PageQueries $pageQueries,
|
||||
protected RevisionRepo $revisionRepo,
|
||||
) {
|
||||
}
|
||||
|
@ -29,7 +31,7 @@ class PageRevisionController extends Controller
|
|||
*/
|
||||
public function index(Request $request, string $bookSlug, string $pageSlug)
|
||||
{
|
||||
$page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
|
||||
$page = $this->pageQueries->findVisibleBySlugsOrFail($bookSlug, $pageSlug);
|
||||
$listOptions = SimpleListOptions::fromRequest($request, 'page_revisions', true)->withSortOptions([
|
||||
'id' => trans('entities.pages_revisions_sort_number')
|
||||
]);
|
||||
|
@ -60,7 +62,7 @@ class PageRevisionController extends Controller
|
|||
*/
|
||||
public function show(string $bookSlug, string $pageSlug, int $revisionId)
|
||||
{
|
||||
$page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
|
||||
$page = $this->pageQueries->findVisibleBySlugsOrFail($bookSlug, $pageSlug);
|
||||
/** @var ?PageRevision $revision */
|
||||
$revision = $page->revisions()->where('id', '=', $revisionId)->first();
|
||||
if ($revision === null) {
|
||||
|
@ -89,7 +91,7 @@ class PageRevisionController extends Controller
|
|||
*/
|
||||
public function changes(string $bookSlug, string $pageSlug, int $revisionId)
|
||||
{
|
||||
$page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
|
||||
$page = $this->pageQueries->findVisibleBySlugsOrFail($bookSlug, $pageSlug);
|
||||
/** @var ?PageRevision $revision */
|
||||
$revision = $page->revisions()->where('id', '=', $revisionId)->first();
|
||||
if ($revision === null) {
|
||||
|
@ -121,7 +123,7 @@ class PageRevisionController extends Controller
|
|||
*/
|
||||
public function restore(string $bookSlug, string $pageSlug, int $revisionId)
|
||||
{
|
||||
$page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
|
||||
$page = $this->pageQueries->findVisibleBySlugsOrFail($bookSlug, $pageSlug);
|
||||
$this->checkOwnablePermission('page-update', $page);
|
||||
|
||||
$page = $this->pageRepo->restoreRevision($page, $revisionId);
|
||||
|
@ -136,7 +138,7 @@ class PageRevisionController extends Controller
|
|||
*/
|
||||
public function destroy(string $bookSlug, string $pageSlug, int $revId)
|
||||
{
|
||||
$page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
|
||||
$page = $this->pageQueries->findVisibleBySlugsOrFail($bookSlug, $pageSlug);
|
||||
$this->checkOwnablePermission('page-delete', $page);
|
||||
|
||||
$revision = $page->revisions()->where('id', '=', $revId)->first();
|
||||
|
@ -162,7 +164,7 @@ class PageRevisionController extends Controller
|
|||
*/
|
||||
public function destroyUserDraft(string $pageId)
|
||||
{
|
||||
$page = $this->pageRepo->getById($pageId);
|
||||
$page = $this->pageQueries->findVisibleByIdOrFail($pageId);
|
||||
$this->revisionRepo->deleteDraftsForCurrentUser($page);
|
||||
|
||||
return response('', 200);
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace BookStack\Entities\Controllers;
|
||||
|
||||
use BookStack\Entities\Queries\PageQueries;
|
||||
use BookStack\Entities\Repos\PageRepo;
|
||||
use BookStack\Exceptions\NotFoundException;
|
||||
use BookStack\Http\Controller;
|
||||
|
@ -9,14 +10,10 @@ use Illuminate\Http\Request;
|
|||
|
||||
class PageTemplateController extends Controller
|
||||
{
|
||||
protected $pageRepo;
|
||||
|
||||
/**
|
||||
* PageTemplateController constructor.
|
||||
*/
|
||||
public function __construct(PageRepo $pageRepo)
|
||||
{
|
||||
$this->pageRepo = $pageRepo;
|
||||
public function __construct(
|
||||
protected PageRepo $pageRepo,
|
||||
protected PageQueries $pageQueries,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -26,7 +23,19 @@ class PageTemplateController extends Controller
|
|||
{
|
||||
$page = $request->get('page', 1);
|
||||
$search = $request->get('search', '');
|
||||
$templates = $this->pageRepo->getTemplates(10, $page, $search);
|
||||
$count = 10;
|
||||
|
||||
$query = $this->pageQueries->visibleTemplates()
|
||||
->orderBy('name', 'asc')
|
||||
->skip(($page - 1) * $count)
|
||||
->take($count);
|
||||
|
||||
if ($search) {
|
||||
$query->where('name', 'like', '%' . $search . '%');
|
||||
}
|
||||
|
||||
$templates = $query->paginate($count, ['*'], 'page', $page);
|
||||
$templates->withPath('/templates');
|
||||
|
||||
if ($search) {
|
||||
$templates->appends(['search' => $search]);
|
||||
|
@ -44,7 +53,7 @@ class PageTemplateController extends Controller
|
|||
*/
|
||||
public function get(int $templateId)
|
||||
{
|
||||
$page = $this->pageRepo->getById($templateId);
|
||||
$page = $this->pageQueries->findVisibleByIdOrFail($templateId);
|
||||
|
||||
if (!$page->template) {
|
||||
throw new NotFoundException();
|
||||
|
|
|
@ -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]));
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,38 +13,9 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|||
* @property int $priority
|
||||
* @property string $book_slug
|
||||
* @property Book $book
|
||||
*
|
||||
* @method Builder whereSlugs(string $bookSlug, string $childSlug)
|
||||
*/
|
||||
abstract class BookChild extends Entity
|
||||
{
|
||||
protected static function boot()
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
// Load book slugs onto these models by default during query-time
|
||||
static::addGlobalScope('book_slug', function (Builder $builder) {
|
||||
$builder->addSelect(['book_slug' => function ($builder) {
|
||||
$builder->select('slug')
|
||||
->from('books')
|
||||
->whereColumn('books.id', '=', 'book_id');
|
||||
}]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope a query to find items where the child has the given childSlug
|
||||
* where its parent has the bookSlug.
|
||||
*/
|
||||
public function scopeWhereSlugs(Builder $query, string $bookSlug, string $childSlug)
|
||||
{
|
||||
return $query->with('book')
|
||||
->whereHas('book', function (Builder $query) use ($bookSlug) {
|
||||
$query->where('slug', '=', $bookSlug);
|
||||
})
|
||||
->where('slug', '=', $childSlug);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the book this page sits in.
|
||||
*/
|
||||
|
|
|
@ -11,7 +11,6 @@ use Illuminate\Support\Collection;
|
|||
* Class Chapter.
|
||||
*
|
||||
* @property Collection<Page> $pages
|
||||
* @property string $description
|
||||
* @property ?int $default_template_id
|
||||
* @property ?Page $defaultTemplate
|
||||
*/
|
||||
|
@ -70,13 +69,4 @@ class Chapter extends BookChild
|
|||
->orderBy('priority', 'asc')
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a visible chapter by its book and page slugs.
|
||||
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
|
||||
*/
|
||||
public static function getBySlugs(string $bookSlug, string $chapterSlug): self
|
||||
{
|
||||
return static::visible()->whereSlugs($bookSlug, $chapterSlug)->firstOrFail();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,9 +32,6 @@ class Page extends BookChild
|
|||
{
|
||||
use HasFactory;
|
||||
|
||||
public static $listAttributes = ['name', 'id', 'slug', 'book_id', 'chapter_id', 'draft', 'template', 'text', 'created_at', 'updated_at', 'priority'];
|
||||
public static $contentAttributes = ['name', 'id', 'slug', 'book_id', 'chapter_id', 'draft', 'template', 'html', 'text', 'created_at', 'updated_at', 'priority'];
|
||||
|
||||
protected $fillable = ['name', 'priority'];
|
||||
|
||||
public string $textField = 'text';
|
||||
|
@ -145,13 +142,4 @@ class Page extends BookChild
|
|||
|
||||
return $refreshed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a visible page by its book and page slugs.
|
||||
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
|
||||
*/
|
||||
public static function getBySlugs(string $bookSlug, string $pageSlug): self
|
||||
{
|
||||
return static::visible()->whereSlugs($bookSlug, $pageSlug)->firstOrFail();
|
||||
}
|
||||
}
|
||||
|
|
72
app/Entities/Queries/BookQueries.php
Normal file
72
app/Entities/Queries/BookQueries.php
Normal file
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
namespace BookStack\Entities\Queries;
|
||||
|
||||
use BookStack\Entities\Models\Book;
|
||||
use BookStack\Exceptions\NotFoundException;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class BookQueries implements ProvidesEntityQueries
|
||||
{
|
||||
protected static array $listAttributes = [
|
||||
'id', 'slug', 'name', 'description',
|
||||
'created_at', 'updated_at', 'image_id', 'owned_by',
|
||||
];
|
||||
|
||||
public function start(): Builder
|
||||
{
|
||||
return Book::query();
|
||||
}
|
||||
|
||||
public function findVisibleById(int $id): ?Book
|
||||
{
|
||||
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 */
|
||||
$book = $this->start()
|
||||
->scopes('visible')
|
||||
->where('slug', '=', $slug)
|
||||
->first();
|
||||
|
||||
if ($book === null) {
|
||||
throw new NotFoundException(trans('errors.book_not_found'));
|
||||
}
|
||||
|
||||
return $book;
|
||||
}
|
||||
|
||||
public function visibleForList(): Builder
|
||||
{
|
||||
return $this->start()->scopes('visible')
|
||||
->select(static::$listAttributes);
|
||||
}
|
||||
|
||||
public function visibleForListWithCover(): Builder
|
||||
{
|
||||
return $this->visibleForList()->with('cover');
|
||||
}
|
||||
|
||||
public function recentlyViewedForCurrentUser(): Builder
|
||||
{
|
||||
return $this->visibleForList()
|
||||
->scopes('withLastView')
|
||||
->having('last_viewed_at', '>', 0)
|
||||
->orderBy('last_viewed_at', 'desc');
|
||||
}
|
||||
|
||||
public function popularForList(): Builder
|
||||
{
|
||||
return $this->visibleForList()
|
||||
->scopes('withViewCount')
|
||||
->having('view_count', '>', 0)
|
||||
->orderBy('view_count', 'desc');
|
||||
}
|
||||
}
|
77
app/Entities/Queries/BookshelfQueries.php
Normal file
77
app/Entities/Queries/BookshelfQueries.php
Normal file
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
namespace BookStack\Entities\Queries;
|
||||
|
||||
use BookStack\Entities\Models\Bookshelf;
|
||||
use BookStack\Exceptions\NotFoundException;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class BookshelfQueries implements ProvidesEntityQueries
|
||||
{
|
||||
protected static array $listAttributes = [
|
||||
'id', 'slug', 'name', 'description',
|
||||
'created_at', 'updated_at', 'image_id', 'owned_by',
|
||||
];
|
||||
|
||||
public function start(): Builder
|
||||
{
|
||||
return Bookshelf::query();
|
||||
}
|
||||
|
||||
public function findVisibleById(int $id): ?Bookshelf
|
||||
{
|
||||
return $this->start()->scopes('visible')->find($id);
|
||||
}
|
||||
|
||||
public function findVisibleByIdOrFail(int $id): Bookshelf
|
||||
{
|
||||
$shelf = $this->findVisibleById($id);
|
||||
|
||||
if (is_null($shelf)) {
|
||||
throw new NotFoundException(trans('errors.bookshelf_not_found'));
|
||||
}
|
||||
|
||||
return $shelf;
|
||||
}
|
||||
|
||||
public function findVisibleBySlugOrFail(string $slug): Bookshelf
|
||||
{
|
||||
/** @var ?Bookshelf $shelf */
|
||||
$shelf = $this->start()
|
||||
->scopes('visible')
|
||||
->where('slug', '=', $slug)
|
||||
->first();
|
||||
|
||||
if ($shelf === null) {
|
||||
throw new NotFoundException(trans('errors.bookshelf_not_found'));
|
||||
}
|
||||
|
||||
return $shelf;
|
||||
}
|
||||
|
||||
public function visibleForList(): Builder
|
||||
{
|
||||
return $this->start()->scopes('visible')->select(static::$listAttributes);
|
||||
}
|
||||
|
||||
public function visibleForListWithCover(): Builder
|
||||
{
|
||||
return $this->visibleForList()->with('cover');
|
||||
}
|
||||
|
||||
public function recentlyViewedForCurrentUser(): Builder
|
||||
{
|
||||
return $this->visibleForList()
|
||||
->scopes('withLastView')
|
||||
->having('last_viewed_at', '>', 0)
|
||||
->orderBy('last_viewed_at', 'desc');
|
||||
}
|
||||
|
||||
public function popularForList(): Builder
|
||||
{
|
||||
return $this->visibleForList()
|
||||
->scopes('withViewCount')
|
||||
->having('view_count', '>', 0)
|
||||
->orderBy('view_count', 'desc');
|
||||
}
|
||||
}
|
69
app/Entities/Queries/ChapterQueries.php
Normal file
69
app/Entities/Queries/ChapterQueries.php
Normal file
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
namespace BookStack\Entities\Queries;
|
||||
|
||||
use BookStack\Entities\Models\Chapter;
|
||||
use BookStack\Exceptions\NotFoundException;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class ChapterQueries implements ProvidesEntityQueries
|
||||
{
|
||||
protected static array $listAttributes = [
|
||||
'id', 'slug', 'name', 'description', 'priority',
|
||||
'book_id', 'created_at', 'updated_at', 'owned_by',
|
||||
];
|
||||
|
||||
public function start(): Builder
|
||||
{
|
||||
return Chapter::query();
|
||||
}
|
||||
|
||||
public function findVisibleById(int $id): ?Chapter
|
||||
{
|
||||
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()
|
||||
->scopes('visible')
|
||||
->with('book')
|
||||
->whereHas('book', function (Builder $query) use ($bookSlug) {
|
||||
$query->where('slug', '=', $bookSlug);
|
||||
})
|
||||
->where('slug', '=', $chapterSlug)
|
||||
->first();
|
||||
|
||||
if (is_null($chapter)) {
|
||||
throw new NotFoundException(trans('errors.chapter_not_found'));
|
||||
}
|
||||
|
||||
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')
|
||||
->whereColumn('books.id', '=', 'chapters.book_id');
|
||||
}]));
|
||||
}
|
||||
}
|
62
app/Entities/Queries/EntityQueries.php
Normal file
62
app/Entities/Queries/EntityQueries.php
Normal file
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
namespace BookStack\Entities\Queries;
|
||||
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class EntityQueries
|
||||
{
|
||||
public function __construct(
|
||||
public BookshelfQueries $shelves,
|
||||
public BookQueries $books,
|
||||
public ChapterQueries $chapters,
|
||||
public PageQueries $pages,
|
||||
public PageRevisionQueries $revisions,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an entity via an identifier string in the format:
|
||||
* {type}:{id}
|
||||
* Example: (book:5).
|
||||
*/
|
||||
public function findVisibleByStringIdentifier(string $identifier): ?Entity
|
||||
{
|
||||
$explodedId = explode(':', $identifier);
|
||||
$entityType = $explodedId[0];
|
||||
$entityId = intval($explodedId[1]);
|
||||
$queries = $this->getQueriesForType($entityType);
|
||||
|
||||
return $queries->findVisibleById($entityId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a query of visible entities of the given type,
|
||||
* suitable for listing display.
|
||||
*/
|
||||
public function visibleForList(string $entityType): Builder
|
||||
{
|
||||
$queries = $this->getQueriesForType($entityType);
|
||||
return $queries->visibleForList();
|
||||
}
|
||||
|
||||
protected function getQueriesForType(string $type): ProvidesEntityQueries
|
||||
{
|
||||
/** @var ?ProvidesEntityQueries $queries */
|
||||
$queries = match ($type) {
|
||||
'page' => $this->pages,
|
||||
'chapter' => $this->chapters,
|
||||
'book' => $this->books,
|
||||
'bookshelf' => $this->shelves,
|
||||
default => null,
|
||||
};
|
||||
|
||||
if (is_null($queries)) {
|
||||
throw new InvalidArgumentException("No entity query class configured for {$type}");
|
||||
}
|
||||
|
||||
return $queries;
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace BookStack\Entities\Queries;
|
||||
|
||||
use BookStack\Entities\EntityProvider;
|
||||
use BookStack\Permissions\PermissionApplicator;
|
||||
|
||||
abstract class EntityQuery
|
||||
{
|
||||
protected function permissionService(): PermissionApplicator
|
||||
{
|
||||
return app()->make(PermissionApplicator::class);
|
||||
}
|
||||
|
||||
protected function entityProvider(): EntityProvider
|
||||
{
|
||||
return app()->make(EntityProvider::class);
|
||||
}
|
||||
}
|
112
app/Entities/Queries/PageQueries.php
Normal file
112
app/Entities/Queries/PageQueries.php
Normal file
|
@ -0,0 +1,112 @@
|
|||
<?php
|
||||
|
||||
namespace BookStack\Entities\Queries;
|
||||
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Exceptions\NotFoundException;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class PageQueries implements ProvidesEntityQueries
|
||||
{
|
||||
protected static array $contentAttributes = [
|
||||
'name', 'id', 'slug', 'book_id', 'chapter_id', 'draft',
|
||||
'template', 'html', 'text', 'created_at', 'updated_at', 'priority',
|
||||
'created_by', 'updated_by', 'owned_by',
|
||||
];
|
||||
protected static array $listAttributes = [
|
||||
'name', 'id', 'slug', 'book_id', 'chapter_id', 'draft',
|
||||
'template', 'text', 'created_at', 'updated_at', 'priority', 'owned_by',
|
||||
];
|
||||
|
||||
public function start(): Builder
|
||||
{
|
||||
return Page::query();
|
||||
}
|
||||
|
||||
public function findVisibleById(int $id): ?Page
|
||||
{
|
||||
return $this->start()->scopes('visible')->find($id);
|
||||
}
|
||||
|
||||
public function findVisibleByIdOrFail(int $id): Page
|
||||
{
|
||||
$page = $this->findVisibleById($id);
|
||||
|
||||
if (is_null($page)) {
|
||||
throw new NotFoundException(trans('errors.page_not_found'));
|
||||
}
|
||||
|
||||
return $page;
|
||||
}
|
||||
|
||||
public function findVisibleBySlugsOrFail(string $bookSlug, string $pageSlug): Page
|
||||
{
|
||||
/** @var ?Page $page */
|
||||
$page = $this->start()->with('book')
|
||||
->scopes('visible')
|
||||
->whereHas('book', function (Builder $query) use ($bookSlug) {
|
||||
$query->where('slug', '=', $bookSlug);
|
||||
})
|
||||
->where('slug', '=', $pageSlug)
|
||||
->first();
|
||||
|
||||
if (is_null($page)) {
|
||||
throw new NotFoundException(trans('errors.page_not_found'));
|
||||
}
|
||||
|
||||
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($this->mergeBookSlugForSelect(static::$listAttributes));
|
||||
}
|
||||
|
||||
public function visibleForChapterList(int $chapterId): Builder
|
||||
{
|
||||
return $this->visibleForList()
|
||||
->where('chapter_id', '=', $chapterId)
|
||||
->orderBy('draft', 'desc')
|
||||
->orderBy('priority', 'asc');
|
||||
}
|
||||
|
||||
public function visibleWithContents(): Builder
|
||||
{
|
||||
return $this->start()
|
||||
->scopes('visible')
|
||||
->select($this->mergeBookSlugForSelect(static::$contentAttributes));
|
||||
}
|
||||
|
||||
public function currentUserDraftsForList(): Builder
|
||||
{
|
||||
return $this->visibleForList()
|
||||
->where('draft', '=', true)
|
||||
->where('created_by', '=', user()->id);
|
||||
}
|
||||
|
||||
public function visibleTemplates(): Builder
|
||||
{
|
||||
return $this->visibleForList()
|
||||
->where('template', '=', true);
|
||||
}
|
||||
|
||||
protected function mergeBookSlugForSelect(array $columns): array
|
||||
{
|
||||
return array_merge($columns, ['book_slug' => function ($builder) {
|
||||
$builder->select('slug')
|
||||
->from('books')
|
||||
->whereColumn('books.id', '=', 'pages.book_id');
|
||||
}]);
|
||||
}
|
||||
}
|
44
app/Entities/Queries/PageRevisionQueries.php
Normal file
44
app/Entities/Queries/PageRevisionQueries.php
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace BookStack\Entities\Queries;
|
||||
|
||||
use BookStack\Entities\Models\PageRevision;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class PageRevisionQueries
|
||||
{
|
||||
public function start(): Builder
|
||||
{
|
||||
return PageRevision::query();
|
||||
}
|
||||
|
||||
public function findLatestVersionBySlugs(string $bookSlug, string $pageSlug): ?PageRevision
|
||||
{
|
||||
return PageRevision::query()
|
||||
->whereHas('page', function (Builder $query) {
|
||||
$query->scopes('visible');
|
||||
})
|
||||
->where('slug', '=', $pageSlug)
|
||||
->where('type', '=', 'version')
|
||||
->where('book_slug', '=', $bookSlug)
|
||||
->orderBy('created_at', 'desc')
|
||||
->first();
|
||||
}
|
||||
|
||||
public function findLatestCurrentUserDraftsForPageId(int $pageId): ?PageRevision
|
||||
{
|
||||
/** @var ?PageRevision $revision */
|
||||
$revision = $this->latestCurrentUserDraftsForPageId($pageId)->first();
|
||||
|
||||
return $revision;
|
||||
}
|
||||
|
||||
public function latestCurrentUserDraftsForPageId(int $pageId): Builder
|
||||
{
|
||||
return $this->start()
|
||||
->where('created_by', '=', user()->id)
|
||||
->where('type', 'update_draft')
|
||||
->where('page_id', '=', $pageId)
|
||||
->orderBy('created_at', 'desc');
|
||||
}
|
||||
}
|
34
app/Entities/Queries/ProvidesEntityQueries.php
Normal file
34
app/Entities/Queries/ProvidesEntityQueries.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace BookStack\Entities\Queries;
|
||||
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
/**
|
||||
* Interface for our classes which provide common queries for our
|
||||
* entity objects. Ideally all queries for entities should run through
|
||||
* these classes.
|
||||
* Any added methods should return a builder instances to allow extension
|
||||
* via building on the query, unless the method starts with 'find'
|
||||
* in which case an entity object should be returned.
|
||||
* (nullable unless it's a *OrFail method).
|
||||
*/
|
||||
interface ProvidesEntityQueries
|
||||
{
|
||||
/**
|
||||
* Start a new query for this entity type.
|
||||
*/
|
||||
public function start(): Builder;
|
||||
|
||||
/**
|
||||
* Find the entity of the given ID, or return null if not found.
|
||||
*/
|
||||
public function findVisibleById(int $id): ?Entity;
|
||||
|
||||
/**
|
||||
* Start a query for items that are visible, with selection
|
||||
* configured for list display of this item.
|
||||
*/
|
||||
public function visibleForList(): Builder;
|
||||
}
|
|
@ -3,24 +3,32 @@
|
|||
namespace BookStack\Entities\Queries;
|
||||
|
||||
use BookStack\Activity\Models\View;
|
||||
use BookStack\Entities\EntityProvider;
|
||||
use BookStack\Entities\Models\BookChild;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Permissions\PermissionApplicator;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class Popular extends EntityQuery
|
||||
class QueryPopular
|
||||
{
|
||||
public function __construct(
|
||||
protected PermissionApplicator $permissions,
|
||||
protected EntityProvider $entityProvider,
|
||||
) {
|
||||
}
|
||||
|
||||
public function run(int $count, int $page, array $filterModels = null)
|
||||
{
|
||||
$query = $this->permissionService()
|
||||
$query = $this->permissions
|
||||
->restrictEntityRelationQuery(View::query(), 'views', 'viewable_id', 'viewable_type')
|
||||
->select('*', 'viewable_id', 'viewable_type', DB::raw('SUM(views) as view_count'))
|
||||
->groupBy('viewable_id', 'viewable_type')
|
||||
->orderBy('view_count', 'desc');
|
||||
|
||||
if ($filterModels) {
|
||||
$query->whereIn('viewable_type', $this->entityProvider()->getMorphClasses($filterModels));
|
||||
$query->whereIn('viewable_type', $this->entityProvider->getMorphClasses($filterModels));
|
||||
}
|
||||
|
||||
$entities = $query->with('viewable')
|
||||
|
@ -35,7 +43,7 @@ class Popular extends EntityQuery
|
|||
return $entities;
|
||||
}
|
||||
|
||||
protected function loadBooksForChildren(Collection $entities)
|
||||
protected function loadBooksForChildren(Collection $entities): void
|
||||
{
|
||||
$bookChildren = $entities->filter(fn(Entity $entity) => $entity instanceof BookChild);
|
||||
$eloquent = (new \Illuminate\Database\Eloquent\Collection($bookChildren));
|
43
app/Entities/Queries/QueryRecentlyViewed.php
Normal file
43
app/Entities/Queries/QueryRecentlyViewed.php
Normal file
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace BookStack\Entities\Queries;
|
||||
|
||||
use BookStack\Activity\Models\View;
|
||||
use BookStack\Entities\Tools\MixedEntityListLoader;
|
||||
use BookStack\Permissions\PermissionApplicator;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class QueryRecentlyViewed
|
||||
{
|
||||
public function __construct(
|
||||
protected PermissionApplicator $permissions,
|
||||
protected MixedEntityListLoader $listLoader,
|
||||
) {
|
||||
}
|
||||
|
||||
public function run(int $count, int $page): Collection
|
||||
{
|
||||
$user = user();
|
||||
if ($user->isGuest()) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
$query = $this->permissions->restrictEntityRelationQuery(
|
||||
View::query(),
|
||||
'views',
|
||||
'viewable_id',
|
||||
'viewable_type'
|
||||
)
|
||||
->orderBy('views.updated_at', 'desc')
|
||||
->where('user_id', '=', user()->id);
|
||||
|
||||
$views = $query
|
||||
->skip(($page - 1) * $count)
|
||||
->take($count)
|
||||
->get();
|
||||
|
||||
$this->listLoader->loadIntoRelations($views->all(), 'viewable', false);
|
||||
|
||||
return $views->pluck('viewable')->filter();
|
||||
}
|
||||
}
|
|
@ -3,10 +3,18 @@
|
|||
namespace BookStack\Entities\Queries;
|
||||
|
||||
use BookStack\Activity\Models\Favourite;
|
||||
use BookStack\Entities\Tools\MixedEntityListLoader;
|
||||
use BookStack\Permissions\PermissionApplicator;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
|
||||
class TopFavourites extends EntityQuery
|
||||
class QueryTopFavourites
|
||||
{
|
||||
public function __construct(
|
||||
protected PermissionApplicator $permissions,
|
||||
protected MixedEntityListLoader $listLoader,
|
||||
) {
|
||||
}
|
||||
|
||||
public function run(int $count, int $skip = 0)
|
||||
{
|
||||
$user = user();
|
||||
|
@ -14,7 +22,7 @@ class TopFavourites extends EntityQuery
|
|||
return collect();
|
||||
}
|
||||
|
||||
$query = $this->permissionService()
|
||||
$query = $this->permissions
|
||||
->restrictEntityRelationQuery(Favourite::query(), 'favourites', 'favouritable_id', 'favouritable_type')
|
||||
->select('favourites.*')
|
||||
->leftJoin('views', function (JoinClause $join) {
|
||||
|
@ -25,11 +33,13 @@ class TopFavourites extends EntityQuery
|
|||
->orderBy('views.views', 'desc')
|
||||
->where('favourites.user_id', '=', user()->id);
|
||||
|
||||
return $query->with('favouritable')
|
||||
$favourites = $query
|
||||
->skip($skip)
|
||||
->take($count)
|
||||
->get()
|
||||
->pluck('favouritable')
|
||||
->filter();
|
||||
->get();
|
||||
|
||||
$this->listLoader->loadIntoRelations($favourites->all(), 'favouritable', false);
|
||||
|
||||
return $favourites->pluck('favouritable')->filter();
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace BookStack\Entities\Queries;
|
||||
|
||||
use BookStack\Activity\Models\View;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class RecentlyViewed extends EntityQuery
|
||||
{
|
||||
public function run(int $count, int $page): Collection
|
||||
{
|
||||
$user = user();
|
||||
if ($user === null || $user->isGuest()) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
$query = $this->permissionService()->restrictEntityRelationQuery(
|
||||
View::query(),
|
||||
'views',
|
||||
'viewable_id',
|
||||
'viewable_type'
|
||||
)
|
||||
->orderBy('views.updated_at', 'desc')
|
||||
->where('user_id', '=', user()->id);
|
||||
|
||||
return $query->with('viewable')
|
||||
->skip(($page - 1) * $count)
|
||||
->take($count)
|
||||
->get()
|
||||
->pluck('viewable')
|
||||
->filter();
|
||||
}
|
||||
}
|
|
@ -8,7 +8,7 @@ use BookStack\Entities\Models\Chapter;
|
|||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Entities\Models\HasCoverImage;
|
||||
use BookStack\Entities\Models\HasHtmlDescription;
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Entities\Queries\PageQueries;
|
||||
use BookStack\Exceptions\ImageUploadException;
|
||||
use BookStack\References\ReferenceStore;
|
||||
use BookStack\References\ReferenceUpdater;
|
||||
|
@ -23,6 +23,7 @@ class BaseRepo
|
|||
protected ImageRepo $imageRepo,
|
||||
protected ReferenceUpdater $referenceUpdater,
|
||||
protected ReferenceStore $referenceStore,
|
||||
protected PageQueries $pageQueries,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -125,8 +126,7 @@ class BaseRepo
|
|||
return;
|
||||
}
|
||||
|
||||
$templateExists = Page::query()->visible()
|
||||
->where('template', '=', true)
|
||||
$templateExists = $this->pageQueries->visibleTemplates()
|
||||
->where('id', '=', $templateId)
|
||||
->exists();
|
||||
|
||||
|
|
|
@ -5,79 +5,23 @@ namespace BookStack\Entities\Repos;
|
|||
use BookStack\Activity\ActivityType;
|
||||
use BookStack\Activity\TagRepo;
|
||||
use BookStack\Entities\Models\Book;
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Entities\Tools\TrashCan;
|
||||
use BookStack\Exceptions\ImageUploadException;
|
||||
use BookStack\Exceptions\NotFoundException;
|
||||
use BookStack\Facades\Activity;
|
||||
use BookStack\Uploads\ImageRepo;
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class BookRepo
|
||||
{
|
||||
public function __construct(
|
||||
protected BaseRepo $baseRepo,
|
||||
protected TagRepo $tagRepo,
|
||||
protected ImageRepo $imageRepo
|
||||
protected ImageRepo $imageRepo,
|
||||
protected TrashCan $trashCan,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all books in a paginated format.
|
||||
*/
|
||||
public function getAllPaginated(int $count = 20, string $sort = 'name', string $order = 'asc'): LengthAwarePaginator
|
||||
{
|
||||
return Book::visible()->with('cover')->orderBy($sort, $order)->paginate($count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the books that were most recently viewed by this user.
|
||||
*/
|
||||
public function getRecentlyViewed(int $count = 20): Collection
|
||||
{
|
||||
return Book::visible()->withLastView()
|
||||
->having('last_viewed_at', '>', 0)
|
||||
->orderBy('last_viewed_at', 'desc')
|
||||
->take($count)->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the most popular books in the system.
|
||||
*/
|
||||
public function getPopular(int $count = 20): Collection
|
||||
{
|
||||
return Book::visible()->withViewCount()
|
||||
->having('view_count', '>', 0)
|
||||
->orderBy('view_count', 'desc')
|
||||
->take($count)->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the most recently created books from the system.
|
||||
*/
|
||||
public function getRecentlyCreated(int $count = 20): Collection
|
||||
{
|
||||
return Book::visible()->orderBy('created_at', 'desc')
|
||||
->take($count)->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a book by its slug.
|
||||
*/
|
||||
public function getBySlug(string $slug): Book
|
||||
{
|
||||
$book = Book::visible()->where('slug', '=', $slug)->first();
|
||||
|
||||
if ($book === null) {
|
||||
throw new NotFoundException(trans('errors.book_not_found'));
|
||||
}
|
||||
|
||||
return $book;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new book in the system.
|
||||
*/
|
||||
|
@ -130,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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,81 +3,19 @@
|
|||
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\Exceptions\NotFoundException;
|
||||
use BookStack\Facades\Activity;
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class BookshelfRepo
|
||||
{
|
||||
protected $baseRepo;
|
||||
|
||||
/**
|
||||
* BookshelfRepo constructor.
|
||||
*/
|
||||
public function __construct(BaseRepo $baseRepo)
|
||||
{
|
||||
$this->baseRepo = $baseRepo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all bookshelves in a paginated format.
|
||||
*/
|
||||
public function getAllPaginated(int $count = 20, string $sort = 'name', string $order = 'asc'): LengthAwarePaginator
|
||||
{
|
||||
return Bookshelf::visible()
|
||||
->with(['visibleBooks', 'cover'])
|
||||
->orderBy($sort, $order)
|
||||
->paginate($count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bookshelves that were most recently viewed by this user.
|
||||
*/
|
||||
public function getRecentlyViewed(int $count = 20): Collection
|
||||
{
|
||||
return Bookshelf::visible()->withLastView()
|
||||
->having('last_viewed_at', '>', 0)
|
||||
->orderBy('last_viewed_at', 'desc')
|
||||
->take($count)->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the most popular bookshelves in the system.
|
||||
*/
|
||||
public function getPopular(int $count = 20): Collection
|
||||
{
|
||||
return Bookshelf::visible()->withViewCount()
|
||||
->having('view_count', '>', 0)
|
||||
->orderBy('view_count', 'desc')
|
||||
->take($count)->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the most recently created bookshelves from the system.
|
||||
*/
|
||||
public function getRecentlyCreated(int $count = 20): Collection
|
||||
{
|
||||
return Bookshelf::visible()->orderBy('created_at', 'desc')
|
||||
->take($count)->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a shelf by its slug.
|
||||
*/
|
||||
public function getBySlug(string $slug): Bookshelf
|
||||
{
|
||||
$shelf = Bookshelf::visible()->where('slug', '=', $slug)->first();
|
||||
|
||||
if ($shelf === null) {
|
||||
throw new NotFoundException(trans('errors.bookshelf_not_found'));
|
||||
}
|
||||
|
||||
return $shelf;
|
||||
public function __construct(
|
||||
protected BaseRepo $baseRepo,
|
||||
protected BookQueries $bookQueries,
|
||||
protected TrashCan $trashCan,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -124,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) {
|
||||
|
@ -141,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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,12 +4,11 @@ namespace BookStack\Entities\Repos;
|
|||
|
||||
use BookStack\Activity\ActivityType;
|
||||
use BookStack\Entities\Models\Book;
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Entities\Models\Chapter;
|
||||
use BookStack\Entities\Queries\EntityQueries;
|
||||
use BookStack\Entities\Tools\BookContents;
|
||||
use BookStack\Entities\Tools\TrashCan;
|
||||
use BookStack\Exceptions\MoveOperationException;
|
||||
use BookStack\Exceptions\NotFoundException;
|
||||
use BookStack\Exceptions\PermissionsException;
|
||||
use BookStack\Facades\Activity;
|
||||
use Exception;
|
||||
|
@ -17,26 +16,12 @@ use Exception;
|
|||
class ChapterRepo
|
||||
{
|
||||
public function __construct(
|
||||
protected BaseRepo $baseRepo
|
||||
protected BaseRepo $baseRepo,
|
||||
protected EntityQueries $entityQueries,
|
||||
protected TrashCan $trashCan,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a chapter via the slug.
|
||||
*
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function getBySlug(string $bookSlug, string $chapterSlug): Chapter
|
||||
{
|
||||
$chapter = Chapter::visible()->whereSlugs($bookSlug, $chapterSlug)->first();
|
||||
|
||||
if ($chapter === null) {
|
||||
throw new NotFoundException(trans('errors.chapter_not_found'));
|
||||
}
|
||||
|
||||
return $chapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new chapter in the system.
|
||||
*/
|
||||
|
@ -75,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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -91,8 +75,8 @@ class ChapterRepo
|
|||
*/
|
||||
public function move(Chapter $chapter, string $parentIdentifier): Book
|
||||
{
|
||||
$parent = $this->findParentByIdentifier($parentIdentifier);
|
||||
if (is_null($parent)) {
|
||||
$parent = $this->entityQueries->findVisibleByStringIdentifier($parentIdentifier);
|
||||
if (!$parent instanceof Book) {
|
||||
throw new MoveOperationException('Book to move chapter into not found');
|
||||
}
|
||||
|
||||
|
@ -106,24 +90,4 @@ class ChapterRepo
|
|||
|
||||
return $parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a page parent entity via an identifier string in the format:
|
||||
* {type}:{id}
|
||||
* Example: (book:5).
|
||||
*
|
||||
* @throws MoveOperationException
|
||||
*/
|
||||
public function findParentByIdentifier(string $identifier): ?Book
|
||||
{
|
||||
$stringExploded = explode(':', $identifier);
|
||||
$entityType = $stringExploded[0];
|
||||
$entityId = intval($stringExploded[1]);
|
||||
|
||||
if ($entityType !== 'book') {
|
||||
throw new MoveOperationException('Chapters can only be in books');
|
||||
}
|
||||
|
||||
return Book::visible()->where('id', '=', $entityId)->first();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,114 +8,30 @@ use BookStack\Entities\Models\Chapter;
|
|||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Entities\Models\PageRevision;
|
||||
use BookStack\Entities\Queries\EntityQueries;
|
||||
use BookStack\Entities\Tools\BookContents;
|
||||
use BookStack\Entities\Tools\PageContent;
|
||||
use BookStack\Entities\Tools\PageEditorData;
|
||||
use BookStack\Entities\Tools\TrashCan;
|
||||
use BookStack\Exceptions\MoveOperationException;
|
||||
use BookStack\Exceptions\NotFoundException;
|
||||
use BookStack\Exceptions\PermissionsException;
|
||||
use BookStack\Facades\Activity;
|
||||
use BookStack\References\ReferenceStore;
|
||||
use BookStack\References\ReferenceUpdater;
|
||||
use Exception;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
|
||||
class PageRepo
|
||||
{
|
||||
public function __construct(
|
||||
protected BaseRepo $baseRepo,
|
||||
protected RevisionRepo $revisionRepo,
|
||||
protected EntityQueries $entityQueries,
|
||||
protected ReferenceStore $referenceStore,
|
||||
protected ReferenceUpdater $referenceUpdater
|
||||
protected ReferenceUpdater $referenceUpdater,
|
||||
protected TrashCan $trashCan,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a page by ID.
|
||||
*
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function getById(int $id, array $relations = ['book']): Page
|
||||
{
|
||||
/** @var Page $page */
|
||||
$page = Page::visible()->with($relations)->find($id);
|
||||
|
||||
if (!$page) {
|
||||
throw new NotFoundException(trans('errors.page_not_found'));
|
||||
}
|
||||
|
||||
return $page;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a page its book and own slug.
|
||||
*
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function getBySlug(string $bookSlug, string $pageSlug): Page
|
||||
{
|
||||
$page = Page::visible()->whereSlugs($bookSlug, $pageSlug)->first();
|
||||
|
||||
if (!$page) {
|
||||
throw new NotFoundException(trans('errors.page_not_found'));
|
||||
}
|
||||
|
||||
return $page;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a page by its old slug but checking the revisions table
|
||||
* for the last revision that matched the given page and book slug.
|
||||
*/
|
||||
public function getByOldSlug(string $bookSlug, string $pageSlug): ?Page
|
||||
{
|
||||
$revision = $this->revisionRepo->getBySlugs($bookSlug, $pageSlug);
|
||||
|
||||
return $revision->page ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get pages that have been marked as a template.
|
||||
*/
|
||||
public function getTemplates(int $count = 10, int $page = 1, string $search = ''): LengthAwarePaginator
|
||||
{
|
||||
$query = Page::visible()
|
||||
->where('template', '=', true)
|
||||
->orderBy('name', 'asc')
|
||||
->skip(($page - 1) * $count)
|
||||
->take($count);
|
||||
|
||||
if ($search) {
|
||||
$query->where('name', 'like', '%' . $search . '%');
|
||||
}
|
||||
|
||||
$paginator = $query->paginate($count, ['*'], 'page', $page);
|
||||
$paginator->withPath('/templates');
|
||||
|
||||
return $paginator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a parent item via slugs.
|
||||
*/
|
||||
public function getParentFromSlugs(string $bookSlug, string $chapterSlug = null): Entity
|
||||
{
|
||||
if ($chapterSlug !== null) {
|
||||
return Chapter::visible()->whereSlugs($bookSlug, $chapterSlug)->firstOrFail();
|
||||
}
|
||||
|
||||
return Book::visible()->where('slug', '=', $bookSlug)->firstOrFail();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the draft copy of the given page for the current user.
|
||||
*/
|
||||
public function getUserDraft(Page $page): ?PageRevision
|
||||
{
|
||||
return $this->revisionRepo->getLatestDraftForCurrentUser($page);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new draft page belonging to the given parent entity.
|
||||
*/
|
||||
|
@ -269,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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -324,8 +239,8 @@ class PageRepo
|
|||
*/
|
||||
public function move(Page $page, string $parentIdentifier): Entity
|
||||
{
|
||||
$parent = $this->findParentByIdentifier($parentIdentifier);
|
||||
if (is_null($parent)) {
|
||||
$parent = $this->entityQueries->findVisibleByStringIdentifier($parentIdentifier);
|
||||
if (!$parent instanceof Chapter && !$parent instanceof Book) {
|
||||
throw new MoveOperationException('Book or chapter to move page into not found');
|
||||
}
|
||||
|
||||
|
@ -343,28 +258,6 @@ class PageRepo
|
|||
return $parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a page parent entity via an identifier string in the format:
|
||||
* {type}:{id}
|
||||
* Example: (book:5).
|
||||
*
|
||||
* @throws MoveOperationException
|
||||
*/
|
||||
public function findParentByIdentifier(string $identifier): ?Entity
|
||||
{
|
||||
$stringExploded = explode(':', $identifier);
|
||||
$entityType = $stringExploded[0];
|
||||
$entityId = intval($stringExploded[1]);
|
||||
|
||||
if ($entityType !== 'book' && $entityType !== 'chapter') {
|
||||
throw new MoveOperationException('Pages can only be in books or chapters');
|
||||
}
|
||||
|
||||
$parentClass = $entityType === 'book' ? Book::class : Chapter::class;
|
||||
|
||||
return $parentClass::visible()->where('id', '=', $entityId)->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new priority for a page.
|
||||
*/
|
||||
|
|
|
@ -4,39 +4,13 @@ namespace BookStack\Entities\Repos;
|
|||
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Entities\Models\PageRevision;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use BookStack\Entities\Queries\PageRevisionQueries;
|
||||
|
||||
class RevisionRepo
|
||||
{
|
||||
/**
|
||||
* Get a revision by its stored book and page slug values.
|
||||
*/
|
||||
public function getBySlugs(string $bookSlug, string $pageSlug): ?PageRevision
|
||||
{
|
||||
/** @var ?PageRevision $revision */
|
||||
$revision = PageRevision::query()
|
||||
->whereHas('page', function (Builder $query) {
|
||||
$query->scopes('visible');
|
||||
})
|
||||
->where('slug', '=', $pageSlug)
|
||||
->where('type', '=', 'version')
|
||||
->where('book_slug', '=', $bookSlug)
|
||||
->orderBy('created_at', 'desc')
|
||||
->with('page')
|
||||
->first();
|
||||
|
||||
return $revision;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latest draft revision, for the given page, belonging to the current user.
|
||||
*/
|
||||
public function getLatestDraftForCurrentUser(Page $page): ?PageRevision
|
||||
{
|
||||
/** @var ?PageRevision $revision */
|
||||
$revision = $this->queryForCurrentUserDraft($page->id)->first();
|
||||
|
||||
return $revision;
|
||||
public function __construct(
|
||||
protected PageRevisionQueries $queries,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -44,7 +18,7 @@ class RevisionRepo
|
|||
*/
|
||||
public function deleteDraftsForCurrentUser(Page $page): void
|
||||
{
|
||||
$this->queryForCurrentUserDraft($page->id)->delete();
|
||||
$this->queries->latestCurrentUserDraftsForPageId($page->id)->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -53,7 +27,7 @@ class RevisionRepo
|
|||
*/
|
||||
public function getNewDraftForCurrentUser(Page $page): PageRevision
|
||||
{
|
||||
$draft = $this->getLatestDraftForCurrentUser($page);
|
||||
$draft = $this->queries->findLatestCurrentUserDraftsForPageId($page->id);
|
||||
|
||||
if ($draft) {
|
||||
return $draft;
|
||||
|
@ -116,16 +90,4 @@ class RevisionRepo
|
|||
PageRevision::query()->whereIn('id', $revisionsToDelete->pluck('id'))->delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Query update draft revisions for the current user.
|
||||
*/
|
||||
protected function queryForCurrentUserDraft(int $pageId): Builder
|
||||
{
|
||||
return PageRevision::query()
|
||||
->where('created_by', '=', user()->id)
|
||||
->where('type', 'update_draft')
|
||||
->where('page_id', '=', $pageId)
|
||||
->orderBy('created_at', 'desc');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -3,20 +3,13 @@
|
|||
namespace BookStack\Entities\Tools;
|
||||
|
||||
use BookStack\App\Model;
|
||||
use BookStack\Entities\EntityProvider;
|
||||
use BookStack\Entities\Queries\EntityQueries;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
|
||||
class MixedEntityListLoader
|
||||
{
|
||||
protected array $listAttributes = [
|
||||
'page' => ['id', 'name', 'slug', 'book_id', 'chapter_id', 'text', 'draft'],
|
||||
'chapter' => ['id', 'name', 'slug', 'book_id', 'description'],
|
||||
'book' => ['id', 'name', 'slug', 'description'],
|
||||
'bookshelf' => ['id', 'name', 'slug', 'description'],
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
protected EntityProvider $entityProvider
|
||||
protected EntityQueries $queries,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -26,7 +19,7 @@ class MixedEntityListLoader
|
|||
* This will look for a model id and type via 'name_id' and 'name_type'.
|
||||
* @param Model[] $relations
|
||||
*/
|
||||
public function loadIntoRelations(array $relations, string $relationName): void
|
||||
public function loadIntoRelations(array $relations, string $relationName, bool $loadParents): void
|
||||
{
|
||||
$idsByType = [];
|
||||
foreach ($relations as $relation) {
|
||||
|
@ -40,7 +33,7 @@ class MixedEntityListLoader
|
|||
$idsByType[$type][] = $id;
|
||||
}
|
||||
|
||||
$modelMap = $this->idsByTypeToModelMap($idsByType);
|
||||
$modelMap = $this->idsByTypeToModelMap($idsByType, $loadParents);
|
||||
|
||||
foreach ($relations as $relation) {
|
||||
$type = $relation->getAttribute($relationName . '_type');
|
||||
|
@ -56,21 +49,14 @@ class MixedEntityListLoader
|
|||
* @param array<string, int[]> $idsByType
|
||||
* @return array<string, array<int, Model>>
|
||||
*/
|
||||
protected function idsByTypeToModelMap(array $idsByType): array
|
||||
protected function idsByTypeToModelMap(array $idsByType, bool $eagerLoadParents): array
|
||||
{
|
||||
$modelMap = [];
|
||||
|
||||
foreach ($idsByType as $type => $ids) {
|
||||
if (!isset($this->listAttributes[$type])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$instance = $this->entityProvider->get($type);
|
||||
$models = $instance->newQuery()
|
||||
->select($this->listAttributes[$type])
|
||||
->scopes('visible')
|
||||
$models = $this->queries->visibleForList($type)
|
||||
->whereIn('id', $ids)
|
||||
->with($this->getRelationsToEagerLoad($type))
|
||||
->with($eagerLoadParents ? $this->getRelationsToEagerLoad($type) : [])
|
||||
->get();
|
||||
|
||||
if (count($models) > 0) {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace BookStack\Entities\Tools;
|
||||
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Entities\Queries\PageQueries;
|
||||
use BookStack\Entities\Tools\Markdown\MarkdownToHtml;
|
||||
use BookStack\Exceptions\ImageUploadException;
|
||||
use BookStack\Facades\Theme;
|
||||
|
@ -21,9 +22,12 @@ use Illuminate\Support\Str;
|
|||
|
||||
class PageContent
|
||||
{
|
||||
protected PageQueries $pageQueries;
|
||||
|
||||
public function __construct(
|
||||
protected Page $page
|
||||
) {
|
||||
$this->pageQueries = app()->make(PageQueries::class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -325,13 +329,14 @@ class PageContent
|
|||
protected function getContentProviderClosure(bool $blankIncludes): Closure
|
||||
{
|
||||
$contextPage = $this->page;
|
||||
$queries = $this->pageQueries;
|
||||
|
||||
return function (PageIncludeTag $tag) use ($blankIncludes, $contextPage): PageIncludeContent {
|
||||
return function (PageIncludeTag $tag) use ($blankIncludes, $contextPage, $queries): PageIncludeContent {
|
||||
if ($blankIncludes) {
|
||||
return PageIncludeContent::fromHtmlAndTag('', $tag);
|
||||
}
|
||||
|
||||
$matchedPage = Page::visible()->find($tag->getPageId());
|
||||
$matchedPage = $queries->findVisibleById($tag->getPageId());
|
||||
$content = PageIncludeContent::fromHtmlAndTag($matchedPage->html ?? '', $tag);
|
||||
|
||||
if (Theme::hasListeners(ThemeEvents::PAGE_INCLUDE_PARSE)) {
|
||||
|
|
|
@ -4,7 +4,7 @@ namespace BookStack\Entities\Tools;
|
|||
|
||||
use BookStack\Activity\Tools\CommentTree;
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Entities\Repos\PageRepo;
|
||||
use BookStack\Entities\Queries\EntityQueries;
|
||||
use BookStack\Entities\Tools\Markdown\HtmlToMarkdown;
|
||||
use BookStack\Entities\Tools\Markdown\MarkdownToHtml;
|
||||
|
||||
|
@ -15,7 +15,7 @@ class PageEditorData
|
|||
|
||||
public function __construct(
|
||||
protected Page $page,
|
||||
protected PageRepo $pageRepo,
|
||||
protected EntityQueries $queries,
|
||||
protected string $requestedEditor
|
||||
) {
|
||||
$this->viewData = $this->build();
|
||||
|
@ -35,7 +35,12 @@ class PageEditorData
|
|||
{
|
||||
$page = clone $this->page;
|
||||
$isDraft = boolval($this->page->draft);
|
||||
$templates = $this->pageRepo->getTemplates(10);
|
||||
$templates = $this->queries->pages->visibleTemplates()
|
||||
->orderBy('name', 'asc')
|
||||
->take(10)
|
||||
->paginate()
|
||||
->withPath('/templates');
|
||||
|
||||
$draftsEnabled = auth()->check();
|
||||
|
||||
$isDraftRevision = false;
|
||||
|
@ -47,8 +52,8 @@ class PageEditorData
|
|||
}
|
||||
|
||||
// Check for a current draft version for this user
|
||||
$userDraft = $this->pageRepo->getUserDraft($page);
|
||||
if ($userDraft !== null) {
|
||||
$userDraft = $this->queries->revisions->findLatestCurrentUserDraftsForPageId($page->id);
|
||||
if (!is_null($userDraft)) {
|
||||
$page->forceFill($userDraft->only(['name', 'html', 'markdown']));
|
||||
$isDraftRevision = true;
|
||||
$this->warnings[] = $editActivity->getEditingActiveDraftMessage($userDraft);
|
||||
|
|
|
@ -4,10 +4,16 @@ namespace BookStack\Entities\Tools;
|
|||
|
||||
use BookStack\Entities\Models\Book;
|
||||
use BookStack\Entities\Models\Bookshelf;
|
||||
use BookStack\Entities\Queries\BookshelfQueries;
|
||||
|
||||
class ShelfContext
|
||||
{
|
||||
protected $KEY_SHELF_CONTEXT_ID = 'context_bookshelf_id';
|
||||
protected string $KEY_SHELF_CONTEXT_ID = 'context_bookshelf_id';
|
||||
|
||||
public function __construct(
|
||||
protected BookshelfQueries $shelfQueries,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current bookshelf context for the given book.
|
||||
|
@ -20,8 +26,7 @@ class ShelfContext
|
|||
return null;
|
||||
}
|
||||
|
||||
/** @var Bookshelf $shelf */
|
||||
$shelf = Bookshelf::visible()->find($contextBookshelfId);
|
||||
$shelf = $this->shelfQueries->findVisibleById($contextBookshelfId);
|
||||
$shelfContainsBook = $shelf && $shelf->contains($book);
|
||||
|
||||
return $shelfContainsBook ? $shelf : null;
|
||||
|
@ -30,7 +35,7 @@ class ShelfContext
|
|||
/**
|
||||
* Store the current contextual shelf ID.
|
||||
*/
|
||||
public function setShelfContext(int $shelfId)
|
||||
public function setShelfContext(int $shelfId): void
|
||||
{
|
||||
session()->put($this->KEY_SHELF_CONTEXT_ID, $shelfId);
|
||||
}
|
||||
|
@ -38,7 +43,7 @@ class ShelfContext
|
|||
/**
|
||||
* Clear the session stored shelf context id.
|
||||
*/
|
||||
public function clearShelfContext()
|
||||
public function clearShelfContext(): void
|
||||
{
|
||||
session()->forget($this->KEY_SHELF_CONTEXT_ID);
|
||||
}
|
||||
|
|
|
@ -7,10 +7,17 @@ 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,
|
||||
protected ShelfContext $shelfContext,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Search among the siblings of the entity of given type and id.
|
||||
*/
|
||||
|
@ -26,23 +33,23 @@ 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
|
||||
// Gets just the books in a shelf if shelf is in context
|
||||
if ($entity instanceof Book) {
|
||||
$contextShelf = (new ShelfContext())->getContextualShelfForBook($entity);
|
||||
$contextShelf = $this->shelfContext->getContextualShelfForBook($entity);
|
||||
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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -4,10 +4,10 @@ namespace BookStack\Permissions;
|
|||
|
||||
use BookStack\Entities\Models\Book;
|
||||
use BookStack\Entities\Models\BookChild;
|
||||
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 +20,12 @@ use Illuminate\Support\Facades\DB;
|
|||
*/
|
||||
class JointPermissionBuilder
|
||||
{
|
||||
public function __construct(
|
||||
protected EntityQueries $queries,
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Re-generate all entity permission from scratch.
|
||||
*/
|
||||
|
@ -36,7 +42,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 +94,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 +105,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']);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -2,16 +2,14 @@
|
|||
|
||||
namespace BookStack\References;
|
||||
|
||||
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 +18,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 +32,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 +46,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 +60,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', [
|
||||
|
|
|
@ -23,7 +23,7 @@ class ReferenceFetcher
|
|||
public function getReferencesToEntity(Entity $entity): Collection
|
||||
{
|
||||
$references = $this->queryReferencesToEntity($entity)->get();
|
||||
$this->mixedEntityListLoader->loadIntoRelations($references->all(), 'from');
|
||||
$this->mixedEntityListLoader->loadIntoRelations($references->all(), 'from', true);
|
||||
|
||||
return $references;
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
namespace BookStack\Search;
|
||||
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Entities\Queries\Popular;
|
||||
use BookStack\Entities\Queries\PageQueries;
|
||||
use BookStack\Entities\Queries\QueryPopular;
|
||||
use BookStack\Entities\Tools\SiblingFetcher;
|
||||
use BookStack\Http\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
@ -11,7 +11,8 @@ use Illuminate\Http\Request;
|
|||
class SearchController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
protected SearchRunner $searchRunner
|
||||
protected SearchRunner $searchRunner,
|
||||
protected PageQueries $pageQueries,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -66,7 +67,7 @@ class SearchController extends Controller
|
|||
* Search for a list of entities and return a partial HTML response of matching entities.
|
||||
* Returns the most popular entities if no search is provided.
|
||||
*/
|
||||
public function searchForSelector(Request $request)
|
||||
public function searchForSelector(Request $request, QueryPopular $queryPopular)
|
||||
{
|
||||
$entityTypes = $request->filled('types') ? explode(',', $request->get('types')) : ['page', 'chapter', 'book'];
|
||||
$searchTerm = $request->get('term', false);
|
||||
|
@ -77,7 +78,7 @@ class SearchController extends Controller
|
|||
$searchTerm .= ' {type:' . implode('|', $entityTypes) . '}';
|
||||
$entities = $this->searchRunner->searchEntities(SearchOptions::fromString($searchTerm), 'all', 1, 20)['results'];
|
||||
} else {
|
||||
$entities = (new Popular())->run(20, 0, $entityTypes);
|
||||
$entities = $queryPopular->run(20, 0, $entityTypes);
|
||||
}
|
||||
|
||||
return view('search.parts.entity-selector-list', ['entities' => $entities, 'permission' => $permission]);
|
||||
|
@ -95,12 +96,11 @@ class SearchController extends Controller
|
|||
$searchOptions->setFilter('is_template');
|
||||
$entities = $this->searchRunner->searchEntities($searchOptions, 'page', 1, 20)['results'];
|
||||
} else {
|
||||
$entities = Page::visible()
|
||||
->where('template', '=', true)
|
||||
$entities = $this->pageQueries->visibleTemplates()
|
||||
->where('draft', '=', false)
|
||||
->orderBy('updated_at', 'desc')
|
||||
->take(20)
|
||||
->get(Page::$listAttributes);
|
||||
->get();
|
||||
}
|
||||
|
||||
return view('search.parts.entity-selector-list', [
|
||||
|
@ -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']);
|
||||
}
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
namespace BookStack\Search;
|
||||
|
||||
use BookStack\Entities\EntityProvider;
|
||||
use BookStack\Entities\Models\BookChild;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Entities\Queries\EntityQueries;
|
||||
use BookStack\Permissions\PermissionApplicator;
|
||||
use BookStack\Users\Models\User;
|
||||
use Illuminate\Database\Connection;
|
||||
|
@ -20,9 +20,6 @@ use SplObjectStorage;
|
|||
|
||||
class SearchRunner
|
||||
{
|
||||
protected EntityProvider $entityProvider;
|
||||
protected PermissionApplicator $permissions;
|
||||
|
||||
/**
|
||||
* Acceptable operators to be used in a query.
|
||||
*
|
||||
|
@ -38,10 +35,11 @@ class SearchRunner
|
|||
*/
|
||||
protected $termAdjustmentCache;
|
||||
|
||||
public function __construct(EntityProvider $entityProvider, PermissionApplicator $permissions)
|
||||
{
|
||||
$this->entityProvider = $entityProvider;
|
||||
$this->permissions = $permissions;
|
||||
public function __construct(
|
||||
protected EntityProvider $entityProvider,
|
||||
protected PermissionApplicator $permissions,
|
||||
protected EntityQueries $entityQueries,
|
||||
) {
|
||||
$this->termAdjustmentCache = new SplObjectStorage();
|
||||
}
|
||||
|
||||
|
@ -72,10 +70,9 @@ class SearchRunner
|
|||
continue;
|
||||
}
|
||||
|
||||
$entityModelInstance = $this->entityProvider->get($entityType);
|
||||
$searchQuery = $this->buildQuery($searchOpts, $entityModelInstance);
|
||||
$searchQuery = $this->buildQuery($searchOpts, $entityType);
|
||||
$entityTotal = $searchQuery->count();
|
||||
$searchResults = $this->getPageOfDataFromQuery($searchQuery, $entityModelInstance, $page, $count);
|
||||
$searchResults = $this->getPageOfDataFromQuery($searchQuery, $entityType, $page, $count);
|
||||
|
||||
if ($entityTotal > ($page * $count)) {
|
||||
$hasMore = true;
|
||||
|
@ -108,8 +105,7 @@ class SearchRunner
|
|||
continue;
|
||||
}
|
||||
|
||||
$entityModelInstance = $this->entityProvider->get($entityType);
|
||||
$search = $this->buildQuery($opts, $entityModelInstance)->where('book_id', '=', $bookId)->take(20)->get();
|
||||
$search = $this->buildQuery($opts, $entityType)->where('book_id', '=', $bookId)->take(20)->get();
|
||||
$results = $results->merge($search);
|
||||
}
|
||||
|
||||
|
@ -122,8 +118,7 @@ class SearchRunner
|
|||
public function searchChapter(int $chapterId, string $searchString): Collection
|
||||
{
|
||||
$opts = SearchOptions::fromString($searchString);
|
||||
$entityModelInstance = $this->entityProvider->get('page');
|
||||
$pages = $this->buildQuery($opts, $entityModelInstance)->where('chapter_id', '=', $chapterId)->take(20)->get();
|
||||
$pages = $this->buildQuery($opts, 'page')->where('chapter_id', '=', $chapterId)->take(20)->get();
|
||||
|
||||
return $pages->sortByDesc('score');
|
||||
}
|
||||
|
@ -131,17 +126,17 @@ class SearchRunner
|
|||
/**
|
||||
* Get a page of result data from the given query based on the provided page parameters.
|
||||
*/
|
||||
protected function getPageOfDataFromQuery(EloquentBuilder $query, Entity $entityModelInstance, int $page = 1, int $count = 20): EloquentCollection
|
||||
protected function getPageOfDataFromQuery(EloquentBuilder $query, string $entityType, int $page = 1, int $count = 20): EloquentCollection
|
||||
{
|
||||
$relations = ['tags'];
|
||||
|
||||
if ($entityModelInstance instanceof BookChild) {
|
||||
if ($entityType === 'page' || $entityType === 'chapter') {
|
||||
$relations['book'] = function (BelongsTo $query) {
|
||||
$query->scopes('visible');
|
||||
};
|
||||
}
|
||||
|
||||
if ($entityModelInstance instanceof Page) {
|
||||
if ($entityType === 'page') {
|
||||
$relations['chapter'] = function (BelongsTo $query) {
|
||||
$query->scopes('visible');
|
||||
};
|
||||
|
@ -157,18 +152,13 @@ class SearchRunner
|
|||
/**
|
||||
* Create a search query for an entity.
|
||||
*/
|
||||
protected function buildQuery(SearchOptions $searchOpts, Entity $entityModelInstance): EloquentBuilder
|
||||
protected function buildQuery(SearchOptions $searchOpts, string $entityType): EloquentBuilder
|
||||
{
|
||||
$entityQuery = $entityModelInstance->newQuery()->scopes('visible');
|
||||
|
||||
if ($entityModelInstance instanceof Page) {
|
||||
$entityQuery->select(array_merge($entityModelInstance::$listAttributes, ['owned_by']));
|
||||
} else {
|
||||
$entityQuery->select(['*']);
|
||||
}
|
||||
$entityModelInstance = $this->entityProvider->get($entityType);
|
||||
$entityQuery = $this->entityQueries->visibleForList($entityType);
|
||||
|
||||
// Handle normal search terms
|
||||
$this->applyTermSearch($entityQuery, $searchOpts, $entityModelInstance);
|
||||
$this->applyTermSearch($entityQuery, $searchOpts, $entityType);
|
||||
|
||||
// Handle exact term matching
|
||||
foreach ($searchOpts->exacts as $inputTerm) {
|
||||
|
@ -198,7 +188,7 @@ class SearchRunner
|
|||
/**
|
||||
* For the given search query, apply the queries for handling the regular search terms.
|
||||
*/
|
||||
protected function applyTermSearch(EloquentBuilder $entityQuery, SearchOptions $options, Entity $entity): void
|
||||
protected function applyTermSearch(EloquentBuilder $entityQuery, SearchOptions $options, string $entityType): void
|
||||
{
|
||||
$terms = $options->searches;
|
||||
if (count($terms) === 0) {
|
||||
|
@ -216,7 +206,7 @@ class SearchRunner
|
|||
|
||||
$subQuery->addBinding($scoreSelect['bindings'], 'select');
|
||||
|
||||
$subQuery->where('entity_type', '=', $entity->getMorphClass());
|
||||
$subQuery->where('entity_type', '=', $entityType);
|
||||
$subQuery->where(function (Builder $query) use ($terms) {
|
||||
foreach ($terms as $inputTerm) {
|
||||
$inputTerm = str_replace('\\', '\\\\', $inputTerm);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -4,7 +4,6 @@ namespace BookStack\Uploads;
|
|||
|
||||
use BookStack\Exceptions\FileUploadException;
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Filesystem\FileNotFoundException;
|
||||
use Illuminate\Contracts\Filesystem\Filesystem as Storage;
|
||||
use Illuminate\Filesystem\FilesystemManager;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace BookStack\Uploads\Controllers;
|
||||
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Entities\Queries\PageQueries;
|
||||
use BookStack\Exceptions\FileUploadException;
|
||||
use BookStack\Http\ApiController;
|
||||
use BookStack\Uploads\Attachment;
|
||||
|
@ -15,7 +15,8 @@ use Illuminate\Validation\ValidationException;
|
|||
class AttachmentApiController extends ApiController
|
||||
{
|
||||
public function __construct(
|
||||
protected AttachmentService $attachmentService
|
||||
protected AttachmentService $attachmentService,
|
||||
protected PageQueries $pageQueries,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -48,7 +49,7 @@ class AttachmentApiController extends ApiController
|
|||
$requestData = $this->validate($request, $this->rules()['create']);
|
||||
|
||||
$pageId = $request->get('uploaded_to');
|
||||
$page = Page::visible()->findOrFail($pageId);
|
||||
$page = $this->pageQueries->findVisibleByIdOrFail($pageId);
|
||||
$this->checkOwnablePermission('page-update', $page);
|
||||
|
||||
if ($request->hasFile('file')) {
|
||||
|
@ -132,7 +133,7 @@ class AttachmentApiController extends ApiController
|
|||
$page = $attachment->page;
|
||||
if ($requestData['uploaded_to'] ?? false) {
|
||||
$pageId = $request->get('uploaded_to');
|
||||
$page = Page::visible()->findOrFail($pageId);
|
||||
$page = $this->pageQueries->findVisibleByIdOrFail($pageId);
|
||||
$attachment->uploaded_to = $requestData['uploaded_to'];
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace BookStack\Uploads\Controllers;
|
||||
|
||||
use BookStack\Entities\Queries\PageQueries;
|
||||
use BookStack\Entities\Repos\PageRepo;
|
||||
use BookStack\Exceptions\FileUploadException;
|
||||
use BookStack\Exceptions\NotFoundException;
|
||||
|
@ -18,6 +19,7 @@ class AttachmentController extends Controller
|
|||
{
|
||||
public function __construct(
|
||||
protected AttachmentService $attachmentService,
|
||||
protected PageQueries $pageQueries,
|
||||
protected PageRepo $pageRepo
|
||||
) {
|
||||
}
|
||||
|
@ -36,7 +38,7 @@ class AttachmentController extends Controller
|
|||
]);
|
||||
|
||||
$pageId = $request->get('uploaded_to');
|
||||
$page = $this->pageRepo->getById($pageId);
|
||||
$page = $this->pageQueries->findVisibleByIdOrFail($pageId);
|
||||
|
||||
$this->checkPermission('attachment-create-all');
|
||||
$this->checkOwnablePermission('page-update', $page);
|
||||
|
@ -152,7 +154,7 @@ class AttachmentController extends Controller
|
|||
]), 422);
|
||||
}
|
||||
|
||||
$page = $this->pageRepo->getById($pageId);
|
||||
$page = $this->pageQueries->findVisibleByIdOrFail($pageId);
|
||||
|
||||
$this->checkPermission('attachment-create-all');
|
||||
$this->checkOwnablePermission('page-update', $page);
|
||||
|
@ -173,7 +175,7 @@ class AttachmentController extends Controller
|
|||
*/
|
||||
public function listForPage(int $pageId)
|
||||
{
|
||||
$page = $this->pageRepo->getById($pageId);
|
||||
$page = $this->pageQueries->findVisibleByIdOrFail($pageId);
|
||||
$this->checkOwnablePermission('page-view', $page);
|
||||
|
||||
return view('attachments.manager-list', [
|
||||
|
@ -192,7 +194,7 @@ class AttachmentController extends Controller
|
|||
$this->validate($request, [
|
||||
'order' => ['required', 'array'],
|
||||
]);
|
||||
$page = $this->pageRepo->getById($pageId);
|
||||
$page = $this->pageQueries->findVisibleByIdOrFail($pageId);
|
||||
$this->checkOwnablePermission('page-update', $page);
|
||||
|
||||
$attachmentOrder = $request->get('order');
|
||||
|
@ -213,7 +215,7 @@ class AttachmentController extends Controller
|
|||
$attachment = Attachment::query()->findOrFail($attachmentId);
|
||||
|
||||
try {
|
||||
$page = $this->pageRepo->getById($attachment->uploaded_to);
|
||||
$page = $this->pageQueries->findVisibleByIdOrFail($attachment->uploaded_to);
|
||||
} catch (NotFoundException $exception) {
|
||||
throw new NotFoundException(trans('errors.attachment_not_found'));
|
||||
}
|
||||
|
|
|
@ -8,8 +8,6 @@ use BookStack\Uploads\ImageRepo;
|
|||
use BookStack\Uploads\ImageResizer;
|
||||
use BookStack\Util\OutOfMemoryHandler;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class GalleryImageController extends Controller
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace BookStack\Uploads\Controllers;
|
||||
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Entities\Queries\PageQueries;
|
||||
use BookStack\Http\ApiController;
|
||||
use BookStack\Uploads\Image;
|
||||
use BookStack\Uploads\ImageRepo;
|
||||
|
@ -18,6 +18,7 @@ class ImageGalleryApiController extends ApiController
|
|||
public function __construct(
|
||||
protected ImageRepo $imageRepo,
|
||||
protected ImageResizer $imageResizer,
|
||||
protected PageQueries $pageQueries,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -66,9 +67,9 @@ class ImageGalleryApiController extends ApiController
|
|||
{
|
||||
$this->checkPermission('image-create-all');
|
||||
$data = $this->validate($request, $this->rules()['create']);
|
||||
Page::visible()->findOrFail($data['uploaded_to']);
|
||||
$page = $this->pageQueries->findVisibleByIdOrFail($data['uploaded_to']);
|
||||
|
||||
$image = $this->imageRepo->saveNew($data['image'], $data['type'], $data['uploaded_to']);
|
||||
$image = $this->imageRepo->saveNew($data['image'], $data['type'], $page->id);
|
||||
|
||||
if (isset($data['name'])) {
|
||||
$image->refresh();
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace BookStack\Uploads;
|
||||
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Entities\Queries\PageQueries;
|
||||
use BookStack\Exceptions\ImageUploadException;
|
||||
use BookStack\Permissions\PermissionApplicator;
|
||||
use Exception;
|
||||
|
@ -15,6 +15,7 @@ class ImageRepo
|
|||
protected ImageService $imageService,
|
||||
protected PermissionApplicator $permissions,
|
||||
protected ImageResizer $imageResizer,
|
||||
protected PageQueries $pageQueries,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -77,14 +78,13 @@ class ImageRepo
|
|||
*/
|
||||
public function getEntityFiltered(
|
||||
string $type,
|
||||
string $filterType = null,
|
||||
int $page = 0,
|
||||
int $pageSize = 24,
|
||||
int $uploadedTo = null,
|
||||
string $search = null
|
||||
?string $filterType,
|
||||
int $page,
|
||||
int $pageSize,
|
||||
int $uploadedTo,
|
||||
?string $search
|
||||
): array {
|
||||
/** @var Page $contextPage */
|
||||
$contextPage = Page::visible()->findOrFail($uploadedTo);
|
||||
$contextPage = $this->pageQueries->findVisibleByIdOrFail($uploadedTo);
|
||||
$parentFilter = null;
|
||||
|
||||
if ($filterType === 'book' || $filterType === 'page') {
|
||||
|
@ -225,9 +225,9 @@ class ImageRepo
|
|||
*/
|
||||
public function getPagesUsingImage(Image $image): array
|
||||
{
|
||||
$pages = Page::visible()
|
||||
$pages = $this->pageQueries->visibleForList()
|
||||
->where('html', 'like', '%' . $image->url . '%')
|
||||
->get(['id', 'name', 'slug', 'book_id']);
|
||||
->get();
|
||||
|
||||
foreach ($pages as $page) {
|
||||
$page->setAttribute('url', $page->getUrl());
|
||||
|
|
|
@ -2,9 +2,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 +18,7 @@ class ImageService
|
|||
public function __construct(
|
||||
protected ImageStorage $storage,
|
||||
protected ImageResizer $resizer,
|
||||
protected EntityQueries $queries,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -278,15 +277,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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('views', function (Blueprint $table) {
|
||||
$table->index(['updated_at'], 'views_updated_at_index');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('views', function (Blueprint $table) {
|
||||
$table->dropIndex('views_updated_at_index');
|
||||
});
|
||||
}
|
||||
};
|
|
@ -1,5 +1,5 @@
|
|||
@extends('layouts.simple')
|
||||
|
||||
@inject('popular', \BookStack\Entities\Queries\QueryPopular::class)
|
||||
@section('content')
|
||||
<div class="container mt-l">
|
||||
|
||||
|
@ -28,7 +28,7 @@
|
|||
<div class="card mb-xl">
|
||||
<h3 class="card-title">{{ trans('entities.pages_popular') }}</h3>
|
||||
<div class="px-m">
|
||||
@include('entities.list', ['entities' => (new \BookStack\Entities\Queries\Popular)->run(10, 0, ['page']), 'style' => 'compact'])
|
||||
@include('entities.list', ['entities' => $popular->run(10, 0, ['page']), 'style' => 'compact'])
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -36,7 +36,7 @@
|
|||
<div class="card mb-xl">
|
||||
<h3 class="card-title">{{ trans('entities.books_popular') }}</h3>
|
||||
<div class="px-m">
|
||||
@include('entities.list', ['entities' => (new \BookStack\Entities\Queries\Popular)->run(10, 0, ['book']), 'style' => 'compact'])
|
||||
@include('entities.list', ['entities' => $popular->run(10, 0, ['book']), 'style' => 'compact'])
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -44,7 +44,7 @@
|
|||
<div class="card mb-xl">
|
||||
<h3 class="card-title">{{ trans('entities.chapters_popular') }}</h3>
|
||||
<div class="px-m">
|
||||
@include('entities.list', ['entities' => (new \BookStack\Entities\Queries\Popular)->run(10, 0, ['chapter']), 'style' => 'compact'])
|
||||
@include('entities.list', ['entities' => $popular->run(10, 0, ['chapter']), 'style' => 'compact'])
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -24,6 +24,9 @@ class BooksApiTest extends TestCase
|
|||
'id' => $firstBook->id,
|
||||
'name' => $firstBook->name,
|
||||
'slug' => $firstBook->slug,
|
||||
'owned_by' => $firstBook->owned_by,
|
||||
'created_by' => $firstBook->created_by,
|
||||
'updated_by' => $firstBook->updated_by,
|
||||
],
|
||||
]]);
|
||||
}
|
||||
|
|
|
@ -28,6 +28,9 @@ class ChaptersApiTest extends TestCase
|
|||
'book_id' => $firstChapter->book->id,
|
||||
'priority' => $firstChapter->priority,
|
||||
'book_slug' => $firstChapter->book->slug,
|
||||
'owned_by' => $firstChapter->owned_by,
|
||||
'created_by' => $firstChapter->created_by,
|
||||
'updated_by' => $firstChapter->updated_by,
|
||||
],
|
||||
]]);
|
||||
}
|
||||
|
@ -149,6 +152,16 @@ class ChaptersApiTest extends TestCase
|
|||
'id' => $page->id,
|
||||
'slug' => $page->slug,
|
||||
'name' => $page->name,
|
||||
'owned_by' => $page->owned_by,
|
||||
'created_by' => $page->created_by,
|
||||
'updated_by' => $page->updated_by,
|
||||
'book_id' => $page->id,
|
||||
'chapter_id' => $chapter->id,
|
||||
'priority' => $page->priority,
|
||||
'book_slug' => $chapter->book->slug,
|
||||
'draft' => $page->draft,
|
||||
'template' => $page->template,
|
||||
'editor' => $page->editor,
|
||||
],
|
||||
],
|
||||
'default_template_id' => null,
|
||||
|
|
|
@ -27,6 +27,10 @@ class PagesApiTest extends TestCase
|
|||
'slug' => $firstPage->slug,
|
||||
'book_id' => $firstPage->book->id,
|
||||
'priority' => $firstPage->priority,
|
||||
'owned_by' => $firstPage->owned_by,
|
||||
'created_by' => $firstPage->created_by,
|
||||
'updated_by' => $firstPage->updated_by,
|
||||
'revision_count' => $firstPage->revision_count,
|
||||
],
|
||||
]]);
|
||||
}
|
||||
|
|
|
@ -25,6 +25,9 @@ class ShelvesApiTest extends TestCase
|
|||
'id' => $firstBookshelf->id,
|
||||
'name' => $firstBookshelf->name,
|
||||
'slug' => $firstBookshelf->slug,
|
||||
'owned_by' => $firstBookshelf->owned_by,
|
||||
'created_by' => $firstBookshelf->created_by,
|
||||
'updated_by' => $firstBookshelf->updated_by,
|
||||
],
|
||||
]]);
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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");
|
||||
|
|
Loading…
Reference in New Issue
Block a user