]> BookStack Code Mirror - bookstack/blobdiff - app/Entities/Tools/TrashCan.php
respective book and chapter structure added.
[bookstack] / app / Entities / Tools / TrashCan.php
index 62d373e97eba0861e45c3beb68f483e13561d3f5..39c982cdc92a25219b0df21bfaadac9c4b52a372 100644 (file)
@@ -1,38 +1,51 @@
-<?php namespace BookStack\Entities\Tools;
+<?php
+
+namespace BookStack\Entities\Tools;
 
-use BookStack\Entities\Book;
-use BookStack\Entities\Bookshelf;
-use BookStack\Entities\Chapter;
-use BookStack\Entities\Deletion;
-use BookStack\Entities\Entity;
 use BookStack\Entities\EntityProvider;
-use BookStack\Entities\HasCoverImage;
-use BookStack\Entities\Page;
+use BookStack\Entities\Models\Book;
+use BookStack\Entities\Models\Bookshelf;
+use BookStack\Entities\Models\Chapter;
+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 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();
     }
 
     /**
      * Send a book to the recycle bin.
+     *
      * @throws Exception
      */
     public function softDestroyBook(Book $book)
     {
+        $this->ensureDeletable($book);
         Deletion::createForEntity($book);
 
         foreach ($book->pages as $page) {
@@ -48,11 +61,13 @@ class TrashCan
 
     /**
      * Send a chapter to the recycle bin.
+     *
      * @throws Exception
      */
     public function softDestroyChapter(Chapter $chapter, bool $recordDelete = true)
     {
         if ($recordDelete) {
+            $this->ensureDeletable($chapter);
             Deletion::createForEntity($chapter);
         }
 
@@ -67,40 +82,72 @@ class TrashCan
 
     /**
      * Send a page to the recycle bin.
+     *
      * @throws Exception
      */
     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());
+            }
+            $removeCustomHome = true;
+        }
+
+        // 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;
             }
-            setting()->remove('app-homepage');
         }
 
-        $page->delete();
+        if ($removeCustomHome) {
+            setting()->remove('app-homepage');
+        }
     }
 
     /**
      * Remove a bookshelf from the system.
+     *
      * @throws Exception
      */
     protected function destroyShelf(Bookshelf $shelf): int
     {
         $this->destroyCommonRelations($shelf);
         $shelf->forceDelete();
+
         return 1;
     }
 
     /**
      * Remove a book from the system.
      * Destroys any child chapters and pages.
+     *
      * @throws Exception
      */
     protected function destroyBook(Book $book): int
@@ -120,45 +167,59 @@ class TrashCan
 
         $this->destroyCommonRelations($book);
         $book->forceDelete();
+
         return $count + 1;
     }
 
     /**
      * Remove a chapter from the system.
      * Destroys all pages within.
+     *
      * @throws Exception
      */
     protected function destroyChapter(Chapter $chapter): int
     {
         $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);
         $chapter->forceDelete();
+
         return $count + 1;
     }
 
     /**
      * Remove a page from the system.
+     *
      * @throws Exception
      */
     protected function destroyPage(Page $page): int
     {
         $this->destroyCommonRelations($page);
+        $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;
     }
 
@@ -168,12 +229,12 @@ class TrashCan
      */
     public function getTrashedCounts(): array
     {
-        $provider = app(EntityProvider::class);
         $counts = [];
 
-        /** @var Entity $instance */
-        foreach ($provider->all() as $key => $instance) {
-            $counts[$key] = $instance->newQuery()->onlyTrashed()->count();
+        foreach ((new EntityProvider())->all() as $key => $instance) {
+            /** @var Builder<Entity> $query */
+            $query = $instance->newQuery();
+            $counts[$key] = $query->onlyTrashed()->count();
         }
 
         return $counts;
@@ -181,6 +242,7 @@ class TrashCan
 
     /**
      * Destroy all items that have pending deletions.
+     *
      * @throws Exception
      */
     public function empty(): int
@@ -190,11 +252,13 @@ class TrashCan
         foreach ($deletions as $deletion) {
             $deleteCount += $this->destroyFromDeletion($deletion);
         }
+
         return $deleteCount;
     }
 
     /**
      * Destroy an element from the given deletion model.
+     *
      * @throws Exception
      */
     public function destroyFromDeletion(Deletion $deletion): int
@@ -207,28 +271,33 @@ class TrashCan
             $count = $this->destroyEntity($deletion->deletable);
         }
         $deletion->delete();
+
         return $count;
     }
 
     /**
      * Restore the content within the given deletion.
+     *
      * @throws Exception
      */
     public function restoreFromDeletion(Deletion $deletion): int
     {
         $shouldRestore = true;
         $restoreCount = 0;
-        $parent = $deletion->deletable->getParent();
 
-        if ($parent && $parent->trashed()) {
-            $shouldRestore = false;
+        if ($deletion->deletable instanceof Entity) {
+            $parent = $deletion->deletable->getParent();
+            if ($parent && $parent->trashed()) {
+                $shouldRestore = false;
+            }
         }
 
-        if ($shouldRestore) {
+        if ($deletion->deletable instanceof Entity && $shouldRestore) {
             $restoreCount = $this->restoreEntity($deletion->deletable);
         }
 
         $deletion->delete();
+
         return $restoreCount;
     }
 
@@ -236,6 +305,7 @@ class TrashCan
      * Automatically clear old content from the recycle bin
      * depending on the configured lifetime.
      * Returns the total number of deleted elements.
+     *
      * @throws Exception
      */
     public function autoClearOld(): int
@@ -274,11 +344,11 @@ class TrashCan
             $count++;
         };
 
-        if ($entity->isA('chapter') || $entity->isA('book')) {
+        if ($entity instanceof Chapter || $entity instanceof Book) {
             $entity->pages()->withTrashed()->withCount('deletions')->get()->each($restoreAction);
         }
 
-        if ($entity->isA('book')) {
+        if ($entity instanceof Book) {
             $entity->chapters()->withTrashed()->withCount('deletions')->get()->each($restoreAction);
         }
 
@@ -287,21 +357,25 @@ class TrashCan
 
     /**
      * Destroy the given entity.
+     *
+     * @throws Exception
      */
-    protected function destroyEntity(Entity $entity): int
+    public function destroyEntity(Entity $entity): int
     {
-        if ($entity->isA('page')) {
+        if ($entity instanceof Page) {
             return $this->destroyPage($entity);
         }
-        if ($entity->isA('chapter')) {
+        if ($entity instanceof Chapter) {
             return $this->destroyChapter($entity);
         }
-        if ($entity->isA('book')) {
+        if ($entity instanceof Book) {
             return $this->destroyBook($entity);
         }
-        if ($entity->isA('shelf')) {
+        if ($entity instanceof Bookshelf) {
             return $this->destroyShelf($entity);
         }
+
+        return 0;
     }
 
     /**
@@ -317,10 +391,14 @@ class TrashCan
         $entity->jointPermissions()->delete();
         $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());
         }
     }
 }