97 lines
3.3 KiB
PHP
97 lines
3.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Budget;
|
|
|
|
use PDO;
|
|
|
|
final class Settings
|
|
{
|
|
private const DEFAULTS = [
|
|
'app.name' => 'Neon Ledger',
|
|
'app.currency_symbol' => '$',
|
|
'app.timezone' => 'America/New_York',
|
|
'theme.default' => 'dark',
|
|
'modules.income' => true,
|
|
'modules.bills' => true,
|
|
'modules.calendar' => true,
|
|
'modules.dead_account' => true,
|
|
'modules.comparisons' => true,
|
|
'modules.alerts' => true,
|
|
'modules.import_export' => true,
|
|
'dead.income_mode' => 'percent',
|
|
'dead.income_value' => 0,
|
|
'dead.transaction_mode' => 'fixed',
|
|
'dead.transaction_value' => 0,
|
|
'dead.hide_balance' => true,
|
|
'alerts.email' => '',
|
|
'alerts.windows' => [7, 3, 1],
|
|
'alerts.times' => ['09:00'],
|
|
'mailgun.api_key' => '',
|
|
'mailgun.domain' => '',
|
|
'mailgun.region' => 'us',
|
|
'mailgun.from' => 'Budget Planner <budget@example.com>',
|
|
'security.2fa_required' => false,
|
|
];
|
|
|
|
public function __construct(private PDO $pdo)
|
|
{
|
|
$this->seed();
|
|
}
|
|
|
|
public function get(string $key, mixed $fallback = null): mixed
|
|
{
|
|
$statement = $this->pdo->prepare('SELECT setting_value FROM settings WHERE setting_key = :key');
|
|
$statement->execute(['key' => $key]);
|
|
$value = $statement->fetchColumn();
|
|
if ($value === false) {
|
|
return self::DEFAULTS[$key] ?? $fallback;
|
|
}
|
|
$decoded = json_decode((string) $value, true);
|
|
return json_last_error() === JSON_ERROR_NONE ? $decoded : $value;
|
|
}
|
|
|
|
public function set(string $key, mixed $value): void
|
|
{
|
|
$encoded = json_encode($value, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
|
if ($encoded === false) {
|
|
throw new \InvalidArgumentException('Setting value cannot be encoded.');
|
|
}
|
|
$sql = $this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME) === 'mysql'
|
|
? 'INSERT INTO settings (setting_key, setting_value) VALUES (:key, :value)
|
|
ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value), updated_at = CURRENT_TIMESTAMP'
|
|
: 'INSERT INTO settings (setting_key, setting_value) VALUES (:key, :value)
|
|
ON CONFLICT(setting_key) DO UPDATE SET setting_value = excluded.setting_value, updated_at = CURRENT_TIMESTAMP';
|
|
$statement = $this->pdo->prepare($sql);
|
|
$statement->execute(['key' => $key, 'value' => $encoded]);
|
|
}
|
|
|
|
/** @return array<string, mixed> */
|
|
public function all(): array
|
|
{
|
|
$settings = self::DEFAULTS;
|
|
foreach ($this->pdo->query('SELECT setting_key, setting_value FROM settings')->fetchAll() as $row) {
|
|
$decoded = json_decode((string) $row['setting_value'], true);
|
|
$settings[$row['setting_key']] = json_last_error() === JSON_ERROR_NONE ? $decoded : $row['setting_value'];
|
|
}
|
|
return $settings;
|
|
}
|
|
|
|
public function module(string $name): bool
|
|
{
|
|
return (bool) $this->get('modules.' . $name, true);
|
|
}
|
|
|
|
private function seed(): void
|
|
{
|
|
$count = (int) $this->pdo->query('SELECT COUNT(*) FROM settings')->fetchColumn();
|
|
if ($count > 0) {
|
|
return;
|
|
}
|
|
foreach (self::DEFAULTS as $key => $value) {
|
|
$this->set($key, $value);
|
|
}
|
|
}
|
|
}
|