2013年11月18日

簡単なフェードイン&アウトローテータ / Simple jQuery Fade In / Out Continuous Rotator

jQuery(function() {
    jQuery('.rotate div').hide();
    jQuery('.rotate div:first').show();
    setInterval(function() {
        jQuery('.rotate div:first-child').fadeOut().hide().next('div').fadeIn().end().appendTo('.rotate');
    }, 10000);
});

2013年10月3日

WordPress+マルチサイトのドメイン変更に際して

結構面倒なので、今後のために手順をメモ。
  1. データベースでドメインを検索
  2. 該当する箇所を変更。
  3. 該当する行数が多すぎる場合には以下のクエリを参考に。

UPDATE wp_posts SET guid=REPLACE(guid, 'http://古いドメイン名', 'http://新しいドメイン名');
UPDATE wp_posts SET post_content=REPLACE(post_content, 'http://古いドメイン名', 'http://新しいドメイン名');

2013年2月12日

複数ウェブサイト+複数通貨での支払い / Multi Website + Currency Checkout - Magento

MagentoのチェックアウトはウェブサイトのBase Currencyでしかチェックアウトできないようになっています。
そのためアメリカのユーザが米ドル(USD)で、日本のユーザが日本円(JPY)でチェックアウトできるようにするには、Paypalプラグインを使用するか、サイトを複数作成する方法が一般的です。
このページは複数のウェブサイトを作成し、それぞれに通貨を設定する方法を記載しています。

完成図
www.example.com (JPY, 日本語)
www.example.com/en/ (USD, 英語)

  1. ルートカテゴリ作成
    Catalog ⇛ Manage Categories ⇛ New Root Category
    Name: English Root
    Is Active: Yes
    Include in Navigation Menu: Yes / No
  2. ウェブサイト作成
    System ⇛ Manage Stores ⇛ Create Website
    Name: English
    Code: en
  3. ストア作成
    System ⇛ Manage Stores ⇛ Create Store
    Website: (2)で作成したウェブサイト
    Name: Main Website
    Root Category: (1)で作成したルートカテゴリ
  4. ストアビュー作成
    System ⇛ Manage Stores ⇛ Create Store View
    Store: (3)で作成したストア
    Name: English View
    Code: en
    Status: Enabled
  5. URL設定
    左上の Current Configuration Scope を(2)で作成したウェブサイトに変更。
    System ⇛ Configuration ⇛ Web ( GENERAL ) ⇛ Unsecure ⇛ Base Link URL を {{unsecure_base_url}}en/ に変更。
    同じように
    System ⇛ Configuration ⇛ Web ( GENERAL ) ⇛ Secure ⇛ Base Link URL を {{secure_base_url}}en/ に変更。

  6. en ディレクトリ作成
    1. ルートディレクトリにenディレクトリを作成。
    2. index.php と .htaccess ファイルをenディレクトリ内にコピー
    3. en/index.php の下記の部分を
    $mageRunCode = isset($_SERVER['MAGE_RUN_CODE']) ? $_SERVER['MAGE_RUN_CODE'] : '';
    $mageRunType = isset($_SERVER['MAGE_RUN_TYPE']) ? $_SERVER['MAGE_RUN_TYPE'] : 'store';
    以下のように変更。
    $mageRunCode = isset($_SERVER['MAGE_RUN_CODE']) ? $_SERVER['MAGE_RUN_CODE'] : 'en';
    $mageRunType = isset($_SERVER['MAGE_RUN_TYPE']) ? $_SERVER['MAGE_RUN_TYPE'] : 'website';
  7. ウェブサイト通貨
    左上の Current Configuration Scope が Default Config になっていることを確認の上
    System ⇛ Configuration ⇛ Catalog ( CATALOG ) ⇛ Price ⇛ Catalog Price Scope が Website になっているか確認。

  8. Base Currency設定
    左上の Current Configuration Scope を(2)で作成したウェブサイトに変更。
    System ⇛ Configuration ⇛ Currency Setup ( GENERAL ) ⇛ Currency Options ⇛ Base Currency を変更。(この場合は US Dollar)

  9. 各種設定
    Productの値段はそれぞれサイトごとに指定する必要があります。でないと日本円で100円と指定したものが、100米ドルにそのまま使用されます。

1~6までが複数のウェブサイト作成。
7,8でウェブサイトごとに別々のBase Currencyを設定することで複数の通貨での支払いが可能になります。

2013年2月8日

よく使いそうな日付関連まとめ - PHP



タイムゾーンの取得
<?php echo date_default_timezone_get(); ?>
タイムゾーンの設定
<?php date_default_timezone_set('Asia/Tokyo'); ?>
サポートされるタイムゾーンのリスト
http://www.php.net/manual/ja/timezones.php



日付
<?php
 echo date('Y年n月j日');  // 2013年2月8日
 echo date('Y-m-d H:i:s'); // 2013-02-08 11:28:04
 echo date('F jS, l');  // February 8th, Friday
?>


時刻
<?php
 echo date('H:i:s');   // 11:28:04
 echo date('g:ia');   // 11:28am
?>


現在のタイムスタンプ
<?php
 echo time();
 echo date('U');
 echo mktime(date('H'),date('i'),date('s'),date('m'),date('d'),date('Y'));
 echo strtotime('now');
?>


日付情報
<?php var_dump(getdate()); 
// array
//  'seconds' => int 20
//  'minutes' => int 29
//  'hours' => int 11
//  'mday' => int 8
//  'wday' => int 5
//  'mon' => int 2
//  'year' => int 2013
//  'yday' => int 38
//  'weekday' => string 'Friday' (length=6)
//  'month' => string 'February' (length=8)
//  0 => int 1360276160
?>
月の日数
<?php echo date('t'); ?>  // 28
うるう年判定
<?php echo date('L'); ?>  // 0 or 1
年始から何日目か
<?php echo date('z日目'); ?>


日付計算例

現在時刻より1週間と2日、4時間2秒前
<?php
 echo date('Y年n月j日 H時i分s秒', mktime(date('H') - 4,date('i'),date('s') - 2,date('m'),date('d') - 9,date('Y')));
 echo date('Y年n月j日 H時i分s秒', strtotime("-1 week -2 days -4 hours -2 seconds"));
?>
第17週目の日曜日
<?php echo date("Y年n月j日", strtotime("2013-W17-0")); 
// 0=日曜日, 1=月曜日, 2=火曜日, 3=水曜日, 4=木曜日, 5=金曜日, 6=土曜日
?>
年始から123日目の日付
<?php
 echo date("Y年n月j日", mktime(0, 0, 0, 1, 1+123, 2013));
 echo date("Y年n月j日", strtotime("+123 days", strtotime('2013-01-01')));
?>

2013年1月16日

CodeIgniter + 超シンプルObject Relational Mapping(ORM)





■ はじめに

このページで紹介するORMはCodeIgniterのメソッドから極力外れないように作成した非常に簡素なものです。後述しますが、既にCodeIgniterで使えるORM用のライブラリが存在します。多機能なものを使いたい方はそちらをお勧めします。

■ Object Relational Mapping(ORM)とは

オブジェクト関係マッピング(英: Object-relational mapping、O/RM、ORM)とは、データベースとオブジェクト指向プログラミング言語の間の非互換なデータを変換するプログラミング技法である。オブジェクト関連マッピングとも呼ぶ。実際には、オブジェクト指向言語から使える「仮想」オブジェクトデータベースを構築する手法である。
(引用元:オブジェクト関係マッピング)



■ CodeIgniterで使えるORMライブラリ





■ コンセプト


  • シンプル、簡単、拡張可
  • CodeIgnierのメソッドを極力変更しない
  • モデルファイルを自動で作成。



■ 完成図


実際に作成するファイルは MY_Loader.php, MY_Model.php, orm_helper.php の3つです。
モデルファイルは自動で作成されます。
MY_Controllerはこちらのページを参照してください。


■ 開発環境

Windows + WAMP + CodeIgniter 2.1.3 を使用しました。
$config['base_url'] は http://localhost/CodeIgniter_2.1.3/ になります。


■ データテーブル作成

コードを始める前にサンプル用のテーブルを作成します。

CREATE TABLE sample (
  id int(11) NOT NULL AUTO_INCREMENT,
  field1 varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  field2 varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (id)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

テーブル名がそのままモデルのファイル名、およびクラス名になるので、コントローラに利用する名称は避けるようにしてください。


■ モデル自動生成ヘルパの作成

たいていのORMだとデータベースに追加・変更・削除が発生した場合に自分でORM用のファイルを変更しなければならないですが、面倒なのでデータベースからモデルファイルを自動で作成する簡単なヘルパを作ります。

/application/helpers/orm_helper.php
<?php
/**
* Object-relational mapping (ORM) file generator
* 
* Generate folloing files 
* /modles/--database table name--.php
* /models/basemodels/base--database table name--.php
*/
function generate_models()
{
    $ci =& get_instance();
    $tables = $ci->db->list_tables();
    
    foreach($tables as $table)
    {
        $res = $ci->db->query('DESCRIBE `'.$table.'`');
        
        $primary = FALSE;
        $data = '<?php // '.$table.' table mapping'.PHP_EOL; 
        $data .= 'class Base'.ucfirst($table).' extends MY_Model'.PHP_EOL;
        $data .= '{'.PHP_EOL;
        foreach($res->result() as $row)
        {
            $data .= "\tvar $".$row->Field.";".PHP_EOL;
            if($row->Key == 'PRI')
                $primary = $row->Field;
        }
        
        if($primary)
            $data .= "\t"."var \$primary = '".$primary."';".PHP_EOL;
        else
            $data .= "\t"."var \$primary = FALSE;".PHP_EOL;

        $data .= "".PHP_EOL;
        $data .= "\tfunction __construct(\$table=null)".PHP_EOL;
        $data .= "\t{".PHP_EOL;
        $data .= "\t\tparent::__construct(\$table);".PHP_EOL;
        $data .= "\t}".PHP_EOL;
        
        $data .= '}';
        
        $base_model_path = CSTPATH.'models/BaseModels/';
        if( ! file_exists($base_model_path)) mkdir($base_model_path);
        // generate base model files
        file_put_contents($base_model_path.'Base'.ucfirst($table).EXT, $data);
        
        // check general model files
        if( ! file_exists(CSTPATH.'models/'.$table.EXT))
        {
            $data = '<?php'.PHP_EOL; 
            $data .= 'class '.ucfirst($table).' extends Base'.ucfirst($table).PHP_EOL;
            $data .= '{'.PHP_EOL;
            $data .= '}'.PHP_EOL;
            file_put_contents(CSTPATH.'models/'.$table.EXT, $data);
        }
    }
}

このヘルパは実行すると1つのデータテーブルから2つのファイルを作成します。


■ Modelの拡張


/application/core/MY_Model.php
<?php
class MY_Model extends CI_Model
{
    var $table;
    
    function __construct($table=null)
    {
        $this->table = ($table) ? $table : get_called_class();
        log_message('debug', ucfirst($this->table) . " Class Initialized");
    }
    
    
    /**
    * find by primary key
    * 
    * @param mixed $id
    * @return class object or FALSE
    */
    public function find($pk)
    {
        $ci =& get_instance();
        $ci->db->where($this->primary, $pk);
        $q = $ci->db->get($this->table);
        if($q->num_rows())
        {
            $array = $q->row_array();
            foreach($array as $key => $value)
            {
                $this->$key = $value;
            }
            // return object
            return $this;
        }
        else return FALSE;
    }
    
    
    public function save()
    {
        $ci =& get_instance();
        $primary = $this->primary;
        $table = $this->table;
        // temporary unset values to run query
        unset($this->primary);
        unset($this->table);
        if($where = $this->$primary)
        {   // update row
            $ci->db->where($primary, $where);
            $ci->db->update($table, $this);
        }
        else
        {   // insert new row
            $ci->db->set($this);
            $ci->db->insert($table);
        }
        
        // re-set value and return obj
        $this->primary = $primary;
        $this->table = $table;
        return $this;
    }    
}

$this->load->model('sample') でモデルをロードすると $this->sample が利用できるようになります。
$sample = new Sample(); でも良かったんですが、できるだけCodeIgniterの書式に沿った書き方をしたかったのでこうなりました。


■ Loaderの拡張

/application/models/BaseModels/Base--Data Table--.php が存在する場合に読み込む条件文を83-85行目に追加するだけです。

/application/core/MY_Loader.php
<?php
class MY_Loader extends CI_Loader
{
    /**
     * Model Loader
     *
     * This function lets users load and instantiate models.
     *
     * @access    public
     * @param    string    the name of the class
     * @param    string    name for the model
     * @param    bool    database connection
     * @return    void
     */
    function model($model, $name = '', $db_conn = FALSE)
    {
        if (is_array($model))
        {
            foreach ($model as $babe)
            {
                $this->model($babe);
            }
            return;
        }

        if ($model == '')
        {
            return;
        }

        $path = '';

        // Is the model in a sub-folder? If so, parse out the filename and path.
        if (($last_slash = strrpos($model, '/')) !== FALSE)
        {
            // The path is in front of the last slash
            $path = substr($model, 0, $last_slash + 1);

            // And the model name behind it
            $model = substr($model, $last_slash + 1);
        }

        if ($name == '')
        {
            $name = $model;
        }

        if (in_array($name, $this->_ci_models, TRUE))
        {
            return;
        }

        $CI =& get_instance();
        if (isset($CI->$name))
        {
            show_error('The model name you are loading is the name of a resource that is already being used: '.$name);
        }

        $model = strtolower($model);

        foreach ($this->_ci_model_paths as $mod_path)
        {
            if ( ! file_exists($mod_path.'models/'.$path.$model.EXT))
            {
                continue;
            }

            if ($db_conn !== FALSE AND ! class_exists('DB'))
            {
                if ($db_conn === TRUE)
                {
                    $db_conn = '';
                }

                $CI->load->database($db_conn, FALSE, TRUE);
            }

            if ( ! class_exists('Model'))
            {
                load_class('Model', 'core');
            }
            
            if(file_exists($mod_path.'models/BaseModels/'.'Base'.ucfirst($model).EXT))
            {
                require_once($mod_path.'models/BaseModels/'.'Base'.ucfirst($model).EXT);
            }

            require_once($mod_path.'models/'.$path.$model.EXT);

            $class = ucfirst($model);

            $CI->$name = new $class($model);

            $this->_ci_models[] = $name;
            return;
        }

        // couldn't find the model
        show_error('Unable to locate the model you have specified: '.$model);
    }

}



■ サンプル

/application/controller/welcome.php
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class Welcome extends MY_Controller {

 /**
  * Index Page for this controller.
  *
  * Maps to the following URL
  *   http://example.com/index.php/welcome
  * - or -  
  *   http://example.com/index.php/welcome/index
  * - or -
  * Since this controller is set as the default controller in 
  * config/routes.php, it's displayed at http://example.com/
  *
  * So any other public methods not prefixed with an underscore will
  * map to /index.php/welcome/<method_name>
  * @see http://codeigniter.com/user_guide/general/urls.html
  */
 public function index()
 {
  $this->load->view('welcome_message');
 }
    
    public function generate()
    {
        $this->load->database();
        $this->load->helper('orm');
        $this->load->helper('url');
        generate_models();
        
        redirect('/');
    }
    
    public function insert()
    {
        $this->load->database();
        $this->load->model('sample');
        
        $sample = $this->sample;
        // set data
        $sample->field1 = 'aaaaa';
        $sample->field2 = 'bbbb';
        // insert
        $sample->save();
    }
    
    public function update()
    {
        $this->load->database();
        $this->load->model('sample');
        
        // retrieve data by id = 1
        $sample = $this->sample->find(1);
        // set data
        $sample->field1 = 'cccc';
        $sample->field2 = 'bbbb';
        // update
        $sample->save();
    }
}

/* End of file welcome.php */
/* Location: ./application/controllers/welcome.php */

□ モデルファイル作成
http://localhost/CodeIgniter_2.1.3/index.php/welcome/generate

実行すると (1) /application/models/sample.php(2) /application/models/BaseModels/BaseSample.php のファイルが作成されます。

(1)は拡張用のモデルです。CodeIgniterからロードされるファイルはこちらのファイルです。
(2)がデータテーブルに対応した情報を持つ拡張用のモデル専用ファイルです。データテーブルが変更されると上書きされるのであまり手を入れないほうがいいでしょう。ファイル名やクラス名がCodeIgniterの形式に沿っていないのでCodeIgniterからは直接ロードできません。


□ insert
http://localhost/CodeIgniter_2.1.3/index.php/welcome/insert
実行するとsampleテーブルに一行データが追加されます。

□ update
http://localhost/CodeIgniter_2.1.3/index.php/welcome/update
実行すると上で追加したデータのidが1の行を変更します。


■ 最後に

ライブラリ化や機能の追加などは予定していません。
使う人が改善、拡張していって使いやすいようにしてください。(使う人が居れば、ですけど・・・)

俺自身、あまりサイズが大きくて多機能なORMは覚えるのが面倒な上、ほとんどの機能は必要なかったので簡単な拡張で済ませました。
普段使いそうな機能を追加すれば便利に使えると思います。

・・・だったらいいな(笑)

2013年1月9日

レスポンシブ・ウェブデザイン(RWD)まとめ



■ レスポンシブ・ウェブデザイン(RWD)とは

レスポンシブ・ウェブデザインは、CSS3のメディアクエリを使用して見た目を変更するWEB ページの構築手法です。つまり、デバイスに関わらず共通の1つのHTMLを用意し、CSS メディアクエリを使用して、そのページを表示する画面サイズからデバイスを判断しCSSを選択し、そのデザインを変更します。
(引用元:Google がお勧めするスマートフォンに最適化されたウェブサイトの構築方法)

パソコン、タブレット、スマートフォンなどのデバイスをWEBサイト表示の判断基準にするのではなく、ブラウザの横幅サイズをWEBサイト表示の判断基準にしているのでデザインを柔軟に調整できるのが特徴。


■ RWDのメリット

□ RWDでしか実現できないメリット
  1. ユーザーエージェントを判別せずにレイアウトを調整できる
    • デバイス判定の技術・知識が必要がない
  2. サーバーサイドプログラムに頼らず、CSSだけで実現できる
    • プログラムの技術・知識がなくても実装できる。
    • 何らかの理由でサーバーサイドプログラムが使用できないときにも有効。

□ 代替手段はあるが、RWDでも実現できるメリット
  1. 各デバイスのURL統一化
    • URL統一化にはSEO面でもメリットあり

■ RWDのデメリット

  1. WEBサイトの容量が大きくなり、表示速度に影響がでやすい
    • RWDは仕組み上どうしても最適化されたWEBサイトより表示速度が遅くなる。
  2. デバイスごとのWEBサイトの最適化ができない
    • ユーザビリティの面でもマーケティングの面でも「最適化」にはならない
    • 不可能ではないけれど、デバイスごとに細かい配慮をすることが難しい。
□ ビジネスシーンでのデメリット
  1. 制作&管理コストが安いわけではない
    • モバイル用とPC用サイトを別に作っても制作&管理コストが安くなる場合もある。(もちろん作り方次第だが・・・)
    • 手法自体が古いブラウザに対応していないので、場合によってはコスト増
  2. ユーザにとってのメリットが見えにくい
    • ユーザが求めているのはコンテンツであって、作り手のテクニックではない
    • お客さんの理解が得られないことが多々ある


■ RWDの導入を考える前に

レスポンシブWebデザインはあくまでマルチデバイス対応の一手法です。導入ありきで話を進めてしまうと、誰も幸せにならない結果となりかねません。
サイトの目的やターゲットユーザーから必要とされる要件を洗い出し、見込まれる効果とコストを他の手法と比較した上で、導入を検討していくべきと考えています。
(引用元:レスポンシブWebデザインのメリット/デメリットをできるだけ中立的に検証してみた)


■ RWDは検索順位的に有利なのか?

先に引用したGoogle がお勧めするスマートフォンに最適化されたウェブサイトの構築方法にも記載されているように、SEOの面でもメリットがある手法の1つです。

ただし、あくまでオプションの1つで「RWDにしないと、順位が上がらなくなる」などの宣伝文句は大嘘そんなことを言ってる人や会社は正しい知識をつけていないものとして対応したほうがいい。




■ まとめるきっかけ

今年初めての仕事がある会社で一時的にやるイベント用のモバイルサイト製作だったのだが、1つ妙な条件が付随していた。
それが「RWDで作れ」とのこと。PC用のデザインがあるのかと思いきや、「見れればOKレベルでいい」のにRWDで作れという。モバイルサイトなのに?RWDの必要性は?一時的なイベント用のサイトでしょ?などなど・・・正直なところ、?マークが飛びまくりだった(苦)


■ RWDに対する個人的なコメント

RWDという手法(というか概念かな?)それ自体新しいものじゃない。
可変長のサイトに毛が生えたものだと思ったほうがいいんじゃないだろうか。
この手法の唯一のメリットはプログラムの知識がほとんどいらないことだよね。HTML+CSSでURL統一化ができるのはRWD導入の敷居を下げているのは否めない。

ただ上述のメリットは別の方法で実現できるものばかりだし、プログラマとしてはあまり使う必要性を感じないというのが本音。サイトはできる限り最適化する方がいい自分としてはデメリットが多すぎてあまり推奨できない。

結論としては
- モバイルがメインでPCからの閲覧は見れればOKというなら有効性はあると思う。
- Wikiとかブログのような画像を多用しないテキストベースのサイトも大丈夫かな。
- 反対にPC閲覧用の凝ったサイトをモバイルに対応させる際にはオススメしない。

CONDENSEのようなサイトこそRWDにするべきだよね。


△ちょっとだけ批判
  • HTMLが1つのファイルに全部入っているからといって管理が楽かと言えば、そうでもないんじゃないか?「条件分岐が一箇所にまとめてあるから全体像が把握しやすいし管理しやすい」とか言われたら俺なら苦笑いだけどな・・・
  • リンク項最後のページにある「メンテナンスが楽」「一貫性のあるデザイン」「親切、安心設計の操作性」とか、正直なところデタラメもいいところ。きちんと作れば実際そういうメリットはあるかもしれないけど、それは別にRWDがもともと持ってるメリットじゃない
  • 古いブラウザに対応してない時点でレスポンシブじゃないよね?