【WordPress入門】アクションフック・フィルターフックを使い倒してWordPressをカスタマイズする

前回の記事「【WordPress入門】アクションフックとフィルターフックを使いこなそう」で WordPress で主に使用するアクションフックとフィルターフックの一覧を紹介しましたが、今回はそれらのフックを使って WordPress をカスタマイズしたいと思います。



ここでは add_actionadd_filter で登録する関数を無名関数にしていますが、それぞれ関数を別に定義して、その関数名を文字列として add_actionadd_filter に渡すこともできます。独自テーマ内で利用し、remove_actionremove_filter することがなければ無名関数でも問題はありません。逆にプラグインや親テーマ内で利用する場合は、remove_actionremove_filter で登録解除することができなくなるため、無名関数を使ってはいけません。

wp_head で出力される不要な要素を head 要素から削除する

デフォルトでは wp_head 関数によって多くの要素が head 要素に追加されますが、不要なものも多くあります。これらを出力されないようにするには、 remove_actionwp_head フックに登録されているアクションフックを解除します。

<?php
// wp_head による feed 出力をしない.
remove_action( 'wp_head', 'feed_links', 2 );
remove_action( 'wp_head', 'feed_links_extra', 3 );

// 外部ツールを使ったブログ更新用の URL(EditURI)の出力をしない.
remove_action( 'wp_head', 'rsd_link' );

// wlwmanifest (Windows Live Writer) を使った記事投稿 URL の出力をしない.
remove_action( 'wp_head', 'wlwmanifest_link' );

// noindex のデフォルト出力をしない.
remove_action( 'wp_head', 'noindex', 1 );

// WordPress のバージョン情報の出力をしない.
remove_action( 'wp_head', 'wp_generator' );

// デフォルトパーマリンクの URL の出力をしない.
remove_action( 'wp_head', 'wp_shortlink_wp_head' );

// oEmbed を使った記事内埋め込み機能をオフ.
remove_action( 'wp_head', 'wp_oembed_add_discovery_links' );
remove_action( 'wp_head', 'wp_oembed_add_host_js' );

// WP JSON API のリンク
// <link rel='https://api.w.org/' href='http://example.com/wp-json/' />
// を出力しない.
remove_action( 'wp_head', 'rest_output_link_wp_head' );

// WordPress.org の DNS prefetch
// <link rel='dns-prefetch' href='//s.w.org' />
// の出力をしない.
remove_action( 'wp_head', 'wp_resource_hints', 2 );

wp_head でスクリプトを注入する際に付与される type=text/javascript や type=text/css を削除する.

wp_head 関数でJSやCSSを注入すると、WordPress 側で自動的に type="text/javascript"type="text/css" という type 属性が追加されます。通常は気にすることはないと思いますが、W3C のマークアップバリデーターでは

Warning: The type attribute is unnecessary for JavaScript resources.

Warning: The type attribute for the style element is not needed and should be omitted.

のように警告が表示されます。この警告が邪魔な場合は、script_loader_tag / style_loader_tag フックを利用して type 属性を削除することができます。これらは wp_enqueue_script 関数や wp_enqueue_style 関数で読み込まれたスクリプトやスタイルファイルを注入する際の HTML タグに対してフィルターすることができます。

<?php
add_filter( 'script_loader_tag', function( $tag ) {
    return preg_replace( "/type=['\"]text\/(javascript|css)['\"] /", '', $tag );
});

add_filter( 'style_loader_tag', function( $tag ) {
    return preg_replace( "/type=['\"]text\/(javascript|css)['\"] /", '', $tag );
});

body 要素に [投稿タイプ]-[投稿スラッグ] クラスを付与する

CSS でページ毎に切り替えを行う際に、body 要素に固有のクラス名を付与すると便利なことがあります。WordPress で用意されている body_class 関数を使用すると body 要素にクラスが自動的に付与されますが、スラッグ名に関しては付与されません。そこで body_class フックに対してフィルターフックを追加すると、body 要素に付与するクラスにスラッグ名を追加することができます。

<?php
add_filter( 'body_class', function( $classes ) {
    global $post;
    if ( isset( $post ) ):
        $classes[] = $post->post_type . '-' . $post->post_name;
    endif;
    return $classes;
} );

テーマサポートを有効にする

デフォルトでは投稿や固定ページのアイキャッチ画像は無効化されています。またタイトルタグを wp_head 関数で出力するのも無効化されています。これらを有効にするには、after_setup_theme フックにアクションを追加して、その中で add_theme_support 関数を呼ぶことで有効化することができます。

<?php
add_action( 'after_setup_theme', function() {
    // アイキャッチを有効にする.
    add_theme_support( 'post-thumbnails' );

    // タイトルタグをwp_head関数で出力する.
    add_theme_support( 'title-tag' );
} );

タイトルのセパレーターを"-"から"|"へ変更する.

wp_head 関数でタイトルを出力するときに、デフォルトではタイトルのセパレーターが "-" になっています。これを変更するには document_title_separator フックを利用します。

add_filter( 'document_title_separator', function( $sep ) {
    return '|';
} );

フロントページのタイトル出力を [サイト名] | [キャッチフレーズ] から [サイト名] に変更する.

wp_head 関数でタイトルを出力するときに、トップページに関してはデフォルトで『 [サイト名] – [キャッチフレーズ]』のように出力されます。ここのキャッチフレーズ部分を削除したい場合は、document_title_parts フックを利用します。

<?php
add_filter( 'document_title_parts', function( $title ) {
    if ( is_front_page() ) unset( $title['tagline'] );
    return $title;
} );

meta-description と canonical を wp_head 関数で出力する.

meta 要素の description や canonical を wp_head 関数で出力したい場合は、wp_head フックでアクションを追加してページの種類によって出力内容を分岐させます。例えばこのような感じになります。

<?php
add_action( 'wp_head', function() {
    global $page, $paged, $wp_query;

    // meta description.
    if ( is_front_page() ):
        echo '<meta name="description" content="' . get_bloginfo( 'description' ) . '">';
    else:
        echo '<meta name="description" content="' . get_the_title() . ' | ' . get_bloginfo( 'description' ) . '">';
    endif;

    // canonical用タグの出力.
    if ( is_front_page() ):
        $canonical_url = home_url( '/' );
    elseif ( is_tag() ):
        $canonical_url = get_tag_link( get_query_var( 'tag_id' ) );
    elseif ( is_category() ):
        $canonical_url = get_category_link( get_query_var( 'cat' ) );
    elseif ( is_page() || is_single() ):
        $canonical_url = get_permalink();
    elseif ( is_search() ):
        $canonical_url = home_url() . '?s=' . urlencode( get_search_query() );
    else:
        $canonical_url = null;
    endif;
    if ( ! $canonical_url ) return;

    // ページネーションがある場合
    if ( $paged >= 2 || $page >= 2 ):
        $canonical_url = $canonical_url . 'page/'.max( $paged, $page ).'/';
    endif;
    echo '<link rel="canonical" href="' . $canonical_url . '">';

    // 前後ページがある場合は prev/next を出力する.
    $max_page = $wp_query->max_num_pages;
    if ( ! $paged ):
        $paged = 1;
        $nextpage = intval( $paged ) + 1;
    endif;
    if ( ! is_singular() && ( $nextpage <= $max_page ) ):
    echo '<link rel="next" href="' . next_posts( $max_page, false ) . '"';
    endif;
    if ( ! is_singular() && $paged > 1 ):
    echo '<link rel="prev" href="' . previous_posts( false ) . '" />';
    endif;
}, 1 );

デフォルトで注入される jQuery 1.12.4 を解除し、別のバージョンを wp_head に注入する

WordPress では wp_head 関数によって、デフォルトで ver.1.12.4 の jQuery が読み込まれるように script 要素が出力されます。そのため、jQuery のバージョンを変えたい場合は、wp_enqueue_scripts フックを用いてすでに登録されている jQuery を解除し、別のバージョンの jQuery を登録する必要があります。次の例では、ver.3.3.1 の jQuery を wp_head 関数で出力させています。

<?php
add_action( 'wp_enqueue_scripts', function() {
    wp_deregister_script( 'jquery' );
    wp_register_script( 'jquery', 'https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js', array(), '2.0.3' );
    wp_enqueue_script( 'jquery' );
}, 100 );

投稿タイプごとに表示件数を変更する

カスタム投稿タイプをいくつか追加すると、一覧表示する際に投稿タイプごとに表示件数を変えたい場合があります。このときは pre_get_posts フックを利用します。

<?php
add_action( 'pre_get_posts', function( $query ) {
  // 管理画面やメインクエリの場合は何もしない.
    if ( is_admin() || ! $query->is_main_query() ) return;

  // 管理画面やメインクエリ以外、つまり new WP_Query した場合などで適用される.
    if ( $query->is_archive( 'XXXXXX' ) ):
        $query->set( 'posts_per_page', '5' );
    elseif ( $query->is_archive( 'YYYYYY' ) ):
        $query->set( 'posts_per_page', '10' );
    endif;
} );

XXXXXXYYYYYY にはカスタム投稿タイプのスラッグ名が入ります。

抜粋の文字数を変更する

抜粋の文字数を変更するには excerpt_length フックを利用します。例えばフロントページの場合のみ30文字に制限する場合はこのようになります。

<?php
add_filter( 'excerpt_length', function( $length ) {
    return is_front_page() ? 50 : $length;
}, 999 );

本文の最初の画像を投稿のアイキャッチ画像に設定する

publish_post フックを利用すると、投稿が公開されたときに、アイキャッチ画像がセットされておらず、なおかつ本文に画像が挿入されていれば、最初の画像をアイキャッチ画像に設定することができます。

<?php
add_action( 'publish_post', function( $post_id, $post ) {
    if ( has_post_thumbnail( $post_id ) ) return;

    $matches = array();
    // preg_match_all( '/<\s*img [^\>]*src\s*=\s*[\""\']?([^\""\'>]*)/i', $post->post_content, $matches );

    // WPのメディアギャラリーから追加された画像であれば wp-image-XXX というように
    // アイキャッチ画像の ID がクラスとして付与されているため、この ID を探す.
    preg_match_all( '/<\s*img [^\>]*class\s*=\s*[\""\']?([^\""\'>]*)/i', $post->post_content, $matches );

    if ( count( $matches ) ):
        foreach ($matches[0] as $image):
            preg_match( '/wp-image-([\d]*)/i', $image, $thumb_id );
            if ( $thumb_id ):
                $thumb_id = $thumb_id[1];
                update_post_meta( $post_id, '_thumbnail_id', $thumb_id );
                break;
            endif;
        endforeach;
    endif;
}, 10, 2 );

Contact Form 7 の JS/CSS を特定ページでのみ読み込む

コンタクトフォームの管理で Contact Form 7を利用している方は多いと思います。ただ Contact Form 7 はデフォルトでは全ページに JS/CSS を注入してしまいます。フォームがないページでは無駄なリソースを読み込んでいることになるため、これを Contact Form 7 で定義されている wpcf7_load_js フィルターフックと wpcf7_load_css フィルターフックを利用して改善します。

Contact Form 7 では wp_enqueue_scriptswpcf7_do_enqueue_scripts という関数をフックしています。この関数内では wpcf7_load_js / wpcf7_load_css 関数が呼ばれ、true が返されると wp_enqueue_script / wp_enqueue_style 関数でスクリプトとスタイルが注入されるようになっています。wpcf7_load_js / wpcf7_load_css 関数では

function wpcf7_load_js() {
    return apply_filters( 'wpcf7_load_js', WPCF7_LOAD_JS );
}

のようにフィルターフックが用意されているので、ここに関数をフックさせればいいことになります。例えばテーマの functions.php

<?php
add_filter( 'wpcf7_load_js', function ( $wpcf7_load_js ) { return is_page( 'inquiry' ); } );
add_filter( 'wpcf7_load_css', function ( $wpcf7_load_css ) { return is_page( 'inquiry' ); } );

のようにしておくと、inquiry というスラッグをもったページでだけスクリプト・スタイルファイルを読み込むことができるようになります。複数ページある場合は、is_page 関数が配列を受け取ることを利用して、is_page( 'inquiry' )is_page( ['top', 'inquiry'] ); に変更します。

投稿一覧ページで投稿をランダムに表示させる

WordPress では投稿一覧ページでの並び順が日付順になっています。サイトによってはこれをランダムに表示させたい場面があるかと思います。この場合はまず init アクションでセッションを開始する関数をフックさせ、posts_orderby フィルターで投稿をランダムに取得するようにします。

<?php

add_action( 'init', function() {
    session_name( 'seed' );
    session_start();
} );

add_filter( 'posts_orderby', function( $orderby, $query ) {
    if ( ! $query->is_main_query() ) {
        return $orderby;
    }

    $paged = 0 === get_query_var( 'paged', 0 ) ? 1 : get_query_var( 'paged', 1 );
    $seed = isset( $_SESSION['seed'] ) ? $_SESSION['seed'] : null;

    if ( ( empty( $seed ) || 1 === $paged ) ) {
        $seed = rand();
        $_SESSION['seed'] = $seed;
    }

    $orderby = 'RAND(' . $seed. ')';
    return $orderby;
}, 10, 2 );

ログインしているユーザー名をクエリパラメータに付与する

ログインユーザーごとの閲覧状況などのアクセス解析を行う際に、URLにユーザーを識別するようなクエリパラメータを付与することで、Google Analytics 側でよしなにフィルターをかけることができます。nicename というカスタムクエリを追加して、ユーザーがログイン状態かつ nicename クエリが空の場合に add_query_vars でクエリを追加してリダイレクトしてやれば、ユーザー識別子を URL に付与することができます。

<?php

add_filter(
    'query_vars',
    function( array $public_query_vars ) {
        $public_query_vars[] = 'nicename';
        return $public_query_vars;
    }
);

add_action(
    'template_redirect',
    function () {
        global $wp;
        if ( is_user_logged_in() && ! get_query_var( 'nicename' ) ) {
            $user = wp_get_current_user();
            wp_safe_redirect( add_query_arg( array( 'nicename' => $user->user_nicename ), home_url( $wp->request ) ) );
            exit();
        }
    }
);

検索結果をハイライト表示し、ヒットした単語の前後を表示させる

検索結果一覧ページで、検索した単語にヒットした部分にハイライトを入れたいような場合を考えます。検索結果一覧を表示させるテンプレートは search.php ですが、このテンプレート内で the_title フィルターと the_content フィルターにハイライト表示させるような関数をフックさせるようにします。

投稿内で more タグ <!--more--> を利用している場合は、the_content フィルターについて一つ注意するべきことがあります。それは the_content フィルターにフックさせる関数に渡される引数には、more タグ <!--more--> の前までしか渡ってこないということです。つまりデフォルトのままだと、投稿本文の more タグ以降に検索した単語が含まれている場合に、the_content フィルターを利用して検索結果をハイライト表示させることができません。

この挙動はグローバル変数 $more の値に由来します。WordPress では is_single()is_page()true でないようなページでは、wp-includes/class-wp.php で定義されている register_globals メソッド内にてグローバル変数の $more0 にセットされます。$more0 だと、wp-includes/post-template.php で定義されている get_the_content 関数内で、more タグ以前の部分だけが返されるようになっています。

そこでまずは search.php 内でグローバル変数 $more1 に上書きします。あとは以下のように the_titlethe_content フィルターにフックさせて the_title 関数、the_content 関数を呼び出すことで検索対象の単語に search-highlight というクラスを付与して、検索結果をハイライト表示させることができます。

<?php
// search.php

global $more;
$more = 1;

add_filter(
    'the_title',
    function ( string $title ) {
        if ( ! is_search() ) {
            return $title;
        }
        $keys    = implode( '|', array_filter( explode( ' ', get_search_query() ) ) );
        $pattern = '/(' . $keys . ')/iu';
        return preg_replace( $pattern, '<strong class="search-highlight">\0</strong>', $title );
    }
);

add_filter(
    'the_content',
    function ( string $content ) {
        if ( ! is_search() ) {
            return $content;
        }
        $count   = 100; // 検索した単語の前後の100文字を表示させます.
        $content = wp_strip_all_tags( $content );
        $keys    = implode( '|', array_filter( explode( ' ', get_search_query() ) ) );
        $pattern = '/(' . $keys . ')/iu';
        preg_match( $pattern, $content, $matches, PREG_OFFSET_CAPTURE );

        if ( ! ( isset( $matches[0] ) && isset( $matches[0][1] ) ) ) {
            return $content;
        }

        $start = mb_strlen( substr( $content, 0, $matches[0][1] ), 'utf-8' );
        return preg_replace( $pattern, '<strong class="search-highlight">\0</strong>', mb_substr( $content, max( $start - $count, 0 ), $count * 2 ) );
    }
);

ブラウザの言語に合わせてクエリパラメータを付与してリダイレクトさせる

$_SERVER['HTTP_ACCEPT_LANGUAGE'] 変数を見ることで、ユーザーが利用しているブラウザでどのような言語が設定されているかを取得することができます。これを利用して、ブラウザが日本語以外の言語を優先的に設定している場合に、?lang=en のようなクエリパラメータを付与することができます。このとき利用するのは parse_request です。このアクションフックではリクエストURLを元に $wp 変数がセットされているため、必要なクエリパラメータを付与してからリダイレクトする、といった操作が可能になります。

add_action(
    'parse_request',
    function( $wp ) {
        $lang_list = array_reduce(
            explode( ',', $_SERVER['HTTP_ACCEPT_LANGUAGE'] ),
            function( $carry, $lang ) {
                list( $lang, $q ) = array_map( fn( $v ) => trim( $v ), explode( ';q=', trim( $lang ) ) ) + array( '', 1 );
                $carry[ "$lang" ] = (float) $q;
                return $carry;
            },
            array()
        );

        arsort( $lang_list );

        $user_lang = array_keys( $lang_list );

        if ( preg_match( '/^ja/', $user_lang[0] ) ) {
            return;
        }

        $query_var = $_GET['lang'] ?? '';

        if ( 'en' !== $query_var ) {
            wp_safe_redirect( add_query_arg( array( 'lang' => 'en' ), home_url( $wp->request ) ) );
            exit();
        }
    }
);

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です