diff --git a/app/Http/Controllers/MaintenanceController.php b/app/Http/Controllers/MaintenanceController.php index f13266d7c..8bfefb7ac 100644 --- a/app/Http/Controllers/MaintenanceController.php +++ b/app/Http/Controllers/MaintenanceController.php @@ -5,6 +5,7 @@ namespace BookStack\Http\Controllers; use BookStack\Actions\ActivityType; use BookStack\Entities\Tools\TrashCan; use BookStack\Notifications\TestEmail; +use BookStack\References\ReferenceStore; use BookStack\Uploads\ImageService; use Illuminate\Http\Request; @@ -74,6 +75,24 @@ class MaintenanceController extends Controller $this->showErrorNotification($errorMessage); } - return redirect('/settings/maintenance#image-cleanup')->withInput(); + return redirect('/settings/maintenance#image-cleanup'); + } + + /** + * Action to regenerate the reference index in the system. + */ + public function regenerateReferences(ReferenceStore $referenceStore) + { + $this->checkPermission('settings-manage'); + $this->logActivity(ActivityType::MAINTENANCE_ACTION_RUN, 'regenerate-references'); + + try { + $referenceStore->updateForAllPages(); + $this->showSuccessNotification(trans('settings.maint_regen_references_success')); + } catch (\Exception $exception) { + $this->showErrorNotification($exception->getMessage()); + } + + return redirect('/settings/maintenance#regenerate-references'); } } diff --git a/resources/lang/en/settings.php b/resources/lang/en/settings.php index 3bfe70bc4..9dbd96c5a 100755 --- a/resources/lang/en/settings.php +++ b/resources/lang/en/settings.php @@ -89,6 +89,10 @@ return [ 'maint_send_test_email_mail_text' => 'Congratulations! As you received this email notification, your email settings seem to be configured properly.', 'maint_recycle_bin_desc' => 'Deleted shelves, books, chapters & pages are sent to the recycle bin so they can be restored or permanently deleted. Older items in the recycle bin may be automatically removed after a while depending on system configuration.', 'maint_recycle_bin_open' => 'Open Recycle Bin', + 'maint_regen_references' => 'Regenerate References', + 'maint_regen_references_desc' => 'This action will rebuild the cross-item reference index within the database. This is usually handled automatically but this action can be useful to index old content or content added via unofficial methods.', + 'maint_regen_references_success' => 'Reference index has been regenerated!', + 'maint_timeout_command_note' => 'Note: This action can take time to run, which can lead to timeout issues in some web environments. As an alternative, this action be performed using a terminal command.', // Recycle Bin 'recycle_bin' => 'Recycle Bin', diff --git a/resources/views/settings/maintenance.blade.php b/resources/views/settings/maintenance.blade.php index a2a9ebc81..7ee966e00 100644 --- a/resources/views/settings/maintenance.blade.php +++ b/resources/views/settings/maintenance.blade.php @@ -25,9 +25,10 @@ <div id="image-cleanup" class="card content-wrap auto-height"> <h2 class="list-heading">{{ trans('settings.maint_image_cleanup') }}</h2> - <div class="grid half gap-xl"> + <div class="grid left-focus gap-xl"> <div> <p class="small text-muted">{{ trans('settings.maint_image_cleanup_desc') }}</p> + <p class="small text-muted italic">{{ trans('settings.maint_timeout_command_note') }}</p> </div> <div> <form method="POST" action="{{ url('/settings/maintenance/cleanup-images') }}"> @@ -55,7 +56,7 @@ <div id="send-test-email" class="card content-wrap auto-height"> <h2 class="list-heading">{{ trans('settings.maint_send_test_email') }}</h2> - <div class="grid half gap-xl"> + <div class="grid left-focus gap-xl"> <div> <p class="small text-muted">{{ trans('settings.maint_send_test_email_desc') }}</p> </div> @@ -68,5 +69,21 @@ </div> </div> + <div id="regenerate-references" class="card content-wrap auto-height"> + <h2 class="list-heading">{{ trans('settings.maint_regen_references') }}</h2> + <div class="grid left-focus gap-xl"> + <div> + <p class="small text-muted">{{ trans('settings.maint_regen_references_desc') }}</p> + <p class="small text-muted italic">{{ trans('settings.maint_timeout_command_note') }}</p> + </div> + <div> + <form method="POST" action="{{ url('/settings/maintenance/regenerate-references') }}"> + {!! csrf_field() !!} + <button class="button outline">{{ trans('settings.maint_regen_references') }}</button> + </form> + </div> + </div> + </div> + </div> @stop diff --git a/routes/web.php b/routes/web.php index dc46821cb..26d4b6f13 100644 --- a/routes/web.php +++ b/routes/web.php @@ -218,6 +218,7 @@ Route::middleware('auth')->group(function () { Route::get('/settings/maintenance', [MaintenanceController::class, 'index']); Route::delete('/settings/maintenance/cleanup-images', [MaintenanceController::class, 'cleanupImages']); Route::post('/settings/maintenance/send-test-email', [MaintenanceController::class, 'sendTestEmail']); + Route::post('/settings/maintenance/regenerate-references', [MaintenanceController::class, 'regenerateReferences']); // Recycle Bin Route::get('/settings/recycle-bin', [RecycleBinController::class, 'index']); diff --git a/tests/RecycleBinTest.php b/tests/Settings/RecycleBinTest.php similarity index 99% rename from tests/RecycleBinTest.php rename to tests/Settings/RecycleBinTest.php index 0e0524338..465c1aaad 100644 --- a/tests/RecycleBinTest.php +++ b/tests/Settings/RecycleBinTest.php @@ -1,6 +1,6 @@ <?php -namespace Tests; +namespace Tests\Settings; use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Bookshelf; @@ -10,6 +10,7 @@ use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\Page; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\DB; +use Tests\TestCase; class RecycleBinTest extends TestCase { diff --git a/tests/Settings/RegenerateReferencesTest.php b/tests/Settings/RegenerateReferencesTest.php new file mode 100644 index 000000000..0f3122074 --- /dev/null +++ b/tests/Settings/RegenerateReferencesTest.php @@ -0,0 +1,55 @@ +<?php + +namespace Tests\Settings; + +use BookStack\Actions\ActivityType; +use BookStack\References\ReferenceStore; +use Tests\TestCase; + +class RegenerateReferencesTest extends TestCase +{ + public function test_option_visible_on_maintenance_page() + { + $pageView = $this->asAdmin()->get('/settings/maintenance'); + $formCssSelector = 'form[action$="/settings/maintenance/regenerate-references"]'; + $html = $this->withHtml($pageView); + $html->assertElementExists('#regenerate-references'); + $html->assertElementExists($formCssSelector); + $html->assertElementContains($formCssSelector . ' button', 'Regenerate References'); + } + + public function test_action_runs_reference_regen() + { + $this->mock(ReferenceStore::class) + ->shouldReceive('updateForAllPages') + ->once(); + + $resp = $this->asAdmin()->post('/settings/maintenance/regenerate-references'); + $resp->assertRedirect('/settings/maintenance#regenerate-references'); + $this->assertSessionHas('success', 'Reference index has been regenerated!'); + $this->assertActivityExists(ActivityType::MAINTENANCE_ACTION_RUN, null, 'regenerate-references'); + } + + public function test_settings_manage_permission_required() + { + $editor = $this->getEditor(); + $resp = $this->actingAs($editor)->post('/settings/maintenance/regenerate-references'); + $this->assertPermissionError($resp); + + $this->giveUserPermissions($editor, ['settings-manage']); + + $resp = $this->actingAs($editor)->post('/settings/maintenance/regenerate-references'); + $this->assertNotPermissionError($resp); + } + + public function test_action_failed_shown_as_error_notification() + { + $this->mock(ReferenceStore::class) + ->shouldReceive('updateForAllPages') + ->andThrow(\Exception::class, 'A badger stopped the task'); + + $resp = $this->asAdmin()->post('/settings/maintenance/regenerate-references'); + $resp->assertRedirect('/settings/maintenance#regenerate-references'); + $this->assertSessionError('A badger stopped the task'); + } +} diff --git a/tests/TestEmailTest.php b/tests/Settings/TestEmailTest.php similarity index 98% rename from tests/TestEmailTest.php rename to tests/Settings/TestEmailTest.php index 97f98225d..31c51158f 100644 --- a/tests/TestEmailTest.php +++ b/tests/Settings/TestEmailTest.php @@ -1,10 +1,11 @@ <?php -namespace Tests; +namespace Tests\Settings; use BookStack\Notifications\TestEmail; use Illuminate\Contracts\Notifications\Dispatcher; use Illuminate\Support\Facades\Notification; +use Tests\TestCase; class TestEmailTest extends TestCase {