【プラグイン不要】カテゴリー選択をラジオボタンにする方法(ブロックエディター対応)

【プラグイン不要】カテゴリー選択をラジオボタンにする方法(ブロックエディター対応)

カテゴリーのラジオボタン実装には、以前「Radio Buttons for Taxonomies」プラグインを使用していました。
このプラグインはブロックエディターにも対応しており、簡単にカテゴリー選択をラジオボタンにできるので重宝していました。
しかし、いくつかの課題がありました:

  1. プラグインの開発が2年以上更新されておらず、メンテナンス状況に懸念がありました。
  2. 実装後、ラジオボタンの選択肢に「カテゴリーなし」という不要なオプションが自動的に追加されてしまいました。

これらの問題を解決するため、カスタム実装への移行を決定しました。
新しい実装は、生成AI「Claude」との協力のもとで開発しました。
この方法では、functions.phpにコードをコピペしたあと、少しコードを変更するだけで、特定のカテゴリー選択をラジオボタンに変更することができます。

実装イメージ

投稿編集画面

ブロックエディターのサイドバーに、このような形で出ます。

投稿一覧の管理画面

投稿記事の一覧にある「クイック編集」にも対応させました。
(この対応に結構苦労しました…)

実装方法

functions.phpに以下のコードをコピペしてください。
(必ずテスト環境でテストしてから、本番に入れてくださいね。)

その際に、4行目あたりのworks_categoryをタクソノミーのスラッグに変更してください。


<?php
// ラジオボタンにするカテゴリー選択
function change_interviews_category_to_radio($args, $post_type, $taxonomy) {
    if ($taxonomy !== 'works_category') {//←ここに変更したいカスタムタクソノミーのスラッグを入れる
        return $args;
    }

    // カスタムメタボックスコールバックとサニタイズ関数を設定
    $args['meta_box_cb'] = 'interviews_category_radio_meta_box';
    $args['meta_box_sanitize_cb'] = 'sanitize_interviews_category';

    return $args;
}
add_filter('register_taxonomy_args', 'change_interviews_category_to_radio', 10, 3);

// カスタムメタボックスの表示
function interviews_category_radio_meta_box($post, $box) {
    $defaults = array('taxonomy' => 'interviews_category');
    if (!isset($box['args']) || !is_array($box['args']))
        $args = array();
    else
        $args = $box['args'];
    $args = wp_parse_args($args, $defaults);
    $taxonomy = $args['taxonomy'];
    $tax = get_taxonomy($taxonomy);
    $selected = wp_get_object_terms($post->ID, $taxonomy, array('fields' => 'ids'));
    $selected = (empty($selected) || is_wp_error($selected)) ? array() : $selected;
    ?>
    <div id="taxonomy-<?php echo $taxonomy; ?>" class="categorydiv">
        <ul id="<?php echo $taxonomy; ?>checklist" class="categorychecklist form-no-clear">
            <?php
            // カスタムウォーカーを使用してラジオボタンを表示
            wp_terms_checklist($post->ID, array(
                'taxonomy'      => $taxonomy,
                'selected_cats' => $selected,
                'walker'        => new Walker_Category_Radio(),
                'checked_ontop' => false
            ));
            ?>
        </ul>
        <!-- 新しいカテゴリー追加フォーム -->
        <div id="<?php echo $taxonomy; ?>-adder" class="wp-hidden-children">
            <a id="<?php echo $taxonomy; ?>-add-toggle" href="#<?php echo $taxonomy; ?>-add" class="hide-if-no-js">
                + <?php echo $tax->labels->add_new_item; ?>
            </a>
            <p id="<?php echo $taxonomy; ?>-add" class="category-add wp-hidden-child">
                <label class="screen-reader-text" for="new<?php echo $taxonomy; ?>"><?php echo $tax->labels->add_new_item; ?></label>
                <input type="text" name="new<?php echo $taxonomy; ?>" id="new<?php echo $taxonomy; ?>" class="form-required form-input-tip" value="<?php echo esc_attr($tax->labels->new_item_name); ?>" aria-required="true"/>
                <input type="button" id="<?php echo $taxonomy; ?>-add-submit" class="button category-add-submit" value="<?php echo esc_attr($tax->labels->add_new_item); ?>" />
                <?php wp_nonce_field('add-' . $taxonomy, '_ajax_nonce-add-' . $taxonomy, false); ?>
                <span id="<?php echo $taxonomy; ?>-ajax-response"></span>
            </p>
        </div>
    </div>
    <?php
}

// カテゴリー選択のサニタイズ
function sanitize_interviews_category($term_ids) {
    if (!is_array($term_ids)) {
        return array(absint($term_ids));
    }
    return array_map('absint', $term_ids);
}

// カスタムウォーカークラス:チェックボックスをラジオボタンに変更
class Walker_Category_Radio extends Walker {
    public function start_el(&$output, $category, $depth = 0, $args = array(), $id = 0) {
        $taxonomy = $args['taxonomy'];
        $name = 'tax_input[' . $taxonomy . '][]';
        $args['popular_cats'] = empty($args['popular_cats']) ? array() : $args['popular_cats'];
        $class = in_array($category->term_id, $args['popular_cats']) ? ' class="popular-category"' : '';

        $args['selected_cats'] = empty($args['selected_cats']) ? array() : $args['selected_cats'];
        $checked = in_array($category->term_id, $args['selected_cats']) ? ' checked="checked"' : '';

        $output .= "\n<li id='{$taxonomy}-{$category->term_id}'$class>" .
                   '<label class="selectit"><input value="' . $category->term_id . '" type="radio" name="' . $name . '" id="in-'.$taxonomy.'-' . $category->term_id . '"' . $checked . '/> ' .
                   esc_html(apply_filters('the_category', $category->name)) . '</label>';
    }
}

// Gutenbergエディタ用のラジオボタン実装
function interviews_category_radio_for_gutenberg() {
    $script = "
    ( function( wp ) {
        var el = wp.element.createElement;
        var RadioControl = wp.components.RadioControl;
        var __ = wp.i18n.__;
        var useSelect = wp.data.useSelect;
        var useDispatch = wp.data.useDispatch;

        wp.hooks.addFilter(
            'editor.PostTaxonomyType',
            'my-plugin/radio-control-interviews-category',
            function( OriginalComponent ) {
                return function( props ) {
                    if ( props.slug !== 'interviews_category' ) {
                        return el( OriginalComponent, props );
                    }

                    var terms = useSelect( function( select ) {
                        return select( 'core' ).getEntityRecords( 'taxonomy', 'interviews_category', { per_page: -1 } );
                    }, [] );

                    var selectedTerms = useSelect( function( select ) {
                        return select( 'core/editor' ).getEditedPostAttribute( 'interviews_category' );
                    }, [] );

                    var { editPost } = useDispatch( 'core/editor' );

                    if ( !terms ) {
                        return 'Loading...';
                    }

                    var options = terms.map( function( term ) {
                        return { label: term.name, value: term.id.toString() };
                    });

                    return el( RadioControl, {
                        label: __( 'Interviews Category', 'text-domain' ),
                        selected: selectedTerms && selectedTerms.length ? selectedTerms[0].toString() : '',
                        options: options,
                        onChange: function( newTerm ) {
                            editPost( { interviews_category: [ parseInt(newTerm) ] } );
                        }
                    } );
                };
            }
        );
    } )( window.wp );
    ";
    wp_add_inline_script( 'wp-edit-post', $script );
}
add_action( 'enqueue_block_editor_assets', 'interviews_category_radio_for_gutenberg' );

// カテゴリーの保存処理
function save_interviews_category($post_id) {
    // 自動保存、権限チェック
    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
    if (!current_user_can('edit_post', $post_id)) return;

    // post type チェック
    if (get_post_type($post_id) != 'interviews') return;

    // カテゴリーの保存
    if (isset($_POST['tax_input']['interviews_category']) && is_array($_POST['tax_input']['interviews_category'])) {
        $term_id = (int) $_POST['tax_input']['interviews_category'][0];
        wp_set_object_terms($post_id, $term_id, 'interviews_category');
    }
}

// クイック編集用のカスタムボックス(内容は空)
function custom_quick_edit_interviews_category($column_name, $post_type) {
    if ($column_name != 'taxonomy-interviews_category' || $post_type != 'interviews') return;
    // この関数の中身は空にします
}
add_action('quick_edit_custom_box', 'custom_quick_edit_interviews_category', 10, 2);

// チェックボックスをラジオボタンに変換するJavaScript
function convert_interviews_category_to_radio() {
    global $post_type;
    if ($post_type !== 'interviews') return;
    ?>
    <script type="text/javascript">
	jQuery(document).ready(function($) {
		// チェックボックスをラジオボタンに変換する関数
		function convertToRadio() {
			$('.interviews_category-checklist, #interviews_category-checklist').each(function() {
				var $checklist = $(this);
				$checklist.find('input[type="checkbox"]').each(function() {
					if ($(this).attr('type') !== 'radio') {
						$(this).attr('type', 'radio');
						// name属性を変更
						$(this).attr('name', 'tax_input[interviews_category][]');
					}
				});
			});
		}

		// 初期ロード時と動的に追加された要素に対して実行
		convertToRadio();
		$(document).ajaxComplete(function() {
			convertToRadio();
		});

		// 新しいカテゴリー追加処理
		$('#interviews_category-add-submit').on('click', function(e) {
			e.preventDefault();
			var newCat = $('#newinterviews_category').val();
			var nonce = $('#_ajax_nonce-add-interviews_category').val();

			$.post(ajaxurl, {
				action: 'add-interviews_category',
				newinterviews_category: newCat,
				_ajax_nonce: nonce,
				taxonomy: 'interviews_category'
			}, function(response) {
				if (response && response.success) {
					// 新しいカテゴリーをリストに追加
					var newTerm = response.data;
					var newItem = '<li id="interviews_category-' + newTerm.term_id + '">' +
								'<label class="selectit">' +
								'<input value="' + newTerm.term_id + '" type="radio" name="tax_input[interviews_category][]" id="in-interviews_category-' + newTerm.term_id + '"> ' +
								newTerm.name +
								'</label></li>';
					$('#interviews_categorychecklist').append(newItem);

					// 入力フィールドをクリア
					$('#newinterviews_category').val('');

					// ラジオボタンに変換
					convertToRadio();
				}
			});
		});

        // クイック編集用の処理
        var $wp_inline_edit = inlineEditPost.edit;
        inlineEditPost.edit = function(id) {
            $wp_inline_edit.apply(this, arguments);
            var post_id = 0;
            if (typeof(id) == 'object') {
                post_id = parseInt(this.getId(id));
            }
            if (post_id > 0) {
                var edit_row = $('#edit-' + post_id);
                var post_row = $('#post-' + post_id);

                // カテゴリー情報を取得
                var categoryText = $('.column-taxonomy-interviews_category', post_row).text().trim();

                // ラジオボタンに変換
                convertToRadio();

                // 重複したチェックリストを削除
                edit_row.find('.cat-checklist.interviews_category-checklist').not(':first').remove();

                // 各カテゴリーに対してチェックを入れる
                edit_row.find('input[name="tax_input[interviews_category][]"]').each(function() {
                    var label = $(this).parent().text().trim();
                    if (label === categoryText) {
                        $(this).prop('checked', true);
                    } else {
                        $(this).prop('checked', false);
                    }
                });
            }
        };

        // 既存の投稿一覧ページのチェックボックスをラジオボタンに変換
        $('.widefat.posts').on('click', '.editinline', function() {
            setTimeout(function() {
                convertToRadio();
            }, 200);
        });
    });
    </script>
    <?php
}
add_action('admin_footer-post.php', 'convert_interviews_category_to_radio');
add_action('admin_footer-post-new.php', 'convert_interviews_category_to_radio');
add_action('admin_footer-edit.php', 'convert_interviews_category_to_radio');

注意点

  1. プラグイン「Radio Buttons for Taxonomies」は削除してから、このコードを入れてください。
    サーバーエラー(500エラー)が発生して、サイトが閲覧できなくなります。
  2. 新しいカテゴリーを作成する時、なかなかページが更新されない」です。
    その時は「ショートカットキーなどで、ページ更新」をしてください。
    すると、新しいカテゴリーが作成されています。
【プラグイン不要】カテゴリー選択をラジオボタンにする方法(ブロックエディター対応)

この記事が気に入ったら
フォローしてね!

よかったらシェアしてね!