Compare commits

...

2 Commits

5 changed files with 146 additions and 89 deletions
+1 -1
View File
@@ -255,7 +255,7 @@
try { try {
const res = await fetch('/api/notifications.php?action=get_events', { cache: 'no-store' }); const res = await fetch('/api/notifications.php?action=get_events', { cache: 'no-store' });
if (res.status === 401) { if (res.status === 401) {
window.location.href = '/auth/login.php?expired=1'; console.warn('Notificatiesessie niet geauthenticeerd.');
return; return;
} }
if (res.ok) { if (res.ok) {
+3 -2
View File
@@ -112,9 +112,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$_SESSION['is_2fa_verified'] = true; $_SESSION['is_2fa_verified'] = true;
$_SESSION['auth_date'] = date('Y-m-d'); $_SESSION['auth_date'] = date('Y-m-d');
$_SESSION['session_expires_at'] = strtotime('today 23:59:59'); $_SESSION['session_expires_at'] = strtotime('today 23:59:59');
$_SESSION['last_activity'] = time();
// Zorg dat er geen persistente remember cookies worden gebruikt // Dagsessie-token uitgeven (strikt geldig tot 23:59:59 van vandaag)
clear_remember_cookie(); issue_day_session_token((int)$user['id']);
unset($_SESSION['pending_2fa_user_id'], $_SESSION['pending_2fa_user_email'], $_SESSION['pending_2fa_user_name']); unset($_SESSION['pending_2fa_user_id'], $_SESSION['pending_2fa_user_email'], $_SESSION['pending_2fa_user_name']);
+85 -43
View File
@@ -35,17 +35,15 @@ function is_https_request(): bool {
function start_secure_session(): void { function start_secure_session(): void {
if (session_status() === PHP_SESSION_NONE) { if (session_status() === PHP_SESSION_NONE) {
// Dagsessie: verloopt iedere nacht om 23:59:59 (blijft niet meerdere dagen staan) // Dagsessie: 24 uur (86400s) zodat server-garbage collectors de sessie niet tussentijds wissen
$secondsUntilMidnight = max(60, strtotime('today 23:59:59') - time()); ini_set('session.gc_maxlifetime', '86400');
ini_set('session.cookie_lifetime', '86400');
ini_set('session.gc_maxlifetime', (string)$secondsUntilMidnight);
ini_set('session.cookie_lifetime', (string)$secondsUntilMidnight);
ini_set('session.use_strict_mode', '1'); ini_set('session.use_strict_mode', '1');
ini_set('session.use_only_cookies', '1'); ini_set('session.use_only_cookies', '1');
$isSecure = is_https_request(); $isSecure = is_https_request();
session_set_cookie_params([ session_set_cookie_params([
'lifetime' => $secondsUntilMidnight, // Verloopt vannacht om 23:59:59 'lifetime' => 86400,
'path' => '/', 'path' => '/',
'domain' => '', 'domain' => '',
'secure' => $isSecure, 'secure' => $isSecure,
@@ -82,8 +80,8 @@ function ensure_remember_token_columns(PDO $db): void {
if (!$result || $result->rowCount() === 0) { if (!$result || $result->rowCount() === 0) {
$db->exec("ALTER TABLE `users` ADD COLUMN `remember_token` VARCHAR(64) NULL AFTER `backup_codes`, ADD COLUMN `remember_token_expires_at` DATETIME NULL AFTER `remember_token`"); $db->exec("ALTER TABLE `users` ADD COLUMN `remember_token` VARCHAR(64) NULL AFTER `backup_codes`, ADD COLUMN `remember_token_expires_at` DATETIME NULL AFTER `remember_token`");
} else { } else {
// Maak alle bestaande actieve tokens direct leeg zodat geen enkele oude persistente browser-sessie blijft bestaan // Schoon uitsluitend verlopen tokens van gisteren op, wis NOOIT actieve tokens van vandaag
$db->exec("UPDATE `users` SET `remember_token` = NULL, `remember_token_expires_at` = NULL WHERE `remember_token` IS NOT NULL"); $db->exec("UPDATE `users` SET `remember_token` = NULL, `remember_token_expires_at` = NULL WHERE `remember_token_expires_at` IS NOT NULL AND `remember_token_expires_at` < NOW()");
} }
} catch (Throwable $e) { } catch (Throwable $e) {
// Stil negeren indien al aanwezig of tijdens migraties // Stil negeren indien al aanwezig of tijdens migraties
@@ -127,42 +125,70 @@ function ensure_theme_column(PDO $db): void {
} }
/** /**
* Verouderde functie voor persistent login. * Genereert een cryptografisch veilig dagsessie-token dat strikt geldig is tot 23:59:59 van vandaag.
* Bewust geneutraliseerd: cookies worden direct gewist en geen tokens meer aangemaakt, * Voorkomt dat gebruikers tussentijds worden uitgelogd bij idle timeouts of browserherstart.
* zodat gebruikers iedere dag om 23:59:59 worden uitgelogd en daadwerkelijk opnieuw moeten inloggen.
*/ */
function issue_remember_token(int $userId): string { function issue_day_session_token(int $userId): string {
clear_remember_cookie(); clear_day_session_cookie();
try { try {
$token = bin2hex(random_bytes(32));
$expiresAt = date('Y-m-d 23:59:59');
$secondsUntilMidnight = max(60, strtotime('today 23:59:59') - time());
$db = get_db_connection(); $db = get_db_connection();
ensure_remember_token_columns($db); ensure_remember_token_columns($db);
$stmt = $db->prepare("UPDATE users SET remember_token = NULL, remember_token_expires_at = NULL WHERE id = ?"); $stmt = $db->prepare("UPDATE users SET remember_token = ?, remember_token_expires_at = ? WHERE id = ?");
$stmt->execute([$userId]); $stmt->execute([$token, $expiresAt, $userId]);
} catch (Throwable $e) {}
$isSecure = is_https_request();
setcookie('fociz_day_session', $token, [
'expires' => time() + $secondsUntilMidnight,
'path' => '/',
'secure' => $isSecure,
'httponly' => true,
'samesite' => 'Lax'
]);
return $token;
} catch (Throwable $e) {
error_log("Fout bij aanmaken dagsessie token: " . $e->getMessage());
return ''; return '';
} }
}
/** /**
* Verwijdert de persistente remember cookie uit de browser * Backward compatibility helper
*/ */
function clear_remember_cookie(): void { function issue_remember_token(int $userId): string {
return issue_day_session_token($userId);
}
/**
* Verwijdert de dagsessie cookies uit de browser
*/
function clear_day_session_cookie(): void {
$isSecure = is_https_request(); $isSecure = is_https_request();
setcookie('fociz_remember', '', [ foreach (['fociz_day_session', 'fociz_remember'] as $cookieName) {
setcookie($cookieName, '', [
'expires' => time() - 86400, 'expires' => time() - 86400,
'path' => '/', 'path' => '/',
'secure' => $isSecure, 'secure' => $isSecure,
'httponly' => true, 'httponly' => true,
'samesite' => 'Lax' 'samesite' => 'Lax'
]); ]);
if (isset($_COOKIE['fociz_remember'])) { if (isset($_COOKIE[$cookieName])) {
unset($_COOKIE['fociz_remember']); unset($_COOKIE[$cookieName]);
} }
} }
}
function clear_remember_cookie(): void {
clear_day_session_cookie();
}
/** /**
* Haalt de huidige ingelogde gebruiker op uit de database. * Haalt de huidige ingelogde gebruiker op uit de database.
* Garandeert dat de sessie strikt om 23:59:59 gereset wordt en nooit meerdere dagen blijft staan. * Garandeert dat de sessie strikt tot 23:59:59 van vandaag actief blijft en morgen gegarandeerd verloopt.
* Bevat GEEN persistente cookies of auto-login bypass: de gebruiker moet iedere dag daadwerkelijk opnieuw inloggen.
*/ */
function get_logged_in_user(): ?array { function get_logged_in_user(): ?array {
start_secure_session(); start_secure_session();
@@ -170,44 +196,56 @@ function get_logged_in_user(): ?array {
$today = date('Y-m-d'); $today = date('Y-m-d');
$now = time(); $now = time();
// Verwijder altijd eventuele oude of achtergebleven fociz_remember cookies // 1. Controleer of de huidige sessie van een eerdere dag is (dagwissel om middernacht)
if (isset($_COOKIE['fociz_remember'])) {
clear_remember_cookie();
}
// Controleer of de sessie van een eerdere dag is of voorbij 23:59:59
$isExpired = false;
if (!empty($_SESSION['auth_date']) && $_SESSION['auth_date'] !== $today) { if (!empty($_SESSION['auth_date']) && $_SESSION['auth_date'] !== $today) {
$isExpired = true;
}
if (!empty($_SESSION['session_expires_at']) && $now > (int)$_SESSION['session_expires_at']) {
$isExpired = true;
}
if ($isExpired) {
$_SESSION = ['session_was_expired' => true]; $_SESSION = ['session_was_expired' => true];
clear_remember_cookie(); clear_day_session_cookie();
if (session_status() === PHP_SESSION_ACTIVE) { if (session_status() === PHP_SESSION_ACTIVE) {
@session_regenerate_id(true); @session_regenerate_id(true);
} }
return null; return null;
} }
// Controleer actieve PHP sessie (uitsluitend de sessie van vandaag is geldig)
if (!empty($_SESSION['user_id']) && !empty($_SESSION['is_2fa_verified'])) {
$db = get_db_connection(); $db = get_db_connection();
ensure_remember_token_columns($db); ensure_remember_token_columns($db);
// 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, created_at FROM users WHERE id = ?"); $stmt = $db->prepare("SELECT id, name, email, role, two_factor_enabled, created_at FROM users WHERE id = ?");
$stmt->execute([$_SESSION['user_id']]); $stmt->execute([$_SESSION['user_id']]);
$user = $stmt->fetch(); $user = $stmt->fetch();
if ($user) { if ($user) {
$_SESSION['auth_date'] = $today; $_SESSION['auth_date'] = $today;
$_SESSION['session_expires_at'] = strtotime('today 23:59:59'); $_SESSION['session_expires_at'] = strtotime('today 23:59:59');
$_SESSION['last_activity'] = $now;
return $user; return $user;
} }
// Gebruiker bestaat niet meer in database // Gebruiker bestaat niet meer
$_SESSION = []; $_SESSION = [];
clear_remember_cookie(); clear_day_session_cookie();
return null;
}
// 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, 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) {
// Herstel sessie voor vandaag
$_SESSION['user_id'] = $user['id'];
$_SESSION['user_name'] = $user['name'];
$_SESSION['user_email'] = $user['email'];
$_SESSION['user_role'] = $user['role'];
$_SESSION['is_2fa_verified'] = true;
$_SESSION['auth_date'] = $today;
$_SESSION['session_expires_at'] = strtotime('today 23:59:59');
$_SESSION['last_activity'] = $now;
return $user;
}
// Token is ongeldig of van gisteren
clear_day_session_cookie();
} }
return null; return null;
@@ -219,7 +257,11 @@ function get_logged_in_user(): ?array {
function require_auth(bool $json = false): array { function require_auth(bool $json = false): array {
start_secure_session(); start_secure_session();
$isAjax = $json || $requestUri = $_SERVER['REQUEST_URI'] ?? '';
$scriptName = $_SERVER['SCRIPT_NAME'] ?? '';
$isApiCall = str_contains($requestUri, '/api/') || str_contains($scriptName, '/api/');
$isAjax = $json || $isApiCall ||
(isset($_SERVER['HTTP_ACCEPT']) && str_contains($_SERVER['HTTP_ACCEPT'], 'application/json')) || (isset($_SERVER['HTTP_ACCEPT']) && str_contains($_SERVER['HTTP_ACCEPT'], 'application/json')) ||
(isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest'); (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest');
+9 -17
View File
@@ -61,33 +61,25 @@ $currentUserTheme = !empty($currentUser['id']) ? get_user_theme((int)$currentUse
<script> <script>
(function() { (function() {
try { try {
const sessionDate = "<?= date('Y-m-d') ?>"; const sessionExpiresAt = <?= strtotime('today 23:59:59') ?> * 1000;
const checkMidnight = function() { const checkExpiry = function() {
const now = new Date(); if (Date.now() > sessionExpiresAt + 1000) {
const midnight = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999);
const todayStr = now.getFullYear() + '-' + String(now.getMonth() + 1).padStart(2, '0') + '-' + String(now.getDate()).padStart(2, '0');
if (todayStr !== sessionDate || now.getTime() > midnight.getTime()) {
window.location.href = '/auth/login.php?expired=1'; window.location.href = '/auth/login.php?expired=1';
} }
}; };
const now = new Date(); const msUntilExpiry = sessionExpiresAt - Date.now();
const midnight = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999); if (msUntilExpiry > 0) {
const msUntilMidnight = midnight.getTime() - now.getTime(); setTimeout(checkExpiry, msUntilExpiry + 500);
if (msUntilMidnight > 0) {
setTimeout(function() {
window.location.href = '/auth/login.php?expired=1';
}, msUntilMidnight);
} }
// Controleer direct zodra de gebruiker de browser weer activeert (bij bijv. standby / slaapstand overnacht)
document.addEventListener('visibilitychange', function() { document.addEventListener('visibilitychange', function() {
if (document.visibilityState === 'visible') { if (document.visibilityState === 'visible') {
checkMidnight(); checkExpiry();
} }
}); });
window.addEventListener('focus', checkMidnight); window.addEventListener('focus', checkExpiry);
setInterval(checkMidnight, 30000); setInterval(checkExpiry, 60000);
} catch(e) {} } catch(e) {}
})(); })();
</script> </script>
+36 -14
View File
@@ -21,6 +21,13 @@ require_once __DIR__ . '/includes/integrations/jira.php';
$db = get_db_connection(); $db = get_db_connection();
$userId = (int)($currentUser['id'] ?? 0); $userId = (int)($currentUser['id'] ?? 0);
$today = date('Y-m-d'); $today = date('Y-m-d');
// Indien de datum van vandaag via de URL is meegegeven bij een normale pagina-aanroep, schoon de URL op naar zuiver 'planner.php'
if (!$isAjaxRequest && isset($_GET['date']) && $_GET['date'] === $today) {
header('Location: planner.php', true, 302);
exit;
}
$selectedDate = (!empty($_GET['date']) && preg_match('/^\d{4}-\d{2}-\d{2}$/', $_GET['date'])) ? $_GET['date'] : $today; $selectedDate = (!empty($_GET['date']) && preg_match('/^\d{4}-\d{2}-\d{2}$/', $_GET['date'])) ? $_GET['date'] : $today;
$isTodaySelected = ($selectedDate === $today); $isTodaySelected = ($selectedDate === $today);
$selectedTs = strtotime($selectedDate); $selectedTs = strtotime($selectedDate);
@@ -298,13 +305,16 @@ for ($h = $startHour; $h <= $endHour; $h++) {
$hours[] = sprintf('%02d:00', $h); $hours[] = sprintf('%02d:00', $h);
} }
?> ?>
<?php if (!$isAjaxRequest): ?>
<style> <style>
.line-clamp-1 { display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden; } .line-clamp-1 { display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden; }
.line-clamp-2 { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } .line-clamp-2 { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
</style> </style>
<div id="plannerContentArea" class="space-y-6 max-w-7xl mx-auto transition-opacity duration-200"> <div id="plannerContentArea" class="space-y-6 max-w-7xl mx-auto transition-opacity duration-200">
<?php if ($isAjaxRequest) ob_start(); ?> <?php else: ?>
<?php ob_start(); ?>
<?php endif; ?>
<!-- Top Banner: Datum, Hoofddoel & Integraties Status --> <!-- Top Banner: Datum, Hoofddoel & Integraties Status -->
<div class="fociz-card p-6 sm:p-7 relative overflow-hidden"> <div class="fociz-card p-6 sm:p-7 relative overflow-hidden">
<div class="flex flex-col lg:flex-row lg:items-center justify-between gap-4"> <div class="flex flex-col lg:flex-row lg:items-center justify-between gap-4">
@@ -392,16 +402,16 @@ for ($h = $startHour; $h <= $endHour; $h++) {
<div class="flex flex-col md:flex-row md:items-center justify-between gap-4 pb-4 mb-4 border-b border-slate-100 dark:border-slate-800/60"> <div class="flex flex-col md:flex-row md:items-center justify-between gap-4 pb-4 mb-4 border-b border-slate-100 dark:border-slate-800/60">
<!-- Navigatie controls --> <!-- Navigatie controls -->
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<a href="planner.php?date=<?= $prevWeekDate ?>" onclick="switchPlannerDate('<?= $prevWeekDate ?>', event)" class="flex items-center space-x-1.5 px-3.5 py-1.5 rounded-xl bg-slate-100 hover:bg-slate-200 dark:bg-slate-800/80 dark:hover:bg-slate-700/80 text-slate-700 dark:text-slate-300 text-xs font-semibold transition shadow-2xs" title="Vorige week (Maandag <?= date('j M', strtotime($prevWeekDate)) ?>)"> <a href="planner.php?date=<?= $prevWeekDate ?>" onclick="switchPlannerDate('<?= $prevWeekDate ?>', event); return false;" class="flex items-center space-x-1.5 px-3.5 py-1.5 rounded-xl bg-slate-100 hover:bg-slate-200 dark:bg-slate-800/80 dark:hover:bg-slate-700/80 text-slate-700 dark:text-slate-300 text-xs font-semibold transition shadow-2xs" title="Vorige week (Maandag <?= date('j M', strtotime($prevWeekDate)) ?>)">
<i data-lucide="chevron-left" class="w-4 h-4"></i> <i data-lucide="chevron-left" class="w-4 h-4"></i>
<span class="hidden sm:inline">Vorige week</span> <span class="hidden sm:inline">Vorige week</span>
</a> </a>
<a href="planner.php?date=<?= $today ?>" onclick="switchPlannerDate('<?= $today ?>', event)" class="px-3.5 py-1.5 rounded-xl <?= $isTodaySelected ? 'bg-slate-900 text-white dark:bg-white dark:text-slate-900 font-bold shadow-xs' : 'bg-slate-100 hover:bg-slate-200 dark:bg-slate-800/80 dark:hover:bg-slate-700/80 text-slate-700 dark:text-slate-300 font-semibold shadow-2xs' ?> text-xs transition" title="Spring direct naar vandaag"> <a href="planner.php" onclick="switchPlannerDate('<?= $today ?>', event); return false;" class="px-3.5 py-1.5 rounded-xl <?= $isTodaySelected ? 'bg-slate-900 text-white dark:bg-white dark:text-slate-900 font-bold shadow-xs' : 'bg-slate-100 hover:bg-slate-200 dark:bg-slate-800/80 dark:hover:bg-slate-700/80 text-slate-700 dark:text-slate-300 font-semibold shadow-2xs' ?> text-xs transition" title="Spring direct naar vandaag">
Vandaag Vandaag
</a> </a>
<a href="planner.php?date=<?= $nextWeekDate ?>" onclick="switchPlannerDate('<?= $nextWeekDate ?>', event)" class="flex items-center space-x-1.5 px-3.5 py-1.5 rounded-xl bg-slate-100 hover:bg-slate-200 dark:bg-slate-800/80 dark:hover:bg-slate-700/80 text-slate-700 dark:text-slate-300 text-xs font-semibold transition shadow-2xs" title="Volgende week (Maandag <?= date('j M', strtotime($nextWeekDate)) ?>)"> <a href="planner.php?date=<?= $nextWeekDate ?>" onclick="switchPlannerDate('<?= $nextWeekDate ?>', event); return false;" class="flex items-center space-x-1.5 px-3.5 py-1.5 rounded-xl bg-slate-100 hover:bg-slate-200 dark:bg-slate-800/80 dark:hover:bg-slate-700/80 text-slate-700 dark:text-slate-300 text-xs font-semibold transition shadow-2xs" title="Volgende week (Maandag <?= date('j M', strtotime($nextWeekDate)) ?>)">
<span class="hidden sm:inline">Volgende week</span> <span class="hidden sm:inline">Volgende week</span>
<i data-lucide="chevron-right" class="w-4 h-4"></i> <i data-lucide="chevron-right" class="w-4 h-4"></i>
</a> </a>
@@ -424,7 +434,7 @@ for ($h = $startHour; $h <= $endHour; $h++) {
type="date" type="date"
id="jumpDateInput" id="jumpDateInput"
value="<?= htmlspecialchars($selectedDate) ?>" value="<?= htmlspecialchars($selectedDate) ?>"
onchange="if(this.value) switchPlannerDate(this.value, event);" onchange="if(this.value) { switchPlannerDate(this.value, event); }"
class="bg-slate-50 dark:bg-slate-950 border border-slate-200 dark:border-slate-800 rounded-xl px-3 py-1.5 text-xs text-slate-800 dark:text-slate-200 focus:outline-none focus:ring-2 focus:ring-indigo-500/30 font-medium cursor-pointer shadow-2xs" class="bg-slate-50 dark:bg-slate-950 border border-slate-200 dark:border-slate-800 rounded-xl px-3 py-1.5 text-xs text-slate-800 dark:text-slate-200 focus:outline-none focus:ring-2 focus:ring-indigo-500/30 font-medium cursor-pointer shadow-2xs"
> >
</div> </div>
@@ -434,8 +444,8 @@ for ($h = $startHour; $h <= $endHour; $h++) {
<div class="grid grid-cols-2 sm:grid-cols-4 md:grid-cols-7 gap-3"> <div class="grid grid-cols-2 sm:grid-cols-4 md:grid-cols-7 gap-3">
<?php foreach ($weekDays as $wd): ?> <?php foreach ($weekDays as $wd): ?>
<a <a
href="planner.php?date=<?= $wd['date'] ?>" href="<?= ($wd['date'] === $today) ? 'planner.php' : ('planner.php?date=' . $wd['date']) ?>"
onclick="switchPlannerDate('<?= $wd['date'] ?>', event)" onclick="switchPlannerDate('<?= $wd['date'] ?>', event); return false;"
class="fociz-card-interactive group relative flex flex-col p-3.5 rounded-2xl border transition-all text-left <?= $wd['is_selected'] class="fociz-card-interactive group relative flex flex-col p-3.5 rounded-2xl border transition-all text-left <?= $wd['is_selected']
? 'bg-gradient-to-b from-indigo-500/15 via-indigo-500/5 to-transparent dark:from-indigo-500/25 dark:via-indigo-500/10 dark:to-transparent border-indigo-500 ring-2 ring-indigo-500/40 shadow-sm' ? 'bg-gradient-to-b from-indigo-500/15 via-indigo-500/5 to-transparent dark:from-indigo-500/25 dark:via-indigo-500/10 dark:to-transparent border-indigo-500 ring-2 ring-indigo-500/40 shadow-sm'
: 'bg-slate-50/70 dark:bg-[#0c0f17]/50 border-slate-200/80 dark:border-slate-800/80 hover:border-indigo-300 dark:hover:border-indigo-600/50 hover:bg-white dark:hover:bg-[#111624] shadow-2xs' ?>" : 'bg-slate-50/70 dark:bg-[#0c0f17]/50 border-slate-200/80 dark:border-slate-800/80 hover:border-indigo-300 dark:hover:border-indigo-600/50 hover:bg-white dark:hover:bg-[#111624] shadow-2xs' ?>"
@@ -931,6 +941,9 @@ for ($h = $startHour; $h <= $endHour; $h++) {
<?php <?php
if ($isAjaxRequest) { if ($isAjaxRequest) {
$contentHtml = ob_get_clean(); $contentHtml = ob_get_clean();
while (ob_get_level() > 0) {
ob_end_clean();
}
header('Content-Type: application/json; charset=utf-8'); header('Content-Type: application/json; charset=utf-8');
echo json_encode([ echo json_encode([
'success' => true, 'success' => true,
@@ -950,7 +963,9 @@ if ($isAjaxRequest) {
exit; exit;
} }
?> ?>
<?php if (!$isAjaxRequest): ?>
</div> </div>
<?php endif; ?>
<!-- Modal: Taak & Tijdblok Bewerken (Vrij invullen van tijden & duur) --> <!-- Modal: Taak & Tijdblok Bewerken (Vrij invullen van tijden & duur) -->
<div id="editTaskModal" class="fixed inset-0 z-50 bg-slate-950/70 backdrop-blur-sm hidden items-center justify-center p-4"> <div id="editTaskModal" class="fixed inset-0 z-50 bg-slate-950/70 backdrop-blur-sm hidden items-center justify-center p-4">
@@ -1229,7 +1244,8 @@ async function switchPlannerDate(newDate, event = null, pushState = true) {
// Werk URL bij in adresbalk zonder paginaherlaad // Werk URL bij in adresbalk zonder paginaherlaad
if (pushState && window.history && window.history.pushState) { if (pushState && window.history && window.history.pushState) {
const newUrl = `planner.php?date=${encodeURIComponent(newDate)}`; const todayStr = '<?= $today ?>';
const newUrl = (newDate === todayStr) ? 'planner.php' : `planner.php?date=${encodeURIComponent(newDate)}`;
window.history.pushState({ date: newDate }, data.title || '', newUrl); window.history.pushState({ date: newDate }, data.title || '', newUrl);
} }
@@ -1246,8 +1262,6 @@ async function switchPlannerDate(newDate, event = null, pushState = true) {
} catch (err) { } catch (err) {
console.error('Fout bij wisselen van datum via AJAX:', err); console.error('Fout bij wisselen van datum via AJAX:', err);
// Veilige fallback bij netwerkonderbreking
window.location.href = 'planner.php?date=' + encodeURIComponent(newDate);
} finally { } finally {
isDateSwitching = false; isDateSwitching = false;
container.classList.remove('opacity-60', 'pointer-events-none'); container.classList.remove('opacity-60', 'pointer-events-none');
@@ -1257,12 +1271,20 @@ async function switchPlannerDate(newDate, event = null, pushState = true) {
// Browser Terug/Vooruit knoppen opvangen (popstate) // Browser Terug/Vooruit knoppen opvangen (popstate)
window.addEventListener('popstate', (event) => { window.addEventListener('popstate', (event) => {
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
const dateFromUrl = urlParams.get('date'); const dateFromUrl = urlParams.get('date') || '<?= $today ?>';
if (dateFromUrl && dateFromUrl !== activePlannerDate) { if (dateFromUrl !== activePlannerDate) {
switchPlannerDate(dateFromUrl, null, false); switchPlannerDate(dateFromUrl, null, false);
} }
}); });
// Schoon direct de browser URL op indien deze ?date=VANDAAG bevat
if (window.history && window.history.replaceState) {
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('date') === '<?= $today ?>') {
window.history.replaceState({}, document.title, 'planner.php');
}
}
// --- Dagdoel & Taken AJAX --- // --- Dagdoel & Taken AJAX ---
async function saveGoal() { async function saveGoal() {
@@ -1694,8 +1716,8 @@ async function syncTaskToOutlook(taskId, btn) {
btn.innerHTML = '<i data-lucide="check" class="w-3.5 h-3.5 text-teal-500"></i>'; btn.innerHTML = '<i data-lucide="check" class="w-3.5 h-3.5 text-teal-500"></i>';
if (typeof lucide !== 'undefined') lucide.createIcons(); if (typeof lucide !== 'undefined') lucide.createIcons();
setTimeout(() => { setTimeout(() => {
location.reload(); switchPlannerDate(activePlannerDate, null, false);
}, 600); }, 300);
} else { } else {
alert(data.error || 'Kon taak niet synchroniseren naar Outlook'); alert(data.error || 'Kon taak niet synchroniseren naar Outlook');
btn.innerHTML = originalHtml; btn.innerHTML = originalHtml;