prepare("SELECT * FROM integrations WHERE user_id = ? AND provider = 'OUTLOOK'"); $stmtExisting->execute([$userId]); $existing = $stmtExisting->fetch(); $existingSettings = json_decode($existing['settings'] ?? '{}', true) ?: []; if (($clientSecret === '' || $clientSecret === '••••••••••••••••') && !empty($existingSettings['client_secret']) && $existingSettings['client_secret'] !== '••••••••••••••••') { $clientSecret = $existingSettings['client_secret']; } $newSettings = array_merge($existingSettings, [ 'client_id' => $clientId, 'client_secret' => $clientSecret, 'tenant_id' => $tenantId, ]); $stmt = $db->prepare(" INSERT INTO integrations (user_id, provider, is_enabled, is_demo_mode, settings, last_sync_at) VALUES (?, 'OUTLOOK', 1, ?, ?, NOW()) ON DUPLICATE KEY UPDATE is_demo_mode = VALUES(is_demo_mode), settings = VALUES(settings), last_sync_at = NOW() "); $stmt->execute([$userId, $isDemo, json_encode($newSettings)]); fociz_log_audit($userId, 'SETTINGS_UPDATED', 'integration', null, ['provider' => 'OUTLOOK', 'tenant_id' => $tenantId], 'SUCCESS', $adminUser['email']); $message = "Centrale Microsoft 365 instellingen (Agenda, E-mail & Teams) zijn succesvol opgeslagen."; $messageType = 'success'; } // 2. Centraal Jira Bedrijfsdomein opslaan elseif ($action === 'save_jira_domain') { $activeTab = 'projects'; $rawDomain = trim($_POST['jira_domain'] ?? ''); $companyDomain = !empty($rawDomain) ? jira_normalize_domain($rawDomain) : ''; $stmtExisting = $db->prepare("SELECT * FROM integrations WHERE user_id = ? AND provider = 'JIRA'"); $stmtExisting->execute([$userId]); $existing = $stmtExisting->fetch(); $existingSettings = json_decode($existing['settings'] ?? '{}', true) ?: []; $existingSettings['jira_domain'] = $companyDomain; $stmt = $db->prepare(" INSERT INTO integrations (user_id, provider, is_enabled, is_demo_mode, settings, last_sync_at) VALUES (?, 'JIRA', 1, 0, ?, NOW()) ON DUPLICATE KEY UPDATE settings = VALUES(settings), last_sync_at = NOW() "); $stmt->execute([$userId, json_encode($existingSettings)]); fociz_log_audit($userId, 'SETTINGS_UPDATED', 'integration', null, ['provider' => 'JIRA', 'domain' => $companyDomain], 'SUCCESS', $adminUser['email']); $message = "Standaard Atlassian Jira bedrijfsdomein is succesvol opgeslagen."; $messageType = 'success'; } // 2b. Jira Wachtrijfilter toevoegen of bewerken elseif ($action === 'save_jira_queue_filter') { $activeTab = 'projects'; $filterId = (int)($_POST['filter_id'] ?? 0); $filterCode = strtoupper(trim($_POST['filter_code'] ?? '')); $filterName = trim($_POST['filter_name'] ?? ''); $projectKey = strtoupper(trim($_POST['project_key'] ?? '')); $jqlClause = trim($_POST['jql_clause'] ?? ''); $description = trim($_POST['description'] ?? ''); $isActive = isset($_POST['is_active']) ? 1 : 0; $sortOrder = (int)($_POST['sort_order'] ?? 0); if (empty($filterCode) || empty($filterName) || empty($jqlClause)) { $message = "Filtercode, naam en JQL-clausule zijn verplicht."; $messageType = 'error'; } else { if ($filterId > 0) { $stmt = $db->prepare(" UPDATE jira_queue_filters SET filter_code = ?, filter_name = ?, project_key = ?, jql_clause = ?, description = ?, is_active = ?, sort_order = ?, updated_at = NOW() WHERE id = ? "); $stmt->execute([$filterCode, $filterName, $projectKey ?: null, $jqlClause, $description, $isActive, $sortOrder, $filterId]); $message = "Wachtrijfilter '{$filterName}' is succesvol bijgewerkt."; } else { $stmt = $db->prepare(" INSERT INTO jira_queue_filters (filter_code, filter_name, project_key, jql_clause, description, is_active, sort_order) VALUES (?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE filter_name = VALUES(filter_name), project_key = VALUES(project_key), jql_clause = VALUES(jql_clause), description = VALUES(description), is_active = VALUES(is_active), sort_order = VALUES(sort_order), updated_at = NOW() "); $stmt->execute([$filterCode, $filterName, $projectKey ?: null, $jqlClause, $description, $isActive, $sortOrder]); $message = "Nieuw wachtrijfilter '{$filterName}' is succesvol toegevoegd."; } fociz_log_audit($userId, 'SETTINGS_UPDATED', 'jira_queue_filter', $filterId ?: null, ['filter_code' => $filterCode, 'filter_name' => $filterName], 'SUCCESS', $adminUser['email']); $messageType = 'success'; } } // 2c. Jira Wachtrijfilter verwijderen elseif ($action === 'delete_jira_queue_filter') { $activeTab = 'projects'; $filterId = (int)($_POST['filter_id'] ?? 0); if ($filterId > 0) { $stmt = $db->prepare("DELETE FROM jira_queue_filters WHERE id = ?"); $stmt->execute([$filterId]); fociz_log_audit($userId, 'SETTINGS_UPDATED', 'jira_queue_filter', $filterId, ['action' => 'delete'], 'SUCCESS', $adminUser['email']); $message = "Wachtrijfilter is succesvol verwijderd."; $messageType = 'success'; } } // 2d. Teamleden toewijzen aan Jira Wachtrijfilters (meervoudig) elseif ($action === 'save_user_jira_assignments') { $activeTab = 'projects'; $assignments = $_POST['user_filters'] ?? []; $submittedUserIds = isset($_POST['user_ids']) && is_array($_POST['user_ids']) ? array_map('intval', $_POST['user_ids']) : []; // Fallback als user_ids niet meegestuurd werd (bv. oude formulier submission met user_filter) if (empty($submittedUserIds) && !empty($_POST['user_filter']) && is_array($_POST['user_filter'])) { $submittedUserIds = array_map('intval', array_keys($_POST['user_filter'])); foreach ($_POST['user_filter'] as $uId => $sCode) { $sCode = trim($sCode); $assignments[$uId] = $sCode !== '' ? [$sCode] : []; } } if (!empty($submittedUserIds)) { $upd = $db->prepare("UPDATE users SET default_jira_filter_codes = ?, default_jira_filter_code = ? WHERE id = ?"); foreach ($submittedUserIds as $uId) { $rawList = isset($assignments[$uId]) && is_array($assignments[$uId]) ? $assignments[$uId] : []; $cleanCodes = array_values(array_unique(array_filter(array_map('trim', $rawList)))); $jsonVal = !empty($cleanCodes) ? json_encode($cleanCodes) : null; $firstVal = !empty($cleanCodes) ? $cleanCodes[0] : null; $upd->execute([$jsonVal, $firstVal, (int)$uId]); } fociz_log_audit($userId, 'SETTINGS_UPDATED', 'jira_assignments', null, [], 'SUCCESS', $adminUser['email']); $message = "Standaard Jira wachtrijtoewijzingen voor het team zijn succesvol opgeslagen."; $messageType = 'success'; } } // 3. Organisatie Werkdag Standaarden opslaan (Release v1.4) elseif ($action === 'save_workday_settings') { $rawWorkdays = isset($_POST['workdays']) && is_array($_POST['workdays']) ? array_values(array_filter(array_map('intval', $_POST['workdays']), fn($d) => $d >= 1 && $d <= 7)) : [1, 2, 3, 4, 5]; if (empty($rawWorkdays)) { $rawWorkdays = [1, 2, 3, 4, 5]; } sort($rawWorkdays); $workdaysStr = implode(',', $rawWorkdays); $workdayStart = trim($_POST['workday_start'] ?? '08:30'); $workdayEnd = trim($_POST['workday_end'] ?? '17:00'); $lunchStart = trim($_POST['lunch_start'] ?? '12:30'); $lunchEnd = trim($_POST['lunch_end'] ?? '13:00'); $pomoFocus = max(10, min(120, (int)($_POST['pomodoro_focus'] ?? 25))); $pomoDeep = max(20, min(180, (int)($_POST['pomodoro_deep'] ?? 50))); $pomoBreak = max(2, min(30, (int)($_POST['pomodoro_break'] ?? 5))); $taskDuration = in_array((int)($_POST['default_task_duration'] ?? 30), [15, 30, 45, 60, 90, 120]) ? (int)$_POST['default_task_duration'] : 30; set_system_setting('workdays', $workdaysStr); set_system_setting('workday_start', $workdayStart); set_system_setting('workday_end', $workdayEnd); set_system_setting('lunch_start', $lunchStart); set_system_setting('lunch_end', $lunchEnd); set_system_setting('pomodoro_focus', $pomoFocus); set_system_setting('pomodoro_deep', $pomoDeep); set_system_setting('pomodoro_break', $pomoBreak); set_system_setting('default_task_duration', $taskDuration); fociz_log_audit($userId, 'SETTINGS_UPDATED', 'workday_settings', null, ['workdays' => $workdaysStr, 'workday_start' => $workdayStart, 'workday_end' => $workdayEnd], 'SUCCESS', $adminUser['email']); $message = "Organisatie werkdagstandaarden zijn succesvol bijgewerkt."; $messageType = 'success'; $activeTab = 'calendar'; } // 3b. Agenda Blokkade Opties / Redenen opslaan elseif ($action === 'save_calendar_block_reasons') { $rawReasons = $_POST['reasons'] ?? []; $sanitizedReasons = []; if (is_array($rawReasons)) { foreach ($rawReasons as $r) { $code = strtoupper(trim((string)($r['code'] ?? ''))); $code = preg_replace('/[^A-Z0-9_]/', '', $code); $title = trim((string)($r['title'] ?? '')); $icon = trim((string)($r['icon'] ?? '📅')); $isActive = isset($r['is_active']) && ($r['is_active'] == '1' || $r['is_active'] === 'on') ? 1 : 0; $isDelete = isset($r['is_delete']) && ($r['is_delete'] == '1' || $r['is_delete'] === 'on'); if ($isDelete || empty($code) || empty($title)) { continue; } $sanitizedReasons[] = [ 'code' => $code, 'title' => $title, 'icon' => !empty($icon) ? $icon : '📅', 'is_active' => $isActive, ]; } } // Nieuwe optie toevoegen indien ingevuld $newCode = strtoupper(trim((string)($_POST['new_code'] ?? ''))); $newCode = preg_replace('/[^A-Z0-9_]/', '', $newCode); $newTitle = trim((string)($_POST['new_title'] ?? '')); $newIcon = trim((string)($_POST['new_icon'] ?? '📅')); if (!empty($newCode) && !empty($newTitle)) { $exists = false; foreach ($sanitizedReasons as &$sr) { if ($sr['code'] === $newCode) { $sr['title'] = $newTitle; $sr['icon'] = !empty($newIcon) ? $newIcon : '📅'; $sr['is_active'] = 1; $exists = true; break; } } unset($sr); if (!$exists) { $sanitizedReasons[] = [ 'code' => $newCode, 'title' => $newTitle, 'icon' => !empty($newIcon) ? $newIcon : '📅', 'is_active' => 1, ]; } } if (empty($sanitizedReasons)) { $sanitizedReasons = [ ['code' => 'VACATION', 'title' => 'Vakantie', 'icon' => '🏖️', 'is_active' => 1], ['code' => 'LEAVE', 'title' => 'Verlof / Vrij', 'icon' => '✈️', 'is_active' => 1], ['code' => 'SICK', 'title' => 'Ziek / Afwezig', 'icon' => '🤒', 'is_active' => 1], ['code' => 'FOCUS', 'title' => 'Focusdag', 'icon' => '🎯', 'is_active' => 1], ['code' => 'ADV', 'title' => 'ADV Dag', 'icon' => '📋', 'is_active' => 1], ]; } set_system_setting('calendar_block_reasons', $sanitizedReasons); fociz_log_audit($userId, 'SETTINGS_UPDATED', 'calendar_block_reasons', null, ['count' => count($sanitizedReasons)], 'SUCCESS', $adminUser['email']); $message = "Agenda blokkade opties zijn succesvol opgeslagen."; $messageType = 'success'; $activeTab = 'calendar'; } // 3c. Brainstorm Categorieën opslaan elseif ($action === 'save_brainstorm_categories') { $rawCategories = $_POST['categories'] ?? []; $sanitizedCategories = []; if (is_array($rawCategories)) { foreach ($rawCategories as $c) { $name = trim((string)($c['name'] ?? '')); $desc = trim((string)($c['description'] ?? '')); $isActive = isset($c['is_active']) && ($c['is_active'] == '1' || $c['is_active'] === 'on') ? 1 : 0; $isDelete = isset($c['is_delete']) && ($c['is_delete'] == '1' || $c['is_delete'] === 'on'); if ($isDelete || empty($name)) { continue; } $sanitizedCategories[] = [ 'name' => $name, 'description' => $desc, 'is_active' => $isActive, ]; } } // Nieuwe categorie toevoegen indien ingevuld $newName = trim((string)($_POST['new_category_name'] ?? '')); $newDesc = trim((string)($_POST['new_category_desc'] ?? '')); if (!empty($newName)) { $exists = false; foreach ($sanitizedCategories as &$sc) { if (strcasecmp($sc['name'], $newName) === 0) { $sc['name'] = $newName; if (!empty($newDesc)) $sc['description'] = $newDesc; $sc['is_active'] = 1; $exists = true; break; } } unset($sc); if (!$exists) { $sanitizedCategories[] = [ 'name' => $newName, 'description' => $newDesc, 'is_active' => 1, ]; } } if (empty($sanitizedCategories)) { $sanitizedCategories = [ ['name' => 'Product & Strategie', 'description' => 'Strategische richting, nieuwe producten en visie', 'is_active' => 1], ['name' => 'Procesverbetering', 'description' => 'Optimalisatie van werkprocessen en efficiëntie', 'is_active' => 1], ['name' => 'Klant & Service', 'description' => 'Klanttevredenheid, support en klantervaring', 'is_active' => 1], ['name' => 'Technologie & IT', 'description' => 'Architectuur, tooling, infrastructuur en tech stack','is_active' => 1], ['name' => 'Marketing & Groei', 'description' => 'Leadgeneratie, branding en marktpositie', 'is_active' => 1], ]; } set_system_setting('brainstorm_categories', $sanitizedCategories); fociz_log_audit($userId, 'SETTINGS_UPDATED', 'brainstorm_categories', null, ['count' => count($sanitizedCategories)], 'SUCCESS', $adminUser['email']); $message = "Brainstorm categorieën zijn succesvol opgeslagen."; $messageType = 'success'; $activeTab = 'projects'; } // 4. AI Co-Pilot instellingen opslaan (Release v1.5) elseif ($action === 'save_ai_settings') { $activeTab = 'system'; try { $aiEnabled = isset($_POST['ai_enabled']) ? 1 : 0; $aiModel = trim((string)($_POST['ai_model'] ?? 'gemini-1.5-pro')); $aiProvider = trim((string)($_POST['ai_provider'] ?? '')); if (empty($aiProvider)) { $aiProvider = str_contains($aiModel, 'gpt') ? 'openai' : 'gemini'; } $apiKeyIn = trim((string)($_POST['ai_api_key'] ?? '')); if ($apiKeyIn !== '' && $apiKeyIn !== '••••••••••••••••') { set_system_setting('ai_api_key', fociz_encrypt($apiKeyIn)); } set_system_setting('ai_enabled', $aiEnabled); set_system_setting('ai_provider', $aiProvider); set_system_setting('ai_model', $aiModel); fociz_log_audit($userId, 'SETTINGS_UPDATED', 'ai_settings', null, [ 'ai_enabled' => $aiEnabled, 'ai_provider' => $aiProvider, 'ai_model' => $aiModel ], 'SUCCESS', $adminUser['email'] ?? null); $message = "AI Co-Pilot instellingen zijn succesvol opgeslagen."; $messageType = 'success'; } catch (Throwable $e) { error_log("Fout bij opslaan AI instellingen: " . $e->getMessage()); $message = "Fout bij opslaan van AI instellingen: " . htmlspecialchars($e->getMessage()); $messageType = 'error'; } } // 5. Test e-mail versturen via SMTP (Release v1.6) elseif ($action === 'send_test_email') { $activeTab = 'system'; require_once __DIR__ . '/includes/mailer.php'; $testEmail = trim($_POST['test_email'] ?? ''); if (empty($testEmail) || !filter_var($testEmail, FILTER_VALIDATE_EMAIL)) { $message = "Vul een geldig e-mailadres in om een test e-mail te sturen."; $messageType = 'error'; } else { $mailResult = FocizMailer::sendTestEmail($testEmail, $currentUser['name']); if ($mailResult['success']) { $message = "Test e-mail succesvol verzonden via mail.fociz.nl naar " . htmlspecialchars($testEmail) . "!"; $messageType = 'success'; } else { $message = "Fout bij verzenden test e-mail via mail.fociz.nl: " . htmlspecialchars($mailResult['error'] ?? 'Onbekende fout'); $messageType = 'error'; } } } // 6. Sessie- & Beveiligingsinstellingen opslaan (ISO 27001 / NIST SP 800-63B) elseif ($action === 'save_security_settings') { $activeTab = 'security'; $timeout = (int)($_POST['session_idle_timeout'] ?? 1800); if (!in_array($timeout, [900, 1800, 3600, 7200], true)) { $timeout = 1800; } set_system_setting('session_idle_timeout', $timeout); fociz_log_audit($userId, 'SETTINGS_UPDATED', 'security_settings', null, ['session_idle_timeout' => $timeout], 'SUCCESS', $adminUser['email']); $message = "Sessie- en beveiligingsinstellingen zijn succesvol opgeslagen."; $messageType = 'success'; } } } // Haal actuele configuratie op $appConfig = microsoft_get_app_config(); $companyJiraDomain = jira_get_company_domain(); $workdaySettings = get_workday_settings(); $aiSettings = get_ai_settings(); $aiUsageStats = get_ai_usage_stats(); $sessionIdleTimeout = get_session_idle_timeout(); $calendarBlockReasons = get_calendar_block_reasons(); $brainstormCategories = get_brainstorm_categories(); // Redirect URI $redirectUri = rtrim(APP_URL, '/') . '/auth/callback_microsoft.php'; // Admin Consent URL $tenantForConsent = !empty($appConfig['tenant_id']) ? $appConfig['tenant_id'] : 'common'; $adminConsentUrl = "https://login.microsoftonline.com/" . urlencode($tenantForConsent) . "/adminconsent?client_id=" . urlencode($appConfig['client_id']) . "&redirect_uri=" . urlencode($redirectUri); // Statistieken ophalen voor Systeem & Team Monitor $totalUsers = 0; $totalAdmins = 0; $totalMembers = 0; $pendingInvites = 0; $outlookConnectedCount = 0; $jiraConnectedCount = 0; $recentUsers = []; try { $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()); } try { $pendingInvites = (int)$db->query("SELECT COUNT(*) FROM invites WHERE used_at IS NULL AND expires_at > NOW()")->fetchColumn(); } catch (Throwable $e) { error_log("Fout bij ophalen invites stats: " . $e->getMessage()); ensure_tables_exist($db); } try { $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()); } $allUsersList = []; try { $allUsersList = $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.name ASC ")->fetchAll() ?: []; } catch (Throwable $e) { error_log("Fout bij ophalen allUsersList: " . $e->getMessage()); } $allUsersForJira = []; $allJiraFilters = []; try { $allUsersForJira = $db->query("SELECT id, name, email, role, avatar_url, default_jira_filter_code, default_jira_filter_codes FROM users ORDER BY name ASC")->fetchAll() ?: []; $allJiraFilters = $db->query("SELECT * FROM jira_queue_filters ORDER BY sort_order ASC, id ASC")->fetchAll() ?: []; } catch (Throwable $e) { error_log("Fout bij ophalen jira filters/users: " . $e->getMessage()); } $auditLogs = []; try { $auditLogs = $db->query("SELECT * FROM audit_logs ORDER BY created_at DESC LIMIT 50")->fetchAll() ?: []; } catch (Throwable $e) { error_log("Fout bij ophalen audit logs: " . $e->getMessage()); } $pageTitle = 'Systeembeheer & Integraties — Fociz'; require_once __DIR__ . '/includes/header.php'; ?>
Centrale organisatiekoppelingen (Microsoft 365 & Jira), beveiligingsinstellingen en teamstatus.
Centraal overzicht van alle teamaccounts, beheerdersrollen en gekoppelde integraties
| Medewerker | E-mailadres | Rol | Outlook 365 | Jira | Geregistreerd | Beheer |
|---|---|---|---|---|---|---|
|
= strtoupper(substr($u['name'], 0, 1)) ?>
= htmlspecialchars($u['name']) ?>
|
= htmlspecialchars($u['email']) ?> | Beheerder Medewerker | Gekoppeld Niet gekoppeld | Gekoppeld Niet gekoppeld | = date('d-m-Y', strtotime($u['created_at'])) ?> | Beheren |