]> BookStack Code Mirror - bookstack/commitdiff
Added maintenance view with image-cleanup
authorDan Brown <redacted>
Sun, 27 May 2018 18:40:07 +0000 (19:40 +0100)
committerDan Brown <redacted>
Sun, 27 May 2018 18:40:07 +0000 (19:40 +0100)
app/Console/Commands/CleanupImages.php
app/Http/Controllers/SettingController.php
app/Services/ImageService.php
resources/assets/icons/spanner.svg [new file with mode: 0644]
resources/lang/en/settings.php
resources/views/settings/maintenance.blade.php [new file with mode: 0644]
resources/views/settings/navbar.blade.php
routes/web.php
tests/ImageTest.php

index 5eadf275157424ef80e83f16ce682fffa728f149..e05508d5e31803626fe4aa148c674cd4676ebc8e 100644 (file)
@@ -55,7 +55,7 @@ class CleanupImages extends Command
             }
         }
 
-        $deleted = $this->imageService->deleteUnusedImages($checkRevisions, ['gallery', 'drawio'], $dryRun);
+        $deleted = $this->imageService->deleteUnusedImages($checkRevisions, $dryRun);
         $deleteCount = count($deleted);
 
         if ($dryRun) {
index e0e351458b0440cb8b2716073058f3d62ad06c66..d9d66042e19ba0bc998d0b74e14b0855b14ff4be 100644 (file)
@@ -1,5 +1,6 @@
 <?php namespace BookStack\Http\Controllers;
 
+use BookStack\Services\ImageService;
 use Illuminate\Http\Request;
 use Illuminate\Http\Response;
 use Setting;
@@ -13,7 +14,7 @@ class SettingController extends Controller
     public function index()
     {
         $this->checkPermission('settings-manage');
-        $this->setPageTitle('Settings');
+        $this->setPageTitle(trans('settings.settings'));
 
         // Get application version
         $version = trim(file_get_contents(base_path('version')));
@@ -43,4 +44,48 @@ class SettingController extends Controller
         session()->flash('success', trans('settings.settings_save_success'));
         return redirect('/settings');
     }
+
+    /**
+     * Show the page for application maintenance.
+     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+     */
+    public function showMaintenance()
+    {
+        $this->checkPermission('settings-manage');
+        $this->setPageTitle(trans('settings.maint'));
+
+        // Get application version
+        $version = trim(file_get_contents(base_path('version')));
+
+        return view('settings/maintenance', ['version' => $version]);
+    }
+
+    /**
+     * Action to clean-up images in the system.
+     * @param Request $request
+     * @param ImageService $imageService
+     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
+     */
+    public function cleanupImages(Request $request, ImageService $imageService)
+    {
+        $this->checkPermission('settings-manage');
+
+        $checkRevisions = !($request->get('ignore_revisions', 'false') === 'true');
+        $dryRun = !($request->has('confirm'));
+
+        $imagesToDelete = $imageService->deleteUnusedImages($checkRevisions, $dryRun);
+        $deleteCount = count($imagesToDelete);
+        if ($deleteCount === 0) {
+            session()->flash('warning', trans('settings.maint_image_cleanup_nothing_found'));
+            return redirect('/settings/maintenance')->withInput();
+        }
+
+        if ($dryRun) {
+            session()->flash('cleanup-images-warning', trans('settings.maint_image_cleanup_warning', ['count' => $deleteCount]));
+        } else {
+            session()->flash('success', trans('settings.maint_image_cleanup_success', ['count' => $deleteCount]));
+        }
+
+        return redirect('/settings/maintenance#image-cleanup')->withInput();
+    }
 }
index d1193ab4fd3d74787a0f2de13f541bed0bb8e859..73a677ac23ecf144634b3447aff95c83b45c1fd8 100644 (file)
@@ -306,11 +306,11 @@ class ImageService extends UploadService
      *
      * Returns the path of the images that would be/have been deleted.
      * @param bool $checkRevisions
-     * @param array $types
      * @param bool $dryRun
+     * @param array $types
      * @return array
      */
-    public function deleteUnusedImages($checkRevisions = true, $types = ['gallery', 'drawio'], $dryRun = true)
+    public function deleteUnusedImages($checkRevisions = true, $dryRun = true, $types = ['gallery', 'drawio'])
     {
         $types = array_intersect($types, ['gallery', 'drawio']);
         $deletedPaths = [];
diff --git a/resources/assets/icons/spanner.svg b/resources/assets/icons/spanner.svg
new file mode 100644 (file)
index 0000000..8ab25a2
--- /dev/null
@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+    <path clip-rule="evenodd" fill="none" d="M0 0h24v24H0z"/>
+    <path d="M22.7 19l-9.1-9.1c.9-2.3.4-5-1.5-6.9-2-2-5-2.4-7.4-1.3L9 6 6 9 1.6 4.7C.4 7.1.9 10.1 2.9 12.1c1.9 1.9 4.6 2.4 6.9 1.5l9.1 9.1c.4.4 1 .4 1.4 0l2.3-2.3c.5-.4.5-1.1.1-1.4z"/>
+</svg>
index de48942802896372dd13332f29d8591ece565780..3a3dbb9a4dfff2b08a4edf9950c8d33aa5d34eb0 100755 (executable)
@@ -50,6 +50,19 @@ return [
     'reg_confirm_restrict_domain_desc' => 'Enter a comma separated list of email domains you would like to restrict registration to. Users will be sent an email to confirm their address before being allowed to interact with the application. <br> Note that users will be able to change their email addresses after successful registration.',
     'reg_confirm_restrict_domain_placeholder' => 'No restriction set',
 
+    /**
+     * Maintenance settings
+     */
+
+    'maint' => 'Maintenance',
+    'maint_image_cleanup' => 'Cleanup Images',
+    'maint_image_cleanup_desc' => "Scans page & revision content to check which images and drawings are currently in use and which images are redundant. Ensure you create a full database and image backup before running this.",
+    'maint_image_cleanup_ignore_revisions' => 'Ignore images in revisions',
+    'maint_image_cleanup_run' => 'Run Cleanup',
+    'maint_image_cleanup_warning' => ':count potentially unused images were found. Are you sure you want to delete these images?',
+    'maint_image_cleanup_success' => ':count potentially unused images found and deleted!',
+    'maint_image_cleanup_nothing_found' => 'No unused images found, Nothing deleted!',
+
     /**
      * Role settings
      */
diff --git a/resources/views/settings/maintenance.blade.php b/resources/views/settings/maintenance.blade.php
new file mode 100644 (file)
index 0000000..abf793a
--- /dev/null
@@ -0,0 +1,49 @@
+@extends('simple-layout')
+
+@section('toolbar')
+    @include('settings/navbar', ['selected' => 'maintenance'])
+@stop
+
+@section('body')
+<div class="container small">
+
+    <div class="text-right text-muted container">
+        <br>
+        BookStack @if(strpos($version, 'v') !== 0) version @endif {{ $version }}
+    </div>
+
+
+    <div class="card" id="image-cleanup">
+        <h3>@icon('images') {{ trans('settings.maint_image_cleanup') }}</h3>
+        <div class="body">
+            <div class="row">
+                <div class="col-sm-6">
+                    <p class="small muted">{{ trans('settings.maint_image_cleanup_desc') }}</p>
+                </div>
+                <div class="col-sm-6">
+                    <form method="POST" action="{{ baseUrl('/settings/maintenance/cleanup-images') }}">
+                        {!! csrf_field()  !!}
+                        <input type="hidden" name="_method" value="DELETE">
+                        <div>
+                            @if(session()->has('cleanup-images-warning'))
+                                <p class="text neg">
+                                    {{ session()->get('cleanup-images-warning') }}
+                                </p>
+                                <input type="hidden" name="ignore_revisions" value="{{ session()->getOldInput('ignore_revisions', 'false') }}">
+                                <input type="hidden" name="confirm" value="true">
+                            @else
+                                <label>
+                                    <input type="checkbox" name="ignore_revisions" value="true">
+                                    {{ trans('settings.maint_image_cleanup_ignore_revisions') }}
+                                </label>
+                            @endif
+                        </div>
+                        <button class="button outline">{{ trans('settings.maint_image_cleanup_run') }}</button>
+                    </form>
+                </div>
+            </div>
+        </div>
+    </div>
+
+</div>
+@stop
index 0547b168c0fa25c29f7720e7964971811dace64a..f9db96894cf0b19bcb0d93b41b9b270697956f55 100644 (file)
@@ -2,6 +2,7 @@
 <div class="col-md-12 setting-nav nav-tabs">
     @if($currentUser->can('settings-manage'))
         <a href="{{ baseUrl('/settings') }}" @if($selected == 'settings') class="selected text-button" @endif>@icon('settings'){{ trans('settings.settings') }}</a>
+        <a href="{{ baseUrl('/settings/maintenance') }}" @if($selected == 'maintenance') class="selected text-button" @endif>@icon('spanner'){{ trans('settings.maint') }}</a>
     @endif
     @if($currentUser->can('users-manage'))
         <a href="{{ baseUrl('/settings/users') }}" @if($selected == 'users') class="selected text-button" @endif>@icon('users'){{ trans('settings.users') }}</a>
index 7cad0c58512d1d81f09c45913b31ccd701deb055..7c9a5a60ed6b4e17178e1cdb7b26f4b27151107a 100644 (file)
@@ -152,6 +152,10 @@ Route::group(['middleware' => 'auth'], function () {
         Route::get('/', 'SettingController@index')->name('settings');
         Route::post('/', 'SettingController@update');
 
+        // Maintenance
+        Route::get('/maintenance', 'SettingController@showMaintenance');
+        Route::delete('/maintenance/cleanup-images', 'SettingController@cleanupImages');
+
         // Users
         Route::get('/users', 'UserController@index');
         Route::get('/users/create', 'UserController@create');
index 810fb3edf3ba7ec194c49633058a940aa946f6b2..0f1e772e937e86660110f1ca171642872b33d693 100644 (file)
@@ -2,6 +2,8 @@
 
 use BookStack\Image;
 use BookStack\Page;
+use BookStack\Repos\EntityRepo;
+use BookStack\Services\ImageService;
 
 class ImageTest extends TestCase
 {
@@ -234,4 +236,59 @@ class ImageTest extends TestCase
         ]);
     }
 
+    public function test_deleted_unused_images()
+    {
+        $page = Page::first();
+        $admin = $this->getAdmin();
+        $this->actingAs($admin);
+
+        $imageName = 'unused-image.png';
+        $relPath = $this->getTestImagePath('gallery', $imageName);
+        $this->deleteImage($relPath);
+
+        $upload = $this->uploadImage($imageName, $page->id);
+        $upload->assertStatus(200);
+        $image = Image::where('type', '=', 'gallery')->first();
+
+        $entityRepo = app(EntityRepo::class);
+        $entityRepo->updatePage($page, $page->book_id, [
+            'name' => $page->name,
+            'html' => $page->html . "<img src=\"{$image->url}\">",
+            'summary' => ''
+        ]);
+
+        // Ensure no images are reported as deletable
+        $imageService = app(ImageService::class);
+        $toDelete = $imageService->deleteUnusedImages(true, true);
+        $this->assertCount(0, $toDelete);
+
+        // Save a revision of our page without the image;
+        $entityRepo->updatePage($page, $page->book_id, [
+            'name' => $page->name,
+            'html' => "<p>Hello</p>",
+            'summary' => ''
+        ]);
+
+        // Ensure revision images are picked up okay
+        $imageService = app(ImageService::class);
+        $toDelete = $imageService->deleteUnusedImages(true, true);
+        $this->assertCount(0, $toDelete);
+        $toDelete = $imageService->deleteUnusedImages(false, true);
+        $this->assertCount(1, $toDelete);
+
+        // Check image is found when revisions are destroyed
+        $page->revisions()->delete();
+        $toDelete = $imageService->deleteUnusedImages(true, true);
+        $this->assertCount(1, $toDelete);
+
+        // Check the image is deleted
+        $absPath = public_path($relPath);
+        $this->assertTrue(file_exists($absPath), "Existing uploaded file at path {$absPath} exists");
+        $toDelete = $imageService->deleteUnusedImages(true, false);
+        $this->assertCount(1, $toDelete);
+        $this->assertFalse(file_exists($absPath));
+
+        $this->deleteImage($relPath);
+    }
+
 }
\ No newline at end of file