]> BookStack Code Mirror - bookstack/blobdiff - app/Entities/Tools/TrashCan.php
Perms: Fixed some issues made when adding transactions
[bookstack] / app / Entities / Tools / TrashCan.php
index fcf933726d2e0b6d8320ef0e9b72ab07b989dbc3..5e8a9371942ea246098bf066851956401e942c1c 100644 (file)
@@ -10,20 +10,31 @@ 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;
 use BookStack\Uploads\ImageService;
+use BookStack\Util\DatabaseTransaction;
 use Exception;
+use Illuminate\Database\Eloquent\Builder;
 use Illuminate\Support\Carbon;
 
 class TrashCan
 {
+    public function __construct(
+        protected EntityQueries $queries,
+    ) {
+    }
+
     /**
      * Send a shelf to the recycle bin.
+     *
+     * @throws NotifyException
      */
     public function softDestroyShelf(Bookshelf $shelf)
     {
+        $this->ensureDeletable($shelf);
         Deletion::createForEntity($shelf);
         $shelf->delete();
     }
@@ -35,6 +46,7 @@ class TrashCan
      */
     public function softDestroyBook(Book $book)
     {
+        $this->ensureDeletable($book);
         Deletion::createForEntity($book);
 
         foreach ($book->pages as $page) {
@@ -56,6 +68,7 @@ class TrashCan
     public function softDestroyChapter(Chapter $chapter, bool $recordDelete = true)
     {
         if ($recordDelete) {
+            $this->ensureDeletable($chapter);
             Deletion::createForEntity($chapter);
         }
 
@@ -76,19 +89,47 @@ class TrashCan
     public function softDestroyPage(Page $page, bool $recordDelete = true)
     {
         if ($recordDelete) {
+            $this->ensureDeletable($page);
             Deletion::createForEntity($page);
         }
 
-        // Check if set as custom homepage & remove setting if not used or throw error if active
-        $customHome = setting('app-homepage', '0:');
-        if (intval($page->id) === intval(explode(':', $customHome)[0])) {
-            if (setting('app-homepage-type') === 'page') {
-                throw new NotifyException(trans('errors.page_custom_home_deletion'), $page->getUrl());
+        $page->delete();
+    }
+
+    /**
+     * Ensure the given entity is deletable.
+     * Is not for permissions, but logical conditions within the application.
+     * Will throw if not deletable.
+     *
+     * @throws NotifyException
+     */
+    protected function ensureDeletable(Entity $entity): void
+    {
+        $customHomeId = intval(explode(':', setting('app-homepage', '0:'))[0]);
+        $customHomeActive = setting('app-homepage-type') === 'page';
+        $removeCustomHome = false;
+
+        // Check custom homepage usage for pages
+        if ($entity instanceof Page && $entity->id === $customHomeId) {
+            if ($customHomeActive) {
+                throw new NotifyException(trans('errors.page_custom_home_deletion'), $entity->getUrl());
             }
-            setting()->remove('app-homepage');
+            $removeCustomHome = true;
         }
 
-        $page->delete();
+        // Check custom homepage usage within chapters or books
+        if ($entity instanceof Chapter || $entity instanceof Book) {
+            if ($entity->pages()->where('id', '=', $customHomeId)->exists()) {
+                if ($customHomeActive) {
+                    throw new NotifyException(trans('errors.page_custom_home_deletion'), $entity->getUrl());
+                }
+                $removeCustomHome = true;
+            }
+        }
+
+        if ($removeCustomHome) {
+            setting()->remove('app-homepage');
+        }
     }
 
     /**
@@ -141,11 +182,9 @@ class TrashCan
     {
         $count = 0;
         $pages = $chapter->pages()->withTrashed()->get();
-        if (count($pages)) {
-            foreach ($pages as $page) {
-                $this->destroyPage($page);
-                $count++;
-            }
+        foreach ($pages as $page) {
+            $this->destroyPage($page);
+            $count++;
         }
 
         $this->destroyCommonRelations($chapter);
@@ -165,11 +204,21 @@ class TrashCan
         $page->allRevisions()->delete();
 
         // Delete Attached Files
-        $attachmentService = app(AttachmentService::class);
+        $attachmentService = app()->make(AttachmentService::class);
         foreach ($page->attachments as $attachment) {
             $attachmentService->deleteFile($attachment);
         }
 
+        // Remove book template usages
+        $this->queries->books->start()
+            ->where('default_template_id', '=', $page->id)
+            ->update(['default_template_id' => null]);
+
+        // Remove chapter template usages
+        $this->queries->chapters->start()
+            ->where('default_template_id', '=', $page->id)
+            ->update(['default_template_id' => null]);
+
         $page->forceDelete();
 
         return 1;
@@ -183,9 +232,10 @@ class TrashCan
     {
         $counts = [];
 
-        /** @var Entity $instance */
         foreach ((new EntityProvider())->all() as $key => $instance) {
-            $counts[$key] = $instance->newQuery()->onlyTrashed()->count();
+            /** @var Builder<Entity> $query */
+            $query = $instance->newQuery();
+            $counts[$key] = $query->onlyTrashed()->count();
         }
 
         return $counts;
@@ -308,25 +358,26 @@ class TrashCan
 
     /**
      * Destroy the given entity.
+     * Returns the number of total entities destroyed in the operation.
      *
      * @throws Exception
      */
-    protected function destroyEntity(Entity $entity): int
+    public function destroyEntity(Entity $entity): int
     {
-        if ($entity instanceof Page) {
-            return $this->destroyPage($entity);
-        }
-        if ($entity instanceof Chapter) {
-            return $this->destroyChapter($entity);
-        }
-        if ($entity instanceof Book) {
-            return $this->destroyBook($entity);
-        }
-        if ($entity instanceof Bookshelf) {
-            return $this->destroyShelf($entity);
-        }
+        $result = (new DatabaseTransaction(function () use ($entity) {
+            if ($entity instanceof Page) {
+                return $this->destroyPage($entity);
+            } else if ($entity instanceof Chapter) {
+                return $this->destroyChapter($entity);
+            } else if ($entity instanceof Book) {
+                return $this->destroyBook($entity);
+            } else if ($entity instanceof Bookshelf) {
+                return $this->destroyShelf($entity);
+            }
+            return null;
+        }))->run();
 
-        return 0;
+        return $result ?? 0;
     }
 
     /**
@@ -343,10 +394,13 @@ class TrashCan
         $entity->searchTerms()->delete();
         $entity->deletions()->delete();
         $entity->favourites()->delete();
+        $entity->watches()->delete();
+        $entity->referencesTo()->delete();
+        $entity->referencesFrom()->delete();
 
-        if ($entity instanceof HasCoverImage && $entity->cover) {
+        if ($entity instanceof HasCoverImage && $entity->cover()->exists()) {
             $imageService = app()->make(ImageService::class);
-            $imageService->destroy($entity->cover);
+            $imageService->destroy($entity->cover()->first());
         }
     }
 }