3 namespace BookStack\Entities\Tools;
5 use BookStack\Entities\EntityProvider;
6 use BookStack\Entities\Models\Book;
7 use BookStack\Entities\Models\Bookshelf;
8 use BookStack\Entities\Models\Chapter;
9 use BookStack\Entities\Models\Deletion;
10 use BookStack\Entities\Models\Entity;
11 use BookStack\Entities\Models\HasCoverImage;
12 use BookStack\Entities\Models\Page;
13 use BookStack\Exceptions\NotifyException;
14 use BookStack\Facades\Activity;
15 use BookStack\Uploads\AttachmentService;
16 use BookStack\Uploads\ImageService;
18 use Illuminate\Support\Carbon;
23 * Send a shelf to the recycle bin.
25 public function softDestroyShelf(Bookshelf $shelf)
27 Deletion::createForEntity($shelf);
32 * Send a book to the recycle bin.
36 public function softDestroyBook(Book $book)
38 Deletion::createForEntity($book);
40 foreach ($book->pages as $page) {
41 $this->softDestroyPage($page, false);
44 foreach ($book->chapters as $chapter) {
45 $this->softDestroyChapter($chapter, false);
52 * Send a chapter to the recycle bin.
56 public function softDestroyChapter(Chapter $chapter, bool $recordDelete = true)
59 Deletion::createForEntity($chapter);
62 if (count($chapter->pages) > 0) {
63 foreach ($chapter->pages as $page) {
64 $this->softDestroyPage($page, false);
72 * Send a page to the recycle bin.
76 public function softDestroyPage(Page $page, bool $recordDelete = true)
79 Deletion::createForEntity($page);
82 // Check if set as custom homepage & remove setting if not used or throw error if active
83 $customHome = setting('app-homepage', '0:');
84 if (intval($page->id) === intval(explode(':', $customHome)[0])) {
85 if (setting('app-homepage-type') === 'page') {
86 throw new NotifyException(trans('errors.page_custom_home_deletion'), $page->getUrl());
88 setting()->remove('app-homepage');
95 * Remove a bookshelf from the system.
99 protected function destroyShelf(Bookshelf $shelf): int
101 $this->destroyCommonRelations($shelf);
102 $shelf->forceDelete();
108 * Remove a book from the system.
109 * Destroys any child chapters and pages.
113 protected function destroyBook(Book $book): int
116 $pages = $book->pages()->withTrashed()->get();
117 foreach ($pages as $page) {
118 $this->destroyPage($page);
122 $chapters = $book->chapters()->withTrashed()->get();
123 foreach ($chapters as $chapter) {
124 $this->destroyChapter($chapter);
128 $this->destroyCommonRelations($book);
129 $book->forceDelete();
135 * Remove a chapter from the system.
136 * Destroys all pages within.
140 protected function destroyChapter(Chapter $chapter): int
143 $pages = $chapter->pages()->withTrashed()->get();
145 foreach ($pages as $page) {
146 $this->destroyPage($page);
151 $this->destroyCommonRelations($chapter);
152 $chapter->forceDelete();
158 * Remove a page from the system.
162 protected function destroyPage(Page $page): int
164 $this->destroyCommonRelations($page);
165 $page->allRevisions()->delete();
167 // Delete Attached Files
168 $attachmentService = app(AttachmentService::class);
169 foreach ($page->attachments as $attachment) {
170 $attachmentService->deleteFile($attachment);
173 $page->forceDelete();
179 * Get the total counts of those that have been trashed
180 * but not yet fully deleted (In recycle bin).
182 public function getTrashedCounts(): array
186 /** @var Entity $instance */
187 foreach ((new EntityProvider())->all() as $key => $instance) {
188 $counts[$key] = $instance->newQuery()->onlyTrashed()->count();
195 * Destroy all items that have pending deletions.
199 public function empty(): int
201 $deletions = Deletion::all();
203 foreach ($deletions as $deletion) {
204 $deleteCount += $this->destroyFromDeletion($deletion);
211 * Destroy an element from the given deletion model.
215 public function destroyFromDeletion(Deletion $deletion): int
217 // We directly load the deletable element here just to ensure it still
218 // exists in the event it has already been destroyed during this request.
219 $entity = $deletion->deletable()->first();
222 $count = $this->destroyEntity($deletion->deletable);
230 * Restore the content within the given deletion.
234 public function restoreFromDeletion(Deletion $deletion): int
236 $shouldRestore = true;
239 if ($deletion->deletable instanceof Entity) {
240 $parent = $deletion->deletable->getParent();
241 if ($parent && $parent->trashed()) {
242 $shouldRestore = false;
246 if ($deletion->deletable instanceof Entity && $shouldRestore) {
247 $restoreCount = $this->restoreEntity($deletion->deletable);
252 return $restoreCount;
256 * Automatically clear old content from the recycle bin
257 * depending on the configured lifetime.
258 * Returns the total number of deleted elements.
262 public function autoClearOld(): int
264 $lifetime = intval(config('app.recycle_bin_lifetime'));
269 $clearBeforeDate = Carbon::now()->addSeconds(10)->subDays($lifetime);
272 $deletionsToRemove = Deletion::query()->where('created_at', '<', $clearBeforeDate)->get();
273 foreach ($deletionsToRemove as $deletion) {
274 $deleteCount += $this->destroyFromDeletion($deletion);
281 * Restore an entity so it is essentially un-deleted.
282 * Deletions on restored child elements will be removed during this restoration.
284 protected function restoreEntity(Entity $entity): int
289 $restoreAction = function ($entity) use (&$count) {
290 if ($entity->deletions_count > 0) {
291 $entity->deletions()->delete();
298 if ($entity instanceof Chapter || $entity instanceof Book) {
299 $entity->pages()->withTrashed()->withCount('deletions')->get()->each($restoreAction);
302 if ($entity instanceof Book) {
303 $entity->chapters()->withTrashed()->withCount('deletions')->get()->each($restoreAction);
310 * Destroy the given entity.
314 protected function destroyEntity(Entity $entity): int
316 if ($entity instanceof Page) {
317 return $this->destroyPage($entity);
319 if ($entity instanceof Chapter) {
320 return $this->destroyChapter($entity);
322 if ($entity instanceof Book) {
323 return $this->destroyBook($entity);
325 if ($entity instanceof Bookshelf) {
326 return $this->destroyShelf($entity);
333 * Update entity relations to remove or update outstanding connections.
335 protected function destroyCommonRelations(Entity $entity)
337 Activity::removeEntity($entity);
338 $entity->views()->delete();
339 $entity->permissions()->delete();
340 $entity->tags()->delete();
341 $entity->comments()->delete();
342 $entity->jointPermissions()->delete();
343 $entity->searchTerms()->delete();
344 $entity->deletions()->delete();
345 $entity->favourites()->delete();
347 if ($entity instanceof HasCoverImage && $entity->cover) {
348 $imageService = app()->make(ImageService::class);
349 $imageService->destroy($entity->cover);