prepare("SELECT settings FROM integrations WHERE user_id = ? AND provider = 'OUTLOOK'"); $stmtExisting->execute([$userId]); $existing = $stmtExisting->fetch(); $settings = json_decode($existing['settings'] ?? '{}', true) ?: []; $settings['sync_interval_seconds'] = $syncInterval; $stmtUp = $db->prepare("UPDATE integrations SET settings = ? WHERE user_id = ? AND provider = 'OUTLOOK'"); $stmtUp->execute([json_encode($settings), $userId]); $message = "Uw Microsoft Outlook synchronisatievoorkeur is succesvol opgeslagen."; $messageType = 'success'; } // 3. Jira Opslaan & Testen elseif ($action === 'save_jira') { $companyDomain = jira_get_company_domain(); $rawDomain = trim($_POST['jira_domain'] ?? ''); $jiraDomain = !empty($rawDomain) ? jira_normalize_domain($rawDomain) : $companyDomain; $jiraEmail = trim($_POST['jira_email'] ?? ''); $jiraToken = trim($_POST['jira_api_token'] ?? ''); // Haal bestaande instellingen op $stmtExisting = $db->prepare("SELECT * FROM integrations WHERE user_id = ? AND provider = 'JIRA'"); $stmtExisting->execute([$userId]); $existing = $stmtExisting->fetch(); $existingSettings = json_decode($existing['settings'] ?? '{}', true) ?: []; if (($jiraToken === '' || $jiraToken === '••••••••••••••••') && !empty($existingSettings['jira_api_token']) && $existingSettings['jira_api_token'] !== '••••••••••••••••') { $jiraToken = fociz_decrypt($existingSettings['jira_api_token']); } $accountName = $existingSettings['account_name'] ?? ''; $accountId = $existingSettings['account_id'] ?? ''; if (!empty($jiraDomain) && !empty($jiraEmail) && !empty($jiraToken)) { $testRes = jira_test_connection($jiraDomain, $jiraEmail, $jiraToken); if (!$testRes['success']) { $message = "Jira Verbindingsfout: " . $testRes['error']; $messageType = 'error'; } else { $accountName = $testRes['user']['displayName'] ?? 'Jira Gebruiker'; $accountId = $testRes['user']['accountId'] ?? ''; $message = "Verbinding met Jira geslaagd! Ingelogd als: {$accountName}"; $messageType = 'success'; } } else { $message = "Jira instellingen opgeslagen."; $messageType = 'success'; } $newSettings = [ 'jira_domain' => $jiraDomain, 'jira_email' => $jiraEmail, 'jira_api_token' => fociz_encrypt($jiraToken), 'account_name' => $accountName, 'account_id' => $accountId, ]; $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 is_demo_mode = 0, settings = VALUES(settings), last_sync_at = NOW() "); $stmt->execute([$userId, json_encode($newSettings)]); unset($_SESSION["jira_issues_user_{$userId}"]); } // 4. Jira ontkoppelen elseif ($action === 'disconnect_jira') { jira_disconnect($userId); header('Location: /profile.php?success=' . urlencode('Atlassian Jira is succesvol ontkoppeld.')); exit; } // 4b. Persoonlijke Google Gemini / AI instellingen opslaan elseif ($action === 'save_personal_ai') { $aiEnabled = isset($_POST['ai_enabled']) ? 1 : 0; $aiProvider = trim($_POST['ai_provider'] ?? 'gemini'); $aiModel = trim($_POST['ai_model'] ?? 'gemini-2.0-flash'); $apiKeyIn = trim($_POST['ai_api_key'] ?? ''); // Haal eventueel bestaande integratie op $stmtEx = $db->prepare("SELECT id, access_token, settings FROM integrations WHERE user_id = ? AND provider IN ('GEMINI', 'AI') LIMIT 1"); $stmtEx->execute([$userId]); $existing = $stmtEx->fetch(); $tokenToSave = $existing['access_token'] ?? null; if ($apiKeyIn !== '' && $apiKeyIn !== '••••••••••••••••') { $tokenToSave = fociz_encrypt($apiKeyIn); } $settingsJson = json_encode([ 'provider' => $aiProvider, 'model' => $aiModel, ]); if ($existing) { $stmtUp = $db->prepare("UPDATE integrations SET provider = 'GEMINI', is_enabled = ?, access_token = ?, settings = ?, last_sync_at = NOW() WHERE id = ?"); $stmtUp->execute([$aiEnabled, $tokenToSave, $settingsJson, $existing['id']]); } else { $stmtIn = $db->prepare("INSERT INTO integrations (user_id, provider, is_enabled, access_token, settings, last_sync_at) VALUES (?, 'GEMINI', ?, ?, ?, NOW())"); $stmtIn->execute([$userId, $aiEnabled, $tokenToSave, $settingsJson]); } $message = "Uw persoonlijke AI instellingen zijn succesvol opgeslagen."; $messageType = 'success'; } // 4c. Persoonlijke AI sleutel ontkoppelen / verwijderen elseif ($action === 'disconnect_personal_ai') { $stmtDel = $db->prepare("DELETE FROM integrations WHERE user_id = ? AND provider IN ('GEMINI', 'AI')"); $stmtDel->execute([$userId]); $message = "Uw persoonlijke AI sleutel is ontkoppeld. Fociz gebruikt nu de centrale instellingen van de organisatie."; $messageType = 'success'; } // 5. Notificatievoorkeuren opslaan elseif ($action === 'save_notifications') { $settings = [ 'enabled' => isset($_POST['notif_enabled']) ? 1 : 0, 'lead_time_minutes' => (int)($_POST['lead_time_minutes'] ?? 5), 'repeat_count' => (int)($_POST['repeat_count'] ?? 2), 'repeat_interval_minutes' => (int)($_POST['repeat_interval_minutes'] ?? 2), 'sound_enabled' => isset($_POST['sound_enabled']) ? 1 : 0, ]; update_user_notification_settings($userId, $settings); $message = "Uw persoonlijke notificatievoorkeuren zijn succesvol opgeslagen."; $messageType = 'success'; } // 6. Profielnaam bijwerken elseif ($action === 'update_profile') { $name = trim($_POST['name'] ?? ''); if (strlen($name) < 2) { $message = "Voer een geldige naam in van minimaal 2 tekens."; $messageType = 'error'; } else { $stmt = $db->prepare("UPDATE users SET name = ? WHERE id = ?"); $stmt->execute([$name, $userId]); $_SESSION['user_name'] = $name; $user['name'] = $name; $message = "Uw profielnaam is succesvol bijgewerkt."; $messageType = 'success'; } } // 7. Wachtwoord wijzigen elseif ($action === 'change_password') { $currentPassword = $_POST['current_password'] ?? ''; $newPassword = $_POST['new_password'] ?? ''; $confirmPassword = $_POST['confirm_password'] ?? ''; $stmtU = $db->prepare("SELECT password_hash FROM users WHERE id = ?"); $stmtU->execute([$userId]); $storedHash = $stmtU->fetchColumn(); if (!password_verify($currentPassword, $storedHash)) { $message = "Uw huidige wachtwoord is niet correct ingevoerd."; $messageType = 'error'; } elseif (($pwErr = fociz_validate_password($newPassword)) !== null) { $message = $pwErr; $messageType = 'error'; } elseif ($newPassword !== $confirmPassword) { $message = "Het nieuwe wachtwoord en de bevestiging komen niet overeen."; $messageType = 'error'; $newHash = password_hash($newPassword, PASSWORD_DEFAULT); $stmtUp = $db->prepare("UPDATE users SET password_hash = ? WHERE id = ?"); $stmtUp->execute([$newHash, $userId]); // SOC 2 CC6.1 / ISO 27001 A.9.4.2: Trek overige sessies in bij wachtwoordwijziging fociz_revoke_other_sessions($userId, session_id()); fociz_log_audit($userId, 'PASSWORD_CHANGED', 'user', $userId, ['method' => 'profile'], 'SUCCESS', $user['email']); $message = "Uw wachtwoord is succesvol gewijzigd. Alle overige sessies op andere apparaten zijn uit veiligheidsoverwegingen beëindigd."; $messageType = 'success'; } } // 7b. Alle overige sessies op afstand beëindigen (SOC 2 / ISO 27001) elseif ($action === 'revoke_other_sessions') { $count = fociz_revoke_other_sessions($userId, session_id()); $message = "Succesvol {$count} andere actieve sessie(s) op afstand beëindigd."; $messageType = 'success'; } // 8. Standaard avatar kiezen uit bibliotheek elseif ($action === 'select_preset_avatar') { $preset = trim($_POST['avatar_preset'] ?? ''); if (preg_match('/^\/assets\/avatars\/avatar-(0[1-9]|1[0-9]|2[0-4])\.svg$/', $preset)) { $stmtUp = $db->prepare("UPDATE users SET avatar_url = ? WHERE id = ?"); $stmtUp->execute([$preset, $userId]); $_SESSION['user_avatar_url'] = $preset; $user['avatar_url'] = $preset; $message = "Uw avatar is succesvol bijgewerkt!"; $messageType = 'success'; } else { $message = "Ongeldige avatar geselecteerd."; $messageType = 'error'; } } // 9. Eigen profielfoto uploaden elseif ($action === 'upload_custom_avatar') { if (!isset($_FILES['avatar_file']) || $_FILES['avatar_file']['error'] !== UPLOAD_ERR_OK) { $message = "Selecteer een geldige afbeelding om te uploaden."; $messageType = 'error'; } else { $file = $_FILES['avatar_file']; $maxSize = 4 * 1024 * 1024; // 4MB if ($file['size'] > $maxSize) { $message = "De afbeelding is te groot (maximaal 4MB toegestaan)."; $messageType = 'error'; } else { $processed = fociz_process_avatar_upload($file, $userId); if (!$processed['success']) { $message = $processed['error'] ?? "Fout bij verwerken van de afbeelding."; $messageType = 'error'; } else { $avatarUri = $processed['data_uri']; $stmtUp = $db->prepare("UPDATE users SET avatar_url = ? WHERE id = ?"); $stmtUp->execute([$avatarUri, $userId]); $_SESSION['user_avatar_url'] = $avatarUri; $user['avatar_url'] = $avatarUri; $message = "Uw profielfoto is succesvol geüpload en permanent bewaard!"; $messageType = 'success'; } } } } // 10. Avatar verwijderen elseif ($action === 'remove_avatar') { $stmtUp = $db->prepare("UPDATE users SET avatar_url = NULL WHERE id = ?"); $stmtUp->execute([$userId]); unset($_SESSION['user_avatar_url']); $user['avatar_url'] = null; $message = "Uw avatar is teruggezet naar de standaard weergave met uw initialen."; $messageType = 'success'; } } } // Actuele gegevens ophalen $stmtUser = $db->prepare("SELECT * FROM users WHERE id = ?"); $stmtUser->execute([$userId]); $userProfile = $stmtUser->fetch(); if ($userProfile) { $userProfile['avatar_url'] = fociz_get_valid_avatar_url($userProfile['avatar_url'] ?? null, $userId); } // Outlook status ophalen $stmtOutlook = $db->prepare("SELECT * FROM integrations WHERE user_id = ? AND provider = 'OUTLOOK'"); $stmtOutlook->execute([$userId]); $outlookRow = $stmtOutlook->fetch(); $isOutlookConnected = !empty($outlookRow['access_token']) && empty($outlookRow['is_demo_mode']); $outlookSettings = json_decode($outlookRow['settings'] ?? '{}', true) ?: []; // Jira status ophalen $stmtJira = $db->prepare("SELECT * FROM integrations WHERE user_id = ? AND provider = 'JIRA'"); $stmtJira->execute([$userId]); $jiraRow = $stmtJira->fetch(); $jiraSettings = json_decode($jiraRow['settings'] ?? '{}', true) ?: []; $isJiraConnected = !empty($jiraSettings['jira_api_token']); $companyJiraDomain = jira_get_company_domain(); $activeJiraDomain = !empty($jiraSettings['jira_domain']) ? $jiraSettings['jira_domain'] : $companyJiraDomain; // Centrale Azure App configuratie status $appConfig = microsoft_get_app_config(); // Notificatie-instellingen $notifSettings = get_user_notification_settings($userId); // AI Co-Pilot & Google Gemini instellingen voor gebruiker $userAiSettings = get_ai_settings($userId); $stmtAiInt = $db->prepare("SELECT id, access_token, is_enabled, settings FROM integrations WHERE user_id = ? AND provider IN ('GEMINI', 'AI') LIMIT 1"); $stmtAiInt->execute([$userId]); $userAiIntRow = $stmtAiInt->fetch(); $hasPersonalAiKey = !empty($userAiIntRow['access_token']); $userAiRowSettings = json_decode($userAiIntRow['settings'] ?? '{}', true) ?: []; // Thema ophalen $userTheme = get_user_theme($userId); // Actieve apparaten & sessies ophalen (SOC 2 / ISO 27001) $activeSessions = fociz_get_user_active_sessions($userId, session_id()); $pageTitle = 'Mijn Profiel & Koppelingen — Fociz'; require_once __DIR__ . '/includes/header.php'; ?>
= htmlspecialchars($userProfile['email']) ?> • Lid sinds = date('d-m-Y', strtotime($userProfile['created_at'])) ?>
Kies uw gewenste uiterlijk: rustig donker grafiet, kraakhelder licht of synchroniseer met uw apparaat.
Kies een mini-avatar uit de 24 stijlvolle karakters of upload een eigen afbeelding.
Synchroniseer uw agenda-afspraken en zakelijke Outlook e-mails met Fociz
Krijg uw dagelijkse afspraken automatisch te zien in Fociz en ontvang herinneringen vóórdat uw vergaderingen beginnen.
Importeer uw toegewezen Jira issues direct naar uw dagelijkse takenlijst
Koppel uw eigen Google Gemini Pro account of gebruik de centrale organisatieconfiguratie
Uw gevoelige gegevens uit Jira tickets, Outlook e-mails en Microsoft Teams worden strikt afgeschermd en NOOIT naar Google Gemini of OpenAI verstuurd.
Krijg realtime meldingen vanuit uw browser zodra een afspraak nadert
Uw Fociz account is optimaal beveiligd met een Authenticator app (TOTP) voor maximale privacy- en dataveiligheid.
Centraal overzicht van alle actieve inlogapparaten conform ISO 27001 en SOC 2 richtlijnen (maximaal 20 actieve sessies).