【WordPress入門】アクションフック・フィルターフックを使い倒してWordPressをカスタマイズする
前回の記事「【WordPress入門】アクションフックとフィルターフックを使いこなそう」で WordPress で主に使用するアクションフックとフィルターフックの一覧を紹介しましたが、今回はそれらのフックを使って WordPress をカスタマイズしたいと思います。
ここでは add_action
や add_filter
で登録する関数を無名関数にしていますが、それぞれ関数を別に定義して、その関数名を文字列として add_action
や add_filter
に渡すこともできます。独自テーマ内で利用し、remove_action
や remove_filter
することがなければ無名関数でも問題はありません。逆にプラグインや親テーマ内で利用する場合は、remove_action
や remove_filter
で登録解除することができなくなるため、無名関数を使ってはいけません。
目次
- 1 wp_head で出力される不要な要素を head 要素から削除する
- 2 wp_head でスクリプトを注入する際に付与される type=text/javascript や type=text/css を削除する.
- 3 body 要素に [投稿タイプ]-[投稿スラッグ] クラスを付与する
- 4 テーマサポートを有効にする
- 5 タイトルのセパレーターを"-"から"|"へ変更する.
- 6 フロントページのタイトル出力を [サイト名] | [キャッチフレーズ] から [サイト名] に変更する.
- 7 meta-description と canonical を wp_head 関数で出力する.
- 8 デフォルトで注入される jQuery 1.12.4 を解除し、別のバージョンを wp_head に注入する
- 9 投稿タイプごとに表示件数を変更する
- 10 抜粋の文字数を変更する
- 11 本文の最初の画像を投稿のアイキャッチ画像に設定する
- 12 Contact Form 7 の JS/CSS を特定ページでのみ読み込む
- 13 投稿一覧ページで投稿をランダムに表示させる
- 14 ログインしているユーザー名をクエリパラメータに付与する
- 15 検索結果をハイライト表示し、ヒットした単語の前後を表示させる
- 16 ブラウザの言語に合わせてクエリパラメータを付与してリダイレクトさせる
wp_head で出力される不要な要素を head 要素から削除する
デフォルトでは wp_head
関数によって多くの要素が head
要素に追加されますが、不要なものも多くあります。これらを出力されないようにするには、 remove_action
で wp_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;
} );
XXXXXX
や YYYYYY
にはカスタム投稿タイプのスラッグ名が入ります。
抜粋の文字数を変更する
抜粋の文字数を変更するには 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_scripts
に wpcf7_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
メソッド内にてグローバル変数の $more
が 0
にセットされます。$more
が 0
だと、wp-includes/post-template.php
で定義されている get_the_content
関数内で、more タグ以前の部分だけが返されるようになっています。
そこでまずは search.php
内でグローバル変数 $more
を 1
に上書きします。あとは以下のように the_title
、the_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();
}
}
);