目次


PHP アプリケーションから Google カレンダーを利用する

PHP を使って Google カレンダーのデータを処理し、カスタム・アプリケーションに統合する

Comments

はじめに

長いこと、私が個人用スケジューリング・ツールとして日常的に使っていたのは、PalmPilot に付属のツールのみでしたが、この 2、3 年の間にその忠誠心は次第に Google カレンダーへと傾いてきました。Google カレンダーは Web に対応しているというだけでなく、イベントに関するニュースを共有したり、招待メールや返信を整理したりするのが簡単で、しかもさまざまな種類のイベントを処理しやすいからです。

私は開発者としても、Google カレンダーは興味深い話題の種だと思います。Google Calendar Data API を使えば、開発者は公開カレンダーや個人用のユーザー・カレンダーに保存されたデータを使った新しいアプリケーションを簡単に構築することができます。REST モデルに従ったこの API は、XML 対応のあらゆる開発ツールキットによってアクセスすることが可能です。さらに、私のお気に入りの PHP をはじめ、よく使われる多くのプログラミング言語に対応したクライアント・ライブラリーもすでに備わっています。

この記事では、カレンダー・データをカスタム PHP アプリケーションに取り込んで使用する手順をとおして、Google Calendar Data API を紹介します。記事には以下の方法を説明するサンプル・コードを記載しています。

ユーザーの公開ノートブックから予定を取得する

  • 新しい予定を追加する
  • 予定を変更したり削除したりする
  • キーワードまたは日付範囲を指定して予定を検索する
  • それでは早速、本題に入りましょう。

Calendar Data API について

PHP コードの詳細を検討する前に、Google Calendar Data API について少し説明しておきます。REST をベースとしたサービスの例に漏れず、この API は、XML にエンコードされた 1 つ以上の入力を引数として含む HTTP リクエストを受け入れ、XML 対応のあらゆるクライアントで構文解析可能なように XML にエンコードしたレスポンスを返すことによって機能します。Google Calendar Data API の場合、レスポンスを構成するのは常に、要求された情報を含む Atom または RSS フィードです。

標準的な Google カレンダーのフィードには、便利な関連アプリケーションを構築するのに十分な情報が含まれています。その一例を見るには、Google カレンダーのアカウントにログインして、カレンダー設定までナビゲートしてください。このカレンダー設定には、カレンダーの専用アドレス URL のリンクが含まれているはずです。この URL (常に秘密にしておかなければなりません) を使うと、最初に許可を要することなく、カレンダーのフィードに読み取り専用でアクセスすることができます。この URL は、例えば http://www.google.com/calendar/feeds/userid/private-magicCookie/basic のような形になっています。この URL を Web ブラウザーに入力すると (または、HTTP クライアントからフィードに対し GET リクエストを送信すると)、リスト 1 のようなフィードが表示されます。

リスト 1. Google カレンダーのフィードの一例
<?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>

Google カレンダーのフィードはすべて、ルート要素である <feed> 要素から始まります。<feed> 要素のなかには、フィードのバージョンごとの URL が含まれる <link> 要素、そして要約統計情報が含まれる <openSearch:> 要素があります。

最も外側の <feed> 要素の内側にはカレンダーに登録されている予定を表す <entry> 要素が 1 つ以上含まれます。各 <entry> に含まれるのは、それぞれの予定のタイトル、説明、公開日、最終更新日、予定のフィードの URL、作成者などの詳細情報です。これらの情報は、それぞれ <title>、<summary>、<published>、<updated>、<link>、<author> 要素で表されます。

SimpleXML を使用して予定の一覧を取得する

それでは、PHP を使用して Google カレンダーのフィードを処理するためのサンプル・コードを見てみましょう。リスト 2 では、リスト 1 のフィードを取得し、SimpleXML でフィードから関連データのフラグメントを抽出して Web ページ用にフォーマット設定を行っています。

リスト 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 ページ
SimpleXML によって生成され、カレンダーに登録されている予定の一覧を表示する Web ページ

リスト 2 のコードでは、simplexml_load_file() オブジェクトがフィード URL にリクエストを送信し、そのレスポンスを SimpleXML オブジェクトに変換します。続いて foreach() ループを使用した繰り返し処理により、レスポンスに含まれる <entry> の各要素を処理して、図 1 に表示された情報を取得します。各 <entry> の子ノードは、SimpleXML オブジェクトのプロパティーとして表されます。例えば、<title> ノードの場合は $entry->title、<summary> ノードの場合は $entry->summary という表現になります。

ここで使用しているフィード URL はユーザーの専用カレンダー・フィードを参照する URL で、ユーザーの E メールアドレスと、いわゆる magic cookie の両方が含まれています。フィード URL はこの magic cookie によって、最初に許可を要することなくカレンダーのデータに対する読み取り専用アクセスを取得しています。前に説明したように、このフィード URL の取得は手作業で行います。つまり、対応する Google カレンダーのページにアクセスして、カレンダーの設定に含まれる URL を手動で PHP スクリプトにコピーする必要が出てきます。

Zend GData Client Library を使用して予定の一覧を取得する

magic cookie による認証は確かに便利ですが、本格的な PHP アプリケーションの場合にはそれほど実用的とは言えません。その理由としては、以下の 2 つが挙げられます。

  • 許可されるのはカレンダー・フィードに対する読み取り専用アクセスであるため。
  • すべてのカレンダー操作に使用できるわけではないため。

Calendar Data API を利用して実際に実のある操作を行うには、Google が認定する 2 つの認証メソッド、AuthSub または ClientLogin のいずれかを使用して、ユーザー認証をアプリケーションに追加する必要があります。

このような認証を手動で行うのは、かなり厄介なタスクです。さらに、通常の認証処理の間に発生する可能性のあるさまざまなシナリオを考慮するとなると大量のコードが必要になります。しかし幸い、認証についてはそれほど心配する必要はありません。PHP アプリケーションから Google Data API を使おうとする開発者を対象に設計された Zend GData Client Library が、すべての詳細を代わって処理してくれるためです。別途ダウンロードできるこのライブラリー (「参考文献」にリンクを記載) は、認証をはじめとする最も一般的なタスクをカプセル化した、Google Data API への便利なオブジェクト指向のインターフェースとなります。そのため、開発者はアプリケーションのコアの機能に専念できるというわけです。

リスト 3 に、リスト 2 の機能を今度は Zend GData Client Library を使って再現してみます。

リスト 3. Zend ライブラリーによる予定の一覧の取得
<!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 クラスのインスタンスを初期化しています。このクライアントは必要なユーザー認証情報が提供されると、カレンダー・サービスとの認証接続を開始します。認証接続が開始されたら、getCalendarEventFeed() メソッドでカレンダー・フィードを取得します。このメソッドは、EventQuery オブジェクトを引数として受け取ります。このオブジェクトは、ユーザー名、フィードのタイプ (公開または専用)、そしてフィードでの必要な詳細レベル (完全または基本) といった各種のパラメーターで構成されます。getCalendarEventFeed() API 呼び出しに対して返されるのは XML 文書で、この文書が構文解析されて PHP オブジェクトに変換されます。あとは、このデータをオブジェクト・プロパティーによって取得し、そのデータを使って HTML ページを生成するだけに過ぎません。

図 2 に、上記のスクリプトによる出力例を記載します。

図 2. Zend GData Client Library によって生成され、カレンダーに登録されている予定の一覧を表示する Web ページ
Zend GData Client Library によって生成され、カレンダーに登録されている予定の一覧を表示する Web ページ
Zend GData Client Library によって生成され、カレンダーに登録されている予定の一覧を表示する Web ページ

新しい予定を追加する

これで、クライアント・アプリケーションによって予定の一覧を表示できるようになりましたが、新しい予定を追加するにはどうすればよいでしょうか。

新しい予定を追加するのは、実際にはそれほど複雑なことではありません。Calendar Data API では、新しく XML にエンコードした予定として <entry> を作成し、この XML をカレンダーのフィードに POST することで、簡単に新しい予定をカレンダーに追加できるようになっています。リスト 4 に、このようにして登録された予定の一例を記載します。

リスト 4. 新たに登録された予定の例
        <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 を作成し、カレンダーのフィードに POST してくれます。リスト 5 のサンプル・コードによる Web フォームは、各種の予定に関連する詳細をユーザーに要求し、それから Zend ライブラリーを使用して、対応する XML を作成してその予定をカレンダーに保存します。

リスト 5. Web フォームによる新しい予定の追加
<!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 は、実際には 2 つの部分で構成されています。1 つは Web フォーム、そしてもう 1 つはフォームから送信された入力を処理する PHP コードです。このフォームは図 3 のように表示されます。

図 3. 新しい予定を追加するための Web フォーム
新しい予定を追加するための Web フォーム
新しい予定を追加するための Web フォーム

ユーザーがこのフォームに予定の詳細を入力して送信すると、後半のスクリプトが動作を開始します。後半のスクリプトはまず、Calendar Data API との認証接続を開始する HTTP クライアントを初期化します。次に、Web フォームに入力されたデータを検証し、予定の開始日と終了日をチェックして、日付を RFC 3339 フォーマットに変換します。

フォームに入力されたデータが検証されると、新しい EventEntry オブジェクトが作成されます。このオブジェクトはカレンダーに挿入される新しい予定を表すオブジェクトで、予定のタイトルを設定する newTitle() メソッドと、開始日/終了日を設定する newWhen() メソッドを公開しています。オブジェクトのこれらのプロパティーが設定されると、insertEvent() メソッドが実際に予定を Google サーバーに保存します。予定は追加されると即時にカレンダーに表示されるはずです。

図 4 に示すのは、新しい予定が正常にカレンダーに追加された後の出力です。

図 4. 新しい予定を追加した結果
新しい予定を追加した結果
新しい予定を追加した結果

既存の予定を削除、変更する

予定を削除する方法はさらに簡単です。当該の予定に固有の URL を取得して、その 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 リクエストをその URL に送信します。Zend ライブラリーでこのプロセスを実行するには、予定のオブジェクトのプロパティーに新しい値を設定してから、オブジェクトの save() メソッドによって更新した予定をサーバーに保存します。#c7リスト 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 メソッドを使って予定の ID (登録されている予定から抽出) をこれらのスクリプトに渡します。

リスト 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 ページ
予定の一覧を表示する Web ページ

リスト 9 に、delete.php スクリプトのコードを記載します。このスクリプトはリスト 6 で概説した方法に従って、GET メソッドによって予定の ID を受け取り、この ID を使って予定を削除します。

リスト 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>

edit.php スクリプトのコードは、リスト 10 のとおりです。

リスト 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>

リスト 10 ではリスト 9 と同じように、GET メソッドによって予定の ID を受け取り、この ID を使用して getCalendarEventEntry() メソッドで予定の詳細を取得しています。取得した詳細は Web フォームに予め入力しておくことで、ユーザーはフォームの予定の詳細を変更して送信できるようになります。フォームが送信されると、スクリプトは再び Calendar Data API を用いて、新しい予定の詳細で更新された <entry> を構成し、その変更を save() メソッドを使ってサーバーに保存します。

予定を検索する

あらゆる Google Data フィードと同じく、Calendar API でも開発者が以下のパラメーターのいくつかを REST による照会に追加することで、出力をカスタマイズすることができます。

  • start-index パラメーター。エントリーの開始オフセットを指定します。
  • max-results パラメーター。取得するエントリーの数を指定します。
  • start-min および start-max パラメーター。返されるエントリーの日付範囲を指定します。
  • orderby パラメーター。エントリーの並び替えの基準を指定します。

リスト 11 は、上記のパラメーターのいくつかを設定した例です。ここでは、リスト 2 のデフォルト出力を翌 7 日間のエントリーのみに制限し、開始日時を基準にエントリーを並び替えています。

リスト 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 クライアント・ライブラリーでも、これらのパラメーターのサポートが組み込まれています。その例として、以下のリスト 13 では、リスト 8 を更新して検索フォームとフォーム・プロセッサーを組み込んでいます。

リスト 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 クライアント・サービスの両方を使って、Google カレンダー・サービスのデータを PHP アプリケーションから利用する方法を簡単に説明しました。この記事に記載したサンプル・コードから、以下のことを学べたはずです。

  • Google カレンダーのフィード・フォーマットの概要
  • 日付やキーワードを基準にカレンダーのエントリーを検索する方法
  • カレンダーのエントリーを追加、変更、削除する方法
  • カスタマイズしたフロントエンドをカレンダー・サービスに組み込む方法

これらのサンプル・コードが明らかにしているように、Google Calendar Data API は、開発者が Google カレンダーの独自のカスタム Web フロントエンドを作成するための、完成度の高い、便利で柔軟な方法となります。ぜひ、実際に使って確かめてみてください。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=XML, Open source, Web development
ArticleID=326466
ArticleTitle=PHP アプリケーションから Google カレンダーを利用する
publish-date=07082008