0 ? $id : null; } public static function setDefaultTenantId(?int $tenantId, ?string $description = null): bool { $value = $tenantId && $tenantId > 0 ? (string) $tenantId : null; if ($value !== null) { $exists = TenantRepository::find($tenantId); if (!$exists) { return false; } } $desc = $description ?? 'setting.default_tenant'; return SettingRepository::set(self::DEFAULT_TENANT_KEY, $value, $desc); } public static function getDefaultRoleId(): ?int { $id = self::getInt(self::DEFAULT_ROLE_KEY); return $id && $id > 0 ? $id : null; } public static function setDefaultRoleId(?int $roleId, ?string $description = null): bool { $value = $roleId && $roleId > 0 ? (string) $roleId : null; if ($value !== null) { $exists = RoleRepository::find($roleId); if (!$exists) { return false; } } $desc = $description ?? 'setting.default_role'; return SettingRepository::set(self::DEFAULT_ROLE_KEY, $value, $desc); } public static function getDefaultDepartmentId(): ?int { $id = self::getInt(self::DEFAULT_DEPARTMENT_KEY); return $id && $id > 0 ? $id : null; } public static function setDefaultDepartmentId(?int $departmentId, ?string $description = null): bool { $value = $departmentId && $departmentId > 0 ? (string) $departmentId : null; if ($value !== null) { $exists = DepartmentRepository::find($departmentId); if (!$exists) { return false; } } $desc = $description ?? 'setting.default_department'; return SettingRepository::set(self::DEFAULT_DEPARTMENT_KEY, $value, $desc); } public static function getAppTitle(): ?string { $value = SettingRepository::getValue(self::APP_TITLE_KEY); $value = $value !== null ? trim($value) : null; return $value !== '' ? $value : null; } public static function setAppTitle(?string $title, ?string $description = null): bool { $value = $title !== null ? trim($title) : null; if ($value === '') { $value = null; } $desc = $description ?? 'setting.app_title'; return SettingRepository::set(self::APP_TITLE_KEY, $value, $desc); } public static function getAppLocale(): ?string { $value = SettingRepository::getValue(self::APP_LOCALE_KEY); $value = $value !== null ? trim((string) $value) : ''; return $value !== '' ? $value : null; } public static function setAppLocale(?string $locale, ?string $description = null): bool { $value = $locale !== null ? trim((string) $locale) : ''; if ($value === '') { $value = null; } else { $allowed = defined('APP_LOCALES') ? APP_LOCALES : []; if ($allowed && !in_array($value, $allowed, true)) { return false; } } $desc = $description ?? 'setting.app_locale'; return SettingRepository::set(self::APP_LOCALE_KEY, $value, $desc); } public static function getAppTheme(): ?string { $value = SettingRepository::getValue(self::APP_THEME_KEY); $value = $value !== null ? strtolower(trim((string) $value)) : ''; if (!in_array($value, self::allowedThemes(), true)) { return null; } return $value; } public static function setAppTheme(?string $theme, ?string $description = null): bool { $value = $theme !== null ? strtolower(trim((string) $theme)) : ''; if ($value === '' || !in_array($value, self::allowedThemes(), true)) { $value = null; } $desc = $description ?? 'setting.app_theme'; return SettingRepository::set(self::APP_THEME_KEY, $value, $desc); } public static function isUserThemeAllowed(): bool { $value = SettingRepository::getValue(self::APP_THEME_USER_KEY); if ($value === null || $value === '') { return true; } return in_array(strtolower($value), ['1', 'true', 'yes', 'on'], true); } public static function setUserThemeAllowed(bool $allowed, ?string $description = null): bool { $value = $allowed ? '1' : '0'; $desc = $description ?? 'setting.app_theme_user'; return SettingRepository::set(self::APP_THEME_USER_KEY, $value, $desc); } private static function allowedThemes(): array { return array_keys(ThemeConfigService::all()); } public static function isRegistrationEnabled(): bool { $value = SettingRepository::getValue(self::APP_REGISTRATION_KEY); if ($value === null || $value === '') { return true; } return in_array(strtolower($value), ['1', 'true', 'yes', 'on'], true); } public static function setRegistrationEnabled(bool $allowed, ?string $description = null): bool { $value = $allowed ? '1' : '0'; $desc = $description ?? 'setting.app_registration'; return SettingRepository::set(self::APP_REGISTRATION_KEY, $value, $desc); } public static function getApiTokenDefaultTtlDays(): int { $maxDays = self::getApiTokenMaxTtlDays(); $value = self::getInt(self::API_TOKEN_DEFAULT_TTL_DAYS_KEY); if ($value !== null && $value >= self::API_TOKEN_TTL_DAYS_MIN && $value <= self::API_TOKEN_TTL_DAYS_HARD_MAX) { return min($value, $maxDays); } return min(self::API_TOKEN_DEFAULT_TTL_DAYS_FALLBACK, $maxDays); } public static function setApiTokenDefaultTtlDays(?int $days, ?string $description = null): bool { $value = $days ?? self::API_TOKEN_DEFAULT_TTL_DAYS_FALLBACK; if ($value < self::API_TOKEN_TTL_DAYS_MIN || $value > self::API_TOKEN_TTL_DAYS_HARD_MAX) { return false; } if ($value > self::getApiTokenMaxTtlDays()) { return false; } $desc = $description ?? 'setting.api_token_default_ttl_days'; return SettingRepository::set(self::API_TOKEN_DEFAULT_TTL_DAYS_KEY, (string) $value, $desc); } public static function getApiTokenMaxTtlDays(): int { $value = self::getInt(self::API_TOKEN_MAX_TTL_DAYS_KEY); if ($value !== null && $value >= self::API_TOKEN_TTL_DAYS_MIN && $value <= self::API_TOKEN_TTL_DAYS_HARD_MAX) { return $value; } return self::API_TOKEN_MAX_TTL_DAYS_FALLBACK; } public static function setApiTokenMaxTtlDays(?int $days, ?string $description = null): bool { $value = $days ?? self::API_TOKEN_MAX_TTL_DAYS_FALLBACK; if ($value < self::API_TOKEN_TTL_DAYS_MIN || $value > self::API_TOKEN_TTL_DAYS_HARD_MAX) { return false; } $currentDefault = self::getInt(self::API_TOKEN_DEFAULT_TTL_DAYS_KEY); if ($currentDefault !== null && $currentDefault > $value) { $defaultUpdated = SettingRepository::set( self::API_TOKEN_DEFAULT_TTL_DAYS_KEY, (string) $value, 'setting.api_token_default_ttl_days' ); if (!$defaultUpdated) { return false; } } $desc = $description ?? 'setting.api_token_max_ttl_days'; return SettingRepository::set(self::API_TOKEN_MAX_TTL_DAYS_KEY, (string) $value, $desc); } public static function getUserInactivityDeactivateDays(): int { $value = self::getInt(self::USER_INACTIVITY_DEACTIVATE_DAYS_KEY); if ($value !== null && self::isValidInactivityDays($value)) { return $value; } return self::USER_INACTIVITY_DEACTIVATE_DAYS_FALLBACK; } public static function setUserInactivityDeactivateDays(?int $days, ?string $description = null): bool { $value = $days ?? self::USER_INACTIVITY_DEACTIVATE_DAYS_FALLBACK; if (!self::isValidInactivityDays($value)) { return false; } if ($value === 0) { $deleteUpdated = SettingRepository::set( self::USER_INACTIVITY_DELETE_DAYS_KEY, '0', 'setting.user_inactivity_delete_days' ); if (!$deleteUpdated) { return false; } } $desc = $description ?? 'setting.user_inactivity_deactivate_days'; return SettingRepository::set(self::USER_INACTIVITY_DEACTIVATE_DAYS_KEY, (string) $value, $desc); } public static function getUserInactivityDeleteDays(): int { $deactivateDays = self::getUserInactivityDeactivateDays(); if ($deactivateDays <= 0) { return 0; } $value = self::getInt(self::USER_INACTIVITY_DELETE_DAYS_KEY); if ($value !== null && self::isValidInactivityDays($value)) { return $value; } return self::USER_INACTIVITY_DELETE_DAYS_FALLBACK; } public static function setUserInactivityDeleteDays(?int $days, ?string $description = null): bool { $value = $days ?? self::USER_INACTIVITY_DELETE_DAYS_FALLBACK; if (!self::isValidInactivityDays($value)) { return false; } if ($value > 0 && self::getUserInactivityDeactivateDays() <= 0) { return false; } $desc = $description ?? 'setting.user_inactivity_delete_days'; return SettingRepository::set(self::USER_INACTIVITY_DELETE_DAYS_KEY, (string) $value, $desc); } public static function getApiCorsAllowedOrigins(): array { $stored = SettingRepository::getValue(self::API_CORS_ALLOWED_ORIGINS_KEY); if ($stored === null || trim($stored) === '') { return []; } return self::parseCorsOrigins($stored, false) ?? []; } public static function getApiCorsAllowedOriginsText(): string { $origins = self::getApiCorsAllowedOrigins(); return implode("\n", $origins); } public static function setApiCorsAllowedOrigins(?string $rawOrigins, ?string $description = null): bool { $value = (string) ($rawOrigins ?? ''); if (trim($value) === '') { return SettingRepository::set( self::API_CORS_ALLOWED_ORIGINS_KEY, null, $description ?? 'setting.api_cors_allowed_origins' ); } $origins = self::parseCorsOrigins($value, true); if ($origins === null) { return false; } return SettingRepository::set( self::API_CORS_ALLOWED_ORIGINS_KEY, implode("\n", $origins), $description ?? 'setting.api_cors_allowed_origins' ); } private static function parseCorsOrigins(string $rawOrigins, bool $strict): ?array { $parts = preg_split('/[\r\n,]+/', $rawOrigins) ?: []; $normalizedOrigins = []; foreach ($parts as $part) { $origin = self::normalizeCorsOrigin($part); if ($origin === null) { if ($strict && trim((string) $part) !== '') { return null; } continue; } $normalizedOrigins[$origin] = true; } return array_keys($normalizedOrigins); } private static function normalizeCorsOrigin(string $origin): ?string { $origin = trim($origin); if ($origin === '') { return null; } // Browser Origin header never contains a trailing slash. $origin = rtrim($origin, '/'); if ($origin === '') { return null; } $parsed = parse_url($origin); if (!is_array($parsed)) { return null; } $scheme = strtolower((string) ($parsed['scheme'] ?? '')); $host = strtolower((string) ($parsed['host'] ?? '')); $port = isset($parsed['port']) ? (int) $parsed['port'] : null; if (!in_array($scheme, ['http', 'https'], true) || $host === '') { return null; } if ( isset($parsed['path']) || isset($parsed['query']) || isset($parsed['fragment']) || isset($parsed['user']) || isset($parsed['pass']) ) { return null; } $defaultPort = ($scheme === 'https') ? 443 : 80; $portSuffix = ($port !== null && $port !== $defaultPort) ? ':' . $port : ''; return $scheme . '://' . $host . $portSuffix; } public static function getMicrosoftSharedClientId(): ?string { $value = SettingRepository::getValue(self::MICROSOFT_SHARED_CLIENT_ID_KEY); $value = $value !== null ? trim((string) $value) : ''; return $value !== '' ? $value : null; } public static function setMicrosoftSharedClientId(?string $clientId, ?string $description = null): bool { $value = $clientId !== null ? trim((string) $clientId) : ''; if ($value === '') { $value = null; } $desc = $description ?? 'setting.microsoft_shared_client_id'; return SettingRepository::set(self::MICROSOFT_SHARED_CLIENT_ID_KEY, $value, $desc); } public static function getMicrosoftSharedClientSecretEncrypted(): ?string { $value = SettingRepository::getValue(self::MICROSOFT_SHARED_CLIENT_SECRET_ENC_KEY); $value = $value !== null ? trim((string) $value) : ''; return $value !== '' ? $value : null; } public static function getMicrosoftSharedClientSecret(): ?string { $encrypted = self::getMicrosoftSharedClientSecretEncrypted(); if ($encrypted === null) { return null; } try { $decrypted = Crypto::decryptString($encrypted); } catch (\Throwable $exception) { return null; } $decrypted = trim($decrypted); return $decrypted !== '' ? $decrypted : null; } public static function setMicrosoftSharedClientSecret(?string $secret, ?string $description = null): bool { $value = $secret !== null ? trim((string) $secret) : ''; if ($value === '') { return SettingRepository::set( self::MICROSOFT_SHARED_CLIENT_SECRET_ENC_KEY, null, $description ?? 'setting.microsoft_shared_client_secret_enc' ); } $encrypted = Crypto::encryptString($value); $desc = $description ?? 'setting.microsoft_shared_client_secret_enc'; return SettingRepository::set(self::MICROSOFT_SHARED_CLIENT_SECRET_ENC_KEY, $encrypted, $desc); } public static function getMicrosoftAuthority(): string { $value = SettingRepository::getValue(self::MICROSOFT_AUTHORITY_KEY); $value = trim((string) ($value ?? '')); if ($value === '') { return 'https://login.microsoftonline.com/common/v2.0'; } return rtrim($value, '/'); } public static function setMicrosoftAuthority(?string $authority, ?string $description = null): bool { $value = trim((string) ($authority ?? '')); if ($value === '') { $value = 'https://login.microsoftonline.com/common/v2.0'; } if (!preg_match('#^https://#i', $value)) { return false; } $desc = $description ?? 'setting.microsoft_authority'; return SettingRepository::set(self::MICROSOFT_AUTHORITY_KEY, rtrim($value, '/'), $desc); } public static function getAppPrimaryColor(): ?string { $value = SettingRepository::getValue(self::APP_PRIMARY_COLOR_KEY); $value = $value !== null ? strtolower(trim((string) $value)) : ''; if ($value === '') { return null; } if (!preg_match('/^#([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i', $value)) { return null; } return $value; } public static function setAppPrimaryColor(?string $color, ?string $description = null): bool { $value = $color !== null ? strtolower(trim((string) $color)) : ''; if ($value !== '' && $value[0] !== '#') { if (preg_match('/^[0-9a-f]{3}$/i', $value) || preg_match('/^[0-9a-f]{4}$/i', $value) || preg_match('/^[0-9a-f]{6}$/i', $value) || preg_match('/^[0-9a-f]{8}$/i', $value)) { $value = '#' . $value; } } if ($value === '') { $value = null; } elseif (!preg_match('/^#([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i', $value)) { return false; } $desc = $description ?? 'setting.app_primary_color'; return SettingRepository::set(self::APP_PRIMARY_COLOR_KEY, $value, $desc); } public static function getSmtpHost(): ?string { $value = SettingRepository::getValue(self::SMTP_HOST_KEY); $value = $value !== null ? trim((string) $value) : ''; return $value !== '' ? $value : null; } public static function setSmtpHost(?string $host, ?string $description = null): bool { $value = $host !== null ? trim((string) $host) : ''; if ($value === '') { $value = null; } $desc = $description ?? 'setting.smtp_host'; return SettingRepository::set(self::SMTP_HOST_KEY, $value, $desc); } public static function getSmtpPort(): ?int { $value = SettingRepository::getValue(self::SMTP_PORT_KEY); if ($value === null || $value === '') { return null; } $port = (int) $value; return $port > 0 ? $port : null; } public static function setSmtpPort(?int $port, ?string $description = null): bool { $value = $port && $port > 0 ? (string) $port : null; $desc = $description ?? 'setting.smtp_port'; return SettingRepository::set(self::SMTP_PORT_KEY, $value, $desc); } public static function getSmtpUser(): ?string { $value = SettingRepository::getValue(self::SMTP_USER_KEY); $value = $value !== null ? trim((string) $value) : ''; return $value !== '' ? $value : null; } public static function setSmtpUser(?string $user, ?string $description = null): bool { $value = $user !== null ? trim((string) $user) : ''; if ($value === '') { $value = null; } $desc = $description ?? 'setting.smtp_user'; return SettingRepository::set(self::SMTP_USER_KEY, $value, $desc); } public static function getSmtpPassword(): ?string { $value = SettingRepository::getValue(self::SMTP_PASSWORD_KEY); $value = $value !== null ? (string) $value : ''; return $value !== '' ? $value : null; } public static function setSmtpPassword(?string $password, ?string $description = null): bool { $value = $password !== null ? (string) $password : ''; if ($value === '') { $value = null; } $desc = $description ?? 'setting.smtp_password'; return SettingRepository::set(self::SMTP_PASSWORD_KEY, $value, $desc); } public static function getSmtpSecure(): ?string { $value = SettingRepository::getValue(self::SMTP_SECURE_KEY); $value = $value !== null ? strtolower(trim((string) $value)) : ''; if (!in_array($value, ['tls', 'ssl', 'none'], true)) { return null; } return $value === 'none' ? null : $value; } public static function setSmtpSecure(?string $secure, ?string $description = null): bool { $value = $secure !== null ? strtolower(trim((string) $secure)) : ''; if ($value === '' || $value === 'none') { $value = null; } elseif (!in_array($value, ['tls', 'ssl'], true)) { return false; } $desc = $description ?? 'setting.smtp_secure'; return SettingRepository::set(self::SMTP_SECURE_KEY, $value, $desc); } public static function getSmtpFrom(): ?string { $value = SettingRepository::getValue(self::SMTP_FROM_KEY); $value = $value !== null ? trim((string) $value) : ''; return $value !== '' ? $value : null; } public static function setSmtpFrom(?string $from, ?string $description = null): bool { $value = $from !== null ? trim((string) $from) : ''; if ($value === '') { $value = null; } $desc = $description ?? 'setting.smtp_from'; return SettingRepository::set(self::SMTP_FROM_KEY, $value, $desc); } public static function getSmtpFromName(): ?string { $value = SettingRepository::getValue(self::SMTP_FROM_NAME_KEY); $value = $value !== null ? trim((string) $value) : ''; return $value !== '' ? $value : null; } public static function setSmtpFromName(?string $fromName, ?string $description = null): bool { $value = $fromName !== null ? trim((string) $fromName) : ''; if ($value === '') { $value = null; } $desc = $description ?? 'setting.smtp_from_name'; return SettingRepository::set(self::SMTP_FROM_NAME_KEY, $value, $desc); } private static function isValidInactivityDays(int $days): bool { return $days >= self::USER_INACTIVITY_DAYS_MIN && $days <= self::USER_INACTIVITY_DAYS_MAX; } }