X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/876bc10d4d414c0680ea13e008b764c3ee70060f..refs/pull/5721/head:/app/Entities/Tools/TrashCan.php diff --git a/app/Entities/Tools/TrashCan.php b/app/Entities/Tools/TrashCan.php index fcf933726..5e8a93719 100644 --- a/app/Entities/Tools/TrashCan.php +++ b/app/Entities/Tools/TrashCan.php @@ -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 $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()); } } }