CodeIgniterの学習 7 - キャプチャ(CAPTCHA)を使えるようにする (実験)

(2009/10/02 add)
Captcha Test , Take 2 : Please look the end of this page.
(2009/10/02)


今日はCodeIgniterでキャプチャを使えるようにする。

サンプルアプリを作るのは、実際のコーディング量はたかが知れているので、
先に汎用性のあるプラグインを使いこなせるようにしておく。

キャプチャのプラグインは、system/plugins/captcha_pi.php にはじめから入っていた。
使い方もソースの頭に書いてあった。(マニュアルには無いみたい。)

素直にソースの指示通りに使えるようにしてみる。


以下作業メモ:

1)captchaテーブルを作成する。

CREATE TABLE captcha (
 captcha_id bigint(13) unsigned NOT NULL auto_increment,
 captcha_time int(10) unsigned NOT NULL,
 ip_address varchar(16) default '0' NOT NULL,
 word varchar(20) NOT NULL,
 PRIMARY KEY `captcha_id` (`captcha_id`),
 KEY `word` (`word`)
);

2)サンプルソースを書く。(生成側)

キャプチャの生成部分は、

<?php
 //(上略)
//プラグインの読み込み
$this->load->plugin('captcha');

//キャプチャ生成用引数のセット
$vals = array(
        'img_path'   => './captcha/',
        'img_url'  => 'http://example.com/captcha/'
      );

//キャプチャを生成
$cap = create_captcha($vals);

//キャプチャ確認用にレコードを挿入
$data = array(
        'captcha_id'  => '',
        'captcha_time'  => $cap['time'],
        'ip_address'  => $this->input->ip_address(),
        'word'      => $cap['word'],
      );
$query = $this->db->insert_string('captcha', $data);
$this->db->query($query);

 //(下略)
 ?>

でいけるようだ。(画像は $cap['image'] に img タグ付きで入ってくる。)
$valsの部分は、

<?php
 $vals = array(
  'word'     => 'Random word', #文字列
  'img_path'   => './captcha/',#画像の保存パス(ドキュメントルートから見たパス)
  'img_url'  => 'http://example.com/captcha/', #画像のURL
  'font_path'  => './system/fonts/texb.ttf',   #使用するフォント
  'img_width'  => '150', #幅
  'img_height' => 30,    #高さ
  'expiration' => 7200 , #有効期限
);
 ?>

のようにいろいろ設定できる。( captcha 画像 の保存先ディレクトリは事前に作成しておく。)

/usr/share/fonts/sazanami-fonts-gothic/sazanami-gothic.ttf を
system/fonts/
にコピーしてttfフォントを使えるようにした。

ついでにフォントサイズが $font_size= 16; となっていて小さすぎるので、
application/plugins/ ディレクトリを作り、captcha_pi.phpをこの中にコピーしてフォントサイズを変更した。
(これくらいなら直接変更してもいいけど、一応流儀に沿った。)


3)サンプルソースを書く。(確認側)
確認側の処理は、

<?php
 //(上略)
//古いキャプチャ確認用データをまず削除(ここでは2時間)
$expiration = time()-7200 ;
$sql = " DELETE FROM captcha WHERE captcha_time < ? ";
$binds = array($expiration);
$query = $this->db->query($sql, $binds);

//画面から入力された値が正しいかチェック
$sql = "SELECT COUNT(*) AS count FROM captcha WHERE word = ? AND ip_address = ? AND captcha_time > ?";
$binds = array($_POST['captcha'], $this->input->ip_address(), $expiration);
$query = $this->db->query($sql, $binds);
$row = $query->row();

if ($row->count == 0)
{
 //入力値が不正なときの処理
}

 //(下略)
 ?>

でいけるらしい。
ソース内のサンプルのままだと一部変数とカラム名に表記揺れがあり、コピペのままだと動かないので部分的に変更した。


実践してみる:

前回までの実験ソースに追記する。(セッション、xajax のテストソース入り)

だんだん読みにくくなってきたけど、
今回追加したのは _hoge4() _hoge5() hoge6() 、
変更したのは index() hoge1()

適当な実験ソースなので美しくないのは勘弁して。

コントローラー側:

application/controllers/tasklist.php

<?php  
if ( ! defined('BASEPATH')) exit('No direct script access allowed');
// タスクリストというかいろいろ実験
class Tasklist extends Controller {
// コンストラクタ
function Tasklist()
{
  parent :: Controller(); 
  // urlヘルパ
  $this -> load -> helper( 'url' ); 
  // xajaxを有効に
  $this -> load -> library( 'xajax' ); 
  // _hoge3をajax(xajax)のファンクション化する。
  // javascript側では xajax__hoge3 で呼び出せる
  $this -> xajax -> registerFunction( array( '_hoge3', &$this, '_hoge3' ) );
  $this -> xajax -> processRequest();
} 
// デフォルトインデックス
function index()
{ 
  // xajaxの準備
  foreach( $this -> _xajax_load() as $k => $v ) {
    $data[$k] = $v;
  } 
  // セッションの値確認テスト用
  $data["info"] = ( $this -> db_session -> userdata( 'info' ) ) ?
  $this -> db_session -> userdata( 'info' ) : "中に誰もいませんよ"; 
  // キャプチャタグを生成する
  $data["cap_img"] = $this -> _hoge4();
  $data["cap_msg"] = "";

  $this -> load -> view( 'tasklist_view', $data );
} 
// セッション格納テスト画面
function hoge1()
{ 
  // xajaxの準備
  foreach( $this -> _xajax_load() as $k => $v ) {
    $data[$k] = $v;
  } 
  // セッションに値を格納
  $this -> db_session -> set_userdata( 'info', 'なんか入ってきた:' . date( "H:i:s", time() ) ); 
  // セッションから取り出し
  $data["info"] = $this -> db_session -> userdata( 'info' ); 
  // キャプチャタグを生成する
  $data["cap_img"] = $this -> _hoge4();
  $data["cap_msg"] = "";

  $this -> load -> view( 'tasklist_view', $data );
} 
// セッション破棄テスト→リダイレクト
function hoge2()
{ 
  // xajaxの準備
  foreach( $this -> _xajax_load() as $k => $v ) {
    $data[$k] = $v;
  } 
  // セッションの破棄
  $this -> db_session -> sess_destroy();
  redirect( '/tasklist' );
} 
// xajaxのレスポンス用
function _hoge3( $number )
{ 
  // xajax用(ボタン押下時のイベント)
  $objResponse = new xajaxResponse(); 
  // SomeElementId とついたID内をinnerHTML書き換えで、入れ替える。
  $objResponse -> Assign( "SomeElementId", "innerHTML", "足し算結果:" . ( $number + 3 ) );
  return $objResponse;
} 
// キャプチャの生成用
function _hoge4()
{ 
  // キャプチャの生成
  // プラグインの読み込み
  $this -> load -> plugin( 'captcha' ); 
  // キャプチャ生成用引数のセット
  $vals = array( 'img_path' => './img/captcha/', // 画像の保存パス
    'img_url' => '/img/captcha/', // 画像のURL
    'font_path' => BASEPATH . 'fonts/sazanami-gothic.ttf', // 使用するフォント
    'img_width' => '250', // 幅
    'img_height' => 60, // 高さ
    ); 
  // キャプチャを生成
  $cap = create_captcha( $vals ); 
  // キャプチャ確認用にレコードを挿入
  if ( $cap ) {
    $data = array( 'captcha_id' => '',
      'captcha_time' => $cap['time'],
      'ip_address' => $this -> input -> ip_address(),
      'word' => $cap['word'] , 
      );
    $query = $this -> db -> insert_string( 'captcha', $data );
    $this -> db -> query( $query );
  } else {
    return "captchaが生成できないよ" ;
  } 
  return $cap['image'] ;
} 
// キャプチャの整合性チェック
function _hoge5()
{
  // 古いキャプチャ確認用データをまず削除(ここでは2時間)
  $expiration = time()-7200 ;
  $sql = " DELETE FROM captcha WHERE captcha_time < ? ";
  $binds = array( $expiration );
  $query = $this -> db -> query( $sql, $binds );
  // 画面から入力された値が正しいかチェック
  $sql = "SELECT COUNT(*) AS count FROM captcha WHERE word = ? AND ip_address = ? AND captcha_time > ?";
  $binds = array( $_POST['captcha'], $this -> input -> ip_address(), $expiration );
  $query = $this -> db -> query( $sql, $binds );
  $row = $query -> row();

  if ( $row -> count == 0 ) {
    return false;
  } 
  return true;
} 
// キャプチャの確認画面
function hoge6()
{ 
  // xajaxの準備
  foreach( $this -> _xajax_load() as $k => $v ) {
    $data[$k] = $v;
  } 
  // セッションの値確認テスト
  $data["info"] = ( $this -> db_session -> userdata( 'info' ) ) ?
  $this -> db_session -> userdata( 'info' ) : "中に誰もいませんよ"; 
  // キャプチャタグの整合性チェックと再生成
  $data["cap_img"] = $this -> _hoge4();
  $data["cap_msg"] = $this -> _hoge5() ? "正解!だけどもう一回" : "残念なのでもう一回";

  $this -> load -> view( 'tasklist_view', $data );
} 

function _xajax_load()
{ 
  // とりあえずまとめただけ
  $xajax_js = $this -> xajax -> getJavascript( base_url() ); 
  // _hoge3は、xajax_ をつけて xajax__hoge3(引数) で呼べる
  // 外部から直接呼ばれないようにファンクション名を
  // _hoge3としてアンダーバーをつけているので_が2つある
  $content = '<div id="SomeElementId">下のボタンを押してちょ、中身が書き変わるよ</div>';
  $content .= '<p/><input type="button" value="押してみる" onclick="xajax__hoge3(2);">';
  return array( "xajax_js" => $xajax_js, "content" => $content );
} 
} 
/**
* End of file tasklist.php
*/
/**
* Location: ./application/controllers/tasklist.php
*/
 ?>


ビュー側:

application/views/tasklist_view.php

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Tasklistへようこそ</title>
<?=$xajax_js?>
<style type="text/css">
body {
 background-color: #fff; margin: 40px;
 font-family: Lucida Grande, Verdana, Sans-serif;
 font-size: 14px; line-height:26px; color: #4F5155;}
a {
 color: #003399; background-color: transparent;
 font-weight: normal; text-decoration:underline;}
h1 {
 color: #444; background-color: transparent;
 border-bottom: 1px solid #D0D0D0;
 font-size: 16px; font-weight: bold;
 margin: 24px 0 2px 0; padding: 5px 0 6px 0;}
h2 {
 color: #666; background-color: transparent;
 border-bottom: 1px solid #ccc ; width:50%;
 font-size: 14px; font-weight: bold; margin-top:20px;}
#txt_red{
 color:#dc143c; font-weight: bold;}
#msg_red{
 color:#dc143c; font-weight: bold;
 background: #ffbbcc;
 border:1px solid #ffaacc; width:60%;
 padding:10px; font-size:1.4em;}
#SomeElementId{
 color:#ff0000; font-weight: bold;
 background: #b8e08f;
 border:1px solid #bfdb58; width:60%;
 padding:10px; font-size:1.4em;}
</style>
</head>
<body>
<h1>Tasklistへようこそ!</h1>
<h2>↓セッションの値確認↓</h2>
<p id="msg_red"><?= $info ?></p>
<ul>
<li><a href="/tasklist/hoge1">/tasklist/hoge1 に行ってセッションを格納してみる</a></li>
<li><a href="/tasklist/hoge2">/tasklist/hoge2 でセッションを破棄して/tasklistにリダイレクト</a></li>
</ul>

<h2>↓XAJAXのテスト↓</h2>
<?=$content?>

<h2>↓キャプチャのテスト↓</h2>
<form method="post" action="/tasklist/hoge6">
<?=$cap_img?>
<p>上に見える文字を素早く入力してください</p>
<p id="txt_red"><?=$cap_msg?></p>
<input type="text" name="captcha" value="" />
<input type="submit" name="submit" value="★キャプチャのテスト★">
</form>

</body>
</html>

動作結果

こんな感じ、

キャプチャを見て入力:

正解、今度は間違えて入力:

不正解:

まあまあかな。一応動いたし。
実装時はまともな条件分岐が必要だけど。

さて、次はログインか。うーんあんまりやりたくないなあ。他のを先にしようかな。





(2009/10/02 add)

Captcha Test , Take 2

Hi! I tried it again.
Simple Example like this .

(original CI 1.7.1 and 'fonts/sazanami-gothic.ttf' : custom font , and mkdir htdocs/captcha )
(Linux-PHP5-Mysql)


(url:http://example.com/example_captcha)


Auto translation may be a bit off (ex1. this to This , ex2. $aaa to $ Aaa).

1. Controller: application/controllers/example_captcha.php

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

class Example_captcha extends Controller {
  function Example_captcha()
  {
    parent :: Controller();
  }
  function index()
  {
    /**
     * --SQL DDL 
     * 
     * CREATE TABLE captcha (
     * captcha_id bigint(13) unsigned NOT NULL auto_increment,
     * captcha_time int(10) unsigned NOT NULL,
     * ip_address varchar(16) default '0' NOT NULL,
     * word varchar(20) NOT NULL,
     * PRIMARY KEY `captcha_id` (`captcha_id`),
     * KEY `word` (`word`)
     * );
     */

    $captcha_result = '';
    $data["cap_img"] = $this -> _make_captcha();
    if ( $this -> input -> post( 'submit' ) ) {
      if ( $this -> _check_capthca() ) {
        $captcha_result = 'GOOD';
      }else {
        $captcha_result = 'BAD';
      }
    }
    $data["cap_msg"] = $captcha_result;
    $this -> load -> view( 'testcaptcha_view', $data );
  }
  function _make_captcha()
  {
    $this -> load -> plugin( 'captcha' );
    $vals = array( 
      #'img_path' => './captcha/', // PATH for captcha ( *Must mkdir (htdocs)/captcha )
      #'img_url' => '/captcha/', // URL for captcha img
      'img_path' => dirname(FCPATH).'/captcha/', // PATH for captcha ( *Must mkdir (htdocs)/captcha ) 
      'img_url' => base_url().'captcha/', // URL for captcha img
      'img_width' => 280, // width
      'img_height' => 60, // height
      #'font_path' => BASEPATH . 'fonts/sazanami-gothic.ttf', // custom font ( not in Original CI )
      'expiration' => 7200 , 
      ); 
    // Create captcha
    $cap = create_captcha( $vals ); 
    // Write to DB
    if ( $cap ) {
      $data = array( 'captcha_id' => '',
        'captcha_time' => $cap['time'],
        'ip_address' => $this -> input -> ip_address(),
        'word' => $cap['word'] , 
        );
      $query = $this -> db -> insert_string( 'captcha', $data );
      $this -> db -> query( $query );
    }else {
      return "Umm captcha not work" ;
    }
    return $cap['image'] ;
  }

  function _check_capthca()
  { 
    var_dump( $_POST );//debug

    // Delete old data ( 2hours)
    $expiration = time()-7200 ;

    $this -> db -> where( 'captcha_time < ', $expiration );
    $this -> db -> delete( 'captcha' );

    var_dump( $this -> db -> last_query() ); 

    // Check input data
    $this -> db -> select( 'count(*) as count' );
    $this -> db -> where( 'word', $this -> input -> post( 'captcha' ) );
    $this -> db -> where( 'ip_address', $this -> input -> ip_address() );
    $this -> db -> where( 'captcha_time > ', $expiration );
    $query = $this -> db -> get( 'captcha' );
    $row = $query -> row();

    var_dump( $this -> db -> last_query() );//debug

    if ( $row -> count > 0 ) {
      return true;
    }
    return false;

  }

}//endofclass
/**
 * End of file example_captcha.php
 */
/**
 * Location: ./application/controllers/example_captcha.php
 */

?>


2 View: ./application/views/testcaptcha_view.php

<html>
<head></head>
<body>
<h2>Captcha Test</h2>
<form method="post">
<?php echo $cap_img ;?>
<p><?php echo $cap_msg ;?></p>
<input type="text" name="captcha" value="" />
<input type="submit" name="submit" value="Submit">
</form>
</body>
</html>

(2009/10/02 end)