diff --git a/README.md b/README.md index ab3f850..558cd0d 100644 --- a/README.md +++ b/README.md @@ -56,13 +56,19 @@ In het bestand **`includes/env_badge.php`**: - Voortgangsbalken (0 - 100%) en deadlines. - Gekoppeld aan de oorspronkelijke brainstormkaarten en optioneel Jira Issue Keys (bijv. `FOC-101`). -4. **Integraties Hub (`/integrations.php`)**: - - **Microsoft Outlook**: Synchroniseert agenda-afspraken direct naar de dagplanning. - - **Microsoft Teams**: Directe deelnamelinks naar online meetings en kanaalnotificaties. - - **Atlassian Jira**: Issues synchroniseren en direct met één klik inplannen als dagtaak. - - **Sandbox / Demo Modus**: Schakel met één vinkje tussen live productie en interactieve testdata. +4. **Mijn Profiel & Persoonlijke Koppelingen (`/profile.php`)**: + - **Microsoft Outlook & Teams**: Koppel met 1 klik uw persoonlijke werkagenda voor dagelijkse afsprakensynchronisatie en directe Teams meeting links. + - **Atlassian Jira**: Koppel uw persoonlijke Atlassian API token om aan u toegewezen tickets in te zien en in te plannen. + - **Browser Push Meldingen**: Configureer persoonlijke waarschuwingen (vooraankondigingstijd, herhalingsfrequentie, audio chime en live test). + - **Account & Beveiliging**: Wachtwoord wijzigen en 2FA beveiligingsstatus. -5. **Team & Invites Beheer (`/users.php` - Admin only)**: +5. **Systeembeheer & Integraties Portaal (`/admin.php` - Admin only)**: + - Centrale Microsoft 365 Azure App Registratie (Client ID, Client Secret, Tenant ID, Admin Consent verlenen). + - Standaard Jira bedrijfsdomein voor alle teamleden. + - Systeem & Team Monitor (statistieken over actieve leden, agenda koppelingen en API-status). + - Extensibiliteit Hub voor toekomstige modules (AI Co-Pilot, werkdag standaarden). + +6. **Team & Invites Beheer (`/users.php` - Admin only)**: - Genereer beveiligde uitnodigingslinks voor collega's met kopieerknop. - Overzicht van actieve gebruikers, rollen en 2FA-status. diff --git a/admin.php b/admin.php new file mode 100644 index 0000000..c52e023 --- /dev/null +++ b/admin.php @@ -0,0 +1,592 @@ +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'; + } +} + +// Haal actuele configuratie op +$appConfig = microsoft_get_app_config(); +$companyJiraDomain = jira_get_company_domain(); + +// 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 = (int)$db->query("SELECT COUNT(*) FROM users")->fetchColumn(); +$totalAdmins = (int)$db->query("SELECT COUNT(*) FROM users WHERE role = 'ADMIN'")->fetchColumn(); +$totalMembers = $totalUsers - $totalAdmins; +$pendingInvites = (int)$db->query("SELECT COUNT(*) FROM invites WHERE used_at IS NULL AND expires_at > NOW()")->fetchColumn(); + +$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(); + +// Haal recente gebruikers op voor snelle inspectie +$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(); + +$pageTitle = 'Systeembeheer & Integraties — Fociz'; +require_once __DIR__ . '/includes/header.php'; +?> + +
+ +
+
+
+ +
+
+
+ + Beheerderspaneel • Alleen toegankelijk voor Beheer +
+

+ Systeembeheer & Integraties +

+

+ Beheer hier de centrale organisatiekoppelingen zoals Microsoft 365 Azure App Registratie en Jira, inspecteer de teamstatus en configureer toekomstige modules. +

+
+ + +
+
+ + + +
+ +
+
+ + + +
+
+
+ +
+
+ Totaal Teamleden + + Beheerders • Collega's +
+
+ +
+
+ +
+
+ Outlook Live Gekoppeld + + +
+
+ +
+
+ +
+
+ Jira Gekoppeld + + +
+
+ +
+
+ +
+
+ Openstaande Invites + + Beheer uitnodigingen → +
+
+
+ + +
+ + +
+ + +
+
+
+
+ +
+
+
+

Microsoft 365 & Azure App Registratie

+ + + Geconfigureerd + + + + Configuratie Vereist + + +
+

Centrale OAuth 2.0 instellingen voor de hele organisatie

+
+
+ + + + + Admin Consent Verlenen + + + +
+ +
+ + + +
+
+ + Kopieer dit exact naar de Azure Web Redirect URIs +
+
+ + +
+
+ + +
+
+ + +
+ +
+ + + Gebruik common of uw specifieke domeinnaam (bijv. planningsagenda.nl) +
+
+ + +
+
+ + Wordt veilig en versleuteld opgeslagen +
+
+ + +
+
+ +
+
+ Zodra opgeslagen kunnen al uw collega's met 1 klik hun Outlook agenda koppelen. +
+ +
+
+ + +
+ + + +
+
+ + +
+
+
+ +
+
+
+

Atlassian Jira Organisatie-instellingen

+ + + Actief + + + + Optioneel + + +
+

Standaard Jira Cloud domein voor het hele team

+
+
+ +
+ + +
+ + +

+ Wanneer u dit instelt, wordt dit domein automatisch vooraf ingevuld op de profielpagina van alle collega's. Zij hoeven dan enkel nog hun eigen persoonlijke e-mailadres en Atlassian API-token in te voeren. +

+
+ +
+ +
+
+
+ + +
+
+
+ +
+
+

Toekomstige Modules & Extensies

+

Voorbereid architectuurkader voor nieuwe bedrijfssystemen

+
+
+ +
+ +
+
+
+ + + AI & Brainstorm Co-Pilot + + + Voorbereid + +
+

+ Centrale OpenAI of Anthropic Claude API-sleutels instellen voor automatische categorisatie van sticky notes en taakschattingen. +

+
+
+ Gepland voor release v1.5 +
+
+ + +
+
+
+ + + Organisatie Werkdag Standaarden + + + Binnenkort + +
+

+ Standaard kantoortijden instellen (bijv. 08:30 - 17:00), standaard Pomodoro tijden (25m / 50m) en lunchpauze blokken. +

+
+
+ Gepland voor release v1.4 +
+
+
+
+
+ + +
+ + +
+
+
+ +

Team & Integratiestatus

+
+ + Alle gebruikers → + +
+ +
+ +
+
+
+ +
+
+ + +
+
+ +
+ + + + + + +
+
+ +
+ + +
+ + +
+
+ + Systeem & Beveiliging +
+
+
+ Tweestapsverificatie (2FA) + + + Verplicht + +
+ +
+ Database Versie + MariaDB 10.x +
+ +
+ Push Notificaties + Actief +
+ +
+ Graph API Sync + Live v1.0 +
+
+
+ +
+
+
+ + + + diff --git a/auth/callback_microsoft.php b/auth/callback_microsoft.php index e9aba2b..6087e3e 100644 --- a/auth/callback_microsoft.php +++ b/auth/callback_microsoft.php @@ -46,7 +46,7 @@ $db = get_db_connection(); // 3. Foutafhandeling vanuit Microsoft (bijv. gebruiker weigerde machtiging) if (!empty($_GET['error'])) { $errorDescription = $_GET['error_description'] ?? $_GET['error']; - header('Location: /integrations.php?error=' . urlencode("Microsoft melding: {$errorDescription}")); + header('Location: /profile.php?error=' . urlencode("Microsoft melding: {$errorDescription}")); exit; } @@ -55,20 +55,20 @@ $expectedState = $_SESSION['ms_oauth_state'] ?? ''; unset($_SESSION['ms_oauth_state'], $_SESSION['ms_oauth_user_id']); if (!$stateUserId && (empty($receivedState) || empty($expectedState) || !hash_equals($expectedState, $receivedState))) { - header('Location: /integrations.php?error=' . urlencode('Ongeldige sessie of CSRF-controle mislukt. Probeer opnieuw in te loggen via Microsoft.')); + header('Location: /profile.php?error=' . urlencode('Ongeldige sessie of CSRF-controle mislukt. Probeer opnieuw in te loggen via Microsoft.')); exit; } // Afhandeling van beheerderstoestemming (Admin Consent) if (!empty($_GET['admin_consent'])) { - header('Location: /integrations.php?success=' . urlencode('Beheerdersgoedkeuring voor uw organisatie is succesvol verleend! Klik nu op "Koppel Outlook" om uw agenda te synchroniseren.')); + header('Location: /admin.php?success=' . urlencode('Beheerdersgoedkeuring voor uw organisatie is succesvol verleend! U en uw collega\'s kunnen nu direct hun Outlook agenda koppelen via Mijn Profiel.')); exit; } // 5. Autorisatiecode controleren $code = $_GET['code'] ?? ''; if (empty($code)) { - header('Location: /integrations.php?error=' . urlencode('Geen autorisatiecode ontvangen van Microsoft.')); + header('Location: /profile.php?error=' . urlencode('Geen autorisatiecode ontvangen van Microsoft.')); exit; } @@ -80,7 +80,7 @@ $clientSecret = $appConfig['client_secret']; $tenantId = $appConfig['tenant_id']; if (empty($clientId) || empty($clientSecret)) { - header('Location: /integrations.php?error=' . urlencode('Microsoft 365 configuratie (Client ID of Secret) ontbreekt in Fociz. Neem contact op met de beheerder.')); + header('Location: /profile.php?error=' . urlencode('Microsoft 365 configuratie (Client ID of Secret) ontbreekt in Fociz. Neem contact op met de beheerder.')); exit; } @@ -90,7 +90,7 @@ $redirectUri = rtrim(APP_URL, '/') . '/auth/callback_microsoft.php'; $tokenResult = microsoft_exchange_code($code, $clientId, $clientSecret, $redirectUri, $tenantId); if (!$tokenResult['success']) { - header('Location: /integrations.php?error=' . urlencode('Token uitwisseling mislukt: ' . $tokenResult['error'])); + header('Location: /profile.php?error=' . urlencode('Token uitwisseling mislukt: ' . $tokenResult['error'])); exit; } @@ -136,7 +136,7 @@ $saveStmt = $db->prepare(" "); $saveStmt->execute([$user['id'], $accessToken, $refreshToken, json_encode($newSettings)]); -// 8. Succesvol terugkeren naar de Integraties Hub -header('Location: /integrations.php?success=' . urlencode("Microsoft Outlook succesvol gekoppeld als {$accountEmail}!")); +// 8. Succesvol terugkeren naar de Persoonlijke Profiel Hub +header('Location: /profile.php?success=' . urlencode("Microsoft Outlook succesvol gekoppeld als {$accountEmail}!")); exit; diff --git a/auth/oauth_microsoft.php b/auth/oauth_microsoft.php index a0c6ecc..f30a180 100644 --- a/auth/oauth_microsoft.php +++ b/auth/oauth_microsoft.php @@ -17,9 +17,9 @@ $tenantId = !empty($_GET['tenant_id']) ? trim($_GET['tenant_id']) : $appConfig[' if (empty($clientId)) { $err = ($user['role'] === 'ADMIN') - ? 'Vul eerst de Application (Client) ID en het Client Secret in bij de Outlook instellingen om te koppelen met Microsoft 365.' + ? 'Vul eerst de Application (Client) ID en het Client Secret in bij het Systeembeheer om te koppelen met Microsoft 365.' : 'Microsoft 365 is nog niet geconfigureerd door de beheerder van uw organisatie. Vraag uw beheerder om de koppeling in te stellen.'; - header('Location: /integrations.php?error=' . urlencode($err)); + header('Location: /profile.php?error=' . urlencode($err)); exit; } diff --git a/includes/header.php b/includes/header.php index 69a99e2..71b141a 100644 --- a/includes/header.php +++ b/includes/header.php @@ -82,14 +82,18 @@ require_once __DIR__ . '/env_badge.php'; Roadmap - - - Integraties + + + Mijn Profiel - Team & Invites + Team + + + + Beheer @@ -107,12 +111,15 @@ require_once __DIR__ . '/env_badge.php'; 2FA Actief - @@ -134,18 +141,18 @@ require_once __DIR__ . '/env_badge.php'; Roadmap - - - Koppelingen + + + Profiel - - - Team + + + Beheer diff --git a/includes/integrations/jira.php b/includes/integrations/jira.php index 1a94bed..57b8240 100644 --- a/includes/integrations/jira.php +++ b/includes/integrations/jira.php @@ -18,6 +18,51 @@ function jira_normalize_domain(string $domain): string { return rtrim($domain, '/'); } +/** + * Haalt het centrale bedrijfsdomein voor Atlassian Jira op + */ +function jira_get_company_domain(): string { + $domain = fociz_env('JIRA_DOMAIN', ''); + if (!empty($domain)) { + return jira_normalize_domain($domain); + } + + try { + $db = get_db_connection(); + // Zoek bij beheerder(s) in de integrations tabel + $stmt = $db->query(" + SELECT i.settings + FROM integrations i + JOIN users u ON i.user_id = u.id + WHERE u.role = 'ADMIN' AND i.provider = 'JIRA' AND i.settings IS NOT NULL + ORDER BY i.updated_at DESC + LIMIT 1 + "); + $row = $stmt->fetch(); + if (!empty($row['settings'])) { + $settings = json_decode($row['settings'], true) ?: []; + if (!empty($settings['jira_domain'])) { + return jira_normalize_domain($settings['jira_domain']); + } + } + + // Fallback naar een willekeurige JIRA rij + $fallback = $db->query(" + SELECT settings FROM integrations WHERE provider = 'JIRA' AND settings LIKE '%jira_domain%' LIMIT 1 + ")->fetch(); + if (!empty($fallback['settings'])) { + $settings = json_decode($fallback['settings'], true) ?: []; + if (!empty($settings['jira_domain'])) { + return jira_normalize_domain($settings['jira_domain']); + } + } + } catch (Throwable $e) { + error_log("jira_get_company_domain error: " . $e->getMessage()); + } + + return ''; +} + /** * Genereert de Basic Authentication header voor Atlassian REST API */ @@ -240,7 +285,7 @@ function jira_get_user_issues(int $userId, bool $forceRefresh = false): array { } $settings = json_decode($integration['settings'] ?? '{}', true) ?: []; - $domain = $settings['jira_domain'] ?? fociz_env('JIRA_DOMAIN', ''); + $domain = !empty($settings['jira_domain']) ? $settings['jira_domain'] : jira_get_company_domain(); $email = $settings['jira_email'] ?? fociz_env('JIRA_EMAIL', ''); $apiToken = $settings['jira_api_token'] ?? fociz_env('JIRA_API_TOKEN', ''); diff --git a/integrations.php b/integrations.php index c95a988..9af5d24 100644 --- a/integrations.php +++ b/integrations.php @@ -1,925 +1,19 @@ 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)]); - - if (empty($clientId)) { - $message = "Vul eerst de Application (Client) ID in van uw Azure App Registratie."; - $messageType = 'error'; - } elseif (empty($clientSecret)) { - $message = "Vul eerst het Client Secret in van uw Azure App Registratie."; - $messageType = 'error'; - } else { - header("Location: /auth/oauth_microsoft.php"); - exit; - } - } else { - // Collega of Beheerder zonder inline velden - if (!$appConfig['is_configured']) { - $message = "Microsoft 365 is nog niet geconfigureerd door de beheerder. Vraag een beheerder om de Application ID en Client Secret in te stellen."; - $messageType = 'error'; - } else { - header("Location: /auth/oauth_microsoft.php"); - exit; - } - } - } - - // B. Alleen Beheerders mogen de centrale Azure instellingen opslaan - elseif ($action === 'save') { - if (!$isAdmin) { - $message = "Alleen beheerders kunnen centrale integratie-instellingen wijzigen."; - $messageType = 'error'; - } else { - $clientId = trim($_POST['client_id'] ?? ''); - $clientSecret = trim($_POST['client_secret'] ?? ''); - $tenantId = trim($_POST['tenant_id'] ?? 'common'); - if (empty($tenantId)) $tenantId = 'common'; - $isDemo = isset($_POST['is_demo_mode']) ? 1 : 0; - - $stmtExisting = $db->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 instellingen voor Microsoft Outlook zijn succesvol opgeslagen."; - $messageType = 'success'; - } - } - } - - // 3. Teams Opslaan - elseif ($provider === 'TEAMS') { - $isDemo = isset($_POST['is_demo_mode']) ? 1 : 0; - $stmt = $db->prepare(" - INSERT INTO integrations (user_id, provider, is_enabled, is_demo_mode, last_sync_at) - VALUES (?, 'TEAMS', 1, ?, NOW()) - ON DUPLICATE KEY UPDATE is_demo_mode = VALUES(is_demo_mode), last_sync_at = NOW() - "); - $stmt->execute([$userId, $isDemo]); - $message = "Instellingen voor Microsoft Teams zijn bijgewerkt."; - } - - // 4. Jira Opslaan & Testen - elseif ($provider === 'JIRA') { - $jiraDomain = jira_normalize_domain($_POST['jira_domain'] ?? ''); - $jiraEmail = trim($_POST['jira_email'] ?? ''); - $jiraToken = trim($_POST['jira_api_token'] ?? ''); - $isDemo = isset($_POST['is_demo_mode']) ? 1 : 0; - - // 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 = $existingSettings['jira_api_token']; - } - - // Als niet in demo mode, test verbinding - $accountName = $existingSettings['account_name'] ?? ''; - $accountId = $existingSettings['account_id'] ?? ''; - if (!$isDemo && !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' => $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, ?, ?, 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)]); - - // Wis cache - unset($_SESSION["jira_issues_user_{$userId}"]); - } -} - -// Laad nu pas de HTML header in -require_once __DIR__ . '/includes/header.php'; - -// Haal huidige configuraties op -$stmtConfigs = $db->prepare("SELECT * FROM integrations WHERE user_id = ?"); -$stmtConfigs->execute([$userId]); -$configsRaw = $stmtConfigs->fetchAll(); -$configs = []; -foreach ($configsRaw as $c) { - $configs[$c['provider']] = $c; - $configs[$c['provider']]['settings_parsed'] = json_decode($c['settings'] ?? '{}', true) ?: []; -} - -// Bepaal statussen -$isOutlookConnected = !empty($configs['OUTLOOK']['access_token']) && empty($configs['OUTLOOK']['is_demo_mode']); -$outlookSettings = $configs['OUTLOOK']['settings_parsed'] ?? []; - -$isJiraConnected = !empty($configs['JIRA']) && empty($configs['JIRA']['is_demo_mode']) && !empty($configs['JIRA']['settings_parsed']['jira_api_token']); -$jiraSettings = $configs['JIRA']['settings_parsed'] ?? []; - -// Haal actuele Jira issues op -$forceRefresh = isset($_GET['refresh_jira']); -$jiraData = jira_get_user_issues($userId, $forceRefresh); -$jiraIssues = $jiraData['issues'] ?? []; -$jiraIsDemo = !empty($jiraData['is_demo']); - -// Callback redirect URI -$redirectUri = rtrim(APP_URL, '/') . '/auth/callback_microsoft.php'; - -$appConfig = microsoft_get_app_config(); -$isAdmin = ($user['role'] === 'ADMIN'); - -// Voor beheerders tonen we eventueel centraal opgeslagen waarden als fallback in de inputvelden -if ($isAdmin) { - if (empty($outlookSettings['client_id']) && !empty($appConfig['client_id'])) { - $outlookSettings['client_id'] = $appConfig['client_id']; - } - if (empty($outlookSettings['client_secret']) && !empty($appConfig['client_secret'])) { - $outlookSettings['client_secret'] = $appConfig['client_secret']; - } - if (empty($outlookSettings['tenant_id']) && !empty($appConfig['tenant_id'])) { - $outlookSettings['tenant_id'] = $appConfig['tenant_id']; - } -} -?> - -
- -
-
-
-
- - Live Koppelingen & Data Synchronisatie -
-

- Integraties Hub -

-

- -

-
- - -
- - - -
- - Outlook: -
-
- - Jira: -
-
-
-
- - - -
-
- - -
- -
- - - -
- - -
-
-
-
- -
- - - - Live Verbonden - - - - Sandbox Modus - - - - Niet Gekoppeld - - -
- -
-

Microsoft Outlook (365)

-

- Synchroniseert automatisch uw zakelijke agenda-afspraken en meetings naar de dagplanner tijdlijn. -

-
- - -
-
- Account: - - - -
-
- Naam: - -
-
- Laatste Sync: - -
-
- - - - -
- -
- Centrale Beheerdersinstellingen - Deze Azure App gegevens gelden voor alle collega's in de organisatie. Gewone gebruikers zien deze technische details niet en hoeven alleen op 'Koppelen' te klikken. -
-
- - -
- - - - Azure App Registratie Instructies - - - -
-

1. Open Azure App Registraties en maak een nieuwe registratie.

-

2. Kies Accounts in elke organisatiemap (...) en persoonlijke Microsoft-accounts.

-

3. Voeg een Web Omleidings-URI toe:

-
- - -
-

4. Maak een geheim aan onder Certificaten en geheimen.

-

5. Voeg API-machtigingen toe: Calendars.ReadWrite, User.Read, offline_access.

-

6. Cruciaal voor organisaties: Klik direct boven de tabel met machtigingen op de knop "Beheerdersgoedkeuring verlenen voor [Organisatie]" (Grant admin consent) en bevestig met 'Ja'. Hiermee voorkomt u de melding "Beheerdersgoedkeuring vereist".

- - - -
-
- - - -
- -
- Configuratie vereist door beheerder - - Uw beheerder heeft de Microsoft 365 integratie nog niet geconfigureerd voor uw organisatie. Vraag uw beheerder om de Azure App gegevens in te stellen. - -
-
- -
- -
- Gereed voor Koppeling - - Uw beheerder heeft Microsoft 365 klaargezet voor uw organisatie. Koppel uw persoonlijke werkagenda om al uw meetings en Teams-afspraken automatisch in uw dagplanning te laden. - -
-
- - -
- - - -
- - -
- - -

Te vinden op de Overzichtspagina (Overview) van uw Azure App Registratie.

-
- -
- -
- - -
-

Aangemaakt onder Certificaten en geheimen > Nieuw clientgeheim (kopieer de Waarde / Value).

-
- -
-
- - Standaard: common -
- -
-

- 💡 Wat moet hier staan? -

-

• Voor uw organisatie: vul uw domein in (bijv. planningsagenda.nl) of uw Tenant GUID.

-

• Voor multi-tenant apps: vul organizations of common in.

-
- - -
- -
- Krijgt u "Beheerdersgoedkeuring vereist"? - Stuur deze officiële Microsoft goedkeuringslink naar uw IT-beheerder om Fociz met 1 klik goed te keuren voor : -
- - -
- -
-
- -
- - - -
- - - -
- - - - -
- - -
- - - -
- - - -
- -
- - -
- -
- -
- - -
-
-
-
- -
- - - Inbegrepen bij Outlook - -
- -
-

Microsoft Teams

-

- Detecteert Teams vergaderingen automatisch vanuit uw Outlook agenda en toont directe 'Deelnemen' knoppen. -

-
- -
-
- Direct Deelnemen: - Ondersteund -
-
- Meeting detectie: - Via Graph API -
-
- Status: - -
-
- -
-

- Teams vergaderingen worden automatisch uitgelezen zodra Microsoft Outlook is gekoppeld. U hoeft voor Teams geen aparte koppeling te configureren! -

-
-
- -
- - - - - - -
-
- - -
-
-
-
- -
- - - - Live Verbonden - - - - Sandbox Modus - - - - Niet Gekoppeld - - -
- -
-

Atlassian Jira Cloud

-

- Koppelt uw Jira tickets rechtstreeks aan de Fociz dagplanning en roadmap. -

-
- - -
-
- Ingelogd als: - - - -
-
- Domein: - -
-
- Tickets Toegewezen: - open taken -
-
- - -
-
- Atlassian API Token nodig? - - Genereer Token - - -
-
-
- - -
- - -
- - -
- -
- - -
- -
- -
- - -
-
- - - -
- -
- - - - -
-
-
- - -
-
-
- -

- Jira Issues Toegewezen aan U -

- - Sandbox - - Live Jira - -
- -
- issues - - - Verversen - -
-
- - -
- Geen openstaande tickets gevonden die aan u zijn toegewezen in Jira. -
- -
- 'text-red-400 bg-red-500/10 border-red-500/20', - 'medium', 'gemiddeld' => 'text-amber-400 bg-amber-500/10 border-amber-500/20', - default => 'text-slate-400 bg-slate-800/60 border-slate-700', - }; - ?> -
-
-
-
- - - - - - -
- - - -
- -

- -

- -
- Type: - - Vervaldatum: - -
-
- -
- - - - -
- - - - - - - - -
-
-
- -
- -
-
- - - - +exit; diff --git a/planner.php b/planner.php index f4c0ce4..dccdbd5 100644 --- a/planner.php +++ b/planner.php @@ -137,7 +137,7 @@ $formattedDate = $dutchDays[date('w')] . ' ' . date('j') . ' ' . $dutchMonths[(i - + Koppel Outlook @@ -149,7 +149,7 @@ $formattedDate = $dutchDays[date('w')] . ' ' . date('j') . ' ' . $dutchMonths[(i Jira: tickets - + Koppel Jira @@ -353,7 +353,7 @@ $formattedDate = $dutchDays[date('w')] . ' ' . date('j') . ' ' . $dutchMonths[(i - + Beheren diff --git a/profile.php b/profile.php new file mode 100644 index 0000000..2527541 --- /dev/null +++ b/profile.php @@ -0,0 +1,702 @@ +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 = $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' => $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; + } + + // 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 (strlen($newPassword) < 8) { + $message = "Het nieuwe wachtwoord moet minimaal 8 tekens lang zijn."; + $messageType = 'error'; + } elseif ($newPassword !== $confirmPassword) { + $message = "Het nieuwe wachtwoord en de bevestiging komen niet overeen."; + $messageType = 'error'; + } else { + $newHash = password_hash($newPassword, PASSWORD_DEFAULT); + $stmtUp = $db->prepare("UPDATE users SET password_hash = ? WHERE id = ?"); + $stmtUp->execute([$newHash, $userId]); + $message = "Uw wachtwoord is succesvol gewijzigd."; + $messageType = 'success'; + } + } +} + +// Actuele gegevens ophalen +$stmtUser = $db->prepare("SELECT * FROM users WHERE id = ?"); +$stmtUser->execute([$userId]); +$userProfile = $stmtUser->fetch(); + +// 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); + +$pageTitle = 'Mijn Profiel & Koppelingen — Fociz'; +require_once __DIR__ . '/includes/header.php'; +?> + +
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+

+ +

+ + + + Beheerder + + + + + Collega + + + + + + 2FA Beveiligd + +
+ +

+ + • + Lid sinds +

+
+
+ + + + +
+
+ + + +
+ +
+
+ + + +
+
+
+
+ +
+
+
+

Mijn Microsoft 365 Agenda

+ + + + Live Verbonden + + + + Niet Gekoppeld + + +
+

Synchroniseer uw agenda-afspraken automatisch met uw dagplanning

+
+
+ + +
+ + Opnieuw Verbinden + +
+ + +
+
+ +
+ +
+ +
+
+ Gekoppeld Microsoft Account + + +
+ +
+ Microsoft Teams Koppeling + + + Automatische Meeting Links + + Vergaderlinks in uw agenda worden direct klikbaar in uw dagplanning +
+
+ +
+
+ + Uw agenda synchroniseert live. Afspraken van vandaag verschijnen direct in uw Dagplanning. +
+ + Naar Dagplanning → + +
+ + +
+
+ +
+
+

Verbind uw Microsoft Outlook Agenda

+

+ Krijg uw dagelijkse afspraken automatisch te zien in Fociz en ontvang herinneringen vóórdat uw vergaderingen beginnen. +

+
+ + +
+
+ + +
+ U wordt veilig doorgestuurd naar Microsoft om in te loggen +
+ +
+ + Microsoft 365 koppeling is nog niet geconfigureerd door de beheerder. + + Stel Azure in via Systeembeheer → + + Vraag uw beheerder om dit in te schakelen. + +
+ +
+ +
+
+ + +
+
+
+
+ +
+
+
+

Mijn Atlassian Jira Koppeling

+ + + Verbonden + + + + Niet Gekoppeld + + +
+

Importeer uw toegewezen Jira issues direct naar uw dagelijkse takenlijst

+
+
+ + +
+ + +
+ +
+ +
+ + +
+
+ + + Standaard ingevuld door beheerder indien beschikbaar +
+ +
+ + +
+
+ +
+
+ + + API Token Aanmaken + + +
+
+ + +
+
+ +
+ + + + Verbonden als: + + + Verbinding wordt automatisch gevalideerd bij het opslaan + + + +
+
+
+ + +
+
+
+ +
+
+

Mijn Meldingen & Agenda Herinneringen

+

Krijg realtime meldingen vanuit uw browser zodra een afspraak nadert

+
+
+ +
+ + + +
+
+ +
+ Toestemming van Browser Vereist + Klik op de knop om pushmeldingen in te schakelen voor Fociz. +
+
+ +
+ + + +
+ + +
+
+ Meldingen Actief + Automatisch waarschuwen bij naderende afspraken +
+ +
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+
+ +
+ Subtiel Geluidssignaal + Speel een zachte chime af bij binnenkomst van de herinnering +
+
+ +
+ +
+ +
+
+
+
+ + +
+ +
+
+ +

Persoonlijke Gegevens

+
+ +
+ + +
+ + +
+ +
+ + + E-mailadres is gekoppeld aan uw account en 2FA. +
+ +
+ +
+
+
+ + +
+
+ +

Wachtwoord Wijzigen

+
+ +
+ + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+
+
+
+ + + + diff --git a/roadmap.php b/roadmap.php index c109df3..cf4f252 100644 --- a/roadmap.php +++ b/roadmap.php @@ -118,7 +118,7 @@ $quarters = ['Q1', 'Q2', 'Q3', 'Q4']; - +