2

I'm wondering if it's possible to determine WP_Query parameters by only using the given page URL. The reason I'm trying to do this because I'm developing a SPA theme (and ultimately a JSON response) and would need to be able to determine a query without reloading the page.

Thought it's pretty hacky, I've been successful in pairing a post URL with the Permalink structure in order to determine what a single post query should be:

<?php

function api_query_url() {

  // Check that the URL is set and is in fact a string
  if ( ! isset( $_POST['url'] ) || gettype( $_POST['url'] ) !== 'string' ) {
    api_response( false, 400, "'url' is a required parameter!" );
  }

  // Parse the URL for parameters and path
  $query_url = parse_url( $_POST['url'] );

  // Pass query-based URL directly to Single API request
  if ( isset( $query_url['query'] ) ) {
    api_single( $query_url['query'] );
  }

  // Map Permalink structure tags to WP_Query parameters
  $structure_refs = array(
    'year'     => 'year',
    'monthnum' => 'monthnum',
    'day'      => 'day',
    'hour'     => 'hour',
    'minute'   => 'minute',
    'second'   => 'second',
    'post_id'  => 'p',
    'postname' => 'name',
    'category' => 'category_name',
    'author'   => 'author_name'
  );

  // Retreive the site's Permalink Structure and break it in to an array
  $structure = array_filter( explode( '/', get_option( 'permalink_structure' ) ) );

  // Break our query parameters in to an array
  $query_params = array_filter( explode( '/', $query_url['path'] ) );

  // Set up an empty query array
  $query = array();

  // For each query parameter received
  foreach ($query_params as $param) {

    // Using the parameter value's position, locate its corresponding structure key
    $key_position  = array_search( $param, $query_params );
    $structure_key = str_replace( '%', '', $structure[$key_position] );

    // If the structure key exists in the structure references, add to to the query
    if ( array_key_exists( $structure_key, $structure_refs) ) {
      $query[$structure_refs[$structure_key]] = $param;
    }
  }

  // Pass off to Query handler to create JSON response
  api_single( $query );

}
add_action( 'wp_ajax_nopriv_api_query_url', 'api_query_url' );

Using the above function I'm able to pass url as a parameter to it to perform an ajax call, and it will parse it to find the WP_Query parameters.

So, a URL like http://example.com/blog/my-first-post/ will be parsed to extract name => 'my-first-post' and a URL like http://example.com/?p=124 will be parsed to extract p => 124. This then gets passed to a WP_Query and so forth. FTR, I also remove any blacklisted parameters, like post_status or has_password in an attempt to keep things secure.

In order to parse a pretty URL structure I take the components of the path, so for /2014/07/hello-world/ we get 2014, 07, and hello-world, and then I compare them to the set permalink structure, which in this case would be /%year%/%monthnum%/%postname%/ - and since some permalink tags don't work with WP_Query, I do some conversion; for example, postname would be name.

Anyhow... it felt like I was off to a good start, until I ran in to pages. With my above code a page URL like http://example.com/about/ and the above permalink structure the parameters extracted would be year => 'about'. Not good.

If anyone has any guidance as to how I should approach this it would be much appreciated. Also, please let me know if I'm approaching this all wrong. I just need to determine a query without reloading the page.

  • 1
    have you looked at how WordPress parses URLs to query parameters? that's probably a good place to start. – Milo Jul 14 '14 at 19:56
  • Trying to understand how the functions canonical.php work but no having much luck :( – Jody Heavener Jul 14 '14 at 20:23
  • request parsing happens in wp-includes/class-wp.php – Milo Jul 14 '14 at 20:31
5

Edited Jul, 07 2014 at 6:28


There are a lot of gotchas with your approach. It will not work if there are any custom rewrite rule, it (probably) will not work if server is IIS and pretty permalink are active, it will not work for custom post types, it will not work for archive or search urls and so on.

To obtain a sing post/page url from a url in a affordable way, there is the function url_to_postid, e.g.

$id = url_to_postid( $query_url['path'] );

if ( is_numeric( $id ) && (int) $id > 0 ) {
  // the required post id is: $id
} else {
  // the url is not for a single post, may be an archive url, a search url or even
  // a non-valid 404 url
}

If you are interested only is single post (even CPT) or page urls, then probably the few lines above are the only thing you need, but if you need to parse all kind of WordPress urls, then you need something else, and is not something really easy.

The only way to obtain query vars from a url in an affordable way, is to reproduce the way WordPress uses to parse an url.

That is done in core by the method parse_request of the WP class.

However, using that method for our purpose explained above is hard/discouraged because:

  • it directly accesses to $_SERVER, $_POST and $_GET variables, making hard to parse arbitrary urls not related with current HTTP request
  • it triggers some action hooks strictly related to current HTTP request parsing, that makes no sense to trigger for arbitrary urls
  • it accesses and modifies properties of global $wp variable that should not be changed after request is parsed or very likely things will break.

What we can do is starting from the code there, remove the parts we don't need, and make the code use an arbitrary url instead of the one from current HTTP request.

Core parse_request method is 214 lines long (v. 3.9.1) and do that work can be such a pain, but luckily someone has already done the hard work, see here.

  • could you elaborate why parse_request should only be run once? I'm a little confused about that part – Jody Heavener Jul 16 '14 at 22:19
  • 1
    That action is fired by WordPress once, when the url is first parsed. So a lot of devs use that action to trigger something that is expected to run once. As example see the Shazzad anwser here. If you run it more than once I bet some plugin will break. In addition 'parse_request' is and action that WordPress fire to say "I've parsed the the request uri", but here, WP method is used to parse an arbitrary url, so even from a logical point of view is wrong to fire that action. @JodyHeavener – gmazzap Jul 17 '14 at 3:34
  • @JodyHeavener edited the answer, because previous code had some problems. – gmazzap Jul 17 '14 at 5:29
  • did you write that plugin just for this question?! – Jody Heavener Jul 17 '14 at 17:10
  • G. M. what license is this plugin under? – Jody Heavener Jul 18 '14 at 15:20
1

There's a simple solution using a Good and an Odd Mechanism.

The Good Part

function api_query_url()
{
  // Check that the URL is set and is in fact a string
  if ( ! isset( $_POST['url'] ) || gettype( $_POST['url'] ) !== 'string' ) {
    api_response( false, 400, "'url' is a required parameter!" );
  }

  // Add an extra parameter to the url.
  $api_url = add_query_arg( 'json_vars', time(), $_POST['url'] );

  $request = wp_remote_request( $api_url );
  $content = wp_remote_retrieve_body( $request );

  if( is_wp_error($content) ){
    api_response( false, 400, "couldn't retrive the data" );
  }

  if( !empty($content) )
      $vars = json_decode( $content );
  else
      $vars = array();

  // pass the array to your function
  api_single( $vars );
}
add_action( 'wp_ajax_nopriv_api_query_url', 'api_query_url' );  

The Odd Part

/* Comparing the extra parameter (json_vars), we return the json of the vars & no html */
function api_parse_request( $q )
{
    /* home/front-page doesn't have any q-vars */
    /* so you might be setting some defaults for that */
    $vars = $q->query_vars;
    if( isset($_REQUEST['json_vars']) ){
        echo json_encode( $vars );
        die();
    }
}
add_action( 'parse_request', 'api_parse_request' );
  • 1
    The approach is not very "odd", I think is good, IMHO the real problem here is the additional HTTP request that I would avoid. – gmazzap Jul 14 '14 at 22:12
  • This looks great. Any idea how I would about getting the front page/post page query? Unsure about how I would handle / – Jody Heavener Jul 14 '14 at 22:16
  • If the variable is empty, then it's possibly the front page. All other page will have at least 1 variable. – Shazzad Jul 14 '14 at 23:58

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.