Control WordPress 404 Error
I have site built with Wordpress and I wanted to catch all 404 page that Wordpress try to display and make my own custom 404 page depending on the requested page, or in fact I just wanted to whether or not show a 404 page.
If the user, let's say, go to http://mysite.com/someword
on mysite, and if that is not a post/page it would probably display a 404 page, but I didn't want that behavior I wanted to search if someword
is a category slug or a tag and display posts in that category or tag, otherwise would show the normal 404 page.
So, this is what I did, I added a action to template_redirect
hook, that is executed before WordPress determine which page is going to be loaded.
<?php add_action('template_redirect', '_custom_redirect'); function _custom_redirect() { global $wp_query; if ( $wp_query->is_404() ) { // Here I take over 404 page } }
So now, I know when it's a 404 page, I need to get the string it was passed on the url, in this case someword
, so I can check if the string match a category or a tag name with that name.
This string is stored in a array named query_vars
with the key name
. query_vars
is a variable member of WP_Query
which WordPress uses to execute the main query.
<?php add_action('template_redirect', '_custom_redirect'); function _custom_redirect() { global $wp_query; if ( $wp_query->is_404() ) { $page_name = $wp_query->query_vars['name']; if ( ! $page_name ) return; if ( ($category = get_category_by_slug( $page_name )) ) { // Here it found a category } elseif ( ($tag = get_term_by('slug', $page_name, 'post_tag') ) ) { // Here it found a tag } else { // Is not a category or a tag return; } } }
After this code WordPress would still "believe" it's a 404 page, I needed to add some more lines to changed this.
<?php add_action('template_redirect', '_custom_redirect'); function _custom_redirect() { global $wp_query; if ( $wp_query->is_404() ) { $page_name = $wp_query->query_vars['name']; if ( ! $page_name ) return; if ( ($category = get_category_by_slug( $page_name )) ) { // Here it found a category $wp_query->is_category = true; } elseif ( ($tag = get_term_by('slug', $page_name, 'post_tag') ) ) { // Here it found a tag $wp_query->is_tag = true; } else { // Is not a category or a tag return; } $wp_query->is_404 = false; status_header( 200 ); } }
This is pretty much what I wanted to do, now I can get a tag
or a category
object if one exists, otherwise WordPress will keep its own process and will display its normal 404 page.
If I actually got a tag or category object, I need to query all posts under it.
<?php add_action('template_redirect', '_custom_redirect'); function _custom_redirect() { global $wp_query; if ( $wp_query->is_404() ) { $page_name = $wp_query->query_vars['name']; if ( ! $page_name ) return; $query_args = ''; if ( ($category = get_category_by_slug( $page_name )) ) { // Here we found a category $wp_query->is_category = true; $query_args = 'category_name=' . $category->slug; } elseif ( ($tag = get_term_by('slug', $page_name, 'post_tag') ) ) { // Here we found a tag $wp_query->is_tag = true; $query_args = 'tag=' . $tag->slug; } else { // Is not a category or a tag return; } $wp_query->is_404 = false; $wp_query->is_archive = true; status_header( 200 ); query_posts( $query_args ); } }
If status_header( 200 );
isn't added the HTTP status will always be a 404, so this line change the status code from 404 to 200.
This is about it, but I want a little bit more, I want if is_category();
or is_tag();
functions are used, it has to return true. In order to make this to happen I needed to set a couple of variables more.
<?php $wp_query->set('category_name', $category->slug); $wp_query->set('cat', $category->term_id); $wp_query->set('tag', $tag->slug); $wp_query->set('tag_id', $tag->term_id);
$wp_query->set()
will set a variable to query_vars
array, which as I mentioned before is used by WordPress main query. With this variables set, when WordPress executes is_category();
or is_tag();
would have category or tag specific variable values to check if is a category/tag or not.
Final code#
<?php add_action('template_redirect', '_custom_redirect'); function _custom_redirect() { global $wp_query; if ( $wp_query->is_404() ) { $page_name = $wp_query->query_vars['name']; if ( ! $page_name ) return; $query_args = ''; if ( ($category = get_category_by_slug( $page_name )) ) { // Here we found a category $wp_query->is_category = true; $wp_query->set('category_name', $category->slug); $wp_query->set('cat', $category->term_id); $query_args = 'category_name=' . $category->slug; } elseif ( ($tag = get_term_by('slug', $page_name, 'post_tag') ) ) { // Here we found a tag $wp_query->is_tag = true; $wp_query->set('tag', $tag->slug); $wp_query->set('tag_id', $tag->term_id); $query_args = 'tag=' . $tag->slug; } else { // Is not a category or a tag return; } $wp_query->is_404 = false; $wp_query->is_archive = true; status_header( 200 ); query_posts( $query_args ); } }
This would be helpful too if you want to log/register/email requested pages that ends up being a 404 page.
NOTE: if you don't have any category or tag template page will returns a 404 page template, just take that in mind.