Skip to content

Support for Hierarchical Taxonomies #640

Description

@stabilimenta

Hi,
I have a custom hierarchical taxonomy for my custom post type. I was trying to use CMB2 to create checkboxes for my form user to select (using taxonomy_multicheck, but I also tried the other taxonomy variants).
It pulls in the taxonomy, but the terms lose their order and hierarchy. (It returns them all in alphabetical order.)
For example,
Art Type Category
Paintings
------Oil
------Acylic
------Watercolor
Sculpture
------Steel
------Bronze
------Mixed Materials

Returns as
Acylic
Bronze
Mixed Materials
Oil
Paintings
Sculpture
Steel
Watercolor

It would be a nice feature to be able to preserve the hierarchy.
Thank you for CMB2.

Activity

  1. tw2113 commented on Sep 9, 2016

    @tw2113
    Contributor

    Honestly not sure there's any great way to handle this, whether by CMB2 core itself, or a user specifically for their own site.

    We rely on the get_terms() function, which doesn't really offer much for that type of preservation. It would be technically possible to use the get_terms filter to intercept, but you'd need to make sure you're affecting only the intended query, figure out how to re-order the total results to include the hierarchy, and then also amend the label to indent however you intend.

    Risks getting messy pretty quickly.

    That said, I do have to wonder how the default category metabox handles things. That's something I hadn't checked on tonight, as it's getting late. Will have to another time :D

  2. eriktelepovsky commented on Sep 14, 2016

    @eriktelepovsky

    I am using my own implementation of hierarchical multicheck taxonomy field. Maybe it will help you.

    Use taxonomy_multicheck_hierarchy as a type of your field.

    <?php
    /**
     * Class Inventor_Field_Types_Taxonomy_Multicheck_Hierarchy
     */
    class Inventor_Field_Types_Taxonomy_Multicheck_Hierarchy {
        /**
         * Initialize the plugin by hooking into CMB2
         */
        public function __construct() {
            add_filter( 'cmb2_render_taxonomy_multicheck_hierarchy', array( $this, 'render' ), 10, 5 );
            add_filter( 'cmb2_sanitize_taxonomy_multicheck_hierarchy', array( $this, 'sanitize' ), 10, 5 );
        }
    
        /**
         * Render field
         *
         * @access public
         * @param $field
         * @param $field_escaped_value
         * @param $field_object_id
         * @param $field_object_type
         * @param $field_type_object
         * @return string
         */
        public function render( $field, $field_escaped_value, $field_object_id, $field_object_type, $field_type_object ) {
            $real_field_type_object = class_exists( 'CMB2_Type_Taxonomy_Multicheck' ) ? new CMB2_Type_Taxonomy_Multicheck( $field_type_object ) : $field_type_object;
    
            $names       = $real_field_type_object->get_object_terms();
            $saved_terms = is_wp_error( $names ) || empty( $names )
                ? $field_type_object->field->get_default()
                : wp_list_pluck( $names, 'slug' );
    
            $terms_query_args = array(
                'hide_empty'    => false,
                'parent'        => 0,
            );
    
            $post_type = empty( $field->args['post_type'] ) ? null : $field->args['post_type'];
    
            if ( ! empty( $post_type ) ) {
                $terms_query_args['meta_query'] = array(
                    'relation' => 'OR',
                    array(
                        'key' => INVENTOR_LISTING_CATEGORY_PREFIX . 'listing_types',
                        'compare' => 'NOT EXISTS',
                    ),
                    array(
                        'key'       => INVENTOR_LISTING_CATEGORY_PREFIX . 'listing_types',
                        'value'     => serialize( strval( $post_type ) ),
                        'compare'   => 'LIKE'
                    )
                );
            }
    
            $terms       = get_terms( $field_type_object->field->args( 'taxonomy' ), $terms_query_args );
    
            $name        = $field_type_object->_name() . '[]';
            $options     = ''; $i = 1;
    
            if ( ! $terms ) {
                $options .= sprintf( '<li><label>%s</label></li>', esc_html($field_type_object->_text( 'no_terms_text', __( 'No terms', 'cmb2' ) ) ) );
            } else {
                foreach ( $terms as $term ) {
                    $args = array(
                        'type'  => 'checkbox',
                        'class' => 'cmb2_option',
                        'id'    => $term->term_id,
                        'value' => $term->slug,
                        'label' => $term->name,
                        'name'  => $name,
                    );
    
                    if ( is_array( $saved_terms ) && in_array( $term->slug, $saved_terms ) ) {
                        $args['checked'] = 'checked';
                    }
    
                    $options .= $real_field_type_object->list_input( $args, $i );
                    $children = $this->build_children( $real_field_type_object, $term, $saved_terms );
    
                    if ( ! empty( $children ) ) {
                        $options .= $children;
                    }
    
                    $i++;
                }
            }
            $classes = false === $field_type_object->field->args( 'select_all_button' )
                ? 'cmb2-checkbox-list no-select-all cmb2-list'
                : 'cmb2-checkbox-list cmb2-list';
    
            echo sprintf( '<ul class="%s">%s</ul>', $classes, $options );
        }
    
        /**
         * Save proper values
         *
         * @access public
         * @param $override_value
         * @param $value
         * @param $object_id
         * @param $field_args
         * @return void
         */
        public function sanitize( $override_value, $value, $object_id, $field_args ) {
            wp_set_object_terms( $object_id, $value, $field_args['taxonomy'] );
        }
    
        /**
         * Build children hierarchy
         *
         * @access public
         * @param $object
         * @param $parent_term
         * @param $saved_terms
         * @return null|string
         */
        public function build_children( $object, $parent_term, $saved_terms ) {
            $output = null;
    
            $terms = get_terms( $object->field->args( 'taxonomy' ), array(
                'hide_empty'    => false,
                'parent'        => $parent_term->term_id,
            ) );
    
            if ( ! empty( $terms ) && is_array( $terms ) ) {
                $output = '<li style="padding-left: 24px;"><ul>';
    
                foreach( $terms as $term ) {
                    $args = array(
                        'type'  => 'checkbox',
                        'id'    => $term->term_id,
                        'name'  => $object->_name() . '[]',
                        'value' => $term->slug,
                        'label' => $term->name,
                    );
    
                    if ( is_array( $saved_terms ) && in_array( $term->slug, $saved_terms ) ) {
                        $args['checked'] = 'checked';
                    }
    
                    $output .= $object->list_input( $args, $term->term_id );
                    $children = $this->build_children( $object, $term, $saved_terms );
    
                    if ( ! empty( $children ) ) {
                        $output .= $children;
                    }
                }
    
                $output .= '</ul></li>';
            }
    
            return $output;
        }
    }
    
    new Inventor_Field_Types_Taxonomy_Multicheck_Hierarchy();
    
    
  3. izuro commented on Oct 5, 2016

    @izuro

    @eriktelepovsky works wonderfully, many thanks! This should be added to the snippet library

  4. bhwebworks commented on Apr 18, 2017

    @bhwebworks

    @eriktelepovsky Great solution - thank you!

  5. jtsternberg commented on Apr 19, 2017

    @jtsternberg
    Member

    Yep, thanks @eriktelepovsky. I've move your solution to core: 519e809

  6. eklingen88 commented on May 23, 2018

    @eklingen88

    Absolutely stoked to find out that this was in the core. Great work @jtsternberg and @eriktelepovsky.

    I was using a nasty replication of the normal taxonomy select found in posts, but it's much easier to manage with CMB2.

    Any reason this isn't document in the field types?

  7. jtsternberg commented on May 24, 2018

    @jtsternberg
    Member

    @eklingen88 No good reason. I have added them to the wiki.

  8. eklingen88 commented on May 24, 2018

    @eklingen88

    @jtsternberg Fantastic! Hats off to you, sir, for both the super fast response time and all of your work on this mu-plugin!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions