3月 31

CodeIgniterの学習でBBSを作ったら、携帯電話から使ってみたくなりました。DoCoMoはShifJISのバイナリーで送られて来るので、そのShifJISを表すタグにして保存、SoftBank(3G)はUTF8で送られてくるのでUTF8を表すタグにして保存してみました。
例)
DoCoMoから送られた『晴』マーク : [m:do='f89f']
SoftBankから送られた『晴』マーク : [m:sb='ee818a']
PCに表示する際にはそれぞれに用意したGIF画像を呼び出すimgタグに摩り替えるフィルタを通せば簡単に表示できる。
ではその反対はどうだろうか、携帯電話からBBSを見た場合、このままでも最近の機種であればimgタグで表示された絵文字が見れると思うのだが、折角なので(メモリーが少ない機種でも問題がでないように)ネイティブに表示させたいところであります。
ドコモの場合はタグに埋め込まれた「ShiftJIS文字列」をバイナリー変換し文字列に埋め込み送れば携帯側で表示される、ところがソフトバンクはUTF8をバイナリーにパックして送っても表示されないのです。ダンプして調べたら何となく法則性が見えてきたのでメモ。。。

// SoftBank絵文字、UTF8文字表記から機種表示コード変換
function sb_utf2emoji( $utf )
{
	$utf = preg_replace('/^ee/i','',$utf);
	$byte1 = strval((int)(hexdec($utf)/256));
	$byte2 = strval(hexdec($utf)%256);
	switch ($byte1)
	{
		case 0x80:	$byte1 -= 0x39;	$byte2 -= 0x60;	break;
		case 0x81:	$byte1 -= 0x3A;	$byte2 -= 0x20;	break;
		case 0x84:	$byte1 -= 0x3F;	$byte2 -= 0x60;	break;
		case 0x85:	$byte1 -= 0x37;	$byte2 -= 0x20;	break;
		case 0x88:	$byte1 -= 0x42;	$byte2 -= 0x60;	break;
		case 0x89:	$byte1 -= 0x43;	$byte2 -= 0x20;	break;
		case 0x8C:	$byte1 -= 0x3D;	$byte2 -= 0x60;	break;
		case 0x8D:	$byte1 -= 0x3E;	$byte2 -= 0x20;	break;
		case 0x90:	$byte1 -= 0x40;	$byte2 -= 0x60;	break;
		case 0x91:	$byte1 -= 0x41;	$byte2 -= 0x20;	break;
		case 0x94:	$byte1 -= 0x43;	$byte2 -= 0x60;	break;
	}
	return pack("n",0x1b24) . pack("n",$byte1*0x100+$byte2) . pack("c",0x0f);
}

昔NECの日本語OSで使われていた、漢字IN/漢字OUTの形式に似てる。(笑
絵文字開始コード0x1b24、絵文字終了コード0x0f、となっているようです、本来連続した絵文字を送る場合は、[0x1b24]甲乙丙[0x0f]のようにIN/OUTコードで『絵文字コード列』を挟んで送るのが正しい姿だと思いますが、[0x1b24][0x0f][0x1b24][0x0f][0x1b24][0x0f]のように絵文字コードとセットで送っても問題ないようです(当然バイト数は増えますが)。

CIではこんな感じのヘルパーから呼ぶと便利ですね。

// テキストから機種依存コードへ変換
function text2emoji($str = '')
{
	$find = array(
	"'\[m=do:(.*?)\]'ies",
	"'\[m=sb:(.*?)\]'ies"
	);
	$replace = array(
	'pack("n",hexdec("\\1"))',
	'sb_utf2emoji("\\1")'
	);
	return preg_replace($find, $replace, $str);
}

ps.
ここに書いた法則、全部表示させて検証したわけではないので悪しからず。。。

Tagged with:
3月 26

クッキーが使えない環境のみでのログイン

今更ながらCIのセッションライブラリはクッキーと親密性が高い事に気づき、今回はセッションクラスは全く使っていません、PCオンリーで考えた場合は全く不便はないのだけれど、モバイル系作るには使い難いと思います(海外の機種は平気なのか?)。
VIEWも使わずコントローラーにハードコーディング、実際に使う時は、パスワードの暗号化やUA、IP、有効TIMEのチェックが必要だったりすると思うけど、基本的にはこんな流れで良いのではなかろうか?
それと現状のままではCIのセッションライブラリと併用した場合に問題が発生するかもしれませんね(直接 $_SESSION 使ってるし。w)、本来ならばCIのセッションクラスから派生させてクッキー無効状態でも使えるように拡張するのが良いと思う。。
因みにCIの標準ライブラリでの、セッションリード のメソッドはこんなんです
※// No cookie? Goodbye cruel world!…. (笑)

function sess_read()
{
	// Fetch the cookie
	$session = $this->CI->input->cookie($this->sess_cookie_name);

	// No cookie?  Goodbye cruel world!...
	if ($session === FALSE)
	{
		log_message('debug', 'A session cookie was not found.');
		return FALSE;
	}

さて、ここから先が今回書いたコードです(ページネーションのテストを兼ねて)。
ベースクラスでURLにセッションIDが含まれているかを検査、含まれていない場合はログイン画面を表示、ユザー照合成立したらsession_start() して、$_SESSION['id'] にuser_id をセットと同時にURLにセッションIDを埋め込み再度認証。URLにセッションIDが含まれていればそれを session_id へセット、 session_start() して $_COOKIE['id']を読んでみて読めたら認可、これでやっとコンストラクターから派生クラスへ流れます。ダメならURLからセッションIDを削除、再認証。
※認証成功するとベースクラスのプロパティ$uid が常にユーザIDを保持してるので、派生クラスからは $this->$uid で簡単に利用できるわけです。これもクラス化して $this->uid->get_attr(‘nickname’) とか $this->uid->get_attr(‘birthday’) とかしたら便利かも。。。

コントローラー

< ?php
class K_auth01 extends MY_Controller {

	function K_auth01()
	{
		parent::MY_Controller();
		// ヘルパーロード
		$this->load->helper(array('form','url'));
		// ライブラリロード
		$this->load->library('pagination');
	}

	function index()
	{
		echo anchor('k_auth01/test', 'ページネーションテストへ');
	}

	function test($offset='')
	{
		$offset = (int)$offset;
		//-------------------------------------
		// ページネーション設定
		$conf['base_url']   = site_url('k_auth01/test');
		$conf['total_rows'] = 100;
		$conf['per_page']   = 5;
		$this->pagination->initialize($conf);
		//-------------------------------------
		echo $this->pagination->create_links();
		echo "<hr />";
		echo "offset= $offset<br />";
		// 基底クラスがuidを保持
		echo "userId= $this->uid<br />";
		echo anchor('k_auth01', 'index') . ' | ' . anchor('k_auth01/logout', 'Logout');
	}

	function logout()
	{
		// 基底クラスのログアウトメソッドを呼び出す
		$this->_logout();
		echo anchor('k_auth01', 'Loginへ');
		exit;
	}

}
?>

認証機能を組み込んだベースクラス

< ?php if (!defined('BASEPATH')) exit('No direct script access allowed');

class MY_Controller extends Controller {

	protected $user_table = 'users';
	protected $uid;

	function MY_Controller()
	{
		parent::Controller();

		$this->load->database();

		// エレメントにセッションIDが含まれているか?
		if (0 < preg_match("/sid([0-9a-z]+)/i", $this->uri->segment($this->uri->total_segments()), $sid))
		{
			// セッションIDが含まれている場合、セッションIDを指定しセッション開始
			session_id( $sid[1] );
			session_start();

			if ( isset($_SESSION['id']) AND 0< (int)$_SESSION['id'] )
			{
				// id値がセットされている場合、ログイン継続状態とみなす
				$this->uid = (int)$_SESSION['id'];
				// URLサフィックスにセッションIDを付加
				$this->config->set_item('url_suffix','/sid'.$sid[1] );
				// 正常終了
				return;
			}
			// idがセットされていないのは偽装の疑い、作成したセッションを抹殺し、サフィックスも消す。
			session_destroy();
			$this->config->set_item('url_suffix', '');
		}

		// ログインシーケンスへ
		$this->uid = $this->_login();
		exit;
	}

	/*
	 * ログイン処理
	 * 成功すると、$_SESSION['id']に user_id がセットされる
	 */
	function _login()
	{
		// ライブラリロード
		$this->load->library('form_validation');
		// ヘルパーロード
		$this->load->helper(array('form','url'));

		// バリデーションルール設定
		$valiRule = array(
		array(
			'field' => 'username',
			'label' => 'User name',
			'rules' => 'trim|required|max_length[24]'
			),
		array(
			'field' => 'password',
			'label' => 'User Password',
			'rules' => 'trim|required|max_length[12]'
			)
		);
		$this->form_validation->set_rules( $valiRule );

		if ($this->form_validation->run()==FALSE)
		{
			// HTTPヘッダー送出
			$this->output->set_header('Content-Type: text/html; charset=UTF-8');
			echo form_open(current_url());
			echo "<input type='text' name='username' value='".set_value('username')."'/><br />";
			echo "<input type='text' name='password' value='".set_value('password')."'/><br />";
			echo "<input type='submit' value='LOGIN'/>";
			echo form_close();
			echo validation_errors();
		}
		else
		{
			// ログイン中のユーザーであってもサブミットされたら一旦ログアウトし新たに認証を受ける
			$this->_logout();
			$id = $this->_auth($this->input->post('username'), $this->input->post('password'));
			if ($id > 0)
			{
				// セッション開始(セッションIDはシステムに振らせる)
				session_start();
				// URLサフィックスにセッションIDを付加
				$this->config->set_item('url_suffix','/sid'.session_id());
				$_SESSION['id'] = $id;
				// 再度認証ロジックに投げる(今度は$_SESSION['id']が入っているので認可されるはず)
				header('Location: ' . current_url());
			}
			else
			{
				// 失敗
				header('Refresh: 3; URL=' . current_url());
				echo "<a href='".current_url()."'>Login faild . RETRY</a>";
			}
		}
	}

	/*
	 * ログアウト
	 */
	function _logout()
	{
		$this->config->set_item('url_suffix','');
		$_SESSION['id'] = 0;
		@session_destroy();
	}

	/*
	 * ユーザ認証
	 * 成功すれば user_id が返る
	 */
	function _auth($username = '', $password = '')
	{
		if ($username != '' AND $password != '')
		{
			$this->db->where('username', $username);
			$this->db->where('password', $password);
			$query = $this->db->getwhere($this->user_table);

			if ($query->num_rows() > 0)
			{
				// ===== ログイン成功処理 =====
				// userテーブルのインデックスをセッションに保存
				$row = $query->row_array();
				return (int)$row['id'];
			}
		}

		// Login失敗
		return 0;
	}
}

?>
Tagged with:
3月 25

PHPフレームワークCodeIgniter

昨年 「CodeIgniter徹底入門 」 を参考に version1.6 をインストールした辺りで色々忙しくなってきて放置状態、あーやらなきゃ。。。っつーわけでまたボチボチ触り始めましょう。笑

久々に本家のサイトを見に行ったら、Version1.7.1 が!、折角なのでコレを使いましょう。笑
因みに「CodeIgniter徹底入門」 はバージョン1.6を使って書かれていますが、今のところCodeIgniterを理解する上では全く不便を感じていません(新機能に関しても十分な1.7用の日本語ヘルプが整備されてますから)。ただ1.6用に作られた日本語パッチはそのままでは使えませんので悪しからず。

2~3日ゴチャゴチャいじって感じたことは、とても簡単なPHPフレームワークということ(簡単と言うと語弊があるかもしれません「とっつき易い」と言った方がいいかな?)、例えばWordpressのテンプレートを自分で直せる人ならほんとスラスラと書けちゃうと思います。笑

さて、参考書を読んでいるだけではツマラナイ、でも何か課題がないと先へ進まないので、簡単なSNSのフレームにでも挑戦してみようと思います。
SNSの最低限の枠って、ログイン・ログアウト機能か?、まさかそれだけではつまらないので、BBSかな?、認証ロジックもBBSも 「 CodeIgniter徹底入門 」 にサンプルが載ってたし。あ~でも携帯電話からの投稿は今や外せない機能なので最終的には携帯電話からも書けるBBSを目指してみます。

まず機能を知りたいのでマニュアルの順に機能チェック、version1.7のマニュアルは コチラ 、既にしっかりと日本語に翻訳されているのには驚き!。

URLヘルパをチェックしてて気が付いた事が。。。
uri_segments() と言うファンクションは実装されていませんね、多分uri_string() の間違えだと思います。

あと気になったのが、URLサフィクス(「CodeIgniter徹底入門」の3.3.3)ですが、設定ファイルでサフックスを指定しておくと、FWが勝手にサフィクスを付加してくれます(FORMからSUBMITボタンをクリックした時とかでも勝手にURIに補完される)。徹底入門によると例えば、$config['url_suffix'] =’.html’ としておくと、あたかも静的なhtmlファイルへアクセスしているかのように見せられるとか。。。
これも動的に設定可能なので、セッションIDの埋め込みなんかもスグできそうです、ただ「ページネーション」機能ではこれが働かないみたいです、仕様なのかバグなのかは不明です。ついでにページネーション絡みですは、ページネーションは最低以下の6行書くだけでこのような感じで表示されます。pagination

$this->load->library('pagination');
$config['base_url'] = 'http://example.com/index.php/test/page/';
$config['total_rows'] = '200';
$config['per_page'] = '20';
$this->pagination->initialize($config);
echo $this->pagination->create_links();

この base_url の部分ですが、上の例の様な直書きでは問題ありませんが、これを

$config['base_url'] = current_url();

と書きたい所なのですが上手く行かないですね、ページ番号をクリックする度にページ番号を表すセグメントが、末尾にどんどん追加されてしまいます。
例 ) http://example.com/index.php/test/page/4/8/16/32/64/32/16/32/64/128
$config['uri_segment']= でページ番号に使うエレメントも指定できるようなのですが、上手くいきませんでした。

ps.
ところで、『 CodeIgniter 』 どう発音するのでしょうか?
コードイグナイター?、コードイグニター?

ページネーションとサフィックス(セッションID)を共存させるには

まだ始めたばかりで、この手法で良いのかわかりませんが、手っ取り早くURLの最後にサフィックスを反映させるには、
{プロジェクト名}/libraries/Pagination.php の修正が簡単。
※元ソースを修正するのではなく、
{プロジェクト名}/system/application/libraries/Pagination.php へコピーしたモノを修正する。

まず、サフィックスの取得、さらにそれを利用して base_url に埋め込まれているサフィックス(セッションID)を削除しておく

$url_suffix = $CI->config->item('url_suffix');
$this->base_url = str_replace($url_suffix, '', $this->base_url);

以下の5行が出てくるので(連続はしていません)、上記で取得した、$url_suffix を最後のエレメントに足す。

$output .= $this->first_tag_open.'<a href="'.$this->base_url.'">'.$this->first_link.'</a>'.$this->first_tag_close;
$output .= $this->prev_tag_open.'<a href="'.$this->base_url.$i.'">'.$this->prev_link.'</a>'.$this->prev_tag_close;
$output .= $this->num_tag_open.'<a href="'.$this->base_url.$n.'">'.$loop.'</a>'.$this->num_tag_close;
$output .= $this->next_tag_open.'<a href="'.$this->base_url.($this->cur_page * $this->per_page).'">'.$this->next_link.'</a>'.$this->next_tag_close;
$output .= $this->last_tag_open.'<a href="'.$this->base_url.$i.'">'.$this->last_link.'</a>'.$this->last_tag_close;

※修正後

$output .= $this->first_tag_open.'<a href="'.$this->base_url.$url_suffix.'">'.$this->first_link.'</a>'.$this->first_tag_close;
$output .= $this->prev_tag_open.'<a href="'.$this->base_url.$i.$url_suffix.'">'.$this->prev_link.'</a>'.$this->prev_tag_close;
$output .= $this->num_tag_open.'<a href="'.$this->base_url.$n.$url_suffix.'">'.$loop.'</a>'.$this->num_tag_close;
$output .= $this->next_tag_open.'<a href="'.$this->base_url.($this->cur_page * $this->per_page).$url_suffix.'">'.$this->next_link.'</a>'.$this->next_tag_close;
$output .= $this->last_tag_open.'<a href="'.$this->base_url.$i.$url_suffix.'">'.$this->last_link.'</a>'.$this->last_tag_close;

これで、URLが、『 base_url / ページ番号 / セッションID 』の順番になりました。
かなり強引ですね、まぁ理解が深まる過程でもっとマトモな方法が見つかることを期待しながら先に進みましょう。

Tagged with:
3月 10

昨年から手つかずだったOpenID認証を色々と実験中です、ライブラリは OpenID EnabledPHP OpenID Library が有名で資料も多いのでこれを利用してみましたが、とりあえず以下の国内大手のOpenIDプロバイダを使っての認証実験は成功しました。

mixi OpenID
初OpenID! ということで、まずはFWなど使わず書いてみて何となく(結構大変でしたが)認証まで行けたので、次にCodeIgniterで書いてみたところ、書くまでもなく超親切なサンプル付きライブラリが用意されていました。w
さてこれを使って何かを作ってみようと思ったのですが、アイディアが浮かびません。(苦笑
ってゆうか、自分で作っていながらイマイチOpenID認証の便利さが理解できていない気がします、確かに1つのIDで色々なサイトへログインできるのは便利なんだけど、結局ログイン先でメールアドレスを登録したりプロフィールを登録したり、大して便利ぢゃない感じがしてなりません。

そんな不満はあるものの mixi OpenID には他のサイトにない面白い機能があります、「マイミク認証」、「コミュニティ認証」と呼ばれるもので、誰かとマイミク関係にある人しかログインさせないとか、特定のコミュニティのメンバーしかログインできない等の条件がつけられるのです。
mixi OpenID
※「フェンダージャズマスターコミュニティ」会員以外は認証資格なし。

mixi OpenId
※「フェンダージャズマスターコミュニティ」会員の場合は規約が表示される。

それから、OpenIDプロバイダ によってサポートされる項目は全く違うのですが、SREG(OpenID Simple Registration Extension)を使ってのユーザー情報の取得は面白いですね。

  • openid.sreg.nickname:ニックネーム
    Any UTF-8 string that the End User wants to use as a nickname.
  • openid.sreg.email:メールアドレス
    The email address of the End User as specified in section 3.4.1 of [RFC2822] (Resnick, P., “Internet Message Format,” .).
  • openid.sreg.fullname:フルネーム
    UTF-8 string free text representation of the End User’s full name.
  • openid.sreg.dob:生年月日
    The End User’s date of birth as YYYY-MM-DD. Any values whose representation uses fewer than the specified number of digits should be zero-padded. The length of this value MUST always be 10. If the End User user does not want to reveal any particular component of this value, it MUST be set to zero.
    For instance, if a End User wants to specify that his date of birth is in 1980, but not the month or day, the value returned SHALL be “1980-00-00″.
  • openid.sreg.gender:性別
    The End User’s gender, “M” for male, “F” for female.
  • openid.sreg.postcode:郵便番号
    UTF-8 string free text that SHOULD conform to the End User’s country’s postal system.
  • openid.sreg.country:国
    The End User’s country of residence as specified by ISO3166.
  • openid.sreg.language:言語
    End User’s preferred language as specified by ISO639.
  • openid.sreg.timezone:タイムゾーン
    ASCII string from TimeZone database
    For example, “Europe/Paris” or “America/Los_Angeles”.

※現状mixiでサポートされている項目は「nickname」のみのようです。

さてこれらをどう応用しましょうか?、使ってみたいのは山々なのですが、これまたアイディアが。。。(^^;
OpenPNEの認証にmixi ID。なんてのは so-net さんがやってるしなぁ。。。
もっと面白いことはないでしょうか?※ご意見募集中

ps.
マイミクしかコメントできないBlogならまだいいけど、マイミクしか入れないSNS?、マイミク切られた瞬間にログイン不可!エーー(泣。。

参考サイト
http://www.openid.or.jp/
[いますぐ使えるOpenID]第1回 OpenIDサービスを利用して,OpenIDの仕組みを理解する
ミクシィ,OpenIDを使った認証サービス「mixi OpenID」の提供を開始
速報、1500万人が使える mixi OpenID の技術面を解説するでござるの巻

Tagged with:
preload preload preload