-<?php namespace BookStack\Entities\Tools;
+<?php
+namespace BookStack\Entities\Tools;
+
+use BookStack\Entities\EntityProvider;
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\EntityProvider;
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) {
/**
* 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);
}
/**
* 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());
}
- 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');
+ }
}
/**
* 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
$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;
}
{
$counts = [];
- /** @var Entity $instance */
- foreach ((new EntityProvider)->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;
/**
* Destroy all items that have pending deletions.
+ *
* @throws Exception
*/
public function empty(): int
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
$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;
}
* 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
$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);
}
/**
* 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;
}
/**
$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());
}
}
}