Skip to content

Instantly share code, notes, and snippets.

@samhernandez
Last active August 20, 2024 20:30
Show Gist options
  • Save samhernandez/8062848 to your computer and use it in GitHub Desktop.
Save samhernandez/8062848 to your computer and use it in GitHub Desktop.
A simple class to add custom routes to Wordpress.
<?php
/**
* NOTE: This gist is very old. You might want to check out recent forks
* like this one: https://github.com/Alexlytle/Wordpress_custom_route/blob/main/Wordpress_Custom_route.php
* (thanks @Alexlytle) If you have an improvement to this gist, please
* post a link in a comment for others who might benefit. Thanks!
*
* A class to create simple custom routes.
*
* Example usage:
*
* $theme_routes = new CustomRoutes();
* $theme_routes->addRoute(
*
* // required, regex to match the route
* '^api/([^/]*)/([^/]*)/?',
*
* // required, a callback function name or
* // callable array like `array($object, 'method')`
* 'api_callback',
*
* // optional template path or array of template path candidates
* get_template_directory() . '/api-template.php',
*
* // query vars based on regex matches
* // will be passed to the callback function in the same order
* array($param1 => 1, $param2 => 2),
* );
*
* function api_callback($param1, $param2) {
* // called before the optional template is invoked
* }
*
* Also:
*
* // Force flush rewrite rules.
* $theme_routes->forceFlush();
*
* @author Sam Hernandez ([email protected])
*/
class CustomRoutes {
/**
* An array of route data indexed by regex.
*
* Example value:
*
* $routes['^widgets/([^/]*)/([^/]*)/?'] = array(
* 'callback' => 'some_function', // or array($object, 'someMethod')
* 'template' => '/template/path/to/file.php',
* 'query_vars' => array('param1' => 1, 'param2' => 2)
* );
*
* @var array
*/
protected $routes = array();
/**
* Flag to force flushing existing rewrite rules.
* @var boolean
*/
protected $force_flush = false;
/**
* Constructor
*/
public function __construct()
{
add_action('parse_request', array($this, 'parseRequestAction'));
add_filter('query_vars', array($this, 'queryVarsFilter'));
add_filter('rewrite_rules_array', array($this, 'rewriteRulesArrayFilter'));
add_action('wp_loaded', array($this, 'wpLoadedAction'));
}
/*
*
* Public methods
*
*/
/**
* Force flush existing rewrite rules.
*/
public function forceFlush() {
$this->force_flush = true;
}
/**
* Action callback for 'parse_request'. Handles the execution
* of custom routes by invoking the optional callback and optional
* template file if they are given for the matched route.
*
* @param WP $query
*/
public function parseRequestAction($query)
{
if ($query->matched_rule and isset($this->routes[$query->matched_rule]))
{
$route = $this->routes[$query->matched_rule];
$this->doCallback($route, $query);
if ($route['template']) {
$html = $this->doTemplate($route);
}
exit;
}
}
/**
* Add a route with params.
*
* @param string $match A regular expression for the url match
* @param string $callback Callback; function name or callable array such as `array($object, 'method')`
* @param string $template Optional template path
* @param array $query_vars An array of url query vars indexed by the var name, value being the regex match number.
*/
public function addRoute($match, $callback, $template = null, $query_vars = array())
{
$this->routes[$match] = compact('callback', 'template', 'query_vars');
}
/*
*
* Action and filter callbacks
*
*/
/**
* Action callback for 'wp_loaded'.
* Flushes the current rewrite rules if a newly defined rule
* is missing or if the `$force_flush` flag is raised.
*/
public function wpLoadedAction()
{
$rules = get_option('rewrite_rules');
$missing_routes = false;
foreach ($this->routes as $key => $value) {
$missing_routes += !isset($rules[$key]);
}
if ($missing_routes || $this->force_flush) {
global $wp_rewrite;
$wp_rewrite->flush_rules();
}
}
/**
* Filter callback for 'rewrite_rules_array'.
* Adds new rules for newly defined routes.
*
* @param array $rules
* @return array
*/
public function rewriteRulesArrayFilter($rules)
{
$newrules = array();
foreach ($this->routes as $match => $route) {
$newrules[$match] = $this->makeRuleUrl($route);
}
return $newrules + $rules;
}
/**
* Filter callback for 'query_vars'.
* @param array $vars
* @return array
*/
public function queryVarsFilter($vars)
{
foreach($this->routes as $route)
{
foreach($route['query_vars'] as $key => $value) {
$vars[] = $key;
}
}
return $vars;
}
/*
*
* Protected methods
*
*/
/**
* Invoke the callback for a given route.
*
* @param array $route An item from $this->routes
* @param WP $query
*/
protected function doCallback($route, $query)
{
$params = array();
// params are in the same order as given in the array
foreach($route['query_vars'] as $name => $match) {
$params[] = $query->query_vars[$name];
}
call_user_func_array($route['callback'], $params);
}
/**
* Includes a template for a given route if one is found.
* @param array $route An item from $this->routes
*/
protected function doTemplate($route)
{
$candidates = (array) $route['template'];
foreach($candidates as $candidate)
{
if (file_exists($candidate))
{
include $candidate;
break;
}
}
}
/**
* Returns a url with query string key/value pairs as
* needed for rewrite rules.
*
* @param array $route An item from $this->routes
* @return string
*/
protected function makeRuleUrl($route)
{
$q_vars = array();
foreach($route['query_vars'] as $name => $match) {
$q_vars[] = $name . '=$matches[' . $match . ']';
}
return 'index.php?' . implode('&', $q_vars);
}
}
@ejntaylor
Copy link

Worked it out. Needed to set them in my api_callback()

       set_query_var('param1', $param1);

@PierreLebedel
Copy link

Hi! Thanks for this very nice class for custom GET routes, I used it many times!

To create API endpoints with the other REST methods, it's possible to combine the rest_url_prefix hook filter and the WP_REST_Server::register_route method.

First, customize the REST API prefix to match what you want, /api for example :

add_filter('rest_url_prefix', 'loomis_rest_url_prefix');
function loomis_rest_url_prefix( $slug ) { 
    return 'api'; 
}

And finally add your routes without namespace by passing them directly to the WP_REST_Server instance :

add_action('rest_api_init', function($server){
    $server->register_route('myendpoint', '/myendpoint', [
        'methods'  => 'POST',
        'callback' => 'my_callback_action',
    ]);
});

This will add /api/myendpoint POST route to native REST API.

@Alexlytle
Copy link

Thank you so much this will completely change my work flow

@Alexlytle
Copy link

How can I view the query_vars in the template:

My code:

      // new route
      $theme_routes = new CustomRoutes();
      $theme_routes->addRoute(
          "^parent/([^/]*)/?",
          [$this, 'api_callback'],
          get_stylesheet_directory() . '/parent-template.blade.php',
          array('param1' => 1, 'param2' => 2,)
      );

in my parent-template.blade.php I get null from

  global $wp_query;
  var_dump($wp_query->query_vars);

Bit stumped - any ideas? Thanks for useful class!

Worked it out. Needed to set them in my api_callback()

       set_query_var('param1', $param1);

Thank you for the api call back of setting a query he should of put this in the example comment

@Alexlytle
Copy link

They only problem with this is that it hides the admin on the routes being used

@samhernandez
Copy link
Author

@Alexlytle I am so sorry, I wish I could help, but I haven't done any WordPress work since I published this gist 8 years ago and I'm not familiar with it any more. If you have any improvements, I'm happy to publish them here or to link to a gist that you publish.

@Alexlytle
Copy link

@samhernandez samhernandez

Oh wow no problem. I didn't realize it was so long ago
Here is a link to my GitHub of a updated version of this class!
https://github.com/Alexlytle/Wordpress_custom_route/blob/main/Wordpress_Custom_route.php

@samhernandez
Copy link
Author

@Alexlytle thanks for the link! I updated the gist with a comment at the top.

If anyone else has an improvement please post a link to your work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment