【WordPress】URLに「#038;」が入る問題とその解決方法

WordPressでページネーションを実装する際に、URLに「#038;」が入ってしまうことはありませんか?これは、URLが二重にエスケープ処理されてしまうことが原因で発生します。今回は、この問題の具体的な原因と、その解決方法について紹介します。

スポンサーリンク

URLに「#038;」が入る原因とは?

「&」はHTMLエンティティの一つで、「&」を表す記号です。WordPressで使用するget_pagenum_link()esc_url()関数は、このエンティティをエスケープすることで、URLに「#038;」が含まれることがあります。

特に、カスタムプラグインやテーマを作成している際、ページネーション機能を実装していると、URLに「#038;」が含まれ、ページ番号をクリックしても正しく動作しないことがあります。

例えば、以下のようなコードでページネーションを実装している場合、URLに「#038;」が混入する可能性があります。

問題が発生するコード例
$big = 999999999;
$args_paginate = array(
    'base' => str_replace($big, '%#%', esc_url(get_pagenum_link($big))),
    'current' => max(1, $paged),
    'total' => $my_query->max_num_pages,
);
echo paginate_links($args_paginate);

このようなコードを使用していると、get_pagenum_link()esc_url()によってURLが二重にエスケープされ、&が#038;となってしまうことがあります。get_pagenum_link()の第2引数$escapeは既定でtrueのため、外側で改めてesc_url()を呼ぶと二重にエスケープされてしまうのが根本原因です。

解決方法

この問題を解決するためには、いくつかの方法があります。以下に3つの方法を紹介します。

方法1:get_pagenum_link()でesc_url()を使わない

最も簡単な解決策は、get_pagenum_link()関数の第2引数にfalseを渡し、内部のesc_url()処理を無効にすることです。これにより、二重エスケープが防がれます。

方法1:第2引数にfalseを渡す
$args_paginate = array(
    'base' => str_replace($big, '%#%', get_pagenum_link($big, false)),
    'current' => max(1, $paged),
    'total' => $my_query->max_num_pages,
);
echo paginate_links($args_paginate);

方法2:html_entity_decode()でデコードする

もう一つの方法は、get_pagenum_link()が返す値をhtml_entity_decode()でデコードして、エスケープされた文字を元に戻すことです。

方法2:html_entity_decode()でデコード
$args_paginate = array(
    'base' => str_replace($big, '%#%', html_entity_decode(get_pagenum_link($big))),
    'current' => max(1, $paged),
    'total' => $my_query->max_num_pages,
);
echo paginate_links($args_paginate);

方法3:str_replace()でエンティティを置換する

もう一つのアプローチとして、str_replace()を使って「&」を「&」に置き換える方法もあります。

$bigの置換を忘れると全ページが同じURLになる
エンティティの置換だけに気を取られて$big(999999999)を'%#%'に置き換える処理を忘れると、paginate_links()がページ番号を差し込む位置を見つけられず、生成される全ページのリンクが同一URLになってしまいます。次のようなコードは一見動きそうに見えて、この落とし穴にはまっています。
NG:$bigの置換が抜けており全ページが同一URLになる
$args_paginate = array(
    'base' => str_replace(['&', '%#%'], ['&', '%#%'], get_pagenum_link($big)),
    'current' => max(1, $paged),
    'total' => $my_query->max_num_pages,
);
echo paginate_links($args_paginate);
// $big -> '%#%' の置換が抜けているため、全ページのリンクが同一URLになってしまう

エンティティの置換と%#%への置換の両方を行う必要があります。

OK:str_replace()でエンティティと%#%を両方置換
$args_paginate = array(
    'base' => str_replace($big, '%#%', str_replace('&', '&', get_pagenum_link($big))),
    'current' => max(1, $paged),
    'total' => $my_query->max_num_pages,
);
echo paginate_links($args_paginate);

関連記事

よくある質問(FAQ)

QWordPressのURLに「#038;」が含まれてしまう原因は何ですか?
Aget_pagenum_link()は第2引数$escapeが既定でtrueのため、内部でesc_url()相当の処理が実行され&&に変換されます。この戻り値をさらに外側でesc_url()にかけると二重にエスケープされ、URLに「#038;」が現れます。
Q「#038;」が混入するのを防ぐ方法は?
Aget_pagenum_link($big, false)のように第2引数にfalseを渡し内部のエスケープを無効にするか、html_entity_decode()で戻り値をデコードします。自前で文字列連結する場合はstr_replace('&', '&', $url)で変換することもできます。
Qadd_query_arg()で生成したURLにも#038;が含まれる場合は?
Aadd_query_arg()の結果をesc_url()でエスケープすると解決します。HTML属性(href等)に出力する場合は必ずesc_url()を通すのが正しい実装です。すでにエスケープ済みの値を再度esc_url()に通さないよう注意してください。

まとめ

WordPressでURLに「#038;」が含まれる問題は、get_pagenum_link()esc_url()による二重エスケープが原因です。この問題を解決するためには、esc_url()相当の処理を無効にするか、エンティティをデコードまたは置換する方法があります。最も簡単で効果的な方法は、get_pagenum_link()の第2引数にfalseを渡すことです。str_replace()でエンティティを置換する方法を使う場合は、ページ番号のプレースホルダー%#%への置換を忘れないようにしてください。