2010年12月30日木曜日

CodeIgniterでGoogleCalendarAPIのテスト(2)

さてちょっと時間が空いてしまいましたがGoogleCalendarAPIをCI上から利用してカレンダー機能にアクセスはほぼ実装が終わりました。そのあとjsなどをいじっていたので更新が遅くなってしまった訳ですが各機能についてご紹介したいと思います。

前回IBMのサンプルをそのまま用いてカレンダーの予定からタイトルやサマリを取得した訳ですがサマリだとちょっと見づらいので取得する情報をbasicからfullへと変更し各情報の詳細を取得出来るように変更しました。

public function getCalendarEntryList() {

 try{
  $gcal = Zend_Gdata_Calendar::AUTH_SERVICE_NAME;
  $client =Zend_Gdata_ClientLogin::getHttpClient($this->user, $this->pass, $gcal);
  $gcal = new Zend_Gdata_Calendar($client);

  $query = $gcal->newEventQuery();
  $query->setUser('default');
  $query->setVisibility('private');
  $query->setProjection('full');
  $query->setOrderby('starttime');

  $feed=$gcal->getCalendarEventFeed($query);

  $data['title'] = (string)$feed->title;
  $data['totalResults'] = (String)$feed->totalResults;

  $events = array();
  foreach ($feed as $event) {
   $obj = new stdClass;

   $obj->title = $event->title;

   $when = $event->when;
   $obj->when = date('Y/n/j',strtotime($when[0]->getStartTime()));
   $obj->startTime = date('G:i',strtotime($when[0]->getStartTime()));
   $obj->endTime = date('G:i',strtotime($when[0]->getEndTime()));

   $reminders = $when[0]->reminders;
   if (count($reminders)!=0) {
    foreach ($reminders as $reminder) {
     if ($reminder->getMethod()=='email') {
      $obj->reminder_email = $reminder->getMinutes();
     } else {
      $obj->reminder_alert = $reminder->getMinutes();
     }
    }
   }
   $obj->content = $event->content;
   $obj->edit_key = $event->id;

   $where = $event->where;
   $obj->where = $where[0]->getValueString();

   $events[] = $obj;
  }

  $data['events'] = $events;
 }catch(Exception $e){
  echo "エラー:".$e->getMessage();
 }

 return $data;
}

ソースをご覧頂くとEventQuery生成部分でbasicがfullに変更されているのがおわかり頂けるかと思います。またforeach文の中で各種詳細データを取得しているのが前回との変更点です。

詳細データの取得に関して注意が必要なところは「when」「reminders」そして「where」等の情報はイベントデータ内に配列として保持されている点です。基本的にwhenやwhereは1つだけ保持していると思われるので$when[0]等で決め打ちでアクセスしてしまっています。またリマインダについてはGoogleカレンダー上では複数登録することが出来ますが今回はメールとアラートもしくはメール+アラートという3つの選択肢から選択し設定時間は1つだけ保持できる仕様としております。

続いては削除メソッドです。

public function deleteEntry() {
 try {
  $gcal = Zend_Gdata_Calendar::AUTH_SERVICE_NAME;
  $client =Zend_Gdata_ClientLogin::getHttpClient($this->user, $this->pass, $gcal);
  $gcal = new Zend_Gdata_Calendar($client);

  $edit_key = $this->input->post('edit_key', TRUE);
  if (isset($edit_key)) {
   $event = $gcal->getCalendarEventEntry('http://www.google.com/calendar/feeds/default/private/full/'.$edit_key);
   $event->delete();
  }
 }catch(Exception $e){
  echo "エラー:".$e->getMessage();
 }
}

イベントエントリに含まれているidを使って個別EventEntryを取得してdelete()メソッドを実行するだけで削除が可能です。Contactsの時はeditキーを送っていましたがZFの実装がこのようになっています。これはなんなく実装できました。

次は登録メソッドを見てみたいと思います。

public function add_entry() {
 try {
  $gcal = Zend_Gdata_Calendar::AUTH_SERVICE_NAME;
  $client =Zend_Gdata_ClientLogin::getHttpClient($this->user, $this->pass, $gcal);
  $gcal = new Zend_Gdata_Calendar($client);

  $dates = explode('/', $this->input->post('when', TRUE));
  $stimes = explode(':', $this->input->post('start_time', TRUE));
  $etimes = explode(':', $this->input->post('end_time', TRUE));
  date_default_timezone_set('Asia/Tokyo');
  $stime = date(DATE_RFC3339, mktime($stimes[0],$stimes[1],0,$dates[1],$dates[2],$dates[0]));
  $etime = date(DATE_RFC3339, mktime($etimes[0],$etimes[1],0,$dates[1],$dates[2],$dates[0]));

  $event = $gcal->newEventEntry();
  $event->title = $gcal->newTitle($this->input->post('title', TRUE));
  $when = $gcal->newWhen();
  $when->startTime = $stime;
  $when->endTime = $etime;
  if (isset($_POST['reminder_cbox'])) {
   $time = $this->input->post('reminder_time', TRUE);
   $method = $this->input->post('reminder', TRUE);
   $reminders = array();
   switch ($method) {
    case 'm':
     $reminder = $gcal->newReminder();
     $reminder->method = "email";
     $reminder->minutes = $time;
     $reminders[] = $reminder;
     break;
    case 'p':
     $reminder = $gcal->newReminder();
     $reminder->method = "alert";
     $reminder->minutes = $time;
     $reminders[] = $reminder;
     break;
    case 'mp':
     $reminder = $gcal->newReminder();
     $reminder->method = "email";
     $reminder->minutes = $time;
     $reminders[] = $reminder;
     $reminder = $gcal->newReminder();
     $reminder->method = "alert";
     $reminder->minutes = $time;
     $reminders[] = $reminder;
     break;
   }
   $when->reminders = $reminders;
  }
  $event->when = array($when);
  $event->where = array($gcal->newWhere($this->input->post('where', TRUE)));
  $event->content = $gcal->newContent($this->input->post('content', TRUE));

  $newEvent = $gcal->insertEvent($event);
 }catch(Exception $e){
  echo "エラー:".$e->getMessage();
 }
}

この部分はわりと苦労しました。日時をRFC3339形式にして送らないといけませんがこれはphpのdate()メソッドが対応してくれていますので$_POSTから取得した日時データをexplode()メソッドでバラして生成しています。冒頭で書いたようにリマインダについては「メールのみ」「ポップアップのみ」そして「メールとポップアップ併用」という3種類を選択可能とし時間は1つだけ指定できる仕様のためこのような実装になっています。「when」「reminders」そして「where」についてはしっかりと配列で渡してあげることをお忘れなく。これを忘れるとエラーとなります。

また各種データについてZF側でインスタンス生成メソッドが用意されているのでそのメソッドを利用してデータを登録しています。xmlを生成していたContactsに比べてとても楽チンになっているので助かりました。いや…逆にメソッドを調べるのが大変だったかも?(笑

最後にデータ修正です。

public function correct_entry() {
 try {
  $gcal = Zend_Gdata_Calendar::AUTH_SERVICE_NAME;
  $client =Zend_Gdata_ClientLogin::getHttpClient($this->user, $this->pass, $gcal);
  $gcal = new Zend_Gdata_Calendar($client);

  $dates = explode('/', $this->input->post('when', TRUE));
  $stimes = explode(':', $this->input->post('start_time', TRUE));
  $etimes = explode(':', $this->input->post('end_time', TRUE));
  date_default_timezone_set('Asia/Tokyo');
  $stime = date(DATE_RFC3339, mktime($stimes[0],$stimes[1],0,$dates[1],$dates[2],$dates[0]));
  $etime = date(DATE_RFC3339, mktime($etimes[0],$etimes[1],0,$dates[1],$dates[2],$dates[0]));

  $event = $gcal->getCalendarEventEntry($this->input->post('edit_key', TRUE));
  $event->title = $gcal->newTitle($this->input->post('title', TRUE));
  $when = $gcal->newWhen();
  $when->startTime = $stime;
  $when->endTime = $etime;
  if (isset($_POST['reminder_cbox'])) {
   $time = $this->input->post('reminder_time', TRUE);
   $method = $this->input->post('reminder', TRUE);
   $reminders = array();
   switch ($method) {
    case 'm':
     $reminder = $gcal->newReminder();
     $reminder->method = "email";
     $reminder->minutes = $time;
     $reminders[] = $reminder;
     break;
    case 'p':
     $reminder = $gcal->newReminder();
     $reminder->method = "alert";
     $reminder->minutes = $time;
     $reminders[] = $reminder;
     break;
    case 'mp':
     $reminder = $gcal->newReminder();
     $reminder->method = "email";
     $reminder->minutes = $time;
     $reminders[] = $reminder;
     $reminder = $gcal->newReminder();
     $reminder->method = "alert";
     $reminder->minutes = $time;
     $reminders[] = $reminder;
     break;
   }
   $when->reminders = $reminders;
  }
  $event->when = array($when);
  $event->where = array($gcal->newWhere($this->input->post('where', TRUE)));
  $event->content = $gcal->newContent($this->input->post('content', TRUE));

  $event->save();
 }catch(Exception $e){
  echo "エラー:".$e->getMessage();
 }
}

内容については登録時とほぼ同じですがeditキーを使ってEventEntryを取得しその内容を書き換えてsave()メソッドで登録しております。ここで注意が必要なのが削除時や個別イベントデータ取得時に利用するidとデータ修正を行うeditキーは別のデータということです。

IBMのサンプルはbasicでデータを取得しているためこの違いには言及していませんでしたが以下のxmlデータをご覧ください。

<atom:id>http://www.google.com/calendar/feeds/default/private/full/57a84t1b243radd31209erdd9k≷/atom:id>
<atom:link href="http://www.google.com/calendar/event?eid=NTdhODR0MWIyNDNyYWRkMzEyMDllcmRkOWtfMjAxMDEyMjdUMDIzMDAwWiB0ZXN0LmthZXBhcGFAbQ" rel="alternate" type="text/html" title="alternate"/>
<atom:link href="http://www.google.com/calendar/feeds/default/private/full/57a84t1b243radd31209erdd9k" rel="self" type="application/atom+xml"/>
<atom:link href="http://www.google.com/calendar/feeds/default/private/full/57a84t1b243radd31209erdd9k/63429101549" rel="edit" type="application/atom+xml"/>

最後のeditだけ数字のデータが付加されているのがおわかり頂けるかと思います。このデータを渡してあげないとデータ修正は上手くいかずエントリの先頭のデータが書き換えられてしまったりしましたのでご注意ください。

さて実際のブラウザでの表示も掲載しておきたいと思います。


こんな具合にイベントのタイトルと時間、説明と場所が表示されリマインダについてはアイコン表示にしてあります。下の削除と編集リンクからそれぞれの機能が利用できて一番下の「新しいイベントを追加」で新規イベント登録が出来ます。最下段の検索機能についてはまだ実装しておりません。この後実装予定です。


上記はイベントの登録と修正に利用するフォームです。日時と時間の入力はjqueryを利用して入力補助をしています。またリマインダについてはチェックを入れるとセレクトフォームが表示されデータ登録が可能となります。いや…jsで遊んでみたかったんです。jqueryを使うととっても楽にいろいろと出来るようになりますねぇ。もっと勉強しないと!

2010年12月27日月曜日

CodeIgniterでGoogleCalendarAPIのテスト(1)

さてGoogleContactsAPIに続いて今度はCalendarAPIのテストをCodeIgniter上で行なてみたいと思います。

Contactsに比べZendFramework自身が直接対応していますし他の情報もいくつか存在しているので苦労の度合いは少ないように予想してます。今考えたら先にCalendarをテストしておいた方が楽だったのではないかと思わなくもありませんが後の祭りというやつですね。

はてさてGoogleカレンダーは私も利用していますがAjaxがたっぷり使われていてとても使いやすいUIが提供されています。それを今さらならがwebアプリから使えるようにしたところで何になるんだ?という気もしなくもありませんがまあそれはそれとして今回はCodeIgniter上で動作するwebアプリからカレンダーの作成、修正やカレンダーを指定しての予定の追加、修正そして削除を実装してみる予定です。またContactsではClientLoginを使用しましたが実際のwebアプリではwebアプリ製作者である私にもユーザの認証情報の伝わらないAuthSubで実装してみたいと思います。まずはClientLogin認証で機能を実装してから最後にAuthSub認証に変更してみる予定です。

それぞれの詳しい情報については以下のドキュメントをご参照ください。

Zend Framework GDataプログラマーズリファレンスガイド Google Calendar の使用法
Google Calendar APIs and Tools Data API Developer's Guide: Protocol
Google Calendar APIs and Tools API Reference Guide
Authentication and Authorization for Google APIs AuthSub for Web Applications

なおGoogleCalendarAPIのドキュメントには「これは古いバージョンである」旨記載がありますがVer.2のPHPGuideはまだありませんし。ZendFrameworkがまだ対応してないような雰囲気なので古い方のドキュメントを参照してますが詳細はまだわかっていません。
コーディングを進めるうちになにかわかりましたら加筆予定です。

また今回もIBM developerWorksの記事[http://www.ibm.com/developerworks/jp/xml/library/x-googleclndr/index.html]を参考に進めていきたいと思います。

ではdWの記事を熟読した後今回もこのソースをCIに移植していく訳ですがその前にそのままCI上でviewを表示してみたいと思います。

DL可能となっているソースのview.php(idとpassを書き換え)をCIのコントローラからloadするだけです。
CodeIgniterのsystemフォルダやindex.phpファイルを今回もeclipseを利用してコーディングするので作成したPHPプロジェクトフォルダ内にコピーしてconfig.phpやroutes.phpについては適切に設定を行なってください。

まずはコントローラの./system/application/controllers/calendar.phpファイルから見ていきましょう。

<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class Calendar extends Controller {

 public function __construct() {

  parent::Controller();

  $this->output->set_header('Content-Type: text/html; charset=UTF-8');
 }

 /*
  * デフォルトメソッド
  */
 public function index() {
  $this->load->view('view');
 }
}

/* End of file calendar.php */
/* Location: ./system/application/controllers/calendar.php */

Controllerクラスを継承したCalendarクラスを作成してコンストラクタで親クラスのコンストラクタを呼び文字化け対策にUTF-8を指定しています。デフォルトメソッドのindex()ではIBMで配布されているview.phpファイルを一部手直ししたものをロードしているだけです。

続いてはview.phpファイルを見てみましょう。

<!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="ja" lang="ja">
<head>
 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
 <title>Googleカレンダー予定一覧</title>
 <style type="text/css">
 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 = "password";
    $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 $title ?></h1>
 <?php echo $totalResults ?> 件の予定があります。
 <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\">編集</a> | ";
   echo "<a href=\"delete.php?id=$id\">削除</a> <br/>\n";
   echo "</li>\n";
  }
  echo "</ul>";
 ?>
 </ol>
 <p/>
 <a href="add.php">新しいイベントを追加</a><p/>
 <form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="get">
  予定の検索:<br/>
  <input type="text" name="q" size="10"/>
  <input type="submit" name="submit" value="検索"/>
 </form>
</body>
</html>


ヘッダ部分を日本語対応とし表示を日本語化した以外はそのままのコードになっています。

では簡単にphpスクリプト部分を読んでいってみましょう。

最初に必要となるZendFrameworkのクラスをロード後、ClientLogin認証で$gcalインスタンスを生成しZend_Gdata_Calendarクラスを生成しています。さらにdefaultカレンダーのprivate領域のデータをbasicなデータ量で取得するというクエリを作成していますね。$_GET['q']を使っている部分が予定の検索時に利用する条件です。そしてtry~catch文の中でこの作成したクエリを発行してデータを取得しています。得られたfeedをhtml内に埋め込んでいき表示を行なうという実装ですね。ソースコード内の$userと$passについてはご自身のアカウント情報を入力してください。


上記がこのview.phpを表示した状態です。表示されているEventは私が適当に作成したものです。今回もあっさりと表示されて安心しました。

問題なく表示されましたのでCI化していきたいと思います。

まずはモデルの./system/application/models/calendar_model.phpファイルから作成しました。

<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class Calendar_model extends Model {

 private $user;
 private $pass;

 public function __construct() {
  parent::Model();

  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');

  $this->config->load('calendar_config', TRUE);
  $this->user = $this->config->item('username','calendar_config');
  $this->pass = $this->config->item('password','calendar_config');
 }

 public function getCalendarEntryList() {

  try{
   $gcal = Zend_Gdata_Calendar::AUTH_SERVICE_NAME;
   $client =Zend_Gdata_ClientLogin::getHttpClient($this->user, $this->pass, $gcal);
   $gcal = new Zend_Gdata_Calendar($client);

   $query = $gcal->newEventQuery();
   $query->setUser('default');
   $query->setVisibility('private');
   $query->setProjection('basic');
   $query->setOrderby('starttime');

   $feed=$gcal->getCalendarEventFeed($query);

   $data['title'] = (string)$feed->title;
   $data['totalResults'] = (String)$feed->totalResults;

   $events = array();
   foreach ($feed as $event) {
    $obj = new stdClass;

    $obj->title = $event->title;
    $obj->summary = $event->summary;
    $obj->id = $event->id;

    $events[] = $obj;
   }

   $data['events'] = $events;
  }catch(Zend_Gdata_App_Exception $e){
   echo "エラー:".$e->getResponse();
  }

  return $data;
 }
}

/* End of file calendar_model.php */
/* Location: ./system/application/model/calendar_model.php */

Modelクラスを継承したCalendar_modelクラスを作成してコンストラクタの中でZFの必要なクラスをロード、Contactsの時と同様にユーザ名とパスワードはcalender_config.php内に記述してあり、それをload後取得しています。

getCalendarEntryList()メソッドでGoogleCalendarにアクセスしてfeedを取得し$dataに必要なデータを入れて返すというContactsのときとまったく同じ手法です。

続いては変更を加えたコントローラクラスのcalender.phpを見てみます。

<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class Calendar extends Controller {

 public function __construct() {

  parent::Controller();

  $this->load->model('calendar_model');

  $this->output->set_header('Content-Type: text/html; charset=UTF-8');
 }

 /*
  * デフォルトメソッド
  */
 public function index() {
  $data = $this->calendar_model->getCalendarEntryList();
  $this->load->view('calendar_index',$data);
 }
}

/* End of file calendar.php */
/* Location: ./system/application/controllers/calendar.php */

コンストラクタメソッド内でCalendar_modelをロードして先程作成したgetCalendarEntryList()メソッドでデータを取得後view.phpから名称を変更したcalendar_index.phpファイルにデータを渡してロードするよう変更しました。

最後にビューのcalendear_index.phpを見てみましょう。

<!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="ja" lang="ja">
<head>
 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
 <title>Googleカレンダー予定一覧</title>
 <style type="text/css">
 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><?php echo $title ?></h1>
 <?php echo $totalResults ?> 件の予定があります。
 <p/>
 <ol>
 <?php
  foreach ($events as $e) {
   echo "<li>\n";
   echo "<h2>" . stripslashes($e->title) . "</h2>\n";
   echo stripslashes($e->summary) . " <br/>\n";
   $id = substr($e->id, strrpos($e->id, '/')+1);
   echo "<a href=\"edit.php?id=$id\">編集</a> | ";
   echo "<a href=\"delete.php?id=$id\">削除</a> <br/>\n";
   echo "</li>\n";
  }
  echo "</ul>";
 ?>
 </ol>
 <p/>
 <a href="add.php">新しいイベントを追加</a><p/>
 <form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="get">
  予定の検索:<br/>
  <input type="text" name="q" size="10"/>
  <input type="submit" name="submit" value="検索"/>
 </form>
</body>
</html>

実装部分がなくなりかなりすっきりした感がありますね。これぞMVCモデルの利点でしょうか。データ表示部を数カ所実装にある変数名に変更しただけですがこれでview.phpと同じ表示が得られるようになりました。

3ヶ所ほどある他機能へのリンクはまだ実装していないのでそのままになっています。そもそもCIはデフォルトではGETを受け取らないのでこのままでは利用できません。

さてここまででGoogleカレンダーにAPIを使ってアクセスしEventデータを取得する機能が実装できました。実装といってもそのままコピーしたような状態ではありますがまあ良しとしましょう。このあとEventの追加、削除そして修正を実装したりカレンダーの追加や修正等もチャレンジする予定です。

2010年12月26日日曜日

CodeIgniterでGoogleContactsAPIのテスト(8)

さてGoogleContactAPIを叩いての連絡先データをいじいじするテストですが今回で最終回です。連絡先の新規登録、修正及び削除、グループの新規登録、修正及び削除が出来るようになっています。未実装なのは連絡先のデータにグループデータを含める部分ということになりますね。今回の記事では連絡先データの中でのグループデータについて一覧取得、登録そして修正時のxmlデータ扱い方についてご紹介します。

これまでのデータの扱い方が理解できていれば特に問題ない範疇だと思います。

まずは一覧取得時のデータ取得方法です。

$group_data = $this->gdata_groups_model->getGroupsElement();
foreach ($xml->groupMembershipInfo as $g) {
 foreach ($group_data as $key => $val) {
  if ($key == $g['href']) {
   $obj->groups[] = $val;
  }
 }
}

feedデータにはhrefとしてグループのidしか含まれていないのでgroupデータを扱うモデルからElementデータを配列で取得してグループ名を代入していますがやり方としてはおわかり頂けるかと思います。

続いては登録時のxml要素追加方法です。

// add group elements
$groups = $this->input->post('groups', TRUE);
foreach ($groups as $g) {
 $group = $doc->createElement('gContact:groupMembershipInfo');
 $group->setAttribute('deleted','false');
 $group->setAttribute('href',$g);

 $entry->appendChild($group);
}

グループデータはwebサイトデータと同様にgContactに定義されているのでそれで指定してあげます。deleted要素は必要ないような気もしますがデベロッパーガイドに従い加えてあります。

最後に修正時のxml要素追加方法ですが登録時とちょっとだけ違います。

// add group elements
$groups = $this->input->post('groups', TRUE);
foreach ($groups as $g) {
 $group = $doc->createElement('groupMembershipInfo');
 $group->setAttribute('xmlns','http://schemas.google.com/contact/2008');
 $group->setAttribute('deleted','false');
 $group->setAttribute('href',$g);

 $entry->appendChild($group);
}

違いはxmlns要素が追加になっているだけですのでお約束みたいなものでしょうか。
この要素がないとエラーが帰ってしまいますのでご注意ください。

さてCodeIgniterでGoogleContactsAPIを叩きながらwebアプリから連絡先データを操作することが出来るようになりました。まだ連絡先データには様々なデータが扱えるようになっていますがリファレンスガイド等を見ればおそらく操作できるのではないかと思います。

ZendFrameworkを利用したので本当に簡単にデータ操作が出来ました。フレームワークの中でフレームワークを使うような愚はどうかとも思いますがまあ軽量フレームワークのCodeIgniterの利点を活かしつつこのテストを活用したwebアプリをこれから製作する予定です。

しかし連絡先だけでなくカレンダーのデータ操作も実や予定していまして次のシリーズは「CodeIgniterでGoogleCalenderAPIをテスト」シリーズが始まります。(笑

CodeIgniterでGoogleContactsAPIのテスト(7)

さていろいろとハマりまくっているCodeIgniterですがバリデーションにも一癖二癖ある感じです。既に使われている方々にはあたりまえの事かもしれませんがこれから始めて触ってみようという方のために記事にしておきたいと思います。

まずはバリデーションを通らなかったときの対応について。

一般的にはform画面に戻って入力結果をinput等に戻して表示という流れになるかと思います。今回複数のメールアドレスや電話番号登録が可能なように実装しているのでformのnameを配列としてCIに渡しております。

このように配列を利用するとバリデーションに渡した結果を受けるときのset_valueメソッドが上手く動作していないようで値を得ることが出来ませんでした。

仕方がないのでMy_Form_validation.phpを作成してForm_vaidationクラスを継承した独自バリデーションクラスを作成してちょこっとset_validationをいじりました。

function set_value($field = '', $default = '') {
 if ( ! isset($this->_field_data[$field]))
 {
  return $default;
 }

 if ( ! isset($this->_field_data[$field]['postdata'])) {
  return $default;
 } else {
  return $this->_field_data[$field]['postdata'];
 }
}

バリデーション後にviewを呼びだす前に$_POSTに必要データを突っ込んでおいて

set_value('edit', $this->input->post('edit'));

のような感じでデフォルト値として$_POSTからのデータを入れておいてあげると上手く行きました。

また複数formのバリデーションを行なう場合にユーザガイドでは http://codeigniter.jp/user_guide_ja/libraries/form_validation.html#savingtoconfig の「検証ルールのセットを作る」に従ってセット名を付けて呼びだすようになっています。

例えば…

if ($this->form_validation->run('contacts') == TRUE) {
 $this->gdata_model->post();
 $data = $this->gdata_model->getContactsList();
 $this->load->view('contacts_index', $data);
} else {
 $this->load->view('form_contacts_view', $this->gdata_model->getFormData());
}

こんな感じで呼びだすことになる訳です。

このときに利用するルールをApplication/config/form_validation.phpに配列として記載する訳ですがユーザドキュメントでは2010/12/25現在「=>」とすべきところが「=」となってしまっています。
こんな間違いに30分もハマるのは私くらいのものかと思いますが念のため書いておきます。(笑

軽快なCodeIgniterなだけあって痒いところに手が届くという機能は実装されてない感じもありますが個人的にはわりと気に入ったのでもうちょっと深く勉強してみたいと思っています。

2010年12月25日土曜日

CodeIgniterでGoogleContactsAPIのテスト(6)

時間がかかると云ってた割にはすぐに更新がきました。もっと苦労するかと思っていましたがすんなりと実装することが出来ました。ん?すんなり?嘘です。思いきりどハマりしたところが何箇所かありましたねぇ。

ということで今回はGoogleContactsのグループデータの取得、登録、更新そして削除についてまとめてご紹介します。

まずは一覧の取得方法です。

try {
 // perform login and set protocol version to 3.0
 $client = Zend_Gdata_ClientLogin::getHttpClient($this->user, $this->pass, 'cp');
 $gdata = new Zend_Gdata($client);
 $gdata->setMajorProtocolVersion(3);

 // perform query and get feed of all results
 $query = new Zend_Gdata_Query('http://www.google.com/m8/feeds/groups/default/full');
 $query->maxResults = 0;
 $query->setParam('orderby', 'lastmodified');
 $query->setParam('sortorder', 'descending');
 $feed = $gdata->getFeed($query);

 $data['title'] = (string)$feed->id;
 $data['totalResults'] = (String)$feed->totalResults;

 // parse feed and extract contact information
 // into simpler objects
 $groups = array();
 foreach($feed as $entry){
  $obj = new stdClass;
  $xml = simplexml_load_string($entry->getXML());
  if (! isset($xml->systemGroup)) {
   $obj->edit = (string)$entry->getEditLink()->href;
   $obj->group_name = (string) $entry->title;
  } else {
   $obj->edit = 'system_group';
   $val = $this->config->item('system_groups','config_contacts');
   $obj->group_name = $val[(string)$xml->systemGroup['id']];
  }
  $groups[] = $obj;
 }

} catch (Exception $e) {
 die('ERROR:' . $e->getMessage());
}

$data['groups'] = $groups;

return $data;

contactの時と何が違うかというと宛先URLが変っただけですね。グループデータの取得についてはhttp://www.google.com/m8/feeds/groups/default/fullこちらからの取得になります。それ以降はcontactsの時と同じ処理ですのでおわかり頂けるかと思います。

続いては登録部分です。

try {
 // perform login and set protocol version to 3.0
 $client = Zend_Gdata_ClientLogin::getHttpClient($this->user, $this->pass, 'cp');
 $gdata = new Zend_Gdata($client);
 $gdata->setMajorProtocolVersion(3);

 // create new entry
 $doc = new DOMDocument();
 $doc->formatOutput = true;
 $entry = $doc->createElement('atom:entry');
 $entry->setAttributeNS('http://www.w3.org/2000/xmlns/' ,'xmlns:atom', 'http://www.w3.org/2005/Atom');
 $entry->setAttributeNS('http://www.w3.org/2000/xmlns/' ,'xmlns:gd', 'http://schemas.google.com/g/2005');
 $doc->appendChild($entry);

 // add category element
 $category = $doc->createElement('atom:category');
 $category->setAttribute('scheme', 'http://schemas.google.com/g/2005#kind');
 $category->setAttribute('term','http://schemas.google.com/contact/2008#group');
 $entry->appendChild($category);

 // add title element
 $title = $doc->createElement('atom:title', $this->input->post('group_name', TRUE));
 $title->setAttribute('type', 'text');
 $entry->appendChild($title);

 $entryResult = $gdata->insertEntry( $doc->saveXML(), 'https://www.google.com/m8/feeds/groups/default/full');

} catch (Exception $e) {
 die('ERROR:' . $e->getMessage());
}

return $entryResult->id;

登録もputするURLがgroups用になっているだけでそれ以外には項目がグループの方がかなり少ないといったところでしょうか。本来「gd:extendedProperty」という項目にグループの情報を登録することが出来るのですが登録自体は成功するもののデータ更新時にこのinfoデータをxmlから取得するのが上手くいかず登録から断念することになりました。取得した$feedの中に改行が入ってしまっていたりしたのでなにかバグがあるのかも?
作成するxmlはデベロッパーガイドを参考にして作成しています。

続いては削除です。

try {
 // perform login and set protocol version to 3.0
 $client = Zend_Gdata_ClientLogin::getHttpClient($this->user, $this->pass, 'cp');
 // set GData delete headers
 $client->setHeaders('If-Match: *');
 $gdata = new Zend_Gdata($client);
 $gdata->setMajorProtocolVersion(3);

 // delete entry
 $gdata->delete($this->input->post('edit',TRUE));
} catch (Exception $e) {
 die('ERROR:' . $e->getMessage());
}

削除もcontactsと同様editタグに指定されているキーを送ってあげることで簡単に削除が出来ます。今回もIf-Match:*を指定して簡単に削除を行なっています。

最後に更新です。

try {
 // perform login and set protocol version to 3.0
 $client = Zend_Gdata_ClientLogin::getHttpClient($this->user, $this->pass, 'cp');
 $client->setHeaders('If-Match: *');
 $gdata = new Zend_Gdata($client);
 $gdata->setMajorProtocolVersion(3);

 $query = new Zend_Gdata_Query($this->input->post('edit'));
 $entry = $gdata->getEntry($query);
 $xml = simplexml_load_string($entry->getXML());

 $xml->title = $this->input->post('group_name');

 $entryResult = $gdata->updateEntry($xml->saveXML(), $entry->getEditLink()->href);

} catch (Exception $e) {
 die('ERROR:' . $e->getMessage());
}

return $entryResult->id;

更新も項目が少ないのでとても短かなソースになっています。editデータを送りqueryを実行、取得したxmlデータの中でtitleタグに新しいグループ名を代入してZendFrameworkのupdateEntryコマンドで送信して完了です。

contactsの方で苦労したせいかgroupデータについてはわりと簡単に実装することが出来ました。えっ?ハマったところ?データをjavascriptでいじる部分でハマっただけです。(笑

2010年12月24日金曜日

CodeIgniterでGoogleContactsAPIのテスト(5)

さてちょっと時間が空いてしまいましたが今回はContactsの更新です。
これまで一覧取得、登録そして削除ときましたので更新が出来たら一通りデータをいじれるようになる訳です。更新も削除と同じく一覧取得で得ているidをキーに行ないます。

$obj->edit = (string)$entry->getEditLink()->href;

こんな感じで取得してましたね。
ZendFramework様々といったところでしょうか。

今回時間が空いてしまったのはjavascript等をいじっていたせいなんですがこれはまあ他のサイトを参考にしてアレンジしていただけなので省略してContactsデータの更新に焦点をあててご紹介します。

// perform login and set protocol version to 3.0
$client = Zend_Gdata_ClientLogin::getHttpClient($this->user, $this->pass, 'cp');
$client->setHeaders('If-Match: *');
$gdata = new Zend_Gdata($client);
$gdata->setMajorProtocolVersion(3);

$query = new Zend_Gdata_Query($this->input->post('edit'));
$org_entry = $gdata->getEntry($query);
$xml = simplexml_load_string($org_entry->getXML());

上記のような形でアクセスを行ないqueryのデータとしてeditキーを渡しています。
通常はheaderにETagの値を指定して更新を行うらしいですが自分しかデータをいじらないアカウントですのでアスタリスクを指定しETagはデータに含めていません。

このqueryで得られたデータを上書きしていってputすることでデータが更新されるのですがデータ削除を行なうことを考えて一旦メールアドレス等のデータを全削除してからデータ登録を行なったのですがエレメントの順番が大切なようでエラーが出てしまい今回は全てのデータを新たなDOMDocumentに追加してくという手法を取りました。

// create new entry
$doc = new DOMDocument();
$doc->formatOutput = true;
$entry = $doc->createElement('atom:entry');
$entry->setAttributeNS('http://www.w3.org/2000/xmlns/' ,'xmlns:atom', 'http://www.w3.org/2005/Atom');
$doc->appendChild($entry);

// add name element
$name = $doc->createElement('name');
$name->setAttribute('xmlns', 'http://schemas.google.com/g/2005');
$entry->appendChild($name);
//input->post()メソッドを利用してXSS対策
$fullName = $doc->createElement('fullName', $this->input->post('name', TRUE));
$name->appendChild($fullName);

// add org name element
$org = $doc->createElement('organization');
$org->setAttribute('xmlns', 'http://schemas.google.com/g/2005');
$org->setAttribute('rel' ,'http://schemas.google.com/g/2005#work');
$entry->appendChild($org);
$orgName = $doc->createElement('orgName', $this->input->post('org', TRUE));
$org->appendChild($orgName);

// add email elements
$emails = $this->input->post('email', TRUE);
$epleces = $this->input->post('email_place', TRUE);
foreach ($emails as $key => $e) {
 if ($e != '') {
  $email = $doc->createElement('email');
  $email->setAttribute('xmlns','http://schemas.google.com/g/2005');
  if ($epleces[$key] == 'custom') {
   $email->setAttribute('label', $this->input->post('email_custom', TRUE));
  } else {
   $email->setAttribute('rel', 'http://schemas.google.com/g/2005#' . $epleces[$key]);
  }
  $email->setAttribute('address', $e);
  $entry->appendChild($email);
 }
}

// add phone elements
$phones = $this->input->post('phone', TRUE);
$pplaces = $this->input->post('phone_place', TRUE);
foreach ($phones as $key => $p) {
 if ($p != '') {
  $phone = $doc->createElement('phoneNumber', $p);
  $phone->setAttribute('xmlns','http://schemas.google.com/g/2005');
  if ($pplaces[$key] == 'custom') {
   $phone->setAttribute('label', $this->input->post('phone_custom', TRUE));
  } else {
   $phone->setAttribute('rel', 'http://schemas.google.com/g/2005#' . $pplaces[$key]);
  }
  $entry->appendChild($phone);
 }
}

// add web elements
$webs =$this->input->post('web', TRUE);
$wplaces = $this->input->post('web_place', TRUE);
foreach ($webs as $key => $w) {
 if ($w != '') {
  $web = $doc->createElement('website');
  $web->setAttribute('xmlns','http://schemas.google.com/contact/2008');
  $web->setAttribute('href',$w);
  if ($wplaces[$key] == 'custom') {
   $web->setAttribute('label', $this->input->post('web_custom', TRUE));
  } else {
   $web->setAttribute('rel', $wplaces[$key]);
  }
  $entry->appendChild($web);
 }
}

foreach ($org_entry->category as $category) {
 $categorys = $doc->createElement('atom:category');
 $categorys->setAttribute('term', $category->getTerm());
 $categorys->setAttribute('scheme', $category->getScheme());
 $entry->appendChild($categorys);
}


$id = $doc->createElement('atom:id', $org_entry->id);
$entry->appendChild($id);

foreach ($org_entry->link as $link) {
 $links = $doc->createElement('atom:link');
 $links->setAttribute('href', $link->getHref());
 $links->setAttribute('rel', $link->getRel());
 $links->setAttribute('type', $link->getType());
 $entry->appendChild($links);
}

$title = $doc->createElement('atom:title',$this->input->post('name', TRUE));
$entry->appendChild($title);

$updated = $doc->createElement('atom:updated', $org_entry->updated);
$entry->appendChild($updated);

$edited = $doc->createElement('atom:edited', $org_entry->edited);
$entry->appendChild($edited);

更新データ以外のデータについては先程行なったqueryの結果をそのまま入れるようにしてあります。じっくりコードを見るとわかると思いますがGmailではメール等の付随データとして仕事や自宅等が選べるようになっていますがその機能にも対応しています。コード中にcustomの文字が見えるかと思います。カスタムデータの場合にはrel要素ではなくlabel要素としてデータをタグに含めて送ることでデータが登録されます。

$entryResult = $gdata->updateEntry($entry_data, $org_entry->getEditLink()>href);

最後にZendFrameworkGDataAPIのupdateEntryメソッドでデータを送って更新が完了です。一緒に送っているのは冒頭で述べたlinkタグに含まれるeditキーです。

これでContactsについては一通りデータのやりとりが出来るようになりました。次の予定ではこれら連絡先を管理するには大切なグループについて登録や更新、削除を出来るようにしてみたいと思います。この部分についてはIBMのサンプルがないので英語ドキュメントを読みながらの作業になります。さらに時間がかかってしまうかも?

2010年12月18日土曜日

CodeIgniterでGoogleContactsAPIのテスト(4)

IBMのGoogleContactsサンプルのCI化第三弾はデータ削除です。

削除機能実装についてはわりとスムーズに行きました。

formタグで削除する連絡先のデータをhiddenで隠し持ちsubmitボタンで確認ダイアログを出しOKボタンで削除実行という流れです。

確認ダイアログのjavascriptは以下のようになっています。

<script language="JavaScript">
<!--
function check(){
    if(window.confirm('削除して宜しいですか?')){
        return true;
    } else {
        window.alert('削除を中止しました。');
        return false;
    }
}
//-->

一般的なスクリプトですから説明はいらないと思いますがOKボタンを押したときにtrueを返し呼び出し側でそれを検知してsubmitを実行するか判断するというものです。




表示した画像は上記のようになっています。リンククリックで即座に削除はちょっと怖いかな?とこんな機構を取り入れてみました。

<div class="name">
    <?php echo $r->name; ?>
    <span class="links">
        <?php echo form_open('contacts/delete','onSubmit="return check()"'); ?>
        <?php echo form_hidden('id',$r->edit)?>
        <?php echo form_submit('submit', '削除'); ?>
        <?php echo form_close(); ?>
    </span>
</div>

上記がその判断を行なうformタグ部分です。CodeIgniterのformヘルパーを利用しているのでちょっとわかりにくいかもしれませんがsubmit先がcontacts.phpのdeleteメソッドで連絡先のidを送っていることがおわかり頂けますでしょうか。

続いてはコントローラのdelete()メソッドを見てみましょう。

public function delete() {
    $this-<gdata_model-<delete();

    $data = $this-<gdata_model-<getContactsList();
    $this-<load-<view('contacts-index', $data);
}

gdata_modelのdeleteメソッドを呼びだしそちらでデータを削除後一覧表示画面を呼びだしている形になっています。削除すべき連絡先のIDについては$_POSTから受けとるため引数として渡しておりません。このあたりのお作法がよくわかっていませんがmodelでも$_POSTが受けとれるようになっているので問題ないかな?

続いてはmodelのdelete()メソッドです。メソッド名がかぶってしまっていますがまあ気にしない方向で…。(笑

public function delete() {
    try {
        // perform login and set protocol version to 3.0
        $client = Zend_Gdata_ClientLogin::getHttpClient($this-<user, $this-<pass, 'cp');
        // set GData delete headers
        $client-<setHeaders('If-Match: *');
        $gdata = new Zend_Gdata($client);
        $gdata-<setMajorProtocolVersion(3);

        // delete entry
        $gdata-<delete($this-<input-<post('id',TRUE));
    } catch (Exception $e) {
        die('ERROR:' . $e-<getMessage());
    }
}

こちらも登録に比べると非常にシンプルに完了しています。ログインしてプロトコルバージョンの指定およびdelete()メソッドの実行です。たったこれだけで削除については実装できてしまいました。

残すはデータ修正ですがこれは再び苦労しそうな予感がします。CIの使い方の問題とGoogleDataAPIともにまだはっきりと指針を詰められていません。じっくりドキュメントを読んで実装方法を考えてみたいと思います。IBMのサンプルプログラムに修正はないんですよねぇ。(笑