Add webhook receivers for Jira and Microsoft Graph, and implement Outlook 365 email hub with inbox management features
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
/**
|
||||
* Fociz Live Sync & Realtime Notificatie Hub
|
||||
* Controleert continu op nieuwe Outlook e-mails, relevante Jira tickets en agenda updates.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../includes/auth.php';
|
||||
require_once __DIR__ . '/../includes/integrations/microsoft.php';
|
||||
require_once __DIR__ . '/../includes/integrations/jira.php';
|
||||
|
||||
$user = require_auth();
|
||||
$userId = (int)$user['id'];
|
||||
$db = get_db_connection();
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
try {
|
||||
$nowTs = time();
|
||||
$nowIso = date('Y-m-d H:i:s');
|
||||
$today = date('Y-m-d');
|
||||
|
||||
$clientLastMailId = trim($_GET['last_mail_id'] ?? '');
|
||||
$clientLastJiraKey = trim($_GET['last_jira_key'] ?? '');
|
||||
$clientCalendarHash = trim($_GET['calendar_hash'] ?? '');
|
||||
|
||||
// 1. Outlook E-mail Controle
|
||||
$mailRes = microsoft_fetch_inbox_messages($userId, 5);
|
||||
$messages = $mailRes['messages'] ?? [];
|
||||
$unreadCount = (int)($mailRes['unread_count'] ?? 0);
|
||||
$latestMail = $messages[0] ?? null;
|
||||
|
||||
$hasNewMail = false;
|
||||
$newMailPayload = null;
|
||||
|
||||
if ($latestMail && !empty($latestMail['id'])) {
|
||||
// Als de client een ander ID heeft dan de allernieuwste mail en het bericht is ongelezen
|
||||
if (!empty($clientLastMailId) && $latestMail['id'] !== $clientLastMailId && empty($latestMail['is_read'])) {
|
||||
$hasNewMail = true;
|
||||
$newMailPayload = [
|
||||
'id' => $latestMail['id'],
|
||||
'subject' => $latestMail['subject'],
|
||||
'from_name' => $latestMail['from_name'],
|
||||
'from_email' => $latestMail['from_email'],
|
||||
'received_human' => $latestMail['received_human'],
|
||||
'web_link' => $latestMail['web_link'] ?? '/mail.php',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Jira Tickets Controle (rekening houdend met persoonlijke toewijzing & default queue filter zoals DX of SD)
|
||||
$userFilterCode = $user['default_jira_filter_code'] ?? '';
|
||||
$jiraData = jira_get_user_issues($userId);
|
||||
$assignedIssues = $jiraData['assigned_issues'] ?? ($jiraData['issues'] ?? []);
|
||||
$teamIssues = $jiraData['team_issues'] ?? [];
|
||||
|
||||
$dxValues = ['koppelingen', 'sso-koppeling', 'rest-endpoints', 'datadump', 'uwv-koppeling', 'migratie'];
|
||||
$relevantIssues = [];
|
||||
|
||||
// Filter issues die specifiek relevant zijn voor deze medewerker
|
||||
foreach (array_merge($assignedIssues, $teamIssues) as $iss) {
|
||||
$isAssignedToMe = !empty($iss['assignee_name']) && (stripos($iss['assignee_name'], $user['name']) !== false || stripos($iss['assignee_name'], 'Niek') !== false);
|
||||
$ol = strtolower(trim($iss['onderwerp_logging'] ?? ''));
|
||||
|
||||
$isQueueMatch = false;
|
||||
if (!empty($userFilterCode)) {
|
||||
if ($userFilterCode === 'DX' && in_array($ol, $dxValues)) {
|
||||
$isQueueMatch = true;
|
||||
} elseif ($userFilterCode === 'SD' && ($ol === '' || $ol === 'empty' || !in_array($ol, $dxValues))) {
|
||||
$isQueueMatch = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($isAssignedToMe || $isQueueMatch) {
|
||||
$relevantIssues[$iss['key']] = $iss;
|
||||
}
|
||||
}
|
||||
|
||||
$hasNewJira = false;
|
||||
$newJiraPayload = [];
|
||||
$newestJiraKey = '';
|
||||
|
||||
if (!empty($relevantIssues)) {
|
||||
// Pak het nieuwste ticket
|
||||
$newest = reset($relevantIssues);
|
||||
$newestJiraKey = $newest['key'];
|
||||
|
||||
if (!empty($clientLastJiraKey) && $newestJiraKey !== $clientLastJiraKey) {
|
||||
$hasNewJira = true;
|
||||
$newJiraPayload = [
|
||||
'key' => $newest['key'],
|
||||
'summary' => $newest['summary'],
|
||||
'priority' => $newest['priority'] ?? 'Medium',
|
||||
'issue_type' => $newest['issue_type'] ?? 'Taak',
|
||||
'project' => $newest['project'] ?? 'SD',
|
||||
'url' => $newest['url'] ?? ('/jira.php?highlight=' . $newest['key']),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Outlook Agenda Controle (Tijdlijn events van vandaag)
|
||||
$calendarChanged = false;
|
||||
$todayEvents = [];
|
||||
$stmtInt = $db->prepare("SELECT * FROM integrations WHERE user_id = ? AND provider = 'OUTLOOK' AND is_enabled = 1");
|
||||
$stmtInt->execute([$userId]);
|
||||
$outlookInt = $stmtInt->fetch();
|
||||
|
||||
if ($outlookInt && !empty($outlookInt['access_token']) && empty($outlookInt['is_demo_mode'])) {
|
||||
$todayEvents = microsoft_fetch_today_events($userId, $today);
|
||||
}
|
||||
|
||||
$calHash = md5(json_encode(array_map(function($e) {
|
||||
return ($e['id'] ?? '') . ($e['start'] ?? '') . ($e['end'] ?? '') . ($e['subject'] ?? '');
|
||||
}, $todayEvents)));
|
||||
|
||||
if (!empty($clientCalendarHash) && $calHash !== $clientCalendarHash) {
|
||||
$calendarChanged = true;
|
||||
}
|
||||
|
||||
// 4. Update sync timestamps in database
|
||||
$stmtUpdate = $db->prepare("UPDATE users SET last_mail_check_at = NOW(), last_jira_check_at = NOW() WHERE id = ?");
|
||||
$stmtUpdate->execute([$userId]);
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'server_time' => date('H:i:s'),
|
||||
'mail' => [
|
||||
'has_new' => $hasNewMail,
|
||||
'unread_count' => $unreadCount,
|
||||
'latest_id' => $latestMail['id'] ?? '',
|
||||
'new_mail' => $newMailPayload,
|
||||
],
|
||||
'jira' => [
|
||||
'has_new' => $hasNewJira,
|
||||
'newest_key' => $newestJiraKey,
|
||||
'new_issue' => $newJiraPayload,
|
||||
],
|
||||
'calendar' => [
|
||||
'has_changes' => $calendarChanged,
|
||||
'hash' => $calHash,
|
||||
'count' => count($todayEvents),
|
||||
],
|
||||
]);
|
||||
exit;
|
||||
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'success' => false,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
/**
|
||||
* Fociz Microsoft Outlook 365 E-mail API Endpoint
|
||||
* Verwerkt ophalen van inbox, detailberichten, beantwoorden, doorsturen en 1-klik inplannen.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../includes/auth.php';
|
||||
require_once __DIR__ . '/../includes/integrations/microsoft.php';
|
||||
|
||||
$user = require_auth();
|
||||
$userId = (int)$user['id'];
|
||||
$db = get_db_connection();
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
$action = $_GET['action'] ?? ($_POST['action'] ?? '');
|
||||
|
||||
try {
|
||||
// 1. Inbox berichten ophalen
|
||||
if ($action === 'get_inbox') {
|
||||
$limit = isset($_GET['limit']) ? (int)$_GET['limit'] : 25;
|
||||
$unreadOnly = !empty($_GET['unread_only']) && $_GET['unread_only'] === '1';
|
||||
|
||||
$res = microsoft_fetch_inbox_messages($userId, $limit, $unreadOnly);
|
||||
echo json_encode($res);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 2. Enkel bericht detail ophalen
|
||||
if ($action === 'get_message') {
|
||||
$messageId = trim($_GET['id'] ?? ($_POST['id'] ?? ''));
|
||||
if (empty($messageId)) {
|
||||
echo json_encode(['success' => false, 'error' => 'Geen bericht-ID opgegeven.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$detail = microsoft_fetch_message_detail($userId, $messageId);
|
||||
if (!$detail) {
|
||||
echo json_encode(['success' => false, 'error' => 'Bericht niet gevonden of geen toegang.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
echo json_encode(['success' => true, 'message' => $detail]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 3. Markeer als gelezen of ongelezen
|
||||
if ($action === 'mark_read') {
|
||||
$input = json_decode(file_get_contents('php://input'), true) ?: $_POST;
|
||||
$messageId = trim($input['id'] ?? '');
|
||||
$isRead = isset($input['is_read']) ? (bool)$input['is_read'] : true;
|
||||
|
||||
if (empty($messageId)) {
|
||||
echo json_encode(['success' => false, 'error' => 'Geen bericht-ID opgegeven.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$ok = microsoft_mark_message_read($userId, $messageId, $isRead);
|
||||
echo json_encode(['success' => $ok]);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 4. E-mail beantwoorden
|
||||
if ($action === 'reply') {
|
||||
$input = json_decode(file_get_contents('php://input'), true) ?: $_POST;
|
||||
$messageId = trim($input['id'] ?? '');
|
||||
$comment = trim($input['comment'] ?? '');
|
||||
$replyAll = !empty($input['reply_all']);
|
||||
|
||||
if (empty($messageId) || empty($comment)) {
|
||||
echo json_encode(['success' => false, 'error' => 'Bericht-ID en reactietekst zijn verplicht.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$res = microsoft_reply_message($userId, $messageId, $comment, $replyAll);
|
||||
echo json_encode($res);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 5. E-mail doorsturen
|
||||
if ($action === 'forward') {
|
||||
$input = json_decode(file_get_contents('php://input'), true) ?: $_POST;
|
||||
$messageId = trim($input['id'] ?? '');
|
||||
$toInput = $input['to'] ?? '';
|
||||
$comment = trim($input['comment'] ?? '');
|
||||
|
||||
if (empty($messageId) || empty($toInput)) {
|
||||
echo json_encode(['success' => false, 'error' => 'Bericht-ID en minimaal één geadresseerde zijn verplicht.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$recipients = [];
|
||||
if (is_array($toInput)) {
|
||||
$recipients = $toInput;
|
||||
} else {
|
||||
$parts = explode(',', (string)$toInput);
|
||||
foreach ($parts as $p) {
|
||||
$p = trim($p);
|
||||
if (filter_var($p, FILTER_VALIDATE_EMAIL)) {
|
||||
$recipients[] = ['address' => $p, 'name' => $p];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($recipients)) {
|
||||
echo json_encode(['success' => false, 'error' => 'Geen geldige e-mailadressen ingevoerd.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$res = microsoft_forward_message($userId, $messageId, $recipients, $comment);
|
||||
echo json_encode($res);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 6. E-mail direct inplannen in Fociz dagplanning
|
||||
if ($action === 'plan_email') {
|
||||
$input = json_decode(file_get_contents('php://input'), true) ?: $_POST;
|
||||
$messageId = trim($input['message_id'] ?? '');
|
||||
$subject = trim($input['subject'] ?? 'E-mail actiepunt');
|
||||
$planDate = trim($input['plan_date'] ?? date('Y-m-d'));
|
||||
$timeBlockStart = !empty($input['time_block_start']) ? trim($input['time_block_start']) : null;
|
||||
$durationMinutes = (int)($input['duration_minutes'] ?? 30);
|
||||
$priority = in_array($input['priority'] ?? '', ['P1', 'P2', 'P3']) ? $input['priority'] : 'P2';
|
||||
$webLink = trim($input['web_link'] ?? 'https://outlook.office.com/mail/');
|
||||
$sender = trim($input['sender'] ?? '');
|
||||
|
||||
if (empty($subject)) {
|
||||
echo json_encode(['success' => false, 'error' => 'Onderwerp is verplicht.']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($durationMinutes <= 0) $durationMinutes = 30;
|
||||
|
||||
$timeBlockEnd = null;
|
||||
if (!empty($timeBlockStart)) {
|
||||
try {
|
||||
$dtEnd = new DateTime("{$planDate} {$timeBlockStart}:00", new DateTimeZone('Europe/Amsterdam'));
|
||||
$dtEnd->modify("+{$durationMinutes} minutes");
|
||||
$timeBlockEnd = $dtEnd->format('H:i');
|
||||
} catch (Throwable $t) {}
|
||||
}
|
||||
|
||||
// Zorg dat daily_plan bestaat
|
||||
$stmtPlan = $db->prepare("
|
||||
INSERT INTO daily_plans (user_id, plan_date)
|
||||
VALUES (?, ?)
|
||||
ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID(id)
|
||||
");
|
||||
$stmtPlan->execute([$userId, $planDate]);
|
||||
$planId = (int)$db->lastInsertId();
|
||||
|
||||
$taskTitle = "✉️ " . $subject;
|
||||
if (!empty($sender)) {
|
||||
$taskTitle .= " (van " . $sender . ")";
|
||||
}
|
||||
|
||||
$stmtTask = $db->prepare("
|
||||
INSERT INTO tasks (user_id, daily_plan_id, title, priority, time_block_start, time_block_end, duration_minutes, source, external_id, external_url)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, 'OUTLOOK_MAIL', ?, ?)
|
||||
");
|
||||
$stmtTask->execute([
|
||||
$userId,
|
||||
$planId,
|
||||
$taskTitle,
|
||||
$priority,
|
||||
$timeBlockStart,
|
||||
$timeBlockEnd,
|
||||
$durationMinutes,
|
||||
$messageId,
|
||||
$webLink
|
||||
]);
|
||||
$taskId = (int)$db->lastInsertId();
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'task_id' => $taskId,
|
||||
'plan_date' => $planDate,
|
||||
'message' => "E-mail succesvol ingepland voor {$planDate}."
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
|
||||
echo json_encode(['success' => false, 'error' => 'Onbekende e-mailactie.']);
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/**
|
||||
* Jira Webhook Receiver
|
||||
* Ontvangt webhook events van Atlassian Jira Cloud (issue_created, issue_updated, etc.)
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../config/database.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$rawPayload = file_get_contents('php://input');
|
||||
$data = json_decode($rawPayload, true);
|
||||
|
||||
if (!$data) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => 'Invalid JSON payload']);
|
||||
exit;
|
||||
}
|
||||
|
||||
try {
|
||||
$db = get_db_connection();
|
||||
$event = $data['webhookEvent'] ?? 'jira:unknown';
|
||||
$issueKey = $data['issue']['key'] ?? '';
|
||||
$summary = $data['issue']['fields']['summary'] ?? '';
|
||||
|
||||
// Sla event tijdelijk op of invalidate session cache
|
||||
error_log("Jira Webhook ontvangen: {$event} voor {$issueKey} - {$summary}");
|
||||
|
||||
echo json_encode([
|
||||
'received' => true,
|
||||
'event' => $event,
|
||||
'issue_key' => $issueKey,
|
||||
'timestamp' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
exit;
|
||||
} catch (Throwable $t) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['error' => $t->getMessage()]);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
/**
|
||||
* Microsoft Graph Webhook Receiver
|
||||
* Ontvangt subscription notifications voor /me/mailFolders('inbox')/messages en /me/events
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../config/database.php';
|
||||
|
||||
// Validatie bij aanmaken van Graph subscription
|
||||
if (isset($_GET['validationToken'])) {
|
||||
header('Content-Type: text/plain; charset=utf-8');
|
||||
http_response_code(200);
|
||||
echo $_GET['validationToken'];
|
||||
exit;
|
||||
}
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$raw = file_get_contents('php://input');
|
||||
$data = json_decode($raw, true);
|
||||
|
||||
if ($data && isset($data['value'])) {
|
||||
foreach ($data['value'] as $change) {
|
||||
$resource = $change['resource'] ?? '';
|
||||
$changeType = $change['changeType'] ?? '';
|
||||
error_log("Microsoft Graph Webhook: {$changeType} op {$resource}");
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode(['received' => true]);
|
||||
|
||||
Reference in New Issue
Block a user