237 lines
7.0 KiB
PHP
237 lines
7.0 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Event;
|
|
|
|
use App\Models\Event;
|
|
use App\Models\User;
|
|
use Illuminate\Support\Arr;
|
|
use Illuminate\Support\Str;
|
|
use Sabre\VObject\Component\VEvent;
|
|
use Sabre\VObject\Reader;
|
|
|
|
class EventAttendeeSynchronizer
|
|
{
|
|
public function syncFromCalendarData(Event $event, ?string $calendarData = null): void
|
|
{
|
|
$attendees = $this->extractFromCalendarData($calendarData ?? $event->calendardata);
|
|
$this->syncRows($event, $attendees);
|
|
}
|
|
|
|
public function syncRows(Event $event, array $attendees): void
|
|
{
|
|
$rows = $this->normalizeRows($attendees);
|
|
|
|
$event->attendees()->delete();
|
|
if (empty($rows)) {
|
|
return;
|
|
}
|
|
|
|
foreach ($rows as &$row) {
|
|
$email = $row['email'] ?? null;
|
|
if (!$email) {
|
|
$row['attendee_user_id'] = null;
|
|
continue;
|
|
}
|
|
|
|
$row['attendee_user_id'] = User::query()
|
|
->whereRaw('lower(email) = ?', [Str::lower($email)])
|
|
->value('id');
|
|
}
|
|
unset($row);
|
|
|
|
$event->attendees()->createMany($rows);
|
|
}
|
|
|
|
public function extractFromCalendarData(?string $calendarData): array
|
|
{
|
|
if (!$calendarData) {
|
|
return [];
|
|
}
|
|
|
|
try {
|
|
$vcalendar = Reader::read($calendarData);
|
|
} catch (\Throwable $e) {
|
|
return [];
|
|
}
|
|
|
|
$vevent = $vcalendar->select('VEVENT')[0] ?? null;
|
|
if (!$vevent instanceof VEvent) {
|
|
return [];
|
|
}
|
|
|
|
return $this->extractFromVevent($vevent);
|
|
}
|
|
|
|
public function extractFromVevent(VEvent $vevent): array
|
|
{
|
|
$items = [];
|
|
|
|
foreach ($vevent->select('ATTENDEE') as $attendee) {
|
|
$params = $this->extractParams($attendee);
|
|
$uri = $this->normalizeAttendeeUri((string) $attendee->getValue());
|
|
$email = $this->extractEmail($uri, $params);
|
|
|
|
$items[] = [
|
|
'attendee_uri' => $uri,
|
|
'email' => $email,
|
|
'name' => $params['CN'] ?? null,
|
|
'role' => isset($params['ROLE']) ? strtoupper($params['ROLE']) : null,
|
|
'partstat' => isset($params['PARTSTAT']) ? strtoupper($params['PARTSTAT']) : null,
|
|
'cutype' => isset($params['CUTYPE']) ? strtoupper($params['CUTYPE']) : null,
|
|
'rsvp' => $this->normalizeBooleanParam($params['RSVP'] ?? null),
|
|
'is_organizer' => false,
|
|
'extra' => $this->extraParams($params, [
|
|
'CN',
|
|
'ROLE',
|
|
'PARTSTAT',
|
|
'CUTYPE',
|
|
'RSVP',
|
|
'EMAIL',
|
|
]),
|
|
];
|
|
}
|
|
|
|
if (isset($vevent->ORGANIZER)) {
|
|
$organizer = $vevent->ORGANIZER;
|
|
$params = $this->extractParams($organizer);
|
|
$uri = $this->normalizeAttendeeUri((string) $organizer->getValue());
|
|
$email = $this->extractEmail($uri, $params);
|
|
|
|
$items[] = [
|
|
'attendee_uri' => $uri,
|
|
'email' => $email,
|
|
'name' => $params['CN'] ?? null,
|
|
'role' => 'CHAIR',
|
|
'partstat' => null,
|
|
'cutype' => isset($params['CUTYPE']) ? strtoupper($params['CUTYPE']) : null,
|
|
'rsvp' => null,
|
|
'is_organizer' => true,
|
|
'extra' => $this->extraParams($params, ['CN', 'CUTYPE', 'EMAIL']),
|
|
];
|
|
}
|
|
|
|
return $items;
|
|
}
|
|
|
|
private function normalizeRows(array $attendees): array
|
|
{
|
|
$rows = [];
|
|
$seen = [];
|
|
|
|
foreach ($attendees as $item) {
|
|
$email = $this->normalizeEmail(Arr::get($item, 'email'));
|
|
$uri = $this->normalizeAttendeeUri(Arr::get($item, 'attendee_uri'));
|
|
if (!$uri && $email) {
|
|
$uri = 'mailto:' . $email;
|
|
}
|
|
|
|
if (!$uri && !$email) {
|
|
continue;
|
|
}
|
|
|
|
$key = Str::lower($uri ?: $email);
|
|
if (isset($seen[$key])) {
|
|
continue;
|
|
}
|
|
$seen[$key] = true;
|
|
|
|
$role = Arr::get($item, 'role');
|
|
$partstat = Arr::get($item, 'partstat');
|
|
$cutype = Arr::get($item, 'cutype');
|
|
|
|
$rows[] = [
|
|
'attendee_uri' => $uri,
|
|
'email' => $email,
|
|
'name' => $this->normalizeString(Arr::get($item, 'name')),
|
|
'role' => $role ? strtoupper((string) $role) : null,
|
|
'partstat' => $partstat ? strtoupper((string) $partstat) : null,
|
|
'cutype' => $cutype ? strtoupper((string) $cutype) : null,
|
|
'rsvp' => Arr::exists($item, 'rsvp') ? (bool) Arr::get($item, 'rsvp') : null,
|
|
'is_organizer' => (bool) Arr::get($item, 'is_organizer', false),
|
|
'extra' => is_array(Arr::get($item, 'extra')) ? Arr::get($item, 'extra') : null,
|
|
];
|
|
}
|
|
|
|
return $rows;
|
|
}
|
|
|
|
private function extractParams($property): array
|
|
{
|
|
$params = [];
|
|
foreach ($property->parameters() as $parameter) {
|
|
$params[strtoupper((string) $parameter->name)] = trim((string) $parameter->getValue());
|
|
}
|
|
|
|
return $params;
|
|
}
|
|
|
|
private function extractEmail(?string $uri, array $params): ?string
|
|
{
|
|
$fromParam = $this->normalizeEmail($params['EMAIL'] ?? null);
|
|
if ($fromParam) {
|
|
return $fromParam;
|
|
}
|
|
|
|
if (!$uri) {
|
|
return null;
|
|
}
|
|
|
|
if (str_starts_with(Str::lower($uri), 'mailto:')) {
|
|
return $this->normalizeEmail(substr($uri, 7));
|
|
}
|
|
|
|
return str_contains($uri, '@') ? $this->normalizeEmail($uri) : null;
|
|
}
|
|
|
|
private function normalizeAttendeeUri(mixed $uri): ?string
|
|
{
|
|
$value = trim((string) $uri);
|
|
if ($value === '') {
|
|
return null;
|
|
}
|
|
|
|
if (str_contains($value, '@') && !str_contains($value, ':')) {
|
|
return 'mailto:' . Str::lower($value);
|
|
}
|
|
|
|
if (str_starts_with(Str::lower($value), 'mailto:')) {
|
|
return 'mailto:' . Str::lower(substr($value, 7));
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
private function normalizeEmail(mixed $value): ?string
|
|
{
|
|
$email = trim((string) $value);
|
|
if ($email === '') {
|
|
return null;
|
|
}
|
|
|
|
return Str::lower($email);
|
|
}
|
|
|
|
private function normalizeString(mixed $value): ?string
|
|
{
|
|
$string = trim((string) $value);
|
|
return $string === '' ? null : $string;
|
|
}
|
|
|
|
private function normalizeBooleanParam(?string $value): ?bool
|
|
{
|
|
if ($value === null || $value === '') {
|
|
return null;
|
|
}
|
|
|
|
return in_array(strtoupper($value), ['1', 'TRUE', 'YES'], true);
|
|
}
|
|
|
|
private function extraParams(array $params, array $known): ?array
|
|
{
|
|
$knownMap = array_fill_keys($known, true);
|
|
$extra = array_diff_key($params, $knownMap);
|
|
|
|
return empty($extra) ? null : $extra;
|
|
}
|
|
}
|