Google Mapsブロックを作る過程で悩んだ件

説明

自分なりのGoogle Mapsブロックを作るべく、カスタムHTMLブロックをベースにいくつかのパラメータ要素をサイドバーに追加していく。Google Maps用のAPIキーについてはオプションデータ(wp_optionsテーブル)に格納して利用していこうと思ったのだが、その方法がさっぱりわからなかった。

core-dataのuseEntityProp

ブロックでオプションデータを使う方法がまったくわからなかったので、いろいろ検索してみるとGitHubに気になる投稿「Ability to get (and set) site options from wp_options table using useSelect()」が見つかった。このやり取りの中で「site title block」が紹介されていたので、見に行ってみる(以下は一部抜粋)。

import { useSelect } from '@wordpress/data';
import { useEntityProp, store as coreStore } from '@wordpress/core-data';

// 略

import { decodeEntities } from '@wordpress/html-entities';

// 略

export default function SiteTitleEdit( {
	attributes,
	setAttributes,
	insertBlocksAfter,
} ) {
	const { level, textAlign, isLink, linkTarget } = attributes;
	const [ title, setTitle ] = useEntityProp( 'root', 'site', 'title' );
	const { canUserEdit, readOnlyTitle } = useSelect( ( select ) => {
		const { canUser, getEntityRecord } = select( coreStore );
		const siteData = getEntityRecord( 'root', '__unstableBase' );
		return {
			canUserEdit: canUser( 'update', 'settings' ),
			readOnlyTitle: decodeEntities( siteData?.name ),
		};
	}, [] );

赤字の部分がオプションデータのブログタイトルを取得しているようなので、作成中のコードに追加してみる。

import { useEntityProp, store as coreStore } from '@wordpress/core-data';

// 略

const [ title, setTitle ] = useEntityProp( 'root', 'site', 'title' );
console.log( title );

作成中のブロックを追加してコンソールを確認すると、undefinedが2回続いた後にブログタイトルが表示され、オプションデータを取得できることが確認できた。

useEntityPropの第3パラメータは、オプションデータのキーに対応する感じなので、本来の目的である'google_maps_api_key'キーに修正してみる。

const [ apiKey, setApiKey ] = useEntityProp( 'root', 'site', 'google_maps_api_key' );
console.log( apiKey );

投稿編集ページをリロードすると、コンソールにはundefinedが繰り返し出力されるばかり。'google_maps_api_key'キーのデータは取得できていない。PHP側で何かやることがあるのではと思い、「useEntityProp」をキーにして検索すると「WordPressのカスタムブロックで、wp_optionsのデータを使う」に register_setting関数を呼び出す必要がある旨の記載があった。ブロックで投稿メタを使用する際、 register_post_meta関数を呼び出すことと同じようだ。

ということで、PHP側に次のコードを追加した。

function myblock_setting() {
	register_setting( 'options', 'google_maps_api_key', array(
		'type'              => 'string',
		'sanitize_callback' => 'esc_attr',
		'show_in_rest'      => true,
	) );
}
add_action( 'rest_api_init', 'myblock_setting' );

もう一度、投稿編集ページをリロード。コンソールに事前に登録していた内容が出力された。

オプションデータの更新

'google_maps_api_key'キーのデータを更新できるようにするため、サイドバー用にTextControlを追加。onChangeにはuseEntityPropから受け取ったsetApiKeyを指定した。

<InspectorControls key="setting">
	<PanelBody
		title={ __( 'Settings' ) }
		className="maps-embed-settings"
	>
		<TextControl
			type="text"
			className="maps-embed-control__apikey"
			label={ __( 'API Key' ) }
			value={ apiKey }
			onChange={ setApiKey }
		/>

投稿編集ページをリロードし、サイドバーを確認する。

サイドバーに「API Key」の入力ボックスが表示された

更新されるか確認するために、入力ボックスの内容を適当に編集してwp_optionsテーブルを確認。option_valueは書き換わっていない。

mysql> select * from wp_options where option_name='google_maps_api_key';
+-----------+---------------------+--------------+----------+
| option_id | option_name         | option_value | autoload |
+-----------+---------------------+--------------+----------+
|     24485 | google_maps_api_key | 1234567890   | yes      |
+-----------+---------------------+--------------+----------+

続いて「下書き保存」をクリックし、wp_optionsテーブルを確認。やはりoption_valueは書き換わっていない。

mysql> select * from wp_options where option_name='google_maps_api_key';
+-----------+---------------------+--------------+----------+
| option_id | option_name         | option_value | autoload |
+-----------+---------------------+--------------+----------+
|     24485 | google_maps_api_key | 1234567890   | yes      |
+-----------+---------------------+--------------+----------+

「公開」ボタンをクリックすると、サイドバーの上に確認用パネルが表示され、google_maps_api_keyのチェックボックスが表示される。

更新確認パネルにgoogle_maps_api_keyのチェックボックスが出現する

チェックされた状態のまま「保存」ボタンをクリック。wp_optionsテーブルを確認すると、option_valueが書き換わった。

mysql> select * from wp_options where option_name='google_maps_api_key';
+-----------+---------------------+---------------+----------+
| option_id | option_name         | option_value  | autoload |
+-----------+---------------------+---------------+----------+
|     24485 | google_maps_api_key | 1234567890ABC | yes      |
+-----------+---------------------+---------------+----------+

無事オプションデータを更新することができたので、ブロックのほかの部分を作りこむ。これで完成かと思ったのだが、オプションデータの取り扱いには落とし穴があった。

投稿者はオプションデータを取得できない!?

ここまでは管理者アカウントで確認を行っていたので、投稿者アカウントに切り替えてみる。新規追加で投稿編集ページを開き、作っているブロックを追加すると、API Keyの入力ボックスは空となり、コンソールには次のようなエラーログが出力された。

GET http://localhost/wp-json/wp/v2/settings?_locale=user 403 (Forbidden)

REST APIの呼び出しのレスポンスが「403」になっており、オプションデータが取得できていないようだ。

この理由を調べるため、エンドポイントのソースコード(wp-includes/rest-api/endpoints/class-wp-rest-settings-controller.php)を見てみる。 register_rest_route関数のパラメータでは2つのメソッドが指定され、そのどちらのpermission_callbackも同じメソッドになっていた。

public function get_item_permissions_check( $request ) {
	return current_user_can( 'manage_options' );
}

このエンドポイントはmanage_options権限を持っているユーザー、すなわち管理者のみが利用できることになる。このようなケース、いつもならフィルターが用意されているものだが、今回はそれが見当たらない。

しかたなくPHP側でインラインスクリプトを出力することを考えたが、register_rest_route関数で上書きすれば対応できるのではと思い、PHP側のrest_api_initアクションのコールバック関数を次のように変更した(赤字が追加した部分)。

function myblock_get_item_permissions_check( $request ) {
	return current_user_can( 'publish_posts' );
}

function myblock_update_item_permissions_check( $request ) {
	return current_user_can( 'manage_options' );
}

function myblock_setting() {
	register_setting( 'options', 'google_maps_api_key', array(
		'type' => 'string',
		'sanitize_callback' => 'esc_attr',
		'show_in_rest' => true,
	) );

	$controller = new WP_REST_Settings_Controller;
	register_rest_route(
		'wp/v2',
		'/settings',
		array(
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $controller, 'get_item' ),
				'args'                => array(),
				'permission_callback' => 'myblock_get_item_permissions_check',
			),
			array(
				'methods'             => WP_REST_Server::EDITABLE,
				'callback'            => array( $controller, 'update_item' ),
				'args'                => $controller->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
				'permission_callback' => 'myblock_update_item_permissions_check',
			),
			'schema' => array( $controller, 'get_public_item_schema' ),
		),
		true	// override
	);
}
add_action( 'rest_api_init', 'myblock_setting', 199 );

※2022/08/23 rest_api_initアクションの優先度を199(99より大きい値)を指定し、'permission_callback'を青字の内容に変更した。

同時にJavaScript側も修正した(赤字が追加した部分)。

import { useSelect } from '@wordpress/data';

// 中略

const [ apiKey, setApiKey ] = useEntityProp( 'root', 'site', 'google_maps_api_key' );
const { canUserEdit } = useSelect( ( select ) => {
	const { canUser } = select( coreStore );
	return { canUserEdit: canUser( 'update', 'settings' ) };
}, [] );

// 中略

		{ canUserEdit ? (
			<TextControl
				type="text"
				className="maps-embed-control__apikey"
				label={ __( 'API Key' ) }
				value={ apiKey }
				onChange={ setApiKey }
			/>
		) : (
			<span>API Key: { apiKey }</span>
		) }

ポイントはuseSelectを使ってオプションデータを更新できるかをcanUserEditに保存しているところ。これで管理者の場合はAPI Keyの入力ボックスを、投稿者以上の場合はspan要素でAPI Keyを表示するようにした。


今回はregister_rest_route関数で既存のエンドポイントを変更してみたが、PHP側のコードは少なくて済むが、セキュリティ面で懸念がないわけでもない。そのリスクを考慮するなら、PHP側でインラインスクリプトを出力し、JavaScript側はインラインスクリプトのデータを参照する方法が無難だろう。


最終更新 : 2022年08月23日 11:16


お勧め

the_modified_author(2019年10月17日 更新)

void the_modified_author()
更新者の表示名を表示する。

wp_assign_widget_to_sidebar(2021年7月29日 更新)

void wp_assign_widget_to_sidebar( string $widget_id, string $sidebar_id )
ウィジェットをサイドバーに配置する。

get_comment_ID(2018年5月27日 更新)

int get_comment_ID( )
現在のコメントのIDを取得する。

term_is_ancestor_of(2019年3月15日 更新)

bool term_is_ancestor_of( int | object $term1, int | object $term2, string $taxonomy )
タームが子孫関係か調べる。

wp_get_custom_css_post(2019年11月14日 更新)

WP_Post wp_get_custom_css_post( [ string $stylesheet = '' ] )
カスタマイザーで追加したCSS情報を取得する。