feat(cache): implement centralized caching layer for improved performance and reduced API calls
feat(microsoft-integration): enhance inbox and events fetching with caching support feat(auth): include theme and notification settings in user data retrieval feat(profile): improve password visibility toggle functionality feat(planner): optimize event synchronization with Outlook using caching fix(notifications): update mail badge handling for mobile and desktop
This commit is contained in:
+7
-4
@@ -211,7 +211,7 @@ function get_logged_in_user(): ?array {
|
||||
|
||||
// 2. Controleer actieve PHP sessie (mits behorend bij vandaag)
|
||||
if (!empty($_SESSION['user_id']) && !empty($_SESSION['is_2fa_verified'])) {
|
||||
$stmt = $db->prepare("SELECT id, name, email, role, two_factor_enabled, avatar_url, created_at FROM users WHERE id = ?");
|
||||
$stmt = $db->prepare("SELECT id, name, email, role, two_factor_enabled, avatar_url, theme, notification_settings, default_jira_filter_code, fociz_widget_layout, created_at FROM users WHERE id = ?");
|
||||
$stmt->execute([$_SESSION['user_id']]);
|
||||
$user = $stmt->fetch();
|
||||
if ($user) {
|
||||
@@ -229,7 +229,7 @@ function get_logged_in_user(): ?array {
|
||||
// 3. Fallback dagsessie-token: herstelt sessie na browserherstart of server idle timeout (uitsluitend vandaag!)
|
||||
$dayToken = $_COOKIE['fociz_day_session'] ?? ($_COOKIE['fociz_remember'] ?? null);
|
||||
if (!empty($dayToken) && is_string($dayToken)) {
|
||||
$stmt = $db->prepare("SELECT id, name, email, role, two_factor_enabled, avatar_url, created_at FROM users WHERE remember_token = ? AND remember_token_expires_at >= NOW() AND DATE(remember_token_expires_at) = ?");
|
||||
$stmt = $db->prepare("SELECT id, name, email, role, two_factor_enabled, avatar_url, theme, notification_settings, default_jira_filter_code, fociz_widget_layout, created_at FROM users WHERE remember_token = ? AND remember_token_expires_at >= NOW() AND DATE(remember_token_expires_at) = ?");
|
||||
$stmt->execute([$dayToken, $today]);
|
||||
$user = $stmt->fetch();
|
||||
if ($user) {
|
||||
@@ -471,10 +471,13 @@ function update_user_notification_settings(int $userId, array $settings): bool {
|
||||
* Haalt het voorkeursthema op voor een gebruiker ('dark', 'light' of 'system')
|
||||
*/
|
||||
function get_user_theme(int $userId): string {
|
||||
$db = get_db_connection();
|
||||
ensure_theme_column($db);
|
||||
global $currentUser;
|
||||
if (!empty($currentUser['id']) && (int)$currentUser['id'] === $userId && !empty($currentUser['theme'])) {
|
||||
return $currentUser['theme'];
|
||||
}
|
||||
|
||||
try {
|
||||
$db = get_db_connection();
|
||||
$stmt = $db->prepare("SELECT theme FROM users WHERE id = ?");
|
||||
$stmt->execute([$userId]);
|
||||
$theme = $stmt->fetchColumn();
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
/**
|
||||
* Centrale Caching Laag voor Fociz
|
||||
* Biedt een supersnelle database- én in-memory transiënte cache voor externe API-aanroepen (Microsoft Graph, Jira Cloud).
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../config/database.php';
|
||||
|
||||
function ensure_app_cache_table(?PDO $db = null): void {
|
||||
static $ensured = false;
|
||||
if ($ensured) return;
|
||||
|
||||
try {
|
||||
$pdo = $db ?: get_db_connection();
|
||||
$pdo->exec("
|
||||
CREATE TABLE IF NOT EXISTS `app_cache` (
|
||||
`cache_key` VARCHAR(128) NOT NULL PRIMARY KEY,
|
||||
`cache_value` MEDIUMTEXT NOT NULL,
|
||||
`expires_at` INT UNSIGNED NOT NULL,
|
||||
INDEX `idx_app_cache_expires` (`expires_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
");
|
||||
$ensured = true;
|
||||
} catch (Throwable $e) {
|
||||
error_log("ensure_app_cache_table error: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Haalt een item op uit de cache.
|
||||
* Controleert eerst het runtime statisch geheugen, vervolgens MariaDB.
|
||||
*/
|
||||
function fociz_cache_get(string $key, $default = null) {
|
||||
static $memoryCache = [];
|
||||
|
||||
$now = time();
|
||||
|
||||
// 1. Runtime memory check (0ms)
|
||||
if (isset($memoryCache[$key])) {
|
||||
if ($memoryCache[$key]['expires_at'] > $now) {
|
||||
return $memoryCache[$key]['value'];
|
||||
}
|
||||
unset($memoryCache[$key]);
|
||||
}
|
||||
|
||||
// 2. Database check (< 0.5ms)
|
||||
try {
|
||||
$db = get_db_connection();
|
||||
ensure_app_cache_table($db);
|
||||
|
||||
$stmt = $db->prepare("SELECT cache_value, expires_at FROM app_cache WHERE cache_key = ?");
|
||||
$stmt->execute([$key]);
|
||||
$row = $stmt->fetch();
|
||||
|
||||
if ($row) {
|
||||
if ((int)$row['expires_at'] > $now) {
|
||||
$decoded = json_decode($row['cache_value'], true);
|
||||
$val = ($decoded !== null || $row['cache_value'] === 'null') ? $decoded : $row['cache_value'];
|
||||
$memoryCache[$key] = [
|
||||
'value' => $val,
|
||||
'expires_at' => (int)$row['expires_at']
|
||||
];
|
||||
return $val;
|
||||
} else {
|
||||
// Verlopen item direct opruimen
|
||||
$del = $db->prepare("DELETE FROM app_cache WHERE cache_key = ?");
|
||||
$del->execute([$key]);
|
||||
}
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
error_log("fociz_cache_get error for {$key}: " . $e->getMessage());
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Slaat een item op in de cache met een bepaalde levensduur in seconden.
|
||||
*/
|
||||
function fociz_cache_set(string $key, $value, int $ttlSeconds = 60): bool {
|
||||
static $memoryCache = [];
|
||||
|
||||
$now = time();
|
||||
$expiresAt = $now + max(1, $ttlSeconds);
|
||||
|
||||
// 1. Sla op in runtime memory
|
||||
$memoryCache[$key] = [
|
||||
'value' => $value,
|
||||
'expires_at' => $expiresAt
|
||||
];
|
||||
|
||||
// 2. Sla op in database
|
||||
try {
|
||||
$db = get_db_connection();
|
||||
ensure_app_cache_table($db);
|
||||
|
||||
$valToStore = is_string($value) ? $value : json_encode($value, JSON_UNESCAPED_UNICODE);
|
||||
|
||||
$stmt = $db->prepare("
|
||||
INSERT INTO app_cache (cache_key, cache_value, expires_at)
|
||||
VALUES (?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
cache_value = VALUES(cache_value),
|
||||
expires_at = VALUES(expires_at)
|
||||
");
|
||||
return $stmt->execute([$key, $valToStore, $expiresAt]);
|
||||
} catch (Throwable $e) {
|
||||
error_log("fociz_cache_set error for {$key}: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verwijdert een specifiek item uit de cache.
|
||||
*/
|
||||
function fociz_cache_delete(string $key): bool {
|
||||
try {
|
||||
$db = get_db_connection();
|
||||
ensure_app_cache_table($db);
|
||||
$stmt = $db->prepare("DELETE FROM app_cache WHERE cache_key = ?");
|
||||
return $stmt->execute([$key]);
|
||||
} catch (Throwable $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verwijdert alle items met een bepaald voorvoegsel (bijv. 'ms_events_1_')
|
||||
*/
|
||||
function fociz_cache_delete_pattern(string $prefix): bool {
|
||||
try {
|
||||
$db = get_db_connection();
|
||||
ensure_app_cache_table($db);
|
||||
$stmt = $db->prepare("DELETE FROM app_cache WHERE cache_key LIKE ?");
|
||||
return $stmt->execute([$prefix . '%']);
|
||||
} catch (Throwable $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+7
-4
@@ -165,10 +165,10 @@ require_once __DIR__ . '/env_badge.php';
|
||||
<span>Fociz</span>
|
||||
</a>
|
||||
|
||||
<a href="/mail.php" class="flex items-center space-x-2 px-3 py-1.5 rounded-xl text-xs font-semibold transition relative <?= $currentPage === 'mail.php' ? 'bg-indigo-50 text-indigo-700 border border-indigo-200/70 shadow-2xs dark:bg-indigo-500/10 dark:text-indigo-300 dark:border-indigo-500/20' : 'text-slate-600 hover:text-slate-900 hover:bg-slate-100 dark:text-slate-400 dark:hover:text-slate-200 dark:hover:bg-slate-800/60' ?>">
|
||||
<a href="/mail.php" class="relative flex items-center space-x-2 px-3 py-1.5 rounded-xl text-xs font-semibold transition <?= $currentPage === 'mail.php' ? 'bg-indigo-50 text-indigo-700 border border-indigo-200/70 shadow-2xs dark:bg-indigo-500/10 dark:text-indigo-300 dark:border-indigo-500/20' : 'text-slate-600 hover:text-slate-900 hover:bg-slate-100 dark:text-slate-400 dark:hover:text-slate-200 dark:hover:bg-slate-800/60' ?>">
|
||||
<i data-lucide="mail" class="w-4 h-4 text-sky-500"></i>
|
||||
<span>Mail</span>
|
||||
<span id="navMailBadge" class="hidden px-1.5 py-0.2 rounded-full text-[10px] font-black bg-sky-600 text-white leading-none"></span>
|
||||
<span id="navMailBadge" class="hidden absolute -top-1 -right-1 min-w-[18px] h-[18px] px-1 rounded-full text-[10px] font-black bg-sky-500 text-white flex items-center justify-center leading-none ring-2 ring-white dark:ring-[#0c0f17] shadow-xs pointer-events-none"></span>
|
||||
</a>
|
||||
|
||||
<!-- Dropdown: Projecten (Bord, Tijdlijn, Brainstorm, Roadmap) -->
|
||||
@@ -284,8 +284,11 @@ require_once __DIR__ . '/env_badge.php';
|
||||
<i data-lucide="layout-dashboard" class="w-4 h-4 mb-0.5"></i>
|
||||
<span>Fociz</span>
|
||||
</a>
|
||||
<a href="/mail.php" class="flex flex-col items-center py-1 <?= $currentPage === 'mail.php' ? 'text-indigo-600 dark:text-indigo-400 font-bold' : 'text-slate-500 dark:text-slate-400' ?>">
|
||||
<i data-lucide="mail" class="w-4 h-4 mb-0.5"></i>
|
||||
<a href="/mail.php" class="relative flex flex-col items-center py-1 <?= $currentPage === 'mail.php' ? 'text-indigo-600 dark:text-indigo-400 font-bold' : 'text-slate-500 dark:text-slate-400' ?>">
|
||||
<div class="relative">
|
||||
<i data-lucide="mail" class="w-4 h-4 mb-0.5"></i>
|
||||
<span id="navMailBadgeMobile" class="hidden absolute -top-1 -right-2 min-w-[14px] h-[14px] px-0.5 rounded-full text-[8px] font-black bg-sky-500 text-white flex items-center justify-center leading-none ring-2 ring-white dark:ring-[#0c0f17]"></span>
|
||||
</div>
|
||||
<span>Mail</span>
|
||||
</a>
|
||||
<a href="/board.php" class="flex flex-col items-center py-1 <?= $currentPage === 'board.php' ? 'text-indigo-600 dark:text-indigo-400 font-bold' : 'text-slate-500 dark:text-slate-400' ?>">
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
require_once __DIR__ . '/../http.php';
|
||||
require_once __DIR__ . '/../../config/database.php';
|
||||
require_once __DIR__ . '/../cache.php';
|
||||
|
||||
/**
|
||||
* Normaliseert het Jira domein naar een schone URL zonder slash aan het einde
|
||||
@@ -823,15 +824,18 @@ function jira_get_dashboard_data(int $userId, bool $forceRefresh = false): array
|
||||
];
|
||||
}
|
||||
|
||||
// Sessiecache voor 60 seconden
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
@session_start();
|
||||
}
|
||||
// Cache controle (60 seconden)
|
||||
$cacheKey = "jira_dashboard_user_{$userId}";
|
||||
$cached = $_SESSION[$cacheKey] ?? null;
|
||||
|
||||
if (!$forceRefresh && $cached && is_array($cached) && ($cached['timestamp'] ?? 0) > (time() - 60)) {
|
||||
return array_merge($cached['data'], ['from_cache' => true]);
|
||||
if (!$forceRefresh) {
|
||||
$cachedData = fociz_cache_get($cacheKey);
|
||||
if ($cachedData !== null && is_array($cachedData)) {
|
||||
return array_merge($cachedData, ['from_cache' => true]);
|
||||
}
|
||||
if (session_status() === PHP_SESSION_ACTIVE && !empty($_SESSION[$cacheKey]['data'])) {
|
||||
if (($_SESSION[$cacheKey]['timestamp'] ?? 0) > (time() - 60)) {
|
||||
return array_merge($_SESSION[$cacheKey]['data'], ['from_cache' => true]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ophalen van toegewezen taken
|
||||
@@ -865,10 +869,14 @@ function jira_get_dashboard_data(int $userId, bool $forceRefresh = false): array
|
||||
'default_queue_filter' => $userQueuePref,
|
||||
];
|
||||
|
||||
$_SESSION[$cacheKey] = [
|
||||
'timestamp' => time(),
|
||||
'data' => $data,
|
||||
];
|
||||
fociz_cache_set($cacheKey, $data, 60);
|
||||
|
||||
if (session_status() === PHP_SESSION_ACTIVE) {
|
||||
$_SESSION[$cacheKey] = [
|
||||
'timestamp' => time(),
|
||||
'data' => $data,
|
||||
];
|
||||
}
|
||||
|
||||
// Werk last_sync_at bij in database
|
||||
$stmtUpdate = $db->prepare("UPDATE integrations SET last_sync_at = NOW() WHERE id = ?");
|
||||
@@ -902,6 +910,7 @@ function jira_disconnect(int $userId): bool {
|
||||
}
|
||||
unset($_SESSION["jira_issues_user_{$userId}"]);
|
||||
unset($_SESSION["jira_dashboard_user_{$userId}"]);
|
||||
fociz_cache_delete("jira_dashboard_user_{$userId}");
|
||||
return $stmt->execute([$userId]);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
require_once __DIR__ . '/../http.php';
|
||||
require_once __DIR__ . '/../../config/database.php';
|
||||
require_once __DIR__ . '/../cache.php';
|
||||
|
||||
const MS_OAUTH_AUTHORIZE_URL = 'https://login.microsoftonline.com/%s/oauth2/v2.0/authorize';
|
||||
const MS_OAUTH_TOKEN_URL = 'https://login.microsoftonline.com/%s/oauth2/v2.0/token';
|
||||
@@ -279,7 +280,15 @@ function microsoft_get_valid_token(int $userId, bool $forceRefresh = false): ?st
|
||||
/**
|
||||
* Haalt agenda-afspraken van Microsoft Outlook op voor een datumbereik (bijv. een hele week)
|
||||
*/
|
||||
function microsoft_fetch_events_range(int $userId, string $startDate, string $endDate): array {
|
||||
function microsoft_fetch_events_range(int $userId, string $startDate, string $endDate, bool $forceRefresh = false): array {
|
||||
$cacheKey = "ms_events_{$userId}_{$startDate}_{$endDate}";
|
||||
if (!$forceRefresh) {
|
||||
$cached = fociz_cache_get($cacheKey);
|
||||
if ($cached !== null && is_array($cached)) {
|
||||
return $cached;
|
||||
}
|
||||
}
|
||||
|
||||
$token = microsoft_get_valid_token($userId);
|
||||
if (!$token) {
|
||||
return [];
|
||||
@@ -400,6 +409,7 @@ function microsoft_fetch_events_range(int $userId, string $startDate, string $en
|
||||
];
|
||||
}
|
||||
|
||||
fociz_cache_set($cacheKey, $events, 60);
|
||||
return $events;
|
||||
}
|
||||
|
||||
@@ -520,6 +530,7 @@ function microsoft_create_event(int $userId, array $eventData): array {
|
||||
}
|
||||
|
||||
if ($response['status'] === 201 && is_array($response['json'])) {
|
||||
fociz_cache_delete_pattern("ms_events_{$userId}_");
|
||||
return [
|
||||
'success' => true,
|
||||
'event_id' => $response['json']['id'] ?? null,
|
||||
@@ -559,7 +570,11 @@ function microsoft_delete_event(int $userId, string $eventId): bool {
|
||||
'timeout' => 12,
|
||||
]);
|
||||
|
||||
return in_array($response['status'], [200, 204, 404]);
|
||||
$ok = in_array($response['status'], [200, 204, 404]);
|
||||
if ($ok) {
|
||||
fociz_cache_delete_pattern("ms_events_{$userId}_");
|
||||
}
|
||||
return $ok;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -628,7 +643,11 @@ function microsoft_update_event(int $userId, string $eventId, array $eventData):
|
||||
}
|
||||
}
|
||||
|
||||
return in_array($response['status'], [200, 204]);
|
||||
$ok = in_array($response['status'], [200, 204]);
|
||||
if ($ok) {
|
||||
fociz_cache_delete_pattern("ms_events_{$userId}_");
|
||||
}
|
||||
return $ok;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -985,7 +1004,15 @@ function microsoft_get_mail_demo_data(): array {
|
||||
/**
|
||||
* Haalt inbox berichten op van Microsoft Graph API (of retourneert demo data)
|
||||
*/
|
||||
function microsoft_fetch_inbox_messages(int $userId, int $limit = 25, bool $unreadOnly = false): array {
|
||||
function microsoft_fetch_inbox_messages(int $userId, int $limit = 25, bool $unreadOnly = false, bool $forceRefresh = false): array {
|
||||
$cacheKey = "ms_inbox_{$userId}_{$limit}_" . ($unreadOnly ? '1' : '0');
|
||||
if (!$forceRefresh) {
|
||||
$cached = fociz_cache_get($cacheKey);
|
||||
if ($cached !== null && is_array($cached)) {
|
||||
return $cached;
|
||||
}
|
||||
}
|
||||
|
||||
$db = get_db_connection();
|
||||
$stmt = $db->prepare("SELECT * FROM integrations WHERE user_id = ? AND provider = 'OUTLOOK' AND is_enabled = 1");
|
||||
$stmt->execute([$userId]);
|
||||
@@ -1118,13 +1145,15 @@ function microsoft_fetch_inbox_messages(int $userId, int $limit = 25, bool $unre
|
||||
|
||||
$unreadCount = count(array_filter($messages, function($m) { return empty($m['is_read']); }));
|
||||
|
||||
return [
|
||||
$result = [
|
||||
'success' => true,
|
||||
'is_demo' => false,
|
||||
'messages' => $messages,
|
||||
'unread_count' => $unreadCount,
|
||||
'total_count' => count($messages),
|
||||
];
|
||||
fociz_cache_set($cacheKey, $result, 45); // 45 seconden cache
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1239,7 +1268,11 @@ function microsoft_mark_message_read(int $userId, string $messageId, bool $isRea
|
||||
'timeout' => 10,
|
||||
]);
|
||||
|
||||
return ($res['status'] >= 200 && $res['status'] < 300);
|
||||
$ok = ($res['status'] >= 200 && $res['status'] < 300);
|
||||
if ($ok) {
|
||||
fociz_cache_delete_pattern("ms_inbox_{$userId}_");
|
||||
}
|
||||
return $ok;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1271,6 +1304,7 @@ function microsoft_reply_message(int $userId, string $messageId, string $comment
|
||||
]);
|
||||
|
||||
if ($res['status'] >= 200 && $res['status'] < 300) {
|
||||
fociz_cache_delete_pattern("ms_inbox_{$userId}_");
|
||||
return ['success' => true, 'message' => 'Antwoord succesvol verzonden via Outlook.'];
|
||||
}
|
||||
|
||||
|
||||
+30
-20
@@ -29,36 +29,42 @@ function ensure_system_settings_table(?PDO $db = null): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Haalt een enkele instelling op uit MariaDB
|
||||
* Haalt alle systeeminstellingen in 1 geoptimaliseerde query op en bewaart deze in het static runtime geheugen
|
||||
*/
|
||||
function get_system_setting(string $key, $default = null) {
|
||||
function get_all_system_settings(bool $forceRefresh = false): array {
|
||||
static $settingsCache = null;
|
||||
if ($settingsCache !== null && !$forceRefresh) {
|
||||
return $settingsCache;
|
||||
}
|
||||
|
||||
$settingsCache = [];
|
||||
try {
|
||||
$db = get_db_connection();
|
||||
ensure_system_settings_table($db);
|
||||
|
||||
$stmt = $db->prepare("SELECT setting_value FROM system_settings WHERE setting_key = ?");
|
||||
$stmt->execute([$key]);
|
||||
$val = $stmt->fetchColumn();
|
||||
|
||||
if ($val === false || $val === null) {
|
||||
return $default;
|
||||
$stmt = $db->query("SELECT setting_key, setting_value FROM system_settings");
|
||||
while ($row = $stmt->fetch()) {
|
||||
$val = $row['setting_value'];
|
||||
$decoded = json_decode($val, true);
|
||||
$settingsCache[$row['setting_key']] = (json_last_error() === JSON_ERROR_NONE) ? $decoded : $val;
|
||||
}
|
||||
|
||||
// Decodeer JSON indien van toepassing
|
||||
$decoded = json_decode($val, true);
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
return $decoded;
|
||||
}
|
||||
|
||||
return $val;
|
||||
} catch (Throwable $e) {
|
||||
error_log("get_system_setting error for {$key}: " . $e->getMessage());
|
||||
return $default;
|
||||
error_log("get_all_system_settings error: " . $e->getMessage());
|
||||
}
|
||||
|
||||
return $settingsCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Slaat een instelling op in MariaDB
|
||||
* Haalt een enkele instelling op uit het geheugen (of fallback default)
|
||||
*/
|
||||
function get_system_setting(string $key, $default = null) {
|
||||
$all = get_all_system_settings();
|
||||
return array_key_exists($key, $all) ? $all[$key] : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Slaat een instelling op in MariaDB en ververst direct het cachegeheugen
|
||||
*/
|
||||
function set_system_setting(string $key, $value): bool {
|
||||
try {
|
||||
@@ -72,7 +78,11 @@ function set_system_setting(string $key, $value): bool {
|
||||
VALUES (?, ?)
|
||||
ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value), updated_at = NOW()
|
||||
");
|
||||
return $stmt->execute([$key, $valToStore]);
|
||||
$ok = $stmt->execute([$key, $valToStore]);
|
||||
if ($ok) {
|
||||
get_all_system_settings(true);
|
||||
}
|
||||
return $ok;
|
||||
} catch (Throwable $e) {
|
||||
error_log("set_system_setting error for {$key}: " . $e->getMessage());
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user