Wordpress - WordPress taxonomy radio buttons
However, this will turn ALL terms checkboxes to radio buttons.
Not only that, it'll turn any checkbox in a meta box - not ideal!
Instead, let's specifically target the wp_terms_checklist()
function, which is used to generate the list of checkboxes across the admin (including quick edit).
/**
* Use radio inputs instead of checkboxes for term checklists in specified taxonomies.
*
* @param array $args
* @return array
*/
function wpse_139269_term_radio_checklist( $args ) {
if ( ! empty( $args['taxonomy'] ) && $args['taxonomy'] === 'category' /* <== Change to your required taxonomy */ ) {
if ( empty( $args['walker'] ) || is_a( $args['walker'], 'Walker' ) ) { // Don't override 3rd party walkers.
if ( ! class_exists( 'WPSE_139269_Walker_Category_Radio_Checklist' ) ) {
/**
* Custom walker for switching checkbox inputs to radio.
*
* @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' );
We hook onto the wp_terms_checklist_args
filter, then implement our own custom "walker" (a family of classes used to generate hierarchical lists). From there, it's a simply string replace of type="checkbox"
with type="radio"
if the taxonomy is whatever we've configured it to match (in this case "category").
The following does pretty much what @TheDeadMedic did at his answer good answer, brought me there half the way, so this is kind of just an addition to it. Out of personal preference I opted to do it with start_el
.
→ make sure to replace YOUR-TAXONOMY in below code according to your needs
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' ) ) { // Don't override 3rd party 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'];
/** This filter is documented in 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;
}
Now as @ Howdy_McGee correctly stated in his comment this doesn't work nicely, correctly with the quick/inline edit. The above code handles the saving correctly, but the radio at the inline edit isn't checked. Of course we want that, for this I have done this:
→ write some JQuery code to handle the checked state
→ file name: editphp-inline-edit-tax-radio-hack.js - used below for enqueueing
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' );
}
});
});
});
→ we need an AJAX action - as seen in above code block
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();
}
→ enqueueing above script
→ change the path information according to your needs
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' )
);
}
This is working quite nicely so far, but only for the first time, when opening the inline edit a second time we have lost the checked state again. We obviously don't want that. To get around it I used a method I've found here by @brasofilo. What it does is reloading the updated inline edit section. This leads to the radio checkbox being correctly shown, no matter how often it is changed.
→ make sure to replace YOUR-POST-TYPE in below code according to your needs
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( __( 'You are not allowed to edit this page.' ) );
} else {
if ( ! current_user_can( 'edit_post', $post_ID ) )
wp_die( __( 'You are not allowed to edit this post.' ) );
}
if ( $last = wp_check_post_lock( $post_ID ) ) {
$last_user = get_userdata( $last );
$last_user_name = $last_user ? $last_user->display_name : __( 'Someone' );
printf( $_POST['post_type'] == 'page' ? __( 'Saving is disabled: %s is currently editing this page.' ) : __( 'Saving is disabled: %s is currently editing this post.' ), esc_html( $last_user_name ) );
wp_die();
}
$data = &$_POST;
$post = get_post( $post_ID, ARRAY_A );
// Since it's coming from the database.
$post = wp_slash($post);
$data['content'] = $post['post_content'];
$data['excerpt'] = $post['post_excerpt'];
// Rename.
$data['user_ID'] = get_current_user_id();
if ( isset($data['post_parent']) )
$data['parent_id'] = $data['post_parent'];
// Status.
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';
// Exclude terms from taxonomies that are not supposed to appear in Quick Edit.
if ( ! empty( $data['tax_input'] ) ) {
foreach ( $data['tax_input'] as $taxonomy => $terms ) {
$tax_object = get_taxonomy( $taxonomy );
/** This filter is documented in 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 ] );
}
}
}
// Hack: wp_unique_post_slug() doesn't work for drafts, so we will fake that our post is published.
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'] );
}
// Update the post.
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();
}
Note: Not extensively tested, but so far working well