PHP」タグアーカイブ

PHP 備忘録:設定

php.iniの設定

strtotime()関数を使用する

date.timezone = “Asia/Tokyo”

php echoの省略の設定

short_open_tag = on

GD関係のインストール

yum -y install gd gd-devel php-gd

DOMのインストール

DOMDocumentを使ってXMLを操作
yum -y install php-xml

CakePHP + jQuery webcam plugin

プロフィールの画像を直接取得できればいいかなぁって思い、このプラグインを使ってみました。

最初は、jQueryだけでwebカメラの操作ができるんだって思っていました。
でも実際は、flashとjQueryを組み合わせて使います。

概要

  • ウェブカメラからの画像を画面にキャプチャする(html5 canvas)
  • ウェブカメラからの画像を取得しサーバー上に保存する

※デスクトップキャプチャソフト等も使えます。

完成イメージ

参考サイト

配布元
Ajalog.7z:jQueryでWebカメラを制御する

環境

  • XAMPP
  • CakePHP1.3.5
  • jQuery1.4.3
  • jQuery webcam plugin

準備

  1. 配布元からファイルをDL&展開
  2. 展開してできたファイルを適当な場所に設置(e.g. app/webroot/js/jquery.webcam.js app/webroot/swf/jscam.swf and jscam_canvas_only.swf)
  3. ウェブカメラを接続 or デスクトップキャプチャ等のソフトをインストール

webcam plugin

swfファイル

次の2つがあります。使いたい状況に応じて設定します。

  1. jscam.swf
  2. jscam_canvas_only.swf

1.はすべての機能が使えます。
2.はcanvasに描画する機能のみです。サーバー側にsaveしたい場合は1.を使います。

function

  • capture(delay):delay秒後にcaptureします
  • save(file): キャプチャ画像を保存 mode:”save”時にfile名を設定するとサーバー側に保存
  • getCameraList():有効なカメラリストの取得
  • setCamera(index):getCameraList()のindexでカメラを切り替える

mode

  • callback
  • save
  • stream

の3種類があります。
callbackは各コールバック関数を呼びます。
saveはcallbackと変わりませんが、save(file)を実行するときにデータを指定したファイルパスへ送信します。
streamは配布元を参照して下さい。

他のパラメータは、配布元を参照してください。

キャプチャした画像をcanvasに描画する処理は上記の参考サイトに書いてあるので、ここではサーバー側の処理を行います。

mode:“save”
onSave():画像の保存完了後に呼び出される。
保存画像を表示させるには、
このコールバックの中でページを書き換え処理を入れるか、
リダイレクトを行えばいいと思います。

jQuery関係のファイルは適当な場所で読み込む。
app/views/webcams/upload

	<strong>Webカメラから画像を設定</strong>
		<div id="jquery_camera"></div>
		<div id="camerastatus" style="margin-left: 10px;">
			<strong>カメラリスト</strong>
			<ul id="cams"></ul>
			<div id="status"></div>
			<button id="btn">キャプチャ</button>
		</div>
		<?php $this->Html->scriptStart(array('inline' => false)); ?>
			$(function(){
				var width = 320;
				var height = 240;
				$("#jquery_camera").webcam({
					width: width, // webcamの横サイズ
					height: height,// webcamの縦サイズ
					mode: "save",
					//swffile: "/swf/jscam_canvas_only.swf",
					swffile: "/swf/jscam.swf",
					// captrue(delay)が実行されたとき、残り時間を引数にもち、
					// 1秒ごとに呼び出される
					// e.g. delay = 5のときは remainが5,4,...,1と変化して呼び出される。
					onTick: function(remain) {
						if(0 == remain){
							$("#status").text("Cheese!");
						}else{
							$("#status").text(remain + "seconds remaining...");
						}
					},
					// mode:saveの場合
					// サーバー側での保存が完了し、サーバーからのレスポンスがくると呼ばれる
					onSave : function () {
						//キャプチャした画像をwebcam.saveメソッドで保存すると呼び出される。
						$("#status").text("done!!");
						var url = "リダイレクト先";
						window.location = url;
					},
					// Capture時に呼び出される
					onCapture: function() {
						webcam.save('/users/upload/<?= $this->data['User']['id'] ?>');
					},
					// debug用
					debug: function(type, string) {
						$("#status").html(type + ": " + string);
					},
					// 読み込み完了時
					onLoad: function() {
						var cams = webcam.getCameraList();
						for(var i in cams) {
							$("#cams").append("<li>" + cams[i] + "</li>");
						}
						$("#btn").click(function(){
							webcam.capture();
						});
					}
				});
			})
	<?php $this->Html->scriptEnd(); ?>

CakePHP

app/controllers/webcams_controller.php

・・・
	function upload($id = null){
		$this->laout = '';
		$updir = ini_get('upload_tmp_dir');
		$str = file_get_contents("php://input");
		$filepath = $updir . DS . 'user.jpg';
		file_put_contents($filepath, pack("H*", $str));
		// これ以降にDBへの登録処理をおこなう
		// e.g.下の処理はMediaPluginを使った時の処理の例
		$this->data['Picture']['title'] = '';
		$this->data['Picture']['description'] = '';
		$this->data['Picture']['model'] = 'User';
		$this->data['Picture']['foreign_key'] = $id;
		$this->data['Picture']['file'] = $filepath;
		if($this->Picture->save($this->data)){
			@unlink($filepath);
			exit();
		}else{
			//失敗時の処理
		}
・・・

PHP 二重送信防止処理(サーバー側)

サーバ側での二重送信防止

前にも二重送信防止のことを書きました。
jQueryでの二重送信防止

前の二重送信の防止はjQueryで制御していました。

つまり、クライアント側で送信防止をしていたのです。

でももしかすると、javascriptが有効になっていない場合だってあるかもしれません。

そんなときに、Enterキーを

ケンシロウ並に連打!!!されたら、ひとたまりもありません。

不必要なデータがたくさん登録されてしまいます。。。

そんなときは、サーバ側で防止するしかありません。

サーバー側での二重送信防止のイメージ

二重送信防止

流れは以下の感じです。

  • 最初にランダムなトークン(適当な文字列)を生成
  • sessionとhtmlのhidden属性に保存する
  • 送信したいデータとともに、hidden属性に設定したトークンをPOSTする
  • POSTされてきたトークンとsessionに保存していたトークンとを比較
  • 同じであれば登録しsessionのトークン情報を破棄、違っていれば、エラー処理

二重送信防止

CakePHPで実装

app/controllers/main_controller.php

	function doublesubmit(){
		if(!empty($this->data)){
			// Tokenの比較
			if($this->Session->read('dstoken') == $this->data['Example']['dstoken']){
				unset($this->data['Example']['dstoken']);
				$this->Session->delete('dstoken');
				if($this->Example->save($this->data)){
					$this->flash('おーけーです', array('controller'=>'main', 'action' => 'doublesubmit') ,5);
				}else{
					$this->flash('えぬじーです', array('controller'=>'main', 'action' => 'doublesubmit'), 5);
				}
			}else{
				$this->flash('すでに送信済みです', array('controller'=>'main', 'action' => 'doublesubmit'), 5);
			}
		}

		// Tokenの発行&Sessionに保持
		$dstoken = $this->_rand(12); //ランダムな文字列を生成する関数用意
		$this->Session->write('dstoken', $dstoken);
		$this->data['Example']['dstoken'] = $dstoken;
	}

app/views/main/doublesubmit.ctp

<h1>二重送信防止</h1>
<?php
	echo $form->create('Example', array('url' => array('controller' => 'main', 'action' => 'doublesubmit')));
	echo $form->input('title', array('type' => 'text'));
	echo $form->input('place', array('type' => 'text'));
	echo $form->input('dstoken', array('type' => 'hidden'));
	echo $form->submit('送信');
	echo $form->end();
?>

ランダムな文字列の生成は下記のサイトのコードを使用させてもらいました。

てくめも

コンポーネント化?

CakePHPだとSecurityComponentを使えばいいという情報を見つけたが、
せっかくなので自分でコンポーネント化してみることにした。

※まだ動作確認はあまりしていません。。。
app/controllers/components/prevent_double_submit.php

<?php
/************************************************
 * PreventDoubleSubmitComponent
 * 		二重送信を防止する
 * Usage
 * Controller:
 * 		$conponentsに追加。$doublesubmitsにオプションの設定をする。
 * View:
 * 		$this->Form->input('Token.token', array('type' => 'hidden'));を追記する。
 *
 ************************************************/
class PreventDoubleSubmitComponent extends Object{

	public $controller = null;
	public $components = array(
		'Session',
	);
	/**
	 * @var
	 * model:string		モデル名
	 * token:string		view側のhidden属性に設定するfield名
	 * auto:bool		自動でtokenの設定&二重送信のチェックをする(true)かどうか
	 * url:array		デフォルトは現在のアクセスパス
	 * message:string	setFlashに設定する文字列
	 */
	public $option = array(
		'model' => 'Token',
		'token' => 'token',
		'auto' => true,
		'url' => array(),
		'message' => null,
		'token_length' => 10,
	);

	//===============================================
	// コールバックメソッド
	//===============================================
	function initialize(&$controller){
		$this->controller = &$controller;
		if(isset($this->controller->doublesubmits)){
			$this->option = am($this->option, $this->controller->doublesubmits);
		}
	}

	function startup(&$controller){
		if(!empty($this->controller->data)){
			extract($this->option);
			if($this->option['auto']){
				if($this->isDoubleSubmit()){
					if(!empty($message)){
						$this->Session->setFlash($message);
					}
					$u  = array(
						'controller' => $this->controller->params['controller'],
						'action' => $this->controller->params['action'],
						$this->controller->params['pass'],
					);
					if(!empty($url)){
						$u = am($u, $url);
					}
					unset($this->controller->data);
					$this->controller->redirect($u);
				}else{
					$this->Session->delete($token);
					unset($this->controller->data[$model][$token]);
				}
			}
		}
	}
	function beforeRender(&$controller){
		if($this->option['auto']){
			$this->setToken();
		}
	}

	//===============================================
	// メソッド
	//===============================================
	/**
	 * トークンを生成しセットする
	 * @return
	 */
	function setToken(){
		extract($this->option);
		$t = $this->rand($token_length);
		$this->Session->write($token, $t);
		$this->controller->data[$model][$token] = $t;
	}

	/**
	 * 二重送信のチェック
	 * @return bool
	 */
	function isDoubleSubmit(){
		$ret = false;
		extract($this->option);
		if(isset($this->controller->data[$model][$token])){
			if($this->Session->read($token) != $this->controller->data[$model][$token]){
				$ret = true;
			}
		}
		return $ret;
	}

	/**
	 * ランダムな文字列を生成する
	 * @param object $length [optional]
	 * @return string ランダムな文字列
	 */
	function rand($length = 8){
	    $char = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
   		mt_srand();
    	$ret = "";
    	for($i = 0; $i < $length; $i++){
       		$ret .= $char{mt_rand(0, strlen($char) - 1)};
    	}
    	return $ret;
	}
}
?>

【追記 start 2011/04/26】==================================
2重送信のアクセスがときどきあるので、
PreventWSubmitComponentを更新してみました。
修正したものはこちらです。

※CakePHP2.xではセキュリティコンポーネントを使って2重送信の防止ができるのでそちらを利用したほうがいいかもしれません。
【追記 end 2011/04/26】==================================