Fix HTTP 500 on live after login, unify database config in config.php, sanitize env example, and harden pentest security
This commit is contained in:
-13
@@ -8,16 +8,3 @@ Thumbs.db
|
|||||||
*.log
|
*.log
|
||||||
.vscode/
|
.vscode/
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
# Environment
|
|
||||||
.env
|
|
||||||
.env*.local
|
|
||||||
|
|
||||||
# Systeem & Editor
|
|
||||||
.DS_Store
|
|
||||||
Thumbs.db
|
|
||||||
*.log
|
|
||||||
.vscode/
|
|
||||||
.idea/
|
|
||||||
config/.env.example
|
|
||||||
config/config.php
|
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# =========================================================================
|
||||||
|
# Fociz Pentest & Security Hardening (.htaccess)
|
||||||
|
# =========================================================================
|
||||||
|
|
||||||
|
# 1. Schakel directory listing uit
|
||||||
|
Options -Indexes
|
||||||
|
|
||||||
|
# 2. Blokkeer directe toegang tot verborgen systeembestanden (.env, .git, etc.)
|
||||||
|
<FilesMatch "^\.">
|
||||||
|
<IfModule authz_core_module>
|
||||||
|
Require all denied
|
||||||
|
</IfModule>
|
||||||
|
<IfModule !authz_core_module>
|
||||||
|
Order deny,allow
|
||||||
|
Deny from all
|
||||||
|
</IfModule>
|
||||||
|
</FilesMatch>
|
||||||
|
|
||||||
|
# 3. Blokkeer directe toegang tot gevoelige bestandsformaten
|
||||||
|
<FilesMatch "\.(sql|log|example|md|json|lock|bak|conf)$">
|
||||||
|
<IfModule authz_core_module>
|
||||||
|
Require all denied
|
||||||
|
</IfModule>
|
||||||
|
<IfModule !authz_core_module>
|
||||||
|
Order deny,allow
|
||||||
|
Deny from all
|
||||||
|
</IfModule>
|
||||||
|
</FilesMatch>
|
||||||
|
|
||||||
|
# 4. Beveiligingsheaders
|
||||||
|
<IfModule mod_headers.c>
|
||||||
|
Header always set X-Content-Type-Options "nosniff"
|
||||||
|
Header always set X-Frame-Options "SAMEORIGIN"
|
||||||
|
Header always set X-XSS-Protection "1; mode=block"
|
||||||
|
Header always set Referrer-Policy "strict-origin-when-cross-origin"
|
||||||
|
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
|
||||||
|
Header unset X-Powered-By
|
||||||
|
</IfModule>
|
||||||
@@ -152,23 +152,49 @@ $tenantForConsent = !empty($appConfig['tenant_id']) ? $appConfig['tenant_id'] :
|
|||||||
$adminConsentUrl = "https://login.microsoftonline.com/" . urlencode($tenantForConsent) . "/adminconsent?client_id=" . urlencode($appConfig['client_id']) . "&redirect_uri=" . urlencode($redirectUri);
|
$adminConsentUrl = "https://login.microsoftonline.com/" . urlencode($tenantForConsent) . "/adminconsent?client_id=" . urlencode($appConfig['client_id']) . "&redirect_uri=" . urlencode($redirectUri);
|
||||||
|
|
||||||
// Statistieken ophalen voor Systeem & Team Monitor
|
// Statistieken ophalen voor Systeem & Team Monitor
|
||||||
$totalUsers = (int)$db->query("SELECT COUNT(*) FROM users")->fetchColumn();
|
$totalUsers = 0;
|
||||||
$totalAdmins = (int)$db->query("SELECT COUNT(*) FROM users WHERE role = 'ADMIN'")->fetchColumn();
|
$totalAdmins = 0;
|
||||||
$totalMembers = $totalUsers - $totalAdmins;
|
$totalMembers = 0;
|
||||||
$pendingInvites = (int)$db->query("SELECT COUNT(*) FROM invites WHERE used_at IS NULL AND expires_at > NOW()")->fetchColumn();
|
$pendingInvites = 0;
|
||||||
|
$outlookConnectedCount = 0;
|
||||||
|
$jiraConnectedCount = 0;
|
||||||
|
$recentUsers = [];
|
||||||
|
|
||||||
$outlookConnectedCount = (int)$db->query("SELECT COUNT(*) FROM integrations WHERE provider = 'OUTLOOK' AND access_token IS NOT NULL")->fetchColumn();
|
try {
|
||||||
$jiraConnectedCount = (int)$db->query("SELECT COUNT(*) FROM integrations WHERE provider = 'JIRA' AND settings LIKE '%jira_api_token%' AND settings NOT LIKE '%\"jira_api_token\":\"\"%'")->fetchColumn();
|
$totalUsers = (int)$db->query("SELECT COUNT(*) FROM users")->fetchColumn();
|
||||||
|
$totalAdmins = (int)$db->query("SELECT COUNT(*) FROM users WHERE role = 'ADMIN'")->fetchColumn();
|
||||||
|
$totalMembers = $totalUsers - $totalAdmins;
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
error_log("Fout bij ophalen user stats: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
// Haal recente gebruikers op voor snelle inspectie
|
try {
|
||||||
$recentUsers = $db->query("
|
$pendingInvites = (int)$db->query("SELECT COUNT(*) FROM invites WHERE used_at IS NULL AND expires_at > NOW()")->fetchColumn();
|
||||||
SELECT u.id, u.name, u.email, u.role, u.created_at,
|
} catch (Throwable $e) {
|
||||||
(SELECT COUNT(*) FROM integrations i WHERE i.user_id = u.id AND i.provider = 'OUTLOOK' AND i.access_token IS NOT NULL) as has_outlook,
|
error_log("Fout bij ophalen invites stats: " . $e->getMessage());
|
||||||
(SELECT COUNT(*) FROM integrations i WHERE i.user_id = u.id AND i.provider = 'JIRA' AND i.settings LIKE '%jira_api_token%' AND i.settings NOT LIKE '%\"jira_api_token\":\"\"%') as has_jira
|
ensure_tables_exist($db);
|
||||||
FROM users u
|
}
|
||||||
ORDER BY u.created_at DESC
|
|
||||||
LIMIT 6
|
try {
|
||||||
")->fetchAll();
|
$outlookConnectedCount = (int)$db->query("SELECT COUNT(*) FROM integrations WHERE provider = 'OUTLOOK' AND access_token IS NOT NULL")->fetchColumn();
|
||||||
|
$jiraConnectedCount = (int)$db->query("SELECT COUNT(*) FROM integrations WHERE provider = 'JIRA' AND settings LIKE '%jira_api_token%' AND settings NOT LIKE '%\"jira_api_token\":\"\"%'")->fetchColumn();
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
error_log("Fout bij ophalen integrations stats: " . $e->getMessage());
|
||||||
|
ensure_tables_exist($db);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$recentUsers = $db->query("
|
||||||
|
SELECT u.id, u.name, u.email, u.role, u.created_at,
|
||||||
|
(SELECT COUNT(*) FROM integrations i WHERE i.user_id = u.id AND i.provider = 'OUTLOOK' AND i.access_token IS NOT NULL) as has_outlook,
|
||||||
|
(SELECT COUNT(*) FROM integrations i WHERE i.user_id = u.id AND i.provider = 'JIRA' AND i.settings LIKE '%jira_api_token%' AND i.settings NOT LIKE '%\"jira_api_token\":\"\"%') as has_jira
|
||||||
|
FROM users u
|
||||||
|
ORDER BY u.created_at DESC
|
||||||
|
LIMIT 6
|
||||||
|
")->fetchAll() ?: [];
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
error_log("Fout bij ophalen recente gebruikers: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
$pageTitle = 'Systeembeheer & Integraties — Fociz';
|
$pageTitle = 'Systeembeheer & Integraties — Fociz';
|
||||||
require_once __DIR__ . '/includes/header.php';
|
require_once __DIR__ . '/includes/header.php';
|
||||||
|
|||||||
+14
-8
@@ -29,14 +29,20 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Haal alle sessies op
|
// Haal alle sessies op
|
||||||
$stmtSessions = $db->query("
|
$sessions = [];
|
||||||
SELECT s.*, u.name as creator_name,
|
try {
|
||||||
(SELECT COUNT(*) FROM brainstorm_cards WHERE session_id = s.id) as cards_count
|
$stmtSessions = $db->query("
|
||||||
FROM brainstorm_sessions s
|
SELECT s.*, u.name as creator_name,
|
||||||
JOIN users u ON s.created_by_id = u.id
|
(SELECT COUNT(*) FROM brainstorm_cards WHERE session_id = s.id) as cards_count
|
||||||
ORDER BY s.created_at DESC
|
FROM brainstorm_sessions s
|
||||||
");
|
JOIN users u ON s.created_by_id = u.id
|
||||||
$sessions = $stmtSessions->fetchAll();
|
ORDER BY s.created_at DESC
|
||||||
|
");
|
||||||
|
$sessions = $stmtSessions->fetchAll() ?: [];
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
error_log("Fout bij ophalen brainstorm sessies: " . $e->getMessage());
|
||||||
|
ensure_tables_exist($db);
|
||||||
|
}
|
||||||
require_once __DIR__ . '/includes/header.php';
|
require_once __DIR__ . '/includes/header.php';
|
||||||
?>
|
?>
|
||||||
|
|
||||||
|
|||||||
+16
-14
@@ -1,26 +1,28 @@
|
|||||||
# MariaDB Verbinding
|
# =========================================================================
|
||||||
DB_HOST="192.168.2.33"
|
# FOCIZ OMGEVINGSCONFIGURATIE VOORBEELD (TEMPLATE)
|
||||||
|
# =========================================================================
|
||||||
|
# Alle actieve database- en applicatie-instellingen staan centraal in:
|
||||||
|
# config/config.php
|
||||||
|
#
|
||||||
|
# Indien gewenst kan een lokaal .env bestand in de hoofdmap worden geplaatst
|
||||||
|
# om specifieke variabelen lokaal te overschrijven.
|
||||||
|
|
||||||
|
DB_HOST="127.0.0.1"
|
||||||
DB_PORT="3306"
|
DB_PORT="3306"
|
||||||
DB_NAME="fociz"
|
DB_NAME="fociz"
|
||||||
DB_USER="focizlive_user"
|
DB_USER="fociz_user"
|
||||||
DB_PASS="F0c1z_Live_Secure_2026!"
|
DB_PASS=""
|
||||||
|
|
||||||
# Omgeving: 'development' (knippert rood) of 'live' (knippert groen)
|
|
||||||
APP_ENV="live"
|
APP_ENV="live"
|
||||||
|
APP_URL="https://www.fociz.nl"
|
||||||
|
APP_SECRET=""
|
||||||
|
|
||||||
# Applicatie URL (voor uitnodigingslinks en OAuth redirects: https://dev.fociz.nl of https://fociz.nl)
|
|
||||||
APP_URL="https://live.fociz.nl"
|
|
||||||
|
|
||||||
# Applicatie Geheime Sleutel (willekeurige tekenreeks)
|
|
||||||
APP_SECRET="fociz-maria-db-super-secret-key-998877"
|
|
||||||
|
|
||||||
# Microsoft 365 / Outlook (Optionele globale defaults; kan ook via web UI worden ingesteld)
|
|
||||||
MICROSOFT_CLIENT_ID=""
|
MICROSOFT_CLIENT_ID=""
|
||||||
MICROSOFT_CLIENT_SECRET=""
|
MICROSOFT_CLIENT_SECRET=""
|
||||||
MICROSOFT_TENANT_ID="common"
|
MICROSOFT_TENANT_ID=""
|
||||||
|
|
||||||
# Atlassian Jira Cloud (Optionele globale defaults; kan ook via web UI worden ingesteld)
|
|
||||||
JIRA_DOMAIN=""
|
JIRA_DOMAIN=""
|
||||||
JIRA_EMAIL=""
|
JIRA_EMAIL=""
|
||||||
JIRA_API_TOKEN=""
|
JIRA_API_TOKEN=""
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# Beveiliging van de interne configuratiemap
|
||||||
|
<IfModule authz_core_module>
|
||||||
|
Require all denied
|
||||||
|
</IfModule>
|
||||||
|
<IfModule !authz_core_module>
|
||||||
|
Order deny,allow
|
||||||
|
Deny from all
|
||||||
|
</IfModule>
|
||||||
+85
-46
@@ -1,44 +1,47 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* Fociz Applicatie- en Databaseconfiguratie
|
* Fociz Centrale Applicatie- en Databaseconfiguratie
|
||||||
* Laadt automatisch .env (of .env.example indien .env niet bestaat)
|
* Alle configuratie staat overzichtelijk en veilig in dit ene bestand.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Blokkeer directe HTTP toegang tot dit configuratiebestand (Pen Test Beveiliging)
|
||||||
|
if (basename($_SERVER['SCRIPT_FILENAME'] ?? '') === 'config.php') {
|
||||||
|
http_response_code(403);
|
||||||
|
exit('Toegang geweigerd.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stuur direct strenge beveiligingsheaders mee op elk verzoek (Pen Test Beveiliging)
|
||||||
|
if (!headers_sent()) {
|
||||||
|
header('X-Content-Type-Options: nosniff');
|
||||||
|
header('X-Frame-Options: SAMEORIGIN');
|
||||||
|
header('X-XSS-Protection: 1; mode=block');
|
||||||
|
header('Referrer-Policy: strict-origin-when-cross-origin');
|
||||||
|
header('Permissions-Policy: camera=(), microphone=(), geolocation=()');
|
||||||
|
header_remove('X-Powered-By');
|
||||||
|
}
|
||||||
|
@ini_set('expose_php', 'off');
|
||||||
|
|
||||||
|
// 1. Optionele .env inlezing uit de hoofdmap (uitsluitend .env, GEEN .env.example fallback)
|
||||||
$envVars = [];
|
$envVars = [];
|
||||||
|
$envFile = __DIR__ . '/../.env';
|
||||||
// 1. Zoek naar .env of val terug op config/.env.example
|
if (file_exists($envFile)) {
|
||||||
$envCandidates = [
|
$lines = file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
||||||
__DIR__ . '/../.env',
|
if ($lines !== false) {
|
||||||
__DIR__ . '/.env.example',
|
foreach ($lines as $line) {
|
||||||
__DIR__ . '/../.env.example',
|
$line = trim($line);
|
||||||
];
|
if ($line === '' || str_starts_with($line, '#') || str_starts_with($line, '//')) {
|
||||||
|
continue;
|
||||||
foreach ($envCandidates as $envFile) {
|
}
|
||||||
if (file_exists($envFile)) {
|
if (strpos($line, '=') !== false) {
|
||||||
$lines = file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
list($name, $value) = explode('=', $line, 2);
|
||||||
if ($lines !== false) {
|
$name = trim($name);
|
||||||
foreach ($lines as $line) {
|
$value = trim($value, " \t\n\r\0\x0B\"'");
|
||||||
$line = trim($line);
|
$envVars[$name] = $value;
|
||||||
// Negeer lege regels en commentaarregels (# of //)
|
$_ENV[$name] = $value;
|
||||||
if ($line === '' || str_starts_with($line, '#') || str_starts_with($line, '//')) {
|
$_SERVER[$name] = $value;
|
||||||
continue;
|
@putenv("{$name}={$value}");
|
||||||
}
|
|
||||||
if (strpos($line, '=') !== false) {
|
|
||||||
list($name, $value) = explode('=', $line, 2);
|
|
||||||
$name = trim($name);
|
|
||||||
$value = trim($value);
|
|
||||||
// Verwijder omlijstende aanhalingstekens (" of ')
|
|
||||||
$value = trim($value, " \t\n\r\0\x0B\"'");
|
|
||||||
|
|
||||||
// Sla op in array en zet in globale PHP variabelen
|
|
||||||
$envVars[$name] = $value;
|
|
||||||
$_ENV[$name] = $value;
|
|
||||||
$_SERVER[$name] = $value;
|
|
||||||
@putenv("{$name}={$value}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break; // Stop zodra we het eerste geldige bestand hebben ingeladen
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +68,9 @@ if (!function_exists('fociz_env')) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MariaDB Verbindingsgegevens (uit .env of .env.example)
|
// =========================================================================
|
||||||
|
// 2. CENTRALE DATABASE CONFIGURATIE (DE ENIGE BRON VAN WAARHEID)
|
||||||
|
// =========================================================================
|
||||||
define('DB_HOST', fociz_env('DB_HOST', '192.168.2.33'));
|
define('DB_HOST', fociz_env('DB_HOST', '192.168.2.33'));
|
||||||
define('DB_PORT', fociz_env('DB_PORT', '3306'));
|
define('DB_PORT', fociz_env('DB_PORT', '3306'));
|
||||||
define('DB_NAME', fociz_env('DB_NAME', 'fociz'));
|
define('DB_NAME', fociz_env('DB_NAME', 'fociz'));
|
||||||
@@ -73,15 +78,22 @@ define('DB_USER', fociz_env('DB_USER', 'focizlive_user'));
|
|||||||
define('DB_PASS', fociz_env('DB_PASS', 'F0c1z_Live_Secure_2026!'));
|
define('DB_PASS', fociz_env('DB_PASS', 'F0c1z_Live_Secure_2026!'));
|
||||||
define('DB_CHARSET', 'utf8mb4');
|
define('DB_CHARSET', 'utf8mb4');
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// 3. APPLICATIE INSTELLINGEN & OMGEVING
|
||||||
|
// =========================================================================
|
||||||
|
define('APP_NAME', 'Fociz');
|
||||||
|
define('APP_ENV', fociz_env('APP_ENV', 'live')); // 'live' of 'development'
|
||||||
|
define('APP_SECRET', fociz_env('APP_SECRET', 'fociz-maria-db-super-secret-key-998877'));
|
||||||
|
|
||||||
// Bepaal de dynamische of geconfigureerde applicatie URL
|
// Bepaal de dynamische of geconfigureerde applicatie URL
|
||||||
if (!function_exists('fociz_detect_app_url')) {
|
if (!function_exists('fociz_detect_app_url')) {
|
||||||
function fociz_detect_app_url(): string {
|
function fociz_detect_app_url(): string {
|
||||||
$envUrl = fociz_env('APP_URL');
|
$envUrl = fociz_env('APP_URL');
|
||||||
if (!empty($envUrl) && !str_contains($envUrl, 'uw-domein.nl') && !str_contains($envUrl, 'www.fociz.nl')) {
|
if (!empty($envUrl) && !str_contains($envUrl, 'uw-domein.nl')) {
|
||||||
return rtrim($envUrl, '/');
|
return rtrim($envUrl, '/');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dynamische detectie op basis van het huidige domein (werkt direct op dev.fociz.nl én fociz.nl)
|
// Dynamische detectie op basis van het huidige domein (werkt direct op www.fociz.nl én dev.fociz.nl)
|
||||||
if (!empty($_SERVER['HTTP_HOST'])) {
|
if (!empty($_SERVER['HTTP_HOST'])) {
|
||||||
$isHttps = (
|
$isHttps = (
|
||||||
(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ||
|
(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ||
|
||||||
@@ -93,22 +105,49 @@ if (!function_exists('fociz_detect_app_url')) {
|
|||||||
return $scheme . $_SERVER['HTTP_HOST'];
|
return $scheme . $_SERVER['HTTP_HOST'];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Standaard fallback per omgeving
|
return (defined('APP_ENV') && APP_ENV === 'live') ? 'https://www.fociz.nl' : 'https://dev.fociz.nl';
|
||||||
return (defined('APP_ENV') && APP_ENV === 'live') ? 'https://fociz.nl' : 'https://dev.fociz.nl';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Applicatie instellingen
|
|
||||||
define('APP_NAME', 'Fociz');
|
|
||||||
define('APP_ENV', fociz_env('APP_ENV', 'live')); // 'development' of 'live'
|
|
||||||
define('APP_URL', fociz_detect_app_url());
|
define('APP_URL', fociz_detect_app_url());
|
||||||
define('APP_SECRET', fociz_env('APP_SECRET', 'fociz-maria-db-super-secret-key-998877'));
|
|
||||||
|
|
||||||
// Tijdzone
|
// Tijdzone
|
||||||
date_default_timezone_set('Europe/Amsterdam');
|
date_default_timezone_set('Europe/Amsterdam');
|
||||||
|
|
||||||
// Foutrapportage: in development tonen we fouten direct op het scherm
|
// =========================================================================
|
||||||
if (APP_ENV !== 'live') {
|
// 4. VEILIGE FOUTAFHANDELING & PEN TEST BESCHERMING
|
||||||
|
// =========================================================================
|
||||||
|
if (APP_ENV === 'live') {
|
||||||
|
ini_set('display_errors', '0');
|
||||||
|
ini_set('display_startup_errors', '0');
|
||||||
|
error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT);
|
||||||
|
|
||||||
|
// Registreer een nette noodfoutpagina voor onverhoopte fatale PHP fouten
|
||||||
|
register_shutdown_function(function() {
|
||||||
|
$error = error_get_last();
|
||||||
|
if ($error && in_array($error['type'], [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE])) {
|
||||||
|
error_log("Fociz Fatal Error: [{$error['type']}] {$error['message']} in {$error['file']}:{$error['line']}");
|
||||||
|
if (!headers_sent()) {
|
||||||
|
http_response_code(500);
|
||||||
|
}
|
||||||
|
if (isset($_SESSION['user_role']) && $_SESSION['user_role'] === 'ADMIN') {
|
||||||
|
echo "<div style='font-family: monospace; background: #0c0f17; color: #ef4444; padding: 25px; margin: 20px; border-radius: 12px; border: 1px solid #dc2626;'>
|
||||||
|
<h3>⚠️ Fociz Systeemfout (Alleen zichtbaar voor Beheerder)</h3>
|
||||||
|
<p><strong>Fout:</strong> " . htmlspecialchars($error['message']) . "</p>
|
||||||
|
<p><strong>Bestand:</strong> " . htmlspecialchars($error['file']) . " op regel " . $error['line'] . "</p>
|
||||||
|
</div>";
|
||||||
|
} else {
|
||||||
|
echo "<div style='font-family: sans-serif; background: #0c0f17; color: #f8fafc; min-height: 100vh; display: flex; align-items: center; justify-content: center;'>
|
||||||
|
<div style='max-width: 480px; text-align: center; padding: 40px;'>
|
||||||
|
<div style='width: 48px; height: 48px; margin: 0 auto 16px; border-radius: 12px; background: rgba(99, 102, 241, 0.1); border: 1px solid rgba(99, 102, 241, 0.3); display: flex; align-items: center; justify-content: center; color: #818cf8; font-weight: bold;'>F</div>
|
||||||
|
<h2 style='font-size: 20px; margin-bottom: 8px;'>Er is iets misgegaan</h2>
|
||||||
|
<p style='color: #94a3b8; font-size: 14px; line-height: 1.5;'>De pagina kon niet worden geladen. Probeer de pagina te vernieuwen of log opnieuw in.</p>
|
||||||
|
<a href='/auth/login.php' style='display: inline-block; margin-top: 20px; padding: 10px 20px; border-radius: 8px; background: #4f46e5; color: white; text-decoration: none; font-size: 13px; font-weight: 600;'>Naar Inloggen</a>
|
||||||
|
</div>
|
||||||
|
</div>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
ini_set('display_errors', '1');
|
ini_set('display_errors', '1');
|
||||||
ini_set('display_startup_errors', '1');
|
ini_set('display_startup_errors', '1');
|
||||||
error_reporting(E_ALL);
|
error_reporting(E_ALL);
|
||||||
|
|||||||
+73
-59
@@ -1,8 +1,14 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* MariaDB PDO Databaseverbinding & Automatische Migratie
|
* MariaDB PDO Databaseverbinding & Automatische Tabellencontrole
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Blokkeer directe HTTP toegang (Pen Test Beveiliging)
|
||||||
|
if (basename($_SERVER['SCRIPT_FILENAME'] ?? '') === 'database.php') {
|
||||||
|
http_response_code(403);
|
||||||
|
exit('Toegang geweigerd.');
|
||||||
|
}
|
||||||
|
|
||||||
require_once __DIR__ . '/config.php';
|
require_once __DIR__ . '/config.php';
|
||||||
|
|
||||||
function get_db_connection(): PDO {
|
function get_db_connection(): PDO {
|
||||||
@@ -15,11 +21,11 @@ function get_db_connection(): PDO {
|
|||||||
// 1. Controleer of de PDO MySQL/MariaDB driver aanwezig is in PHP
|
// 1. Controleer of de PDO MySQL/MariaDB driver aanwezig is in PHP
|
||||||
$availableDrivers = PDO::getAvailableDrivers();
|
$availableDrivers = PDO::getAvailableDrivers();
|
||||||
if (!in_array('mysql', $availableDrivers)) {
|
if (!in_array('mysql', $availableDrivers)) {
|
||||||
$driversList = !empty($availableDrivers) ? implode(', ', $availableDrivers) : 'geen';
|
error_log("PDO MySQL driver (pdo_mysql) ontbreekt in PHP.");
|
||||||
die("
|
die("
|
||||||
<div style='font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif; background: #0c0f17; color: #f1f5f9; min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 20px;'>
|
<div style='font-family: sans-serif; background: #0c0f17; color: #f1f5f9; min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 20px;'>
|
||||||
<div style='max-width: 580px; background: #111827; border: 1px solid #ef4444; border-radius: 16px; padding: 30px; box-shadow: 0 20px 25px -5px rgba(0,0,0,0.5);'>
|
<div style='max-width: 580px; background: #111827; border: 1px solid #ef4444; border-radius: 16px; padding: 30px;'>
|
||||||
<h2 style='color: #ef4444; margin-top: 0;'>⚠️ PHP MySQL Driver Ontbreekt (pdo_mysql)</h2>
|
<h2 style='color: #ef4444; margin-top: 0;'>⚠️ Database Driver Ontbreekt</h2>
|
||||||
<p style='color: #94a3b8; font-size: 14px;'>De extensie <strong>pdo_mysql</strong> is niet actief in deze PHP-omgeving.</p>
|
<p style='color: #94a3b8; font-size: 14px;'>De extensie <strong>pdo_mysql</strong> is niet actief in deze PHP-omgeving.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -45,19 +51,16 @@ function get_db_connection(): PDO {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 2. CHECK: Bestaat de database al?
|
// 2. CHECK: Bestaat de database al?
|
||||||
// We proberen eerst direct verbinding te maken met de database.
|
|
||||||
// Als de database al bestaat, lukt dit direct zonder database-aanmaakrechten te vereisen!
|
|
||||||
try {
|
try {
|
||||||
$pdo = new PDO($dsnWithDb, DB_USER, DB_PASS, $options);
|
$pdo = new PDO($dsnWithDb, DB_USER, DB_PASS, $options);
|
||||||
|
|
||||||
// De database bestaat al! Controleer enkel of de tabellen al aanwezig zijn
|
|
||||||
ensure_tables_exist($pdo);
|
ensure_tables_exist($pdo);
|
||||||
return $pdo;
|
return $pdo;
|
||||||
|
|
||||||
} catch (PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
|
error_log("MariaDB Verbindingsfout [{$e->getCode()}]: {$e->getMessage()}");
|
||||||
|
|
||||||
// Foutcode 1049 betekent: Database 'DB_NAME' bestaat nog NIET op de MariaDB server
|
// Foutcode 1049 betekent: Database 'DB_NAME' bestaat nog NIET op de MariaDB server
|
||||||
if ($e->getCode() == 1049 || str_contains($e->getMessage(), 'Unknown database')) {
|
if ($e->getCode() == 1049 || str_contains($e->getMessage(), 'Unknown database')) {
|
||||||
// Probeer de database eenmalig aan te maken indien de gebruiker CREATE rechten heeft
|
|
||||||
try {
|
try {
|
||||||
$serverDsn = sprintf('mysql:host=%s;port=%s;charset=%s', DB_HOST, DB_PORT, DB_CHARSET);
|
$serverDsn = sprintf('mysql:host=%s;port=%s;charset=%s', DB_HOST, DB_PORT, DB_CHARSET);
|
||||||
$serverPdo = new PDO($serverDsn, DB_USER, DB_PASS, $options);
|
$serverPdo = new PDO($serverDsn, DB_USER, DB_PASS, $options);
|
||||||
@@ -66,82 +69,93 @@ function get_db_connection(): PDO {
|
|||||||
DB_NAME
|
DB_NAME
|
||||||
));
|
));
|
||||||
|
|
||||||
// Nu opnieuw verbinden met de nieuw aangemaakte database
|
|
||||||
$pdo = new PDO($dsnWithDb, DB_USER, DB_PASS, $options);
|
$pdo = new PDO($dsnWithDb, DB_USER, DB_PASS, $options);
|
||||||
ensure_tables_exist($pdo);
|
ensure_tables_exist($pdo);
|
||||||
return $pdo;
|
return $pdo;
|
||||||
|
|
||||||
} catch (PDOException $createEx) {
|
} catch (PDOException $createEx) {
|
||||||
die("
|
error_log("Kan database " . DB_NAME . " niet automatisch aanmaken: " . $createEx->getMessage());
|
||||||
<div style='font-family: sans-serif; background: #0c0f17; color: #fff; padding: 40px; text-align: center; min-height: 100vh; display: flex; align-items: center; justify-content: center;'>
|
|
||||||
<div style='max-width: 500px; background: #111827; border: 1px solid #f59e0b; border-radius: 16px; padding: 30px; text-align: left;'>
|
|
||||||
<h2 style='color: #f59e0b; margin-top: 0;'>⚠️ Database '" . htmlspecialchars(DB_NAME) . "' bestaat nog niet</h2>
|
|
||||||
<p style='color: #94a3b8; font-size: 14px; line-height: 1.6;'>
|
|
||||||
De database <strong>" . htmlspecialchars(DB_NAME) . "</strong> bestaat nog niet op MariaDB server <strong>" . htmlspecialchars(DB_HOST) . "</strong> en de gebruiker <em>" . htmlspecialchars(DB_USER) . "</em> heeft geen rechten om automatisch databases aan te maken.
|
|
||||||
</p>
|
|
||||||
<p style='color: #cbd5e1; font-size: 13px;'>
|
|
||||||
Maak de database <code>" . htmlspecialchars(DB_NAME) . "</code> eenmalig aan via phpMyAdmin, HeidiSQL of de MariaDB CLI:
|
|
||||||
</p>
|
|
||||||
<code style='display: block; background: #000; color: #4ade80; padding: 10px; border-radius: 8px; font-size: 12px; margin: 10px 0;'>
|
|
||||||
CREATE DATABASE IF NOT EXISTS `" . htmlspecialchars(DB_NAME) . "` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
|
||||||
</code>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Andere verbindingsfout (bijv. verkeerd wachtwoord, poort of host onbereikbaar)
|
// Pen Test beveiliging: in live omgeving geen interne IP's of database-details tonen!
|
||||||
die("
|
if (defined('APP_ENV') && APP_ENV === 'live') {
|
||||||
<div style='font-family: sans-serif; background: #0c0f17; color: #fff; padding: 40px; text-align: center; min-height: 100vh; display: flex; align-items: center; justify-content: center;'>
|
die("
|
||||||
<div style='max-width: 520px; background: #111827; border: 1px solid #ef4444; border-radius: 16px; padding: 30px; text-align: left;'>
|
<div style='font-family: sans-serif; background: #0c0f17; color: #fff; padding: 40px; text-align: center; min-height: 100vh; display: flex; align-items: center; justify-content: center;'>
|
||||||
<h2 style='color: #ef4444; margin-top: 0;'>MariaDB Verbindingsfout (" . htmlspecialchars((string)$e->getCode()) . ")</h2>
|
<div style='max-width: 480px; background: #111827; border: 1px solid #334155; border-radius: 16px; padding: 30px; text-align: center;'>
|
||||||
<p style='color: #cbd5e1; font-size: 14px;'>" . htmlspecialchars($e->getMessage()) . "</p>
|
<div style='width: 48px; height: 48px; margin: 0 auto 16px; border-radius: 12px; background: rgba(239, 68, 68, 0.1); border: 1px solid rgba(239, 68, 68, 0.3); display: flex; align-items: center; justify-content: center; color: #f87171; font-weight: bold;'>⚠️</div>
|
||||||
<div style='background: #0b0f17; border: 1px solid #1f2937; padding: 15px; border-radius: 10px; font-size: 13px; margin-top: 15px;'>
|
<h2 style='color: #f87171; margin-top: 0; font-size: 18px;'>Database Tijdelijk Niet Bereikbaar</h2>
|
||||||
Host: <strong>" . htmlspecialchars(DB_HOST) . "</strong><br>
|
<p style='color: #94a3b8; font-size: 13px; line-height: 1.6;'>
|
||||||
Poort: <strong>" . htmlspecialchars(DB_PORT) . "</strong><br>
|
Er kon geen veilige verbinding tot stand worden gebracht met de database. Probeer het over enkele ogenblikken opnieuw.
|
||||||
Database: <strong>" . htmlspecialchars(DB_NAME) . "</strong><br>
|
</p>
|
||||||
Gebruiker: <strong>" . htmlspecialchars(DB_USER) . "</strong>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
");
|
||||||
");
|
} else {
|
||||||
|
die("
|
||||||
|
<div style='font-family: sans-serif; background: #0c0f17; color: #fff; padding: 40px; text-align: center; min-height: 100vh; display: flex; align-items: center; justify-content: center;'>
|
||||||
|
<div style='max-width: 520px; background: #111827; border: 1px solid #ef4444; border-radius: 16px; padding: 30px; text-align: left;'>
|
||||||
|
<h2 style='color: #ef4444; margin-top: 0;'>MariaDB Verbindingsfout (" . htmlspecialchars((string)$e->getCode()) . ")</h2>
|
||||||
|
<p style='color: #cbd5e1; font-size: 14px;'>" . htmlspecialchars($e->getMessage()) . "</p>
|
||||||
|
<div style='background: #0b0f17; border: 1px solid #1f2937; padding: 15px; border-radius: 10px; font-size: 13px; margin-top: 15px;'>
|
||||||
|
Host: <strong>" . htmlspecialchars(DB_HOST) . "</strong><br>
|
||||||
|
Poort: <strong>" . htmlspecialchars(DB_PORT) . "</strong><br>
|
||||||
|
Database: <strong>" . htmlspecialchars(DB_NAME) . "</strong><br>
|
||||||
|
Gebruiker: <strong>" . htmlspecialchars(DB_USER) . "</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zorgt ervoor dat alle benodigde tabellen uit schema.sql aanwezig zijn in MariaDB
|
* Zorgt ervoor dat ALLE benodigde tabellen en kolommen uit schema.sql aanwezig zijn in MariaDB
|
||||||
* Voert niets uit als de tabellen al bestaan!
|
* Voert elke tabel individueel uit zodat ontbrekende tabellen direct aangemaakt worden.
|
||||||
*/
|
*/
|
||||||
function ensure_tables_exist(PDO $pdo): void {
|
function ensure_tables_exist(PDO $pdo): void {
|
||||||
try {
|
static $tablesChecked = false;
|
||||||
// Controleer of de hoofdtabel 'users' al bestaat
|
if ($tablesChecked) return;
|
||||||
$result = $pdo->query("SHOW TABLES LIKE 'users'");
|
$tablesChecked = true;
|
||||||
if ($result && $result->rowCount() > 0) {
|
|
||||||
// Tabellen bestaan al. Voer eventuele niet-destructieve kolom-upgrades uit indien nodig
|
|
||||||
try {
|
|
||||||
$pdo->exec("ALTER TABLE tasks MODIFY external_id VARCHAR(255) NULL");
|
|
||||||
} catch (Throwable $t) {
|
|
||||||
// Kolom bestaat wellicht al of heeft al de juiste lengte
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tabellen ontbreken nog: voer schema.sql uit statement voor statement
|
try {
|
||||||
$schemaFile = file_exists(__DIR__ . '/schema.sql') ? (__DIR__ . '/schema.sql') : (__DIR__ . '/../schema.sql');
|
$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)));
|
||||||
foreach ($statements as $stmt) {
|
foreach ($statements as $stmt) {
|
||||||
// Verwijder eventuele SQL commentaren aan het begin
|
|
||||||
$stmtClean = preg_replace('/^--.*$/m', '', $stmt);
|
$stmtClean = preg_replace('/^--.*$/m', '', $stmt);
|
||||||
$stmtClean = trim($stmtClean);
|
$stmtClean = trim($stmtClean);
|
||||||
if (!empty($stmtClean)) {
|
if (!empty($stmtClean)) {
|
||||||
$pdo->exec($stmtClean);
|
try {
|
||||||
|
$pdo->exec($stmtClean);
|
||||||
|
} catch (Throwable $t) {
|
||||||
|
// Stil negeren indien tabel of relatie al bestaat
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception $e) {
|
|
||||||
error_log("Fout bij aanmaken tabellen: " . $e->getMessage());
|
// Zorg ervoor dat alle kolommen en upgrades aanwezig zijn
|
||||||
|
$colUpgrades = [
|
||||||
|
"ALTER TABLE tasks MODIFY external_id VARCHAR(255) NULL",
|
||||||
|
"ALTER TABLE tasks ADD COLUMN external_url TEXT NULL AFTER external_id",
|
||||||
|
"ALTER TABLE users ADD COLUMN remember_token VARCHAR(64) NULL AFTER backup_codes",
|
||||||
|
"ALTER TABLE users ADD COLUMN remember_token_expires_at DATETIME NULL AFTER remember_token",
|
||||||
|
"ALTER TABLE users ADD COLUMN notification_settings TEXT NULL AFTER remember_token_expires_at",
|
||||||
|
"ALTER TABLE users ADD COLUMN theme VARCHAR(20) NOT NULL DEFAULT 'dark' AFTER notification_settings",
|
||||||
|
"ALTER TABLE users ADD COLUMN avatar_url VARCHAR(255) NULL AFTER theme",
|
||||||
|
"ALTER TABLE users ADD COLUMN backup_codes TEXT NULL AFTER two_factor_enabled",
|
||||||
|
];
|
||||||
|
foreach ($colUpgrades as $sql) {
|
||||||
|
try {
|
||||||
|
$pdo->exec($sql);
|
||||||
|
} catch (Throwable $t) {
|
||||||
|
// Stil negeren indien kolom al bestaat
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
error_log("Fout bij controleren tabellen: " . $e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<?php
|
||||||
|
http_response_code(403);
|
||||||
|
exit('Toegang geweigerd.');
|
||||||
@@ -3,6 +3,11 @@
|
|||||||
* Authenticatie & Autorisatie Guard voor Fociz
|
* Authenticatie & Autorisatie Guard voor Fociz
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
if (basename($_SERVER['SCRIPT_FILENAME'] ?? '') === 'auth.php') {
|
||||||
|
http_response_code(403);
|
||||||
|
exit('Toegang geweigerd.');
|
||||||
|
}
|
||||||
|
|
||||||
require_once __DIR__ . '/../config/database.php';
|
require_once __DIR__ . '/../config/database.php';
|
||||||
require_once __DIR__ . '/totp.php';
|
require_once __DIR__ . '/totp.php';
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<?php
|
||||||
|
http_response_code(403);
|
||||||
|
exit('Toegang geweigerd.');
|
||||||
+80
-44
@@ -43,11 +43,16 @@ $formattedSelectedDate = $dutchFullDays[(int)date('N', $selectedTs)] . ' ' . dat
|
|||||||
$formattedWeekRange = date('j', $mondayTs) . ' ' . $dutchShortMonths[(int)date('n', $mondayTs)] . ' – ' . date('j', $sundayTs) . ' ' . $dutchShortMonths[(int)date('n', $sundayTs)] . ' ' . date('Y', $sundayTs);
|
$formattedWeekRange = date('j', $mondayTs) . ' ' . $dutchShortMonths[(int)date('n', $mondayTs)] . ' – ' . date('j', $sundayTs) . ' ' . $dutchShortMonths[(int)date('n', $sundayTs)] . ' ' . date('Y', $sundayTs);
|
||||||
|
|
||||||
// 1. Haal integratiestatussen op
|
// 1. Haal integratiestatussen op
|
||||||
$stmtInt = $db->prepare("SELECT * FROM integrations WHERE user_id = ?");
|
|
||||||
$stmtInt->execute([$userId]);
|
|
||||||
$userIntegrations = [];
|
$userIntegrations = [];
|
||||||
foreach ($stmtInt->fetchAll() as $row) {
|
try {
|
||||||
$userIntegrations[$row['provider']] = $row;
|
$stmtInt = $db->prepare("SELECT * FROM integrations WHERE user_id = ?");
|
||||||
|
$stmtInt->execute([$userId]);
|
||||||
|
foreach ($stmtInt->fetchAll() as $row) {
|
||||||
|
$userIntegrations[$row['provider']] = $row;
|
||||||
|
}
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
error_log("Fout bij ophalen integraties: " . $e->getMessage());
|
||||||
|
ensure_tables_exist($db);
|
||||||
}
|
}
|
||||||
|
|
||||||
$isOutlookConnected = !empty($userIntegrations['OUTLOOK']['access_token']) && empty($userIntegrations['OUTLOOK']['is_demo_mode']);
|
$isOutlookConnected = !empty($userIntegrations['OUTLOOK']['access_token']) && empty($userIntegrations['OUTLOOK']['is_demo_mode']);
|
||||||
@@ -58,12 +63,18 @@ $weekOutlookEvents = [];
|
|||||||
$isLiveOutlook = false;
|
$isLiveOutlook = false;
|
||||||
|
|
||||||
if ($isOutlookConnected) {
|
if ($isOutlookConnected) {
|
||||||
// Haal afspraken op voor het volledige datumbereik van de week
|
try {
|
||||||
$weekOutlookEvents = microsoft_fetch_events_range($userId, $weekStart, $weekEnd);
|
// Haal afspraken op voor het volledige datumbereik van de week
|
||||||
$isLiveOutlook = true;
|
$weekOutlookEvents = microsoft_fetch_events_range($userId, $weekStart, $weekEnd);
|
||||||
|
$isLiveOutlook = true;
|
||||||
|
|
||||||
// Synchroniseer / reconcilieer Fociz taken direct met actuele Outlook data
|
// Synchroniseer / reconcilieer Fociz taken direct met actuele Outlook data
|
||||||
microsoft_reconcile_tasks_with_outlook($userId, $weekStart, $weekEnd, $weekOutlookEvents);
|
microsoft_reconcile_tasks_with_outlook($userId, $weekStart, $weekEnd, $weekOutlookEvents);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
error_log("Outlook synchronisatiefout in planner: " . $e->getMessage());
|
||||||
|
$isLiveOutlook = false;
|
||||||
|
$weekOutlookEvents = [];
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Demo data indien Microsoft Outlook nog niet gekoppeld is
|
// Demo data indien Microsoft Outlook nog niet gekoppeld is
|
||||||
$weekOutlookEvents = [
|
$weekOutlookEvents = [
|
||||||
@@ -101,45 +112,64 @@ if ($isOutlookConnected) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. Haal dagplanning op of maak aan voor $selectedDate
|
// 2. Haal dagplanning op of maak aan voor $selectedDate
|
||||||
$stmtPlan = $db->prepare("SELECT * FROM daily_plans WHERE user_id = ? AND plan_date = ?");
|
$dailyPlan = null;
|
||||||
$stmtPlan->execute([$userId, $selectedDate]);
|
try {
|
||||||
$dailyPlan = $stmtPlan->fetch();
|
$stmtPlan = $db->prepare("SELECT * FROM daily_plans WHERE user_id = ? AND plan_date = ?");
|
||||||
|
$stmtPlan->execute([$userId, $selectedDate]);
|
||||||
|
$dailyPlan = $stmtPlan->fetch();
|
||||||
|
|
||||||
if (!$dailyPlan) {
|
if (!$dailyPlan) {
|
||||||
$defaultGoal = ($selectedDate === $today) ? 'Mijn belangrijkste doel van vandaag behalen' : 'Focusdoel voor deze dag bepalen';
|
$defaultGoal = ($selectedDate === $today) ? 'Mijn belangrijkste doel van vandaag behalen' : 'Focusdoel voor deze dag bepalen';
|
||||||
$stmtCreatePlan = $db->prepare("
|
$stmtCreatePlan = $db->prepare("
|
||||||
INSERT INTO daily_plans (user_id, plan_date, focus_goal)
|
INSERT INTO daily_plans (user_id, plan_date, focus_goal)
|
||||||
VALUES (?, ?, ?)
|
VALUES (?, ?, ?)
|
||||||
");
|
ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID(id)
|
||||||
$stmtCreatePlan->execute([$userId, $selectedDate, $defaultGoal]);
|
");
|
||||||
$planId = (int)$db->lastInsertId();
|
$stmtCreatePlan->execute([$userId, $selectedDate, $defaultGoal]);
|
||||||
$dailyPlan = ['id' => $planId, 'plan_date' => $selectedDate, 'focus_goal' => $defaultGoal, 'notes' => ''];
|
$planId = (int)$db->lastInsertId();
|
||||||
|
$dailyPlan = ['id' => $planId, 'plan_date' => $selectedDate, 'focus_goal' => $defaultGoal, 'notes' => ''];
|
||||||
|
}
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
error_log("Fout bij dagplanning ophalen/aanmaken: " . $e->getMessage());
|
||||||
|
ensure_tables_exist($db);
|
||||||
|
$dailyPlan = ['id' => 0, 'plan_date' => $selectedDate, 'focus_goal' => 'Mijn belangrijkste doel van vandaag behalen', 'notes' => ''];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$planId = !empty($dailyPlan['id']) ? (int)$dailyPlan['id'] : 0;
|
||||||
|
|
||||||
// 3. Haal taken op voor de geselecteerde dag (vers bijgewerkt na eventuele reconciliatie)
|
// 3. Haal taken op voor de geselecteerde dag (vers bijgewerkt na eventuele reconciliatie)
|
||||||
$stmtTasks = $db->prepare("
|
$tasks = [];
|
||||||
SELECT * FROM tasks
|
try {
|
||||||
WHERE user_id = ? AND (daily_plan_id = ? " . ($selectedDate === $today ? "OR daily_plan_id IS NULL" : "") . ")
|
$stmtTasks = $db->prepare("
|
||||||
ORDER BY is_completed ASC, priority ASC, time_block_start ASC, created_at DESC
|
SELECT * FROM tasks
|
||||||
");
|
WHERE user_id = ? AND (daily_plan_id = ? " . ($selectedDate === $today ? "OR daily_plan_id IS NULL" : "") . ")
|
||||||
$stmtTasks->execute([$userId, $dailyPlan['id']]);
|
ORDER BY is_completed ASC, priority ASC, time_block_start ASC, created_at DESC
|
||||||
$tasks = $stmtTasks->fetchAll();
|
");
|
||||||
|
$stmtTasks->execute([$userId, $planId]);
|
||||||
|
$tasks = $stmtTasks->fetchAll() ?: [];
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
error_log("Fout bij ophalen taken: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
// 4. Haal weekstatistieken voor taken op (aantal per dag)
|
// 4. Haal weekstatistieken voor taken op (aantal per dag)
|
||||||
$stmtWeekTasks = $db->prepare("
|
|
||||||
SELECT dp.plan_date, COUNT(t.id) as total_tasks, SUM(CASE WHEN t.is_completed = 1 THEN 1 ELSE 0 END) as completed_tasks
|
|
||||||
FROM daily_plans dp
|
|
||||||
LEFT JOIN tasks t ON t.daily_plan_id = dp.id AND t.user_id = ?
|
|
||||||
WHERE dp.user_id = ? AND dp.plan_date BETWEEN ? AND ?
|
|
||||||
GROUP BY dp.plan_date
|
|
||||||
");
|
|
||||||
$stmtWeekTasks->execute([$userId, $userId, $weekStart, $weekEnd]);
|
|
||||||
$weekTaskStats = [];
|
$weekTaskStats = [];
|
||||||
foreach ($stmtWeekTasks->fetchAll() as $row) {
|
try {
|
||||||
$weekTaskStats[$row['plan_date']] = [
|
$stmtWeekTasks = $db->prepare("
|
||||||
'total' => (int)$row['total_tasks'],
|
SELECT dp.plan_date, COUNT(t.id) as total_tasks, SUM(CASE WHEN t.is_completed = 1 THEN 1 ELSE 0 END) as completed_tasks
|
||||||
'completed' => (int)$row['completed_tasks'],
|
FROM daily_plans dp
|
||||||
];
|
LEFT JOIN tasks t ON t.daily_plan_id = dp.id AND t.user_id = ?
|
||||||
|
WHERE dp.user_id = ? AND dp.plan_date BETWEEN ? AND ?
|
||||||
|
GROUP BY dp.plan_date
|
||||||
|
");
|
||||||
|
$stmtWeekTasks->execute([$userId, $userId, $weekStart, $weekEnd]);
|
||||||
|
foreach ($stmtWeekTasks->fetchAll() as $row) {
|
||||||
|
$weekTaskStats[$row['plan_date']] = [
|
||||||
|
'total' => (int)$row['total_tasks'],
|
||||||
|
'completed' => (int)$row['completed_tasks'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
error_log("Fout bij weekstatistieken ophalen: " . $e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Groepeer Outlook afspraken per datum
|
// Groepeer Outlook afspraken per datum
|
||||||
@@ -174,9 +204,15 @@ for ($i = 0; $i < 7; $i++) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 5. Haal Jira issues op voor de snelle import widget
|
// 5. Haal Jira issues op voor de snelle import widget
|
||||||
$jiraData = jira_get_user_issues($userId);
|
$jiraIssues = [];
|
||||||
$jiraIssues = $jiraData['issues'] ?? [];
|
$isJiraLive = false;
|
||||||
$isJiraLive = !empty($jiraData['success']) && empty($jiraData['is_demo']);
|
try {
|
||||||
|
$jiraData = jira_get_user_issues($userId);
|
||||||
|
$jiraIssues = $jiraData['issues'] ?? [];
|
||||||
|
$isJiraLive = !empty($jiraData['success']) && empty($jiraData['is_demo']);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
error_log("Fout bij ophalen Jira issues: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
// Filter Outlook events die afkomstig zijn van Fociz taken voor de geselecteerde dag om duplicaten op de tijdlijn te voorkomen
|
// Filter Outlook events die afkomstig zijn van Fociz taken voor de geselecteerde dag om duplicaten op de tijdlijn te voorkomen
|
||||||
$dayOutlookEvents = $outlookEventsByDate[$selectedDate] ?? [];
|
$dayOutlookEvents = $outlookEventsByDate[$selectedDate] ?? [];
|
||||||
|
|||||||
+9
-4
@@ -27,10 +27,15 @@ if ($selectedStatus !== 'ALL') {
|
|||||||
$params[] = $selectedStatus;
|
$params[] = $selectedStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
$sql .= " ORDER BY r.quarter ASC, r.priority DESC, r.due_date ASC";
|
$roadmapItems = [];
|
||||||
$stmt = $db->prepare($sql);
|
try {
|
||||||
$stmt->execute($params);
|
$stmt = $db->prepare($sql);
|
||||||
$roadmapItems = $stmt->fetchAll();
|
$stmt->execute($params);
|
||||||
|
$roadmapItems = $stmt->fetchAll() ?: [];
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
error_log("Fout bij ophalen roadmap items: " . $e->getMessage());
|
||||||
|
ensure_tables_exist($db);
|
||||||
|
}
|
||||||
|
|
||||||
$quarters = ['Q1', 'Q2', 'Q3', 'Q4'];
|
$quarters = ['Q1', 'Q2', 'Q3', 'Q4'];
|
||||||
?>
|
?>
|
||||||
|
|||||||
@@ -55,15 +55,26 @@ if (isset($_GET['delete_user'])) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Haal alle gebruikers op
|
// Haal alle gebruikers op
|
||||||
$users = $db->query("SELECT * FROM users ORDER BY created_at DESC")->fetchAll();
|
$users = [];
|
||||||
|
try {
|
||||||
|
$users = $db->query("SELECT * FROM users ORDER BY created_at DESC")->fetchAll() ?: [];
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
error_log("Fout bij ophalen gebruikers: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
// Haal alle uitnodigingen op
|
// Haal alle uitnodigingen op
|
||||||
$invites = $db->query("
|
$invites = [];
|
||||||
SELECT i.*, u.name as inviter_name
|
try {
|
||||||
FROM invites i
|
$invites = $db->query("
|
||||||
JOIN users u ON i.created_by_id = u.id
|
SELECT i.*, u.name as inviter_name
|
||||||
ORDER BY i.created_at DESC
|
FROM invites i
|
||||||
")->fetchAll();
|
JOIN users u ON i.created_by_id = u.id
|
||||||
|
ORDER BY i.created_at DESC
|
||||||
|
")->fetchAll() ?: [];
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
error_log("Fout bij ophalen uitnodigingen: " . $e->getMessage());
|
||||||
|
ensure_tables_exist($db);
|
||||||
|
}
|
||||||
|
|
||||||
$pageTitle = 'Team & Uitnodigingen — Fociz';
|
$pageTitle = 'Team & Uitnodigingen — Fociz';
|
||||||
require_once __DIR__ . '/includes/header.php';
|
require_once __DIR__ . '/includes/header.php';
|
||||||
|
|||||||
Reference in New Issue
Block a user