2007年4月アーカイブ

結局のところ、Movable TypeのCMSは現状「コンテンツマネジメントシステム」ではあっても「ファイルマネジメントシステム」ではないということなんだろう。

アップロードしたファイルをMTから削除したりすることができないというのも典型的な例か(TypePadベースのココログとかでは可能だし(インターフェイスは最悪だけど)、今後のバージョンでは実装されるのではないだろうか)。

で、前回・前々回の続きである。

結局どうプログラミングしてどう実装するというよりも、どう設計するか。

現状は産んだら産みっぱなし...というかファイルを生成したら生成しっぱなしなので、書き出したファイルの情報を保持しておくのが楽だろう。アーカイブマッピングやファイル名、カテゴリが変わるごとに旧パス情報と新パス情報を比較して何らかの処理をするという方法もあるにはあるだろうが色んな意味でコストが高くつきそうだし。

ということで以下のようにしてみた。


package MT::Content;
use strict;

use MT::Object;
@MT::Content::ISA = qw( MT::Object );
__PACKAGE__->install_properties({
   columns => [ 'id', 'file', 'content',
       'modified', 'size', 'blog_id',
       'digest', 'status',
   ],
 indexes => {
  id => 1,
  file => 1,
  modified => 1,
  blog_id => 1,
  status => 1,
 },
 datasource => 'content',
});

MT::Content(MT::Objectのサブクラス)

フィールド名タイプ説明
idinteger
(primary key,auto_incriment)
一意なID
filetextファイルのパス
contenttext書き出される(べき)HTML
modified(※1)integer書き出し時刻(Unixtime)
sizeintegerファイルサイズ
blog_idintegerブログのID
digesttexttextの要約(digest)
statusintegerステータス

利用方法は様々。一例として、前回の例であげたような「疑似的」ダイナミックパブリッシング等で使える。再構築の際にファイルシステムにファイルを書き出すのとデータベースに保存する違いだけで、構築されるデータは(プラグインの処理も含めて)まったく同様になる(一部BuildPage, BuildFileコールバックの際の処理は反映されないが、これはプラグインを書き換えるかコールバックも含めて上書きしてやることで(=プラグインの中からさらにコールバックを呼び出すことが可能なら)対応できるだろう)。

環境にかなり依存すると思うが、実際にテストしてみた環境ではファイルに書き出す約半分強の時間で全再構築が完了した。

あとは、ダイナミックパブリッシングの閲覧プログラム側でConditional GET([HTTP_IF_MODIFIED_SINCE]によって304を返してブラウザキャッシュを使わせる)に対応させたり、何ならリクエストが発生した段階でファイルを書き出すような処理も簡単だろう。

ダイナミックパブリッシングを利用しないのであれば content フィールドにHTMLを丸ごと入れる必要はなく、digest(※2) と size のみを比較して更新されたかどうかをチェックすれば良い。


my $digest = &md5_hex($html);
my $len = length($html);
use MT::Content;
my $content = MT::Content->load({ file => $file });
my $if_mod = 1;
unless (defined $content) {
 $content = MT::Content->new;
} else {
 if (($content->size == $len) && ($content->digest eq $digest)) {
  $if_mod = 0;
 }
}
if ($if_mod) {
# 更新されていたらDBを更新
}

CMSから吐き出されたファイルのパス/書き出されたファイルの時間を保持しているので、例えば「前回サイトを更新した以後に更新されているファイルの一覧」をリストアップすることも簡単(※3)

また、status フィールドをうまく使えば、

  • 全レコードの status を 一旦 0 にする
  • 全再構築して status を 1 にする
  • status が 0 のファイルが存在していたら(ゴミ)ファイルとレコードを削除する

というようなしくみも作れる。(※4)


  • ※1) modified_on にしてはいけない。というか modified_on にするのであれば timestamp 形式にしないといけない...みたいだ。
  • ※2) MT::Utilの perl_sha1_digest_hex を使ってdigestを生成したところ、再構築の時間が2倍くらいかかったのでDigest::MD5(md5_hex)を利用した。
  • ※3) 以前にも必要があって作ったことがあって、「CMSはコンテンツ管理のみに利用, 納品は出来上がったファイルのみ, 更新時には差分ファイルだけ納品」というケースがWeb制作の現場では良くある。
    以前作った時はまだMTのしくみがよく分かっていなかったのでPHPからMySQLにアクセスしたりファイルのタイムスタンプをチェックしたりという感じでかなり力技の実装だった。
  • ※4) というか既に殆ど出来た。

nph-mtview.cgi(それなりにintelligence ? なHTTPサーバ)


#!/usr/bin/perl -w
use strict;

use lib qw (/Applications/MAMP/htdocs/mt/lib/);
use lib qw (/Applications/MAMP/htdocs/mt/extlib/);
my $file = '/Applications/MAMP/htdocs';
my $index = 'index.html';

use MT;
use MT::Content;
use HTTP::Date;

my $CRLF = "\r\n";

my $mt = MT->new(Config => '/Applications/MAMP/htdocs/mt/mt-config.cgi');

$file .= $ENV{"REDIRECT_URL"};

if ($file =~ /\/$/) {
 $file .= $index;
}

my $content = MT::Content->load({ file => $file });

if (defined $content) {
 my $length = $content->size;
 my $http_if_mod = $ENV{'HTTP_IF_MODIFIED_SINCE'};
 my $http_if_none = $ENV{'HTTP_IF_NONE_MATCH'};
 my $modified = &time2str($content->modified);
 my $etag = $content->digest;
 if (($http_if_mod && ($http_if_mod < $modified)) ||
   ($http_if_none && ($http_if_none != $etag)) ||
   ! $http_if_mod ) {
  print "HTTP/1.1 200 OK$CRLF";
  print "Content-length: $length$CRLF";
  print "Last-modified: $modified$CRLF";
  print "Etag: $etag$CRLF";
  print "Content-Type: text/html$CRLF$CRLF";
  print $content->content;
 } else {
  print "HTTP/1.1 304 Not Modified$CRLF";
  print "Content-length: $length$CRLF";
  print "Last-modified: $modified$CRLF$CRLF";
 }
} else {
  print "HTTP/1.1 404 File Not Found$CRLF";
  print "Content-Type: text/html$CRLF$CRLF";
  print "404";
}

結局のところ、誰が、どこでコストを負担するかの話なんだよな。色んな意味で。


まだ続く...(Movable Type用ファイルマネージャーの設計と実装(1))

こちら↑の続き。考察だけじゃなんだから少し実証もしてみよう。 せっかくゴールデンウィークだし...

そないに難しく考えずにファイル構築の際にページの文字列まるごととアドレスをデータベースに保存してmod_rewriteで動的に処理させればよいような気が…
わりとすぐに出来そう…

さて、すぐにできるだろうか。

テーブル(mt_content)の追加


# sqlite3 mt.db
sqlite> create table mt_content(
content_id INTEGER PRIMARY KEY,
content_file text,
content_content text); (return)

MT::ObjectのサブクラスMT::Contentの作成(lib/MT/Content.pm)


package MT::Content;
use strict;

use MT::Object;
@MT::Content::ISA = qw( MT::Object );
__PACKAGE__->install_properties({
  columns => ['id', 'file', 'content',
   ],
 indexes => {
  id => 1,
  file => 1,
 },
 datasource => 'content',
});

プラグインの作成

再構築時にファイルを書き出さずにDBへコンテンツを入れる(plugins/Content.pl )


package MT::Plugin::AltDynamicPublishing;
use strict;
use MT;
our $VERSION = "0.1";
@MT::Plugin::AltDynamicPublishing::ISA = qw(MT::Plugin);

my $plugin = new MT::Plugin::AltDynamicPublishing({
 name => 'AltDynamicPublishing',
 version => $VERSION,
});

MT->add_plugin($plugin);
MT->add_callback('BuildFileFilter', 1, $plugin, ¥&_addContent);

sub _addContent {
 my ($eh, %args) = @_;
 my $ctx = $args{'Context'};
 my $blog = $args{'Blog'};
 my $file = $args{'File'};
 my $tmpl = $args{'Template'};
 my $cond;
 my $html = undef;
 $ctx->stash('blog', $blog);
 $html = $tmpl->build($ctx, $cond);
 use MT::Content;
 my $content = MT::Content->load({ file => $file });
 unless (defined $content) {
  $content = MT::Content->new;
 }
 $content->file($file);
 $content->content($html);
 $content->save or die $content->errstr;
 if (-f $file) {
  rename($file, $file.'.static');
 }
 return 0;
}
1;

閲覧用CGIの作成(mt-view.cgi)


#!/usr/bin/perl -w
use strict;

use lib qw (/Applications/MAMP/htdocs/mt/lib/);
use lib qw (/Applications/MAMP/htdocs/mt/extlib/);
my $file = '/Applications/MAMP/htdocs';
my $index = 'index.html';

use MT;
use MT::Content;
my $mt = MT->new(Config => '/Applications/MAMP/htdocs/mt/mt-config.cgi');

print "content-type: text/html¥n¥n";
$file .= $ENV{"REDIRECT_URL"};
if ($file =~ /¥/$/) {
 $file .= $index;
}
my $content = MT::Content->load({ file => $file });
if (defined $content) {
 print $content->content;
} else {
 print <<HTML;
<?xml version="1.0" encoding="UTF-8"?>
<html>
<head>
<body>
404
HTML
}

.htaccess


ErrorDocument 404 /online/mtview.cgi
ErrorDocument 403 /online/mtview.cgi

で、ここからが考察というか本題。

これで"とりあえず" 動くようだ(というか目の前では動いている)。
負荷は(たぶん)たいして減らない。再構築は多少速くなるが(再構築が必要)閲覧時にDBにアクセスするからその分は負担になる(それでも「構築」は済んでいるから「ダイナミック」に生成するよりは速い。ただし公開サイトでやると[現状では]かえって負荷は増えるかも)。

テーブルにタイムスタンプとダイジェスト, ファイルサイズを加えて条件を満たした時だけ更新するようにして、mt-view.cgi をConditional GET 対応するようにすれば使い物になるだろう。

殆どのプラグインもそのまま動作するだろうし、スタティック、ダイナミックの切り替えも容易だ。

この例では再構築をBuildFileFilterコールバックに "0" を返すことで無効にしているが、1を返してやればファイルはそのまま生成され、管理するためだけのDBができる(この場合はファイルの中身をDBに入れる必要はないだろう)。

主目的はファイルの管理なのでその点で考えると、DBに入っているファイルはMTが吐き出したものであるから、

  • すべてを再構築してDBを更新。更新されていないもの(再構築時点より古いタイムスタンプのもの)はイコール「ゴミ」ファイルなので削除する
  • ただし手動でアップしたファイルである可能性があるのでファイルのタイムスタンプと長さとダイジェストをチェックする

という処理が可能になると思う。

同様に 画像等のファイル管理用テーブルを作って CMSUploadImage が呼ばれた時に情報を登録すればファイルマネージャーができそう。

※というか、誰かやらんかね


※再構築の前に一旦ファイルを全削除して(その間はダイナミックにページを送り出す)、再度書き出し直せば「ゴミ」は出来ないよな。色々応用が効きそう。


→さらに続く。

エントリーのアーカイブ保存先がカテゴリーに依存する設定になっている設定でエントリーの主カテゴリーを変更した時に保存先が「旧保存先」になってしまう問題を修正しました。

Download:

Related Entry:

副作用? として、上記のケースで古いファイルをきちんと削除してくれるようになりました。

※DeleteFilesAtRebuild 1を指定している場合のみ

またmt-config.cgiの DeleteFilesAtRebuildの設定を参照するようにしたため、プラグイン設定画面のDeleteFilesAtRebuildの項目は廃止されました。

清掃週間? の一環としてちょっとしたプラグインを書いてみた。

関連エントリ:

プラグラムをpluginsフォルダにアップ後 [メイン・メニュー→ブログを選択]、もしくは[エントリー一覧] 画面にリンクが表示される。

プラグインメニュー



  1. エントリー一覧画面から例えば公開中のいくつかのエントリーを選択して「非公開にする」を選択して「Go」クリック。

    エントリー一覧画面の編集の様子

  2. ページ下部のプラグインメニューに表示される「Clean Up Files by Status」をクリック。
  3. チェックボックスで選択された状態で非公開のファイル一覧が表示されるので、確認後「Clean Up!」をクリック。

    確認画面

  4. 削除結果が表示される。

    削除結果

削除するのはステータスが「下書き」と「指定日公開」のファイル。

※指定日公開はやったことがないので公開後のステータスがどうなるか誰か知っている方いたら教えてください。

MTでサイト制作をしている時に「ゴミ」が出来てしまう件について調べているうちにBackgroundRebuilderにバグを見つけた。主にカテゴリー情報の保存についてのバグで、修正はだいたいできているので明日中にアップします。

何故この件が気になったかというと、サイト制作にCMSを使う場合、CMSごと納品してダイナミックパブリッシングで運用する時には問題ないが、「制作」のためだけにCMS使用して静的ファイルだけを納品する場合に問題になることがあるから。

制作過程で出来た「ゴミ」が一緒に納品されると困る。「このファイルは一体何?」

ゴミが出来るケースについては、先のエントリーでも書いた通りMTの例でいえば「DeleteFilesAtRebuild 1」をmt-config.cgiに指定してやることで基本的には回避出来る。

※ DeleteFilesAtRebuildについての参考情報

ところがやはりそれでも「ゴミ」が残るケースがある。

例えばエントリーのアーカイブマッピングにカテゴリーのパスが含まれている場合(例:%c/%i等)で、エントリーの主カテゴリーを変更して保存した場合。

  • /cate1/example.html
  • /cate2/example.html

また、同様にアーカイブマッピングにカテゴリーのパスが含まれている場合カテゴリーのbasenameを変更した時に旧フォルダが丸ごと残ってしまう。

  • /old_cate/以下のファイル
  • /new_cate/以下のファイル

詳しく調査したわけではないが、Blogの保存先を変更してもやはり残るのでないか。

アーカイブマッピングを修正した時にもファイルは残る。

また、先のエントリーに書いたように、エントリーの一覧からチェックしたエントリーを下書きに戻した際にもファイルは残る。一括編集画面でカテゴリーを変更した場合もファイルは残る。

現実的にはコマンドライン等から静的なHTMLを一括削除して再構築をかけるというような処理を最終段階で行うことになる。CMSで管理していない静的なファイルが混在している場合等は結構面倒。また場合によっては画像の保存場所なんかもぐちゃぐちゃになってしまう。

最初の設計が大切でディレクトリ構成なんかをきちんと固めてから作ろう、ってのは建前としては正解ですが現実的にはそうもいかないし、何より「何のためのCMSなの?」と言われかねない。

CMS側でのアプローチとしては、やはり吐き出された「静的ファイル」や「画像」等の管理機能を持たせることだろう。

1ファイル1レコード、1つのオブジェクトと結びついたテーブルをデータベースに持っておいて、前回吐き出されたファイルを保持しておき保存先が変更されたら(あるいはステータスがDraftになったら)ファイルを削除もしくは移動するという方法があると思う。

ただ、現状の例えばMovable Typeでは難しいかもしれない。アーカイブマッピングやテンプレートの組み合わせが複雑で、一つのエントリーが一つのファイルとは限らないし。それでも例えば以下のような考え方はどうだろうか。

MT::Permalink(MT::Objectのサブクラス)

  • permalink_id
  • permalink_type (Entry, Category 又は Blog)
  • permalink_record_id (permalink_typeに対応したオブジェクトのID)
  • permalink_blog_id
  • permalink_uri (現在あるいは次に構築される静的ファイルのuri)
  • permalink_uri_old (前回構築された静的ファイルのuri)
  • permalink_parent_type (Category 又は Blog)
  • permalink_parent_id (permalink_parent_typeに対応したオブジェクトのID)
  • permalink_template_id (構築に使用されたテンプレートのID)
  • permalink_status (既に削除と書き出しを行ったかどうか)
  • permalink_modified_on (データが更新されたタイムスタンプ)
  • permalink_build_on (ファイルが最後に書き出されたタイムスタンプ)

BuildFile のタイミングとか、MT::FileMgrが利用されるタイミングなんか(※ここがまだ実はよく分かっていない)で更新されるとともに、カテゴリーのbasenameとかblogのパスが変更された際に更新されるイメージ。

BuildFileの時点で、permalink_uriにあたるファイルが書き出されたらpermalink_uri_oldが(存在していたら)削除されるとか(正確には新たに生成されたファイルでないかどうかチェックする必要があるけれども)。

あるいは、画像やcss, jsファイル等のすべてのファイルをDBで管理してしまえば削除や移動が可能になるかもしれない。その分DBが肥大化してしまうわけですが。

もっとも現実的なアプローチは、制作過程ではダイナミックパブリッシング、納品時点では静的ファイルベースという方法でしょうか。ダイナミックであっても実際は静的HTMLのようなアドレスで制作途中のレビューが出来、実際はファイルは生成されていない状態。これでサイトが完成した段階で静的HTMLファイルを生成する。

これであれば静的ファイルとの混在も可能だろうし、変更があっても柔軟に対処できるだろう。

まぁ、理想を言えばきりがない。すぐにできる対処として考えつくのは

  • ステータスが下書きのエントリーの静的ファイルが残っていないかチェックして残っていれば削除する、あるいはステータスが下書きになったらファイルを削除する
  • エントリー等の保存時に、Permalinkが変更になっていたら古いファイルを削除するあるいは移動してから再構築する
  • カテゴリーのbasenameやブログのパスが変わっていたらリネームする

といったところ。いずれにしても(高価な商用製品についてはわからないが)ファイルをうまく管理出来るCMSを追求していきたいと思う。


追記:そないに難しく考えずにファイル構築の際にページの文字列まるごととアドレスをデータベースに保存してmod_rewriteで動的に処理させればよいような気が…
わりとすぐに出来そう…


※↑ていうか、ダイナミックパブリッシングのキャッシュのしくみということね。

→続きはこちら。実際にやってみた。


→さらに続きはこちら。

mt-config.cgiに

DeleteFilesAtRebuild 1

と書くと、エントリーを削除したり非公開にした時に静的ファイルを削除してくれる。

ところが「エントリー一覧」からチェックボックスをクリックして「非公開」にしたときは削除してくれない。

半端な実装?


そうそう、環境書くの忘れた。
MT3.34、MAMP+SQLite。他の環境でも確認してから追記します。


あれこれプラグイン入れてる影響がないとはいえませんが、なぜここが気になるかといえば、Hyper Estraierの検索インデックスをリアルタイムに更新するしくみを作っていてひっかかったのだ。

PostDelet_Entry(だったっけ?)コールバックが呼ばれる時、またはPreSave_Entryコールバックが呼ばれる時(かつステータスが公開から非公開に変わった時)にインデックスから該当エントリーを削除するようにしたのだが、ここでひっかかった。

もうひと工夫が必要だわ。


さらに追記:
ファイルが削除されない時は、おそらく、

1.エントリー一覧からチェックボックスでエントリーを選択して「非公開」にした時。(一応解決?)
2.エントリー編集時にカテゴリーを変更した時(且つアーカイブパスにカテゴリー情報が含まれている時)
3.(さらにさらに追記) エントリー一覧→一括画面でカテゴリーを変えた時も2.と同様の状態になる...これがやっかいそうな感じだわ。

basenameが変更された時には正常に削除されている様子。


さらに(さらに)追記:

1, 2は一応解決できたっぽい(東京へ向かう新幹線の道中で書けた)。3はホネのある...ってか要望出した方が賢いか。

かなり慣れてきたけど、時々どうなってんだろうなぁ...と悩むことがありますわ。

MT::Trackback は載っていないし...多分? こんな感じのようだ。メモがてら書いておこう。

まず、エントリーと受信トラックバックは、

  • MT::TBPing->tb_id→MT::Trackback->id
  • MT::Trackback->entry_id→MT::->entry_id

という形で紐づけられている。

Movable Type オブジェクト・リファレンス > MT::TBPing

junkかどうかの判断基準はともかく、dbへの格納は

MT::TBPing ->visible = 1 → 公開
MT::TBPing ->junk_status = 0 且つ MT::TBPing ->visible = 0 → 非公開
MT::TBPing ->junk_status = -1 → 迷惑トラックバック扱い

と見た(?)。...あってんだかなぁ。間違ってたら誰か教えてください。

というか、ソース嫁, もとい読め>俺。

一昨日も「トラックバック書けた...!」と思ってから「更新Ping」の送り方ってどう...と思って/lib/MTの中をgrepで検索してたら...


my $res = $mt->ping_and_save(Blog => $blog, Entry => $entry,
										OldStatus => $old_status) ;

これだけかい!


追伸?

「概要」のところ、<pre><code>〜</pre></code> 忘れてます...

バグ報告:
修正版を公開しました。
RC1を既にダウンロードされた方は差し替えお願いします。
エントリーの主カテゴリーを変更した時に(アーカイブマッピングにカテゴリーのbasenameが含まれている場合)、ファイルが古いカテゴリーのディレクトリに吐き出される問題があります。念のためエントリーのカテゴリーを変更して保存する場合は再度保存し直す等してください。

この件に関する修正版を今週末に公開予定です。

※↑修正しました。


エントリーの保存時にトラックバックを送信できるようになったので、一応すべてのタスク完了ということで1.0RC1とします。

Download:

What's New!:

  • エントリーの再構築時にトラックバックが送信できるように(注
  • 再構築の結果を電子メールで受け取れる機能の追加
  • 再構築の順番を変更 (インデックス・テンプレートを先に再構築するように→参考)
  • カテゴリー変更時の保存先問題(バグ)の修正
  • カテゴリー変更時(保存先パスが変更された場合)旧ファイルを削除するように

(注)トラックバックの送信はフォアグラウンドで行います

License:

『クリエイティブ・コモンズ・ライセンス(表示-非営利-継承 2.5)』とします。
商用利用・設置代行等はお問合せください

Creative Commons License

Related Entry:


※残りタスクとしては「テンプレート」「コメント」「トラックバック」の編集画面からの再構築のバックグラウンド化と、バックグラウンド再構築の選択が出来るようにするあたり。

鯖とか書いたりして...

データセンターにおいてあるサーバではクリティカルなものが動いているのと余計な負荷をかけたくないということもあって、会社のオフィスにサーバーを立てている。

で、今日携帯からのトラックバック機能を実装したのだが、このブログからこのブログへのトラックバックが打てないわ。DNSの設定だろうね。サーバーの/etc/hosts をさわるとApacheがおかしくなるし...

自blogへのトラックバックはHTTP_POSTでなく内部で処理することにしましょう。

外部のBlogへの送信は成功した。本当はこっち↓にトラックバック機能を実装しようと思っていたのだが、マイ・ブーム的に携帯を先にしてしまった。

MovableType Background Rebuilder Plugin(Beta8).

※別に必要ないっちゃないんだが、下書をPCで書いて携帯から更新・公開してみよう。

# 一応出来たのでTrackbackの送信テストをしてみる。

モブログその後(改)。

| コメント(0) | トラックバック(1)

モブログのプログラムをゴリゴリ書いているうちにココログが携帯対応になっていたので(知らなかっただけ?)、作成中のプログラムと比較したり意地になって負けないようにとか。

[ココログ(TypePadなのか?)にできないこと(で、このMTで出来ること)]

・トラックバックの送信。
・複数カテゴリーの選択。
・携帯から長い文章の更新。

と、書きはじめて入力(というより整形)の面倒くささが嫌になったのでWikiライクに入力出来るようにしよう。

リンクの部分は既に実装してみたのでテスト。

→ココログの最新エントリー

PCから追記。↑ちょっとバグってたようだけど修正してうまくいった模様。またメール投稿時の画像のAltを設定できるようにした。あと、何つーか、インストーラを作成した。モジュールのチェックやパス関係を出来る限り自動取得して導入の敷居を下げようかと。

さて、リリース方法を考えているところ。同種のサービスを調べてみると結構あるわけですが。

  • メールから投稿できる系(XML-RPCを使っているものがほとんどのようだ。確かにこの方が利用範囲は広がるものの、セキュリティ的にどうなんだろう...)
  • 携帯でブラウズ出来る系

今作成しているものは、そのどちらもできて且つセキュア、カスタマイズ性の高いもの。ページ分割と管理機能の関係で現状は動的生成しているけどいずれは静的ファイルで構築できるようにしたい。

MovableType をテンプレートエンジンとして使って動的なページを作成する方法の例。

以下のエントリーの例で説明。※エラー処理等は省略

/mt/lib/MT/Myapp.pm

タブ区切りテキスト(別にどんなフォーマットでも良いけど[注1])を $entry>text に渡して1行毎にコンテナタグ[注2]のループから取り出す。

$blog, $authorを取得しておくか生成しておいて、MT::Entry オブジェクトを生成してページをビルドする (動的ページ生成のためのオブジェクトなので保存はしない) 。

my $title = 'Title';
my $text =<<LIST;
http://example.com/1.html	Example1
http://example.com/2.html	Example2
http://example.com/3.html	Example3
LIST

# エントリーの生成
use MT::Entry;
my $entry = MT::Entry->new;
$entry->blog_id($blog->id);
$entry->author_id($author->id);
$entry->status(1);
$entry->convert_breaks(0);
$entry->title($title);
$entry->text($text);

# テンプレートの読み込み
use MT::Template;
my $tmpl_name = 'MyExampleTemplate';
my $template = MT::Template->load({
					 blog_id=>$blog->id,
					 name=>$tmpl_name,
					});
$template = $template->text;

# エントリーのビルド
my $ctx = MT::Template::Context->new;
$ctx->stash('entry', $entry);
$ctx->stash('blog', $blog);
$ctx->stash('blog_id', $blog->id);
my $build = MT::Builder->new;
my $tokens = $build->compile($ctx, $template);
my $html = $build->build($ctx, $tokens);

return $html;

/mt/plugin/Myapp.pl

# 前段は省略
MT::Template::Context->add_container_tag(MyContainer => \&_my_container);
MT::Template::Context->add_tag(MyURI => \&_my_uri);
MT::Template::Context->add_tag(MyLabel => \&_my_label);
MT::Template::Context->add_conditional_tag(IfMyOdd => \&_my_odd);

# <MTMyContainer>~</MTMyContainer>
sub _my_container {
	my ($ctx, $args, $cond) = @_;
	my $tokens = $ctx->stash('tokens');
	my $builder = $ctx->stash('builder');
	my $blog_id = $ctx->stash('blog_id');
	my $entry = $ctx->stash('entry');
	my $text = $entry->text;
	my @items = split(/\n/, $text);
	my $res = '';
	my $i = 1;
	foreach my $item (@items) {
		my @splitem = split(/\t/, $item);
		local $ctx->{__stash}{'uri'} = $splitem[0];
		local $ctx->{__stash}{'label'} = $splitem[1];
		local $ctx->{__stash}{'num'} = $i;
		my $out = $builder->build($ctx, $tokens, $cond);
		$res .= $out;
		$i++;
	}
	$res;
}

# <$MTMyURI$>
sub _my_uri {
	my ($ctx, $args, $cond) = @_;
	$ctx->stash('uri');
}

# <$MTMyLabel$>
sub _my_label {
	my ($ctx, $args, $cond) = @_;
	$ctx->stash('label');
}

# <MTIfMyOdd>~<MTElse>~</MTElse></MTIfMyOdd>
sub _my_odd {
	my ($ctx, $args, $cond) = @_;
	my $num = $ctx->stash('num');
	$num % 2;
}
1;

MyExampleTemplate

管理画面からアーカイブテンプレートとして登録。

<h1><$MTEntryTitle$></h1>
<ol>
<MTMyContainer>
	<li <MTIfMyOdd>class="odd"<MTElse>class="even"</MTElse></MTIfMyOdd>><a href="<$MTMyURI$>"><$MTMyLabel$></a></li>
</MTMyContainer></ol>

出力結果

<h1>Title</h1>
<ol>
	<li class="odd"><a href="http://example.com/1.html">Example1</a></li>
	<li class="even"><a href="http://example.com/2.html">Example2</a></li>
	<li class="odd"><a href="http://example.com/3.html">Example3</a></li>
</ol>

  • 注1) 本当はハッシュとか配列が渡せるといいんだけど。
  • 注2) こんな渡し方だったらグローバルフィルターで分解でも良いのだけれど、コンテナタグ化することによってデザイン担当と分業できるので。

Regexプラグインで出来るわけなのですが...難しいよ。

ということで、これ。

<MTIfMatchEntry search="&lt;!--flag--&gt;" item="excerpt">

とか。

<MTIfMatchEntry search="(MovableType|MT)" item="all">

とか、ね。


追記: もうちょっと丁寧に書こう...

<MTIfMatchEntry search="MovableType" item="text">
	entry_text(MTEntryBody)に"MovableType"を含む場合
<MTElse>
	entry_text(MTEntryBody)に"MovableType"を含まない場合
</MTElse>
</MTIfMatchEntry>

属性値itemに指定できるのは title, text, text_more, excerpt, keywords, basename。 item="all" とするとこれらすべてを検索対象とします。

上述の例のようにコメントタグ等にマッチさせたい場合は文字参照にします。また、正規表現で検索しているので、search="(MovableType|MT)" とすると、"MovableType" または "MT" にマッチするかどうかで分岐できます。

#!/usr/bin/perl -w

use strict;
use HTTP::Request::Common;
use LWP::UserAgent;

print "content-type: text/html¥n¥n";
print <<'HTML';
<?xml version="1.0" encoding="UTF-8"?>
<html>
<head>
<body><pre>
HTML

my $url = 'http://trackbackurl;
my %TBdata = (
	'url' => 'http://trackbackurl_from',
	'blog_name' => 'Junnama Online',
	'title' => 'エントリータイトル',
	'excerpt'=> 'エントリーの要約');
my $request  = POST($url, [%TBdata]);
my $ua = LWP::UserAgent->new;
my $res = $ua->request($request);
print $res->as_string;

これだけで送れるのだから...なるほどTrackbackスパムが多いわけだ。

属性値に指定できるのは以下の4種類。デフォルトはNFKC。

  • NFD(Normalization Form D)
  • NFC(Normalization Form C)
  • NFKD(Normalization Form KD)
  • NFKC(Normalization Form KC)
<$MTEntryBody normalize="NFD"$>

単にPerlのUnicode::Normalize;で正規化しているだけですが、半角に変換されると困る以下の文字については事前に置換するようにしました。

$text =~ s/</&lt;/g;
$text =~ s/>/&gt;/g;
$text =~ s/&/&amp;/g;
$text =~ s/[“”]/&quot;/g;

MT3.34, Perl5.8, 文字コードUTF-8環境でのみ検証済みです。

ダウンロード:

※正規化って何? という方は以下を参照ください。

Moooooble。

| コメント(2) | トラックバック(0)

MovableTypeの携帯拡張パッケージのプロジェクト名(コードネーム)。

  • MobileTypeはフォント名称にあったし、
  • MobableTypeはMovableTypeのTypoだし(MovableTypoってのも考えたけど)

やっぱりネーミングからすると「検索」は必須かな。5月にはリリースします。

モバイルサイトのページ分割のコード書いてたのだが、汚いコードになってしまった。ちょっと整理しなくては。

  • エントリーにタグを含むかどうか判別
  • 含んでいなければバイト数で切る
  • 但しマルチバイト文字の途中で切れないように
  • タグを含む場合、「>」でsplitしてLoopで回し、指定Byteを越えたらページ送り
  • 但しリンクテキストの途中では切らない
  • ul,ol,dl,tableの途中で切れた場合、終了タグを補完する
  • 終了タグを補完した場合、次のページで開始タグを補完する
  • 上記のタグ以外でも、終了タグが最初に現れるページでは開始タグを表示する

結構面倒くさい。
省略可能な終了タグをばっさり切ってしまえば少しは楽か?

# しかし、この手の文章を携帯から入力するのも結構面倒だな。

ラーメンの丼だった...

(追記) 携帯から写真の追加もできるのだ。

(さらに追記) ついでにもう一枚あげてみよう。

モブログ改良。

| コメント(2) | トラックバック(0)

投稿時にテキスト、追記、概要、キーワード、カテゴリー(複数指定可能)を指定可能。写真の添付も可能。

ステータス、コメント・トラックバックの受付、改行設定が携帯管理画面から行う。

エントリーの修正はメールを通じて行う(僕の機種ではTextareaに512Byteの制限があるので長い文章が切れないようにするため)。
再構築はメールからでも管理画面からでも可能。メールからは下書き投稿のみという制限もできる。

さて、複数カテゴリーを指定して投稿テスト。

で、メールから追記してみる。そういえば今日久しぶりにココログにログインしたら携帯から管理出来るようになっていた。ちょっと触ってみたが、やっぱり長いエントリーがTextareaに収まらないや。

そうそう、閲覧画面でカテゴリーの絞り込みを出来るようにした。

mt-config.cgiに、

TrackbackScript tb-mt.cgi

と記述して、mt-tb.cgi を tb-mt.cgi にリネーム。

<$MTEntryTrackbackData$> を <!--MTEntryTrackbackData--> に置換。
以上。これで、毎日100件近く来ていた迷惑TBが一件も来ないや。
来るなら来やがれ

さらに進化!

最終的にはエントリーに関するすべてのコントロールを携帯から出来るようにしたい。あとはmixiライクに「新しいコメントが1件あります」とか、そんなのを実装しようと思っている。

頭の中では既に出来てるんだけどね...

これまでに出来ていたもの

  • 携帯から画像付き投稿が可能
  • 携帯から閲覧・コメントが可能
  • 携帯からの閲覧では画像をリンク化してクリック→サムネイル表示
  • 携帯サイトのテンプレートはMTのテンプレート(moblog-index,moblog-error,moblog-entry,moblog-comment,moblog-trackback)として作成
  • 携帯からの画像付き投稿もMTライク? なテンプレートを作成可能
  • 設定はブログ毎に可能
  • サムネイルのサイズ等は設定で変更可能
  • 携帯から管理画面へログイン
  • 携帯管理画面から、エントリーの内容修正、ステータスの変更、プライマリーカテゴリーの変更、再構築、コメント・トラックバックの管理(ステータスの変更)が可能
  • 携帯からの投稿に関する設定で新たに「再構築する|しない」「公開|非公開」が設定可能に。

新たに追加したもの

  • 半角カナ対応(設定で変更可能)
  • 携帯管理画面からタスクの実行(cronでメールを取得しにいく仕様だったが, これでcronの設定は不要になった)
  • 期限切れセッションデータを定期的に削除
  • 携帯画面出力で要素・属性の削除部分を見直し
  • メールからエントリーを修正する際のセッションIDのチェック(よりセキュアに)
  • その他細かなバグフィックス

歩く。

| コメント(1) | トラックバック(0)



日曜日はジムに行ってひと汗かくというのが習慣なのだが、帰り2駅ほど歩いてみた。

発見という程のことはないがこんな景色もあったのね、という程度のことで写真を一枚。

大阪へ戻る車中から。

| コメント(0) | トラックバック(0)

携帯から投稿、管理画面からblogを再構築して反映させてみる。

# しかしこんなもんしか撮影するネタないんか...

追記も携帯から:
道中いくつかの機能を追加しました。で、さらに続く

モブログ一通り完成。

| コメント(0) | トラックバック(0)

QRコード

一通り完成。

これまでに出来ていたもの

  • 携帯から画像付き投稿が可能
  • 携帯から閲覧・コメントが可能
  • 携帯からの閲覧では画像をリンク化してクリック→サムネイル表示
  • 携帯サイトのテンプレートはMTのテンプレート(moblog-index,moblog-error,moblog-entry,moblog-comment,moblog-trackback)として作成
  • 携帯からの画像付き投稿もMTライク? なテンプレートを作成可能
  • 設定はブログ毎に可能
  • サムネイルのサイズ等は設定で変更可能

新たに追加したもの

  • 携帯から管理画面へログイン
  • 携帯管理画面から、エントリーの内容修正、ステータスの変更、プライマリーカテゴリーの変更、再構築、コメント・トラックバックの管理(ステータスの変更)が可能
  • 携帯からの投稿に関する設定で新たに「再構築する|しない」「公開|非公開」が設定可能に。

携帯から管理できるようにした。

今のところ、エントリーのステータスやカテゴリーの変更と内容(body)の修正のみだが、コメントやトラックバックの管理も出来るようにする予定。

しかし、iモードというか何というか、textareaの512バイト制限が欝陶しいな。結局エントリーの修正はメールを経由させることにした。

あとは...コメントやトラックバックの管理ができればそれでえ−かな。

今やっている「MTの携帯用blog拡張」もそうだけど、「何で今までなかったんだろう?」を提案、提供する会社にシフトします。

3月のドタバタがひと段落ついて、6月の決算期に向けた来期への布石。

6月あたり、いろんなものをリリースする予定なのでご期待の程。

また、それにあわせてWebサービス運用やサーバー運用とかホスティングサービス運用経験のある人材を募集します。

興味のある方は是非ご応募を。“MTいぢくる”のが楽しい方は特に向いてます!

携帯用サイト(続き)。

| コメント(0) | トラックバック(0)

QRコード

出来ているところ

  • 携帯から画像付き投稿が可能
  • 携帯から閲覧・コメントが可能
  • 携帯からの閲覧では画像をリンク化してクリック→サムネイル表示
  • 携帯サイトのテンプレートはMTのテンプレート(moblog-index,moblog-error,moblog-entry,moblog-comment,moblog-trackback)として作成
  • 携帯からの画像付き投稿もMTライク? なテンプレートを作成可能
  • 設定はブログ毎に可能
  • サムネイルのサイズ等は設定で変更可能
  • →プラグイン設定画面 (プラグインは設定の保存のためだけの役割で、投稿はcronにPerlスクリプトを登録する方式)

テンプレートは現在のところこんな感じ

<MTAttachmentImages>
	<MTIfAttachmentIsLager>
		<p><$MTAttachmentImageThumbToLink$></p>
	<MTElse>
		<p><$MTAttachmentImage$></p>
	</MTIfAttachmentIsLager>
</MTAttachmentImages>
<$MTMailBody$>

出来ていないところ

  • 不要なタグ, 属性の削除、変換
  • 半角カタカナへの変換(個人的に嫌いなので設定で振り分けかなぁ)
  • 長いページの分割(やっぱり要るかなぁ...)
  • メール投稿時の認証(メールを返信→URLクリック, 主にセキュリティのため)
  • 携帯での管理画面へのアクセス
  • 各キャリアでの検証

5月中旬にβリリース予定?

携帯用サイト。

| コメント(0) | トラックバック(0)

携帯で閲覧できるようにしてみた。

http://junnama.alfasado.net/mobile/

ついでに携帯用画像のサムネイル自動作成とか、イメージがあった時、なかった時のテンプレートを分岐させるタグとかも作ってみた。今回はサムネイル作成のテスト。

今のところコメントやカテゴリーなどは一切機能しない。エントリーの閲覧のみ。

この投稿も携帯から。さて、うまくいくかね?

追記:
ページ分割とか、いるかなぁ。あとは携帯から管理画面にアクセスできたら良いかな...

※携帯から修正できるようにした。で、テストしてみる

写真があって本文がない時にエントリー本文に画像が文字列として流し込まれてしまうのを修正(要は、multipartのメールの1つめのパートを本文と決め打ちしていたのを修正した)。

また、携帯からの投稿が特定のカテゴリーに属するようにした。

あとは…blogごとに投稿設定ができたりエントリーのレイアウト指定とか、画像のリサイズなんかが出来るといいだろうね。

現状は、写真→本文という流れでエントリーを作成する仕様にしてみた。

上野城。

| コメント(0) | トラックバック(0)

上野は上野でも、伊賀上野、ね。

携帯からの投稿、でかい写真も正常にアップできるものの、本文空だとおかしなことになるな。画像がASCII? 化されてエントリの本文扱いになってる。

※↑この件は既に修正済み

これ潰して、投稿アドレスとかの設定をブログ毎にできるようにして、あとは...画像のリサイズですかね。

# しかし疲れた...休みってのも楽じゃないな。

モブログ。

| コメント(0) | トラックバック(0)

中ふ頭駅へ続く桜並木。

インターフェイスが変わると書く内容も変わるもんですね。

中ふ頭駅前の桜並木の写真

SixApartのProNetミーティングに行ってきた。

僕が感じていることは当然彼らも感じているわけで、「なんか同じようなこと考えてるんだなぁ」というのが感想。

まぁ、楽しみでもあり、つまらなくもある(いじりがいがなくなるから)。

いくつか興味深い話もあって、話しを聞いているうちにムズムズ? してきて何か作りたくなってきた。

元々モバイル(モブログ)についてちょっと考えていたのだが、会社に戻ってから少し調べて帰りの新幹線でコードを書いてみた。なんとなくできたっぽい。

ということでケータイからテスト投稿してみる。

写真に意味はないけど、添付ファイルのテストってことで。

# プロジェクトのネーミングは思いつきだけど、なかなか良いんじゃない?

追記:フォント名称にあるのね

2月にサーバーを変えてFastCGI環境に移行してから、とにかく快適・高速になった。 FastCGIだけでなくサーバーのメモリ,CPUやHDD,等の問題も大きいのだと思う。

こうなったらとことんMovableTypeの高速化にチャレンジしてやろう! ということで、体感速度を上げる取り組みや検索の高速化等色々やっているのだが、もっと基本(?)的な部分に着手することにした。

参考エントリー:

このブログは決してエントリーも多い方ではないが(エントリー数で140強)、検索インデックス作成用の Hyper Estraier文書ドラフトを同時に吐き出している関係もあり、一回の全体再構築で300強のファイルが書き出される。一時は全体の再構築が10秒前後まで高速化されていたが、テンプレートの改造やエントリーの増加、さまざまなプラグインによるカスタマイズに従って、再び20秒強かかるようになっていた(それでも充分速いと思うけど)。

今回やってみたのが、テンプレートのうちの「共通部分(サイドバー)」の共通ファイル化である。

サイドバー部分

参考サイト:

PHPやSSIで再構築を高速化する手法と考え方は同じ。但し今回はPHPでもSSIでもなく、<$MTInclude file="/path/to/file"$> で再構築時にファイルをインクルードする。あくまでもスタティックなファイル出力、閲覧時のサーバー負荷にこだわるのだ。
また、ファイルのインクルード以外にも思いつくところは潰しておこうと思ったのであわせて書いておこう。

プラグインやMTの拡張プログラムを作ったり開発案件とかにも使っているから、時間のかかる「ツボ」部分はよく分かっているつもりで、このサイトの右側のカラムのように各ページカテゴリーの一覧や最新のエントリーの一覧を出力するためにいちいちデータベースにアクセスしてループさせてたら時間がかかって当たり前。

具体的な施策は以下の通り。

  1. 不要なテンプレートを再構築対象から外す。
  2. 不要な処理をテンプレートから削除する。
  3. 共通部分(右ナビゲーション)を「インデックスアーカイブ」として(例: include/sidebar.html) ファイルに吐き出すようにする。
  4. 各テンプレートのナビゲーション部分を「<$MTInclude file="/path/to/htdocs/online/include/sidebar.html"$> 」のようにしてインクルードするようにする。

Site JavaScript, スタイルシートなんかは毎回再構築の必要はなく修正時にだけ再構築すれば良い。ということで「インデックス・テンプレートを再構築するときに、このテンプレートを自動的に再構築する」チェックを外す。ダイナミック・パブリッシングでなければ「Dynamic Site Bootstrapper(mtview.php)」も不要。

再構築オプションのチェックを外す

たいした問題ではないかもしれないが mt-config.cgiにも「PublishCommenterIcon 0」を指定して「nav-commenters.gif」が吐かれないようにする。

また <MTBlogIfCCLicense> とか一度決めてしまえば不要なもの、あるいは「追記(more)」を使用していないのであれば「 <MTIfNonEmpty tag="EntryMore" convert_breaks="0">」のブロックとかも不要。

カテゴリーアーカイブが必ず吐かれる設定であるのなら「<MTIfArchiveTypeEnabled archive_type="Category">」のブロックもいらない。「タグ」を使っていないとか、自分のスタイルにあわせて不要な部分を削除していく。その分プログラムがデータをチェックする(僅かではあるが)処理が省略できる。

デフォルトテンプレートではこれらのタグがあるし、前述の通り毎回再構築の必要ないテンプレートも入っているから、そのあたりの掃除を行う。

基本これで良い筈なのだが、ひとつ問題がある。PHPやSSIと違ってこの方法の場合、インクルードされるのは再構築の瞬間であるから、エントリーを保存する時に「エントリー保存」→「サイドバー保存」の流れだと、エントリー保存時にサイドバーはまだ更新されていない、という問題が発生するのだ(そして、MTは実際にそういう振る舞いをする)。

エントリーを更新するたびに2回再構築する、あるいは「サイドバー」を再構築してから全体の再構築を行えば良いわけだが、いかにも「運用でカバー」である。これはエレガントじゃない!

エントリーはともかく、インデックスアーカイブの中でそれを防ぐためには、テンプレートIDを入れ替えてやると良い。
つまり、大抵の場合「メイン・ページ」がテンプレートID=1であるから、「メイン・ページ」を別途新規テンプレートとして保存してから、元々「メイン・ページ」だったテンプレートを「サイドバー」にして保存する

こうすると「サイド・バー」がテンプレートID=1となり、インデックス・アーカイブの保存時にはまず最初に「サイド・バー」が保存されるようになる。

IDが1のテンプレート

エントリー保存の際に、サイドバーを先に再構築する方法については...長くなったので、続く

(要は、MovableType Background Rebuilder Plugin に手を入れて、再構築の順番を変えたのだ。)

追記: 何とか10秒を切った

再構築結果:7秒

エントリーやカテゴリーの偶数奇数を判別するMovableType の条件タグ。

カテゴリーの1行ごとに色をつけた様子

元々はある案件用にカテゴリーを2段組のテーブルレイアウトで出力させる際に<tr>と</tr>を挿入する目的で作成したのだが、こんな風に1行おきに色を引くとかの用途にも使えるのでエントリーにも対応させて単独のプラグインとして公開。

カテゴリーとエントリーのループの中で使え、属性としてkey(ソートのキー), direction(降順/昇順,省略時はascend), toplevel(カテゴリーの場合、トップレベルカテゴリーかどうか) が指定できる。アイテムの数が偶数か奇数かを判別するための<MTIfItemCountIsOdd>〜</MTIfItemCountIsOdd>も用意したので、2段組みのテーブルの最後の行に「colspan="2"」を加える、なんてこともできるようになっている。

<li<MTIfItemIsOdd key="label"> class="odd"</MTIfItemIsOdd>><a href="<$MTCategoryArchiveLink$>"><MTCategoryLabel></a></li>

日本社会で起業するため本当に必要な9つのモノ

う〜ん。何とも言えぬ内容だが。

やりたいことはあるが、既存の企業だと思うようにそれができないので、自由に自分の意志で経営方向を決定するために起業する、というケースがあります。特に昨今のWeb2.0的な流れの中ではアイディア一つで起業していくことが多いためです。この場合にぶつかる壁が、「自分はやりたいことを思うがままにやりたいだけであり、決して経営や会社の成長などに興味があるわけではない」というものです。

そんなケースがあるのか? 経営に興味が無くて起業? ギャップも良いところ。本当にいるんだろうか。

単純に「社長」「代表取締役」などという肩書きがかっこいいのでそれにあこがれ、起業したいという人であればそれはそれで実は問題など無いのですが、そういうわけではないのに起業したからには経営もやらざるを得ないという場合、自分の好きなことだけをやり続けたくて、それ以外のことに手抜きになってしまい、夢実現の半ばで頓挫するという羽目になります。

どうでも良いのでスルー。そんなの「夢」でも何でもない。

このような場合、本来であれば「経営のプロ」をトップに据え、実権は自分が最大株式数を保有するなどで確保しつつ、自分のやりたいことに集中する…というのが正しい方向です。

絶対に無理だって。社員一人にしったって理想のパートナー見つけるのは困難だし。ましてや経営が嫌で好きなことやりたい人(責任を負いたくない人)の会社の経営を都合よくやってくれる人がどこにいますか?

「で、どうやって経営を軌道に乗せるの?」と聞かれると途端に具体的な言葉が出てこなくなると言うわけです。

出てくるわけないじゃん。出て来たところでどれだけリアリティがあるっていうんだ? 経験も後ろ盾もないのが起業じゃないの? 自分で経営しようという強い意思があったとて、言葉が出てこなくてもそれでいい。立て板に水のごとく言葉が出てくれば良いってもんじゃない。

続きは...眠いから、後で書く。


さて、続き。

これまでにも書いているので、まずは参照いただきたい。

クリエーターが独立して会社を作るのに1円起業は有効か?

「本当に必要な一つのもの」は何か?

答えは「人とケースによる」。

但し、あるケースではそれは「潤沢ではないが、それなりの」資金だったりするのだ。まずは500万円程でいい。貯めてみましょう。話はそれからだ。いやマジでね。

で、続きはさらに後で書く。

カテゴリーアーカイブのパスを得る方法がどうにもわからなくて、これまでこんな風に書いていたのだが、カテゴリーアーカイブの公開の設定(つまりTemplateMap)がどうなっているか決め打ちできないから汎用性が無いよなぁ...って...

my $cat_pth = &get_cat_arc($category, $blog_url, '/', 'index.html');
sub get_cat_arc {
	my ($cat, $blog_base, $separator, $file_pth) = @_;
	my $str = $cat->basename;
	my $parent = $cat->parent;
	while ($parent != 0) {
		my $category = MT::Category->load({
		id=>$parent});
		$str = $category->basename.$separator.$str;
		$parent = $category->parent;
	}
	return $blog_base.$str.$file_pth;
}

lib::MT::Category.pmにあるじゃねぇか!

sub category_path {
    my $cat = shift;
    return $cat->{__path} if exists $cat->{__path};
    my $result = $cat->basename;
    do {
        $cat = $cat->parent ? __PACKAGE__->load($cat->parent) : undef;
        $result = join "/", $cat->basename, $result if $cat;
    } while ($cat);
    # caching this information may be problematic IF
    # parent category basenames are changed.
    $cat->{__path} = $result;
}

ちゃんとキャッシュもしてるみたいだし。
やっぱり、ちゃんとドキュメント整備しようよ。これじゃやっぱり「ハック」の領域だよ。

まぁいいや、これでタスクリストのいくつかが潰れた。

Facebook

Twitter

このアーカイブについて

このページには、2007年4月に書かれたブログ記事が新しい順に公開されています。

前のアーカイブは2007年3月です。

次のアーカイブは2007年5月です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

Powered by Movable Type 6.2.6