firstOrFail(); $calendar = $instance->calendar()->with(['events.meta'])->firstOrFail(); $timezone = $instance->timezone ?? 'UTC'; $ical = $this->generateICalendarFeed($calendar->events, $timezone); return Response::make($ical, 200, [ 'Content-Type' => 'text/calendar; charset=utf-8', 'Content-Disposition' => 'inline; filename="' . $calendarUri . '.ics"', ]); } protected function generateICalendarFeed($events, string $tz): string { $vcalendar = new VCalendar(); $vcalendar->add('VERSION', '2.0'); $vcalendar->add('PRODID', '-//Kithkin Calendar//EN'); $vcalendar->add('CALSCALE', 'GREGORIAN'); $vcalendar->add('METHOD', 'PUBLISH'); foreach ($events as $event) { $ical = $event->calendardata ?? null; if ($ical) { try { $parsed = Reader::read($ical); foreach ($parsed->select('VEVENT') as $vevent) { $vcalendar->add(clone $vevent); } continue; } catch (\Throwable $e) { // fall through to meta-based output } } $meta = $event->meta; if (!$meta || !$meta->start_at || !$meta->end_at) { continue; } $start = Carbon::parse($meta->start_at)->timezone($tz); $end = Carbon::parse($meta->end_at)->timezone($tz); $vevent = $vcalendar->add('VEVENT', []); $vevent->add('UID', $event->uid); $vevent->add('SUMMARY', $meta->title ?? '(Untitled)'); $vevent->add('DESCRIPTION', $meta->description ?? ''); $vevent->add('DTSTART', $start, ['TZID' => $tz]); $vevent->add('DTEND', $end, ['TZID' => $tz]); $vevent->add('DTSTAMP', Carbon::parse($event->lastmodified)->utc()); if ($meta->location) { $vevent->add('LOCATION', $meta->location); } } return $vcalendar->serialize(); } protected function escape(?string $text): string { return str_replace(['\\', ';', ',', "\n"], ['\\\\', '\;', '\,', '\n'], $text ?? ''); } }