2011年10月28日

Windows上でCodeigniterのユーザガイドをMakeする手順


個人的にCodeigniterの配布元(Repository)がMercuryからGitHubに移ったのは非常にありがたいんですが、ユーザガイドがSphinxとやらに移行したのはだいぶいただけないです。
GitHubのソースにはSphinx用のファイルが入ってるだけで、ユーザガイドを見たいならHTMLファイルを生成しないといけないらしい。面倒すぎる・・・
HTMLで配布してくれたままだったら楽だったのに・・・どうやらSphinxってもともとPythonの文書生成のためのものらしいですね。

だいぶ面倒だったのでSphinx用のソースファイルからHTMLファイルを生成するまでの手順を以下にメモしておきます。


Pythonのインストール
まず、WindowsにPythonをインストールします。
前述のとおりPHPなのになぜかPython用の文書生成アプリを使ってるので、Windowsには標準装備されてないPythonのインストールからやらないといけない。
Pythonを既にインストールしてる人はこの手順をスキップしてください。

Pythonバージョン2系をダウンロード。
3系はダメ(だそうです)。

Windows用のインストーラをダウンロードした場合はそのままインストール。
必須ではないですが、インストールが完了したら環境変数にPythonのパスを追加しておくと便利です。

環境変数への登録(XP)
コントロールパネル→システム→詳細設定→環境変数
Pathを選択してPythonのパスを追加。

eg) C:\Python27\;C:\Python27\Scripts;


登録が終わったらコマンドプロンプトで python と入力すれば確認できます。


easy-installをインストール
ez_setup.pyをダウンロード、コマンドラインで python ez_setup.py と入力すると勝手にインストールしてくれます(以下同文)。


Sphinxをインストール
コマンドラインで easy_install sphinx と入力。

Sphinxの設定
コマンドラインで sphinx-quickstart と入力すると会話形式の設定画面が表示されます。
基本的にEnterで進んで行って問題ないです。わかる人は変更してください。
'Project Name', 'Version Number','Author'が必須項目なので適当に。


sphinxcontrib-phpdomainをインストール
コマンドラインで easy_install sphinxcontrib-phpdomain と入力。


CI Lexer をインストール
Gitでコピーしたファイルの中に user_guide_src というフォルダがあるので、コマンドプロンプトで移動します。
さらにその中に cilexer があるので移動。
python setup.py install と入力。
インストールが終わったら pygmentize -L | more と入力して以下の記述があるか確認します。あればインストール完了。
* ci, codeigniter:
    CodeIgniter (filenames *.html, *.css, *.php, *.xml, *.static)


HTMLファイルを作成
上述 user_guide_src フォルダの中に source フォルダがあるので今度はこちらに移動。
make html と入力すると _build フォルダが作成されます。更にその中に doctrees, html とフォルダが作成され、そのうち html が作成されたユーザガイドです。
この html フォルダをコピー(もしくはカット)して Codeigniter のトップに貼り付け user_guide とフォルダ名を変更すれば完了。


ほんっと、お疲れさまでした!!
 

2011年9月1日

Codeigniterを使ってショッピングカートをゼロから作ってみよう。 # 7

ショッピングカートだけでなく、ほとんどのサイトで設定が必要になります。
今回はその設定をファイルに保存するロジックを作ります。

設定はDBに保存して必要な時にだけ呼び出してもいいんですが、アプリケーションを作っていく過程、もしくはリリースしたアプリケーションをアップデートする時に新しい機能を付けるなど、後で設定が増えていくことも考慮するとメンテナンスを楽にするならファイルだろうとの結論に達しました。DBアクセスは極力減らした方がいいですしね。
(もちろん作る前に使うであろう全ての設定項目を考えて一切追加しないなら別ですが・・・)

コントローラ:admin/application/controllers/setting.php
<?php
class Setting extends Controller
{
    function __construct()
    {
        parent::__construct();
        if( ! _s('admin')) redirect();
    }
    
    function index()
    {
        $this->form_validation->set_error_delimiters('<span class="error">', '</span>');
        $this->form_validation->set_rules('name',_l('SHOP_NAME'),'required|trim|xss_clean');
        $this->form_validation->set_rules('url',_l('SHOP_URL'),'required|trim|xss_clean');
        $this->form_validation->set_rules('title',_l('PAGE_TITLE'),'trim|xss_clean');
        $this->form_validation->set_rules('keywords',_l('PAGE_KEYWORDS'),'trim|xss_clean');
        $this->form_validation->set_rules('desc',_l('PAGE_DESCRIPTION'),'trim|xss_clean');
        if($this->form_validation->run())
        {
            $data['i'] = $this->input->post();
            
            $config_data = $this->load->view('setting/config_template', $data, TRUE);
            file_put_contents(EXTPATH.'config/'.((defined('ENVIRONMENT'))?ENVIRONMENT:'').'/setting.php', $config_data);
            setMessage('success', _l('SUCCESS_UPDATE_SETTING'));
            redirect('setting');
        }
        
        $data['setting'] = $this->config->item('setting');
        $data['breadcrumbs'] = array(_l('HOME')=>'', _l('SETTINGS')=>'settings');
        $data['h1'] = _l('SETTINGS_SHOP');
        $this->layout->view('setting/shop', $data);
    }
}


コントローラ22行目で読み込む設定ファイルテンプレート: admin/application/views/setting/config_template.php
<?php echo '<?php'.PHP_EOL; ?>
/*
 *  Setting Updated: <?php echo date('Y-m-d H:i:s').PHP_EOL; ?>
 */
    $config['site_name']        = '<?php echo $i['name']; ?>';
    $config['site_url']         = '<?php echo $i['url']; ?>';
    $config['page_title']       = '<?php echo $i['title']; ?>';
    $config['page_keywords']    = '<?php echo $i['keywords']; ?>';
    $config['page_desc']        = '<?php echo $i['desc']; ?>';

これで設定をアップデートするたびにsetting.phpを再作成することになります。
最後にconfig/autoload.phpにsettingを追加しておけば毎回設定を読み込んでくれます。

ビューファイルは設定項目が増えたらスクリーンショットを載せたいと思います。
今回はロジックのみでご勘弁をΣ(; ̄□ ̄A

2011年8月25日

Codeigniter on fluxflex


日本人が起業したfluxflexはGithubと連動する安くて簡単なクラウド・ホスティング・サービスを目指す
http://jp.techcrunch.com/archives/20110817github-integrated-fluxflex-aims-at-making-cloud-hosting-easier-and-cheaper/
(だいぶ亀ですが・・・)現在作成中のショッピングカートをどこかでデモさせてもらえないかと探していたら、タイミングよくTechCrunchに記事が掲載されていたので試してみました。
記事が出た次の日には一時アクセスできなくなる、プライベートURLが動かないなど、まだまだ色々とこれからのサービスのようですが同じ日本人が起業したということで頑張って欲しいですね!

fluxflexの構成がApache+FastCGIの組み合わせで、index.phpをURLから削除して動作させるのに少し苦労したので、メモしておきます。

■ .htaccessを使ってindex.phpをURLから削除する on fluxflex
コンフィグのuri_protocolをREQUEST_URIに変更
code/config.php
$config['uri_protocol'] = 'REQUEST_URI';

.htaccessにRewriteBaseを追加(2行目)。
.htaccess
RewriteEngine on
RewriteBase /
RewriteCond $1 !^(index\.php|robots\.txt|favicon\.ico)
RewriteRule ^(.*)$ /index.php?/-- default controller --$1 [L]
そして最後にRewriteRuleにCodeigniterのdefault_controllerを指定して動作させることができました。
(一定の条件下では指定しなくても動作するようですが、指定した方が確実に動きます。)


fluxflexが安定稼動し、ショッピングカーとがもう少し見れるようになったらデモ用のURLをこのブログで公開します(^_^)

2011年8月19日

Codeigniter と オートロード(Autoload in PHP5)

※注:この記事はCodeigniter付属のautoloadに関するものではありません。

PHP5にはクラスのオートローディングという便利な関数(function __autoload())があります。
未定義のクラス/インターフェイスを使用しようとした時に 自動的にコールされる __autoload 関数を定義することができます。 この関数をコールすることにより、 スクリプトエンジンは、PHPがエラーで止まる前にクラスをロードする最後の チャンスを与えます。
引用元:php.net
これを使ってモデルを呼べれば楽なんですよね。そうすると
$this->load->model('model-name');
$this->model-name->function();
という書き方から以下のように普通のPHP5の書き方に変えられる。
$model = new Model-name;
$model->function();

そこで軽く調査してみたんですが、どうやら以下のサイトのような方法が多数散見されました。
6 CodeIgniter Hacks for the Masters
http://net.tutsplus.com/tutorials/php/6-codeigniter-hacks-for-the-masters/
ほうほう、config/config.phpの最後に__autoload()を入れるとな。

・・・いやいやいや、これダメだろ(゜Д゜) ??
config.phpの最後になんか入れたらログとかベンチマークとか全部こっちいっちゃうんじゃ?
ということで念のためテスト。config.phpの最後にコピーしてlog_messageで何をロードしようとしてるかチェック。

config/config.php
function __autoload($class)
{
    log_message('debug', 'Trying to load class: '.$class);
    if(file_exists(EXTPATH."models/".strtolower($class).EXT))
    {
        include_once(EXTPATH."models/".strtolower($class).EXT);  
    }
}


■ 結果

思った通り。load_class()で呼んでるものは全部オートロードに行っちゃう。この方法は採用しちゃダメな方法です。

それならどうすればいいのか?
正解はspl_autoload_registerを使う、です(断言しちゃったけど大丈夫かな?w)

オートローディングを追加するなら、ヘルパにしてautoload.phpに追加するのが一番簡単でいいんじゃないでしょうか。
(個人的にはファイルが増えるの嫌なのでLoader.phpにstaticメソッドを作ってcore/Controller.phpの最後に追加してますが)

■ spl_autoload_registerをコールするヘルパ
spl_autoload_register — 指定した関数を __autoload() の実装として登録する
http://nz.php.net/manual/ja/function.spl-autoload-register.php
引用元:php.net
サンプルヘルパ: spl_autoload_helper.php
<?php
/**
 * SAMPLE SPL Autoload Helper
 * ファイル名、メソッド名は適宜修正してください。
 */
 
function sample_loader($class) 
{
	if(file_exists(APPPATH."models/".strtolower($class).EXT))
	{
		log_message('debug', 'Load '.$class.' models in php5 style');
		include_once(APPPATH."models/".strtolower($class).EXT);  
	}
}
 
spl_autoload_register('sample_loader');
上記ではモデルだけ追加してますが、ライブラリもヘルパも追加することが可能です。
$this->load->model(~~~) とか面倒な人向けの方法でした。

2011年8月16日

Codeigniterを使ってショッピングカートをゼロから作ってみよう。 # 6

今回はユーザ管理です。
ユーザの一覧表示、新規作成(追加)、編集、最後に削除です。
苦手なjavascriptで手間取って時間かかりました( ̄Д ̄;;


■ File Tree


■ コントローラ:User
admin/application/controllers/user.php
<?php
class User extends Controller
{
    function __construct()
    {
        parent::__construct();
        if( ! _s('admin')) redirect();
        $this->load->model('users_model', 'usersdb');
    }
    
    function index()
    {
        $this->accounts();
    }
    
    function accounts($page=0)
    {
        $this->form_validation->set_rules('delete', '', 'isArray');
        if($this->form_validation->run())
        {
            $this->usersdb->delete_array($this->input->post('delete'));
        }
        
        $data['breadcrumbs'] = array(_l('HOME')=>'', _l('USERLIST')=>'user');
        $data['h1'] = _l('USERLIST');
        $data['all'] = $this->usersdb->all($page);
        $this->layout->view('user/accounts', $data);
    }
    
    function create()
    {
        $this->form_validation->set_error_delimiters('<span class="error">', '</span>');
        $this->form_validation->set_rules('email',_l('EMAIL'),'required|valid_email|email_exist');
        $this->form_validation->set_rules('firstname',_l('FIRSTNAME'),'required|min_length[2]');
        $this->form_validation->set_rules('lastname',_l('LASTNAME'),'required|min_length[2]');
        $this->form_validation->set_rules('passwd',_l('PASSWORD'),'required|min_length[6]|matches[passwdconf]');
        if($this->form_validation->run())
        {   // insert into db
            $data = array(
                'email'     => $this->input->post('email'),
                'password'  => $this->input->post('passwd'),
                'firstname' => $this->input->post('firstname'),
                'lastname'  => $this->input->post('lastname'),
                'status'    => $this->input->post('status')
            );
            
            $this->usersdb->newuser($data);
            
            redirect('user');
        }

        $data['breadcrumbs'] = array(_l('HOME')=>'', _l('USERLIST')=>'user', _l('CREATE_USER')=>'user/create');
        $data['h1'] = _l('CREATE_USER');
        $this->layout->view('user/create', $data);
    }
    
    function edit($id)
    {   // just in case
        if($user = $this->usersdb->byId($id))
        {
            $this->form_validation->set_error_delimiters('<span class="error">', '</span>');
            $this->form_validation->set_rules('email',_l('EMAIL'),'required|valid_email|email_exist['.$id.']');
            $this->form_validation->set_rules('firstname',_l('FIRSTNAME'),'required|min_length[2]');
            $this->form_validation->set_rules('lastname',_l('LASTNAME'),'required|min_length[2]');
            if($this->input->post('passwd')) $this->form_validation->set_rules('passwd',_l('PASSWORD'),'required|min_length[6]|matches[passwdconf]');

            if($this->form_validation->run())
            {   // update user info
                $data['email'] = $this->input->post('email');
                $data['firstname'] = $this->input->post('firstname');
                $data['lastname'] = $this->input->post('lastname');
                $data['status'] = $this->input->post('status');
                if($this->input->post('passwd')) $data['password'] = $this->input->post('password');
                
                $this->usersdb->update($user, $data);
                
                redirect('user');
            }

            $data['breadcrumbs'] = array(_l('HOME')=>'', _l('USERLIST')=>'user', _l('EDIT_USER')=>'user/edit');
            $data['h1'] = _l('EDIT_USER');
            $data['user'] = $user;
            $this->layout->view('user/edit', $data);
        }
        else redirect();
    }
}
□ メソッド
index ( accounts )で一覧表示と削除。
create で新規ユーザ作成
edit で編集しています。

■ モデル:Users_model.php
extension/models/users_model.php
function newuser($data)
    {
        $data['hash'] = $this->auth->_generate_hash();
        $data['password'] = $this->auth->_encode($data['password'], $data['hash']);
        $this->db->insert($this->table, $data);
        setMessage('success', _l('SUCCESS_CREATE_USER'));
    }
    
    function update($obj, $data)
    {
        if(array_key_exists('password', $data)) $data['password'] = $this->auth->_encode($data['password'], $obj->hash);
        $this->db->where('id', $obj->id);
        $this->db->update($this->table, $data);
        setMessage('success', _l('SUCCESS_UPDATE_USER'));
    }
ユーザ追加用とアップデート用のメソッドを追加。

■ Form_validationライブラリにルール追加
extension/libraries/MY_Form_validation.php
<?php
class MY_Form_validation extends Form_validation
{
    function __construct()
    {
        parent::__construct();
    }
    
    function email_exist($email, $id=FALSE)
    {
        $this->CI->form_validation->set_message('email_exist', $this->CI->lang->line('in_use'));
        $this->CI->load->model('users_model', 'usersdb');
        return ($this->CI->usersdb->exist($email, $id)) ? FALSE : TRUE;
    }
    
    function isArray($arr)
    {
        $this->CI->form_validation->set_message('isArray', $this->CI->lang->line('is_array'));
        return (is_array($arr) && count($arr) > 0) ? TRUE : FALSE;
    }
}

■ ビュー:一覧表示&削除
admin/application/views/user/accounts.php
<div class="buttons">
<button onclick="javascript:location.href = '<?php echo base_url('user/create'); ?>';"><?php echo _l('CREATE_USER'); ?></button>
<button onclick="confirmDelSelected();"><?php echo _l('DELETE_SELECTED'); ?></button>
</div>
<?php echo form_open(uri_string()); ?>
<?php if($all): ?>
<table class="list">
<tr>
    <th><input type="checkbox" onclick="applyToAll(this);" /></th>
    <th><?php echo _l('FULLNAME'); ?></th>
    <th><?php echo _l('EMAIL'); ?></th>
<?php /*    <th><?php echo _l('ROLE'); ?></th> */ ?>
    <th><?php echo _l('STATUS'); ?></th>
    <th><?php echo _l('ACTION'); ?></th>
</tr>
<?php foreach($all as $a): ?>
<tr>
    <td><input type="checkbox" name="delete[]" value="<?php echo $a->id; ?>"<?php if($a->id==1): ?> disabled="disabled"<?php endif; ?>></td>
    <td><?php echo $a->firstname.' '.$a->lastname; ?></td>
    <td><?php echo $a->email; ?></td>
<?php /*    <td><?php echo $a->role; ?></td> */ ?>
    <td><?php echo ($a->status) ? _l('ACTIVE') : _l('INACTIVE') ; ?></td>
    <td>
        <a href="<?php echo base_url('user/edit/'.$a->id); ?>"><?php echo _l('EDIT'); ?></a>
    </td>
</tr>
<?php endforeach; ?>
</table>
<?php endif; ?>
<?php echo form_close(); ?>

<script type="text/javascript">
function confirmDelSelected() {
    var c = 0;            
    $(":checkbox").each(function(){
        if($(this).attr('checked')) c++;
    })
    if(c > 0) { 
        if(confirm("<?php echo _l('CONFIRM_DELETE_SELECTED'); ?>")) $('form').submit();
    }
    else { alert("<?php echo _l('ALERT_PLZ_SELECT'); ?>"); }
}
</script>



全くスタイルしてない画面ですが(苦)
後でユーザレベル(タイプ)なんかを付け加えることになるでしょう。

削除する時には確認用のポップアップが表示されます。



■ ビュー:新規作成
admin/application/views/user/create.php
<?php echo form_open(uri_string()); ?>
<input type="button" value="<?php echo _l('CANCEL'); ?>" onclick="javascript:location.href = '<?php echo base_url('user'); ?>';" tabindex="9" />

<h2><?php echo _l('USER_DETAILS'); ?></h2>
<dl class="horizontal">
    <dt><?php echo _l('EMAIL'); ?></dt>
    <dd><input class="field" id="email" type="email" name="email" value="<?php echo set_value('email'); ?>" tabindex="1" /><?php echo form_error('email'); ?></dd>
    <dt><?php echo _l('LASTNAME'); ?></dt>
    <dd><input class="field" id="lastname" type="text" name="lastname" value="<?php echo set_value('lastname'); ?>" tabindex="2" /><?php echo form_error('lastname'); ?></dd>
    <dt><?php echo _l('FIRSTNAME'); ?></dt>
    <dd><input class="field" id="firstname" type="text" name="firstname" value="<?php echo set_value('firstname'); ?>" tabindex="3" /><?php echo form_error('firstname'); ?></dd>
    <dt><?php echo _l('PASSWORD'); ?></dt>
    <dd><input class="field" id="passwd" type="password" name="passwd" value="" tabindex="4" /><?php echo form_error('passwd'); ?></dd>
    <dt><?php echo _l('PASSWORDCONF'); ?></dt>
    <dd><input class="field" id="passwdconf" type="password" name="passwdconf" value="" tabindex="5" /></dd>
    <dt><?php echo _l('STATUS'); ?></dt>
    <dd>
        <select name="status" class="field" tabindex="6">
            <option value="1"><?php echo _l('ACTIVE'); ?></option>
            <option value="0"><?php echo _l('INACTIVE'); ?></option>
        </select>
    </dd>
    
    <dd><input type="submit" value="<?php echo _l('SAVE'); ?>" onclick="return check(['email','lastname','firstname','passwd','passwdconf']);" tabindex="7" /> <input type="reset" value="<?php echo _l('RESET'); ?>" tabindex="8" /></dd>
</dl>
<?php echo form_close(); ?>



javascriptは非常に簡単なチェックだけですが、javascriptとphpの両方で入力チェックしてます。
入力しないとこんな感じにハイライトされます。




■ ビュー:編集
admin/application/views/user/edit.php
<?php echo form_open(uri_string()); ?>
<input type="button" value="<?php echo _l('CANCEL'); ?>" onclick="javascript:location.href = '<?php echo base_url('user'); ?>';" tabindex="9" />

<h2><?php echo _l('USER_DETAILS'); ?></h2>
<dl class="horizontal">
    <dt><?php echo _l('EMAIL'); ?></dt>
    <dd><input class="field" id="email" type="email" name="email" value="<?php echo $user->email; ?>" tabindex="1" /><?php echo form_error('email'); ?></dd>
    <dt><?php echo _l('LASTNAME'); ?></dt>
    <dd><input class="field" id="lastname" type="text" name="lastname" value="<?php echo $user->lastname; ?>" tabindex="2" /><?php echo form_error('lastname'); ?></dd>
    <dt><?php echo _l('FIRSTNAME'); ?></dt>
    <dd><input class="field" id="firstname" type="text" name="firstname" value="<?php echo $user->firstname; ?>" tabindex="3" /><?php echo form_error('firstname'); ?></dd>
    <dt><?php echo _l('PASSWORD'); ?></dt>
    <dd><input class="field" id="passwd" type="password" name="passwd" value="" tabindex="4" /><?php echo form_error('passwd'); ?></dd>
    <dt><?php echo _l('PASSWORDCONF'); ?></dt>
    <dd><input class="field" id="passwdconf" type="password" name="passwdconf" value="" tabindex="5" /></dd>
    <dt><?php echo _l('STATUS'); ?></dt>
    <dd>
        <select name="status" class="field" tabindex="6">
            <option value="1"<?php if($user->status==1): ?> selected="selected"<?php endif; ?>><?php echo _l('ACTIVE'); ?></option>
            <option value="0"<?php if($user->status==0): ?> selected="selected"<?php endif; ?>><?php echo _l('INACTIVE'); ?></option>
        </select>
    </dd>
    
    <dd><input type="submit" value="<?php echo _l('SAVE'); ?>" onclick="return check(['email','lastname','firstname']);" tabindex="7" /> <input type="reset" value="<?php echo _l('RESET'); ?>" tabindex="8" /></dd>
</dl>
<?php echo form_close(); ?>



編集画面も似たようなものです。パスワードが入力されてない場合はスルーします。
パスワードのエラーはphpでのみ表示。javascriptは苦手・・・


だいぶ長くなってしまったので、今日はここまでにします。

2011年8月11日

Codeigniterを使ってショッピングカートをゼロから作ってみよう。 # 5

前回ユーザ登録やりますと言って実際作り始めたんですが①ユーザ一覧表示②ユーザ登録ボタンをクリックしたら登録画面という流れを考えると、ユーザ一覧表示した時にナビを表示する必要あるんじゃ・・・

ということでユーザ登録の前にナビ用のパンくずリストを作ります。


■ パンくずリスト( Breadcrumb )
まずパンくずHTMLを返すメソッドをヘルパーに作成。
extension/helpers/html_helpers.php
<?php
function breadcrumb($array)
{
    $count = count($array); $i = 1;
    $html = '<div id="breadcrumb"><ul>';
    foreach($array as $k=>$v)
    {
        $class = (empty($v)) ? ' class="home"' : '';
        if($i != $count)
            $html .= '<li><a href="'.base_url($v).'"'.$class.'>'.$k.'</a></li>';
        else    // no link for the last one
            $html .= '<li class="last"><span>'.$k.'</span></li>';
        $i++;
    }
    $html .= '</ul></div>';
    
    return $html;
}

コントローラーにパンくず用の配列を作成。
admin/application/controllers/users.php
<?php
class User extends Controller
{
    function __construct()
    {
        parent::__construct();
        if( ! _s('admin')) redirect();
    }
    
    function index()
    {
        $this->accounts();
    }
    
    function accounts($page=0)
    {
        $data['breadcrumbs'] = array(_l('HOME')=>'', _l('USERLIST')=>'admin/user');
        $this->layout->view('user/accounts', $data);
    }
}

レイアウトにパンくず表示ラインを追加
admin/application/views/_layouts/main.php
<?php if(isset($breadcrumbs) && is_array($breadcrumbs)) echo breadcrumb($breadcrumbs); ?>


見た目はAppleのようなパンくずリストにしてみました。




■ 最後に
この方法だとパンくずを表示するなら全てのメソッド内に$data['breadcrumbs']を追加しなければいけないんですよね。
他に良いやり方を思いつかなかったのですが、正直このパンくずリストあまりいい方法ではないと思います。
「こうしたほうが良いよ~」とアイデアのある方は是非アドバイスください。

2011年8月10日

Codeigniterを使ってショッピングカートをゼロから作ってみよう。 # 4

前回フロントエンドとバックエンドを分けたので、そこに認証機能をつけます。




■ ユーザに関する考察
ショッピングカートにはサイトの管理者と登録ユーザという2種類のユーザがいると思います。
サイトを運営するのは個人であったり会社であったりしますが、管理者が1人じゃないこともあるでしょう。そう考えると登録ユーザ(お客さん)と管理ユーザはわけたほうが良いかもしれません。

そこで登録ユーザをCustomer、管理ユーザをUserと位置づけ開発を続けていきたいと思います。


■ Usersテーブル作成
まず管理ユーザ用のDBテーブルを作成します。
(とりあえず現在のところは最低限必要な情報で構成しています。)

CREATE TABLE IF NOT EXISTS `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `email` varchar(50) COLLATE utf8_unicode_ci NOT NULL,
  `password` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `firstname` varchar(50) COLLATE utf8_unicode_ci NOT NULL,
  `lastname` varchar(50) COLLATE utf8_unicode_ci NOT NULL,
  `status` tinyint(4) NOT NULL,
  `role` int(11) NOT NULL,
  `hash` varchar(32) COLLATE utf8_unicode_ci NOT NULL,
  `lastlogin` timestamp NULL DEFAULT NULL,
  `updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `email` (`email`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ;



■ Authライブラリ作成
Codeigniterでは有名(?)なDX Authなどのライブラリの使用も考慮しましたが、結局どれを使っても用途に合わせてカスタマイズしないといけないので自作します。
△要件△
- メールアドレスとパスワードで認証。ユーザ名は別途考慮。
- パスワード暗号化
- UserとCustomerの別認証

/extension/libraries/Auth.php
<?php
class Auth
{
    var $_error;

    public function __construct()
    {
        $this->ci =& get_instance();
    }
        
    function error()
    {
        return $this->_error;
    }
    
    function login($email, $passwd, $target='customer')
    {
        if($target === 'user')
        {   // load user model
            $this->ci->load->model('users_model','usersdb');
            // check if exists
            if($user = $this->ci->usersdb->exist($email))
            {   // retrieve user hash & matching up with entered password
                if($this->_encode($passwd, $user->hash) === $user->password)
                {   // update last login
                    $this->ci->usersdb->lastlogin($user->id);
                    // set admin session
                    $this->ci->session->set_userdata('admin',TRUE);
                    
                    return TRUE;
                }
                else $this->_error = _l('error_wrong_combination');
            }
            else $this->_error = _l('error_not_exist');
        }
        
        return FALSE;
    }
    
    private function _encode($passwd, $hash)
    {
        /* 暗号化スクリプト */
        
        return $passwd;
    }
}

/extension/models/users_model.php
<?php
class Users_model extends Model
{
    var $table = 'users';
    
    function __construct()
    {
        parent::__construct();
    }
    
    function exist($email)
    {
        $q = $this->db->get_where($this->table, array('email'=>$email));
        return ($q->num_rows() == 1) ? $q->row() : FALSE;
    }
    
    function lastlogin($id)
    {
        $this->db->where('id',$id);
        $this->db->update($this->table, array('lastlogin'=>date('Y-m-d H:i:s')));
    }
}



パスワードはユーザごとにハッシュを作成し、一定の法則で入力パスワードとマッチングさせてます。
法則は_encodeメソッド内に好きなように指定してください。
ユーザごとのハッシュは登録時に作成しないといけないので、それは次回に。

とりあえずのところ認証ライブラリ(Auth)はできたので、テストユーザを直接DBに作ってテストしてみます。

DBにテストユーザ追加
INSERT INTO `users` (`id`, `email`, `password`, `firstname`, `lastname`, `status`, `role`, `hash`, `lastlogin`, `updated`) VALUES
(1, 'test@test.com', '723e66f900dcb555d089050c80455331feb2b7ca', 'firstname', 'lastname', 0, 0, '5898e88516d636dbd3571c7d24550f67', '2011-08-04 10:49:04', '2011-08-04 10:49:04');


■ autoload変更
認証でデータベース、およびセッションを使うようになるのでconfig/autoload.phpを以下のように変更します。
$autoload['libraries'] = array('Auth','database','Form_validation','Session');


■ コントローラ
admin/application/controllers/login.php
<?php
class Login extends Controller
{
    function __construct()
    {
        parent::__construct();
        if(_s('admin')) redirect();
    }
    
    function index()
    {
        $this->form_validation->set_rules('email',_l('EMAIL'),'required|trim|valid_email');
        $this->form_validation->set_rules('password', _l('PASSWORD'), 'required|trim');
        if($this->form_validation->run())
        {
            $login = $this->auth->login($this->input->post('email'), $this->input->post('password'), 'user');
            if($login)  redirect();
            else $data['error'] = $this->auth->error();
        }
        else $data['error'] = validation_errors();
        
        $data['css'] = array('login');
        $data['page_title'] = _l('CONTROLPANEL');
        $this->layout->view('login', $data);
    }
}


■ ビュー
admin/application/view/login.php
<?php echo form_open(uri_string()); ?>
<div>
<h1><?php echo _l('CONTROLPANEL'); ?></h1>
<p><?php echo _l('MSG_LOGIN'); ?></p>
<dl>
    <dt><?php echo _l('EMAIL'); ?></dt>
    <dd><input type="email" name="email" value="<?php echo set_value('email'); ?>" /></dd>
    <dt><?php echo _l('PASSWORD'); ?></dt>
    <dd><input type="password" name="password" value="" /></dd>
    <dd><input type="submit" value="<?php echo _l('LOGIN'); ?>"></dd>
    <?php if(isset($error)): ?><dd><?php echo $error; ?></dd><?php endif; ?>
</dl>
</div>
<?php echo form_close(); ?>

これで準備は完了です。


■ログイン画面
どうせだったらと簡単にデザインしてみました。デザインは苦手なんですよねー(^_^;;



上で追加したユーザでログインしてみます。


うん、ちゃんと admin セッションが登録されていますね。
ログインした時に登録するべきセッションは admin だけではないですが、それは必要に応じて追加して行きたいと思います。


次回はユーザ登録をやります。

2011年7月28日

Codeigniterを使ってショッピングカートをゼロから作ってみよう。 # 3

テンプレート化の下地ができたので、調子に乗ってそのままバックエンドの分離に着手します。
テンプレート化はこちら


■フロントエンドとバックエンド(管理画面)の分離


一般的にフロントエンドとバックエンドはデザインが異なることが多いですし、機能的にも違ったものになります。そう考えると分離させるのはそれほど悪い考えでもないでしょう(であって欲しいw)

/admin/index.php./index.php をほぼコピーした感じです。

/admin/index.php
<?php
define('EXT', '.php');
define('FCPATH', __FILE__);
$pathinfo = pathinfo(FCPATH);
define('SELF', $pathinfo['basename']);
define('ROOT',realpath($pathinfo['dirname'].'/../').'/');
unset($pathinfo);

define('SYSPATH', ROOT.'system/');
define('EXTPATH', ROOT.'extension/');
define('APPPATH', ROOT.'admin/application/');

$f=pathinfo(__FILE__, PATHINFO_BASENAME);
if (!is_dir(SYSPATH))  exit("Your system folder path does not appear to be set correctly. Please open the following file and correct this: ".$f);
if (!is_dir(APPPATH))   exit("Your application folder path does not appear to be set correctly. Please open the following file and correct this: ".$f);
if (!is_dir(EXTPATH)) exit("Your extension folder path does not appear to be set correctly. Please open the following file and correct this: ".$f);

define('ENVIRONMENT', 'development');

if (defined('ENVIRONMENT'))
{
    switch (ENVIRONMENT)
    {
        case 'development':
            error_reporting(E_ALL);
        break;
    
        case 'testing':
        case 'production':
            error_reporting(0);
        break;

        default:
            exit('The application environment is not set correctly.');
    }
}

require(SYSPATH.'core/Bootstrap'.EXT);


ここで問題になってしまったのがLayoutライブラリです。
./index.phpTEMPATHを設定していたため$this->layout->view()が全てTEMPATHに向いてしまっていました。

そこで両方のindex.phpにADMIN定数を設定し、ADMIN=TRUEの場合にはTEMPATHに向かないように/extension/core/Layout.phpを変更しました。
(毎回コントローラで$this->layout->set('admin');とやるのは無駄ですからね…)

./indexphp
define('ENVIRONMENT', 'development')直前

define('ADMIN', FALSE);

/admin/indexphp
define('ENVIRONMENT', 'development')直前

define('ADMIN', TRUE);

/extension/core/Layout.php
18行目

if(! ADMIN) $load->set_view_path(TEMPATH.$template.'/');


■ コントローラの設定
admin/application/controllers/home.php
<?php
class Home extends Controller
{
    function index()
    {
        $this->layout->view('home');
    }
}


■ ビューの設定
admin/application/views/home.php
this is admin directory - <?php echo __FILE__; ?>


■ レイアウトの設定
admin/application/views/_layout/main.php
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Admin Template</title>
</head>
<body>

<?php echo $contents; ?>

<p><br />Page rendered in {elapsed_time} seconds</p>

</body>
</html>


■ てすと
http://localhost/lsc/admin/ にアクセスしてみます。


ちゃんとアクセスできてます。大丈夫そうです。
無事に分けられたところで、次はアクセス制御かな?

2011年7月25日

Codeigniterを使ってショッピングカートをゼロから作ってみよう。 # 2

さてインストールが終わったので目的に合わせて色々と変更していきます。
#1 インストールはこちらからどうぞ。



■ テンプレート化
デザインを拡張したり、変更したりできる機能は人に使ってもらうことを考えると外せないですよね。
もちろん、簡単に拡張できれば使ってくれる人も多くなるでしょうし。


そこで初期設定では application/views に設定されているview用のディレクトリを templates ディレクトリに移します。HTML/CSSなどのデザイン部分は1つにまとめられて楽になるんじゃないかな。(ダメそうなら後で変更しますw)



後で変更できるようにindex.phpTEMPATHを追加。

define('TEMPATH', ROOT.'templates/');


■ Layoutライブラリ
当然templatesディレクトリに移しただけでは使えないので、ライブラリを1つ追加します。

extension/core/Layout.php
<?php
class Layout 
{
    var $layout;
    
    function __construct()
    {
        $this->layout = config_item('layout_default_file');
        log_message('debug', 'Layout Class Initialized');
    }
    
    function set($layout) { $this->layout = $layout; }
    
    function view($view = '' , $data = array(), $template = 'default', $return = FALSE)
    {
        $load =& load_class('Loader','core');
        // set template path as view path if not admin
        if($template !== 'admin') $load->set_view_path(TEMPATH.$template.'/');
        // load contetns in side of the layout
        $view_data['contents'] = $load->view($view, $data, TRUE);
        // load layout directory config
        $this->layout = config_item('layout_directory').$this->layout;

        // then finaly load layout
        return $load->view($this->layout, $view_data, $return);
    }
}


ライブラリ内で使われている設定をconfig/config.phpに2つ追加。
$config['layout_default_file']  = 'main';
$config['layout_directory']     = '_layouts/';
1つ目はLayoutライブラリがロードされた時にレイアウトファイルが指定されなかった場合に自動的に読み込まれるファイル名を指定します。
2つ目はディレクトリを分けたい際に指定できます。空欄も可。


■ Loaderクラス拡張
ビューファイルのパスを指定するのにLoaderクラスを拡張します。

extension/core/MY_Loader.php
<?php
class MY_Loader extends Loader
{
    /**
     * set view file path
     *
     * This function sets the location of view file.
     *
     * @access    public
     */
    public function set_view_path($path, $absolute=FALSE)
    {
        $this->_ci_view_path = ($absolute) ? ROOT.$path : $path;
    }
}
これで_ci_view_pathはtemplatesディレクトリに向きます。


■ Layoutライブラリ読み込み
最後にライブラリを読み込みます。
普通にconfig/autoload.phpに追加でもいいんですが、Layoutライブラリは常に使うことになるし、1行だけなのでsystem/core/Controller.phpに書き込んじゃいました。

$this->layout =& load_class('Layout', 'core');

Codeigniterのアップデートの時に面倒とか、わからない人はconfig/autoload.phpに追加してくださいね。

$autoload['libraries'] = array('Layout');



■ てすと

コントローラ:application/controllers/home.php
<?php
class Home extends Controller
{
    function __construct()
    {
        parent::__construct();
    }
    
    function index()
    {
        $this->layout->view('home/index');
    }
}

ビュー:templates/default/home/index.php
<?php echo __FILE__; ?>


結果:


大丈夫そうです(^_^v
これでテンプレート化の下地ができまた。

2011年7月24日

Codeigniterを使ってショッピングカートをゼロから作ってみよう。 # 1

■ 環境設定&インストール

本来なら開発は本番環境と同じ状態でやるのが一番良いのですが、ネットがない環境でも作業ができるようにローカルに開発環境をセットアップします。

まずWampをダウンロードしてDドライブにインストール。インストールする場所はご自由に。
www.wampserver.com

D:\wamp\www
がlocalhost(以下、ローカル)になります。


次にCodeigniterをダウンロード。
www.codeigniter.com/downloads

ダウンロードしたファイルを解凍してローカル配下に設置。
同梱されているuser_guideは開発自体には必要ないので別の場所に保管します。

開発ディレクトリ:
D:\wamp\www\lsc

マニュアルディレクトリ:
D:\wamp\www\manual\ci


とりあえずは、これで環境設定とインストールできました。


■ 微調整

初期設定でCodeigniterはsystemとapplicationの2つのディレクトリに別れてます。
これを少しだけ以下のように変更しました。


変更の理由は後日記載します。

それに伴い、index.phpも多少変更。

<?php
/*
 *  Constants: Core Application Constants
 *  EXT - The file extension.  Typically ".php"
 *  FCPATH - The full server path to THIS file
 *  SELF - The name of THIS file (typically "index.php")
 *  ROOT - ROOT direcotry
 *  SYSPATH - The full server path to the "system" folder
 *  APPPATH         - The full server path to the "application" folder
 *  EXTPATH- The full server path to the "extension" folder
 */

define('EXT', '.php');
define('FCPATH', __FILE__);
$pathinfo = pathinfo(FCPATH);
define('SELF', $pathinfo['basename']);
define('ROOT',realpath($pathinfo['dirname'].'/').'/');
unset($pathinfo);
define('SYSPATH', ROOT.'system/');
define('EXTPATH', ROOT.'extension/');
define('APPPATH', ROOT.'application/');

$f=pathinfo(__FILE__, PATHINFO_BASENAME);
if (!is_dir(SYSPATH)) exit("Your system folder path does not appear to be set correctly. Please open the following file and correct this: ".$f);
if (!is_dir(APPPATH)) exit("Your application folder path does not appear to be set correctly. Please open the following file and correct this: ".$f);
if (!is_dir(EXTPATH)) exit("Your extension folder path does not appear to be set correctly. Please open the following file and correct this: ".$f);

define('ENVIRONMENT', 'development');

if (defined('ENVIRONMENT'))
{
    switch (ENVIRONMENT)
    {
        case 'development':
            error_reporting(E_ALL);
        break;
   
        case 'testing':
        case 'production':
            error_reporting(0);
        break;

        default:
            exit('The application environment is not set correctly.');
    }
}

require(SYSPATH.'core/Bootstrap'.EXT);


よく見たらほとんど全部変えてた(笑)
わからない人は触らないほうがいいかもしれませんねー。


変更がちゃんと動いてるかhttp://localhost/lsc/にアクセスして確認。




大丈夫そう。
それじゃあ次回からガリガリ開発です!

2011年7月22日

Codeigniterを使ってショッピングカートをゼロから作ってみよう。 # 0

仕事柄毎日コード書いてるけど、色々と思うこともあるので日記風に書き残しておこうかと思います。

とは言えだ、ただの日記じゃ芸がないし自分が飽きそうなので企画っぽくしようかと。
Google先生に聞いてみたけど見当たらなかったのと慣れていることもあるので企画内容決定。


Codeigniterを使ってショッピングカートをゼロから作ってみよう。


それじゃ企画の概要。
- CodeigniterとjQueryをメインにiscなみのショッピングカートを作る。
- 原則としてコードは恥も外聞もなく公開。まぁでも徹底入門よりはマシだと思います、はいw
- 既に企画内容が途方もなく大きいので、いつ終わるかは不明。
- 途中で投げ出すこともあるでしょう。・・・っつーか、投げ出す気満々なんであまり期待しないでください(苦)
- 投げ出さずにできたら商用化。(あくまでも予定)

以下、仕様。
コア部分:
- カート機能
- ユーザ機能
- 管理ページ
- 製品管理系

準コア部分:
- 決済系(Paypalとか色々)
- 税金系
- 配送系
- 在庫管理系
- 返品受付機能

デザイン部分:
- テンプレート(スキン)機能

余力があれば:
- B2B機能
- SEO系
- プロモーション機能
- API


6ヶ月~1年くらいあればある程度まともなのできるかな?
機能はこのブログを読んでくれる人がいて、要望出してくれたりアドバイスくれたりしたら追加されるかもしれません。

投げ出す気は満々ですが、投げ出さないように励ましつつ見守ってくれれば幸いです。

んじゃ、始めますか。

2011年2月8日

カレンダーをCSSだけで表現 / Calender icon by CSS3

HTML:
<p class="calendar">7 <em>Feb</em></p>

CSS:
/******************************************************************
    Section; Calendar CSS
******************************************************************/
.calendar {
    background              : #ededef;
    background              : -moz-linear-gradient(top,  #ededef,  #ccc); 
    background              : -webkit-gradient(linear, left top, left bottom, from(#ededef), to(#ccc)); 
    color                   : #000;
    float                   : left;
    font                    : bold 20px/40px Arial Black, Arial, Helvetica, sans-serif;
    margin                  : .25em 10px 10px 0;
    padding-top             : 5px;
    position                : relative;
    text-align              : center;
    width                   : 50px;
    border-radius           : 3px;    
    box-shadow              : 0 2px 2px #888;
    text-shadow             : #fff 0 1px 0;    
    -moz-border-radius      : 3px;
    -moz-box-shadow         : 0 2px 2px #888;
    -webkit-border-radius   : 3px;
    -webkit-box-shadow      : 0 2px 2px #888;
}

.calendar em {
    background                          : #04599a;
    background                          : -moz-linear-gradient(top,  #04599a,  #00365a); 
    background                          : -webkit-gradient(linear, left top, left bottom, from(#04599a), to(#00365a)); 
    border-top                          : 1px solid #00365a;
    color                               : #fff;
    display                             : block;
    font                                : normal bold 11px/20px Arial, Helvetica, sans-serif;
    text-transform                      : uppercase;
    border-bottom-left-radius           : 3px;    
    border-bottom-right-radius          : 3px;
    text-shadow                         : #00365a 0 -1px 0;
    -moz-border-radius-bottomleft       : 3px;
    -moz-border-radius-bottomright      : 3px;
    -webkit-border-bottom-left-radius   : 3px;
    -webkit-border-bottom-right-radius  : 3px;
}

.calendar:before,
.calendar:after {
    background              : #111;
    content                 : '';
    float                   : left;
    height                  : 6px;
    position                : absolute;
    top                     : 4px;
    width                   : 6px;
    z-index                 : 1;
    border-radius           : 10px;
    box-shadow              : 0 1px 1px #fff;
    -moz-border-radius      : 10px;
    -moz-box-shadow         : 0 1px 1px #fff;
    -webkit-border-radius   : 10px;
    -webkit-box-shadow      : 0 1px 1px #fff;
}

.calendar:before { left: 7px; }
.calendar:after{ right: 7px; }

.calendar em:before,
.calendar em:after {
    background              : #dadada;
    background              : -moz-linear-gradient(top,  #f1f1f1,  #aaa);
    background              : -webkit-gradient(linear, left top, left bottom, from(#f1f1f1), to(#aaa));
    content                 : '';
    float                   : left;
    height                  : 10px;
    position                : absolute;
    top                     : -3px;
    width                   : 2px;
    z-index                 : 2;
    border-radius           : 2px;
    -moz-border-radius      : 2px;
    -webkit-border-radius   : 2px;
}

.calendar em:before { left: 9px; }
.calendar em:after { right: 9px; }

元ネタ: Drawing Calendar Icon With CSS3