PR

【WordPress】URLに#038;が入るのはエスケープ処理が原因?具体事例と解決法のご紹介

「&」はHTMLエンティティ( & と ; で囲まれた文字列)の1つで、HTMLでは「&」(アンパサンド)を示すものになりますね。( 「&」も「&」に同じ)

ワードプレスの自作プラグインとかでWP_Queryを使って記事の一覧表示させてページネーションを付けた場合、ページネーションの数字(2ページ目とか)をクリックすると、この「&」の「&」を除いた「#038;」という文字列がURLに入ってしまう場合があります。

原因を色々調べた結果、
URLにページネーションとは関係のないキーが入る場合(今回ご紹介するケースでは「&settings-updated=true」)、ページネーションを表示する関数の中を通る際に、WordPressのセキュリティ保護(エスケープ処理)が働きます。

この時、「&」が安全な文字列である「&」に自動変換されるのですが、条件が重なると「&」が消えて「#038;」だけがURLに残ってしまうようですね。

これはWordPressの仕様に起因するもののようですが、その具体的症状や原因、解決する方法を参考までに。

実際のコードと症状

自作プラグインを作っていたらURLになぜか「#038;」が入り、
ページネーションがうまくできない。
(ページ番号をクリックしてもそのページが表示されない)

その時のコードは以下のような感じです。

Code
<?php
	// ここにSettings APIを使った、設定を保存するのための
	// コードがここにいろいろある
	//
	// でその後に以下のように一覧表示を付けている

	//ページ番号を取得:最初は1ページ目(PHP7.0以降の安全な書き方)
	$paged = $_GET['paged'] ?? 1;

	//WP_Queryの検索条件
	$args_query	= array(
		'post_type' 	=> 'post',	// 投稿(カスタム投稿タイプならそのスラッグ)
		'post_status' 	=> 'publish',	// 公開済みの記事のみ
		'paged' 	=> $paged,	// 表示するページ番号
	);

	//一覧の取得と表示
	$my_query = new WP_Query($args_query);
	if ( $my_query->have_posts() ) :
		echo '<ul>';
		while ( $my_query->have_posts() ) : $my_query->the_post();
			//表示したいHTMLコードを書く
		endwhile;
		echo '</ul>';
		wp_reset_postdata();	//WP_Query のリセット

	else:
		echo '<p>no data</p>';
	endif;

	//ページペーション
	$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 );
?>
Jin Simple Code Block

ページネーションのコードは、ワードプレスの公式リファレンス(基本的な例の箇所)を基本にして若干変えているだけ。

(ワードプレスの管理画面だと get_query_var()がうまく動作しないので$_GET[‘paged’]でページ番号($paged)を取得しているところが異なる。詳しくは 管理画面や設定ページではget_query_varが使えない?!を参照 ))

これで実際に一覧表示させてみるとうまく動作しているようですが、
例えば以下のような操作をするとURLに「#038;settings-updated=true」が入り、ページネーションがうまく動作しなくなってしまいます。
(ページ番号をクリックしてもそのページが表示されない)

  • 1)ワードプレス管理画面の「設定」メニューからプラグインの設定画面を表示
    (この時のURLは以下)
    http://xxxx/options-general.php?page=jin-more-tag-auto
  • 2)同じページにあるSettings APIを使った設定を保存
    (保存後のURLは以下)
    http://xxxx/options-general.php?page=jin-more-tag-auto
  • 3)その後、記事一覧のページネーションで「2ページ目」をクリックすると、以下のように「&」が「#038;」に化けたURLになってしまいます
    http://xxxx/options-general.php?page=jin-more-tag-auto#038;settings-updated=true&paged=2

※)補足
「More Tag Auto」というプラグインを作り、その設定画面のURLを「jin-more-tag-auto」としているので、URLは http://xxxxx/options-general.php?page=jin-more-tag-auto となっている

原因と解決法

なぜ「#038;~」 といった文字列がURLに入るのか、
その原因はページネーションのコード中にあるesc_url()関数とget_pagenum_link()関数にあるようです。(35行目)

Code
//ページペーション
$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 );
Jin Simple Code Block

そもそも get_pagenum_link() 関数は、
初期状態(第二引数を省略、または true の場合)では内部で esc_url() を実行してURLを返します。

そのため、元のコードのように esc_url(get_pagenum_link($big)) と書くと「エスケープの2重がけ」が発生します。

この過剰な保護によって「&」が「&」になり、さらに「&」へと変換され、最終的にブラウザ側で正しく処理できず「#038;」だけがURLにこぼれ落ちてしまう…となるようですね。

※)詳しくは Paginate Link generate additional #038(英語)

esc_url() は「&」をHTMLエンティティの「&#038;」に変換し、
(上の例では「&#038;settings-updated=true」となり)
ページネーションを表示する paginate_links()(39行目)がこの「&」以降「#038;settings-updated」をキー(「true」を値)と解釈してしまい、結果URLにそれが表示される、となるようです。

※)今回の例では、記事一覧を表示する同じ画面上に別の設定があり、その設定を保存したことでページネーションのリンクURLに余計な「&settings-updated=true」が付き、その「&」がget_pagenum_link() の中で変にエスケープされて「&#038;」になった、となりそうです)

また、get_pagenum_link() の値を元にページネーションを表示する paginate_links()(39行目)は、関数リファレンスにあるソースコードを見ると、内部的に一旦デコード(html_entity_decode)してesc_url()を使ってURLを返すようになってます。

以上からURLに出てくる「#038;」の表示をなくすには以下の3つの対応が考えられる、ってことになりますね。

  • その1)get_pagenum_link の第2引数を false にして出力直前でエスケープ
    'base' => str_replace($big, '%#%', esc_url(get_pagenum_link($big, false)))
  • その2)get_pagenum_link()の返り値をHTMLエンティティデコードしておく
    (「#038」を「&」に戻しておく)
    'base' => str_replace($big, '%#%', html_entity_decode( get_pagenum_link( $big ) ) )
  • その3)特定の文字列「&#038;」を直接「&」に置換する
    'base' => str_replace([$big, '&#038;'], ['%#%', '&'], get_pagenum_link($big))

この3つの方法を各々試してみると、
いずれも「#038;」がなくなり、正しいURLでページネーションも動作するようです。

私の場合、コードが最もスッキリして、またWordPressのセキュリティ基準(エスケープは出力の直前に行う)を満たせる「その1」を採用しました。

Code
//ページネーション(修正版)
$big = 999999999;

$args_paginate = array(
    'base'    => str_replace($big, '%#%', esc_url(get_pagenum_link($big, false))), 
    'current' => max(1, $paged),
    'total'   => $my_query->max_num_pages,
);
echo paginate_links($args_paginate);
Jin Simple Code Block

以上、ご参考までに。

早期退職して海外で奮闘する JIN のメールマガジン

時間や場所に縛られず稼いだJINが教える

~ 最短で月収10万円稼げるようになる方法 ~

お名前/ニックネーム

隣のあの人にも、思わず教えたくなる秘密

配信停止は、いつでもできます

迷惑メールは一切配信されませんので、ご安心くださいね

自らの手で未来を変える力を手に入れる!

コメント