]> BookStack Code Mirror - bookstack/commitdiff
Made image cleanup safer
authorDan Brown <redacted>
Sun, 27 May 2018 13:33:50 +0000 (14:33 +0100)
committerDan Brown <redacted>
Sun, 27 May 2018 13:33:50 +0000 (14:33 +0100)
Also fixed drawing update in markdown editor.
Added shortcut for MD editor to view drawing manager.

app/Console/Commands/CleanupImages.php
app/Services/ImageService.php
resources/assets/js/components/markdown-editor.js
resources/assets/js/vues/image-manager.js

index 310a7bb24e44cb978c979065d4ac9de0519c6880..5eadf275157424ef80e83f16ce682fffa728f149 100644 (file)
@@ -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);
+        }
+    }
 }
index ce108e172054e32e841a8535e6fd13623e6b812c..d1193ab4fd3d74787a0f2de13f541bed0bb8e859 100644 (file)
@@ -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;
     }
 
     /**
index 46c54408bf6a0c1bef08ed306d7ab69669ad339a..06426bf349a0bfba4d9cda94ec06c769aa99e4b6 100644 (file)
@@ -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;
index f355107c08859d7dd4ec49fc590931aa8b3ef1cf..16c8ef9cfbf5c91a7107fefd8f704d86cce2a51c 100644 (file)
@@ -101,6 +101,7 @@ const methods = {
     },
 
     cancelSearch() {
+        if (!this.searching) return;
         this.searching = false;
         this.searchTerm = '';
         this.images = preSearchImages;
Morty Proxy This is a proxified and sanitized view of the page, visit original site.