feat(integrations): add role-based security and 1-click Outlook connect for colleagues

This commit is contained in:
2026-09-08 20:39:33 +02:00
parent 09cc85fcc1
commit 8cb7bd8acd
4 changed files with 450 additions and 202 deletions
+310 -171
View File
@@ -43,54 +43,106 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// 2. Outlook Opslaan of Koppelen
elseif ($provider === 'OUTLOOK') {
$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;
$isAdmin = ($user['role'] === 'ADMIN');
$appConfig = microsoft_get_app_config();
// Haal bestaande tokens op
$stmtExisting = $db->prepare("SELECT * FROM integrations WHERE user_id = ? AND provider = 'OUTLOOK'");
$stmtExisting->execute([$userId]);
$existing = $stmtExisting->fetch();
$existingSettings = json_decode($existing['settings'] ?? '{}', true) ?: [];
// A. Koppelen via OAuth (zowel voor Collega als Beheerder)
if ($action === 'connect_oauth') {
// Als de beheerder vanaf het admin-formulier koppelt en eventueel gewijzigde velden meestuurt
if ($isAdmin && isset($_POST['client_id'])) {
$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;
// Als geheim veld leeg is gelaten of bullet tekens bevat, behoud het bestaande geheim
if (($clientSecret === '' || $clientSecret === '••••••••••••••••') && !empty($existingSettings['client_secret']) && $existingSettings['client_secret'] !== '••••••••••••••••') {
$clientSecret = $existingSettings['client_secret'];
$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)]);
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;
}
}
}
$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)]);
// Als gebruiker direct op 'Koppel nu' klikte
if ($action === 'connect_oauth') {
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.";
// 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 {
header("Location: /auth/oauth_microsoft.php?client_id=" . urlencode($clientId) . "&tenant_id=" . urlencode($tenantId));
exit;
$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';
}
} else {
$message = "Instellingen voor Microsoft Outlook zijn succesvol opgeslagen.";
$messageType = 'success';
}
}
@@ -193,6 +245,22 @@ $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'];
}
}
?>
<div class="space-y-6">
@@ -208,12 +276,17 @@ $redirectUri = rtrim(APP_URL, '/') . '/auth/callback_microsoft.php';
Integraties Hub
</h1>
<p class="text-slate-400 text-xs sm:text-sm mt-1 max-w-2xl">
Koppel Microsoft 365 Outlook (voor realtime agenda & Teams meetings) en Atlassian Jira Cloud (om direct uw toegewezen tickets in te plannen).
<?= $isAdmin
? 'Beheer centrale organisatie-integraties en koppel uw persoonlijke Outlook agenda en Jira tickets.'
: 'Koppel uw persoonlijke Microsoft 365 Outlook agenda en Atlassian Jira tickets om direct uw dagplanning in Fociz te vullen.' ?>
</p>
</div>
<!-- Snelle status pills -->
<div class="flex items-center space-x-2">
<span class="px-2.5 py-1 rounded-xl text-xs font-semibold <?= $isAdmin ? 'bg-emerald-500/10 border border-emerald-500/30 text-emerald-400' : 'bg-slate-800/80 border border-slate-700 text-slate-300' ?>">
<?= $isAdmin ? 'Beheerdersweergave' : 'Collega Weergave' ?>
</span>
<div class="flex items-center space-x-1.5 px-3 py-1.5 rounded-xl border text-xs font-semibold <?= $isOutlookConnected ? 'bg-emerald-500/10 border-emerald-500/30 text-emerald-400' : 'bg-slate-800/80 border-slate-700 text-slate-400' ?>">
<span class="w-2 h-2 rounded-full <?= $isOutlookConnected ? 'bg-emerald-400 animate-pulse' : 'bg-slate-500' ?>"></span>
<span>Outlook: <?= $isOutlookConnected ? 'Verbonden' : 'Niet verbonden' ?></span>
@@ -291,151 +364,217 @@ $redirectUri = rtrim(APP_URL, '/') . '/auth/callback_microsoft.php';
</div>
<?php endif; ?>
<!-- Azure App Instructies Accordion -->
<details class="group text-xs bg-slate-950/50 rounded-xl border border-slate-800/80 p-3">
<summary class="cursor-pointer font-semibold text-blue-400 flex items-center justify-between">
<span class="flex items-center space-x-1.5">
<i data-lucide="help-circle" class="w-3.5 h-3.5"></i>
<span>Azure App Registratie Instructies</span>
</span>
<i data-lucide="chevron-down" class="w-3.5 h-3.5 group-open:rotate-180 transition"></i>
</summary>
<div class="mt-3 space-y-2 text-slate-400 text-[11px] leading-relaxed border-t border-slate-800/60 pt-2">
<p>1. Open <a href="https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade" target="_blank" class="text-blue-400 underline">Azure App Registraties</a> en maak een nieuwe registratie.</p>
<p>2. Kies <em>Accounts in elke organisatiemap (...) en persoonlijke Microsoft-accounts</em>.</p>
<p>3. Voeg een <strong>Web</strong> Omleidings-URI toe:</p>
<div class="flex items-center space-x-1 bg-slate-900 p-1.5 rounded border border-slate-800">
<input type="text" readonly id="msRedirectUriInput" value="<?= htmlspecialchars($redirectUri) ?>" class="bg-transparent text-[10px] text-emerald-400 font-mono flex-1 focus:outline-none">
<button onclick="copyToClipboard('msRedirectUriInput')" class="text-slate-400 hover:text-white px-1.5 py-0.5 rounded bg-slate-800 text-[10px]">Kopieer</button>
<?php if ($isAdmin): ?>
<!-- ADMIN WEERGAVE: Centrale melding & Instructies Accordion -->
<div class="p-2.5 bg-blue-500/10 border border-blue-500/20 rounded-xl text-[11px] text-blue-300 flex items-start space-x-2">
<i data-lucide="shield" class="w-4 h-4 flex-shrink-0 mt-0.5 text-blue-400"></i>
<div class="leading-relaxed">
<strong class="text-white block font-semibold">Centrale Beheerdersinstellingen</strong>
<span>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.</span>
</div>
<p>4. Maak een geheim aan onder <em>Certificaten en geheimen</em>.</p>
<p>5. Voeg API-machtigingen toe: <code>Calendars.ReadWrite</code>, <code>User.Read</code>, <code>offline_access</code>.</p>
<p>6. <strong class="text-amber-300">Cruciaal voor organisaties:</strong> Klik direct boven de tabel met machtigingen op de knop <strong class="text-white">"Beheerdersgoedkeuring verlenen voor [Organisatie]"</strong> (<em>Grant admin consent</em>) en bevestig met 'Ja'. Hiermee voorkomt u de melding <em>"Beheerdersgoedkeuring vereist"</em>.</p>
</div>
<!-- Azure App Instructies Accordion -->
<details class="group text-xs bg-slate-950/50 rounded-xl border border-slate-800/80 p-3">
<summary class="cursor-pointer font-semibold text-blue-400 flex items-center justify-between">
<span class="flex items-center space-x-1.5">
<i data-lucide="help-circle" class="w-3.5 h-3.5"></i>
<span>Azure App Registratie Instructies</span>
</span>
<i data-lucide="chevron-down" class="w-3.5 h-3.5 group-open:rotate-180 transition"></i>
</summary>
<div class="mt-3 space-y-2 text-slate-400 text-[11px] leading-relaxed border-t border-slate-800/60 pt-2">
<p>1. Open <a href="https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade" target="_blank" class="text-blue-400 underline">Azure App Registraties</a> en maak een nieuwe registratie.</p>
<p>2. Kies <em>Accounts in elke organisatiemap (...) en persoonlijke Microsoft-accounts</em>.</p>
<p>3. Voeg een <strong>Web</strong> Omleidings-URI toe:</p>
<div class="flex items-center space-x-1 bg-slate-900 p-1.5 rounded border border-slate-800">
<input type="text" readonly id="msRedirectUriInput" value="<?= htmlspecialchars($redirectUri) ?>" class="bg-transparent text-[10px] text-emerald-400 font-mono flex-1 focus:outline-none">
<button onclick="copyToClipboard('msRedirectUriInput')" class="text-slate-400 hover:text-white px-1.5 py-0.5 rounded bg-slate-800 text-[10px]">Kopieer</button>
</div>
<p>4. Maak een geheim aan onder <em>Certificaten en geheimen</em>.</p>
<p>5. Voeg API-machtigingen toe: <code>Calendars.ReadWrite</code>, <code>User.Read</code>, <code>offline_access</code>.</p>
<p>6. <strong class="text-amber-300">Cruciaal voor organisaties:</strong> Klik direct boven de tabel met machtigingen op de knop <strong class="text-white">"Beheerdersgoedkeuring verlenen voor [Organisatie]"</strong> (<em>Grant admin consent</em>) en bevestig met 'Ja'. Hiermee voorkomt u de melding <em>"Beheerdersgoedkeuring vereist"</em>.</p>
<?php
$curClientId = !empty($outlookSettings['client_id']) ? $outlookSettings['client_id'] : $appConfig['client_id'];
$curTenantId = !empty($outlookSettings['tenant_id']) ? $outlookSettings['tenant_id'] : (!empty($appConfig['tenant_id']) ? $appConfig['tenant_id'] : 'organizations');
if (!empty($curClientId)):
$adminConsentUrl = "https://login.microsoftonline.com/" . urlencode($curTenantId) . "/v2.0/adminconsent?client_id=" . urlencode($curClientId) . "&redirect_uri=" . urlencode($redirectUri) . "&scope=" . urlencode("https://graph.microsoft.com/.default");
?>
<div class="mt-2 pt-2 border-t border-slate-800/80">
<a href="<?= htmlspecialchars($adminConsentUrl) ?>" target="_blank" class="inline-flex items-center space-x-1.5 text-[11px] text-amber-400 hover:text-amber-300 underline font-semibold">
<i data-lucide="shield-alert" class="w-3.5 h-3.5"></i>
<span>Direct Beheerdersgoedkeuring verlenen via Microsoft &rarr;</span>
</a>
</div>
<?php endif; ?>
</div>
</details>
<?php else: ?>
<!-- COLLEGA WEERGAVE: Informatiebanner over status -->
<?php if (!$appConfig['is_configured']): ?>
<div class="p-3.5 bg-amber-500/10 border border-amber-500/20 rounded-xl text-xs text-amber-300 flex items-start space-x-3">
<i data-lucide="clock" class="w-5 h-5 flex-shrink-0 text-amber-400 mt-0.5"></i>
<div>
<strong class="block font-semibold mb-0.5">Configuratie vereist door beheerder</strong>
<span class="text-[11px] text-slate-300 leading-relaxed">
Uw beheerder heeft de Microsoft 365 integratie nog niet geconfigureerd voor uw organisatie. Vraag uw beheerder om de Azure App gegevens in te stellen.
</span>
</div>
</div>
<?php elseif (!$isOutlookConnected): ?>
<div class="p-3.5 bg-blue-500/10 border border-blue-500/20 rounded-xl text-xs text-slate-300 flex items-start space-x-3">
<i data-lucide="shield-check" class="w-5 h-5 flex-shrink-0 text-blue-400 mt-0.5"></i>
<div>
<strong class="text-white block font-semibold mb-0.5">Gereed voor Koppeling</strong>
<span class="text-[11px] text-slate-300 leading-relaxed">
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.
</span>
</div>
</div>
<?php endif; ?>
<?php endif; ?>
</div>
<?php if ($isAdmin): ?>
<!-- ADMIN FORMULIER -->
<form method="POST" action="/integrations.php" class="pt-4 border-t border-slate-800 space-y-2.5 mt-4">
<input type="hidden" name="provider" value="OUTLOOK">
<div>
<label class="block text-[10px] font-semibold text-slate-400 mb-0.5">
Application (Client) ID <span class="text-blue-400">*</span>
</label>
<input
type="text"
name="client_id"
value="<?= htmlspecialchars($outlookSettings['client_id'] ?? '') ?>"
placeholder="bijv. e8f498c1-1234-4567-89ab-cdef01234567"
class="w-full bg-slate-950 border border-slate-800 rounded-lg px-2.5 py-1.5 text-xs text-white placeholder-slate-600 focus:outline-none focus:border-blue-500 font-mono"
>
<p class="text-[10px] text-slate-500 mt-0.5">Te vinden op de Overzichtspagina (Overview) van uw Azure App Registratie.</p>
</div>
<div>
<label class="block text-[10px] font-semibold text-slate-400 mb-0.5">
Client Secret (Geheim) <span class="text-blue-400">*</span>
</label>
<div class="relative">
<input
type="password"
id="msClientSecret"
name="client_secret"
value="<?= htmlspecialchars(!empty($outlookSettings['client_secret']) && $outlookSettings['client_secret'] !== '••••••••••••••••' ? $outlookSettings['client_secret'] : '') ?>"
placeholder="Plak hier de Waarde (Value) van uw Client Secret"
class="w-full bg-slate-950 border border-slate-800 rounded-lg px-2.5 py-1.5 pr-8 text-xs text-white placeholder-slate-600 focus:outline-none focus:border-blue-500 font-mono"
>
<button type="button" onclick="togglePasswordVisibility('msClientSecret', this)" class="absolute right-2 top-2 text-slate-500 hover:text-slate-300 transition" title="Toon / verberg geheim">
<i data-lucide="eye" class="w-3.5 h-3.5"></i>
</button>
</div>
<p class="text-[10px] text-slate-500 mt-0.5">Aangemaakt onder <em>Certificaten en geheimen &gt; Nieuw clientgeheim</em> (kopieer de <strong>Waarde / Value</strong>).</p>
</div>
<div>
<div class="flex items-center justify-between mb-0.5">
<label class="text-[10px] font-semibold text-slate-400">Directory (Tenant) ID</label>
<span class="text-[9px] text-blue-400 font-semibold bg-blue-500/10 px-1.5 py-0.5 rounded">Standaard: common</span>
</div>
<input
type="text"
name="tenant_id"
value="<?= htmlspecialchars(!empty($outlookSettings['tenant_id']) ? $outlookSettings['tenant_id'] : 'common') ?>"
placeholder="common of organizations"
class="w-full bg-slate-950 border border-slate-800 rounded-lg px-2.5 py-1.5 text-xs text-white placeholder-slate-600 focus:outline-none focus:border-blue-500 font-mono"
>
<div class="text-[10px] text-slate-400 mt-1.5 leading-relaxed bg-slate-950/80 p-2.5 rounded-lg border border-slate-800/80 space-y-1">
<p class="font-semibold text-slate-300 flex items-center space-x-1">
<span>💡 Wat moet hier staan?</span>
</p>
<p>• Voor uw organisatie: vul uw domein in (bijv. <code>planningsagenda.nl</code>) of uw Tenant GUID.</p>
<p>• Voor multi-tenant apps: vul <code>organizations</code> of <code>common</code> in.</p>
</div>
<?php
$curClientId = !empty($outlookSettings['client_id']) ? $outlookSettings['client_id'] : fociz_env('MICROSOFT_CLIENT_ID', '');
$curTenantId = !empty($outlookSettings['tenant_id']) ? $outlookSettings['tenant_id'] : 'organizations';
$curClientId = !empty($outlookSettings['client_id']) ? $outlookSettings['client_id'] : $appConfig['client_id'];
$curTenantId = !empty($outlookSettings['tenant_id']) ? $outlookSettings['tenant_id'] : (!empty($appConfig['tenant_id']) ? $appConfig['tenant_id'] : 'organizations');
if (!empty($curClientId)):
$adminConsentUrl = "https://login.microsoftonline.com/" . urlencode($curTenantId) . "/v2.0/adminconsent?client_id=" . urlencode($curClientId) . "&redirect_uri=" . urlencode($redirectUri) . "&scope=" . urlencode("https://graph.microsoft.com/.default");
?>
<div class="mt-2 pt-2 border-t border-slate-800/80">
<a href="<?= htmlspecialchars($adminConsentUrl) ?>" target="_blank" class="inline-flex items-center space-x-1.5 text-[11px] text-amber-400 hover:text-amber-300 underline font-semibold">
<i data-lucide="shield-alert" class="w-3.5 h-3.5"></i>
<span>Direct Beheerdersgoedkeuring verlenen via Microsoft &rarr;</span>
</a>
<div class="mt-2 p-2.5 rounded-lg bg-amber-500/10 border border-amber-500/30 text-[11px] flex items-start space-x-2">
<i data-lucide="shield-alert" class="w-4 h-4 flex-shrink-0 mt-0.5 text-amber-400"></i>
<div class="text-slate-300 leading-snug flex-1">
<strong class="text-amber-300 block">Krijgt u "Beheerdersgoedkeuring vereist"?</strong>
<span>Stuur deze officiële Microsoft goedkeuringslink naar uw IT-beheerder om Fociz met 1 klik goed te keuren voor <?= htmlspecialchars($curTenantId) ?>:</span>
<div class="mt-2 flex items-center space-x-2">
<input type="text" readonly id="adminConsentInput" value="<?= htmlspecialchars($adminConsentUrl) ?>" class="bg-slate-950/80 border border-slate-800 rounded px-2 py-1 text-[10px] text-amber-300 font-mono flex-1 focus:outline-none select-all">
<button type="button" onclick="copyToClipboard('adminConsentInput')" class="bg-amber-500/20 hover:bg-amber-500/30 border border-amber-500/40 text-amber-300 text-[10px] font-semibold px-2 py-1 rounded transition flex items-center space-x-1 flex-shrink-0">
<i data-lucide="copy" class="w-3 h-3"></i>
<span>Kopieer Link</span>
</button>
</div>
<div class="mt-1.5">
<a href="<?= htmlspecialchars($adminConsentUrl) ?>" target="_blank" class="inline-flex items-center space-x-1 font-bold text-amber-400 hover:text-white underline">
<span>Zelf beheerder? Open de goedkeuring direct &rarr;</span>
</a>
</div>
</div>
</div>
<?php endif; ?>
</div>
</details>
</div>
<!-- Instellingen formulier -->
<form method="POST" action="/integrations.php" class="pt-4 border-t border-slate-800 space-y-2.5 mt-4">
<input type="hidden" name="provider" value="OUTLOOK">
<div>
<label class="block text-[10px] font-semibold text-slate-400 mb-0.5">
Application (Client) ID <span class="text-blue-400">*</span>
<label class="flex items-center space-x-2 text-xs text-slate-300 pt-1">
<input type="checkbox" name="is_demo_mode" value="1" <?= !empty($configs['OUTLOOK']['is_demo_mode']) ? 'checked' : '' ?> class="rounded bg-slate-900 border-slate-700 text-blue-500">
<span>Gebruik interactieve Sandbox data</span>
</label>
<input
type="text"
name="client_id"
value="<?= htmlspecialchars($outlookSettings['client_id'] ?? fociz_env('MICROSOFT_CLIENT_ID', '')) ?>"
placeholder="bijv. e8f498c1-1234-4567-89ab-cdef01234567"
class="w-full bg-slate-950 border border-slate-800 rounded-lg px-2.5 py-1.5 text-xs text-white placeholder-slate-600 focus:outline-none focus:border-blue-500 font-mono"
>
<p class="text-[10px] text-slate-500 mt-0.5">Te vinden op de Overzichtspagina (Overview) van uw Azure App Registratie.</p>
</div>
<div>
<label class="block text-[10px] font-semibold text-slate-400 mb-0.5">
Client Secret (Geheim) <span class="text-blue-400">*</span>
</label>
<div class="relative">
<input
type="password"
id="msClientSecret"
name="client_secret"
value="<?= htmlspecialchars(!empty($outlookSettings['client_secret']) && $outlookSettings['client_secret'] !== '••••••••••••••••' ? $outlookSettings['client_secret'] : fociz_env('MICROSOFT_CLIENT_SECRET', '')) ?>"
placeholder="Plak hier de Waarde (Value) van uw Client Secret"
class="w-full bg-slate-950 border border-slate-800 rounded-lg px-2.5 py-1.5 pr-8 text-xs text-white placeholder-slate-600 focus:outline-none focus:border-blue-500 font-mono"
>
<button type="button" onclick="togglePasswordVisibility('msClientSecret', this)" class="absolute right-2 top-2 text-slate-500 hover:text-slate-300 transition" title="Toon / verberg geheim">
<i data-lucide="eye" class="w-3.5 h-3.5"></i>
<div class="flex space-x-2 pt-2">
<button type="submit" name="action" value="save" class="flex-1 py-2 rounded-xl bg-slate-800 hover:bg-slate-700 text-slate-200 text-xs font-semibold transition">
Instellingen Opslaan
</button>
<button type="submit" name="action" value="connect_oauth" class="flex-1 py-2 rounded-xl bg-blue-600 hover:bg-blue-500 text-white text-xs font-bold transition flex items-center justify-center space-x-1 shadow-md shadow-blue-500/20">
<i data-lucide="key" class="w-3.5 h-3.5"></i>
<span><?= $isOutlookConnected ? 'Opnieuw Koppelen' : 'Koppel Mijn Outlook' ?></span>
</button>
</div>
<p class="text-[10px] text-slate-500 mt-0.5">Aangemaakt onder <em>Certificaten en geheimen &gt; Nieuw clientgeheim</em> (kopieer de <strong>Waarde / Value</strong>).</p>
</div>
<div>
<div class="flex items-center justify-between mb-0.5">
<label class="text-[10px] font-semibold text-slate-400">Directory (Tenant) ID</label>
<span class="text-[9px] text-blue-400 font-semibold bg-blue-500/10 px-1.5 py-0.5 rounded">Standaard: common</span>
</div>
<input
type="text"
name="tenant_id"
value="<?= htmlspecialchars(!empty($outlookSettings['tenant_id']) ? $outlookSettings['tenant_id'] : 'common') ?>"
placeholder="common of organizations"
class="w-full bg-slate-950 border border-slate-800 rounded-lg px-2.5 py-1.5 text-xs text-white placeholder-slate-600 focus:outline-none focus:border-blue-500 font-mono"
>
<div class="text-[10px] text-slate-400 mt-1.5 leading-relaxed bg-slate-950/80 p-2.5 rounded-lg border border-slate-800/80 space-y-1">
<p class="font-semibold text-slate-300 flex items-center space-x-1">
<span>💡 Wat moet hier staan?</span>
</p>
<p>• Voor uw organisatie: vul uw domein in (bijv. <code>planningsagenda.nl</code>) of uw Tenant GUID.</p>
<p>• Voor multi-tenant apps: vul <code>organizations</code> of <code>common</code> in.</p>
</div>
<?php
$curClientId = !empty($outlookSettings['client_id']) ? $outlookSettings['client_id'] : fociz_env('MICROSOFT_CLIENT_ID', '');
$curTenantId = !empty($outlookSettings['tenant_id']) ? $outlookSettings['tenant_id'] : 'organizations';
if (!empty($curClientId)):
$adminConsentUrl = "https://login.microsoftonline.com/" . urlencode($curTenantId) . "/v2.0/adminconsent?client_id=" . urlencode($curClientId) . "&redirect_uri=" . urlencode($redirectUri) . "&scope=" . urlencode("https://graph.microsoft.com/.default");
?>
<div class="mt-2 p-2.5 rounded-lg bg-amber-500/10 border border-amber-500/30 text-[11px] flex items-start space-x-2">
<i data-lucide="shield-alert" class="w-4 h-4 flex-shrink-0 mt-0.5 text-amber-400"></i>
<div class="text-slate-300 leading-snug flex-1">
<strong class="text-amber-300 block">Krijgt u "Beheerdersgoedkeuring vereist"?</strong>
<span>Stuur deze officiële Microsoft goedkeuringslink naar uw IT-beheerder om Fociz met 1 klik goed te keuren voor <?= htmlspecialchars($curTenantId) ?>:</span>
<div class="mt-2 flex items-center space-x-2">
<input type="text" readonly id="adminConsentInput" value="<?= htmlspecialchars($adminConsentUrl) ?>" class="bg-slate-950/80 border border-slate-800 rounded px-2 py-1 text-[10px] text-amber-300 font-mono flex-1 focus:outline-none select-all">
<button type="button" onclick="copyToClipboard('adminConsentInput')" class="bg-amber-500/20 hover:bg-amber-500/30 border border-amber-500/40 text-amber-300 text-[10px] font-semibold px-2 py-1 rounded transition flex items-center space-x-1 flex-shrink-0">
<i data-lucide="copy" class="w-3 h-3"></i>
<span>Kopieer Link</span>
</button>
</div>
<div class="mt-1.5">
<a href="<?= htmlspecialchars($adminConsentUrl) ?>" target="_blank" class="inline-flex items-center space-x-1 font-bold text-amber-400 hover:text-white underline">
<span>Zelf beheerder? Open de goedkeuring direct &rarr;</span>
</a>
</div>
</div>
</div>
<?php if ($isOutlookConnected): ?>
<button type="submit" name="action" value="disconnect" onclick="return confirm('Weet u zeker dat u Microsoft Outlook wilt ontkoppelen voor uw account?')" class="w-full py-1.5 text-[11px] text-red-400 hover:text-red-300 transition text-center block">
Mijn Koppeling Verwijderen
</button>
<?php endif; ?>
</form>
<?php else: ?>
<!-- COLLEGA FORMULIER: Eenvoudig & Veilig (1-klik) -->
<div class="pt-4 border-t border-slate-800 mt-4">
<?php if (!$appConfig['is_configured']): ?>
<button type="button" disabled class="w-full py-2.5 rounded-xl bg-slate-800/60 border border-slate-700/60 text-slate-500 text-xs font-semibold cursor-not-allowed flex items-center justify-center space-x-2">
<i data-lucide="lock" class="w-3.5 h-3.5"></i>
<span>Wachten op Configuratie Beheerder</span>
</button>
<?php elseif ($isOutlookConnected): ?>
<form method="POST" action="/integrations.php" class="space-y-2">
<input type="hidden" name="provider" value="OUTLOOK">
<button type="submit" name="action" value="connect_oauth" class="w-full py-2.5 rounded-xl bg-slate-800 hover:bg-slate-700 text-slate-200 text-xs font-semibold transition flex items-center justify-center space-x-2">
<i data-lucide="refresh-cw" class="w-3.5 h-3.5"></i>
<span>Opnieuw Aanmelden bij Microsoft</span>
</button>
<button type="submit" name="action" value="disconnect" onclick="return confirm('Weet u zeker dat u uw persoonlijke Outlook agenda wilt ontkoppelen?')" class="w-full py-2 rounded-xl bg-red-500/10 hover:bg-red-500/20 border border-red-500/20 text-red-400 text-xs font-semibold transition flex items-center justify-center space-x-1.5">
<i data-lucide="link-2-off" class="w-3.5 h-3.5"></i>
<span>Mijn Agenda Ontkoppelen</span>
</button>
</form>
<?php else: ?>
<form method="POST" action="/integrations.php">
<input type="hidden" name="provider" value="OUTLOOK">
<button type="submit" name="action" value="connect_oauth" class="w-full py-3 rounded-xl bg-blue-600 hover:bg-blue-500 text-white text-xs font-bold transition flex items-center justify-center space-x-2 shadow-lg shadow-blue-500/25">
<i data-lucide="calendar-plus" class="w-4 h-4"></i>
<span>Koppel Mijn Microsoft Outlook Agenda</span>
</button>
</form>
<?php endif; ?>
</div>
<label class="flex items-center space-x-2 text-xs text-slate-300 pt-1">
<input type="checkbox" name="is_demo_mode" value="1" <?= !empty($configs['OUTLOOK']['is_demo_mode']) ? 'checked' : '' ?> class="rounded bg-slate-900 border-slate-700 text-blue-500">
<span>Gebruik interactieve Sandbox data</span>
</label>
<div class="flex space-x-2 pt-2">
<button type="submit" name="action" value="save" class="flex-1 py-2 rounded-xl bg-slate-800 hover:bg-slate-700 text-slate-200 text-xs font-semibold transition">
Opslaan
</button>
<button type="submit" name="action" value="connect_oauth" class="flex-1 py-2 rounded-xl bg-blue-600 hover:bg-blue-500 text-white text-xs font-bold transition flex items-center justify-center space-x-1 shadow-md shadow-blue-500/20">
<i data-lucide="key" class="w-3.5 h-3.5"></i>
<span><?= $isOutlookConnected ? 'Opnieuw Koppelen' : 'Koppel Outlook' ?></span>
</button>
</div>
<?php if ($isOutlookConnected): ?>
<button type="submit" name="action" value="disconnect" onclick="return confirm('Weet u zeker dat u Microsoft Outlook wilt ontkoppelen?')" class="w-full py-1.5 text-[11px] text-red-400 hover:text-red-300 transition text-center block">
Koppeling Verwijderen
</button>
<?php endif; ?>
</form>
<?php endif; ?>
</div>
<!-- 2. MICROSOFT TEAMS -->