テーマやプラグインを書くときの決まり事——出力する値は必ずエスケープする。理由は、投稿やフォームから入った文字列に<script>が混ざっていた場合に、そのまま実行されてしまうのを防ぐためです。
覚えるのは「どこに出すか」で関数を選ぶというルールだけです。
出力する場所ごとの正解
| 出す場所 | 使う関数 | 例 |
|---|---|---|
| 画面のテキスト | esc_html() | <p><?php echo esc_html($name); ?></p> |
| タグの属性値 | esc_attr() | <img alt="<?php echo esc_attr($alt); ?>"> |
| href・srcなどのURL | esc_url() | <a href="<?php echo esc_url($url); ?>"> |
| textareaの中身 | esc_textarea() | <textarea><?php echo esc_textarea($v); ?></textarea> |
| JavaScriptの文字列 | esc_js() | インライン用(できればwp_localize_script) |
| HTMLを保ちたい値 | wp_kses_post() | リッチテキスト・投稿本文相当 |
<?php $img = get_field('photo'); ?>
<a href="<?php echo esc_url(get_permalink()); ?>" class="card">
<img src="<?php echo esc_url($img['url']); ?>" alt="<?php echo esc_attr($img['alt']); ?>">
<h3><?php echo esc_html(get_the_title()); ?></h3>
</a>WordPress関数のエスケープ事情
「関数で出しているから安全」とは限りません。エスケープするかどうかは関数ごとに違います。
| 関数 | 扱い |
|---|---|
the_title() | 基本的に安全に出力される |
the_permalink() | URLとして安全に出力される |
the_content() | 投稿本文としてフィルタを通る(HTML可) |
the_field()(ACF/SCF) | エスケープしない——値をそのまま出す |
get_〇〇()系 | 値を返すだけ——自分でエスケープする |
get_で始まる関数は「値を返す」ので、echoするときは自分でエスケープが必要です。この一点を押さえるだけで、書き方が安定します。
入力は「洗ってから保存」
出力側のエスケープと対になるのが、保存前のサニタイズです。
| 用途 | 関数 |
|---|---|
| 1行テキスト | sanitize_text_field() |
| 複数行テキスト | sanitize_textarea_field() |
| メールアドレス | sanitize_email() |
| URL | esc_url_raw()(保存用) |
| 整数 | absint() / (int) |
| スラッグ | sanitize_title() |
| キー名 | sanitize_key() |
| HTMLを許可する本文 | wp_kses_post() |
$name = sanitize_text_field($_POST['name'] ?? '');
$email = sanitize_email($_POST['email'] ?? '');
$count = absint($_POST['count'] ?? 0);外から来た値($_POST $_GET $_COOKIE)は、必ず型と形を決めてから使う——これが基本姿勢です。
フォームの保存処理は3点セット
管理画面や公開側でデータを保存する処理では、nonce・権限・サニタイズの3つを必ず入れます。
// フォーム側
function my_form() {
?>
<form method="post">
<?php wp_nonce_field('my_save_action', 'my_nonce'); ?>
<input type="text" name="nickname">
<button type="submit">保存</button>
</form>
<?php
}
// 受け取り側
add_action('admin_post_my_save', function () {
// ① nonceの確認(このサイトの画面から来たか)
if (!isset($_POST['my_nonce']) || !wp_verify_nonce($_POST['my_nonce'], 'my_save_action')) {
wp_die('不正なリクエストです');
}
// ② 権限の確認(この人に許された操作か)
if (!current_user_can('edit_posts')) {
wp_die('権限がありません');
}
// ③ 値を洗ってから保存
update_option('my_nickname', sanitize_text_field($_POST['nickname'] ?? ''));
wp_safe_redirect(admin_url('options-general.php'));
exit;
});- nonce——その送信が自分のサイトの画面から来たかを確認する使い捨ての値
- 権限——
current_user_can()で「この人がやっていい操作か」を確認する - サニタイズ——値の形を決める
3つのうちどれが欠けても穴になります。特にnonceだけ確認して権限を見ないケースは多いので、セットで書く癖をつけます。
べんりワザget_post_metaでカスタムフィールドを扱う投稿の保存時にメタを更新する例はこちらデータベースを直接触るときはprepare
$wpdbで自分でSQLを書くときは、値を必ずprepare()経由にします。
global $wpdb;
// ❌ 危険(SQLインジェクションの原因)
$wpdb->get_results("SELECT * FROM {$wpdb->posts} WHERE post_title = '$title'");
// ✅ prepareでプレースホルダに渡す
$wpdb->get_results(
$wpdb->prepare("SELECT * FROM {$wpdb->posts} WHERE post_title = %s", $title)
);%s(文字列)・%d(整数)・%f(小数)を使い分けます。そもそもWP_Queryやget_posts()で済むならそちらを使うのがいちばん安全です。
リダイレクトはwp_safe_redirect
wp_safe_redirect(home_url('/thanks/'));
exit;wp_safe_redirect()は自サイト内のURLにしか飛ばないので、外部サイトへ誘導される「オープンリダイレクト」を防げます。exitを忘れると処理が続いてしまうので、必ずセットで書きます。
翻訳関数と組み合わせる
多言語対応するテーマでは、翻訳とエスケープを兼ねた関数があります。
<?php esc_html_e('お問い合わせ', 'my-theme'); ?> <!-- 出力 -->
<?php echo esc_attr__('検索', 'my-theme'); ?> <!-- 属性用 -->__()(値を返す)と_e()(出力する)に、esc_html・esc_attrが付いた形です。命名の規則が分かれば、必要なものを推測できます。
まとめ
- 出力する場所で関数を選ぶ——本文
esc_html()/属性esc_attr()/URLesc_url() get_で始まる関数は値を返すだけ。echoするときは自分でエスケープ(the_field()も対象)- 保存処理はnonce・権限(
current_user_can)・サニタイズの3点セット - SQLを書くなら
$wpdb->prepare()。リダイレクトはwp_safe_redirect()+exit
エスケープは覚えることが少ないのに効果が大きい習慣です。手が勝手に書くようになれば、それだけで安全性が一段上がります。