Files
RelaisSMS/app/Services/Calendar/CalendarOccurrenceExpander.php
T

83 lines
2.9 KiB
PHP

<?php
namespace App\Services\Calendar;
use Carbon\CarbonInterface;
use DateTimeZone;
use RuntimeException;
use Sabre\VObject\Component\VCalendar;
use Sabre\VObject\Component\VEvent;
use Sabre\VObject\Reader;
class CalendarOccurrenceExpander
{
public function expand(string $ical, CarbonInterface $from, CarbonInterface $to): string
{
if (! preg_match('/^(RRULE|RDATE|RECURRENCE-ID)[;:]/mi', $ical)) {
return $ical;
}
$calendar = Reader::read($ical);
if (! $calendar instanceof VCalendar) {
throw new RuntimeException('La source agenda ne contient pas un calendrier iCalendar.');
}
$additionalDates = clone $calendar;
$additionalUids = [];
foreach ($calendar->select('VEVENT') as $event) {
// A cancelled detached occurrence may contain only UID and RECURRENCE-ID.
// It must still suppress the original occurrence during expansion.
if (! isset($event->DTSTART) && isset($event->{'RECURRENCE-ID'})) {
$start = clone $event->{'RECURRENCE-ID'};
$start->name = 'DTSTART';
$event->add($start);
}
// VObject chooses RDATE instead of RRULE when both are present.
// Expand their union separately, then deduplicate by occurrence identity.
if (isset($event->RRULE, $event->RDATE)) {
$additionalUids[] = (string) $event->UID;
unset($event->RDATE);
}
}
$timezone = new DateTimeZone((string) config('app.timezone', 'UTC'));
$expanded = $calendar->expand($from, $to, $timezone);
if ($additionalUids !== []) {
foreach ($additionalDates->select('VEVENT') as $event) {
if (! in_array((string) $event->UID, $additionalUids, true)) {
$additionalDates->remove($event);
} else {
unset($event->RRULE);
if (! isset($event->DTSTART) && isset($event->{'RECURRENCE-ID'})) {
$start = clone $event->{'RECURRENCE-ID'};
$start->name = 'DTSTART';
$event->add($start);
}
}
}
$seen = [];
foreach ($expanded->select('VEVENT') as $event) {
$seen[$this->identity($event)] = true;
}
foreach ($additionalDates->expand($from, $to, $timezone)->select('VEVENT') as $event) {
$key = $this->identity($event);
if (! isset($seen[$key])) {
$expanded->add(clone $event);
$seen[$key] = true;
}
}
}
return $expanded->serialize();
}
private function identity(VEvent $event): string
{
return (string) $event->UID.'|'.(string) $event->{'RECURRENCE-ID'};
}
}