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)]); $message = "Centrale Microsoft 365 Azure App instellingen zijn succesvol opgeslagen."; $messageType = 'success'; } // 2. Centraal Jira Bedrijfsdomein opslaan elseif ($action === 'save_jira_domain') { $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)]); $message = "Standaard Atlassian Jira bedrijfsdomein is succesvol opgeslagen."; $messageType = 'success'; } // 2b. Jira Wachtrijfilter toevoegen of bewerken elseif ($action === 'save_jira_queue_filter') { $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."; } $messageType = 'success'; } } // 2c. Jira Wachtrijfilter verwijderen elseif ($action === 'delete_jira_queue_filter') { $filterId = (int)($_POST['filter_id'] ?? 0); if ($filterId > 0) { $stmt = $db->prepare("DELETE FROM jira_queue_filters WHERE id = ?"); $stmt->execute([$filterId]); $message = "Wachtrijfilter is succesvol verwijderd."; $messageType = 'success'; } } // 2d. Teamleden toewijzen aan Jira Wachtrijfilter elseif ($action === 'save_user_jira_assignments') { $assignments = $_POST['user_filter'] ?? []; if (is_array($assignments)) { $upd = $db->prepare("UPDATE users SET default_jira_filter_code = ? WHERE id = ?"); foreach ($assignments as $uId => $fCode) { $fCode = trim($fCode); $upd->execute([$fCode !== '' ? $fCode : null, (int)$uId]); } $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') { $workdayStart = trim($_POST['workday_start'] ?? '08:30'); $workdayEnd = trim($_POST['workday_end'] ?? '17:30'); $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('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); $message = "Organisatie werkdagstandaarden zijn succesvol bijgewerkt."; $messageType = 'success'; } // 4. AI Co-Pilot instellingen opslaan (Release v1.5) elseif ($action === 'save_ai_settings') { $aiEnabled = isset($_POST['ai_enabled']) ? 1 : 0; $aiProvider = trim($_POST['ai_provider'] ?? 'openai'); $aiModel = trim($_POST['ai_model'] ?? 'gpt-4o-mini'); $apiKeyIn = trim($_POST['ai_api_key'] ?? ''); $currentAi = get_ai_settings(); if ($apiKeyIn !== '' && $apiKeyIn !== '••••••••••••••••') { set_system_setting('ai_api_key', $apiKeyIn); } set_system_setting('ai_enabled', $aiEnabled); set_system_setting('ai_provider', $aiProvider); set_system_setting('ai_model', $aiModel); $message = "AI Co-Pilot instellingen zijn succesvol opgeslagen."; $messageType = 'success'; } // 5. Test e-mail versturen via SMTP (Release v1.6) elseif ($action === 'send_test_email') { 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'; } } } } // Haal actuele configuratie op $appConfig = microsoft_get_app_config(); $companyJiraDomain = jira_get_company_domain(); $workdaySettings = get_workday_settings(); $aiSettings = get_ai_settings(); // 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()); } $allUsersForJira = []; $allJiraFilters = []; try { $allUsersForJira = $db->query("SELECT id, name, email, role, avatar_url, default_jira_filter_code 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()); } $pageTitle = 'Systeembeheer & Integraties — Fociz'; require_once __DIR__ . '/includes/header.php'; ?>
Beheer hier de centrale organisatiekoppelingen zoals Microsoft 365 Azure App Registratie en Jira, inspecteer de teamstatus en configureer toekomstige modules.
Centrale OAuth 2.0 instellingen voor de hele organisatie
= htmlspecialchars($redirectUri) ?>.
User.Read, Calendars.ReadWrite, Mail.ReadWrite en Mail.Send.
Standaard Jira Cloud domein voor het hele team
Beheer aangepaste JQL-wachtrijfilters (zoals DX en SD) en wijs teamleden toe aan hun standaard wachtrij.
Nog geen wachtrijfilters ingesteld. Klik op "Nieuw Wachtrijfilter" om te beginnen.
= htmlspecialchars($filter['description']) ?>
Wijs collega's toe aan hun primaire wachtrij (bijv. DX of SD). Wanneer zij de Jira pagina openen, wordt deze wachtrij direct als standaard getoond.
Centrale kantoortijden, lunchpauzes en Pomodoro presets voor het team
Intelligente assistentie voor post-it categorisatie, ideegeneratie en slimme planning
Uitgaande e-mailserver voor wachtwoord-vergeten, uitnodigingen en notificaties
Verstuur een realtime testmail met het Fociz HTML-sjabloon om de verbinding, STARTTLS-handshake en aflevering direct te verifiëren.