開発メモ,主に補足by子連れ親父プログラマー

2011-12-26

OSX の MacPorts で入れた mysql-connector-java は一体どうやって使えばいいのか?

OSX の MacPorts で入れた mysql-connector-java は一体どうやって使えばいいのか?

引き続き java の servlet でのデータベース接続をテストしていた時のことである。
tomcat も MacPorts で入れたんだから Connector/J も当然 MacPortsで 入れるべきだろう、ということで、

sudo port install mysql-connector-java
こうした訳ですが、これはどうやら以下の場所に設置されるようです。

/opt/local/share/java/mysql-connector-java-5.0.jar

で、こいつをどう使ったらいいのか?
まずはコンパイルする時のために自分の.profile にパスを追加。
エディターで、

vim ~/.profile  
として、以下を追加。

export CLASSPATH=/opt/local/share/java/tomcat6/lib/servlet-api.jar
export CLASSPATH=/opt/local/share/java/mysql-connector-java-5.0.jar:$CLASSPATH
export CLASSPATH=$CLASSPATH:.

パスが通ったかどうか、

echo $CLASSPATH
して確認。

これでコンパイルは通る。

で、tomcat上で、作ったservletを実行するためには、

/opt/local/share/java/mysql-connector-java-5.0.jar
このファイルを、
/opt/local/share/java/tomcat6/webapps/sample/WEB-INF/lib
以下に置く(sampleは自分で作ったservlet置き場)。

もしくは、

/opt/local/share/java/tomcat6/lib/
以下に置く。

シンボリックリンクでもいけるので、

cd /opt/local/share/java/tomcat6/lib
sudo ln -s /opt/local/share/java/mysql-connector-java-5.0.jar mysql-connector-java-5.0.jar
こうしておけばよいかもしれない。

2011-11-20

OSX Lion に Tomcat6 を入れて Java のサーブレットで HelloWorld するまでのすったもんだ

OSX Lion に Tomcat6 を入れて Java のサーブレットで HelloWorld するまでのすったもんだ

そういえばLionにしてから自宅の開発環境がほったらかしになってたな、と、嫌な予感はしていたんだが、いざ始めてみると大仕事に。 Java の SDK は入ってるはずだから Tomcat だけ入れればいいはず、と思って、

sudo port install tomcat6

と打った最初っからつまづいた。 まず MacPorts そのものがバージョン2.0.3にアップしていてこの selfupdate ができない。 Xcode を4.1にバージョンアップしろ、と言われる。 でApp Store で Xcode を検索してインストール。 App Store の画面でインストール済みになって、それからどうすればいいのか分からず、途中作業中断もはさんで大幅に時間ロス。 ふと見ると Application の中に Install Xcode というアイコンができているのでそれを起動で無事完了。 MacPorts のバージョンアップをして、引き続き Tomcat を入れる。

http://localhost:8080

を開いてみると得体の知れない EnterpriseDB とかいう画面が開くだけなので、(←なんすかね、これ)

sudo vim /opt/local/share/java/tomcat6/conf/server.xml

として、8080になってるところを8090に変更。 さらに、

sudo vim /opt/local/share/java/tomcat6/conf/tomcat-users.xml

として、Tomcat のアプリケーションマネージャ画面に入るためのIDとパスワードを設定する。

<tomcat-users>
    <role rolename="manager-gui"/>
    <user username="myname" password="mypassword" roles="manager-gui"/>
</tomcat-users>

HelloWorld 用のjava ファイルを用意して、

javac HelloWorld.java 

とするも文字化け。

vim ~/.profile

として、末尾に

alias javac="javac -J-Dfile.encoding=UTF8"
alias java="java -Dfile.encoding=UTF8"

を追加。文字化けは収まったが、コンパイルに失敗するので、 さらに .profile に

export CLASSPATH=/opt/local/share/java/tomcat6/lib/servlet-api.jar

を追加。

javac HelloWorld.java 

とやって、HelloWorld.class を作成する。

/opt/local/share/java/tomcat6/webapps

以下に、

/opt/local/share/java/tomcat6/webapps/sample

というフォルダーを作り、さらにその中に、

/opt/local/share/java/tomcat6/webapps/sample/WEB-INF
/opt/local/share/java/tomcat6/webapps/sample/WEB-INF/classes
/opt/local/share/java/tomcat6/webapps/sample/WEB-INF/src
/opt/local/share/java/tomcat6/webapps/sample/WEB-INF/lib

とフォルダーを作って、 classes の中に HelloWorld.class を、src の中に HelloWorld.java を、 WEB-INF 直下に web.xml を置いて、

http://localhost:8090/sample/Hello

で完成。

2011-11-13

JavaScript の継承とカプセル化

JavaScript の継承とカプセル化

参考文献:Supercharged JavaScript Graphics: with HTML5 canvas, jQuery, and More より。

いろんなやり方があるが、これが一番自然な方法なんじゃないかと筆者は言っている。

<script type="text/javascript">
var pet = function(name, legs) {
    var that = {
        name: name,
        getDetails: function(){
            return that.name + ' has ' + legs + ' legs';
        }
    };
    return that;
};
var cat = function(name) {
    var that = pet(name, 4);
    that.action = function(){
        return 'free as a bird';
    };
    return that;
};
</script>
var neko = cat('tama');

こうして neko を作った後で、neko の name を変えることはできるが、legs は変えられない、という訳である。

2011-11-06

CakePHP1.3で作る会員管理システム(26) バッチ処理

バッチ処理

では最後、バッチを使ってデータを更新したり、削除したりします。 バッチの処理は /app/venders/shells/ の中にファイルを作ります。今回はmembersテーブルのデータを処理するので、 /app/venders/shells/member.php とします。 で、中に以下のように書きます。

<?php
class MemberShell extends Shell {
    var $uses = array('Member');
    function main() {

        //メールアドレスにgを含むデータを削除、関連テーブルからも
        $this->Member->deleteAll(
            array('Member.email LIKE' => "%g%"), true, false
        );
    }
}
?>

$uses で使うモデルを指定しています。 削除ではなく、更新をする場合は、

$this->Member->updateAll(
    array('Member.birthday' => "'1970-03-15'"),
    array('Member.created <=' => "$this_day")
);

こんな感じになります。 このファイルを用意しておいて、コンソールから、

cd /Users/myname/Sites/cake/app
cake member

と実行します。app に入っていることが大事です。 crontab から実行する時はフルパスで書けばよいでしょう。

/Users/myname/Sites/cake/cake/console/cake member -app /Users/myname/Sites/cake/app

以上で今回作ったシステムのまとめは終わりです。

[おわり]

CakePHP1.3で作る会員管理システム(25) 検索結果CSVダウンロード

検索結果CSVダウンロード

検索結果をCSVでダウンロードする処理を追加します。 検索の処理部分は全く同じで言い訳ですから、同じadmin_serchアクションに、またモードを指定して、ダウンロードモードの時はCSVにするようにします。 まず、レイアウトとビューを用意しておきます。 /app/views/layouts/csv.ctp を新規作成して、

<?php
    header('content-type: text/plain');
    header("Content-Type: application/octet-stream");
    header("Content-Disposition: attachment; filename=member.csv");
?>
<?php echo $content_for_layout; ?>

とします。 ビューは、 /app/views/members/admin_download.ctpとして、

<?php foreach ($members as $member): ?>
<?php 
    echo $member['Member']['id'];
    echo $member['Member']['email'];
    echo $member['Member']['password'];
    echo $member['Type']['name'];
    echo $this->Time->format($format = 'Y/m/d', $member['Member']['birthday']);
    echo $this->Time->format($format = 'Y/m/d', $member['Member']['created']); 
?> 
<?php endforeach; ?>

このように書いておきます。 で、会員検索のビュー、admin_search.ctp に以下を追加しておきます。

<p><?php echo $this->Paginator->link(__('CSVダウンロード', true), array('mode'=>'DL')); ?></p>

こうするとリンクに引数を渡せます。 で、コントローラ members_controller.php のadmin_searchアクションの一番下に、

        if ((isset($this->passedArgs['mode'])) && ($this->passedArgs['mode'] == 'DL')) {
            Configure::write('debug', 0);
            $this->layout = 'csv';
            $data = $this->Member->find('all',  array(
                'order'=>array('Member.id'),
                'conditions' => $conditions
            ));
            $this->set("members", $data);
            $this->render('admin_download');
        }

を追加します。 mode=”DL” で来た時は $conditions だけ引き継いで検索し直してる感じですかね。

CakePHP1.3で作る会員管理システム(24) 検索とPaginationとHABTM

検索とPaginationとHABTM

で、さらにやっかいなのが、ここに「好きな物」のチェックボックスの複数選択検索を入れる場合です。 HABTMの関連テーブルですね。 一体どうすりゃいいんでしょうか。ということで相当悩みました。 結論としては、一旦その「好きな物」を選択している会員のIDを全部取り出して、そいつを条件に入れちゃえばいいんじゃなかろうかと。 クエリーのイメージはこんな感じ。

SELECT m.id FROM members m WHERE m.id IN
(SELECT member_id FROM members_favorites mf WHERE mf.favorite_id IN (1,2));

つまり()内のSELECTの部分をあらかじめデータ取得しておけばいいと。 ということでこうなった。

            $fid = $this->Member->MembersFavorite->find('list', array(
                'order'=>array('MembersFavorite.member_id DESC'),
                'group'=>array('MembersFavorite.member_id'),
                'fields' => array('MembersFavorite.member_id'),
                'conditions' => array('MembersFavorite.favorite_id' => $favorite_id)
            ));
            $conditions = array("Member.id" => $fid);
            $data = $this->paginate('Member', $conditions);

これは上記ではIN ()の中はSELECT文になっているが、CakePHPの文法だとそれは入らないようなので、実際に配列が入る。こんな感じになる。

SELECT `MembersFavorite`.`id`, `MembersFavorite`.`member_id` 
FROM `members_favorites` AS `MembersFavorite` 
WHERE `MembersFavorite`.`favorite_id` IN (1, 2, 3) 
GROUP BY `MembersFavorite`.`member_id` 
ORDER BY `MembersFavorite`.`member_id` DESC 

最終的にこうなりました。

    function admin_search() {
        $this->Member->recursive = 0;

        if (!empty($this->data)) {
            $f = $this->data['Member']['from'];
            $t = $this->data['Member']['to'];
            $email = $this->data['Member']['email'];
            $type_id = $this->data['Member']['type_id'];
            $favorite_id = $this->data['Member']['favorites'];
        }else{
            foreach ($this->passedArgs as $k => $v){
                if ($k == 'from'){
                    list($f['year'], $f['month'], $f['day']) = preg_split("/-/", $v);
                }elseif($k == 'to'){
                    list($t['year'], $t['month'], $t['day']) = preg_split("/-/", $v);
                }elseif($k == 'email'){
                    $email = urldecode($v);
                }elseif($k == 'type_id'){
                    $type_id = urldecode($v);
                } elseif(preg_match("/^favorite_id_([0-9]+)$/", $k, $regs)) {
                    $favorite_id[$regs[1]] = $v;
                }
            }
        }
     
        if (isset($f) && isset($t)){
            if ($this->_from_to_check($f, $t)) {
                $from = $f['year']."-".$f['month']."-".$f['day'];
                $to = $t['year']."-".$t['month']."-".$t['day'];
                $this->data['Member']['from'] = $f;
                $this->data['Member']['to'] = $t;
            }
        }
        if(isset($email)){
            $this->data['Member']['email'] = $email;
        }
        if(isset($type_id)){
            $this->data['Member']['type_id'] = $type_id;
        }
        if (isset($favorite_id)){
            $this->data['Member']['favorites'] = $favorite_id;
        }
        
     
        $searchword = array();
        $conditions = array();
        if (isset($from) && isset($to)){
            $searchword = array(
                "from" => urlencode($from),
                "to" => urlencode($to),
            );
            $conditions = array("Member.created BETWEEN ? AND ?" => array($from,$to));
        }
        if (isset($email) && $email){
            $searchword = $searchword + array(
                "email" => urlencode("$email"),
            );
            $conditions = $conditions + array("Member.email LIKE" => "%$email%");
        }
        if (isset($type_id) && $type_id){
            $searchword = $searchword + array(
                "type_id" => urlencode("$type_id"),
            );
            $conditions = $conditions + array("Member.type_id" => $type_id);
        }
        if (isset($favorite_id) && $favorite_id){
            foreach ($favorite_id as $k => $v){
                $temp_favotite['favorite_id_'.$k] = $v;
            }
            $searchword = $searchword + $temp_favotite;
            $fid = $this->Member->MembersFavorite->find('list', array(
                'order'=>array('MembersFavorite.member_id DESC'),
                'group'=>array('MembersFavorite.member_id'),
                'fields' => array('MembersFavorite.member_id'),
                'conditions' => array('MembersFavorite.favorite_id' => $favorite_id)
            ));
            $conditions = $conditions + array("Member.id" => $fid);
        }
     
        $data = $this->paginate('Member', $conditions);
        $this->set('searchword', $searchword);
        $this->set("members", $data);
         
        $favorites = $this->Member->Favorite->find('list');
        $types = $this->Member->Type->find('list');
        $this->set(compact('favorites','types'));
    }

2011-10-28

CakePHP1.3で作る会員管理システム(23) 検索とPagination

検索とPagination

で、検索しつつ Pagination する部分ですが、ちょっとややこしくて、一気に全部書くと訳が分からないので、仮にメールアドレスだけで検索する場合を最初に考えます。
普通に「検索」ボタンで検索した場合は

$this->data['Member']['email']

に値が渡ってきます。
一方 paginate で次へ、とかページ数をクリックした場合は、

$this->passedArgs['email']

に値が渡ってきます。
これをふまえて考えると、
まず、ボタンかリンクかで判別して渡ってきた値を変数に入れます。

if (!empty($this->data)) {
    $email = $this->data['Member']['email'];
} else {
    $email = urldecode($this->passedArgs['email']);
}

ボタンできた場合はいいですが、リンクで来た場合は$this->data['Member']に値が入っていないので、入れ直します。

if (isset($email)) {
    $this->data['Member']['email'] = $email;
}

前回ビューで作った、peginator に検索キーワードを渡すためのオプションに値を設定します。

$searchword = array(
    "email" => urlencode("$email"),
);

検索のクエリーの条件をセットします。

$conditions = array("Member.email LIKE" => "%$email%");

条件を使って検索してビューに渡します。

$data = $this->paginate('Member', $conditions);
$this->set('searchword', $searchword);
$this->set("members", $data);

基本的にはこの流れでいきます。
やっかいなのは日付データです。年月日を分割したり、結合したり、しないといけません。
ボタンから来た時は年月日の配列になってるので、そのまま入れて、リンクから来た時は文字列になってるのでsplitしたものを配列に入れます。

if (!empty($this->data)) {
    $f = $this->data['Member']['from'];
    $t = $this->data['Member']['to'];
} else {
    list($f['year'], $f['month'], $f['day']) = preg_split("/-/", $this->passedArgs['from']);
    list($t['year'], $t['month'], $t['day']) = preg_split("/-/", $this->passedArgs['to']);
}

同じように $this->data に戻しておきます。

$this->data['Member']['from'] = $f;
$this->data['Member']['to'] = $t;

conditions や searchword 用には結合したものを使います。

$from = $f['year']."-".$f['month']."-".$f['day'];
$to = $t['year']."-".$t['month']."-".$t['day'];

こんな感じになります。

$searchword = array(
    "from" => urlencode("$from"),
    "to" => urlencode("$to"),
);
$conditions = array("Member.created BETWEEN ? AND ?" => array($from,$to));

で、存在しない日付とかを入れると思いっきりエラーになるので、日付データのチェックを別途 function を作って入れておきます。
こんな感じで。

    function _from_to_check($f, $t) {
        if (!preg_match("/^[0-9]{4}$/", $f['year'])) {return false;}
        if (!preg_match("/^[0-9]{2}$/", $f['month'])) {return false;}
        if (!preg_match("/^[0-9]{2}$/", $f['day'])) {return false;}
        if (!preg_match("/^[0-9]{4}$/", $t['year'])) {return false;}
        if (!preg_match("/^[0-9]{2}$/", $t['month'])) {return false;}
        if (!preg_match("/^[0-9]{2}$/", $t['day'])) {return false;}
        if (!checkdate($f['month'], $f['day'], $f['year'])) {return false;}
        if (!checkdate($t['month'], $t['day'], $t['year'])) {return false;}
         
        return true;
    }

ここまでを整理するとこうなります。

    function admin_search() {
        $this->Member->recursive = 0;

        if (!empty($this->data)) {
            $f = $this->data['Member']['from'];
            $t = $this->data['Member']['to'];
            $email = $this->data['Member']['email'];
            $type_id = $this->data['Member']['type_id'];
        }else{
            foreach ($this->passedArgs as $k => $v){
                if ($k == 'from'){
                    list($f['year'], $f['month'], $f['day']) = preg_split("/-/", $v);
                }elseif($k == 'to'){
                    list($t['year'], $t['month'], $t['day']) = preg_split("/-/", $v);
                }elseif($k == 'email'){
                    $email = urldecode($v);
                }elseif($k == 'type_id'){
                    $type_id = urldecode($v);
                }
            }
        }
     
        if (isset($f) && isset($t)){
            if ($this->_from_to_check($f, $t)) {
                $from = $f['year']."-".$f['month']."-".$f['day'];
                $to = $t['year']."-".$t['month']."-".$t['day'];
                $this->data['Member']['from'] = $f;
                $this->data['Member']['to'] = $t;
            }
        }
        if(isset($email)){
            $this->data['Member']['email'] = $email;
        }
        if(isset($type_id)){
            $this->data['Member']['type_id'] = $type_id;
        }
     
        $searchword = array();
        $conditions = array();
        if (isset($from) && isset($to)){
            $searchword = array(
                "from" => urlencode($from),
                "to" => urlencode($to),
            );
            $conditions = array("Member.created BETWEEN ? AND ?" => array($from,$to));
        }
        if (isset($email) && $email){
            $searchword = $searchword + array(
                "email" => urlencode("$email"),
            );
            $conditions = $conditions + array("Member.email LIKE" => "%$email%");
        }
        if (isset($type_id) && $type_id){
            $searchword = $searchword + array(
                "type_id" => urlencode("$type_id"),
            );
            $conditions = $conditions + array("Member.type_id" => "$type_id");
        }
     
        $data = $this->paginate('Member', $conditions);
        $this->set('searchword', $searchword);
        $this->set("members", $data);
         
        $favorites = $this->Member->Favorite->find('list');
        $types = $this->Member->Type->find('list');
        $this->set(compact('favorites','types'));
    }

CakePHP1.3で作る会員管理システム(22) 会員一覧と会員検索

会員一覧と会員検索

会員管理を作ります。
最初に、いまのままだと見づらいので、一覧(indexアクション)の整形をしておきます。
admin_index.ctp を開いて、不要な列を削除します。
今は、

    <table cellpadding="0" cellspacing="0">
    <tr>
            <th><?php echo $this->Paginator->sort('id');?></th>
            <th><?php echo $this->Paginator->sort('email');?></th>
            <th><?php echo $this->Paginator->sort('password');?></th>
            <th><?php echo $this->Paginator->sort('type_id');?></th>
            <th><?php echo $this->Paginator->sort('birthday');?></th>
            <th><?php echo $this->Paginator->sort('img1');?></th>
            <th><?php echo $this->Paginator->sort('img2');?></th>
            <th><?php echo $this->Paginator->sort('created');?></th>
            <th><?php echo $this->Paginator->sort('modified');?></th>
            <th class="actions"><?php __('Actions');?></th>
    </tr>

こうなっていますが、password img1 img2 modified あたりはなくてもよいと思います。
(クライアント次第ですが)
対応する td の方も削除しておきます。

と、一見して、登録日(created)がDBの日付データそのままで出力されていて見づらいです。
ということで、 members_controller.php の helper にさらに ‘Time’ を追加します。

class MembersController extends AppController {

    var $name = 'Members';
    var $helpers = array('Javascript', 'Time');

で、ビューに戻り、誕生日と登録日は、
こうなっているのを

        <td><?php echo $member['Member']['birthday']; ?> </td>
        <td><?php echo $member['Member']['created']; ?> </td>

こうします。

        <td><?php echo $this->Time->format($format='Y/m/d', $member['Member']['birthday']); ?> </td>
        <td><?php echo $this->Time->format($format='Y/m/d', $member['Member']['created']); ?> </td>

ついでに、

<div class="actions">
...
</div>

の中のアクションリストは不要なので丸ごと削除しておきます。
最後に paginate の設定を入れておきます。
members_controller.php の上の方に、

    var $paginate = array(
        'limit' => 15,
        'order' => array(
            'Member.id' => 'desc'
        ),
    );

を入れます。15件で改ページ、デフォルトの並び順を会員のID降順にします。
引き続き、会員検索のページの準備をします。
一覧の admin_index.ctp にそのまま検索フォームをつけてしまうというのもありかとは思いますが、とりあえず今回は別ビュー、別アクションにします。
admin_index.ctp を別の名前で保存で admin_serch.ctp を作ります。
で、フォーム開始を

    <?php echo $this->Form->create(array("action" => "search", "type" => "post")); ?>

として、

    <table>
        <tr>
            <th>入会日</th>
            <td>
                <?php echo $this->Form->year("from", '2011','2012'); ?>年
                <?php echo $this->Form->month("from",null,array('monthNames'=>false)); ?>月
                <?php echo $this->Form->day("from",null); ?>日
                ~
                <?php echo $this->Form->year("to", '2011','2012'); ?>年
                <?php echo $this->Form->month("to",null,array('monthNames'=>false)); ?>月
                <?php echo $this->Form->day("to",null); ?>日
            </td>
        </tr>
        <tr>
            <th>メールアドレス</th>
            <td>
            <?php echo $this->Form->text("email"); ?>
            </td>
        </tr>
        <tr>
            <th>種別</th>
            <td>
            <?php echo $this->Form->select("type_id", $types); ?>
            </td>
        </tr>
        <tr>
            <th>好きな物</th>
            <td>
            <?php echo $this->Form->input('favorites', array('multiple'=>'checkbox', 'label' => false)); ?>
            </td>
        </tr>
    </table>
     
    <?php echo $this->Form->end('        検索        '); ?>

このような検索フォーム部分を追加します。
さらにその下に

    <?php $this->Paginator->options(array('url' => $searchword  )); ?>

peginator のオプションを入れておきます。
これは peginator に検索キーワードを渡すためのものです。
とりあえず、members_controller.php に以下のアクションと追加しておきます。

    function admin_search() {
        $this->Member->recursive = 0;
        $this->set('members', $this->paginate());
        $types = $this->Member->Type->find('list');
        $favorites = $this->Member->Favorite->find('list');
        $this->set(compact('types', 'favorites'));
    }

2011-10-27

CakePHP1.3で作る会員管理システム(21) アカウント管理

アカウント管理

次に、アカウント管理を完成させます。
users_controller.php を開きます。

アカウント管理画面のtitleタグに各ページのタイトルを入れるので、各アクションの冒頭に、

        $this->set('title_for_layout', 'アカウント一覧');
というように、タイトルを設定します。上記は admin_index の場合の例。

次に一覧のビューを調整します。admin_index.ctp を開きます。
以下の paginator が全部出しでだるいので、

    <p>
    <?php
    echo $this->Paginator->counter(array(
    'format' => __('Page %page% of %pages%, showing %current% records out of %count% total, starting on record %start%, ending on %end%', true)
    ));
    ?>    </p>

以下のようにすっきりさせます(笑)。

    <p>
    <?php
    echo $this->Paginator->counter(array(
    'format' => __('全 %count% 件', true)
    ));
    ?>    </p>

パスワードの表示はいらないので

            <th><?php echo $this->Paginator->sort('password');?></th>

と、

        <td><?php echo $user['User']['password']; ?> </td>

を削除します。
最初に登録した管理者を削除されてしまうとどうにもならないので

            <?php echo $this->Html->link(__('Delete', true), array('action' => 'delete', $user['User']['id']), null, sprintf(__('Are you sure you want to delete # %s?', true), $user['User']['id'])); ?>

こうなっているところに、if ($user['User']['id'] != 1){} をつけておきます。

            <?php 
                if ($user['User']['id'] != 1) {
                    echo $this->Html->link(__('Delete', true), array('action' => 'delete', $user['User']['id']), null, sprintf(__('Are you sure you want to delete # %s?', true), $user['User']['id']));
                }
            ?>

気休めでしかないですけどね。操作ミスを防ぐだけの目的であれば十分でしょう。
アカウントの新規登録へ進みます。 admin_add.ctp を開いて、例によって

<?php echo $javascript->link(array('jquery','util'), false); ?>

を追加し、

        echo $this->Form->input('password');

こうなっているのを

        echo $this->Form->input('password', array('label' => 'パスワード', 'value'=>''));
        echo $this->Form->input('password_confirm', array('label' => 'パスワード(確認)', 'value'=>''));

こうします。
一方、モデルの user.php に、validate のルールを追加します。
例によって、この中に書いていきます。

    var $validate = array(
        'username' => array(
            'notempty' => array(
                'rule' => array('notempty'),
                //'message' => 'Your custom message here',
                //'allowEmpty' => false,
                //'required' => false,
                //'last' => false, // Stop validation after this rule
                //'on' => 'create', // Limit validation to 'create' or 'update' operations
            ),
        ),
        'password' => array(
            'notempty' => array(
                'rule' => array('notempty'),
                //'message' => 'Your custom message here',
                //'allowEmpty' => false,
                //'required' => false,
                //'last' => false, // Stop validation after this rule
                //'on' => 'create', // Limit validation to 'create' or 'update' operations
            ),
        ),
    );

まずログインID(ユーザー名)は、

        'username' => array(
            "rule1" => array(
                "rule" => 'isUnique',
                "message" => "このユーザー名は既に登録されています",
            ),
            "rule2" => array(
                "rule" => array("custom", '/^[a-zA-Z0-9\_\-]{6,10}$/i'),
                "message" => "ユーザー名は6文字以上10文字以内で入力してください",
            ),
            'notempty' => array(
                'rule' => array('notempty'),
                'message' => 'ユーザー名を入力してください',
            ),
        ),

パスワードを入れたかどうか、一致してるか、は password_confirm の方でチェックします。

        'password_confirm' => array(
            'rule1' => array(
                'rule' => array('passwordConfirm'),
                'message'=>'パスワードの入力が一致しません',
            ),
            "rule2" => array(
                "rule" => 'notEmpty',
                "message" => "パスワードを入力してください",
            ),
        ),

上記の passwordConfirm はカスタムルールなので別途作ります。
password_confirm の方の入力を暗号化して、それと password の方が一致するかを確認します。
例の、Authコンポーネントが自動で暗号化してしまうことのためです。

    function passwordConfirm(){
        if ($this->data['User']['password'] == Security::hash(Configure::read('Security.salt') . $this->data['User']['password_confirm'])) {
            return true;
        } else {
            return false;
        }
    }

ブラウザで動作テストします。他で入力エラー出すとパスワードの入力が消えてしまいますが、ま、よしとします。
引き続き admin_edit.ctp もビューを修正します。
パスワードのフォーム

        echo $this->Form->input('password');

を以下に変えます

        echo $this->Form->input('password', array('label' => 'パスワード', 'value'=>''));
        echo $this->Form->input('password_confirm', array('label' => 'パスワード(確認)', 'value'=>''));

パスワードは編集の度に入れなくてはいけませんがこれもしょうがないでしょう。
ブラウザ動作テストします。
その他不要なメニューの削除やら、見出しやら、ビューの整形を行います。

2011-10-03

CakePHP1.3で作る会員管理システム(20) Authコンポーネント

Authコンポーネント

管理者側のアカウント管理(users)にいきます。
users_controller.php を開いて、アクションの add とadmin用のアクションを残して、他のアクションを削除します。
要らないので。
$helpers に ‘Javascript’ を追加します。

<?php
class UsersController extends AppController {

    var $name = 'Users';
    var $helpers = array('Javascript');

さらにその下に、

    function beforeFilter() {
        parent::beforeFilter();
        $this->Auth->allow('add');
    }
を追加します。

これは、アクションaddだけはAuthコンポーネントの認証から除外するって意味です。
なにしろまだアカウントを作っていないのに、Authコンポーネントで認証をかけてしまったら、誰も管理者画面にログインできなくなってしまいます。
かといってコンポーネント Auth を使う設定にしないで新規アカウントを登録しても、パスワードはplainなままで登録されてしまい、その後で Auth をかけても使えません。
次に、直下の app_controller.php を開いて、 $components に ‘Auth’ を追加します。

class AppController extends Controller {

    var $components = array(
        'Auth',
        'Session', 
        'Email' => array(
            'from' => 'your@email',
            'sendAs' => 'text',
        ),
    );

この状態で、ブラウザから、 http://localhost/~myname/cake/users/add を開いて、一人目のアカウント(管理担当者)を登録します。

エラーが出ますが、

The user has been saved
のメッセージが表示されていると思います。

データベースを直接のぞいて、パスワードが暗号化されているのを確認します。

SELECT * FROM users;

以降、管理者画面へはこの一人目のアカウントでログインします。
アクションの add はもう削除してしまって構いません。
アカウントは出来たので、ログイン用の画面を作ります。ビューで、
/views/users/admin_login.ctp として、

<?php echo $this->Session->flash('auth'); ?>
<div class="users form">
<?php echo $this->Form->create('User', array('action' => 'login'));?>

    <h2><?php __('ログイン'); ?></h2>
    <?php
        echo $this->Form->input('username',array('label' => array('text'=>'ユーザー名')));
        echo $this->Form->input('password',array('label' => array('text'=>'パスワード')));
    ?>
    <?php echo $this->Form->end(__('ログイン', true));?>
</div>
上記のような画面を作ります。

で、usersコントローラに以下のアクションを追加します。

    function admin_login() {
    }

ブラウザからともかくなにかの admin のアドレス、例えば
http://localhost/~bob/cake/index.php/admin/users/
とかを入れて、ともかくもログイン画面が表示されることを確認します。
そして作った一人目のアカウントでログインできることを確認します。
さらに、ログアウトを追加します。

    function admin_logout() {
        $this->redirect($this->Auth->logout());
    }

こちらでログアウトできることを確認します。
とりあえず動かしてみて、エラーメッセージが英語なのが気になりますよね。
そこで、直下の app_controller.php を開いて、beforeFilter の中に以下を追加します。

        $this->Auth->loginError = "ユーザー名もしくはパスワードが正しくありません";
        $this->Auth->authError = "ログインしてください";

これで日本語になると思います。
ついでに、ログインした時に今ログインしているアカウント名を常に左メニューの上の方に表示したいので、 set しておきます。
以下を直下の app_controller.php の function beforeFilter() { } の中に追加

        $this->set('loginName', $this->Auth->user('username'));

そして、レイアウトの /app/views/layouts/admin.ctp を開いて、div id=”menu” の下に一行追加します。

            <div class="actions">
                <h3><?php __('メニュー'); ?></h3>
                <p><?php echo $loginName; ?>でログイン中</p>
                <ul>

ブラウザで開いてみて、ログイン中のアカウントの名前が表示されていることを確認します。
あと、ログイン画面のレイアウトが admin 用のもので、左メニューがあっておかしいので、左メニューのないレイアウトに変更します。
コントローラ users_controller.php を開いて、admin_login アクションに

$this->layout = 'no_menu';
を追加します。

で、

/admin/users/add
こんなアドレスを直接叩くと、
/admin/users/login
ログイン画面にとばされる訳ですが、そのログイン画面からログインすると、ログイン後は
/admin/users/add
に飛んでくれるようになっています。 で、最初に作った公開側の会員登録画面を見てみると、こちらにもAuthコンポーネントが適用されて大変なことになってしまっているので、除外設定を行います。member_controller.php の最初に以下を追加します。

    function beforeFilter() {
        parent::beforeFilter();
        $this->Auth->allow('add','complete', 'login', 'logout', 'menu', 'edit', 'leave');
    }

allow のところに除外するアクションを列挙するだけです。
ついでに、member_controller.php から、公開側の index() とか view() とか、使わないアクションを削除しておきます。

CakePHP1.3で作る会員管理システム(19) 退会処理

退会処理

最後に退会の処理を作ります。 アクション delete() のままでもいいんですが、お客さんに delete ってのもどうかと思ったので、 leave にしてみました。

    function leave() {
        if (!$this->Session->check('Member.id')) {
            $this->redirect(array('action'=>'login'));
        }
        if (!empty($this->data)) {
            if ($this->Member->delete($this->Session->read('Member.id'))) {
                $this->Session->destroy();
                $this->render('quit');
            } else {
                $this->Session->setFlash(__('退会できませんでした', true));
            }
        } else {
            $this->data = $this->Member->read(null, $this->Session->read('Member.id'));
            $this->set('member', $this->data);
        }
        $types = $this->Member->Type->find('list');
        $favorites = $this->Member->Favorite->find('list');
        $this->set(compact('types', 'favorites'));
    }

また、 confirm.ctp をコピーしてきて改造します。フォームの始まりと終わりの部分だけ書き換えます。

<?php echo $javascript->link(array('jquery','util'), false); ?>
<div class="members view">
<?php echo $this->Form->create(null, array('url'=>'/members/leave'));?>
<?php echo $this->Form->input('id');?>

・
・
・

<div class="submit">
    <?php echo $this->Form->button('戻る', array('type'=>'button', 'id'=>'back_button')); ?>
</div>
<?php echo $this->Form->end('退会する'); ?>

</div>

ここで id=’back_button’ というのを作ったので、これも javascript の util.js に追加しておきます。

$('#back_button').click(function(){
    history.back();
});

戻れればいいだけなので、ただのヒストリーバックです。

退会の完了画面用にビューで quit.ctp を用意しておきます。

CakePHP1.3で作る会員管理システム(18) 編集処理

編集画面

続けて編集画面もやってしまいます。 edit.ctp を開いて、 以下のフォーム開始を、

<?php echo $this->Form->create('Member');?>
以下のように変更します。

<?php echo $this->Form->create('Member', array('type' => 'file'));?>
<div style="display:none;">
    <?php echo $this->Form->input('img1', array('type'=>'hidden')); ?>
    <?php echo $this->Form->input('img2', array('type'=>'hidden')); ?>
</div>

以下のフォームのメインの部分は、

    <?php
        echo $this->Form->input('id');
        echo $this->Form->input('email');
        echo $this->Form->input('password');
        echo $this->Form->input('type_id');
        echo $this->Form->input('birthday');
        echo $this->Form->input('img1');
        echo $this->Form->input('img2');
        echo $this->Form->input('Favorite');
    ?>
add.ctp と同じになるように修正しておきます。

    <?php
        echo $this->Form->input('email',array('label' => array('text'=>'メールアドレス')));
        echo $this->Form->input('password',array('label' => array('text'=>'パスワード')));
        echo $this->Form->input('type_id',array('label' => array('text'=>'種別')));
        echo $this->Form->input('birthday',array('dateFormat'=>'YMD','maxYear'=>date('Y'),'minYear'=>date('Y')-100,'monthNames'=>false, 'label' => array('text'=>'誕生日')));
        echo $this->Form->input('solid1', array('type'=>'file', 'label' => '画像1'));
        echo $this->Form->input('solid2', array('type'=>'file', 'label' => '画像2'));
        echo $this->Form->input('favorites', array('multiple'=>'checkbox', 'label' => '好きな物'));
    ?>

次に、

        echo $this->Form->input('solid1', array('type'=>'file', 'label' => '画像1'));
となっている画像のところを、以下のようにちょっと変えます。

        echo $this->Form->input('solid1', array('type'=>'file', 'label' => '画像1'));
        if (!$this->Form->error('solid1')){
            if ($this->data['Member']['img1']){
                echo '<div class="input img1"><label for="MemberImg1"></label>';
                echo $this->Html->image("/files/".$this->data['Member']['img1']);
                echo '</div>';
            }
        }

出力されるソースはこんなになります。

<div class="input file required">
    <label for="MemberSolid1">画像1</label>
    <input type="file" id="MemberSolid1" name="data[Member][solid1]">
</div>
<div class="input img1">
    <label for="MemberImg1"></label>
    <img alt="" src="/cake/files/xxxxxxxxxxxxxxxxx.jpg">
</div>

続いてコントローラを修正します。

デフォルトでは、

    function edit($id = null) {
        if (!$this->Session->check('Member.id')) {
            $this->redirect(array('action'=>'login'));
        }

        if (!$id && empty($this->data)) {
            $this->Session->setFlash(__('Invalid member', true));
            $this->redirect(array('action' => 'index'));
        }
        if (!empty($this->data)) {
            if ($this->Member->save($this->data)) {
                $this->Session->setFlash(__('The member has been saved', true));
                $this->redirect(array('action' => 'index'));
            } else {
                $this->Session->setFlash(__('The member could not be saved. Please, try again.', true));
            }
        }
        if (empty($this->data)) {
            $this->data = $this->Member->read(null, $id);
        }
        $types = $this->Member->Type->find('list');
        $favorites = $this->Member->Favorite->find('list');
        $this->set(compact('types', 'favorites'));
    }
となっているのを、
    function edit() {
        if (!$this->Session->check('Member.id')) {
            $this->redirect(array('action'=>'login'));
        }
        if (!empty($this->data)) {
            $this->data['Favorite']['Favorite'] = $this->data['Member']['favorites'];
            if ($this->data['Member']['solid1']['type']){
                $this->data['Member']['img1'] = $this->_imgUpload('solid1');
            }
            if ($this->data['Member']['solid2']['type']){
                $this->data['Member']['img2'] = $this->_imgUpload('solid2');
            }
            if ($this->Member->save($this->data)) {
                $this->Session->setFlash(__('会会員情報を更新しました', true));
                $this->redirect(array('action' => 'menu'));
            } else {
                $this->Session->setFlash(__('エラーがあります', true));
            }
        }
        if (empty($this->data)) {
            $this->data = $this->Member->read(null, $this->Session->read('Member.id'));
            if ($this->data['Favorite']){
                foreach ($this->data['Favorite'] as $k => $v){
                    $this->data['Member']['favorites'][] = $v['id'];
                }
            }
        }
        $types = $this->Member->Type->find('list');
        $favorites = $this->Member->Favorite->find('list');
        $this->set(compact('types', 'favorites'));
    }
と編集します。

セッションで認証するので、

function edit($id = null) {
の引数部分はなしにしてます。その代わりに、$this->Member->read に対して、セッションの Member.id を渡しています。

他は add の方と同じですが、 初期に編集画面へ来た時の、DBからデータを読む処理の後に、その「好きな物」データを変換する処理を入れています。上記20〜24行目。 Favorite を favorites に入れ直しています。 これにより、

        echo $this->Form->input('favorites', array('multiple'=>'checkbox', 'label' => '好きな物'));
この書き方のビューに反映されます。

チェックボックスにチェックが入っているを確認します。

CakePHP1.3で作る会員管理システム(17) 画像アップロード

画像アップロード

今回画像はデータベースにはそのファイル名を登録します。
つまり、 members テーブルの img1 、 img2 はファイル名です。
ということで別途、画像の実体という意味合いで solid1 、 solid2 という名前を用意します。
( img1 、 img2 を使い回すと面倒なことになるので)
登録する画像はとりあえず、/app/wwwroot/files の中に保存することにします。
まず add.ctp を書き換えます。

        echo $this->Form->input('img1');
        echo $this->Form->input('img2');

こうなっているところを、

        echo $this->Form->input('solid1', array('type'=>'file', 'label' => '画像1'));
        echo $this->Form->input('solid2', array('type'=>'file', 'label' => '画像2'));
こうします。

おっと、
formを enctype=”multipart/form-data” にしないといけませんので。

<?php echo $this->Form->create('Member', array('type'=>'file')); ?>
に変えます。

これで、出力は

<form accept-charset="utf-8" method="post" enctype="multipart/form-data" id="MemberAddForm" action="/~myname/cake/members/add">
となります。 /app/models/member.php を開いて、モデルの validate を追加します。

        'solid1' => array(
            'rule1' => array(
                'rule' => array('imgType', 'solid1'),
                'message'=>'画像1はJPEG形式で登録してください',
            ),
        ),

これは ’solid2‘ についても同じように追加しておきます。
いわゆるCakePHP本家Docの4.1.5.2 Adding your own Validation Methodsのやり方なので、自分で ‘imgType’ という名前の function を作ります。
array(‘imgType’, ’solid1′), のうしろの ’solid1′ はその function に渡す引数になります。
以下の function imgType を作って member.php の下の方に入れておきます。

    function imgType($data, $name){
        if (!$this->data['Member'][$name]['type'] || ($this->data['Member'][$name]['type'] == 'image/pjpeg') || ($this->data['Member'][$name]['type'] == 'image/jpeg')) {
            return true;
        } else {
            return false;
        }
    }

単に画像の type が jpeg がどうかをチェックしているだけです。
他にファイルサイズのチェックとか、ファイル名のチェックとか、入れたければ入れます。
で、ちょっとブラウザからテストしてみます。
gif画像なんかを登録した時に、ちゃんと「画像1はJPEG形式で登録してください」のエラーが出たでしょうか。
最後にコントローラ members_controller.php に画像を保存する処理を追加します。

確認画面の時に作った validates の下に、

            if ($this->data['Member']['mode'] == 'confirm') {
                $this->Member->set($this->data);
                if ($this->Member->validates($this->data)) {
                    if ($this->data['Member']['solid1']['type']){
                        $this->data['Member']['img1'] = $this->_imgUpload('solid1');
                    }
                    if ($this->data['Member']['solid2']['type']){
                        $this->data['Member']['img2'] = $this->_imgUpload('solid2');
                    }
                    $this->set('member', $this->data);
                    $this->render('confirm');
                } else {
                    $this->Session->setFlash(__('エラーがあります', true));
                }
            } elseif ($this->data['Member']['mode'] == 'back') {
とします。

ここの _imgUpload は画像を保存するための function で、自分で作ります。
solid1 の画像実体を保存し、その時に画像のファイル名を生成して、そのファイル名を img1 として返します。

    function _imgUpload($name) {
        if ($this->data['Member'][$name]['tmp_name']){
            $fd = fopen($this->data['Member'][$name]['tmp_name'], "r");
            $filesize = $this->data['Member'][$name]['size'];
            $filedata = fread ($fd, $filesize);
            fclose ($fd);
            
            $img_file = md5($filedata). ".jpg";
            $res = fopen(WWW_ROOT . "files/" . $img_file,'w');
            fwrite($res,$filedata);
            fclose($res);
            
            return $img_file;
        }
    }

こんな感じです。とりあえず、同じコントローラの下の方に入れておきます。 確認画面 confirm.ctp に表示されるよう修正します。

こうなってるのを、

        <dt<?php if ($i % 2 == 0) echo $class;?>><?php __('Img1'); ?></dt>
        <dd<?php if ($i++ % 2 == 0) echo $class;?>>
            <?php echo $member['Member']['img1']; ?>
             
        </dd>
こうします。

        <dt<?php if ($i % 2 == 0) echo $class;?>><?php __('画像1'); ?></dt>
        <dd<?php if ($i++ % 2 == 0) echo $class;?>>
            <?php if (isset($member['Member']['img1'])){ echo $this->Html->image("/files/".$member['Member']['img1']);} ?>
             
        </dd>

hidden 部分には

    echo $this->Form->input('solid1.type', array('type'=>'hidden'));
    echo $this->Form->input('solid1.size', array('type'=>'hidden'));
    echo $this->Form->input('solid2.type', array('type'=>'hidden'));
    echo $this->Form->input('solid2.size', array('type'=>'hidden'));
を追加します。

確認画面で画像が表示されたでしょうか。

最後、メールの添付ファイルに指定します。
members_controller.php に戻って、addアクションの、saveしてるところの下に以下を追加します。

                $this->Member->create();
                if ($this->Member->save($this->data)) {
                    $this->set('member', $this->data);
                    $attachments = array();
                    if ($this->data['Member']['img1']){
                        $attachments[] = WWW_ROOT . "files/" . $this->data['Member']['img1'];
                    }
                    if ($this->data['Member']['img2']){
                        $attachments[] = WWW_ROOT . "files/" . $this->data['Member']['img2'];
                    }

$attachments 配列に画像のパスを追加してるだけです。

2011-09-18

CakePHP1.3で作る会員管理システム(16) ログイン認証

ログイン認証

会員がログインできるようにします。 最初にログインする画面とログインした後の会員用のメニュー画面のテンプレを作っておきます。 それぞれこんな感じで適当に。 会員ログイン画面 login.ctp のファイル名で。

<div class="members form">
<?php echo $this->Form->create('Member', array('action' => 'login'));?>

    <h2><?php __('ログイン'); ?></h2>
    <?php
        echo $this->Form->input('email',array('label' => array('text'=>'メールアドレス')));
        echo $this->Form->input('password',array('label' => array('text'=>'パスワード')));
    ?>
    <?php echo $this->Form->end(__('ログイン', true));?>
</div>
<div class="actions">
    <ul>
        <li><?php echo $this->Html->link(__('新規登録はこちら', true), array('action'=>'add')); ?></li>
    </ul>
</div>

会員メニュー画面 menu.ctp のファイル名で。

<div class="members index">
    <h2><?php __('会員メニュー'); ?></h2>
</div>
<div class="actions">
    <ul>
        <li><?php echo $this->Html->link(__('会員情報の更新', true), array('action'=>'edit')); ?></li>
        <li><?php echo $this->Html->link(__('退会手続き', true), array('action'=>'leave')); ?> </li>
        <li><?php echo $this->Html->link(__('ログアウト', true), array('action'=>'logout')); ?> </li>
    </ul>
</div>

ログアウトの完了画面も作っておきます。logout.ctp のファイル名で。

<div class="members index">
    <h2><?php __('ログアウトしました'); ?></h2>
</div>

簡単なやつでいいです。 で、コントローラ members_controller.php にログイン、ログアウトそれぞれのアクションを入れます。 ログインがこんな感じで。

    function login() {
        $this->Member->recursive = 0;
        $this->Session->destroy();
        if (!empty($this->data)) {
            if ($this->data['Member']['email']) {
                $member = $this->Member->findbyEmail($this->data['Member']['email'], array('id','password'));
                if ($member['Member']['password'] === $this->data['Member']['password']) {
                    $this->Session->write('Member.id', $member['Member']['id']);
                    $this->redirect(array('action'=>'menu'));
                } else {
                    $this->Session->setFlash(__('ログインIDまたはパスワードが違います', true));
                }
            }
        }
    }

入力された email の値で、 findbyEmail を使ってクエリーを投げます。 実行されたクエリーはこのようなものです。

SELECT `Member`.`id`, `Member`.`password` FROM `members` AS `Member` LEFT JOIN `types` AS `Type` ON (`Member`.`type_id` = `Type`.`id`) WHERE `Member`.`email` = 'your@email' LIMIT 1

結果のパスワードと入力されたパスワードの値を比較します。 認証OKだったら、 ‘Member.id’ というセッションに結果の id を入れます。 メニュー画面へリダイレクトします。

ログアウトは、

function logout() {
    $this->Session->destroy();
}
ただセッションを破棄するだけです。

メニューは、

function menu() {
    if (!$this->Session->check('Member.id')) {
        $this->redirect(array('action'=>'login'));
    }
}
Member.id というセッションがあるかどうかチェックします。

なければログイン画面にリダイレクトします。 function edit() にも、以下のチェックを入れておきます。

    function edit($id = null) {
        if (!$this->Session->check('Member.id')) {
            $this->redirect(array('action'=>'login'));
        }

        if (!$id && empty($this->data)) {
            $this->Session->setFlash(__('Invalid member', true));

2011-09-16

CakePHP1.3で作る会員管理システム(15) メール送信

メール送信

ここで会員登録時のメール送信を作ります。 Emailコンポーネントの使い方は本家サイトのドキュメントの通りです。

/app 直下の app_controller.php を開いて、

class AppController extends Controller {
}
となっている中に、
    var $components = array('Session', 'Email');
と入れます。

そして、以下のようなファンクションを入れます。

function _sendEmail($template, $subject, $to, $attachments, $from, $replyto, $fromName, $sendAs, $bcc) {
    if (!$from) {$from = 'your@email';}
    if (!$replyto) {$replyto = 'your@email';}
    if ($fromName) {$from = $fromName . " <$from>";}
    $this->Email->to = $to;
    $this->Email->bcc = $bcc;
    $this->Email->subject = $subject;
    $this->Email->replyTo = $replyto;
    $this->Email->from = $from;
    $this->Email->template = $template;
    if ($attachments) {$this->Email->attachments = $attachments;}
    $this->Email->sendAs = $sendAs;
    $this->Email->send();
}

smtp で送信する場合は、$this->Email->send() の部分を以下に書き換えます。

        $this->Email->smtpOptions = array(
            'port'=>'587',
            'timeout'=>'30',
            'host' => 'your.host.name',
            'username'=>'username',
            'password'=>'password'
        );
        $this->Email->delivery = 'smtp';
        $this->Email->send();
        $this->set('smtp-errors', $this->Email->smtpError);

gmailで送る場合は、smtpOptions のところを以下のようにします。

        $this->Email->smtpOptions = array(
            'port'=>'465',
            'timeout'=>'30',
            'host' => 'ssl://smtp.gmail.com',
            'username'=>'your@email',
            'password'=>'yourpassword'
        );

メールの文字コードは、デフォルトだとUTF-8で送られます。 charsetは以下で設定できますが、ISO-2022-JP にすると subject が文字化けするようです。

$this->Email->charset = "ISO-2022-JP";

from のメールアドレスは固定だし、text形式のメールしか送らない、というのであれば、 最初に設定した app_controller.php で、

    var $components = array(
        'Session', 
        'Email' => array(
            'from' => 'your@email',
            'sendAs' => 'text',
        ),
    );
というように書くこともできます。

こうすると、function _sendEmail() の中では指定する必要がありません。

//        $this->Email->sendAs = $sendAs;

つまり、var $components の方にどんどん指定していってしまうと、function _sendEmail() の方に書くことがなくなり、どんどん減っていきます。
自分の使いやすいと思うあたりに調節すればよいと思います。

メール文面のテンプレートは、まずレイアウトを

  • /app/views/layouts/email/html/default.ctp
  • /app/views/layouts/email/text/default.ctp
に入れます。

テキストメールの場合は、とりあえず、default.ctp に

<?php echo $content_for_layout; ?>
とだけ書いて保存します。

文面は

  • /app/elements/email/html/default.ctp
  • /app/elements/email/text/default.ctp
に入れます。

文面は普通に、確認画面の view.ctp と同じように作ればいいと思います。

以下の会員登録がありました。
-----------------------------------------------------
メールアドレス: <?php echo $member['Member']['email'] ."\n"; ?>
会員種別:<?php echo $types[$member['Member']['type_id']] ."\n"; ?>
誕生日:<?php echo $member['Member']['birthday']['year']; ?>年<?php echo $member['Member']['birthday']['month']; ?>月<?php echo $member['Member']['birthday']['day']; ?>日
好きな物:
<?php
if (!empty($member['Favorite'])) {
    foreach ($member['Favorite']['Favorite'] as $favorite) {
        echo "\t".$favorites[$favorite['id']]."\n";
    }
}
?>

こんな感じです。
こちらの elements の中の方のファイル名を上記 $this->Email->template に指定します。

ここではとりあえず、

/app/elements/email/text/member_add.ctp
としました。

で、コントローラ members_cntroller.php のアクション add のsaveしてる部分の下に

            } else {
                $this->Member->create();
                if ($this->Member->save($this->data)) {
                    $this->set('member', $this->data);
                    $attachments = array();
                    $this->_sendEmail('member_add', 'ユーザー登録のお知らせ', 'your@email', $attachments,'','','','text',array());
                    $this->Session->setFlash(__('The member has been saved', true));
                    $this->redirect(array('action' => 'complete'));
                } else {
                    $this->Session->setFlash(__('The member could not be saved. Please, try again.', true));
                }
            }
を入れます。
メールを受信して確認します。

CakePHP1.3で作る会員管理システム(14) checkbox の validate

checkbox の validate

動作がおかしい「好きな物」のチェックボックスを直していきます。

「好きな物」は Favorite モデルですが、あくまでここでは Member を登録している訳ですから Member モデルとして処理します。
今 add.ctp でこうなってる部分を、

        echo $this->Form->input('Favorite', array('multiple'=>'checkbox', 'label' => '好きな物'));
こうします。

        echo $this->Form->input('favorites', array('multiple'=>'checkbox', 'label' => '好きな物'));

するとなんと!

<input type="checkbox" id="MemberFavorites1" value="1" name="data[Member][favorites][]">

こういうソースが生成されます。
これはモデル Member です。

ということで Member モデルの方にルールを書きます。
/app/models/member.php の $validate が書かれている部分の 'birthday' の下に以下を追加します。

        'favorites'=>array(
            'custom' => array(
                'rule'=>array('multiple', array('min' => 1, 'max' => 3)),
                'message' => '好きな物は一つ以上選択してください'
            ),
        ),

次にコントローラに移動して、確認画面の時に validate するように members_controller.php を書き換えます。

if (!empty($this->data)) {
から始まる if 文の最初に、
$this->data['Favorite']['Favorite'] = $this->data['Member']['favorites'];
を入れます。

これで favorites を本来の Favorite に入れ直しています。 で、

if ($this->data['Member']['mode'] == 'confirm') {
から始まる mode="confirm" の部分の if 文を以下のように変えます。

if ($this->data['Member']['mode'] == 'confirm') {
    $this->Member->set($this->data);
    if ($this->Member->validates($this->data)) {
        $this->set('member', $this->data);
        $this->render('confirm');
    } else {
        $this->Session->setFlash(__('エラーがあります', true));
    }
} elseif ($this->data['Member']['mode'] == 'back') {//

これで正しくエラーチェックが動くようになりました。

あとまだ、好きな物チェックボックスの確認画面がおかしいので、
今 confirm.ctp の

    echo $this->Form->input('Favorite', array('type'=>'hidden'));
こうなっているところを、
    foreach ($member['Member']['favorites'] as $k => $v){
        echo $this->Form->input("Member.favorites.$k", array('type'=>'hidden'));
    }
このようにします。

これで生成されるHTMLの hidden の部分が、

<input id="MemberFavorites0" type="hidden" value="1" name="data[Member][favorites][0]">
<input id="MemberFavorites1" type="hidden" value="3" name="data[Member][favorites][1]">
のように出力されるようになります。

戻るボタンクリックでも値が保持されています。
今まで保存されていなかった members_favorites テーブル にもデータが保存されています。

CakePHP1.3で作る会員管理システム(13) checkbox、 戻るボタン、年月セレクト、完了画面

checkbox、 戻るボタン、年月セレクト、完了画面

次に、add.ctp 入力フォームの、Favorite(好きな物)が multiple セレクトになってるのを直します。
日本では複数選択にセレクトなんてほとんど使われないですよね。
こうなっているのを、

        echo $this->Form->input('Favorite');
こうします。

        echo $this->Form->input('Favorite', array('multiple'=>'checkbox', 'label' => '好きな物'));

これでとりあえずチェックボックスになるんですが、
どうも一つのチェックで一行になっているので収まりが悪いです。
横に並べたいですね。
生成されたHTMLを確認すると、以下のようになっています。

<div class="input select">
    <label for="FavoriteFavorite">好きな物</label>
    <input type="hidden" id="FavoriteFavorite" value="" name="data[Favorite][Favorite]">
    <div class="checkbox">
        <input type="checkbox" id="FavoriteFavorite1" value="1" name="data[Favorite][Favorite][]">
        <label for="FavoriteFavorite1">apple</label>
    </div>
    <div class="checkbox">
        <input type="checkbox" id="FavoriteFavorite2" value="2" name="data[Favorite][Favorite][]">
        <label for="FavoriteFavorite2">orange</label>
    </div>
    <div class="checkbox">
        <input type="checkbox" id="FavoriteFavorite3" value="3" name="data[Favorite][Favorite][]">
        <label for="FavoriteFavorite3">grape</label>
    </div>
</div>

チェックボックス一つにつき一つの div で囲まれています。
css を書くのもしんどいので、jQuery で処理することにします。
jQuery をダウロードして解凍しておいてください。

/app/webroot/js 以下に jquery.js としてコピーしておきます。
で、さらに /app/webroot/js 以下に、新規に util.js という名前でファイルを作ります。

中身は、こんな感じで。

$(document).ready(function() {
    $('div.checkbox').css('display','inline');
    $(':checkbox').css('float','none');
});

周りを囲んでいる div を inline 表示に変えて、左に float しているチェックボックスの float を解除しているだけです。

で、 add.ctp の一番上に、

<?php echo $javascript->link(array('jquery','util'), false); ?>
を追加します。

さらに、members_controller.php の 上の方に $helpers を追加します。

<?php
class MembersController extends AppController {

    var $name = 'Members';
    var $helpers = array('Javascript');

    function index() {
        $this->Member->recursive = 0;
        $this->set('members', $this->paginate());
    }

ブラウザで確認してみると、チェックボックスが横に並んでいます。
これはあくまでその場しのぎの付け焼刃ですので、本来はきちんとデザイナーさんに css を書いてもらいましょう。

ついでなので、さきほどのJavascriptの util.js の $(document).ready(function() {});の中に、以下を追加しておきます。

    $('#back').click(function(){
        $('#MemberMode').val('back');
        $("#MemberConfirm").submit();
    });

解説:
1行目:id="back" のボタンをクリックしたら
2行目:id="MemberMode" の valueを "back" にして(hidden で追加した mode の value です)
3行目:id="MemberConfirm" のフォームをサブミットする(確認画面の form です)

confirm.ctp の一行目に以下を追加します。

<?php echo $javascript->link(array('jquery','util'), false); ?>

members_controller.php の前回追加した if ~ else の中に以下の elseif を追加します。{} の中は空でいいです。

            if ($this->data['Member']['mode'] == 'confirm') {
                //・・・
            } elseif ($this->data['Member']['mode'] == 'back') {

            } else {
                //・・・
            }

これで、確認画面から入力画面への「戻る」ボタンが動くようになります。
いろいろエラーが出ているかもしれませんが、たいした問題ではないので気にしないで進みます。

あとは、誕生日の入力フォームが洋風で気に入らないので、変更します。

今こうなってるのを、

        echo $form->input('birthday');
以下に変更します。

        echo $this->Form->input('birthday',array('dateFormat'=>'YMD','maxYear'=>date('Y'),'minYear'=>date('Y')-100,'monthNames'=>false, 'label' => array('text'=>'誕生日')));

最後、一応公開側は完了画面を出すことにするので、「登録されました」みたいな適当なビューを complete.ctp という名前で作っておきます。

<div class="members form">
    <h2><?php __('会員登録'); ?></h2>

    <p>登録されました。</p>
</div>

そして、members_controller.php の add アクションの save の後の、

                    $this->redirect(array('action' => 'index'));
となっているのを、
                    $this->redirect(array('action' => 'complete'));
に変更し、さらに、
function complete() {
}
を追加しておきます。
ブラウザで完了画面が表示されることを確認します。

2011-09-15

CakePHP1.3で作る会員管理システム(12) 確認画面その2

確認画面その2

ではビューについて、順番に整理していきます。

confirm.ctp から管理メニュー部分

<div class="actions">
....
</div>
で囲まれたところをざくっと削除します。いらないので。

Birthday(誕生日)のところ、

<?php echo $member['Member']['birthday']; ?>
こうなっているのを、以下に変更します。

<?php echo $member['Member']['birthday']['year'] .'/' . $member['Member']['birthday']['month'] .'/'. $member['Member']['birthday']['day']; ?>

同じく “hidden” の Birthday 部分、

echo $this->Form->input('birthday', array('type'=>'hidden'));
これを以下に変更します。

echo $this->Form->input('birthday.year', array('type'=>'hidden'));
echo $this->Form->input('birthday.month', array('type'=>'hidden'));
echo $this->Form->input('birthday.day', array('type'=>'hidden'));

Type(会員種別) のところ、

<?php echo $this->Html->link($member['Type']['name'], array('controller' => 'types', 'action' => 'view', $member['Type']['id'])); ?>
こうなっているのを、以下に変更します。

<?php echo $types[$member['Member']['type_id']]; ?>

次に「好きな物」に関して、

<div class="related">
....
</div>
ではさまれたの関連テーブル部分をごっそり削除し、その代わりにメインのテーブルの Img2 の表示部分(tr で囲まれた一角)の下に以下を追加します。

  <dt<?php if ($i % 2 == 0) echo $class;?>><?php __('好きな物'); ?></dt>
  <dd<?php if ($i++ % 2 == 0) echo $class;?>>
   <?php if (!empty($member['Favorite']['Favorite'])):?>
   <ul>
    <?php foreach ($member['Favorite']['Favorite'] as $favorite): ?>
     <li><?php echo $favorites[$favorite['id']];?></li>
    <?php endforeach; ?>
   </ul>
   <?php endif; ?>
    
  </dd>

members_controller.php に戻って、 function add() の、一番下の

  $types = $this->Member->Type->find('list');
  $favorites = $this->Member->Favorite->find('list');
  $this->set(compact('types', 'favorites'));
3行を、一番上に持ってきます。

こうなります。

 function add() {
  $types = $this->Member->Type->find('list');
  $favorites = $this->Member->Favorite->find('list');
  $this->set(compact('types', 'favorites'));
  
  if (!empty($this->data)) {
   if ($this->data['Member']['mode'] == 'confirm') {
    $this->Member->set($this->data);

ブラウザで確認します。
ここまでで、Type(会員種別)、Birthday(誕生日)、好きな物 が表示されるようになったはずです。
ボタン「送信」をクリックで、一覧画面に戻り、

The Member has been saved
と表示されればOKです。membersテーブルのデータは保存されています。

2011-09-14

CakePHP1.3で作る会員管理システム(11) 確認画面その1

確認画面その1

まず、確認画面のビューを作ります。 bake で自動生成した、 view.ctp がそのまま使えそうなので、 view.ctp を開いてから「名前を付けて保存」で confirm.ctp を作成します。

今回確認画面はhidden方式でいくので、

<div class="members view">
となっている所の、直下にフォームを作ります。

<?php echo $this->Form->create('Member', array('id'=>'MemberConfirm')); ?>

こんな感じです。 ただの

$this->Form->create('Member');
だけでもいいっちゃいいんですが、そうすると、以下のような add の画面と全く同じ id でフォームタグが生成されてしまうので、
<form id="MemberAddForm" accept-charset="utf-8" method="post" action="/~myname/cake/members/add">
これはなんとなく気持ち悪いので、
array('id'=>'MemberConfirm')
を指定して、確認画面用のフォームの id を指定しておきます。

で、その下に add.ctp のフォーム部分、

<?php
    echo $this->Form->input('email');
    echo $this->Form->input('password');
    echo $this->Form->input('type_id');
    echo $this->Form->input('birthday');
    echo $this->Form->input('img1');
    echo $this->Form->input('img2');
    echo $this->Form->input('Favorite');
?>
をごっそりそのままコピーして貼り付けます。

hidden にするので

array('type'=>'hidden')
を一個一個全部に指定して、一応全体を div で囲んでおきます。 こんな感じになりました。

<div class="members view">
<?php echo $this->Form->create('Member', array('id'=>'MemberConfirm'));?>
<div style="display:none;">
<?php
    echo $this->Form->input('email', array('type'=>'hidden'));
    echo $this->Form->input('password', array('type'=>'hidden'));
    echo $this->Form->input('type_id', array('type'=>'hidden'));
    echo $this->Form->input('birthday', array('type'=>'hidden'));
    echo $this->Form->input('img1', array('type'=>'hidden'));
    echo $this->Form->input('img2', array('type'=>'hidden'));
    echo $this->Form->input('Favorite', array('type'=>'hidden'));
?>
</div>

<h2><?php  __('Member');?></h2>

ただ、アクションとしては同じ add で処理するので、なにかで識別するようにしないと「今は確認画面だ!」というのが分かりません。

ということで、 mode = ‘confirm’ というのを作り出して、これを add.ctp (フォーム入力画面)の方に、 hidden で入れておきます。

add.ctp 側

<div class="members form">
<?php echo $this->Form->create('Member');?>
<div style="display:none;">
    <?php echo $this->Form->hidden('mode', array('value'=>'confirm')); ?>
</div>

    <h2>
        <?php __('Add Member'); ?></h2>

confirm.ctp 側はvalueなしで。以下を追加。

    echo $this->Form->input('mode', array('type'=>'hidden','value'=>''));

これで、

  • add → confirm へは mode = ‘confirm’ で、
  • confirm → complete へは mode = ” で
行く事になります。 最後に、ボタンを追加しておきます。

<div class="submit">
    <?php echo $this->Form->button('戻る', array('id'=>'back')); ?>
</div>
<?php echo $this->Form->end('送信'); ?>

これはどっか適当に下の方に入れておきます。 一応ただの id=”back” ボタンをつけておきます。

あと、view.ctp との違いで、そっちには id がありますが、こっちにはまだ id は決まっていない、存在していないので、以下の id に関する表示部分を削除しておきます。

        <dt<?php if ($i % 2 == 0) echo $class;?>><?php __('Id'); ?></dt>
        <dd<?php if ($i++ % 2 == 0) echo $class;?>>
            <?php echo $member['Member']['id']; ?>
             
        </dd>

同様に、 Created と Modified も要らないので削除しておきます。

        <dt<?php if ($i % 2 == 0) echo $class;?>><?php __('Created'); ?></dt>
        <dd<?php if ($i++ % 2 == 0) echo $class;?>>
            <?php echo $member['Member']['created']; ?>
             
        </dd>
        <dt<?php if ($i % 2 == 0) echo $class;?>><?php __('Modified'); ?></dt>
        <dd<?php if ($i++ % 2 == 0) echo $class;?>>
            <?php echo $member['Member']['modified']; ?>
             
        </dd>

ここでもうそろそろ画面を見たいのでコントローラをいじります。

/app/controllers/members_controller.php を開いて、function add() の以下の部分、

        if (!empty($this->data)) {
            $this->Member->create();
            if ($this->Member->save($this->data)) {
                $this->Session->setFlash(__('The member has been saved', true));
                $this->redirect(array('action' => 'index'));
            } else {
                $this->Session->setFlash(__('The member could not be saved. Please, try again.', true));
            }
        }
この1行目からのif文の中にさらにもう一個if文を入れて、もとからあった部分はelseのあとにつっこみます。

構造としてはこんな感じ。

if (!empty($this->data)) {
    if () {
     
    } else {
    // もとからあった部分
    }
}

で、その新しいif文を以下のようにします。

    function add() {
        if (!empty($this->data)) {
            if ($this->data['Member']['mode'] == 'confirm') {
                $this->Member->set($this->data);
                $this->set('member', $this->data);
                $this->render('confirm');
            } else {
                $this->Member->create();
                if ($this->Member->save($this->data)) {
                    $this->Session->setFlash(__('The member has been saved', true));
                    $this->redirect(array('action' => 'index'));
                } else {
                    $this->Session->setFlash(__('The member could not be saved. Please, try again.', true));
                }
            }
        }

これで確認画面が出るはずなのでブラウザで /members/add を開いて適当に入力して進むと・・・
ぼこぼこにエラーが出てますが、入力したEmailの値とかは表示されてるはずです。
エラーは配列とかの問題だけなので今は気にしなくていいです。

2011-09-13

CakePHP1.3で作る会員管理システム(10) レイアウトその2

レイアウトその2

続いて、一般の人が登録したり、ログインして編集する画面(公開側)のレイアウトを作ります。 通常公開側はデザイナーさんがカッチリデザインしてくるものですから、どっちみち最終的にそのデザインに合わせて調整することになると思います。 ということで、ここでは admin 側をそのまま流用してファイルだけ作っておくことにします。

まず、先ほど作った

/app/views/layouts/admin.ctp
をそのまま同じ場所にコピーして、member.ctp にリネームします。
/app/views/layouts/member.ctp

そして左メニューの部分を以下のように編集します。

            <div class="actions">
                <h3><?php __('メニュー'); ?></h3>
                <ul>
                    <li><?php echo $this->Html->link(__('登録情報変更', true), array('controller'=>'members','action' => 'edit'));?></li>
                    <li><?php echo $this->Html->link(__('退会', true), array('controller' => 'members', 'action' => 'delete')); ?> </li>
                    <li><?php echo $this->Html->link(__('ログアウト', true), array('controller' => 'users', 'action' => 'logout')); ?> </li>
                </ul>
            </div>

member.ctp のヘッダー部の以下の部分、

<head>
    <?php echo $this->Html->charset(); ?>
    <title>
        <?php echo $title_for_layout; ?>
    </title>
    <?php
        echo $this->Html->meta('icon');

        echo $this->Html->css('cake.generic');

        echo $scripts_for_layout;
    ?>
</head>
cssを指定しているところを以下のように書き換えます。

        echo $this->Html->css('member');

これで出力されるソースは

<link href="/~myname/cake/css/member.css" type="text/css" rel="stylesheet">
となります。

css の置き場所は /app/webroot/css/ ですので、

/app/webroot/css/cake.generic.css
をそのまま同じ場所にコピーして、member.css にリネームします。

/app/webroot/css/memmber.css

最後に、admin の場合と、member の場合で、自動でレイアウトが切り替わるように設定します。

まず、前回 member_controller.php に入れた、

    function beforeFilter() {
        $this->layout = 'admin';
    }
を削除しておきます。

そして、/app 直下に app_controller.php を配置します。

cd Sites/cake
cp cake/libs/controller/app_controller.php app/

今コピーした、/app/app_controller.php に以下を追加します。

<?php
/**
 * Application level Controller
 *
 * This file is application-wide controller file. You can put all
 * application-wide controller-related methods here.
 *
 * PHP versions 4 and 5
 *
 * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
 * Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 *
 * @copyright     Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
 * @link          http://cakephp.org CakePHP(tm) Project
 * @package       cake
 * @subpackage    cake.cake.libs.controller
 * @since         CakePHP(tm) v 0.2.9
 * @license       MIT License (http://www.opensource.org/licenses/mit-license.php)
 */

/**
 * This is a placeholder class.
 * Create the same file in app/app_controller.php
 *
 * Add your application-wide methods in the class below, your controllers
 * will inherit them.
 *
 * @package       cake
 * @subpackage    cake.cake.libs.controller
 * @link http://book.cakephp.org/view/957/The-App-Controller
 */
class AppController extends Controller {

    function beforeFilter(){
        if (!empty($this->params['admin'])) {
            $this->layout = "admin";
        }else{
            $this->layout = "member";
        }
    }

}

これで admin の時と admin でない時でレイアウトが切り替わります。

2011-09-11

CakePHP1.3で作る会員管理システム(9) レイアウトその1

レイアウトその1

で、ついでなので管理画面っぽく、レフトメニューがあって作業を選べるようなページレイアウトに変更します。

/cake/libs/view/layouts/default.ctp がもともとのレイアウトファイルなので、これを開いて参考にしながら、自分の admin 用のレイアウト admin.ctp を作ります。 それは /app/views/layouts に保存します。

/app/views/layouts/admin.ctp

今まで add.ctp などをいじってきて気がついていると思いますが、フォームなどのメイン部分の下に、左メニュー部分が Actions として入っています。

つまり全体としては、こんな構造になっていて、

<div id="header"></div>
<div id="content">
    <div class="members form"></div>
    <div class="actions"></div>
</div>

各ページのテンプレート($content_for_layout の中に入る部分)が、
    <div class="members form"></div>
    <div class="actions"></div>
になっている、という訳です。

ということで、このactionsを分離して、レイアウトの方に入れてしまいます。

add.ctp から

<div class="actions">
 <h3><?php __('Actions'); ?></h3>
 <ul>

  <li><?php echo $this->Html->link(__('List Members', true), array('action' => 'index'));?></li>
  <li><?php echo $this->Html->link(__('List Types', true), array('controller' => 'types', 'action' => 'index')); ?> </li>
  <li><?php echo $this->Html->link(__('New Type', true), array('controller' => 'types', 'action' => 'add')); ?> </li>
  <li><?php echo $this->Html->link(__('List Favorites', true), array('controller' => 'favorites', 'action' => 'index')); ?> </li>
  <li><?php echo $this->Html->link(__('New Favorite', true), array('controller' => 'favorites', 'action' => 'add')); ?> </li>
 </ul>
</div>
この部分をカットし、admin.ctp (もともとはdefault.ctp)の $content_for_layout の下ににコピーします。

<?php
/**
 *
 * PHP versions 4 and 5
 *
 * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
 * Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 *
 * @copyright     Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
 * @link          http://cakephp.org CakePHP(tm) Project
 * @package       cake
 * @subpackage    cake.cake.libs.view.templates.layouts
 * @since         CakePHP(tm) v 0.10.0.1076
 * @license       MIT License (http://www.opensource.org/licenses/mit-license.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">
<head>
 <?php echo $this->Html->charset(); ?>
 <title>
  <?php __('CakePHP: the rapid development php framework:'); ?>
  <?php echo $title_for_layout; ?>
 </title>
 <?php
  echo $this->Html->meta('icon');

  echo $this->Html->css('cake.generic');

  echo $scripts_for_layout;
 ?>
</head>
<body>
 <div id="container">
  <div id="header">
   <h1><?php echo $this->Html->link(__('CakePHP: the rapid development php framework', true), 'http://cakephp.org'); ?></h1>
  </div>
  <div id="content">

   <?php echo $this->Session->flash(); ?>

   <?php echo $content_for_layout; ?>

   <div class="actions">
    <h3><?php __('Actions'); ?></h3>
    <ul>
     <li><?php echo $this->Html->link(__('List Members', true), array('action' => 'index'));?></li>
     <li><?php echo $this->Html->link(__('List Types', true), array('controller' => 'types', 'action' => 'index')); ?> </li>
     <li><?php echo $this->Html->link(__('New Type', true), array('controller' => 'types', 'action' => 'add')); ?> </li>
     <li><?php echo $this->Html->link(__('List Favorites', true), array('controller' => 'favorites', 'action' => 'index')); ?> </li>
     <li><?php echo $this->Html->link(__('New Favorite', true), array('controller' => 'favorites', 'action' => 'add')); ?> </li>
    </ul>
   </div>
  </div>
  <div id="footer">
   <?php echo $this->Html->link(
     $this->Html->image('cake.power.gif', array('alt'=> __('CakePHP: the rapid development php framework', true), 'border' => '0')),
     'http://www.cakephp.org/',
     array('target' => '_blank', 'escape' => false)
    );
   ?>
  </div>
 </div>
 <?php echo $this->element('sql_dump'); ?>
</body>
</html>

こんな感じになります。

そして、<div class="actions"> の中を書き換えます。

   <div class="actions">
    <h3><?php __('メニュー'); ?></h3>
    <ul>
     <li><?php echo $this->Html->link(__('会員管理', true), array('controller'=>'members','action' => 'index'));?></li>
     <li><?php echo $this->Html->link(__('担当者アカウント管理', true), array('controller' => 'users', 'action' => 'index')); ?> </li>
     <li><?php echo $this->Html->link(__('ログアウト', true), array('controller' => 'users', 'action' => 'logout')); ?> </li>
    </ul>
   </div>

で、一旦このレイアウトを確認します。 /app/controllers/members_controller.php を開いて、 var $name = 'Members'; の下に、

class MembersController extends AppController {

    var $name = 'Members';

    function beforeFilter() {
        $this->layout = 'admin';
    }
とレイアウトの指定を追加します。(上記5〜7行目)

ブラウザで見てみます。

CakePHP1.3で作る会員管理システム(8) フォームの見た目を変える

フォームの見た目を変える

ということで無事 members の会員登録部分が動くようになりました。 すぐに気づくことですが、デフォルトのテンプレートの見た目が気に入らないです。 会員の一覧画面はともかく、 /members/add の登録画面とか。 見た目の整形は最後にまとめてやっても構わない訳ですが、なんだかこのまま作業するのも気が重い。 ここでちょっといじりたい、というのが人情でしょう。 とりあえず、アクション add の場合で言うと、今自動生成されたテンプレートはこうなっています。

 <fieldset>
  <legend><?php __('Add Member'); ?></legend>
 <?php
  echo $this->Form->input('email');
  echo $this->Form->input('password');
  echo $this->Form->input('type_id');
  echo $this->Form->input('birthday');
  echo $this->Form->input('img1');
  echo $this->Form->input('img2');
  echo $this->Form->input('Favorite');
 ?>
 </fieldset>

fieldset とか legend が気に入らないですね。 ということで、本家サイトのドキュメントにあるとおり、デフォルトのテンプレートをコピーして、編集します。

9.3 Modify default HTML produced by "baked" templates

先に、/app/vendors/shells/templates/ 以下に、自分が作るテンプレート用のディレクトリを作っておきます。 ここでは

/app/vendors/shells/templates/mytheme
としました。

そして、

cd Sites/cake/cake/console/templates/default
cp -r views /Users/myname/Sites/cake/app/vendors/shells/templates/mytheme/views
として、デフォルトのテンプレートを自分のところへコピーします。

上記 views の中にある form.ctp を開いて、

 <fieldset>
  <legend><?php printf("<?php __('%s %s'); ?>", Inflector::humanize($action), $singularHumanName); ?></legend>
<?php
  echo "\t<?php\n";
  foreach ($fields as $field) {
   if (strpos($action, 'add') !== false && $field == $primaryKey) {
    continue;
   } elseif (!in_array($field, array('created', 'modified', 'updated'))) {
    echo "\t\techo \$this->Form->input('{$field}');\n";
   }
  }
  if (!empty($associations['hasAndBelongsToMany'])) {
   foreach ($associations['hasAndBelongsToMany'] as $assocName => $assocData) {
    echo "\t\techo \$this->Form->input('{$assocName}');\n";
   }
  }
  echo "\t?>\n";
?>
 </fieldset>
こうなっているところを、
 <h2><?php printf("<?php __('%s %s'); ?>", Inflector::humanize($action), $singularHumanName); ?></h2>
<?php
  echo "\t<?php\n";
  foreach ($fields as $field) {
   if (strpos($action, 'add') !== false && $field == $primaryKey) {
    continue;
   } elseif (!in_array($field, array('created', 'modified', 'updated'))) {
    echo "\t\techo \$this->Form->input('{$field}');\n";
   }
  }
  if (!empty($associations['hasAndBelongsToMany'])) {
   foreach ($associations['hasAndBelongsToMany'] as $assocName => $assocData) {
    echo "\t\techo \$this->Form->input('{$assocName}');\n";
   }
  }
  echo "\t?>\n";
?>
とします。

fieldset と legend を削除して、h2 に変えたってだけです。 再度 cake bake でビューを作り直します。

前回同様yes-noで進んでいくと、今度は、以下のようにテンプレートが選べるメニューが出ます。

---------------------------------------------------------------
You have more than one set of templates installed.
Please choose the template set you wish to use:
---------------------------------------------------------------
1. mytheme
2. default
Which bake theme would you like to use? (1/2) 
[1] > 1
自分が作ったテンプレートを選びます。

/app/views/members/add.ctp を確認すると、

 <h2>
  <?php __('Add Member'); ?></h2>
 <?php
  echo $this->Form->input('email');
  echo $this->Form->input('password');
  echo $this->Form->input('type_id');
  echo $this->Form->input('birthday');
  echo $this->Form->input('img1');
  echo $this->Form->input('img2');
  echo $this->Form->input('Favorite');
 ?>
自分用のテンプレートが適用されています。


CakePHP1.3で作る会員管理システム(7) Validate

Validate

ということで無事 members の会員登録部分が動くようになりました。
ブラウザから、

http://localhost/~myname/cake/members

を開いて、いろいろクリックしたりしてみます。

で、会員の新規登録

http://localhost/~myname/cake/members/add

をやってみると、custom エラーになって登録ができません。

最初に bake でモデルを作った時に、バリデーションの設定で、email と password に custom をセットして、その内容を入れていないのが原因です。
ということで validate をちゃんと作ります。

/app/models/member.php の、 今こうなっているところを

  'email' => array(
   'custom' => array(
    'rule' => array('custom'),
    //'message' => 'Your custom message here',
    //'allowEmpty' => false,
    //'required' => false,
    //'last' => false, // Stop validation after this rule
    //'on' => 'create', // Limit validation to 'create' or 'update' operations
   ),
   'email' => array(
    'rule' => array('email'),
    //'message' => 'Your custom message here',
    //'allowEmpty' => false,
    //'required' => false,
    //'last' => false, // Stop validation after this rule
    //'on' => 'create', // Limit validation to 'create' or 'update' operations
   ),
   'notempty' => array(
    'rule' => array('notempty'),
    //'message' => 'Your custom message here',
    //'allowEmpty' => false,
    //'required' => false,
    //'last' => false, // Stop validation after this rule
    //'on' => 'create', // Limit validation to 'create' or 'update' operations
   ),
  ),

以下のように書き換えます。

  'email' => array(
   'custom' => array(
    'rule' => array('isUnique'),
    'message' => 'このメールアドレスは既に登録されています',
    'on' => 'create', // Limit validation to 'create' or 'update' operations
   ),
   'email' => array(
    'rule' => array('email'),
    'message' => 'メールアドレスを正しく入力してください',
   ),
   'notempty' => array(
    'rule' => array('notempty'),
    'message' => 'メールアドレスを入力してください',
   ),
  ),

パスワードの部分は、以下のように書き換えます。

  'password' => array(
   'custom' => array(
    'rule' => array('custom', '/^[a-zA-Z0-9\_\-]{6,10}$/i'),
    'message' => 'パスワードは6文字以上10文字以内で入力してください',
   ),
   'notempty' => array(
    'rule' => array('notempty'),
    'message' => 'パスワードを入力してください',
   ),
  ),

ソース中に日本語が入りましたので文字コードをutf-8に設定して保存します。
会員の新規登録が出来ることを確認します。


http://localhost/~myname/cake/members/add

2011-09-09

CakePHP1.3で作る会員管理システム(6) bakeする(controller,view)

bakeする(controller,view)

次に bake のメニューから、 C を選んでコントローラを作ります。

> C
---------------------------------------------------------------
Bake Controller
Path: /Users/myname/Sites/cake/app/controllers/
---------------------------------------------------------------
Possible Controllers based on your current database:
1. Favorites
2. Members
3. MembersFavorites
4. Types
5. Users
Enter a number from the list above,
type in the name of another controller, or 'q' to exit  
[q] > 2

モデルの時と同様の選択画面になるので、

2. Members
を選択します。

---------------------------------------------------------------
Baking MembersController
---------------------------------------------------------------
Would you like to build your controller interactively? (y/n) 
[y] > y

とりあえずYESですね。

Would you like to use dynamic scaffolding? (y/n) 
[n] > n

スキャフォルドをするかどうかですが、しないのでNOです。

Would you like to create some basic class methods 
(index(), add(), view(), edit())? (y/n) 
[n] > y

indexとかaddとかの基本のアクションを作るかどうかですね。ここでYESです。

Would you like to create the basic class methods for admin routing? (y/n) 
[n] > y

adminルーティングを使うかどうかです。これはYESです。

Would you like this controller to use other helpers
besides HtmlHelper and FormHelper? (y/n) 
[n] > n
Would you like this controller to use any components? (y/n) 
[n] > n
Would you like to use Session flash messages? (y/n) 
[y] > y

---------------------------------------------------------------
The following controller will be created:
---------------------------------------------------------------
Controller Name:
 Members
---------------------------------------------------------------
Look okay? (y/n) 
[y] > y

その後はそのままです。 /app/controllers/members_controller.php が作られました。

次に、 V を選んでビューを作ります。

> V
---------------------------------------------------------------
Bake View
Path: /Users/myname/Sites/cake/app/views/
---------------------------------------------------------------
Possible Controllers based on your current database:
1. Favorites
2. Members
3. MembersFavorites
4. Types
5. Users
Enter a number from the list above,
type in the name of another controller, or 'q' to exit  
[q] > 2

やはり2番のMembersを選んで進みます。

Would you like bake to build your views interactively?
Warning: Choosing no will overwrite Members views if it exist. (y/n) 
[n] > y

ここはYESにします。

Would you like to create some CRUD views
(index, add, view, edit) for this controller?
NOTE: Before doing so, you'll need to create your controller
and model classes (including associated models). (y/n) 
[y] > y

ここもとりあえずYESにします。

Would you like to create the views for admin routing? (y/n) 
[n] > y

admin 用のviewを作るかどうかです。これもYESにします。

/app/views/ 以下に members というフォルダーが作られ、 index.ctp 他の各アクション用ビューが自動生成されます。 登録フォームの view は以下のようになりました。

<div class="members form">
<?php echo $this->Form->create('Member');?>
 <fieldset>
  <legend><?php __('Add Member'); ?></legend>
 <?php
  echo $this->Form->input('email');
  echo $this->Form->input('password');
  echo $this->Form->input('type_id');
  echo $this->Form->input('birthday');
  echo $this->Form->input('img1');
  echo $this->Form->input('img2');
  echo $this->Form->input('Favorite');
 ?>
 </fieldset>
<?php echo $this->Form->end(__('Submit', true));?>
</div>
<div class="actions">
 <h3><?php __('Actions'); ?></h3>
 <ul>

  <li><?php echo $this->Html->link(__('List Members', true), array('action' => 'index'));?></li>
  <li><?php echo $this->Html->link(__('List Types', true), array('controller' => 'types', 'action' => 'index')); ?> </li>
  <li><?php echo $this->Html->link(__('New Type', true), array('controller' => 'types', 'action' => 'add')); ?> </li>
  <li><?php echo $this->Html->link(__('List Favorites', true), array('controller' => 'favorites', 'action' => 'index')); ?> </li>
  <li><?php echo $this->Html->link(__('New Favorite', true), array('controller' => 'favorites', 'action' => 'add')); ?> </li>
 </ul>
</div>

users についても同じようにしてコントローラとビューを作ります。

2011-09-05

CakePHP1.3で作る会員管理システム(5) bakeする(model)

bakeする(model)

続いてbakeしてモデル、コントローラ、ビューを作ります。
作るんですが、その前にマスターにデータを入れておきます。
テーブルの types と favorites は、値が入ってるだけのマスターテーブルです。
今回これの管理機能は提供しません。
ということで最初にテーブルにデータを入れておきます。
別に普通にSQLのinsert文で入れておくだけです。

INSERT INTO types (id, name) VALUES (1, '一般');
INSERT INTO types (id, name) VALUES (2, '専門');
INSERT INTO favorites (id, name) VALUES (1, 'apple');
INSERT INTO favorites (id, name) VALUES (2, 'orange');
INSERT INTO favorites (id, name) VALUES (3, 'grape');

で、最初にmembersテーブルのモデルから作ります。

cd Sites/cake/app
cake bake
と打つと、
Welcome to CakePHP v1.3.11 Console
---------------------------------------------------------------
App : app
Path: /Users/myname/Sites/cake/app
---------------------------------------------------------------
Interactive Bake Shell
---------------------------------------------------------------
[D]atabase Configuration
[M]odel
[V]iew
[C]ontroller
[P]roject
[F]ixture
[T]est case
[Q]uit
What would you like to Bake? (D/M/V/C/P/F/T/Q) 
> 
と表示されて入力待ちになるので、 M を入れます。
> M
---------------------------------------------------------------
Bake Model
Path: /Users/myname/Sites/cake/app/models/
---------------------------------------------------------------
Possible Models based on your current database:
1. Favorite
2. Member
3. MembersFavorite
4. Type
5. User
Enter a number from the list above,
type in the name of another model, or 'q' to exit  
[q] >
となるのでここで2番を選びます。

あとは対話しながら選択していくだけです。

A displayField could not be automatically detected
would you like to choose one? (y/n) 
> y
displayField が自動で見つけられなかった、自分で選ぶかい? と聞かれるのでyesとすると、
1. id
2. email
3. password
4. type_id
5. birthday
6. img1
7. img2
8. created
9. modified
Choose a field from the options above:  
> 2
members テーブルのカラム名が一覧されるので、2番の email を選択します。

Would you like to supply validation criteria 
for the fields in your model? (y/n) 
[y] > y
続いてバリデーションの設定をする。
Field: id
Type: integer
---------------------------------------------------------------
Please select one of the following validation options:
---------------------------------------------------------------
1 - alphanumeric
2 - between
3 - blank
4 - boolean
5 - cc
6 - comparison
7 - custom
8 - date
9 - decimal
10 - email
11 - equalto
12 - extension
13 - inlist
14 - ip
15 - maxlength
16 - minlength
17 - money
18 - multiple
19 - notempty
20 - numeric
21 - phone
22 - postal
23 - range
24 - ssn
25 - time
26 - url
27 - userdefined
28 - uuid
29 - Do not do any validation on this field.
... or enter in a valid regex validation string.
  
[29] > 
というように、カラム id について1から29までのルールが表示されるので、その中から適用するルールを選びます。 [] 内に表示されているのはcakephpが推測する値です。 一回選ぶ度に、
Would you like to add another validation rule? (y/n) 
[n] > 
と、追加のルールがあるかどうか聞いてくるので、ルールは複数選べます。 とりあえず、
id:
29
email:
7, 10, 19
password:
7, 19
type_id:
20
birthday:
8
img1:
29
img2:
29
created:
29
modified:
29
と、番号を選択して進みます。

Would you like to define model associations
(hasMany, hasOne, belongsTo, etc.)? (y/n) 
[y] > 
関連づけをするかどうかの確認です。YESです。
One moment while the associations are detected.
---------------------------------------------------------------
Please confirm the following associations:
---------------------------------------------------------------
Member belongsTo Type? (y/n) 
[y] > y
Member hasAndBelongsToMany Favorite? (y/n) 
[y] > y
Would you like to define some additional model associations? (y/n) 
[n] > n
とすると、
---------------------------------------------------------------
The following Model will be created:
---------------------------------------------------------------
Name:       Member
DB Table:   `members`
Validation: Array
(
    [email] => Array
        (
            [custom] => custom
            [email] => email
            [notempty] => notempty
        )

    [password] => Array
        (
            [custom] => custom
            [notempty] => notempty
        )

    [type_id] => Array
        (
            [numeric] => numeric
        )

    [birthday] => Array
        (
            [date] => date
        )

)

Associations:
 Member belongsTo Type
 Member hasAndBelongsToMany Favorite
---------------------------------------------------------------
Look okay? (y/n) 
[y] > 
とでるので、yesすると、
Baking model class for Member...

Creating file /Users/myname/Sites/cake/app/models/member.php
Wrote `/Users/myname/Sites/cake/app/models/member.php`
モデルが作られました。

最後にtestも作っておきます。

SimpleTest is not installed. Do you want to bake unit test files anyway? (y/n) 
[y] > y

You can download SimpleTest from http://simpletest.org

Baking test fixture for Member...

Creating file /Users/myname/Sites/cake/app/tests/fixtures/member_fixture.php
Wrote `/Users/myname/Sites/cake/app/tests/fixtures/member_fixture.php`
Bake is detecting possible fixtures..

Creating file /Users/myname/Sites/cake/app/tests/cases/models/member.test.php
Wrote `/Users/myname/Sites/cake/app/tests/cases/models/member.test.php`

作成された /app/models/member.php の中味はこんな感じです。

class Member extends AppModel {
 var $name = 'Member';
 var $displayField = 'email';
 var $validate = array(
  'email' => array(
   'custom' => array(
    'rule' => array('custom'),
    //'message' => 'Your custom message here',
    //'allowEmpty' => false,
    //'required' => false,
    //'last' => false, // Stop validation after this rule
    //'on' => 'create', // Limit validation to 'create' or 'update' operations
   ),
   'email' => array(
    'rule' => array('email'),
    //'message' => 'Your custom message here',
    //'allowEmpty' => false,
    //'required' => false,
    //'last' => false, // Stop validation after this rule
    //'on' => 'create', // Limit validation to 'create' or 'update' operations
   ),
   'notempty' => array(
    'rule' => array('notempty'),
    //'message' => 'Your custom message here',
    //'allowEmpty' => false,
    //'required' => false,
    //'last' => false, // Stop validation after this rule
    //'on' => 'create', // Limit validation to 'create' or 'update' operations
   ),
  ),
  'password' => array(
   'custom' => array(
    'rule' => array('custom'),
    //'message' => 'Your custom message here',
    //'allowEmpty' => false,
    //'required' => false,
    //'last' => false, // Stop validation after this rule
    //'on' => 'create', // Limit validation to 'create' or 'update' operations
   ),
   'notempty' => array(
    'rule' => array('notempty'),
    //'message' => 'Your custom message here',
    //'allowEmpty' => false,
    //'required' => false,
    //'last' => false, // Stop validation after this rule
    //'on' => 'create', // Limit validation to 'create' or 'update' operations
   ),
  ),
  'type_id' => array(
   'numeric' => array(
    'rule' => array('numeric'),
    //'message' => 'Your custom message here',
    //'allowEmpty' => false,
    //'required' => false,
    //'last' => false, // Stop validation after this rule
    //'on' => 'create', // Limit validation to 'create' or 'update' operations
   ),
  ),
  'birthday' => array(
   'date' => array(
    'rule' => array('date'),
    //'message' => 'Your custom message here',
    //'allowEmpty' => false,
    //'required' => false,
    //'last' => false, // Stop validation after this rule
    //'on' => 'create', // Limit validation to 'create' or 'update' operations
   ),
  ),
 );
 //The Associations below have been created with all possible keys, those that are not needed can be removed

 var $belongsTo = array(
  'Type' => array(
   'className' => 'Type',
   'foreignKey' => 'type_id',
   'conditions' => '',
   'fields' => '',
   'order' => ''
  )
 );

 var $hasAndBelongsToMany = array(
  'Favorite' => array(
   'className' => 'Favorite',
   'joinTable' => 'members_favorites',
   'foreignKey' => 'member_id',
   'associationForeignKey' => 'favorite_id',
   'unique' => true,
   'conditions' => '',
   'fields' => '',
   'order' => '',
   'limit' => '',
   'offset' => '',
   'finderQuery' => '',
   'deleteQuery' => '',
   'insertQuery' => ''
  )
 );

}
よさそうな感じですね。

あとは同様にusersテーブルについても作っておきます。関連づけはなしですね。

class User extends AppModel {
 var $name = 'User';
 var $displayField = 'name';
 var $validate = array(
  'username' => array(
   'notempty' => array(
    'rule' => array('notempty'),
    //'message' => 'Your custom message here',
    //'allowEmpty' => false,
    //'required' => false,
    //'last' => false, // Stop validation after this rule
    //'on' => 'create', // Limit validation to 'create' or 'update' operations
   ),
  ),
  'password' => array(
   'notempty' => array(
    'rule' => array('notempty'),
    //'message' => 'Your custom message here',
    //'allowEmpty' => false,
    //'required' => false,
    //'last' => false, // Stop validation after this rule
    //'on' => 'create', // Limit validation to 'create' or 'update' operations
   ),
  ),
 );
}


ブログ アーカイブ

このブログを検索

Powered by Blogger.

ラベル

php (17) jQuery (13) OSX (10) MySQL (8) Javascript (7) Postgres (7) port (7) apache (6) Java (3) Smarty (2) html (2) pear (2) FCKEditor (1) XAMPP (1) css (1) git (1) perl (1) ruby (1)

Facebookバナー