From 72ad1fb70e4f45fc2f1ab9a6c62dc730624f5755 Mon Sep 17 00:00:00 2001 From: Niek Rabelink Date: Thu, 10 Sep 2026 15:19:46 +0200 Subject: [PATCH] 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 --- admin.php | 16 ++-- api/live_sync.php | 5 + api/mail.php | 8 +- api/notifications.php | 5 + assets/js/notifications.js | 45 +++++---- config/schema.sql | 7 ++ includes/auth.php | 11 ++- includes/cache.php | 139 ++++++++++++++++++++++++++++ includes/header.php | 11 ++- includes/integrations/jira.php | 33 ++++--- includes/integrations/microsoft.php | 46 +++++++-- includes/settings.php | 50 ++++++---- mail.php | 7 +- planner.php | 20 +++- profile.php | 16 ++-- 15 files changed, 334 insertions(+), 85 deletions(-) create mode 100644 includes/cache.php diff --git a/admin.php b/admin.php index 8b60250..df84755 100644 --- a/admin.php +++ b/admin.php @@ -471,8 +471,8 @@ require_once __DIR__ . '/includes/header.php'; Wordt veilig en versleuteld opgeslagen
- -
@@ -1204,16 +1204,18 @@ function copyAdminRedirectUri() { function toggleAdminSecret() { const input = document.getElementById('adminClientSecret'); - const icon = document.getElementById('eyeSecretIcon'); - if (!input || !icon) return; + const btn = document.getElementById('toggleSecretBtn'); + if (!input) return; if (input.type === 'password') { input.type = 'text'; - icon.setAttribute('data-lucide', 'eye-off'); + if (btn) btn.innerHTML = ''; } else { input.type = 'password'; - icon.setAttribute('data-lucide', 'eye'); + if (btn) btn.innerHTML = ''; + } + if (window.lucide && typeof lucide.createIcons === 'function') { + lucide.createIcons(); } - if (window.lucide) lucide.createIcons(); } function toggleAdminGuide() { diff --git a/api/live_sync.php b/api/live_sync.php index 0fe34b5..4afef42 100644 --- a/api/live_sync.php +++ b/api/live_sync.php @@ -12,6 +12,11 @@ $user = require_auth(); $userId = (int)$user['id']; $db = get_db_connection(); +// Geef het PHP sessieslot direct vrij zodat gelijktijdige paginanavigatie nooit geblokkeerd wordt +if (session_status() === PHP_SESSION_ACTIVE) { + session_write_close(); +} + header('Content-Type: application/json; charset=utf-8'); try { diff --git a/api/mail.php b/api/mail.php index 52c8571..4ae95cd 100644 --- a/api/mail.php +++ b/api/mail.php @@ -11,6 +11,11 @@ $user = require_auth(); $userId = (int)$user['id']; $db = get_db_connection(); +// Geef het PHP sessieslot direct vrij zodat paginanavigatie nooit geblokkeerd wordt +if (session_status() === PHP_SESSION_ACTIVE) { + session_write_close(); +} + header('Content-Type: application/json; charset=utf-8'); $action = $_GET['action'] ?? ($_POST['action'] ?? ''); @@ -20,8 +25,9 @@ try { if ($action === 'get_inbox') { $limit = isset($_GET['limit']) ? (int)$_GET['limit'] : 25; $unreadOnly = !empty($_GET['unread_only']) && $_GET['unread_only'] === '1'; + $forceRefresh = !empty($_GET['refresh']) && $_GET['refresh'] === '1'; - $res = microsoft_fetch_inbox_messages($userId, $limit, $unreadOnly); + $res = microsoft_fetch_inbox_messages($userId, $limit, $unreadOnly, $forceRefresh); echo json_encode($res); exit; } diff --git a/api/notifications.php b/api/notifications.php index 9ff490f..31f1401 100644 --- a/api/notifications.php +++ b/api/notifications.php @@ -9,6 +9,11 @@ require_once __DIR__ . '/../includes/integrations/microsoft.php'; $user = require_auth(); $db = get_db_connection(); +// Geef het PHP sessieslot direct vrij zodat paginanavigatie nooit geblokkeerd wordt +if (session_status() === PHP_SESSION_ACTIVE) { + session_write_close(); +} + header('Content-Type: application/json; charset=utf-8'); $action = $_GET['action'] ?? ($_POST['action'] ?? ''); diff --git a/assets/js/notifications.js b/assets/js/notifications.js index 96862b5..8ef2959 100644 --- a/assets/js/notifications.js +++ b/assets/js/notifications.js @@ -425,15 +425,21 @@ if (res.ok) { const data = await res.json(); if (data && data.success) { - // Update mail badge in navigatie + // Update mail badge in navigatie (desktop & mobiel) const mailBadge = document.getElementById('navMailBadge'); - if (mailBadge && typeof data.mail?.unread_count !== 'undefined') { - if (data.mail.unread_count > 0) { - mailBadge.innerText = data.mail.unread_count; - mailBadge.classList.remove('hidden'); - } else { - mailBadge.classList.add('hidden'); - } + const mailBadgeMobile = document.getElementById('navMailBadgeMobile'); + if (typeof data.mail?.unread_count !== 'undefined') { + const count = parseInt(data.mail.unread_count, 10) || 0; + const badgeText = count > 99 ? '99+' : count; + [mailBadge, mailBadgeMobile].forEach(el => { + if (!el) return; + if (count > 0) { + el.innerText = badgeText; + el.classList.remove('hidden'); + } else { + el.classList.add('hidden'); + } + }); } // E-mail melding @@ -500,18 +506,25 @@ setInterval(refreshEvents, 60000); // Evaluatie interval elke 15 seconden voor agenda popups setInterval(evaluateEvents, 15000); - // Live sync interval elke 12 seconden voor instant mail, Jira & calendar updates - setInterval(checkLiveSync, 12000); + // Live sync interval elke 30 seconden voor mail, Jira & calendar updates + setInterval(checkLiveSync, 30000); - window.addEventListener('focus', () => { - checkLiveSync(); - refreshEvents(); - }); + // Intelligente throttle voor window focus en tabwisseling (max 1x per 20s) + let lastFocusCheck = Date.now(); + const throttledSync = () => { + const now = Date.now(); + if (now - lastFocusCheck > 20000) { + lastFocusCheck = now; + checkLiveSync(); + refreshEvents(); + } + }; + + window.addEventListener('focus', throttledSync); document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible') { - checkLiveSync(); - refreshEvents(); + throttledSync(); } }); diff --git a/config/schema.sql b/config/schema.sql index afd353e..dd49ea9 100644 --- a/config/schema.sql +++ b/config/schema.sql @@ -235,3 +235,10 @@ CREATE TABLE IF NOT EXISTS `jira_queue_filters` ( `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +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; diff --git a/includes/auth.php b/includes/auth.php index f2b892a..a5dc805 100644 --- a/includes/auth.php +++ b/includes/auth.php @@ -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(); diff --git a/includes/cache.php b/includes/cache.php new file mode 100644 index 0000000..003a620 --- /dev/null +++ b/includes/cache.php @@ -0,0 +1,139 @@ +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; + } +} diff --git a/includes/header.php b/includes/header.php index fc0a5e6..a52c0f6 100644 --- a/includes/header.php +++ b/includes/header.php @@ -165,10 +165,10 @@ require_once __DIR__ . '/env_badge.php'; Fociz - + Mail - + @@ -284,8 +284,11 @@ require_once __DIR__ . '/env_badge.php'; Fociz - - + +
+ + +
Mail
diff --git a/includes/integrations/jira.php b/includes/integrations/jira.php index c9caade..f88d5a7 100644 --- a/includes/integrations/jira.php +++ b/includes/integrations/jira.php @@ -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]); } diff --git a/includes/integrations/microsoft.php b/includes/integrations/microsoft.php index 16f3a01..1f83a8c 100644 --- a/includes/integrations/microsoft.php +++ b/includes/integrations/microsoft.php @@ -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.']; } diff --git a/includes/settings.php b/includes/settings.php index 43bbd66..d0f6dd3 100644 --- a/includes/settings.php +++ b/includes/settings.php @@ -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; diff --git a/mail.php b/mail.php index b4772e9..9577c8d 100644 --- a/mail.php +++ b/mail.php @@ -11,8 +11,9 @@ require_once __DIR__ . '/includes/integrations/microsoft.php'; $userId = (int)($currentUser['id'] ?? 0); $db = get_db_connection(); -// Haal initiële inbox data op -$inboxData = microsoft_fetch_inbox_messages($userId, 30); +// Haal initiële inbox data op (met 45s cache, tenzij ?refresh=1) +$forceRefresh = !empty($_GET['refresh']) && $_GET['refresh'] === '1'; +$inboxData = microsoft_fetch_inbox_messages($userId, 30, false, $forceRefresh); $messages = $inboxData['messages'] ?? []; $isDemo = !empty($inboxData['is_demo']); $unreadCount = (int)($inboxData['unread_count'] ?? 0); @@ -720,7 +721,7 @@ async function refreshMailbox() { if (icon) icon.classList.add('animate-spin'); try { - const res = await fetch('/api/mail.php?action=get_inbox'); + const res = await fetch('/api/mail.php?action=get_inbox&refresh=1'); const data = await res.json(); if (data.success && data.messages) { allMessages = data.messages; diff --git a/planner.php b/planner.php index 631f386..af1a02a 100644 --- a/planner.php +++ b/planner.php @@ -79,14 +79,24 @@ $syncIntervalSeconds = isset($outlookIntegrationSettings['sync_interval_seconds' $weekOutlookEvents = []; $isLiveOutlook = false; +$forceRefresh = !empty($_GET['refresh']) && $_GET['refresh'] === '1'; + if ($isOutlookConnected) { try { - // Haal afspraken op voor het volledige datumbereik van de week - $weekOutlookEvents = microsoft_fetch_events_range($userId, $weekStart, $weekEnd); + // Haal afspraken op voor het volledige datumbereik van de week (met 60s cache) + $weekOutlookEvents = microsoft_fetch_events_range($userId, $weekStart, $weekEnd, $forceRefresh); $isLiveOutlook = true; // Synchroniseer / reconcilieer Fociz taken direct met actuele Outlook data - microsoft_reconcile_tasks_with_outlook($userId, $weekStart, $weekEnd, $weekOutlookEvents); + // Alleen uitvoeren indien events gewijzigd zijn of bij expliciete refresh om overtollige DB load te voorkomen + $eventsHash = md5(json_encode($weekOutlookEvents)); + $lastReconciledHash = $_SESSION["last_reconciled_{$userId}"] ?? ''; + if ($eventsHash !== $lastReconciledHash || $forceRefresh) { + microsoft_reconcile_tasks_with_outlook($userId, $weekStart, $weekEnd, $weekOutlookEvents); + if (session_status() === PHP_SESSION_ACTIVE) { + $_SESSION["last_reconciled_{$userId}"] = $eventsHash; + } + } } catch (Throwable $e) { error_log("Outlook synchronisatiefout in planner: " . $e->getMessage()); $isLiveOutlook = false; @@ -224,7 +234,7 @@ for ($i = 0; $i < 7; $i++) { $jiraIssues = []; $isJiraLive = false; try { - $jiraData = jira_get_user_issues($userId); + $jiraData = jira_get_user_issues($userId, $forceRefresh); $jiraIssues = $jiraData['issues'] ?? []; $isJiraLive = !empty($jiraData['success']) && empty($jiraData['is_demo']); } catch (Throwable $e) { @@ -236,7 +246,7 @@ $recentEmails = []; $unreadEmailCount = 0; $mailNeedsReconsent = false; try { - $mailData = microsoft_fetch_inbox_messages($userId, 5); + $mailData = microsoft_fetch_inbox_messages($userId, 5, false, $forceRefresh); $recentEmails = $mailData['messages'] ?? []; $unreadEmailCount = (int)($mailData['unread_count'] ?? 0); $mailNeedsReconsent = !empty($mailData['needs_reconsent']); diff --git a/profile.php b/profile.php index 64bced7..3e3c637 100644 --- a/profile.php +++ b/profile.php @@ -840,8 +840,8 @@ require_once __DIR__ . '/includes/header.php';
- -
@@ -1122,16 +1122,18 @@ function updateProfileThemeUI(theme) { function toggleJiraToken() { const input = document.getElementById('jiraApiTokenInput'); - const icon = document.getElementById('eyeJiraIcon'); - if (!input || !icon) return; + const btn = document.getElementById('toggleJiraTokenBtn'); + if (!input) return; if (input.type === 'password') { input.type = 'text'; - icon.setAttribute('data-lucide', 'eye-off'); + if (btn) btn.innerHTML = ''; } else { input.type = 'password'; - icon.setAttribute('data-lucide', 'eye'); + if (btn) btn.innerHTML = ''; + } + if (window.lucide && typeof lucide.createIcons === 'function') { + lucide.createIcons(); } - if (window.lucide) lucide.createIcons(); } function previewUploadAvatar(input) {