I’m trying to load/utilise an existing page template (templates/results.php) while making use of the “s” (search) query parameter. For example: example.com/results?s=lorem
Currently, this results in a 404.
I’ve got a filter using template_includes, however it appears that the page has already been “decided” before that hook/filter (is_search(), is_page('results') are both false) before loading the template. I could just load the template files based on some other parameters, but then I’d have to set the title and other things away from the 404 page.
The pagename and s fields are recognised in the global $wp_query‘s raw query vars, so it’d just be a case of “deciding” what the query actually is before the template is hit.
Usecase for this being able to show a filtered set of posts for a custom post type “listings”. The Listings archive already shows listings, automatically filtered for “open” ones, and optionally filtered for its taxonomy and “s” for any searches. I’d have the results page do the same thing, being able to pick up the same optional query parameters as its archive counterpart.
How can I load a page, and utilise an existing page template while still using the in-built s query parameter?
This answer‘s solution 1 mentions replacing the default ‘s’ query var with a custom one, aiming to avoid that and hopefully filter/hook in before WordPress decides to search the results page for the ‘s’ string.
How the page/query are currently used:
Template file:
<?php
// get_header() and the_post() related parts have already been called in the main template file.
// Temporary query to replace the main one so we can utilise pagination, etc.
global $post;
global $wp_query;
$listing_results_query = new WP_Query( [
'listing_type' => get_query_var( 'listing_type' ),
'pricing' => get_query_var( 'pricing' ),
's' => get_query_var( 's' ),
'paged' => get_query_var( 'paged', 1 ),
'post_type' => 'listing',
'query_id' => 'listing-results',
'listing_status_compare' => '!='
] );
$temp_query = $wp_query;
$wp_query = NULL;
$wp_query = $listing_results_query;
?>
<span>Various HTML and get_template_part() calls being used to load the archive</span>
<?php
// Reset main query object
$wp_query = NULL;
$wp_query = $temp_query;
?>
Hook/function file (equivalent of functions.php)
I’ve cut some checks/other query parts out to simplify it for here. At the point of calling this hook (pre_get_posts), $query->is_404 is already set to true.
function wpse_390935_modify_listing_archive_query( $query ) {
$is_listing_archive_results = ! is_admin() && ( $query->query['query_id'] ?? false ) === 'listing-results';
if ( $is_listing_archive_results ) {
$page_no = $query->query['paged'] ?? 1;
$listing_type = $query->query['listing_type'] ?? '';
$pricing = $query->query['pricing'] ?? '';
$search = $query->query['s'] ?? '';
$status_compare = $query->query['listing_status_compare'] ?? '=';
$meta_query = [];
// Search
if ( $search ) {
$query->set( 's', sanitize_text_field( $search ) );
}
// Other custom field/taxonomy query bits
}
}
add_action( 'pre_get_posts', 'wpse_390935_modify_listing_archive_query', 10, 1 );
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
Custom Query Var
Moving s to a custom query variable when a page is explicitly requested should be simple enough:
function wpse390935_set_page_search_qv( $wp ) {
$qvs = $wp->query_vars;
if( empty( $qvs['s'] ) || ( empty( $qvs['pagename'] ) && empty( $qvs['page_id'] ) ) )
return;
$wp->query_vars['page_search'] = $qvs['s'];
unset( $wp->query_vars['s'] );
}
add_action( 'parse_query', 'wpse390935_set_page_search_qv' );
This prevents it from getting factored into the main query while, leaves it accessible later via get_query_var( 'page_search' ), and continues to present the variable as s to end-users.
Stashing the Query Var
If it absolutely needs to remain in s internally, then you could use a bit of a hack and remove s from the request’s query_vars using a parse_request action hook, then add it back again after the query executes.
I’ve used a singleton class here just to hold the s value in between hooks.
class WPSE390935_Search_Param_Passthrough {
private static $instance = null;
private $search = '';
public static function get_instance() {
if( is_null( self::$instance ) )
self::$instance = new self();
return self::$instance;
}
private function __construct() {
add_action( 'parse_request', [ $this, 'stash_query_var' ], 2 );
}
public function is_page_search() {
if( is_admin() )
return false;
if( did_action( 'parse_request' ) > 0 )
return ! empty( $this->search );
throw new Error( 'Too early to determine is_page_search() condition.' );
}
public function restore_query_var() {
set_query_var( 's', $this->search );
}
public function stash_query_var( $wp ) {
$qvs = $wp->query_vars;
if( ( empty( $qvs['pagename'] ) && empty( $qvs['page_id'] ) ) || empty( $qvs['s'] ) )
return;
$this->search = $qvs['s'];
unset( $wp->query_vars['s'] );
add_action( 'wp', [ $this, 'restore_query_var' ], 2 );
}
}
function is_page_search() {
return WPSE390935_Search_Param_Passthrough::get_instance()->is_page_search();
}
WPSE390935_Search_Param_Passthrough::get_instance();
I’d strongly recommend not adding the search value back to the main query, and instead just retrieving it from the class later when needed. This way the state of the main query’s query vars would not misrepresent what the main query actually is.
To a similar end, I’ve also provided an alternate conditional predicate in the form of is_page_search(). While you could set $wp_query->is_search = true in the restore_query_var() method, I would be concerned about any side-effects which that might have. Both in core and any plugin depending on the core predicate.
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0