Skip to content

Commit f6d3944

Browse files
authored
Merge pull request #3994 from BookStackApp/app_icon_setting
Added ability to control app icon (favicon) via settings
2 parents d835425 + a50b0ea commit f6d3944

File tree

12 files changed

+186
-45
lines changed

12 files changed

+186
-45
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: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
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 = [180, 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+
$this->destroyExistingSettingImage('app-icon-' . $size);
39+
$icon = $this->imageRepo->saveNew($iconFile, 'system', 0, $size, $size);
40+
setting()->put('app-icon-' . $size, $icon->url);
41+
}
42+
}
43+
44+
// Clear icon image if requested
45+
if ($request->get('app_icon_reset')) {
46+
$this->destroyExistingSettingImage('app-icon');
47+
setting()->remove('app-icon');
48+
foreach ($sizes as $size) {
49+
$this->destroyExistingSettingImage('app-icon-' . $size);
50+
setting()->remove('app-icon-' . $size);
51+
}
52+
}
53+
}
54+
55+
protected function updateAppLogo(Request $request): void
56+
{
57+
// Update logo image if set
58+
if ($request->hasFile('app_logo')) {
59+
$logoFile = $request->file('app_logo');
60+
$this->destroyExistingSettingImage('app-logo');
61+
$image = $this->imageRepo->saveNew($logoFile, 'system', 0, null, 86);
62+
setting()->put('app-logo', $image->url);
63+
}
64+
65+
// Clear logo image if requested
66+
if ($request->get('app_logo_reset')) {
67+
$this->destroyExistingSettingImage('app-logo');
68+
setting()->remove('app-logo');
69+
}
70+
}
71+
72+
protected function storeSimpleSettings(Request $request): void
73+
{
74+
foreach ($request->all() as $name => $value) {
75+
if (strpos($name, 'setting-') !== 0) {
76+
continue;
77+
}
78+
79+
$key = str_replace('setting-', '', trim($name));
80+
setting()->put($key, $value);
81+
}
82+
}
83+
84+
protected function destroyExistingSettingImage(string $settingKey)
85+
{
86+
$existingVal = setting()->get($settingKey);
87+
if ($existingVal) {
88+
$this->imageRepo->destroyByUrlAndType($existingVal, 'system');
89+
}
90+
}
91+
}

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: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,10 @@ public function getEntityFiltered(
123123
public function saveNew(UploadedFile $uploadFile, string $type, int $uploadedTo = 0, int $resizeWidth = null, int $resizeHeight = null, bool $keepRatio = true): Image
124124
{
125125
$image = $this->imageService->saveNewFromUpload($uploadFile, $type, $uploadedTo, $resizeWidth, $resizeHeight, $keepRatio);
126-
$this->loadThumbs($image);
126+
127+
if ($type !== 'system') {
128+
$this->loadThumbs($image);
129+
}
127130

128131
return $image;
129132
}
@@ -180,13 +183,17 @@ public function destroyImage(Image $image = null): void
180183
}
181184

182185
/**
183-
* Destroy all images of a certain type.
186+
* Destroy images that have a specific URL and type combination.
184187
*
185188
* @throws Exception
186189
*/
187-
public function destroyByType(string $imageType): void
190+
public function destroyByUrlAndType(string $url, string $imageType): void
188191
{
189-
$images = Image::query()->where('type', '=', $imageType)->get();
192+
$images = Image::query()
193+
->where('url', '=', $url)
194+
->where('type', '=', $imageType)
195+
->get();
196+
190197
foreach ($images as $image) {
191198
$this->destroyImage($image);
192199
}

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: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@
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.',
37+
'app_icon' => 'Application Icon',
38+
'app_icon_desc' => 'This icon is used for browser tabs and shortcut icons. This should be a 256px square PNG image.',
3739
'app_primary_color' => 'Application Primary Color',
3840
'app_primary_color_desc' => 'Sets the primary color for the application including the banner, buttons, and links.',
3941
'app_homepage' => 'Application Homepage',

resources/views/layouts/base.blade.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ class="{{ setting()->getForCurrentUser('dark-mode-enabled') ? 'dark-mode ' : ''
66
<title>{{ isset($pageTitle) ? $pageTitle . ' | ' : '' }}{{ setting('app-name') }}</title>
77

88
<!-- Meta -->
9+
<meta charset="utf-8">
910
<meta name="viewport" content="width=device-width">
1011
<meta name="token" content="{{ csrf_token() }}">
1112
<meta name="base-url" content="{{ url('/') }}">
12-
<meta charset="utf-8">
13+
<meta name="theme-color" content="{{ setting('app-color') }}"/>
1314

1415
<!-- Social Cards Meta -->
1516
<meta property="og:title" content="{{ isset($pageTitle) ? $pageTitle . ' | ' : '' }}{{ setting('app-name') }}">
@@ -20,6 +21,14 @@ class="{{ setting()->getForCurrentUser('dark-mode-enabled') ? 'dark-mode ' : ''
2021
<link rel="stylesheet" href="{{ versioned_asset('dist/styles.css') }}">
2122
<link rel="stylesheet" media="print" href="{{ versioned_asset('dist/print-styles.css') }}">
2223

24+
<!-- Icons -->
25+
<link rel="icon" type="image/png" sizes="256x256" href="{{ setting('app-icon') ?: url('/icon.png') }}">
26+
<link rel="icon" type="image/png" sizes="180x180" href="{{ setting('app-icon-180') ?: url('/icon-180.png') }}">
27+
<link rel="apple-touch-icon" sizes="180x180" href="{{ setting('app-icon-180') ?: url('/icon-180.png') }}">
28+
<link rel="icon" type="image/png" sizes="128x128" href="{{ setting('app-icon-128') ?: url('/icon-128.png') }}">
29+
<link rel="icon" type="image/png" sizes="64x64" href="{{ setting('app-icon-64') ?: url('/icon-64.png') }}">
30+
<link rel="icon" type="image/png" sizes="32x32" href="{{ setting('app-icon-32') ?: url('/icon-32.png') }}">
31+
2332
@yield('head')
2433

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

0 commit comments

Comments
 (0)