How to force a query conditional?

There is a long-standing WordPress core bug (#16373) in which, if custom query variables are registered and present in the query string, the query will not set is_front_page() true, even if 'page' == get_option( 'show_on_front' ). Full details are in the trac ticket, but the end result is that the front page returns an invalid page instead of get_option( 'page_on_front' ).

I have a certain use case in which I need to pass custom query variables to the query string on the front page (using a form to pass registered query variables, which are in turn used to filter Theme options – a front-end Theme options demo). The above-mentioned trac ticket was closed as invalid, so I need a work-around.

Read More

I can force the front page template easily enough, like so:

function themeslug_force_front_page_template( $template ) {
    if ( '' != get_query_var( 'foobar' ) ) { // Registered custom query var
        return get_front_page_template();
    }
    return $template;
}
add_filter( 'template_include', 'themeslug_force_front_page_template' );

However, the underlying query conditionals are not impacted. So any code dependent on is_front_page() being true (such as, say, a slider that only displays on the site front page) will still not render properly.

I have attempted to modify $query at pre_get_posts, but this does not appear to be working:

function themeslug_force_static_front_page( $query ) {
    if ( $query->is_main_query() ) {
        if ( 'page' == get_option( 'show_on_front' ) ) {
            if ( '' != get_query_var( 'foobar' ) ) { // Registered custom query var
                $query->set( 'page_id', get_option( 'page_on_front' ) );
                $query->set( 'is_home', false );
                $query->set( 'is_page', true );
                $query->set( 'is_front_page', true );
            }
        }
    }
}
add_action( 'pre_get_posts', 'themeslug_force_static_front_page' );

The query conditionals are not modified. Am I attempting incorrectly inside my callback? Is there a different/better way to force the query conditionals, in particular is_front_page()?

Edit

Note that setting the page_id does work in the pre_get_posts callback:

$query->set( 'page_id', get_option( 'page_on_front' ) );

If I omit the template_include filter, the displayed page is, indeed, the page assigned to the front page; however, get_page_template() is used, rather than get_front_page_template(). So, setting page_id at pre_get_posts does cause is_page() to be true. But the aforementioned bug prevents is_front_page() from being set to true.

Edit 2

Per @toscho’s request, here’s some query var code, for context.

Register query variables:

/**
 * Add options-related query variables
 */
function themslug_add_theme_demo_query_vars( $qvars ) {
    $qvars[] = 'demo_foo';
    $qvars[] = 'demo_bar';
    $qvars[] = 'demo_baz';
    return $qvars;
}
add_filter( 'query_vars', 'themeslug_add_theme_demo_query_vars' );

(Where foo, bar, and baz are Theme options – background, various colors, etc.)

How they are used is simply to add a filter to a Theme option – that part is out of the scope of the question, so I’ll omit it here.

They are output on the front end via custom Widget. The Widget output is just a form. Here’s an example of one of the form fields:

<h3>Foo</h3>
<input name="demo_foo" id="demo_foo" class="pickcolor"  type="text" value="<?php echo $foo_setting; ?>" data-default-color="<?php echo $foo_setting; ?>" />

Generated query string on-submit:

www.example.com/?foo=some_value

Related posts

2 comments

  1. Did you check what part of is_front_page() is causing it to return false?

    I could reproduce the problem by following the setup from the trac ticket. In my case inside this function the call to is_page() was returning false.

    I guess this is due to using $wp_query->set() for page_id and is_page is only causing the query_vars to be changed, you don’t change the “state of the query” itself.

    I was able to create a workaround for this by appending this two lines to your code:

    $query->is_page = true;
    $query->queried_object = get_post(get_option('page_on_front') );
    

    Which resulted (in my setup) in the page being correctly displayed on the frontpage (despite the query_var being present in the URL) and also is_front_page() returning true.

    I hope this is helping you somewhat further.


    If you look at the definition of the WP_Query class, you’ll see that there is a variable $query_vars and a variable $is_page.

    The set function you used ($query->set( 'is_page', true );) does only set a query var:

    function set($query_var, $value) {
        $this->query_vars[$query_var] = $value;
    }
    

    It does not change the state information that is saved in $query->is_page = true, instead it sets the query var $query->query_vars['is_page'] = true.

    That is a bit misleading if you come from an OOP-approach and understand WP_Query::set() as a classic class-setter function – which it isn’t.

  2. I replied in that Trac ticket too, but I think the base problem here is a lot simpler than this.

    You’re hooking in demo_foo, demo_bar, demo_baz as “Query variables”, but they’re not; at least, not in the intended sense.

    While the ?foo=bar string after the URL is often called the query string, the “query variables” are actually just variables that affect the main query. If you don’t hook in a variable, it doesn’t disappear, it just doesn’t get read by the main WP_Query object.

    And in your case, that appears to be exactly what you want. Your variables are theme options, they’re not part of the main Query (which gets posts from the database). You’re not using them to select what posts to display, you’re using them to set theme options and such.

    If you don’t hook them in as query_vars, then they won’t affect the main WP_Query object at all. You will have to get them from the main $_GET superglobal instead of using get_query_var, and you will have to perform proper sanitization on them to prevent reflective XSS attacks (using esc_attr or whatever is appropriate), but this should solve your primary problem by eliminating the code causing the problem in the first place, instead of adding in an extra workaround.

Comments are closed.