質問: カスタム投稿タイプごとにカスタム分類法で保存できる用語の数を制限する 投稿ごとに作成されるカスタム分類を1に制限するようにします。したがって、カスタム投稿タイプに追加されるタグは1つだけです。私はwp_set_object_termsを調べましたが、残念なことに、アクション/フィルタはこれに関して何かをするには少し遅すぎます。何か案は?配列の残りの部分の設定を解除して1だけを残すために自分をフックすることができる場所はどこですか。
回答: 分類単項 は、この機能を実装するのに役立つ小さなライブラリです。使用法 プラグインまたはテーマ内からclass.taxonomy-single-term.phpファイルを含めます クラスを初期化します(分類スラッグを自分のもので更新します):$custom_tax_mb = new taxonomy_single_term( 'custom-tax-slug' ); オプション 2番目のパラメータはpost_typesの配列で、3番目のパラメータは 'radio'か 'select'のどちらかです(デフォルトはradio)。 select post_typeでfoo型を使用するには:$custom_tax_mb = new taxonomy_single_term( 'custom-tax-slug', array( 'foo' ), 'select' ); このようにオプションのクラスプロパティを更新します。 // priority of the metabox placement. $custom_tax_mb->set( 'priority', 'low' ); // 'normal' to move it under the post content. $custom_tax_mb->set( 'context', 'normal' ); // custom title for your metabox $custom_tax_mb->set( 'metabox_title', __( 'custom metabox title', 'your-text-domain' ) ); // makes a selection required. $custom_tax_mb->set( 'force_selection', true ); // will keep radio elements from indenting for child-terms. $custom_tax_mb->set( 'indented', false ); // allows adding of new terms from the metabox $custom_tax_mb->set( 'allow_new_terms', true ); 完全を期すために、これが主なインクルードclass.taxonomy-single-term.phpです。<?php if ( ! class_exists( 'taxonomy_single_term' ) ) : /** * removes and replaces the built-in taxonomy metabox with <select> or series of <input type="radio" /> * * usage: * * $custom_tax_mb = new taxonomy_single_term( 'custom-tax-slug', array( 'post_type' ), 'type' ); // 'type' can be 'radio' or 'select' (default: radio) * * update optional properties: * * $custom_tax_mb->set( 'priority', 'low' ); * $custom_tax_mb->set( 'context', 'normal' ); * $custom_tax_mb->set( 'metabox_title', __( 'custom metabox title', 'yourtheme' ) ); * $custom_tax_mb->set( 'force_selection', true ); * $custom_tax_mb->set( 'indented', false ); * $custom_tax_mb->set( 'allow_new_terms', true ); * * @link http://codex.wordpress.org/function_reference/add_meta_box#parameters * @link https://github.com/webdevstudios/taxonomy_single_term/blob/master/readme.md * @version 0.2.1 */ class taxonomy_single_term { /** * post types where metabox should be replaced (defaults to all post_types associated with taxonomy) * @since 0.1.0 * @var array */ protected $post_types = array(); /** * taxonomy slug * @since 0.1.0 * @var string */ protected $slug = ''; /** * taxonomy object * @since 0.1.0 * @var object */ protected $taxonomy = false; /** * taxonomy_single_term_walker object * @since 0.1.0 * @var object */ protected $walker = false; /** * new metabox title. defaults to taxonomy name * @since 0.1.0 * @var string */ protected $metabox_title = ''; /** * metabox priority. (vertical placement) * 'high', 'core', 'default' or 'low' * @since 0.1.0 * @var string */ protected $priority = 'high'; /** * metabox position. (column placement) * 'normal', 'advanced', or 'side' * @since 0.1.0 * @var string */ protected $context = 'side'; /** * set to true to hide "none" option & force a term selection * @since 0.1.1 * @var boolean */ protected $force_selection = false; /** * whether hierarchical taxonomy inputs should be indented to represent hierarchy * @since 0.1.2 * @var boolean */ protected $indented = true; /** * checks if there is a bulk-edit term to set * @var boolean|term object */ protected $to_set = false; /** * array of post ids whose terms have been reset from bulk-edit. (prevents recursion) * @var array */ protected $single_term_set = array(); /** * what input element to use in the taxonomy meta box (radio or select) * @var array */ protected $input_element = 'radio'; /** * whether adding new terms via the metabox is permitted * @since 0.2.0 * @var boolean */ protected $allow_new_terms = false; /** * initiates our metabox action * @since 0.1.0 * @param string $tax_slug taxonomy slug * @param array $post_types post-types to display custom metabox */ public function __construct( $tax_slug, $post_types = array(), $type = 'radio' ) { $this->slug = $tax_slug; $this->post_types = is_array( $post_types ) ? $post_types : array( $post_types ); $this->input_element = in_array( (string) $type, array( 'radio', 'select' ) ) ? $type : $this->input_element; add_action( 'add_meta_boxes', array( $this, 'add_input_element' ) ); add_action( 'admin_footer', array( $this, 'js_checkbox_transform' ) ); add_action( 'wp_ajax_taxonomy_single_term_add', array( $this, 'ajax_add_term' ) ); // handle bulk-editing if ( isset( $_request['bulk_edit'] ) && 'update' == $_request['bulk_edit'] ) { $this->bulk_edit_handler(); } } /** * removes and replaces the built-in taxonomy metabox with our own. * @since 0.1.0 */ public function add_input_element() { // test the taxonomy slug construtor is an actual taxonomy if ( ! $this->taxonomy() ) { return; } foreach ( $this->post_types() as $key => $cpt ) { // remove default category type metabox remove_meta_box( $this->slug . 'div', $cpt, 'side' ); // remove default tag type metabox remove_meta_box( 'tagsdiv-' . $this->slug, $cpt, 'side' ); // add our custom radio box add_meta_box( $this->slug . '_input_element', $this->metabox_title(), array( $this, 'input_element' ), $cpt, $this->context, $this->priority ); } } /** * displays our taxonomy input metabox * @since 0.1.0 * @todo abstract inline javascript to it's own file and localize it */ public function input_element() { // uses same noncename as default box so no save_post hook needed wp_nonce_field( 'taxonomy_'. $this->slug, 'taxonomy_noncename' ); $class = $this->indented ? 'taxonomydiv' : 'not-indented'; $class .= 'category' !== $this->slug ? ' ' . $this->slug . 'div' : ''; $class .= ' tabs-panel'; $this->namefield = 'category' == $this->slug ? 'post_category' : 'tax_input[' . $this->slug . ']'; $this->namefield = $this->taxonomy()->hierarchical ? $this->namefield . '[]' : $this->namefield; $el_open_cb = $this->input_element . '_open'; $el_close_cb = $this->input_element . '_close'; ?> <div id="taxonomy-<?php echo $this->slug; ?>" class="<?php echo $class; ?>"> <?php $this->{$el_open_cb}() ?> <?php $this->term_fields_list(); ?> <?php $this->{$el_close_cb}() ?> <?php if ( $this->allow_new_terms ) { $this->terms_adder_button(); } ?> <div style="clear:both;"></div> </div> <?php } /** * select wrapper open * @since 0.2.0 */ public function select_open() { ?> <select style="display:block;width:100%;margin-top:12px;" name="<?php echo $this->namefield; ?>" id="<?php echo $this->slug; ?>checklist" class="form-no-clear"> <?php if ( ! $this->force_selection ) : ?> <option value="0"><?php echo esc_html( apply_filters( 'taxonomy_single_term_select_none', __( 'none' ) ) ); ?></option> <?php endif; } /** * radio wrapper open * @since 0.2.0 */ public function radio_open() { ?> <ul id="<?php echo $this->slug; ?>checklist" data-wp-lists="list:<?php echo $this->slug; ?>" class="categorychecklist form-no-clear"> <?php if ( ! $this->force_selection ) : ?> <li style="display:none;"> <input id="taxonomy-<?php echo $this->slug; ?>-clear" type="radio" name="<?php echo $this->namefield; ?>" value="0" /> </li> <?php endif; } /** * select wrapper close * @since 0.2.0 */ public function select_close() { ?> </select> <?php } /** * radio wrapper close * @since 0.2.0 */ public function radio_close() { ?> </ul> <p style="margin-bottom:0;float:left;width:50%;"> <a class="button" id="taxonomy-<?php echo $this->slug; ?>-trigger-clear" href="#"><?php _e( 'clear' ); ?></a> </p> <script type="text/javascript"> jquery(document).ready(function($){ $('#taxonomy-<?php echo $this->slug; ?>-trigger-clear').click(function(){ $('#taxonomy-<?php echo $this->slug; ?> input:checked').prop( 'checked', false ); $('#taxonomy-<?php echo $this->slug; ?>-clear').prop( 'checked', true ); return false; }); }); </script> <?php } /** * wp_terms_checklist wrapper which outputs the terms list * @since 0.2.0 */ public function term_fields_list() { wp_terms_checklist( get_the_id(), array( 'taxonomy' => $this->slug, 'selected_cats' => false, 'popular_cats' => false, 'checked_ontop' => false, 'walker' => $this->walker(), ) ); } /** * adds button (and associated js) for adding new terms * @since 0.2.0 */ public function terms_adder_button() { ?> <p style="margin-bottom:0;float:right;width:50%;text-align:right;"> <a class="button-secondary" id="taxonomy-<?php echo $this->slug; ?>-new" href="#"<?php if ( 'radio' == $this->input_element ) : ?> style="display:inline-block;margin-top:0.4em;"<?php endif; ?>><?php _e( 'add new' ); ?></a> </p> <script type="text/javascript"> jquery(document).ready(function($){ $('#taxonomy-<?php echo $this->slug; ?>-new').click(function(e){ e.preventdefault(); var termname = prompt( "add new <?php echo esc_attr( $this->taxonomy()->labels->singular_name ); ?>", "new <?php echo esc_attr( $this->taxonomy()->labels->singular_name ); ?>" ); if( ! termname ) { return; } if(termname != null) { var data = { 'action' : 'taxonomy_single_term_add', 'term_name' : termname, 'taxonomy' : '<?php echo $this->slug; ?>', 'nonce' : '<?php echo wp_create_nonce( 'taxonomy_'. $this->slug, '_add_term' ); ?>' }; $.post( ajaxurl, data, function(response) { window.console.log( 'response', response ); if( response.success ){ <?php if ( 'radio' == $this->input_element ) : ?> $('#taxonomy-<?php echo $this->slug; ?> input:checked').prop( 'checked', false ); <?php else : ?> $('#taxonomy-<?php echo $this->slug; ?> option').prop( 'selected', false ); <?php endif; ?> $('#<?php echo $this->slug; ?>checklist').append( response.data ); } else { window.alert( '<?php printf( __( 'there was a problem adding a new %s' ), esc_attr( $this->taxonomy()->labels->singular_name ) ); ?>: ' + "\n" + response.data ); } }); } }); }); </script> <?php } /** * ajax callback to add terms inline * @since 0.2.0 */ function ajax_add_term() { $nonce = isset( $_post['nonce'] ) ? sanitize_text_field( $_post['nonce'] ) : ''; $term_name = isset( $_post['term_name'] ) ? sanitize_text_field( $_post['term_name'] ) : false; $taxonomy = isset( $_post['taxonomy'] ) ? sanitize_text_field( $_post['taxonomy'] ) : false; $friendly_taxonomy = $this->taxonomy()->labels->singular_name; // ensure user is allowed to add new terms if( !$this->allow_new_terms ) { wp_send_json_error( __( "new $friendly_taxonomy terms are not allowed" ) ); } if( !taxonomy_exists( $taxonomy ) ) { wp_send_json_error( __( "taxonomy $friendly_taxonomy does not exist. cannot add term" ) ); } if( !wp_verify_nonce( $nonce, 'taxonomy_' . $taxonomy, '_add_term' ) ) { wp_send_json_error( __( "cheatin' huh? could not verify security token" ) ); } if( term_exists( $term_name, $taxonomy ) ) { wp_send_json_error( __( "the term '$term_name' already exists in $friendly_taxonomy" ) ); } $result = wp_insert_term( $term_name, $taxonomy ); if ( is_wp_error( $result ) ) { wp_send_json_error( $result->get_error_message() ); } $term = get_term_by( 'id', $result['term_id'], $taxonomy ); if ( ! isset( $term->term_id ) ) { wp_send_json_error(); } $field_name = $taxonomy == 'category' ? 'post_category' : 'tax_input[' . $taxonomy . ']'; $field_name = $this->taxonomy()->hierarchical ? $field_name . '[]' : $field_name; $args = array( 'id' => $taxonomy . '-' . $term->term_id, 'name' => $field_name, 'value' => $this->taxonomy()->hierarchical ? $term->term_id : $term->slug, 'checked' => ' checked="checked"', 'selected' => ' selected="selected"', 'disabled' => '', 'label' => esc_html( apply_filters( 'the_category', $term->name ) ), ); $output = ''; $output .= 'radio' == $this->input_element ? $this->walker()->start_el_radio( $args ) : $this->walker()->start_el_select( $args ); // $output is handled by reference $this->walker()->end_el( $output, $term ); wp_send_json_success( $output ); } /** * add some js to the post listing page to transform the quickedit inputs * @since 0.1.3 */ public function js_checkbox_transform() { $screen = get_current_screen(); $taxonomy = $this->taxonomy(); if ( empty( $taxonomy ) || empty( $screen ) || ! isset( $taxonomy->object_type ) || ! isset( $screen->post_type ) || ! in_array( $screen->post_type, $taxonomy->object_type ) ) return; ?> <script type="text/javascript"> // handles changing input types to radios for wds_taxonomy_radio jquery(document).ready(function($){ var $postsfilter = $('#posts-filter'); var $thelist = $postsfilter.find('#the-list'); // handles changing the input type attributes var changetoradio = function( $context ) { $context = $context ? $context : $thelist; var $taxlistinputs = $context.find( '.<?php echo $this->slug; ?>-checklist li input' ); if ( $taxlistinputs.length ) { // loop and switch input types $taxlistinputs.each( function() { $(this).attr( 'type', 'radio' ).addclass('transformed-to-radio'); }); } }; $postsfilter // handle converting radios in bulk-edit row .on( 'click', '#doaction, #doaction2', function(){ var name = $(this).attr('id').substr(2); if ( 'edit' === $( 'select[name="' + name + '"]' ).val() ) { settimeout( function() { changetoradio( $thelist.find('#bulk-edit') ); }, 50 ); } }) // when clicking new radio inputs, be sure to uncheck all but the one clicked .on( 'change', '.transformed-to-radio', function() { var $this = $(this); $siblings = $this.parents( '.<?php echo $this->slug; ?>-checklist' ).find( 'li .transformed-to-radio' ).prop( 'checked', false ); $this.prop( 'checked', true ); }); // handle converting radios in inline-edit rows $thelist.find('.editinline').on( 'click', function() { var $this = $(this); settimeout( function() { var $editrow = $this.parents( 'tr' ).next().next(); changetoradio( $editrow ); }, 50 ); }); }); </script> <?php } /** * handles checking if object terms need to be set when bulk-editing posts * @since 0.2.1 */ public function bulk_edit_handler() { // get wp tax name designation $name = $this->slug; if ( 'category' == $name ) { $name = 'post_category'; } if ( 'tag' == $name ) { $name = 'post_tag'; } // if this tax name exists in the query arg if ( isset( $_request[ $name ] ) && is_array( $_request[ $name ] ) ) { $this->to_set = end( $_request[ $name ] ); } elseif ( isset( $_request['tax_input'][ $name ] ) && is_array( $_request['tax_input'][ $name ] ) ) { $this->to_set = end( $_request['tax_input'][ $name ] ); } // then get it's term object if ( $this->to_set ) { $this->to_set = get_term( $this->to_set, $this->slug ); // and hook in our re-save action add_action( 'set_object_terms', array( $this, 'maybe_resave_terms' ), 10, 5 ); } } /** * handles resaving terms to post when bulk-editing so that only one term will be applied * @since 0.1.4 * @param int $object_id object id. * @param array $terms an array of object terms. * @param array $tt_ids an array of term taxonomy ids. * @param string $taxonomy taxonomy slug. * @param bool $append whether to append new terms to the old terms. * @param array $old_tt_ids old array of term taxonomy ids. */ public function maybe_resave_terms( $object_id, $terms, $tt_ids, $taxonomy, $append ) { if ( // if the terms being edited are not this taxonomy $taxonomy != $this->slug // or we already did our magic || in_array( $object_id, $this->single_term_set, true ) ) { // then bail return; } // prevent recursion $this->single_term_set[] = $object_id; // replace terms with the one term wp_set_object_terms( $object_id, $this->to_set->slug, $taxonomy, $append ); } /** * gets the taxonomy object from the slug * @since 0.1.0 * @return object taxonomy object */ public function taxonomy() { $this->taxonomy = $this->taxonomy ? $this->taxonomy : get_taxonomy( $this->slug ); return $this->taxonomy; } /** * gets the taxonomy's associated post_types * @since 0.1.0 * @return array taxonomy's associated post_types */ public function post_types() { $this->post_types = !empty( $this->post_types ) ? $this->post_types : $this->taxonomy()->object_type; return $this->post_types; } /** * gets the metabox title from the taxonomy object's labels (or uses the passed in title) * @since 0.1.0 * @return string metabox title */ public function metabox_title() { $this->metabox_title = !empty( $this->metabox_title ) ? $this->metabox_title : $this->taxonomy()->labels->name; return $this->metabox_title; } /** * gets the taxonomy_single_term_walker object for use in term_fields_list and ajax_add_term * @since 0.2.0 * @return object taxonomy_single_term_walker object */ public function walker() { if ( $this->walker ) { return $this->walker; } require_once( 'walker.taxonomy-single-term.php' ); $this->walker = new taxonomy_single_term_walker( $this->taxonomy()->hierarchical, $this->input_element ); return $this->walker; } /** * set the object properties. * * @since 0.2.1 * * @param string $property property in object. must be set in object. * @param mixed $value value of property. * * @return taxonomy_single_term returns taxonomy_single_term object, allows for chaining. */ public function set( $property, $value ) { if ( property_exists( $this, $property ) ) { $this->$property = $value; } return $this; } /** * magic getter for our object. * * @since 0.2.1 * * @param string property in object to retrieve. * @throws exception throws an exception if the field is invalid. * * @return mixed property requested. */ public function __get( $property ) { if ( property_exists( $this, $value ) ) { return $this->{$property}; } else { throw new exception( 'invalid '. __class__ .' property: ' . $field ); } } } endif; // class_exists check このライブラリは、カスタムウォーカーwalker.taxonomy-single-term.phpも使用します。<?php if ( ! class_exists( 'taxonomy_single_term_walker' ) && class_exists( 'walker' ) ) : /** * walker to output an unordered list of taxonomy radio <input> elements. * * @see walker * @see wp_category_checklist() * @see wp_terms_checklist() * @since 0.1.2 */ class taxonomy_single_term_walker extends walker { public $tree_type = 'category'; public $db_fields = array( 'parent' => 'parent', 'id' => 'term_id' ); //todo: decouple this public function __construct( $hierarchical, $input_element ) { $this->hierarchical = $hierarchical; $this->input_element = $input_element; } /** * starts the list before the elements are added. * * @see walker:start_lvl() * * @since 0.1.2 * * @param string $output passed by reference. used to append additional content. * @param int $depth depth of category. used for tab indentation. * @param array $args an array of arguments. @see wp_terms_checklist() */ public function start_lvl( &$output, $depth = 0, $args = array() ) { if ( 'radio' == $this->input_element ) { $indent = str_repeat("\t", $depth); $output .= "$indent<ul class='children'>\n"; } } /** * ends the list of after the elements are added. * * @see walker::end_lvl() * * @since 0.1.2 * * @param string $output passed by reference. used to append additional content. * @param int $depth depth of category. used for tab indentation. * @param array $args an array of arguments. @see wp_terms_checklist() */ public function end_lvl( &$output, $depth = 0, $args = array() ) { if ( 'radio' == $this->input_element ) { $indent = str_repeat("\t", $depth); $output .= "$indent</ul>\n"; } } /** * start the element output. * * @see walker::start_el() * * @since 0.1.2 * * @param string $output passed by reference. used to append additional content. * @param object $term the current term object. * @param int $depth depth of the term in reference to parents. default 0. * @param array $args an array of arguments. @see wp_terms_checklist() * @param int $id id of the current term. */ public function start_el( &$output, $term, $depth = 0, $args = array(), $id = 0 ) { $taxonomy = empty( $args['taxonomy'] ) ? 'category' : $args['taxonomy']; $name = $taxonomy == 'category' ? 'post_category' : 'tax_input['.$taxonomy.']'; // input name $name = $this->hierarchical ? $name .'[]' : $name; // input value $value = $this->hierarchical ? $term->term_id : $term->slug; $selected_cats = empty( $args['selected_cats'] ) ? array() : $args['selected_cats']; $in_selected = in_array( $term->term_id, $selected_cats ); $args = array( 'id' => $taxonomy .'-'. $term->term_id, 'name' => $name, 'value' => $value, 'checked' => checked( $in_selected, true, false ), 'selected' => selected( $in_selected, true, false ), 'disabled' => disabled( empty( $args['disabled'] ), false, false ), 'label' => esc_html( apply_filters('the_category', $term->name ) ) ); $output .= 'radio' == $this->input_element ? $this->start_el_radio( $args ) : $this->start_el_select( $args ); } /** * creates the opening markup for the radio input * * @since 0.2.0 * * @param array $args array of arguments for creating the element * * @return string opening li element and radio input */ public function start_el_radio( $args ) { return "\n".sprintf( '<li id="%s"><label class="selectit"><input value="%s" type="radio" name="%s" id="in-%s" %s %s/>%s</label>', $args['id'], $args['value'], $args['name'], $args['id'], $args['checked'], $args['disabled'], $args['label'] ); } /** * creates the opening markup for the select input * * @since 0.2.0 * * @param array $args array of arguments for creating the element * * @return string opening option element and option text */ public function start_el_select( $args ) { return "\n".sprintf( '<option %s %s id="%s" value="%s" class="class-single-term">%s', $args['selected'], $args['disabled'], $args['id'], $args['value'], $args['label'] ); } /** * ends the element output, if needed. * * @see walker::end_el() * * @since 0.1.2 * * @param string $output passed by reference. used to append additional content. * @param object $term the current term object. * @param int $depth depth of the term in reference to parents. default 0. * @param array $args an array of arguments. @see wp_terms_checklist() */ public function end_el( &$output, $term, $depth = 0, $args = array() ) { if ( 'radio' == $this->input_element ) { $output .= "</li>\n"; } else { $output .= "</option>\n"; } } } endif; // class_exists check クイック編集を修正これを書いている時点(wordpress v4.6.1)では、クイック編集画面で単一の用語を設定することに問題があります。 これはまだマージされていない修正 です。class.taxonomy.single-term.phpの438行目を次のように変更します。var $editrow = $this.parents( 'tr' ).next().next();