mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-11-29 04:10:09 +08:00
c7a2d568bf
Tools seems to fit better since the classes were a bit of a mixed bunch and did not always manage. Also simplified the structure of the SlugGenerator class. Also focused EntityContext on shelves and simplified to use session helper.
327 lines
8.8 KiB
PHP
327 lines
8.8 KiB
PHP
<?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\Exceptions\NotifyException;
|
|
use BookStack\Facades\Activity;
|
|
use BookStack\Uploads\AttachmentService;
|
|
use BookStack\Uploads\ImageService;
|
|
use Exception;
|
|
use Illuminate\Support\Carbon;
|
|
|
|
class TrashCan
|
|
{
|
|
|
|
/**
|
|
* Send a shelf to the recycle bin.
|
|
*/
|
|
public function softDestroyShelf(Bookshelf $shelf)
|
|
{
|
|
Deletion::createForEntity($shelf);
|
|
$shelf->delete();
|
|
}
|
|
|
|
/**
|
|
* Send a book to the recycle bin.
|
|
* @throws Exception
|
|
*/
|
|
public function softDestroyBook(Book $book)
|
|
{
|
|
Deletion::createForEntity($book);
|
|
|
|
foreach ($book->pages as $page) {
|
|
$this->softDestroyPage($page, false);
|
|
}
|
|
|
|
foreach ($book->chapters as $chapter) {
|
|
$this->softDestroyChapter($chapter, false);
|
|
}
|
|
|
|
$book->delete();
|
|
}
|
|
|
|
/**
|
|
* Send a chapter to the recycle bin.
|
|
* @throws Exception
|
|
*/
|
|
public function softDestroyChapter(Chapter $chapter, bool $recordDelete = true)
|
|
{
|
|
if ($recordDelete) {
|
|
Deletion::createForEntity($chapter);
|
|
}
|
|
|
|
if (count($chapter->pages) > 0) {
|
|
foreach ($chapter->pages as $page) {
|
|
$this->softDestroyPage($page, false);
|
|
}
|
|
}
|
|
|
|
$chapter->delete();
|
|
}
|
|
|
|
/**
|
|
* Send a page to the recycle bin.
|
|
* @throws Exception
|
|
*/
|
|
public function softDestroyPage(Page $page, bool $recordDelete = true)
|
|
{
|
|
if ($recordDelete) {
|
|
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());
|
|
}
|
|
setting()->remove('app-homepage');
|
|
}
|
|
|
|
$page->delete();
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
{
|
|
$count = 0;
|
|
$pages = $book->pages()->withTrashed()->get();
|
|
foreach ($pages as $page) {
|
|
$this->destroyPage($page);
|
|
$count++;
|
|
}
|
|
|
|
$chapters = $book->chapters()->withTrashed()->get();
|
|
foreach ($chapters as $chapter) {
|
|
$this->destroyChapter($chapter);
|
|
$count++;
|
|
}
|
|
|
|
$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++;
|
|
}
|
|
}
|
|
|
|
$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);
|
|
|
|
// Delete Attached Files
|
|
$attachmentService = app(AttachmentService::class);
|
|
foreach ($page->attachments as $attachment) {
|
|
$attachmentService->deleteFile($attachment);
|
|
}
|
|
|
|
$page->forceDelete();
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Get the total counts of those that have been trashed
|
|
* but not yet fully deleted (In recycle bin).
|
|
*/
|
|
public function getTrashedCounts(): array
|
|
{
|
|
$provider = app(EntityProvider::class);
|
|
$counts = [];
|
|
|
|
/** @var Entity $instance */
|
|
foreach ($provider->all() as $key => $instance) {
|
|
$counts[$key] = $instance->newQuery()->onlyTrashed()->count();
|
|
}
|
|
|
|
return $counts;
|
|
}
|
|
|
|
/**
|
|
* Destroy all items that have pending deletions.
|
|
* @throws Exception
|
|
*/
|
|
public function empty(): int
|
|
{
|
|
$deletions = Deletion::all();
|
|
$deleteCount = 0;
|
|
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
|
|
{
|
|
// We directly load the deletable element here just to ensure it still
|
|
// exists in the event it has already been destroyed during this request.
|
|
$entity = $deletion->deletable()->first();
|
|
$count = 0;
|
|
if ($entity) {
|
|
$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 ($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
|
|
{
|
|
$lifetime = intval(config('app.recycle_bin_lifetime'));
|
|
if ($lifetime < 0) {
|
|
return 0;
|
|
}
|
|
|
|
$clearBeforeDate = Carbon::now()->addSeconds(10)->subDays($lifetime);
|
|
$deleteCount = 0;
|
|
|
|
$deletionsToRemove = Deletion::query()->where('created_at', '<', $clearBeforeDate)->get();
|
|
foreach ($deletionsToRemove as $deletion) {
|
|
$deleteCount += $this->destroyFromDeletion($deletion);
|
|
}
|
|
|
|
return $deleteCount;
|
|
}
|
|
|
|
/**
|
|
* Restore an entity so it is essentially un-deleted.
|
|
* Deletions on restored child elements will be removed during this restoration.
|
|
*/
|
|
protected function restoreEntity(Entity $entity): int
|
|
{
|
|
$count = 1;
|
|
$entity->restore();
|
|
|
|
$restoreAction = function ($entity) use (&$count) {
|
|
if ($entity->deletions_count > 0) {
|
|
$entity->deletions()->delete();
|
|
}
|
|
|
|
$entity->restore();
|
|
$count++;
|
|
};
|
|
|
|
if ($entity->isA('chapter') || $entity->isA('book')) {
|
|
$entity->pages()->withTrashed()->withCount('deletions')->get()->each($restoreAction);
|
|
}
|
|
|
|
if ($entity->isA('book')) {
|
|
$entity->chapters()->withTrashed()->withCount('deletions')->get()->each($restoreAction);
|
|
}
|
|
|
|
return $count;
|
|
}
|
|
|
|
/**
|
|
* Destroy the given entity.
|
|
*/
|
|
protected function destroyEntity(Entity $entity): int
|
|
{
|
|
if ($entity->isA('page')) {
|
|
return $this->destroyPage($entity);
|
|
}
|
|
if ($entity->isA('chapter')) {
|
|
return $this->destroyChapter($entity);
|
|
}
|
|
if ($entity->isA('book')) {
|
|
return $this->destroyBook($entity);
|
|
}
|
|
if ($entity->isA('shelf')) {
|
|
return $this->destroyShelf($entity);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update entity relations to remove or update outstanding connections.
|
|
*/
|
|
protected function destroyCommonRelations(Entity $entity)
|
|
{
|
|
Activity::removeEntity($entity);
|
|
$entity->views()->delete();
|
|
$entity->permissions()->delete();
|
|
$entity->tags()->delete();
|
|
$entity->comments()->delete();
|
|
$entity->jointPermissions()->delete();
|
|
$entity->searchTerms()->delete();
|
|
$entity->deletions()->delete();
|
|
|
|
if ($entity instanceof HasCoverImage && $entity->cover) {
|
|
$imageService = app()->make(ImageService::class);
|
|
$imageService->destroy($entity->cover);
|
|
}
|
|
}
|
|
}
|