Show tags of private posts in the tag cloud

No WordCamp last weekend, so today, you get a regular post 😉 This week, I had an interesting request in a client project. The WordPress website had a FAQ section and some of the questions were set as private posts. The website was using a tag cloud, to make it easier for visitors, to find questions. But even if the user was signed in, he couldn’t see the tags used only on private posts.

How are tags in the tag cloud generated?

When WordPress generates the HTML for the tag cloud, it queries a limited number of tags, ordered by count. But how is this count calculated. You would think, that a database query groups the tags on the posts, then groups by the tag ID and calculates the count. But this is not how it works in WordPress. For performance reasons, the count is stored statically in the database table. But why are private posts not showing up in the tag cloud? Once you change the visibility of a post to private, the posts is not added to the count of the tag. Only public posts are used to calculate the count. So there is not easy way using a filter/action to include the private posts. So how could it be solved?

Altering the query

Almost every query in WordPress, that resolves in the output of “a list”, is using a WP_Query. For the tag cloud, the WP_Term_Query is used. So we simple have to alter this query. But how do we know, that we are in the WP_Term_Query used in the tag cloud? Ther is no is_tag_cloud() or similar conditional tag.

The tag cloud is using two very specific arguments, that are only present in the tag cloud: largest and smallest. These two arguments are used, to set the minimum and maximum font size used in the tag cloud. So we can easily use one of this arguments, to catch the tag cloud’s WP_Term_Query. The rest is rather easy. We use the term_clauses filter to change the SQL query:

function private_posts_in_tc_terms_clauses( $pieces, $taxonomies, $args ) {
	if ( isset( $args['largest'] ) && is_user_logged_in() ) {
		// Count by the grouped term_id.
		$pieces['fields'] .= ', COUNT(t.term_id) AS grouped_count';
		// Join the relationship to the posts.
		$pieces['join'] .= ' INNER JOIN wp_term_relationships AS tr ON tr.term_taxonomy_id = tt.term_taxonomy_id';
		// Remove the "redundant" count (only for public posts).
		$pieces['where'] = str_replace( 'AND tt.count > 0', '', $pieces['where'] );
		// Group by the term_id to remove duplicates and calculate the count.
		$pieces['where'] .= ' GROUP BY tt.term_id HAVING grouped_count > 0';
	}

	return $pieces;
}
add_filter( 'terms_clauses', 'private_posts_in_tc_terms_clauses', 10, 3 );

We also make sure, that we only show the private tags to logged in users. In the altered SQL query, we join the term_relationship table, group by the term ID and count on the terms. This way, we get the correct count including the private posts. The query itself is a bit slower than the original one, as it is not using the “cached” count from the table. But every query to the WP_Term_Query is cached in a transient anyways, so the performance impact should not be too high.

Conclusion

It’s not always as easy as using a simple hook, to change a query in WordPress. But as usually, there is always a way to get the desired result. If you want to use this function yourself, you can find the code as a plugin in a GIST. So simply download it as a ZIP file and install it as a plugin on your site.

Posted by

Bernhard is a full time web developer who likes to write WordPress plugins in his free time and is an active member of the WP Meetups in Berlin and Potsdam.

Leave a Reply

Your email address will not be published. Required fields are marked *