Files
domnitor/app/Models/Setting.php
Hosteroid ed3e5739f4 Add TLD registry import/export/create & logging
Add CSV/JSON export and import endpoints and UI for the TLD registry, plus a manual Create TLD modal and drag-and-drop import UX. Standardize import/export logging by adding Logger('import'/'export') calls to Domains, Tags, Notification Groups and TLD flows. Add TldRegistry model helpers (findByTld, getAll) used for deduplication and exports. Update routes for /tld-registry export/import/create and add a migration to bump app_version to 1.1.4. Also update default app_version, enhance WhoisService parsing (registrar regex and ISO-8601 date handling), and adjust the TLD registry index view to include IANA and Export dropdowns, import modal, create modal, and related JS behavior.
2026-03-02 11:17:58 +02:00

359 lines
11 KiB
PHP

<?php
namespace App\Models;
use Core\Model;
use App\Services\Logger;
class Setting extends Model
{
protected static string $table = 'settings';
private Logger $logger;
public function __construct()
{
parent::__construct();
$this->logger = new Logger('settings');
}
/**
* Get setting by key
*/
public function getByKey(string $key): ?array
{
$stmt = $this->db->prepare("SELECT * FROM settings WHERE setting_key = ?");
$stmt->execute([$key]);
$result = $stmt->fetch();
return $result ?: null;
}
/**
* Get setting value by key
*/
public function getValue(string $key, $default = null)
{
$setting = $this->getByKey($key);
return $setting ? $setting['setting_value'] : $default;
}
/**
* Set or update setting value
*/
public function setValue(string $key, $value): bool
{
$existing = $this->getByKey($key);
if ($existing) {
$stmt = $this->db->prepare("UPDATE settings SET setting_value = ?, updated_at = NOW() WHERE setting_key = ?");
return $stmt->execute([$value, $key]);
} else {
$stmt = $this->db->prepare("INSERT INTO settings (setting_key, setting_value) VALUES (?, ?)");
return $stmt->execute([$key, $value]);
}
}
/**
* Get all settings as key-value pairs
*/
public function getAllAsKeyValue(): array
{
$settings = $this->all();
$result = [];
foreach ($settings as $setting) {
$result[$setting['setting_key']] = $setting['setting_value'];
}
return $result;
}
/**
* Get notification days as array
*/
public function getNotificationDays(): array
{
$value = $this->getValue('notification_days_before', '30,15,7,3,1');
return array_map('intval', explode(',', $value));
}
/**
* Get check interval hours
*/
public function getCheckIntervalHours(): int
{
return (int)$this->getValue('check_interval_hours', 24);
}
/**
* Update notification days
*/
public function updateNotificationDays(array $days): bool
{
$value = implode(',', array_map('intval', $days));
return $this->setValue('notification_days_before', $value);
}
/**
* Update check interval
*/
public function updateCheckInterval(int $hours): bool
{
return $this->setValue('check_interval_hours', $hours);
}
/**
* Get last check run timestamp
*/
public function getLastCheckRun(): ?string
{
return $this->getValue('last_check_run');
}
/**
* Update last check run timestamp
*/
public function updateLastCheckRun(): bool
{
return $this->setValue('last_check_run', date('Y-m-d H:i:s'));
}
/**
* Get application version
*/
public function getAppVersion(): string
{
return $this->getValue('app_version', '1.1.4');
}
/**
* Get application settings
*/
public function getAppSettings(): array
{
return [
'app_name' => $this->getValue('app_name', 'Domain Monitor'),
'app_url' => $this->getValue('app_url', 'http://localhost:8000'),
'app_timezone' => $this->getValue('app_timezone', 'UTC'),
'app_version' => $this->getAppVersion()
];
}
/**
* Get email settings
*/
public function getEmailSettings(): array
{
$encryptedPassword = $this->getValue('mail_password', '');
// Decrypt password if it's encrypted
$decryptedPassword = '';
if (!empty($encryptedPassword)) {
try {
$encryption = new \Core\Encryption();
$decryptedPassword = $encryption->decrypt($encryptedPassword);
} catch (\Exception $e) {
// If decryption fails, it might be plaintext (migration scenario)
// Try to use as-is but log the issue
$this->logger->warning("Failed to decrypt mail_password", [
'error' => $e->getMessage()
]);
$decryptedPassword = $encryptedPassword;
}
}
return [
'mail_host' => $this->getValue('mail_host', 'smtp.mailtrap.io'),
'mail_port' => $this->getValue('mail_port', '2525'),
'mail_username' => $this->getValue('mail_username', ''),
'mail_password' => $decryptedPassword,
'mail_encryption' => $this->getValue('mail_encryption', 'tls'),
'mail_from_address' => $this->getValue('mail_from_address', 'noreply@domainmonitor.com'),
'mail_from_name' => $this->getValue('mail_from_name', 'Domain Monitor')
];
}
/**
* Update application settings
*/
public function updateAppSettings(array $settings): bool
{
$result = true;
foreach ($settings as $key => $value) {
if (!$this->setValue($key, $value)) {
$result = false;
}
}
return $result;
}
/**
* Update email settings
*/
public function updateEmailSettings(array $settings): bool
{
$result = true;
foreach ($settings as $key => $value) {
// Encrypt mail_password before storing
if ($key === 'mail_password' && !empty($value)) {
try {
$encryption = new \Core\Encryption();
$value = $encryption->encrypt($value);
} catch (\Exception $e) {
$this->logger->error("Failed to encrypt mail_password", [
'error' => $e->getMessage()
]);
return false;
}
}
if (!$this->setValue($key, $value)) {
$result = false;
}
}
return $result;
}
/**
* Get CAPTCHA settings
*/
public function getCaptchaSettings(): array
{
$encryptedSecret = $this->getValue('captcha_secret_key', '');
// Decrypt secret key if it's encrypted
$decryptedSecret = '';
if (!empty($encryptedSecret)) {
try {
$encryption = new \Core\Encryption();
$decryptedSecret = $encryption->decrypt($encryptedSecret);
} catch (\Exception $e) {
// If decryption fails, it might be plaintext (migration scenario)
$this->logger->warning("Failed to decrypt captcha_secret_key", [
'error' => $e->getMessage()
]);
$decryptedSecret = $encryptedSecret;
}
}
return [
'provider' => $this->getValue('captcha_provider', 'disabled'),
'site_key' => $this->getValue('captcha_site_key', ''),
'secret_key' => $decryptedSecret,
'score_threshold' => $this->getValue('recaptcha_v3_score_threshold', '0.5')
];
}
/**
* Update CAPTCHA settings
*/
public function updateCaptchaSettings(array $settings): bool
{
$result = true;
// Encrypt secret key before storing
if (isset($settings['captcha_secret_key']) && !empty($settings['captcha_secret_key'])) {
try {
$encryption = new \Core\Encryption();
$settings['captcha_secret_key'] = $encryption->encrypt($settings['captcha_secret_key']);
} catch (\Exception $e) {
$this->logger->error("Failed to encrypt captcha_secret_key", [
'error' => $e->getMessage()
]);
return false;
}
}
foreach ($settings as $key => $value) {
if (!$this->setValue($key, $value)) {
$result = false;
}
}
return $result;
}
/**
* Get 2FA settings
*/
public function getTwoFactorSettings(): array
{
return [
'policy' => $this->getValue('two_factor_policy', 'optional'),
'rate_limit_minutes' => (int)$this->getValue('two_factor_rate_limit_minutes', 15),
'email_code_expiry_minutes' => (int)$this->getValue('two_factor_email_code_expiry_minutes', 10)
];
}
/**
* Update 2FA settings
*/
public function updateTwoFactorSettings(array $settings): bool
{
$result = true;
foreach ($settings as $key => $value) {
if (!$this->setValue($key, $value)) {
$result = false;
}
}
return $result;
}
/**
* Get notification status triggers as array
* Returns which domain status changes should trigger notifications
*/
public function getNotificationStatusTriggers(): array
{
$value = $this->getValue('notification_status_triggers', 'available,registered,expired,redemption_period,pending_delete');
return array_map('trim', explode(',', $value));
}
/**
* Update notification status triggers
*/
public function updateNotificationStatusTriggers(array $triggers): bool
{
$validTriggers = ['available', 'registered', 'expired', 'redemption_period', 'pending_delete'];
$triggers = array_intersect($triggers, $validTriggers);
$value = implode(',', $triggers);
return $this->setValue('notification_status_triggers', $value);
}
/**
* Get update settings
*/
public function getUpdateSettings(): array
{
return [
'update_channel' => $this->getValue('update_channel', 'stable'),
'last_update_check' => $this->getValue('last_update_check', null),
'latest_available_version' => $this->getValue('latest_available_version', null),
'latest_release_notes' => $this->getValue('latest_release_notes', ''),
'latest_release_url' => $this->getValue('latest_release_url', ''),
'latest_release_published_at' => $this->getValue('latest_release_published_at', ''),
'installed_commit_sha' => $this->getValue('installed_commit_sha', null),
'update_backup_path' => $this->getValue('update_backup_path', null),
'update_db_backup_path' => $this->getValue('update_db_backup_path', null),
'commits_behind_count' => (int) $this->getValue('commits_behind_count', 0),
'latest_remote_sha' => $this->getValue('latest_remote_sha', ''),
'update_badge_enabled' => $this->getValue('update_badge_enabled', '1'),
];
}
/**
* Clear old notification logs
*/
public function clearOldNotificationLogs(int $daysOld = 30): int
{
$stmt = $this->db->prepare(
"DELETE FROM notification_logs WHERE sent_at < DATE_SUB(NOW(), INTERVAL ? DAY)"
);
$stmt->execute([$daysOld]);
return $stmt->rowCount();
}
}