Compare commits
2 Commits
2062fd065a
...
bcf2323124
| Author | SHA1 | Date | |
|---|---|---|---|
| bcf2323124 | |||
| 963342f858 |
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user