「ほっ」と。キャンペーン
タグ:CakePHP ( 19 ) タグの人気記事
(PHP)ページ遷移ありで違うサーバからファイルをダウンロードさせる方法
(PHP)PHPでファイルをダンロード(アクセス制限付き)とかぶってる内容ありです。

現在下記のような感じでダウンロードページを構築していました。
ダウンロードボタン押下→サンクスページに遷移→ファイル保存ダイアログが開く。
ちょうど、ベクターのダウロードのような感じです。

これで、現在はファイルをPHPスクリプトを置いてるレンタルサーバ上(ServerA)においてるんですが、このレンタルサーバがファイル転送の速度が遅いので、ダウンロードさせるファイルだけ、もっと高速な別レンタルサーバ(ServerB)に置いて欲しいという要件でした。
(ダウンロード一覧処理やページ遷移後のダウンロードなどは全て CakePHP でDBと連携させてますが、これらは元のレンタルサーバに置いたままとします。)

で、考えたのは、ServerBにダウンロードロジックを置いた PHP スクリプトを置いて、ServerA からリダイレクトでその PHP をたたくという方法です。

以下サンプルソースです。(ServerA側は CakePHP 使ってますが、普通のPHPに書き直しても使えると思います。)

ServerA側の CakePHP ダウンロード用コントローラ
<?php
class DownloadsController extends AppController{
var $name = "Downloads";
/**
* ダウンロードファイルリストの一覧作成・表示
*
* @access public
*/

function index(){
//省略。。。ここで、DBからダウンロードさせるファイルの一覧を取得してます。*/
}
 
/*
* ダウンロードメッセージ表示ページ
* ダウンロード案内のメッセージを表示し、ダウンロードを行うページへの遷移を行う。
*
* @param int $id ダウンロードする対象のid (downloads.id)
* @access public
*/

function get( $id ){
//レイアウトファイルはdown.ctpを使う。
$this->layout = 'down';
//DBから指定されたidのデータ取得
$where = array("id" => $id );
$dwn = $this->Download->find( $where );
 
//存在しないもしくはダウンロード許可されていない場合はエラーメッセージ
if ($dwn == null)
{
$msg = '存在しないファイルが指定されました。ダウンロードできません。';
}else{
$msg = '
※ダウンロードに関する注意点<br/>
お使いのブラウザや設定によっては「セキュリティ保護のため...」という情報バーが表示される場合があります。<br/>
<br/>
もしくは<a href="/downloads/down/'
. $dwn['Download']['name'] . '">こちらのリンク</a>からダウンロードしてください。 <br/>';
//ダウンロードページ遷移のヘッダを埋め込む($dwn['Download']['name']にはダウンロードさせるファイル名が格納)
$metaRefresh = '<meta http-equiv="Refresh" content="1;URL=/downloads/down/' . $dwn['Download']['name'] .'">';
//ダウンページ遷移ヘッダをレイアウトにセット(レイアウトファイルのヘッダにこのメタデータを出力する記述がいる)
$this->set('metaRefresh', $metaRefresh);
}
$this->set('msg', $msg);
}
 
/*
* ダウンロード処理
* 引数で渡されたidのダウンロードファイルを実際にダイアログを表示させてダウンロードさせる。
*
* @param int $filename ダウンロードする対象のファイル名
* @access public
*/

function down( $filename )
{
if ($filename != null)
{
//ServerBのダウンロード処理PHPにリダイレクト
header("Location:http://ServerB/down.php?file=" . $filename);
exit;
}
}
 
}
?>


ServerA側の CakePHP ダウンロード用レイアウトファイル(ダウンロードページ遷移へのメタデータ)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
 
<head>
<title>test</title>
<?php //ダウンロード用メタデータ。メタデータがあれば、それを出力。メタ情報用変数がなければ何もしない ?>
<?php if ( isset( $metaRefresh ) ) : ?>
<?php echo $metaRefresh ?>
<?php endif; ?>
省略....
</head>
 
<body>
省略....
</body>
</html>



ServerB側で実際にダウンロード処理を行うPHP(GET変数でファイル名をもらう)
<?php
//ダウンロード開始させる
 
//GET引数取得(downloadファイル名が入る)
$path_file = $_GET["file"];
/* ファイルの存在確認 */
if (!file_exists($path_file)) {
die("Error: File(".$path_file.") does not exist");
}
 
/* オープンできるか確認 */
if (!($fp = fopen($path_file, "r"))) {
die("Error: Cannot open the file(".$path_file.")");
}
fclose($fp);
 
/* ファイルサイズの確認 */
if (($content_length = filesize($path_file)) == 0) {
die("Error: File size is 0.(".$path_file.")");
}
 
/* ダウンロード用のHTTPヘッダ送信 */
header("Content-Disposition: inline; filename=\"".basename($path_file)."\"");
header("Content-Length: ".$content_length);
header("Content-Type: application/octet-stream");
 
/* ファイルを読んで出力 */
if (!readfile($path_file)) {
die("Cannot read the file(".$path_file.")");
}
 
?>

[PR]
by jehoshaphat | 2009-08-18 18:21 | PHP開発 | Trackback | Comments(0)
(CakePHP)findAllでのレコードの取得順序の指定方法
久しぶりにメンテのため CakePHP 触ったのですが、使い方ほとんど忘れかけてますorz

とりあえず、DBの商品テーブルから取得した項目に対して価格順に並べ変えて欲しいという依頼だったので、調べたら、findAllの引数で指定できるんですね。

下記のように第三引数に指定するだけでいいっぽいです。
$this->Product->findAll("prd_del=0" , null , "prd_price DESC" );


上記の条件だと下記のようなSQLと等価になります。

SELECT *
FROM Products
WHERE prd_del = 0
ORDER BY prd_price DESC



参考までに、findAllの書式です。

findAll(string $conditions, array $fields, string $order, int $limit, int $page, int $recursive)
$conditions : 取得する条件。where句に相当。(省略時は全件取得)
$fields : 取得するフィールドリスト。select句に相当。(省略時は全フィールド取得)
$order : レコードの取得順。order by句に相当。(省略時はレコードの格納順)
$limit : 取得する件数。(省略時は全件取得)
$page : 取得するページ数(よくわかりません)
$recursive : 再帰的に取得するアソシエーションの深さ(アソシエーション使ってないのでよくわかりません)


で、CakePHP公式マニュアル 3.7.3.2 findAll「findAll は非推奨となりました。代わりに find('all') を使用してください。」って書いてました。
どうやらいつの間にか findAll 非推奨になったんですね。(多分1.2 RTM リリースの時かな? 今回のメンテサイトは1.2RC版使ってるので)

技術の変化が激しいWeb,オープンソースの世界、ちょっとでも最新に乗り遅れると浦島太郎になってしまいます。。。。
[PR]
by jehoshaphat | 2009-08-18 10:26 | PHP開発 | Trackback | Comments(0)
(CakePHP)1.2RC へのバージョンアップ
(cakePHP)コントローラからモデルにアクセスで、1.2RCへのバージョンアップメモを書くと書いたまま忘れてたので、いまさらですが、upしときます。

と、いっても当時(10か月前)の作業メモがちょっとしかなかったので、そこに書いてる範囲内のないようです。

(ちなみに、今って最新版は 1.2.3.8166 にまでなってるんですね。時間ある時にアップデートしなくては。。。)

まず、バージョンアップ時に参考にさせてもらったのが、「CakePHP1.1 to 1.2」発表資料PDFと、blog.katsuma.tv:CakePHPを1.1から1.2へ上げるときの注意点 です。


CakePHP1.1 to 1.2」発表資料PDFにあるように、app ディレクトリ配下を 1.2 にコピーします。

そして、1.2 では view ファイルの拡張子が、thtml から ctp に変更になっているので、そこを修正します。

また、ヘルパーも $html だったのが、大部分 $form に移ったようなので、そこも修正します。

特にヘルパー関連が大幅に変わったのですが、これでだいぶ楽になった部分もありました。
たとえば、1.1 時代には $html->selectTag で select タグを生成していたのが、$form->input('モデル' , array('type' => 'select' , 'label'=>'' , 'options' => コントローラから渡された変数 , 'selected' => '初期値' , 'empty'=>'初期値空の時の文字列')) という感じで、input で出来るようになった点などです。

これで実行したら、

Warning (512): Cache not configured properly. Please check Cache::config(); in APP/config/core.php [ファイルパス\cake\libs\configure.php, line 628]

Code | Context
$boot=true
$vendorPaths=null
$pluginPaths=null
$helperPaths=null
$viewPaths=null
$componentPaths=null
$controllerPaths=null
$behaviorPaths=null
$modelPaths=null
$cache=false

if (empty($cache['settings'])) {
trigger_error('Cache not configured properly. Please check Cache::config(); in APP/config/core.php', E_USER_WARNING);
以下省略....

みたいなエラーが発生しました。

これを解消するには app\config\core.php で下記のようにすればいいそうです。

Configure::write('Cache.disable', true);


バリデート周りも 1.2 で大幅に変わりましたが、このバージョンアップ時には 1.1 でバリデートはまだ作ってなかったので、特に問題ありませんでした。
[PR]
by jehoshaphat | 2009-07-05 13:22 | PHP開発 | Trackback | Comments(2)
(CakePHP)Sanitize::clean($this->data)はバリデート使ってるときはやめた方がいい!?
(CakePHP)$form->textareaはサニタイジングしてくれない!?の追記で $this->data 全体をSanitize::cleanしてましたが、どうやらこれはバリデート使っている時はまずいみたいです。

たとえば、バリデートでメールのチェックを定義し、Sanitize::clean($this->data) にします。
そして、「-」付のメールアドレスを入れます。
そうすると、先にサニタイジングが走るので、- が &#45; となります。
そして、$Model->Save 時にバリデートで 「&」 とか 「#」 とかがチェックに引っ掛かりそこで処理が止まってしまうわけです。

なので、結局各モデルの個別のフィールドごとにサニタイズするようにしました。
そして、メールアドレス等バリデートしてるところは、 Sanitize::html($string , true ) でhtmlタグを削除します。
( Sanitize::html() についてはhttp://book.cakephp.org/ja/view/462/html参照)

このやり方はあまりスマートでないので好きじゃないのですが、解決策が見つかるまで当面この方法でやることにしました。
//モデル
class Member extends AppModel {
//バリデーションの定義。
var $validate = array(
//氏名
'fname' => array(
'between' => array(
'rule' => array('between', 1, 50 , ) , //1-50文字
'allowEmpty' => false, //空の値を許さない
'message' => '氏名は1-25文字の範囲で入力してください。', //エラー時にhelperが出すメッセージ
),
),
//氏名カナ
'kana' => array(
'kanjiChk' => array(
'rule' => array('custom', '/^(?:\xE3\x82[\xA1-\xBF]|\xE3\x83[\x80-\xB6])+$/'),//'/^[ア-ヶ]+$/') , //半角文字以外(全角ONLY)
'allowEmpty' => false, //空の値を許さない
'message' => '氏名カナは全角カタカナで入力してください。', //エラー時にhelperが出すメッセージ
),
'between' => array(
'rule' => array('between', 1, 50 , ) , //1-50文字
'allowEmpty' => false, //空の値を許さない
'message' => '氏名カナは1-25文字の範囲で入力してください。', //エラー時にhelperが出すメッセージ
),
),
//メールアドレス
'mail' => array(
'rule' => 'email' , //メールの書式
'allowEmpty' => false, //空の値を許さない
'message' => '正しいメールアドレスを入力してください。', //エラー時にhelperが出すメッセージ
),
//電話番号
'mem_tel' => array(
'rule' => array('custom', '/\d{2,4}-\d{2,4}-\d{4}/') , //電話番号の書式
'allowEmpty' => true, //空の値を許す
'message' => '正しい電話番号を入力してください。', //エラー時にhelperが出すメッセージ
),
//FAX番号
'mem_fax' => array(
'rule' => array('custom', '/\d{2,4}-\d{2,4}-\d{4}/') , //FAX番号の書式
'allowEmpty' => true, //空の値を許す
'message' => '正しいFAX番号を入力してください。', //エラー時にhelperが出すメッセージ
),
);
}
 
//コントローラ
function index(){
...
if (empty($this->data))
{
//初回アクセス時
$this->render();
}
else
{
//2回目以降のアクセス(POSTデータがあったアクセス)
//モデルにPOSTデータセット
$this->Member->set( $this->data );
$this->Order->set( $this->data );
//サニタイズされないので、手動でサニタイジング
$this->_sanitize();
....
}
}
 
 
/**
* サニタイズする必要がある項目のみ、サニタイジング
*/

function _sanitize(){
App::import('Sanitize');
$this->data['Member']['name'] = Sanitize::clean($this->data['Member']['name']);
$this->data['Member']['kana'] = Sanitize::clean($this->data['Member']['kana']);
//mail,tel,faxは Sanitize::html を用いる
$this->data['Member']['mail'] = Sanitize::html($this->data['Member']['mail'], true);
$this->data['Member']['tel'] = Sanitize::html($this->data['Member']['tel'], true);
$this->data['Member']['fax'] = Sanitize::html($this->data['Member']['fax'], true);
$this->data['Member']['addr'] = Sanitize::clean($this->data['Member']['addr']);
$this->data['Order']['note'] = Sanitize::clean($this->data['Order']['note']);
}

[PR]
by jehoshaphat | 2009-01-27 19:22 | PHP開発 | Trackback | Comments(1)
(CakePHP)$form->textareaはサニタイジングしてくれない!?
CakePHP1.2

CakePHPは基本的に $form->input 等でデータを入力するときは htmlspecialchars() でサニタイズしてくれます。
が、なぜかテキストエリアをする $from->textarea() だけはサニタイズしてくれません。
なので、容易にクロスサイトスクリプティング(XSS)されてしまいます。

ということで、ここここを参考にしてみましたが、1.2のせいなのかどうかわかりませんが、うまくいきません。

仕方ないので、コントローラ内でモデルにセットした後にh()でエスケープすることにしました。
( h() は htmlspecialchars() の省略形らしいです)
//ビュー
<?php echo $form->textarea("Order.note", array("cols"=>"40" ,"rows"=>"5" , "label"=>"" ))?>
 
//コントローラ
//モデルにPOSTデータセット
$this->Order->set( $this->data );

//textareaはサニタイズされないので、手動でサニタイジング
$this->data['Order']['note'] = h($this->data['Order']['note']) ;

まあ、データはそのままで出力時にサニタイズするというアプローチもあると思いますが、今回は入力時にサニタイジングしてます。

追記: 2009/1/26
上で「 $form->input はサニタイズしてくれる」と書きましたが、試したところサニタイズしてくれませんでした。どうやら1.1とはさっぱし変わったようです。

結局 CakePHP の Sanitize::clean() を使うことにしました。
//コントローラ
 
//モデルにPOSTデータセット
$this->Order->set( $this->data );
//サニタイズされないので、手動でサニタイジング
App::import('Sanitize');
$this->data = Sanitize::clean($this->data, true);

$this->を丸ごとサニタイジングします。
なお、Sanitizeクラスについては下記参照。
http://book.cakephp.org/ja/view/153/%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AE%E3%82%B5%E3%83%8B%E3%82%BF%E3%82%A4%E3%82%BA-Data-Sanitization

追記2 2009/1/27
上の方法でもまずい場合があります。
詳しくは(CakePHP)Sanitize::clean($this->data)はバリデート使ってるときはやめた方がいい!?を参照。
[PR]
by jehoshaphat | 2009-01-26 19:41 | PHP開発 | Trackback | Comments(2)
(CakePHP)ヘルパーの$htmlAttributesの意味
CakePHPのポケットリファレンス見てるとヘルパーのところでメソッドの引数に「$htmlAttributes=array() 要素の属性」とあるヘルパーが結構あります。
CakePHP ポケットリファレンス (Pocket Reference)
CakePHP ポケットリファレンス



たとえば link ヘルパーの定義は下記のようになっていますが、やっぱり htmlAttributes があります。
link( $title, $url , $htmlAttributes, $confirmMessage = false, $escapeTitle = true, $return = false);

この $htmlAttributes とはなんじゃらほいということで調べたらCakePHP: $html->link 関数の説明に答えが。。

要はHTMLの属性のことですね。たとえばリンク(aタグ)ならHTML共通の属性も含めると href,name,style,class,id 等々がありますね。
これらを $htmlAttributes で指定できるっぽいです。

前の記事の後半でaタグにclass属性でスタイルを定義してましたが、それをCakePHPのヘルパーを使うとこうできます。
<?php echo $html->link(null, "リンク先URL" , array( 'class'=>'linktest' ) )?>
(HTMLに直すと)
<a href="リンク先URL" class="linktest" ></a>


余談ですが、この属性を指定しないときは null ではなく空の配列 array() を渡したほうがいいです。詳しくは(cakePHP)Formヘルパーのselectボックス(時,分)表示 Warning array_merge()が出た。を参照。
[PR]
by jehoshaphat | 2009-01-20 20:04 | PHP開発 | Trackback | Comments(0)
(CakePHP)ビューでループや条件分岐がしたい
コントーラー側で findAll で複数のレコードを取得しそれをそのままビューに渡したときにビューでループ処理できないと不便ですよね。
また、これはあんまり好ましくないかもしれませんが、条件分岐もビューでできるようです。

下記のような感じになります。
<!--ループ処理-->
<?php foreach ($list as $value): ?>
<!--条件分岐(ファイルがあればという条件)-->
<?php if ( file_exists("/product/" . $value['Download']['name']) == true): ?>
 
いろいろな処理。。。
 
<!--if文の終了-->
<?php endif; ?>
<!--ループの終了-->
<?php endforeach; ?>

endforeachってなんぞそれ! の記事によると、while():~endwhile; もあるらしいです。

if: / endif;が使えなくなる?という記事もありましたが、これを見る限りCakePHPというよりかはPHP自体の機能なんでしょうかね?

参考:
endforeach文
[PR]
by jehoshaphat | 2009-01-19 20:50 | PHP開発 | Trackback | Comments(0)
(cakePHP)ビューにて、日付を指定したフォーマット表示する
通常のphpだと date("Y年 n月 j日" , $timestamp) というようにタイムスタンプの値をdate関数に渡してやれば指定したフォーマットの文字列が帰ってきます。

で、cakePHPでは日時は文字列で持っているので、 strtotime 関数で一旦日付をタイムスタンプ型に戻してやらないといけません。
こうなります。
<?php echo date("Y年 n月 j日" , strtotime($value['Model']['field'])) ?>

[PR]
by jehoshaphat | 2009-01-19 19:10 | PHP開発 | Trackback | Comments(0)
(CalePHP)日付で初期値を空白にさせる
CakePHP1.2

$fomm->inputヘルパー で'type'=>'date' で日付を入力するコンボボックスを作れますが、デフォルトだと値が本日の日付となってしまいます。
これを選択されていないように空とするには下記のようにすればOKです。
<?php echo $form->input('Member.mem_birthday', array("type" => "date" , "label"=>"" , "dateFormat"=>"YMD" , "minYear"=>"1950" , "maxYear"=>"2008" , "monthNames"=>false , 'separator' => ' / ' , 'selected' => ' ' , 'empty'=>' '))?>

要は、'empty'=>' 'で空の要素を有効にしておき、'selected'=>' 'で空の要素をデフォルト値としておきます。
注意しないといけないのは selected の値は半角空白( ' ' )をクォーテーションで囲む必要があります。自分の環境の場合、空文字( '' )だけだと月と日は本日の日付が入ってしまっていました。
[PR]
by jehoshaphat | 2008-12-27 14:50 | PHP開発 | Trackback | Comments(0)
(CakePHP)携帯電話でもセッションを使えるようにする
最近のauやソフトバンクならクッキーを使える(ゲートウェイでクッキーつけてるっぽい?)ので問題ないのですが、厄介なのはドコモです。
ドコモはクッキーも使えませんし、HTMLのほうも、CSSが全く利用できません。
つくづく、独自規格をいくキャリアですね。そんなだから世界の中で孤立してしまうんですよ。
とぼやいてもしかたないので、URLにセッションIDを持たすようにCakePHPを改造します。

参考にさせていただいたのはCakePHPで携帯サイトを作るです。

CakePHPのバージョンは1.2.0.7692 RC3です。

■まず、index.phpと同じディレクトリにある.htaccessに下記を追加します。

php_flag session.use_trans_sid On
php_flag session.use_cookies Off

これにより、クッキーの使用をOFFにして、リンクで移動するところ全てにGETセッションIDを引き継げるようになるらしいですが。。(つまり、リンクに勝手にセッションIDがつくということなんでしょう。)
しかし、なぜか自分の環境だとリンクに勝手にセッションIDがついてくれませんでした。
ということで、リンクや、フォームに全て手動でセッションIDをつけるようにしました。

■app/config/core/phpの設定変更
Security.levelをmidiumにします。
また、Security.levelをmidiumにするとセッションタイムアウトの時間がSession.timeout*100秒になってしまいます。デフォルトの120だと 120秒*100/60=200分ということで、3時間半にもなってしまうので値を小さくします。
なお、Security.levelとSession.timeoutの関係にはこちらを参照のこと。

//Configure::write('Security.level', 'high');
Configure::write('Security.level', 'medium');
//Configure::write('Session.timeout', '120');
Configure::write('Session.timeout', '20');//Security.level=mediumで33分


■cake/libs/controller/app_controller.php redirectメソッドをオーバーライド
リダイレクト時にセッションIDを負荷するよう redirect メソッドを下記のようにオーバーライドします。
class AppController extends Controller {
 
//redirect メソッド書き換え
function redirect($url,$status = null){
//携帯用にリダイレクトのURLの後ろにセッションIDを付けておく
if (strpos($url, "?") === false) {
$url = $url."?".session_name()."=".session_id();
}else{
$url = $url."&".session_name()."=".session_id();
}
parent::redirect($url,$status);
}
}


■cake/libs/router.php function url()の890行目付近を次のように書き換えます。

if (!empty($named)) {
foreach ($named as $name => $value) {
$output .= '/' . $name . $_this->named['separator'] . $value;
}
}

新  ↓
//携帯対応書き直し分
if (!emptyempty($named)) {
$i = 0;
foreach ($named as $name => $value) {
//$output .= '/' . $name . $_this->named['separator'] . $value;
if ($i == 0) {
$output .= '?' . $name . "=" . $value;
}else{
$output .= '&' . $name . "=" . $value;
}
$i++;
}
}



■ビューでリンク時、フォーム送信時にセッションIDをつける
ビューにおいて、リンク時は下記のように最後にセッションIDをつけます。

<?php echo $html->link('リンク', '/links/add?CAKEPHP=' . $_GET["CAKEPHP"] ); ?>

また、フォームのactionにもセッションIDを付与してあげます。

<form action="<?php echo $form->url('/links/add?CAKEPHP=' . $_GET["CAKEPHP"] )?>" method="post">


これでなんとか、URLでセッションを引き回せるようになりました。
ほんとはPCと携帯で振り分けてPCならクッキー、携帯ならGETでという風にしたいのですが、これは時間あるときにやってみたいと思います。
[PR]
by jehoshaphat | 2008-12-21 01:00 | PHP開発 | Trackback | Comments(0)