feat(outlook): fix calendar sync format and enable bidirectional task-to-outlook scheduling
This commit is contained in:
@@ -269,6 +269,10 @@ function microsoft_get_valid_token(int $userId): ?string {
|
||||
}
|
||||
|
||||
error_log("Microsoft token refresh mislukt voor user {$userId}: " . ($response['error'] ?? json_encode($response['json'])));
|
||||
// Fallback: indien het huidige token nog aanwezig is, probeer dit toch te gebruiken
|
||||
if (!empty($integration['access_token'])) {
|
||||
return $integration['access_token'];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -283,9 +287,21 @@ function microsoft_fetch_today_events(int $userId, ?string $date = null): array
|
||||
return [];
|
||||
}
|
||||
|
||||
// Venster van 00:00:00 tot 23:59:59 lokale tijd
|
||||
$startIso = "{$targetDate}T00:00:00";
|
||||
$endIso = "{$targetDate}T23:59:59";
|
||||
// Bereken het dagvenster in Europe/Amsterdam en converteer naar UTC ISO 8601 (eindigend op 'Z')
|
||||
// Microsoft Graph vereist strikt een ISO 8601 formaat met tijdzone of 'Z' (bijv. 2026-09-09T00:00:00Z)
|
||||
try {
|
||||
$tzLocal = new DateTimeZone('Europe/Amsterdam');
|
||||
$tzUtc = new DateTimeZone('UTC');
|
||||
|
||||
$dtStart = new DateTime("{$targetDate} 00:00:00", $tzLocal);
|
||||
$dtEnd = new DateTime("{$targetDate} 23:59:59", $tzLocal);
|
||||
|
||||
$startIso = $dtStart->setTimezone($tzUtc)->format('Y-m-d\TH:i:s\Z');
|
||||
$endIso = $dtEnd->setTimezone($tzUtc)->format('Y-m-d\TH:i:s\Z');
|
||||
} catch (Throwable $e) {
|
||||
$startIso = "{$targetDate}T00:00:00Z";
|
||||
$endIso = "{$targetDate}T23:59:59Z";
|
||||
}
|
||||
|
||||
$url = MS_GRAPH_BASE_URL . '/me/calendarView?' . http_build_query([
|
||||
'startDateTime' => $startIso,
|
||||
@@ -305,7 +321,12 @@ function microsoft_fetch_today_events(int $userId, ?string $date = null): array
|
||||
'timeout' => 12,
|
||||
]);
|
||||
|
||||
if ($response['status'] !== 200 || empty($response['json']['value'])) {
|
||||
if ($response['status'] !== 200) {
|
||||
error_log("microsoft_fetch_today_events error (HTTP {$response['status']}): " . ($response['error'] ?? json_encode($response['json'])));
|
||||
return [];
|
||||
}
|
||||
|
||||
if (empty($response['json']['value'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -321,14 +342,23 @@ function microsoft_fetch_today_events(int $userId, ?string $date = null): array
|
||||
$rawStart = $e['start']['dateTime'] ?? '';
|
||||
$rawEnd = $e['end']['dateTime'] ?? '';
|
||||
|
||||
// Formatteer naar H:i (bijv. 09:30)
|
||||
// Formatteer naar H:i (bijv. 09:30). Omdat we 'Prefer: outlook.timezone="Europe/Amsterdam"'
|
||||
// meesturen, levert Graph de tijden al aan in de Nederlandse tijdzone.
|
||||
$startTime = '';
|
||||
$endTime = '';
|
||||
if (!empty($rawStart)) {
|
||||
$startTime = date('H:i', strtotime($rawStart));
|
||||
if (preg_match('/T(\d{2}:\d{2})/', $rawStart, $m)) {
|
||||
$startTime = $m[1];
|
||||
} else {
|
||||
$startTime = date('H:i', strtotime($rawStart));
|
||||
}
|
||||
}
|
||||
if (!empty($rawEnd)) {
|
||||
$endTime = date('H:i', strtotime($rawEnd));
|
||||
if (preg_match('/T(\d{2}:\d{2})/', $rawEnd, $m)) {
|
||||
$endTime = $m[1];
|
||||
} else {
|
||||
$endTime = date('H:i', strtotime($rawEnd));
|
||||
}
|
||||
}
|
||||
|
||||
// Teams of meeting URL detecteren
|
||||
@@ -353,6 +383,7 @@ function microsoft_fetch_today_events(int $userId, ?string $date = null): array
|
||||
'end_raw' => $rawEnd,
|
||||
'location' => $locationName ?: 'Geen locatie',
|
||||
'meeting_url' => $meetingUrl,
|
||||
'web_link' => $e['webLink'] ?? '',
|
||||
'is_all_day' => !empty($e['isAllDay']),
|
||||
'source' => 'OUTLOOK_LIVE',
|
||||
];
|
||||
@@ -361,6 +392,127 @@ function microsoft_fetch_today_events(int $userId, ?string $date = null): array
|
||||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maakt een nieuwe afspraak aan in de Microsoft Outlook agenda via Graph API
|
||||
*
|
||||
* @param int $userId
|
||||
* @param array $eventData [
|
||||
* 'subject' => string,
|
||||
* 'description' => ?string,
|
||||
* 'date' => ?string (Y-m-d),
|
||||
* 'start_time' => ?string (H:i),
|
||||
* 'end_time' => ?string (H:i),
|
||||
* 'duration_minutes' => ?int,
|
||||
* ]
|
||||
* @return array ['success' => bool, 'event_id' => ?string, 'web_link' => ?string, 'error' => ?string]
|
||||
*/
|
||||
function microsoft_create_event(int $userId, array $eventData): array {
|
||||
$token = microsoft_get_valid_token($userId);
|
||||
if (!$token) {
|
||||
return ['success' => false, 'error' => 'Geen geldige Outlook 365 verbinding gevonden'];
|
||||
}
|
||||
|
||||
$subject = trim($eventData['subject'] ?? 'Fociz Dagplanning Taak');
|
||||
$description = trim($eventData['description'] ?? 'Gepland via Fociz Dagplanning & Structuur');
|
||||
$date = !empty($eventData['date']) ? trim($eventData['date']) : date('Y-m-d');
|
||||
$startTime = !empty($eventData['start_time']) ? trim($eventData['start_time']) : '09:00';
|
||||
|
||||
// Als tijd alleen een uur is (bijv. "9" of "09"), converteer naar "09:00"
|
||||
if (preg_match('/^(\d{1,2})$/', $startTime, $m)) {
|
||||
$startTime = sprintf('%02d:00', (int)$m[1]);
|
||||
}
|
||||
|
||||
// Bepaal de eindtijd
|
||||
$endTime = !empty($eventData['end_time']) ? trim($eventData['end_time']) : '';
|
||||
$duration = (int)($eventData['duration_minutes'] ?? 30);
|
||||
if ($duration <= 0) $duration = 30;
|
||||
|
||||
if (empty($endTime)) {
|
||||
try {
|
||||
$dt = new DateTime("{$date} {$startTime}:00", new DateTimeZone('Europe/Amsterdam'));
|
||||
$dt->modify("+{$duration} minutes");
|
||||
$endTime = $dt->format('H:i');
|
||||
} catch (Throwable $e) {
|
||||
$endTime = '10:00';
|
||||
}
|
||||
}
|
||||
|
||||
$startDateTimeStr = "{$date}T{$startTime}:00";
|
||||
$endDateTimeStr = "{$date}T{$endTime}:00";
|
||||
|
||||
$payload = [
|
||||
'subject' => $subject,
|
||||
'body' => [
|
||||
'contentType' => 'HTML',
|
||||
'content' => nl2br(htmlspecialchars($description)) . '<br><br><small style="color: #64748b;">Aangemaakt via <a href="https://dev.fociz.nl/planner.php">Fociz Dagplanning</a></small>',
|
||||
],
|
||||
'start' => [
|
||||
'dateTime' => $startDateTimeStr,
|
||||
'timeZone' => 'Europe/Amsterdam',
|
||||
],
|
||||
'end' => [
|
||||
'dateTime' => $endDateTimeStr,
|
||||
'timeZone' => 'Europe/Amsterdam',
|
||||
],
|
||||
'showAs' => 'busy',
|
||||
'isReminderOn' => true,
|
||||
'reminderMinutesBeforeStart' => 15,
|
||||
];
|
||||
|
||||
$response = fociz_http_request(MS_GRAPH_BASE_URL . '/me/events', [
|
||||
'method' => 'POST',
|
||||
'headers' => [
|
||||
'Authorization' => "Bearer {$token}",
|
||||
'Content-Type' => 'application/json',
|
||||
'Accept' => 'application/json',
|
||||
],
|
||||
'body' => $payload,
|
||||
'timeout' => 15,
|
||||
]);
|
||||
|
||||
if ($response['status'] === 201 && is_array($response['json'])) {
|
||||
return [
|
||||
'success' => true,
|
||||
'event_id' => $response['json']['id'] ?? null,
|
||||
'web_link' => $response['json']['webLink'] ?? null,
|
||||
'start_time'=> $startTime,
|
||||
'end_time' => $endTime,
|
||||
];
|
||||
}
|
||||
|
||||
$errMsg = $response['json']['error']['message'] ?? ($response['error'] ?? "Fout bij aanmaken Outlook afspraak (HTTP {$response['status']})");
|
||||
error_log("microsoft_create_event fout: {$errMsg}");
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $errMsg,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Verwijdert een afspraak uit Microsoft Outlook via Graph API
|
||||
*/
|
||||
function microsoft_delete_event(int $userId, string $eventId): bool {
|
||||
if (empty($eventId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$token = microsoft_get_valid_token($userId);
|
||||
if (!$token) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$url = MS_GRAPH_BASE_URL . '/me/events/' . urlencode($eventId);
|
||||
$response = fociz_http_request($url, [
|
||||
'method' => 'DELETE',
|
||||
'headers' => [
|
||||
'Authorization' => "Bearer {$token}",
|
||||
],
|
||||
'timeout' => 12,
|
||||
]);
|
||||
|
||||
return in_array($response['status'], [200, 204, 404]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ontkoppelt Microsoft integratie voor een gebruiker
|
||||
* Wist uitsluitend de persoonlijke tokens; behoudt de centrale Azure App gegevens indien aanwezig.
|
||||
|
||||
Reference in New Issue
Block a user