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

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



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

[adsense]

Contents

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 );

固定ページの編集画面でビジュアルエディタを禁止する

制作現場では固定ページに関しては HTML を直に書くことが多いと思います。このような場合に固定ページの編集画面でビジュアルエディタを無効化するには user_can_richedit フックを利用します。

<?php
add_filter( 'user_can_richedit', function( $wp_rich_edit ) {
  return get_post_type() === 'page' ? false : $wp_rich_edit;
} );

ここでは投稿タイプが page、つまり固定ページの場合に false を返してビジュアルエディタを無効化しています。

管理画面で「投稿」を非表示にする

カスタム投稿をメインで利用するようなサイトの場合、デフォルトの「投稿」が不要になることがあります。これを管理画面の左サイドメニューで表示させないようにしたい場合は、admin_menu フックを利用します。

<?php
add_action( 'admin_menu', function() {
    global $menu;
    unset( $menu[5] );
} );

管理画面のカスタム投稿タイプの一覧表示でアイキャッチ画像を表示させ、コメント欄を非表示にする

管理画面の一覧表示では、デフォルトではタイトル、コメント、日付が表示されます。ここに何か別の項目を追加したい場合は manage_XXXXXX_posts_columns フックと manage_XXXXXX_posts_custom_column フックを利用します。ここで XXXXXX にはカスタム投稿タイプのスラッグ名が入ります。例えばコメント欄を非表示にし、アイキャッチ画像をタイトルの前に表示させる場合は次のようになります。

<?php
add_filter( 'manage_XXXXXX_posts_columns' , function( $columns ) {
  // コメント欄を非表示にする.
    unset( $columns['comments'] );

    $new_columns = [];
    foreach ( $columns as $column_name => $column_display_name ):
        if ( $column_name == 'title' ):
            $new_columns['thumbnail'] = __('Thumbnail');
        endif;
        $new_columns[ $column_name ] = $column_display_name;
    endforeach;
    return $new_columns;
} );

add_action( 'manage_XXXXXX_posts_custom_column' , function( $column, $post_id ) {
    switch ( $column ):
        case 'thumbnail':
            $thumbnail = get_the_post_thumbnail( $post_id, 'medium' );
            if ( isset( $thumbnail ) && $thumbnail ):
                echo $thumbnail;
            else:
                echo __('No thumbnail');
            endif;
            break;
    endswitch;
}, 10, 2 );

add_filter( 'manage_XXXXXX_posts_columns, ...) の方では一覧のカラムにサムネイル用のカラムを追加しています。そして add_action( 'manage_XXXXXX_posts_custom_column', ...) の方で、そのカラムに何を echo させるかを記述しています。ここではアイキャッチ画像を表示するようにしていますが、add_action( 'manage_XXXXXX_posts_custom_column', ...) で指定する関数の引数には $post_id が渡ってくるため、カスタムフィールドをはじめとした様々な情報を表示させることができます。Advanced Custom Field などのプラグインを利用してカスタムフィールドを設定している場合は、そのフィールドを表示させるようにしておくと、投稿を編集するユーザーの利便性を上げることができます。

固定ページの一覧表示でスラッグとページパスを表示する

固定ページを作成していると、スラッグやページパスをページ一覧から確認したい場合があります。デフォルトでは「クイック編集」をクリックしないとスラッグを確認できませんが、manage_pages_columns フックと manage_pages_custom_column フックに次のような関数を登録することで、一覧に表示させることができます。

<?php
add_filter( 'manage_pages_columns' , function( $columns ) {
    $new_columns = [];
    foreach ( $columns as $column_name => $column_display_name ):
        $new_columns[ $column_name ] = $column_display_name;
        if ( $column_name == 'title' ):
            $new_columns['slug'] = 'スラッグ';
            $new_columns['path'] = 'ページパス';
        endif;
    endforeach;
    return $new_columns;
} );

add_action( 'manage_pages_custom_column' , function( $column, $post_id ) {
    switch ( $column ):
        case 'slug':
            echo get_post( $post_id )->post_name;
            break;
        case 'path':
            $ancestors = array();
            $current_post_id = $post_id;
            while ( $current_post_id !== 0 ):
                array_unshift( $ancestors, $current_post_id );
                $current_post_id = wp_get_post_parent_id( get_post( $current_post_id ) );
            endwhile;
            $path = array_reduce( $ancestors, function( $carry, $id ) {
                return $carry . '/' . get_post( $id )->post_name;
            }, '' );
            echo '<a href="' . esc_attr( home_url() . $path ) . '" target="_blank">' . esc_attr( $path ) . '</a>';
            break;
    endswitch;
}, 10, 2 );

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

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 );

管理画面の一覧表示にスタイルを注入する

manage_XXXXXX_posts_columnsmanage_pages_columns フックでカラムを追加したときに、その幅を変更したいことがあると思います。こうした場合には、admin_enqueue_scripts フックでスタイルを注入することができます。例えば上の例のようにアイキャッチ画像を追加した場合には、次のようにするとアイキャッチ画像の幅を 150px にすることができます。

<?php
add_action( 'admin_enqueue_scripts', function() {
    echo '<style>
    .column-thumbnail {
        width: 150px;
    }
    .column-thumbnail img {
        max-width: 100%;
        height: auto;
        max-height: 150px;
        object-fit: contain;
    }
    </style>'.PHP_EOL;
} );

一覧テーブルでは column-[カラム名] というクラスが各カラムに付与されるため、上記のスラッグやページパスを追加する例の場合には .column-slug.column-path というセレクタに対して CSS を書くことで、カラムのスタイルを上書きすることができます。

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'] ); に変更します。

新規投稿・投稿編集時に何らかの処理をしたい

新規投稿時、投稿編集時に主役となる関数は wp-includes/post.php で定義されている wp_insert_post 関数です。ここにはいくつかのアクションフック・フィルターフックが用意されています。これらをコメント文と前後の制御構文とともに抜き出してみます。

<?php
// wp-includes/post.php でのフィルター・アクション

...

/**
 * Filters whether the post should be considered "empty".
 *
 * The post is considered "empty" if both:
 * 1. The post type supports the title, editor, and excerpt fields
 * 2. The title, editor, and excerpt fields are all empty
 *
 * Returning a truthy value to the filter will effectively short-circuit
 * the new post being inserted, returning 0. If $wp_error is true, a WP_Error
 * will be returned instead.
 *
 * @since 3.3.0
 *
 * @param bool  $maybe_empty Whether the post should be considered "empty".
 * @param array $postarr     Array of post data.
 */
if ( apply_filters( 'wp_insert_post_empty_content', $maybe_empty, $postarr ) ) {
...
}

...

/**
 * Filters the post parent -- used to check for and prevent hierarchy loops.
 *
 * @since 3.1.0
 *
 * @param int   $post_parent Post parent ID.
 * @param int   $post_ID     Post ID.
 * @param array $new_postarr Array of parsed post data.
 * @param array $postarr     Array of sanitized, but otherwise unmodified post data.
 */
$post_parent = apply_filters( 'wp_insert_post_parent', $post_parent, $post_ID, compact( array_keys( $postarr ) ), $postarr );

...

if ( 'attachment' === $post_type ) {
  /**
   * Filters attachment post data before it is updated in or added to the database.
   *
   * @since 3.9.0
   *
   * @param array $data    An array of sanitized attachment post data.
   * @param array $postarr An array of unsanitized attachment post data.
   */
  $data = apply_filters( 'wp_insert_attachment_data', $data, $postarr );
} else {
  /**
   * Filters slashed post data just before it is inserted into the database.
   *
   * @since 2.7.0
   *
   * @param array $data    An array of slashed post data.
   * @param array $postarr An array of sanitized, but otherwise unmodified post data.
   */
  $data = apply_filters( 'wp_insert_post_data', $data, $postarr );
}

...

if ( $update ) {
  /**
   * Fires immediately before an existing post is updated in the database.
   *
   * @since 2.5.0
   *
   * @param int   $post_ID Post ID.
   * @param array $data    Array of unslashed post data.
   */
  do_action( 'pre_post_update', $post_ID, $data );
  ...
}

...

if ( 'attachment' !== $postarr['post_type'] ) {
  wp_transition_post_status( $data['post_status'], $previous_status, $post );
} else {
  if ( $update ) {
    /**
     * Fires once an existing attachment has been updated.
     *
     * @since 2.0.0
     *
     * @param int $post_ID Attachment ID.
     */
    do_action( 'edit_attachment', $post_ID );
    $post_after = get_post( $post_ID );

    /**
     * Fires once an existing attachment has been updated.
     *
     * @since 4.4.0
     *
     * @param int     $post_ID      Post ID.
     * @param WP_Post $post_after   Post object following the update.
     * @param WP_Post $post_before  Post object before the update.
     */
    do_action( 'attachment_updated', $post_ID, $post_after, $post_before );
  } else {

    /**
     * Fires once an attachment has been added.
     *
     * @since 2.0.0
     *
     * @param int $post_ID Attachment ID.
     */
    do_action( 'add_attachment', $post_ID );
  }

  return $post_ID;
}

if ( $update ) {
  /**
   * Fires once an existing post has been updated.
   *
   * @since 1.2.0
   *
   * @param int     $post_ID Post ID.
   * @param WP_Post $post    Post object.
   */
  do_action( 'edit_post', $post_ID, $post );
  $post_after = get_post($post_ID);

  /**
   * Fires once an existing post has been updated.
   *
   * @since 3.0.0
   *
   * @param int     $post_ID      Post ID.
   * @param WP_Post $post_after   Post object following the update.
   * @param WP_Post $post_before  Post object before the update.
   */
  do_action( 'post_updated', $post_ID, $post_after, $post_before);
}

/**
 * Fires once a post has been saved.
 *
 * The dynamic portion of the hook name, `$post->post_type`, refers to
 * the post type slug.
 *
 * @since 3.7.0
 *
 * @param int     $post_ID Post ID.
 * @param WP_Post $post    Post object.
 * @param bool    $update  Whether this is an existing post being updated or not.
 */
do_action( "save_post_{$post->post_type}", $post_ID, $post, $update );

/**
 * Fires once a post has been saved.
 *
 * @since 1.5.0
 *
 * @param int     $post_ID Post ID.
 * @param WP_Post $post    Post object.
 * @param bool    $update  Whether this is an existing post being updated or not.
 */
do_action( 'save_post', $post_ID, $post, $update );

/**
 * Fires once a post has been saved.
 *
 * @since 2.0.0
 *
 * @param int     $post_ID Post ID.
 * @param WP_Post $post    Post object.
 * @param bool    $update  Whether this is an existing post being updated or not.
 */
do_action( 'wp_insert_post', $post_ID, $post, $update );

さらに wp_transition_post_status 関数では3つのアクションフックが用意されています。

<?php
// wp-includes/post.php で定義されている wp_transition_post_status 関数

/**
 * Fires actions related to the transitioning of a post's status.
 *
 * When a post is saved, the post status is "transitioned" from one status to another,
 * though this does not always mean the status has actually changed before and after
 * the save. This function fires a number of action hooks related to that transition:
 * the generic {@see 'transition_post_status'} action, as well as the dynamic hooks
 * {@see '$old_status_to_$new_status'} and {@see '$new_status_$post->post_type'}. Note
 * that the function does not transition the post object in the database.
 *
 * For instance: When publishing a post for the first time, the post status may transition
 * from 'draft' – or some other status – to 'publish'. However, if a post is already
 * published and is simply being updated, the "old" and "new" statuses may both be 'publish'
 * before and after the transition.
 *
 * @since 2.3.0
 *
 * @param string  $new_status Transition to this post status.
 * @param string  $old_status Previous post status.
 * @param WP_Post $post Post data.
 */
function wp_transition_post_status( $new_status, $old_status, $post ) {
    /**
     * Fires when a post is transitioned from one status to another.
     *
     * @since 2.3.0
     *
     * @param string  $new_status New post status.
     * @param string  $old_status Old post status.
     * @param WP_Post $post       Post object.
     */
    do_action( 'transition_post_status', $new_status, $old_status, $post );

    /**
     * Fires when a post is transitioned from one status to another.
     *
     * The dynamic portions of the hook name, `$new_status` and `$old status`,
     * refer to the old and new post statuses, respectively.
     *
     * @since 2.3.0
     *
     * @param WP_Post $post Post object.
     */
    do_action( "{$old_status}_to_{$new_status}", $post );

    /**
     * Fires when a post is transitioned from one status to another.
     *
     * The dynamic portions of the hook name, `$new_status` and `$post->post_type`,
     * refer to the new post status and post type, respectively.
     *
     * Please note: When this action is hooked using a particular post status (like
     * 'publish', as `publish_{$post->post_type}`), it will fire both when a post is
     * first transitioned to that status from something else, as well as upon
     * subsequent post updates (old and new status are both the same).
     *
     * Therefore, if you are looking to only fire a callback when a post is first
     * transitioned to a status, use the {@see 'transition_post_status'} hook instead.
     *
     * @since 2.3.0
     *
     * @param int     $post_id Post ID.
     * @param WP_Post $post    Post object.
     */
    do_action( "{$new_status}_{$post->post_type}", $post->ID, $post );
}

ここまでのフックで、主に使用するのは以下のフックになると思います。

  • データベースへの保存前
    • wp_insert_post_data – 投稿がデータベースに挿入される前に投稿データをフィルターするフック
    • wp_insert_attachment_datawp_insert_post_data のメディア版
    • pre_post_update – すでに存在している投稿がデータベースで更新される直前に発火するアクション
  • データベースへの保存後
    • transition_post_status – 投稿がある状態から別の状態へ遷移したときに発火するアクション
    • {$old_status}_to_{$new_status} – 投稿がある状態から別の状態へ遷移したときに発火するアクション
    • {$new_status}_{$post->post_type} – 投稿がある状態から別の状態へ遷移したときに発火するアクション
    • edit_attachment – 存在しているメディアが更新されたら発火するアクション
    • attachment_updated – 存在しているメディアが更新されたら発火するアクション
    • add_attachment – メディアが新規に追加された直後に発火するアクション
    • edit_post – すでに存在している投稿(メディアを含む)が更新されたら発火するアクション
    • post_updated – すでに存在している投稿(メディアを含む)が更新されたら発火するアクション
    • save_post_{$post->post_type} – 投稿(メディアを含む)が保存されたら発火するアクション
    • save_post – 投稿(メディアを含む)が保存されたら発火するアクション
    • wp_insert_post – 投稿(メディアを含む)が保存されたら発火するアクション

save_post_{$post->post_type}save_postwp_insert_postはどれも同じ引数を取り順番に呼ばれるため、どれを使用しても同じ結果になります。ただし v3.7.0 から追加された save_post_{$post->post_type} は投稿タイプで限定されるため、フック関数内で if などによる制御構文を書かずに済むという利点があります。特に理由がない限りはこのアクションにフックさせるのが無難だと思われます。

例えば book というカスタム投稿タイプの新規投稿・投稿編集時に追加の処理をしたい場合は

<?php
add_action( 'save_post_book', function( $post_ID, $post, $update ) {
  // ここで追加の処理を行う
} );

のように関数をフックさせます。

テーマカスタマイザーの設定項目を非表示にする

テーマカスタマイザーで設定項目を加える際に、デフォルトの項目を非表示にしたいことがあります。この場合は customize_register フックを利用します。

<?php
add_action( 'customize_register', function( $wp_customize ) {
    $wp_customize->remove_section( 'title_tagline' );
    $wp_customize->remove_section( 'colors' );
    $wp_customize->remove_section( 'header_image' );
    $wp_customize->remove_section( 'background_image' );
    $wp_customize->remove_section( 'static_front_page');
    $wp_customize->remove_section( 'custom_css');
    $wp_customize->remove_section( 'add_menu');
    remove_action( 'customize_register', array( $wp_customize->nav_menus, 'customize_register' ), 11 );
} );

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

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 ) );
    }
);

投稿内容が半角ひらがなを含む場合にプレビュー画面でハイライト表示する

WordPress ではプレビュー画面かどうかを判定するために is_preview という関数が用意されています。この関数を the_content フィルター内で使用することで、投稿本文が半角のひらがなを含む場合にそれをハイライト表示することができます。

add_filter('the_content', function( string $content ) {
    if ( is_preview() ) {
        $content = preg_replace( '/([ヲ-゚]+)/u', '<span style="background:yellow">$1</span>', $content );
    }

    return $content;
});

全角英数字を含む場合もハイライト表示させたい場合は、次のようにすることで実現できます。

add_filter('the_content', function( string $content ) {
    if ( is_preview() ) {
        $content = preg_replace( '/([ヲ-゚0-9A-Za-z]+)/u', '<span style="background:yellow">$1</span>', $content );
    }

    return $content;
});

さらに機種依存文字についてもハイライト表示させたい場合は次のようになります。

add_filter('the_content', function( string $content ) {
    if ( is_preview() ) {
        $content = preg_replace( '/([ヲ-゚0-9A-Za-z①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑯⑰⑱⑲⑳ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩ㍉㌔㌢㍍㌘㌧㌃㌶㍑㍗㌍㌦㌣㌫㍊㌻㎜㎝㎞㎎㎏㏄㎡㍻〝〟№㏍℡㊤㊥㊦㊧㊨㈱㈲㈹㍾㍽㍼∮∟⊿纊褜鍈銈蓜俉炻昱棈鋹曻彅丨仡仼伀伃伹佖侒侊侚侔俍偀倢俿倞偆偰偂傔僴僘兊兤冝冾凬刕劜劦勀勛匀匇匤卲厓厲叝﨎咜咊咩哿喆坙坥垬埈埇﨏塚增墲夋奓奛奝奣妤妺孖寀甯寘寬尞岦岺峵崧嵓﨑嵂嵭嶸嶹巐弡弴彧德忞恝悅悊惞惕愠惲愑愷愰憘戓抦揵摠撝擎敎昀昕昻昉昮昞昤晥晗晙晴晳暙暠暲暿曺朎朗杦枻桒柀栁桄棏﨓楨﨔榘槢樰橫橆橳橾櫢櫤毖氿汜沆汯泚洄涇浯涖涬淏淸淲淼渹湜渧渼溿澈澵濵瀅瀇瀨炅炫焏焄煜煆煇凞燁燾犱犾猤猪獷玽珉珖珣珒琇珵琦琪琩琮瑢璉璟甁畯皂皜皞皛皦益睆劯砡硎硤礰礼神祥禔福禛竑竧靖竫箞精絈絜綷綠緖繒罇羡羽茁荢荿菇菶葈蒴蕓蕙蕫﨟薰蘒﨡蠇裵訒訷詹誧誾諟諸諶譓譿賰賴贒赶﨣軏﨤逸遧郞都鄕鄧釚釗釞釭釮釤釥鈆鈐鈊鈺鉀鈼鉎鉙鉑鈹鉧銧鉷鉸鋧鋗鋙鋐﨧鋕鋠鋓錥錡鋻﨨錞鋿錝錂鍰鍗鎤鏆鏞鏸鐱鑅鑈閒隆﨩隝隯霳霻靃靍靏靑靕顗顥飼餧館馞驎髙髜魵魲鮏鮱鮻鰀鵰鵫鶴鸙黑ⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹ¬¦'"]+)/u', '<span style="background:yellow">$1</span>', $content );
    }

    return $content;
});

投稿のタイトルがセットされていない場合にエラーを表示させる

投稿のタイトルが空になっていてセットされていない場合に投稿を保存できないようにするには wp_insert_post_empty_content フィルターを使います。このフィルターは wp-includes/post.php で定義されている wp_insert_post 関数内で使用されており、投稿の新規作成時や更新時、自動保存時に、投稿のタイトルや内容が空かどうかを確認しています。このフィルター内で投稿内容をチェックし、タイトルが空の場合に wp_die を呼び出すことで、タイトルがセットされていないときにエラーを表示することができます。

add_filter( 'wp_insert_post_empty_content', function( bool $maybe_empty, array $postarr ): bool {
    if ( '' === $postarr['post_title'] ) {
        wp_die( 'タイトルが空になっています。' );
        return false;
    }
    return $maybe_empty;
}, 10, 2 );

メディアの代替テキストがセットされていないときに警告を表示する

WordPress のメディアライブラリにアップロードした添付ファイルは、メディアマネージャーで代替テキストやタイトル、キャプションなどを設定することができます。メディアマネージャーは wp-includes/media-template.php で定義されている wp_print_media_templates 関数で出力されます。この関数の最後に print_media_templates というアクションフックが用意されているため、メディアマネージャーをカスタマイズしたい場合はこのアクションフックを利用することになります。

ここでは代替テキスト入力用の input 要素に対して placeholder 属性をもたせて、:placeholder-shown 擬似クラスを利用して入力欄が空の場合(つまりプレースホルダーが表示されている場合)に before 擬似要素をもたせることで警告を表示させています。

add_action( 'print_media_templates', function() {
    $html = <<<HTML
    <style>span:has(#attachment-details-two-column-alt-text:placeholder-shown):before {
        content: "代替テキスト(ALT)が設定されていません。";
        display: block;
        width: 100%;
        color: red;
        font-weight: bold;
        text-align: right;
    }</style>
    <script>
    function addPlaceholder() {
        const input = document.getElementById('attachment-details-two-column-alt-text');
        if (input && !input.hasAttribute('placeholder')) input.setAttribute('placeholder', ' ');
        setTimeout(addPlaceholder, 1000);
    }
    window.onload = addPlaceholder;
    </script>
    HTML;
    echo $html;
}, PHP_INT_MAX );

コメントを残す

メールアドレスが公開されることはありません。