Cmb2: Support for Hierarchical Taxonomies

Created on 28 Apr 2016  路  8Comments  路  Source: CMB2/CMB2

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.

enhancement

Most helpful comment

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

All 8 comments

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

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();

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

@eriktelepovsky Great solution - thank you!

Yep, thanks @eriktelepovsky. I've move your solution to core: https://github.com/CMB2/CMB2/commit/519e809427ca18313db79aa238bddd1113ffd77a

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?

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

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

Was this page helpful?
0 / 5 - 0 ratings

Related issues

alanef picture alanef  路  4Comments

noquierouser picture noquierouser  路  8Comments

apokyro picture apokyro  路  5Comments

needtakehave picture needtakehave  路  7Comments

gareth-gillman picture gareth-gillman  路  6Comments