Provide subscribable calendar with Laravel
For a small event platform for my friends and me, I needed a subscribable calendar for e. g. Google Calendar. I had to pay attention to a few things and find out, so that it behaves correctly.
Looking for a suitable package, I found https://github.com/jasvrcek/ICS which I used for this short guide (thank’s to jasvrcek).
Install the package
Just install the package as every composer package with:
composer require jsvrcek/ics
Add endpoint for ics file
The calendar should be provided simply as file that can be downloaded. As we don’t want to allow every one to see the calender, I created a random string and added a route like this:
Route::get('calendar/jECECfZ49WqxQ4HVfhwDxrTROK6r7806c1UzPMW6ZeiLnLGrrOqglwQHfbpZ/CalendarName.ics', 'CalendarController') ->name("shared_calendar");
Of course you can protect this url via a web or api guard or via basic auth, but this is not part of this guide.
Build up the calendar
The following code will live in the __invoke
method of the (Single Action) CalendarController.
In the choosen package we can create a new calendar like this:
$calendar = new Calendar(); $calendar->setProdId('-//Calendar Name');
Because an event has a date, we have to set the time zone correctly so that the corresponding calendar can display it correctly. As we use Laravel we can just use the global timezone set in the config/app.php file.
$timezone = config('app.timezone'); $timeZone = new \DateTimeZone($timezone); $calendar->setTimezone($timeZone);
For supporting more Calendar programms we add some non standard headers (Follow the links if you like to learn more about them):
$calendar->setCustomHeaders([ 'X-WR-TIMEZONE' => $timezone, // Support e.g. Google Calendar -> https://blog.jonudell.net/2011/10/17/x-wr-timezone-considered-harmful/ 'X-WR-CALNAME' => 'Calendar Name', // https://en.wikipedia.org/wiki/ICalendar 'X-PUBLISHED-TTL' => 'PT15M' // update calendar every 15 minutes ]);
Add the events
Now we are ready to add the events from the DB to the calendar. You can find the uses properties of the Event-Model by following the code.
$events = Event::all(); foreach ($events as $event) { $calendarEvent = new CalendarEvent(); $calendarEvent->setStart($event->date_from) ->setEnd($event->date_to) ->setSummary($event->summary) ->setStatus('CONFIRMED') ->setUid("event.{$event->id}"); $calendar->addEvent($calendarEvent); }
All the events in the Database are confirmed so I will hardcoded this status. You can dynamically set it with other values (https://en.wikipedia.org/wiki/ICalendar, https://icalendar.org/RFC-Specifications/iCalendar-RFC-5545/ ).
You can add the organizer or attendees or return it as a batched stream by following the readme in the package’s repo under https://github.com/jasvrcek/ICS/blob/master/README.md
Return the file
When we built the calendar with it’s events, we can return it as a downloadable file. I found this (https://stackoverflow.com/a/41434119/4444567) useful response macro (https://laravel.com/docs/5.5/responses#response-macros) and changed it a bit to fit my needs:
Response::macro('attachment', function ($content, $name, $contentType) { $headers = [ 'Content-type' => $contentType.'; charset=utf-8', 'Content-Disposition' => 'attachment; filename="'.$name.'.csv"', ]; return response($content, 200, $headers); });
After I added this to my AppServiceProvider’s boot method the calendar could be provided like this:
$calendarExport = new CalendarExport(new CalendarStream, new Formatter()); $calendarExport->addCalendar($calendar); return response()->attachment($calendarExport->getStream(), $name, 'text/calendar', 'ics');
A important part while providing the download, is to set the correct filename in the Content-Disposition-Header. Otherwise Google Calendar will use the url as name which is kinda ugly.
Validate the output
You can use this validator to check if you missed something or wan’t to check the format after added more properties.
Accessing the Calendar
You can provide the link with the webcal protocol so that it is opened with the default calendar program like this:
webcal://yourhost.com/calendar/jECECfZ49WqxQ4HVfhwDxrTROK6r7806c1UzPMW6ZeiLnLGrrOqglwQHfbpZ/CalendarName.ics
Note that access could be unencrypted (without https). Furthermore, webcal is not an official protocol and cannot be generated with the Laravel route
helper.
Update: Thanks to jules
for reporting some bugs and giving tips.
4 Comments
Good guide, but I think you meant to put another extension in the ‘attachment’ macro. ‘ics’ instead ‘csv’ most likely.
Another point I’d like to add: linking to this endpoint with a `webcal` protocol (instead of http / https) will automatically make it a subscription calendar for iCal / Outlook.
Thank you, I’ve fixed the bug and added a notice about the webcal protocol.
Great guide just a helpful note, this line of code wasn’t liked by my editor ->setUid(‘event.’$event->id); so I changed it to ->setUid(“event.{$event->id}”) which incorporates the event ID in the same string rather than concatenating.
Thanks, there was a missing string-concat-point.