現在、提供中の WordPress テーマ「DigiPress」に、Transient API を利用したキャッシュ機能を追加しているのですが、キャッシュ対象のひとつとして、トップページのヘッダーバナー全体も含んでいます。
ただし、このトップページのヘッダーバナー上には、バナー上に自由なコンテンツを表示するために専用のウィジェットスペースも設けているため、キャッシュを削除するタイミングは、ヘッダーバナーを別の画像やスライダーなどに変更(テーマオプションの更新)した時点に加えて、バナー上にあるウィジェットスペース内の任意のウィジェットが更新されたタイミングでもキャッシュを削除する必要がでてきました。
WordPress にはあらゆるアクションフックやフィルターが用意されているため、調べれば簡単に実装できるだろうと高を括っていたのですが、調べ方が悪いのか思っていたより「ウィジェットの更新時に対象ウィジェットがあるウィジェットスペース(対象エリアのID)を特定」するには少し手間を加える必要があったため、備忘録として残しておきます。
ウィジェットの更新を知るには?
ウィジェットを更新するタイミングで何か処理を挟みたい場合、つまり今回の要件の一つである「ウィジェット更新時に対象ウィジェットの既存のキャッシュを削除」するには、widget_update_callback というフィルターフックを利用すれば簡単です。
add_filter( 'widget_update_callback', function( $instance, $new_instance, $old_instance, $current ){
// 更新の対象となるウィジェットの固有ID : $current->id
// 自作ウィジェット側で set_transient で保存しておいたキー('this-transient-key-' . $current->id)のキャッシュを削除
delete_transient( 'this-transient-key-' . $current->id );
return $instance;
}, 10, 4 );
ちなみに、上のコードの delete_transient で指定している $current->id は、ウィジェットごとに持つ固有のIDで、WP_Widget を継承した自作ウィジェットクラスでは、$this->id とすると取得できます。
この固有のIDを利用して、set_transient でウィジェットの出力内容をデータベースに保持しておきます。
class Foo_Widget extends WP_Widget {
function __construct() {
// コンストラクタ
}
/**
* ウィジェットのフロントエンド表示
*/
public function widget( $args, $instance ) {
// キャッシュを取得
// $this->id で、表示するウィジェット固有のIDを取得できる
$cache = get_transient( 'this-transient-key-' . $this->id );
// キャッシュがあればそれを表示して終わり
if ( $cache !== false ) {
echo $cache;
return;
}
// フロントエンドのコードを書く↓
$code = $args['before_widget'];
if ( ! empty( $instance['title'] ) ) {
$code .= $args['before_title'] . apply_filters( 'widget_title', $instance['title'] ). $args['after_title'];
}
$code .= '<p>My ID is ' . $this->id . '</p>';
$code .= $args['after_widget'];
// 表示
echo $code;
// 出力内容をキャッシュに保存
set_transient( 'this-transient-key-' . $this->id, $code );
}
public function form( $instance ) {
// ここにウィジェットのフォーム
}
public function update( $new_instance, $old_instance ) {
// ここに保存処理
}
}
set_transient の引数に指定する固有のキー(名前)は、45文字以下であることに注意してください
Transients API については公式ガイドで詳しく解説されています。
ウィジェットの更新時に対象ウィジェットが任意のエリアにあるか判定する方法
さて、ここからが問題。
もう一つの要件である「ウィジェットが更新される際に、該当のウィジェットが任意のウィジェットエリアに存在するか」を判定するには、特定に必要な情報(register_sidebar の ID)が widget_update_callback フックに渡される引数のみではウィジェットエリアに関する情報は取得できません。
そこで、wp_get_sidebars_widgets という関数を利用して、現在登録されているウィジェットエリア(エリアID)と各エリア内にあるウィジェット(ウィジェットID)の一覧を取得し、更新対象となるウィジェット(ID)が、指定したエリアにあるすべてのウィジェット(ID)と比較して合致すれば、目的の処理(バナーのキャッシュを削除)をします。
add_filter( 'widget_update_callback', function( $instance, $new_instance, $old_instance, $current ){
// 登録済みのウィジェットエリアと各ウィジェットの一覧を取得
$target_widgets = wp_get_sidebars_widgets();
// そのうち、検知対象としたいウィジェットエリアのみのウィジェットに絞り込み
$target_widgets = $target_widgets[ 'register_sidebar で指定した ID をここに' ];
// 絞り込んだウィジェットエリアにあるウィジェットを順にチェック
foreach ( $target_widgets as $widget_id ) {
// 更新対象のウィジェットと対象エリア内にあるウィジェット(ID)が一致したとき
if ( $current->id === $widget_id ) {
// 任意の処理(ここではバナーキャッシュの削除)
delete_transient( 'top-banner' );
}
}
return $instance;
}, 10, 4 );
【おまけ】ウィジェット更新時に対象ウィジェットがあるエリアを特定する方法
ウィジェット更新時に、更新対象のウィジェットがどこのウィジェットエリア(register_sidebar の ID)にあるのかを知るには、以下のようにして把握できます。
add_filter( 'widget_update_callback', function( $instance, $new_instance, $old_instance, $current ){
$target_area = null;
// 登録済みのウィジェットエリアと各ウィジェットの一覧を取得
$all_widget_area = wp_get_sidebars_widgets();
// エリアごとにチェック
foreach ( $all_widget_area as $area_id => $widget_ids ) {
// 更新対象のウィジェットと現在のループ対象のエリアにあるウィジェットが一致するか順にチェック
foreach ( $widget_ids as $widget_id ) {
// 更新対象のウィジェットと対象エリア内にあるウィジェット(ID)が一致したとき
if ( $current->id === $widget_id ) {
// 現在の $area_id が更新対象ウィジェットのあるエリア(ID)となる($target_area に代入)
$target_area = $area_id;
break 2;
}
}
}
return $instance;
}, 10, 4 );
