なんとなく、みんながこう書いているから...という感覚で Movable Type プラグインを書いている人には少し役に立つかもしれません。
ブロックタグ(Movable Type 3 系でいうところのコンテナタグ)をプラグイン内で定義する場合、第一引数として $ctx を受け取ります。もちろんこれは、必ずしも $ctx という名前にしてやる必要はないのですが、公開されている Movable Type オブジェクト・リファレンス - MT::Template::Context でも $ctx と書かれているためか、一般的に公開されているプラグイン内でもこの様に書く場合が多いようです。
例えば、あるブログのエントリを一回ループするためのブロックタグを、以下のように定義していたとします。このブロックタグは、引数として count (表示件数), blog_id (対象とするブログの ID) をとり、blog_id で指定されたブログに属するエントリを一回ループします。ループ内では、エントリに関するファンクションタグと、ループ回数を出力するタグが使えます。一番下にあるサブルーチン _sample_counter は、ループ回数を出力するファンクションタグの定義です。
sub _sample_entry_contanier {
my ($ctx, $args, $cond) = @_;
my $count = $args->{count};
my $blog_id = $args->{blog_id};
my @entries;
if ($blog_id) {
@entries = MT::Entry->load({ blog_id => $blog_id });
} else {
@entries = MT::Entry->load({ blog_id => $blog->id });
}
my $res;
my $counter = 0;
foreach my $entry (@entries) {
local $ctx->{__stash}{entry} = $entry;
local $ctx->{__stash}{counter} = $counter + 1;
my $out = $builder->build($ctx, $tokens, $cond);
return $ctx->error( $builder->errstr ) unless defined $out;
$res .= $out;
last if ($count && ($counter + 1) eq $count);
$counter++;
}
if ($res) {
return $res;
} else {
return '';
}
}
sub _sample_counter {
my ($ctx, $args, $cond) = @_;
my $counter = $ctx->stash('counter');
if ($counter) {
return $counter;
}
return '';
}
で、MT タグを以下のような感じで組んだとします。
<ul>
<MTSampleEntryContainer count="10" blog_id="1">
<li><$MTSampleCounter$>. <$MTEntryTitle$></li>
</MTSampleEntryContainer>
</ul>
この場合、$ctx はいったいどんな形になっているのか。サブルーチン _sample_entry_contanier で第一引数として受け取った直後の $ctx を、Data::Dumper で Dumper して出力すると、以下のようになっています。これは、僕のブログ(Movable Type 3.35)の場合ですが、Movable Type 4 になると、もう少し多くの情報が入っています。面倒なのでほとんどそのまま貼付けます。
$VAR1 = bless( {
'__stash' => {
'tokens_else' => [],
'tokens' => [
[
'TEXT',
'<li>'
],
[
'SampleCounter',
{},
undef,
undef,
[]
],
[
'TEXT',
'. '
],
[
'EntryTitle',
{},
undef,
undef,
[]
],
[
'TEXT',
'</li>
'
]
],
'builder' => bless( {}, 'MT::Builder' ),
'uncompiled' => '<li><$MTSampleCounter$>. <$MTEntryTitle$></li>
',
'tag' => 'SampleEntryContainer',
'blog_id' => '1',
'blog' => bless( {
'column_values' => {
'ping_others' => 'http://blog.with2.net/ping.php/20090/1098680138
http://ping.rss.drecom.jp/
http://rpc.blogrolling.com/pinger/
http://bulkfeeds.net/rpc
http://ping.bloggers.jp/rpc/
http://www.blogpeople.net/servlet/weblogUpdates
http://blogstyle.jp/xmlrpc/
http://ping.myblog.jp
http://ping.namaan.net/rpc/
http://ping.bloggers.jp/rpc/',
'moderate_pings' => '1',
'entries_on_index' => '10',
'allow_unreg_comments' => '1',
'id' => '1',
'site_path' => '/var/vhosts/weeeblog.net/htdocs',
'archive_type_preferred' => 'Individual',
'convert_paras_comments' => '__default__',
'remote_auth_token' => '',
'ping_technorati' => '1',
'internal_autodiscovery' => '0',
'sort_order_comments' => 'ascend',
'name' => 'Weeeblog.net',
'junk_score_threshold' => '0',
'description' => 'プログラマ okayama の ウェッッッブログ',
'autolink_urls' => '1',
'site_url' => 'http://weeeblog.net/',
'archive_url' => 'http://weeeblog.net/logs/',
'junk_folder_expiry' => '14',
'custom_dynamic_templates' => 'none',
'autodiscover_links' => '0',
'ping_blogs' => '1',
'archive_path' => '/var/vhosts/weeeblog.net/htdocs/logs',
'words_in_excerpt' => '40',
'sanitize_spec' => '0',
'email_new_comments' => '1',
'archive_type' => 'Individual,Monthly',
'allow_reg_comments' => '1',
'days_on_index' => '0',
'allow_comment_html' => '1',
'basename_limit' => '30',
'mt_update_key' => '',
'status_default' => '1',
'allow_pings_default' => '1',
'sort_order_posts' => 'descend',
'allow_pings' => '1',
'require_comment_emails' => '0',
'email_new_pings' => '1',
'ping_weblogs' => '1',
'language' => 'en',
'file_extension' => 'php',
'cc_license' => '',
'children_modified_on' => '20080411061751',
'convert_paras' => '__default__',
'allow_comments_default' => '1',
'moderate_unreg_comments' => '0',
'server_offset' => '9'
},
'__file_mgr' => bless( {
'cfg' => bless( {
'__var' => {
'MailTransfer' => 'sendmail',
'ObjectDriver' => 'DBI::mysql',
'Database' => 'weeeblog',
'StaticWebPath' => 'http://weeeblog.net/mt/mt-static/',
'PluginPath' => [
'/var/vhosts/weeeblog.net/htdocs/mt/plugins'
],
'SearchTemplatePath' => '/var/vhosts/weeeblog.net/htdocs/mt/search_templates',
'DBUser' => '(データベースのユーザー)',
'AltTemplatePath' => '/var/vhosts/weeeblog.net/htdocs/mt/alt-tmpl',
'DBPassword' => '(データベースのパスワード)',
'TimeOffset' => '9',
'CGIPath' => 'http://weeeblog.net/mt/',
'DBHost' => 'localhost',
'WeblogTemplatesPath' => '/var/vhosts/weeeblog.net/htdocs/mt/default_templates',
'TemplatePath' => '/var/vhosts/weeeblog.net/htdocs/mt/tmpl',
'SendMailPath' => '/usr/lib/sendmail',
'ImportPath' => '/var/vhosts/weeeblog.net/htdocs/mt/import'
},
'__settings' => {
'IdentityURL' => {
'Default' => 'http://profile.typekey.com/'
},
'DefaultTimezone' => {
'Default' => '9'
},
'DBHost' => {},
'PublishCharset' => {
'Default' => 'UTF-8'
},
'LogExportEncoding' => {
'Default' => 'Shift_JIS'
},
'ExportEncoding' => {
'Default' => 'Shift_JIS'
},
'LaunchBackgroundTasks' => {
'Default' => 0
},
'DebugMode' => {
'Default' => 0
},
'AtomScript' => {
'Default' => 'mt-atom.cgi'
},
'AdminScript' => {
'Default' => 'mt.cgi'
},
'MTPingURL' => {
'Default' => 'http://www.Movable Type.org/update/'
},
'OneHourMaxPings' => {
'Default' => 10
},
'NoLocking' => {
'Default' => 0
},
'OutboundTrackbackLimit' => {
'Default' => 'any'
},
'DefaultEntryPrefs' => {
'Type' => 'HASH',
'Default' => {
'Type' => 'Basic',
'Button' => 'Below'
}
},
'CookieDomain' => {},
'PublishCommenterIcon' => {
'Default' => 1
},
'DynamicComments' => {
'Default' => 0
},
'GlobalSanitizeSpec' => {
'Default' => 'a href,b,i,br/,p,strong,em,ul,ol,li,blockquote,pre'
},
'SafeMode' => {
'Default' => 1
},
'ImportPath' => {
'Default' => 'import',
'Path' => 1
},
'PingProxy' => {},
'UseJcodeModule' => {
'Default' => 0
},
'UsePlugins' => {
'Default' => 1
},
'HTMLUmask' => {
'Default' => '0111'
},
'HTMLPerms' => {
'Default' => '0666'
},
'UseNFSSafeLocking' => {
'Default' => 0
},
'SearchThrottleIPWhitelist' => {},
'IdentitySystem' => {
'Default' => 'http://www.typekey.com/t/typekey'
},
'TrackbackScript' => {
'Default' => 'mt-tb.cgi'
},
'TransparentProxyIPs' => {
'Default' => 0
},
'TempDir' => {
'Default' => '/tmp'
},
'EnableArchivePaths' => {
'Default' => 0
},
'SearchCutoff' => {
'Default' => '9999999'
},
'PluginSchemaVersion' => {
'Type' => 'HASH'
},
'NewsboxURL' => {
'Default' => 'http://www.sixapart.jp/Movable Type/news/newsbox.html'
},
'SearchTemplatePath' => {
'Default' => 'search_templates',
'Path' => 1
},
'DBUmask' => {
'Default' => '0111'
},
'IgnoreISOTimezones' => {
'Default' => 0
},
'CSSPath' => {
'Default' => 'css'
},
'CGIMaxUpload' => {
'Default' => 1000000
},
'RegexSearch' => {
'Default' => 0
},
'PingNoProxy' => {
'Default' => 'localhost'
},
'SearchSortOrder' => {
'Default' => 'ascend'
},
'ExcludeBlogs' => {},
'EmailVerificationSecret' => {},
'HTTPTimeout' => {
'Default' => 60
},
'DefaultTemplate' => {
'Default' => 'default.tmpl'
},
'StaticWebPath' => {
'Default' => ''
},
'SearchSortBy' => {},
'AltTemplate' => {
'Type' => 'ARRAY',
'Default' => 'feed results_feed.tmpl'
},
'UpgradeScript' => {
'Default' => 'mt-upgrade.cgi'
},
'SignOffURL' => {
'Default' => 'https://www.typekey.com/t/typekey/logout?'
},
'DBPort' => {},
'WeblogTemplatesPath' => {
'Default' => 'default_templates',
'Path' => 1
},
'NetPBMPath' => {},
'CaseSearch' => {
'Default' => 0
},
'ActivityFeedItemLimit' => {
'Default' => 50
},
'SchemaVersion' => {},
'SupportURL' => {
'Default' => 'http://www.sixapart.jp/Movable Type/support/'
},
'PluginPath' => {
'Type' => 'ARRAY',
'Default' => 'plugins',
'Path' => 1
},
'HelpURL' => {
'Default' => 'http://www.sixapart.jp/Movable Type/manual/3.3/'
},
'SQLSetNames' => {},
'IndexBasename' => {
'Default' => 'index'
},
'UseSQLite2' => {
'Default' => 0
},
'SearchElement' => {
'Default' => 'entries'
},
'DefaultSiteRoot' => {
'Default' => ''
},
'ResultDisplay' => {
'Default' => 'descend'
},
'MailEncoding' => {
'Default' => 'ISO-2022-JP'
},
'EmailReplyTo' => {},
'TemplatePath' => {
'Default' => 'tmpl',
'Path' => 1
},
'RegKeyURL' => {
'Default' => 'http://www.typekey.com/extras/regkeys.txt'
},
'AdminCGIPath' => {},
'SendMailPath' => {
'Default' => '/usr/lib/sendmail'
},
'ObjectDriver' => {},
'NoPlacementCache' => {
'Default' => 0
},
'CommentSessionTimeout' => {
'Default' => 3600
},
'DBPassword' => {},
'AltTemplatePath' => {
'Default' => 'alt-tmpl',
'Path' => 1
},
'WeblogsPingURL' => {
'Default' => 'http://rpc.weblogs.com/RPC2'
},
'NoOverride' => {
'Default' => ''
},
'AtomApp' => {
'Type' => 'HASH',
'Default' => 'weblog=MT::AtomServer::Weblog'
},
'ExcerptWords' => {
'Default' => 40
},
'TimeOffset' => {
'Default' => 0
},
'GenerateTrackBackRSS' => {
'Default' => 0
},
'DBSocket' => {},
'HTTPNoProxy' => {
'Default' => 'localhost'
},
'Serializer' => {
'Default' => 'MT'
},
'DirUmask' => {
'Default' => '0000'
},
'ShowIPInformation' => {
'Default' => 0
},
'NewsURL' => {
'Default' => 'http://www.sixapart.jp/Movable Type/'
},
'MailTransfer' => {
'Default' => 'sendmail'
},
'PingTimeout' => {
'Default' => 60
},
'SignOnPublicKey' => {
'Default' => ''
},
'EmailNotificationBcc' => {
'Default' => 1
},
'HTTPProxy' => {},
'SignOnURL' => {
'Default' => 'https://www.typekey.com/t/typekey/login?'
},
'NoHTMLEntities' => {
'Default' => 1
},
'DefaultListPrefs' => {
'Type' => 'HASH',
'Default' => {
'DateFormat' => 'Relative',
'Button' => 'Above',
'SortOrder' => 'Ascend',
'Rows' => 20,
'Format' => 'Compact'
}
},
'AllowPings' => {
'Default' => 1
},
'Database' => {},
'ImageDriver' => {
'Default' => 'ImageMagick'
},
'MaxResults' => {
'Default' => '9999999'
},
'CommentScript' => {
'Default' => 'mt-comments.cgi'
},
'TypeKeyVersion' => {
'Default' => '1.1'
},
'OneDayMaxPings' => {
'Default' => 50
},
'DeleteFilesAtRebuild' => {
'Default' => 0
},
'EntriesPerRebuild' => {
'Default' => 40
},
'CategoryNameNodash' => {
'Default' => '1'
},
'NoCDATA' => {
'Default' => 0
},
'SearchScript' => {
'Default' => 'mt-search.cgi'
},
'CommentSearchCutoff' => {
'Default' => '30'
},
'AllowComments' => {
'Default' => 1
},
'Type' => {
'Default' => 'straight'
},
'WSSETimeout' => {
'Default' => 120
},
'ThrottleSeconds' => {
'Default' => 20
},
'ActivityFeedScript' => {
'Default' => 'mt-feed.cgi'
},
'PluginSwitch' => {
'Type' => 'HASH'
},
'CookiePath' => {},
'DefaultSiteURL' => {
'Default' => ''
},
'PingInterface' => {},
'SMTPServer' => {
'Default' => 'localhost'
},
'ViewScript' => {
'Default' => 'mt-view.cgi'
},
'EmailAddressMain' => {},
'NoTempFiles' => {
'Default' => 0
},
'IncludeBlogs' => {},
'NoPublishMeansDraft' => {
'Default' => 0
},
'DebugEmailAddress' => {},
'DataSource' => {
'Path' => 1
},
'CGIPath' => {
'Default' => '/cgi-bin/'
},
'BlogsPingURL' => {
'Default' => 'http://ping.blo.gs/'
},
'TechnoratiPingURL' => {
'Default' => 'http://rpc.technorati.com/rpc/ping'
},
'XMLRPCScript' => {
'Default' => 'mt-xmlrpc.cgi'
},
'ActivityFeedsRunTasks' => {
'Default' => 1
},
'UploadUmask' => {
'Default' => '0111'
},
'DBUser' => {},
'OutboundTrackbackDomains' => {
'Type' => 'ARRAY'
},
'HTTPInterface' => {},
'DefaultLanguage' => {
'Default' => 'ja'
},
'UserSessionTimeout' => {
'Default' => 14400
},
'UploadPerms' => {
'Default' => '0666'
}
},
'__paths' => [
'DataSource',
'TemplatePath',
'WeblogTemplatesPath',
'AltTemplatePath',
'ImportPath',
'PluginPath',
'SearchTemplatePath'
],
'__dbvar' => {
'SchemaVersion' => '3.31',
'SQLSetNames' => '1',
'PluginSwitch' => {
'RebuildIndexById.pl' => '1'
}
}
}, 'MT::ConfigMgr' )
}, 'MT::FileMgr::Local' )
}, 'MT::Blog' )
}
}, 'MT::Template::Context' );
基本的にすべて、bless されたオブジェクトであることがわかります。bless することの意味がよくわからなければ、続・初めての perl を読むとよいでしょう。
一番最後に MT::Template::Context があることから、$ctx は MT::Template::Context のオブジェクトであることがわかります。また、サブルーチン内の動作として行われている
my $tokens = $ctx->stash('tokens');
での $tokens には、ブロックタグ内の記述を分割したものが入っていることがわかります。また、
my $builder = $ctx->stash('builder');
での $builder には、MT::Builder オブジェクトが入っていることがわかります。
また、$ctx には、MT::Blog オブジェクトなど、ブロックタグの構築に必要なほとんどすべての必要な情報が入っています。ないのはブロックタグに指定している引数ですが、これはサブルーチン内の第二引数 $arg としてまとめて受け取り、
my $count = $args->{count};
のような形式でデータにアクセスして取り出すことになります。
Data::Dumper で Dumper してよくわからないものの構造を確認してみるだけで、Movable Type のプラグインを書く場合には、いろいろと役に立ちます。例えば、二つのブロックタグを同じサブルーチンとして定義し、
$ctx->stash('tag');
で取り出された値(タグ名)で分岐、などということもできます。
Movable Type のプラグインを書く場合の他にも、Perl プログラミングで「向こうが定義しているものを使わなければいけない場合」には大きな手助けとなるでしょう。Data::Dumper; は、ソースコードの冒頭に use Data::Dumper; と記述し、
my $datas = Dumper($ctx);
とすることで、$ctx のデータ構造をわかりやすい形に直したものを、$datas に代入することができます。print するなり、ファイルに書き出すなどして確認するとよいでしょう。
Comments