X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/1c43602f4bed60a84f47735ca8bc4a399018e013..refs/pull/5280/head:/app/Settings/SettingService.php diff --git a/app/Settings/SettingService.php b/app/Settings/SettingService.php index f2c4c8305..31debdaea 100644 --- a/app/Settings/SettingService.php +++ b/app/Settings/SettingService.php @@ -2,50 +2,30 @@ namespace BookStack\Settings; -use BookStack\Auth\User; -use Illuminate\Contracts\Cache\Repository as Cache; +use BookStack\Users\Models\User; /** * Class SettingService * The settings are a simple key-value database store. * For non-authenticated users, user settings are stored via the session instead. + * A local array-based cache is used to for setting accesses across a request. */ class SettingService { - protected $setting; - protected $cache; - protected $localCache = []; - - protected $cachePrefix = 'setting-'; - - /** - * SettingService constructor. - */ - public function __construct(Setting $setting, Cache $cache) - { - $this->setting = $setting; - $this->cache = $cache; - } + protected array $localCache = []; /** * Gets a setting from the database, * If not found, Returns default, Which is false by default. */ - public function get(string $key, $default = null) + public function get(string $key, $default = null): mixed { if (is_null($default)) { $default = config('setting-defaults.' . $key, false); } - if (isset($this->localCache[$key])) { - return $this->localCache[$key]; - } - $value = $this->getValueFromStore($key) ?? $default; - $formatted = $this->formatValue($value, $default); - $this->localCache[$key] = $formatted; - - return $formatted; + return $this->formatValue($value, $default); } /** @@ -67,7 +47,7 @@ class SettingService $default = config('setting-defaults.user.' . $key, false); } - if ($user->isDefault()) { + if ($user->isGuest()) { return $this->getFromSession($key, $default); } @@ -83,52 +63,78 @@ class SettingService } /** - * Gets a setting value from the cache or database. - * Looks at the system defaults if not cached or in database. - * Returns null if nothing is found. + * Gets a setting value from the local cache. + * Will load the local cache if not previously loaded. */ - protected function getValueFromStore(string $key) + protected function getValueFromStore(string $key): mixed { - // Check the cache - $cacheKey = $this->cachePrefix . $key; - $cacheVal = $this->cache->get($cacheKey, null); - if ($cacheVal !== null) { - return $cacheVal; + $cacheCategory = $this->localCacheCategory($key); + if (!isset($this->localCache[$cacheCategory])) { + $this->loadToLocalCache($cacheCategory); } - // Check the database - $settingObject = $this->getSettingObjectByKey($key); - if ($settingObject !== null) { - $value = $settingObject->value; + return $this->localCache[$cacheCategory][$key] ?? null; + } - if ($settingObject->type === 'array') { - $value = json_decode($value, true) ?? []; - } + /** + * Put the given value into the local cached under the given key. + */ + protected function putValueIntoLocalCache(string $key, mixed $value): void + { + $cacheCategory = $this->localCacheCategory($key); + if (!isset($this->localCache[$cacheCategory])) { + $this->loadToLocalCache($cacheCategory); + } - $this->cache->forever($cacheKey, $value); + $this->localCache[$cacheCategory][$key] = $value; + } - return $value; + /** + * Get the category for the given setting key. + * Will return 'app' for a general app setting otherwise 'user:' for a user setting. + */ + protected function localCacheCategory(string $key): string + { + if (str_starts_with($key, 'user:')) { + return implode(':', array_slice(explode(':', $key), 0, 2)); } - return null; + return 'app'; } /** - * Clear an item from the cache completely. + * For the given category, load the relevant settings from the database into the local cache. */ - protected function clearFromCache(string $key) + protected function loadToLocalCache(string $cacheCategory): void { - $cacheKey = $this->cachePrefix . $key; - $this->cache->forget($cacheKey); - if (isset($this->localCache[$key])) { - unset($this->localCache[$key]); + $query = Setting::query(); + + if ($cacheCategory === 'app') { + $query->where('setting_key', 'not like', 'user:%'); + } else { + $query->where('setting_key', 'like', $cacheCategory . ':%'); + } + $settings = $query->toBase()->get(); + + if (!isset($this->localCache[$cacheCategory])) { + $this->localCache[$cacheCategory] = []; + } + + foreach ($settings as $setting) { + $value = $setting->value; + + if ($setting->type === 'array') { + $value = json_decode($value, true) ?? []; + } + + $this->localCache[$cacheCategory][$setting->setting_key] = $value; } } /** * Format a settings value. */ - protected function formatValue($value, $default) + protected function formatValue(mixed $value, mixed $default): mixed { // Change string booleans to actual booleans if ($value === 'true') { @@ -159,21 +165,22 @@ class SettingService * Add a setting to the database. * Values can be an array or a string. */ - public function put(string $key, $value): bool + public function put(string $key, mixed $value): bool { - $setting = $this->setting->newQuery()->firstOrNew([ + $setting = Setting::query()->firstOrNew([ 'setting_key' => $key, ]); + $setting->type = 'string'; + $setting->value = $value; if (is_array($value)) { $setting->type = 'array'; - $value = $this->formatArrayValue($value); + $setting->value = $this->formatArrayValue($value); } - $setting->value = $value; $setting->save(); - $this->clearFromCache($key); + $this->putValueIntoLocalCache($key, $value); return true; } @@ -194,10 +201,12 @@ class SettingService /** * Put a user-specific setting into the database. + * Can only take string value types since this may use + * the session which is less flexible to data types. */ public function putUser(User $user, string $key, string $value): bool { - if ($user->isDefault()) { + if ($user->isGuest()) { session()->put($key, $value); return true; @@ -206,6 +215,16 @@ class SettingService return $this->put($this->userKey($user->id, $key), $value); } + /** + * Put a user-specific setting into the database for the current access user. + * Can only take string value types since this may use + * the session which is less flexible to data types. + */ + public function putForCurrentUser(string $key, string $value): bool + { + return $this->putUser(user(), $key, $value); + } + /** * Convert a setting key into a user-specific key. */ @@ -223,15 +242,19 @@ class SettingService if ($setting) { $setting->delete(); } - $this->clearFromCache($key); + + $cacheCategory = $this->localCacheCategory($key); + if (isset($this->localCache[$cacheCategory])) { + unset($this->localCache[$cacheCategory][$key]); + } } /** * Delete settings for a given user id. */ - public function deleteUserSettings(string $userId) + public function deleteUserSettings(string $userId): void { - return $this->setting->newQuery() + Setting::query() ->where('setting_key', 'like', $this->userKey($userId) . '%') ->delete(); } @@ -241,7 +264,16 @@ class SettingService */ protected function getSettingObjectByKey(string $key): ?Setting { - return $this->setting->newQuery() - ->where('setting_key', '=', $key)->first(); + return Setting::query() + ->where('setting_key', '=', $key) + ->first(); + } + + /** + * Empty the local setting value cache used by this service. + */ + public function flushCache(): void + { + $this->localCache = []; } }