Радиокнопки для таксономий WordPress - как настроить для одной таксономии
Я пытаюсь заменить чекбоксы для терминов в админке на радиокнопки. Нашел эту тему: Изменение вида элементов управления для пользовательских таксономий, которая помогла мне в этом. Однако этот код изменяет ВСЕ чекбоксы терминов на радиокнопки.
Можно ли применить это только для одной таксономии?
Мой код:
add_action('add_meta_boxes','mysite_add_meta_boxes',10,2);
function mysite_add_meta_boxes($post_type, $post) {
ob_start(); // Начинаем буферизацию вывода
}
add_action('dbx_post_sidebar','mysite_dbx_post_sidebar');
function mysite_dbx_post_sidebar() {
$html = ob_get_clean(); // Получаем содержимое буфера
$html = str_replace('"checkbox"','"radio"',$html); // Заменяем чекбоксы на радиокнопки
echo $html; // Выводим измененный HTML
}
спасибо

Однако это превратит ВСЕ чекбоксы терминов в радиокнопки.
Более того, это затронет любые чекбоксы внутри метабоксов — что не идеально!
Вместо этого давайте точечно нацелимся на функцию wp_terms_checklist()
, которая используется для генерации списка чекбоксов в админке (включая быстрое редактирование).
/**
* Заменяем чекбоксы на радиокнопки в чеклистах терминов для указанных таксономий.
*
* @param array $args
* @return array
*/
function wpse_139269_term_radio_checklist( $args ) {
if ( ! empty( $args['taxonomy'] ) && $args['taxonomy'] === 'category' /* <== Укажите вашу таксономию */ ) {
if ( empty( $args['walker'] ) || is_a( $args['walker'], 'Walker' ) ) { // Не переопределяем сторонние walker'ы.
if ( ! class_exists( 'WPSE_139269_Walker_Category_Radio_Checklist' ) ) {
/**
* Кастомный walker для замены чекбоксов на радиокнопки.
*
* @see Walker_Category_Checklist
*/
class WPSE_139269_Walker_Category_Radio_Checklist extends Walker_Category_Checklist {
function walk( $elements, $max_depth, ...$args ) {
$output = parent::walk( $elements, $max_depth, ...$args );
$output = str_replace(
array( 'type="checkbox"', "type='checkbox'" ),
array( 'type="radio"', "type='radio'" ),
$output
);
return $output;
}
}
}
$args['walker'] = new WPSE_139269_Walker_Category_Radio_Checklist;
}
}
return $args;
}
add_filter( 'wp_terms_checklist_args', 'wpse_139269_term_radio_checklist' );
Мы используем фильтр wp_terms_checklist_args
, затем реализуем собственный кастомный "walker" (классы, используемые для генерации иерархических списков). После этого просто заменяем строку type="checkbox"
на type="radio"
, если таксономия соответствует указанной (в данном случае "category").

Внимание, это отличное решение, но оно вызывает проблему с быстрым редактированием. Хотя оно и преобразует чекбоксы быстрого редактирования в радиокнопки, оно не выбирает отмеченную радиокнопку, а вместо этого очищает список. При быстром редактировании каждый раз приходится заново выбирать категорию, иначе она удаляется.

@Howdy_McGee Смотрите мой ответ для подхода с дополнительными изменениями, которые преодолевают это ограничение.

При быстром редактировании категории значение не устанавливается. Пожалуйста, подскажите решение.

Небольшая корректировка, необходимая для WP версии 5.3 и выше:
function walk( $elements, $max_depth, ...$args ) {
$output = parent::walk( $elements, $max_depth, ...$args );
В этих двух строках необходимо поставить многоточие перед $args
, иначе будет выдаваться ошибка.
Попробую внести правки в ответ выше...

Есть запрос на добавление этой функциональности в ядро WordPress, который помечен как "принятый", но долгое время неактивен. Можете присоединиться к обсуждению/помочь в реализации: https://core.trac.wordpress.org/ticket/14877

Следующий код делает практически то же самое, что и решение @TheDeadMedic в его ответе, который помог мне наполовину, так что это своего рода дополнение к нему. По личным предпочтениям я выбрал реализацию через start_el
.
→ убедитесь заменить YOUR-TAXONOMY в коде ниже в соответствии с вашими потребностями
add_filter( 'wp_terms_checklist_args', 'wpse_139269_term_radio_checklist_start_el_version', 10, 2 );
function wpse_139269_term_radio_checklist_start_el_version( $args, $post_id ) {
if ( ! empty( $args['taxonomy'] ) && $args['taxonomy'] === 'YOUR-TAXONOMY' ) {
if ( empty( $args['walker'] ) || is_a( $args['walker'], 'Walker' ) ) { // Не переопределяем сторонние walkers.
if ( ! class_exists( 'WPSE_139269_Walker_Category_Radio_Checklist_Start_El_Version' ) ) {
class WPSE_139269_Walker_Category_Radio_Checklist_Start_El_Version extends Walker_Category_Checklist {
public function start_el( &$output, $category, $depth = 0, $args = array(), $id = 0 ) {
if ( empty( $args['taxonomy'] ) ) {
$taxonomy = 'category';
} else {
$taxonomy = $args['taxonomy'];
}
if ( $taxonomy == 'category' ) {
$name = 'post_category';
} else {
$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'];
/** Этот фильтр описан в wp-includes/category-template.php */
if ( ! empty( $args['list_only'] ) ) {
$aria_cheched = 'false';
$inner_class = 'category';
if ( in_array( $category->term_id, $args['selected_cats'] ) ) {
$inner_class .= ' selected';
$aria_cheched = 'true';
}
$output .= "\n" . '<li' . $class . '>' .
'<div class="' . $inner_class . '" data-term-id=' . $category->term_id .
' tabindex="0" role="checkbox" aria-checked="' . $aria_cheched . '">' .
esc_html( apply_filters( 'the_category', $category->name ) ) . '</div>';
} else {
$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( in_array( $category->term_id, $args['selected_cats'] ), true, false ) .
disabled( empty( $args['disabled'] ), false, false ) . ' /> ' .
esc_html( apply_filters( 'the_category', $category->name ) ) . '</label>';
}
}
}
}
$args['walker'] = new WPSE_139269_Walker_Category_Radio_Checklist_Start_El_Version;
}
}
return $args;
}
Как правильно отметил в своем комментарии @Howdy_McGee, это не работает корректно с быстрым/встроенным редактированием. Приведенный выше код правильно обрабатывает сохранение, но радио-кнопка при встроенном редактировании не отмечена. Конечно, мы хотим исправить это, поэтому я сделал следующее:
→ написать jQuery-код для обработки состояния checked
→ имя файла: editphp-inline-edit-tax-radio-hack.js - используется ниже для подключения
jQuery(document).ready(function($) {
var taxonomy = 'status',
post_id = null,
term_id = null,
li_ele_id = null;
$('a.editinline').on('click', function() {
post_id = inlineEditPost.getId(this);
$.ajax({
url: ajaxurl,
data: {
action: 'wpse_139269_inline_edit_radio_checked_hack',
'ajax-taxonomy': taxonomy,
'ajax-post-id': post_id
},
type: 'POST',
dataType: 'json',
success: function (response) {
term_id = response;
li_ele_id = 'in-' + taxonomy + '-' + term_id;
$( 'input[id="'+li_ele_id+'"]' ).attr( 'checked', 'checked' );
}
});
});
});
→ нам нужно AJAX-действие - как видно в блоке кода выше
add_action( 'wp_ajax_wpse_139269_inline_edit_radio_checked_hack', 'wpse_139269_inline_edit_radio_checked_hack' );
add_action( 'wp_ajax_nopriv_wpse_139269_inline_edit_radio_checked_hack', 'wpse_139269_inline_edit_radio_checked_hack' );
function wpse_139269_inline_edit_radio_checked_hack() {
$terms = wp_get_object_terms(
$_POST[ 'ajax-post-id' ],
$_POST[ 'ajax-taxonomy' ],
array( 'fields' => 'ids' )
);
$result = $terms[ 0 ];
echo json_encode($result);
exit;
die();
}
→ подключение скрипта выше
→ измените путь в соответствии с вашими потребностями
add_action( 'admin_enqueue_scripts', 'wpse_139269_inline_edit_radio_checked_hack_enqueue_script' );
function wpse_139269_inline_edit_radio_checked_hack_enqueue_script() {
wp_enqueue_script(
'editphp-inline-edit-tax-radio-hack-js',
get_template_directory_uri() . '/your/path/editphp-inline-edit-tax-radio-hack.js',
array( 'jquery' )
);
}
Это работает довольно хорошо, но только в первый раз - при повторном открытии встроенного редактирования состояние checked снова теряется. Конечно, мы этого не хотим. Чтобы обойти это, я использовал метод, найденный здесь от @brasofilo. Он перезагружает обновленный раздел встроенного редактирования. Это приводит к правильному отображению радио-кнопки, независимо от того, сколько раз она изменялась.
→ убедитесь заменить YOUR-POST-TYPE в коде ниже в соответствии с вашими потребностями
add_action( 'wp_ajax_inline-save', 'wpse_139269_wp_ajax_inline_save', 0 );
function wpse_139269_wp_ajax_inline_save() {
global $wp_list_table;
check_ajax_referer( 'inlineeditnonce', '_inline_edit' );
if ( ! isset($_POST['post_ID']) || ! ( $post_ID = (int) $_POST['post_ID'] ) )
wp_die();
if ( 'page' == $_POST['post_type'] ) {
if ( ! current_user_can( 'edit_page', $post_ID ) )
wp_die( __( 'Вы не можете редактировать эту страницу.' ) );
} else {
if ( ! current_user_can( 'edit_post', $post_ID ) )
wp_die( __( 'Вы не можете редактировать эту запись.' ) );
}
if ( $last = wp_check_post_lock( $post_ID ) ) {
$last_user = get_userdata( $last );
$last_user_name = $last_user ? $last_user->display_name : __( 'Кто-то' );
printf( $_POST['post_type'] == 'page' ? __( 'Сохранение отключено: %s сейчас редактирует эту страницу.' ) : __( 'Сохранение отключено: %s сейчас редактирует эту запись.' ), esc_html( $last_user_name ) );
wp_die();
}
$data = &$_POST;
$post = get_post( $post_ID, ARRAY_A );
// Поскольку данные приходят из базы.
$post = wp_slash($post);
$data['content'] = $post['post_content'];
$data['excerpt'] = $post['post_excerpt'];
// Переименование.
$data['user_ID'] = get_current_user_id();
if ( isset($data['post_parent']) )
$data['parent_id'] = $data['post_parent'];
// Статус.
if ( isset($data['keep_private']) && 'private' == $data['keep_private'] )
$data['post_status'] = 'private';
else
$data['post_status'] = $data['_status'];
if ( empty($data['comment_status']) )
$data['comment_status'] = 'closed';
if ( empty($data['ping_status']) )
$data['ping_status'] = 'closed';
// Исключаем термины таксономий, которые не должны появляться в Быстром редактировании.
if ( ! empty( $data['tax_input'] ) ) {
foreach ( $data['tax_input'] as $taxonomy => $terms ) {
$tax_object = get_taxonomy( $taxonomy );
/** Этот фильтр описан в wp-admin/includes/class-wp-posts-list-table.php */
if ( ! apply_filters( 'quick_edit_show_taxonomy', $tax_object->show_in_quick_edit, $taxonomy, $post['post_type'] ) ) {
unset( $data['tax_input'][ $taxonomy ] );
}
}
}
// Хак: wp_unique_post_slug() не работает для черновиков, поэтому мы сделаем вид, что наша запись опубликована.
if ( ! empty( $data['post_name'] ) && in_array( $post['post_status'], array( 'draft', 'pending' ) ) ) {
$post['post_status'] = 'publish';
$data['post_name'] = wp_unique_post_slug( $data['post_name'], $post['ID'], $post['post_status'], $post['post_type'], $post['post_parent'] );
}
// Обновляем запись.
edit_post();
$wp_list_table = _get_list_table( 'WP_Posts_List_Table', array( 'screen' => $_POST['screen'] ) );
$level = 0;
$request_post = array( get_post( $_POST['post_ID'] ) );
$parent = $request_post[0]->post_parent;
while ( $parent > 0 ) {
$parent_post = get_post( $parent );
$parent = $parent_post->post_parent;
$level++;
}
$wp_list_table->display_rows( array( get_post( $_POST['post_ID'] ) ), $level );
if( $_POST['post_type'] == 'YOUR-POST-TYPE' ) {
?>
<script type="text/javascript">
document.location.reload(true);
</script>
<?php
}
wp_die();
}
Примечание: Не всесторонне протестировано, но пока работает хорошо

Вы можете использовать параметр meta_box_cb
функции register_taxonomy
для определения собственной функции метабокса. С помощью этой статьи я создал следующий сниппет:
function YOUR_TAXONOMY_NAME_meta_box($post, $meta_box_properties){
$taxonomy = $meta_box_properties['args']['taxonomy'];
$tax = get_taxonomy($taxonomy);
$terms = get_terms($taxonomy, array('hide_empty' => 0));
$name = 'tax_input[' . $taxonomy . ']';
$postterms = get_the_terms( $post->ID, $taxonomy );
$current = ($postterms ? array_pop($postterms) : false);
$current = ($current ? $current->term_id : 0);
?>
<div id="taxonomy-<?php echo $taxonomy; ?>" class="categorydiv">
<ul id="<?php echo $taxonomy; ?>-tabs" class="category-tabs">
<li class="tabs"><a href="#<?php echo $taxonomy; ?>-all"><?php echo $tax->labels->all_items; ?></a></li>
</ul>
<div id="<?php echo $taxonomy; ?>-all" class="tabs-panel">
<input name="tax_input[<?php echo $taxonomy; ?>][]" value="0" type="hidden">
<ul id="<?php echo $taxonomy; ?>checklist" data-wp-lists="list:symbol" class="categorychecklist form-no-clear">
<?php foreach($terms as $term){
$id = $taxonomy.'-'.$term->term_id;?>
<li id="<?php echo $id?>"><label class="selectit"><input value="<?php echo $term->term_id; ?>" name="tax_input[<?php echo $taxonomy; ?>][]" id="in-<?php echo $id; ?>"<?php if( $current === (int)$term->term_id ){?> checked="checked"<?php } ?> type="radio"> <?php echo show_symbol( $term->name ); ?></label></li>
<?php }?>
</ul>
</div>
</div>
<?php
}
Чтобы использовать этот метабокс, вам нужно передать этот параметр в функцию register_taxonomy
:
'meta_box_cb' => 'YOUR_TAXONOMY_NAME_meta_box'
Прелесть этого кода в том, что вам не нужно передавать никаких параметров, так как он использует параметры, переданные ему функцией register_taxonomy
. Это объект post
и массив, содержащий информацию о самом метабоксе.

Отличный ответ в целом. Но будьте осторожны: у вас есть <?php echo show_symbol( $term->name ); ?> в качестве вывода, разве не должно быть <?php echo $term->name; ?>? Не смог найти ничего о функции с таким названием. И как предложение: раз вы показываете все доступные категории, вероятно, вам не нужно выводить вкладки.

Если вы предпочитаете реализовать это с помощью плагина, обратите внимание на https://wordpress.org/plugins/radio-buttons-for-taxonomies/
Этот плагин позволяет заменить стандартные блоки таксономий на пользовательские метабоксы с радиокнопками, что фактически ограничивает каждую запись одним термином в этой таксономии.
