From c31e6a03ce3d45df2d79399820bbbfc7df7fe2b8 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 20 May 2018 18:16:01 +0100 Subject: [PATCH] Added command to clean-up old images, Unfinished --- app/Console/Commands/CleanupImages.php | 68 ++++++++++++++++++++++++++ app/Providers/CustomFacadeProvider.php | 2 + app/Services/ImageService.php | 51 +++++++++++++++++-- 3 files changed, 116 insertions(+), 5 deletions(-) create mode 100644 app/Console/Commands/CleanupImages.php diff --git a/app/Console/Commands/CleanupImages.php b/app/Console/Commands/CleanupImages.php new file mode 100644 index 000000000..310a7bb24 --- /dev/null +++ b/app/Console/Commands/CleanupImages.php @@ -0,0 +1,68 @@ +imageService = $imageService; + parent::__construct(); + } + + /** + * Execute the console command. + * + * @return mixed + */ + public function handle() + { + $checkRevisions = $this->option('all') ? false : true; + $dryRun = $this->option('force') ? false : true; + + if (!$dryRun) { + $proceed = $this->confirm('This operation is destructive and is not guaranteed to be fully accurate. Ensure you have a backup of your images. Are you sure you want to proceed?'); + if (!$proceed) { + return; + } + } + + $deleteCount = $this->imageService->deleteUnusedImages($checkRevisions, ['gallery', 'drawio'], $dryRun); + + if ($dryRun) { + $this->comment('Dry run, No images have been deleted'); + $this->comment($deleteCount . ' images found that would have been deleted'); + $this->comment('Run with -f or --force to perform deletions'); + return; + } + + $this->comment($deleteCount . ' images deleted'); + } +} diff --git a/app/Providers/CustomFacadeProvider.php b/app/Providers/CustomFacadeProvider.php index a97512e8c..c81a5529d 100644 --- a/app/Providers/CustomFacadeProvider.php +++ b/app/Providers/CustomFacadeProvider.php @@ -3,6 +3,7 @@ namespace BookStack\Providers; use BookStack\Activity; +use BookStack\Image; use BookStack\Services\ImageService; use BookStack\Services\PermissionService; use BookStack\Services\ViewService; @@ -57,6 +58,7 @@ class CustomFacadeProvider extends ServiceProvider $this->app->bind('images', function () { return new ImageService( + $this->app->make(Image::class), $this->app->make(ImageManager::class), $this->app->make(Factory::class), $this->app->make(Repository::class) diff --git a/app/Services/ImageService.php b/app/Services/ImageService.php index c087c1e03..ce108e172 100644 --- a/app/Services/ImageService.php +++ b/app/Services/ImageService.php @@ -3,6 +3,7 @@ use BookStack\Exceptions\ImageUploadException; use BookStack\Image; use BookStack\User; +use DB; use Exception; use Intervention\Image\Exception\NotSupportedException; use Intervention\Image\ImageManager; @@ -16,15 +17,18 @@ class ImageService extends UploadService protected $imageTool; protected $cache; protected $storageUrl; + protected $image; /** * ImageService constructor. - * @param $imageTool - * @param $fileSystem - * @param $cache + * @param Image $image + * @param ImageManager $imageTool + * @param FileSystem $fileSystem + * @param Cache $cache */ - public function __construct(ImageManager $imageTool, FileSystem $fileSystem, Cache $cache) + public function __construct(Image $image, ImageManager $imageTool, FileSystem $fileSystem, Cache $cache) { + $this->image = $image; $this->imageTool = $imageTool; $this->cache = $cache; parent::__construct($fileSystem); @@ -146,7 +150,7 @@ class ImageService extends UploadService $imageDetails['updated_by'] = $userId; } - $image = (new Image()); + $image = $this->image->newInstance(); $image->forceFill($imageDetails)->save(); return $image; } @@ -294,6 +298,43 @@ class ImageService extends UploadService return $image; } + + /** + * Delete gallery and drawings that are not within HTML content of pages or page revisions. + * @param bool $checkRevisions + * @param array $types + * @param bool $dryRun + * @return int + */ + public function deleteUnusedImages($checkRevisions = true, $types = ['gallery', 'drawio'], $dryRun = true) + { + // TODO - The checking here isn't really good enough. + // Thumbnails would also need to be searched for as we can't guarantee the full image will be in the content. + // Would also be best to simplify the string to not include the base host? + $types = array_intersect($types, ['gallery', 'drawio']); + $deleteCount = 0; + $this->image->newQuery()->whereIn('type', $types) + ->chunk(1000, function($images) use ($types, $checkRevisions, &$deleteCount, $dryRun) { + foreach ($images as $image) { + $inPage = DB::table('pages') + ->where('html', 'like', '%' . $image->url . '%')->count() > 0; + $inRevision = false; + if ($checkRevisions) { + $inRevision = DB::table('page_revisions') + ->where('html', 'like', '%' . $image->url . '%')->count() > 0; + } + + if (!$inPage && !$inRevision) { + $deleteCount++; + if (!$dryRun) { + $this->destroy($image); + } + } + } + }); + return $deleteCount; + } + /** * Convert a image URI to a Base64 encoded string. * Attempts to find locally via set storage method first.