自作プラグインの紹介ページを書いて、WordPress に貼りました。使い方の説明なので、本文にはショートコードが並びます。このページに [rapls_sitemap] を置いてください、という類の話です。プレビューを開いたら、記事の途中にサイト全体のページ一覧が出ていました。
免責事項、プライバシーポリシー、運営者情報、他のプラグインのマニュアル、ブログ記事のタイトル40本ぶん。プラグインの説明文の真ん中に、目次がまるごと挟まっています。
先に、この記事で分かったことを3つ置いておきます。ひとつ、do_shortcode() は HTML のタグを見ていません。<code> で囲んでも <pre> で囲んでも実行されます。ふたつ、よく紹介されている二重括弧のエスケープは、登録済みのショートコードにしか効きません。みっつ、角括弧そのものを実体参照で書けば、登録の有無に関係なく実行されません。以下は、この3つを1つずつ確かめていった記録です。

プレビュー画面。プラグインの説明が続いている途中から、免責事項やプライバシーポリシーを含むサイト全体のページ一覧が始まっている。説明として書いたショートコードが、命令として読まれた結果です。

do_shortcode() が本文を走査するときに見ているもの。角括弧と、登録済みの名前だけを探しています。その手前にある code や pre のタグは、判定に関わりません。
確かめた環境は、WordPress 7.0 系 / PHP 8.2 / Local by Flywheel。本番は Xserver スタンダードプランです。
記事の途中に出てきたもの
原因はすぐ分かりました。移行機能の説明として、こう書いていた箇所です。
| 1 | <li><strong>WP Sitemap Page</strong> — <code>[wp_sitemap_page]</code> ショートコードに応答します</li> |
この [wp_sitemap_page] が、説明ではなく命令として読まれていました。うちのサイトには対応するプラグインが入っているので、素直に実行されたわけです。ショートコードの説明を書いているのだから、本文にショートコードが並ぶのは当たり前です。それを WordPress に貼るとどうなるかを、書いている最中は一度も考えませんでした。
code タグは、なぜ守ってくれなかったのか
引っかかったのは、<code> タグで囲んであったのに実行された点です。コードとして表示する印なのだから、そこは除外されるものだと思い込んでいました。実際には関係ありません。do_shortcode() は本文を文字列として受け取り、登録済みのショートコード名に一致する角括弧を探して置き換えます。HTML のタグは見ていません。
思い込みで書くのも気持ちが悪いので、ローカルの WordPress で確かめました。一緒に手を動かすつもりで読んでください。demo_registered という名前でショートコードを登録して、いくつかの書き方を通してみます。
| 1 2 3 4 5 6 | add_shortcode( 'demo_registered', function () { return '<実行された>'; } ); echo do_shortcode( '<code>[demo_registered]</code>' ); echo do_shortcode( '<pre>[demo_registered]</pre>' ); |
結果です。
| 書き方 | 出力 |
|---|---|
[demo_registered] | <実行された> |
<code>[demo_registered]</code> | <code><実行された></code> |
<pre>[demo_registered]</pre> | <pre><実行された></pre> |
タグごと残して、中身だけ差し替えます。守ってくれません。<pre> でも同じでした。整形済みテキストという意味のタグですが、ショートコードの処理はその前に終わっています。処理の順番を考えれば当たり前の話で、the_content に積まれたフィルターが順に本文を触っていくだけです。当たり前なのに、コードとして表示する印を付けたのだから守られるはずだ、と勝手に期待していました。
まだ動いていなかった18個
直すついでに、ページ全体で角括弧を数えました。
| 1 2 | [rapls_sitemap 18 箇所 [wp_sitemap_page 3 箇所 |
実際に暴発したのは [wp_sitemap_page] だけです。残る18個が静かにしていた理由は、単純でした。そのプラグインを、まだこのサイトに入れていなかったからです。
自作プラグインの紹介ページなので、当然いずれ入れます。入れた瞬間に、18箇所が一斉に動きます。プラグインを有効化しただけで、無関係な固定ページの中身が変わる。原因を探す側に回ったら、まず設定を疑って、キャッシュを疑って、テーマを疑って、最後まで本文にショートコードが書いてあるには辿り着かない気がします。暴発した3個より、静かにしていた18個のほうが怖い。そう思いました。
余談ですが、この構図は前に Dropbox の更新ヘルパーで見たものと同じでした。バンドルの中の原本は新しいのに、展開された実体が古いまま残っている。あのときも、壊れているものより、壊れる準備ができたまま黙っているもののほうが厄介でした。判定の材料が手元にないと、静かなものは見つけられません。
二重括弧という近道には、条件がありました
WordPress には、ショートコードを実行させずに書くための仕組みがあります。角括弧を二重にする方法です。
| 1 | [[demo_registered]] -> [demo_registered] |
外側の括弧が外れて、中身がそのまま表示されます。読みやすいし、書くのも楽です。ただ、これには条件があります。同じ形で、登録していない名前を通すとこうなりました。
| 入力 | 登録済み | 未登録 |
|---|---|---|
[[name]] | [name] | [[name]] |
未登録だと、二重括弧のまま画面に出ます。理由は core の wp-includes/shortcodes.php にありました。
| 1 2 3 4 5 6 7 8 9 10 11 12 | function get_shortcode_regex( $tagnames = null ) { global $shortcode_tags; if ( empty( $tagnames ) ) { $tagnames = array_keys( $shortcode_tags ); } $tagregexp = implode( '|', array_map( 'preg_quote', $tagnames ) ); return '\\[' . '(\\[?)' // 1: Optional second opening bracket for escaping shortcodes: [[tag]]. . "($tagregexp)" // 2: Shortcode name. // ... |
正規表現そのものが、登録済みの名前の一覧から組み立てられています。二重括弧を外す分岐は、その名前に一致したあとの話です。知らない名前は最初から探しにいかないので、外す括弧もありません。
ここが厄介でした。プラグインの説明ページは、そのプラグインが入っていないサイトにも貼られます。移行元プラグインの名前も書きます。貼り先でどのプラグインが有効かによって、同じ本文の見え方が変わる。片方のサイトでは [wp_sitemap_page]、もう片方では [[wp_sitemap_page]]。読者の環境を、こちらは選べません。
二重括弧を勧めている記事はよく見かけますし、書き手が全部のショートコードを把握している自分のブログなら、これで足りるようです。自分は使いませんでした。条件付きで正しいものより、条件のないもののほうが、あとで考えなくて済みます。

実体参照に書き換える
結局、角括弧そのものを実体参照にしました。
| 1 | [rapls_sitemap] |
ブラウザは [rapls_sitemap] と表示します。do_shortcode() は角括弧を探すので、この文字列は目に入りません。登録済みかどうかも関係ありません。読者から見た表示は変わらないので、コードブロックからコピーして貼れば、ちゃんと動くショートコードになります。ソースが少し読みにくくなるのが唯一の難点で、そこは諦めました。21箇所を置換しています。
貼る前に確かめる
直したあと、思い込みでもう一度やらかすのが嫌だったので、確認の手順を決めました。3つあります。
原稿を do_shortcode に通して、1バイトも変わらないことを見る
| 1 2 3 4 | $src = file_get_contents( '原稿.html' ); $out = do_shortcode( $src ); var_dump( $out === $src ); // true なら、何も実行されていない |
入力と出力が完全に一致すれば、その原稿の中で動くものは何もありません。判定が真偽値ひとつで済むので、貼る前の儀式として続けやすい形になりました。
説明対象が登録された環境で回す
ここが肝でした。記事で説明しているショートコードが、そのサイトで実際に登録されている状態で実行しないと意味がありません。未登録の環境で通すと、何もしないのが当たり前なので、必ず true になります。安心して、暴発する環境に貼ることになります。
自分の場合は、対象のプラグインを有効にしたローカル環境で回しました。登録されていないものは、テスト用に add_shortcode() で仮登録してから通しています。移行元プラグインのように、自分のサイトには入れたくないものもあるので、名前だけ借りて空の関数を返す形にしました。
実体参照を戻したときに、読める形になっているかを見る
| 1 2 3 | $decoded = html_entity_decode( $src, ENT_QUOTES, 'UTF-8' ); preg_match_all( '/\[[A-Za-z_][^\]\n]*\]/', $decoded, $m ); print_r( $m[0] ); |
21個ぜんぶ [rapls_sitemap] などとして出てくれば、エスケープしすぎて表示が壊れてもいない、ということになります。逃がすほうだけ確認して、届くほうを見ていないと、今度は読者がコピーできない状態を作ります。
隣のプラグインでも起きていた
同じ書き方で、別のプラグインの紹介ページも書いていました。念のため見たら、こちらにも5箇所ありました。
| 1 2 | [rapls_passkey_login] 2 箇所 [rapls_passkey_register] 3 箇所 |
こちらのプラグインは、サイトで有効にしています。[rapls_passkey_login] は、ログアウト中の訪問者にサインインボタンを出すショートコードです。公開中の紹介ページの本文に、説明ではなく本物のログインボタンが2つ並んでいた可能性が高い。しばらく気づいていませんでした。半年です。
これも余談になりますが、気づかなかった理由はたぶん、自分がログイン状態でしか自分のサイトを見ていなかったからです。ログイン中の訪問者にはボタンが出ない作りにしてあるので、書いた本人の画面では何も起きません。作った側の目でしか見ていない、というのはこういう形で出るのだと思いました。
持ち帰るもの
do_shortcode は HTML のタグを見ない
<code> でも <pre> でも実行されます。表示上の意味と、処理の順番は別の話です。
二重括弧が効くのは登録済みのものだけ
未登録だと二重のまま画面に出ます。貼り先の環境によって見え方が変わるので、配る文章には向きません。
実体参照なら登録の有無に関係ない
角括弧を [ ] で書けば、どの環境でも実行されません。ソースは読みにくくなります。
貼る前に do_shortcode へ通す
入出力が一致するかを見ます。ただし、対象のショートコードが登録された環境で。
ショートコードを説明する記事は、その説明対象を本文に持っています。当たり前すぎて、書いている最中はまず思い出しません。この記事の角括弧も、全部実体参照で書いてあります。
それで、いま気になっているのはこの先のほうです。うちの固定ページは40枚を超えていて、その中には他の製品のマニュアルもあります。ショートコードを扱っているのは、たぶんこの2ページだけではない。あなたのサイトの本文には、まだ登録されていない名前の角括弧が、いくつ眠っているでしょうか。
関連記事
- DropboxMacUpdate だけが Intel のまま残っていた ── 壊れているものより、壊れる準備ができたまま黙っているもののほうが厄介だった、という別の記録です。
- WordPress.org に初めて出した自作プラグインが2回差し戻された話 ── プラグインを配る側の、別の躓きです。

コメント