1. ホーム
  2. WordPress

【WordPress】Block Hooks(blockHooks)の使い方|自作ブロックをテンプレートに自動挿入する方法

Share

プラグインで「SNS シェアボタン」や「関連記事一覧」のブロックを作ったとき、困るのが設置してもらう手間です。README に「投稿テンプレートを編集して、本文の下にこのブロックを追加してください」と書いても、実際にやってもらえるとは限りません。

WordPress 6.4 で入った Block Hooks(ブロックフック) は、この問題を解決する仕組みです。block.json に数行書くだけで、指定した既存ブロックの前後に、自分のブロックを自動で挿入してもらえます。この記事では、blockHooks の書き方、動く条件と動かない条件、PHP 側から細かく制御する方法までを解説します。

「このブロックの隣に置いてほしい」を宣言する

Block Hooks の考え方はシンプルです。自分のブロックの block.json に、基準にするブロック(アンカーブロック)と、その相対位置を書きます。あとは WordPress が、テンプレートやパターンの中からそのアンカーブロックを探し出し、指定した位置に自分のブロックを差し込んでくれます。

block.json
{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "webool/share-buttons",
  "title": "シェアボタン",
  "category": "widgets",
  "blockHooks": {
    "core/post-content": "after"
  },
  "render": "file:./render.php"
}

これだけで、投稿テンプレートの本文(core/post-content)の直後にシェアボタンが並ぶようになります。利用者が何も設定しなくても、プラグインを有効化した時点で配置されます。

blockHooks はオブジェクトなので、複数のアンカーを指定することもできます。

block.json
{
  "blockHooks": {
    "core/post-content": "after",
    "core/comments": "before"
  }
}

指定できる4つの位置

値挿入される位置
beforeアンカーブロックの直前(兄弟として)
afterアンカーブロックの直後(兄弟として)
firstChildアンカーブロックの中の、先頭の子ブロックとして
lastChildアンカーブロックの中の、末尾の子ブロックとして

firstChild と lastChild は、core/group や core/columns のように中に他のブロックを入れられるブロックにだけ意味があります。子を持てないブロックに指定しても何も起きません。

動く条件を先に押さえる

Block Hooks は便利ですが、何にでも使えるわけではありません。思ったとおりに動かない原因のほとんどは、この2つの条件に引っかかっています。

条件1:フックされる側は動的ブロックであること

自動挿入できるのは、render.php や render_callback で出力する動的ブロックに限られます。save 関数を持つ静的ブロックは対象外です。

理由は、静的ブロックが「保存された HTML とブロック定義が一致しているか」を検証する仕組みを持っているためです。自動で差し込まれたブロックはデータベースに保存されていないため、静的ブロックだと検証エラーの原因になります。自分のブロックを Block Hooks に対応させたいなら、動的ブロックとして作り直してください。

条件2:投稿本文には挿入されない

挿入の対象になるのは、ブロックテンプレート、テンプレートパーツ、パターンといったサイトの枠組みにあたる領域です。個々の投稿や固定ページの本文(post_content)には挿入されません。

これは仕様として妥当な線引きです。投稿本文は書き手が直接編集したデータなので、プラグインが勝手に書き換えるべきではありません。一方、テンプレートは「サイト全体の構造」なので、プラグインが拡張しても筋が通ります。

そのため、Block Hooks が実際に効くのはブロックテーマ(サイトエディターが使えるテーマ)です。クラシックテーマで single.php に the_content() と書いている環境では、挿入する先のテンプレートがブロックで出来ていないため機能しません。

PHP 側から登録する

block.json を持たない、あるいは書き換えたくない場合は、register_block_type() の引数でも指定できます。

plugin.php
<?php

add_action( 'init', 'webool_register_share_block' );

function webool_register_share_block() {
	register_block_type(
		'webool/share-buttons',
		array(
			'render_callback' => 'webool_render_share_buttons',
			// block.json の blockHooks に対応する引数
			'block_hooks'     => array(
				'core/post-content' => 'after',
			),
		)
	);
}

PHP 側の引数名は block_hooks(スネークケース)で、block.json の blockHooks(キャメルケース)と対応しています。ほかの項目と同じ命名規則です。

条件によって挿入を切り替える

block.json の指定は「常にここへ入れる」という固定の宣言です。「投稿だけに入れたい」「特定のテンプレートでは入れたくない」といった条件を付けたいときは、hooked_block_types フィルターを使います。

plugin.php
<?php

add_filter( 'hooked_block_types', 'webool_filter_hooked_blocks', 10, 4 );

/**
 * @param string[] $hooked_blocks     挿入予定のブロック名の配列
 * @param string   $position          before / after / first_child / last_child
 * @param string   $anchor_block      アンカーブロックの名前
 * @param mixed    $context           テンプレート・テンプレートパーツ・パターンの情報
 */
function webool_filter_hooked_blocks( $hooked_blocks, $position, $anchor_block, $context ) {
	// 本文の直後という条件以外は何もしない
	if ( 'core/post-content' !== $anchor_block || 'after' !== $position ) {
		return $hooked_blocks;
	}

	// 単一投稿のテンプレート以外では挿入しない
	if ( $context instanceof WP_Block_Template && 'single' !== $context->slug ) {
		$hooked_blocks = array_diff( $hooked_blocks, array( 'webool/share-buttons' ) );
	}

	return $hooked_blocks;
}

このフィルターは配列を返すので、取り除くことも、追加することもできます。配列に別のブロック名を足せば、そのブロックの block.json に blockHooks が書かれていなくても挿入されます。他社のプラグインが提供しているブロックを自分のサイトの都合で自動配置したい、といった使い方ができます。

第4引数の $context には、挿入先がテンプレートなら WP_Block_Template、パターンなら配列といった具合に、対象に応じたオブジェクトが渡ってきます。型が一定ではないので、上の例のように instanceof で確認してから使ってください。

挿入するブロックの中身を調整する

挿入されるブロックに属性を設定したい場合は、hooked_block フィルター(特定のブロックだけなら hooked_block_{$block_type})を使います。パース済みのブロック配列を受け取って、加工して返す形です。

plugin.php
<?php

add_filter( 'hooked_block_webool/share-buttons', 'webool_adjust_hooked_block', 10, 5 );

function webool_adjust_hooked_block( $parsed_block, $hooked_block_type, $position, $parsed_anchor_block, $context ) {
	// 挿入されるブロックの属性を差し替える
	$parsed_block['attrs']['align'] = 'wide';
	$parsed_block['attrs']['services'] = array( 'x', 'facebook' );

	return $parsed_block;
}

このフィルターで null を返すと、そのブロックの挿入自体が取りやめになります。hooked_block_types で配列から取り除くのと結果は同じですが、アンカーブロックの情報($parsed_anchor_block)まで見て判断できる分、細かい条件を書けます。

ユーザーが消したらどうなるか

自動挿入されたブロックは、サイトエディターで普通に削除できます。「プラグインが勝手に置いたものを消せない」ということはありません。

このとき WordPress は、アンカーブロック側に metadata.ignoredHookedBlocks という記録を残します。編集後のテンプレートのマークアップは次のようになります。

テンプレートのマークアップ
<!-- wp:post-content {"metadata":{"ignoredHookedBlocks":["webool/share-buttons"]}} /-->

この記録があると、以降その位置には再挿入されません。ユーザーが一度消したものを毎回押し戻す、という嫌な挙動を避けるための仕組みです。開発中に「一度消したら二度と出てこなくなった」と感じたら、テンプレートのコードエディターでこの記述が残っていないか確認してください。テンプレートをカスタマイズ前の状態に戻せば記録も消えます。

挿入されないときの確認手順

動的ブロックになっているか

最初に確認すべき点です。block.json に render が書かれているか、あるいは register_block_type() に render_callback を渡しているかを見てください。save.js で HTML を出力している静的ブロックのままだと、いくら blockHooks を書いても挿入されません。

アンカーブロックがテンプレートに存在するか

core/post-content を指定していても、使っているテーマのテンプレートがそのブロックを使っていなければ挿入先がありません。サイトエディターでテンプレートを開き、コードエディター表示に切り替えて、アンカーにしたブロックが本当に含まれているかを確認します。

ブロック名のスペルが合っているか

アンカーブロックの名前は 名前空間/ブロック名 の完全な形で書く必要があります。post-content ではなく core/post-content です。存在しないブロック名を書いてもエラーにはならず、静かに何も起きないだけなので気づきにくいところです。

まとめ

Block Hooks は、block.json の blockHooks に「どのブロックの、どの位置に置くか」を書くだけで、自分のブロックをテンプレートへ自動挿入してもらえる仕組みです。位置は before・after・firstChild・lastChild の4つから選びます。

使える条件は、挿入する側が動的ブロックであることと、挿入先がテンプレート・テンプレートパーツ・パターンであることの2つです。投稿本文には入りませんし、クラシックテーマでも効きません。条件を細かく制御したいときは hooked_block_types フィルターで挿入対象を出し入れし、属性まで調整したいときは hooked_block フィルターを使います。ユーザーが削除した場合は ignoredHookedBlocks に記録されて再挿入されない、という点も覚えておくと、動作を追いやすくなります。

参考ページ