WordPressのテーマのコードを読んでいたらよくわからない記述にぶち当たったので。

  • wp-content/themes/twentyeleven/content.php #19
            <h1 class="entry-title"><a href="<?php the_permalink(); ?>" title="<?php printf( esc_attr__( 'Permalink to %s', 'twentyeleven' ), the_title_attribute( 'echo=0' ) ); ?>" rel="bookmark"><?php the_title(); ?></a></h1>

記事のタイトルを表示している箇所なんですが、そのタイトルを表示するのに二通りの関数を使っています。

  • the_title()
  • the_title_attribute()

なんぞ?? と思って調べてみたわけです。

何が出るかな

<b>&copy;"'`&</b>というタイトルにして、それぞれ出力してみます。

関数 結果
the_title() <b>&copy;&#8221;&#8216;`&#038;</b>
the_title_attribute() &copy;&#8221;&#8216;`&amp;

うおおおおい、タグをそのまま出力するのかよまじかよ!!!

記事の投稿はちゃんとした権限を持った、特定可能なアカウントからしか行わない(行えない)という前提の実装なんだろうか。

でも「あのサービスに<script>alert(new Date)</script>と入力するとXSSだぜヒャッホウ」というタイトルの記事は作れない……よね。

公式ドキュメント

日本語より英語の方が詳しかったのでそちらを見てます。翻訳は私ギンペイ。

なお共通の訳註ですが、どちらも引数で結果をechoするかreturnするか指定できます。

the_title()の概要

現在の投稿のタイトルを表示、あるいは返します。必ずループの中で使用してください。投稿がプロテクテッドやプライベートであった場合、”Protected: “や”Private: “といった注釈がタイトルの前に追加されます。

the_title_attribute()の概要

現在の投稿のタイトルを表示、あるいは返します。the_title()関数と似ていますが、HTMLの属性値として使用する場合に、より「クリーン」な値を提供します。HTMLタグは取り除かれ、引用符などは実体参照に変換されます。クエリー文字列形式のものにも使えます。必ずループの中で使用してください。

コード

the_title()のコード

/**
 * Display or retrieve the current post title with optional content.
 *
 * @since 0.71
 *
 * @param string $before Optional. Content to prepend to the title.
 * @param string $after Optional. Content to append to the title.
 * @param bool $echo Optional, default to true.Whether to display or return.
 * @return null|string Null on no title. String if $echo parameter is false.
 */
function the_title($before = '', $after = '', $echo = true) {
        $title = get_the_title();

        if ( strlen($title) == 0 )
                return;

        $title = $before . $title . $after;

        if ( $echo )
                echo $title;
        else
                return $title;
}

なるほど、シンプル。

get_the_title()もこのコードのちょっと下で実装されてるけど、取得して前後に引っ付けて、出力なり返すなりするだけ、と。

the_title_attribute()のコード

/**
 * Sanitize the current title when retrieving or displaying.
 *
 * Works like {@link the_title()}, except the parameters can be in a string or
 * an array. See the function for what can be override in the $args parameter.
 *
 * The title before it is displayed will have the tags stripped and {@link
 * esc_attr()} before it is passed to the user or displayed. The default
 * as with {@link the_title()}, is to display the title.
 *
 * @since 2.3.0
 *
 * @param string|array $args Optional. Override the defaults.
 * @return string|null Null on failure or display. String when echo is false.
 */
function the_title_attribute( $args = '' ) {
        $title = get_the_title();

        if ( strlen($title) == 0 )
                return;

        $defaults = array('before' => '', 'after' =>  '', 'echo' => true);
        $r = wp_parse_args($args, $defaults);
        extract( $r, EXTR_SKIP );


        $title = $before . $title . $after;
        $title = esc_attr(strip_tags($title));

        if ( $echo )
                echo $title;
        else
                return $title;
}

extract()はWPじゃなくてPHPの方の関数。

$defaultsの、JavaScriptで言うところのプロパティがそのまま変数として使えるようになるとか。強力だなー。

で、前後に指定のものを引っ付けて出来上がったものを、エスケープとかしてる。

strip_tags()はエスケープではなく削除なのがミソか。

get_the_title()

これはthe_title()the_title_attribute()のいずれからも呼ばれる部分。

/**
 * Retrieve post title.
 *
 * If the post is protected and the visitor is not an admin, then "Protected"
 * will be displayed before the post title. If the post is private, then
 * "Private" will be located before the post title.
 *
 * @since 0.71
 *
 * @param int $id Optional. Post ID.
 * @return string
 */
function get_the_title( $id = 0 ) {
        $post = &get_post($id);

        $title = isset($post->post_title) ? $post->post_title : '';
        $id = isset($post->ID) ? $post->ID : (int) $id;

        if ( !is_admin() ) {
                if ( !empty($post->post_password) ) {
                        $protected_title_format = apply_filters('protected_title_format', __('Protected: %s'));
                        $title = sprintf($protected_title_format, $title);
                } else if ( isset($post->post_status) && 'private' == $post->post_status ) {
                        $private_title_format = apply_filters('private_title_format', __('Private: %s'));
                        $title = sprintf($private_title_format, $title);
                }
        }
        return apply_filters( 'the_title', $title, $id );
}

“Private:”を追加したりフィルター通したりはしているけれど、基本的には投稿からタイトルを取得して返すだけ。

ただ、the_title()でもここでも、引用符の実体参照への置換は行われていない。じゃあ置換して保存してあるかというと、そうでもない。(実DB覗けばそのまま保存されているのがわかる。) ついでに引用符は置換されてるけど普通に記述した参照(&copy;とか)は残ってる。うううむ??

投稿情報取得まで追う元気はありませんでしたので、調査はここまでで打ち切り。

ぼくのかんがえたさいきょうのタイトル取得処理

function get_the_safety_title( $id = 0 ) {
  return str_replace('<', '&lt;', str_replace('>', '&gt;', get_the_title(&id)));
}

えっ、えっ、よくわかんない。これでいいのかな?

<>だけエスケープして返すようにする。&copy;とかは使いたい場面もあるので、htmlspecialchars()だと嬉しくない。あと削除しちゃうのは良くないんじゃないかなーと思って、残すようにしました。

で、esc_attr()は?

the_title()the_title_attribute()の違いは内部でesc_attr()してるかどうかってところなんだけど、でも内部でしてるものをさらにesc_attr__()してるのが謎。

というわけでよくわかりませんでした。