Is there a way to disable a term rather than deleting it?

Not directly, but I have a need for this functionality as well, as part of a plugin I’m writing for a client and have what I think is a pretty good start on an implementation.

The approach I’m taking is to store a term meta when the term is disabled and then to hook into the get_terms_defaults filter to strip disabled terms that would be returned by WP Core’s calls to get_terms(), get_the_terms(), etc.

My plugin encapsulates all of this into a class which serves as a wrapper around register_taxonomy(). The following is a stripped down version of that class (for the plugin I’m writing, this wrapper does a lot more with custom taxonomies).

See Custom_Taxonomy::enable_term() and Custom_Taxonmy::disable_term() for the code that adds/deletes the relevant term meta; and see Custom_Taxonomy::strip_disabled_terms() for the code that strips disabled terms.

Hopefully, I’ve left in all of the relevant code from this class when I stripped out the stuff that doesn’t apply to this question 🙂

/**
 * this class is a wrapper around register_taxonomy(), that adds additional functionality
 * to allow terms in the taxonomy so registered to be "disabled".
 *
 * Disabled terms will NOT be returned by the various WP Core functions like get_terms(),
 * get_the_terms(), etc.
 *
 * TODO: make sure that this works correctly given WP Core's object caching, see Custom_Taxonomy::strip_disabled_terms()
 */
class
Custom_Taxonomy
{
    const DISABLED_TERM_META_KEY = '_shc_disabled' ;

    public $name ;
    public $can_disable_terms = false ;

    /**
     * construct an instance of Custom_Taxonomy
     *
     * @param $taxonomy string Taxonomy key, must not exceed 32 characters
     * @param $post_types array|string post type or array of post types with which the taxonomy should be associated
     * @param $args array Array or query string of arguments for registering a taxonomy
     *
     * params are the same as WP's register_taxonomy() except that $args may have extra keys:
     *
     *  'can_disable_terms' => true|false
     */
    function
    __construct ($taxonomy, $post_types, $args)
    {
        $this->name = $taxonomy ;

        // modify args, if needed
        $default_args = array (
            'can_disable_terms' => false,
            ) ;
        $args = wp_parse_args ($args, $default_args) ;

        $this->can_disable_terms = $args['can_disable_terms'] ;
        unset ($args['can_disable_terms']) ;

        if ($this->can_disable_terms) {
            // TODO: is there a better filter to hook into than 'get_terms_defaults'?
            // I've tried 'get_terms_args', but that seems to be called too late
            // in the process of builing the WP_Term_Query used by get_terms(), etc
            // to have the meta_query that is added by $this->strip_disabled_terms()
            add_filter ('get_terms_defaults', array ($this, 'strip_disabled_terms'), 10, 2) ;
            }

        // register the taxonomy
        register_taxonomy ($taxonomy, $post_types, $args) ;

        return ;
    }

    /**
     * disable a term
     *
     * disabling a term will make it appear as if the term does not exist, without actually deleting it
     *
     * @param $term int|string|WP_Term the term to disable
     * @param $taxonomy string the taxonomy term is in
     * @return int|WP_Error|bool Meta ID on success. WP_Error when term_id is ambiguous between taxonomies. False on failure
     */
    function
    disable_term ($term, $taxonomy = '', $field = 'name')
    {
        if (!$this->can_disable_terms) {
            return ;
            }

        $taxonomy = $taxonomy ? $taxonomy : $this->name ;
        if (is_string ($term)) {
            $term = get_term_by ($field, $term, $taxonomy) ;
            }
        else {
            $term = get_term ($term, $taxonomy) ;
            }

        return (add_term_meta ($term->term_id, self::DISABLED_TERM_META_KEY, true, true)) ;
    }

    /**
     * enable a term
     *
     * @param $term int|WP_Term the term to disable
     * @param $taxonomy string the taxonomy term is in
     * @return bool True on success, false on failure
     */
    function
    enable_term ($term, $taxonomy = '', $field = 'name')
    {
        if (!$this->can_disable_terms) {
            return ;
            }

        $taxonomy = $taxonomy ? $taxonomy : $this->name ;
        if (is_string ($term)) {
            $term = get_term_by ($field, $term, $taxonomy) ;
            }
        else {
            $term = get_term ($term, $taxonomy) ;
            }

        return (delete_term_meta ($term->term_id, self::DISABLED_TERM_META_KEY)) ;
    }

    /**
     * strip disabled terms from e.g., get_terms() and get_the_terms()
     *
     * TODO: make sure that this works correctly given WP Core's object caching
     *
     * @param $term int|WP_Term the term to disable
     * @param $taxonomy string the taxonomy term is in
     * @return bool True on success, false on failure
     */
    function
    strip_disabled_terms ($args, $taxonomies)
    {
        if (!$this->can_disable_terms) {
            return ($args) ;
            }

        // I *think* the count('taxonomy') check is necesary because get_terms_args() is
        // applied by the WP Core object_term caching infrastructure by
        // passing all taxonomies for a given post_type, and we only want to
        // add this restriction when we are getting the terms for just the
        // this taxonomy
        if (count ($args['taxonomy']) != 1 || !in_array ($this->name, $args['taxonomy'])) {
            return ($args) ;
            }

        $args['meta_query'] = array (
            array (
                'key' => self::DISABLED_TERM_META_KEY,
                'compare' => 'NOT EXISTS',
                )
            ) ;

        return ($args) ;
    }
}

My use case

I’ve got 2 custom post types, type_a and type_b. type_b has a custom taxonomy, taxonomy_b, the terms of which are the post_title’s of all of the posts of type type_a.

Only those posts of type_a whose post_status is publish should have their corresponding terms in taxonomy_b enabled. I accomplish this by hooking into save_post_type_a to insert the terms (if they don’t already exist) and enable/disable them, and hook into delete_post to delete them.

If your use case for disabled terms is at all similar, them the above should at least point you in one possible direction.

Questions I still have

I’m still working on this plugin and there are a few open issues around it’s implementation.

  1. is the get_terms_defaults filter that I hook into to add the meta_query to WP_Term_Query the best hook to use? I’ve tried get_terms_args, but that seems to be called too late in the process of builing the WP_Term_Query used by get_terms(), etc. to correctly process the meta_query that is added by Custom_taxonomy::strip_disabled_terms().

  2. I’m not sure how this interacts with WP Core’s object caching. For example, if a term is disabled when a call to get_terms() caches the terms in the taxonomy, and them the term is enabled in the same execution of wp() will a subsequent call to get_terms() include the term or will it return the cached terms…which wouldn’t include the term. But it seems to be working in the tests I’ve done thus far.

If anyone reading this has any suggestions for improvements on this class, especially about the object caching aspect, please, chime in!

Leave a Comment