diff --git a/app/Http/Controllers/SettingController.php b/app/Http/Controllers/SettingController.php index b12b0e3ce..7f7f4c9ca 100644 --- a/app/Http/Controllers/SettingController.php +++ b/app/Http/Controllers/SettingController.php @@ -9,11 +9,8 @@ use Illuminate\Http\Request; class SettingController extends Controller { - protected $imageRepo; + protected ImageRepo $imageRepo; - /** - * SettingController constructor. - */ public function __construct(ImageRepo $imageRepo) { $this->imageRepo = $imageRepo; @@ -22,7 +19,7 @@ class SettingController extends Controller /** * Display a listing of the settings. */ - public function index() + public function index(string $category) { $this->checkPermission('settings-manage'); $this->setPageTitle(trans('settings.settings')); @@ -30,7 +27,8 @@ class SettingController extends Controller // Get application version $version = trim(file_get_contents(base_path('version'))); - return view('settings.index', [ + return view('settings.' . $category, [ + 'category' => $category, 'version' => $version, 'guestUser' => User::getDefault(), ]); @@ -39,7 +37,7 @@ class SettingController extends Controller /** * Update the specified settings in storage. */ - public function update(Request $request) + public function update(Request $request, string $category) { $this->preventAccessInDemoMode(); $this->checkPermission('settings-manage'); @@ -57,7 +55,7 @@ class SettingController extends Controller } // Update logo image if set - if ($request->hasFile('app_logo')) { + if ($category === 'customization' && $request->hasFile('app_logo')) { $logoFile = $request->file('app_logo'); $this->imageRepo->destroyByType('system'); $image = $this->imageRepo->saveNew($logoFile, 'system', 0, null, 86); @@ -65,16 +63,14 @@ class SettingController extends Controller } // Clear logo image if requested - if ($request->get('app_logo_reset', null)) { + if ($category === 'customization' && $request->get('app_logo_reset', null)) { $this->imageRepo->destroyByType('system'); setting()->remove('app-logo'); } - $section = $request->get('section', ''); - $this->logActivity(ActivityType::SETTINGS_UPDATE, $section); + $this->logActivity(ActivityType::SETTINGS_UPDATE, $category); $this->showSuccessNotification(trans('settings.settings_save_success')); - $redirectLocation = '/settings#' . $section; - return redirect(rtrim($redirectLocation, '#')); + return redirect("/settings/${category}"); } } diff --git a/resources/lang/en/settings.php b/resources/lang/en/settings.php index bfe99c98f..3adefc2eb 100755 --- a/resources/lang/en/settings.php +++ b/resources/lang/en/settings.php @@ -10,6 +10,8 @@ return [ 'settings' => 'Settings', 'settings_save' => 'Save Settings', 'settings_save_success' => 'Settings saved', + 'system_version' => 'System Version', + 'categories' => 'Categories', // App Settings 'app_customization' => 'Customization', diff --git a/resources/sass/_lists.scss b/resources/sass/_lists.scss index ea10f66bf..9cff52972 100644 --- a/resources/sass/_lists.scss +++ b/resources/sass/_lists.scss @@ -677,7 +677,7 @@ ul.pagination { padding: $-s; } a:not(.active) { - @include lightDark(color, #444, #666); + @include lightDark(color, #444, #888); } a:hover { @include lightDark(background-color, rgba(0, 0, 0, 0.05), rgba(255, 255, 255, 0.05)); diff --git a/resources/views/settings/customization.blade.php b/resources/views/settings/customization.blade.php new file mode 100644 index 000000000..5c6841be2 --- /dev/null +++ b/resources/views/settings/customization.blade.php @@ -0,0 +1,137 @@ +@extends('settings.layout') + +@section('card') +

{{ trans('settings.app_customization') }}

+
+ {!! csrf_field() !!} + + +
+ +
+
+ +

{{ trans('settings.app_name_desc') }}

+
+
+ + @include('form.toggle-switch', [ + 'name' => 'setting-app-name-header', + 'value' => setting('app-name-header'), + 'label' => trans('settings.app_name_header'), + ]) +
+
+ +
+
+ +

{{ trans('settings.app_editor_desc') }}

+
+
+ +
+
+ +
+
+ +

{!! trans('settings.app_logo_desc') !!}

+
+
+ @include('form.image-picker', [ + 'removeName' => 'setting-app-logo', + 'removeValue' => 'none', + 'defaultImage' => url('/logo.png'), + 'currentImage' => setting('app-logo'), + 'name' => 'app_logo', + 'imageClass' => 'logo-image', + ]) +
+
+ + +
+
+ +

{!! trans('settings.app_primary_color_desc') !!}

+
+
+ + +
+ + | + +
+ +
+
+ + +
+
+ +

{!! trans('settings.content_colors_desc') !!}

+
+
+
+ @include('settings.parts.setting-entity-color-picker', ['type' => 'bookshelf']) + @include('settings.parts.setting-entity-color-picker', ['type' => 'book']) + @include('settings.parts.setting-entity-color-picker', ['type' => 'chapter']) +
+
+ @include('settings.parts.setting-entity-color-picker', ['type' => 'page']) + @include('settings.parts.setting-entity-color-picker', ['type' => 'page-draft']) +
+
+
+ +
+
+ +

{{ trans('settings.app_homepage_desc') }}

+
+
+ + + +
+
+ +
+ +

{{ trans('settings.app_footer_links_desc') }}

+ @include('settings.parts.footer-links', ['name' => 'setting-app-footer-links', 'value' => setting('app-footer-links', [])]) +
+ + +
+ +

{{ trans('settings.app_custom_html_desc') }}

+ +

{{ trans('settings.app_custom_html_disabled_notice') }}

+
+ + +
+ +
+ +
+
+@endsection + +@section('after-content') + @include('entities.selector-popup', ['entityTypes' => 'page']) +@endsection diff --git a/resources/views/settings/features.blade.php b/resources/views/settings/features.blade.php new file mode 100644 index 000000000..5935e21f5 --- /dev/null +++ b/resources/views/settings/features.blade.php @@ -0,0 +1,66 @@ +@extends('settings.layout') + +@section('card') +

{{ trans('settings.app_features_security') }}

+
+ {!! csrf_field() !!} + + +
+ + +
+
+ +

{!! trans('settings.app_public_access_desc') !!}

+ @if(userCan('users-manage')) +

+ {!! trans('settings.app_public_access_desc_guest') !!} +

+ @endif +
+
+ @include('form.toggle-switch', [ + 'name' => 'setting-app-public', + 'value' => setting('app-public'), + 'label' => trans('settings.app_public_access_toggle'), + ]) +
+
+ +
+
+ +

{{ trans('settings.app_secure_images_desc') }}

+
+
+ @include('form.toggle-switch', [ + 'name' => 'setting-app-secure-images', + 'value' => setting('app-secure-images'), + 'label' => trans('settings.app_secure_images_toggle'), + ]) +
+
+ +
+
+ +

{!! trans('settings.app_disable_comments_desc') !!}

+
+
+ @include('form.toggle-switch', [ + 'name' => 'setting-app-disable-comments', + 'value' => setting('app-disable-comments'), + 'label' => trans('settings.app_disable_comments_toggle'), + ]) +
+
+ + +
+ +
+ +
+
+@endsection \ No newline at end of file diff --git a/resources/views/settings/index.blade.php b/resources/views/settings/index.blade.php deleted file mode 100644 index d22da360c..000000000 --- a/resources/views/settings/index.blade.php +++ /dev/null @@ -1,300 +0,0 @@ -@extends('layouts.simple') - -@section('body') -
- - @include('settings.parts.navbar', ['selected' => 'settings']) - -
- -
-
Categories
- - -
Version
-
- - BookStack @if(strpos($version, 'v') !== 0) version @endif {{ $version }} - -
-
- -
-
-

{{ trans('settings.app_features_security') }}

-
- {!! csrf_field() !!} - - -
- - -
-
- -

{!! trans('settings.app_public_access_desc') !!}

- @if(userCan('users-manage')) -

- {!! trans('settings.app_public_access_desc_guest') !!} -

- @endif -
-
- @include('form.toggle-switch', [ - 'name' => 'setting-app-public', - 'value' => setting('app-public'), - 'label' => trans('settings.app_public_access_toggle'), - ]) -
-
- -
-
- -

{{ trans('settings.app_secure_images_desc') }}

-
-
- @include('form.toggle-switch', [ - 'name' => 'setting-app-secure-images', - 'value' => setting('app-secure-images'), - 'label' => trans('settings.app_secure_images_toggle'), - ]) -
-
- -
-
- -

{!! trans('settings.app_disable_comments_desc') !!}

-
-
- @include('form.toggle-switch', [ - 'name' => 'setting-app-disable-comments', - 'value' => setting('app-disable-comments'), - 'label' => trans('settings.app_disable_comments_toggle'), - ]) -
-
- - -
- -
- -
-
-
- -
-

{{ trans('settings.app_customization') }}

-
- {!! csrf_field() !!} - - -
- -
-
- -

{{ trans('settings.app_name_desc') }}

-
-
- - @include('form.toggle-switch', [ - 'name' => 'setting-app-name-header', - 'value' => setting('app-name-header'), - 'label' => trans('settings.app_name_header'), - ]) -
-
- -
-
- -

{{ trans('settings.app_editor_desc') }}

-
-
- -
-
- -
-
- -

{!! trans('settings.app_logo_desc') !!}

-
-
- @include('form.image-picker', [ - 'removeName' => 'setting-app-logo', - 'removeValue' => 'none', - 'defaultImage' => url('/logo.png'), - 'currentImage' => setting('app-logo'), - 'name' => 'app_logo', - 'imageClass' => 'logo-image', - ]) -
-
- - -
-
- -

{!! trans('settings.app_primary_color_desc') !!}

-
-
- - -
- - | - -
- -
-
- - -
-
- -

{!! trans('settings.content_colors_desc') !!}

-
-
-
- @include('settings.parts.setting-entity-color-picker', ['type' => 'bookshelf']) - @include('settings.parts.setting-entity-color-picker', ['type' => 'book']) - @include('settings.parts.setting-entity-color-picker', ['type' => 'chapter']) -
-
- @include('settings.parts.setting-entity-color-picker', ['type' => 'page']) - @include('settings.parts.setting-entity-color-picker', ['type' => 'page-draft']) -
-
-
- -
-
- -

{{ trans('settings.app_homepage_desc') }}

-
-
- - - -
-
- -
- -

{{ trans('settings.app_footer_links_desc') }}

- @include('settings.parts.footer-links', ['name' => 'setting-app-footer-links', 'value' => setting('app-footer-links', [])]) -
- - -
- -

{{ trans('settings.app_custom_html_desc') }}

- -

{{ trans('settings.app_custom_html_disabled_notice') }}

-
- - -
- -
- -
-
-
- -
-

{{ trans('settings.reg_settings') }}

-
- {!! csrf_field() !!} - - -
-
-
- -

{!! trans('settings.reg_enable_desc') !!}

-
-
- @include('form.toggle-switch', [ - 'name' => 'setting-registration-enabled', - 'value' => setting('registration-enabled'), - 'label' => trans('settings.reg_enable_toggle') - ]) - - @if(in_array(config('auth.method'), ['ldap', 'saml2', 'oidc'])) -
{{ trans('settings.reg_enable_external_warning') }}
- @endif - - - -
-
- -
-
- -

{!! trans('settings.reg_confirm_restrict_domain_desc') !!}

-
-
- -
-
- -
-
- -

{{ trans('settings.reg_confirm_email_desc') }}

-
-
- @include('form.toggle-switch', [ - 'name' => 'setting-registration-confirmation', - 'value' => setting('registration-confirmation'), - 'label' => trans('settings.reg_email_confirmation_toggle') - ]) -
-
- -
- -
- -
-
-
-
- -
- -
- - @include('entities.selector-popup', ['entityTypes' => 'page']) -@stop diff --git a/resources/views/settings/layout.blade.php b/resources/views/settings/layout.blade.php new file mode 100644 index 000000000..e2410895e --- /dev/null +++ b/resources/views/settings/layout.blade.php @@ -0,0 +1,37 @@ +@extends('layouts.simple') + +@section('body') +
+ + @include('settings.parts.navbar', ['selected' => 'settings']) + +
+ +
+
{{ trans('settings.categories') }}
+ + +
{{ trans('settings.system_version') }}
+ +
+ +
+
+ @yield('card') +
+
+ +
+ +
+ + @yield('after-content') +@stop diff --git a/resources/views/settings/registration.blade.php b/resources/views/settings/registration.blade.php new file mode 100644 index 000000000..721839b9b --- /dev/null +++ b/resources/views/settings/registration.blade.php @@ -0,0 +1,71 @@ +@extends('settings.layout') + +@section('card') +

{{ trans('settings.reg_settings') }}

+
+ {!! csrf_field() !!} + + +
+
+
+ +

{!! trans('settings.reg_enable_desc') !!}

+
+
+ @include('form.toggle-switch', [ + 'name' => 'setting-registration-enabled', + 'value' => setting('registration-enabled'), + 'label' => trans('settings.reg_enable_toggle') + ]) + + @if(in_array(config('auth.method'), ['ldap', 'saml2', 'oidc'])) +
{{ trans('settings.reg_enable_external_warning') }}
+ @endif + + + +
+
+ +
+
+ +

{!! trans('settings.reg_confirm_restrict_domain_desc') !!}

+
+
+ +
+
+ +
+
+ +

{{ trans('settings.reg_confirm_email_desc') }}

+
+
+ @include('form.toggle-switch', [ + 'name' => 'setting-registration-confirmation', + 'value' => setting('registration-confirmation'), + 'label' => trans('settings.reg_email_confirmation_toggle') + ]) +
+
+ +
+ +
+ +
+
+@endsection \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index ad4fb9067..223d97c66 100644 --- a/routes/web.php +++ b/routes/web.php @@ -207,10 +207,6 @@ Route::middleware('auth')->group(function () { Route::get('/', [HomeController::class, 'index']); Route::get('/home', [HomeController::class, 'index']); - // Settings - Route::get('/settings', [SettingController::class, 'index'])->name('settings'); - Route::post('/settings', [SettingController::class, 'update']); - // Maintenance Route::get('/settings/maintenance', [MaintenanceController::class, 'index']); Route::delete('/settings/maintenance/cleanup-images', [MaintenanceController::class, 'cleanupImages']); @@ -267,6 +263,11 @@ Route::middleware('auth')->group(function () { Route::put('/settings/webhooks/{id}', [WebhookController::class, 'update']); Route::get('/settings/webhooks/{id}/delete', [WebhookController::class, 'delete']); Route::delete('/settings/webhooks/{id}', [WebhookController::class, 'destroy']); + + // Settings + Route::redirect('/settings', '/settings/features')->name('settings'); + Route::get('/settings/{category}', [SettingController::class, 'index']); + Route::post('/settings/{category}', [SettingController::class, 'update']); }); // MFA routes diff --git a/tests/Auth/AuthTest.php b/tests/Auth/AuthTest.php index fd953021d..0ab6d0e8c 100644 --- a/tests/Auth/AuthTest.php +++ b/tests/Auth/AuthTest.php @@ -202,7 +202,7 @@ class AuthTest extends TestCase { $this->assertFalse(setting('registration-role')); - $resp = $this->asAdmin()->get('/settings'); + $resp = $this->asAdmin()->get('/settings/registration'); $resp->assertElementContains('select[name="setting-registration-role"] option[value="0"][selected]', '-- None --'); } diff --git a/tests/Permissions/RolesTest.php b/tests/Permissions/RolesTest.php index f69b5603c..fe2139e59 100644 --- a/tests/Permissions/RolesTest.php +++ b/tests/Permissions/RolesTest.php @@ -27,7 +27,7 @@ class RolesTest extends TestCase public function test_admin_can_see_settings() { - $this->asAdmin()->get('/settings')->assertSee('Settings'); + $this->asAdmin()->get('/settings/features')->assertSee('Settings'); } public function test_cannot_delete_admin_role() @@ -58,7 +58,7 @@ class RolesTest extends TestCase $testRoleUpdateName = 'An Super Updated role'; // Creation - $resp = $this->asAdmin()->get('/settings'); + $resp = $this->asAdmin()->get('/settings/features'); $resp->assertElementContains('a[href="' . url('/settings/roles') . '"]', 'Roles'); $resp = $this->get('/settings/roles'); @@ -247,13 +247,13 @@ class RolesTest extends TestCase public function test_settings_manage_permission() { - $this->actingAs($this->user)->get('/settings')->assertRedirect('/'); + $this->actingAs($this->user)->get('/settings/features')->assertRedirect('/'); $this->giveUserPermissions($this->user, ['settings-manage']); - $this->get('/settings')->assertOk(); + $this->get('/settings/features')->assertOk(); - $resp = $this->post('/settings', []); - $resp->assertRedirect('/settings'); - $resp = $this->get('/settings'); + $resp = $this->post('/settings/features', []); + $resp->assertRedirect('/settings/features'); + $resp = $this->get('/settings/features'); $resp->assertSee('Settings saved'); } @@ -762,7 +762,7 @@ class RolesTest extends TestCase public function test_public_role_visible_in_default_role_setting() { - $this->asAdmin()->get('/settings') + $this->asAdmin()->get('/settings/registration') ->assertElementExists('[data-system-role-name="admin"]') ->assertElementExists('[data-system-role-name="public"]'); } diff --git a/tests/Settings/FooterLinksTest.php b/tests/Settings/FooterLinksTest.php index f1b5d4294..4b822ba4c 100644 --- a/tests/Settings/FooterLinksTest.php +++ b/tests/Settings/FooterLinksTest.php @@ -8,13 +8,13 @@ class FooterLinksTest extends TestCase { public function test_saving_setting() { - $resp = $this->asAdmin()->post('/settings', [ + $resp = $this->asAdmin()->post('/settings/customization', [ 'setting-app-footer-links' => [ ['label' => 'My custom link 1', 'url' => 'https://example.com/1'], ['label' => 'My custom link 2', 'url' => 'https://example.com/2'], ], ]); - $resp->assertRedirect('/settings'); + $resp->assertRedirect('/settings/customization'); $result = setting('app-footer-links'); $this->assertIsArray($result); @@ -30,7 +30,7 @@ class FooterLinksTest extends TestCase ['label' => 'Another Link', 'url' => 'https://example.com/link-b'], ]]); - $resp = $this->asAdmin()->get('/settings'); + $resp = $this->asAdmin()->get('/settings/customization'); $resp->assertSee('value="My custom link"', false); $resp->assertSee('value="Another Link"', false); $resp->assertSee('value="https://example.com/link-a"', false); diff --git a/tests/Settings/SettingsTest.php b/tests/Settings/SettingsTest.php new file mode 100644 index 000000000..b55911523 --- /dev/null +++ b/tests/Settings/SettingsTest.php @@ -0,0 +1,31 @@ +asAdmin()->get('/settings'); + + $resp->assertRedirect('/settings/features'); + } + + public function test_settings_category_links_work_as_expected() + { + $this->asAdmin(); + $categories = [ + 'features' => 'Features & Security', + 'customization' => 'Customization', + 'registration' => 'Registration', + ]; + + foreach ($categories as $category => $title) { + $resp = $this->get("/settings/{$category}"); + $resp->assertElementContains('h1', $title); + $resp->assertElementExists("form[action$=\"/settings/{$category}\"]"); + } + } +} \ No newline at end of file diff --git a/tests/Uploads/ImageTest.php b/tests/Uploads/ImageTest.php index 32f79e9e0..01754d2de 100644 --- a/tests/Uploads/ImageTest.php +++ b/tests/Uploads/ImageTest.php @@ -314,8 +314,8 @@ class ImageTest extends TestCase $galleryFile = $this->getTestImage('my-system-test-upload.png'); $expectedPath = public_path('uploads/images/system/' . date('Y-m') . '/my-system-test-upload.png'); - $upload = $this->call('POST', '/settings', [], [], ['app_logo' => $galleryFile], []); - $upload->assertRedirect('/settings'); + $upload = $this->call('POST', '/settings/customization', [], [], ['app_logo' => $galleryFile], []); + $upload->assertRedirect('/settings/customization'); $this->assertTrue(file_exists($expectedPath), 'Uploaded image not found at path: ' . $expectedPath);