1
0
mirror of https://github.com/BookStackApp/BookStack.git synced 2025-08-06 12:02:45 +03:00

Merge pull request #947 from BookStackApp/bookshelves

Bookshelves
This commit is contained in:
Dan Brown
2018-09-21 15:29:52 +01:00
committed by GitHub
61 changed files with 2100 additions and 210 deletions

View File

@@ -48,14 +48,6 @@ class Book extends Entity
{
return $this->belongsTo(Image::class, 'image_id');
}
/*
* Get the edit url for this book.
* @return string
*/
public function getEditUrl()
{
return $this->getUrl() . '/edit';
}
/**
* Get all pages within this book.
@@ -75,6 +67,15 @@ class Book extends Entity
return $this->hasMany(Chapter::class);
}
/**
* Get the shelves this book is contained within.
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function shelves()
{
return $this->belongsToMany(Bookshelf::class, 'bookshelves_books', 'book_id', 'bookshelf_id');
}
/**
* Get an excerpt of this book's description to the specified length or less.
* @param int $length

84
app/Bookshelf.php Normal file
View File

@@ -0,0 +1,84 @@
<?php namespace BookStack;
class Bookshelf extends Entity
{
protected $table = 'bookshelves';
public $searchFactor = 3;
protected $fillable = ['name', 'description', 'image_id'];
/**
* Get the books in this shelf.
* Should not be used directly since does not take into account permissions.
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function books()
{
return $this->belongsToMany(Book::class, 'bookshelves_books', 'bookshelf_id', 'book_id')->orderBy('order', 'asc');
}
/**
* Get the url for this bookshelf.
* @param string|bool $path
* @return string
*/
public function getUrl($path = false)
{
if ($path !== false) {
return baseUrl('/shelves/' . urlencode($this->slug) . '/' . trim($path, '/'));
}
return baseUrl('/shelves/' . urlencode($this->slug));
}
/**
* Returns BookShelf cover image, if cover does not exists return default cover image.
* @param int $width - Width of the image
* @param int $height - Height of the image
* @return string
*/
public function getBookCover($width = 440, $height = 250)
{
$default = baseUrl('/book_default_cover.png');
if (!$this->image_id) {
return $default;
}
try {
$cover = $this->cover ? baseUrl($this->cover->getThumb($width, $height, false)) : $default;
} catch (\Exception $err) {
$cover = $default;
}
return $cover;
}
/**
* Get the cover image of the book
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function cover()
{
return $this->belongsTo(Image::class, 'image_id');
}
/**
* Get an excerpt of this book's description to the specified length or less.
* @param int $length
* @return string
*/
public function getExcerpt($length = 100)
{
$description = $this->description;
return strlen($description) > $length ? substr($description, 0, $length-3) . '...' : $description;
}
/**
* Return a generalised, common raw query that can be 'unioned' across entities.
* @return string
*/
public function entityRawQuery()
{
return "'BookStack\\\\BookShelf' as entity_type, id, id as entity_id, slug, name, {$this->textField} as text,'' as html, '0' as book_id, '0' as priority, '0' as chapter_id, '0' as draft, created_by, updated_by, updated_at, created_at";
}
}

View File

@@ -152,7 +152,7 @@ class Entity extends Ownable
*/
public static function getEntityInstance($type)
{
$types = ['Page', 'Book', 'Chapter'];
$types = ['Page', 'Book', 'Chapter', 'Bookshelf'];
$className = str_replace([' ', '-', '_'], '', ucwords($type));
if (!in_array($className, $types)) {
return null;

View File

@@ -0,0 +1,244 @@
<?php namespace BookStack\Http\Controllers;
use Activity;
use BookStack\Book;
use BookStack\Bookshelf;
use BookStack\Repos\EntityRepo;
use BookStack\Repos\UserRepo;
use BookStack\Services\ExportService;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Views;
class BookshelfController extends Controller
{
protected $entityRepo;
protected $userRepo;
protected $exportService;
/**
* BookController constructor.
* @param EntityRepo $entityRepo
* @param UserRepo $userRepo
* @param ExportService $exportService
*/
public function __construct(EntityRepo $entityRepo, UserRepo $userRepo, ExportService $exportService)
{
$this->entityRepo = $entityRepo;
$this->userRepo = $userRepo;
$this->exportService = $exportService;
parent::__construct();
}
/**
* Display a listing of the book.
* @return Response
*/
public function index()
{
$shelves = $this->entityRepo->getAllPaginated('bookshelf', 18);
$recents = $this->signedIn ? $this->entityRepo->getRecentlyViewed('bookshelf', 4, 0) : false;
$popular = $this->entityRepo->getPopular('bookshelf', 4, 0);
$new = $this->entityRepo->getRecentlyCreated('bookshelf', 4, 0);
$shelvesViewType = setting()->getUser($this->currentUser, 'bookshelves_view_type', config('app.views.bookshelves', 'grid'));
$this->setPageTitle(trans('entities.shelves'));
return view('shelves/index', [
'shelves' => $shelves,
'recents' => $recents,
'popular' => $popular,
'new' => $new,
'shelvesViewType' => $shelvesViewType
]);
}
/**
* Show the form for creating a new bookshelf.
* @return Response
*/
public function create()
{
$this->checkPermission('bookshelf-create-all');
$books = $this->entityRepo->getAll('book', false, 'update');
$this->setPageTitle(trans('entities.shelves_create'));
return view('shelves/create', ['books' => $books]);
}
/**
* Store a newly created bookshelf in storage.
* @param Request $request
* @return Response
*/
public function store(Request $request)
{
$this->checkPermission('bookshelf-create-all');
$this->validate($request, [
'name' => 'required|string|max:255',
'description' => 'string|max:1000',
]);
$bookshelf = $this->entityRepo->createFromInput('bookshelf', $request->all());
$this->entityRepo->updateShelfBooks($bookshelf, $request->get('books', ''));
Activity::add($bookshelf, 'bookshelf_create');
return redirect($bookshelf->getUrl());
}
/**
* Display the specified bookshelf.
* @param String $slug
* @return Response
* @throws \BookStack\Exceptions\NotFoundException
*/
public function show(string $slug)
{
$bookshelf = $this->entityRepo->getBySlug('bookshelf', $slug); /** @var $bookshelf Bookshelf */
$this->checkOwnablePermission('book-view', $bookshelf);
$books = $this->entityRepo->getBookshelfChildren($bookshelf);
Views::add($bookshelf);
$this->setPageTitle($bookshelf->getShortName());
return view('shelves/show', [
'shelf' => $bookshelf,
'books' => $books,
'activity' => Activity::entityActivity($bookshelf, 20, 0)
]);
}
/**
* Show the form for editing the specified bookshelf.
* @param $slug
* @return Response
* @throws \BookStack\Exceptions\NotFoundException
*/
public function edit(string $slug)
{
$bookshelf = $this->entityRepo->getBySlug('bookshelf', $slug); /** @var $bookshelf Bookshelf */
$this->checkOwnablePermission('bookshelf-update', $bookshelf);
$shelfBooks = $this->entityRepo->getBookshelfChildren($bookshelf);
$shelfBookIds = $shelfBooks->pluck('id');
$books = $this->entityRepo->getAll('book', false, 'update');
$books = $books->filter(function ($book) use ($shelfBookIds) {
return !$shelfBookIds->contains($book->id);
});
$this->setPageTitle(trans('entities.shelves_edit_named', ['name' => $bookshelf->getShortName()]));
return view('shelves/edit', [
'shelf' => $bookshelf,
'books' => $books,
'shelfBooks' => $shelfBooks,
]);
}
/**
* Update the specified bookshelf in storage.
* @param Request $request
* @param string $slug
* @return Response
* @throws \BookStack\Exceptions\NotFoundException
*/
public function update(Request $request, string $slug)
{
$shelf = $this->entityRepo->getBySlug('bookshelf', $slug); /** @var $bookshelf Bookshelf */
$this->checkOwnablePermission('bookshelf-update', $shelf);
$this->validate($request, [
'name' => 'required|string|max:255',
'description' => 'string|max:1000',
]);
$shelf = $this->entityRepo->updateFromInput('bookshelf', $shelf, $request->all());
$this->entityRepo->updateShelfBooks($shelf, $request->get('books', ''));
Activity::add($shelf, 'bookshelf_update');
return redirect($shelf->getUrl());
}
/**
* Shows the page to confirm deletion
* @param $slug
* @return \Illuminate\View\View
* @throws \BookStack\Exceptions\NotFoundException
*/
public function showDelete(string $slug)
{
$bookshelf = $this->entityRepo->getBySlug('bookshelf', $slug); /** @var $bookshelf Bookshelf */
$this->checkOwnablePermission('bookshelf-delete', $bookshelf);
$this->setPageTitle(trans('entities.shelves_delete_named', ['name' => $bookshelf->getShortName()]));
return view('shelves/delete', ['shelf' => $bookshelf]);
}
/**
* Remove the specified bookshelf from storage.
* @param string $slug
* @return Response
* @throws \BookStack\Exceptions\NotFoundException
* @throws \Throwable
*/
public function destroy(string $slug)
{
$bookshelf = $this->entityRepo->getBySlug('bookshelf', $slug); /** @var $bookshelf Bookshelf */
$this->checkOwnablePermission('bookshelf-delete', $bookshelf);
Activity::addMessage('bookshelf_delete', 0, $bookshelf->name);
$this->entityRepo->destroyBookshelf($bookshelf);
return redirect('/shelves');
}
/**
* Show the Restrictions view.
* @param $slug
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
* @throws \BookStack\Exceptions\NotFoundException
*/
public function showRestrict(string $slug)
{
$bookshelf = $this->entityRepo->getBySlug('bookshelf', $slug);
$this->checkOwnablePermission('restrictions-manage', $bookshelf);
$roles = $this->userRepo->getRestrictableRoles();
return view('shelves.restrictions', [
'shelf' => $bookshelf,
'roles' => $roles
]);
}
/**
* Set the restrictions for this bookshelf.
* @param $slug
* @param Request $request
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
* @throws \BookStack\Exceptions\NotFoundException
*/
public function restrict(string $slug, Request $request)
{
$bookshelf = $this->entityRepo->getBySlug('bookshelf', $slug);
$this->checkOwnablePermission('restrictions-manage', $bookshelf);
$this->entityRepo->updateEntityPermissionsFromRequest($request, $bookshelf);
session()->flash('success', trans('entities.shelves_permissions_updated'));
return redirect($bookshelf->getUrl());
}
/**
* Copy the permissions of a bookshelf to the child books.
* @param string $slug
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
* @throws \BookStack\Exceptions\NotFoundException
*/
public function copyPermissions(string $slug)
{
$bookshelf = $this->entityRepo->getBySlug('bookshelf', $slug);
$this->checkOwnablePermission('restrictions-manage', $bookshelf);
$updateCount = $this->entityRepo->copyBookshelfPermissions($bookshelf);
session()->flash('success', trans('entities.shelves_copy_permission_success', ['count' => $updateCount]));
return redirect($bookshelf->getUrl());
}
}

View File

@@ -33,42 +33,42 @@ class HomeController extends Controller
$recents = $this->signedIn ? Views::getUserRecentlyViewed(12*$recentFactor, 0) : $this->entityRepo->getRecentlyCreated('book', 12*$recentFactor);
$recentlyUpdatedPages = $this->entityRepo->getRecentlyUpdated('page', 12);
$customHomepage = false;
$books = false;
$booksViewType = false;
// Check book homepage
$bookHomepageSetting = setting('app-book-homepage');
if ($bookHomepageSetting) {
$books = $this->entityRepo->getAllPaginated('book', 18);
$booksViewType = setting()->getUser($this->currentUser, 'books_view_type', config('app.views.books', 'list'));
} else {
// Check custom homepage
$homepageSetting = setting('app-homepage');
if ($homepageSetting) {
$id = intval(explode(':', $homepageSetting)[0]);
$customHomepage = $this->entityRepo->getById('page', $id, false, true);
$this->entityRepo->renderPage($customHomepage, true);
}
$homepageOptions = ['default', 'books', 'bookshelves', 'page'];
$homepageOption = setting('app-homepage-type', 'default');
if (!in_array($homepageOption, $homepageOptions)) {
$homepageOption = 'default';
}
$view = 'home';
if ($bookHomepageSetting) {
$view = 'home-book';
} else if ($customHomepage) {
$view = 'home-custom';
}
return view('common/' . $view, [
$commonData = [
'activity' => $activity,
'recents' => $recents,
'recentlyUpdatedPages' => $recentlyUpdatedPages,
'draftPages' => $draftPages,
'customHomepage' => $customHomepage,
'books' => $books,
'booksViewType' => $booksViewType
]);
];
if ($homepageOption === 'bookshelves') {
$shelves = $this->entityRepo->getAllPaginated('bookshelf', 18);
$shelvesViewType = setting()->getUser($this->currentUser, 'bookshelves_view_type', config('app.views.bookshelves', 'grid'));
$data = array_merge($commonData, ['shelves' => $shelves, 'shelvesViewType' => $shelvesViewType]);
return view('common.home-shelves', $data);
}
if ($homepageOption === 'books') {
$books = $this->entityRepo->getAllPaginated('book', 18);
$booksViewType = setting()->getUser($this->currentUser, 'books_view_type', config('app.views.books', 'list'));
$data = array_merge($commonData, ['books' => $books, 'booksViewType' => $booksViewType]);
return view('common.home-book', $data);
}
if ($homepageOption === 'page') {
$homepageSetting = setting('app-homepage', '0:');
$id = intval(explode(':', $homepageSetting)[0]);
$customHomepage = $this->entityRepo->getById('page', $id, false, true);
$this->entityRepo->renderPage($customHomepage, true);
return view('common.home-custom', array_merge($commonData, ['customHomepage' => $customHomepage]));
}
return view('common.home', $commonData);
}
/**

View File

@@ -252,7 +252,7 @@ class UserController extends Controller
return $this->currentUser->id == $id;
});
$viewType = $request->get('book_view_type');
$viewType = $request->get('view_type');
if (!in_array($viewType, ['grid', 'list'])) {
$viewType = 'list';
}
@@ -262,4 +262,27 @@ class UserController extends Controller
return redirect()->back(302, [], "/settings/users/$id");
}
/**
* Update the user's preferred shelf-list display setting.
* @param $id
* @param Request $request
* @return \Illuminate\Http\RedirectResponse
*/
public function switchShelfView($id, Request $request)
{
$this->checkPermissionOr('users-manage', function () use ($id) {
return $this->currentUser->id == $id;
});
$viewType = $request->get('view_type');
if (!in_array($viewType, ['grid', 'list'])) {
$viewType = 'list';
}
$user = $this->user->findOrFail($id);
setting()->putUser($user, 'bookshelves_view_type', $viewType);
return redirect()->back(302, [], "/settings/users/$id");
}
}

View File

@@ -1,6 +1,7 @@
<?php namespace BookStack\Repos;
use BookStack\Book;
use BookStack\Bookshelf;
use BookStack\Chapter;
use BookStack\Entity;
use BookStack\Exceptions\NotFoundException;
@@ -18,6 +19,10 @@ use Illuminate\Support\Collection;
class EntityRepo
{
/**
* @var Bookshelf
*/
public $bookshelf;
/**
* @var Book $book
@@ -67,6 +72,7 @@ class EntityRepo
/**
* EntityRepo constructor.
* @param Bookshelf $bookshelf
* @param Book $book
* @param Chapter $chapter
* @param Page $page
@@ -77,6 +83,7 @@ class EntityRepo
* @param SearchService $searchService
*/
public function __construct(
Bookshelf $bookshelf,
Book $book,
Chapter $chapter,
Page $page,
@@ -86,11 +93,13 @@ class EntityRepo
TagRepo $tagRepo,
SearchService $searchService
) {
$this->bookshelf = $bookshelf;
$this->book = $book;
$this->chapter = $chapter;
$this->page = $page;
$this->pageRevision = $pageRevision;
$this->entities = [
'bookshelf' => $this->bookshelf,
'page' => $this->page,
'chapter' => $this->chapter,
'book' => $this->book
@@ -331,6 +340,17 @@ class EntityRepo
->skip($count * $page)->take($count)->get();
}
/**
* Get the child items for a chapter sorted by priority but
* with draft items floated to the top.
* @param Bookshelf $bookshelf
* @return \Illuminate\Database\Eloquent\Collection|static[]
*/
public function getBookshelfChildren(Bookshelf $bookshelf)
{
return $this->permissionService->enforceEntityRestrictions('book', $bookshelf->books())->get();
}
/**
* Get all child objects of a book.
* Returns a sorted collection of Pages and Chapters.
@@ -533,6 +553,28 @@ class EntityRepo
return $entityModel;
}
/**
* Sync the books assigned to a shelf from a comma-separated list
* of book IDs.
* @param Bookshelf $shelf
* @param string $books
*/
public function updateShelfBooks(Bookshelf $shelf, string $books)
{
$ids = explode(',', $books);
// Check books exist and match ordering
$bookIds = $this->entityQuery('book')->whereIn('id', $ids)->get(['id'])->pluck('id');
$syncData = [];
foreach ($ids as $index => $id) {
if ($bookIds->contains($id)) {
$syncData[$id] = ['order' => $index];
}
}
$shelf->books()->sync($syncData);
}
/**
* Change the book that an entity belongs to.
* @param string $type
@@ -1154,9 +1196,22 @@ class EntityRepo
$this->permissionService->buildJointPermissionsForEntity($book);
}
/**
* Destroy a bookshelf instance
* @param Bookshelf $shelf
* @throws \Throwable
*/
public function destroyBookshelf(Bookshelf $shelf)
{
$this->destroyEntityCommonRelations($shelf);
$shelf->delete();
}
/**
* Destroy the provided book and all its child entities.
* @param Book $book
* @throws NotifyException
* @throws \Throwable
*/
public function destroyBook(Book $book)
{
@@ -1166,17 +1221,14 @@ class EntityRepo
foreach ($book->chapters as $chapter) {
$this->destroyChapter($chapter);
}
\Activity::removeEntity($book);
$book->views()->delete();
$book->permissions()->delete();
$this->permissionService->deleteJointPermissionsForEntity($book);
$this->searchService->deleteEntityTerms($book);
$this->destroyEntityCommonRelations($book);
$book->delete();
}
/**
* Destroy a chapter and its relations.
* @param Chapter $chapter
* @throws \Throwable
*/
public function destroyChapter(Chapter $chapter)
{
@@ -1186,11 +1238,7 @@ class EntityRepo
$page->save();
}
}
\Activity::removeEntity($chapter);
$chapter->views()->delete();
$chapter->permissions()->delete();
$this->permissionService->deleteJointPermissionsForEntity($chapter);
$this->searchService->deleteEntityTerms($chapter);
$this->destroyEntityCommonRelations($chapter);
$chapter->delete();
}
@@ -1198,23 +1246,18 @@ class EntityRepo
* Destroy a given page along with its dependencies.
* @param Page $page
* @throws NotifyException
* @throws \Throwable
*/
public function destroyPage(Page $page)
{
\Activity::removeEntity($page);
$page->views()->delete();
$page->tags()->delete();
$page->revisions()->delete();
$page->permissions()->delete();
$this->permissionService->deleteJointPermissionsForEntity($page);
$this->searchService->deleteEntityTerms($page);
// Check if set as custom homepage
$customHome = setting('app-homepage', '0:');
if (intval($page->id) === intval(explode(':', $customHome)[0])) {
throw new NotifyException(trans('errors.page_custom_home_deletion'), $page->getUrl());
}
$this->destroyEntityCommonRelations($page);
// Delete Attached Files
$attachmentService = app(AttachmentService::class);
foreach ($page->attachments as $attachment) {
@@ -1223,4 +1266,46 @@ class EntityRepo
$page->delete();
}
/**
* Destroy or handle the common relations connected to an entity.
* @param Entity $entity
* @throws \Throwable
*/
protected function destroyEntityCommonRelations(Entity $entity)
{
\Activity::removeEntity($entity);
$entity->views()->delete();
$entity->permissions()->delete();
$entity->tags()->delete();
$entity->comments()->delete();
$this->permissionService->deleteJointPermissionsForEntity($entity);
$this->searchService->deleteEntityTerms($entity);
}
/**
* Copy the permissions of a bookshelf to all child books.
* Returns the number of books that had permissions updated.
* @param Bookshelf $bookshelf
* @return int
* @throws \Throwable
*/
public function copyBookshelfPermissions(Bookshelf $bookshelf)
{
$shelfPermissions = $bookshelf->permissions()->get(['role_id', 'action'])->toArray();
$shelfBooks = $bookshelf->books()->get();
$updatedBookCount = 0;
foreach ($shelfBooks as $book) {
if (!userCan('restrictions-manage', $book)) continue;
$book->permissions()->delete();
$book->restricted = $bookshelf->restricted;
$book->permissions()->createMany($shelfPermissions);
$book->save();
$this->permissionService->buildJointPermissionsForEntity($book);
$updatedBookCount++;
}
return $updatedBookCount;
}
}

View File

@@ -80,7 +80,7 @@ class PermissionsRepo
/**
* Updates an existing role.
* Ensure Admin role always has all permissions.
* Ensure Admin role always have core permissions.
* @param $roleId
* @param $roleData
* @throws PermissionsException
@@ -90,13 +90,18 @@ class PermissionsRepo
$role = $this->role->findOrFail($roleId);
$permissions = isset($roleData['permissions']) ? array_keys($roleData['permissions']) : [];
$this->assignRolePermissions($role, $permissions);
if ($role->system_name === 'admin') {
$permissions = $this->permission->all()->pluck('id')->toArray();
$role->permissions()->sync($permissions);
$permissions = array_merge($permissions, [
'users-manage',
'user-roles-manage',
'restrictions-manage-all',
'restrictions-manage-own',
'settings-manage',
]);
}
$this->assignRolePermissions($role, $permissions);
$role->fill($roleData);
$role->save();
$this->permissionService->buildJointPermissionForRole($role);

View File

@@ -1,6 +1,7 @@
<?php namespace BookStack\Services;
use BookStack\Book;
use BookStack\Bookshelf;
use BookStack\Chapter;
use BookStack\Entity;
use BookStack\EntityPermission;
@@ -25,6 +26,7 @@ class PermissionService
public $book;
public $chapter;
public $page;
public $bookshelf;
protected $db;
@@ -38,22 +40,26 @@ class PermissionService
* PermissionService constructor.
* @param JointPermission $jointPermission
* @param EntityPermission $entityPermission
* @param Role $role
* @param Connection $db
* @param Bookshelf $bookshelf
* @param Book $book
* @param Chapter $chapter
* @param Page $page
* @param Role $role
*/
public function __construct(JointPermission $jointPermission, EntityPermission $entityPermission, Connection $db, Book $book, Chapter $chapter, Page $page, Role $role)
public function __construct(
JointPermission $jointPermission, EntityPermission $entityPermission, Role $role, Connection $db,
Bookshelf $bookshelf, Book $book, Chapter $chapter, Page $page
)
{
$this->db = $db;
$this->jointPermission = $jointPermission;
$this->entityPermission = $entityPermission;
$this->role = $role;
$this->bookshelf = $bookshelf;
$this->book = $book;
$this->chapter = $chapter;
$this->page = $page;
// TODO - Update so admin still goes through filters
}
/**
@@ -159,6 +165,12 @@ class PermissionService
$this->bookFetchQuery()->chunk(5, function ($books) use ($roles) {
$this->buildJointPermissionsForBooks($books, $roles);
});
// Chunk through all bookshelves
$this->bookshelf->newQuery()->select(['id', 'restricted', 'created_by'])
->chunk(50, function ($shelves) use ($roles) {
$this->buildJointPermissionsForShelves($shelves, $roles);
});
}
/**
@@ -174,6 +186,20 @@ class PermissionService
}]);
}
/**
* @param Collection $shelves
* @param array $roles
* @param bool $deleteOld
* @throws \Throwable
*/
protected function buildJointPermissionsForShelves($shelves, $roles, $deleteOld = false)
{
if ($deleteOld) {
$this->deleteManyJointPermissionsForEntities($shelves->all());
}
$this->createManyJointPermissions($shelves, $roles);
}
/**
* Build joint permissions for an array of books
* @param Collection $books
@@ -204,6 +230,7 @@ class PermissionService
/**
* Rebuild the entity jointPermissions for a particular entity.
* @param Entity $entity
* @throws \Throwable
*/
public function buildJointPermissionsForEntity(Entity $entity)
{
@@ -214,7 +241,9 @@ class PermissionService
return;
}
$entities[] = $entity->book;
if ($entity->book) {
$entities[] = $entity->book;
}
if ($entity->isA('page') && $entity->chapter_id) {
$entities[] = $entity->chapter;
@@ -226,13 +255,13 @@ class PermissionService
}
}
$this->deleteManyJointPermissionsForEntities($entities);
$this->buildJointPermissionsForEntities(collect($entities));
}
/**
* Rebuild the entity jointPermissions for a collection of entities.
* @param Collection $entities
* @throws \Throwable
*/
public function buildJointPermissionsForEntities(Collection $entities)
{
@@ -254,6 +283,12 @@ class PermissionService
$this->bookFetchQuery()->chunk(20, function ($books) use ($roles) {
$this->buildJointPermissionsForBooks($books, $roles);
});
// Chunk through all bookshelves
$this->bookshelf->newQuery()->select(['id', 'restricted', 'created_by'])
->chunk(50, function ($shelves) use ($roles) {
$this->buildJointPermissionsForShelves($shelves, $roles);
});
}
/**
@@ -412,7 +447,7 @@ class PermissionService
return $this->createJointPermissionDataArray($entity, $role, $action, $hasAccess, $hasAccess);
}
if ($entity->isA('book')) {
if ($entity->isA('book') || $entity->isA('bookshelf')) {
return $this->createJointPermissionDataArray($entity, $role, $action, $roleHasPermission, $roleHasPermissionOwn);
}
@@ -484,11 +519,6 @@ class PermissionService
*/
public function checkOwnableUserAccess(Ownable $ownable, $permission)
{
if ($this->isAdmin()) {
$this->clean();
return true;
}
$explodedPermission = explode('-', $permission);
$baseQuery = $ownable->where('id', '=', $ownable->id);
@@ -581,17 +611,16 @@ class PermissionService
$query = $this->db->query()->select('*')->from($this->db->raw("({$pageSelect->toSql()} UNION {$chapterSelect->toSql()}) AS U"))
->mergeBindings($pageSelect)->mergeBindings($chapterSelect);
if (!$this->isAdmin()) {
$whereQuery = $this->db->table('joint_permissions as jp')->selectRaw('COUNT(*)')
->whereRaw('jp.entity_id=U.id')->whereRaw('jp.entity_type=U.entity_type')
->where('jp.action', '=', 'view')->whereIn('jp.role_id', $this->getRoles())
->where(function ($query) {
$query->where('jp.has_permission', '=', 1)->orWhere(function ($query) {
$query->where('jp.has_permission_own', '=', 1)->where('jp.created_by', '=', $this->currentUser()->id);
});
// Add joint permission filter
$whereQuery = $this->db->table('joint_permissions as jp')->selectRaw('COUNT(*)')
->whereRaw('jp.entity_id=U.id')->whereRaw('jp.entity_type=U.entity_type')
->where('jp.action', '=', 'view')->whereIn('jp.role_id', $this->getRoles())
->where(function ($query) {
$query->where('jp.has_permission', '=', 1)->orWhere(function ($query) {
$query->where('jp.has_permission_own', '=', 1)->where('jp.created_by', '=', $this->currentUser()->id);
});
$query->whereRaw("({$whereQuery->toSql()}) > 0")->mergeBindings($whereQuery);
}
});
$query->whereRaw("({$whereQuery->toSql()}) > 0")->mergeBindings($whereQuery);
$query->orderBy('draft', 'desc')->orderBy('priority', 'asc');
$this->clean();
@@ -619,11 +648,6 @@ class PermissionService
});
}
if ($this->isAdmin()) {
$this->clean();
return $query;
}
$this->currentAction = $action;
return $this->entityRestrictionQuery($query);
}
@@ -639,10 +663,6 @@ class PermissionService
*/
public function filterRestrictedEntityRelations($query, $tableName, $entityIdColumn, $entityTypeColumn, $action = 'view')
{
if ($this->isAdmin()) {
$this->clean();
return $query;
}
$this->currentAction = $action;
$tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn, 'entityTypeColumn' => $entityTypeColumn];
@@ -675,11 +695,6 @@ class PermissionService
*/
public function filterRelatedPages($query, $tableName, $entityIdColumn)
{
if ($this->isAdmin()) {
$this->clean();
return $query;
}
$this->currentAction = 'view';
$tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn];
@@ -704,19 +719,6 @@ class PermissionService
return $q;
}
/**
* Check if the current user is an admin.
* @return bool
*/
private function isAdmin()
{
if ($this->isAdminUser === null) {
$this->isAdminUser = ($this->currentUser()->id !== null) ? $this->currentUser()->hasSystemRole('admin') : false;
}
return $this->isAdminUser;
}
/**
* Get the current user
* @return User