Introduction
The Google Apps/Calendar API enables programs to access calendar and event information. It seemed like a good way to have a program take the information from several calendars and merge them into a single master calendar. At first blush this seemed like an easy thing to do, but there are some nuances in the Google API that caused some problems.
The technology I used for this educational project was Zend Framework/PHP, in particular the ZF Google Data APIs. This is the basis for the PHP documentation on the Google API developers site, and seemed as good a place as any to get started.
I am writing this as a task that will be executed as a cron job automatically, not as a web application. The key difference between the two is how authentication is performed. For a cron job, authentication uses ClientLogin, instead of AuthSub.
The Setup
The goal of this example is to access all of the calendars available in a single user's Google Apps account. These can be calendars created by that user, calendars shared with that user by other users, either public or in the Google Apps domain, or calendars that are generated from information from other calendar sources, such as iCal.
I am not going to go into detail to describe how to set up the Zend Framework environment for use by normal command line processing applications, as that is dealt with in detail elsewhere. Having said that, the following is the basic structure required to set it up on my system:
echo "\n\nStarting calendar program...\n";
// Add the Zend Framework library to the include path
// so that we can access the ZF classes
set_include_path('/path/to/local/zend/framwork/library/' .
PATH_SEPARATOR .
get_include_path()
);
// Start Zend Framework autoloader
require_once 'Zend/Loader/Autoloader.php';
Zend_Loader_Autoloader::getInstance();
Authentication
The next step is to authenticate the user in the Google Apps domain:
// Substitute your own user account here
$user = "user@domain.com";
// Substitute your own password here
$pass = "mypassword";
$serviceName = Zend_Gdata_Calendar::AUTH_SERVICE_NAME;
// Create an authenticated HTTP client
try {
$client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $serviceName);
} catch (Zend_Gdata_App_AuthException $e) {
echo 'Error: ' . $e->getMessage();
if ($e->getResponse() != null) {
echo 'Body: ' . $e->getResponse()->getBody();
}
}
In one example I saw, there was an additional catch section before the one here in case Google required a captcha response. Presumably this would occur if Google detected suspicious behavior during login. Unfortunately if that happens, I don't know of any way to automatically respond. After all the whole point of captcha is to require require human intervention. So I left it out, and it seemed to work fine. Maybe I am just lucky.
Load Calendar List
The next step is to load the calendar list:
// Create an instance of the Calendar service
$gdataCal = new Zend_Gdata_Calendar($client);
try {
$calFeed = $gdataCal->getCalendarListFeed();
} catch (Zend_Gdata_App_Exception $e) {
echo "Error: " . $e->getMessage();
}
Load Events for Each Calendar
The final step is to load each calendar's events:
// Iterate through calendars in calendar feed
foreach ($calFeed as $calendar) {
// Print calendar title
echo $calendar->title->text . "\n";
$links = $calendar->getLink();
foreach ($links as $link) {
// This is the mystery part
if ($link->getRel() == 'alternate') {
try {
$eventFeed = $gdataCal->getCalendarEventFeed($link->getHref());
} catch (Zend_Gdata_App_Exception $e) {
echo "Error: " . $e->getMessage();
}
foreach ($eventFeed as $event) {
echo "\t".$event->title . "\n";
}
}
}
echo "\n";
}
The main stumbling block here came in trying to specify which calendar I wanted to load the events for. The default is to load the default user calendar. I was able to determine that the calendar entry includes an array of links, one of which is tagged as "alternate". This link happens to coincide with the link that you can view in Google Apps under calendar settings. I am not sure this is the recommended way, but it seems to work.
The other issue I ran into is that there are two different ways to generate an event feed. In one you specify the URL, as is done here. I the other you specify a query, based on a number of query attributes. It would have been nice to use the query approach, because ideally the program would be able to select events based on some criteria, such as start time. Unfortunately because of the way that calendar links are generated, I was not able to use the information from the calendar feed to generate a query that would work. Maybe some helpful reader can shed some light on this.
At this point the program has everything it needs to put together a master calendar. I leave the rest as an exercise for the user!