Integrating Google Calendar with a Laravel site
Tags: php, laravel, googleapis
Published: 2017-11-27 13:39:17

---
title: Integrating Google Calendar with a Laravel site
published: true
description: 
tags: php, laravel, googleapis
---

We are getting to the end of work on integrating Google Calendar via the Google API into a client site (Laravel). I thought I would write up our solution to hopefully help the next person who needs to do this.

#Intro
The client wanted to connect venues that offer free or low cost spaces with youth organisations that needed space for their activities.

The venues have profiles on the site with a calendar to show when they have availability. The requirement was for venues to be able to add bookings to this calendar and it automatically add them to the linked Google Calendar and vice versa.

#Overview
![alt text](https://thepracticaldev.s3.amazonaws.com/i/j9dia7pp02w6e6tpcuoh.png "Google OAuth2 flow diagram")
Image source: [https://developers.google.com/identity/protocols/OAuth2](https://developers.google.com/identity/protocols/OAuth2)

In order to pull any data from the venue's linked Google Calendar the venue must give consent to the site to access it.

As documented in the diagram above the venue granting consent provides the site with an authorisation code that the site can then swap for an access token with the Google API. This access token can only be used to interact with the service the venue has consented access to. In this case it is Google Calendar.

#Creating a project
The very first step was to get the client to setup a project using their Google account. This quick start guide from Google gives a good walkthrough: [https://developers.google.com/google-apps/calendar/quickstart/php](https://developers.google.com/google-apps/calendar/quickstart/php)

It also goes onto getting a demo up and running, but I'm going to skip that.

Once the project was setup the client needed to setup credentials for accessing the Google Calendar API through this project.

![alt text](https://thepracticaldev.s3.amazonaws.com/i/4zh6jnakbpekiwp8rkds.png "Google credentials prompt")

#Adding credentials
There is a wizard for creating credentials. Its not amazingly clear so here are screenshots of what we setup. Note: I've used dummy content instead of the client data.

The first step asks which API will be used and how it will be accessed
![alt text](https://thepracticaldev.s3.amazonaws.com/i/9t0wrsrbvojo0aoact44.png "Google credentials wizard first step")

The second step whitelists URLs and setup oAuth callback paths.
![alt text](https://thepracticaldev.s3.amazonaws.com/i/02mb7rz7cveanlgs2tey.png "Google credentials wizard second step")

The third step sets up settings for the consent form the venues will be presented with.
![alt text](https://thepracticaldev.s3.amazonaws.com/i/nw5ukwaud821m5py55t9.png "Google credentials wizard third step")

The fourth step gives you the client ID and credentials.
![alt text](https://thepracticaldev.s3.amazonaws.com/i/zu1tjdxycpxco3fqxkqw.png "Google credentials wizard fourth step")

Clicking 'download' on that last screen gives you the client_id.json file which is the sites key to the API via the clients project. This should be stored on the server in a private location.


``` json
{  
   "web":{  
      "client_id":"[hash-string].apps.googleusercontent.com",
      "project_id":"calendar-integration-[project-id]",
      "auth_uri":"https://accounts.google.com/o/oauth2/auth",
      "token_uri":"https://accounts.google.com/o/oauth2/token",
      "auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs",
      "client_secret":"[hash-string]",
      "redirect_uris":[  
         "https://www.example.com/oauth2callback"
      ],
      "javascript_origins":[  
         "https://www.example.com"
      ]
   }
}
```

#Require the Google API Client
As this is a Laravel site we already have Composer setup so first up we require the Google API client:

`composer require google/apiclient:^2.0`

This gives us a PHP Library to communicate with the Google APIs plus a load of helper functions for each API and OAuth2.

More information can be found here: [https://github.com/google/google-api-php-client](https://github.com/google/google-api-php-client)

#Authorisation

##Requesting consent
First step for the site is to provide a means for venues to grant consent for the site to access their Google Calendar. 

For that we need to create a link that will send venues to Google which will show the consent screen.

For this we will initialise the Google Client provided by `google/apiclient` and set our application specific settings.

``` php
<?php

namespace App\Helpers;

// Initialise the client.
$client = new Google_Client();
// Set the application name, this is included in the User-Agent HTTP header.
$client->setApplicationName('Calendar integration');
// Set the authentication credentials we downloaded from Google.
$client->setAuthConfig('[private-path]/client_id.json');
// Setting offline here means we can pull data from the venue's calendar when they are not actively using the site.
$client->setAccessType("offline");
// This will include any other scopes (Google APIs) previously granted by the venue
$client->setIncludeGrantedScopes(true);
// Set this to force to consent form to display.
$client->setApprovalPrompt('force');
// Add the Google Calendar scope to the request.
$client->addScope(Google_Service_Calendar::CALENDAR);
// Set the redirect URL back to the site to handle the OAuth2 response. This handles both the success and failure journeys.
$client->setRedirectUri(URL::to('/') . '/oauth2callback');
    
```

*Note:* The above code block is used for all interaction with the Google API. We put this into a helper class so we don't have to duplicate this anywhere.

Once we have the Google Client setup we can use the built in `createAuthUrl()` method to return a link to Google for venues to grant consent to their Google Calendar.

``` php
<?php
// Set state allows us to match the consent to a specific venues
$client->setState($venue->id);
// The Google Client gives us a method for creating the 
$client->createAuthUrl();
```

When venue clicks the link they are redirected to Google and asked to grant consent for the site to access their Google Calendar.

![alt text](https://thepracticaldev.s3.amazonaws.com/i/arawr1f1nixqng5qbbbj.png "Google consent form")

##Handling the response
Google will redirect the venue back to the site at the URL specified here `$client->setRedirectUri(URL::to('/') . '/oauth2callback');`. This route is used both if the venue has consented to access to their Google Calendar and if they have refused.

``` php
<?php
/**
 * Google OAuth2 route
 */
Route::get('oauth2callback', [
    'as' => 'oauth',
    'uses' => 'OAuthController@index'
]);
```

This route says when a GET request is made to `/oauth2callback` then use the `index` method in the `OAuthController` controller.

More information about Laravels routing can be found here: [https://laravel.com/docs/5.5/routing](https://laravel.com/docs/5.5/routing)

This is what that method looks like:
``` php
<?php

public function index(Request $request)
    {
        // Get all the request parameters
        $input = $request->all();

        // Attempt to load the venue from the state we set in $client->setState($venue->id);
        $venue = Venue::findOrFail($input['state']);

        // If the user cancels the process then they should be send back to
        // the venue with a message.
        if (isset($input['error']) &&  $input['error'] == 'access_denied') {
            \Session::flash('global-error', 'Authentication was cancelled. Your calendar has not been integrated.');
            return redirect()->route('venues.show', ['slug' => $venue->slug]);

        } elseif (isset($input['code'])) {
            // Else we have an auth code we can use to generate an access token

            // This is the helper we added to setup the Google Client with our             
            // application settings
            $gcHelper = new GoogleCalendarHelper($venue);

            // This helper method calls fetchAccessTokenWithAuthCode() provided by 
            // the Google Client and returns the access and refresh tokens or 
            // throws an exception
            $accessToken = $gcHelper->getAccessTokenFromAuthCode($input['code']);

            // We store the access and refresh tokens against the venue and set the 
            // integration to active.
            $venue->update([
                'gcalendar_credentials' => json_encode($accessToken),
                'gcalendar_integration_active' => true,
            ]);

            \Session::flash('global-success', 'Google Calendar integration enabled.');
            return redirect()->route('venues.show', ['slug' => $venue->slug]);
        }
    }

```

This now allows us to access the venue's Google Calendar using the access token returned from `fetchAccessTokenWithAuthCode()`. This method doesn't just return the access token it returns a few other bits too:

``` json
{  
   "access_token":"[hash-string]",
   "token_type":"Bearer",
   "expires_in":3600,
   "created":1510917377,
   "refresh_token":"[hash-string]"
}
```
This return has been json encoded for storage in the database.

This token only lives for an hour as denoted by the `expires_in` above. When the access token expires we will need to use the `refresh_token` to request a new access token.

##Refreshing the access token
The Google Client provides methods for checking if the current access token is expired. We pass in the value of `$venue->gcalendar_credentials` which is the JSON above to `setAccessToken()` and then refresh the token if expired.

``` php
<?php
// Refresh the token if it's expired.
$client->setAccessToken($venue->gcalendar_credentials);
if ($client->isAccessTokenExpired()) {
    $accessToken = $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
    $venue->update([
        'gcalendar_credentials' => json_encode($accessToken),
    ]);
}
```

We save that in the database against the venue again. This was added to the helper class we setup that initialises the Google Client.

#Pulling bookings from Google Calendar
Now we have a valid access token we can start polling the venue's Google Calendar for events.

``` php
<?php

// Load up the helper to initialise the Google Client
$gcHelper = new GoogleCalendarHelper($venue);

// Use the Google Client calendar service. This gives us methods for interacting 
// with the Google Calendar API
$service = $gcHelper->getCalendarService();

// Set over what timeframe we want to grab calendar events
// Dates must be an RFC3339 timestamp with mandatory time zone offset, e.g.,
// 2011-06-03T10:00:00-07:00
$optParams = array(
    'orderBy' => 'startTime',
    'singleEvents' => true,
    'timeMin' => '2011-06-03T10:00:00-07:00',
    'timeMax' => '2011-06-03T10:00:00-23:00',
);

// Poll this venue's Google Calendar
$googleBookings = $service->events->listEvents($calendarId, $optParams);

// Check if we have any events returned
if (count($googleBookings->getItems()) > 0) {
```

Once we have a list of events from Google Calendar we save those to the database to display as bookings on the site.

#Pushing bookings to Google Calendar
When a venue adds a booking to the site, one is automatically created in their Google Calendar.

``` php
<?php

// Set the start time and date for pushing to Google.
$tz = new DateTimeZone('Europe/London');
$startTime = Carbon::create(
    2017,
    11,
    25,
    10,
    0,
    0,
    $tz
);

// Use the Google date handling provided by the Google Client
$googleStartTime = new Google_Service_Calendar_EventDateTime();
$googleStartTime->setTimeZone($tz);
$googleStartTime->setDateTime($endTime->format('c'));

// Create the calendar event and give a default title.
$event = new Google_Service_Calendar_Event();
$event->setStart($googleStartTime);
// Same process to create end time as we use for the start time above. Code 
// omitted for brevity.
$event->setEnd($googleEndTime);
$event->setSummary('Booking automatically created from Calendar Example');

// Use the Google Client calendar service to push 
$service = $gcHelper->getCalendarService();
$createdEvent = $service->events->insert($calendarId, $event);

// Save the newly created event id against the booking.
if (!empty($createdEvent->id)) {
```

If the `$createdEvent` has an id then we have been successful in pushing this booking to Google Calendar as a new event.

This id can be used to delete the event from Google Calendar like so `$service->events->delete($calendarId,$eventId);`.

#Gotchas

##Refresh token
From our experience `$client->setApprovalPrompt('force');` is required to get a refresh token back along with the auth token. Having looked at a load of SO articles it looks like this should also force the user to grant consent to their Google Calendar each time the auth token expires, but this hasn't been the case for us.

##Timezones
You'll notice when we created the event in the venue's Google Calendar we set the timezone to `Europe/London`. By default the API uses one of the American timezones. This wasn't suitable for us in the UK.

##Created events
When an event is created via the API the venue still needed to pull them into their calendar. This works the same way as when someone invites you to an event and you have to accept before it goes into your calendar.

#Summary
In summary using the Google API was pretty straight forward with the `google/apiclient`. The OAuth stuff was the trickiest part and that was mostly down to a lot of conflicting information on SO and other sites about how auth/access/refresh tokens work.

The Google documentation itself is good for the best case scenario, but information on when things go wrong is non-existent.


Any and all comments welcome