Skip to content

Commit 55b6a78

Browse files
committed
Added ability to control app icon (favicon) via settings
1 parent 0f113ec commit 55b6a78

File tree

11 files changed

+132
-43
lines changed

11 files changed

+132
-43
lines changed

app/Http/Controllers/SettingController.php

Lines changed: 5 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,14 @@
44

55
use BookStack\Actions\ActivityType;
66
use BookStack\Auth\User;
7+
use BookStack\Settings\AppSettingsStore;
78
use BookStack\Uploads\ImageRepo;
89
use Illuminate\Http\Request;
910

1011
class SettingController extends Controller
1112
{
12-
protected ImageRepo $imageRepo;
13-
1413
protected array $settingCategories = ['features', 'customization', 'registration'];
1514

16-
public function __construct(ImageRepo $imageRepo)
17-
{
18-
$this->imageRepo = $imageRepo;
19-
}
20-
2115
/**
2216
* Handle requests to the settings index path.
2317
*/
@@ -48,37 +42,17 @@ public function category(string $category)
4842
/**
4943
* Update the specified settings in storage.
5044
*/
51-
public function update(Request $request, string $category)
45+
public function update(Request $request, AppSettingsStore $store, string $category)
5246
{
5347
$this->ensureCategoryExists($category);
5448
$this->preventAccessInDemoMode();
5549
$this->checkPermission('settings-manage');
5650
$this->validate($request, [
57-
'app_logo' => array_merge(['nullable'], $this->getImageValidationRules()),
51+
'app_logo' => ['nullable', ...$this->getImageValidationRules()],
52+
'app_icon' => ['nullable', ...$this->getImageValidationRules()],
5853
]);
5954

60-
// Cycles through posted settings and update them
61-
foreach ($request->all() as $name => $value) {
62-
$key = str_replace('setting-', '', trim($name));
63-
if (strpos($name, 'setting-') !== 0) {
64-
continue;
65-
}
66-
setting()->put($key, $value);
67-
}
68-
69-
// Update logo image if set
70-
if ($category === 'customization' && $request->hasFile('app_logo')) {
71-
$logoFile = $request->file('app_logo');
72-
$this->imageRepo->destroyByType('system');
73-
$image = $this->imageRepo->saveNew($logoFile, 'system', 0, null, 86);
74-
setting()->put('app-logo', $image->url);
75-
}
76-
77-
// Clear logo image if requested
78-
if ($category === 'customization' && $request->get('app_logo_reset', null)) {
79-
$this->imageRepo->destroyByType('system');
80-
setting()->remove('app-logo');
81-
}
55+
$store->storeFromUpdateRequest($request, $category);
8256

8357
$this->logActivity(ActivityType::SETTINGS_UPDATE, $category);
8458
$this->showSuccessNotification(trans('settings.settings_save_success'));

app/Settings/AppSettingsStore.php

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?php
2+
3+
namespace BookStack\Settings;
4+
5+
use BookStack\Uploads\ImageRepo;
6+
use Illuminate\Http\Request;
7+
8+
class AppSettingsStore
9+
{
10+
protected ImageRepo $imageRepo;
11+
12+
public function __construct(ImageRepo $imageRepo)
13+
{
14+
$this->imageRepo = $imageRepo;
15+
}
16+
17+
public function storeFromUpdateRequest(Request $request, string $category)
18+
{
19+
$this->storeSimpleSettings($request);
20+
if ($category === 'customization') {
21+
$this->updateAppLogo($request);
22+
$this->updateAppIcon($request);
23+
}
24+
}
25+
26+
protected function updateAppIcon(Request $request): void
27+
{
28+
$sizes = [128, 64, 32];
29+
30+
// Update icon image if set
31+
if ($request->hasFile('app_icon')) {
32+
$iconFile = $request->file('app_icon');
33+
$this->destroyExistingSettingImage('app-icon');
34+
$image = $this->imageRepo->saveNew($iconFile, 'system', 0, 256, 256);
35+
setting()->put('app-icon', $image->url);
36+
37+
foreach ($sizes as $size) {
38+
$icon = $this->imageRepo->saveNew($iconFile, 'system', 0, $size, $size);
39+
setting()->put('app-icon-' . $size, $icon->url);
40+
}
41+
}
42+
43+
// Clear icon image if requested
44+
if ($request->get('app_icon_reset')) {
45+
$this->destroyExistingSettingImage('app-icon');
46+
setting()->remove('app-icon');
47+
foreach ($sizes as $size) {
48+
$this->destroyExistingSettingImage('app-icon-' . $size);
49+
setting()->remove('app-icon-' . $size);
50+
}
51+
}
52+
}
53+
54+
protected function updateAppLogo(Request $request): void
55+
{
56+
// Update logo image if set
57+
if ($request->hasFile('app_logo')) {
58+
$logoFile = $request->file('app_logo');
59+
$this->destroyExistingSettingImage('app-logo');
60+
$image = $this->imageRepo->saveNew($logoFile, 'system', 0, null, 86);
61+
setting()->put('app-logo', $image->url);
62+
}
63+
64+
// Clear logo image if requested
65+
if ($request->get('app_logo_reset')) {
66+
$this->destroyExistingSettingImage('app-logo');
67+
setting()->remove('app-logo');
68+
}
69+
}
70+
71+
protected function storeSimpleSettings(Request $request): void
72+
{
73+
foreach ($request->all() as $name => $value) {
74+
if (strpos($name, 'setting-') !== 0) {
75+
continue;
76+
}
77+
78+
$key = str_replace('setting-', '', trim($name));
79+
setting()->put($key, $value);
80+
}
81+
}
82+
83+
protected function destroyExistingSettingImage(string $settingKey)
84+
{
85+
$existingVal = setting()->get($settingKey);
86+
if ($existingVal) {
87+
$this->imageRepo->destroyByUrlAndType($existingVal, 'system');
88+
}
89+
}
90+
}

app/Settings/SettingService.php

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,11 @@
1212
*/
1313
class SettingService
1414
{
15-
protected $setting;
16-
protected $cache;
17-
protected $localCache = [];
15+
protected Setting $setting;
16+
protected Cache $cache;
17+
protected array $localCache = [];
18+
protected string $cachePrefix = 'setting-';
1819

19-
protected $cachePrefix = 'setting-';
20-
21-
/**
22-
* SettingService constructor.
23-
*/
2420
public function __construct(Setting $setting, Cache $cache)
2521
{
2622
$this->setting = $setting;

app/Uploads/ImageRepo.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,13 +180,17 @@ public function destroyImage(Image $image = null): void
180180
}
181181

182182
/**
183-
* Destroy all images of a certain type.
183+
* Destroy images that have a specific URL and type combination.
184184
*
185185
* @throws Exception
186186
*/
187-
public function destroyByType(string $imageType): void
187+
public function destroyByUrlAndType(string $url, string $imageType): void
188188
{
189-
$images = Image::query()->where('type', '=', $imageType)->get();
189+
$images = Image::query()
190+
->where('url', '=', $url)
191+
->where('type', '=', $imageType)
192+
->get();
193+
190194
foreach ($images as $image) {
191195
$this->destroyImage($image);
192196
}

public/icon-128.png

3.46 KB
Loading

public/icon-32.png

1.31 KB
Loading

public/icon-64.png

1.91 KB
Loading

public/icon.png

6.74 KB
Loading

resources/lang/en/settings.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
'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.',
3434
'app_custom_html_disabled_notice' => 'Custom HTML head content is disabled on this settings page to ensure any breaking changes can be reverted.',
3535
'app_logo' => 'Application Logo',
36-
'app_logo_desc' => 'This image should be 43px in height. <br>Large images will be scaled down.',
36+
'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.',
3737
'app_primary_color' => 'Application Primary Color',
3838
'app_primary_color_desc' => 'Sets the primary color for the application including the banner, buttons, and links.',
3939
'app_homepage' => 'Application Homepage',

resources/views/layouts/base.blade.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ class="{{ setting()->getForCurrentUser('dark-mode-enabled') ? 'dark-mode ' : ''
2020
<link rel="stylesheet" href="{{ versioned_asset('dist/styles.css') }}">
2121
<link rel="stylesheet" media="print" href="{{ versioned_asset('dist/print-styles.css') }}">
2222

23+
<!-- Icons -->
24+
<link rel="icon" type="image/png" sizes="256x256" href="{{ setting('app-icon') ?? url('/icon.png') }}">
25+
<link rel="icon" type="image/png" sizes="128x128" href="{{ setting('app-icon-128') ?? url('/icon-128.png') }}">
26+
<link rel="icon" type="image/png" sizes="64x64" href="{{ setting('app-icon-64') ?? url('/icon-64.png') }}">
27+
<link rel="icon" type="image/png" sizes="32x32" href="{{ setting('app-icon-32') ?? url('/icon-32.png') }}">
28+
2329
@yield('head')
2430

2531
<!-- Custom Styles & Head Content -->

0 commit comments

Comments
 (0)