スタッフブログ Staff Blog

PowerCMS Xのモデルのテンプレートタグを一覧にする

目下PowerCMS Xの案件を進めていて、モデルにカラムが多くカラム名も少々長いため、テンプレートタグを書くことに疲れてきました...。「なんとか楽ができないか」と考えたのですが、モデルの情報はmt_tableテーブルに、カラムの情報はmt_columnテーブルに入っていたので「PADO : PHP Alternative Database Object」を利用してテンプレートタグを一覧にして出力しました!

単純にモデル名とカラム名を繋げたものを出力するだけでなく、テンプレートタグでは省略されるアンダースコアを除去したり、編集表示がリッチテキストの場合はconvert_breaks="auto"を付けたり、リレーションだとブロックタグにしたりと色々工夫しました。またリレーションしているモデルへのリンクを表示して、リレーション先のテンプレートタグも書きやすくしています。
テンプレートタグを一覧した画面

動作サンプルを下記に置きました。(私の研究開発用サーバーで、PowerCMS Xをインストールしたホストとは別のホストに設置しています。データは学習用に作成したものです。)  

以下のコードをサイトルートに設置して$pcmsx_dir_path = '[PowerCMS X設置ディレクトリのパス]';にすると動作します。お試し下さい。なお、使い終わった際は必ず削除するようにしてください。

ちなみに後から気付いたのですが、mt:objectcolsタグでもカラムの情報が取得できるようです。

Googleマップとスライダーを連携させる

とある案件でGoogleマップにマーカー(ピン)を立て、マーカーに関する情報をスライダーに表示させる実装を行いました。
実装した画面のキャプチャ

当初ES6でコードを書いていたのですが、ゴールデンウィークで時間があることもあり以前から関心を寄せていたTypeScriptで書くとどうなるか試してみるなどしました。TypeScriptは型定義について覚えればわりとスムーズに始められるのではないかと感じました。

本題の「Googleマップとスライダーを連携させる」ですが、APIが充実したスライダーのプログラム「Swiper」を使用することで意外と楽に実装が行えました。ただ、普段jQueryやjQueryプラグインのみ使われている方には難しいかもしれませんね。やはり「継承とプロトタイプチェーン」あたりを理解する必要があるでしょう。ただ、ES6で導入された 「クラス」を使用することで以前よりは書きやすいのではないかとも思います。(しかし、ターゲットブラウザにIEが入るとBabelでトランスパイルする必要があり、またややこしくなるのかと...。)

クラス構文を理解したら、後は必要な処理を整理してメソッドを書いていきます。今回は以下のようなメソッドを書きました。スライドが動いた場合の処理・マーカーをクリックした場合の処理を書くために「イベント」も理解する必要があるでしょう。ただ、jQueryでもよく書く処理ですね。

  • Googleマップのインスタンス化
  • Swiperのインスタンス化
    • スライドが動いた場合の処理
  • マッピングデータの取得
  • マーカーの配置
    • マーカーをクリックした場合の処理
  • マーカー画像の変更
  • Swiperスライドの作成

メソッドを書き終えたらGoogleMapのインスタンスを作成し、initメソッドを実行して表示させます。selectorjsonPathを変更することで、同じような機能を持つマップを複数配置することができます。

const schoolMap = new GoogleMap({
    selector: '#schoolmap',
    jsonPath: 'json/schools.json',
    center: {
        lat: 34.51044761493913,
        lng: 133.37627491015624
    }
});
schoolMap.init();

書店でいくつかの書籍を見ましたが、jQueryの世界からステップアップしようにもなかなか良いものが見つからないように感じました。私は今回記事に書いたような実装の仕方を以前在籍していた会社の先輩が書いたコードから学びました。(その他、オライリーの「JavaScript 第5版」を読みました。)TypeScriptで書いたために変数の型が書いてありSwiftやJava等の言語に触れた経験がない方には少し難しいかもしれませんが、JavaScriptの技術を磨いていきたいというエンジニアの方はぜひソースを眺めてみて下さい。

なお、サンプルコードに記載しているGoogleのAPIキーはリファラ制限を設定していますので、ご自身のAPIキーを作成してお試し下さい。

従来から少し変更が必要なPowerCMS Xのテンプレート記法の紹介

この春、PowerCMS Xを採用した案件のテンプレート制作に初めて携わりました。基本的にはPowerCMS 5(及びMovable Type)と同じテンプレート記法なのですが、実際に書いてみると「あれ? 意図した動きにならない...」という場面に遭遇しました。

そこで、今回はみなさまにスムーズにPowerCMS Xのテンプレートを書いて頂けるよう、私がつまづいた点などをご紹介したいと思います。

regex_replaceでの置換が上手くいかないケース

よく使うregex_replaceですが、PowerCMS 5といくつが違う点があります。

検索対象文字列・置換文字列の書き方

今まではregex_replace="検索対象文字列","置換文字列"という書き方でしたが、PowerCMS Xでは以下のように記述します。

regex_replace="'検索対象文字列','置換文字列'"

言葉で表現すると、「ダブルクォーテーションの中にシングルクォーテーションで検索対象文字列と置換文字列をくくり、検索対象文字列と置換文字列の間はカンマで区切る」です。ダブルクォーテーションの中がCSVになるイメージです。

これはテンプレートをDOMDocumentで解釈するようになったことに起因します。

検索対象文字列や置換文字列にHTMLのタグを書きたい時

例えば<mt:EntryBody regex_replace="'/</?p>/',''" />のようにして本文からp要素を削除しようとしても、テンプレートが正しく解釈されませんでした。このような時は検索対象文字列や置換対象文字列を一旦変数に入れて変数を渡すようにします。

<mt:SetVarBlock name="regexp">/<\/?p>/</mt:SetVarBlock>
<mt:EntryBody regex_replace="'$regexp',''" />

これもテンプレートをDOMDocumentで解釈するようになったことに起因します。

後方参照を使用したい時

置換文字列で後方参照を使用したのですが、意図した挙動になりませんでした。これは$1$2の記述がPowerCMS Xの変数として認識されてしまうことによるものです。後方参照を使用する場合は、\$1\$2のように記述をします。(Macだとバックスラッシュ、Windowsだと円マークです。)

ifのtest属性を使用する時

頻度としては少な目ですが、変数を2つ以上チェックする必要があり、かつifを入れ子にしたくない時、ifのtest属性を使用するかと思います。PowerCMS Xでは以下のように記述をします。

<mt:If test="($a === 1 && $b === 'foo')">

PowerCMS XはPHPベースなので、文字列の比較をする時はeqなどではなく===を使用します。また全体をかっこでくくります。

setvarにop属性はなくmathタグを使う

階層を持つモデル(例えばカテゴリ)で今何階層目かを判断するためにhierarchy_levelという変数を用意し、値を1足したり引いたりしようと計画したのですが、意図した挙動になりませんでした。setvarタグにop属性はないようです。

代わりにmathタグがありますのでこれを使用します。以下は計算式がx - 1xは変数hierarchy_level、計算結果を変数hierarchy_levelに格納する例です。つまり今までSetVarで変数hierarchy_levelop="--"したのと同じです。

<mt:math eq="x - 1" x="$hierarchy_level" setvar="hierarchy_level" />

何かと便利なifinArray

配列に目的の値が存在するか否かを確認したい場合に使用できるとても便利なタグです。例えばタグに@formが含まれているかをチェックしたい場合、以下のようなテンプレートになります。

<mt:PageTags include_private="1"><mt:SetVarBlock name="page__tags" function="push"><mt:TagName /></mt:SetVarBlock></mt:PageTags>
<mt:IfInArray name="page__tags" value="@form">
<mt:Include module="Form" />
<mt:Else>
<mt:Include module="Page Detail" />
</mt:IfInArray>

まとめ

まずはPHPベースになったこと、テンプレートがDOMDocumentで解釈されるようになったことの2点に気を付ければ良いのではと考えています。ドキュメントもありますし、PowerCMS Xをご契約頂いたお客さまにはサポートもあるそうですので、安心してPowerCMS Xのテンプレート設計に取り組んで頂けるのではないかと思います。

PowerCMSのダイナミック検索(AltSearchプラグイン)で特定の記事を除外する方法

先日、PowerCMS 5で構築を行うサイトにおいてカスタムフィールドを含めたサイト内検索を実装する必要がありました。

PowerCMS 5ではダイナミック検索(AltSearchプラグイン)を利用することでカスタムフィールドを含めた検索が可能になりますが、特定の記事を除外するにはカスタマイズを行う必要がありますので、その方法を紹介します。

カスタムフィールドの作成

今回は記事・ウェブページ両方を除外出来るようにしたかったので、記事・ウェブページにカスタムフィールドを作成します。全ブログで利用する場合はシステムのカスタムフィールドで作成すると良いでしょう。テンプレートタグは任意で構いません。

記事のカスタムフィールド

  • システムオブジェクト: 記事
  • 名前: サイト内検索から除外
  • 種別: チェックボックス
  • ベースネーム: entry_exclude_altsearch

ウェブページのカスタムフィールド

  • システムオブジェクト: ウェブページ
  • 名前: サイト内検索から除外
  • 種別: チェックボックス
  • ベースネーム: page_exclude_altsearch

プラグインの作成

ダイナミック検索ではコールバックプラグインを作るための仕組みが用意されています。そこで、コールバックプラグインを作成し、上記カスタムフィールドにチェックが入った記事・ウェブページを除外するSQLを追加します。なお、ダイナミック検索ではサーチとカウントで処理が異なるため、二つのコールバックで処理を行う必要があります。

サーチのコールバック

サーチのカスタマイズは pre_altsearch コールバックを利用します。下記PHPコードを記述したファイルを設置します。

/mt/addons/DynamicMTML.pack/php/callbacks/dynamicmtml\_pack\_pre\_altsearch.php

※callbacksディレクトリがない場合は作成してください。

function dynamicmtml_pack_pre_altsearch ( $mt, &$ctx, &$args, &$params ) {
    $sql = $params[ 'sql' ];
    $offset = $params[ 'offset' ];
    $limit = $params[ 'limit' ];
    $sort_by = $params[ 'sort_by' ];
    $sort_order = $params[ 'sort_order' ];

    $_params = array();
    $cf_sql = "SELECT entry_meta_entry_id, entry_meta_type, entry_meta_vinteger_idx ";
    $cf_sql .= "FROM mt_entry_meta ";
    $cf_sql .= "WHERE ((entry_meta_vinteger_idx = 0 AND entry_meta_type = 'field.page_exclude_altsearch') OR (entry_meta_vinteger_idx = 0 AND entry_meta_type = 'field.entry_exclude_altsearch'))";
    $match_fld = $ctx->mt->db()->Execute( $cf_sql );
    $match_cnt = $match_fld->RecordCount();
    if ( $match_cnt ) {
      $match_cnt--;
      $id_col = "entry_meta_entry_id";
      $type_col = "entry_meta_type";
      $results = array();
      for ( $i = 0; $i <= $match_cnt; $i++ ) {
        $match_fld->Move( $i );
        $row = $match_fld->FetchRow();
        $id = $row[ $id_col ];
        $type = $row[ 'entry_meta_type' ];
        $type = preg_replace( '/^field\./', '', $type );
        $col = $_params[ $type ][ 'column_def' ];
        $col = "entry_meta_${col}";
        $value = $row[ $col ];
        $results[ $id ][ $type ] = $value;
      }
    }
    foreach ( $results as $id => $field_values ) {
      $match_all = 1;
      foreach ( $_params as $key => $_values ) {
        $result = $results[ $id ][ $key ];
        if ( !isset( $result ) ) {
          $match_all = NULL;
          break;
        }
      }
      if ( $match_all ) {
        $ids[] = $id;
      }
    }
    $ids = array_unique( $ids );
    $ids = join( ',', $ids );

    $sql = preg_replace( '/AND/', "AND mt_entry.entry_id in ( ${ids} ) AND", $sql, 1 );

    $params = array( 'sql' => $sql, 'sort_by' => $sort_by, 'sort_order' => $sort_order,
                     'limit' => $limit, 'offset' => $offset );
}

カウントのコールバック

サーチ処理にだけSQLの追加を行うと、検索ヒット件数はカスタマイズを行う前の数になっています。そのためヒット件数にズレが生じて不要なページ送りが出来るなど不都合が生じます。同様にカウントの処理もカスタマイズすることでヒット件数を一致するようにします。

カウントのカスタマイズは pre_altsearch_meta コールバックを利用します。下記PHPコードを記述したファイルを設置します。

/mt/addons/DynamicMTML.pack/php/callbacks/dynamicmtml\_pack\_pre\_altsearch\_meta.php
function dynamicmtml_pack_pre_altsearch_meta ( $mt, &$ctx, &$args, &$sql ) {
    $_params = array();
    $cf_sql = "SELECT entry_meta_entry_id, entry_meta_type, entry_meta_vinteger_idx ";
    $cf_sql .= "FROM mt_entry_meta ";
    $cf_sql .= "WHERE ((entry_meta_vinteger_idx = 0 AND entry_meta_type = 'field.page_exclude_altsearch') OR (entry_meta_vinteger_idx = 0 AND entry_meta_type = 'field.entry_exclude_altsearch'))";
    $match_fld = $ctx->mt->db()->Execute( $cf_sql );
    $match_cnt = $match_fld->RecordCount();
    if ( $match_cnt ) {
      $match_cnt--;
      $id_col = "entry_meta_entry_id";
      $type_col = "entry_meta_type";
      $results = array();
      for ( $i = 0; $i <= $match_cnt; $i++ ) {
        $match_fld->Move( $i );
        $row = $match_fld->FetchRow();
        $id = $row[ $id_col ];
        $type = $row[ 'entry_meta_type' ];
        $type = preg_replace( '/^field\./', '', $type );
        $col = $_params[ $type ][ 'column_def' ];
        $col = "entry_meta_${col}";
        $value = $row[ $col ];
        $results[ $id ][ $type ] = $value;
      }
    }
    foreach ( $results as $id => $field_values ) {
      $match_all = 1;
      foreach ( $_params as $key => $_values ) {
        $result = $results[ $id ][ $key ];
        if ( !isset( $result ) ) {
          $match_all = NULL;
          break;
        }
      }
      if ( $match_all ) {
        $ids[] = $id;
      }
    }
    $ids = array_unique( $ids );
    $ids = join( ',', $ids );

    $sql = preg_replace( '/AND/', "AND entry_id in ( ${ids} ) AND", $sql, 1 );
}

まとめ

以上のプラグインを設置することでダイナミック検索から特定の記事・ウェブページを除外出来るようになります。カスタムフィールドの種別やベースネームが異なる場合は調整が必要になりますが、SQLをカスタマイズすることで標準機能では実現出来ない検索が可能になります。

参考記事

PowerCMSのフォーム確認画面に「修正する」ボタンを設置する簡単な方法

この1ヶ月、PowerCMS 5とPowerCMS Xで複雑なフォームを作っていました。その際、フォーム確認画面に「修正する」ボタンを設置する必要が出たのですが、PowerCMS 5で「修正する」ボタンを設置する方法は「ContactFormで フォームの確認画面に「修正する」リンクを追加する | PowerCMS ブログ」で紹介されております。(ちなみに私がJavaScriptを修正いたしました。)

PowerCMS Xでもきっと同じ方法だろうと考えブログ記事のJavaScriptを流用しようとしたのですが、そもそもなぜJavaScriptでなければならないのか?ということを考えはじめました。単純にbutton要素が置けたら簡単です。

JavaScriptにあるコアな動き

JavaScriptにあるコアな動きは以下の3行だと私は気付きます。つまり、修正画面の時は__modeパラメーターをviewにしてPOSTをしたいのです。

buttonElem.addEventListener('click', function () {
  inputElem.value = 'view';
  node.submit();
});

button要素ではname属性とvalue属性が設定できる

ここでbutton要素について改めて確認します。「HTML Standard 日本語訳の4.10.6 button要素」を見ると以下のように記述がありました。

  • name -- フォーム送信およびform.elements APIで使用する要素の名前
  • value -- フォーム送信に対して使用される値

type="submit"のinput要素の場合value属性がボタンのラベルになりますが(If the element has a value attribute, the button's label must be the value of that attribute; )、button要素の場合は内部にHTMLコンテンツを追加して整形しますよね。name属性値やvalue属性値は専らフォーム送信に対して使用されます。

テンプレート

結果、テンプレートはとても簡素になりました。まず、以下の記述は削除します。

<input type="hidden" name="__mode" value="<mt:if name="confirm_ok">submit<mt:else>confirm</mt:if>">

そして、修正するボタン・送信ボタンの所を以下のようにします。

<mt:if name="confirm_ok">
  <button type="submit" name="__mode" value="view">修正する</button>
  <button type="submit" name="__mode" value="submit">送信する</button>
<mt:else>
  <button type="submit" name="__mode" value="confirm">確認画面へ</button>
</mt:if>

PowerCMS 5・PowerCMS Xで意図した動きになる事を確認しました。なお、PowerCMS 5の場合、修正画面に戻った際に入力値を表示するカスタマイズは引き続き必要です。

過去に在籍した会社の採用情報に記されていたように記憶しているのですが、「HTML/CSSの深い理解」はやはり大切だと改めて感じました。

またこれは余談ですが、フォーム作成においてはテキスト・ラジオボタン等のフィールド毎(PowerCMS Xの場合「設問タイプ」として登録されています)にベースとなるテンプレートをきっちり作成しておくことが成否を分けるのではないかと感じました。設問が増えた場合でも修正・変更作業が容易になります。さらに、PowerCMS Xではquestionモデルにベースとなるテンプレートとの差分をメモ書きをするためだけの複数行テキストカラムを追加すると良いのではないかと思います。

お問い合わせ Contact

制作のご依頼、ご相談などは下記のフォームからご連絡ください。