VirtualBoxのGuestAdditions

VirtualBoxdebian wheezyを入れたが、GuestAdditionsの入れ方忘れてたのでメモ。

  1. sudoできるようにする
    1. suでrootになる
    2. visudoで自分を設定
  2. 次のパッケージをインストール(aptitude updateとaptitude apgradeは適宜やっておく)
    • build-essential
    • module-assistant
  3. GuestAdditionsのインストール用のスクリプトを実行
    • sudo sh /media/cdrom/VBoxLinuxAdditions.run

メールによる画像のアップロード

スマホからは

<input type="file">

でファイルをアップロードできないっぽいので、メールによるアップロードで実現する方向になりそうなので、やってみた。
メールによる方法のほかにも、FacebookとかがやってるJavaScriptによる方法もあるっぽいけど。

QdmailReceiverを使う

結論からいうと、iPhone5で画像を添付して送ると、ヘッダの形が期待している形式じゃないからかできなかった。QdmailReceiverに関しては、ヘッダを解析する部分の正規表現をいじると良いという情報がたくさん公開されているようで、それを試してみたけどダメだった。
開発止まってるみたいだなぁ。QdmailReceiverとは - QdmailReceiver Multibyte mail decoder & POP Client

PEARを使う

PEAR::Net_POP3PEAR::Mail_mimeDecodeを使うとよさげ。ってことで、次の記事を参考にやってみた。cronでバッチ処理を想定しているので、vendorに配置してみた。

後者はラッパークラスを作ってくださってて、ありがたやありがたや〜。

<?php

// 配置したパスに応じて変更すること
// http://d.hatena.ne.jp/ya--mada/20080415/1208318475より拝借
App::import('vendor', 'ReceiptMailDecoder', array('file' => 'mail' . DS . 'ReceiptMailDecoder.class.php'));

class CheckmailShell extends Shell {

  function main() {

    $server = 'mail.hogehoge.com'; // サーバ
    $port = 110; // ポート番号(POP3 on SSLなら995)
    $user = 'foobar@hogehoge.com'; // メールアドレス
    $pass = 'foobarbazqux'; // パスワード

    // 保存先のディレクトリ
    $img_dir = 'foo/bar/baz';

    require_once 'Net/POP3.php';
    $pop = new Net_POP3();
    $ret = $pop->connect($server, $port);
    if ( !$ret ) {
      echo 'wrong settings for mail server.';
      exit;
    }

    // ユーザ名 と パスワード でログイン
    $ret = $pop->login($user, $pass);
    if ( !$ret ) {
      echo 'failed to login.';
      exit;
    }

    // メールの件数を取得
    $num = $pop->numMsg();
    for ($mail_id = $num; 1 <= $mail_id; $mail_id--) {

      // メールを取得(IDは1〜件数なので注意。0開始じゃないっぽい)
      $data = $pop->getMsg($mail_id);

      // 解析用のオブジェクト作成
      $decoder = new ReceiptMailDecoder($data);

      /////////////////////////////////////////
      // 必要に応じて利用する
      /////////////////////////////////////////
      //// To:アドレスのみを取得する
      //$toAddr = $decoder->getToAddr();
      //// To:ヘッダの値を取得する
      //$toString = $decoder->getDecodedHeader( 'to' );
      // Subject:ヘッダの値を取得する
      //$subject = $decoder->getDecodedHeader( 'subject' );
      //
      //// text/planなメール本文を取得する
      //$body = mb_convert_encoding($decoder->body['text'],"eucjp-win","jis");
      //// text/htmlなメール本文を取得する
      //$body = mb_convert_encoding($decoder->body['html'],"eucjp-win","jis");

      if ($decoder->isMultipart()) {

        $tempFiles = array();
        $num_of_attaches = $decoder->getNumOfAttach();

        for ($i = 0; $i < $num_of_attaches; $i++) {

          $file = $decoder->attachments[$i];

          // 保存先のディレクトリ
          $fpath = $img_dir . DS . $file['file_name']);

          // とりあえず保存してみる
          $decoder->saveAttachFile($i, $fpath);

          // 確認
          echo $fpath, PHP_EOL;
        }
      }
      // メールを削除
      $pop->deleteMsg($mail_id);
    }
    $pop->disconnect();
  }
}

絵文字の削除

あるサイトのPCサイトとスマホサイト作ってて、スマホで入力された絵文字を削除するという必要に迫られたのでメモ。理想は、

<input type="password">

の時みたいに、絵文字のキーボードを利用不可にして、絵文字を入力できないようにすることだけど。できないっぽい?
ガラケーで使われてる絵文字と、スマホで使われてる絵文字は仕組みが違うのかな?その辺のことはよく分かってないけど、とりあえずiOS6Androidに関していうと、「http://www.unicode.org/~scherer/emoji4unicode/snapshot/full.html」の

が使われてるそうだ。「携帯絵文字変換ライブラリ HTML_Emoji - libemoji.com」というライブラリを

<?php
  // 数値文字参照の絵文字を UTF-8 の絵文字に変換
  $text = $emoji->filter($text, array('DecToUtf8', 'HexToUtf8'));
  // $text に含まれている、3キャリアの UTF-8 の絵文字を削除
  $text = $emoji->removeEmoji($text);
?>

って使うと、iOSの絵文字は消えてくれた。これはガラケーの絵文字を削除するためのものなのかな?よくわからん。消えてくれたからいいや。
一方、Androidで使われているのはGoogle定義の絵文字だからか(?)消えてくれなかった。ってことでCakePHP(1.2.8)のcomponent書いた。

<?php
class DelemojiComponent extends Object
{
  // 1桁の16進数を、4桁の2進数へ変換する
  function toStrBin($strHex) {
    $int = intval($strHex, 16);
    $result = sprintf('%04b', $int);
    return $result;
  }

  // 4桁の2進数を1桁の16進数へ変換する
  function toStrHex($strBin) {
    $int = intval($strBin, 2);
    $result = sprintf('%1X', $int);
    return $result;
  }

  // utf8をunicodeへ変換する
  function toStrUnicode($strUtf8) {
    $len = mb_strlen($strUtf8);
    $b = '';
    // 16進数1文字を2進数4bitへ変換
    for ($i = 0; $i < $len; $i++) {
      $b .= $this->toStrBin($strUtf8[$i]);
    }

    $strUnicodeBin = '';
    $byte = $len / 2;
    $unicodeByte;
    if ($byte == 1) {
      // 1バイト文字
      // unicodeのコードポイントは2バイト: 00000000, 0xxxxxxx
      $unicodeByte = 2;
      $strUnicodeBin = '00000000' . $b;
    } else if ($byte == 2) {
      // 2バイト文字
      // unicodeのコードポイントは2バイト: 00000xxx, xxyyyyyy
      $unicodeByte = 2;
      $strUnicodeBin = '00000' . mb_substr($b, 3, 5) . mb_substr($b, 10, 6);
    } else if ($byte == 3) {
      // 3バイト文字
      // unicodeのコードポイントは2バイト: xxxxyyyy, yyzzzzzz
      $unicodeByte = 2;
      $strUnicodeBin = mb_substr($b, 4, 4) . mb_substr($b, 10, 6) . mb_substr($b, 18, 6);
    } else {
      // 4バイト文字
      // unicodeのコードポイントは3バイト: 000wwwxx, xxxxyyyy, yyzzzzzzz
      $unicodeByte = 3;
      $strUnicodeBin = '000' . mb_substr($b, 5, 3) . mb_substr($b, 10, 6) . mb_substr($b, 18, 6) . mb_substr($b, 26, 6);
    }

    // 16進表現に直す
    $strUnicodeHex = '';
    for ($i = 0; $i < $unicodeByte * 2; $i++) {
      $s = mb_substr($strUnicodeBin, $i * 4, 4);
      $strUnicodeHex .= $this->toStrHex($s);
    }
    return $strUnicodeHex;
  }

  // google定義の絵文字を削除する
  function removeGoogleCharCode($str) {

    // まず、数値文字参照形式のgoogle独自のコードを削除しておく。
    // 正規表現で表わすと &#xFE[0-9a-fA-F]{3}; となる。
    $removedCharRef = mb_ereg_replace('&#xFE[0-9a-fA-F]{3};', '', $str);

    // 以降では、数値文字参照ではなく、UTF-8のバイト列として表わされた絵文字を削除する。
    $hex = bin2hex($removedCharRef);
    $length = mb_strlen($hex);
    $maxByte = 4;  // 最大4バイト
    $offset = 0;
    $result = '';

    while ($offset < $length) {

      $head = $hex[$offset] . $hex[$offset + 1];
      $dec = base_convert($head , 16, 10);
      $byte = 0;
      $skip = false;
      if (240 <= $dec) {
        // 4バイト文字: 1バイト目が1111 0xxx
        $byte = 4;
      } else if (224 <= $dec) {
        // 3バイト文字: 1バイト目が1110 xxxx
        $byte = 3;
      } else if (192 <= $dec) {
        // 2バイト文字: 1バイト目が110x xxxx
        $byte = 2;
      } else {
        // 1バイト文字: 1バイト目が0xxx xxxx
        $byte = 1;
      }

      $b = '';
      for ($i = 0; $i < $byte; $i++) {
        $b .= ($hex[$offset + (2 * $i)] . $hex[$offset + (2 * $i + 1)]);
      }

      // 削除対象かどうか調べる
      if ($byte == 4) {
        $unicode = $this->toStrUnicode($b);
        // UTF-8が4バイトだと、unicodeは3バイトなので6文字
        if ($unicode[0] === '0' &&
          $unicode[1] === 'F' &&
          $unicode[2] === 'E') {
          // コードポイントが 0FE000 以降は、googleの絵文字コードなので削除
          $skip = true;  
        }
      }

      if (!$skip) {
        // この文字は必要なので追加
        $result .= $b;
      }
      $offset += ($byte * 2);
    }

    $result = pack('H*', $result);
    return $result;
  }
} 
?>

これを、

<?php
class HogeController extends AppController {
  var $components = array('Delemoji');

  // ... 中略 ...

  function doSomething () {
    // ... 略 ...
    $text = $this->Delemoji->removeGoogleCharCode($text);
    // ... 略 ...
  }

?>

みたいに使うと、消えるんじゃないかなー。クソ効率悪いからあんまり長い文字列食わせると時間かかるかも。

ページ遷移時にundefined

ちょっとつまづいたので、メモメモ。CakePHP+jQuery MobileでWebページを作成中。リンクをタップしてページ遷移したら、遷移先はまっ白い画面に undefined と表示されてしまう。一方、URL直接入力して遷移しようとすると問題なく表示される。
まぁググればいくらでも情報は出てくるけど。大まかに、次の2通りのアプローチがあるっぽい。

CakePHPでviewを作ってやる

jQuery Mobile はデフォルトでページ遷移をAjaxで処理するけど、CakePHPはデフォルトでAjaxを勝手に判断してAjaxのレイアウトajax.ctpを使うように設計されているのが原因らしい。

iPhoneのSafariのエミュレータ

iPhone向けサイトの開発を行っているならエミュレータはあった方が良いと思う。最終的な確認はiPhone実機でやるとして、開発中の細かな確認はいちいちiPhoneでやるとめんどいなー、みたいな。Mac持っているならApple謹製のエミュレータがあるらしいけど、あいにく仕事はWindowsなんすよね...。
ってことで、エミュレータを2個試してみたので書いておく。2つともAdobeAirが必要。あとSafariも?

Air iPhone

Joe Johnston – Medium
お!いいかも!と一瞬思ったけど、user-agentちゃんと設定してくれてないみたい。user-agentで端末の判定やっている場合は、ちゃんとレンダリングされないっぽい。

iBBDemo3

Google Code Archive - Long-term storage for Google Code Project Hosting.
iBBDemo3
これいいね。大きくて見やすい。ちゃんとUA偽装もやってくれてるっぽい。
ググってたらリンク切れで公開終了したのかなーと思ってたけど、見つけた!よかったー。

window.orientationがundefined???
<head>
  <link rel="stylesheet" href="http://code.jquery.com/mobile/1.1.1/jquery.mobile-1.1.1.min.css" />
  <script type="text/javascript" src="http://code.jquery.com/jquery-1.7.2.min.js"></script>
  <script type="text/javascript" src="http://code.jquery.com/mobile/1.1.1/jquery.mobile-1.1.1.min.js"></script>
  <script>
    jQuery.event.add(window, "orientationchange", function(){ alert(window.orientation); });
  </script>
</head>

ってやると、orientationchangeのタイミングでalertが実行されるけど、window.orientationがundefinedって言い張るのよね...。orientationchangeちゃんと動くのにorientationの値がundefinedとか、そんな実装あり?alertの部分は

alert(window.orientationchange);

にしてもやっぱりundefinedだった。もはや意味不明。orientationchangedが存在してるから、alert実行されとるんじゃないの?
iPhone実機で試すしかないのかなぁ。となるとlocalhostに無線でつなげられるようにしないとダメだな。無線LAN整備してないんだよなぁ。ちなみに
[iPhone] iPhone Safariでデバイスの向き(orientation)を取得してみた - YoheiM .NET
のサンプルをiPhone4のSafariで見た時はちゃんと動いたけど、iBBDemo3で見るとやっぱりダメみたい。

debianにfirefoxをインストール

aptitudeとかでインストールできるパッケージに無いのは、何か大人の事情があるらしいけど、そんなことはよく分からない。できれば新しいブラウザが使いたいよなーってことで。

# cd /usr/local
# wget http://ftp.jaist.ac.jp/pub/mozilla.org/firefox/releases/14.0.1/linux-i686/ja/firefox-14.0.1.tar.bz2
# tar jxf firefox-14.0.1.tar.bz2
# rm -f firefox-14.0.1.tar.bz2

あとは、ランチャ作るなりなんなり。

コマンドのオプション

wgetは-Pオプションで保存先のディレクトリを指定できるそうな。あと、tarのオプションがなかなか覚えられない><

異なる検索条件の結果を1つのページャーで

データベースへ照会した後、その結果を表示したいがレコード数が多い場合、ページ制御が必要になる。
CakePHPにはそのための仕組みがあるので簡単にできるけど

2通りの検索条件による検索結果を1ページに表示し、ページ移動のためのナビ(「次へ」や「前へ」や「○ページ」など)は一つにしてくれ

という要求があって足掻いてみたいのでメモメモ。
環境は

まずコントローラー。

<?php
class HogeController extends AppController
{
  // ... 略 ...

  public function index() {
    // 条件1と条件2でのレコード数を調べておく
    $cnt1 = $this->_countRecords1();
    $cnt2 = $this->_countRecords2();

    // 件数の多いほうを後にしてやる
    if ($cnt1 < $cnt2) {
      $data1 = $this->_paginate1(); // $this->paginate()の戻り値を取得している
      $data2 = $this->_paginate2(); // 同上
    } else {
      $data2 = $this->_paginate2();
      $data1 = $this->_paginate1();
    }

    $this->set('data1', $data1);
    $this->set('data2', $data2);
  }
}
?>

ってしてやると、ビュー側の$paginatorが件数の多い方の設定で上書きされる。

<?php foreach ($data1 as $d1): ?>
	<!-- 表示 -->
<?php endforeach; ?>
<?php foreach ($data2 as $d2): ?>
	<!-- 表示 -->
<?php endforeach; ?>

<?php
	echo $paginator->prev('<< prev');
	echo '&nbsp;';
	echo $paginator->numbers();
	echo '&nbsp;';
	echo $paginator->next('next >>');
?>

件数が少ない方のデータは、最後に表示されたデータがずっと表示されるっぽい。

その後

ページによっては2つのページャーが必要になったりして、結局JavaScriptでやることになった。