PowerCMS™

12月12日東京、12月19日大阪にて PowerCMS X 製品発表会を開催いたします。大阪会場はお席に余裕がございます。

PowerCMS ブログ

ホーム > PowerCMS ブログ

『エスケープ処理を行うモディファイアが変換対象としている文字の一覧はありますか?』という趣旨のお問い合わせを弊社製品サポート宛てにいただきましたので、PowerCMS で利用可能なエスケープ処理を施すモディファイアの置換対象となる文字をご紹介いたします。

※ 各モディファイアの使い方につきましては、それぞれのリファレンスを参照してください。

encode_html
1 を設定すると、テンプレートタグの値が HTML エンコードされます。
encode_json
テンプレートタグの出力結果を JSON として扱えるようにエスケープします。
encode_js
1 に設定すると、テンプレートタグの値が javascript の文字列の値として使えるようにエンコードします。
encode_php
PHP コード内でデータを使えるようにテンプレートタグの出力をエンコードします。
encode_url
1 を設定すると、URL での利用に合わせたフォーマットに、テンプレートタグの値をエンコードします。アルファベット以外の文字を 16 進数ベースのものに変換します。
encode_xml
1 に設定すると、XMLフォーマットに適した形式にエンコードします。データがHTML要素やタグを含んでいる場合、CDATAブロックで囲んで出力します。

encode_html モディファイアによる変換

環境変数 NoHTMLEntities の値によって動作がかわります。

NoHTMLEntities が 0 の場合 (初期値)

encode_html によるエスケープ対象の一部
対象文字 変換後
< &lt;
> &gt;
& &amp;
" &quot;
' &#039;

& は実体参照の場合には変換対象外となります。

NoHTMLEntities が 1 の場合

HTML::Entities モジュールの encode_entities メソッドを利用して変換を行います。encode_entities メソッドの詳細についてはリファレンスを参照してください。

encode_json モディファイアによる変換

JSON モジュールの to_json メソッドを利用して変換を行います。to_json メソッドの詳細についてはリファレンスを参照してください。

encode_js モディファイア モディファイアによる変換

encode_js によるエスケープ対象
対象文字 変換後
" \"
\ \\
> \>
< \<
script s\cript
</ <\/
' \'
改行 \n
キャリッジリターン \r
タブ \t
Null文字 \0
改ページ \f

encode_php モディファイアによる変換

encode_php の仕様につきましてはリファレンスを参照してください。

encode_php におけるエスケープ対象の一部
対象文字 変換後
' \'
\ \\
$ \$
改行 \n
キャリッジリターン \r
タブ \t

encode_url モディファイアによる変換

文字コードを16進数に変換した値にゼロパディングを施して先頭に % を付与します。

encode_url におけるエスケープ対象の一部
対象文字 変換後
< %3C
> %3E
' %27
" %22
& %26
/ %2F
\ %5C
$ %24
? %3F

encode_xml モディファイアによる変換

環境変数 NoCDATA の値によって動作がかわります。

encode_xml によるエスケープ対象の一部
対象文字 変換後
※HTML・テンプレートタグ※ <![CDATA[※HTML・テンプレートタグ※]]>
カテゴリー
テンプレート作成Tips

MySQL サーバーの root パスワードがわからない! もうパスワードを再設定するしかない。

パスワードを再設定するには MySQL サーバーを再起動する必要があります。 でも、PowerCMS でサイト内検索機能や会員サイト機能、DynamicMTML などの動的な機能を利用している場合は、MySQL サーバーを停止したり再起動したりすれば、同時にそれらの動的機能が機能しなくなり、障害となります。 今回は、一時的にメンテナンス表示に切り替えてから、MySQL サーバーを再起動して、できるだけ短時間でメンテナンス表示を解除する手順を考えます。 前提は次の通りで、これらの前提から外れる部分は手直しや工夫が必要です。

  1. RHEL 7 や CentOS 7、Amazon Linux 2 などの Systemd で管理する Red Hat 系の Linux OS。
  2. root ユーザーによるシェル操作。
  3. MySQL 5.7.6 以降。
  4. MySQL サーバー (mysqld) が同居している。
  5. Web サーバーとして rewrite モジュールが有効な Apache HTTP サーバー 2.4 (httpd)。
  6. PowerCMS で管理しているすべてのサイトのバーチャルホストは /var/www/example.jp/html のように、/var/www 配下にまとまっている。
  7. DynamicMTML を使用している。
  8. PSGI や CGI (mt-*.cgi) は非公開。
  9. CMS 管理画面 (mt.cgi) は関係者にメンテナンスを周知して利用を控えてもらうか、httpd 設定でアクセスを拒否しておく。

メンテナンス設定の準備

まず、メンテナンス表示用の HTML を用意します。 既存サイトのデザインを継承し、リッチで違和感のないメンテナンスページを用意してもよいですが、CMS の管理下にあるバーチャルホストの数が多いと、それぞれの HTML ファイルを作成して配置したり、事前確認などもなかなか大変です。 今回の再起動の所要時間は数秒から長くても数十秒ほどの想定なので、扱いやすい簡易な1行 HTML の、簡素なメンテナンス表示で済ませます。 httpd の ErrorDocument ディレクティブを、次のような感じで用意します。

ErrorDocument ディレクティブの下書き
ErrorDocument 503 "<!DOCTYPE html><meta charset=UTF-8><title>一時メンテナンス中</title><p>メンテナンスのため12時34分までの予定でご利用いただけません。ご迷惑をおかけいたしますが、ご理解の程よろしくお願い申し上げます。"

しかし、httpd 2.4 では ErrorDocument ディレクティブに HTML を記述した場合、文字エンコーディングが ISO-8859-1 に強制されますので、このように ISO-8859-1 の範囲外の日本語などを記述するとメンテナンスページが文字化けしてしまいます。 httpd の環境変数「suppress-error-charsetを設定するとこれを回避できますが、デメリットがあったり、httpd の再起動が必要だったりして面倒で、目標とする短時間メンテナンスを阻害するので、次のように ISO-8859-1 のまま文字参照でマルチバイト文字を記述します。 そして、rewrite モジュールで HTTP ステータス503のエラーページに書き換え、機械向けに Retry-After ヘッダーを追加します。

メンテナンス表示用に追加する httpd 設定
ErrorDocument 503 "<!DOCTYPE html><meta charset=ISO-8859-1><title>&#x4E00;&#x6642;&#x30E1;&#x30F3;&#x30C6;&#x30CA;&#x30F3;&#x30B9;&#x4E2D;</title><p>&#x30E1;&#x30F3;&#x30C6;&#x30CA;&#x30F3;&#x30B9;&#x306E;&#x305F;&#x3081;12&#x6642;34&#x5206;&#x307E;&#x3067;&#x306E;&#x4E88;&#x5B9A;&#x3067;&#x3054;&#x5229;&#x7528;&#x3044;&#x305F;&#x3060;&#x3051;&#x307E;&#x305B;&#x3093;&#x3002;&#x3054;&#x8FF7;&#x60D1;&#x3092;&#x304A;&#x304B;&#x3051;&#x3044;&#x305F;&#x3057;&#x307E;&#x3059;&#x304C;&#x3001;&#x3054;&#x7406;&#x89E3;&#x306E;&#x7A0B;&#x3088;&#x308D;&#x3057;&#x304F;&#x304A;&#x9858;&#x3044;&#x7533;&#x3057;&#x4E0A;&#x3052;&#x307E;&#x3059;&#x3002;"
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !\.(png|jpe?g|gif|css|js(on)?|xml|atom)$ [NC]
RewriteRule ^ - [R=503,L]
Header always set Retry-After "Thu, 15 Nov 2018 03:34:00 GMT"

実行

これを /var/www 配下にある分散設定ファイル (いわゆる .htaccess) に対して一斉に追記してメンテナンス表示に切り替えます。 事前に分散設定ファイルをコピーしてバックアップしておき、設定を追記、MySQL サーバーを再起動して root パスワードを再設定、分散設定ファイルをバックアップから復旧して完了です。 この例では「New-Password-0123」というパスワードを再設定しているので、このあと落ち着いたら root ユーザーで接続して、必ずパスワードを更新し直してください。

Systemd の場合 (RHEL 7、Oracle Linux 7、CentOS 7、Amazon Linux 2)
[root]# _YMDHMS=$(date +%Y%m%d) &&
find /var/www -name .htaccess -type f -exec /bin/cp -av {}{,~$_YMDHMS} \; &&
find /var/www -name .htaccess -type f -exec sed -i '1s/^/ErrorDocument 503 "<!DOCTYPE html><meta charset=ISO-8859-1><title>\&#x4E00;\&#x6642;\&#x30E1;\&#x30F3;\&#x30C6;\&#x30CA;\&#x30F3;\&#x30B9;\&#x4E2D;<\/title><p>\&#x30E1;\&#x30F3;\&#x30C6;\&#x30CA;\&#x30F3;\&#x30B9;\&#x306E;\&#x305F;\&#x3081;12\&#x6642;34\&#x5206;\&#x307E;\&#x3067;\&#x306E;\&#x4E88;\&#x5B9A;\&#x3067;\&#x3054;\&#x5229;\&#x7528;\&#x3044;\&#x305F;\&#x3060;\&#x3051;\&#x307E;\&#x305B;\&#x3093;\&#x3002;\&#x3054;\&#x8FF7;\&#x60D1;\&#x3092;\&#x304A;\&#x304B;\&#x3051;\&#x3044;\&#x305F;\&#x3057;\&#x307E;\&#x3059;\&#x304C;\&#x3001;\&#x3054;\&#x7406;\&#x89E3;\&#x306E;\&#x7A0B;\&#x3088;\&#x308D;\&#x3057;\&#x304F;\&#x304A;\&#x9858;\&#x3044;\&#x7533;\&#x3057;\&#x4E0A;\&#x3052;\&#x307E;\&#x3059;\&#x3002;"\nRewriteEngine On\nRewriteCond %{REQUEST_FILENAME} !\\.(gif|png|jpe?g|css|js|xml)$ [NC]\nRewriteRule ^ - [R=503,L]\nHeader always set Retry-After "Thu, 15 Nov 2018 03:34:00 GMT"\n/' {} \; &&
_MYSQLD_INIT_FILE=/var/tmp/mysqld-init.sql &&
echo "ALTER USER 'root'@'localhost' IDENTIFIED BY 'New-Password-0123';" > $_MYSQLD_INIT_FILE &&
systemctl set-environment MYSQLD_OPTS="--init-file=$_MYSQLD_INIT_FILE" &&
systemctl restart mysqld.service &&
find /var/www -name .htaccess~$_YMDHMS -exec bash -c '/bin/mv -v "$1" "${1%\~'$_YMDHMS'}"' _ {} \; &&
systemctl unset-environment MYSQLD_OPTS &&
/bin/rm $_MYSQLD_INIT_FILE

起動管理が Upstart の場合 (RHEL 6、CentOS 6、Amazon Linux) は一時的に /etc/rc.d/init.d/mysqld を書き換えてから systemctl ではなく service を使うなど、違いがあります。 また、MySQL サーバーのバージョン (5.1や5.5、5.6、5.7.5 未満) によっても、user テーブルのカラム構造や利用可能な関数など、再設定の方法にいくつかパターンがあります。

メンテナンススクリプト作成器

文字の変換や日時の RFC 1123 変換など、それなりに面倒なので簡易な作成器を用意しました。手直しして十分に予行と検証をしてご利用ください。

<title>
メンテナンス文章
終了予定日時
MySQL root パスワード
シェルスクリプト
[root]# _

さいごに

MySQL サーバーで管理しているデータベースの規模や my.cnf の設定、サーバーの性能などによりますが、おそらく再起動は数秒で終わるはずなので、一瞬のダウンがあっても許容できる場合は、メンテナンス表示せず再起動という判断もあるかと思います。 本当に瞬断も許されない場合は「$datadir/mysql/user.* を差し替えて SIGHUP を送る」という荒業でも root を復旧できますが、いずれにしても失敗しないよう複製環境を用意して、十分に予行することをお勧めします。

さあ帰ろう。

カテゴリー
技術情報

PowerCMS をカスタマイズして何らかのデータを管理できるようにする場合、記事に関連付けたオブジェクトを作成することがあると思います。そのような場合、そのオブジェクトの情報が記事一覧に表示されるとより便利になります。ここではそのやり方をご紹介します。

表示するオブジェクトとして、以下のような EntryChild クラスと、記事側にその ID を格納する entry_child_id というものがあるとします。EntryChild クラスの「name」プロパティを記事一覧に表示させたいと思います。

object_types:
    entry_child: EntryChild::EntryChild
    entry:
        entry_child_id: integer not null
package EntryChild::EntryChild;
use strict;
use base qw( MT::Object );

__PACKAGE__->install_properties( {
    column_defs => {
        'id' => 'integer not null auto_increment',
        'name' => {
                'label' => 'Name1',
                'type' => 'string',
                'size' => 255,
        },
    },
    datasource => 'entry_child',
    primary_key => 'id',
} );

1;

list_properties の設定

値の表示、ソート、フィルタのそれぞれで独自の処理が必要になるため、html、sort、termsのハンドラを実装する必要があります。したがって list_properties の設定は以下のようになります。

list_properties:
    entry:
        entry_child_name:
            label: Entry child name
            order: 1000
            display: force
            base: __virtual.string
            html: $entrychild::EntryChild::Plugin::list_prop_html
            sort: $entrychild::EntryChild::Plugin::list_prop_sort
            terms: $entrychild::EntryChild::Plugin::list_prop_terms

html ハンドラ

まず、html ハンドラは以下のように単純に ID に該当するオブジェクトを取得して name プロパティを返すだけで実装できます。ただ、この方法だとリストの行数だけクエリを発行してしまうため、bulk_html を使用して一括でオブジェクトを取得するようにすることで高速化することができます。

sub list_prop_html {
    my ($prop, $entry) = @_;

    my $child = MT->model('entry_child')->load({ id => $entry->entry_child_id });

    return $child ? $child->name : '';
}

sort ハンドラ

sort ハンドラの実装にはテーブルの JOIN が必要になります。また、INNER JOIN では子オブジェクトを持たない記事がリストに表示されなくなってしまうため、LEFT OUTER JOIN で行う必要があります。JOIN の指定は MT::Objectの join_on( JOIN_COLUMN, $terms, $args ) メソッドで生成することができ、今回使用する JOIN の指定は以下のコードで生成することができます。

MT->model('entry_child')->join_on(
    undef, undef,
    {
        type => 'LEFT OUTER',
        condition => { 'id' => \'= entry_entry_child_id' }
    },
);

JOIN の種類は引数 $args の type キーで指定することができます。ここに 「LEFT OUTER」を指定することで LEFT OUTER JOIN を行うことができます。join_on() はテーブルの結合に使用するカラムとして親テーブルの主キーと子テーブルの JOIN_COLUMN を使用することを想定していますが、今回は逆の関係になっている引数 $args の condition キーでJOINの条件を指定しています。ハッシュのキーが子テーブル側のカラムになります。値の方は絞り込み使われる値となりますが、スカラのリファレンスになっている場合は、その値がそのまま SQL に組み込まれることを利用して親テーブルのカラムを指定しています。

JOIN の情報は sort と terms のハンドラの引数 $args に設定しますが、これらのハンドラは同時に呼ばれる可能性があるため、各ハンドラで個別に設定すると重複してしまう可能性があります。そのため、$args に JOIN の情報が無かったら追加してそれを返す関数 _join_entry_child() を作成しておきます。

sub _join_entry_child {
    my ($args) = @_;
    my $app = MT->instance;

    my $class = $app->model('entry_child');

    my ($join) = grep $_->[0] eq $class, @{$args->{joins}};
    unless ($join) {
        $join = $class->join_on(
            undef, undef,
            {
                type => 'LEFT OUTER',
                condition => { 'id' => \'= entry_entry_child_id' }
            },
        );
        $args->{joins} ||= [];
        push(@{$args->{joins}}, $join);
    }
    return $join;
}

以上を踏まえ、sort ハンドラの実装は以下のようになります。

sub list_prop_sort {
    my ($prop, $terms, $args) = @_;
    my $join = _join_entry_child($args);

    $join->[3]->{sort} = 'name';
    $join->[3]->{direction} = $args->{direction};

    delete $args->{sort};
    delete $args->{direction};

    return;
}

まず _join_entry_child() で JOIN の情報を追加します。変数 $join は join_on() の戻り値で [ $class, JOIN_COLUMN, $terms, $args ] という配列のリファレンスになっています。JOIN を用いたソートの指定は $args に指定するため4番目の要素にそれを追加します。通常の場合にソートの指定を行う引数 $args にはデフォルトのソート情報が設定されているため、それを削除しておく必要があります。

terms ハンドラ

同様に terms ハンドラの実装は以下のようになります。本来であれば「を含む」「を含まない」などの指定に対応する必要がありますが、ここではその実装を省いています。

sub list_prop_terms {
   my ($prop, $args, $db_terms, $db_args) = @_;

    my $value = $args->{'string'};
    return unless $value;

    my $join = _join_entry_child($db_args);

    $join->[2] ||= [];
    push(@{$join->[2]}, { 'name' => { like => "%$value%" } });
}

sort ハンドラと同様に _join_entry_child() で JOIN の情報を追加します。JOIN を用いた検索条件の指定は変数 $join の $terms に指定するため、3番目の要素にそれを追加します。

以上で、記事一覧に EntryChild オブジェクトの name プロパティのカラムが追加され、ソートやフィルタが使用できる状態になります。

カテゴリー
プラグイン技術情報
投稿者
Sakai Ryuji