Motomichi Works Blog

モトミチワークスブログです。その日学習したことについて書いている日記みたいなものです。

さくらvpsとcakephp2.6.7で開発日記 その0004 以前作成した会員登録フォームにcakephp-captchaを導入する

自分の過去記事

会員登録フォームの作成と、cakephp-captchaの使い方詳細は以下の過去記事にあるので、今回は端折って書く。
以前はSignupコントローラーだったのを、Signupsコントローラーにして作り直した。

さくらvpsとcakephp2.6.7で開発日記 その0002 会員登録フォームの作成をする - MOTOMICHI WORKS BLOG

さくらvpsとcakephp2.6.7で開発日記 その0003 cakephp-captchaを使ってみる - MOTOMICHI WORKS BLOG

usersテーブルを作成する

さくらvpsとcakephp2.6.7で開発日記 その0002 会員登録フォームの作成をする - MOTOMICHI WORKS BLOGで作成したusersテーブルを使う。

必要なファイルのリスト

  • Config/email.php
  • Controller/SignupsController.php
  • Controller/Component/CaptchaComponent.php
  • Model/Capt.php
  • Model/User.php
  • Model/Behavior/CaptchaBehavior.php
  • View/Emails/text/signup.ctp
  • View/Signups/activate.ctp
  • View/Signups/email_sent.ctp
  • View/Signups/index.ctp
  • Lib/Fonts/monofont.ttf
  • Lib/Fonts/Sanchez-Regular.ttf

Config/email.php

email.php.defaultを複製して、ファイル名だけ変更した。

Controller/SignupsController.phpを作成する

記述内容は以下の通り。

<?php
/**
    * (Sample) Controller for Showing the use of Captcha*
    * @author     Arvind Kumar (arvind.mailto@gmail.com)
    * @link       http://www.devarticles.in/
    * @copyright  Copyright © 2014 http://www.devarticles.in/
    * @version 2.5 Tested OK in Cakephp 2.5.4
    */

App::uses('AppController', 'Controller');
App::uses('CakeEmail', 'Network/Email');
App::uses('BlowfishPasswordHasher', 'Controller/Component/Auth');

class SignupsController extends AppController {
  // Controller名
  public $name = 'Signups';
  // 使用するコンポーネントとその設定
  public $components = array(
    'Captcha'=>array(
      'model'=>'Capt',
      'field'=>'security_code'
    )
  );
  // 使用するModel
  public $uses = array('User','Capt');
  // 使用するHelper
  public $helpers = array('Captcha');

  function captcha(){
    // 画像更新がクリックされたときの処理
    $this->autoRender = false;
    $this->layout='ajax';
    $this->Captcha->create();
  }

  function index(){

    // 送信データが無い場合の処理
    if(!$this->request->data){
      $this->render();
      return;
    }

    // 送信された値がUserモデルで使用できるようにset
    $this->User->set($this->request->data);

    // バリデーションを実行
    if(!$this->User->validates(array('fieldList' => array('email')))) {
      // バリデーションNGのときの処理
      $this->render();
      return;
    }

    // emailフォームに入力された値を$emailに格納
    $email = $this->request->data['User']['email'];

    // $emailと現在時刻を結合してハッシュ化
    $signup_code = md5($email.time());

    // 下記2つの条件に一致するレコードを1列取得して$userに格納する
    // - フィールド名emailの値が$email
    // - フィールド名is_registeredの値が0
    //
    // (仮登録されているデータが既にある場合は取得)
    $user = $this->User->find('first',array(
      'conditions' => array(
        'email' => $email,
        'is_registered' => 0
      )
    ));

    // 仮登録データの有無によって、処理を分ける
    if($user){
      // 仮登録データがDBに既にある場合の処理
      // UPDATEする事になるので、saveの第三引数に渡す配列を設定
      $fields = array('signup_code');
      // DBから取得した値のままだと更新されないので、modifiedをnullにする
      $user['User']['modified'] = null;
    }else{
      // 仮登録データがDBにまだ無い場合の処理
      // INSERTする事になるので、saveの第三引数に渡す配列はnullを設定
      $fields = null;
      // INSERT文実行をすることになるので、create()して、フィールドの初期値でModelを初期化
      // UPDATE文を実行するときはcreate()しては駄目なのでいつも注意する!
      $this->User->create();
      // フォームから送信された値を$userに挿入
      $user = array('User' => $this->request->data['User']);
    }

    // 仮登録状態ということをデータベースに登録するので0
    $user['User']['is_registered'] = 0;

    // signup_codeフィールドに入れる値を格納
    $user['User']['signup_code'] = $signup_code;

    // usersテーブルにUPDATEまたはINSERT(UPDATEの場合は$fieldsで指定したものだけを更新)
    if(!$this->User->save($user,false,$fields)){
      $this->Session->setFlash('システムエラーが発生しました。恐れ入りますが始めからやり直してください。');
      $this->render();
      return;
    }

    // "https://hogehoge.com/"みたいなベースURLを取得
    $full_base_url = Router::url( '/', true);
    // 本登録用のURLを生成して、$urlに格納
    $url = $full_base_url.'signups/activate/'.$signup_code;

    //emailの自動送信処理
    $cake_email = new CakeEmail('default');// Config/email.phpの$defaultの設定でインスタンスを生成
    $cake_email->from( array('mw.contacts@hoge.com' => 'Sender'));// 送信元を設定
    $cake_email->to($email);// 送信相手を設定
    $cake_email->subject('仮登録のお知らせ');// 件名を設定
    $cake_email->emailFormat('text');// HTML or テキストメール
    $cake_email->template('signup');// テンプレート
    // テンプレートへ渡す変数
    $cake_email->viewVars(array(
      'url'=>$url
    ));
    // メールを送信
    if($cake_email->send()){
      // メール送信成功時の処理
      // Signup/email_sent.ctpにリダイレクト
      $this->redirect('email_sent');
    }else{
      // メール送信失敗時の処理
      $this->Session->setFlash('システムエラーが発生しました。恐れ入りますが始めからやり直してください。');
      $this->render();
      return;
    }
  }// end of action index

  public function email_sent(){
  }// end of action email_sent

  public function activate($signup_code){

    // 下記2つの条件に一致するレコードを1行取得して$userに格納する
    // - フィールド名signup_codeの値が$signup_code
    // - フィールド名is_registeredの値が0
    $user = $this->User->find(
      'first',
      array(
        'conditions' => array(
          'signup_code' => $signup_code,
          'is_registered' => 0
        )
      )
    );

    // 上記の処理で、条件に一致する行がデータベースに無かった場合、仮登録が済んでいないのでリダイレクトする
    if(!$user){
      $this->Session->setFlash('URLが無効です。恐れ入りますがメール送信からやり直してください。');
      $this->redirect('index');
    }

    // 仮登録情報更新日時のtimestampを取得
    $modified_time = strtotime($user['User']['modified']);
    // 現在日時のtimestampを取得
    $time = time();
    // 仮登録から86,400秒よりも経過しているかを判定
    if(86400 < $time - $modified_time){
      $this->Session->setFlash('URLの有効期限が切れています。恐れ入りますがメール送信からやり直してください。');
      $this->redirect('index');
    }

    // 送信データが無い場合、Signup/activate.ctp(本登録ページ)を表示する
    if(!$this->request->data){
      $this->render();
      return;
    }

    // 送信された値がUserモデルで使用できるようにset
    $this->User->set($this->request->data);
    // 送信された値がCaptモデルで使用できるようにset
    $this->Capt->set($this->request->data);
    // setCaptchaで画像に表示されている文字列をsetする
    $this->Capt->setCaptcha(
      'security_code',
      $this->Captcha->getCode('Capt.security_code')
    );

    // バリデーションを実行
    $result__user = $this->User->validates(array('fieldList' => array('name','password')));
    $result__capt = $this->Capt->validates();
    if(!$result__user && !$result__capt){
      // バリデーションNGのときの処理
      $this->Session->setFlash('UserとCaptのバリデーションはfalseです。');
      $this->render();
      return;
    }else if(!$result__user) {
      // バリデーションNGのときの処理
      $this->Session->setFlash('Userのバリデーションはfalseです。');
      $this->render();
      return;
    }else if(!$result__capt){
      // バリデーションNGのときの処理
      $this->Session->setFlash('Captのバリデーションはfalseです。');
      $this->render();
      return;
    }

    // フォームから送信されたパスワードをハッシュ化
    $passwordHasher = new BlowfishPasswordHasher();
    $user['User']['password'] = $passwordHasher->hash($this->request->data['User']['password']);

    // $user['User']['password_confirm']はもうModel内で使用しないので、unset
    unset($user['User']['password_confirm']);

    // 本登録完了のステータスを保存するので1
    $user['User']['is_registered'] = 1;

    // 送信されたニックネームを保存するので、$user['User']['name']の値を上書き
    $user['User']['name'] = $this->request->data['User']['name'];

    // usersテーブルをUPDATE
    if(!$this->User->save($user,false)){
      //save失敗した場合の処理
      $this->Session->setFlash('システムエラーが発生しました。もう一度送信をお願いします。');
      $this->render();
      return;
    }

    // save成功したら、ログインページにリダイレクト
    $this->redirect('/users/login');

  }// end of action activate

}

Controller/Component/CaptchaComponent.phpの配置と編集

ダウンロードしてきたzipファイルから、開発環境に配置して

private $__fonts = array('monofont', 'Sanchez-Regular');

のような感じでフォントを指定を変更した。

Model/Capt.phpを作成する

記述内容は以下の通り。

<?php
/**
    * (Sample) Model for Showing the use of Captcha*
    * @author     Arvind K. 
    * @link       http://www.devarticles.in/
    * @copyright  Copyright © 2008 www.devarticles.in
    * @version Tested ok in Cakephp 2.x
    */
class Capt extends AppModel {
    var $useTable = false; //i dont have a table right now, just testing captcha
    public $actsAs = array(
        'Captcha' => array(
            'field' => array('security_code'),
            'error' => '文字が一致しませんでした。'
        )
    );
}

Model/User.phpの作成

記述内容は以下の通り。

<?php
App::uses('AppModel', 'Model');

class User extends AppModel {
  public $name = 'User';

  /**
   * 
   * 
   * @return boolean
   */
  public function isUniqueAndActive($valid_field){
    // field名を取得(例えばemail)
    $fieldname = key($valid_field);
    // 下記二つの条件に一致する行をjoinしない設定で取得
    //   - $fieldnameフィールドが、フォームから送信された値である
    //   - is_registeredフィールドが、1である
    $user = $this->find(
      'first',
      array(
        'conditions' => array(
          $fieldname => $this->data[$this->name][$fieldname],
          'is_registered' => 1
        ),
        'recursive' => -1
      )
    );
    if($user){
      // conditionsに該当する行が、既に存在する場合は、false
      return false;
    }

    return true;

  }

  /**
   *
   * @return boolean
   */
  function checkCompare($valid_field , $suffix){
    $fieldname = key($valid_field);
    if($this->data[$this->name][$fieldname] === $this->data[$this->name][$fieldname.$suffix]){
      return true;
    }
    return false;
  }

  /*
   * バリデーション対象となるキーと、そのルールを設定
   */
  public $validate = array(
    'email' => array(
      array(
        'rule' => 'notEmpty',
        'message' => 'メールアドレスを入力してください。'
      ),
      array(
        'rule' => array('custom', '/^.+@.+$/'),
        'message' => '形式が正しくありません。'
      ),
      array(
        'rule' => array('checkCompare','_confirm'),
        'message' => 'メールアドレスが一致していません。'
      ),
      array(
        'rule' => 'isUniqueAndActive',
        'message' => 'すでに使用されています。'
      )
    ),
    'name' => array(
      array(
        'rule' => 'notEmpty',
        'message' => '必須項目です。'
      )
    ),
    'password' => array(
      array(
        'rule' => 'notEmpty',
        'message' => 'パスワードを入力してください。'
      ),
      array(
        'rule' => array('minLength', '8'),
        'message' => '8文字以上入力してください。'
      ),
      array(
        'rule' => array('custom', '/^[a-zA-Z0-9]+$/'),
        'message' => '半角英数字で入力してください。'
      ),
      array(
        'rule' => array('checkCompare','_confirm'),
        'message' => 'パスワードが一致していません。'
      )
    )
  );

}

Model/Behavior/CaptchaBehavior.phpを配置する

ダウンロードしてきたzipファイルから、開発環境に配置した。

View/Emails/text/signup.ctp

記述内容は以下の通り

このメールは自動送信しております。

このメールアドレスにお問い合わせをいただくことはできません。

24時間以内に下記のURLから本登録をしてください。

<?php echo ($url); ?>

また、このメールにお心当たりの無い場合は、このメールを破棄してください。

View/Signups/activate.ctpの作成

記述内容は以下の通り。

<h1>会員登録</h1>
<?php
echo($this->Form->create());
?>

<div>
<?php
echo $this->Form->label('Capt.security_code', 'セキュリティコード: ');
echo $this->Captcha->render();
echo $this->Form->error('Capt.security_code');
?>
</div>

<div>
<?php
echo $this->Form->label('User.name', 'ニックネーム: ');
echo $this->Form->text(
  'User.name',
  array(
    'errorMessage' => false,
    'div' => false,
    'required' => false
  )
);
echo $this->Form->error('User.name');
?>
</div>

<div>
<?php
echo $this->Form->label('User.password', 'パスワード: ');
echo $this->Form->password(
  'User.password',
  array(
    'errorMessage' => false,
    'div' => false,
    'required' => false
  )
);
echo $this->Form->error('User.password');
?>
</div>

<div>
<?php
echo $this->Form->label('User.password_confirm', 'パスワード(確認): ');
echo $this->Form->password(
  'User.password_confirm',
  array(
    'errorMessage' => false,
    'div' => false,
    'required' => false
  )
);
?>
</div>

<?php
echo($this->Form->end('送信'));
?>

View/Signups/email_sent.ctpの作成

記述内容は以下の通り。

<h1>会員登録</h1>

<div>
  ご入力いただいたメールアドレスに、メールを送信しました。<br>
  メール本文に記載されているURLから本登録を完了してください。
</div>

View/Signups/index.ctpの作成

記述内容は以下の通り。

<h1>会員登録</h1>
<?php
echo($this->Form->create());
?>

<div>
<?php
echo $this->Form->label('User.email', 'メールアドレス: ');
echo $this->Form->text(
  'User.email',
  array(
    'errorMessage' => false,
    'div' => false,
    'required' => false
  )
);
echo $this->Form->error('User.email');
?>
</div>

<div>
<?php
echo $this->Form->label('Usr.email_confirm', 'メールアドレス(確認): ') ;
echo $this->Form->text(
  'User.email_confirm',
  array(
    'errorMessage' => false,
    'div' => false,
    'required' => false
  )
);
?>
</div>

<?php
echo($this->Form->end('送信'));
?>

フォントを配置する

下記二つのファイルをダウンロードして配置した。

  • Lib/Fonts/monofont.ttf
  • Lib/Fonts/Sanchez-Regular.ttf

まとめ

以前作成した会員登録フォームと、Captchaの導入テスト的なものを結合した。

まとめてみると、必要なファイルは結構たくさんある。

なんか足りないかもだけど、足りなかったら、そのうち気付くかも。

それぞれ作成したときの記事は以下の二つ。

さくらvpsとcakephp2.6.7で開発日記 その0002 会員登録フォームの作成をする - MOTOMICHI WORKS BLOG

さくらvpsとcakephp2.6.7で開発日記 その0003 cakephp-captchaを使ってみる - MOTOMICHI WORKS BLOG

今回はここまで。