昨日紹介した写真のカレンダー表示を行うMTPhotoCalendarの作り方を紹介します。やぼったいプログラムになってしまってますので、どなたかもっといいコードで書き直して頂けると幸いです。
MovableTypeのプラグインのプログラムは、MTをインストールしたディレクトリの下のpluginsに置いておけば自動的に読み込まれて実行されます。ここでは mt-photo-calendar.plという名前でプラグインプログラムのファイルを作成し、pluginsディレクトリに置いています。
実行されるプログラムでは、次のように書いておくとテンプレートで使用するタグを増やすことができます。下の例では、MTIfCalendarImage、MTCalendarImageSrc、MTCalendarImageWidth、MTCalendarImageHeightという4種類のタグを追加しています。
use MT::Template::Context;
# MTCalendarImageタグを登録
MT::Template::Context->add_conditional_tag("IfCalendarImage" => \&ifcalendarImage );
MT::Template::Context->add_tag("CalendarImageSrc" => \&calendarImageSrc );
MT::Template::Context->add_tag("CalendarImageWidth" => \&calendarImageWidth );
MT::Template::Context->add_tag("CalendarImageHeight" => \&calendarImageHeight );
MTのテンプレートでは4つのタイプのタグを定義することができます。
- 変数タグ
- コンテナタグ
- 条件タグ
- グローバルフィルタ
それぞれ、プラグインの中から下記の呼び出しを行うことで追加することができます。
- MT::Template::Context->add_tag($name, \&subroutine);
- MT::Template::Context->add_container_tag($name, \&subroutine);
- MT::Template::Context->add_conditional_tag($name, \&subroutine);
- MT::Template::Context->add_global_filter($name, \&subroutine);
条件タグでは、サブルーチンの返す値が真であればタグに囲まれた部分の有効になりますが、偽であればタグに囲まれた部分が無効になります。変数タグはサブルーチンの返す値にそのまま置き換えられます。
このあたりの詳しいことは、
MovableType プログラミングインターフェイスで解説されています。
また、シェルのコマンドラインで下記のコマンドを実行すれば解説を読むことができます。これらを読めばプラグインの作り方の大体のところはわかると思います。その他のクラスについても出てくるごとにperldocで読んでみるとよいでしょう。
% cd MTをインストールしたディレクトリ/lib
% perldoc MT::Template::Context
上記のコードでは、条件タグを1つと、変数タグを3つ定義しています。各呼び出しの第一引数がタグの名前で、「MT」を除いた文字列を渡します。第二引数にタグを処理するサブルーチンへの参照を渡します。
最初の条件タグであるMTIfCalendarImageを処理するサブルーチンifcalendarImageは次のようになっています。
sub ifcalendarImage {
my ($ctx, $arg) = @_;
# 当該日のエントリーリストを取り出す
my $entries = $ctx->stash('entries');
# 時間の逆順にソートする
sort { $a->created_on < $b->created_on; } @$entries;
# SiteのURLを取得しておく
my $blog = $ctx->stash('blog');
my $site_path = $blog->site_path;
# 画像取得準備
my $src = "";
my $maxwidth = $arg->{width};
my $maxheight = $arg->{height};
my ($width, $height) = ($maxwidth, $maxheight);
# 当日のimgタグを持つ最後のエントリを探す
for (my $i = $#{$entries}; $i>=0; $i--) {
# 本文を取り出す
my $entry = $entries->[$i];
my $text = $entry->text;
$text .= $entry->text_more if($entry->text_more);
# imgタグを取り出す
while ($text =~ m|<img (.*?)>|sigc) {
# src属性を取り出す
my $img = $1;
if ($img =~ m|src\s*?=\s*?"(.*?)"|si) {
# 画像urlを取得できた場合
$src = $1;
# ImageMagickの準備
my $image = Image::Magick->new;
if (substr($src, 0, 4) cmp "http") {
# ローカルの画像ファイルをロードする
my $filename = $site_path . $src;
$image->Read($filename);
} else {
# リモートサイトから画像ファイルをダウンロードする
my ($imagedata, $retry);
for ($retry=3; $retry>0; $retry--) {
$imagedata = get($src);
last if ($imagedata);
}
if ($retry<=0) {
# ロード失敗
$src = "";
last;
}
$image->BlobToImage($imagedata) if ($imagedata);
}
# 画像サイズを取得
($width, $height) = $image->Get('width', 'height');
if ($width == 0 || $height == 0) {
# 画像を取得できていない?
next;
}
# 指定サイズの収まらない場合はサイズ指定を調整する
if ($width > $maxwidth || $height > $maxheight) {
my $wratio = $maxwidth / $width;
my $hratio = $maxheight / $height;
if ($wratio < $hratio) {
$width = int($width * $wratio);
$height = int($height* $wratio);
} else {
$width = int($width * $hratio);
$height = int($height* $hratio);
}
}
# 画像が見つかったので終了
last;
}
}
last if ($src cmp "");
}
return 0 unless ($src cmp "");
$ctx->stash('calendarImageSrc', $src);
$ctx->stash('calendarImageWidth', $width);
$ctx->stash('calendarImageHeight', $height);
return 1;
}
サブルーチンには2つの引数が渡されます。1つはコンテクストオブジェクト(MT::Template::Context)で、2つ目はタグの属性値を格納したハッシュへの参照です。
コンテクストオブジェクトからは、タグが呼ばれた時点でのページ作成中の中間状態を得ることができます。ここではコンテクストオブジェクトを$ctxという変数で受けています。$ctx->stash('状態名')で各種状態を取り出すことができます。この状態を引き出す名前についてはドキュメントが見当たらないので、MTのソースを探るしかなさそうです。ソースを眺めていると、次のことがわかりました。
MTCalendarタグの内側であれば、カレンダーの各日付を処理しながら順に何度も呼び出されることになるわけですが、呼び出された日付に属するエントリのリストを「entries」という名前で取り出すことができます。
プログラムの残りの部分では、エントリリストから個々のエントリのエントリオブジェクト(MT::Entry)を取り出して、記事の作成時刻や本文を処理しています。本文からimgタグを探し、画像ファイルのURLを取り出しています。
このプログラムでは、各日付の最後に書かれたエントリの最初の画像ファイルを取り出すようになっています。この規則もタグのパラメータにして選択できるようにするとよいでしょうね。
カレンダーの日付の各マスに画像を収めるために、画像サイズがマスのサイズを上回る場合は縦横比を保ったままちょうど収まるように縮小します。各日付のマスのサイズはMTIfCalendarImageタグの属性値として指定するようにしています。ここで指定された値は、サブルーチンの第二引数から取り出すことができます。ここでは$argという変数で受け取っており、$arg->{width}のように取り出します。
そして、実際のファイルから画像サイズを取り出して、縮小後のサイズを計算しています。
画像のサイズの調整に関しては、ここで計算するのではなく JavaScript でページロード時に計算する方法もあります。これもやってみたのですが、うまくサイズ設定が動かないことがあり断念しました。なにかよい方法があれば教えてください。
条件タグでは画像ファイルのURLや画像サイズは使わないので、これをコンテクストオブジェクトに保管しておきます。$ctx->stash('名前', 値)を呼び出すと任意の名前に状態を保管しておくことができます。stashを辞書で引くと「隠し場所」っていう意味なんですね。なるほど。
stashに隠した状態の値、すなわちコンテクストは、後で別なタグの処理の際に取り出すことができます。それが、画像URLと画像のサイズを取り出すタグの処理プログラムである下記のサブルーチンです。ここではstashから値を取り出して、戻り値として返しているだけです。
sub calendarImageSrc {
my $ctx = shift;
return $ctx->stash('calendarImageSrc');
}
sub calendarImageWidth {
my $ctx = shift;
return $ctx->stash('calendarImageWidth');
}
sub calendarImageHeight {
my $ctx = shift;
return $ctx->stash('calendarImageHeight');
}
非常におおざっぱな説明しかできませんでしたが、MovableTypeのプラグインを作成するのは案外簡単であることがわかりました。問題はコンテクストオブジェクトから取り出せるコンテクスト情報の詳細ですね。どなたか整理した人がいたら教えてください。
今回は写真のカレンダー表示をプラグインを作ることで実現しましたが、blogmeterで各種グラフのページを作るのと同じように、外部から記事を読み取って画像のURLを抽出してカレンダーページを作成するようなサービスも実現できると思います。もしかしたら既にあるかもしれません。どなたかぜひ作ってくださいませ。
テキストエリアが空の状態からエディタで入力すると文字化けしますね。フォーム上で少しだけ文字を入れてからエディタで編集すれば大丈夫なようです。