WordPress URLs without posts

The question:

We are working on larger system, which is based on WordPress, but WordPress is used only for “static” content, the main part of the system should be consuming the external API and displaying the data.

My point is: am I able to somehow tell URL rewriting not to use the WordPress internal system for some URLs and use something else?

E.g. mysite.com/news/news-title would show the post, but mysite.com/customcontent/anotherlink would call some mechanism to load data from API and display it.

I don’t know if this is even possible with WordPress.. Thanks for your points.

The Solutions:

Below are the methods you can try. The first solution is probably the best. Try others if the first one doesn’t work. Senior developers aren’t just copying/pasting – they read the methods carefully & apply them wisely to each case.

Method 1

Yes, it is possible.

WordPress frontend workflow can be summarized like so:

  1. A url is visited
  2. By checking current url against all the defaults and custom rewrite rules the url is “converted” to a set of arguments for WP_Query. This is done by the parse_request method of an instance of the WP class stored in the global $wp variable
  3. An instance of WP_Query (saved in the $wp_query global variable) is used to query database and getting the posts related to the arguments retrieved at point #2. This is referenced as the “main query”
  4. Based on the query arguments, a template is choosen according to template hierarchy and it is loaded and used to display results

So, even if you register a set of custom rewrite rules, they will be used to query posts.

However, if you look at the source of parse_request method, in the very first lines you see this:

if ( ! apply_filters( 'do_parse_request', true, $this, $extra_query_vars ) )
  return;

So by attacching a callback to the ‘do_parse_request’ filter, you’ll be able to stop the WordPress parsing process and do whatever you need.

There are different way to do this task, here for sake of simplicity, I’ll give you a rough example.

As said, we need to make a custom url match to… something, probably a callback that retrieve some data and a view to display it.

To make code reusable, we can use a class that accept custom url settings via a filter, and use that custom urls settings to show whatever we need, using a callback to obtain some data and a view to display them.

class MyCustomUrlParser {

  private $matched = array();

  /**
   * Run a filter to obtain some custom url settings, compare them to the current url
   * and if a match is found the custom callback is fired, the custom view is loaded
   * and request is stopped.
   * Must run on 'do_parse_request' filter hook.
   */
  public function parse( $result ) {
    if ( current_filter() !== 'do_parse_request' ) {
      return $result;
    }
    $custom_urls = (array) apply_filters( 'my_custom_urls', array() );
    if ( $this->match( $custom_urls ) && $this->run() ) {
      exit(); // stop WordPress workflow
    }
    return $result;
  }

  private function match( Array $urls = array() ) {
    if ( empty( $urls ) ) {
      return FALSE;
    }
    $current = $this->getCurrentUrl();
    $this->matched = array_key_exists( $current, $urls ) ? $urls[$current] : FALSE;
    return ! empty( $this->matched );
  }

  private function run() {
    if (
      is_array( $this->matched )
      && isset( $this->matched['callback'] )
      && is_callable( $this->matched['callback'] )
      && isset( $this->matched['view'] )
      && is_readable( $this->matched['view'] )
    ) {
      $GLOBALS['wp']->send_headers();
      $data = call_user_func( $this->matched['callback'] );
      require_once $this->matched['view'];
      return TRUE;
    }
  }

  private function getCurrentUrl() {
    $home_path = rtrim( parse_url( home_url(), PHP_URL_PATH ), '/' );
    $path = rtrim( substr( add_query_arg( array() ), strlen( $home_path ) ), '/' );
    return ( $path === '' ) ? '/' : $path;
  }

}

This is a rough class that let users set custom url settings via a filter (‘my_custom_urls’). Custom url settings must be an array where keys are relative urls and every value is an array containing two keyed values: one with key ‘callback’ and one with key ‘view’.

The callback is a callable (anything for which is_callable returns true) and the view is a file used to render the data returned by the callable and accessible in the view file in the $data variable.

Here an example how to use the class.

// first of all let's set custom url settings
add_filter( 'my_custom_urls', 'set_my_urls' );

function set_my_urls( $urls = array() ) {
  $my_urls = array(
     '/customcontent/alink' => array(
       'callback' => 'my_first_content_callback',
       'view'     => get_template_directory() . '/views/my_first_view.php'
     ),
     '/customcontent/anotherlink' => array(
       'callback' => 'my_second_content_callback',
       'view'     => get_template_directory() . '/views/my_second_view.php'
     )
  );
  return array_merge( (array) $urls, $my_urls ); 
}

// require the file that contain the MyCustomUrlParser class
require '/path/to/MyCustomUrlParser';

// attach MyCustomUrlParser::parse() method to 'do_parse_request' filter hook
add_filter( 'do_parse_request', array( new MyCustomUrlParser, 'parse' ) );

Of course, we need to write my_first_content_callback and my_second_content_callback and also my_first_view.php and my_second_view.php.

As example, the callback would be something like this:

function my_first_content_callback() {
  $content = get_transient( 'my_first_content' );
  if ( empty( $content ) ) {
     $api = a_method_to_get_an_external_api_object();
     $json = $api->fetch_some_json_data();
     $content = json_decode( $json );
     // cache content for 1 hour
     set_transient( 'my_first_content', $content, HOUR_IN_SECONDS );
  }
  return $content;
}

Note that whatever callback returns is stored in the $data variable accessible in the view. In facts, a view file would appear something like this:

<?php get_header(); ?>

<h1>My Content from API</h1>

<div>
   <pre><?php print_r( $data ); ?></pre>
</div>

<?php get_footer() ?>

This works and is pretty reusable, you can set all the custom urls you want by using the 'my_custom_urls' filter.

However, there is downside: all the matching against the current url is done via an exact match, but using a regex matching system it would be a lot better for large projects because you can use the urls to pass some variables to callbacks.

E.g. if you have an url settings like this:

'/customcontent/alink/page/{page}' => array(
   'callback' => 'my_first_content_callback',
   'view'     => get_template_directory() . '/views/my_first_view.php'
) 

using a regex system is possible to make the {page} part variable and the matched value can be passed to the callback to retrieve different data.

This is what is commonly called a routing system, and there are some useful PHP libraries like FastRoute, Pux and Symfony Routing Component that can help you to use the workflow I explained here and build your own regex-based routing system in WordPress.

If you have PHP 5.4+ (that is really recommended) there is a plugin I wrote called Cortex that implements Symfony Routing Component and make it usable inside WordPress both for standard WordPress queries (showing posts) or even for custom content like you need.


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

Leave a Comment