ブログ用タグ: ブロックエディタ

  • WordPress: ショートコードのブロック化

    例えば下記のページは、固定ページにショートコードを埋め込んでコンテンツを動的に生成している。

    その編集画面は下図(Before)。ショートコードの中で見出しやリストなどすべてのコンテンツの書き出しを行っているのでブロックエディタの恩恵を被れない。

    カスタムブロックを作ってみたが、開発環境が複雑で維持できる自信がない。そこでカスタムブロックを作ることなく、ブロックエディタでレイアウトする自由を享受する方法を考えた。

    具体的には、下図(After)のようにページのデザインをブロックエディタで行い、表示時にブロックの内容の一部を置き換えることにした。

    Before
    After

    これをするためには、下記の処理が必要。

    • 置換したいブロックに一意なクラス名を指定する
    • render_block フィルターフックでそのクラス名を拾ってブロックの内容を置き換える

    ブロックを一意に特定するならば意味合い的にはID(HTMLアンカー)が適切だが、IDはブロックの属性として定義されていないため、キーとして使いづらい。

    コードは次のような感じで準備して、プラグインファイルから LF_Page_Taginfo::init() を呼んでおく。

    class LF_Page_Taginfo {
    
        // ブロックのクラス名 => 置換情報テーブル
        private static array $blocks = [];
    
        /**
         * 初期化
         */
        public static function init() {
            add_action( 'wp', [ __CLASS__, 'wp' ] );
        }
    
        /**
         * ディスパッチャー
         */
        public static function wp() {
    
            // 対象ページでなければリターン
            if ( ! is_page() || 'taginfo' !== get_query_var( 'pagename' ) ) {
                return;
            }
    
            // パラメーターが無ければリターン
            if ( '' === $tag_slug = get_query_var( 'tag' ) ) {
                return;
            }
    
            // タームオブジェクトが作れなければリダイレクト
            if ( false === $term = get_term_by( 'slug', $tag_slug , 'post_tag' ) ) {
                wp_safe_redirect( home_url(), 302 );
                exit;
            }
    
            // ブロックのクラス名 => 置換情報テーブル
            // クラス名はブロックエディタで一意に指定しておく
            self::$blocks = [
                'lf-page-taginfo-header-cotagged' => [
                    'from' => '(タグ)',
                    'to'   => $term->name,
                ],
                'lf-page-taginfo-header-tagcloud' => [
                    'from' => '(共起タグ)',
                    'to'   => LF_Page_Tag_CoTagged::get_for_taginfo_page( $term, 5 ),
                ],
                'lf-page-taginfo-header-list' => [
                    'from' => '(タグ)',
                    'to'   => $term->name,
                ],
                'lf-page-taginfo-list' => [
                    'from' => '<li>(リスト)</li>',
                    'to'   => LF_Tax_Tag::get_for_taginfo_page( $term, 5 ),
                ],
                'lf-page-taginfo-header-book' => [
                    'from' => '(タグ)',
                    'to'   => $term->name,
                ],
                'lf-page-taginfo-book' => [
                    'from' => '<li>(書籍)</li>',
                    'to'   => LF_Page_Tag_Book::get_for_taginfo_page( $term, 5 ),
                ],
                'lf-page-taginfo-misc' => [
                    'from' => [ 'TAG', '(タグ)' ],
                    'to'   => [ urlencode( $term->name ), $term->name ],
                ],
            ];
    
            // ページタイトル <title> の変更(コールバック関数は省略)
            add_filter( 'document_title_parts', [ __CLASS__, 'document_title' ], 10, 1 );
    
            // ページ説明の追加(コールバック関数は省略)
            add_action( 'wp_head', [ __CLASS__, 'wp_head' ] );
    
            // 投稿タイトルの変更(コールバック関数は省略)
            add_filter( 'the_title', [ __CLASS__, 'the_title' ], 10, 2 );
    
            // 投稿抜粋欄に説明を表示する(コールバック関数は省略)
            add_filter( 'get_the_excerpt', [ __CLASS__, 'excerpt' ], 10, 2 );
    
            // ブロックを置換する
            add_filter( 'render_block', [ __CLASS__, 'render_block' ], 10, 3 );
        }
    
        /**
         * ブロックを置換する
         */
        public static function render_block( $block_content, $parsed_block, $instance ) {
    
            $class = $parsed_block['attrs']['className'] ?? '';
    
            // 置換対象のクラスなら置換
            if ( '' !== $class && array_key_exists( $class, self::$blocks ) ) {
                $block_content = str_replace(
                    self::$blocks[$class]['from'],
                    self::$blocks[$class]['to'],
                    $block_content
                );
            }
    
            return $block_content;
        }
    }
    
    • 表示しようとしているページが処理対象かどうかを知るには ‘init’ アクションフックではタイミングが早すぎるので、URLパラメータの処理が終わった ‘wp’ で行っている。
    • wp() の最初の3つの if 文が対象ページかどうかの判定とパラメータ検証。
    • self::$blocks の定義が肝。ブロック名をキー、置換元先情報を値とする連想配列を定義しておく。置換されない部分についてはできるだけブロックエディタのほうに記述する。置換する部分がエディタ上でわかりやすいようにカッコでくくってみた。
    • 置換は str_replace() で行うので 複数要素の置換も可能。いちばん最後の要素ではリンク先のURLとリンク文字列の置換を行っている。
    • ‘render_block’ フィルターフックのコールバックに置換処理を登録。このフックはブロック描画の最後に設置されており、ヘッダーやフッターも含めて全ブロックで発火する。よってブロック名ごとに発火できる ‘render_block_{$this->name}’ を使うほうが少し軽いのかも。
    • render_block() で置換。クラス名が存在し、かつ置換情報テーブルのキーと一致したら置換。ブロックエディタでクラス名が1つだけ登録されていることを前提としているので、もしカスタムクラス名を2つ登録したくなった場合には改変が必要。
    • $block_content には表示するHTMLがそのまま入っている。置換元のコードはコードエディターに切り替えるなどして確認する。

    ちょっとスペーサーを入れてみたりするような見栄えの調整がずいぶん楽になった。