Yes, it is possible.
WordPress frontend workflow can be summarized like so:
- A url is visited
- 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 theparse_request
method of an instance of theWP
class stored in the global$wp
variable - 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” - 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 ), "https://wordpress.stackexchange.com/" );
$path = rtrim( substr( add_query_arg( array() ), strlen( $home_path ) ), "https://wordpress.stackexchange.com/" );
return ( $path === '' ) ? "https://wordpress.stackexchange.com/" : $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.