MTAppjQuery で特定のユーザーにだけ記事を「公開」させない方法

2026-07-03
13分で読了
更新: 2026-07-03
restrict-publish-button-by-user.webp

目次

Movable Type で記事を運用していると、「保存や更新はしてほしいけれど、公開だけは承認を経てからにしたい」という相談をいただくことがあります。編集担当のかたには自由に下書きを作ってもらいつつ、公開の判断は責任者が行う、という運用ですね。

MTAppjQuery には「公開ボタンを隠す」といった専用の機能はありません。ですが、user.js に少しだけコードを書くことで、この要望に近い形を実現できます。今回はその方法を、つまずきやすいポイントも含めてご紹介します。

まず知っておきたい MT の「公開ボタン」

はじめに、Movable Type の編集画面の作りを確認しておきましょう。ここを理解しておくと、なぜ単純に「ボタンを消す」だけでは済まないのかが見えてきます。

記事編集画面の右側には、青い保存ボタンが並んでいます。一見すると「公開」という独立したボタンがあるように思えますが、実はそうではありません。MT の編集画面には、ステータスを選ぶ <select name="status"> があり、その値に応じて保存ボタンの動作とラベルが「公開」「下書き保存」「更新」と切り替わる仕組みになっています。

つまり、保存ボタンそのものを非表示にしたり無効化したりすると、公開だけでなく保存や更新まで一緒に止まってしまいます。これでは「保存はさせたい」という要望を満たせません。

そこで今回は、ボタンではなくステータスの選択肢のほうを制御するアプローチを取ります。ステータスから「公開」だけを選べなくすれば、保存と更新は通常どおり行いつつ、公開だけを抑えられます。

つまずきポイント - 公開済みの記事を壊さない

方針が決まったところで、素直に実装するとこう書きたくなります。「公開」の <option>disabled を付けて選べなくする、というものです。

Array.from(statusSelect.options).forEach((option) => {
    if (option.textContent.trim() === '公開') {
        option.disabled = true;
    }
});

一見これで良さそうなのですが、ここに落とし穴があります。

HTML の仕様では、disabled を付けた <option> は、たとえ選択された状態であってもフォーム送信のデータから除外されます。すると、すでに公開済みの記事を対象のユーザーが開いて更新しようとしたときに問題が起きます。公開済みの記事は最初から「公開」が選択されていますから、その状態で disabled を付けると、status の値が送信されずに保存時のエラーにつながってしまうのです。

対処はシンプルです。ページを読み込んだ時点ですでに「公開」が選ばれている記事は、disabled を付けないようにします。未公開の記事だけ「公開」を選べなくすれば、公開済みの記事の更新はこれまでどおり通ります。

const initialStatusText = statusSelect.options[statusSelect.selectedIndex]?.textContent.trim() ?? '';
const initiallyPublished = initialStatusText === '公開';

if (!initiallyPublished) {
    Array.from(statusSelect.options).forEach((option) => {
        if (option.textContent.trim() === '公開') {
            option.disabled = true;
        }
    });
}

注釈を「他のフィールドと同じ余白」で添える

選択肢を制限するだけだと、編集担当のかたは「なぜ公開できないのだろう」と戸惑ってしまいます。そこで、公開ウィジェットの下に説明の一文を添えておきましょう。

ここでも小さな工夫が要ります。公開ウィジェット(#entry-publishing-widget)に直接テキストを追加すると、左右の余白が付かず、枠線ギリギリに文字が寄ってしまいます。保存ボタンと同じ内側のコンテナに追加すると、他のフィールドと揃った自然な余白になります。

const note = document.createElement('p');
note.className = 'text-muted small mt-2 mb-0';
note.textContent = 'このアカウントでは公開できません。公開には承認者による操作が必要です。保存・更新は通常どおり行えます。';
const primaryButton = publishingWidget.querySelector('.btn-primary[name="status"]');
const noteContainer = primaryButton?.parentElement ?? publishingWidget;
noteContainer.appendChild(note);

完成したコード

ここまでの内容をまとめたものが、次のコードです。user.js に貼り付けて使えます。restrictedAuthorIds に、公開を制限したいユーザーの author_id を並べてください。

(function ($) {

    // 記事編集画面・コンテンツデータ編集画面でのみ実行
    if (!['edit-entry', 'edit-content-type-data'].includes(mtappVars.screen_id)) {
        return;
    }

    // 公開に承認が必要なユーザーの author_id 一覧
    // ログイン中のユーザーで mtapp.debug() を実行するとコンソールに author_id が表示されます
    const restrictedAuthorIds = [12, 34];

    if (!restrictedAuthorIds.includes(mtappVars.author_id)) {
        return;
    }

    const statusSelect = document.querySelector('select[name="status"]');
    const publishingWidget = document.querySelector('#entry-publishing-widget');

    if (!statusSelect || !publishingWidget) {
        return;
    }

    // ページ読み込み時点の選択状態を記録(既に公開済みの記事かどうかの判定用)
    const initialStatusText = statusSelect.options[statusSelect.selectedIndex]?.textContent.trim() ?? '';
    const initiallyPublished = initialStatusText === '公開';

    // 「公開」の選択肢を非活性化
    // すでに「公開」が選択されている記事(公開済み記事)は disabled にしない。
    // disabled な option は selected であっても送信データから除外されるため、
    // ここで disabled にすると公開済み記事の更新時に status が送信されずエラーになる。
    if (!initiallyPublished) {
        Array.from(statusSelect.options).forEach((option) => {
            if (option.textContent.trim() === '公開') {
                option.disabled = true;
            }
        });
    }

    // 公開ウィジェットの下に注釈を追加
    // #entry-publishing-widget に直接追加すると左右の余白が付かないため、
    // 保存ボタンなどと同じ内側のコンテナ(左右のパディングを持つ要素)に追加する
    const note = document.createElement('p');
    note.className = 'text-muted small mt-2 mb-0';
    note.textContent = 'このアカウントでは公開できません。公開には承認者による操作が必要です。保存・更新は通常どおり行えます。';
    const primaryButton = publishingWidget.querySelector('.btn-primary[name="status"]');
    const noteContainer = primaryButton?.parentElement ?? publishingWidget;
    noteContainer.appendChild(note);

    // 保険:何らかの理由で「公開」が選択された状態のまま送信された場合にブロック
    const form = statusSelect.closest('form');
    if (form) {
        form.addEventListener('submit', (e) => {
            const selectedText = statusSelect.options[statusSelect.selectedIndex]?.textContent.trim() ?? '';

            if (selectedText === '公開' && initialStatusText !== '公開') {
                e.preventDefault();
                alert('このアカウントでは公開できません。下書きとして保存してください。');
            }
        });
    }

})(jQuery);

対象ユーザーの author_id は、そのユーザーでログインした状態で mtapp.debug() を実行するとコンソールに表示されます。あわせて画面ごとの条件文も確認できるので、カスタマイズの下調べに便利です。

注意点

このカスタマイズは、あくまで管理画面の見た目と操作性を整えるものです。JavaScript による制御なので、ブラウザの開発者ツールを使えば技術的には回避できてしまいます。

ですから、これは「うっかり公開してしまうのを防ぎ、運用ルールをわかりやすく示す」ための仕組み、と捉えていただくのが正確です。「権限のないユーザーには何があっても絶対に公開させない」という厳格なアクセス制御が必要な場面では、Perl 側の pre_save.entry コールバックなど、サーバーサイドでの検証を組み合わせることをおすすめします。用途に応じて、どちらのレベルの対応が必要かを見極めていただければと思います。

まとめ

MT の「公開ボタン」は独立したボタンではなく、ステータス選択の値で動作が変わる保存ボタンでした。この性質を踏まえると、公開だけを抑える方法は次のように整理できます。

  1. ボタンではなく、ステータスの「公開」選択肢のほうを非活性にする
  2. 公開済みの記事は選択肢を触らない(disabledoption は送信されないため)
  3. 注釈は保存ボタンと同じコンテナに添えて、余白を他のフィールドと揃える
  4. 厳格な制御が必要なら、サーバーサイドの検証も検討する

user.js だけで、承認フローに近い運用の土台を用意できます。編集担当のかたと責任者で役割を分けたい場面があれば、ぜひ試してみてください。

この記事をシェア

関連記事