Merge pull request 'Snelheid' (#96) from dev into main

Reviewed-on: #96
This commit was merged in pull request #96.
This commit is contained in:
2026-09-16 11:54:05 +00:00
6 changed files with 97 additions and 68 deletions
+8 -1
View File
@@ -126,8 +126,14 @@ function ensure_tables_exist(PDO $pdo): void {
if ($tablesChecked) return; if ($tablesChecked) return;
$tablesChecked = true; $tablesChecked = true;
$schemaFile = file_exists(__DIR__ . '/schema.sql') ? (__DIR__ . '/schema.sql') : (__DIR__ . '/../schema.sql');
$lockFile = sys_get_temp_dir() . '/fociz_schema_' . md5(__DIR__) . '.lock';
if (file_exists($lockFile) && file_exists($schemaFile) && filemtime($lockFile) >= filemtime($schemaFile)) {
return;
}
try { try {
$schemaFile = file_exists(__DIR__ . '/schema.sql') ? (__DIR__ . '/schema.sql') : (__DIR__ . '/../schema.sql');
if (file_exists($schemaFile)) { if (file_exists($schemaFile)) {
$sql = file_get_contents($schemaFile); $sql = file_get_contents($schemaFile);
$statements = array_filter(array_map('trim', explode(';', $sql))); $statements = array_filter(array_map('trim', explode(';', $sql)));
@@ -300,6 +306,7 @@ function ensure_tables_exist(PDO $pdo): void {
} catch (Throwable $e) { } catch (Throwable $e) {
// Stil negeren indien system_settings tabel nog niet bestaat // Stil negeren indien system_settings tabel nog niet bestaat
} }
@touch($lockFile);
} catch (Throwable $e) { } catch (Throwable $e) {
error_log("Fout bij controleren tabellen: " . $e->getMessage()); error_log("Fout bij controleren tabellen: " . $e->getMessage());
} }
+12
View File
@@ -347,6 +347,11 @@ function ensure_remember_token_columns(PDO $db): void {
if ($columnsChecked) return; if ($columnsChecked) return;
$columnsChecked = true; $columnsChecked = true;
$lockFile = sys_get_temp_dir() . '/fociz_auth_cols_' . md5(__DIR__) . '.lock';
if (file_exists($lockFile)) {
return;
}
try { try {
$result = $db->query("SHOW COLUMNS FROM `users` LIKE 'remember_token'"); $result = $db->query("SHOW COLUMNS FROM `users` LIKE 'remember_token'");
if (!$result || $result->rowCount() === 0) { if (!$result || $result->rowCount() === 0) {
@@ -360,6 +365,7 @@ function ensure_remember_token_columns(PDO $db): void {
if (!$resActivity || $resActivity->rowCount() === 0) { if (!$resActivity || $resActivity->rowCount() === 0) {
$db->exec("ALTER TABLE `users` ADD COLUMN `last_activity_at` DATETIME NULL AFTER `remember_token_expires_at`"); $db->exec("ALTER TABLE `users` ADD COLUMN `last_activity_at` DATETIME NULL AFTER `remember_token_expires_at`");
} }
@touch($lockFile);
} catch (Throwable $e) { } catch (Throwable $e) {
// Stil negeren indien al aanwezig of tijdens migraties // Stil negeren indien al aanwezig of tijdens migraties
} }
@@ -374,6 +380,11 @@ function ensure_sessions_table(PDO $db): void {
if ($sessionsTableChecked) return; if ($sessionsTableChecked) return;
$sessionsTableChecked = true; $sessionsTableChecked = true;
$lockFile = sys_get_temp_dir() . '/fociz_sessions_tbl_' . md5(__DIR__) . '.lock';
if (file_exists($lockFile)) {
return;
}
try { try {
$db->exec(" $db->exec("
CREATE TABLE IF NOT EXISTS `user_sessions` ( CREATE TABLE IF NOT EXISTS `user_sessions` (
@@ -394,6 +405,7 @@ function ensure_sessions_table(PDO $db): void {
CONSTRAINT `fk_user_sessions_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE CONSTRAINT `fk_user_sessions_user` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
"); ");
@touch($lockFile);
} catch (Throwable $e) { } catch (Throwable $e) {
error_log("ensure_sessions_table notice: " . $e->getMessage()); error_log("ensure_sessions_table notice: " . $e->getMessage());
} }
+2 -1
View File
@@ -70,7 +70,8 @@ if (!function_exists('fociz_http_request')) {
curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 6); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, min(4, max(1, $timeout - 1)));
curl_setopt($ch, CURLOPT_DNS_CACHE_TIMEOUT, 600);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headerLines); curl_setopt($ch, CURLOPT_HTTPHEADER, $headerLines);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $verifySsl); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $verifySsl);
+21 -19
View File
@@ -343,7 +343,7 @@ function jira_fetch_project_statuses(string $domain, string $email, string $apiT
$cacheKey = "jira_proj_statuses_" . $pKey . "_" . md5($cleanDomain); $cacheKey = "jira_proj_statuses_" . $pKey . "_" . md5($cleanDomain);
$cached = fociz_cache_get($cacheKey); $cached = fociz_cache_get($cacheKey);
if (is_array($cached) && !empty($cached)) { if ($cached !== null && is_array($cached)) {
return $cached; return $cached;
} }
@@ -356,7 +356,7 @@ function jira_fetch_project_statuses(string $domain, string $email, string $apiT
'Authorization' => $authHeader, 'Authorization' => $authHeader,
'Accept' => 'application/json', 'Accept' => 'application/json',
], ],
'timeout' => 8, 'timeout' => 4,
]); ]);
if ($response['status'] === 404 || $response['status'] === 405) { if ($response['status'] === 404 || $response['status'] === 405) {
@@ -367,7 +367,7 @@ function jira_fetch_project_statuses(string $domain, string $email, string $apiT
'Authorization' => $authHeader, 'Authorization' => $authHeader,
'Accept' => 'application/json', 'Accept' => 'application/json',
], ],
'timeout' => 8, 'timeout' => 4,
]); ]);
} }
@@ -403,7 +403,7 @@ function jira_fetch_project_statuses(string $domain, string $email, string $apiT
fociz_cache_set($cacheKey, $statuses, 86400); // 24 uur cache fociz_cache_set($cacheKey, $statuses, 86400); // 24 uur cache
} else { } else {
fociz_cache_set($cacheKey, [], 1800); // 30 min negatieve cache fociz_cache_set($cacheKey, [], 86400); // 24 uur negatieve cache zodat ontbrekende projecten niet herhaaldelijk vertragen
} }
return $statuses; return $statuses;
@@ -471,7 +471,7 @@ function jira_fetch_issues(string $domain, string $email, string $apiToken, stri
'Content-Type' => 'application/json', 'Content-Type' => 'application/json',
], ],
'body' => $payload, 'body' => $payload,
'timeout' => 12, 'timeout' => 6,
]); ]);
if ($response['status'] === 405 || $response['status'] === 400) { if ($response['status'] === 405 || $response['status'] === 400) {
@@ -500,7 +500,7 @@ function jira_fetch_issues(string $domain, string $email, string $apiToken, stri
'Authorization' => $authHeader, 'Authorization' => $authHeader,
'Accept' => 'application/json', 'Accept' => 'application/json',
], ],
'timeout' => 12, 'timeout' => 6,
]); ]);
if ($response['status'] === 404) { if ($response['status'] === 404) {
@@ -513,9 +513,13 @@ function jira_fetch_issues(string $domain, string $email, string $apiToken, stri
$getParams = [ $getParams = [
'jql' => $jql, 'jql' => $jql,
'maxResults' => $currentPageSize, 'maxResults' => $currentPageSize,
'startAt' => $startAt,
'fields' => implode(',', $fieldsList), 'fields' => implode(',', $fieldsList),
]; ];
if (!empty($nextPageToken)) {
$getParams['nextPageToken'] = $nextPageToken;
} elseif ($startAt > 0) {
$getParams['startAt'] = $startAt;
}
$urlV2 = $cleanDomain . '/rest/api/2/search?' . http_build_query($getParams); $urlV2 = $cleanDomain . '/rest/api/2/search?' . http_build_query($getParams);
$response = fociz_http_request($urlV2, [ $response = fociz_http_request($urlV2, [
'method' => 'GET', 'method' => 'GET',
@@ -523,7 +527,7 @@ function jira_fetch_issues(string $domain, string $email, string $apiToken, stri
'Authorization' => $authHeader, 'Authorization' => $authHeader,
'Accept' => 'application/json', 'Accept' => 'application/json',
], ],
'timeout' => 12, 'timeout' => 6,
]); ]);
} }
@@ -1238,6 +1242,11 @@ function jira_get_dashboard_data(int $userId, bool $forceRefresh = false): array
$availableStatuses = jira_fetch_statuses($domain, $email, $apiToken); $availableStatuses = jira_fetch_statuses($domain, $email, $apiToken);
if (!$assignedRes['success'] && !$teamRes['success']) { if (!$assignedRes['success'] && !$teamRes['success']) {
// Val terug op bestaande cache snapshot indien live request faalt
$fallback = fociz_cache_get($cacheKey);
if ($fallback && is_array($fallback)) {
return array_merge($fallback, ['from_cache' => true, 'warning' => 'Kon Jira niet actueel bijwerken; tonen opgeslagen snapshot.']);
}
return [ return [
'is_demo' => false, 'is_demo' => false,
'success' => false, 'success' => false,
@@ -1250,17 +1259,10 @@ function jira_get_dashboard_data(int $userId, bool $forceRefresh = false): array
$assignedIssues = $assignedRes['success'] ? $assignedRes['issues'] : []; $assignedIssues = $assignedRes['success'] ? $assignedRes['issues'] : [];
$teamIssues = $teamRes['success'] ? $teamRes['issues'] : []; $teamIssues = $teamRes['success'] ? $teamRes['issues'] : [];
// Project-specifieke statussen ophalen voor gebruikte projecten // Projectstatussen: haal uitsluitend het actieve hoofdproject op om sequentiële HTTP latency te elimineren
$activeProjKeys = ['SD', 'IN', 'AAN', 'GEN'];
$allLoaded = array_merge($assignedIssues, $teamIssues);
foreach ($allLoaded as $iss) {
$pk = strtoupper(trim($iss['project_key'] ?? ''));
if (!empty($pk) && !in_array($pk, $activeProjKeys, true)) {
$activeProjKeys[] = $pk;
}
}
$projectStatusesMap = []; $projectStatusesMap = [];
foreach ($activeProjKeys as $pk) { $targetProjects = ['SD'];
foreach ($targetProjects as $pk) {
$pStats = jira_fetch_project_statuses($domain, $email, $apiToken, $pk); $pStats = jira_fetch_project_statuses($domain, $email, $apiToken, $pk);
if (!empty($pStats)) { if (!empty($pStats)) {
$projectStatusesMap[$pk] = $pStats; $projectStatusesMap[$pk] = $pStats;
@@ -1283,7 +1285,7 @@ function jira_get_dashboard_data(int $userId, bool $forceRefresh = false): array
'project_statuses' => $projectStatusesMap, 'project_statuses' => $projectStatusesMap,
]; ];
fociz_cache_set($cacheKey, $data, 180); // 3 minuten cache fociz_cache_set($cacheKey, $data, 900); // 15 minuten cache voor supersnelle paginaladingen
if (session_status() === PHP_SESSION_ACTIVE) { if (session_status() === PHP_SESSION_ACTIVE) {
$_SESSION[$cacheKey] = [ $_SESSION[$cacheKey] = [
+4 -4
View File
@@ -330,7 +330,7 @@ function microsoft_fetch_events_range(int $userId, string $startDate, string $en
'Accept' => 'application/json', 'Accept' => 'application/json',
'Prefer' => 'outlook.timezone="Europe/Amsterdam"', 'Prefer' => 'outlook.timezone="Europe/Amsterdam"',
], ],
'timeout' => 12, 'timeout' => 6,
]); ]);
// Retry bij 401 Unauthorized // Retry bij 401 Unauthorized
@@ -345,7 +345,7 @@ function microsoft_fetch_events_range(int $userId, string $startDate, string $en
'Accept' => 'application/json', 'Accept' => 'application/json',
'Prefer' => 'outlook.timezone="Europe/Amsterdam"', 'Prefer' => 'outlook.timezone="Europe/Amsterdam"',
], ],
'timeout' => 12, 'timeout' => 6,
]); ]);
} }
} }
@@ -416,7 +416,7 @@ function microsoft_fetch_events_range(int $userId, string $startDate, string $en
]; ];
} }
fociz_cache_set($cacheKey, $events, 60); fociz_cache_set($cacheKey, $events, 300); // 5 minuten cache
return $events; return $events;
} }
@@ -1567,7 +1567,7 @@ function microsoft_fetch_folder_messages(int $userId, string $folderId = 'inbox'
'Accept' => 'application/json', 'Accept' => 'application/json',
'Prefer' => 'outlook.body-content-type="text"', 'Prefer' => 'outlook.body-content-type="text"',
], ],
'timeout' => 15, 'timeout' => 6,
]); ]);
if ($res['status'] !== 200 || !isset($res['json']['value'])) { if ($res['status'] !== 200 || !isset($res['json']['value'])) {
+50 -43
View File
@@ -127,12 +127,11 @@ if ($isOutlookConnected) {
// Synchroniseer / reconcilieer Fociz taken direct met actuele Outlook data // Synchroniseer / reconcilieer Fociz taken direct met actuele Outlook data
// Alleen uitvoeren indien events gewijzigd zijn of bij expliciete refresh om overtollige DB load te voorkomen // Alleen uitvoeren indien events gewijzigd zijn of bij expliciete refresh om overtollige DB load te voorkomen
$eventsHash = md5(json_encode($weekOutlookEvents)); $eventsHash = md5(json_encode($weekOutlookEvents));
$lastReconciledHash = $_SESSION["last_reconciled_{$userId}"] ?? ''; $cacheReconcileKey = "last_reconciled_{$userId}";
$lastReconciledHash = fociz_cache_get($cacheReconcileKey, '');
if ($eventsHash !== $lastReconciledHash || $forceRefresh) { if ($eventsHash !== $lastReconciledHash || $forceRefresh) {
microsoft_reconcile_tasks_with_outlook($userId, $weekStart, $weekEnd, $weekOutlookEvents); microsoft_reconcile_tasks_with_outlook($userId, $weekStart, $weekEnd, $weekOutlookEvents);
if (session_status() === PHP_SESSION_ACTIVE) { fociz_cache_set($cacheReconcileKey, $eventsHash, 86400);
$_SESSION["last_reconciled_{$userId}"] = $eventsHash;
}
} }
} catch (Throwable $e) { } catch (Throwable $e) {
error_log("Outlook synchronisatiefout in planner: " . $e->getMessage()); error_log("Outlook synchronisatiefout in planner: " . $e->getMessage());
@@ -304,45 +303,7 @@ for ($i = 0; $i < 7; $i++) {
]; ];
} }
// 5. Haal Jira dashboard data op voor de snelle import widget en wachtrijfiltering // 5. Widget Catalogus & Raster Indeling (Fociz v2.0)
$jiraAssignedIssues = [];
$jiraTeamIssues = [];
$jiraAllIssues = [];
$jiraQueueFilters = [];
$isJiraLive = false;
try {
$jiraDashboardData = jira_get_dashboard_data($userId, $forceRefresh);
$jiraAssignedIssues = $jiraDashboardData['assigned_issues'] ?? [];
$jiraTeamIssues = $jiraDashboardData['team_issues'] ?? [];
$jiraQueueFilters = $jiraDashboardData['queue_filters'] ?? [];
$isJiraLive = !empty($jiraDashboardData['success']) && empty($jiraDashboardData['is_demo']);
$seenJiraKeys = [];
foreach (array_merge($jiraAssignedIssues, $jiraTeamIssues) as $iss) {
if (!empty($iss['key']) && !isset($seenJiraKeys[$iss['key']])) {
$seenJiraKeys[$iss['key']] = true;
$jiraAllIssues[] = $iss;
}
}
} catch (Throwable $e) {
error_log("Fout bij ophalen Jira issues in planner: " . $e->getMessage());
}
$jiraIssues = $jiraAllIssues;
// 5b. Haal recente e-mails op voor de Outlook E-mail Widget
$recentEmails = [];
$unreadEmailCount = 0;
$mailNeedsReconsent = false;
try {
$mailData = microsoft_fetch_inbox_messages($userId, 5, false, $forceRefresh);
$recentEmails = $mailData['messages'] ?? [];
$unreadEmailCount = (int)($mailData['unread_count'] ?? 0);
$mailNeedsReconsent = !empty($mailData['needs_reconsent']);
} catch (Throwable $e) {
error_log("Fout bij ophalen recente e-mails in planner: " . $e->getMessage());
}
// 5c. Widget Catalogus & Raster Indeling (Fociz v2.0)
$availableWidgets = [ $availableWidgets = [
'daily_goal' => ['id' => 'daily_goal', 'title' => 'Dagfocus & Hoofddoel', 'category' => 'Planning', 'icon' => 'target', 'enabled' => true, 'col_span' => 12, 'order' => 1, 'desc' => 'Centraal focusdoel voor de dag met snelle tellers'], 'daily_goal' => ['id' => 'daily_goal', 'title' => 'Dagfocus & Hoofddoel', 'category' => 'Planning', 'icon' => 'target', 'enabled' => true, 'col_span' => 12, 'order' => 1, 'desc' => 'Centraal focusdoel voor de dag met snelle tellers'],
'timeline' => ['id' => 'timeline', 'title' => 'Tijdlijn & Dag/Weekoverzicht', 'category' => 'Agenda', 'icon' => 'calendar', 'enabled' => true, 'col_span' => 12, 'order' => 2, 'desc' => 'Gecombineerd weekoverzicht, dagselectie en uren-tijdlijn met time-blocking en AI'], 'timeline' => ['id' => 'timeline', 'title' => 'Tijdlijn & Dag/Weekoverzicht', 'category' => 'Agenda', 'icon' => 'calendar', 'enabled' => true, 'col_span' => 12, 'order' => 2, 'desc' => 'Gecombineerd weekoverzicht, dagselectie en uren-tijdlijn met time-blocking en AI'],
@@ -393,6 +354,52 @@ uasort($userWidgetLayout, function($a, $b) {
return strcmp($a['id'] ?? '', $b['id'] ?? ''); return strcmp($a['id'] ?? '', $b['id'] ?? '');
}); });
// 5b. Haal Jira dashboard data op voor de snelle import widget (indien ingeschakeld)
$jiraAssignedIssues = [];
$jiraTeamIssues = [];
$jiraAllIssues = [];
$jiraQueueFilters = [];
$isJiraLive = false;
$isJiraWidgetEnabled = !empty($userWidgetLayout['jira_queue']['enabled']);
if ($isJiraWidgetEnabled) {
try {
$jiraDashboardData = jira_get_dashboard_data($userId, $forceRefresh);
$jiraAssignedIssues = $jiraDashboardData['assigned_issues'] ?? [];
$jiraTeamIssues = $jiraDashboardData['team_issues'] ?? [];
$jiraQueueFilters = $jiraDashboardData['queue_filters'] ?? [];
$isJiraLive = !empty($jiraDashboardData['success']) && empty($jiraDashboardData['is_demo']);
$seenJiraKeys = [];
foreach (array_merge($jiraAssignedIssues, $jiraTeamIssues) as $iss) {
if (!empty($iss['key']) && !isset($seenJiraKeys[$iss['key']])) {
$seenJiraKeys[$iss['key']] = true;
$jiraAllIssues[] = $iss;
}
}
} catch (Throwable $e) {
error_log("Fout bij ophalen Jira issues in planner: " . $e->getMessage());
}
}
$jiraIssues = $jiraAllIssues;
// 5c. Haal recente e-mails op voor de Outlook E-mail Widget (indien ingeschakeld)
$recentEmails = [];
$unreadEmailCount = 0;
$mailNeedsReconsent = false;
$isMailWidgetEnabled = !empty($userWidgetLayout['outlook_mail']['enabled']);
if ($isMailWidgetEnabled) {
try {
$mailData = microsoft_fetch_inbox_messages($userId, 5, false, $forceRefresh);
$recentEmails = $mailData['messages'] ?? [];
$unreadEmailCount = (int)($mailData['unread_count'] ?? 0);
$mailNeedsReconsent = !empty($mailData['needs_reconsent']);
} catch (Throwable $e) {
error_log("Fout bij ophalen recente e-mails in planner: " . $e->getMessage());
}
}
// Bereken dagstatistieken // Bereken dagstatistieken
$completedTasksCount = 0; $completedTasksCount = 0;
$pendingTasksCount = 0; $pendingTasksCount = 0;