囲み型ショートコードとwpautop

説明

ショートコードは投稿記事の閲覧時に動的に内容を変更できる優れもの。galleryやcaptionなどいくつかのショートコードが標準で組み込まれており、一部のプラグインではその機能を利用する手段としてショートコードが利用されている。

ショートコードは[tag-a /]のように記述する自己完結型と、[tag-b]コンテント[/tag-b]のように記述する囲み型に区別できる。ショートコードの中には、これら両方のタイプをサポートしているものもある。

ショートコードの処理は最後

ショートコードは、 the_content関数の同名のフィルターとして組み込まれている do_shortcode関数によって機能する。the_contentフィルターは、バージョン4.0の標準状態では次のようになっている。

フィルター関数概要プライオリティ
WP_Embed:run_shortcodeショートコードembedを処理8
WP_Embed:autoembedautoembedに対応したURLを処理8
wptexturizeHTMLエンティティを一般的な文字に変換10
convert_smilies絵文字を対応するimgタグに変換10
convert_charsHTML数値エンティティを変換10
wpautop改行、pタグやbrタグを整頓10
shortcode_unautoppタグで囲まれたショートコードを調整10
prepend_attachmentコンテント(本文)前に添付ファイル内容を追加10
capital_P_dangit'Wordpress'を'WordPress'に変換など11
do_shortcodeショートコードを処理11

the_contentフィルターはこの表の上から下へ順番に処理を行う。最初にショートコードembed(埋め込み)が処理され、最後にショートコードを処理するdo_shortcode関数が呼び出されている。同じショートコードでも違うタイミングで処理されていることがわかる。

そこのpタグはいらない

さて前置きはこのへんにしておいて、本題に移っていこう。

今回、指定した条件にマッチする場合のみコンテントを表示するショートコードを作ってみたのだが、思い通りに表示にならなかった。ショートコードはアクセス日時によって表示内容を変更するもので、

function shortcode_period( $atts, $content='' ) {
	extract( shortcode_atts( array(
		'from' => '2000/1/1 0:00:00',
		'to' => '2037/12/31 23:59:59'
		), $atts ) );
	$now_sec = strtotime( date_i18n( 'Y/m/d H:i:s' ) );
	return ( $now_sec >= strtotime( $from ) && $now_sec <= strtotime( $to ) )? do_shortcode( $content ): '';
}
add_shortcode( 'period', 'shortcode_period' );

出力された内容を見てみると、(個人的には)余計なpタグがあり、しかも正しいペアになっていなかった。pタグ自体は我慢できても、正しいペアになっていないのは困る。そんなわけで、実際にどのような変換が行われているか比較してみた。

状況ケース1ケース2-1(10月22日閲覧)ケース2-2(10月23日閲覧)
the_content実行前 <h3>ショートコードについて</h3>\r\n[period to="2014/10/22 23:59:59"]22日まで[/period]\r\n[period from="2014/10/23 0:00:00"]23日から[/period] <h3>ショートコードについて</h3>\r\n[period to="2014/10/22 23:59:59"]<div>22日まで</div>[/period]\r\n[period from="2014/10/23 0:00:00"]<div>23日から</div>[/period]
wpautop実行後 <h3>ショートコードについて</h3>\n<p>[period to="2014/10/22 23:59:59"]22日まで[/period]<br />\n[period from="2014/10/23 0:00:00"]23日から[/period]</p>\n <h3>ショートコードについて</h3>\n<p>[period to="2014/10/22 23:59:59"]\n<div>22日まで</div>\n<p>[/period]<br />\n[period from="2014/10/23 0:00:00"]\n<div>23日から</div>\n<p>[/period]</p>\n
do_shortcode実行後 <h3>ショートコードについて</h3>\n<p>22日まで<br />\n</p>\n <h3>ショートコードについて</h3>\n<p>\n<div>22日まで</div>\n<p><br />\n</p>\n <h3>ショートコードについて</h3>\n<p><br />\n\n<div>23日から</div>\n<p></p>\n

囲み型ショートコード内にdivタグ(pやsectionタグなども同様)を使用した場合、pタグが正しくペアにならないようだ。現状、pタグが正しくペアになっていないことで(ブラウザ次第ではあるが)表示が大きく乱れることは少なくなってきているが、HTMLバリデーションエラーのある状態は好ましくない。またbrタグによって行間が広くなるのも気になるところだ。

対処として、最初は remove_filter関数を使ってthe_contentフィルターからwpautop関数を削除することを考えたのだが、ショートコードembedと同じようにthe_contentフィルターでwpautop関数の前に希望するショートコードだけを処理することを考えてみた。

function run_shortcode_before_wpautop( $content ) {
	global $shortcode_tags;

	// 登録されているショートコードを退避して空に
	$orig_shortcode_tags = $shortcode_tags;
	remove_all_shortcodes();

	// wpautop関数実行前に処理したショートコードをここで追加
	add_shortcode( 'period', 'shortcode_period' );

	$content = do_shortcode( $content );

	// 退避したショートコードを元に戻す
	$shortcode_tags = $orig_shortcode_tags;

	return $content;
}

add_filter( 'the_content', 'run_shortcode_before_wpautop', 9 );

このコードはWP_Embedクラスのrun_shortcodeメソッドを流用したもので、the_contentフィルターとしてwpautop関数よりも優先度の高いプライオリティ値の9として登録している。この対応結果は次のようになり、意図した通りの結果を得ることができた。

状況ケース3-1(10月22日閲覧)ケース3-2(10月23日閲覧)
the_content実行前 <h3>ショートコードについて</h3>\r\n[period to="2014/10/22 23:59:59"]22日まで[/period]\r\n[period from="2014/10/23 0:00:00"]23日から[/period]
run_shortcode_before_wpautop実行後 <h3>ショートコードについて</h3>\r\n<div>22日まで</div>\r\n <h3>ショートコードについて</h3>\r\n\r\n<div>23日から</div>
wpautop実行後 <h3>ショートコードについて</h3>\n<div>22日まで</div>\n <h3>ショートコードについて</h3>\n<div>23日から</div>\n
do_shortcode実行後 <h3>ショートコードについて</h3>\n<div>22日まで</div>\n <h3>ショートコードについて</h3>\n<div>23日から</div>\n

コンテントの中のショートコードを記述できるか

次の2点については後述のCodexに記載されているのだが、少しハマった経験があるのでここでも紹介しておく。

1点目は、囲み型ショートコードのコンテンツ内にショートコードを記述できる(許可する)かどうかである。例えば、次のような投稿内容で、ショートコードtag-aはspanタグで囲み、ショートコードtag-bはstrongタグで囲むものとしよう。

[tag-a][tag-b]コンテントの中のタグB[/tag-b][/tag-a][tag-b]単独のタグB[/tag-b]

ショートコードtag-aがコンテント内にショートコードを記述できない場合は、次の示す内容となる。

<span>[tag-b]コンテントの中のタグB[/tag-b]</span><strong>単独のタグB</strong>

これに対してショートコードtag-aがコンテント内にショートコードを記述できる場合には、次のような内容となる。

<span><p><span><strong>コンテントの中のタグB</strong></span><strong>単独のタグB</strong>

この2つの違いはショートコードの(ハンドラ)関数の記述にあり、コンテントを受け取るパラメータ$contentに対して do_shortcode関数を使用するかどうかで決まる。

/* コンテントのショートコードを許可しない */
function tag_a_func( $atts, $content='' ) {
	return ''.$content.'';
}

/* コンテントのショートコードを許可する */
function tag_a_func( $atts, $content='' ) {
	return ''.do_shortcode( $content ).'';
}

コンテントの中に同じ名前のショートコードは使えない

コンテントのショートコードを許可している場合で注意したいのが、同じ名前のショートコードを使用できないということだ。わかりやすく説明するために、先ほどの投稿内容を少し修正してみる(便宜上色分けする)。

[tag-a][tag-a]コンテントの中のタグ[/tag-a][/tag-a]

この場合の出力内容は、次のようになってしまう。

<span><span></span>コンテントの中のタグ</span>[/tag-a]

本来なら外側(赤)と内側(青)がそれぞれペアとして認識してほしいところだが、実際には赤の[tag-a]のペアとして青の[/tag-a]が適用されてしまうために起こっている。このことは既知の症状として認識されている(要はバグではない)。このようなことを回避するには、ショートコードのコンテントには同名のショートコードを使用しないことを徹底することだ。どうしても同じ機能のショートコードを使用しなければならない場合は、別名のショートコードを登録して使い分けることになる。

まとめ

というわけで、ショートコードを使うなら「Codex日本語版:ショートコード API」を必ず参照しておこう。


最終更新 : 2018年05月27日 10:46


お勧め

get_the_tag_list(2018年5月27日 更新)

string get_the_tag_list( [ string $before = '' [ , string $sep = '' [ , string $after = '' ] ] ] )
投稿記事の投稿タグ(リンク付き)の列挙した文字列を取得する。

get_theme_file_path(2018年5月27日 更新)

string get_theme_file_path( string $file = '' )
テーマ内にあるファイルのパス名を取得する。

wp_list_pages(2015年4月28日 更新)

string wp_list_pages( [ mixed $args = '' ] )
固定ページを一覧表示する。

wp_schedule_single_event(2014年5月26日 更新)

void wp_schedule_single_event( int $timestamp, string $hook [ , array $args = array() ] )
一度だけ実行するアクションをスケジュールに登録する。

get_users(2017年11月27日 更新)

array get_users( [ array $args = array() ] )
ユーザー情報を取得する。