]> BookStack Code Mirror - bookstack/blobdiff - app/Entities/Repos/EntityRepo.php
Show bookshelves that a book belongs to on a book view
[bookstack] / app / Entities / Repos / EntityRepo.php
index 7ca25b785286cb7f4e5e44ccbb4216c1c1881c4e..0dd0fbb0af650f0e3c050171a48673a030a55ee3 100644 (file)
@@ -6,6 +6,7 @@ use BookStack\Actions\ViewService;
 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;
@@ -16,10 +17,10 @@ use BookStack\Exceptions\NotFoundException;
 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;
@@ -79,7 +80,7 @@ class EntityRepo
      * @param string $type
      * @param bool $allowDrafts
      * @param string $permission
-     * @return \Illuminate\Database\Query\Builder
+     * @return QueryBuilder
      */
     protected function entityQuery($type, $allowDrafts = false, $permission = 'view')
     {
@@ -142,25 +143,29 @@ class EntityRepo
      * 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;
     }
 
@@ -408,6 +413,17 @@ class EntityRepo
         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.
@@ -460,25 +476,6 @@ class EntityRepo
         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
@@ -492,70 +489,73 @@ class EntityRepo
         $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;
     }
@@ -582,79 +582,26 @@ class EntityRepo
         $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;
     }
 
     /**
@@ -766,7 +713,7 @@ class EntityRepo
         }
 
         // 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);
         }
@@ -815,24 +762,6 @@ class EntityRepo
         $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
@@ -907,6 +836,7 @@ class EntityRepo
         $shelfBooks = $bookshelf->books()->get();
         $updatedBookCount = 0;
 
+        /** @var Book $book */
         foreach ($shelfBooks as $book) {
             if (!userCan('restrictions-manage', $book)) {
                 continue;
@@ -915,7 +845,7 @@ class EntityRepo
             $book->restricted = $bookshelf->restricted;
             $book->permissions()->createMany($shelfPermissions);
             $book->save();
-            $this->permissionService->buildJointPermissionsForEntity($book);
+            $book->rebuildPermissions();
             $updatedBookCount++;
         }