PHPerのためのMovable Type

- はじめに

- はじめに-「生」PHPからMTを利用する

- テンプレートエンジン

- テンプレートエンジンを扱う(class MTViewer)その1

- テンプレートエンジンを扱う(class MTViewer)その2

- ユーティリティ

- MTUtil.phpの関数群を利用する

- データベース

- データベースにアクセスする(class MTDatabase編) その1

- データベースにアクセスする(class MTDatabase編) その2

- データベースにアクセスする(class BaseObject編)

- プラグイン

- ダイナミックパブリッシング用プラグインを作成する(init.Foo.php編)

- ダイナミックパブリッシング用プラグインを作成する(ブロックタグ編)

- ダイナミックパブリッシング用プラグインを作成する(条件タグ、ファンクションタグ、グローバルモディファイア編)

- DynamicMTML

- DynamicMTMLを利用する

- DynaicMTMLからデータベースにアクセスする(ORマッパー)。

- コピーライト

はじめに

主に、「生」PHPからMovable Typeを扱う方法について説明します。

はじめに-「生」PHPからMTを利用する

MTはもうVersion6だよねDataAPIだよねJavaScriptだよねJSONだよね、な皆さんこんにちは。

何言ってんだよPHPだろ時代はMTで作られてるサイトのカスタマイズ依頼きたんだけどPHPだよPHPでやらなきゃならんのだよこっちは何がDataAPIだよ、というあなたに送る書籍です。

はじめに、DynamicMTMLやプラグインではなく、「生」PHPからMTをいじる前提で、class MTDatabase(/mt/php/lib/mtdb.base.php)のメソッドについて紹介したいと思います。

初期化

<?php
$blog_id = 1;
include('/path/to/mt/php/mt.php');
$mt = MT::get_instance( $blog_id, '/path/to/mt/mt-config.cgi' );
$ctx =& $mt->context();
$ctx->caching = FALSE;
$blog = $mt->db()->fetch_blog( $blog_id );
$ctx->stash( 'blog', $blog );
$ctx->stash( 'blog_id', $blog->id );

まずは初期化から。ウェブサーバーに test.php とかを置き、最後に echo $blog->name とかしてブログ(ウェブサイト)名が返ってきたらOK、成功です。

class MTDatabase はここでは、$mt->db()です。class MTDatabase のメソッドは、$mt->db()->foo とすることで呼び出せます。

Movable TypeはSix Aaprt, Ltd. の商標または登録商標です。

テンプレートエンジン

Smartyを拡張したclass MTViewerの利用について。

テンプレートエンジンを扱う(class MTViewer)その1

ヒアドキュメントの中にMTのテンプレートを書いて、PHPのコード中からビルドする方法。mtevalモディファイアを使うのが簡単なので、ここではmodifier.mteval.phpをrequire_onceして、そのままテンプレートを渡しています。

参考リンク: MTタグとphpで変数の受け渡しをする。 - Junnama Online

<?php
$blog_id = 1;
include('/path/to/mt/php/mt.php');
$mt = MT::get_instance( $blog_id, '/path/to/mt/mt-config.cgi' );
$ctx =& $mt->context();
$ctx->caching = FALSE;
$blog = $mt->db()->fetch_blog( $blog_id );
$ctx->stash( 'blog', $blog );
$ctx->stash( 'blog_id', $blog->id );
$ctx->__stash[ 'vars' ][ 'foo' ] = 'bar';
$tmpl = <<< EOM
<pre>
<mt:var name="foo">
<mt:BlogURL setvar="blog_url">
<MTBlogName>
<MTEntries include_blogs="children">
<MTEntryTitle>
</MTEntries>
EOM;
require_once( 'MTUtil.php' );
require_once( 'modifier.mteval.php' );
$contents = smarty_modifier_mteval( $tmpl, TRUE );
echo $contents;
echo $ctx->__stash[ 'vars' ][ 'blog_url' ];
?>

実際に生PHPのコードの中にテンプレートを書く必要があるのかといえば疑わしいというか、意味がないように見えますが、.htaccessなんかで、URLリライトしてファイルへのリクエストをこのphpに渡して、ファイル中のMTタグを解釈するようにすれば、簡易DynamicMTMLというか、そういう使い方もできますね。MTタグがかかれたテンプレートがView、php が Controllerです。

以前に書いた、MTCakeや、WordPressプラグインなんかは、要はこういうことでPHPでデータベースアクセスしてコンテキスト($ctx)にセットして、MTタグでそれを取り出すような例ですね。

- CakePHPのViewでMTタグを利用可能にするMTCake | PowerCMS ブログ

- WordPressのテンプレートをMovable Typeのタグで処理する | PowerCMS ブログ

PHPからデータベース接続さえできれば、テンプレートエンジン部分のみMTを利用する、といった使い方ができます。

ちなみに、この、$ctxは ファイルmt//php/lib/MTViewer.php で定義されています。

<?php
include_once("Smarty.class.php");
class MTViewer extends Smarty {

MTViewer extends Smarty、つまり、MTのダイナミックパブリッシングはSmartyを拡張したクラスになっています。なので、ダイナミックパブリッシング用のテンプレートタグは、Smartyのプラグインとして作成できるようになっているのですね。

テンプレートエンジンを扱う(class MTViewer)その2

class MTViewer( extends Smarty) 編。$ctxです。そう、コンテキストはSmartyであるのですが、今日はこれ。テンプレートエンジンの謎を探る(謎というわけでもないが)。PHPでのMTタグの作り方です。

公式ドキュメントは以下。

- Movable Type 開発者向けガイド

- ファンクションタグ プラグインの開発について

- ブロックタグ プラグインの開発について

- グローバル・モディファイアプラグインの開発について

公式ドキュメントではPerl(スタティック)の解説が前半、後半がPHPになっていますが、基本的にはタグの作成方法についてのドキュメントではPHPについても言及されています。

ベースにするのは昨日の以下のコード。当面「生」PHPで行くことにします。そろそろプラグインに行きたいところであるけれども。

この例では、「生」PHPですが、MTのお作法に従ってプラグインとして作る場合も基本は一緒です。$ctxのメソッドで動的にタグが登録しているという違いがあるだけで、基本は同じです。

<?php
$blog_id = 1;
include('/path/to/mt/php/mt.php');
$mt = MT::get_instance( $blog_id, '/path/to/mt/mt-config.cgi' );
$ctx =& $mt->context();
$ctx->caching = FALSE;
$blog = $mt->db()->fetch_blog( $blog_id );
$ctx->stash( 'blog', $blog );
$ctx->stash( 'blog_id', $blog->id );
$ctx->__stash[ 'vars' ][ 'foo' ] = 'bar';
$tmpl = <<< EOM
<pre>
<mt:var name="foo">
<mt:BlogURL setvar="blog_url">
<MTBlogName>
<MTEntries include_blogs="children">
<MTEntryTitle>
</MTEntries>
EOM;
require_once( 'MTUtil.php' );
require_once( 'modifier.mteval.php' );
$contents = smarty_modifier_mteval( $tmpl, TRUE );
echo $contents;
echo $ctx->__stash[ 'vars' ][ 'blog_url' ];
?>

動的にテンプレートタグを追加する

add_tag

タグ名と、関数名を渡して、ファンクションタグを登録します。以下の例では、mt:Bar タグで、Barを出力します。

$ctx->add_tag( 'bar', 'bar' );

function bar( $args, &$ctx ) {
return 'Bar';
}

// <mt:bar> => Bar

add_container_tag

タグ名と、関数名を渡して、ブロックタグを登録します。以下の例では、<mt:buz>～</mt: buz>で、1から5までの数字を<mt:var name="__counter__">タグで出力します。

$ctx->add_container_tag( 'buz', 'buz' );

function buz( $args, $content, &$ctx, &$repeat ) {
$localvars = array( '__counter__' );
if ( isset( $args[ 'max' ] ) ) {
$max = $args[ 'max' ];
} else {
$max = 5;
}
if (! isset( $content  ) ) {
$ctx->localize( $localvars );
$ctx->__stash[ 'vars' ][ '__counter__' ] = 0;
}
$counter = $ctx->__stash[ 'vars' ][ '__counter__' ];
if ( $counter < $max ) {
$repeat = TRUE;
$counter++;
$ctx->__stash[ 'vars' ][ '__counter__' ] = $counter;
$ctx->stash( 'counter', $counter );
} else {
$ctx->restore( $localvars );
$repeat = FALSE;
}
return $content;
}

ブロックタグの作り方は、ファンクションタグと違って少し複雑ですが、以下のように理解してください。

1. 引数は、$args, $content, &$ctx, &$repeat

2. $argsには、タグに渡されるモディファイアがキーバリューの配列で格納されている

3. $repeat が TRUEの間、繰り返し呼び出される

4. ループの1回目は、isset( $content ) はFALSEである(なので、これで分岐して初期化等を行う)

5. $ctx->localize( $localvars ); でテンプレート変数の初期化

6. $ctx->restore( $localvars ); で、初期化したテンプレート変数をリストア(戻す)

5、6ですが、ループの中でループを使っている時なんかで、__counter__の値が親ループの中の値なのか、子ループの中の値なのかが同じ変数名なのでわかんなくなりますよね。変数のスコープを局所化するという理解で良いと思います。

__stash は、クラスのプロパティ、stashはクラスのメソッドです。ややこしい? けど、要はキャッシュです。値をセットするのに使われます。stashメソッドでは第一引数にキャッシュのキー、第二引数にキャッシュの値(オブジェクトや配列、もしくはスカラ何でも)を指定します。第二引数を省略すると、キャッシュされた値を取得します。

$ctx->stash( 'blog', $blog );

__stashはプロパティで、配列(のキーが) varsに指定した値が、mt:var name='"hoge"で取得できます。

$ctx->__stash[ 'vars' ][ '__counter__' ] = $counter;

add_conditional_tag

タグ名と、関数名を渡して、条件タグ(mt:ifなんとか)を登録します。以下の例では、常に真を返す mt:ifqux条件タグを登録しています(常に真、って意味がないんですけど、サンプルとして)。

$ctx->add_conditional_tag( 'ifqux', 'ifqux' );

function ifqux( $args, $content, &$ctx, &$repeat ) {
return $ctx->_hdlr_if( $args, $content, $ctx, $repeat, TRUE );
}

返り値 $ctx->_hdlr_if( $args, $content, $ctx, $repeat, TRUE ); のように、第5引数に真偽を指定します。

add_global_filter

モディファイア名と、関数名を渡して、グローバルモディファイアを登録します。以下の例ではモディファイアに渡した文字列を連結して出力するcorgeモディファイアを登録しています。

$ctx->add_global_filter( 'corge', 'corge' );

function corge( $text, $arg ) {
return $text . $arg;
}

// <mt:bar corge="!!"> => Bar!!

ちょっと長くなりますが、ソースは下記のようになります。

<?php
$blog_id = 1;
include('/path/to/mt/php/mt.php');
$mt = MT::get_instance( $blog_id, '/path/to/mt/mt-config.cgi' );
$ctx =& $mt->context();
$ctx->caching = FALSE;
$blog = $mt->db()->fetch_blog( $blog_id );
$ctx->stash( 'blog', $blog );
$ctx->stash( 'blog_id', $blog->id );
$ctx->add_tag( 'bar', 'bar' );
$ctx->add_container_tag( 'buz', 'buz' );
$ctx->add_conditional_tag( 'ifqux', 'ifqux' );
$ctx->add_global_filter( 'corge', 'corge' );
$tmpl = <<< EOM
<pre>
<mt:bar corge="!!">
<mt:buz>
<mt:var name="__counter__">
</mt:buz>
<mt:ifqux>
It is true.
<mt:else>
It is false.
</mt:ifqux>
EOM;
require_once( 'MTUtil.php' );
require_once( 'modifier.mteval.php' );
$contents = smarty_modifier_mteval( $tmpl, TRUE );
echo $contents;

function bar( $args, &$ctx ) {
return 'Bar';
}

function buz( $args, $content, &$ctx, &$repeat ) {
$localvars = array( '__counter__' );
if ( isset( $args[ 'max' ] ) ) {
$max = $args[ 'max' ];
} else {
$max = 5;
}
if (! isset( $content  ) ) {
$ctx->localize( $localvars );
$ctx->__stash[ 'vars' ][ '__counter__' ] = 0;
}
$counter = $ctx->__stash[ 'vars' ][ '__counter__' ];
if ( $counter < $max ) {
$repeat = TRUE;
$counter++;
$ctx->__stash[ 'vars' ][ '__counter__' ] = $counter;
$ctx->stash( 'counter', $counter );
} else {
$ctx->restore( $localvars );
$repeat = FALSE;
}
return $content;
}

function ifqux( $args, $content, &$ctx, &$repeat ) {
return $ctx->_hdlr_if( $args, $content, $ctx, $repeat, TRUE );
}

function corge( $text, $arg ) {
return $text . $arg;
}
?>

出力結果は、以下のようになります。

Bar!!

1

2

3

4

5

It is true.

他に、add_token_tag というのがあるのですが(mt:subcategoriesのように再帰的に呼ばれるタグ)、使うケースは少ないように思いますので、今回は省略しました。

ユーティリティ

MTUtil.phpの関数について紹介します。日付や文字列操作等に便利な関数が含まれます。

MTUtil.phpの関数群を利用する

MTUtil.phpについてご紹介します。日付関係とかエンコードデコードとか、いわゆるユーティリティ的な関数群。

require_once( 'MTUtil.php' );

日付関係

datetime_to_timestamp

Unixタイムスタンプへの変換。 2014-04-17 10:08:57 → 1397696937

$epoc = datetime_to_timestamp( $blog->blog_children_modified_on );

start_end_ts

年(YYYY)、年月(YYYYMM)を渡すと、それぞれ年、月の最初と最後のタイムスタンプを返す。DBで範囲指定をする時などに利用。

list( $start, $end ) = start_end_ts( 20140416 );
//->20140416000000
//->20140416235959

start_end_month

月の最初と最後のタイムスタンプを返す。以下、結果的にはstart_end_tsと同じ。

list( $start, $end ) = start_end_month( 20140416 );
//->20140416000000
//->20140416235959

days_in

その月が、何日まであるか。月、年の順に引数を渡す。

$d = days_in( 4, 2014 );
//->30

start_end_day

その日の最初と終わり。

list( $start, $end ) = start_end_day( 20140416 );
//->20140416000000
//->20140416235959

start_end_year

その年の最初と終わり。

list( $start, $end ) = start_end_year( 20140416 );

start_end_week

その週の最初と終わり。週別アーカイブとか、みんな使ってます?

list( $start, $end ) = start_end_week( 20140416 );

is_leap_year

閏年かどうか。

$leap_year = is_leap_year( 2014 );
var_dump( $leap_year ); // false

gmtime

グリニッジ標準時間 (GMT)を返す(配列で)。引数にタイムスタンプを渡せる。省略時は現在時刻。

var_dump( gmtime() );
array(9) { [0]=> int(59) [1]=> int(30) [2]=> int(2) [3]=> int(17) [4]=> int(3) [5]=> int(114) [6]=> int(4) [7]=> int(106) [8]=> int(0) }

返り値の構造については→PHP: localtime - Manual

offset_time_list

ブログ/ウェブサイトのゾーン設定を反映した時刻を返す。

$ts = offset_time_list( time(), $blog );
$timestamp = sprintf( "%04d%02d%02d%02d%02d%02d",
$ts[5]+1900, $ts[4]+1, $ts[3], $ts[2], $ts[1], $ts[0] );
//->20140417113635

wday_from_ts

今日は何曜日?

$wday = wday_from_ts( 2014, 4, 17 );
//->4(木曜日)

yday_from_ts

今日は今年の何日目?

$wday = yday_from_ts( 2014, 4, 17 );
echo $wday;
//->107(日目)

文字列関係

dirify

basenameの組み立て的な。

$dirify = dirify( 'This is it', '_' );
//->this_is_it

- dirify | グローバル・モディファイアリファレンス

encode_xml

XMLフォーマットに適した形式にエンコードします。データがHTML要素やタグを含んでいる場合、CDATAブロックで囲んで出力。

- encode_xml | グローバル・モディファイアリファレンス

decode_xml

XML エンティティをデコード。

- decode_xml | グローバル・モディファイアリファレンス

encode_js

javascriptエンコード。

- encode_js | グローバル・モディファイアリファレンス

strip_hyphen

連続した'-(ハイフン)'(SGML注釈宣言内で禁止されている)をハイフン1つに変換。

first_n_text

テキスト,数字を引数に渡して↓

- MTEntryBody | テンプレートタグリファレンス

言語設定を日本語にしているときは、ブログ記事の本文から、指定した N 文字分だけ抜き出します。もし、言語設定を英語にしているなら、ブログ記事の本文から、N 語 (単語) 分だけ抜き出します。

注意点は、言語設定を日本語にしている環境で、本文に英語の文章を含むときです。この場合、本文から N 語分抜き出すのではなく、N 文字分抜き出します。

html_text_transform

テキストを引数に渡すと、「改行を変換」処理後のテキストが返る。

munge_comment

使われてない? っぽいのですが、第一引数にテキスト、第二引数に$blogオブジェクトを渡して、コメントHTMLを許可する云々の処理をしてくれるっぽい。URLをリンクに変換したり。

length_text

mb_strlen もしくは使えない場合 strlen の結果を返す。

- PHP: strlen - Manual

substr_text

mb_substr もしくは使えない場合 substr の結果を返す。

- PHP: substr - Manual

tag_split_delim

第一引数に区切り文字、第二引数に文字列を渡して、配列を返す(要するに、タグ付けのためのもの)。

tag_split_delim( ',', 'MT, Movable Type, PowerCMS' );

MT固有? のもの

cat_path_to_category

カテゴリのパスから、カテゴリ(Category)オブジェクトの配列を返します。第三匹数は category か、folder。

$cats = cat_path_to_category( 'category/sub_category', $blog_ctx_arg, 'category' );

- MTSubCategories | テンプレートタグリファレンス

同じ名前のカテゴリが別の階層にある場合は、親カテゴリ/サブカテゴリと記述して、異なるカテゴリを指定します。

static_path(static_file_path)

mt-staticディレクトリのパス

support_directory_url(support_directory_path)

サポートディレクトリのパス(URL)。

- SupportDirectoryPath | 環境変数リファレンス

get_thumbnail_file

サムネイルファイルを生成して、パスや情報を返す。$argsに渡せるのは、MTAssetThumbnailURLタグのモディファイアに指定できる値。

- MTAssetThumbnailURL | テンプレートタグリファレンス

width、height、scale、squareを渡せる

list( $thumb, $width, $height, $res ) = get_thumbnail_file( $asset, $blog, $args );

_strip_index

第一引数にURL、第二引数にブログ(Blog)オブジェクトを渡すと、インデックスベースネームと拡張子設定なんかを反映したURL(通常はindex.htmlを補完するような形式になる)を返します。社内で指摘が(> <。逆だった。index.htmlなんかを削除します。

- MTLink | テンプレートタグリファレンス

with_index="1" 1の値を設定した場合、出力されたURLにインデックスファイル名(通常index.html)を付与します。

↑の、逆
common_loop_vars

ブロックタグを生成するとき、$localvars を初期化する際に、ブロックタグ共通の __counter__、__odd__、__even__、__first__、__last__、__key__、__value__ を配列で返してくれます。

データベース

class MTDatabaseの利用について。データベースにアクセスしてブログ、記事、カテゴリなどを取得する方法。

データベースにアクセスする(class MTDatabase編) その1

メソッド

メソッドによってパラメタや戻り値は様々ですが、基本的に以下の認識で間違いありません。

- 単数が前提のもの、IDを渡してオブジェクトを得るもの(fetch_entryなど)の戻り値は単一のオブジェクト

- 複数の結果が返ることがあるもの(fetch_entriesなど)の戻り値はオブジェクトの配列

- その他は文字列、数字、配列等

fetch_blog(fetch_website)

すでに初期化のところで出てきました。ブログIDを渡すと、Blogオブジェクトが返ります。fetch_websiteはウェブサイト版です。ちなみに、fetch_blogにウェブサイトのIDを渡しても返ってきますが、ウェブサイトではfetch_websiteを使った方が良いでしょう(記事とページ、カテゴリやフォルダでもそうですがclass Website extends Blog、なのでカスタムフィールドとかそのあたりが異なる可能性があるので)。

parse_blog_ids

ブログIDの配列を返します。第二引数はウェブサイトを含めるかどうか。デフォルトはFALSE(含めない)です。

$blog_ids = $mt->db()->parse_blog_ids( 'children', TRUE );
var_dump( $blog_ids );
// => array(2) { [0]=> string(1) "2" [1]=> string(1) "1" }

include_exclude_blogs

これ、昔はpublic functionじゃなかった気がする(何でやねんって言った記憶がある)けど、public functionになってます。MTのPHPはSQLのWHERE文を組み立てることがよくあるんですが、このメソッドはオブジェクトのblog_id に指定する文字列を返します。

$args = array( 'include_with_website' => 1, 'include_blogs' => 'children' );
$include_exclude_blogs = $mt->db()->include_exclude_blogs( $args );
echo $include_exclude_blogs;
// => in (2,1 )

fetch_entry(fetch_page)

$entry_id = 1;
$entry = $mt->db()->fetch_entry( $entry_id );

記事(ウェブページ)のIDを渡すと、記事(ウェブページ)オブジェクトを返します。

db2ts(ts2db)

YYYYMMDDhhmmss形式と、データベースが扱うタイムスタンプ文字列(YYYY-MM-DD hh:mm:ss 等)を相互に変換します。

$entry_id = 1;
$entry = $mt->db()->fetch_entry( $entry_id );
$authored_on = $entry->authored_on;
echo $authored_on;
// => 2014-04-08 12:46:24
$authored_on = $mt->db()->db2ts( $authored_on );
echo $authored_on;
// => 20140408124624
$authored_on = $mt->db()->ts2db( $authored_on );
echo $authored_on;
// => 2014-04-08 12:46:24

resolve_url

リクエストのパスとブログIDを渡すと、該当するFileinfoレコード(mt_fileinfo)を返します。

$blog_id = 1;
$fi = $mt->db()->resolve_url( '/path/to/foo.html', $blog_id, 3 );

3つめの引数(デフォルトは3)は、テンプレートのbuild_typeで、3はダイナミックパブリッシングです。DynamicMTML等ではbuild_type が1(スタティック)なものを使うことがありますが、スタティックなファイルがPHPから見えてはまずいことがあるので(昔、そういう脆弱性が存在したとかそういう経緯でしょうw)、3がデフォルトになってます。

load_index_template

インデックステンプレートをロードします。Templateオブジェクトを返します。

$tmpl = $mt->db()->load_index_template( $ctx, 'メインページ', $blog_id );

load_special_template

テンプレートをロードします。Templateオブジェクトを返します。以下のコードは、上記のload_index_templateと同じ結果を返します。

$tmpl = $mt->db()->load_special_template( $ctx, 'メインページ', 'index', $blog_id );

fetch_templates(fetch_templatemap)

テンプレート(Tempate)、fetch_templatemapはテンプレートマップ(TemplateMap)をロードしてオブジェクトを返します。

$args = array( 'blog_id' => $blog_id, 'type' => 'Individual' );
$templates = $mt->db()->fetch_templates( $args );
$templatemaps = $mt->db()->fetch_templatemap( $args );

get_template_text

テンプレートのテキストを返します。template_typeはデフォルトは'custom'(テンプレートモジュール)。$globalをTRUE(もしくは1など)とすると、グローバルテンプレートを検索対象に含めます。

$template_text = $mt->db()->get_template_text( $ctx, 'テンプレート名', $blog_id, $template_type, $global );

fetch_config

データベースのmt_configテーブルに保存されているオブジェクトを返します(環境設定のうち、データベースに保存されている値が格納されています)。

fetch_blogs(fetch_websites)

ブログ、ウェブサイトオブジェクトの配列を返します。引数に指定条件の配列を指定できます。

$websites = $mt->db()->fetch_websites();
$args = array( 'include_with_website' => 1, 'include_blogs' => 'children' );
$blogs = $mt->db()->fetch_blogs( $args );

category_link

カテゴリIDを渡して、カテゴリーアーカイブのURLを取得します。

$category_id = 1;
$category_link = $mt->db()->category_link( $category_id );

archive_link

日付アーカイブ(年、月など)のアーカイブURLを取得します。

require_once( 'archive_lib.php' );
$at = 'Monthly';
$ar = ArchiverFactory::get_archiver( $at );
$ts = '20140401000000';
$args = array( 'blog_id' => 2, 'type' => $at );
$link_sql = $ar->get_archive_link_sql( $ts, $at, $args );
$archive_link = $mt->db()->archive_link( $ts, $at, $link_sql, $args );

entry_link

記事又はウェブページのURLを取得します。第一引数は記事のID、2つめの引数は、アーカイブタイプ(記事はIndividual、ウェブページはPage)です。デフォルトはIndividual(記事)。

$entry_link = $mt->db()->entry_link( $entry->id, 'Page' );

fetch_entries(fetch_pages)

一番利用する機会が多そうなのがこれです。引数$argsに指定する値は、基本的にはMTEntries、MTPagesテンプレートタグに指定するモディファイアだと考えてよいです(limit、offset、blog_ids、include_blogsなど)。

$args = array( 'blog_ids' => 'all' );
$entries = $mt->db()->fetch_entries( $args );
$pages = $mt->db()->fetch_pages( $args );

fetch_plugin_config(fetch_plugin_data)

基本的には、fetch_plugin_configを使う、と考えてください。プラグインデータの値がキーバリューの形で取得できます。注意点としては、config.yamlなどで指定されたプラグインの初期値がこれでは取得できない点です。DynamicMTMLではそのあたりの対策がなされているので、DynamicMTMLを利用する場合はclass MTPluginを使って実装した方が良いでしょう。

$plugin_data = $mt->db()->fetch_plugin_data( 'MultiBlog', 'configuration:blog:1' );
$plugin_config = $mt->db()->fetch_plugin_config( 'MultiBlog', 'blog:1' );

- DynamicMTML解説(1)-MTPluginクラスとconfig.php - Junnama Online

fetch_entry_tags(fetch_asset_tags)

記事、ページやアイテムの「タグ」を取得します。include_privateで、プライベートタグを含めるかどうかを指定します。

$args = array( 'entry_id' => $entry->entry_id,
'blog_id' => $blog_id,
'class' => $entry->class,
'include_private' => 1 );
$tags = $mt->db()->fetch_entry_tags( $args );

$args = array( 'asset_id' => $asset->id,
'blog_id' => $blog_id,
'include_private' => 1 );
$tags = $mt->db()->fetch_asset_tags( $args );

- プライベートタグでウェブページを部品データにする(寄稿) | MovableType.jp

fetch_category

カテゴリIDを渡して、Catgegoryオブジェクトを返します。

$category = $mt->db()->fetch_category( $category_id );

fetch_categories(fetch_folders)

カテゴリ(フォルダ)オブジェクトの配列を返します。

$args = array( 'blog_id' => $blog_id,
'class' => 'folder' );
$categories = $mt->db()->fetch_categories( $args );
$args = array( 'blog_id' => $blog_id );
$folders = $mt->db()->fetch_folders( $args );

fetch_author

IDを指定するとユーザー(Authorオブジェクト)を返します。

$author = $mt->db()->fetch_author( 1 );

fetch_author_by_name

IDではなく、ユーザー名(ログイン名)を指定してユーザーをロードします。

$author = $mt->db()->fetch_author_by_name( 'junnama' );

fetch_authors

ユーザー(Authorオブジェクト)の配列を返します。 以下の例は、ブログ(ID1)に記事投稿のあるユーザーの配列を返します。その他$argsに指定できるのは、MTAuthorsテンプレートタグに指定できるモディファイアと同じと考えて良いです。

$args = array( 'blog_id' => $blog_id, 'need_entry' => 1 );
$authors = $mt->db()->fetch_authors( $args );

- MTAuthors | テンプレートタグリファレンス

fetch_permission

パーミッション(権限=Permissionオブジェクト)の配列を返します。 以下の例では、blog_idに0を指定しているため、$author_idのユーザーのシステム権限が返ります。

$args = array( 'blog_id' => 0, 'id' => $author_id );
$perms = $mt->db()->fetch_permission( $args );
if ( !empty( $perms ) ) {
$perms = $perms[ 0 ];
}

fetch_all_roles

すべてのロール(Roleオブジェクト)の配列を返します。

$roles = $mt->db()->fetch_all_roles();

fetch_association

Aassociationオブジェクトの配列を返します。Associationとは、ブログとユーザーとロールの関連づけ情報を格納しているオブジェクトです。 ユーザー(ID1)がブログ1に対してロール「ライター」(role_idが1の時)権限を持っている時、associationsにauthor_idが1のAssociationオブジェクトが含まれます。

$args = array( 'blog_id' => 1, 'role_id' => array( 1 ) );
$associations = $mt->db()->fetch_associations( $args );

fetch_tag

IDを指定すると、Tagオブジェクトを返します。

$tag_id = 1;
$tag = $mt->db()->fetch_tag( $tag_id );

fetch_tag_by_name

IDではなく、タグ名(文字列)を渡してTagオブジェクトをロードします。

$tag = $mt->db()->fetch_tag( 'MovableType' );

fetch_scores(fetch_score, fetch_sum_scores, fetch_avg_scores)

使ったことないので、よくわかりませんw(おい) 要するに、MTEntryScore等で使う、「スコア」(MT5から実装されたぽい)をロードすすようなもの?

$scores = $mt->db()->fetch_scores( $namespace, $obj_id, $datasource );
$score = $mt->db()->fetch_score( $namespace, $obj_id, $author_id, $datasource );

cache_permalinks(cache_category_links)

Entryオブジェクトの配列を渡すと(cache_category_linksの場合はCategoryオブジェクトの配列)、permalink をキャッシュします。内部的にパフォーマンスのために使うものです(fetch_entries等の中で使われています)。成功すると、TRUEが返ります。

cache_comment_counts

Entryオブジェクトの配列を渡すと各記事のコメント数をカウントしてキャッシュします。これも、高速化のために内部的に利用されます。

blog_entry_count

その名の通り、ブログの記事数を返します。

$args = array( 'blog_id' => 1 );
$count = $mt->db()->blog_entry_count( $args );

blog_comment_count(category_comment_count)

その名の通り、ブログ(カテゴリ)のコメント数を返します。

blog_ping_count

ブログのトラックバック数を返します。トラックバックは、もういいや。

tags_entry_count

何か、動かないっぽい(おい)

(追記)バグ見つけた。mtdb.base.php の 2706行目、andの前にスペースを入れると、動きます(おい)

$where .= "and entry_status = 2 and entry_class = '$class'";
//      ↓スペース追加
$where .= " and entry_status = 2 and entry_class = '$class'";

entry_comment_count

$entry_idを渡すと、コメント数を返します。

$count = $mt->db()->entry_comment_count( $entry_id );

asset_count

blog_idとtype(アイテムの種類)を指定して、アイテム数を返します。

$args = array( 'blog_id' => $blog_id, 'type' => 'image' );
$count = $mt->db()->asset_count( $args );

author_entry_count

ユーザーの記事数を返します。

$args = array( 'blog_id' => $blog_id, 'author_id' => $author_id );
$count = $mt->db()->author_entry_count( $args );

entry_ping_count(category_ping_count)

記事ID(カテゴリID)を渡すと、トラックバック数を返します。トラックバックはもう(ry

fetch_pings

トラックバックオブジェクトの配列を返します。引数$argsに、MTPingsテンプレートタグに指定するモディファイアの内容が指定可能です。トラックバックはもう(ry

- MTPings | テンプレートタグリファレンス

fetch_assets

アイテム(Assetオブジェクト)の配列を返します。exclude_thumb 指定で、MTが生成したサムネイルを除外してロードします。

$args = array( 'blog_id' => $blog_id,
'type' => 'image',
'exclude_thumb' => 1 );
$assets = $mt->db()->fetch_assets( $args );

データベースにアクセスする(class MTDatabase編) その2

escape

文字列をエスケープします。MTのダイナミックパブリッシングでは、スタティックパブリッシング(Perl)のMT::Objectにあたるものがないので、ADODBを利用してSQLを実行するケースが多くあります(DynamicMTMLには(完全ではないですが)MT::Object互換のメソッドがあります)。

WHERE文に渡す文字列などはこのようにエスケープを行います。ユーザーが入力した文字列で処理するようなケースは稀だと思いますが、テンプレートタグから渡されるモディファイアの値などが主に対象になります。

$str = $mt->db()->escape( $str );

fetch_placements

カテゴリIDのリストを渡して、Placementレコードの配列を得ます。Placement(mt_placement)とは、記事とカテゴリ、ページとフォルダの関連づけを保存するテーブルです。id、entry_id、category_id、blog_id、is_praimary(主カテゴリかどうか)のみで構成されるシンプルな構造になっています。

$cat_ids = array( 1, 2, 3 );
$args = array( 'category_id' => $cat_ids );
fetch_placements( $args );

fetch_objecttags

Objecttag(mt_objecttag)レコードの配列を返します。Objecttagとは、記事、ページやアイテム等と「タグ」の関連づけを保存するテーブルです。

$tag_list = array( 1, 2 );
$ot = $mt->db()->fetch_objecttags( array(
'tag_id' => $tag_list,
'datasource' => 'entry',
'blog_id' => $blog_id));

fetch_comments

Comment(コメント=mt_comment)オブジェクトの配列を返します。 $argsには、以下の例のように記事IDを渡すほか、MTCommentsテンプレートタグで利用できるモディファイア(limit、offset、sort_orderなど)が指定可能です。

$args = array( 'entry_id' => $entry_id );
$comments = $mt->db()->fetch_comments( $args );

- MTComments | テンプレートタグリファレンス

fetch_comment_replies

コメントIDを指定して、そのコメントへの返信(Reply)コメントの配列を返します。$argsには sort_order が指定できますが、ソートキーは指定できません(日付順となります)。

$args = array( 'comment_id' => 1 );
$comments = $mt->db()->fetch_comment_replies( $args );

cache_categories

記事IDのリストを渡して、カテゴリ情報をキャッシュします。 他のキャッシュ系のメソッドでもそうですが(cache_permalinks等)、キャッシュが有効なのはリクエストが終了するまでの間です(Memcachedのようにリクエストをまたがったキャッシュは保持されない)。 MTのテンプレートによって、記事リストが複数の箇所に配置されていたりすると、都度SQLを発行することになるため、一度表示に使ったらキャッシュされる、というようになっています。

$mt->db()->cache_categories( array( 1, 2, 3 ) );

update_archive_link_cache

これも内部的なキャッシュです。日付アーカイブのリンク情報をキャッシュします。

$args = array( 'blog_id' => $blog_id,
'archive_type' => 'Monthly' );
$mt->db()->update_archive_link_cache( $args );

fetch_session

IDを指定して単一のSession(mt_session)オブジェクトを返します。session_kindが'CO'のセッションが対象となります。

$mt->db()->fetch_session( $session_id );

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

※このページには'CO'の説明がないですね。lib/MT/Session.pmのドキュメントによれば

Cached Object records store cache data of objects via MT::Cache::Session class. Cache of the template module is the default use of the kind.

キャッシュオブジェクト、つまり、テンプレートモジュールのキャッシュなんかの値がこれにあたります。

fetch_unexpired_session

第一引数にセッションのID(又はIDの配列)、第二引数に秒を指定して、有効なセッションレコードの配列を返します(戻り値は単一ID指定の際も配列)。fetch_sessionと同じくsession_kindが'CO'のセッションが対象となります(と、いうか、fetch_sessionの中から。fetch_unexpired_sessionをコールしている)。

$mt->db()->fetch_unexpired_session( array( $sess1, $sess2 ),
86400 );

update_session

セッションIDと値を指定して、Sessionレコードを更新します。

$mt->db()->update_session( $session_id, $value );

remove_session

セッションIDを指定して、セッションレコードを削除します。

$mt->db()->update_session( $session_id );

flush_session

session_kindが'CO'のセッションレコードをすべて削除します。このメソッドに引数はありません。

$mt->db()->flush_session();

get_latest_touch

オブジェクトの最終更新日を返します。MTの場合(に限らずですが)、オブジェクトは単一のレコードのみで構成されている訳ではないので(コメントやカテゴリなど複数のレコードの組み合わせで構成されている)、mt_touchというテーブルがあり、オブジェクトの更新時に、Touchオブジェクトのmodified_onを更新するような仕組みになっています。もちろん、オブジェクトが対応しているかどうかは実装次第です。主に、テンプレートモジュールキャッシュの更新タイミングの制御に利用されているようです。

$mt->db()->get_latest_touch( $blog_id, 'author' );

This module is used to store and retrieve the last modification timestamp for any registered object type. This is primarily utilized by the module caching layer of Movable Type, where caches can expire based on changes to entries, pages, categories, etc. Timestamps stored to the 'modified_on' column are in UTC time.

fetch_template_meta

メソッド名からすると、カステムフィールドの配列を読み込むような感じにとれなくもないですが、違うようです。MTIncludeタグの中で、テンプレートをロードするのに使われています。戻り値は、単一のTemplate(mt_template)オブジェクトです。

$blog_id = 1;
$load_type = 'identifier';
$load_name = 'main_index';
$is_global = FALSE;
$tmpl = $mt->db()->fetch_template_meta( $load_type, $load_name,
$blog_id, $is_global );

データベースにアクセスする(class BaseObject編)

引き続き、class BaseObject編(abstract class BaseObject extends ADOdb_Active_Record)。

BaseObject(mt/php/lib/class.baseobject.php)は abstract class なので、継承してサブクラスを作り、そこから利用します。お作法的には、MT::Fooオブジェクトを作った場合、 class.mt_foo.php という名前でファイルを作り、以下のように定義。

<?php
require_once("class.baseobject.php");
class Foo extends BaseObject
{
public $_table = 'mt_foo';
protected $_prefix = "foo_";
}
?>

カスタムフィールド対応の場合は、ADODB_Active_Record::ClassHasMany を追加します。

<?php
require_once("class.baseobject.php");
class Foo extends BaseObject
{
public $_table = 'mt_foo';
protected $_prefix = "foo_";
}
ADODB_Active_Record::ClassHasMany('Foo', 'mt_foo_meta','foo_meta_foo_id');
?>

Load

引数には、WHERE分か、数字(ID)を渡す。戻り値は真偽値(true or false)。ロードされたオブジェクトはそのままクラスインスタンスに代入されます。

require_once( 'class.mt_entry.php' );
$entry = new Entry;
$entry->Load( 1 );
var_dump( $entry );

上記の例では、IDを渡しているが、WHERE文を渡すこともできます。 但し、代入されるのは単一のオブジェクトになることに注意。つまり、以下のWHERE文に該当するmt_entryレコードが複数あっても、単一のオブジェクトしか取得できません。

require_once( 'class.mt_entry.php' );
$entry = new Entry;
$entry->Load( 'entry_status=2' );

オブジェクトの各カラムの値は以下のように取得(例:記事のタイトル)。

echo $entry->title;

Find

複数のオブジェクトを取得するには、LoadではなくFindを使います。以下を実行すると、$entriesにはオブジェクトの配列が格納されます。

require_once( 'class.mt_entry.php' );
$entry = new Entry;
$entries = $entry->Find( 'entry_status=2' );
var_dump( $entries );

Findの第4引数に、orderやlimitなどのオプションを配列で渡すことができます。join、distinctなどの指定も可。

require_once( 'class.mt_entry.php' );
$entry = new Entry;
$entries = $entry->Find( 'entry_status=2', FALSE, FALSE, array( 'limit' => 1 ) );
var_dump( $entries );

もうちょっと複雑な例。テーブルのJOIN。

$tag_id = 1;
$where = 'entry_status=2';
$where .= " AND objecttag_object_id = entry_id ";
$where .= " AND objecttag_tag_id = ${tag_id} ";
$extra = array( 'limit' => $lastn, 'offset' => $offset, );
$join = array();
$join[ 'mt_objecttag' ] = array( 'condition' =>
"objecttag_tag_id=${tag_id}" );
$extra[ 'join' ] = $join;
$entry = new Entry;
$entries = $entry->Find( $where, false, false, $extra );

以下のように、$mt->db()->SelectLimitでも記事のリストが得られますが、得られるのはSQL実行結果がオブジェクトではないことに注意してください。

require_once( 'class.mt_entry.php' );
$entries = $mt->db()->SelectLimit( 'select mt_entry.*
from mt_entry where entry_status=2', 10, 0 );
$count = $entries->RecordCount();
for ( $i = 0; $i < $count; $i++ ) {
$entries->Move( $i );
$_entry = $entries->FetchRow();
$entry = new Entry;
$entry->Load( $_entry[ 'entry_id' ] );
// idからオブジェクトを取得
var_dump( $entry );
}

class BaseObjectの話しと離れてしまいますが、そもそもSQL文をそのまま実行する場合は $mt->db()->execute を使います。

$mt->db()->execute( $sql );

Save( Update )

MTのダイナミックパブリッシングは表示に利用されていて管理機能はないけれども、以下のようにすればちゃんと新規記事が登録できます。

require_once( 'class.mt_entry.php' );
$entry = new Entry;
$entry->set_values( array( 'blog_id' => 1,
'author_id' => 1,
'status' => 1,
'current_revision' => 0,
'title' => 'This is it.' ) );
$entry->Save();

has_column

カラムが存在するかどうか。'field.basename' でカスタムフィールドの存在チェックも可。

require_once( 'class.mt_entry.php' );
$entry = new Entry;
$has_text = $entry->has_column( 'text' );
// true

install_meta

ちょっと長いコードになるんですけど、独自カスタムフィールドをプラグインで以下のように追加した時、

global $customfield_types;
$customfield_types[ 'book' ] = array(
'field_html' => array (
'default' => 'customfield_html_customobject',
'author' =>  'customfield_html_customobject_author',
),
'column_def' => 'vinteger_idx',
);
$customfield_types[ 'book_multi' ] = array(
'field_html' => array (
'default' => 'customfield_html_customobject_multi',
'author' =>  'customfield_html_customobject_multi_author',
),
'column_def' => 'vchar_idx',
);
$customfield_types[ 'book_group' ] = array(
'field_html' => array (
'default' => 'customfield_html_customobject',
'author' =>  'customfield_html_customobject_author',
),
'column_def' => 'vinteger_idx',
);

カスタムフィールドをBaseObject::install_metaしておかないと、$entry_meta_info = Entry::get_meta_info( 'entry' ); みたいに(記事カスタムフィールドを取得)した時に、認識されないので。つまり、カスタムフィールド(メタ情報)を動的にオブジェクトに追加するために呼ぶメソッドです。

$mt = MT::get_instance();
$ctx =& $mt->context();
require_once( 'class.mt_field.php' );
$_field = new Field();
$where = "field_customobject=1";
$customfields = $_field->Find( $where, FALSE, FALSE, array() );
foreach ( $customfields as $field ) {
$type = $field->field_type;
if ( strpos( $type, 'book' ) === 0 ) {
$col_type = $customfield_types[ $type ][ 'column_def' ];
if ( $col_type ) {
BaseObject::install_meta( $field->field_obj_type, 'field.' . $field->field_basename, $col_type );
}
}
}

プラグイン

MTのダイナミックパブリッシング用プラグインの作成方法を解説します。

ダイナミックパブリッシング用プラグインを作成する(init.Foo.php編)

すでにご紹介した通り、MTのダイナミックパブリッシングのうち、テンプレートエンジンは Smartyを継承したクラスになっています。よって、テンプレート・タグを拡張するプラグインは Smartyのプラグインのお作法に従って書くことになっています。

しかしながら、MTのダイナミックパブリッシングのプラグインはテンプレート・タグの拡張だけではありません。

ダイナミックパブリッシング初期化時に実行される init.foo.php

MTのダイナミックパブリッシングプラグインは、mt/plugins/PluginName/php/ 以下に置きます。 init.foo.phpは、ダイナミックパブリッシングの初期化時に require(include)されるファイルです。init.ではじまり、.phpで終るファイル名であれば、名前は何でも構いません。

$MT_DIR/
|__ plugins/
|__ MyPlugin/
|__ php/
|_init.foo.php

通常、MyPluginディレクトリ直下に、config.yamlや、lib/以下にPerlモジュールを配置しますが、ダイナミックパブリッシング専用であれば、それらも不要です。

init.foo.phpでは様々なことができます。例えばこのシリーズの最初の回で紹介した「生」PHPでの処理も、プラグインとして設置したinit.foo.phpの中に記述すれば、ダイナミックパブリッシングの処理(ファイルが存在しない時(DynamicMTMLではファイルが存在する時も)、mtview.phpに処理が渡され、その後require(include)される)の初期化時に処理が通ります。

一般的に、ここで行われる処理については、MultiBlogプラグインが参考になるでしょう。

$MT_DIR/
|__ plugins/
|__ MyPlugin/
|__ multiblog.pl
|__ lib/
|__ tmpl/
|__ php/
|_init.MultiBlog.php

init.MultiBlog.phpで行われている処理は、主にMTのPHPのコアのテンプレートタグ(MTEntriesなど)をマルチブログ対応のタグにオーバーライドする処理です。タグのオーバーライドは、 $ctx->add_tag、$ctx->add_container_tag など、MTViewerクラスのメソッドを使います。このあたりは、MTViewerの項を参考にしてください。

カスタムアーカイブタイプの動的登録

他に行われることとして、カスタムアーカイブの登録、独自カスタムフィールドの登録などが挙げられます。独自アーカイブタイプの登録方法については、以下のプラグインが参考なるでしょう。

- https://github.com/alfasado/mt-plugin-fiscal-yearly-archiver-php

このプラグインは、年度別アーカイブをダイナミックパブリッシング対応にするものです。但し、注意点として、カスタムアーカイブをダイナミックパブリッシング対応にするためには、Perlプラグインを合わせて作成する必要があります。

上記のプラグインは、Hirotaka Ogawaさんが作成されたPerl(スタティックパブリッシング用)の年度別アーカイブを、ダイナミックパブリッシングに対応させる用途で作成したものです。Movable Typeでは、再構築処理(Perl-静的側)によって、mt_fileinfoテーブルにレコードが生成されます。ダイナミックパブリッシングでは、この mt_fileinfo レコードをみて、テンプレートのコンテキストを組み立てるようになっていますので、カスタムアーカイブを作成するためには、Perl側のコードも書かなければならないことに注意する必要があります。

- https://github.com/ogawa/mt-plugin-FiscalYearlyArchives

カスタムフィールドの動的登録

これも、初期化時に実行される init.foo.php に記述されるのが一般的です。数値型カスタムフィールドを登録する以下のプラグインが参考になると思います。

- https://github.com/alfasado/mt-plugin-numeric-fields

init.NumericFields.php

<?php
global $customfield_types;
$customfield_types[ 'ninteger' ] = array(
'field_html' => array (
'default' => 'customfield_html_text',
'author'  => 'customfield_html_text',
),
'column_def' => 'vinteger_idx',
);
$customfield_types[ 'nfloat' ] = array(
'field_html' => array (
'default' => 'customfield_html_text',
'author'  => 'customfield_html_text',
),
'column_def' => 'vfloat_idx',
);
?>

このコードですが、ダイナミックパブリッシングにおいては殆どの場合これで十分なのですが、実際にはBaseObject::install_meta を行っておく必要があります。この処理を忘れると、MTEntriesでのカスタムフィールドでのフィルタリングができなくなりますので注意が必要です。

カスタムフィールドの初期化時の処理については、 addons/Commercial.pack/php/init.CustomFields.php がそのまま参考になるでしょう。

そして、カスタムアーカイブと同じく、PHPコードを用意しただけでは、管理画面にフィールドが現れませんから、Perl側のコードも書いてやる必要があります。そのあたりがMTのプラグイン開発で面倒なところではあるのですが、管理側は常にPerlで拡張する必要があります。

その他、init.foo.php の活用方法

init.foo.phpは、要するに初期化時に実行されるPHPプログラムですから、この一連のシリーズ? の読者の方なら他の利用方法も浮かんでいることだと思います。「生」PHPもよいですが、ドキュメントルートにPHPプログラムを置かずに拡張できるわけですから、メンテナンスやセキュリティ、分業時の役割分担的に、この仕組みを使わない手はありません。

ざっと思い浮かぶ利用方法をまとめます。

- 独自カスタムアーカイブの登録(初期化)

- 独自カスタムフィールドの登録(初期化)

- テンプレートタグの登録(オーバーライド)

- DynamicMTML的な処理の追加

ダイナミックパブリッシング用プラグインを作成する(ブロックタグ編)

今回はブロックタグ・プラグインを取り上げます。

ブロックタグ MTEntriesFromRSS を作る

せっかくなので、ある程度実用的なものを作ってみたいと思います。例えば、以下のようなケースで利用できるものです。

1. ブログをまたがって、カテゴリーで串刺しした一覧ページを作りたい

2. ページ送りに対応させたい

3. Movable Typeで管理していないRSSや別システムから生成したXMLから一覧を生成したい

1番のケースなんて、時々(要件に)出てきませんか?

準備。まずは atom.xml のテンプレートを調整する

今回は1のケース、ブログをまたがってカテゴリ名によって串刺しの一覧ページを作成するようにしてみます。

ブログテンプレート標準の、atom.xml をそのまま使います。実際に利用する時はRSSはRSSで必要でしょうから、コピーして別のテンプレートにするとよいでしょう。

- MTEntriesの include_blogs モディファイア、または blog_idsモディファイアで、対象のブログをすべて含める

- MTEntriesのlastnモディファイアを(デフォルトは15とかになっている筈)大きくして、すべてがちゃんと出力されるようにする

- 調整が終ったら、再構築する

一覧表示のためなら、summary や contentをテンプレートから削除してしまって構いません。必要なのはURL、タイトル、日付、作成者くらいでしょうか。

タグの仕様

xmlにxmlファイルのパス、もしくはURLを、limit、offset、category(カテゴリ名)を指定できるようにします。テンプレートは以下のような感じです。__first__、__last__、__counter__など、MTのお作法的に指定しておいた方が良い変数についても対応するものとします。

<mt:EntriesFromRSS xml="atom.xml" limit="$limit" offset="$offset" category="$category">
<mt:if name="__first__"><ul></mt:if>
<li><a href="<mt:var name="permalink">"><mt:var name="title"></a></li>
<mt:if name="__last__"></ul></mt:if>
</mt:EntriesFromRSS>

例:すべてのブログから、「報道発表」カテゴリに属する記事を一覧で表示する

category.php を作成し、呼び出す際には、以下のようにカテゴリ名を PATH_INFO で渡せるようにします。

/category.php/報道発表/?offset=20&limit=20

Smartyプラグイン形式のブロックタグの作成

$ctx->add_container_tag( 'entriesfromrss', '_hdlr_ entriesfromrss' );

上記の例は、動的に登録するものです。今回は、MTのプラグインの仕様に従い、Smartyプラグイン形式で作成することにします。以下のようにファイルを配置します。

$MT_DIR/
|__ plugins/
|__ EntriesFromRSS/
|__ php/
|_block.mtentriesfromrss.php

ソースは、ちょっと長いですが、せっかっくなのでごそっと貼ってみます。 以下の点をおさえておいてください。

1. 引数は、$args, $content, &$ctx, &$repeat

2. $argsには、タグに渡されるモディファイアがキーバリューの配列で格納されている

3. $repeat が TRUEの間、繰り返し呼び出される

4. ループの1回目は、isset( $content ) はFALSEである(なので、これで分岐して初期化等を行う)

5. $ctx->localize( $localvars ); でテンプレート変数の初期化

6. $ctx->restore( $localvars ); で、初期化したテンプレート変数をリストア(戻す)

(追記)関数名(function hoge)は smarty_block_MTタグ名 である必要があります。この場合は smarty_block_mtentriesfromrss 。

も一個追記。ファイル名は小文字オンリーである必要があります。これは、MT4からそうなったんですけどね(それまでは大文字混在可能だった)。

ローカル変数の初期化に、MTUtilの common_loop_vars を使っています。

<?php
require_once( 'MTUtil.php' );
function smarty_block_mtentriesfromrss ( $args, $content, &$ctx, &$repeat ) {
$localvars = common_loop_vars();
if (! isset( $content ) ) {
if ( isset( $args[ 'xml' ] ) ) {
$xml = $args[ 'xml' ];
} else {
$xml = 'atom.xml';
}
if ( isset( $args[ 'category' ] ) ) {
$current_cat = $args[ 'category' ];
}
if ( isset( $args[ 'limit' ] ) ) {
$limit = $args[ 'limit' ];
}
if ( isset( $args[ 'offset' ] ) ) {
$offset = $args[ 'offset' ];
} else {
$offset = 0;
}
$xml = file_get_contents( $xml );
$xml_obj = simplexml_load_string( $xml );
$xml_vars = get_object_vars( $xml_obj );
$entries = $xml_vars[ 'entry' ];
$entries_from_xml = array();
$_count = 0;
foreach ( $entries as $entry ) {
if ( $current_cat ) {
$categories = get_object_vars( $entry->category );
if ( isset( $categories ) ) {
foreach ( $categories as $category ) {
$cat_label = $category[ 'term' ];
if ( $cat_label == $current_cat ) {
if ( $offset <= $_count ) {
if (! $limit ) {
$entries_from_xml[] = $entry;
} else {
if ( count( $entries_from_xml ) < $limit ) {
$entries_from_xml[] = $entry;
} else {
$last = 1;
}
}
}
$_count++;
break;
}
}
}
if ( $last ) {
break;
}
} else {
if ( $limit || $offset ) {
if ( $offset <= $_count ) {
if (! $limit ) {
$entries_from_xml[] = $entry;
} else {
if ( count( $entries_from_xml ) < $limit ) {
$entries_from_xml[] = $entry;
} else {
$last = 1;
}
}
}
$_count++;
} else {
$entries_from_xml = $entries;
$last = 1;
}
if ( $last ) {
break;
}
}
}
if (! count( $entries_from_xml ) ) {
$repeat = FALSE;
return '';
}
$ctx->localize( $localvars );
$entries = $entries_from_xml;
$ctx->__stash[ 'vars' ][ '__counter__' ] = 0;
$ctx->stash( 'entries', $entries );
} else {
$entries = $ctx->stash( 'entries' );
}
$counter = $ctx->__stash[ 'vars' ][ '__counter__' ];
if ( $counter < count( $entries ) ) {
$entry = $entries[ $counter ];
$counter++;
$ctx->__stash[ 'vars' ][ '__counter__' ] = $counter;
$ctx->__stash[ 'vars' ][ '__odd__' ]     = ( $counter % 2 ) == 1;
$ctx->__stash[ 'vars' ][ '__even__' ]    = ( $counter % 2 ) == 0;
$ctx->__stash[ 'vars' ][ '__first__' ]   = $counter == 1;
$ctx->__stash[ 'vars' ][ '__last__' ]    = ( $counter == count( $entries ) );
$entry_vars = get_object_vars( $entry );
foreach ( $entry_vars as $entry_key => $entry_var ) {
if ( is_string( $entry_var ) ) {
$ctx->__stash[ 'vars' ][ $entry_key ] = $entry_var;
} else {
$entry_var = get_object_vars( $entry_var );
if ( $entry_key == 'link' ) {
$ctx->__stash[ 'vars' ][ 'permalink' ] = $entry_var[ '@attributes' ][ 'href' ];
} elseif ( $entry_key == 'author' ) {
$ctx->__stash[ 'vars' ][ 'author_name' ] = $entry_var[ 'name' ];
}
}
}
$repeat = TRUE;
} else {
$ctx->restore( $localvars );
$repeat = FALSE;
}
return $content;
}
?>

コードをもっとシンプルにしたい? であれば、XMLのテンプレの方を修正するというアプローチがとれますね。permalink とか author_name とか、オブジェクトの入れ子になっているところをルート直下に持って行ってやればいいのです。この目的なら何もRSSリーダーで読める必要ないですから。

category.php を作成する

これまでに紹介してきた、「生」PHPコードです。これまでと違うのは、$mt->init_plugins();でプラグインを初期化しているところ。これがないと、plugins/EntriesFromRSS/php/block.mtentriesfromrss.php が認識されません。

テンプレートはPHPのヒアドキュメントに入れていますが、実際はダイナミックパブリッシングのテンプレートにするとか、ちゃんとファイルにした方がいいでしょう、何かと。ヒアドキュメントでは、$を入れるとPHPの変数とみなされてしまうので、バックスラッシュでエスケープする必要があります(こんなことからも、外部の別ファイルにしたほうがいいと思います)。

<?php
$blog_id = 1;
include('/path/to/mt/php/mt.php');
$mt = MT::get_instance( $blog_id, '/path/to/mt/mt-config.cgi' );
$mt->init_plugins(); // プラグインを認識させるために必要
$ctx =& $mt->context();
$ctx->caching = FALSE;
$blog = $mt->db()->fetch_blog( $blog_id );
$ctx->stash( 'blog', $blog );
$ctx->stash( 'blog_id', $blog->id );
require_once( 'MTUtil.php' );
// /category.php/報道発表/?offset=20&limit=20 から「報道発表」を抽出する
$category_label = $_SERVER[ 'PATH_INFO' ];
if ( $category_label ) {
$category_label = ltrim( $category_label, '/' );
$category_label = preg_replace( '!(^.*?)/.*$!', "$1", $category_label );
}
// mt:varにセットする
$ctx->__stash[ 'vars' ][ 'offset' ] = $_GET[ 'offset' ];
$ctx->__stash[ 'vars' ][ 'limit' ]  = $_GET[ 'limit' ];
$ctx->__stash[ 'vars' ][ 'category' ] = $category_label;

$tmpl = <<< TMPL
<mt:EntriesFromRSS xml="atom.xml" limit="\$limit" offset="\$offset" category="\$category">
<mt:if name="__first__"><ul></mt:if>
<li><a href="<mt:var name="permalink">"><mt:var name="title"></a></li>
<mt:if name="__last__"></ul></mt:if>
</mt:EntriesFromRSS>
TMPL;
require_once( 'modifier.mteval.php' );
$contents = smarty_modifier_mteval( $tmpl, TRUE );
echo $contents;

以上です。明日、案件で使えますよね。 /category.php/報道発表/?offset=20&limit=20 ってURLはどうよ、とも思います。このあたりは、mod_rewriteを使う方法やDynamicMTMLを使う方法、前回の init.foo.php プラグインを使う方法などを組み合わせると、もう少しスマートに実装できるでしょう。

ダイナミックパブリッシング用プラグインを作成する(条件タグ、ファンクションタグ、グローバルモディファイア編)

条件タグ、ファンクションタグ、グローバルモディファイア

前回までに、init.foo.php と ブロックタグプラグイン(Smarty形式)の紹介をしました。

その他に、ブロックタグ(条件タグ)、ファンクションタグ、グローバルモディファイアプラグインがあります。

ブロックタグ(条件タグ)

条件タグは、ブロックタグでもあるので(この言い回しが良いかわからんですが)、ファイル命名規則や関数名、引数等のルールは同じです。$ctx->add_conditional_tag として紹介したものですね。

注意点としては、タグ名にifを含むこと。

正確にはMTIfからはじめることですが、途中にifがあっても条件タグとして解釈されてしまうのだ(これ、バグと言っていいと思うけど、最新版ではどうなってますかね)。つまり、mtassetexif とかいうタグ(ありそうですよね、Exif情報を出力するタグとかこういう名前にしそう)は条件タグとして解釈されてしまうのでした。これってトリビアになりませんか? (余談でした)

$MT_DIR/
|__ plugins/
|__ MyPluginName/
|__ php/
|_block.mtifmorning.php

9時台までだと、朝だよ、を返す(深夜0時台が朝かよ、とかは気にしない)。

function smarty_block_mtifmorning ( $args, $content, &$ctx, &$repeat ) {
$date = getdate();
if ( $date[ 'hours' ] < 10 ) {
return $ctx->_hdlr_if( $args, $content, $ctx, $repeat, TRUE );
} else {
return $ctx->_hdlr_if( $args, $content, $ctx, $repeat, FALSE );
}
}

テンプレートは以下。

<mt:IfMorning>
朝だよ。
<mt:else>
朝じゃないよ。
</mt:IfMorning>

ファンクションタグ

ブロックタグと比べるとシンプルです。その5で、$ctx->add_tag として紹介したものです。単に値を返せば良いので。例として、日付に関するタグを紹介します。

- ファイル名は function.mtタグ名.php。ここではfunction.mtgetdate.php

- 引数は、( $args, &$ctx )

- 関数名は、smarty_function_mtタグ名(smarty_function_mtgetdate)。

MTDateがあるじゃないか、という突っ込みは無視の方向で。

$MT_DIR/
|__ plugins/
|__ MyPluginName/
|__ php/
|_function.mtgetdate.php

function smarty_function_mtgetdate ( $args, &$ctx ) {
$name = $args[ 'name' ];
$date = getdate();
return $date[ $name ];
}

<mt:GetDate name="hours"> => 10 とか。

- PHP: getdate - Manual

ここでは、単純なファンクションタグを紹介していますが、実際は、ブロックタグで$ctxに動的に値をセットしてループで取り出すとか、そういう使い方が多くなるかと思います。

グローバルモディファイア

$ctx->add_global_filter として紹介したやつです。

- ファイル名は modifier.mtタグ名.php。ここではmodifier.split.php

- 引数は、( $text, $arg )

- 関数名は、smarty_modifier_モディファイア(smarty_modifier_split)。

例:文字列を指定した値で分割するモディファイア。

$MT_DIR/
|__ plugins/
|__ MyPluginName/
|__ php/
|_modifier.split.php

function smarty_modifier_split ( $text, $arg ) {
$chars = preg_split( '//', $text );
array_shift( $chars );
array_pop( $chars );
return implode( $arg, $chars );
}

<mt:Blogname split=",">
=> PowerCMS => P,o,w,e,r,C,M,S

DynamicMTML

Movable TypeのPHPフレームワーク、DynamicMTMLを導入すると、静的ファイルのMTタグを動的ビルドできる他、コールバックプラグインなどにより柔軟な処理がスマートに実装できます。

DynamicMTMLを利用する

DynamicMTMLは、Movable Typeのaddonで、PHPによるダイナミックパブリッシングを拡張するフレームワークです。

- https://github.com/PowerCMS/DynamicMTML

ドキュメントはかなり詳しく書いたつもりなので詳細はそちらを見ていただくとして、簡単に言うと、以下のようなことが実現できます。

- スタティックなHTMLファイルの中のMTML(MTタグ)をダイナミックに処理できる

- ダイナミック処理を前提とした様々なMTタグ(ログイン判別、UA判別)

- コールバックプラグインを作成することができる(処理の割り込み、カスタマイズ)

- MT::Object ライクなORマッパ

- class MTPluginによる、プラグイン作成支援

- PHPによるタスク、キューの実行

等々。DynamicMTMLは単なるスタティックなHTMLファイルの中のMTML実行エンジンではありません。

ブログをまたがったカテゴリ串刺しアーカイブをよりスマートに実現する

前々回紹介した「ブログをまたがったカテゴリ串刺しアーカイブ」ですが、DynamicMTMLを使うことで、よりスマートに実装できるようになります。

- 前回は、/category.php/movabletype/ のようなURLでしたが、素直に /movabletype/ 等のパスで動作させられるようになります。

- /movabletype/template/ などのようにサブカテゴリにも対応します。

- 最上位のブログ(ウェブサイト)にカテゴリが存在する必要があります。

- プラグインファイル一つに様々な処理を集約できます(複数のテンプレートタグも、コールバックプラグインもひとまとめにできます)

- テンプレートをヒアドキュメントに書くのではなく、MTのテンプレートとして管理できるようになります。テンプレートとして管理し、パブリッシュしたファイルを出力テンプレートにする、ということです。

プラグインの作成

実は、ここでやろうしていることを少し前にPowerCMSのブログに書きました。以下の記事です。

- DynamicMTMLのコールバックプラグインで擬似的なアーカイブをみせる | PowerCMS ブログ

また、DynamicMTMLの config.php 形式でのプラグイン作成方法は下記の記事で紹介しています。

- config.phpでDynamicMTMLのコールバックプラグインを作成する | PowerCMS ブログ

今回、コードは以下にUPしました。

- https://gist.github.com/junnama/11106932

構造とポイントのみ下記に書きます。

$MT_DIR/
|__ plugins/
|__ EntriesFromRSS/
|__ php/
|_config.php

$registryプロパティに配列でコールバックやタグを登録します。複数のタグや処理も一つのファイルの中にスッキリと書くことができます。

<?php
require_once( 'MTUtil.php' );
class EntriesFromRSS extends MTPlugin {
var $registry = array(
'config_settings' => array(
'RewriteArchiverRewriteTo' => array( 'default' => '/archiver.html' ),
),
'callbacks' => array(
'post_init' => 'rewrite_archiver',
),
'tags' => array(
'block' => array(
'entriesfromrss' => '_hdlr_entriesfromrss',
),
),
);
function rewrite_archiver ( $mt, &$ctx, &$args ) {
// 初期化後に割り込み処理を入れる
// URLを見て、カテゴリベースネームに合致したら$ctxをセットし、
// URLを騙して(/archiver.html)に騙して処理する
}
function _hdlr_entriesfromrss  ( $args, $content, &$ctx, &$repeat ) {
// ブロックタグの処理を書く
}
}
?>

テンプレートの準備

前々回と同じく、atom.xmlのテンプレートをべースにします。前々回と同じように、カテゴリ名でも良いのですが、今回はカテゴリのベースネームを使うことにします。

atom.xmlテンプレートの変更箇所は以下の箇所です。

<MTIgnore>記事数の変更と、対象ブログの変更</MTIgnore>
<mt:Entries blog_ids="all" lastn="1000">

<MTIgnore>カテゴリ名→カテゴリベースネームに変更</MTIgnore>
<mt:EntryCategories>
<category term="<$mt:CategoryBasename encode_xml="1"$>" scheme="http://www.sixapart.com/ns/types#category" />
</mt:EntryCategories>

表示テンプレート(インデックステンプレート)を作る

出力先 archiver.html のインデックスアーカイブを作ります(スタティック)。ポイントはMTDynamicMTMLタグです。このタグで囲まれた部分は、再構築時に処理されず、MTタグがそのまま静的ファイルの中に書き出され、DynamicMTMLによって動的に処理されます。

<MTDynamicMTML>
<h1><MTCategoryLabel escape="html"></h1>

<mt:EntriesFromRSS xml="atom.xml" category="$category" limit="$limit" offset="$offset">
<mt:if name="__first__"><ul></mt:if>
<li><a href="<mt:var name="permalink">"><mt:var name="title"></a></li>
<mt:if name="__last__"></ul></mt:if>
</mt:EntriesFromRSS>
</MTDynamicMTML>

DynaicMTMLからデータベースにアクセスする(ORマッパー)。

これまで、PHPからMTのデータベースに接続する方法について、これまでに class MTDatabase を使う方法、class BaseObject を使う方法について紹介しましたが、DynamicMTMLを利用することで、SQLを書かずにMTのデータベースオブジェクトにアクセスすることができます。

これは、MTの心臓部とも言えるPerlの MT::Object をPHP化する目的で作られた(というか、作った)もので、完全な互換性はありませんが、プラグインを書く人にとってはスタティックパブリッシング(Perl)、ダイナミックパブリッシング(PHP)の両方で同じ書き方ができるメリットがあります。

また、「SQLを文字列操作(連結とか)で組み立てるな」って話しもあって、そういう面からもこれを使うメリットがあるかと思います。

プラグインから class DynamicMTMLを取得する

方法は2つ。

global $app;

とするか、

$app = $ctx->stash( 'bootstrapper' );

これで、class DynamicMTMLが取得できます。

loadメソッド

単一オブジェクトを取得(ID指定)

$entry = $app->load( 'Entry', 1 );

IDが1の記事を読み込みます。戻り値は単一のオブジェクトです。

条件を指定して取得

$terms = array( 'blog_id' => 2,
'status' => 2 );
$extra = array( 'limit' => 10,
'offset' => 0, );
$entries = $app->load( 'Entry', $terms, $extra );
var_dump( $entries );

第一引数にモデル、第二引数にWHERE条件を配列で、第三引数に条件を指定(配列)します。戻り値はオブジェクトの配列です。

カテゴリを指定して記事を取得

$terms = array( 'blog_id' => $blog_id,
'status'  => 2 );
$extra = array( 'sort' => 'authored_on',
'direction' => 'descend',
'limit' => 10 );
$join = array( 'mt_placement', 'entry_id',
array( 'category_id' => $category_id ) );
$extra[ 'join' ] = $join;
$entries = $app->load( 'Entry', $terms, $extra );

第三引数に、'join'指定できます。

LIKE演算子を指定

$current_ts = $app->current_ts( $blog );
$current_ts = $app->db()->ts2db( $current_ts );
$ymd = preg_replace( '/^(([0-9][^0-9]*){8}).*$/', '$1', $current_ts );
// 2014-04-24
$terms = array( 'blog_id' => $blog->id,
'class'   => array( 'entry', 'page' ),
'authored_on' => array( 'like' => $ymd . '%' ),
'status'  => 2 );
$entries = $this->load( 'Entry', $terms );

公開日が本日の記事/ページオブジェクトの配列を取得します。指定できる演算子は 'like', 'not_like', 'not_null', 'not', '>', '>=', '<', '<=', '!=' です。

範囲指定(range_incl(range)もしくはstart_val)

require_once( 'MTUtil.php' );
$terms = array( 'blog_id' => $blog_id,
'authored_on' => start_end_month( 20140401 );,
'status'  => 2 );
$extra = array( 'sort' => 'authored_on',
'direction' => 'descend',
'range_incl' => array( 'authored_on' => 1 ),
// or 'start_val' => '20140401000000',
);
$entries = $this->load( 'Entry', $terms, $extra );

今月の記事の配列を返します。

あわせてMT::Objectで言うところの、get_by_key(条件指定でロード、なければオブジェクトを生成)、exists(存在チェック)が可能です。

詳細は公式ドキュメンを参照ください。

- https://github.com/PowerCMS/DynamicMTML/blob/master/README.ja.md

PHPerのためのMovable Type

Movable TypeのPHPライブラリを探る旅

コピーライト

Copyright (c) 2014 Junnama Noda All rights reserved.

発行者

Junnama Online