ページ毎にPHPテンプレート/ブロックテンプレートを切り替えられるブロックテーマ作成

クラシックテーマからブロックテーマへの乗り換えはとても大変です。

巷では、ブロックテーマに移行すべきか、あるいはまだまだクラシックテーマを使い続けていくか、二者択一を迫られているかも知れませんが、そんなのナンセンスです。もともとWordPress(2024/7現在はVer6.6)は、クラシックテーマのPHPテンプレートもブロックテーマのブロックテンプレートも両方を問題なく扱えるので、両方使える二刀流が望ましいと思っています

使い慣れた従来のPHPテンプレートベースのテーマを使って、一部分の固定ページだけでブロックテンプレートを使いたい場合等、PHPテンプレートとブロックテンプレートを目的に応じて混在して使用出来るようにするのが現時点ではとってもオススメの手法です

WordPressのクラシックテーマを少しだけカスタマイズすることで、固定ページですぐにブロックテンプレートが使用できるようになります。

それでは、私が開発している Jewelbeetle テーマを使って手順を紹介します

ちなみに Jewelbeetle というのは、とても綺麗な色をしているタマムシのことですね。玉虫色というとどちらともとれる曖昧な表現のことを言いますので、PHP/ブロックテンプレートのどちらも自由に扱えるテーマにピッタリだと思い名付けました

Jewelbeetleテーマは、下記ページからダウンロードできます

WordPressテーマ : Jewelbeetle
Jewelbeetl テーマは PHPテンプレートもブロックテンプレートも両方扱える二刀流のWordPress ブロックテーマです
WordPressテーマ : Jewelbeetle

既存のページでは、そのまま従来のPHPテンプレートを使い、特定のランディングページや WooCommerce ページのみブロックテンプレート使うなんてことが出来るようになるんです🤩

クラシックテーマに theme.json 導入

現在では、クラシックテーマでも、投稿記事等の編集で使用するのは、ブロックエディターが基本となります

ここでは、ブロックエディターと theme.json を使用していることを最低限の条件といたします

まだ、theme.json を使用していない場合は、とりあえず導入してください

この設定が、ページ全体に作用するグローバルなCSSやブロック毎のCSSの元データとなります。従来の add_theme_support で設定していた大部分を置き換えることが可能です。

theme.json とは

theme.json は、主にテーマに適用する機能設定やスタイル(CSS)に関するデータを管理するための JSON ファイルです

  • グローバルスタイルの設定: テーマ全体のスタイルを定義できます。カラー、タイポグラフィ、スペーシングなどの設定が含まれ、テーマ全体の一貫性を保つことが可能です
  • プリセットの設定: カラーパレットやフォントサイズなどのプリセットを定義することができます。ユーザーはこれらのプリセットから選択して使用できます。
  • ブロックスタイルの設定: ブロック種別毎のスタイルを設定することが出来て、特定ブロックに対して個別のデザインを適用することができます。

theme.json により、自動的にブロック関連のCSSが生成されるようになります。既存のテーマのCSSと干渉が生じる場合もありますので、どのようなCSSが生成されるかを調べて、自動生成されるCSSを優先し、テーマ側では、出来るだけそれを考慮した補助的なCSSとなるようにします

詳しくは下記ページをご参照ください

theme.json
テーマの theme.json ファイルは、編集体験をキュレートする最良の方法の一つであり、より洗練されたソリ…
theme.json

クラシックテーマで theme.json を使っているテーマを一部ではハイブリッドテーマと呼ばれていたりしますが、theme.json を用意しただけでは、まだPHPテンプレートしか扱えない状態です

なんちゃってブロックテーマ

さて、今一度、WordPress コア側が認識するブロックテーマとはなにか考えてみます

ブロックテーマとは、ブロックテンプレートが最低1つ用意されているテーマ

どーゆうことかというと、ほぼ100% PHPテンプレートで構成されているクラシックテーマに、index.html を1つ用意しただけで WordPress コア側からはブロックテーマと認識されます

これだけで、管理画面の外観からサイトエディターにアクセス出来るようになり、ブロックデザインのスタイル等が利用可能となります

必要なのは、block-templates フォルダーを作り、index.html を配置するだけです

jewelbeetle テーマのファイル構成は、下記画像のような感じです。index.html ブロックテンプレートは、ほぼ使われることがないダミーファイルで中身は post-content ブロックを呼び出すだけのテンプレートとなっています

この状態は、PHPテンプレートもブロックテンプレートも扱えるハイブリッドな状態ですが、実際には、どのテンプレートを使うか選択する処理において、ブロックテンプレートの home.html や page.html 、single.html 等何も用意されていないので home.php、page.php、single.php 等のPHPテンプレートが選択されて使われる、なんちゃってブロックテーマです

なんちゃってブロックテーマとはいえ、ブロックテーマですので、ブロックテーマ用の機能が動作するようになります

まず、投稿や固定ページの編集画面でPHPテンプレートの選択が表示されなくなるのでこれの対策を行います
ブロックテーマなのでPHPテンプレートをわざと指定しにくくしているのではと勘ぐっているのですが、余計なお世話なので、下記コードを functions.php に記述して表示させるようにします

add_action( 'enqueue_block_editor_assets', function() {
    $post = get_post();
    if(is_object($post) && ($post->post_type === 'post' || $post->post_type === 'page')){
        add_filter( 'block_editor_settings_all', function($editor_settings, $block_editor_context ) {
            $editor_settings['__unstableIsBlockBasedTheme'] = false;
            return $editor_settings;
        }, 10, 2);
    }
}, 10);

これで、今までどおりにPHPテンプレートが選択可能な場合は指定出来るようになります

また、ブロック関連のCSSが使用しているブロックのみに分割され出力されるようになります

このブロックCSS分割処理を停止するには、下記コードを functions.php に記述します

//block css ブロック種別毎の分割読み込みを無効化
add_filter( 'should_load_separate_core_block_assets', function( $mode ){ $mode = false; return $mode; } );

PHPテンプレート使用時のCSS互換性向上について

ブロックテーマかどうかでCSSの出力が一部異なる場合があります

例えば、使用しているブロックのみのCSS出力を使うためには、head タグ出力時にコンテント内でどのブロックを使っているのか事前にわかっている必要があり、既存のPHPテンプレートのままでは問題があります

使用しているブロックのみのCSS分割出力を使いたい場合は、PHPテンプレートの構造を変えてブロックテンプレート使用時との互換性を向上させる必要があります

標準的なクラシックテーマ(PHPテンプレート)のフック順番

  1. wp-head
  2. the_content
  3. wp_footer

ブロックテーマ(ブロックテンプレート)のフック順番

  1. the_content
  2. wp-head
  3. wp_footer

このフックの順番を合わせる必要があります

例えば、従来の page.php テンプレートが下記のような場合

<?php
/**
 * The template for displaying all single posts
 *
 */

get_header();

/* Start the Loop */
while ( have_posts() ) :
	the_post();
	get_template_part( 'template-parts/content/content-page' );

	// If comments are open or there is at least one comment, load up the comment template.
	if ( comments_open() || get_comments_number() ) {
		comments_template();
	}
endwhile; // End of the loop.

get_footer();

これを content の処理を先に行わせる為に ob_start を使ったテンプレート処理に変更します

<?php
/**
 * The template for displaying all single posts
 *
 */

$content_html = (function(){
    ob_start();
    
    /* Start the Loop */
    while ( have_posts() ) :
        the_post();
        get_template_part( 'template-parts/content/content-page' );

        // If comments are open or there is at least one comment, load up the comment template.
        if ( comments_open() || get_comments_number() ) {
            comments_template();
        }
    endwhile; // End of the loop.    
     
	return ob_get_clean();
})();

celtis_template_output($content_html);

このコンテントデータを下記のようなテンプレート出力用の関数を使って出力することでフックの順番を合わせることが出来るようになります

function celtis_template_output( $content ){
    ob_start();
    get_header();
    $head = ob_get_clean();

    ob_start();
    wp_body_open();
    echo $content;
    get_footer();
    $body = ob_get_clean();    
?>
<!DOCTYPE html>
<html <?php language_attributes(); ?>>
<head>
<?php echo apply_filters( 'celtis_html_head', $head, $body ); ?>         
</head>
<body <?php body_class(); ?>> 
<?php echo apply_filters( 'celtis_html_body', $body ); ?> 
</body>
</html>
<?php
}

このようにテンプレートを変更をすることでPHPテンプレートでもブロックテンプレートと同じように使用しているブロックのCSS分割出力が問題なく出来るようになります

二刀流テンプレートのブロックテーマ

固定ページをPHPテンプレート/ブロックテンプレートのどちらでも任意に使えるように二刀流(two-way)テーマにします

まずは page.html ブロックテンプレートと使用するブロックテンプレートパーツ header.html、footer.html を用意します

また、ちょっと複雑な処理を行いたい場合は patterns フォルダにブロックテンプレートパーツを補助するような php ファイルを用意することも出来るようです(今回は使用しない)

とはいえ、ブロックテンプレートって全然なじまなくって、まだよくわからないことも多いので、 twentytwentyfour を参考にシンプルなテンプレートをコピー修正して使わせて頂いてます

腕に自信のある方はどんどんカスタマイズしちゃってください😅

この状態では、まだ固定ページで自由にPHPテンプレートやブロックテンプレートを指定することは出来ません

どーゆうことかというと、明示的に使用するPHPテンプレートが指定されているページは、そのPHPテンプレートで表示されますが、何も指定せずテンプレートがデフォルトとなっている固定ページは全てブロックテンプレートで表示されるようになります

これは、よろしくない状態です。使用テンプレートを明示的に指定してないページが全て変わってしまう大惨事です😫

望ましいのは、テンプレートがデフォルト指定の場合に、PHPテンプレートを使うか、ブロックテンプレートを使うか選択可能なことです

カスタマイザーにオプションを追加します(コードの詳細は省略します)、下図のような固定ページのデフォルトテンプレートの選択オプションを設けてPHPテンプレートやブロックテンプレートが指定できるようにしておきます

次にテンプレートの指定がデフォルト(エンプティ)時に使用するテンプレートを選択するコードを functions.php に追加します

function celtis_get_template($value, $object_id, $meta_key, $single) {
    if($value !== null || empty($object_id) || $meta_key !== '_wp_page_template' || $single !== true){
        return $value;
    }
    if ( defined( 'WP_ADMIN' ) || defined('REST_REQUEST')) {
        return $value;
    }       

    $post = get_post();
    if(is_object($post)){
        global $celtis_theme_options;

        if($post->post_type === 'page'){
            $template = '';
            $meta_cache = wp_cache_get($object_id, 'post_meta');
            if ( !empty($meta_cache) && !empty($meta_cache[$meta_key][0])) {
                $template = maybe_unserialize( $meta_cache[$meta_key][0] );
            }
            if(strpos( $template, '.php') !== false ){
//テーマ内に存在するテンプレートか確認する
if(empty(locate_template( $template ))){
$template = '';
}
}
if(empty($template) || $template == 'default'){
//default/empty - 設定により PHP/Block テンプレートを切り替える $tpl_default = $celtis_theme_options['default_page_template']; $pagename = get_query_var( 'pagename' ); if (class_exists('Woocommerce') && $pagename && in_array($pagename, array('cart', 'checkout', 'my-account')) ) { //woocommerce cart/checkout/my-account ならブロックテンプレートを使うのでそのまま } else { if($tpl_default !== 'page'){ //php template に限定するためブロックテンプレートサポートを無効化 remove_theme_support( 'block-templates' ); $value = $tpl_default; } } } else { //投稿毎に指定されているテンプレート (php ファイルならブロックテンプレート対応を無効化) if(strpos( $template, '.php') !== false ){ remove_theme_support( 'block-templates' ); } $value = $template; } } } return $value; }
add_filter('get_post_metadata', 'celtis_get_template', 10, 4);

コードを簡単に解説すると固定ページで選択したテンプレートは、データベーステーブルの wp_postmeta に _wp_page_template という meta_key で保存されています

テンプレートの選択処理をフックして、取得したメタデータがデフォルト(エンプティ)の場合にどのテンプレートを使用するかを事前にテーマオプション等で指定されたテンプレートに置き換えます

選択したテンプレートが page.php のように .php のついたPHPテンプレートの場合は remove_theme_support( 'block-templates' ) を実行してブロックテンプレートが使用できないようにします

テンプレートデータが page というスラッグの場合は、エンプティのメタデータをそのままリターンすることで自動的に固定ページのブロックテンプレートが使用されます

WooCommerceを新規に導入する場合は、ブロックテーマを使用したほうが簡単に導入できるので、Jewelbeetleでは、woocommerce cart/checkout/my-account ページの場合に、ブロックテンプレートが使われるようデフォルトテンプレート選択処理から除外しています

コードを単純化するために固定ページに限定して紹介していますが、投稿やカスタムポストも二刀流にするためには同様の処理を追加する必要があります

これで、テンプレートがデフォルト指定の場合に、PHPテンプレートを使うか、ブロックテンプレートを使うか選択可能になりました

ただ、これだけではデフォルトオプションでPHPテンプレートを選択した場合だと、固定ページでブロックテンプレートは使われません。ブロックテンプレートを使用するには、サイトエディターから選択した固定ページに対し、カスタムテンプレートを作成して割り当てる必要があります

カスタムテンプレートは、サイトエディターから作成する以外にもブロックテンプレートファイル(html)として追加することも出来ます

ブロックテンプレートフォルダーにファイルとして追加したカスタムテンプレートの場合は、theme.json でそのテンプレートを有効とする固定ページ等のポストタイプを指定することで、そのテンプレートは編集画面のテンプレートから選択できるようになります

theme.json カスタムテンプレート指定方法

ブロックテンプレートのカスタムテンプレートを使用すれば、そのカスタムテンプレートを固定ページや投稿のテンプレートとして自由に指定できるようになります

しかし、この状態は、カスタムテンプレートではない固定ページの page.html ブロックテンプレートを指定することがまだ出来ない状態です

ブロックテーマにおいては、固定ページのデフォルトが暗黙的に page.html テンプレートとなるので、今回のようにPHPテンプレートと混在した状態からの選択が想定されていません。そこで、明示的に page.html テンプレートを指定できる用に選択リストに page テンプレートを追加します

固定ページの編集画面から page ブロックテンプレートを選択できるように functions.php に下記コードを追加します

add_filter( "theme_page_templates", function($post_templates, $theme, $post, $post_type ) {
    $post_templates['page'] = __('Block template (page)', 'jewelbeetle');
    return $post_templates;
}, 10, 4);

これでテンプレートの選択から page ブロックテンプレートを選べるようになります

これで、許可されているPHPテンプレート、ブロックテンプレート、カスタムブロックテンプレートから自由自在にテンプレートを指定することが出来るようになります

固定ページで二刀流テンプレートから選択できるのは、残念ながら従来からある固定ページの編集画面からのみとなります。サイトエディターの固定ページ編集画面では、ヘッダーやフッターを含むページ全体のエディターとなっていて、従来の固定ページ編集画面に対する多くのプラグイン等の設定も表示されない状態となります。ここで紹介しているカスタムコードも反映されないので PHPテンプレートは選択出来ず、ブロックテンプレートの選択のみに限定されます

ちょっとコードの追加が多くて大変ですが、クラシックテーマを作成したことがあるなら簡単に対応できると思います

以上、固定ページ毎に任意のPHPテンプレートやブロックテンプレートを指定できる二刀流(two-way)ブロックテーマ化のための手順を紹介しました


まとめ記事紹介

go-to-top