. */ declare(strict_types=1); namespace FireflyIII\Support; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Preference; use FireflyIII\Support\Singleton\PreferencesSingleton; use FireflyIII\User; use Illuminate\Contracts\Encryption\DecryptException; use Illuminate\Contracts\Encryption\EncryptException; use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Session; /** * Class Preferences. */ class Preferences { public function all(): Collection { $user = auth()->user(); if (null === $user) { return new Collection(); } return Preference::where('user_id', $user->id) ->where('name', '!=', 'currencyPreference') ->where(function (Builder $q) use ($user): void { $q->whereNull('user_group_id'); $q->orWhere('user_group_id', $user->user_group_id); }) ->get() ; } public function get(string $name, null|array|bool|int|string $default = null): ?Preference { /** @var null|User $user */ $user = auth()->user(); if (null === $user) { $preference = new Preference(); $preference->data = $default; return $preference; } return $this->getForUser($user, $name, $default); } public function getForUser(User $user, string $name, null|array|bool|int|string $default = null): ?Preference { // Log::debug(sprintf('getForUser(#%d, "%s")', $user->id, $name)); // don't care about user group ID, except for some specific preferences. $userGroupId = $this->getUserGroupId($user, $name); $query = Preference::where('user_id', $user->id)->where('name', $name); if (null !== $userGroupId) { Log::debug('Include user group ID in query'); $query->where('user_group_id', $userGroupId); } $preference = $query->first(['id', 'user_id', 'user_group_id', 'name', 'data', 'updated_at', 'created_at']); if (null !== $preference && null === $preference->data) { $preference->delete(); $preference = null; Log::debug('Removed empty preference.'); } if (null !== $preference) { // Log::debug(sprintf('Found preference #%d for user #%d: %s', $preference->id, $user->id, $name)); return $preference; } // no preference found and default is null: if (null === $default) { Log::debug('Return NULL, create no preference.'); // return NULL return null; } return $this->setForUser($user, $name, $default); } private function getUserGroupId(User $user, string $preferenceName): ?int { $groupId = null; $items = config('firefly.admin_specific_prefs') ?? []; if (in_array($preferenceName, $items, true)) { return (int) $user->user_group_id; } return $groupId; } public function delete(string $name): bool { $fullName = sprintf('preference%s%s', auth()->user()->id, $name); if (Cache::has($fullName)) { Cache::forget($fullName); } Preference::where('user_id', auth()->user()->id)->where('name', $name)->delete(); return true; } public function forget(User $user, string $name): void { $key = sprintf('preference%s%s', $user->id, $name); Cache::forget($key); Cache::put($key, '', 5); } public function setForUser(User $user, string $name, null|array|bool|int|string $value): Preference { $fullName = sprintf('preference%s%s', $user->id, $name); $userGroupId = $this->getUserGroupId($user, $name); $userGroupId = 0 === (int) $userGroupId ? null : (int) $userGroupId; Cache::forget($fullName); $query = Preference::where('user_id', $user->id)->where('name', $name); if (null !== $userGroupId) { Log::debug('Include user group ID in query'); $query->where('user_group_id', $userGroupId); } $preference = $query->first(['id', 'user_id', 'user_group_id', 'name', 'data', 'updated_at', 'created_at']); if (null !== $preference && null === $value) { $preference->delete(); return new Preference(); } if (null === $value) { return new Preference(); } if (null === $preference) { $preference = new Preference(); $preference->user_id = (int) $user->id; $preference->user_group_id = $userGroupId; $preference->name = $name; } $preference->data = $value; $preference->save(); Cache::forever($fullName, $preference); return $preference; } public function beginsWith(User $user, string $search): Collection { $value = sprintf('%s%%', $search); return Preference::where('user_id', $user->id)->whereLike('name', $value)->get(); } /** * Find by name, has no user ID in it, because the method is called from an unauthenticated route any way. */ public function findByName(string $name): Collection { return Preference::where('name', $name)->get(); } public function getArrayForUser(User $user, array $list): array { $result = []; $preferences = Preference::where('user_id', $user->id) ->where(function (Builder $q) use ($user): void { $q->whereNull('user_group_id'); $q->orWhere('user_group_id', $user->user_group_id); }) ->whereIn('name', $list) ->get(['id', 'name', 'data']) ; /** @var Preference $preference */ foreach ($preferences as $preference) { $result[$preference->name] = $preference->data; } foreach ($list as $name) { if (!array_key_exists($name, $result)) { $result[$name] = null; } } return $result; } public function getEncrypted(string $name, mixed $default = null): ?Preference { $result = $this->get($name, $default); if (!$result instanceof Preference) { return null; } if ('' === $result->data) { // Log::warning(sprintf('Empty encrypted preference found: "%s"', $name)); return $result; } try { $result->data = decrypt($result->data); } catch (DecryptException $e) { if ('The MAC is invalid.' === $e->getMessage()) { Log::debug('Set data to NULL'); $result->data = null; } // Log::error(sprintf('Could not decrypt preference "%s": %s', $name, $e->getMessage())); return $result; } return $result; } public function getEncryptedForUser(User $user, string $name, null|array|bool|int|string $default = null): ?Preference { $result = $this->getForUser($user, $name, $default); if ('' === $result->data) { // Log::warning(sprintf('Empty encrypted preference found: "%s"', $name)); return $result; } try { $result->data = decrypt($result->data); } catch (DecryptException $e) { if ('The MAC is invalid.' === $e->getMessage()) { Log::debug('Set data to NULL'); $result->data = null; } // Log::error(sprintf('Could not decrypt preference "%s": %s', $name, $e->getMessage())); return $result; } return $result; } public function getFresh(string $name, null|array|bool|int|string $default = null): ?Preference { /** @var null|User $user */ $user = auth()->user(); if (null === $user) { $preference = new Preference(); $preference->data = $default; return $preference; } return $this->getForUser($user, $name, $default); } /** * @throws FireflyException */ public function lastActivity(): string { $instance = PreferencesSingleton::getInstance(); $pref = $instance->getPreference('last_activity'); if (null !== $pref) { // Log::debug(sprintf('Found last activity in singleton: %s', $pref)); return $pref; } $lastActivity = microtime(); $preference = $this->get('lastActivity', microtime()); if ($preference instanceof Preference && null !== $preference->data) { $lastActivity = $preference->data; } if (is_array($lastActivity)) { $lastActivity = implode(',', $lastActivity); } $setting = hash('sha256', (string) $lastActivity); $instance->setPreference('last_activity', $setting); return $setting; } public function mark(): void { $this->set('lastActivity', microtime()); $instance = PreferencesSingleton::getInstance(); $instance->setPreference('last_activity', microtime()); Session::forget('first'); } public function set(string $name, null|array|bool|int|string $value): Preference { /** @var null|User $user */ $user = auth()->user(); if (null === $user) { // make new preference, return it: $pref = new Preference(); $pref->name = $name; $pref->data = $value; return $pref; } return $this->setForUser($user, $name, $value); } public function setEncrypted(string $name, mixed $value): Preference { try { $encrypted = encrypt($value); } catch (EncryptException $e) { Log::error(sprintf('Could not encrypt preference "%s": %s', $name, $e->getMessage())); throw new FireflyException(sprintf('Could not encrypt preference "%s". Cowardly refuse to continue.', $name)); } return $this->set($name, $encrypted); } }