use BookStack\Auth\Permissions\PermissionService;
use BookStack\Auth\User;
use BookStack\Entities\Book;
+use BookStack\Entities\BookChild;
use BookStack\Entities\Bookshelf;
use BookStack\Entities\Chapter;
use BookStack\Entities\Entity;
use BookStack\Exceptions\NotifyException;
use BookStack\Uploads\AttachmentService;
use DOMDocument;
-use DOMNode;
use DOMXPath;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Database\Query\Builder as QueryBuilder;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Throwable;
* @param string $type
* @param bool $allowDrafts
* @param string $permission
- * @return \Illuminate\Database\Query\Builder
+ * @return QueryBuilder
*/
protected function entityQuery($type, $allowDrafts = false, $permission = 'view')
{
* Get an entity by its url slug.
* @param string $type
* @param string $slug
- * @param string|bool $bookSlug
+ * @param string|null $bookSlug
* @return Entity
* @throws NotFoundException
*/
- public function getBySlug($type, $slug, $bookSlug = false)
+ public function getEntityBySlug(string $type, string $slug, string $bookSlug = null): Entity
{
- $q = $this->entityQuery($type)->where('slug', '=', $slug);
+ $type = strtolower($type);
+ $query = $this->entityQuery($type)->where('slug', '=', $slug);
- if (strtolower($type) === 'chapter' || strtolower($type) === 'page') {
- $q = $q->where('book_id', '=', function ($query) use ($bookSlug) {
+ if ($type === 'chapter' || $type === 'page') {
+ $query = $query->where('book_id', '=', function (QueryBuilder $query) use ($bookSlug) {
$query->select('id')
->from($this->entityProvider->book->getTable())
->where('slug', '=', $bookSlug)->limit(1);
});
}
- $entity = $q->first();
+
+ $entity = $query->first();
+
if ($entity === null) {
- throw new NotFoundException(trans('errors.' . strtolower($type) . '_not_found'));
+ throw new NotFoundException(trans('errors.' . $type . '_not_found'));
}
+
return $entity;
}
return collect($tree);
}
+
+ /**
+ * Get the bookshelves that a book is contained in.
+ * @param Book $book
+ * @return \Illuminate\Database\Eloquent\Collection|static[]
+ */
+ public function getBookParentShelves(Book $book)
+ {
+ return $this->permissionService->enforceEntityRestrictions('shelf', $book->shelves())->get();
+ }
+
/**
* Get the child items for a chapter sorted by priority but
* with draft items floated to the top.
return $slug;
}
- /**
- * Check if a slug already exists in the database.
- * @param string $type
- * @param string $slug
- * @param bool|integer $currentId
- * @param bool|integer $bookId
- * @return bool
- */
- protected function slugExists($type, $slug, $currentId = false, $bookId = false)
- {
- $query = $this->entityProvider->get($type)->where('slug', '=', $slug);
- if (strtolower($type) === 'page' || strtolower($type) === 'chapter') {
- $query = $query->where('book_id', '=', $bookId);
- }
- if ($currentId) {
- $query = $query->where('id', '!=', $currentId);
- }
- return $query->count() > 0;
- }
/**
* Updates entity restrictions from a request
$entity->permissions()->delete();
if ($request->filled('restrictions')) {
- foreach ($request->get('restrictions') as $roleId => $restrictions) {
- foreach ($restrictions as $action => $value) {
- $entity->permissions()->create([
+ $entityPermissionData = collect($request->get('restrictions'))->flatMap(function($restrictions, $roleId) {
+ return collect($restrictions)->keys()->map(function($action) use ($roleId) {
+ return [
'role_id' => $roleId,
- 'action' => strtolower($action)
- ]);
- }
- }
+ 'action' => strtolower($action),
+ ] ;
+ });
+ });
+
+ $entity->permissions()->createMany($entityPermissionData);
}
$entity->save();
- $this->permissionService->buildJointPermissionsForEntity($entity);
+ $entity->rebuildPermissions();
}
-
/**
* Create a new entity from request input.
* Used for books and chapters.
* @param string $type
* @param array $input
- * @param bool|Book $book
+ * @param Book|null $book
* @return Entity
*/
- public function createFromInput($type, $input = [], $book = false)
+ public function createFromInput(string $type, array $input = [], Book $book = null)
{
- $isChapter = strtolower($type) === 'chapter';
$entityModel = $this->entityProvider->get($type)->newInstance($input);
- $entityModel->slug = $this->findSuitableSlug($type, $entityModel->name, false, $isChapter ? $book->id : false);
$entityModel->created_by = user()->id;
$entityModel->updated_by = user()->id;
- $isChapter ? $book->chapters()->save($entityModel) : $entityModel->save();
+
+ if ($book) {
+ $entityModel->book_id = $book->id;
+ }
+
+ $entityModel->refreshSlug();
+ $entityModel->save();
if (isset($input['tags'])) {
$this->tagRepo->saveTagsToEntity($entityModel, $input['tags']);
}
- $this->permissionService->buildJointPermissionsForEntity($entityModel);
+ $entityModel->rebuildPermissions();
$this->searchService->indexEntity($entityModel);
return $entityModel;
}
/**
* Update entity details from request input.
- * Used for books and chapters
- * @param string $type
- * @param Entity $entityModel
- * @param array $input
- * @return Entity
+ * Used for shelves, books and chapters.
*/
- public function updateFromInput($type, Entity $entityModel, $input = [])
+ public function updateFromInput(Entity $entityModel, array $input): Entity
{
- if ($entityModel->name !== $input['name']) {
- $entityModel->slug = $this->findSuitableSlug($type, $input['name'], $entityModel->id);
- }
$entityModel->fill($input);
$entityModel->updated_by = user()->id;
+
+ if ($entityModel->isDirty('name')) {
+ $entityModel->refreshSlug();
+ }
+
$entityModel->save();
if (isset($input['tags'])) {
$this->tagRepo->saveTagsToEntity($entityModel, $input['tags']);
}
- $this->permissionService->buildJointPermissionsForEntity($entityModel);
+ $entityModel->rebuildPermissions();
$this->searchService->indexEntity($entityModel);
return $entityModel;
}
$shelf->books()->sync($syncData);
}
- /**
- * Append a Book to a BookShelf.
- * @param Bookshelf $shelf
- * @param Book $book
- */
- public function appendBookToShelf(Bookshelf $shelf, Book $book)
- {
- if ($shelf->contains($book)) {
- return;
- }
-
- $maxOrder = $shelf->books()->max('order');
- $shelf->books()->attach($book->id, ['order' => $maxOrder + 1]);
- }
-
/**
* Change the book that an entity belongs to.
- * @param string $type
- * @param integer $newBookId
- * @param Entity $entity
- * @param bool $rebuildPermissions
- * @return Entity
*/
- public function changeBook($type, $newBookId, Entity $entity, $rebuildPermissions = false)
+ public function changeBook(BookChild $bookChild, int $newBookId): Entity
{
- $entity->book_id = $newBookId;
+ $bookChild->book_id = $newBookId;
+ $bookChild->refreshSlug();
+ $bookChild->save();
+
// Update related activity
- foreach ($entity->activity as $activity) {
- $activity->book_id = $newBookId;
- $activity->save();
- }
- $entity->slug = $this->findSuitableSlug($type, $entity->name, $entity->id, $newBookId);
- $entity->save();
+ $bookChild->activity()->update(['book_id' => $newBookId]);
// Update all child pages if a chapter
- if (strtolower($type) === 'chapter') {
- foreach ($entity->pages as $page) {
- $this->changeBook('page', $newBookId, $page, false);
+ if ($bookChild->isA('chapter')) {
+ foreach ($bookChild->pages as $page) {
+ $this->changeBook($page, $newBookId);
}
}
- // Update permissions if applicable
- if ($rebuildPermissions) {
- $entity->load('book');
- $this->permissionService->buildJointPermissionsForEntity($entity->book);
- }
-
- return $entity;
- }
-
- /**
- * Alias method to update the book jointPermissions in the PermissionService.
- * @param Book $book
- */
- public function buildJointPermissionsForBook(Book $book)
- {
- $this->permissionService->buildJointPermissionsForEntity($book);
- }
-
- /**
- * Format a name as a url slug.
- * @param $name
- * @return string
- */
- protected function nameToSlug($name)
- {
- $slug = preg_replace('/[\+\/\\\?\@\}\{\.\,\=\[\]\#\&\!\*\'\;\:\$\%]/', '', mb_strtolower($name));
- $slug = preg_replace('/\s{2,}/', ' ', $slug);
- $slug = str_replace(' ', '-', $slug);
- if ($slug === "") {
- $slug = substr(md5(rand(1, 500)), 0, 5);
- }
- return $slug;
+ return $bookChild;
}
/**
}
// Remove data or JavaScript iFrames
- $badIframes = $xPath->query('//*[contains(@src, \'data:\')] | //*[contains(@src, \'javascript:\')]');
+ $badIframes = $xPath->query('//*[contains(@src, \'data:\')] | //*[contains(@src, \'javascript:\')] | //*[@srcdoc]');
foreach ($badIframes as $badIframe) {
$badIframe->parentNode->removeChild($badIframe);
}
$shelf->delete();
}
- /**
- * Destroy the provided book and all its child entities.
- * @param Book $book
- * @throws NotifyException
- * @throws Throwable
- */
- public function destroyBook(Book $book)
- {
- foreach ($book->pages as $page) {
- $this->destroyPage($page);
- }
- foreach ($book->chapters as $chapter) {
- $this->destroyChapter($chapter);
- }
- $this->destroyEntityCommonRelations($book);
- $book->delete();
- }
-
/**
* Destroy a chapter and its relations.
* @param Chapter $chapter
$shelfBooks = $bookshelf->books()->get();
$updatedBookCount = 0;
+ /** @var Book $book */
foreach ($shelfBooks as $book) {
if (!userCan('restrictions-manage', $book)) {
continue;
$book->restricted = $bookshelf->restricted;
$book->permissions()->createMany($shelfPermissions);
$book->save();
- $this->permissionService->buildJointPermissionsForEntity($book);
+ $book->rebuildPermissions();
$updatedBookCount++;
}