Made image cleanup safer
Also fixed drawing update in markdown editor. Added shortcut for MD editor to view drawing manager.
This commit is contained in:
		
							parent
							
								
									c31e6a03ce
								
							
						
					
					
						commit
						1df0bcaf85
					
				| 
						 | 
				
			
			@ -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);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 = `<div drawio-diagram="${resp.data.id}"><img src="${resp.data.url}"></div>`;
 | 
			
		||||
                this.cm.focus();
 | 
			
		||||
                this.cm.replaceSelection(newText);
 | 
			
		||||
                this.cm.setCursor(cursorPos.line, cursorPos.ch + newText.length);
 | 
			
		||||
                this.insertDrawing(resp.data, cursorPos);
 | 
			
		||||
                DrawIO.close();
 | 
			
		||||
            }).catch(err => {
 | 
			
		||||
                window.$events.emit('error', trans('errors.image_upload_error'));
 | 
			
		||||
| 
						 | 
				
			
			@ -336,6 +344,13 @@ class MarkdownEditor {
 | 
			
		|||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    insertDrawing(image, originalCursor) {
 | 
			
		||||
        let newText = `<div drawio-diagram="${image.id}"><img src="${image.url}"></div>`;
 | 
			
		||||
        this.cm.focus();
 | 
			
		||||
        this.cm.replaceSelection(newText);
 | 
			
		||||
        this.cm.setCursor(originalCursor.line, originalCursor.ch + newText.length);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Show draw.io if enabled and handle save.
 | 
			
		||||
    actionEditDrawing(imgContainer) {
 | 
			
		||||
        if (document.querySelector('[drawio-enabled]').getAttribute('drawio-enabled') !== 'true') return;
 | 
			
		||||
| 
						 | 
				
			
			@ -353,8 +368,8 @@ class MarkdownEditor {
 | 
			
		|||
                uploaded_to: Number(document.getElementById('page-editor').getAttribute('page-id'))
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            window.$http.put(window.baseUrl(`/images/drawing/upload/${drawingId}`), data).then(resp => {
 | 
			
		||||
                let newText = `<div drawio-diagram="${resp.data.id}"><img src="${resp.data.url + `?updated=${Date.now()}`}"></div>`;
 | 
			
		||||
            window.$http.post(window.baseUrl(`/images/drawing/upload`), data).then(resp => {
 | 
			
		||||
                let newText = `<div drawio-diagram="${resp.data.id}"><img src="${resp.data.url}"></div>`;
 | 
			
		||||
                let newContent = this.cm.getValue().split('\n').map(line => {
 | 
			
		||||
                    if (line.indexOf(`drawio-diagram="${drawingId}"`) !== -1) {
 | 
			
		||||
                        return newText;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -101,6 +101,7 @@ const methods = {
 | 
			
		|||
    },
 | 
			
		||||
 | 
			
		||||
    cancelSearch() {
 | 
			
		||||
        if (!this.searching) return;
 | 
			
		||||
        this.searching = false;
 | 
			
		||||
        this.searchTerm = '';
 | 
			
		||||
        this.images = preSearchImages;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue