3

I've been experimenting with repurposing WordPress's 404 handler/template file for a Real Estate site that I'm developing. I understand that when a non-existent URL is requested, the 404 page gets served up and that it returns a 404 status code (instead of a 200). But I've recently found a way to adapt the 404 template file so that it does something a bit more creative. More specifically, I'm first parsing the non-existent URL that was requested, and then using the returned value to query an external API and then display the returned data. So far it's working much better than I'd expected, though I'm aware of one significant issue that I'm hoping I can address.

In those situations where the 404 handler is served up, yet data is returned from querying the external API, .. how can I then -- and only then -- re-write the "404" header information so that it's a "200" status code? I ask because I don't want these "Page Not Found" entries littering our Google Analytics reports and mucking up the site's SEO ranking.

Here is an example non-existent URL which triggers the serving up of the 404 handler:

https://example.com/property/123-four-street/

I am parsing this URL using the following PHP code snippet:

$url = $_SERVER['REQUEST_URI'];
$path_parts = explode('/', $url);   
$address = $path_parts[2];  
$parsedaddress = str_replace("-", " ", $address);                   
echo $parsedaddress;    

The "parsedaddress" value is then used to query the external API, and what I'd then like to have happen logic-wise is something along the lines of ...

if (query returns valid MLS data)

then (force a "200" status code and display the MLS data)

else (retain the "404" status code and display the 404 error page)

I've already added this custom function to my functions.php file, which is successfully changing the default page title for the 404 handler ... so I know that I can hook in to it. But how might I adapt this so that it conditionally rewrites the header information?

function theme_slug_filter_wp_title( $title_parts ) {
    if ( is_404() ) {               
        
        $url = $_SERVER['REQUEST_URI'];
        $path_parts = explode('/', $url);   
        $address = $path_parts[2];  
        $parsedaddress = str_replace("-", " ", $address);           
        
        $title_parts['title'] = ucwords($parsedaddress) . ' | AAA Real Estate;
    }

    return $title_parts;
} 
add_filter( 'document_title_parts', 'theme_slug_filter_wp_title' );

Any help or advice would be appreciated.

Thanks, -- Yvan

13
  • 1
    I like this question... like what you’re doing... here’s my immediate thought... the 404 status code is registered before that page loads and before your code runs, because that’s how it knows to run your code. So I’m wondering if it’s even possible to retroactively change the status code? Jan 14, 2021 at 20:35
  • 1
    You bring up a good point. As I've been researching this, however, I kept coming across references to this WordPress status_header()" method: developer.wordpress.org/reference/functions/status_header It's just not clear as to exactly how or where I'd need to implement this within the WordPress framework's execution chain, or if the search engine crawlers would even acknowledge it .. as you've just pointed out. Does anyone else here have any information or advice regarding this? Thanks, -- Yvan Jan 15, 2021 at 17:34
  • 1
    As a potential fallback plan .. is there any way to set up special filters within a Google Analytics account (or within an .htaccess file, perhaps) that would purposefully ignore 404s for any URLs that include "/properties/" in them? By that I mean ... find a way to tell Google "Yes, we already know about those URLs, but please don't hurt our SEO ranking because of them." Jan 15, 2021 at 17:34
  • 1
    If you have any further details you could update your question, or set a bounty on it, or delete it and add a new question with updated info... Jan 28, 2021 at 16:59
  • 1
    I’m curious to see the answer to this, if you like I can put a bounty on it. Jan 28, 2021 at 16:59

1 Answer 1

1

Here is the tested code, you can use it as a mu-plugin:

<?php
/**
 * Filters whether to short-circuit default header status handling.
 *
 * Returning a non-false value from the filter will short-circuit the handling
 * and return early.
 *
 * @param bool     $preempt  Whether to short-circuit default header status handling. Default false.
 * @param WP_Query $wp_query WordPress Query object.
 *
 * @return bool
 * @since 4.5.0
 */
function pre_handle_404_filter( bool $preempt, WP_Query $wp_query ) {
    global $parsed_address, $api_results;

    $parsed_address = get_parsed_address();

    if ( ! $parsed_address ) {
        return $preempt;
    }

    // Make a request to  API, get status.
    $status      = true; // For debug purposes.
    $api_results = null;

    if ( $status ) {
        // Save results to $api_results.
        // Return true to avoid 404 processing by WP.
        return true;
    }

    return $preempt;
}

add_filter( 'pre_handle_404', 'pre_handle_404_filter', 10, 2 );

/**
 * Handle 404.
 */
function handle_404() {
    global $parsed_address, $api_results, $wp_query;

    if ( ! $parsed_address ) {
        return;
    }

    // Set 404 here to prevent bugs in get_header().
    $wp_query->set_404();

    get_header();
    echo 'API Results'; // Output $api_results here.
    get_footer();
    exit();
}

add_action( 'template_redirect', 'handle_404', - PHP_INT_MAX );

/**
 * Get parsed address from the request URI.
 * Make sure we have an address in the URI, otherwise return an empty string.
 *
 * @return string
 */
function get_parsed_address() {
    $request_uri =
        isset( $_SERVER['REQUEST_URI'] ) ?
            filter_var( wp_unslash( $_SERVER['REQUEST_URI'] ), FILTER_SANITIZE_STRING ) :
            '';

    if ( url_to_postid( $request_uri ) ) {
        return '';
    }

    $path_parts = explode( '/', $request_uri );
    $part1      = isset( $path_parts[1] ) ? $path_parts[1] : '';

    if ( 'property' !== $part1 ) {
        return '';
    }

    $address = isset( $path_parts[2] ) ? $path_parts[2] : '';

    return str_replace( '-', ' ', $address );
}

/**
 * Filter page title.
 *
 * @param array $title_parts Title parts.
 *
 * @return mixed
 */
function theme_slug_filter_wp_title( $title_parts ) {
    global $parsed_address, $api_results, $wp_query;

    if ( ! $parsed_address ) {
        return $title_parts;
    }

    $title_parts['title'] = ucwords( $parsed_address ) . ' | AAA Real Estate';

    return $title_parts;
}

add_filter( 'document_title_parts', 'theme_slug_filter_wp_title' );

We catch 404 via pre_handle_404, make the API request and save results. Please note that it works when we have parsed address in the URI only.

Later, on template_redirect event, we check again that have parsed address and set back 404 status to prevent bugs in the get_header(). Please note that we intercept template_redirect as early as possible, at - PHP_INT_MAX priority to execute our code before standard template_redirect hook in the WP core.

13
  • Thank you for this! I understand that I'll need to tinker with this a bit and that I can't just straight up copy and paste -- but know that when I did try to copy and paste these into my child theme's functions.php file as-is, I consistently got critical errors. The "pre_handle_404_filter" and "get_parsed_address" methods seem to play well together, -- but I think I'm going to need to do a bit more work on the "handle_404" method. Question -- can I safely implement this in my child theme's functions.php file? Feb 2, 2021 at 17:26
  • I do not know your API, and cannot write the related code. Code is 100% working, you can just copy and paste it as a mu-plugin. Create a folder mu-plugins under wp-content, create there a file with any name like my-404.php, copy and paste code there, including the first line <?php. And that's it. - you have created a must-use plugin. Feb 2, 2021 at 17:33
  • If you get a critical error, you do something definitely wrong, Code is tested on my test site and works as expected. Just report at least here what kind of error do you have. Feb 2, 2021 at 17:34
  • You're correct that it works as an mu-plugin (that's why I was asking if it would work in functions.php). But I'm still having a problem. This is designed to treat ALL urls that have "property" and an address in them as 404s, when that isn't the case. I have properties set up as custom posts that have the exact same url structure, and I need to display them. So the custom 404 handling be can't be triggered based on the URL structure. I think I might need to update the get_parsed_address() method so that it first checks to see if the post actually exists before doing the custom 404 handling. Feb 3, 2021 at 17:49
  • I do not know your task in details, and I have solved the main problem - careful processing of 404 with accessing your API. You can extend the code, and add some array with the acceptable property names to check the $address - it is easy, Return empty string if the address is not in the array of allowed ones. Feb 3, 2021 at 17:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.