まだ重たいCMSをお使いですか?
毎秒1000リクエスト を捌く超高速CMS「adiary

2015/05/22(金)Version 2.28dリリース情報

Version2系のダウンロードはこちらから

Ver2.28c→Ver2.28dの変更点

  • コメントのロボットSPAM避けルーチンが、Perl 5.20で完全に死んでたいたので修正しました。
  • adiary形式/はてな形式でexportするとき、<等の実体参照があると正しくエスケープされない不具合を修正しました。

過去の変更点

Version2.00(β含む)以降からの乗り換え

  • Ver2.11以前の場合、uploader.conf.cgi をサンプルから再生成してください。

アップデート方法の参考情報。

2015/05/20(水)Perl DBI と UTF8フラグ と 文字化け問題 (2017/05/31追記)

Perl 5.20にしたら、DBIが何やら不可解な動作をするようになりました。

問題の原因

どうやら UTF8 を扱う機能が増えたみたいです。DBIの接続時のパラメーターにこんなものが増えています。

pg_enable_utf8 => 1 , # 結果をUTF8フラグ付きにする(PostgreSQL)

mysql_enable_utf8 => 1 , # 結果をUTF8フラグ付きにする(MySQL)

PerlのDBIモジュールで自動的にUTF8フラグを付ける

この影響で、PostgreSQL運用のシステムを Perl 5.14 から 5.20 にアップグレードしたところ文字化け発生。また厄介なことをしてくれたものです(汗)

保存されいてる文字コードがおかしい

普通に使っているのに「Wide character in print at」の警告がなぜか出まくります。日本語UTF-8として保存した文字列が文字化けしまくるのですが、そのデータだけ単独に取り出して表示しても化けない。

色々調べてみると以下のことがわかりました。

  • 新DBD は(utf8フラグのついていない)文字列を DB に保存するとき「ASCII文字列に特殊な文字が混ざってる」と解釈してエスケープ処理を行う。
  • この文字列は、通常の utf8 文字列ではないので、データベースの中身を直接覗くと、保存した文字列ではなく特殊なエスケープ文字列として表示される。

PostgreSQLではこんな感じです。

  • 文字列「あいう」を保存。バイナリ列で「E3 83 86 E3 82 B9 E3 83 88」
  • psqlコンソールで表示される文字列「a\u0083\u0086a\u00821a\u0083\u0088」
  • DBI経由で取り出し、utf8フラグを取り除いた文字列「C3 A3 C2 83 C2 86 C3 A3 C2 82 C2 B9 C3 A3 C2 83 C2 88」

同じUTF8文字列でありながら、違う内部表現になっています。当然、元の「あいう」と文字列比較を行うと異なると判定されます。

MySQLではこんな感じです。

  • mysqlコンソールで出力される文字列「C3 A3 C2 81 E2 80 9A C3 A3 C2 81 E2 80 9E C3 A3 C2 81 E2 80 A0」
  • DBI経由で取り出した文字列は utf8フラグ がついていない元の文字列。

問題を整理すると2つに集約できます。

  • DBにきちんとしたUTF8文字列として保存されない。
  • DBに一回保存することで、内部表現の異なる UTF-8 文字列が生成されてしまう(PostgreSQL)。

後者も問題と言えば問題ですが、前者はとても気持ち悪い問題です。

解決策

utf8フラグはうまく使えば「Perlによきに計らってもらい」文字コードの問題が楽になるものですが、utf8文字列をutf8として「そのまま扱いたい」時には問題が多すぎる仕組みです。入出力の都度にencode、decodeが発生し処理を重くする原因にもなります。

またutf8フラグありとフラグなしのUTF8文字列が混在すると、それらを連結したときに文字化けする危険があります。

そうなるとutf8フラグなしで扱いたいのですが、そうするためにはどうしたらいいのでしょうか。

DBD::Pg編

  • utf8フラグのない文字列は、必要に応じて特殊エンコードされてDBに保存される。
  • 「pg_enable_utf8」を設定しないとき。
    • 非ASCII文字列や、特殊エンコードされた文字列は utf8 フラグが付いて取り出される。
  • 「pg_enable_utf8 => 1」のとき。
    • 「pg_enable_utf8」を設定しないときと同様。
  • 「pg_enable_utf8 => 0」のとき。
    • いかなる場合も utf8 フラグは付けなくなる。
    • 特殊エンコードされた文字列は文字化けする。

保存時の挙動。

  • DBD::Pg / Version 3.3.0 to 3.5.3
    • utf8フラグを付けた文字列を保存すれば、そのままutf8文字列としてDBに保存される。
    • utf8フラグの付いていな文字列は特殊エンコードされる。
  • DBD::Pg / Version 3.6.0以降
    • 「pg_enable_utf8 => 0」のとき、utf8フラグの付いた文字列を渡すとエラーになる。
    • 「pg_enable_utf8 => 1」のとき、utf8フラグの付いていない文字列を渡すと特殊エンコードされる。

取り出す時用に「pg_enable_utf8 => 0」を設定して、保存時や「検索時」にutf8フラグを付けるのがよさそうです。

追記参照のこと。

DBD::MySQL

  • DB保存時はutf8フラグの有無はすべて無視する(動作は変化しない)。
  • 「mysql_enable_utf8」を設定しないとき。
    • いかなる場合もutf8フラグは付かない。
    • 非ASCII文字列は特殊エンコードされたDBに保存される。
    • 特殊エンコード文字列は、通常の utf8 文字列で取り出される。
    • DBに格納されている通常の utf8 文字列は、エンコード失敗の「?」に置換されDBから取り出される。
  • 「mysql_enable_utf8 => 0」のとき。
    • 「mysql_enable_utf8」を設定しないときと同様。
  • 「mysql_enable_utf8 => 1」のとき。
    • utf8文字列は、通常の utf8 文字列としてDBに保存される。
    • 非ASCII文字列は utf8 フラグが付いて取り出される。
    • 特殊エンコードされた文字列は文字化けする。

DBD::Pgとは実装がまるで違う事がわかります。

ドキュメントを読んだところ「SET NAMES utf8;」というSQL文を発行することで、同じ効果を得て、かつutf8フラグに影響がでないことがわかりました。ただこの状態では、特殊エンコードされた文字列を読み込むときに文字化けするので注意が必要です。

比べると

DBD::Pgの実装のほうが謎かもしれない。DBD::Pgはトランザクション処理がおかしいという素敵な実績があるからなあ……。「pg_enable_utf8 => 0」のとき保存データのutf8フラグも無視しないと矛盾するのはすぐに分かりそうなもんなんだけど。

DBIの仕様

Perl supports two kinds of strings: Unicode (utf8 internally) and non-Unicode (defaults to iso-8859-1 if forced to assume an encoding). Drivers should accept both kinds of strings and, if required, convert them to the character set of the database being used. Similarly, when fetching from the database character data that isn't iso-8859-1 the driver should convert it into utf8.

http://search.cpan.org/~timb/DBI-1.633/DBI.pm

Perl は Unicode(内部表現utf8)と 非Unicode(iso-8859-1)の2つの文字コードをサポートします。DBDドライバはこの2つの文字コードを受け付けるべきで、必要に応じて database で使用可能な文字列に変換する必要があります。

同様に、iso-8859-1ではない文字列は database から取り出す段階でutf8に変換すべきです。

DBD::Pgの仕様変更 2017/05/30

仕様変更というよりBug Fixされ「CHANGES」を読むと、DBD Version 3.6.0あたりから挙動が異なる(正しい挙動に近くなっている)ようです。

しかし、この変更のせいで「pg_enable_utf8 => 0」のときutf8フラグ付の文字列を渡すと「Wide character in subroutine entry」エラーが発生します。

ですので「pg_enable_utf8 => 0」のときは、$DBD::Pg::VERSIONを参照し「3.3.0~3.5.3の間の時だけUTF8フラグをつけてDBIに渡す」必要があります。

クソバグを仕込んだ上に、互換性考えない中途半端なクソ修正をして、そびえ立つクソ状態なDBD::Pgには呆れるばかり……。

まとめ

  • 今どき文字コード問題、しかも UTF8 で悩むとは思わなかった。
  • 1byte文字圏の UTF8 実装は相変わらずクソ。

2015/05/16(土)adiary Version3.0β6公開情報

ダウンロードはこちらから

Ver3.0β6の主な変更点

  • スマホ表示を改善し、投稿や管理も十分行えるようにしました。
  • 記事編集画面を洗練させました。
  • Markdown記法をGFM準拠で拡張し、また非互換動作を改善しました。
  • テーマが少し増えました。

本当はRC1の予定だったのですが、ゴールデウィークにがんばった影響か、あまりにも変更点が積み重なってきたのでひっそりβ公開しておきます。もう仕様変更することはないと思いたい……。

β6.1の変更点

「PostgreSQL/MySQL + Perl 5.20」での運用時にDBに保存される文字列がUTF8にならない(サイズが増える)という問題が見つかったため対応しました。

β6以前で該当環境で運用していてる方は、ブログのデータを一度エクスポートした上で、アップグレード後に再度インポートしなおしてください。

Ver3.0β6の更新情報

  • フッターの出力位置を変更しました
  • 記事編集画面から直接画像をアップロードできるようになりました。
  • 記事編集画面の入力部分にドラッグ&ドロップしてファイルをアップロードできるようになりました。
  • 記事編集画面を洗練させました。
  • FrontPage(固定トップページ)の時、記事タイトルをヘッダ出力しないように変更しました。
  • システムモード非対応テーマを管理画面で使用しようとする時、警告を表示するようにした。
  • 表示パスワード機能やメンテナンスモードでの画面に、適切なHTTPステータスコードを設定した。
  • スマホ判別に、BlackBerry と Windows Phone を加えました。
  • コメント表示数の設定項目がなかったので修正。
  • FrontPageのタイトル設定機能を追加。
  • テーマを書きやすくするため、HTMLの出力構造を微修正しました。
  • タグの編集、コンテンツ一覧の編集画面を少し使いやすくしました。
  • HTML全体から「&nsbp;」を除去しました。
  • 検索結果画面の印刷表示ができるようになりました。(Thanks to 手石
  • 【テーマ】base.cssを import させる仕様をやめました。*1
  • 【スマホ】記事編集を使いやすくしました。
  • 【スマホ】画像アルバムを使いやすくしました。
  • 【スマホ】システム画面の各所でスマホ用に表示を改善しました。
  • 【スマホ】ドラッグ&ドロップエミュレーションを付けました(詳細
  • 【スマホ】ヘッダに選択したメニューを出力できるようにしました。
  • 【スマホ】ヘッダに検索ボックスを出力するようにしました。
  • 【Markdown】GitHub Flavored Markdownのテーブル記法等に対応しました。
  • 【Markdown】自動リンクをonにした場合、自動リンクするように変更しました。
  • 【Markdown】本家では、コードブロックの開始条件として手前が空行である必要があったので、その仕様に準拠しました。
  • 【Markdown】本家では、コードブロックの途中に空行を挟んでもokだったので、その仕様に準拠しました。
  • 【Markdown】【fix】HTMLブロック開始条件が間違っていた(行頭スペースを許可していた)。
  • 【Markdown】【fix】HTMLブロックの後ろに余計な空行が付いてしまう。
  • 【Markdown】【fix】文中に>等の記号があったときのエスケープ処理が正しくなかった。
  • 【Markdown】【fix】Satsuki.pmの目次記法の内部仕様変更に追従していなかった。
  • 【parser】見出し記法で、アンカーが存在しないときは<span class="sanchor">を出力しないようにしました。*2
  • 【design】全体の背景画像を設定できるように変更。
  • 【design】ヘッダ用の「ブログの説明」プラグインを追加。
  • 【design】ブログ情報で参照するRSSアイコンを作り直しました。またRSSにリンクするよう変更しました。
  • 【記法】HTML5準拠のためtable記法のsummary指定を無効にしました。
  • 【fix】デフォルトブログ設定時のサイドバーなどURLが正しく書き換わってなかった。(Thanks to 手石
  • 【fix】ヘッダ部のmargin/padding設定が有効ではなかった。
  • 【fix】ヘッダのタイトル文字位置がブログ表示時とデザイン編集時で異なる。
  • 【fix】コンテンツリスト(ドロップダウンメニュー)で1段目の項目が1つしかない時、メニューを左に開いてしまう。
  • 【fix】コンテンツリスト等の多重配置可能なモジュールがあったとき、プラグイン再インストールでエラーが起きることがある。
  • 【fix】ドロップダウンメニューの項目幅を増やした直後、編集画面で項目が2行に表示されてしまう。
  • 【fix】スマホからデザインモジュールの再インストールを行うとヘッダのHTMLが壊れる。
  • 【fix】des_system-info のコメントの文字コードが間違っていた。
  • 【fix】印刷用表示で FrontPage 機能が動作していなかった。
  • 【fix】印刷用テーマ satsuki2/_print においてデザイン編集の設定を解除していなかった。
  • 【fix】Version1からデータ移行時に、サムネイルディレクトリがうまく移行されない。
  • 【fix】多重編集ロック機能で、編集キャンセル時にもロックしてしまう。
  • 【fix】trust_modeで複数行を含むscript(タグ)の抽出ができていなかった。
  • 【fix】静的HTML出力(エクスポート)が更新から取り残されて完全に腐ってたので修正。*3
  • 【fix】Version2からの移行ツールでトラックバックが取り込めなかった。
  • 【fix】MySQLで動作しなくなっていた。
  • 【fix】【Compiler】begin_array/string等で最終行が空の時、除去するようにしました。
  • テーマが少し増えました。
  • 他多数

*1 : ファイルの場所も移動しました。importはCSS読み込み速度の面からも不利だと思われます。

*2 : テーマのCSS書いてて邪魔でしょうがなかった(苦笑)

*3 : おそらく自分以外誰も使ってませんが(苦笑)

2015/05/14(木)スマホでドラッグ&ドロップのエミュレーション

jQuery UIdynatree を使用していて、ドラッグアンドドロップ操作が必須であるにも関わらずスマホでは何もできないので、汎用的な実装をjQuery pluginで実現しました。

タッチ操作でDnDをエミュレーションする

タッチパネル系イベントは独特らしく、短くタップしたときのみ mousedown や click 等のマウスイベントが発生してくれますが、長く触っているとマウスイベントは発生しないようです。

ですので、以下のように実装しました。

  • touchstart で mousedown を発火。
  • touchmove で mousemove を発火し、mouseleave と mouseenter をエミュレーション。
  • touchend で mouseup を発火。

mouseover, mouseoutも実装はできますが無視しました。jQuery UIが問題なく動く程度には実装しているつもりです。

エミュレーションがonの状態で短くタップすると、mousedown/mouseupイベントが2重に発生する可能性がありますが、解決策がないので保留です。

ソース

修正BSDライセンスとします。jQuery pluginですので適当に読み込ませて次のように使ってください。

$(dom).dndEmulation();

以下のソースは最新でない可能性があります。最新版は、adiaryのサイトからGitHub経由で「js/adiary.js」を参照して該当部のみ抜粋してください。該当部のみ抜粋する限り修正BSDライセンスで扱って構いません。

var TouchDnDTime  = 700;
$.fn.extend({
//////////////////////////////////////////////////////////////////////////////
// Copyright (C)2015 nabe@abk, New BSD License.
//////////////////////////////////////////////////////////////////////////////
dndEmulation: function(){
	var self = this[0];
	if (!self) return;

	// mouseイベント作成
	function make_mouse_event(name, evt, touch) {
		var e = $.Event(name);
		e.altKey   = evt.altKey;
		e.metaKey  = evt.metaKey;
		e.ctrlKey  = evt.ctrlKey;
		e.shiftKey = evt.shiftKey;
		e.clientX = touch.clientX;
		e.clientY = touch.clientY;
		e.screenX = touch.screenX;
		e.screenY = touch.screenY;
		e.pageX   = touch.pageX;
		e.pageY   = touch.pageY;
		e.which   = 1;
		return e;
	}
	// 自分自身を含めた親要素をすべて取得
	function get_par_elements(dom) {
		var ary  = [];
		while(dom) {
			ary.push( dom );
			if (dom == self) break;
			dom = dom.parentNode;
		}
		return ary;
	}

	// クロージャ変数
	var prev;
	var flag;

	// mousedownエミュレーション
	this.bind('touchstart', function(_evt){
		var evt = _evt.originalEvent;
		prev = evt.target;
		var e = make_mouse_event('mousedown', evt, evt.touches[0]);
		$( prev ).trigger(e);
		
		// ある程度時間が経過しないときは処理を無効化する。
		flag = false;
		setTimeout(function(){
			flag=true;
		}, TouchDnDTime)
	});

	// mouseupエミュレーション
	this.bind('touchend', function(_evt){
		var evt = _evt.originalEvent;
		var e = make_mouse_event('mouseup', evt, evt.changedTouches[0]);
		$( evt.target ).trigger(e);
	});

	// ドラッグエミュレーション
	this.bind('touchmove', function(_evt){
		var evt = _evt.originalEvent;

		// 一定時間立たなければ、処理を開始しない
		if (!flag) return;

		var touch = evt.changedTouches[0];
		var dom   = document.elementFromPoint(touch.clientX, touch.clientY);
		var enter = get_par_elements(dom);

		// マウス移動イベント
		var e = make_mouse_event('mousemove', evt, touch);
		$(enter).trigger(e);

		// 要素移動がなければこれで終了
		evt.preventDefault();
		if (dom == prev) return;

		// 要素移動があれば leave と enter イベント生成
		var leave = get_par_elements(prev);

		// 重複要素を除去
		while(leave.length && enter.length
		   && leave[leave.length -1] == enter[enter.length -1]) {
			leave.pop();
			enter.pop();
		}

		// イベント発火
		var e_leave = make_mouse_event('mouseleave', evt, touch);
		var e_enter = make_mouse_event('mouseenter', evt, touch);
		$(leave).trigger( e_leave );
		$(leave).trigger( e_enter );

		// 新しい要素を保存
		prev=dom;
	});
}
//////////////////////////////////////////////////////////////////////////////
});

2015/05/13(水)Lightbox2 にスワイプ対応パッチを充てた

充てたというかパッチしました。作りました。jQueryのみです。

画像をめくるときにタッチパネルのスワイプ操作でも反応するようになっています。特にアニメーションはしません。

jQueryのバグなのか「touchstart」「touchmove」イベントを全く拾わないので、「window.addEventListener」に逃げました。もしもっとスマートな解決方法を見つけたら教えてください。

これを使用しているadiaryもよろしくお願いします

その他の変更点

#lightbox-min-width {
	display:		none;
	width:			300px;
}

と設定すると、横幅が小さすぎる画像を表示するとき、指定したピクセルまでアスペクト比を保持して拡大して表示します。

極端に縦長の画像の場合はアスペクト比を保持できて、画面に収まる範囲内で拡大されます。