WordPressのブロック開発メモ その14 ウィジェットとブロックへの変換

前回はブロックの変換についてでした。

WordPressのブロック開発メモ その13 ブロックの変換

今回はウィジェットと、ブロックへの変換についてです。

これまでのウィジェット

WordPressにはウィジェットという機能があります(外観→ウィジェット)。
以前はウィジェットはPHPで書かれていました。
しかし、そんなウィジェットにもブロックの波がやってきてオワコン化してしまいました。

だって投稿と同じくウィジェットもブロック追加していけばすむ話だよね!
ということでウィジェットのエディタでも段落ブロックなどのブロックを追加していく式に変わりました

段落ブロックやクラゲブロック、詩ブロックなどを追加してます。

じゃー昔のPHP製ウィジェットはどうなるの!?

で、それをホストするブロックがレガシーウィジェットブロック('core/legacy-widget')です。
リストには従来のウィジェットなんて項目になってますね。
仕組みはREST APIを通じてウィジェットにアクセスします。

前半では従来のPHPでのウィジェットの作り方を、
後半ではPHP製ウィジェットをブロックに変換する方法を紹介します。

後半は新しく作ったブロックを作って設定しなおせばいいわけでけど、
配布物に旧ウィジェットが含まれるような場合以外は必要性にかけるんですけど
まぁ興味がある人用です。

従来のウィジェット

まずは従来でのウィジェットを作成、追加する方法です。

add_action('widgets_init', function(){

    $widget = new class extends WP_Widget
    {
        public function __construct()
        {
            parent::__construct(
                'kikurage',
                'キクラゲ ウィジェット',
                [

                ]
            );
        }

        public function widget($args, $instance)
        {

            $title = $instance['title'] ?? '-';
            $title = apply_filters('widget_title', $title);

            $message = $instance['message'] ?? '-';

            $befWidget = $args['before_widget'];
            $aftWidget = $args['after_widget'];
            $befTitle = $args['before_title'];
            $aftTitle = $args['after_title'];

            $titles = $title ? [$befTitle, $title, $aftTitle] : [];

            echo join([$befWidget, ...$titles, $message, $aftWidget]);
        }

        public function form($instance)
        {
            // Title values
            [$tid, $tName, $tValue] = [
                $this->get_field_id('title'),
                $this->get_field_name('title'),
                $instance['title'] ?? 'NO TITLE'
            ];

            // Message values
            [$mid, $mName, $mValue] = [
                $this->get_field_id('message'),
                $this->get_field_name('message'),
                $instance['message'] ?? 'NO MESSAGE'
            ];

            echo join([

                // Title Field
                sprintf('<label for="%s">%s</label>', $tid, __('Title:')),
                sprintf('<input type="text" id="%s" name="%s" value="%s" />', $tid, $tName, $tValue),

                // Message Field
                sprintf('<label for="%s">%s</label>', $mid, 'メッセージ:'),
                sprintf('<input type="text" id="%s" name="%s" value="%s" />', $mid, $mName, $mValue),
            ]);
        }

        public function update($new_instance, $old_instance)
        {
            return $new_instance;           
        }

    };

    // select * from wp_options where option_name like '%kikurage%'  limit 3;
    register_widget($widget);

});

まずWP_Widgetを継承したインスタンスを生成します。
親コンストラクタに識別ID(今回はkikurage)とウィジェット名(今回はキクラゲ ウィジェット)を渡します。

widget()メソッドは表示用のHTMLを、form()メソッドは入力フォームのHTMLを出力します。
では従来のウィジェットの追加方法を見てみましょう。

外観からウィジェットを開く。
追加(+)をクリック

従来のウィジェットを選択

今回作成したウィジェット名のキクラゲウィジェットを選択

form()メソッドで出力したHTMLが表示。
タイトルとメッセージの入力欄が現れる

適当に入力する。

フォーカスを外すとwidget()メソッドで出力したHTMLが表示。

入力しただけでは保存されないので保存ボタンを押す。
そしてフロントエンドを開いてみると、ウィジェットがサイドバーに表示されている。

ところでデータはどこに保存してあるのか!
答えはデータベースのwp_optionsである。

select * from wp_options where option_name like '%kikurage%';

オプション名は命名規則で widget_ とその後に識別IDがくっついた名前になる。
今回の場合はwidget_kikurageにデータがシリアライズして保存してある。

もう一個同じウィジェットを追加してみよう。

データベースを見るとこうなってる。

同じカラム名(widget_kikurage)にマージされてますね。

さて、フォームを見てみよう。

public function form($instance)
{
    // Title values
    [$tid, $tName, $tValue] = [
        $this->get_field_id('title'),
        $this->get_field_name('title'),
        $instance['title'] ?? 'NO TITLE'
    ];

    // Message values
    [$mid, $mName, $mValue] = [
        $this->get_field_id('message'),
        $this->get_field_name('message'),
        $instance['message'] ?? 'NO MESSAGE'
    ];

    echo join([

        // Title Field
        sprintf('<label for="%s">%s</label>', $tid, __('Title:')),
        sprintf('<input type="text" id="%s" name="%s" value="%s" />', $tid, $tName, $tValue),

        // Message Field
        sprintf('<label for="%s">%s</label>', $mid, 'メッセージ:'),
        sprintf('<input type="text" id="%s" name="%s" value="%s" />', $mid, $mName, $mValue),
    ]);
}

DOMを覗くとよくわかる。

コメントに出力結果を書いているとおり、以下の規則でIDや名前を作成するメソッドのようです。

// widget-kikurage-5-title
$this->get_field_id('title')

// widget-kikurage[5][title]
$this->get_field_name('title')

保存したデータから引っ張ってくるときは以下のようにします。

$instance['title']

ところでウィジェットのデータはどこに保存してある?

wp_optionsテーブルのoption_nameカラムがsidebars_widgetsの行にシリアライズしてから保存してある。

select * from wp_options where option_name like '%sidebar%';

また、REST APIで以下のように取得することもできる。

await wp.apiFetch({ path: '/wp/v2/widgets' });

旧ウィジェットをブロックへ変換

旧ウィジェットをブロックに変換する方法です。
その前にPHP側を変更します。

show_instance_in_rest を true に

まずウィジェット側の設定です。
ウィジェットのコンストラクタにオプション(show_instance_in_resttrue)を追加する。

public function __construct()
{
    parent::__construct(
        'kikurage',
        'キクラゲ ウィジェット',
        [
            'show_instance_in_rest' => true
        ]
    );
}

ドキュメントにも書いてあるけどどういうこと!?

ウィジェットを編集(入力するたびに)すると以下のREST APIを叩いているようだ。

https://kurage/wp-json/wp/v2/widget-types/kikurage/encode?_locale=user

ちなみにPOSTでのアクセス。

そしてshow_instance_in_restの真偽で結果に違いがでる。

false、あるいは何も指定しない時

trueの時

違いはinstancerawがあるかどうかのようです。
ウィジェットの変換にはこのrawを取得する必要があるようです。

ただ注意する必要があるようです。

ちょっと意味が分かりにくいですけど、
このデータがJSONで表すことが出来、他の権限付きユーザにもれて困るデータが入ってないならshow_instance_in_resttrueに出来るということでしょうか?

ブロックへの変換

まず旧ウィジェットに対応するブロックを作成します。

これまでのクラゲブロックを変更します。
属性にmessagetitleを設定します。

block.json

"attributes": {
    "title": {
        "type": "string",
        "default": ""
    },
    "message": {
        "type": "string",
        "default": ""
    }
},

props.ts

export default interface KurageExampleBlockProps
{
    title: string;
    message: string;
}

index.tsx

const blockConf: BlockConfiguration<KurageExampleBlockProps> =
{
    ...metadata,
    edit: Edit,
    save,
    transforms: {
        from: [
            {
                type: 'block',
                blocks: ['core/legacy-widget'],

                // @ts-ignore
                isMatch: ( { idBase, instance } ) =>
                {
                    return instance?.raw && idBase === 'kikurage';
                },

                // @ts-ignore
                transform: ( { instance } ) =>
                {
                    const { title, message } = instance.raw;

                    return createBlock(
                        'create-block/kurage-worker',
                        {
                            title,
                            message
                        }
                    )
                }
            }
        ]
    }
};
blocks: ['core/legacy-widget']

レガシーウィジェットブロックのブロック名はcore/legacy-widgetです
レガシーウィジェットブロックからの変換なのでfromに追加します。

isMatch: ( { idBase, instance } ) =>

変換可能かをチェックします。
instance.rawが存在し、なおかつidBasekikurage(旧ウィジェットの名前)であれば変換可能です(ツールバーで変換用のボタンが追加される)

後はtransforminstanceからデータを取ってきてcreateBlock()でクラゲブロックを作成して返します。

editor.tsx

import React from 'react';
import { useBlockProps } from '@wordpress/block-editor';
import { BlockEditProps } from '@wordpress/blocks';
import { TextControl } from '@wordpress/components';

import KurageExampleBlockProps from './props';
import './editor.scss';

export default (props: BlockEditProps<KurageExampleBlockProps>) =>
{

    const { title, message } = props.attributes;
    const { setAttributes } = props;

    return (
        <div {...useBlockProps()}>

            <h2>Kikurage Widget!</h2>

            <label>
                タイトル:
                <TextControl value={title} onChange={title => setAttributes({ title })} />
            </label>

            <label>
                メッセージ:
                <TextControl value={message} onChange={message => setAttributes({ message })} />
            </label>

        </div>
    );
}

タイトルとメッセージを編集出来るようにしました。

save.tsx

import React from 'react';
import { useBlockProps } from '@wordpress/block-editor';
import { BlockEditProps } from '@wordpress/blocks';
import { TextControl } from '@wordpress/components';

import KurageExampleBlockProps from './props';
import './editor.scss';

export default (props: BlockEditProps<KurageExampleBlockProps>) =>
{

    const { title, message } = props.attributes;
    const { setAttributes } = props;

    return (
        <div {...useBlockProps()}>

            <h2>Kikurage Widget!</h2>

            <label>
                タイトル:
                <TextControl value={title} onChange={title => setAttributes({ title })} />
            </label>

            <label>
                メッセージ:
                <TextControl value={message} onChange={message => setAttributes({ message })} />
            </label>

        </div>
    );
}

旧ウィジェットを開いてます。

ツールバーの左ボタンからKurage Workerに変換をクリック。

クラゲブロックに変換されました。

ここで注目すべきはリストにある従来のウィジェットKurage Workerに変わっていることです。

では、変換後のクラゲブロックでも編集しましょうかね。

編集して更新ボタンを押します。

フロントエンドを開くと、右サイドバー(環境によりことなる)にウィジェットが表示されています。

BlockEditor certificate css DataGrid Docker Gutenberg Hyper-V iframe MUI openssl PHP React ReduxToolkit REST ubuntu WordPress オレオレ認証局 フレームワーク