Skip to content

Commit 44c41e9

Browse files
committed
Updated footer links to be a configurable list
Made so footer link ordering, names and urls can be set. Cleaned up some of the setting-service and added support for array setting types, which are cleaned on entry and stored as json with a new type indicator column on the settings table for auto-decode. Also added testing to cover this feature. Related to #1973 and #854
1 parent a663364 commit 44c41e9

File tree

9 files changed

+197
-96
lines changed

9 files changed

+197
-96
lines changed

app/Settings/SettingService.php

Lines changed: 54 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?php namespace BookStack\Settings;
22

3+
use BookStack\Auth\User;
34
use Illuminate\Contracts\Cache\Repository as Cache;
45

56
/**
@@ -9,7 +10,6 @@
910
*/
1011
class SettingService
1112
{
12-
1313
protected $setting;
1414
protected $cache;
1515
protected $localCache = [];
@@ -18,8 +18,6 @@ class SettingService
1818

1919
/**
2020
* SettingService constructor.
21-
* @param Setting $setting
22-
* @param Cache $cache
2321
*/
2422
public function __construct(Setting $setting, Cache $cache)
2523
{
@@ -30,11 +28,8 @@ public function __construct(Setting $setting, Cache $cache)
3028
/**
3129
* Gets a setting from the database,
3230
* If not found, Returns default, Which is false by default.
33-
* @param $key
34-
* @param string|bool $default
35-
* @return bool|string
3631
*/
37-
public function get($key, $default = false)
32+
public function get(string $key, $default = false)
3833
{
3934
if ($default === false) {
4035
$default = config('setting-defaults.' . $key, false);
@@ -44,33 +39,25 @@ public function get($key, $default = false)
4439
return $this->localCache[$key];
4540
}
4641

47-
$value = $this->getValueFromStore($key, $default);
42+
$value = $this->getValueFromStore($key) ?? $default;
4843
$formatted = $this->formatValue($value, $default);
4944
$this->localCache[$key] = $formatted;
5045
return $formatted;
5146
}
5247

5348
/**
5449
* Get a value from the session instead of the main store option.
55-
* @param $key
56-
* @param bool $default
57-
* @return mixed
5850
*/
59-
protected function getFromSession($key, $default = false)
51+
protected function getFromSession(string $key, $default = false)
6052
{
6153
$value = session()->get($key, $default);
62-
$formatted = $this->formatValue($value, $default);
63-
return $formatted;
54+
return $this->formatValue($value, $default);
6455
}
6556

6657
/**
6758
* Get a user-specific setting from the database or cache.
68-
* @param \BookStack\Auth\User $user
69-
* @param $key
70-
* @param bool $default
71-
* @return bool|string
7259
*/
73-
public function getUser($user, $key, $default = false)
60+
public function getUser(User $user, string $key, $default = false)
7461
{
7562
if ($user->isDefault()) {
7663
return $this->getFromSession($key, $default);
@@ -80,23 +67,18 @@ public function getUser($user, $key, $default = false)
8067

8168
/**
8269
* Get a value for the current logged-in user.
83-
* @param $key
84-
* @param bool $default
85-
* @return bool|string
8670
*/
87-
public function getForCurrentUser($key, $default = false)
71+
public function getForCurrentUser(string $key, $default = false)
8872
{
8973
return $this->getUser(user(), $key, $default);
9074
}
9175

9276
/**
9377
* Gets a setting value from the cache or database.
9478
* Looks at the system defaults if not cached or in database.
95-
* @param $key
96-
* @param $default
97-
* @return mixed
79+
* Returns null if nothing is found.
9880
*/
99-
protected function getValueFromStore($key, $default)
81+
protected function getValueFromStore(string $key)
10082
{
10183
// Check the cache
10284
$cacheKey = $this->cachePrefix . $key;
@@ -109,18 +91,22 @@ protected function getValueFromStore($key, $default)
10991
$settingObject = $this->getSettingObjectByKey($key);
11092
if ($settingObject !== null) {
11193
$value = $settingObject->value;
94+
95+
if ($settingObject->type === 'array') {
96+
$value = json_decode($value, true) ?? [];
97+
}
98+
11299
$this->cache->forever($cacheKey, $value);
113100
return $value;
114101
}
115102

116-
return $default;
103+
return null;
117104
}
118105

119106
/**
120107
* Clear an item from the cache completely.
121-
* @param $key
122108
*/
123-
protected function clearFromCache($key)
109+
protected function clearFromCache(string $key)
124110
{
125111
$cacheKey = $this->cachePrefix . $key;
126112
$this->cache->forget($cacheKey);
@@ -131,17 +117,13 @@ protected function clearFromCache($key)
131117

132118
/**
133119
* Format a settings value
134-
* @param $value
135-
* @param $default
136-
* @return mixed
137120
*/
138121
protected function formatValue($value, $default)
139122
{
140123
// Change string booleans to actual booleans
141124
if ($value === 'true') {
142125
$value = true;
143-
}
144-
if ($value === 'false') {
126+
} else if ($value === 'false') {
145127
$value = false;
146128
}
147129

@@ -154,99 +136,97 @@ protected function formatValue($value, $default)
154136

155137
/**
156138
* Checks if a setting exists.
157-
* @param $key
158-
* @return bool
159139
*/
160-
public function has($key)
140+
public function has(string $key): bool
161141
{
162142
$setting = $this->getSettingObjectByKey($key);
163143
return $setting !== null;
164144
}
165145

166-
/**
167-
* Check if a user setting is in the database.
168-
* @param $key
169-
* @return bool
170-
*/
171-
public function hasUser($key)
172-
{
173-
return $this->has($this->userKey($key));
174-
}
175-
176146
/**
177147
* Add a setting to the database.
178-
* @param $key
179-
* @param $value
180-
* @return bool
148+
* Values can be an array or a string.
181149
*/
182-
public function put($key, $value)
150+
public function put(string $key, $value): bool
183151
{
184-
$setting = $this->setting->firstOrNew([
152+
$setting = $this->setting->newQuery()->firstOrNew([
185153
'setting_key' => $key
186154
]);
155+
$setting->type = 'string';
156+
157+
if (is_array($value)) {
158+
$setting->type = 'array';
159+
$value = $this->formatArrayValue($value);
160+
}
161+
187162
$setting->value = $value;
188163
$setting->save();
189164
$this->clearFromCache($key);
190165
return true;
191166
}
192167

168+
/**
169+
* Format an array to be stored as a setting.
170+
* Array setting types are expected to be a flat array of child key=>value array items.
171+
* This filters out any child items that are empty.
172+
*/
173+
protected function formatArrayValue(array $value): string
174+
{
175+
$values = collect($value)->values()->filter(function(array $item) {
176+
return count(array_filter($item)) > 0;
177+
});
178+
return json_encode($values);
179+
}
180+
193181
/**
194182
* Put a user-specific setting into the database.
195-
* @param \BookStack\Auth\User $user
196-
* @param $key
197-
* @param $value
198-
* @return bool
199183
*/
200-
public function putUser($user, $key, $value)
184+
public function putUser(User $user, string $key, string $value): bool
201185
{
202186
if ($user->isDefault()) {
203-
return session()->put($key, $value);
187+
session()->put($key, $value);
188+
return true;
204189
}
190+
205191
return $this->put($this->userKey($user->id, $key), $value);
206192
}
207193

208194
/**
209195
* Convert a setting key into a user-specific key.
210-
* @param $key
211-
* @return string
212196
*/
213-
protected function userKey($userId, $key = '')
197+
protected function userKey(string $userId, string $key = ''): string
214198
{
215199
return 'user:' . $userId . ':' . $key;
216200
}
217201

218202
/**
219203
* Removes a setting from the database.
220-
* @param $key
221-
* @return bool
222204
*/
223-
public function remove($key)
205+
public function remove(string $key): void
224206
{
225207
$setting = $this->getSettingObjectByKey($key);
226208
if ($setting) {
227209
$setting->delete();
228210
}
229211
$this->clearFromCache($key);
230-
return true;
231212
}
232213

233214
/**
234215
* Delete settings for a given user id.
235-
* @param $userId
236-
* @return mixed
237216
*/
238-
public function deleteUserSettings($userId)
217+
public function deleteUserSettings(string $userId)
239218
{
240-
return $this->setting->where('setting_key', 'like', $this->userKey($userId) . '%')->delete();
219+
return $this->setting->newQuery()
220+
->where('setting_key', 'like', $this->userKey($userId) . '%')
221+
->delete();
241222
}
242223

243224
/**
244225
* Gets a setting model from the database for the given key.
245-
* @param $key
246-
* @return mixed
247226
*/
248-
protected function getSettingObjectByKey($key)
227+
protected function getSettingObjectByKey(string $key): ?Setting
249228
{
250-
return $this->setting->where('setting_key', '=', $key)->first();
229+
return $this->setting->newQuery()
230+
->where('setting_key', '=', $key)->first();
251231
}
252232
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
class AddSettingsTypeColumn extends Migration
8+
{
9+
/**
10+
* Run the migrations.
11+
*
12+
* @return void
13+
*/
14+
public function up()
15+
{
16+
Schema::table('settings', function (Blueprint $table) {
17+
$table->string('type', 50)->default('string');
18+
});
19+
}
20+
21+
/**
22+
* Reverse the migrations.
23+
*
24+
* @return void
25+
*/
26+
public function down()
27+
{
28+
Schema::table('settings', function (Blueprint $table) {
29+
$table->dropColumn('type');
30+
});
31+
}
32+
}

resources/lang/en/common.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,9 @@
7777
// Email Content
7878
'email_action_help' => 'If you’re having trouble clicking the ":actionText" button, copy and paste the URL below into your web browser:',
7979
'email_rights' => 'All rights reserved',
80+
81+
// Footer Link Options
82+
// Not directly used but available for convenience to users.
83+
'privacy_policy' => 'Privacy Policy',
84+
'terms_of_service' => 'Terms of Service',
8085
];

resources/lang/en/settings.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,10 @@
3838
'app_homepage_desc' => 'Select a view to show on the homepage instead of the default view. Page permissions are ignored for selected pages.',
3939
'app_homepage_select' => 'Select a page',
4040
'app_footer_links' => 'Footer Links',
41-
'app_footer_links_desc' => 'Certain countries may require that websites include a privacy policy or terms of service. You may provide links to those here, which will then be displayed at the bottom of each page.',
42-
'app_privacy_policy' => 'Privacy Policy',
43-
'app_terms_of_service' => 'Terms of Service',
41+
'app_footer_links_desc' => 'Add links to show within the site footer. These will be displayed at the bottom of most pages, including those that do not require login. You can use a label of "trans::<key>" to use system-defined translations. For example: Using "trans::common.privacy_policy" will provide the translated text "Privacy Policy" and "trans::common.terms_of_service" will provide the translated text "Terms of Service".',
42+
'app_footer_links_label' => 'Link Label',
43+
'app_footer_links_url' => 'Link URL',
44+
'app_footer_links_add' => 'Add Footer Link',
4445
'app_disable_comments' => 'Disable Comments',
4546
'app_disable_comments_toggle' => 'Disable comments',
4647
'app_disable_comments_desc' => 'Disables comments across all pages in the application. <br> Existing comments are not shown.',

resources/sass/_footer.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
footer {
66
flex-shrink: 0;
7-
padding: .5em;
7+
padding: 1rem 1rem 2rem 1rem;
88
text-align: center;
99
}
1010

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
1-
@if(setting('app-privacy-policy') | setting('app-terms-of-service'))
1+
@if(count(setting('app-footer-links', [])) > 0)
22
<footer>
3-
@if(setting('app-privacy-policy'))
4-
<a href="{{ setting('app-privacy-policy') }}">{{ trans('settings.app_privacy_policy') }}</a>
5-
@endif
6-
@if(setting('app-terms-of-service'))
7-
<a href="{{ setting('app-terms-of-service') }}">{{ trans('settings.app_terms_of_service') }}</a>
8-
@endif
3+
@foreach(setting('app-footer-links', []) as $link)
4+
<a href="{{ $link['url'] }}" target="_blank">{{ strpos($link['label'], 'trans::') === 0 ? trans(str_replace('trans::', '', $link['label'])) : $link['label'] }}</a>
5+
@endforeach
96
</footer>
107
@endif

0 commit comments

Comments
 (0)