'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 ', '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 */ 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); } } }