. */ declare(strict_types=1); namespace FireflyIII\Support; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Preference; 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 { // don't care about user group ID, except for some specific preferences. $userGroupId = $this->getUserGroupId($user, $name); $preference = Preference::where('user_group_id', $userGroupId)->where('user_id', $user->id)->where('name', $name)->first(['id', 'user_id', 'name', 'data', 'updated_at', 'created_at']); if (null !== $preference && null === $preference->data) { $preference->delete(); $preference = null; } if (null !== $preference) { return $preference; } // no preference found and default is null: if (null === $default) { // 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)) { $groupId = (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); $groupId = $this->getUserGroupId($user, $name); $groupId = 0 === (int) $groupId ? null : (int) $groupId; Cache::forget($fullName); /** @var null|Preference $pref */ $pref = Preference::where('user_group_id', $groupId)->where('user_id', $user->id)->where('name', $name)->first(['id', 'name', 'data', 'updated_at', 'created_at']); if (null !== $pref && null === $value) { $pref->delete(); return new Preference(); } if (null === $value) { return new Preference(); } if (null === $pref) { $pref = new Preference(); $pref->user_id = (int) $user->id; $pref->user_group_id = $groupId; $pref->name = $name; } $pref->data = $value; $pref->save(); Cache::forever($fullName, $pref); return $pref; } 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 (null === $result) { 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 { $lastActivity = microtime(); $preference = $this->get('lastActivity', microtime()); if (null !== $preference && null !== $preference->data) { $lastActivity = $preference->data; } if (is_array($lastActivity)) { $lastActivity = implode(',', $lastActivity); } return hash('sha256', (string) $lastActivity); } public function mark(): void { $this->set('lastActivity', 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); } }