WordPress で Trackback URL が切れるのは日本語のせいじゃない!

以前、トラックバック を送信した際に、送信先でURLが壊れているとの指摘を受けました。今回はその理由と、関連する WordPress の制限事項について解き明かして行こうと思います。

取りあえずこうしたほうが良いのは知っているという人は、これを機に もやもや を解消させましょう。

wordpress wp_comments table on db画像が既に確信を付いてしまっていますが、さらに納得したい方は先を読んで下さい。

コメント元のURLを保持するデータ

WordPress は Trackback URL を コメント として受け取ります。

コメントは WordPress を管理する データベース 上の wp_comments というテーブルで管理されていて、この中の comment_type のデータが 'trackback’ に設定されているものが、記事上にトラックバック元として表示されます。

ここで重要なのが、コメント元の URL のデータ長です。 データベース  上では utf8_general_ci varchar(200) と定義されています。つまり受け取り側が WordPress の場合は 200文字 までの制限が存在する事になります。

ここに入るデータは WordPress 上で esc_url 関数で処理され 固有の言語文字などを省いた HTMLエンティティ に正規化されます。エンティティってどういう物?という人は Web便利ツール 等で確認して下さい。

エンティティでコード化された場合の日本語の長さは?

200文字 というと多そうな気もしますが、 UTF-8 の漢字コードは3バイです。これをエンティティ化すると %Ex%xx%xx この様になり、漢字1つを表現するのに9文字分も消費し、 何と22文字分にしかなりません。

最終的なデータが 200文字 を超える分に関しては切り取られてしまいます。筆者の様に、カテゴリを階層化している人も URL が長くなりがちで危険な気がします。

また、この 200文字 制限はポストタイトルやスラッグにも存在するので注意が必要です。

送信元と、送信先、どっちのせい?

送信に関してですが、 エディタ に付属する トラックバック の メタボックス のコードを見てみましょう。重要な部分をマーキングしてあります。

function post_trackback_meta_box($post) {
	$form_trackback = '<input type="text" name="trackback_url" id="trackback_url" class="code" value="'. esc_attr( str_replace("\n", ' ', $post->to_ping) ) .'" />';
	if ('' != $post->pinged) {
		$pings = '<p>'. __('Already pinged:') . '</p><ul>';
		$already_pinged = explode("\n", trim($post->pinged));
		foreach ($already_pinged as $pinged_url) {
			// redmemo
			$pings .= "\n\t<li>" . esc_html($pinged_url) . "</li>";
		}
		$pings .= '</ul>';
	}

?>
<p><label for="trackback_url"><?php _e('Send trackbacks to:'); ?></label> <?php echo $form_trackback; ?><br /> (<?php _e('Separate multiple URLs with spaces'); ?>)</p>
<p><?php _e('Trackbacks are a way to notify legacy blog systems that you&#8217;ve linked to them. If you link other WordPress sites they&#8217;ll be notified automatically using <a href="http://codex.wordpress.org/Introduction_to_Blogging#Managing_Comments" target="_blank">pingbacks</a>, no other action necessary.'); ?></p>
<?php
if ( ! empty($pings) )
	echo $pings;
}

これを見ると $post->to_ping で inputタグ がリプレスされています。 $post->to_ping は送信の完了していないURL群で、文字列長の制限はありません。複数指定が出来るので当然と言えば当然です。

続いて送信処理です。 WordPress のバージョンによりフィルタリング処理などが挟まる場合がありますが、 $post->to_ping がほぼそのまま使用され、 foreach 文で分解されて送信されます。受理されたトラックバックURLは $post->pinged へ貯まっていき、重複するURLへトラックバックが送られないようチェックするのに使われます。

function do_trackbacks($post_id) {
	global $wpdb;

	$post = get_post( $post_id );
	$to_ping = get_to_ping($post_id);
	$pinged  = get_pung($post_id);
	if ( empty($to_ping) ) {
		$wpdb->update($wpdb->posts, array('to_ping' => ''), array('ID' => $post_id) );
		return;
	}

	if ( empty($post->post_excerpt) ) {
		/** This filter is documented in wp-admin/post-template.php */
		$excerpt = apply_filters( 'the_content', $post->post_content, $post->ID );
	} else {
		/** This filter is documented in wp-admin/post-template.php */
		$excerpt = apply_filters( 'the_excerpt', $post->post_excerpt );
	}

	$excerpt = str_replace(']]>', ']]>', $excerpt);
	$excerpt = wp_html_excerpt($excerpt, 252, '&#8230;');

	/** This filter is documented in wp-includes/post-template.php */
	$post_title = apply_filters( 'the_title', $post->post_title, $post->ID );
	$post_title = strip_tags($post_title);

	if ( $to_ping ) {
		foreach ( (array) $to_ping as $tb_ping ) {
			$tb_ping = trim($tb_ping);
			if ( !in_array($tb_ping, $pinged) ) {
				trackback($tb_ping, $post_title, $excerpt, $post_id);
				$pinged[] = $tb_ping;
			} else {
				$wpdb->query( $wpdb->prepare("UPDATE $wpdb->posts SET to_ping = TRIM(REPLACE(to_ping, %s, '')) WHERE ID = %d", $tb_ping, $post_id) );
			}
		}
	}
}

つまりは送信側のせいでは無いという事になりますが、そもそも 200文字 を超える URL を送ってくれるなという話もありまして、送る側にも問題があるのは事実です。

日本語URL は日本人的には視認しやすいというイメージがありますが、 検索結果 に関しては、 URL の代わりに パンくずリスト を使う事も可能です。

この不具合を回避するために短い日本語を考えるより、 URL を英語化して検索エンジンへの最適化に労力を割くべきだと筆者は考えます。

最後に

実はこの記事と並行してこういうプラグインを作りかけていたんですよね…。

wordpress-trackback-maniaxそして、記事を書き終えまして、これは必要ないなと思うに至りました。