WordPress で the_excerpt()
などで本文を切り出して自動的に抜粋を表示したとき、ショートコードを 2 重カギ括弧でエスケープしたにもかかわらず実行されてしまっていることに気がついたので対処しました。
目次
不具合の発見過程
サーバーのエラーログを久しぶりに見たら大量のエラーを吐いていました。
[Sat Mar 03 14:15:03 2018] [warn] [client 66.249.79.46] mod_fcgid: stderr: PHP Warning: Division by zero in /home/xxxxx/1010uzu.com/public_html/xxx/wp-content/plugins/photo-express-for-google/class-photo-renderer.php on line 1279
どうやらこのエラーはある数字をゼロで割っているときに出るエラーみたいです。しかしこのエラーがどのページで起こっているのかわからなかったので、アクセスログを見てみたら、ちょうど同じくらいの時刻に Googlebot がクロールにきていました。
66.249.79.46 - - [03/Mar/2018:14:15:02 +0900] "GET /feed/rdf HTTP/1.1" 200 90579 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
/feed/rdf を見てみたけれどおかしなところはなく、 class-photo-renderer.php の該当箇所の割り算している数値を var_dump()
で表示してみたら、なんと抜粋部分で [[xxx]]
と 2 重カッコでエスケープしたショートコードが実行されてしまっています。このときショートコードの属性のエスケープされた "
も値として入力してしまっているため、 0 として処理されエラーが発生しているようでした。
該当箇所で割り算するときに 0 以上であることを確認すればいいのですが、必要ない処理が走っているのがなんとなくいやだったので、エスケープしたショートコードが抜粋時に動作しないようにしてみました。
ちなみに問題となった記事は下記のもので、記事にエスケープされたショートコードが含まれています。
抜粋時にエスケープしたショートコードが実行されてしまう原因
feed での抜粋は the_excerpt_rss()
が使われており get_the_excerpt()
を呼び出していました。この関数は投稿ページで指定した抜粋を呼び出しているのですが、自動的に本文を切り出して抜粋を生成している部分がわかりません。探してみると wp-includes/default-filters.php の 148 行目で以下のようにフィルターフックがかけられており、 wp_trim_excerpt()
にて本文を取得し指定された文字列で切り出しているようです。
→ WordPress.org : Reference / Functions / get_the_excerpt()
→ WordPress.org : Reference / Functions / wp_trim_excerpt()
add_filter( 'get_the_excerpt', 'wp_trim_excerpt' );
wp_trim_excerpt()
を見てみます。
function wp_trim_excerpt( $text = '' ) {
$raw_excerpt = $text;
if ( '' == $text ) {
$text = get_the_content('');
$text = strip_shortcodes( $text );
/** This filter is documented in wp-includes/post-template.php */
$text = apply_filters( 'the_content', $text );
$text = str_replace(']]>', ']]>', $text);
/**
* Filters the number of words in an excerpt.
(中略)
*/
return apply_filters( 'wp_trim_excerpt', $text, $raw_excerpt );
}
wp_trim_excerpt()
内でコンテンツを切り出す前にショートコードを削除し、 the_content
のフィルターフックにかけられている関数を実行しているようです。このときショートコードも実行されます。 wp-includes/default-filters.php の 460 行目に記述があります。
add_filter( 'the_content', 'do_shortcode', 11 ); // AFTER wpautop()
どうやら strip_shortcodes()
でショートコードを除去した後、なぜかショートコードのエスケープがはずれた文字列が返ってきました。なので続く the_content
のフィルターフックでショートコードが実行されてしまうのです。本来は文字列として表示してほしい部分なのに……。
カギ括弧 []
を数値参照にしてみたりしたのですが、中で使われている unescape_invalid_shortcodes()
で元に戻されてしまうので、意味がありませんでした。
strip_shortcodes()
のロジックが根本的には悪いのかもしれませんが、フックのポイントがないのでいじるのはやめました。
解決コード
結局どうしたかというと、カギ括弧を 3 重 [[[
にすると strip_shortcodes()
のあとの文字列が 2 重カギ括弧で普通にエスケープされた状態で出力されたので、 wp_trim_excerpt
フックをリムーブして新しい関数を登録することにしました。
/***********************
* 自動抜粋の際にショートコードが実行されてしまう不具合を修正
***********************/
remove_filter( 'get_the_excerpt', 'wp_trim_excerpt' );
add_filter( 'get_the_excerpt', 'custom_wp_trim_excerpt' );
function custom_wp_trim_excerpt( $text = '' ) {
$raw_excerpt = $text;
if ( '' == $text ) {
$text = get_the_content('');
//ショートコードが抜粋中で実行されてしまうのを防止
$text = str_replace( array( '[[', ']]'), array( '[[[', ']]]' ), $text);
$text = strip_shortcodes( $text );
/** This filter is documented in wp-includes/post-template.php */
$text = apply_filters( 'the_content', $text );
$text = str_replace(']]>', ']]>', $text);
//the_contentでフックで置換している狭いスペースを削除(なくてよい)
$text = str_replace(' ', '', $text);
$excerpt_length = apply_filters( 'excerpt_length', 55 );
$excerpt_more = apply_filters( 'excerpt_more', ' ' . '[…]' );
$text = wp_trim_words( $text, $excerpt_length, $excerpt_more );
}
return apply_filters( 'wp_trim_excerpt', $text, $raw_excerpt );
}
これならデフォルトのフィルターフックも使えるし、置き換えてもそんなに問題ないのではないかと思っています。ちなみに 22 行目は独自に半角英数字と全角文字の間に狭いスペースを挿入しているため、切り出したあと汚くなるので削除するために入れてあります。
あと 13 行目のカギ括弧の置換ですが、きちんと正規表現を使って書いた方がよいかな、という気もしますが、これはおいおいにします。
ちなみに同じように悩んでいる方が質問してそれに対しての回答が載っているページがありましたが、 wp_trim_excerpt
のフィルターフックを使っているので、出力は変えられますが the_content のフック処理はかかってしまっており、ショートコードが実行されるので、サーバーでエラーが出るのは変わりませんでした。
→ teratail :【 WordPress 】抜粋でのショートコードの処理について
終了タグがあるショートコードが抜粋時に丸ごと削除されるのに対処
今回の不具合に直接関係があるわけではないのですが、このサイトでは見出しのレベルを簡単に変えられるように、記事内の <h3>
など見出しをショートコードで出力しています。
[hs]見出し[/hs]
こんな感じです。終了タグがある形のショートコードは抜粋のショートコードを除去するときに、挟まれたの文字列まで一緒に除去されてしまいます。見出しが抜粋に入らないのは結構致命的な感じがするので対処しました。
ショートコードを除去する関数である strip_shortcodes()
内に、除去するショートコードのリストから除外するためのフィルターフックがあったのでそれを使い、一度そのままショートコードで出力し the_content
のフィルターフックがかかると HTML タグに戻ります。続く wp_trim_words()
内にて wp_strip_all_tags()
という関数で HTML タグが取り除かれるのでそれで対応することにしました。 wp_strip_all_tags()
は HTML タグに挟まれていてもタグだけ取り除いてくれるようです。
//見出しのショートコードも削除されてしまうのを防止
function remove_strip_shortcodes_tagnames( $tags_to_remove ){
$tags_to_remove = array_diff($tags_to_remove, array('hs', 'hss', 'hsss'));
$tags_to_remove = array_values( $tags_to_remove );
return $tags_to_remove;
}
add_filter( 'strip_shortcodes_tagnames', 'remove_strip_shortcodes_tagnames' );
除去するショートコードの一覧が配列で渡されるので、そこから見出しとして使っているショートコードを取り除き、一応配列のインデックスを詰めておきました。
抜粋時に文字数を変更する方法
ついでに抜粋の文字数を変更する方法を載せておきます。
普通に excerpt_length
のフィルターフックを使えばよいと思われますが、日本語の場合はそれでは動きません。なぜならマルチバイト文字の不具合を解消する WP Multibyte Patch というプラグインで上書きされているからです。なので excerpt_mblength
という WP Multibyte Patch のフックを使います。
function my_excerpt_mblength( $length ) {
return 150;
}
add_filter( 'excerpt_mblength', 'my_excerpt_mblength' );
WP Multibyte Patch の wp-multibyte-patch.php の 435 行目にて excerpt_length
にフックをかけています。
add_filter( 'excerpt_length', array( $this, 'excerpt_mblength' ), 99 );
その実行順が 99 になっているので、 excerpt_length
を使って書くならば以下のようになります。数値は 100 以上ならどの数でも構いません。
function my_excerpt_length( $length ) {
return 150;
}
add_filter( 'excerpt_length', 'my_excerpt_length', 100 );