use BookStack\Actions\ActivityType;
use BookStack\Auth\User;
+use BookStack\Settings\AppSettingsStore;
use BookStack\Uploads\ImageRepo;
use Illuminate\Http\Request;
class SettingController extends Controller
{
- protected ImageRepo $imageRepo;
-
protected array $settingCategories = ['features', 'customization', 'registration'];
- public function __construct(ImageRepo $imageRepo)
- {
- $this->imageRepo = $imageRepo;
- }
-
/**
* Handle requests to the settings index path.
*/
/**
* Update the specified settings in storage.
*/
- public function update(Request $request, string $category)
+ public function update(Request $request, AppSettingsStore $store, string $category)
{
$this->ensureCategoryExists($category);
$this->preventAccessInDemoMode();
$this->checkPermission('settings-manage');
$this->validate($request, [
- 'app_logo' => array_merge(['nullable'], $this->getImageValidationRules()),
+ 'app_logo' => ['nullable', ...$this->getImageValidationRules()],
+ 'app_icon' => ['nullable', ...$this->getImageValidationRules()],
]);
- // Cycles through posted settings and update them
- foreach ($request->all() as $name => $value) {
- $key = str_replace('setting-', '', trim($name));
- if (strpos($name, 'setting-') !== 0) {
- continue;
- }
- setting()->put($key, $value);
- }
-
- // Update logo image if set
- 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);
- setting()->put('app-logo', $image->url);
- }
-
- // Clear logo image if requested
- if ($category === 'customization' && $request->get('app_logo_reset', null)) {
- $this->imageRepo->destroyByType('system');
- setting()->remove('app-logo');
- }
+ $store->storeFromUpdateRequest($request, $category);
$this->logActivity(ActivityType::SETTINGS_UPDATE, $category);
$this->showSuccessNotification(trans('settings.settings_save_success'));
--- /dev/null
+<?php
+
+namespace BookStack\Settings;
+
+use BookStack\Uploads\ImageRepo;
+use Illuminate\Http\Request;
+
+class AppSettingsStore
+{
+ protected ImageRepo $imageRepo;
+
+ public function __construct(ImageRepo $imageRepo)
+ {
+ $this->imageRepo = $imageRepo;
+ }
+
+ public function storeFromUpdateRequest(Request $request, string $category)
+ {
+ $this->storeSimpleSettings($request);
+ if ($category === 'customization') {
+ $this->updateAppLogo($request);
+ $this->updateAppIcon($request);
+ }
+ }
+
+ protected function updateAppIcon(Request $request): void
+ {
+ $sizes = [128, 64, 32];
+
+ // Update icon image if set
+ if ($request->hasFile('app_icon')) {
+ $iconFile = $request->file('app_icon');
+ $this->destroyExistingSettingImage('app-icon');
+ $image = $this->imageRepo->saveNew($iconFile, 'system', 0, 256, 256);
+ setting()->put('app-icon', $image->url);
+
+ foreach ($sizes as $size) {
+ $icon = $this->imageRepo->saveNew($iconFile, 'system', 0, $size, $size);
+ setting()->put('app-icon-' . $size, $icon->url);
+ }
+ }
+
+ // Clear icon image if requested
+ if ($request->get('app_icon_reset')) {
+ $this->destroyExistingSettingImage('app-icon');
+ setting()->remove('app-icon');
+ foreach ($sizes as $size) {
+ $this->destroyExistingSettingImage('app-icon-' . $size);
+ setting()->remove('app-icon-' . $size);
+ }
+ }
+ }
+
+ protected function updateAppLogo(Request $request): void
+ {
+ // Update logo image if set
+ if ($request->hasFile('app_logo')) {
+ $logoFile = $request->file('app_logo');
+ $this->destroyExistingSettingImage('app-logo');
+ $image = $this->imageRepo->saveNew($logoFile, 'system', 0, null, 86);
+ setting()->put('app-logo', $image->url);
+ }
+
+ // Clear logo image if requested
+ if ($request->get('app_logo_reset')) {
+ $this->destroyExistingSettingImage('app-logo');
+ setting()->remove('app-logo');
+ }
+ }
+
+ protected function storeSimpleSettings(Request $request): void
+ {
+ foreach ($request->all() as $name => $value) {
+ if (strpos($name, 'setting-') !== 0) {
+ continue;
+ }
+
+ $key = str_replace('setting-', '', trim($name));
+ setting()->put($key, $value);
+ }
+ }
+
+ protected function destroyExistingSettingImage(string $settingKey)
+ {
+ $existingVal = setting()->get($settingKey);
+ if ($existingVal) {
+ $this->imageRepo->destroyByUrlAndType($existingVal, 'system');
+ }
+ }
+}
*/
class SettingService
{
- protected $setting;
- protected $cache;
- protected $localCache = [];
+ protected Setting $setting;
+ protected Cache $cache;
+ protected array $localCache = [];
+ protected string $cachePrefix = 'setting-';
- protected $cachePrefix = 'setting-';
-
- /**
- * SettingService constructor.
- */
public function __construct(Setting $setting, Cache $cache)
{
$this->setting = $setting;
}
/**
- * Destroy all images of a certain type.
+ * Destroy images that have a specific URL and type combination.
*
* @throws Exception
*/
- public function destroyByType(string $imageType): void
+ public function destroyByUrlAndType(string $url, string $imageType): void
{
- $images = Image::query()->where('type', '=', $imageType)->get();
+ $images = Image::query()
+ ->where('url', '=', $url)
+ ->where('type', '=', $imageType)
+ ->get();
+
foreach ($images as $image) {
$this->destroyImage($image);
}
'app_custom_html_desc' => 'Any content added here will be inserted into the bottom of the <head> section of every page. This is handy for overriding styles or adding analytics code.',
'app_custom_html_disabled_notice' => 'Custom HTML head content is disabled on this settings page to ensure any breaking changes can be reverted.',
'app_logo' => 'Application Logo',
- 'app_logo_desc' => 'This image should be 43px in height. <br>Large images will be scaled down.',
+ 'app_logo_desc' => 'This is used in the application header bar, among other areas. This image should be 86px in height. Large images will be scaled down.',
'app_primary_color' => 'Application Primary Color',
'app_primary_color_desc' => 'Sets the primary color for the application including the banner, buttons, and links.',
'app_homepage' => 'Application Homepage',
<link rel="stylesheet" href="{{ versioned_asset('dist/styles.css') }}">
<link rel="stylesheet" media="print" href="{{ versioned_asset('dist/print-styles.css') }}">
+ <!-- Icons -->
+ <link rel="icon" type="image/png" sizes="256x256" href="{{ setting('app-icon') ?? url('/icon.png') }}">
+ <link rel="icon" type="image/png" sizes="128x128" href="{{ setting('app-icon-128') ?? url('/icon-128.png') }}">
+ <link rel="icon" type="image/png" sizes="64x64" href="{{ setting('app-icon-64') ?? url('/icon-64.png') }}">
+ <link rel="icon" type="image/png" sizes="32x32" href="{{ setting('app-icon-32') ?? url('/icon-32.png') }}">
+
@yield('head')
<!-- Custom Styles & Head Content -->
</div>
</div>
+ <div class="grid half gap-xl">
+ <div>
+ <label class="setting-list-label">{{ 'Application Icon' }}</label>
+ <p class="small">
+ This icon is used for browser tabs and shortcut icons.
+ This should be a 256px square PNG image.
+ </p>
+ </div>
+ <div class="pt-xs">
+ @include('form.image-picker', [
+ 'removeValue' => 'none',
+ 'defaultImage' => url('/icon.png'),
+ 'currentImage' => setting('app-icon'),
+ 'name' => 'app_icon',
+ 'imageClass' => 'logo-image',
+ ])
+ </div>
+ </div>
+
<!-- Primary Color -->
<div class="grid half gap-xl">
<div>