Stop WordPress to run main query is hard: you have to dig inside the deep heart of WordPress.
WordPress frontend workflow:
wp-blog-header.php
call the function wp()
wp()
function creates an instance of wp class, and call main()
method on it
main()
method code:
public function main($query_args = '') {
$this->init();
$this->parse_request($query_args); // build query vars starting from url
$this->send_headers();
$this->query_posts(); // run main query via WP_Query using built query vars
$this->handle_404(); // if query has no results set 404 headers and WP_Query props
$this->register_globals();
}
wp-blog-header.php
include template-loader.php
that load template file based on query
So only way to stop main query is prevent wp()
to run.
That can be done hooking an early hook, do what you need, and finally exit()
.
The problem of this approach is that preventing wp()
to run, you also prevent wp class to parse request, so the rewrite stuff you add are not parsed.
So, instead of to add a rewrite tag and a rewrite rule, you may want to look straight at the url to choose if it is the one that should trigger your custom action.
First of all let's write a function that return only the relative parte of the url, e.g. if current url is something like example.com/wp/path/to/somewhere?foo=bar
and your home url is example.com/wp/
the function returns path/to/somewhere
:
function get_relative_url() {
$home_path = rtrim( parse_url( home_url(), PHP_URL_PATH ), '/' );
$path = trim( substr( add_query_arg( array() ), strlen( $home_path ) ), '/' );
$qs = array_keys( $_GET );
if ( ! empty( $qs ) ) {
$path = remove_query_arg( $qs, $path );
}
return $path;
}
Now we can use an early hook to look at the url, and only if it contains the desired path we'll set the $settore
variable, load the template and exit:
add_action( 'wp_loaded', function() { // 'wp_loaded' hook happen before wp() is ran
$path = get_relative_url();
$url_parts = explode( '/', $path );
if (
isset( $url_parts[1] )
&& $url_parts[0] === 'settore'
&& in_array( $url_parts[1], array( 'caldareria', 'tessile' ), TRUE )
) {
// ok the current url is something like example.com/settore/tessile
// and 'tessile' is in the variable $url_parts[1]
$template = locate_template( 'root-taxonomy.php' );
if ( empty( $template ) ) return; // do nothing if template not found
global $settore;
$settore = $url_parts[1]; // in the template you can access to $settore variable.
require_once $template;
exit(); // prevent WordPress to do anything else
}
} );
That's all.
In code above, understanding if the current url is the right one, and set the $settore
variable was easy because the url structure was easy, for more complex url structures, you probably need a regex matching, and maybe use a tool like Symfony routing component to match the url to some variables.
This approach is the one I've used in Cortex, have a look there if you plan to commonly do thing like that.
Gotchas
as you can see, the main query (global $wp_query
) in code I posted is not used, in your template it will be an empty WP_Query
object, so anything will refer to main query, e.g. template tags like is_tax()
, is_archive()
or such, will not work.
That will apply to header and footer as well, because very probably in your 'root-taxonomy.php' you'll use get_header()
and get_footer()
.
Also some plugins may not work properly, e.g. if you are using any SEO plugin that set title tag based on the query, than it will have issues.
A possible solution is to manually set main query properties accessing to global $wp_query
or use a custom header, e.g. get_header('root-tax')
, for your custom template and handle in header-root-tax.php
custom title tag and/or any other issue.
template_include
andtemplate_redirect
are fired main query already happen, so using any of the two hooks you can't stop it.