Hierarchical Taxonomy Query Example

/**
* Querying by hierarchical taxonomy when children terms have the same
* name but have different parents.
*
* Copyright 2015 Steven F. LeBrun (email: steven@lebruns.com)
* using the GNU General Public License
* http://www.gnu.org/licenses/gpl-2.0.html
*/

// Defining Text Domain for Internationalization.
define( ‘TEXT_DOMAIN’, ‘my_topic’ );

// Class defining constants
class Topic
{
const TAX_NAME = ‘topic’;
const SLUG = ‘topic’;
}

/**
* Action Function that registers our Topic taxonomy.
*
* This taxonomy is associated with one custom post type that we shall
* call my_post_type that is not defined here.
*
* The key part of this registration function is that the Topic taxonomy
* is hierarchical.
*/
function topic_create()
{
// Define Labels to be used for different aspects of displaying
// This taxonomy.
$labels = array(
‘name’ => _x(‘Topics’, ‘plural name’, TEXT_DOMAIN),
‘single_name’ => _x(‘Topic’, ‘single name’, TEXT_DOMAIN),
);

// Setting up arguments for registering taxonomy
$args = array(
‘labels’ => $labels,
‘public’ => true,
‘description’ => __(‘Topic’, TEXT_DOMAIN),
‘hierarchical’ => true,
‘rewrite’ => array(
‘slug’ => Topic::SLUG,
‘hierarchical’ => true,
),
);

// List all the Custom Post Types that use this taxonomy.
$types = array( ‘my_post_type’);

// Register the Taxonomy
register_taxonomy(
Topic::TAX_NAME,
$types,
$args
);

// All Done
return;

} // end of topic_create()

// Add function as INIT Hook
add_action( ‘init’, ‘topic_create’);

/**
* Function that builds a query of the custom post type ‘my_post_type’
* querying for all posts with the specified Topic taxonomy term are
* selected.
*
* @param $term_name The name of the Topic Taxonomy term to query by.
*
* @return Returns the WP_Query object based on the Topic Taxonomy query.
*
* The $term_name may be a child term that is not unique by itself. Combine
* the child term with its parent terms does result in a unique name.
* Therefore, we will allow the $term to take the form of:
*
* ‘…/grandparent/parent/child’
*
* Where the number of parents or ancestors required are what ever is
* sufficient to define a unique string. In other words, the term does
* not have to be an absolute path.
*/
function do_query( $term_name )
{
// Post Type part of Query arguments
$query_args = array(
‘post_type’ => ‘my_post_type’,
‘orderby’ => ‘title’,
‘order’ => ‘ASC’,
);

// Add the Taxonomy Query argument to the rest of the query arguments.
$query_args[‘tax_query’] = build_taxonomy_query( $term_name );

// Run query
$query = new WP_Query( $query_args );

return $query;

} // end of do_query()

/**
* Function to build the taxonomy portion of a query argument array.
*
* @param $term_name The Taxonomy term to query by. The term may includes
* one or more parent terms separated by slashes.
*
* @return Returns an array that can be added to the full query argument
* array using the key ‘tax_query’.
*/
function build_taxonomy_query( $term_name )
{
// Obtain an array of all the taxonomy terms indexed by ID
// Note, we do this here so that it is done once during the query
// to minimize database access.
$tax_terms = get_all_terms_by_id( Topic::TAX_NAME );

// Build the taxonomy query array.
$topic_args = array(
‘taxonomy’ => Topic::TAX_NAME,
‘field’ => ‘id’,
‘terms’ => get_term_id_by_name( $term_name, $tax_terms ),
);

$tax_args = array( $topic_args );

return $tax_args;

} // end of build_taxonomy_query()

/**
* Function that fetches all the terms in a taxonomy.
*
* @param $tax_name The name of the taxonomy.
*
* @return Returns an array of taxonomy term objects indexed by the
* taxonomy term ids.
*/
function get_all_terms_by_id( $tax_name )
{
// Remember that the taxonomy is hierarchical
$all_terms = get_terms(
$tax_name,
array(
‘hierarchical’ => ‘1’, // true
‘fields’ => ‘all’,
)
);

$terms = array();

// Build array indexed by Term ID.
foreach ( $all_terms as $term )
{
$terms[$term->term_id] = $term;
}

return $terms;

} // get_all_terms_by_id()

/**
* Function that obtains the term id of taxonomy term that may be
* a child term.
*
* @param $term The Taxonomy term to query by. The term may includes
* one or more parent terms separated by slashes.
*
* @param $tax_terms An array of taxonomy terms as produced by
* get_all_terms_by_id()
*
* The term is considered to be a match for $term_name if the term name
* matches a taxonomy term and, if present, all the parent terms match
* the parents in the taxonomy of that term.
*/
function get_term_id_by_name( $term_name, $tax_terms )
{
// Break up the string into an array and then reverse the order
// of the array so that the child term is first, followed by its
// parent, followed by its grandparent, …
$parent_names = array_reverse(explode(‘/’, $term_term_name));

// Get child term, leaving the array with just the parents
$parent_name = array_shift( $parent_names );

$nelements = count( $tax_terms );

foreach( $tax_terms as $term_id => $term_object )
{
if ( $parent_name == $term_object->name )
{
if ( do_parents_match( $term_object,
$parent_names,
$tax_terms) )
{
// We have a match
return $term_object->term_id;
}
}
}

// If we reached this point, no match was found
return 0;

} // end of get_term_id_by_name()

/**
* Recursive function used to verify that the parent terms all match
* the parents of the taxonomy term.
*
* @param $child The taxonomy object for the child term.
*
* @param $parents An array of the names of the parents, starting with the
* closest parent.
*
* @param $tax_terms An array of taxonomy terms as produced by
* get_all_terms_by_id()
*
* Each call to this function is called with the first element in the
* $terms array shifted out. Resulting in the $terms array is one
* element shorter with each recursive call. If all the parents
* match, we end up calling this function with an empty $terms array.
*/
function do_parents_match( $child, $parent_names, $tax_terms )
{
// If $parent_names is an empty array, than all the previous parents
// matched, we are done and the term matches child and all its
// parents.
if ( count($parent_names) == 0 )
{
// All parents listed match
return true;
}
// If $term has no parent, and we still have parent terms,
// Or if the parent ID does not exist in the taxonomy,
// we also have no match.
if ( $child->parent == 0 ||
!array_key_exists( $child->parent, $tax_terms ) )
{
// No Match.
return false;
}

$parent_name = array_shift($parent_names);

if ( $parent_name == $tax_terms[$child->parent]->name )
{
// We have a match, go deeper.
return do_parents_match(
$tax_terms[$term->parent],
$parent_names,
$tax_terms );
}

// If we reached this point, no match
return false;

} // end of do_parents_match()

Comments are closed.