blob: 6781454580819565eba7acdda8f0940d26036695 [file] [log] [blame]
adminb0dd10f2006-08-25 17:25:49 +00001<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
2/**
3 * Code Igniter
4 *
5 * An open source application development framework for PHP 4.3.2 or newer
6 *
7 * @package CodeIgniter
8 * @author Rick Ellis
9 * @copyright Copyright (c) 2006, pMachine, Inc.
10 * @license http://www.codeignitor.com/user_guide/license.html
11 * @link http://www.codeigniter.com
12 * @since Version 1.0
13 * @filesource
14 */
15
16// ------------------------------------------------------------------------
17
18/**
19 * Router Class
20 *
21 * Parses URIs and determines routing
22 *
23 * @package CodeIgniter
24 * @subpackage Libraries
25 * @author Rick Ellis
26 * @category Libraries
27 * @link http://www.codeigniter.com/user_guide/general/routing.html
28 */
29class CI_Router {
30
31 var $config;
32 var $uri_string = '';
33 var $segments = array();
34 var $routes = array();
35 var $class = '';
36 var $method = 'index';
admin45c872b2006-08-26 04:51:38 +000037 var $directory = '';
adminb0dd10f2006-08-25 17:25:49 +000038 var $uri_protocol = 'auto';
39 var $default_controller;
40 var $scaffolding_request = FALSE; // Must be set to FALSE
41
42 /**
43 * Constructor
44 *
45 * Runs the route mapping function.
46 */
47 function CI_Router()
48 {
49 $this->config =& _load_class('CI_Config');
50 $this->_set_route_mapping();
51 log_message('debug', "Router Class Initialized");
52 }
53
54 // --------------------------------------------------------------------
55
56 /**
57 * Set the route mapping
58 *
59 * This function determies what should be served based on the URI request,
60 * as well as any "routes" that have been set in the routing config file.
61 *
62 * @access private
63 * @return void
64 */
65 function _set_route_mapping()
66 {
67 // Are query strings enabled? If so we're done...
68 if ($this->config->item('enable_query_strings') === TRUE AND isset($_GET[$this->config->item('controller_trigger')]))
69 {
70 $this->set_class($_GET[$this->config->item('controller_trigger')]);
71
72 if (isset($_GET[$this->config->item('function_trigger')]))
73 {
74 $this->set_method($_GET[$this->config->item('function_trigger')]);
75 }
76
77 return;
78 }
79
80 // Load the routes.php file
81 include_once(APPPATH.'config/routes'.EXT);
82 $this->routes = ( ! isset($route) OR ! is_array($route)) ? array() : $route;
83 unset($route);
84
85 // Set the default controller
86 $this->default_controller = ( ! isset($this->routes['default_controller']) OR $this->routes['default_controller'] == '') ? FALSE : strtolower($this->routes['default_controller']);
87
88 // Fetch the URI string Depending on the server,
89 // the URI will be available in one of two globals
90 switch ($this->config->item('uri_protocol'))
91 {
92 case 'path_info' : $this->uri_string = (isset($_SERVER['PATH_INFO'])) ? $_SERVER['PATH_INFO'] : @getenv('PATH_INFO');
93 break;
94 case 'query_string' : $this->uri_string = (isset($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : @getenv('QUERY_STRING');
95 break;
96 default :
97 $path_info = (isset($_SERVER['PATH_INFO'])) ? $_SERVER['PATH_INFO'] : @getenv('PATH_INFO');
98
99 if ($path_info != '' AND $path_info != "/".SELF)
100 {
101 $this->uri_string = $path_info;
102 }
103 else
104 {
105 $this->uri_string = (isset($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : @getenv('QUERY_STRING');
106 }
107 break;
108 }
109
110 // Is there a URI string? If not, the default controller specified
111 // by the admin in the "routes" file will be shown.
112 if ($this->uri_string == '')
113 {
114 if ($this->default_controller === FALSE)
115 {
116 show_error("Unable to determine what should be displayed. A default route has not been specified in the routing file.");
117 }
118
119 $this->set_class($this->default_controller);
120 $this->set_method('index');
121
122 log_message('debug', "No URI present. Default controller set.");
123 return;
124 }
admin45c872b2006-08-26 04:51:38 +0000125 unset($this->routes['default_controller']);
adminb0dd10f2006-08-25 17:25:49 +0000126
127 // Do we need to remove the suffix specified in the config file?
128 if ($this->config->item('url_suffix') != "")
129 {
130 $this->uri_string = preg_replace("|".preg_quote($this->config->item('url_suffix'))."$|", "", $this->uri_string);
131 }
132
133 // Explode the URI Segments. The individual segments will
admin45c872b2006-08-26 04:51:38 +0000134 // be stored in the $this->segments array.
admin45c872b2006-08-26 04:51:38 +0000135 foreach(explode("/", preg_replace("|/*(.+?)/*$|", "\\1", $this->uri_string)) as $val)
136 {
137 // Filter segments for security
138 $val = trim($this->_filter_uri($val));
139
140 if ($val != '')
admine07fbb32006-08-26 17:11:01 +0000141 $this->segments[] = $val;
admin45c872b2006-08-26 04:51:38 +0000142 }
adminb0dd10f2006-08-25 17:25:49 +0000143
admine07fbb32006-08-26 17:11:01 +0000144 // Parse any custom routing that may exist
145 $this->_parse_routes();
admin45c872b2006-08-26 04:51:38 +0000146
admine07fbb32006-08-26 17:11:01 +0000147 // Re-index the segment array so that it starts with 1 rather than 0
148 $i = 1;
149 foreach ($this->segments as $val)
adminb0dd10f2006-08-25 17:25:49 +0000150 {
admine07fbb32006-08-26 17:11:01 +0000151 $this->segments[$i++] = $val;
adminb0dd10f2006-08-25 17:25:49 +0000152 }
admine07fbb32006-08-26 17:11:01 +0000153 unset($this->segments['0']);
adminb0dd10f2006-08-25 17:25:49 +0000154 }
155 // END _set_route_mapping()
156
157 // --------------------------------------------------------------------
158
159 /**
160 * Compile Segments
161 *
162 * This function takes an array of URI segments as
163 * input, and puts it into the $this->segments array.
164 * It also sets the current class/method
165 *
166 * @access private
167 * @param array
168 * @param bool
169 * @return void
170 */
admin45c872b2006-08-26 04:51:38 +0000171 function _compile_segments($segments = array())
172 {
173 $segments = $this->_validate_segments($segments);
adminb0dd10f2006-08-25 17:25:49 +0000174
admin45c872b2006-08-26 04:51:38 +0000175 if (count($segments) == 0)
176 {
177 return;
178 }
179
admine07fbb32006-08-26 17:11:01 +0000180 $this->set_class($segments['0']);
adminb0dd10f2006-08-25 17:25:49 +0000181
admine07fbb32006-08-26 17:11:01 +0000182 if (isset($segments['1']))
adminb0dd10f2006-08-25 17:25:49 +0000183 {
184 // A scaffolding request. No funny business with the URL
admine07fbb32006-08-26 17:11:01 +0000185 if ($this->routes['scaffolding_trigger'] == $segments['1'] AND $segments['1'] != '_ci_scaffolding')
adminb0dd10f2006-08-25 17:25:49 +0000186 {
187 $this->scaffolding_request = TRUE;
188 unset($this->routes['scaffolding_trigger']);
189 }
190 else
191 {
192 // A standard method request
admine07fbb32006-08-26 17:11:01 +0000193 $this->set_method($segments['1']);
adminb0dd10f2006-08-25 17:25:49 +0000194 }
195 }
adminb0dd10f2006-08-25 17:25:49 +0000196 }
197 // END _compile_segments()
198
199 // --------------------------------------------------------------------
200
201 /**
admin45c872b2006-08-26 04:51:38 +0000202 * Validates the supplied segments. Attempts to determine the path to
203 * the controller.
204 *
205 * @access private
206 * @param array
207 * @return array
208 */
209 function _validate_segments($segments)
210 {
admine07fbb32006-08-26 17:11:01 +0000211 // Does the requested controller exist in the root folder?
212 if (file_exists(APPPATH.'controllers/'.$segments['0'].EXT))
admin45c872b2006-08-26 04:51:38 +0000213 {
admine07fbb32006-08-26 17:11:01 +0000214 return $segments;
admin45c872b2006-08-26 04:51:38 +0000215 }
216
admine07fbb32006-08-26 17:11:01 +0000217 // Is the controller in a sub-folder?
218 if (is_dir(APPPATH.'controllers/'.$segments['0']))
219 {
220 // Set the directory and remove it from the segment array
221 $this->set_directory($segments['0']);
222 $segments = array_slice($segments, 1);
223
224 if (count($segments) == 0)
225 {
226 $this->set_class($this->default_controller);
227 $this->set_method('index');
228 $this->directory = '';
229 return array();
230 }
231
232 return $segments;
233 }
234
235 // Can't find the requested controller...
236 show_404();
admin45c872b2006-08-26 04:51:38 +0000237 }
238 // END _validate_segments()
239
240 // --------------------------------------------------------------------
241
242 /**
adminb0dd10f2006-08-25 17:25:49 +0000243 * Filter segments for malicious characters
244 *
245 * @access private
246 * @param string
247 * @return string
248 */
249 function _filter_uri($str)
250 {
251 if ( ! preg_match("/^[a-z0-9~\s\%\.:_-]+$/i", $str))
252 {
253 exit('The URI you submitted has disallowed characters: '.$str);
254 }
255
256 return $str;
257 }
258 // END _filter_uri()
259
260 // --------------------------------------------------------------------
261
262 /**
adminb0dd10f2006-08-25 17:25:49 +0000263 * Parse Routes
264 *
265 * This function matches any routes that may exist in
266 * the config/routes.php file against the URI to
267 * determine if the class/method need to be remapped.
268 *
269 * @access private
270 * @return void
271 */
272 function _parse_routes()
273 {
admine07fbb32006-08-26 17:11:01 +0000274 // Do we even have any custom routing to deal with?
275 if (count($this->routes) == 0)
276 {
277 $this->_compile_segments($this->segments);
278 return;
279 }
280
adminb0dd10f2006-08-25 17:25:49 +0000281 // Turn the segment array into a URI string
282 $uri = implode('/', $this->segments);
283 $num = count($this->segments);
284
285 // Is there a literal match? If so we're done
286 if (isset($this->routes[$uri]))
287 {
admin45c872b2006-08-26 04:51:38 +0000288 $this->_compile_segments(explode('/', $this->routes[$uri]));
adminb0dd10f2006-08-25 17:25:49 +0000289 return;
290 }
admine07fbb32006-08-26 17:11:01 +0000291
adminb0dd10f2006-08-25 17:25:49 +0000292 // Loop through the route array looking for wildcards
admind4e95072006-08-26 01:15:06 +0000293 foreach (array_slice($this->routes, 1) as $key => $val)
adminb0dd10f2006-08-25 17:25:49 +0000294 {
295 if (count(explode('/', $key)) != $num)
296 continue;
admind4e95072006-08-26 01:15:06 +0000297
298 // Convert wildcards to RegEx
299 $key = str_replace(':any', '.+', str_replace(':num', '[0-9]+', $key));
300
admin45c872b2006-08-26 04:51:38 +0000301 // Does the RegEx match?
admind4e95072006-08-26 01:15:06 +0000302 if (preg_match('|^'.$key.'$|', $uri))
303 {
admin45c872b2006-08-26 04:51:38 +0000304 // Do we have a back-reference?
admind4e95072006-08-26 01:15:06 +0000305 if (strpos($val, '$') !== FALSE AND strpos($key, '(') !== FALSE)
306 {
307 $val = preg_replace('|^'.$key.'$|', $val, $uri);
308 }
309
admin45c872b2006-08-26 04:51:38 +0000310 $this->_compile_segments(explode('/', $val));
311 return;
adminb0dd10f2006-08-25 17:25:49 +0000312 }
admine07fbb32006-08-26 17:11:01 +0000313 }
314
315 // If we got this far it means we didn't encounter a
316 // matching route so we'll set the site default route
317 $this->_compile_segments($this->segments);
adminb0dd10f2006-08-25 17:25:49 +0000318 }
319 // END set_method()
admin45c872b2006-08-26 04:51:38 +0000320
321 // --------------------------------------------------------------------
322
323 /**
324 * Set the class name
325 *
326 * @access public
327 * @param string
328 * @return void
329 */
330 function set_class($class)
331 {
332 $this->class = $class;
333 }
334 // END set_class()
335
336 // --------------------------------------------------------------------
337
338 /**
339 * Fetch the current class
340 *
341 * @access public
342 * @return string
343 */
344 function fetch_class()
345 {
346 return $this->class;
347 }
348 // END fetch_class()
349
350 // --------------------------------------------------------------------
351
352 /**
353 * Set the method name
354 *
355 * @access public
356 * @param string
357 * @return void
358 */
359 function set_method($method)
360 {
361 $this->method = $method;
362 }
363 // END set_method()
364
365 // --------------------------------------------------------------------
366
367 /**
368 * Fetch the current method
369 *
370 * @access public
371 * @return string
372 */
373 function fetch_method()
374 {
375 return $this->method;
376 }
377 // END fetch_method()
378
379 // --------------------------------------------------------------------
380
381 /**
382 * Set the directory name
383 *
384 * @access public
385 * @param string
386 * @return void
387 */
388 function set_directory($dir)
389 {
390 $this->directory = $dir.'/';
391 }
392 // END set_directory()
393
394 // --------------------------------------------------------------------
395
396 /**
397 * Fetch the sub-directory (if any) that contains the requested controller class
398 *
399 * @access public
400 * @return string
401 */
402 function fetch_directory()
403 {
404 return $this->directory;
405 }
406 // END fetch_directory()
407
adminb0dd10f2006-08-25 17:25:49 +0000408}
409// END Router Class
410?>