WordPress has a lot of powerful tools that will speed up the development process by a great amount, the first one that you will encounter when creating themes is the WP_Query.

For those who are not aware of what is the WP_Query, you can assume its the WordPress tool that will give you control over the Queries made to fetch posts in the database.

On this article I will talk a little bit of how to change and modify your WordPress theme or plugin to allow better control over the WP_Query taxonomies Module, which gives the developer a very easy way to handle the Taxonomy related searches within WordPress.

The WP_Tax_Query

When you call for a new instance of the WP_Query with any mention to any taxonomy, as example Categories and Tags, WordPress internally will use WP_Tax_Query to create the SQL related to the Taxonomies.

Main Query vs new WP_Query

When you request a url inside of WordPress it will load a main WP_Query instance that will try to fetch the contents for the template that you are currently trying to see.

For the instances of the WP_Query that you fire inside of a template you will have the full control over which variables are going to be used, when dealing with the Main WordPress Query you will need to leverage the power of some actions or filters in order to be able to change which arguments are being used to build the SQL.

How to Change the WordPress Main Query

There is one action that is important when it comes to changing the main WordPress Query, pre_get_posts is an action that has 1 single variable attached to it, the $query, which is the WP_Query instance that is about to run.

It’s very important to note, that this action will run before the SQL query to grab the posts for ANY WP_Query, so you will need to make sure that you are dealing with the main query before doing anything.

Exclude two Categories

On our first example we will have a few rules that should be considered before we change the $query instance.

  • If we are not in the main Query, we don’t want to continue
  • When the requested page is one of the two categories archives
  • Don’t do anything if we are inside of a single page
add_action( 'pre_get_posts', static function ( $query ) {
	// It’s always better to look for negative then returning
	// Makes our code cleaner and with less indentation
	if ( ! $query->is_main_query() ) {
		return; // Actions on WordPress require no Return
	}

	// Excluded Categories
	$excluded_categories = array( 1, 5 );

	// Check for single page, like a Post or Attachment page – This will avoid posts from that category from giving a 404
	if ( $query->is_singular ) {
		return;
	}

	// If we are on the Category Archive we don’t want to exclude the posts
	if ( is_category( $excluded_categories ) ) {
		return;
	}

	// Here we do our magic
	$query->set( 'category__not_in', $excluded_categories );
} );

Link Two Categories together

Imagine the following scenario, I have two categories in WordPress that are almost the same and I want to make sure that whenever I’m searching for the Archive of the first One I will show results from the second.

In this case we will allow this structure to pass to any instance of the WP_Query not only the main.

add_action( 'pre_get_posts', static function ( $query ) {
	// Linked Categories
	$linked = array( 2, 3 );

	// If singular we don't care
	if ( $query->is_singular ) {
		return;
	}

	// Check the possible variables
	// Don't forget to check if isset to avoid warnings
	if ( isset( $query->query_vars['cat'] ) ) {
		$intersect = array_intersect( (array) $query->query_vars['cat'], $linked );

		if ( ! empty( $intersect ) ) {
			$query->query_vars['cat'] = array_unique( (array) $query->query_vars['cat'] + $linked );
		}
	}

	// Check both the variables
	if ( isset( $query->query_vars['category__in'] ) ) {
		$intersect = array_intersect( (array) $query->query_vars['category__in'], $linked );

		if ( ! empty( $intersect ) ) {
			$query->query_vars['category__in'] = array_unique( (array) $query->query_vars['category__in'] + $linked );
		}
	}

// If I'm excluding one of the Categories then Exclude the other
	if ( isset( $query->query_vars['category__not_in'] ) ) {
		$intersect = array_intersect( (array) $query->query_vars['category__not_in'], $linked );

		if ( ! empty( $intersect ) ) {
			$query->query_vars['category__not_in'] = array_unique( (array) $query->query_vars['category__not_in'] + $linked );
		}
	}
} );

Restricted Category

You might see yourself in a situation where you will need to restrict the contents of a category based on a user capability, which can of course be done with the help of plugins, but for the sake of the demonstrations let’s assume that you need to do it yourself.

add_action( 'pre_get_posts', static function ( $query ) {
	$restricted = array( 8 );
	$capability = 'read_private_posts';
	// If user cant, then remove any posts from that category from the query
	if ( ! current_user_can( $capability ) ) {
		if ( isset( $query->query_vars['category__not_in'] ) ) {
			$query->query_vars['category__not_in'] = ( (array) $query->query_vars['category__not_in'] ) + $restricted;
		} else {
			$query->query_vars['category__not_in'] = $restricted;
		}
	}
} );

Important Conditionals:
– We don’t care if it’s the main query
– Users need to have the permission to read_private_posts to see any of the content

Questions and Mistakes

If I made any mistakes on my codes above or said something that is wrong, please comment below and I will make sure to amend it.

2 Comments

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.