ファイルアップロードコンポーネントの不具合

http://www.reversefolds.com/articles/show/filehandler:ReverseFolds - CakePHP File Uploads
このコンポーネントを使ってcakephpでファイルアップロード機能をつけてみたけど、たぶん1つバグがあるっぽい?handlerTypeをdbにして、後でlastUploadData();を呼ぶと、$lastUploadData["id"]が常に0で返ってしまう。

app/controllers/components/file_handler.php

<?php
        if ($this->_handlerType == 'db') {//389行目

            //〜省略〜

            $fileId=$this->controller->{$this->_dbModel}->getLastInsertId(); //409行目
        }
?>

以上のようにして、getLastInsertIdを使って変数$fileIdを書き換える必要がある。

cakephp1.1系の$html->textareaについて

最近、初めてのPHPフレームワークとしてcakephpを使い始めた。とてもシンプルでわかりやすくサイトを構築できて、とても気に入っている。しばらくはサンプルに毛が生えた程度のものを作って動かしていたけど、慣れてきたのでそろそろ一般に公開するサイトに仕上げようと思い、セキュリティ関連の対策を確認してみたところ、いきなり落とし穴にハマった。

ハマったのは、データを編集するフォーム(例えば/news/editとかみたいな)。HTMLヘルパーはとても便利で、ビューで$html->input()$html->textarea()等としていれば、コントローラから渡される$this->dataの値をそれぞれの要素の値に入れてくれる。だけどその値は当然htmlspecialchars()というかh()でescapeかけてからそれぞれの要素の中に入れ込んでいるだろうと思っていたら、確認してみればびっくり、textareaにおいてはそういう気を利かせてくれないようだ。これはちょっといろいろ困る。1.2系では修正されているのかも知れないが、現時点での安定版ということで1.1.18.5850を使っているので、HTMLヘルパーに手を加えることにした。HTMLヘルパーは/cake/view/helpers/html.phpにあるけど、/app/view/helpers/html.phpにコピーして編集するのが無難みたい。

<?php
//  /app/view/helpers/html.php 234行目ぐらい

//$value = $this->tagValue($fieldName);
$value = $this->tagValue($fieldName,
      isset($htmlAttributes["escape"])?$htmlAttributes["escape"]:true);

?>

これでデフォルトでescapeされるようになる。特別に、escapeしたくないときは、ビューで

<?=$html->textarea("News/edit",array("escape"=false))?>

と明示すればOK。

mixiミュージック×Winamp×MSNメッセ

現行の純正mixi stationWinampプラグインは、mp3しかメタデータを送信できない。NapsterでダウンロードしてWinampで聴くことが多い自分にとっては、wmaが送信されないのは非常に残念だ。他にも、mp3以外の音楽ファイルをWinampで聴いている人は結構多いんじゃないかと思う。

経緯と挫折

mixiミュージックのサービス開始当初はそもそもWinamp自体に対応しておらず、Winampユーザーは有志の方の作ったgen_mixi.dllというプラグインを使ってmixi stationにデータを渡していた。でもそのプラグインが正常に動いたのはmixi stationのbuild 20060626まで。それ以降のmixi stationでは動かないので、mixi stationをアップデートしてしまってからはこのプラグインは使えなくなってしまっていた。それから随分と時が経ったが、ふとPCに入っていた最新版のmixi stationをアンインストールし、build 20060626を入れ直し、gen_mixi.dllを再び入れてみた。過去にはこれで動いていたので、これでまたwmaファイルのメタデータが送れるだろうなと。でも期待は裏切られた。さすがにbuild 20060626というのは古すぎるらしく、mixiのサーバー側で通信を蹴っている様子。これではどうしようもない。あきらめかけた。

Windows Live Messenger(MSN Messenger)を通す

そこで、MSNメッセの曲名表示機能を利用する方法を試してみた。データの送信のイメージとしては、Winamp→MSNメッセ→mixi stationmixiという流れ。

これで問題なく送信されている!注意点としては、各ソフトを起動する順番。MSNメッセ→Winamp→M2M.exe→mixi stationという順番を守らないと動かないかも。とりあえずM2M.exe初回起動時は必ずmixi stationは終了させておくこと(エラーメッセージが出ます)。

モブログするためのalias

メールを受信したらPHPスクリプトに標準入力しよう、というありがちなお話。qmailのaliasはやったことがあったのだけど、今回はMTAがpostfixだった。いろいろなサイトを参考に、当初、以下のように設定してみたけど、これだとうまく動かなかった。

/etc/aliases 動かなかった例

blog_example: "|/home/example/public_html/mt/p.php blog"

/var/log/maillog エラー文面(抜粋)

execvp /home/example/public_html/mt/p.php : No such file or directory.

No such fileとか言われても、ファイルはちゃんと存在しているのだが…。postfixの仕様書とかほとんど読んでないから、原因不明。とりあえず以下のように書くと動いた。

/etc/aliases 意図した通りに動いた例

blog_example: "|php -q /home/example/public_html/mt/p.php blog"

「-q」オプションを使用したのは、phpCLI版じゃなくてCGI版だったから、HTTPヘッダ等が吐き出されてしまったので、それを抑えるため。

p.phpの中で、コマンドラインで指定した値(この例では「blog」)が入るのは、$_SERVER['argv'][1]。これを使えば、複数のメールアカウントでp.phpを使うときに、メールをパースする前にアカウント名を渡せたりして便利。複数指定するときは$_SERVER['argv'][2],$_SERVER['argv'][3],,,となる。$_SERVER['argv'][0]には、実行しているスクリプト名が入る。

p.phpデバッグには、

tail -f /var/log/maillog

で監視するのと、

<?php
$text="エラーの内容";
error_log(date("Y.m.d H:i")." ".$text."\n",3,dirname($_SERVER['argv'][0])."/error.txt");
?>

って感じでerror.txtに自前のエラーを吐くのとで、両方使ったり片方にしたり。

qmailではp.phpの最後にexit(1);とすると「無事に受け取ったよ&その後の処理はしないでね」って意味のことをqmailに伝えられる感じだけど、postfixだとそういうのはないみたい?

モバイル用でSJISに変換するclass

Movable TypeとかのCMSで、「DBで文字化けとかそういうのありそうだからCharsetはとりあえずSJISはやめとけ」って感じは普通にあると思うけど、モバイルサイトと連動させる時にauで化ける、みたいな(auだけじゃないけどauで苦情多い)。普通にSJIS以外でも読めるって機種も増えてるけど、その他を切り捨てるわけにもいかないっていう。これへの対処はいろいろとあるけど、PHPオブジェクト指向を勉強中なので今回はこんな風にしてみた。

mobile.php

<?php
class mobile{
  var $charset;
  function mobile(){//コンストラクタ
    //ここで機種判別とかやる(他で使う)
  }
  function fixCharsetFrom($charset){
    $this->charset=$charset;
    ob_start(array(&$this,'fixCharset'));
  }
  function fixCharset($t){
    return mb_convert_encoding($t,"sjis-win",$this->charset);
  }
  function display(){
    while(ob_get_level() > 1) {ob_end_flush();}
    header("Content-type: text/html");
    header("Content-length: ".ob_get_length());
    ob_end_flush();
  }
}
?>

MTのテンプレート(UTF-8で保存しとく)

<?php
require("mobile.php");
$mobile=new mobile();
$mobile->fixCharsetFrom("<MTPublishCharset>");
?><html>
<head><title>ブログだよ</title></head>
<body>
<MTEntryTitle><hr><MTEntryBody>
</body>
</html>
<?php
$mobile->display();
?>

クラスのメンバ関数をバッファリングのコールバック関数に指定するやり方を知らなかったから今回調べて勉強になった。メンバ関数display()でob_end_flush()をwhileにしているのはPHP: ob_end_flush - Manualを見たからだけど、今回の文字コード変換以外にも例えば絵文字をキャリア別に変換したりする関数を追加する場合にも、同じようにバッファリングを使うことでサクッといく。一番最後のob_end_flush()だけwhileから外してあるのは、Content-Lengthをきちんと吐かないといけないっていう仕様をどっかのキャリアが書いてたから。

むしろロッカージェスチャなら

「右クリック+ホイールでタブ切り替え」に加えてロッカージェスチャ、ダブルクリックなんかもできるhttp://yanako.blog26.fc2.com/blog-entry-117.htmlのmouseGesture2.4.2.uc.jsがかなりいい感じだった。マウスジェスチャは使わないのでその部分の処理は削って、ありがたく使わせてもらうことに。

自分でこれを作ろうとゴチャゴチャと書いてたけど、ロッカージェスチャの時にコンテキストメニューの表示をさせないことがどうしてもできなかった。mouseGesture2.4.2.uc.jsでは、以下のようにしてあって、目からうろこだった。

gBrowser.mPanelContainer.addEventListener("contextmenu", contextmenu, true);
function contextmenu(event){
	if(_willStopContext || _draging) {
		event.preventDefault();
		event.stopPropagation();
	}
}

あと、「右ダブルクリック」をアピールポイントにしているみたいだけど、普通の左ダブルクリックでもアクションも指定したい場合は158行目のifを以下のようにしてevent.button値での制限をやめる。

}else if(DOUBLE_CLICK && event.detail == 2){

その後15行目あたりで

function doubleClick(command){
  // ====ダブルクリック====
  switch(command){
  case "LL":
    document.getElementById("Browser:ReloadSkipCache").doCommand();
  break;
  // 未定義のジェスチャ
  default: throw "Unknown Gesture: "+command;
  }
}

とかやってみると素敵です。

でもこの158行目でif(DOUBLE_CLICK && event.detail == 2 && event.button==2)ってしてる意図ってなんなんだろう?右クリックでないと何か弊害でもあるのかしら。気になったのでトラバを送ってみるテスト。