Add Outlook synchronization features: implement real-time sync check, save sync preferences, and reconcile tasks with Outlook events
This commit is contained in:
@@ -594,3 +594,223 @@ function microsoft_disconnect(int $userId): bool {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Haalt één specifieke afspraak op uit Microsoft Outlook via Graph API.
|
||||
* Retourneert:
|
||||
* - array met event data indien gevonden (HTTP 200)
|
||||
* - false indien definitief verwijderd / geannuleerd (HTTP 404 of isCancelled)
|
||||
* - null indien er een netwerk/server error optreedt (beschermt taken tegen onterecht wissen)
|
||||
*/
|
||||
function microsoft_get_single_event(int $userId, string $eventId): false|array|null {
|
||||
if (empty($eventId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$token = microsoft_get_valid_token($userId);
|
||||
if (!$token) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$url = MS_GRAPH_BASE_URL . '/me/events/' . urlencode($eventId) . '?' . http_build_query([
|
||||
'$select' => 'id,subject,start,end,isCancelled',
|
||||
]);
|
||||
|
||||
$response = fociz_http_request($url, [
|
||||
'method' => 'GET',
|
||||
'headers' => [
|
||||
'Authorization' => "Bearer {$token}",
|
||||
'Accept' => 'application/json',
|
||||
'Prefer' => 'outlook.timezone="Europe/Amsterdam"',
|
||||
],
|
||||
'timeout' => 8,
|
||||
]);
|
||||
|
||||
if ($response['status'] === 401) {
|
||||
$freshToken = microsoft_get_valid_token($userId, true);
|
||||
if ($freshToken && $freshToken !== $token) {
|
||||
$token = $freshToken;
|
||||
$response = fociz_http_request($url, [
|
||||
'method' => 'GET',
|
||||
'headers' => [
|
||||
'Authorization' => "Bearer {$token}",
|
||||
'Accept' => 'application/json',
|
||||
'Prefer' => 'outlook.timezone="Europe/Amsterdam"',
|
||||
],
|
||||
'timeout' => 8,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($response['status'] === 404) {
|
||||
return false; // Definitief niet meer gevonden in Outlook
|
||||
}
|
||||
|
||||
if ($response['status'] === 200 && is_array($response['json'])) {
|
||||
$e = $response['json'];
|
||||
if (!empty($e['isCancelled'])) {
|
||||
return false; // Geannuleerd
|
||||
}
|
||||
|
||||
$rawStart = $e['start']['dateTime'] ?? '';
|
||||
$rawEnd = $e['end']['dateTime'] ?? '';
|
||||
|
||||
$eventDate = !empty($rawStart) ? substr($rawStart, 0, 10) : '';
|
||||
$startTime = '';
|
||||
$endTime = '';
|
||||
|
||||
if (!empty($rawStart) && preg_match('/T(\d{2}:\d{2})/', $rawStart, $m)) {
|
||||
$startTime = $m[1];
|
||||
}
|
||||
if (!empty($rawEnd) && preg_match('/T(\d{2}:\d{2})/', $rawEnd, $m)) {
|
||||
$endTime = $m[1];
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $e['id'],
|
||||
'subject' => !empty($e['subject']) ? $e['subject'] : '(Geen onderwerp)',
|
||||
'date' => $eventDate,
|
||||
'start' => $startTime ?: '09:00',
|
||||
'end' => $endTime ?: '10:00',
|
||||
'start_raw' => $rawStart,
|
||||
'end_raw' => $rawEnd,
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchroniseert en reconciled Fociz taken met de live Microsoft Outlook agenda.
|
||||
*
|
||||
* - Als een gekoppelde afspraak in Outlook is verwijderd of geannuleerd, wordt de taak in Fociz gewist.
|
||||
* - Als een afspraak in Outlook van tijd, datum of titel is gewijzigd, wordt de taak in Fociz bijgewerkt.
|
||||
*
|
||||
* @param int $userId
|
||||
* @param string $startDate Datumbereik begin (Y-m-d)
|
||||
* @param string $endDate Datumbereik einde (Y-m-d)
|
||||
* @param array $liveEvents Reeds opgehaalde Outlook events via microsoft_fetch_events_range
|
||||
* @return array ['deleted' => int, 'updated' => int]
|
||||
*/
|
||||
function microsoft_reconcile_tasks_with_outlook(int $userId, string $startDate, string $endDate, array $liveEvents): array {
|
||||
$db = get_db_connection();
|
||||
$deletedCount = 0;
|
||||
$updatedCount = 0;
|
||||
|
||||
// 1. Indexeer live events op id
|
||||
$liveEventMap = [];
|
||||
foreach ($liveEvents as $evt) {
|
||||
if (!empty($evt['id'])) {
|
||||
$liveEventMap[$evt['id']] = $evt;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Haal alle taken op voor deze gebruiker in dit datumbereik met een external_id (gekoppeld aan Outlook)
|
||||
$stmt = $db->prepare("
|
||||
SELECT t.id, t.title, t.time_block_start, t.time_block_end, t.duration_minutes, t.external_id, dp.plan_date, dp.id as daily_plan_id
|
||||
FROM tasks t
|
||||
JOIN daily_plans dp ON t.daily_plan_id = dp.id
|
||||
WHERE t.user_id = ?
|
||||
AND t.source = 'OUTLOOK'
|
||||
AND t.external_id IS NOT NULL
|
||||
AND t.external_id != ''
|
||||
AND dp.plan_date BETWEEN ? AND ?
|
||||
");
|
||||
$stmt->execute([$userId, $startDate, $endDate]);
|
||||
$syncedTasks = $stmt->fetchAll();
|
||||
|
||||
foreach ($syncedTasks as $task) {
|
||||
$extId = $task['external_id'];
|
||||
|
||||
if (isset($liveEventMap[$extId])) {
|
||||
// Het event bestaat in het huidige week/datumbereik!
|
||||
$evt = $liveEventMap[$extId];
|
||||
|
||||
$dateChanged = (!empty($evt['date']) && $evt['date'] !== $task['plan_date']);
|
||||
$titleChanged = (!empty($evt['subject']) && $evt['subject'] !== $task['title']);
|
||||
$startChanged = (!empty($evt['start']) && $evt['start'] !== $task['time_block_start']);
|
||||
$endChanged = (!empty($evt['end']) && $evt['end'] !== $task['time_block_end']);
|
||||
|
||||
if ($dateChanged || $titleChanged || $startChanged || $endChanged) {
|
||||
$targetPlanId = (int)$task['daily_plan_id'];
|
||||
|
||||
// Als datum gewijzigd is, verplaats naar de daily_plan van de nieuwe datum
|
||||
if ($dateChanged) {
|
||||
$stmtPlan = $db->prepare("
|
||||
INSERT INTO daily_plans (user_id, plan_date)
|
||||
VALUES (?, ?)
|
||||
ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID(id)
|
||||
");
|
||||
$stmtPlan->execute([$userId, $evt['date']]);
|
||||
$targetPlanId = (int)$db->lastInsertId();
|
||||
}
|
||||
|
||||
// Bereken duur
|
||||
$newStart = !empty($evt['start']) ? $evt['start'] : $task['time_block_start'];
|
||||
$newEnd = !empty($evt['end']) ? $evt['end'] : $task['time_block_end'];
|
||||
$newDuration = (int)$task['duration_minutes'];
|
||||
if (!empty($newStart) && !empty($newEnd)) {
|
||||
$durCalc = (strtotime("2000-01-01 {$newEnd}") - strtotime("2000-01-01 {$newStart}")) / 60;
|
||||
if ($durCalc > 0) {
|
||||
$newDuration = (int)$durCalc;
|
||||
}
|
||||
}
|
||||
|
||||
$newTitle = !empty($evt['subject']) ? $evt['subject'] : $task['title'];
|
||||
|
||||
$updateStmt = $db->prepare("
|
||||
UPDATE tasks
|
||||
SET daily_plan_id = ?, title = ?, time_block_start = ?, time_block_end = ?, duration_minutes = ?
|
||||
WHERE id = ? AND user_id = ?
|
||||
");
|
||||
$updateStmt->execute([$targetPlanId, $newTitle, $newStart, $newEnd, $newDuration, $task['id'], $userId]);
|
||||
$updatedCount++;
|
||||
}
|
||||
} else {
|
||||
// Event niet gevonden in het weekbereik!
|
||||
// Controleer specifiek of het event in Outlook gewist is of naar een andere week verplaatst
|
||||
$singleEvent = microsoft_get_single_event($userId, $extId);
|
||||
|
||||
if ($singleEvent === false) {
|
||||
// Event is definitief verwijderd of geannuleerd in Outlook!
|
||||
$delStmt = $db->prepare("DELETE FROM tasks WHERE id = ? AND user_id = ?");
|
||||
$delStmt->execute([$task['id'], $userId]);
|
||||
$deletedCount++;
|
||||
} elseif (is_array($singleEvent)) {
|
||||
// Event bestaat nog, maar staat op een andere datum (buiten huidig bereik)
|
||||
$newDate = $singleEvent['date'];
|
||||
if (!empty($newDate)) {
|
||||
$stmtPlan = $db->prepare("
|
||||
INSERT INTO daily_plans (user_id, plan_date)
|
||||
VALUES (?, ?)
|
||||
ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID(id)
|
||||
");
|
||||
$stmtPlan->execute([$userId, $newDate]);
|
||||
$newPlanId = (int)$db->lastInsertId();
|
||||
|
||||
$newStart = $singleEvent['start'];
|
||||
$newEnd = $singleEvent['end'];
|
||||
$newDuration = (int)$task['duration_minutes'];
|
||||
if (!empty($newStart) && !empty($newEnd)) {
|
||||
$durCalc = (strtotime("2000-01-01 {$newEnd}") - strtotime("2000-01-01 {$newStart}")) / 60;
|
||||
if ($durCalc > 0) $newDuration = (int)$durCalc;
|
||||
}
|
||||
|
||||
$updateStmt = $db->prepare("
|
||||
UPDATE tasks
|
||||
SET daily_plan_id = ?, title = ?, time_block_start = ?, time_block_end = ?, duration_minutes = ?
|
||||
WHERE id = ? AND user_id = ?
|
||||
");
|
||||
$updateStmt->execute([$newPlanId, $singleEvent['subject'], $newStart, $newEnd, $newDuration, $task['id'], $userId]);
|
||||
$updatedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'deleted' => $deletedCount,
|
||||
'updated' => $updatedCount,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user