PowerCMS Xでショートコードを利用できるようにしてみる

公開

PowerCMS X Advent Calendar 2024 12日目で「お悩み解決チャレンジ ページの一部を自動で更新させたい | PowerCMS X ブログ」という記事が出ました。「ページの途中でページの一部を自動で更新させたい(何かのリストを出したい)」と思うことは頻度低めなものの確かにあります。

いわゆるpagebodypagemoreを使うとか、タグを書いて_evalするとかで解決するのですが、WordPressのショートコードのような機能があればと思うこともあり、試作してみました。

  • 「お悩み解決チャレンジ」の記事は公開前に社内でやりとりをしていたようですが、風邪でダウンしかかっていたため僕は確認できていません
  • 風邪の回復途上でリハビリを兼ねて書いているので、違う事があれば後日直します…

モデル設計

ショートコードとそれに対応するテンプレートを記述できるようにしました。モデルを限定可能にするか否かはどちらでも良いように思っています。
画面キャプチャ:ショートコードモデルの編集画面

記事やページの本文

記事やページの本文で記事一覧を表示したいところに[mt:entrylist]と記述します。
画面キャプチャ:記事モデルの編集画面。文章の間にショートコードを書いている。

先のモデル編集画面でentrylistと書いたので[mt:entrylist]となっていますが、日本語で[mt:記事リスト]としても構いません。mt:を付けるかどうかもいわゆる決めの問題です。

テンプレート

テンプレートでは本文を出力する<mt:entrytext />にグローバルモディファイア_evalを追加しておきます。パブリッシュ直前にプラグインで本文にテンプレートタグを差し込むため、_evalが必要なことは変わりません。

プラグインの作成

記事やページの本文に記述したショートコードをパブリッシュまで(厳密に言えばオブジェクトをロードしてテンプレートをビルドするまでの間)にテンプレートタグに置き変える必要があります。PowerCMS Xのコードを追ったのですが、オブジェクトを保存した場合はデータを保存するために既にオブジェクトを作成している状態なのでデータベースからはロードしていないようでした。ポップアップウインドウの再構築時はオブジェクトをまとめて取得して1オブジェクトずつパブリッシュしていたような…。

というわけで、パブリッシュ直前のpre_publishコールバックで置換処理をするようにしました。ショートコードオブジェクトを取得して置換しているだけなので、特に難しいことはしていません。

public function apply_shortcode_template(&$cb, $app)
{
    // パブリッシュ対象オブジェクトを取得
    $obj = $app->ctx->stash('current_object');

    // オブジェクトのモデルのIDを調べる
    $table = $app->db->model('table')->get_by_key(['name' => $obj->_model], [], 'id');
    if (!$table->id) return true;

    // 指定されたモデルに紐付くショートコードオブジェクトIDを見つける
    $relation_objs = $app->db->model('relation')->load([
        'name' => 'model',
        'from_obj' => 'shortcode',
        'to_obj' => 'table',
        'to_id' => $table->id,
    ]);
    if (!count($relation_objs)) return true;
    $short_code_obj_ids = array_map(fn($relation_obj) => $relation_obj->from_id, $relation_objs);

    // ショートコードオブジェクトを取得してテンプレートに置換する
    $text = $obj->text;
    $short_code_objs = $app->db->model('shortcode')->load(['id' => ['IN' => $short_code_obj_ids]]);
    foreach ($short_code_objs as $short_code_obj) {
        $text = str_replace("<p>[mt:{$short_code_obj->label}]</p>", $short_code_obj->template, $text);
    }
    $obj->text($text);

    return true;
}

表示確認

ショートコード部分が記事リストになり、2つの記事タイトルが表示されました。
画面キャプチャ:記事を表示した画面。

ライブプレビューはどうかな…と思ったのですが、意外にも対応できているようで、2025年1月1日に公開予約を指定している「未来の記事」が表示されています。
画面キャプチャ:ライブプレビュー設定をして記事を表示した画面。

あとはpre_previewコールバックでプレビュー対応するのも必要かなと考えています。

まとめ

少しの手間でショートコード機能を実現することができました。「ちょっと分かりづらいコードが本文にありますが、記事リストに置き換わるのでスルーしてくださいね」という合意さえ取れると、オブジェクトの編集画面で誤ってテンプレートタグを壊してしまう危険性がないので便利ではないかなと考えています。

このケースでは1時間ぐらいで実現できましたので、何かありましたらお気軽にエンジニアにご相談ください。