blob: 34a2512a60da563324f10ca74fe5a5c107b0c0a8 [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();
admin99bccd62006-09-21 17:05:40 +000034 var $rsegments = array();
adminb0dd10f2006-08-25 17:25:49 +000035 var $routes = array();
36 var $class = '';
37 var $method = 'index';
admin45c872b2006-08-26 04:51:38 +000038 var $directory = '';
adminb0dd10f2006-08-25 17:25:49 +000039 var $uri_protocol = 'auto';
40 var $default_controller;
41 var $scaffolding_request = FALSE; // Must be set to FALSE
42
43 /**
44 * Constructor
45 *
46 * Runs the route mapping function.
47 */
48 function CI_Router()
49 {
50 $this->config =& _load_class('CI_Config');
51 $this->_set_route_mapping();
52 log_message('debug', "Router Class Initialized");
53 }
54
55 // --------------------------------------------------------------------
56
57 /**
58 * Set the route mapping
59 *
60 * This function determies what should be served based on the URI request,
61 * as well as any "routes" that have been set in the routing config file.
62 *
63 * @access private
64 * @return void
65 */
66 function _set_route_mapping()
67 {
admin17a890d2006-09-27 20:42:42 +000068
69 // Are query strings enabled in the config file?
70 // If so, we're done since segment based URIs are not used with query strings.
adminb0dd10f2006-08-25 17:25:49 +000071 if ($this->config->item('enable_query_strings') === TRUE AND isset($_GET[$this->config->item('controller_trigger')]))
72 {
73 $this->set_class($_GET[$this->config->item('controller_trigger')]);
74
75 if (isset($_GET[$this->config->item('function_trigger')]))
76 {
77 $this->set_method($_GET[$this->config->item('function_trigger')]);
78 }
79
80 return;
81 }
admin592cdcb2006-09-22 18:45:42 +000082
admin17a890d2006-09-27 20:42:42 +000083 // Load the routes.php file.
admin99bccd62006-09-21 17:05:40 +000084 @include_once(APPPATH.'config/routes'.EXT);
adminb0dd10f2006-08-25 17:25:49 +000085 $this->routes = ( ! isset($route) OR ! is_array($route)) ? array() : $route;
86 unset($route);
87
admin17a890d2006-09-27 20:42:42 +000088 // Set the default controller so we can display it in the event
89 // the URI doesn't correlated to a valid controller.
admin592cdcb2006-09-22 18:45:42 +000090 $this->default_controller = ( ! isset($this->routes['default_controller']) OR $this->routes['default_controller'] == '') ? FALSE : strtolower($this->routes['default_controller']);
adminb0dd10f2006-08-25 17:25:49 +000091
admin17a890d2006-09-27 20:42:42 +000092 // Fetch the complete URI string
93 $this->uri_string = $this->_get_uri_string();
94
95 // If the URI contains only a slash we'll kill it
96 if ($this->uri_string == '/')
97 {
98 $this->uri_string = '';
99 }
100
admin592cdcb2006-09-22 18:45:42 +0000101 // Is there a URI string? If not, the default controller specified in the "routes" file will be shown.
admin17a890d2006-09-27 20:42:42 +0000102 if ($this->uri_string == '')
adminb0dd10f2006-08-25 17:25:49 +0000103 {
104 if ($this->default_controller === FALSE)
105 {
106 show_error("Unable to determine what should be displayed. A default route has not been specified in the routing file.");
107 }
108
109 $this->set_class($this->default_controller);
110 $this->set_method('index');
111
112 log_message('debug', "No URI present. Default controller set.");
113 return;
114 }
admin45c872b2006-08-26 04:51:38 +0000115 unset($this->routes['default_controller']);
adminb0dd10f2006-08-25 17:25:49 +0000116
117 // Do we need to remove the suffix specified in the config file?
118 if ($this->config->item('url_suffix') != "")
119 {
120 $this->uri_string = preg_replace("|".preg_quote($this->config->item('url_suffix'))."$|", "", $this->uri_string);
121 }
122
admin17a890d2006-09-27 20:42:42 +0000123
adminb0dd10f2006-08-25 17:25:49 +0000124 // Explode the URI Segments. The individual segments will
admin45c872b2006-08-26 04:51:38 +0000125 // be stored in the $this->segments array.
admin45c872b2006-08-26 04:51:38 +0000126 foreach(explode("/", preg_replace("|/*(.+?)/*$|", "\\1", $this->uri_string)) as $val)
127 {
128 // Filter segments for security
129 $val = trim($this->_filter_uri($val));
130
131 if ($val != '')
admine07fbb32006-08-26 17:11:01 +0000132 $this->segments[] = $val;
admin45c872b2006-08-26 04:51:38 +0000133 }
adminb0dd10f2006-08-25 17:25:49 +0000134
admine07fbb32006-08-26 17:11:01 +0000135 // Parse any custom routing that may exist
136 $this->_parse_routes();
admin45c872b2006-08-26 04:51:38 +0000137
admine07fbb32006-08-26 17:11:01 +0000138 // Re-index the segment array so that it starts with 1 rather than 0
admin99bccd62006-09-21 17:05:40 +0000139 $this->_reindex_segments();
adminb0dd10f2006-08-25 17:25:49 +0000140 }
141 // END _set_route_mapping()
142
143 // --------------------------------------------------------------------
144
145 /**
146 * Compile Segments
147 *
148 * This function takes an array of URI segments as
149 * input, and puts it into the $this->segments array.
150 * It also sets the current class/method
151 *
152 * @access private
153 * @param array
154 * @param bool
155 * @return void
156 */
admin45c872b2006-08-26 04:51:38 +0000157 function _compile_segments($segments = array())
158 {
159 $segments = $this->_validate_segments($segments);
adminb0dd10f2006-08-25 17:25:49 +0000160
admin45c872b2006-08-26 04:51:38 +0000161 if (count($segments) == 0)
162 {
163 return;
164 }
165
admin83b05a82006-09-25 21:06:46 +0000166 $this->set_class($segments[0]);
adminb0dd10f2006-08-25 17:25:49 +0000167
admin83b05a82006-09-25 21:06:46 +0000168 if (isset($segments[1]))
adminb0dd10f2006-08-25 17:25:49 +0000169 {
170 // A scaffolding request. No funny business with the URL
admin83b05a82006-09-25 21:06:46 +0000171 if ($this->routes['scaffolding_trigger'] == $segments[1] AND $segments[1] != '_ci_scaffolding')
adminb0dd10f2006-08-25 17:25:49 +0000172 {
173 $this->scaffolding_request = TRUE;
174 unset($this->routes['scaffolding_trigger']);
175 }
176 else
177 {
178 // A standard method request
admin83b05a82006-09-25 21:06:46 +0000179 $this->set_method($segments[1]);
adminb0dd10f2006-08-25 17:25:49 +0000180 }
181 }
admin99bccd62006-09-21 17:05:40 +0000182
183 // Update our "routed" segment array to contain the segments.
184 // Note: If there is no custom routing, this array will be
185 // identical to $this->segments
186 $this->rsegments = $segments;
adminb0dd10f2006-08-25 17:25:49 +0000187 }
188 // END _compile_segments()
189
190 // --------------------------------------------------------------------
191
192 /**
admin45c872b2006-08-26 04:51:38 +0000193 * Validates the supplied segments. Attempts to determine the path to
194 * the controller.
195 *
196 * @access private
197 * @param array
198 * @return array
199 */
200 function _validate_segments($segments)
201 {
admine07fbb32006-08-26 17:11:01 +0000202 // Does the requested controller exist in the root folder?
admin83b05a82006-09-25 21:06:46 +0000203 if (file_exists(APPPATH.'controllers/'.$segments[0].EXT))
admin45c872b2006-08-26 04:51:38 +0000204 {
admine07fbb32006-08-26 17:11:01 +0000205 return $segments;
admin45c872b2006-08-26 04:51:38 +0000206 }
admin1cf89aa2006-09-03 18:24:39 +0000207
admine07fbb32006-08-26 17:11:01 +0000208 // Is the controller in a sub-folder?
admin83b05a82006-09-25 21:06:46 +0000209 if (is_dir(APPPATH.'controllers/'.$segments[0]))
admin1cf89aa2006-09-03 18:24:39 +0000210 {
admine07fbb32006-08-26 17:11:01 +0000211 // Set the directory and remove it from the segment array
admin83b05a82006-09-25 21:06:46 +0000212 $this->set_directory($segments[0]);
admine07fbb32006-08-26 17:11:01 +0000213 $segments = array_slice($segments, 1);
214
admin1cf89aa2006-09-03 18:24:39 +0000215 if (count($segments) > 0)
216 {
217 // Does the requested controller exist in the sub-folder?
admin83b05a82006-09-25 21:06:46 +0000218 if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$segments[0].EXT))
admin1cf89aa2006-09-03 18:24:39 +0000219 {
220 show_404();
221 }
222 }
223 else
admine07fbb32006-08-26 17:11:01 +0000224 {
225 $this->set_class($this->default_controller);
226 $this->set_method('index');
admin27818492006-09-05 03:31:28 +0000227
228 // Does the default controller exist in the sub-folder?
229 if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$this->default_controller.EXT))
230 {
231 $this->directory = '';
232 return array();
233 }
234
admine07fbb32006-08-26 17:11:01 +0000235 }
236
237 return $segments;
238 }
239
240 // Can't find the requested controller...
241 show_404();
admin45c872b2006-08-26 04:51:38 +0000242 }
243 // END _validate_segments()
admin99bccd62006-09-21 17:05:40 +0000244
245 // --------------------------------------------------------------------
246 /**
247 * Re-index Segments
248 *
249 * This function re-indexes the $this->segment array so that it
250 * starts at 1 rather then 0. Doing so makes it simpler to
251 * use functions like $this->uri->segment(n) since there is
252 * a 1:1 relationship between the segment array and the actual segments.
253 *
254 * @access private
255 * @return void
256 */
257 function _reindex_segments()
258 {
259 // Is the routed segment array different then the main segment array?
260 $diff = (count(array_diff($this->rsegments, $this->segments)) == 0) ? FALSE : TRUE;
261
262 $i = 1;
263 foreach ($this->segments as $val)
264 {
265 $this->segments[$i++] = $val;
266 }
admin83b05a82006-09-25 21:06:46 +0000267 unset($this->segments[0]);
admin99bccd62006-09-21 17:05:40 +0000268
269 if ($diff == FALSE)
270 {
271 $this->rsegments = $this->segments;
272 }
273 else
274 {
275 $i = 1;
276 foreach ($this->rsegments as $val)
277 {
278 $this->rsegments[$i++] = $val;
279 }
admin83b05a82006-09-25 21:06:46 +0000280 unset($this->rsegments[0]);
admin99bccd62006-09-21 17:05:40 +0000281 }
282 }
283 // END _reindex_segments()
admin45c872b2006-08-26 04:51:38 +0000284
285 // --------------------------------------------------------------------
286
287 /**
admin592cdcb2006-09-22 18:45:42 +0000288 * Get the URI String
289 *
290 * @access private
291 * @return string
292 */
293 function _get_uri_string()
294 {
295 if (strtoupper($this->config->item('uri_protocol')) == 'AUTO')
296 {
297 $path_info = getenv('PATH_INFO');
298 if ($path_info != '' AND $path_info != "/".SELF)
299 {
300 return $path_info;
301 }
302 else
303 {
304 $req_uri = $this->_parse_request_uri();
305
306 if ($req_uri != "")
307 {
308 return $req_uri;
309 }
310 else
311 {
312 $path_info = getenv('ORIG_PATH_INFO');
313 if ($path_info != '' AND $path_info != "/".SELF)
314 {
315 return $path_info;
316 }
317 else
318 {
319 return getenv('QUERY_STRING');
320 }
321 }
322 }
323 }
324 else
325 {
326 $uri = strtoupper($this->config->item('uri_protocol'));
327
328 if ($uri == 'REQUEST_URI')
329 {
330 return $this->_parse_request_uri();
331 }
332
333 return getenv($uri);
334 }
335 }
336 // END _get_uri_string()
337
338 // --------------------------------------------------------------------
339
340 /**
341 * Parse the REQUEST_URI
342 *
343 * Due to the way REQUEST_URI works it usually contains path info
344 * that makes it unusable as URI data. We'll trim off the unnecessary
345 * data, hopefully arriving at a valid URI that we can use.
346 *
347 * @access private
348 * @return string
349 */
350 function _parse_request_uri()
351 {
admindbd8aec2006-09-22 19:20:09 +0000352 if (($request_uri = getenv('REQUEST_URI')) == '')
353 {
354 return '';
355 }
356
admin592cdcb2006-09-22 18:45:42 +0000357 $fc_path = FCPATH;
358
359 if (strpos($request_uri, '?') !== FALSE)
360 {
361 $fc_path .= '?';
362 }
363
364 $parsed_uri = explode("/", preg_replace("|/(.*)|", "\\1", str_replace("\\", "/", $request_uri)));
365
366 $i = 0;
367 foreach(explode("/", $fc_path) as $segment)
368 {
369 if ($segment == $parsed_uri[$i])
370 {
371 $i++;
372 }
373 }
374
375 $parsed_uri = implode("/", array_slice($parsed_uri, $i));
376
377 if ($parsed_uri != '')
378 {
379 $parsed_uri = '/'.$parsed_uri;
380 }
381
382 return $parsed_uri;
383 }
384 // END _parse_request_uri()
385
386 // --------------------------------------------------------------------
387
388 /**
adminb0dd10f2006-08-25 17:25:49 +0000389 * Filter segments for malicious characters
390 *
391 * @access private
392 * @param string
393 * @return string
394 */
395 function _filter_uri($str)
396 {
admin1082bdd2006-08-27 19:32:02 +0000397 if ($this->config->item('permitted_uri_chars') != '')
398 {
399 if ( ! preg_match("|^[".preg_quote($this->config->item('permitted_uri_chars'))."]+$|i", $str))
400 {
401 exit('The URI you submitted has disallowed characters: '.$str);
402 }
403 }
404 return $str;
adminb0dd10f2006-08-25 17:25:49 +0000405 }
406 // END _filter_uri()
407
408 // --------------------------------------------------------------------
409
410 /**
adminb0dd10f2006-08-25 17:25:49 +0000411 * Parse Routes
412 *
413 * This function matches any routes that may exist in
414 * the config/routes.php file against the URI to
415 * determine if the class/method need to be remapped.
416 *
417 * @access private
418 * @return void
419 */
420 function _parse_routes()
421 {
admine07fbb32006-08-26 17:11:01 +0000422 // Do we even have any custom routing to deal with?
423 if (count($this->routes) == 0)
424 {
425 $this->_compile_segments($this->segments);
426 return;
427 }
428
adminb0dd10f2006-08-25 17:25:49 +0000429 // Turn the segment array into a URI string
430 $uri = implode('/', $this->segments);
431 $num = count($this->segments);
432
433 // Is there a literal match? If so we're done
434 if (isset($this->routes[$uri]))
435 {
admin45c872b2006-08-26 04:51:38 +0000436 $this->_compile_segments(explode('/', $this->routes[$uri]));
adminb0dd10f2006-08-25 17:25:49 +0000437 return;
438 }
admine07fbb32006-08-26 17:11:01 +0000439
adminb0dd10f2006-08-25 17:25:49 +0000440 // Loop through the route array looking for wildcards
admind4e95072006-08-26 01:15:06 +0000441 foreach (array_slice($this->routes, 1) as $key => $val)
adminb071bb52006-08-26 19:28:37 +0000442 {
admind4e95072006-08-26 01:15:06 +0000443 // Convert wildcards to RegEx
444 $key = str_replace(':any', '.+', str_replace(':num', '[0-9]+', $key));
445
admin45c872b2006-08-26 04:51:38 +0000446 // Does the RegEx match?
admin71430b42006-09-15 20:29:25 +0000447 if (preg_match('#^'.$key.'$#', $uri))
admind4e95072006-08-26 01:15:06 +0000448 {
admin45c872b2006-08-26 04:51:38 +0000449 // Do we have a back-reference?
admind4e95072006-08-26 01:15:06 +0000450 if (strpos($val, '$') !== FALSE AND strpos($key, '(') !== FALSE)
451 {
admin71430b42006-09-15 20:29:25 +0000452 $val = preg_replace('#^'.$key.'$#', $val, $uri);
admind4e95072006-08-26 01:15:06 +0000453 }
454
admin45c872b2006-08-26 04:51:38 +0000455 $this->_compile_segments(explode('/', $val));
456 return;
adminb0dd10f2006-08-25 17:25:49 +0000457 }
admine07fbb32006-08-26 17:11:01 +0000458 }
459
460 // If we got this far it means we didn't encounter a
461 // matching route so we'll set the site default route
462 $this->_compile_segments($this->segments);
adminb0dd10f2006-08-25 17:25:49 +0000463 }
464 // END set_method()
admin45c872b2006-08-26 04:51:38 +0000465
466 // --------------------------------------------------------------------
467
468 /**
469 * Set the class name
470 *
471 * @access public
472 * @param string
473 * @return void
474 */
475 function set_class($class)
476 {
477 $this->class = $class;
478 }
479 // END set_class()
480
481 // --------------------------------------------------------------------
482
483 /**
484 * Fetch the current class
485 *
486 * @access public
487 * @return string
488 */
489 function fetch_class()
490 {
491 return $this->class;
492 }
493 // END fetch_class()
494
495 // --------------------------------------------------------------------
496
497 /**
498 * Set the method name
499 *
500 * @access public
501 * @param string
502 * @return void
503 */
504 function set_method($method)
505 {
506 $this->method = $method;
507 }
508 // END set_method()
509
510 // --------------------------------------------------------------------
511
512 /**
513 * Fetch the current method
514 *
515 * @access public
516 * @return string
517 */
518 function fetch_method()
519 {
520 return $this->method;
521 }
522 // END fetch_method()
523
524 // --------------------------------------------------------------------
525
526 /**
527 * Set the directory name
528 *
529 * @access public
530 * @param string
531 * @return void
532 */
533 function set_directory($dir)
534 {
535 $this->directory = $dir.'/';
536 }
537 // END set_directory()
538
539 // --------------------------------------------------------------------
540
541 /**
542 * Fetch the sub-directory (if any) that contains the requested controller class
543 *
544 * @access public
545 * @return string
546 */
547 function fetch_directory()
548 {
549 return $this->directory;
550 }
551 // END fetch_directory()
552
adminb0dd10f2006-08-25 17:25:49 +0000553}
554// END Router Class
555?>