Creating an all term taxonomy archive

The following was originally publisehd at How to Create a Taxonomy Archive: Excuse Me, Can You Show Me All Your Grey Poupons? and has been modified to be a more straightforward documentation reference.

Created an archive for all terms from a taxonomy.

WordPress does not provide the easiest way to achieve post archives that have any term from a taxonomy. There is no functional URL that provides all the posts for a given taxonomy. WordPress does provide term archives, but that is just one term at a time. If we're blogging about mustards of the world, I don't want to see just "yellow mustard" posts. I want that dijon! I want that honey mustard!

If you are willing to be a little flexible, we can create a taxonomy archive. The way we will achieve that is by tagging each post with a generic "all" tag. We will need to accept that we will have a URL like http://domain.com/mustards/all. We could do much worse.

The Setup

For our fictional foodies blog, we are going to use Custom Post Type UI to handle content type registration. We will imagine we have one post type of "condiments" and one taxonomy of "mustards".

Cook's note: If you have more than one post type registered, you may need to amend spots using cptui_get_post_type_slugs().

Automating the "all" term assignment

To handle the assignment of the "all term", we are going to use the following:

/**
 * Add publish hooks to all our CPTUI post types.
 *
 * @since 1.0.0
 */
function pluginize_post_type_listener() {
	$post_types = cptui_get_post_type_slugs(); // We default to just our CPTUI post types.

	foreach ( $post_types as $type ) {
		add_action( "publish_{$type}", 'pluginize_auto_add_taxonomy_terms_on_publish' );
	}
}
add_action( 'init', 'pluginize_post_type_listener' );
/**
 * Automatically add our taxonomy term upon publish.
 *
 * Term needs to already be created in the database.
 *
 * @since 1.0.0
 *
 * @param int $post_id Published post ID.
 */
function pluginize_auto_add_taxonomy_terms_on_publish( $post_id = 0 ) {
	if ( ! wp_is_post_revision( $post_id ) ) {
		$terms = array( 25 ); // Set to the appropriate term ID.
		wp_set_object_terms( $post_id, $terms, 'category' ); // You will need to customize the taxonomy.
	}
}

Above we have two functions.

The first one is our "listener" plugin, and it runs on the `init` hook. With it, we are fetching our CPTUI post types (in this case, "condiments,") and looping over each. In the loop, we are adding a callback function to the the publish action hook.

For our example, it will be the `publish_condiments` hook. Every time we publish a new condiments post, the `pluginize_auto_add_taxonomy_terms_on_publish` function will run. That function will receive the now published post ID from WordPress.

For safety's sake, we will make sure we are not receiving a revision's post ID. If not, we are going to set the term ID 25, which is our "all" term for the "mustards" taxonomy. With this, we will never have to assign the "all" term ourselves. We just need to worry about the other mustard terms we have, and hit publish.

Assigning existing content with new term.

Chances are you have been posting for awhile already. Plenty of your posts are going to be missing our "all" term. To hep automate this, we will use the following code:

/**
 * Run queries for posts and assign our term to them.
 *
 * Conditionally able to run on just one-to-many post types, or all.
 *
 * @since 1.0.0
 *
 * @param mixed $chosen Individual post type to potentially use.
 */
function pluginize_backfill_posts_by_post_type( $chosen = '' ) {

	// We only want to run this if there is a $_GET param of `?fill_posts=true` or `&fill_posts=true`.
	if ( empty( $_GET ) ) {
		return;
	}

	if ( ! isset( $_GET['fill_posts'] ) || 'true' != $_GET['fill_posts'] ) {
		return;
	}


	if ( ! empty( $chosen ) && is_string( $chosen ) && post_type_exists( $chosen ) ) { // If we are passed a string as a chosen post type and it's valid.
		$post_types = array( $chosen );
	} elseif ( is_array( $chosen ) ) { // If we are passed an array of post types.
		$post_types = array();
		foreach ( $chosen as $item ) {
			if ( post_type_exists( $item ) ) { // If we have valid post types.
				$post_types[] = $item;
			}
		}
	} else { // Nothing at all passed in, default to all of them.
		$post_types = cptui_get_post_type_slugs();
	}

	$args = array(
		'post_type' => $post_types,
		'post_status' => 'publish',
		'fields' => 'ids',
		'posts_per_page' => -1,
	);
	$backfill = new WP_Query( $args ); // We are only needing our post IDs, from published posts.

	while( $backfill->have_posts() ) {
		$backfill->the_post();
		// We can reuse our function from above!
		pluginize_auto_add_taxonomy_terms_on_publish( get_the_ID() );
	}
}
add_action( 'init', 'pluginize_backfill_posts_by_post_type' );

Astute developers will note that this function is a callback on the init hook, and that we have many checks that return early if not met. In order for this code to execute, you need to have either "?fill_posts=true" or "&fill_posts=true" in your url. If we meet that condition, we'll continue into the rest of the function.

A few things to note:

  • We're keeping the function flexible, in case we have another need for it.
  • We're accepting a value as a parameter to the function, so users can pass in a single post type or array of post types. If we receive a single post type, we check if the post type exists, and if so, add it to a new array. If we receive an array, we loop over each and check if each post type exists. If so, we'll add each into the array. If we are not provided anything specific, we'll grab all the post types from CPTUI.

Once we have our post types to work with, we do a query for all posts from all the types. At the same time, we only need the IDs of the posts, so we use the "fields" parameter to limit to just IDs. At this point, we have all the posts we need to update.

We are able to re-use our previous callback, attached to our publish_condiments action hook All we need to do is loop over our WP_Query results and pass in the ID to our `pluginize_auto_add_taxonomy_terms_on_publish()` function, and it will handle the rest.

What if I do not want a custom taxonomy?

That is fine. We have you covered. Say you are already using categories instead of a custom taxonomy. With a little bit of extra code, we can include the condiments post type in the category archives:

/**
 * Add our post types to the query.
 *
 * @since 1.0.0
 *
 * @param object $query WP_Query instance.
 */
function pluginize_amend_term_archive( $query ) {

	if ( is_admin() || ! $query->is_main_query() ) {
		return;
	}

	if ( ! $query->is_category() ) {
		return;
	}

	$query->set(
		'post_type',
		array_merge(
			array( 'post' ),
			cptui_get_post_type_slugs()
		)
	);

}
add_action( 'pre_get_posts', 'pluginize_amend_term_archive' );

The code above uses the pre_get_posts hook to include our "condiments" post type in the query. This will make sure the category archives query for posts in the "post" and "condiments" post types.

Now what?

At this point, we should have plenty of posts associated with our "all" term. All future posts should also be receiving it automatically. The only thing that remains is providing a link to the "all" term archive. When editing a chosen menu in Appearance > Menus, you should have the ability to add the "all" term as a menu item. Finally, we have a place for visitors to sit and consume all of the content from your taxonomy.

Extra! Download the code above in a convenient install-able plugin: pluginize_all_terms_taxonomy.zip