2010年12月17日金曜日

CodeIgniterでGoogleContactsAPIのテスト(3)

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

さて続いてはデータ登録部分を実装することにしたいと思います。

IBMのサンプルでは前記事でも書いたようにcontacts-save.php1ファイルで完結しています。これをmodelとviewに分けてCodeIgniter上で動くように変更します。サンプルでは登録出来る項目と表示している項目に違いがあるのでとりあえず表示している項目については登録を出来るようにしました。

contacts-save.phpでは$_POSTで受けとったデータをhtmlentities()を使ってサニタイズしていますがこれが原因で日本語が通らなくなっていたのでCodeIgniterのバリデーション機能に置き換えて実装しています。また入力フォームの生成にCodeIgniterのformヘルパーを利用するよう変更しています。

いやぁ…やたら苦労しましたこの部分。ハマりどころ満載というか英語が読めないというか。一応希望するデータ項目については登録が出来るようになったので記事化します。

まずは登録フォームから見ていきましょう。

<!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>新規連絡先登録</title>
    <style type="text/css">
        body {
            font-family: "MS Pゴシック", Osaka, "ヒラギノ角ゴ Pro W3";
        }
        div.red {
            color: red;
            text-decoration: none;
            font-weight: bold;
        }
    </style>
</head>
<body>
    <h2>連絡先新規登録</h2>
    <?php echo validation_errors(); ?>
    <?php echo form_open('contacts/post'); ?>
        <p>
            名前(必須): <br/>
            <?php echo form_input('name', set_value('name'), 'size="20"'); ?><br/>
        </p>
        <p>
            会社名: <br/>
            <?php echo form_input('org', set_value('org'), 'size="35"'); ?><br/>
        </p>
        <p>メールアドレスもしくは電話番号は必ず入力してください。
        </p>
        <p>
            メールアドレス:<br/>
            <?php echo form_dropdown('mail1_place', $mail_options); ?>
            <?php echo form_input('mail1', set_value('mail1'), 'size="35"'); ?><br/>
        </p>
        <p>
            電話番号:<br/>
            <?php echo form_dropdown('phone1_place', $phone_options); ?>
            <?php echo form_input('phone1', set_value('phone1'), 'size="35"'); ?><br/>
            <br/>ハイフンは含めず数字のみで入力してください。
        </p>
        <p>
            webサイト:<br/>
            <?php echo form_dropdown('web1_place', $web_options); ?>
            <?php echo form_input('web1', set_value('web1'), 'size="35"'); ?><br/>
        </p>
        <?php echo form_submit('submit', '登録'); ?>
        <?php echo form_reset('reset', 'リセット'); ?>
    <?php echo form_close(); ?>
</body>
</html>

説明が必要そうなところとしては<?php echo validation_errors(); ?>と云う部分でしょうか。validatitonの時は個々の項目についてエラー時にメッセージが指定できたのですがform_validationになってまだその機能が実装されていないようで一箇所にエラーが集まって表示されます。またformヘルパーを利用してformタグを生成しています。
selectタグのoptionについてはController上で配列を用意してviewに渡しviewでformヘルパーを利用して展開している感じです。set_value()メソッドを使ってエラー時に入力済みデータを保持するようにしています。


続いてはこのviewを呼びだすcontrollerに追加した関数です。

public function add() {
    $this->load->view('add_contacts_view', $this->_get_option_data());
}
    
public function _get_option_data() {
    $mail_options = array(
                            'work' => '仕事',
                            'home' => '自宅'
                        );
    $data['mail_options'] = $mail_options;

    $phone_options = array(
                            'mobile'=> '携帯電話',
                            'work' => '仕事',
                            'home' => '自宅',
                            'main' => 'メイン',
                            'work_fax' => 'FAX(勤務先)',
                            'home_fax' => 'FAX(自宅)',
                            'pager' => 'ポケベル'
                        );
    $data['phone_options'] = $phone_options;

    $web_options = array(
                            'profile' => 'プロフィール',
                            'blog' => 'ブログ',
                            'home-page' => 'ホームページ',
                            'work' => '仕事'
                        );
    $data['web_options'] = $web_options;

    return $data;
}

add()メソッドはブラウザからアクセス可能なメソッドとして新規連絡先登録画面として呼びだされます。このメソッド内で先程作成した登録フォームのhtmlを呼びだし_get_option_data()メソッドでselectタグで利用する配列を生成しています。メソッド名の頭にアンダースコアがついているのはCodeIgniterの機能を利用してこのメソッドにブラウザからアクセスするのを防ぐためです。

続いてはブラウザにて登録フォームにデータを入力しそのデータを受けとるpost()メソッドを見ていきたいと思います。

public function post() {
 //バリデーション導入
 //XSSサニタイズはmodelにて行う
 $this->form_validation->set_rules('name','名前','trim|required|max_length[40]');
 $this->form_validation->set_rules('org','会社名','trim|max_length[40]');
 $this->form_validation->set_rules('email1','メールアドレス','trim|callback__mail_require_check|valid_email');
 $this->form_validation->set_rules('email1_place','メールアドレスの場所','trim|alpha_dash');
 $this->form_validation->set_rules('phone1','電話番号','trim|numeric|callback__phone_require_check|max_length[11]');
 $this->form_validation->set_rules('phone1_place','電話番号の場所','trim|alpha_dash');
 $this->form_validation->set_rules('web1','webサイト','trim|max_length[50]');
 $this->form_validation->set_rules('web1_place','webサイトの場所','trim|alpha_dash');

 $this->error_enable = FALSE;

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

public function _mail_require_check($str) {
if ($this->data_require_check()==FALSE) {
  $this->error_enable = TRUE;
  $this->form_validation->set_message('_mail_require_check', 'メールアドレスもしくは電場番号が必須です。');
  return FALSE;
 } else {
  return TRUE;
 }
}

public function _phone_require_check($str) {
 if ($this->error_enable == FALSE) {
  if ($this->data_require_check()==FALSE){
   $this->form_validation->set_message('_phone_require_check', 'メールアドレスもしくは電場番号が必須です。');
   return FALSE;
  } else {
   return TRUE;
  }
 }
 return TRUE;
}

public function data_require_check() {
 if (($_POST['mail1']=='') AND ($_POST['phone1']=='')) {
  return FALSE;
 } else {
  return TRUE;
 }
}

まずpost()メソッドを見ていただくとCodeIgniterのバリデーションクラスを利用してデータ検証を行なっているのがわかるかと思います。名前データは必須でメールアドレスもしくは電話番号の入力が必要としたかったのでユーザ定義のバリデーションを行なっています。

ユーザ定義のバリデーションはcontrollerの中に書けという風にドキュメントにも記載されているのですがそれだとブラウザからのアクセスを許可してしまうことになるので先程ご紹介したメソッド名にアンダースコアを付ける方法を採用。実際にバリデーションするときにはcallback_を付加するとのことでアンダースコアが2重になってしまっていますがいわゆるバッドノウハウってやつでしょうかねぇ。本来なら別ファイルに定義したデータを読み込むとかの手法の方がよいように思うのですがどうなんでしょう?controllerに定義を記述するのってなんとなくイヤンな感じがします。

また個別にバリデーション定義をし片方のバリデーションで一度エラーがとなった時には再度エラーメッセージを追加しないようにしています。もうちょっとスマートな記述方法がありそうですが私のスキルではこれが精一杯でした。(汗

$this->form_validation->run()という部分でバリデーションを実行している訳ですがバリデーションを通ったら連絡先一覧をviewとして読込み通らなかった場合はadd_contacts_viewを読み込んでエラー表示をしています。



続いては実際にGoogleDataAPIを通じてデータ登録を行なっているpost()メソッドを見てみましょう

public function post() {
    // check for required input
    /* バリデーションを実施するので削除
    if (empty($this->input->post('name'))) {
        die('ERROR: Missing name');
    }

    if (empty($_POST['email'])) {
        die('ERROR: Missing email address');
    }

    if (empty($_POST['org'])) {
        die('ERROR: Missing organization');
    }
    */

    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');
        $entry->setAttributeNS('http://www.w3.org/2000/xmlns/' ,'xmlns:gContact', 'http://schemas.google.com/contact/2008');
        $doc->appendChild($entry);

        // add name element
        $name = $doc->createElement('gd:name');
        $entry->appendChild($name);
        //input->post()メソッドを利用してXSS対策
        $fullName = $doc->createElement('gd:fullName', $this->input->post('name', TRUE));
        $name->appendChild($fullName);

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

        // add email elements
        $email = $doc->createElement('gd:email');
        $email->setAttribute('address', $this->input->post('mail1', TRUE));
        $email->setAttribute('rel', 'http://schemas.google.com/g/2005#' . (String)$this->input->post('mail1_place', TRUE));
        $entry->appendChild($email);

        // add phone elements
        $phone = $doc->createElement('gd:phoneNumber', $this->input->post('phone1', TRUE));
        $phone->setAttribute('rel', 'http://schemas.google.com/g/2005#' . (String)$this->input->post('phone1_place', TRUE));
        $entry->appendChild($phone);

        // add web elements
        $web = $doc->createElement('gContact:website');
        $web->setAttribute('href', $this->input->post('web1', TRUE));
        $web->setAttribute('rel', $this->input->post('web1_place', TRUE));
        $entry ->appendChild($web);

        // insert entry
        $hoge = $doc->saveXML();
        //$fp = fopen("c:\sampleEntry.txt", "w");
        //fwrite($fp, $hoge);
        //fclose($fp);

        $entryResult = $gdata->insertEntry($hoge, 'http://www.google.com/m8/feeds/contacts/default/full');

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

    return $entryResult->id;
}

頭の部分のコメントアウトはデータ未入力チェックですがバリデーションでデータチェックは行なっているので必要ありません。入力データをxmlにしてgoogleに送るって登録する訳ですがかなり苦労しました。websiteのデータや記念日等についてはgContactというプレフィックスを付けないと登録できないなどドキュメントが英語だったので読んでいない弊害をまざまざと見せつけられちゃんと読まないとなぁと思ってはいますがどうなることやら。

<?xml version="1.0"?>
<atom:entry xmlns:atom="http://www.w3.org/2005/Atom" xmlns:gd="http://schemas.google.com/g/2005" xmlns:gContact="http://schemas.google.com/contact/2008">
  <gd:name>
    <gd:fullName>薬剤 三郎</gd:fullName>
  </gd:name>
  <gd:organization rel="http://schemas.google.com/g/2005#work">
    <gd:orgName>××株式会社</gd:orgName>
  </gd:organization>
  <gd:email address="fuga@gmail.com" rel="http://schemas.google.com/g/2005#work"/>
  <gd:phoneNumber rel="http://schemas.google.com/g/2005#work_fax">0300000000</gd:phoneNumber>
  <gContact:website href="http://www.fuga.com" rel="profile"/>
</atom:entry>

上記はmodelのpost()メソッドで作成したgoogleに投げるxmlファイルです。こんな形にして登録を行なっています。

またXSS対策として$this->input->post()メソッドつかってサニタイズしています。google contactsに登録できる項目はもっと沢山あるので徐々に登録方法を検証していきたいと思っています。また複数のメールアドレスや電話番号そしてwebサイトの登録機能を後で追加したいと思います。

さて大分機能が実装されてきましたがとりあえず次はデータ削除部分を実装する予定です。登録部分ももうちょっと後でモディファイしさらには修正も出来るようにテスト実装する予定です。しかしテストにいったい何日かかってるんだろう?(汗

0 件のコメント:

コメントを投稿