You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
515 lines
13 KiB
515 lines
13 KiB
<?php |
|
/** |
|
* CodeIgniter |
|
* |
|
* An open source application development framework for PHP |
|
* |
|
* This content is released under the MIT License (MIT) |
|
* |
|
* Copyright (c) 2014 - 2016, British Columbia Institute of Technology |
|
* |
|
* Permission is hereby granted, free of charge, to any person obtaining a copy |
|
* of this software and associated documentation files (the "Software"), to deal |
|
* in the Software without restriction, including without limitation the rights |
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
* copies of the Software, and to permit persons to whom the Software is |
|
* furnished to do so, subject to the following conditions: |
|
* |
|
* The above copyright notice and this permission notice shall be included in |
|
* all copies or substantial portions of the Software. |
|
* |
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
|
* THE SOFTWARE. |
|
* |
|
* @package CodeIgniter |
|
* @author EllisLab Dev Team |
|
* @copyright Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/) |
|
* @copyright Copyright (c) 2014 - 2016, British Columbia Institute of Technology (http://bcit.ca/) |
|
* @license http://opensource.org/licenses/MIT MIT License |
|
* @link https://codeigniter.com |
|
* @since Version 1.0.0 |
|
* @filesource |
|
*/ |
|
defined('BASEPATH') OR exit('No direct script access allowed'); |
|
|
|
/** |
|
* Router Class |
|
* |
|
* Parses URIs and determines routing |
|
* |
|
* @package CodeIgniter |
|
* @subpackage Libraries |
|
* @category Libraries |
|
* @author EllisLab Dev Team |
|
* @link https://codeigniter.com/user_guide/general/routing.html |
|
*/ |
|
class CI_Router { |
|
|
|
/** |
|
* CI_Config class object |
|
* |
|
* @var object |
|
*/ |
|
public $config; |
|
|
|
/** |
|
* List of routes |
|
* |
|
* @var array |
|
*/ |
|
public $routes = array(); |
|
|
|
/** |
|
* Current class name |
|
* |
|
* @var string |
|
*/ |
|
public $class = ''; |
|
|
|
/** |
|
* Current method name |
|
* |
|
* @var string |
|
*/ |
|
public $method = 'index'; |
|
|
|
/** |
|
* Sub-directory that contains the requested controller class |
|
* |
|
* @var string |
|
*/ |
|
public $directory; |
|
|
|
/** |
|
* Default controller (and method if specific) |
|
* |
|
* @var string |
|
*/ |
|
public $default_controller; |
|
|
|
/** |
|
* Translate URI dashes |
|
* |
|
* Determines whether dashes in controller & method segments |
|
* should be automatically replaced by underscores. |
|
* |
|
* @var bool |
|
*/ |
|
public $translate_uri_dashes = FALSE; |
|
|
|
/** |
|
* Enable query strings flag |
|
* |
|
* Determines whether to use GET parameters or segment URIs |
|
* |
|
* @var bool |
|
*/ |
|
public $enable_query_strings = FALSE; |
|
|
|
// -------------------------------------------------------------------- |
|
|
|
/** |
|
* Class constructor |
|
* |
|
* Runs the route mapping function. |
|
* |
|
* @param array $routing |
|
* @return void |
|
*/ |
|
public function __construct($routing = NULL) |
|
{ |
|
$this->config =& load_class('Config', 'core'); |
|
$this->uri =& load_class('URI', 'core'); |
|
|
|
$this->enable_query_strings = ( ! is_cli() && $this->config->item('enable_query_strings') === TRUE); |
|
|
|
// If a directory override is configured, it has to be set before any dynamic routing logic |
|
is_array($routing) && isset($routing['directory']) && $this->set_directory($routing['directory']); |
|
$this->_set_routing(); |
|
|
|
// Set any routing overrides that may exist in the main index file |
|
if (is_array($routing)) |
|
{ |
|
empty($routing['controller']) OR $this->set_class($routing['controller']); |
|
empty($routing['function']) OR $this->set_method($routing['function']); |
|
} |
|
|
|
log_message('info', 'Router Class Initialized'); |
|
} |
|
|
|
// -------------------------------------------------------------------- |
|
|
|
/** |
|
* Set route mapping |
|
* |
|
* Determines what should be served based on the URI request, |
|
* as well as any "routes" that have been set in the routing config file. |
|
* |
|
* @return void |
|
*/ |
|
protected function _set_routing() |
|
{ |
|
// Load the routes.php file. It would be great if we could |
|
// skip this for enable_query_strings = TRUE, but then |
|
// default_controller would be empty ... |
|
if (file_exists(APPPATH.'config/routes.php')) |
|
{ |
|
include(APPPATH.'config/routes.php'); |
|
} |
|
|
|
if (file_exists(APPPATH.'config/'.ENVIRONMENT.'/routes.php')) |
|
{ |
|
include(APPPATH.'config/'.ENVIRONMENT.'/routes.php'); |
|
} |
|
|
|
// Validate & get reserved routes |
|
if (isset($route) && is_array($route)) |
|
{ |
|
isset($route['default_controller']) && $this->default_controller = $route['default_controller']; |
|
isset($route['translate_uri_dashes']) && $this->translate_uri_dashes = $route['translate_uri_dashes']; |
|
unset($route['default_controller'], $route['translate_uri_dashes']); |
|
$this->routes = $route; |
|
} |
|
|
|
// Are query strings enabled in the config file? Normally CI doesn't utilize query strings |
|
// since URI segments are more search-engine friendly, but they can optionally be used. |
|
// If this feature is enabled, we will gather the directory/class/method a little differently |
|
if ($this->enable_query_strings) |
|
{ |
|
// If the directory is set at this time, it means an override exists, so skip the checks |
|
if ( ! isset($this->directory)) |
|
{ |
|
$_d = $this->config->item('directory_trigger'); |
|
$_d = isset($_GET[$_d]) ? trim($_GET[$_d], " \t\n\r\0\x0B/") : ''; |
|
|
|
if ($_d !== '') |
|
{ |
|
$this->uri->filter_uri($_d); |
|
$this->set_directory($_d); |
|
} |
|
} |
|
|
|
$_c = trim($this->config->item('controller_trigger')); |
|
if ( ! empty($_GET[$_c])) |
|
{ |
|
$this->uri->filter_uri($_GET[$_c]); |
|
$this->set_class($_GET[$_c]); |
|
|
|
$_f = trim($this->config->item('function_trigger')); |
|
if ( ! empty($_GET[$_f])) |
|
{ |
|
$this->uri->filter_uri($_GET[$_f]); |
|
$this->set_method($_GET[$_f]); |
|
} |
|
|
|
$this->uri->rsegments = array( |
|
1 => $this->class, |
|
2 => $this->method |
|
); |
|
} |
|
else |
|
{ |
|
$this->_set_default_controller(); |
|
} |
|
|
|
// Routing rules don't apply to query strings and we don't need to detect |
|
// directories, so we're done here |
|
return; |
|
} |
|
|
|
// Is there anything to parse? |
|
if ($this->uri->uri_string !== '') |
|
{ |
|
$this->_parse_routes(); |
|
} |
|
else |
|
{ |
|
$this->_set_default_controller(); |
|
} |
|
} |
|
|
|
// -------------------------------------------------------------------- |
|
|
|
/** |
|
* Set request route |
|
* |
|
* Takes an array of URI segments as input and sets the class/method |
|
* to be called. |
|
* |
|
* @used-by CI_Router::_parse_routes() |
|
* @param array $segments URI segments |
|
* @return void |
|
*/ |
|
protected function _set_request($segments = array()) |
|
{ |
|
$segments = $this->_validate_request($segments); |
|
// If we don't have any segments left - try the default controller; |
|
// WARNING: Directories get shifted out of the segments array! |
|
if (empty($segments)) |
|
{ |
|
$this->_set_default_controller(); |
|
return; |
|
} |
|
|
|
if ($this->translate_uri_dashes === TRUE) |
|
{ |
|
$segments[0] = str_replace('-', '_', $segments[0]); |
|
if (isset($segments[1])) |
|
{ |
|
$segments[1] = str_replace('-', '_', $segments[1]); |
|
} |
|
} |
|
|
|
$this->set_class($segments[0]); |
|
if (isset($segments[1])) |
|
{ |
|
$this->set_method($segments[1]); |
|
} |
|
else |
|
{ |
|
$segments[1] = 'index'; |
|
} |
|
|
|
array_unshift($segments, NULL); |
|
unset($segments[0]); |
|
$this->uri->rsegments = $segments; |
|
} |
|
|
|
// -------------------------------------------------------------------- |
|
|
|
/** |
|
* Set default controller |
|
* |
|
* @return void |
|
*/ |
|
protected function _set_default_controller() |
|
{ |
|
if (empty($this->default_controller)) |
|
{ |
|
show_error('Unable to determine what should be displayed. A default route has not been specified in the routing file.'); |
|
} |
|
|
|
// Is the method being specified? |
|
if (sscanf($this->default_controller, '%[^/]/%s', $class, $method) !== 2) |
|
{ |
|
$method = 'index'; |
|
} |
|
|
|
if ( ! file_exists(APPPATH.'controllers/'.$this->directory.ucfirst($class).'.php')) |
|
{ |
|
// This will trigger 404 later |
|
return; |
|
} |
|
|
|
$this->set_class($class); |
|
$this->set_method($method); |
|
|
|
// Assign routed segments, index starting from 1 |
|
$this->uri->rsegments = array( |
|
1 => $class, |
|
2 => $method |
|
); |
|
|
|
log_message('debug', 'No URI present. Default controller set.'); |
|
} |
|
|
|
// -------------------------------------------------------------------- |
|
|
|
/** |
|
* Validate request |
|
* |
|
* Attempts validate the URI request and determine the controller path. |
|
* |
|
* @used-by CI_Router::_set_request() |
|
* @param array $segments URI segments |
|
* @return mixed URI segments |
|
*/ |
|
protected function _validate_request($segments) |
|
{ |
|
$c = count($segments); |
|
$directory_override = isset($this->directory); |
|
|
|
// Loop through our segments and return as soon as a controller |
|
// is found or when such a directory doesn't exist |
|
while ($c-- > 0) |
|
{ |
|
$test = $this->directory |
|
.ucfirst($this->translate_uri_dashes === TRUE ? str_replace('-', '_', $segments[0]) : $segments[0]); |
|
|
|
if ( ! file_exists(APPPATH.'controllers/'.$test.'.php') |
|
&& $directory_override === FALSE |
|
&& is_dir(APPPATH.'controllers/'.$this->directory.$segments[0]) |
|
) |
|
{ |
|
$this->set_directory(array_shift($segments), TRUE); |
|
continue; |
|
} |
|
|
|
return $segments; |
|
} |
|
|
|
// This means that all segments were actually directories |
|
return $segments; |
|
} |
|
|
|
// -------------------------------------------------------------------- |
|
|
|
/** |
|
* Parse Routes |
|
* |
|
* Matches any routes that may exist in the config/routes.php file |
|
* against the URI to determine if the class/method need to be remapped. |
|
* |
|
* @return void |
|
*/ |
|
protected function _parse_routes() |
|
{ |
|
// Turn the segment array into a URI string |
|
$uri = implode('/', $this->uri->segments); |
|
|
|
// Get HTTP verb |
|
$http_verb = isset($_SERVER['REQUEST_METHOD']) ? strtolower($_SERVER['REQUEST_METHOD']) : 'cli'; |
|
|
|
// Loop through the route array looking for wildcards |
|
foreach ($this->routes as $key => $val) |
|
{ |
|
// Check if route format is using HTTP verbs |
|
if (is_array($val)) |
|
{ |
|
$val = array_change_key_case($val, CASE_LOWER); |
|
if (isset($val[$http_verb])) |
|
{ |
|
$val = $val[$http_verb]; |
|
} |
|
else |
|
{ |
|
continue; |
|
} |
|
} |
|
|
|
// Convert wildcards to RegEx |
|
$key = str_replace(array(':any', ':num'), array('[^/]+', '[0-9]+'), $key); |
|
|
|
// Does the RegEx match? |
|
if (preg_match('#^'.$key.'$#', $uri, $matches)) |
|
{ |
|
// Are we using callbacks to process back-references? |
|
if ( ! is_string($val) && is_callable($val)) |
|
{ |
|
// Remove the original string from the matches array. |
|
array_shift($matches); |
|
|
|
// Execute the callback using the values in matches as its parameters. |
|
$val = call_user_func_array($val, $matches); |
|
} |
|
// Are we using the default routing method for back-references? |
|
elseif (strpos($val, '$') !== FALSE && strpos($key, '(') !== FALSE) |
|
{ |
|
$val = preg_replace('#^'.$key.'$#', $val, $uri); |
|
} |
|
|
|
$this->_set_request(explode('/', $val)); |
|
return; |
|
} |
|
} |
|
|
|
// If we got this far it means we didn't encounter a |
|
// matching route so we'll set the site default route |
|
$this->_set_request(array_values($this->uri->segments)); |
|
} |
|
|
|
// -------------------------------------------------------------------- |
|
|
|
/** |
|
* Set class name |
|
* |
|
* @param string $class Class name |
|
* @return void |
|
*/ |
|
public function set_class($class) |
|
{ |
|
$this->class = str_replace(array('/', '.'), '', $class); |
|
} |
|
|
|
// -------------------------------------------------------------------- |
|
|
|
/** |
|
* Fetch the current class |
|
* |
|
* @deprecated 3.0.0 Read the 'class' property instead |
|
* @return string |
|
*/ |
|
public function fetch_class() |
|
{ |
|
return $this->class; |
|
} |
|
|
|
// -------------------------------------------------------------------- |
|
|
|
/** |
|
* Set method name |
|
* |
|
* @param string $method Method name |
|
* @return void |
|
*/ |
|
public function set_method($method) |
|
{ |
|
$this->method = $method; |
|
} |
|
|
|
// -------------------------------------------------------------------- |
|
|
|
/** |
|
* Fetch the current method |
|
* |
|
* @deprecated 3.0.0 Read the 'method' property instead |
|
* @return string |
|
*/ |
|
public function fetch_method() |
|
{ |
|
return $this->method; |
|
} |
|
|
|
// -------------------------------------------------------------------- |
|
|
|
/** |
|
* Set directory name |
|
* |
|
* @param string $dir Directory name |
|
* @param bool $append Whether we're appending rather than setting the full value |
|
* @return void |
|
*/ |
|
public function set_directory($dir, $append = FALSE) |
|
{ |
|
if ($append !== TRUE OR empty($this->directory)) |
|
{ |
|
$this->directory = str_replace('.', '', trim($dir, '/')).'/'; |
|
} |
|
else |
|
{ |
|
$this->directory .= str_replace('.', '', trim($dir, '/')).'/'; |
|
} |
|
} |
|
|
|
// -------------------------------------------------------------------- |
|
|
|
/** |
|
* Fetch directory |
|
* |
|
* Feches the sub-directory (if any) that contains the requested |
|
* controller class. |
|
* |
|
* @deprecated 3.0.0 Read the 'directory' property instead |
|
* @return string |
|
*/ |
|
public function fetch_directory() |
|
{ |
|
return $this->directory; |
|
} |
|
|
|
}
|
|
|