SproutCMS

This is the code documentation for the SproutCMS project

source of /sprout/Helpers/Router.php

Copyright (C) 2017 Karmabunny Pty Ltd.

This file is a part of SproutCMS.

SproutCMS is free software: you can redistribute it and/or modify it under the terms
of the GNU General Public License as published by the Free Software Foundation, either
version 2 of the License, or (at your option) any later version.

For more information, visit <http://getsproutcms.com>.

This class was originally from Kohana 2.3.4
Copyright 2007-2008 Kohana Team
  1. <?php
  2. /**
  3.  * Copyright (C) 2017 Karmabunny Pty Ltd.
  4.  *
  5.  * This file is a part of SproutCMS.
  6.  *
  7.  * SproutCMS is free software: you can redistribute it and/or modify it under the terms
  8.  * of the GNU General Public License as published by the Free Software Foundation, either
  9.  * version 2 of the License, or (at your option) any later version.
  10.  *
  11.  * For more information, visit <http://getsproutcms.com>.
  12.  *
  13.  * This class was originally from Kohana 2.3.4
  14.  * Copyright 2007-2008 Kohana Team
  15.  */
  16. namespace Sprout\Helpers;
  17.  
  18. use BootstrapConfig;
  19. use Exception;
  20.  
  21. use Kohana;
  22. use Kohana_Exception;
  23.  
  24. use karmabunny\router\Router as KbRouter;
  25.  
  26. /**
  27.  * Router
  28.  */
  29. class Router
  30. {
  31. /** The original URI requested e.g. by a HTTP user agent or via CLI */
  32. public static $current_uri = '';
  33.  
  34. /** Original query string requested by HTTP user agent */
  35. public static $query_string = '';
  36.  
  37. /** Original URI and query string combined. */
  38. public static $complete_uri = '';
  39.  
  40. /** Controller/method URI to use, from the configured routes */
  41. public static $routed_uri = '';
  42.  
  43. /** Optional fake file extension that will be added to all generated URLs, e.g. '.html' */
  44. public static $url_suffix = '';
  45.  
  46. /** Controller to use */
  47. public static $controller;
  48.  
  49. /** Method to call on controller */
  50. public static $method = 'index';
  51.  
  52. /** Arguments to pass to controller method */
  53. public static $arguments = [];
  54.  
  55. /** @var KbRouter */
  56. protected static $router;
  57.  
  58. public static $MODE = KbRouter::MODE_REGEX;
  59.  
  60.  
  61. /**
  62.   * Router setup routine; determines controller/method from URI.
  63.   * Automatically called during Kohana setup process.
  64.   *
  65.   * @return void
  66.   */
  67. public static function setup()
  68. {
  69. // Load configured routes
  70. $routes = Kohana::config('routes');
  71.  
  72. // Use the default route when no segments exist
  73. $uri = self::$current_uri;
  74. if ($uri === '') {
  75. if (!isset($routes['_default'])) {
  76. throw new Kohana_Exception('core.no_default_route');
  77. }
  78.  
  79. $uri = '_default';
  80. }
  81.  
  82. self::$router = KbRouter::create([
  83. 'extract' => KbRouter::EXTRACT_ATTRIBUTES | KbRouter::EXTRACT_CONVERT_REGEX,
  84. 'mode' => KbRouter::MODE_REGEX,
  85. ]);
  86. self::$router->load($routes);
  87.  
  88. // Find matching configured route
  89. $routed_uri = Router::routedUri(Router::$current_uri);
  90. if ($routed_uri === false) return;
  91.  
  92. // The routed URI is now complete
  93. Router::$routed_uri = $routed_uri;
  94.  
  95. // Find the controller from the registered route. If no namespace specified, assume Sprout\Controllers\...
  96. $segments = explode('/', trim(Router::$routed_uri, '/'));
  97. $controller = array_shift($segments);
  98. if (strpos($controller, '\\') === false) $controller = 'Sprout\\Controllers\\' . $controller;
  99. if (class_exists($controller)) {
  100. Router::$controller = $controller;
  101. if (count($segments) > 0) {
  102. Router::$method = array_shift($segments);
  103. Router::$arguments = $segments;
  104. } else {
  105. Router::$arguments = [];
  106. }
  107. }
  108. }
  109.  
  110.  
  111. /**
  112.   * Get the routes tables.
  113.   *
  114.   * @return array
  115.   */
  116. public static function getRoutes()
  117. {
  118. return self::$router->routes;
  119. }
  120.  
  121.  
  122. /**
  123.   * Attempts to determine the current URI using CLI, GET, PATH_INFO, ORIG_PATH_INFO, or PHP_SELF.
  124.   *
  125.   * @return void
  126.   */
  127. public static function findUri()
  128. {
  129. if (isset($_GET['_apache_error']))
  130. {
  131. $e = array(400 => '400 Bad Request', 401 => '401 Unauthorized', 403 => '403 Forbidden', 500 => '500 Internal Server Error');
  132. $e = $e[$_GET['_apache_error']];
  133.  
  134. if (!$e) {
  135. if ($_GET['_apache_error'][0] == '4') {
  136. $e = '403 Forbidden';
  137. } else {
  138. $e = '500 Internal Server Error';
  139. }
  140. }
  141.  
  142. throw new Exception($e);
  143. }
  144.  
  145. if (PHP_SAPI === 'cli')
  146. {
  147. // Command line requires a bit of hacking
  148. if (isset($_SERVER['argv'][1]))
  149. {
  150. Router::$current_uri = $_SERVER['argv'][1];
  151.  
  152. // Remove GET string from segments
  153. if (($query = strpos(Router::$current_uri, '?')) !== FALSE)
  154. {
  155. list (Router::$current_uri, $query) = explode('?', Router::$current_uri, 2);
  156.  
  157. // Parse the query string into $_GET
  158. parse_str($query, $_GET);
  159.  
  160. // Convert $_GET to UTF-8
  161. $_GET = utf8::clean($_GET);
  162. }
  163. }
  164. }
  165. elseif (isset($_GET['kohana_uri']))
  166. {
  167. // Use the URI defined in the query string
  168. Router::$current_uri = $_GET['kohana_uri'];
  169.  
  170. // Remove the URI from $_GET
  171. unset($_GET['kohana_uri']);
  172.  
  173. // Remove the URI from $_SERVER['QUERY_STRING']
  174. $_SERVER['QUERY_STRING'] = preg_replace('~\bkohana_uri\b[^&]*+&?~', '', $_SERVER['QUERY_STRING']);
  175. }
  176. elseif (isset($_SERVER['PATH_INFO']) AND $_SERVER['PATH_INFO'])
  177. {
  178. Router::$current_uri = $_SERVER['PATH_INFO'];
  179. }
  180. elseif (isset($_SERVER['ORIG_PATH_INFO']) AND $_SERVER['ORIG_PATH_INFO'])
  181. {
  182. Router::$current_uri = $_SERVER['ORIG_PATH_INFO'];
  183. }
  184. elseif (isset($_SERVER['PHP_SELF']) AND $_SERVER['PHP_SELF'])
  185. {
  186. Router::$current_uri = $_SERVER['PHP_SELF'];
  187. }
  188.  
  189. if (($strpos_fc = strpos(Router::$current_uri, KOHANA)) !== FALSE)
  190. {
  191. // Remove the front controller from the current uri
  192. Router::$current_uri = (string) substr(Router::$current_uri, $strpos_fc + strlen(KOHANA));
  193. }
  194.  
  195. // Remove slashes from the start and end of the URI
  196. Router::$current_uri = trim(Router::$current_uri, '/');
  197.  
  198. if (Router::$current_uri !== '')
  199. {
  200. if ($suffix = Kohana::config('core.url_suffix') AND strpos(Router::$current_uri, $suffix) !== FALSE)
  201. {
  202. // Remove the URL suffix
  203. Router::$current_uri = preg_replace('#'.preg_quote($suffix).'$#u', '', Router::$current_uri);
  204.  
  205. // Set the URL suffix
  206. Router::$url_suffix = $suffix;
  207. }
  208.  
  209. // Reduce multiple slashes into single slashes
  210. Router::$current_uri = preg_replace('#//+#', '/', Router::$current_uri);
  211. }
  212.  
  213. // Set the query string to the current query string
  214. if (!empty($_SERVER['QUERY_STRING'])) {
  215. Router::$query_string = '?'.trim($_SERVER['QUERY_STRING'], '&/');
  216. }
  217.  
  218. // Remove all dot-paths from the URI, they are not valid
  219. Router::$current_uri = preg_replace('#\.[\s./]*/#', '', Router::$current_uri);
  220. Router::$current_uri = trim(Router::$current_uri, '/');
  221.  
  222. // Remember the complete URI for some reason
  223. Router::$complete_uri = Router::$current_uri . Router::$query_string;
  224. }
  225.  
  226. /**
  227.   * Redirect to alternate hostname and/or protocol if requred
  228.   *
  229.   * The actual business rules for the desired protocol/hostname is defined in
  230.   * the {@see BootstrapConfig} class which is located at config/_bootstrap_config.php
  231.   *
  232.   * @return void Redirects (301) if protocol and/or hostname should change
  233.   */
  234. public static function originCleanup()
  235. {
  236. if (PHP_SAPI === 'cli') return;
  237.  
  238. $old_proto = Request::protocol();
  239. $old_hostname = $_SERVER['HTTP_HOST'];
  240.  
  241. list($new_proto, $new_hostname) = BootstrapConfig::originCleanup($old_proto, $old_hostname);
  242.  
  243. if (BootstrapConfig::ORIGIN_CLEANUP_DEBUG) {
  244. self::originCleanupDebug($old_proto, $old_hostname, $new_proto, $new_hostname);
  245. }
  246.  
  247. if ($new_proto !== $old_proto or $new_hostname !== $old_hostname) {
  248. $url = $new_proto . '://' . $new_hostname . '/' . Router::$complete_uri;
  249. Url::redirect($url, '301');
  250. }
  251. }
  252.  
  253. /**
  254.   * Output information about origin cleanup, and then exit
  255.   * This is turned on by the BootstrapConfig::ORIGIN_CLEANUP_DEBUG constant
  256.   *
  257.   * @param string $old_proto
  258.   * @param string $old_hostname
  259.   * @param string $new_proto
  260.   * @param string $new_hostname
  261.   * @return void Terminates script execution
  262.   */
  263. private static function originCleanupDebug($old_proto, $old_hostname, $new_proto, $new_hostname)
  264. {
  265. header('Content-type: text/plain');
  266.  
  267. echo "Old proto: {$old_proto}\n";
  268. echo "New proto: {$new_proto}\n";
  269. echo "Old hostname: {$old_hostname}\n";
  270. echo "New hostname: {$new_hostname}\n\n";
  271.  
  272. if ($new_proto !== $old_proto or $new_hostname !== $old_hostname) {
  273. $url = $new_proto . '://' . $new_hostname . '/' . Router::$complete_uri;
  274. echo "Redirect:\n{$url}";
  275. } else {
  276. echo "No redirect";
  277. }
  278.  
  279. exit(0);
  280. }
  281.  
  282. /**
  283.   * Generates routed URI (i.e. controller/method/arg1/arg2/...) from given URI.
  284.   *
  285.   * @param string URI to convert, e.g. 'admin/edit/page/3'
  286.   * @return string|bool Routed URI or false, e.g. 'AdminController/edit/page/3'
  287.   * @throws Exception if no routes configured
  288.   */
  289. public static function routedUri($uri)
  290. {
  291. if (Router::$router === NULL or empty(Router::$router->routes)) {
  292. throw new Exception('No routes loaded');
  293. }
  294.  
  295. $routed_uri = $uri = trim($uri, '/');
  296.  
  297. $method = Request::method();
  298. $action = self::$router->find($method, $uri);
  299. if (!$action) return false;
  300.  
  301. $target = $action->target;
  302.  
  303. // Convert class::method into sprout style segments.
  304. if (is_array($target)) {
  305. [$class, $method] = $target;
  306. $target = "{$class}/{$method}";
  307.  
  308. foreach ($action->args as $arg) {
  309. $target .= '/' . $arg;
  310. }
  311. }
  312.  
  313. // Ok now splice the rule args into the target.
  314. // So my/rule/{arg1}/path/{arg2} => 'ns\\to\\class/method/{arg1}/{arg2}'
  315. $routed_uri = preg_replace('#^' . $action->rule . '$#u', $target, $uri);
  316. return trim($routed_uri, '/');
  317. }
  318.  
  319. } // End Router
  320.