type !== QrCode::TYPE_EVENT || ! $this->mail->isConfigured()) { return $event; } $mailbox = $this->resolveMailbox($owner); if ($mailbox === null) { return $event; } $entries = $this->entriesFor($event); $previous = (array) data_get($event->payload, 'calendar_sync.items', []); $synced = []; foreach ($entries as $entry) { $result = $this->mail->upsertEvent(array_merge($entry, [ 'mailbox_email' => $mailbox, ])); if ($result && isset($result['id'])) { $synced[$entry['external_ref']] = (int) $result['id']; } } foreach ($previous as $externalRef => $eventId) { if (! isset($synced[$externalRef]) && is_numeric($eventId)) { $this->mail->deleteEvent((int) $eventId, $mailbox, (string) $externalRef); } } if ($entries === [] && $previous !== []) { foreach ($previous as $externalRef => $eventId) { if (is_numeric($eventId)) { $this->mail->deleteEvent((int) $eventId, $mailbox, (string) $externalRef); } } } $payload = (array) ($event->payload ?? []); $payload['calendar_sync'] = ['items' => $synced]; $event->payload = $payload; $event->save(); Log::info('Events mail calendar synced', [ 'event_id' => $event->id, 'entries' => count($synced), ]); return $event->fresh(); } /** * @return list> */ public function entriesFor(QrCode $event): array { $content = $event->content(); $format = (string) ($content['format'] ?? 'in_person'); $sessions = array_values(array_filter( (array) ($content['virtual_sessions'] ?? []), fn ($session) => is_array($session) && trim((string) ($session['title'] ?? '')) !== '', )); $entries = []; if (in_array($format, ['virtual', 'hybrid'], true) && $sessions !== []) { foreach ($sessions as $session) { $entry = $this->sessionEntry($event, $content, $session); if ($entry !== null) { $entries[] = $entry; } } if ($entries === [] && filled($content['starts_at'] ?? null)) { $entries[] = $this->mainEventEntry($event, $content); } elseif ($format === 'hybrid' && filled($content['starts_at'] ?? null)) { $entries[] = $this->mainEventEntry($event, $content, ' (in person)'); } return $entries; } if (filled($content['starts_at'] ?? null)) { $entries[] = $this->mainEventEntry($event, $content); } return $entries; } /** * @param array $content * @param array $session * @return array|null */ private function sessionEntry(QrCode $event, array $content, array $session): ?array { $sessionId = trim((string) ($session['id'] ?? '')); if ($sessionId === '') { $sessionId = 'vs-'.Str::lower(Str::random(12)); } $starts = $this->parseDate($session['scheduled_at'] ?? $content['starts_at'] ?? null); if ($starts === null) { return null; } $duration = max(15, (int) ($session['duration_minutes'] ?? 60)); $joinUrl = trim((string) ($session['join_url'] ?? '')); $description = trim((string) ($content['description'] ?? '')); return [ 'external_ref' => $this->sessionExternalRef($event->id, $sessionId), 'title' => trim((string) ($session['title'] ?? $content['name'] ?? $event->label)), 'body' => $description !== '' ? $description : null, 'starts_at' => $starts->toIso8601String(), 'ends_at' => $starts->copy()->addMinutes($duration)->toIso8601String(), 'join_url' => $joinUrl !== '' ? $joinUrl : $event->publicUrl(), ]; } /** * @param array $content * @return array */ private function mainEventEntry(QrCode $event, array $content, string $titleSuffix = ''): array { $starts = $this->parseDate($content['starts_at'] ?? null) ?? now(); $ends = $this->parseDate($content['ends_at'] ?? null) ?? $starts->copy()->addHours(2); $name = trim((string) ($content['name'] ?? $event->label)); $location = trim((string) ($content['location'] ?? '')); $body = trim((string) ($content['description'] ?? '')); if ($location !== '') { $body = $body !== '' ? $body."\n\n".$location : $location; } return [ 'external_ref' => $this->eventExternalRef($event->id), 'title' => $name.$titleSuffix, 'body' => $body !== '' ? $body : null, 'starts_at' => $starts->toIso8601String(), 'ends_at' => $ends->toIso8601String(), 'join_url' => $event->publicUrl(), ]; } private function eventExternalRef(int $eventId): string { return 'events:'.$eventId; } private function sessionExternalRef(int $eventId, string $sessionId): string { return 'events:'.$eventId.':session:'.$sessionId; } private function parseDate(mixed $value): ?CarbonInterface { if ($value === null || $value === '') { return null; } try { return Carbon::parse($value); } catch (\Throwable) { return null; } } private function resolveMailbox(User $user): ?string { try { $status = $this->identity->mailboxLinkStatus($user->public_id); $linked = strtolower(trim((string) ($status['linked_mailbox'] ?? ''))); if (filter_var($linked, FILTER_VALIDATE_EMAIL)) { return $linked; } } catch (\Throwable) { // Fall back to the account email. } $email = strtolower(trim((string) $user->email)); return filter_var($email, FILTER_VALIDATE_EMAIL) ? $email : null; } }