在 PHP 应用程序中集成 Google Calendar

使用 PHP 处理 Google Calendar 的数据并集成到自定义应用程序中

Google Calendar 允许 Web 应用程序开发人员通过其基于 REST 的 Developer API 访问用户生成的内容和事件信息。PHP 的 SimpleXML 扩展和 Zend 的 GData Library 非常适合处理这种 API 生成的 XML 提要,可利用它们建立自定义的 PHP 应用程序。本文介绍了 Google Calendar Data API,示范如何浏览用户生成的日程表、添加和修改日程表事件以及按照关键字搜索。

Vikram Vaswani, 创始人, IBM

Vikram Vaswani 是 Melonfire 的创始人兼 CEO,这是一家专长于开放源代码工具和技术的咨询服务公司。他还是 PHP Programming SolutionsHow to do Everything with PHP and MySQL 这两本书的作者。



2008 年 9 月 08 日

简介

很长时间以来,我一直使用的个人计划工具是 PalmPilot 自带的日程表工具。但在过去两年中,我逐渐转向了 Google Calendar:不仅仅因为它更具备 Web 特性,而且更容易共享事件信息、组织预约和处理不同类型的事件。

常用的缩写词

  • API:应用程序编程接口(Application Programming Interface)
  • HTTP:超文本传输协议(Hypertext Transfer Protocol)
  • PHP:超文本预处理器(Hypertext Preprocessor)
  • REST:具象状态传输(Representational state transfer)
  • RSS:真正简单的连锁(Really Simple Syndication)
  • URL:统一资源定位符(Uniform Resource Locator)
  • XML:可扩展标记语言

作为一名开发人员,我发现 Google Calendar 非常便于非正式的谈话:开发人员利用其 Data API 很容易对公共和用户私有日程表中存储的数据创建新的应用程序。这种 API 采用了 REST 模型,可通过任何支持 XML 的开发工具包访问,并且为很多常用的编程语言提供了客户机上的库,包括我钟爱的 PHP。

本文将介绍 Google Calendar Data API,说明如何在自定义的 PHP 应用程序中集成和使用日程表数据。本文通过例子说明了如何:

  • 从用户公共记事本中检索事件
  • 添加新的事件
  • 修改和删除事件
  • 按照关键字或日期范围搜索事件

我们开始吧!


理解 Calendar Data API

编写 PHP 代码之前,首先简单了解一下 Google Calendar Data API。和所有基于 REST 的服务一样,这种 API 也接受包含一个或多个 XML 编码参数的 HTTP 请求,返回任何 XML 感知客户机都能解析的 XML 编码响应。对于 Google Calendar Data API,响应通常是一个包含请求信息的 Atom 或者 RSS 提要。

典型的 Google Calendar 提要包含大量的信息,足以创建有用的相关应用程序。我们来看一个例子,登录到 Google Calendar 帐户然后导航到日程表设置,找到您个人的日程表私有地址 URL。这个应该保密的 URL 提供了到日程表提要的只读访问,不需要首次授权,形式为 http://www.google.com/calendar/feeds/userid/private-magicCookie/basic。将该 URL 粘贴到 Web 浏览器(或者通过 HTTP 客户机发送 GET 来请求该提要),得到的结果如 清单 1 所示:

清单 1. Google Calendar 提要示例
<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns='http://www.w3.org/2005/Atom' 
 xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' 
 xmlns:batch='http://schemas.google.com/gdata/batch' 
 xmlns:gCal='http://schemas.google.com/gCal/2005' 
 xmlns:gd='http://schemas.google.com/g/2005'>
  <id>http://www.google.com/calendar/feeds/user@gmail.com/
   private-cookie/basic</id>
  <updated>2008-06-13T19:15:18.000Z</updated>
  <category scheme='http://schemas.google.com/g/2005#kind' 
  term='http://schemas.google.com/g/2005#event'/>
  <title type='text'>Joe User</title>
  <subtitle type='text'>Joe User</subtitle>
  <link rel='alternate' type='text/html' 
  href='http://www.google.com/calendar/embed?src=user@gmail.com'/>
  <link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' 
   href='http://www.google.com/calendar/feeds/user@gmail.com/private-cookie/basic'/>
  <link rel='http://schemas.google.com/g/2005#batch' type='application/atom+xml' 
   href='http://www.google.com/calendar/feeds/user@gmail.com/private-cookie/basic/batch'
    />
  <link rel='self' type='application/atom+xml' 
  href='http://www.google.com/calendar/feeds/user@gmail.com/private-cookie/basic?
  max-results=25'/>
  <author>
    <name>Joe User</name>
    <email>user@gmail.com</email>
  </author>
  <generator version='1.0' uri='http://www.google.com/calendar'
  >Google Calendar</generator>
  <openSearch:totalResults>4</openSearch:totalResults>
  <openSearch:startIndex>1</openSearch:startIndex>
  <openSearch:itemsPerPage>25</openSearch:itemsPerPage>
  <gCal:timezone value='Asia/Calcutta'/>
  <entry>
    <id>http://www.google.com/calendar/feeds/user@gmail.com/
    private-cookie/basic/xxxxxxxx</id>
    <published>2008-06-12T08:49:38.000Z</published>
    <updated>2008-06-13T19:06:21.000Z</updated>
    <category scheme='http://schemas.google.com/g/2005#kind' 
    term='http://schemas.google.com/g/2005#event'/>
    <title type='html'>Swim party</title>
    <summary type='html'>When: Sat Jun 21, 2008 12pm to 3:30pm&nbsp;
IST<br>
<br>Event Status: confirmed</summary>
    <content type='html'>When: Sat Jun 21, 2008 12pm to 3:30pm 
IST<br />
<br />Event Status: confirmed</content>
    <link rel='alternate' type='text/html' 
    href='http://www.google.com/calendar/event?eid=cGxwbHExOYHHDlOHQ4ZjA
    yMGMgdmlrcmFtLm1lbG9uZmlyZUBnb29nxmNvbQ' title='alternate'/>
    <link rel='self' type='application/atom+xml' 
    href='http://www.google.com/calendar/feeds/user@gmail.com/
    private-cookie/basic/ddddddddddddddd/>
    <author>
      <name>Joe User</name>
      <email>user@gmail.com</email>
    </author>
  </entry>
  <entry>
    <id>http://www.google.com/calendar/feeds/user@gmail.com/
    private-cookie/basic/yyyyyyyyyyyyyyyy</id>
    <published>2008-06-12T08:48:30.000Z</published>
    <updated>2008-06-12T08:48:46.000Z</updated>
    <category scheme='http://schemas.google.com/g/2005#kind' 
    term='http://schemas.google.com/g/2005#event'/>
    <title type='html'>Dinner with the gang</title>
    <summary type='html'>When: Wed Jun 11, 2008 7pm to 9:30pm&nbsp;
IST<br>
<br>Event Status: confirmed</summary>
    <content type='html'>When: Wed Jun 11, 2008 7pm to 9:30pm 
IST<br />
<br />Event Status: confirmed</content>
    <link rel='alternate' type='text/html' 
    href='http://www.google.com/calendar/event?eid=
    MmhpYmV2cmowMM2kam9lZDQgdcmFtLm1lbG9uZmlyZU4858Bnb29nbGVtYWlsLmNvbQ' 
    title='alternate'/>
    <link rel='self' type='application/atom+xml' 
    href='http://www.google.com/calendar/feeds/user@gmail.com/private-cookie/
    basic/hhhhhhhhhhhhhhhhh'/>
    <author>
      <name>Joe User</name>
      <email>user@gmail.com</email>
    </author>
  </entry>
  <entry>
  ...
  </entry>
</feed>

所有的 Calendar 提要都以 <feed> 作为根元素。<feed> 元素中的 <link> 元素包含不同提要版本的 URL,而 <openSearch:> 元素则包含摘要统计信息。

最外层的 <feed> 元素包括一个或多个 <entry> 元素,分别对应一个日程表事件。每个 <entry> 包含更详细的信息,如标题、描述、发布日期、最近更新日期、事件提要 URL 以及每个事件的作者。分别用 <title>、<summary>、<published>、<updated>、<link> 和 <author> 元素表示。


使用 SimpleXML 检索事件列表

下面看一个使用 PHP 处理 Google Calendar 提要的例子。清单 2 读取 清单 1 中的提要并使用 SimpleXML 提取有关的数据,然后再将它转化为页面:

清单 2. 使用 SimpleXML 检索事件列表
<!DOCTYPE html 
  PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title>Listing calendar contents</title>
    <style>
    body {
      font-family: Verdana;      
    }
    li {
      border-bottom: solid black 1px;      
      margin: 10px; 
      padding: 2px; 
      width: auto;
      padding-bottom: 20px;
    }
    h2 {
      color: red; 
      text-decoration: none;  
    }
    span.attr {
      font-weight: bolder;  
    }
    </style>    
  </head>
  <body>
    <?php
    $userid = 'username%40googlemail.com';
    $magicCookie = 'cookie';
    
    // build feed URL
    $feedURL = "http://www.google.com/calendar/feeds/$userid/private-$magicCookie/basic";
    
    // read feed into SimpleXML object
    $sxml = simplexml_load_file($feedURL);
    
    // get number of events
    $counts = $sxml->children('http://a9.com/-/spec/opensearchrss/1.0/');
    $total = $counts->totalResults; 
    ?>
    <h1><?php echo $sxml->title; ?></h1>
    <?php echo $total; ?> event(s) found.
    <p/>
    <ol>
    <?php    
    // iterate over entries in category
    // print each entry's details
    foreach ($sxml->entry as $entry) {
      $title = stripslashes($entry->title);
      $summary = stripslashes($entry->summary);
      
      echo "<li>\n";
      echo "<h2>$title</h2>\n";
      echo "$summary <br/>\n";
      echo "</li>\n";
    }
    ?>
    </ol>
  </body>
</html>

图 1 显示了输出的结果:

图 1. 显示 SimpleXML 检索到的事件列表的 Web 页面
显示 SimpleXML 检索到的事件列表的 Web 页面

清单 2 的代码中,simplexml_load_file() 对象向提要 URL 发送请求并把响应变成 SimpleXML 对象。然后遍历响应中的 <entry> 元素,通过 foreach() 循环检索 图 1 中的信息。<entry> 下的子节点用 SimpleXML 对象属性表示——比如 <title> 节点表示为 $entry->title,<summary> 节点表示为 $entry->summary,依此类推。

这里的提要 URL 引用了用户的私有日程表提要,包括用户的电子邮件地址和 magic cookie,后者用于只读访问日程表数据而不需要获得授权。如前所述,获得该提要 URL 需要手工完成:访问相应的 Google Calendar 页面并将 URL 从日程表设置中手工复制到 PHP 脚本中。


使用 Zend GData Client Library 检索事件列表

虽然 magic cookie 验证非常方便,但对于真正完善的 PHP 应用程序来说并不实用,原因有二:

  • 对日程表提要访问是只读的,
  • 不支持所有日程表操作。

用 Calendar Data API 完成任何真正有意义的工作,需要使用 Google 认可的两种验证方法之一为应用程序增加用户验证功能:AuthSub 和 ClientLogin。

手工执行这类身份验证相当繁琐,需要大量的代码来应付验证事务中可能出现的各种情况。所幸的是不用太担心:Zend 的 GData Client Library 是专门为需要集成 PHP 应用程序和 Google Data API 的开发人员设计的,它可以完成这些细节问题。这个库可以单独下载(参见 参考资料 获得链接),为 Google Data API 提供了方便的、面向对象的接口,并封装了大部分常见的任务(包括身份验证)。因此可以将精力集中到应用程序的核心功能上。

清单 3 使用 Zend GData Client Library 再现了 清单 2 的功能:

清单 3. 使用 Zend Library 检索事件列表
<!DOCTYPE html 
  PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title>Listing calendar contents</title>
    <style>
    body {
      font-family: Verdana;      
    }
    li {
      border-bottom: solid black 1px;      
      margin: 10px; 
      padding: 2px; 
      width: auto;
      padding-bottom: 20px;
    }
    h2 {
      color: red; 
      text-decoration: none;  
    }
    span.attr {
      font-weight: bolder;  
    }
    </style>    
  </head>
  <body>
    <?php
    // load library
    require_once 'Zend/Loader.php';
    Zend_Loader::loadClass('Zend_Gdata');
    Zend_Loader::loadClass('Zend_Gdata_ClientLogin');
    Zend_Loader::loadClass('Zend_Gdata_Calendar');
    Zend_Loader::loadClass('Zend_Http_Client');
    
    // create authenticated HTTP client for Calendar service
    $gcal = Zend_Gdata_Calendar::AUTH_SERVICE_NAME;
    $user = "username@gmail.com";
    $pass = "pass";
    $client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $gcal);
    $gcal = new Zend_Gdata_Calendar($client);
    
    // generate query to get event list
    $query = $gcal->newEventQuery();
    $query->setUser('default');
    $query->setVisibility('private');
    $query->setProjection('basic');
    
    // get and parse calendar feed
    // print output
    try {
      $feed = $gcal->getCalendarEventFeed($query);
    } catch (Zend_Gdata_App_Exception $e) {
      echo "Error: " . $e->getResponse();
    }
    ?>
    <h1><?php echo $feed->title; ?></h1>
    <?php echo $feed->totalResults; ?> event(s) found.
    <p/>
    <ol>

    <?php        
    foreach ($feed as $event) {
      echo "<li>\n";
      echo "<h2>" . stripslashes($event->title) . "</h2>\n";
      echo stripslashes($event->summary) . " <br/>\n";
      echo "</li>\n";
    }
    echo "</ul>";
    ?>
    </ol>

  </body>
</html>

清单 3 首先加载 Zend 类库,初始化 Zend_Http_Client 类实例。该客户机提供了必要的用户身份验证信息,开启了 Calendar 服务的身份验证连接。一旦建立了经过验证的连接,getCalendarEventFeed() 方法就可以检索日程表提要。该方法接受一个 EventQuery 对象,配置了多个参数:用户名、提要类型(公共或私有)以及提要的详细程度(完整还是基本)。getCalendarEventFeed() API 调用的响应是一个 XML 文档,经过解析后转化成 PHP 对象。这样,通过对象属性检索数据和生成 HTML 页面就很简单了。

图 2 显示了可能看到的结果:

图 2. 显示 Zend GData Client Library 检索到的日程表事件列表的 Web 页面
显示 Zend GData Client Library 检索到的日程表事件列表的 Web 页面

添加新事件

通过客户机应用程序显示事件已经实现。那么如何添加新事件呢?

实际情况并没有看到的那么复杂。通过 Calendar Data API 很容易向日程表添加新事件。创建一个 XML 编码的新事件 <entry> 并 POST 给日程表提要就行了。清单 4 给出了一个例子:

清单 4. 新事件的 entry 例子
        <atom:entry xmlns:atom="http://www.w3.org/2005/Atom">
          <atom:title type="text">Dinner with the gang</atom:title>
          <gd:when xmlns:gd="http://schemas.google.com/g/2005" 
          startTime="2008-06-23T18:00:00+05:30" endTime="2008-06-23T20:00:00+05:30"/>
        </atom:entry>

如果使用 Zend 库就更简单了,只需要调用 insertEvent() 方法,该方法将创建 清单 4 所示的 XML 并提交给日程表提要。清单 5 提供了一个 Web 表单让用户输入事件的有关信息,然后使用 Zend 库构造相应的 XML 并保存到日程表中:

清单 5. 通过表单添加新事件
<!DOCTYPE html 
  PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title>Adding calendar events</title>
    <style>
    body {
      font-family: Verdana;      
    }
    li {
      border-bottom: solid black 1px;      
      margin: 10px; 
      padding: 2px; 
      width: auto;
      padding-bottom: 20px;
    }
    h2 {
      color: red; 
      text-decoration: none;  
    }
    span.attr {
      font-weight: bolder;  
    }
    </style>    
  </head>
  <body>
    <h1>Add Event</h1>
    <?php if (!isset($_POST['submit'])) { ?>
    <form method="post" action="
     <?php echo htmlentities($_SERVER['PHP_SELF']); ?>">
      Event title: <br/>
      <input name="title" type="text" size="15" /><p/>
      Start date (dd/mm/yyyy): <br/>
      <input name="sdate_dd" type="text" size="2" />
      <input name="sdate_mm" type="text" size="2" />
      <input name="sdate_yy" type="text" size="4" /><p/>
      Start time (hh:mm): <br/>
      <input name="sdate_hh" type="text" size="2" /> 
      <input name="sdate_ii" type="text" size="2" /><br/>
      End  date (dd/mm/yyyy): <br/>
      <input name="edate_dd" type="text" size="2" />
      <input name="edate_mm" type="text" size="2" />
      <input name="edate_yy" type="text" size="4" /><p/>
      End  time (hh:mm): <br/>
      <input name="edate_hh" type="text" size="2" /> 
      <input name="edate_ii" type="text" size="2" /><br/>
      <input name="submit" type="submit" value="Save" />      
    </form>
    <?php
    } else {
      // load classes
      require_once 'Zend/Loader.php';
      Zend_Loader::loadClass('Zend_Gdata');
      Zend_Loader::loadClass('Zend_Gdata_ClientLogin');
      Zend_Loader::loadClass('Zend_Gdata_Calendar');
      Zend_Loader::loadClass('Zend_Http_Client');
      
      // connect to service
      $gcal = Zend_Gdata_Calendar::AUTH_SERVICE_NAME;
      $user = "username@gmail.com";
      $pass = "pass";
      $client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $gcal);
      $gcal = new Zend_Gdata_Calendar($client);
      
      // validate input
      if (empty($_POST['title'])) {
        die('ERROR: Missing title');
      } 
      
      if (!checkdate($_POST['sdate_mm'], $_POST['sdate_dd'], $_POST['sdate_yy'])) {
        die('ERROR: Invalid start date/time');        
      }
      
      if (!checkdate($_POST['edate_mm'], $_POST['edate_dd'], $_POST['edate_yy'])) {
        die('ERROR: Invalid end date/time');        
      }
      
      $title = htmlentities($_POST['title']);
      $start = date(DATE_ATOM, mktime($_POST['sdate_hh'], $_POST['sdate_ii'], 
       0, $_POST['sdate_mm'], $_POST['sdate_dd'], $_POST['sdate_yy']));
      $end = date(DATE_ATOM, mktime($_POST['edate_hh'], $_POST['edate_ii'], 
       0, $_POST['edate_mm'], $_POST['edate_dd'], $_POST['edate_yy']));

      // construct event object
      // save to server      
      try {
        $event = $gcal->newEventEntry();        
        $event->title = $gcal->newTitle($title);        
        $when = $gcal->newWhen();
        $when->startTime = $start;
        $when->endTime = $end;
        $event->when = array($when);        
        $gcal->insertEvent($event);   
      } catch (Zend_Gdata_App_Exception $e) {
        echo "Error: " . $e->getResponse();
      }
      echo 'Event successfully added!';      
    }
    ?>
  </body>
</html>

清单 5 实际上由两部分组成:Web 表单和处理表单所提交数据的 PHP 代码。表单如 图 3 所示:

图 3. 添加新事件的 Web 表单
添加新事件的 Web 表单

用户在表单中输入详细的事件信息并提交后,脚本第二部分派上用场了。脚本首先初始化开启 Calendar Data API 验证连接的 HTTP 客户机。然后验证 Web 表单的输入,检查事件的开始和结束日期,然后将日期转化成 RFC 3339 格式。

如果输入的数据有效,则创建新的 EventEntry 对象。该对象表示将插入到日程表的新事件,并公开了 newTitle()newWhen() 方法,用于设置事件的标题和始末日期。设置完这些对象属性后,insertEvent() 方法将事件保存到 Google 服务器。 添加的事件应该立即可以在日程表中看到。

图 4 显示了成功添加新事件后的结果:

图 4. 添加新事件的结果
添加新事件的结果

删除和修改已有的事件

删除事件更简单:获得事件的唯一 URL 然后发送 DELETE 请求即可。对于 Zend 库来说,getCalendarEventEntry() 方法可以获得事件对象,然后调用对象的 delete() 方法。清单 6 说明了该过程:

清单 6. 删除事件
<?php
// load classes
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Gdata');
Zend_Loader::loadClass('Zend_Gdata_ClientLogin');
Zend_Loader::loadClass('Zend_Gdata_Calendar');
Zend_Loader::loadClass('Zend_Http_Client');

// connect to service    
$gcal = Zend_Gdata_Calendar::AUTH_SERVICE_NAME;
$user = "username@gmail.com";
$pass = "pass";
$client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $gcal);
$gcal = new Zend_Gdata_Calendar($client);    

// retrieve event
// delete event
try {          
  $event = $gcal->getCalendarEventEntry('http://www.google.com/calendar/
   feeds/default/private/full/xxxxxxx');
  $event->delete();
} catch (Zend_Gdata_App_Exception $e) {
  echo "Error: " . $e->getResponse();
}        
echo 'Event successfully deleted!';  
?>

更新事件的过程与此类似:取得事件唯一的 URL 然后发送 PUT 请求,请求中包含更新的事件数据。对于 Zend 库,可以设置事件对象的属性然后通过 save() 方法保存到服务器上。如 清单 7 所示:

清单 7. 修改事件
<?php
// load classes
require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Gdata');
Zend_Loader::loadClass('Zend_Gdata_ClientLogin');
Zend_Loader::loadClass('Zend_Gdata_Calendar');
Zend_Loader::loadClass('Zend_Http_Client');

// connect to service    
$gcal = Zend_Gdata_Calendar::AUTH_SERVICE_NAME;
$user = "username@gmail.com";
$pass = "pass";
$client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $gcal);
$gcal = new Zend_Gdata_Calendar($client);    

// retrieve event
// set new event properties and update event
try {
  $event = $gcal->getCalendarEventEntry('http://www.google.com/calendar/feeds/
   default/private/full/xxxxxxxxxxx');
  $event->title = $gcal->newTitle($title); 
  $when = $gcal->newWhen();
  $when->startTime = $start;
  $when->endTime = $end;
  $event->when = array($when);         
  $event->save();   
} catch (Zend_Gdata_App_Exception $e) {
  die("Error: " . $e->getResponse());
}
echo 'Event successfully modified!';    
?>

集成日程表操作

了解这些基本的知识后,现在看一个实际的应用。清单 8清单 3 的基础上增加了事件的编辑和删除链接。分别指向 edit.php 和 delete.php 脚本,使用 GET 方法向这些脚本传递事件标识符(从事件条目中提取)。

清单 8. 检索事件列表
<!DOCTYPE html 
  PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title>Listing calendar contents</title>
    <style>
    body {
      font-family: Verdana;      
    }
    li {
      border-bottom: solid black 1px;      
      margin: 10px; 
      padding: 2px; 
      width: auto;
      padding-bottom: 20px;
    }
    h2 {
      color: red; 
      text-decoration: none;  
    }
    span.attr {
      font-weight: bolder;  
    }
    </style>    
  </head>
  <body>
    <?php
    require_once 'Zend/Loader.php';
    Zend_Loader::loadClass('Zend_Gdata');
    Zend_Loader::loadClass('Zend_Gdata_ClientLogin');
    Zend_Loader::loadClass('Zend_Gdata_Calendar');
    Zend_Loader::loadClass('Zend_Http_Client');
    
    $gcal = Zend_Gdata_Calendar::AUTH_SERVICE_NAME;
    $user = "username@gmail.com";
    $pass = "pass";
    $client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $gcal);
    $gcal = new Zend_Gdata_Calendar($client);
    
    $query = $gcal->newEventQuery();
    $query->setUser('default');
    $query->setVisibility('private');
    $query->setProjection('basic');

    try {
      $feed = $gcal->getCalendarEventFeed($query);
    } catch (Zend_Gdata_App_Exception $e) {
      echo "Error: " . $e->getResponse();
    }
    ?>
    <h1><?php echo $feed->title; ?></h1>
    <?php echo $feed->totalResults; ?> event(s) found.
    <p/>
    <ol>

    <?php        
    foreach ($feed as $event) {
      echo "<li>\n";
      echo "<h2>" . stripslashes($event->title) . "</h2>\n";
      echo stripslashes($event->summary) . " <br/>\n";
      $id = substr($event->id, strrpos($event->id, '/')+1);
      echo "<a href=\"edit.php?id=$id\">edit</a> | ";
      echo "<a href=\"delete.php?id=$id\">delete</a> <br/>\n";
      echo "</li>\n";
    }
    echo "</ul>";
    ?>
    </ol>
    <p/>
    <a href="add.php">Add a new event</a><p/>
  </body>
</html>

图 5 显示了 清单 8 的输出结果:

图 5. 包含事件列表的 Web 页面
包含事件列表的 Web 页面

清单 9 包含了 delete.php 脚本代码,该代码通过 GET 方法取得事件标识符,然后用标识符删除事件,方法和 清单 6 相同:

清单 9. 删除事件
<!DOCTYPE html 
  PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title>Deleting calendar events</title>
    <style>
    body {
      font-family: Verdana;      
    }
    li {
      border-bottom: solid black 1px;      
      margin: 10px; 
      padding: 2px; 
      width: auto;
      padding-bottom: 20px;
    }
    h2 {
      color: red; 
      text-decoration: none;  
    }
    span.attr {
      font-weight: bolder;  
    }
    </style>    
  </head>
  <body>
    <h1>Delete Event</h1>
    <?php
    // load classes
    require_once 'Zend/Loader.php';
    Zend_Loader::loadClass('Zend_Gdata');
    Zend_Loader::loadClass('Zend_Gdata_ClientLogin');
    Zend_Loader::loadClass('Zend_Gdata_Calendar');
    Zend_Loader::loadClass('Zend_Http_Client');
    
    // connect to service
    $gcal = Zend_Gdata_Calendar::AUTH_SERVICE_NAME;
    $user = "username@gmail.com";
    $pass = "pass";
    $client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $gcal);
    $gcal = new Zend_Gdata_Calendar($client);
      
    // if event ID is present
    // get event object from feed
    // delete event  
    if (isset($_GET['id'])) {
      try {          
          $event = $gcal->getCalendarEventEntry('http://www.google.com/calendar/
           feeds/default/private/full/' . $_GET['id']);
          $event->delete();
      } catch (Zend_Gdata_App_Exception $e) {
          echo "Error: " . $e->getResponse();
      }        
      echo 'Event successfully deleted!';  
    } else {
      echo 'No event ID available';  
    }
    ?>
  </body>
</html>

清单 10 包含了 edit.php 脚本代码。

清单 10. 编辑事件
<!DOCTYPE html 
  PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title>Updating calendar events</title>
    <style>
    body {
      font-family: Verdana;      
    }
    li {
      border-bottom: solid black 1px;      
      margin: 10px; 
      padding: 2px; 
      width: auto;
      padding-bottom: 20px;
    }
    h2 {
      color: red; 
      text-decoration: none;  
    }
    span.attr {
      font-weight: bolder;  
    }
    </style>    
  </head>
  <body>
    <h1>Edit Event</h1>
    <?php
    // load classes
    require_once 'Zend/Loader.php';
    Zend_Loader::loadClass('Zend_Gdata');
    Zend_Loader::loadClass('Zend_Gdata_ClientLogin');
    Zend_Loader::loadClass('Zend_Gdata_Calendar');
    Zend_Loader::loadClass('Zend_Http_Client');
    
    // connect to service
    $gcal = Zend_Gdata_Calendar::AUTH_SERVICE_NAME;
    $user = "username@gmail.com";
    $pass = "pass";
    $client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $gcal);
    $gcal = new Zend_Gdata_Calendar($client);
     
    // get event details
    if (!isset($_POST['submit'])) {
      if (isset($_GET['id'])) {
        try {          
          $event = $gcal->getCalendarEventEntry('http://www.google.com/calendar/
           feeds/default/private/full/' . $_GET['id']);
        } catch (Zend_Gdata_App_Exception $e) {
          echo "Error: " . $e->getResponse();
        }
      } else {
          die('ERROR: No event ID available!');  
      }  
      
      // format data into human-readable form
      // populate a Web form with the record
      $title = $event->title;
      $when = $event->getWhen();
      $startTime = strtotime($when[0]->getStartTime());
      $sdate_dd = date('d', $startTime);
      $sdate_mm = date('m', $startTime);
      $sdate_yy = date('Y', $startTime);
      $sdate_hh = date('H', $startTime);
      $sdate_ii = date('i', $startTime);
      $endTime = strtotime($when[0]->getEndTime());
      $edate_dd = date('d', $endTime);
      $edate_mm = date('m', $endTime);
      $edate_yy = date('Y', $endTime);
      $edate_hh = date('H', $endTime);
      $edate_ii = date('i', $endTime);      
    ?>
    <form method="post" 
     action="<?php echo htmlentities($_SERVER['PHP_SELF']); ?>">
      <input type="hidden" name="id" value="<?php echo $_GET['id']; ?>">
      Event title: <br/>
      <input name="title" type="text" size="15" 
       value="<?php echo $title; ?>"/><p/>
      Start date (dd/mm/yyyy): <br/>
      <input name="sdate_dd" type="text" size="2" 
       value="<?php echo $sdate_dd; ?>" />
      <input name="sdate_mm" type="text" size="2" 
       value="<?php echo $sdate_mm; ?>"/>
      <input name="sdate_yy" type="text" size="4" 
       value="<?php echo $sdate_yy; ?>"/><p/>
      Start time (hh:mm): <br/>
      <input name="sdate_hh" type="text" size="2" 
       value="<?php echo $sdate_hh; ?>"/> 
      <input name="sdate_ii" type="text" size="2" 
       value="<?php echo $sdate_ii; ?>"/><br/>
      End  date (dd/mm/yyyy): <br/>
      <input name="edate_dd" type="text" size="2" 
       value="<?php echo $edate_dd; ?>" />
      <input name="edate_mm" type="text" size="2" 
       value="<?php echo $edate_mm; ?>" />
      <input name="edate_yy" type="text" size="4" 
       value="<?php echo $edate_yy; ?>" /><p/>
      End  time (hh:mm): <br/>
      <input name="edate_hh" type="text" size="2" 
       value="<?php echo $edate_hh; ?>"  /> 
      <input name="edate_ii" type="text" size="2" 
       value="<?php echo $edate_ii; ?>"  /><br/>
      <input name="submit" type="submit" value="Save" />      
    </form>    
    <?php              
    } else {
      // if form submitted
      // validate input
      if (empty($_POST['id'])) {
        die('ERROR: Missing event ID');
      } 
      
      if (empty($_POST['title'])) {
        die('ERROR: Missing title');
      } 
      
      if (!checkdate($_POST['sdate_mm'], $_POST['sdate_dd'], $_POST['sdate_yy'])) {
        die('ERROR: Invalid start date/time');        
      }
      
      if (!checkdate($_POST['edate_mm'], $_POST['edate_dd'], $_POST['edate_yy'])) {
        die('ERROR: Invalid end date/time');        
      }     
      
      $title = htmlentities($_POST['title']);
      $start = date(DATE_ATOM, mktime($_POST['sdate_hh'], $_POST['sdate_ii'], 
       0, $_POST['sdate_mm'], $_POST['sdate_dd'], $_POST['sdate_yy']));
      $end = date(DATE_ATOM, mktime($_POST['edate_hh'], $_POST['edate_ii'], 
       0, $_POST['edate_mm'], $_POST['edate_dd'], $_POST['edate_yy']));
      
      // get existing event record
      // update event attributes
      // save changes to server
      try {
        $event = $gcal->getCalendarEventEntry('http://www.google.com/calendar/
         feeds/default/private/full/' . $_POST['id']);
        $event->title = $gcal->newTitle($title); 
        $when = $gcal->newWhen();
        $when->startTime = $start;
        $when->endTime = $end;
        $event->when = array($when);        
        $event->save();   
      } catch (Zend_Gdata_App_Exception $e) {
        die("Error: " . $e->getResponse());
      }
      echo 'Event successfully modified!';    
    }    
    ?>
  </body>
</html>

类似于 清单 9清单 10 也通过 GET 方法取得事件标识符。然后通过 getCalendarEventEntry() 方法取得事件的详细内容,然后使用它预先填充 Web 表单。用户可以在表单中修改并提交事件的详细内容;提交后,脚本联系 Calendar Data API,构造修改后的 <entry>,使它包含新的事件详细内容,最后使用 save() 方法把修改保存到服务器上。


搜索事件

和所有的 Google Data 提要一样,Calendar API 也允许开发人员通过在 REST 请求中添加以下参数来改变输出:

  • start-index 参数,指定条目的起始偏移量
  • max-results 参数,指定检索的条目数
  • start-min 和 start-max 参数,指定返回条目的日期范围
  • orderby 参数,指定如何对条目排序

清单 11 示范了其中的一些参数,将 清单 2 的输出限制为今后七天内的条目,并按开始日期排列:

清单 11. 按日期搜索事件
<!DOCTYPE html 
  PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title>Listing calendar contents</title>
    <style>
    body {
      font-family: Verdana;      
    }
    li {
      border-bottom: solid black 1px;      
      margin: 10px; 
      padding: 2px; 
      width: auto;
      padding-bottom: 20px;
    }
    h2 {
      color: red; 
      text-decoration: none;  
    }
    span.attr {
      font-weight: bolder;  
    }
    </style>    
  </head>
  <body>
    <?php
    // set configuration parameters
    $userid = 'username%40googlemail.com';
    $magicCookie = 'cookie';
    $start = urlencode(date(DATE_ATOM, strtotime('today 00:00')));
    $end = urlencode(date(DATE_ATOM, strtotime('+7 days 23:59')));
    
    // build feed URL
    $feedURL = "http://www.google.com/calendar/feeds/$userid/private-
     $magicCookie/basic?start-min=$start&start-max=$end&orderby=starttime";
    
    // read feed into SimpleXML object
    $sxml = simplexml_load_file($feedURL);
    
    // get number of events
    $counts = $sxml->children('http://a9.com/-/spec/opensearchrss/1.0/');
    $total = $counts->totalResults; 
    ?>
    <h1><?php echo $sxml->title; ?></h1>
    <?php echo $total; ?> event(s) found.
    <p/>
    <ol>
    <?php    
    // iterate over entries in category
    // print each entry's details
    foreach ($sxml->entry as $entry) {
      $title = stripslashes($entry->title);
      $summary = stripslashes($entry->summary);
      
      echo "<li>\n";
      echo "<h2>$title</h2>\n";
      echo "$summary <br/>\n";
      echo "</li>\n";
    }
    ?>
    </ol>
  </body>
</html>

也可以对日程表条目进行全文本搜索查询,只返回和特定词语匹配的条目。清单 12 提供了一个例子:

清单 12. 按查询关键字搜索
<!DOCTYPE html 
  PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title>Listing calendar contents</title>
    <style>
    body {
      font-family: Verdana;      
    }
    li {
      border-bottom: solid black 1px;      
      margin: 10px; 
      padding: 2px; 
      width: auto;
      padding-bottom: 20px;
    }
    h2 {
      color: red; 
      text-decoration: none;  
    }
    span.attr {
      font-weight: bolder;  
    }
    </style>    
  </head>
  <body>
    <?php
    // set configuration parameters
    $userid = 'username%40googlemail.com';
    $magicCookie = 'cookie';
    $query = urlencode('party');
    
    // build feed URL
    $feedURL = "http://www.google.com/calendar/feeds/$userid/private-
     $magicCookie/basic?q=$query";
    
    // read feed into SimpleXML object
    $sxml = simplexml_load_file($feedURL);
    
    // get number of events
    $counts = $sxml->children('http://a9.com/-/spec/opensearchrss/1.0/');
    $total = $counts->totalResults; 
    ?>
    <h1><?php echo $sxml->title; ?></h1>
    <?php echo $total; ?> event(s) found.
    <p/>
    <ol>
    <?php    
    // iterate over entries in category
    // print each entry's details
    foreach ($sxml->entry as $entry) {
      $title = stripslashes($entry->title);
      $summary = stripslashes($entry->summary);
      
      echo "<li>\n";
      echo "<h2>$title</h2>\n";
      echo "$summary <br/>\n";
      echo "</li>\n";
    }
    ?>
    </ol>
  </body>
</html>

Zend 客户机库也支持这些参数。出于演示的需要,更新了 清单 8,使它包含搜索表单和表单处理程序,如 清单 13 所示:

清单 13. 添加交互式搜索
<!DOCTYPE html 
  PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title>Listing calendar contents</title>
    <style>
    body {
      font-family: Verdana;      
    }
    li {
      border-bottom: solid black 1px;      
      margin: 10px; 
      padding: 2px; 
      width: auto;
      padding-bottom: 20px;
    }
    h2 {
      color: red; 
      text-decoration: none;  
    }
    span.attr {
      font-weight: bolder;  
    }
    </style>    
  </head>
  <body>
    <?php
    require_once 'Zend/Loader.php';
    Zend_Loader::loadClass('Zend_Gdata');
    Zend_Loader::loadClass('Zend_Gdata_ClientLogin');
    Zend_Loader::loadClass('Zend_Gdata_Calendar');
    Zend_Loader::loadClass('Zend_Http_Client');
    
    $gcal = Zend_Gdata_Calendar::AUTH_SERVICE_NAME;
    $user = "username@gmail.com";
    $pass = "pass";
    $client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $gcal);
    $gcal = new Zend_Gdata_Calendar($client);
    
    $query = $gcal->newEventQuery();
    $query->setUser('default');
    $query->setVisibility('private');
    $query->setProjection('basic');
    $query->setOrderby('starttime');
    if(isset($_GET['q'])) {
      $query->setQuery($_GET['q']);      
    }
    
    try {
      $feed = $gcal->getCalendarEventFeed($query);
    } catch (Zend_Gdata_App_Exception $e) {
      echo "Error: " . $e->getResponse();
    }
    ?>
    <h1><?php echo $feed->title; ?></h1>
    <?php echo $feed->totalResults; ?> event(s) found.
    <p/>
    <ol>

    <?php        
    foreach ($feed as $event) {
      echo "<li>\n";
      echo "<h2>" . stripslashes($event->title) . "</h2>\n";
      echo stripslashes($event->summary) . " <br/>\n";
      $id = substr($event->id, strrpos($event->id, '/')+1);
      echo "<a href=\"edit.php?id=$id\">edit</a> | ";
      echo "<a href=\"delete.php?id=$id\">delete</a> <br/>\n";
      echo "</li>\n";
    }
    echo "</ul>";
    ?>
    </ol>
    <p/>
    <a href="add.php">Add a new event</a><p/>
    <form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="get">
      Search for events containing:<br/>
      <input type="text" name="q" size="10"/><p/>
      <input type="submit" name="submit" value="Search"/>
    </form>
  </body>
</html>

图 6 显示了搜索表单:

图 6. 搜索表单
搜索表单

图 7 显示了搜索包含 “party” 一词得到的所有条目:

图 7. 日程表搜索的结果
日程表搜索的结果

结束语

本文简要介绍了如何使用 SimpleXML 和 Zend client library 将 Google Calendar 服务数据集成到 PHP 应用程序中。通过例子说明了:

  • Google Calendar 提要格式
  • 如何按照日期和关键字搜索日程表条目
  • 如何添加、删除和修改日程表条目
  • 如何创建自定义的 Calendar 服务前端

这些例子表明,对于开发人员来说,Google Calendar Data API 是一种成熟、方便和灵活的方法,可用于创建自定义的 Google Calendar Web 前端。自己尝试一下吧!


下载

描述名字大小
Sample code for articlex-googleclndr-code.ziptKB

参考资料

学习

获得产品和技术

  • SimpleXML 扩展:探索 SimpleXML 工具集,它将 XML 转换成对象,从而允许通过一般的属性选择符和数组迭代器进行处理。
  • The Zend GData Client Library:下载 Google Data API PHP 5 客户机接口。
  • IBM 产品评估试用软件:使用可直接从 developerWorks 下载的 IBM 试用软件构建您的下一个项目,包括来自 DB2®、Lotus®、Rational®、Tivoli® 和 WebSphere® 的应用程序开发工具和中间件产品。

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML, Open source, Web development
ArticleID=336276
ArticleTitle=在 PHP 应用程序中集成 Google Calendar
publish-date=09082008