5月 01

携帯絵文字+CIのお勉強ということで、BBSっぽいモノを作ってみた。
エラーチェック(文字数制限、連続投稿防止とかも)等々まったく組み込んでません、今回は一通りの機能を使ってみて携帯電話の扱いとCIに慣れるという目的なので。
emoji paddocomoiphone1iphone2
http://hiro.oneoctave.jp/cibbs/

一応、i-modeからも絵文字使えるみたいです(PC絵文字との相関テーブルを定義していのでPC絵文字はBBCODE形式で表示されてしまいます)。
一応、iPhoneからも機能の半分くらい使えます、2画面くらいしか作っていないのでFile not found 出まくります。。。

このプログラム自体は、まったく実用には耐えられないけど、何となくCIが見えてきた感じです。

つぎは密かにDJangoに手をつけ。。。(汗

Tagged with:
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:
preload preload preload