ユーザーが入力した文章やオプション設定の値を画面に出すとき、<strong> や <a> のような「ある程度のHTML」は残したいけれど、<script> のような危険なタグは消したい、という場面があります。こうした「HTMLを許可制にして出力する」ために用意されているのが wp_kses_post() と wp_kses() です。この記事では、両者の基本的な使い方、wp_kses() で許可するタグや属性を自分で指定する方法、そして esc_html() との違いやつまずきやすい点までを、実際に動くコードで解説します。
目次
HTMLを残したいときは esc_html では困る
出力を安全にする関数としてよく使われる esc_html() は、< や > といった記号をすべて実体参照(< など)に変換します。これによりタグが「タグ」として解釈されなくなり、<script> を埋め込まれても実行されません。XSS(クロスサイトスクリプティング)対策としては確実ですが、裏を返すと <strong>太字</strong> もそのまま文字として表示されてしまい、せっかくのHTMLが無効化されます。
つまり esc_html() は「HTMLを一切残さない」ための関数です。投稿本文やプロフィール欄のように、リンクや強調といった一部のタグは活かしたいけれど危険なタグだけは取り除きたい——そんなときには esc_html() ではなく、許可したタグだけを通す wp_kses 系の関数を使います。KSES は「KSES Strips Evil Scripts」の略で、許可リストに載っていないタグや属性を取り除く仕組みです。
wp_kses_post の基本
wp_kses_post() は、引数として渡した文字列から、「投稿本文で許可されているのと同じHTML」だけを残して返す関数です。許可タグの一覧を自分で用意する必要がなく、WordPress が投稿本文に対して使っているのと同じルールが適用されます。<a>・<strong>・<em>・<ul>・<img> など一般的なタグは通り、<script> や <iframe>、onclick のようなイベント属性は取り除かれます。
// ユーザーが入力した、ある程度のHTMLを含む文字列
$user_html = '<strong>重要</strong> <a href="https://example.com">リンク</a>'
. '<script>alert("XSS");</script>';
// 投稿本文と同じ許可ルールでHTMLを絞って出力する
echo wp_kses_post( $user_html );
// 出力結果(<script> は丸ごと取り除かれる):
// <strong>重要</strong> <a href="https://example.com">リンク</a>
<strong> や <a> はそのまま残り、<script> だけが消えているのがポイントです。「投稿本文と同じくらいのHTMLを許してよい」場面であれば、許可リストを自分で書かずに済む wp_kses_post() が手軽で、まず候補になります。
wp_kses で許可するタグと属性を自分で指定する
投稿本文の許可ルールよりも、もっと狭く絞りたいこともあります。たとえば「リンクと強調だけ許可して、画像やリストは通したくない」といったケースです。このときは wp_kses() を使い、第2引数に「許可するタグと属性」をまとめた配列を渡します。
許可リストの配列は、キーがタグ名、値が「そのタグで許可する属性」の連想配列という構造です。属性の値を true にすると、その属性を許可するという意味になります。次の例では、<a>(href と title 属性)と <strong>・<em> だけを許可しています。
// 許可するタグと、その属性を定義する
$allowed_html = array(
'a' => array(
'href' => true, // href 属性を許可
'title' => true, // title 属性を許可
),
'strong' => array(), // 属性なしで <strong> を許可
'em' => array(), // 属性なしで <em> を許可
);
$user_html = '<strong>太字</strong> '
. '<a href="https://example.com" onclick="bad()">リンク</a> '
. '<img src="x.png">';
echo wp_kses( $user_html, $allowed_html );
// 出力結果:
// <strong>太字</strong> <a href="https://example.com">リンク</a>
許可していない <img> はタグごと取り除かれ、許可した <a> に付いていた onclick も、許可リストに無いため削除されます。許可した属性だけが残る点に注目してください。空配列(array())を値にすると、「そのタグは許可するが、属性は一切許可しない」という指定になります。
wp_kses_post・wp_kses・wp_kses_data の違い
KSES 系の関数はいくつかありますが、初心者がまず覚えておきたいのは次の3つです。違いは「許可リストをどこから持ってくるか」にあります。
| 関数 | 許可リストの決まり方 |
|---|---|
wp_kses_post( $string ) | 投稿本文で許可されるタグ・属性を使う。リストを自分で用意しなくてよい |
wp_kses( $string, $allowed_html ) | 第2引数で渡した許可リストを使う。許可範囲を自分で細かく決められる |
wp_kses_data( $string ) | コメントなどで許可される、投稿本文より狭い既定のリストを使う |
wp_kses_post() と wp_kses_data() は、いずれも内部で wp_kses() を呼び出し、それぞれに対応する許可リストを渡しているラッパー関数です。許可するHTMLの範囲を細かくコントロールしたいなら wp_kses()、投稿本文と同じでよいなら wp_kses_post()、というように選び分けます。wp_kses_data() は許可範囲が投稿本文より狭く、コメント向けの値などに使われます。
実践的な使いどころ
管理画面で入力したHTMLを表示する
テーマのカスタマイザーや設定画面で、ユーザーが「お知らせ文」などをHTML込みで入力できるようにしている場合、その値をテンプレートで出力するときに wp_kses_post() を通します。これで、許可したタグだけが活き、危険なタグは取り除かれた状態で表示できます。
// オプションに保存されたお知らせ文(HTMLを含む)を取得
$notice = get_option( 'my_theme_notice' );
// 投稿本文と同じ許可ルールで出力する
echo wp_kses_post( $notice );
ショートコードの出力でHTMLを絞る
ショートコードの属性などから受け取った文字列をHTMLとして出力する場面でも、許可タグを限定したいなら wp_kses() が役立ちます。次の例は、利用者が渡した文言にリンクと強調だけを許可して返すショートコードです。
function my_notice_shortcode( $atts, $content = null ) {
// 許可するタグはリンクと強調だけに絞る
$allowed_html = array(
'a' => array( 'href' => true ),
'strong' => array(),
);
return '<div class="notice">'
. wp_kses( $content, $allowed_html )
. '</div>';
}
add_shortcode( 'notice', 'my_notice_shortcode' );
許可したはずのタグが消えるとき
「残るはずのタグが消えてしまう」という相談の多くは、許可リストの書き方が原因です。wp_kses() の第2引数はキーがタグ名、値が属性の連想配列という構造で、ここがずれていると意図どおりに通りません。たとえばタグ名だけを並べた array( 'a', 'strong' ) のような書き方は誤りで、各タグに対応する属性の配列(属性が無ければ空配列)を値として持たせる必要があります。
また、タグは許可しているのに特定の属性だけが消える場合は、その属性を許可リストに書き忘れていないかを確認します。<a> を許可しても、href を true にしていなければリンク先が落ちてしまいます。許可するのは「タグ」と「そのタグの属性」の両方だと意識すると、抜けに気づきやすくなります。
サニタイズは保存時か出力時か
HTMLを絞る処理を「保存するとき」と「出力するとき」のどちらで行うか迷うことがあります。基本の考え方は、出力する直前に必ずエスケープ・サニタイズするです。データベースに入っている値が信頼できるとは限らず、別の経路で書き換えられている可能性もあるため、表示の直前に wp_kses_post() などを通すのが安全です。
そのうえで、保存時にも余計なタグを落としておくと、データベースに危険なコードが残るのを防げます。設定値の保存処理(register_setting() の sanitize_callback など)で wp_kses_post() をかけておけば、保管されるデータ自体がきれいになります。「保存時に整え、出力時にも念のため絞る」という二段構えにしておくと安心です。なお wp_kses 系はHTMLを残すための関数なので、HTMLを一切含めたくない入力(メールアドレスやタイトルなど)には sanitize_text_field() のような別の関数を使い分けます。
まとめ
wp_kses_post() と wp_kses() は、HTMLを残しつつ許可したタグ・属性だけを通すための関数です。要点を振り返ります。
esc_html()はHTMLを全て無効化するので、タグを残したいときはwp_kses系を使うwp_kses_post()は投稿本文と同じ許可ルールを使い、リストを自分で書かなくてよいwp_kses()は第2引数の許可リストで、タグと属性を自分で細かく指定できる- 許可リストはキーがタグ名、値が属性の連想配列。属性が無ければ空配列にする
- 出力する直前に必ず通し、可能なら保存時にも絞っておく
「タグを残したいなら wp_kses_post()、範囲を絞りたいなら wp_kses()」という使い分けを押さえておけば、ユーザー入力やオプション値を安全にHTML出力できます。