blob: 1dd1e5426dbb04010a1b16cfce76501f0df459ef [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
adminb071bb52006-08-26 19:28:37 +000090 if ($this->config->item('uri_protocol') == 'auto')
adminb0dd10f2006-08-25 17:25:49 +000091 {
adminb071bb52006-08-26 19:28:37 +000092 $path_info = getenv('PATH_INFO');
93 if ($path_info != '' AND $path_info != "/".SELF)
94 {
95 $this->uri_string = $path_info;
96 }
97 else
98 {
99 $path_info = getenv('ORIG_PATH_INFO');
100 if ($path_info != '' AND $path_info != "/".SELF)
101 {
102 $this->uri_string = $path_info;
103 }
104 else
105 {
106 $this->uri_string = getenv('QUERY_STRING');
107 }
108 }
adminb0dd10f2006-08-25 17:25:49 +0000109 }
adminb071bb52006-08-26 19:28:37 +0000110 else
111 {
112 $this->uri_string = getenv(strtoupper($this->config->item('uri_protocol')));
113 }
114
adminb0dd10f2006-08-25 17:25:49 +0000115
116 // Is there a URI string? If not, the default controller specified
117 // by the admin in the "routes" file will be shown.
118 if ($this->uri_string == '')
119 {
120 if ($this->default_controller === FALSE)
121 {
122 show_error("Unable to determine what should be displayed. A default route has not been specified in the routing file.");
123 }
124
125 $this->set_class($this->default_controller);
126 $this->set_method('index');
127
128 log_message('debug', "No URI present. Default controller set.");
129 return;
130 }
admin45c872b2006-08-26 04:51:38 +0000131 unset($this->routes['default_controller']);
adminb0dd10f2006-08-25 17:25:49 +0000132
133 // Do we need to remove the suffix specified in the config file?
134 if ($this->config->item('url_suffix') != "")
135 {
136 $this->uri_string = preg_replace("|".preg_quote($this->config->item('url_suffix'))."$|", "", $this->uri_string);
137 }
138
139 // Explode the URI Segments. The individual segments will
admin45c872b2006-08-26 04:51:38 +0000140 // be stored in the $this->segments array.
admin45c872b2006-08-26 04:51:38 +0000141 foreach(explode("/", preg_replace("|/*(.+?)/*$|", "\\1", $this->uri_string)) as $val)
142 {
143 // Filter segments for security
144 $val = trim($this->_filter_uri($val));
145
146 if ($val != '')
admine07fbb32006-08-26 17:11:01 +0000147 $this->segments[] = $val;
admin45c872b2006-08-26 04:51:38 +0000148 }
adminb0dd10f2006-08-25 17:25:49 +0000149
admine07fbb32006-08-26 17:11:01 +0000150 // Parse any custom routing that may exist
151 $this->_parse_routes();
admin45c872b2006-08-26 04:51:38 +0000152
admine07fbb32006-08-26 17:11:01 +0000153 // Re-index the segment array so that it starts with 1 rather than 0
154 $i = 1;
155 foreach ($this->segments as $val)
adminb0dd10f2006-08-25 17:25:49 +0000156 {
admine07fbb32006-08-26 17:11:01 +0000157 $this->segments[$i++] = $val;
adminb0dd10f2006-08-25 17:25:49 +0000158 }
admine07fbb32006-08-26 17:11:01 +0000159 unset($this->segments['0']);
adminb0dd10f2006-08-25 17:25:49 +0000160 }
161 // END _set_route_mapping()
162
163 // --------------------------------------------------------------------
164
165 /**
166 * Compile Segments
167 *
168 * This function takes an array of URI segments as
169 * input, and puts it into the $this->segments array.
170 * It also sets the current class/method
171 *
172 * @access private
173 * @param array
174 * @param bool
175 * @return void
176 */
admin45c872b2006-08-26 04:51:38 +0000177 function _compile_segments($segments = array())
178 {
179 $segments = $this->_validate_segments($segments);
adminb0dd10f2006-08-25 17:25:49 +0000180
admin45c872b2006-08-26 04:51:38 +0000181 if (count($segments) == 0)
182 {
183 return;
184 }
185
admine07fbb32006-08-26 17:11:01 +0000186 $this->set_class($segments['0']);
adminb0dd10f2006-08-25 17:25:49 +0000187
admine07fbb32006-08-26 17:11:01 +0000188 if (isset($segments['1']))
adminb0dd10f2006-08-25 17:25:49 +0000189 {
190 // A scaffolding request. No funny business with the URL
admine07fbb32006-08-26 17:11:01 +0000191 if ($this->routes['scaffolding_trigger'] == $segments['1'] AND $segments['1'] != '_ci_scaffolding')
adminb0dd10f2006-08-25 17:25:49 +0000192 {
193 $this->scaffolding_request = TRUE;
194 unset($this->routes['scaffolding_trigger']);
195 }
196 else
197 {
198 // A standard method request
admine07fbb32006-08-26 17:11:01 +0000199 $this->set_method($segments['1']);
adminb0dd10f2006-08-25 17:25:49 +0000200 }
201 }
adminb0dd10f2006-08-25 17:25:49 +0000202 }
203 // END _compile_segments()
204
205 // --------------------------------------------------------------------
206
207 /**
admin45c872b2006-08-26 04:51:38 +0000208 * Validates the supplied segments. Attempts to determine the path to
209 * the controller.
210 *
211 * @access private
212 * @param array
213 * @return array
214 */
215 function _validate_segments($segments)
216 {
admine07fbb32006-08-26 17:11:01 +0000217 // Does the requested controller exist in the root folder?
218 if (file_exists(APPPATH.'controllers/'.$segments['0'].EXT))
admin45c872b2006-08-26 04:51:38 +0000219 {
admine07fbb32006-08-26 17:11:01 +0000220 return $segments;
admin45c872b2006-08-26 04:51:38 +0000221 }
admin1cf89aa2006-09-03 18:24:39 +0000222
admine07fbb32006-08-26 17:11:01 +0000223 // Is the controller in a sub-folder?
224 if (is_dir(APPPATH.'controllers/'.$segments['0']))
admin1cf89aa2006-09-03 18:24:39 +0000225 {
admine07fbb32006-08-26 17:11:01 +0000226 // Set the directory and remove it from the segment array
227 $this->set_directory($segments['0']);
228 $segments = array_slice($segments, 1);
229
admin1cf89aa2006-09-03 18:24:39 +0000230 if (count($segments) > 0)
231 {
232 // Does the requested controller exist in the sub-folder?
233 if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$segments['0'].EXT))
234 {
235 show_404();
236 }
237 }
238 else
admine07fbb32006-08-26 17:11:01 +0000239 {
240 $this->set_class($this->default_controller);
241 $this->set_method('index');
admin27818492006-09-05 03:31:28 +0000242
243 // Does the default controller exist in the sub-folder?
244 if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$this->default_controller.EXT))
245 {
246 $this->directory = '';
247 return array();
248 }
249
admine07fbb32006-08-26 17:11:01 +0000250 }
251
252 return $segments;
253 }
254
255 // Can't find the requested controller...
256 show_404();
admin45c872b2006-08-26 04:51:38 +0000257 }
258 // END _validate_segments()
259
260 // --------------------------------------------------------------------
261
262 /**
adminb0dd10f2006-08-25 17:25:49 +0000263 * Filter segments for malicious characters
264 *
265 * @access private
266 * @param string
267 * @return string
268 */
269 function _filter_uri($str)
270 {
admin1082bdd2006-08-27 19:32:02 +0000271 if ($this->config->item('permitted_uri_chars') != '')
272 {
273 if ( ! preg_match("|^[".preg_quote($this->config->item('permitted_uri_chars'))."]+$|i", $str))
274 {
275 exit('The URI you submitted has disallowed characters: '.$str);
276 }
277 }
278 return $str;
adminb0dd10f2006-08-25 17:25:49 +0000279 }
280 // END _filter_uri()
281
282 // --------------------------------------------------------------------
283
284 /**
adminb0dd10f2006-08-25 17:25:49 +0000285 * Parse Routes
286 *
287 * This function matches any routes that may exist in
288 * the config/routes.php file against the URI to
289 * determine if the class/method need to be remapped.
290 *
291 * @access private
292 * @return void
293 */
294 function _parse_routes()
295 {
admine07fbb32006-08-26 17:11:01 +0000296 // Do we even have any custom routing to deal with?
297 if (count($this->routes) == 0)
298 {
299 $this->_compile_segments($this->segments);
300 return;
301 }
302
adminb0dd10f2006-08-25 17:25:49 +0000303 // Turn the segment array into a URI string
304 $uri = implode('/', $this->segments);
305 $num = count($this->segments);
306
307 // Is there a literal match? If so we're done
308 if (isset($this->routes[$uri]))
309 {
admin45c872b2006-08-26 04:51:38 +0000310 $this->_compile_segments(explode('/', $this->routes[$uri]));
adminb0dd10f2006-08-25 17:25:49 +0000311 return;
312 }
admine07fbb32006-08-26 17:11:01 +0000313
adminb0dd10f2006-08-25 17:25:49 +0000314 // Loop through the route array looking for wildcards
admind4e95072006-08-26 01:15:06 +0000315 foreach (array_slice($this->routes, 1) as $key => $val)
adminb071bb52006-08-26 19:28:37 +0000316 {
admind4e95072006-08-26 01:15:06 +0000317 // Convert wildcards to RegEx
318 $key = str_replace(':any', '.+', str_replace(':num', '[0-9]+', $key));
319
admin45c872b2006-08-26 04:51:38 +0000320 // Does the RegEx match?
admin71430b42006-09-15 20:29:25 +0000321 if (preg_match('#^'.$key.'$#', $uri))
admind4e95072006-08-26 01:15:06 +0000322 {
admin45c872b2006-08-26 04:51:38 +0000323 // Do we have a back-reference?
admind4e95072006-08-26 01:15:06 +0000324 if (strpos($val, '$') !== FALSE AND strpos($key, '(') !== FALSE)
325 {
admin71430b42006-09-15 20:29:25 +0000326 $val = preg_replace('#^'.$key.'$#', $val, $uri);
admind4e95072006-08-26 01:15:06 +0000327 }
328
admin45c872b2006-08-26 04:51:38 +0000329 $this->_compile_segments(explode('/', $val));
330 return;
adminb0dd10f2006-08-25 17:25:49 +0000331 }
admine07fbb32006-08-26 17:11:01 +0000332 }
333
334 // If we got this far it means we didn't encounter a
335 // matching route so we'll set the site default route
336 $this->_compile_segments($this->segments);
adminb0dd10f2006-08-25 17:25:49 +0000337 }
338 // END set_method()
admin45c872b2006-08-26 04:51:38 +0000339
340 // --------------------------------------------------------------------
341
342 /**
343 * Set the class name
344 *
345 * @access public
346 * @param string
347 * @return void
348 */
349 function set_class($class)
350 {
351 $this->class = $class;
352 }
353 // END set_class()
354
355 // --------------------------------------------------------------------
356
357 /**
358 * Fetch the current class
359 *
360 * @access public
361 * @return string
362 */
363 function fetch_class()
364 {
365 return $this->class;
366 }
367 // END fetch_class()
368
369 // --------------------------------------------------------------------
370
371 /**
372 * Set the method name
373 *
374 * @access public
375 * @param string
376 * @return void
377 */
378 function set_method($method)
379 {
380 $this->method = $method;
381 }
382 // END set_method()
383
384 // --------------------------------------------------------------------
385
386 /**
387 * Fetch the current method
388 *
389 * @access public
390 * @return string
391 */
392 function fetch_method()
393 {
394 return $this->method;
395 }
396 // END fetch_method()
397
398 // --------------------------------------------------------------------
399
400 /**
401 * Set the directory name
402 *
403 * @access public
404 * @param string
405 * @return void
406 */
407 function set_directory($dir)
408 {
409 $this->directory = $dir.'/';
410 }
411 // END set_directory()
412
413 // --------------------------------------------------------------------
414
415 /**
416 * Fetch the sub-directory (if any) that contains the requested controller class
417 *
418 * @access public
419 * @return string
420 */
421 function fetch_directory()
422 {
423 return $this->directory;
424 }
425 // END fetch_directory()
426
adminb0dd10f2006-08-25 17:25:49 +0000427}
428// END Router Class
429?>