1. ホーム
  2. WordPress

【WordPress】ブロックのロック機能の使い方|lock 属性と templateLock で移動・削除を禁止する方法

Share

「お知らせページの見出しだけは動かさないでほしい」「カスタムブロックの中身に勝手にボタンを足されると困る」——複数人で運用するサイトでは、ブロックエディターの自由度がそのままレイアウト崩れの原因になります。WordPress にはこれを防ぐためのロック機能が用意されていて、ブロック1つずつに効く lock 属性と、入れ子のブロック全体に効く templateLock の2種類があります。この記事では、それぞれの設定方法と違い、block.json の supports.lock でロック UI 自体を消す方法、そして「ロックしたのに解除できてしまう」ときの考え方までを解説します。

ロック機能は「編集できる範囲」を絞り込む仕組み

ブロックエディターは既定で、どのブロックも自由に動かせて、消せて、好きな場所に新しいブロックを挿し込めます。制作側にとっては便利ですが、記事の更新だけを担当する編集者にとっては「触ってはいけない場所」が分からず、うっかり構造を壊してしまう原因になります。

ロック機能は、この「できること」を減らすための仕組みです。大きく分けて2つあります。

仕組み効く範囲設定する場所
lock 属性そのブロック1つエディターの「ロック」UI、またはブロックの属性
templateLockInnerBlocks 内のブロック全体InnerBlocks / useInnerBlocksProps の props、投稿タイプの登録

「この見出しだけ動かせないようにしたい」なら lock、「このカスタムブロックの中身は構成を固定したい」なら templateLock、という使い分けになります。両方が同時に効いている場合は、ブロック単位の lock が優先されます。

ブロック1つをロックする lock 属性

エディター上でブロックを選び、ツールバー右端の「オプション」(縦三点のアイコン)から「ロック」を選ぶと、ロックの設定ダイアログが開きます。ここで「移動を無効にする」「削除を無効にする」にチェックを入れると、そのブロックは動かせず消せなくなります。

このとき投稿本文には何が保存されているのか、コードエディター(「ツール」メニューの「コードエディター」)に切り替えると分かります。

投稿本文(コードエディターでの表示)
<!-- wp:heading {"lock":{"move":true,"remove":true}} -->
<h2>お問い合わせ</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>この段落はロックされていないので自由に動かせます。</p>
<!-- /wp:paragraph -->

ブロックコメントの {"lock":{...}} がそれです。lock は特別なキーではなくブロックの属性のひとつとして保存されるので、パターンやテンプレートに書いておけば、最初からロックされた状態のブロックを配置できます。

lock に指定できるキー

キーtrue にしたときの効果
moveブロックを移動できなくする(ドラッグ・上下ボタンが無効になる)
removeブロックを削除できなくする

片方だけ指定することもできます。たとえば {"lock":{"remove":true}} なら「順番は入れ替えてよいが消してはいけない」ブロックになります。中のテキストは通常どおり編集できるので、lock は配置を固定するための設定だと考えてください。文章そのものを編集させたくない場合は、あとで説明する contentOnly とは逆の発想になります。

ブロックパターンに書いておく

編集者に毎回ロックを設定してもらうのは現実的ではないので、ロック済みの状態をパターンとして登録しておくのが実用的です。パターンの内容は投稿本文と同じブロックコメントなので、そのまま lock を書けます。

patterns/notice.php
<?php
/**
 * Title: お知らせ枠
 * Slug: mytheme/notice
 * Categories: text
 */
?>
<!-- wp:group {"lock":{"move":false,"remove":true},"className":"notice"} -->
<div class="wp-block-group notice">
    <!-- wp:heading {"level":3,"lock":{"move":true,"remove":true}} -->
    <h3>お知らせ</h3>
    <!-- /wp:heading -->

    <!-- wp:paragraph -->
    <p>ここに本文を入力してください。</p>
    <!-- /wp:paragraph -->
</div>
<!-- /wp:group -->

この例では、外側のグループは移動できるが削除はできない(move が false、remove が true)、中の見出しは移動も削除もできない、という設定になっています。

block.json の supports.lock でロック UI を消す

ロックの設定ダイアログは、既定ではすべてのブロックで開けます。つまりユーザーが自分でロックを付けたり外したりできるということです。自作ブロックで「ロック状態をユーザーに切り替えさせたくない」場合は、block.json の supports に lock を false で指定します。

src/block.json
{
    "$schema": "https://schemas.wp.org/trunk/block.json",
    "apiVersion": 3,
    "name": "mytheme/notice-box",
    "title": "お知らせボックス",
    "category": "design",
    "supports": {
        "html": false,
        "lock": false
    },
    "textdomain": "mytheme",
    "editorScript": "file:./index.js"
}

supports.lock の既定値は true で、これを false にするとオプションメニューから「ロック」の項目が消えます。名前が紛らわしいのですが、これは「ブロックをロックする」設定ではなく「ロックの切り替え UI を無効にする」設定です。supports.lock を false にしただけではブロックは自由に動かせるままなので、実際に固定したいなら別途 lock 属性や親側の templateLock を設定する必要があります。

この2つを組み合わせると、「パターンや親ブロックの側でロックを指定しておき、supports.lock: false で解除させない」という運用になります。

入れ子の中身を固定する templateLock

カスタムブロックの中に他のブロックを入れられるようにする InnerBlocks では、template で初期構成を決められます。ただし template はあくまで「初期状態」なので、そのままでは編集者がブロックを追加したり消したりできてしまいます。これを制限するのが templateLock です。

src/edit.js
import { InnerBlocks, useBlockProps } from '@wordpress/block-editor';

// 見出しと段落だけの構成にする
const TEMPLATE = [
    [ 'core/heading', { level: 3, placeholder: '見出しを入力' } ],
    [ 'core/paragraph', { placeholder: '本文を入力' } ],
];

export default function Edit() {
    return (
        <div { ...useBlockProps() }>
            <InnerBlocks
                template={ TEMPLATE }
                templateLock="all"
            />
        </div>
    );
}

templateLock="all" を付けると、このブロックの中は見出しと段落の2つで固定されます。ブロックの追加ボタンは表示されず、並べ替えも削除もできません。編集できるのはテキストの中身だけです。

指定できる値の違い

値挿入移動削除備考
'all'不可不可不可構成を完全に固定する
'insert'不可可不可並べ替えだけ許可する
'contentOnly'不可不可不可コンテンツを持たないブロックはリスト表示から隠れ、フォーカスできなくなる
false可可可親から継承されたロックを打ち消す

'all' と 'insert' の差は移動できるかどうかだけです。「並び順は現場で調整させたいが、勝手にブロックを足されたくない」という要件なら 'insert' がちょうどよく当てはまります。

useInnerBlocksProps で書く場合

ラッパーの div を自分で用意せず、useInnerBlocksProps でブロック本体の props とまとめる書き方でも、渡す場所が変わるだけで指定内容は同じです。

src/edit.js
import { useBlockProps, useInnerBlocksProps } from '@wordpress/block-editor';

const TEMPLATE = [
    [ 'core/heading', { level: 3 } ],
    [ 'core/paragraph', {} ],
];

export default function Edit() {
    const blockProps = useBlockProps( { className: 'notice-box' } );

    // 第2引数に InnerBlocks と同じ設定を渡す
    const innerBlocksProps = useInnerBlocksProps( blockProps, {
        template: TEMPLATE,
        templateLock: 'all',
    } );

    return <div { ...innerBlocksProps } />;
}

templateLock は属性ではなく props

ここを間違えやすいのですが、templateLock は block.json に書く設定ではありません。supports にも attributes にも templateLock という決まった項目はなく、あくまで InnerBlocks(または useInnerBlocksProps)に渡す props です。block.json に書いても何も起きないので、効かないときはまずここを確認してください。

ただし、「ブロックごとにロックの強さを変えたい」という要件はあります。その場合は自分で属性を定義して、その値を props に渡します。属性名は何でも構いませんが、分かりやすさを優先して templateLock にしておくとよいでしょう。

src/block.json
{
    "attributes": {
        "templateLock": {
            "type": [ "string", "boolean" ],
            "default": "all"
        }
    }
}
src/edit.js
export default function Edit( { attributes } ) {
    const { templateLock } = attributes;
    const blockProps = useBlockProps();

    // 属性の値をそのまま props として渡す
    const innerBlocksProps = useInnerBlocksProps( blockProps, {
        template: TEMPLATE,
        templateLock,
    } );

    return <div { ...innerBlocksProps } />;
}

こうしておけば、パターン側で <!-- wp:mytheme/notice-box {"templateLock":"insert"} --> のように書き分けられます。InspectorControls に SelectControl を置いて、管理者だけが切り替えられる設定にすることも可能です。

ロックは子の InnerBlocks に引き継がれる

templateLock を指定していない InnerBlocks は、親の InnerBlocks のロック設定をそのまま引き継ぎます。トップレベルのブロックであれば、その投稿タイプに設定されたロックが使われます。

そのため、外側のブロックを templateLock="all" にすると、中に入れたグループブロックやカラムブロックの内側まで芋づる式にロックされます。「外枠は固定したいが、この部分だけは自由に編集させたい」という場合は、子側で明示的に templateLock={ false } を指定して継承を打ち消します。これが値に false が用意されている理由です。

contentOnly でテキストだけ編集させる

'contentOnly' は他の値と少し性格が違います。挿入・移動・削除を禁じるのは 'all' と同じですが、加えてコンテンツを持たないブロックがリスト表示から隠れ、ブロックとして選択できなくなります。編集者から見ると、グループやカラムといった「入れ物」のブロックが存在しないかのように振る舞い、見出しや段落、画像といった中身のブロックだけが操作対象になります。

効いてくるのは、デザインが決まっているセクションを流用する場面です。'all' だと入れ物のブロック自体は選択できてしまうため、編集者が誤ってグループブロックを選び、背景色やスペーシングを変えてしまうことがあります。'contentOnly' なら入れ物には触れられないので、「文言と画像を差し替えるだけ」という作業に自然と収まります。制作側が用意したパターンを、そのままの見た目で使い回してほしいときの選択肢です。

逆に、余白や配置の調整まで現場に任せたいなら 'contentOnly' は窮屈すぎます。まず 'insert' で「増やせない・減らせない」だけを担保し、それでも崩れるようなら 'all'、さらに入れ物にも触らせたくないなら 'contentOnly'、と段階的に強めていくのが運用しやすい進め方です。

投稿タイプ全体をロックする

個々のブロックではなく「このカスタム投稿タイプは必ずこの構成で入力してほしい」という場合は、register_post_type() の template と template_lock を使います。ここで指定したロックは、エディターのトップレベルのブロックに対して効きます。

functions.php
function mytheme_register_recipe() {
    register_post_type( 'recipe', array(
        'label'         => 'レシピ',
        'public'        => true,
        'show_in_rest'  => true, // ブロックエディターを使うために必須
        // 初期構成。[ ブロック名, 属性 ] の配列で並べる
        'template'      => array(
            array( 'core/heading', array( 'level' => 2, 'content' => '材料' ) ),
            array( 'core/list', array() ),
            array( 'core/heading', array( 'level' => 2, 'content' => '作り方' ) ),
            array( 'core/paragraph', array() ),
        ),
        // 構成を固定する
        'template_lock' => 'all',
    ) );
}
add_action( 'init', 'mytheme_register_recipe' );

template_lock に指定できるのは 'all' / 'insert' / 'contentOnly' と、ロックしない false(既定値)です。名前が templateLock ではなくスネークケースの template_lock である点に注意してください。また、template を設定していないと template_lock は意味を持ちません。ロックだけ書いても、そもそも固定すべき構成が無い状態になります。

ロックを設定したのに解除できてしまうとき

ロックはユーザー権限ではなく編集 UI の制限

一番よくあるのがこれです。lock 属性によるロックは、オプションメニューの「ロック」からいつでも解除できます。編集者が意図的に外そうと思えば外せる、という前提の機能です。

ロックを「解除させない」ところまで担保したいなら、自作ブロックであれば block.json の supports.lock を false にして切り替え UI ごと消します。コアブロックが対象なら、親を templateLock で囲ってしまうほうが確実です。ただし、どちらもエディターの表示を制御しているだけで、権限(capability)による制限ではありません。うっかりミスを防ぐためのガードだと考えてください。

子ブロックの lock が親の templateLock より優先されている

親を templateLock="all" にしたのに一部のブロックだけ動かせる、というときは、そのブロックのコメントに lock 属性が入っていないか確認してください。ブロック単位の lock はテンプレートのロックより優先されるため、{"lock":{"move":false,"remove":false}} が書かれていると、ロックされたテンプレートの中でもそのブロックだけ移動・削除できます。

これは不具合ではなく、意図的に用意された抜け道です。「全体は固定したいが、この1ブロックだけは差し替えを許可したい」という設計は、この優先順位を利用して実現します。逆に想定外の挙動として現れたときは、パターンや過去の編集で lock が残っていることが原因です。

子の InnerBlocks で継承が切れている

入れ子が深いブロックでは、途中の InnerBlocks に templateLock={ false } が書かれていると、そこから下はロックされません。自作ブロックを組み合わせている場合、他人が書いたブロックの edit.js に false が指定されている可能性があります。親側をいくら固定しても効かないときは、子ブロック側の実装を疑ってみてください。

コードエディターや REST API は素通りする

ブロックエディターの「コードエディター」に切り替えると、本文は単なるテキストとして編集できます。ロックの仕組みはブロック単位の UI として実装されているので、ここでは何の制約も受けません。REST API から本文を更新する場合も同様です。

つまりロック機能は、通常の編集操作で壊れないようにするための仕組みであって、改変の禁止ではありません。本当に触らせたくない要素は、投稿本文に置かずテンプレート側(テーマファイルやテンプレートパーツ)に持たせるのが根本的な解決になります。

まとめ

ブロックのロックには2つの層があります。ブロック1つに効く lock 属性は {"move":true,"remove":true} の形でブロックコメントに保存され、エディターの「ロック」UI から設定できるほか、パターンに直接書いておけます。入れ子の中身をまとめて固定する templateLock は InnerBlocks / useInnerBlocksProps の props として渡し、'all' / 'insert' / 'contentOnly' / false で強さを選びます。

間違えやすいのは、templateLock が block.json の設定ではなく props だという点と、supports.lock: false が「ロックする」ではなく「ロック UI を消す」設定だという点です。この2つを押さえておけば、「設定したのに効かない」の大半は解消します。

運用としては、まず 'insert' のような弱いロックから始めて、実際に崩れた箇所に合わせて 'all' や 'contentOnly' に強めていくのがおすすめです。最初から全部固定すると、今度は「編集できなくて困る」という別の問い合わせが増えます。編集者が何を変えたいのかを見ながら、ちょうどよい範囲を探してみてください。

参考ページ