diff --git a/app/Console/Commands/CleanupImages.php b/app/Console/Commands/CleanupImages.php index 310a7bb24..5eadf2751 100644 --- a/app/Console/Commands/CleanupImages.php +++ b/app/Console/Commands/CleanupImages.php @@ -4,6 +4,7 @@ namespace BookStack\Console\Commands; use BookStack\Services\ImageService; use Illuminate\Console\Command; +use Symfony\Component\Console\Output\OutputInterface; class CleanupImages extends Command { @@ -48,21 +49,35 @@ class CleanupImages extends Command $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?'); + $proceed = $this->confirm("This operation is destructive and is not guaranteed to be fully accurate.\nEnsure you have a backup of your images.\nAre you sure you want to proceed?"); if (!$proceed) { return; } } - $deleteCount = $this->imageService->deleteUnusedImages($checkRevisions, ['gallery', 'drawio'], $dryRun); + $deleted = $this->imageService->deleteUnusedImages($checkRevisions, ['gallery', 'drawio'], $dryRun); + $deleteCount = count($deleted); if ($dryRun) { $this->comment('Dry run, No images have been deleted'); $this->comment($deleteCount . ' images found that would have been deleted'); + $this->showDeletedImages($deleted); $this->comment('Run with -f or --force to perform deletions'); return; } + $this->showDeletedImages($deleted); $this->comment($deleteCount . ' images deleted'); } + + protected function showDeletedImages($paths) + { + if ($this->getOutput()->getVerbosity() <= OutputInterface::VERBOSITY_NORMAL) return; + if (count($paths) > 0) { + $this->line('Images to delete:'); + } + foreach ($paths as $path) { + $this->line($path); + } + } } diff --git a/app/Services/ImageService.php b/app/Services/ImageService.php index ce108e172..d1193ab4f 100644 --- a/app/Services/ImageService.php +++ b/app/Services/ImageService.php @@ -301,38 +301,41 @@ class ImageService extends UploadService /** * Delete gallery and drawings that are not within HTML content of pages or page revisions. + * Checks based off of only the image name. + * Could be much improved to be more specific but kept it generic for now to be safe. + * + * Returns the path of the images that would be/have been deleted. * @param bool $checkRevisions * @param array $types * @param bool $dryRun - * @return int + * @return array */ 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; + $deletedPaths = []; + $this->image->newQuery()->whereIn('type', $types) - ->chunk(1000, function($images) use ($types, $checkRevisions, &$deleteCount, $dryRun) { + ->chunk(1000, function($images) use ($types, $checkRevisions, &$deletedPaths, $dryRun) { foreach ($images as $image) { + $searchQuery = '%' . basename($image->path) . '%'; $inPage = DB::table('pages') - ->where('html', 'like', '%' . $image->url . '%')->count() > 0; + ->where('html', 'like', $searchQuery)->count() > 0; $inRevision = false; if ($checkRevisions) { $inRevision = DB::table('page_revisions') - ->where('html', 'like', '%' . $image->url . '%')->count() > 0; + ->where('html', 'like', $searchQuery)->count() > 0; } if (!$inPage && !$inRevision) { - $deleteCount++; + $deletedPaths[] = $image->path; if (!$dryRun) { $this->destroy($image); } } } }); - return $deleteCount; + return $deletedPaths; } /** diff --git a/resources/assets/js/components/markdown-editor.js b/resources/assets/js/components/markdown-editor.js index 46c54408b..06426bf34 100644 --- a/resources/assets/js/components/markdown-editor.js +++ b/resources/assets/js/components/markdown-editor.js @@ -52,6 +52,10 @@ class MarkdownEditor { let action = button.getAttribute('data-action'); if (action === 'insertImage') this.actionInsertImage(); if (action === 'insertLink') this.actionShowLinkSelector(); + if (action === 'insertDrawing' && event.ctrlKey) { + this.actionShowImageManager(); + return; + } if (action === 'insertDrawing') this.actionStartDrawing(); }); @@ -293,7 +297,14 @@ class MarkdownEditor { this.cm.focus(); this.cm.replaceSelection(newText); this.cm.setCursor(cursorPos.line, cursorPos.ch + newText.length); - }); + }, 'gallery'); + } + + actionShowImageManager() { + let cursorPos = this.cm.getCursor('from'); + window.ImageManager.show(image => { + this.insertDrawing(image, cursorPos); + }, 'drawio'); } // Show the popup link selector and insert a link when finished @@ -324,10 +335,7 @@ class MarkdownEditor { }; window.$http.post(window.baseUrl('/images/drawing/upload'), data).then(resp => { - let newText = `