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

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 配列に画像のパスを追加してるだけです。

このブログを検索

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バナー