blogスタッフブログ
HOME > スタッフブログ > CMS >プラグインによるPowerCMS Xのテンプレートタグ実装の実例

プラグインによるPowerCMS Xのテンプレートタグ実装の実例

PowerCMS X公式サイトの「プラグインによるテンプレートタグの実装」にブロックタグの作成方法が紹介されており、例ではシンプルなブロックタグの実装が紹介されています。読み進めると以下のような疑問が湧いてきました。

  • モデルのオブジェクトを取得して利用したい
  • コンテキストのデータを置き換えたい...つまり<mt:PrevObject><mt:EntryTitle></mt:PrevObject>のようにブロックタグの間でMTEntryTitleを記述できるようにしたい

そこで実装方法を調べながらプラグインを書いてみました。作成したブロックタグはMTPrevObjectで、オブジェクトの個別ページ(例えば個別の記事ページ)において公開日を基準にして今表示しているオブジェクトの一つ前のオブジェクトを表示するものです。(ちなみにMT[モデル名]Previousタグが用意されているので、実際にはプラグインを書く必要はありません。学習のお題として良いかなと思い設定したものです。)

全体を俯瞰しながら検討頂く方がわかりやすいと考え、コードに一行ずつコメントを入れる形式でご紹介することにしました。なお、サンプルコードを分かりやすくするために公開日が同一の場合の処理を省略しており、実際には上手く表示されないケースが存在することご了承ください。

サンプルコード

SamplePlugin.php

<?php
require_once(LIB_DIR . 'Prototype' . DS . 'class.PTPlugin.php');
class SamplePlugin extends PTPlugin
{
    public function __construct()
    {
        parent::__construct();
    }

    public function hdlrBlockPrevNextObject($args, $content, $ctx, &$repeat, $counter)
    {
        // アプリケーション(Prototype)の取得
        $app = $ctx->app;
        // データベースオブジェクト(PADO)の取得
        $db = $app->db;
        // 現在のモデル名を取得
        $model = $ctx->stash('current_context');
        // 現在のコンテキストの情報を変数に格納
        $localvars = [$model];
        // この関数を呼び出したタグ名を取得
        $this_tag = $args['this_tag'];

        if (!$counter) {
            // 初回のみ実行されるブロック
            // 現在のコンテキストのオブジェクトを変数に格納
            $org_object = $ctx->stash($model);
            // ワークスペースオブジェクトを変数に格納
            $workspace = $ctx->stash('workspace');

            // ここから目的のオブジェクトを取得するための条件などを設定
            // 公開日を取得する時の検索条件
            $op = $this_tag === 'prevobject' ? '<=' : '>=';
            // ソート順
            $direction = $this_tag === 'prevobject' ? 'descend' : 'ascend';
            // 検索条件
            $terms = [
                'id' => ['!=' => $ctx->vars['current_object_id']],
                'published_on' => [$op => $org_object->published_on],
                'workspace_id' => $workspace->id,
                'status' => $app->status_published($model)
            ];
            // モデルがリビジョン対応している場合は現在公開されているリビジョンを検索対象にする
            if ($org_object->has_column('rev_type')) {
                $terms['rev_type'] = 0;
            }
            // 取得数・並び順の指定
            $object_load_args = [
                'limit' => 1,
                'sort' => 'published_on',
                'direction' => $direction
            ];
            // デバッグ時はloadの前後に$db->debugを指定すると発行されたクエリが確認できる
            // $db->debug = 3;
            // データベースからデータのロード
            $objects = $db->model($model)->load($terms, $object_load_args);
            // $db->debug = 0;

            // データがなければ終わり
            if (empty($objects)) {
                return $ctx->false();
            }

            // 変数をローカル変数化
            $ctx->localize($localvars);
            // 取得済みのオブジェクト(例えば記事データ)をコンテキストに格納
            $ctx->local_params = $objects;
        }

        // 毎回実行されるブロック
        // 取得済みのオブジェクト(例えば記事データ)をコンテキストから取り出し
        $objects = $ctx->local_params;
        // 予約変数をセット(__first__, __last__, __counter__など)
        $ctx->set_loop_vars($counter, $objects);
        
        if (isset($objects[$counter])) {
            // オブジェクトがあれば現在のコンテキストにセットする
            $object = $objects[$counter];
            $ctx->stash($model, $object);
            $repeat = true;
        } else {
            // 全てのオブジェクトが処理されている場合は元のコンテキストの情報を復元
            $ctx->restore($localvars);
            $repeat = false;
        }

        return $content;
    }
}

config.json

{
    "label": "SamplePlugin",
    "id": "sampleplugin",
    "component": "SamplePlugin",
    "version": "1.0.0",
    "author": "LAB Inc.",
    "author_link": "https://lab-inc.jp/",
    "description": "Plugin description write here.",
    "tags": {
        "block": {
            "prevobject": {
                "component": "SamplePlugin",
                "method": "hdlrBlockPrevNextObject"
            }
        }
    }
}

補足説明

コンテキストのデータを置き換える

例えば記事ページに<mt:EntryTitle>と書くと現在のページの記事タイトルが表示されると思いますが、<mt:PrevObject><mt:EntryTitle></mt:PrevObject>の間ではプラグインの中で抽出した記事タイトルを表示させる必要があります。

そこで以下のようにして現在のデータを退避させ

$model = $ctx->stash('current_context');
$localvars = [$model];
$ctx->localize($localvars);

抽出したデータに置き換えます

$object = $objects[$counter];
$ctx->stash($model, $object);

ただ、このままだと</mt:PrevObject>の後ろに書いた<mt:EntryTitle>でもプラグインで取得したデータの記事タイトルが表示されてしまいます。そこでブロックタグの処理が終わった時、以下のコードで退避させているデータをコンテキストに復元します。

$ctx->restore($localvars);

データベースのデータを取り出す

データベースのデータは以下のコードで取り出せます。これを覚えるとプラグインで様々な処理が書けるようになります。(実体験です。)

$app->db->model('model_name')->load($terms, $args);

$termsでは検索条件を指定します。今回のサンプルの場合はIDが現在のIDと異なり、公開日が同じか以前のものです。下書きのデータやリビジョンのデータもあるのでそれらが含まれないような条件に加えました。

$op = $this_tag === 'prevobject' ? '<=' : '>=';
$terms = [
    'id' => ['!=' => $ctx->vars['current_object_id']],
    'published_on' => [$op => $org_object->published_on],
    'workspace_id' => $workspace->id,
    'status' => $app->status_published($model)
];
if ($org_object->has_column('rev_type')) {
    $terms['rev_type'] = 0;
}

$argsでは取得数やソート順を指定します。

$direction = $this_tag === 'prevobject' ? 'descend' : 'ascend';
$object_load_args = [
    'limit' => 1,
    'sort' => 'published_on',
    'direction' => $direction
];

ちなみに、published_onの抽出条件やdirectionの方向を変数に入れているのは、実は<mt:NextObject>も同じ関数で処理するためです。

(余談)プラグイン中でパーマリンクを取得したい

プラグインの中でオブジェクトの個別ページのパーマリンクを取得したい時は、$app->get_permalink($object)で取得できました。自分で組み立てるなどする必要がなく簡単ですね。