1 <?php namespace BookStack\Entities\Tools;
3 use BookStack\Entities\Models\Book;
4 use BookStack\Entities\Models\Bookshelf;
5 use BookStack\Entities\Models\Chapter;
6 use BookStack\Entities\Models\Deletion;
7 use BookStack\Entities\Models\Entity;
8 use BookStack\Entities\EntityProvider;
9 use BookStack\Entities\Models\HasCoverImage;
10 use BookStack\Entities\Models\Page;
11 use BookStack\Exceptions\NotifyException;
12 use BookStack\Facades\Activity;
13 use BookStack\Uploads\AttachmentService;
14 use BookStack\Uploads\ImageService;
16 use Illuminate\Support\Carbon;
22 * Send a shelf to the recycle bin.
24 public function softDestroyShelf(Bookshelf $shelf)
26 Deletion::createForEntity($shelf);
31 * Send a book to the recycle bin.
34 public function softDestroyBook(Book $book)
36 Deletion::createForEntity($book);
38 foreach ($book->pages as $page) {
39 $this->softDestroyPage($page, false);
42 foreach ($book->chapters as $chapter) {
43 $this->softDestroyChapter($chapter, false);
50 * Send a chapter to the recycle bin.
53 public function softDestroyChapter(Chapter $chapter, bool $recordDelete = true)
56 Deletion::createForEntity($chapter);
59 if (count($chapter->pages) > 0) {
60 foreach ($chapter->pages as $page) {
61 $this->softDestroyPage($page, false);
69 * Send a page to the recycle bin.
72 public function softDestroyPage(Page $page, bool $recordDelete = true)
75 Deletion::createForEntity($page);
78 // Check if set as custom homepage & remove setting if not used or throw error if active
79 $customHome = setting('app-homepage', '0:');
80 if (intval($page->id) === intval(explode(':', $customHome)[0])) {
81 if (setting('app-homepage-type') === 'page') {
82 throw new NotifyException(trans('errors.page_custom_home_deletion'), $page->getUrl());
84 setting()->remove('app-homepage');
91 * Remove a bookshelf from the system.
94 protected function destroyShelf(Bookshelf $shelf): int
96 $this->destroyCommonRelations($shelf);
97 $shelf->forceDelete();
102 * Remove a book from the system.
103 * Destroys any child chapters and pages.
106 protected function destroyBook(Book $book): int
109 $pages = $book->pages()->withTrashed()->get();
110 foreach ($pages as $page) {
111 $this->destroyPage($page);
115 $chapters = $book->chapters()->withTrashed()->get();
116 foreach ($chapters as $chapter) {
117 $this->destroyChapter($chapter);
121 $this->destroyCommonRelations($book);
122 $book->forceDelete();
127 * Remove a chapter from the system.
128 * Destroys all pages within.
131 protected function destroyChapter(Chapter $chapter): int
134 $pages = $chapter->pages()->withTrashed()->get();
136 foreach ($pages as $page) {
137 $this->destroyPage($page);
142 $this->destroyCommonRelations($chapter);
143 $chapter->forceDelete();
148 * Remove a page from the system.
151 protected function destroyPage(Page $page): int
153 $this->destroyCommonRelations($page);
154 $page->allRevisions()->delete();
156 // Delete Attached Files
157 $attachmentService = app(AttachmentService::class);
158 foreach ($page->attachments as $attachment) {
159 $attachmentService->deleteFile($attachment);
162 $page->forceDelete();
167 * Get the total counts of those that have been trashed
168 * but not yet fully deleted (In recycle bin).
170 public function getTrashedCounts(): array
174 /** @var Entity $instance */
175 foreach ((new EntityProvider)->all() as $key => $instance) {
176 $counts[$key] = $instance->newQuery()->onlyTrashed()->count();
183 * Destroy all items that have pending deletions.
186 public function empty(): int
188 $deletions = Deletion::all();
190 foreach ($deletions as $deletion) {
191 $deleteCount += $this->destroyFromDeletion($deletion);
197 * Destroy an element from the given deletion model.
200 public function destroyFromDeletion(Deletion $deletion): int
202 // We directly load the deletable element here just to ensure it still
203 // exists in the event it has already been destroyed during this request.
204 $entity = $deletion->deletable()->first();
207 $count = $this->destroyEntity($deletion->deletable);
214 * Restore the content within the given deletion.
217 public function restoreFromDeletion(Deletion $deletion): int
219 $shouldRestore = true;
221 $parent = $deletion->deletable->getParent();
223 if ($parent && $parent->trashed()) {
224 $shouldRestore = false;
227 if ($shouldRestore) {
228 $restoreCount = $this->restoreEntity($deletion->deletable);
232 return $restoreCount;
236 * Automatically clear old content from the recycle bin
237 * depending on the configured lifetime.
238 * Returns the total number of deleted elements.
241 public function autoClearOld(): int
243 $lifetime = intval(config('app.recycle_bin_lifetime'));
248 $clearBeforeDate = Carbon::now()->addSeconds(10)->subDays($lifetime);
251 $deletionsToRemove = Deletion::query()->where('created_at', '<', $clearBeforeDate)->get();
252 foreach ($deletionsToRemove as $deletion) {
253 $deleteCount += $this->destroyFromDeletion($deletion);
260 * Restore an entity so it is essentially un-deleted.
261 * Deletions on restored child elements will be removed during this restoration.
263 protected function restoreEntity(Entity $entity): int
268 $restoreAction = function ($entity) use (&$count) {
269 if ($entity->deletions_count > 0) {
270 $entity->deletions()->delete();
277 if ($entity instanceof Chapter || $entity instanceof Book) {
278 $entity->pages()->withTrashed()->withCount('deletions')->get()->each($restoreAction);
281 if ($entity instanceof Book) {
282 $entity->chapters()->withTrashed()->withCount('deletions')->get()->each($restoreAction);
289 * Destroy the given entity.
292 protected function destroyEntity(Entity $entity): int
294 if ($entity instanceof Page) {
295 return $this->destroyPage($entity);
297 if ($entity instanceof Chapter) {
298 return $this->destroyChapter($entity);
300 if ($entity instanceof Book) {
301 return $this->destroyBook($entity);
303 if ($entity instanceof Bookshelf) {
304 return $this->destroyShelf($entity);
309 * Update entity relations to remove or update outstanding connections.
311 protected function destroyCommonRelations(Entity $entity)
313 Activity::removeEntity($entity);
314 $entity->views()->delete();
315 $entity->permissions()->delete();
316 $entity->tags()->delete();
317 $entity->comments()->delete();
318 $entity->jointPermissions()->delete();
319 $entity->searchTerms()->delete();
320 $entity->deletions()->delete();
321 $entity->favourites()->delete();
323 if ($entity instanceof HasCoverImage && $entity->cover) {
324 $imageService = app()->make(ImageService::class);
325 $imageService->destroy($entity->cover);