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/ICalendarhttps://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.

    Reply
  • 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.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

Scroll to Top