アルファサード株式会社 代表取締役 野田 純生のブログ


PHPerのための Movable Type 講座(その9)


公開日 : 2014-04-19 16:27:56


その9まで来ましたね。その10まであとちょっと。今回はブロックタグ・プラグインを取り上げたいと思います。(追記)ちなみに、このサンプル、ダイナミック処理ですが、DBアクセスも(初期化以外は)生じないですし結構軽いですよ。

ブロックタグ 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プラグイン形式のブロックタグの作成

ブロックタグの作成については、第5回($ctx編)で取り上げました。

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

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

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

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

  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 プラグインを使う方法などを組み合わせると、もう少しスマートに実装できるでしょう。ということで、続く。



このブログを書いている人
野田純生の写真
野田 純生 (のだ すみお)

大阪府出身。ウェブアクセシビリティエバンジェリスト。 アルファサード株式会社の創業者であり、現役のプログラマ。経営理念は「テクノロジーによって顧客とパートナーに寄り添い、ウェブを良くする」。 プロフィール詳細へ