スタッフブログ Staff Blog

PowerCMS Xのエディタに書くコンテンツをログイン状態に応じて出し分けしたい

4月からPowerCMS X導入の案件に携わっています。色々な要望がありPHPでプラグインを書いて実装を行いました、今日はその中から「エディタに書くコンテンツをログイン状態に応じて出し分けしたい」という要望についてご紹介したいと思います。

要望の内容

記事やページ、自作のモデルにリッチテキストエディタを用意してコンテンツを書くことは多いのではないかと思います。その際、会員サイトにログインしている場合はAの内容を、そうでない場合はBの内容を出したいというものでした。ログインしている場合・していない場合に応じてカラムが用意してあればmt:ifloginを使用して分岐をすれば良いのですが、リッチテキストエディタの場合はどのようにすればよいでしょうか。

独自の要素(タグ)を作成する

どのように対応するかを検討する中で、以前Web Componentsについて研究した際に独自の要素(タグ)を作成したことを思い出しました。そこでTinyMCEでも独自の要素が使えないかを調べたところ、「custom_elements」の設定により独自の要素が利用できることが分かりました。そこでx-ifloginx-ifnotlogin要素を作成することにしました。

TinyMCEの設定

TinyMCEプラグインを有効にし、マニュアルに沿って設定を記述します。「書式」プルダウンで要素が利用できるような設定も記述します。

custom_elements: 'x-iflogin,x-ifnotlogin',
extended_valid_elements: 'x-iflogin,x-ifnotlogin',
style_formats: [
  {
    title: 'Members',
    items: [
      { title: 'iflogin', block: 'x-iflogin', wrapper: true },
      { title: 'ifnotlogin', block: 'x-ifnotlogin', wrapper: true }
    ]
  }
],
end_container_on_empty_block: true,

編集画面

編集画面にはログインしている場合のコンテンツとログインしていない場合のコンテンツをx-ifloginx-ifnotlogin要素を利用して記述しました。エディタのCSSで色分けをしたりx-ifloginの表示を追加するなどしています。また「書式」プルダウンメニューにx-ifloginx-ifnotloginが表示されており、容易に要素を設定できるように工夫しています。
画面キャプチャ:ログインしている場合のコンテンツとログインしていない場合のコンテンツを記述した編集画面

x-iflogin::before,
x-iflogin::after {
  position: relative;
  display: inline-block;
  padding: 2px;
  background-color: #de4040;
  color: #fff;
  font-size: 7px;
}
x-iflogin::before {
  content: "iflogin";
}
x-iflogin {
  display: block;
  background-color: #fff3f3;
}
x-iflogin::after {
  content: "/iflogin";
}
x-ifnotlogin::before,
x-ifnotlogin::after {
  position: relative;
  display: inline-block;
  padding: 2px;
  background-color: #6cbb26;
  color: #fff;
  font-size: 7px;
}
x-ifnotlogin::before {
  content: "ifnotlogin";
}
x-ifnotlogin {
  display: block;
  background-color: #edfce0;
}
x-ifnotlogin::after {
  content: "/ifnotlogin";
}

プラグイン

ログイン状態に応じてx-ifloginの内容を削除してx-ifnotlogin要素の内容を表示したり、その逆の処理をしたりするモディファイアのコードを記述しました。PHPなので割とスラスラ書ける印象です。

<?php
require_once LIB_DIR . 'Prototype' . DS . 'class.PTPlugin.php';

class EnhancedEditor extends PTPlugin
{
    public function __construct()
    {
        parent::__construct();
    }

    public function modifier_process_xlogin($str, $args, $ctx)
    {
        $members = $ctx->app->component('Members');
        $member = $members->member($ctx->app);

        if ($member) {
            // ログインしている場合
            // x-ifloginの内容は生き、x-ifnotloginの内容は削除
            $remove_block = 'x-ifnotlogin';
            $reserve_block = 'x-iflogin';
        } else {
            // ログインしていない場合
            // x-ifloginの内容は削除、x-ifnotloginの内容は生き
            $remove_block = 'x-iflogin';
            $reserve_block = 'x-ifnotlogin';
        }

        $doc = new DOMDocument();
        $intro = '<!DOCTYPE html><html><head><meta charset="UTF-8"><body>';
        $outro = '</body></html>';
        $doc->loadHTML($intro . $str . $outro);

        $nodes_delete = array();
        foreach ($doc->getElementsByTagName($remove_block) as $delete) {
            $nodes_delete[] = $delete;
        }
        foreach ($nodes_delete as $node) {
            $node->parentNode->removeChild($node);
        }

        $html = $doc->saveHTML($doc->documentElement);
        $html = preg_replace("/<\/?${reserve_block}>/", '', $html);
        $html = preg_replace('/<html>\n?.+\n?<body>/', '', $html);
        $html = preg_replace('/<\/body>\n?<\/html>/', '', $html);
        return $html;
    }
}

コンテンツ表示例

文章と文章の間にある枠線囲み内の文章がログイン状態に応じて変化します。

まずはログインしていない場合の表示例です。「【会員限定情報あり】会員になると会員限定の情報が閲覧できます。」と表示されます。
画面キャプチャ:ログインしていない場合のコンテンツ表示例

次にログインしている場合の表示例です。「会員のみなさまにはテンプレート設計の無料セミナーにご招待します。」と表示されます。
画面キャプチャ:ログインしている場合のコンテンツ表示例

まとめ

今回は独自の要素を作成するというアイデアをPHPを用いてプラグインを書くことにより上手く実現することができました。案件ではその他にも検索をカスタマイズしたり、管理画面をカスタマイズしたりもしました。PHPとPADOの知識を身につけることで仕事の幅が広がるなというのを実感しているこの頃です。

社員旅行へ行ってきました。

先月、社員旅行でディズニーシーへ行ってきました。

私はディズニーランドもシーも行ったことがなかったので、4月にガイドブックを買って、情報収集するところからのスタートでした。

社員旅行で行くのはおそらく最初で最後になるだろうと思い、思い切ってアンバサダーホテルに宿泊。

ディズニーのホテルに宿泊すると15分前に入園できるんですよね。
おかげでトイストーリーの11時台のファストパスを取ることができました。

ただ、梅雨の平日ということもあり、人は少ない方だったと思います。

前日の夕食はホテル内のエンパイア・グリルで七夕仕様のコース。

当日の朝食も同じホテル内のシェフ・ミッキーでビュッフェ。

シェフ・ミッキーはキャラクターが各テーブルに会いにきてくれ、サインや写真撮影をしてくれます。
パーク内でキャラクターと撮影するには長蛇の列だったので、これも予約して正解でした。

Daisy.JPG

Minnie.JPG

当日の昼食はパーク内のホテルミラコスタのレストラン ベッラヴィスタ・ラウンジで七夕のコース。

会社設立当初は「社員旅行は沖縄へ行こう!」と考えていたのに、前回の北海道に続いてなぜか違う場所を選んでしまいます。

会社負担(経費で認められる範囲内)での実施なので行ける場所は限られますが、次回はまたどこか行ったことのない場所へ行きたいな、と思っています。

参考)社員旅行費用が経費と認められる目安
・4泊5日以内の旅行であること
・従業員の過半数が参加していること
・一人あたりの旅費が10万円程度であること

設立4周年を迎えました。

本日、株式会社LAB(ラボ)は設立4周年を迎えました。

この4年間、多くの方からご支援ご協力を賜りましたことを、心より感謝申し上げます。

これからも感謝の気持ちを忘れず、お客様のお役に立てるよう
スタッフ一同取り組んでまいります。

今後とも末永くご指導お引き立てを賜りますようお願いいたします。

株式会社LAB
スタッフ一同

PowerCMS Xで検索フォームの条件指定に応じてリアルタイムに検索マッチ数を表示する

PowerCMS Xの案件において、「検索フォームで検索条件を指定していくと条件に合致する件数がいくつあるかをリアルタイムに表示する」という要件がありました。これはPowerCMS Xのダイナミック出力機能と複数の条件を指定可能なウェブサイト内検索を利用することで容易に実装が可能です。
画面キャプチャ:検索フォームと検索マッチ数の表示

テンプレート

PowerCMS XにはPowerCMS 5のようなData API機能は現在の所ありませんが、出力パスをobject_count.jsonのように拡張子をJSONにするとContent-Typeがapplication/jsonで送信されるような仕組みになっており、DataAPIのような感覚で扱うことができます。

よって、object_count.jsonのテンプレートにJSONのような文字列を出力するコードを書くことで、リクエスト内容に応じたオブジェクトの件数を返すことができるようになります。この時、mt:entriesmt:schools(私が独自に定義したモデル)等のタグを使用しても良いのですが、mt:objectloopを使用することでどのモデルにも対応したテンプレートにすることができ、object_count.jsonを汎用的に利用することが可能となります。検索条件のパラメータは自動で処理されるため、テンプレートへの記述は不要です。

<mt:var name="request._filter" setvar="model" /><mt:var name="request.workspace_id" setvar="workspace_id" />
<mt:objectloop model="$model" workspace_id="$workspace_id">
  <mt:If name="__first__"><mt:var name="object_count" setvar="result_count" /></mt:If>
</mt:objectloop>
{ "totalResult": <mt:If name="result_count"><mt:var name="result_count"><mt:Else>0</mt:If> }

※見やすいように改行・スペースを入れていますが、実際は1行で書いています。

JavaScript

フォームのselect要素やinput要素の状態が変化した時に、条件にマッチする件数を取得するgetCount関数を用意しました。

フォームの内容を$('#school_search').serialize().split('&')で取得して処理を行っているのは、PowerCMS Xの検索仕様に合わせたデータを送信するためです。select要素やinput要素には_filter_value_カラム名[]が指定してあり、これらに値が設定されている時に検索条件を指定する_filter_cond_カラム名[]_filter_and_or_[カラム名]を追加して送信する仕組みです。

const formElem = document.getElementById('school_search');
const formSlectedItems = formElem.querySelectorAll('.Search__formItem.-select');

function getCount() {
    const dataArray = $('#school_search').serialize().split('&');
    const dataArrayForPost = [];
    const existKeys = [];

    dataArray.forEach(function (data) {
        if (/^_filter_value.*\=$/.test(data)) {
            // 送信しない
        } else if (/^_filter_value.*\=.*/.test(data)) {
            const condKey = data.replace(/^_filter_value(.*)\=.*$/, '$1');
            dataArrayForPost.push(
              data,
              '_filter_cond' + condKey + '=eq'
            );
            if (existKeys.indexOf(condKey) === -1) {
              dataArrayForPost.push(
                '_filter_and_or' + condKey.replace('%5B%5D', '') + '=OR'
              );
              existKeys.push(condKey);
            }
        } else {
            dataArrayForPost.push(data);
        }
    });

    fetch('/search/object_count.json?' + dataArrayForPost.join('&'))
      .then(function (response) {
        return response.json();
      })
      .then(function (json) {
        const targetElem = document.getElementById('school_search_result_count');
        targetElem.textContent = json.totalResult;
      })
}

formSlectedItems.forEach(function (formItem) {
  formItem.addEventListener('change', getCount);
});
formElem.addEventListener('reset', function () {
  window.setTimeout(getCount, 200);
});

getCount();

余談ですが、ほぼバニラJSで書いているのですが、フォームのデータを取得する部分のみjQueryに依存しています。ここを上手く書き換えたいですが、ほとんどの構築案件でjQueryが利用されており書き換えてもあまり利がないのが現状でしょうか。

表示速度

データは学校モデルに広島県の公立学校805件を登録しています。学校種別(小・中・高)のカラムは数値(小=1・中=2・高= 3)で格納しています。また、市町村カラムは市町村コードを数値で格納していますが、この市町村コードはデータ登録時に市町村モデルの内容をダイアログ表示し該当する市町村を選択するようになっています。よって検索時は市町村名で市町村モデル検索して該当するIDを取得し、さらに学校モデルの市町村カラムを検索するようになります。サーバーはさくらのVPSにnginx + php-fpmで構築しています。

上記のような条件で検索条件を変化させてリクエストに要した時間を確認したところTTFB(接続が確立した後最初の1バイトが到着するまでの時間)は200ms〜300msとなっており、ストレスを感じることなく検索マッチ数の更新ができています。

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タグでもカラムの情報が取得できるようです。

お問い合わせ Contact

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