1

I had something really working well - a search page with a custom url. It works fine except when I add the 404.php page to my theme. Then the page 404s. And if I remove it, it starts to work again.

Is there a way I can check for a 404 for this url and "cancel" it? I've tried that, sort of, by checking $GLOBALS['wp_query']->is_404 = false; but that didn't work. Can I put my functions into an action that executes before the 404 check occurs? I've tried creating a post with that slug but obviously that takes me to that page, not my custom page.

Here is the code that I created the custom url with:

add_action( 'parse_request', 'allow_blank_search');
function allow_blank_search(){
  $path = $_SERVER['REQUEST_URI'];
  if(substr($path, 0, 16) == '/catalog-of-work'){
    $_GET['s'] = '';
    $GLOBALS['wp_query']->is_search = true;
  }
  $GLOBALS['wp_rewrite']->search_base = 'catalog-of-work';  
}

function search_url_rewrite_rule() {
    if (!empty($_GET['s'])) {
        wp_redirect(home_url("/catalog-of-work/") . urlencode(get_query_var('s')));
        exit();
    }   
}
add_action('parse_request', 'search_url_rewrite_rule');
1
  • Just wondering, why do you have /catalog-of-work at one line, and /catalog-of-work/ at another line? – Maarten Wolfsen Oct 3 '16 at 7:26
1
+100

Redirect early

First of all, your redirect function should be hooked early: you are not relying on query variables there, so you could use init hook:

function search_redirect() {
    if (!empty($_GET['s'])) {
       $home = trailingslashit(home_url('catalog-of-work'));
       wp_safe_redirect($home.urlencode(get_query_var('s')));
       exit();
    }   
}

add_action('init', 'search_redirect');

The reason is that this will redirect the request much earlier, saving a lot of processing.

Set query variables properly

Now, you need to tell WordPress that when the URL /catalog-of-work is visited, it has to consider a search request.

function custom_search_query_var($do, $wp) {
    $path = trim(parse_url(esc_url_raw(add_query_arg([])), PHP_URL_PATH), '/');
    $home_path = trim(parse_url(esc_url_raw(home_url()), PHP_URL_PATH), '/');
    $home_path and $path = trim(substr($path, strlen($home_path)), '/');
    if (strpos($path, 'catalog-of-work') === 0) {
        $wp->query_vars['s'] = trim(substr($path, 15), '/');
        $do = false;
    }

    return $do;
}

add_action('do_parse_request', 'custom_search_query_var', 10, 2);

There are some things to note in the code above:

  • I used do_parse_request hook instead of parse_request. This allows me to completely prevent WordPress parsing rules whe the URL /catalog-of-work is visited. Since processing query vars can be a slow process, this could improve your page performance.
  • My logic to determine current URL make use of add_query_arg instead of directing access to $_SERVER. That's better because the function takes care of some edge cases.
  • My logic handles the case that your home URL is something like example.com/wp instead of example.com. Since the home URL can easily be changed, this ensure that the function is more stable and will work in different situations.
  • The code I used sets the "s" query var in the $wp->query_vars array. $wp is the current instance of WP class that is passed by do_parse_request hook. This is better for two reasons:
    1. you avoid to access global variables directly
    2. setting variables to global $wp_query is not reliable on do_parse_request (neither on parse_request you used) because that hooks happen before $wp_query is processed and some reset may still happen there, deleting the s query var. By setting the query var to $wp object, it will take care to pass variables to $wp_query.

Prevent 404

Doing things like I described, having a 404 template does not affect anything, because WordPress do not set 404 status for search queries.

However, my code (just like your code, at least the code you posted) does not act on the template so, by default, WordPress will load search.php.

It is very possible that your search.php template (or the template you are using) checks for have_posts() and load 404 template (if found) when there are no posts.

That really depends on the theme you are using.

For example, the theme Twentyfifteen, contains something like:

<?php if ( have_posts() ) : ?>
    // ... redacted loop code here...    
else :
    // If no content, include the "No posts found" template.
    get_template_part( 'content', 'none' );
endif;
?>

If your theme contains something like this, 404 template could be loaded when there are no posts, which happens when the search query is empty.

If that is the issue, you should edit your search template (or if the theme is third party or a core theme you should create a child theme) to make the search template handle the case of no posts according to your needs.

1
  • Lots of good advice, thank you. I'm going to implement it. Seems a bit silly now, but what I ended up doing was adding in $GLOBALS['wp_query']->is_404 = false; After the url check. This seems to work fine though I appreciate the best practices you've showed me. Especially moving the redirect up. Thanks for your help. – dgig Oct 3 '16 at 13:58
0

I think you're looking in the wrong direction for a solution. In stead of trying to prevent a search with zero results ending up at the 404 template, I'd try to catch it there. In other words, in your 404 template include a test if it's a failed search and if yes redirect. Like this:

if (!empty($_GET['s']))
  { search_url_rewrite_rule();}
else
  { normal 404 }

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.