blob: c7e855a78a3ce958395eda8c5cb4a95f27d5913c [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');
242 $this->directory = '';
243 return array();
244 }
245
246 return $segments;
247 }
248
249 // Can't find the requested controller...
250 show_404();
admin45c872b2006-08-26 04:51:38 +0000251 }
252 // END _validate_segments()
253
254 // --------------------------------------------------------------------
255
256 /**
adminb0dd10f2006-08-25 17:25:49 +0000257 * Filter segments for malicious characters
258 *
259 * @access private
260 * @param string
261 * @return string
262 */
263 function _filter_uri($str)
264 {
admin1082bdd2006-08-27 19:32:02 +0000265 if ($this->config->item('permitted_uri_chars') != '')
266 {
267 if ( ! preg_match("|^[".preg_quote($this->config->item('permitted_uri_chars'))."]+$|i", $str))
268 {
269 exit('The URI you submitted has disallowed characters: '.$str);
270 }
271 }
272 return $str;
adminb0dd10f2006-08-25 17:25:49 +0000273 }
274 // END _filter_uri()
275
276 // --------------------------------------------------------------------
277
278 /**
adminb0dd10f2006-08-25 17:25:49 +0000279 * Parse Routes
280 *
281 * This function matches any routes that may exist in
282 * the config/routes.php file against the URI to
283 * determine if the class/method need to be remapped.
284 *
285 * @access private
286 * @return void
287 */
288 function _parse_routes()
289 {
admine07fbb32006-08-26 17:11:01 +0000290 // Do we even have any custom routing to deal with?
291 if (count($this->routes) == 0)
292 {
293 $this->_compile_segments($this->segments);
294 return;
295 }
296
adminb0dd10f2006-08-25 17:25:49 +0000297 // Turn the segment array into a URI string
298 $uri = implode('/', $this->segments);
299 $num = count($this->segments);
300
301 // Is there a literal match? If so we're done
302 if (isset($this->routes[$uri]))
303 {
admin45c872b2006-08-26 04:51:38 +0000304 $this->_compile_segments(explode('/', $this->routes[$uri]));
adminb0dd10f2006-08-25 17:25:49 +0000305 return;
306 }
admine07fbb32006-08-26 17:11:01 +0000307
adminb0dd10f2006-08-25 17:25:49 +0000308 // Loop through the route array looking for wildcards
admind4e95072006-08-26 01:15:06 +0000309 foreach (array_slice($this->routes, 1) as $key => $val)
adminb071bb52006-08-26 19:28:37 +0000310 {
admind4e95072006-08-26 01:15:06 +0000311 // Convert wildcards to RegEx
312 $key = str_replace(':any', '.+', str_replace(':num', '[0-9]+', $key));
313
admin45c872b2006-08-26 04:51:38 +0000314 // Does the RegEx match?
admin813711e2006-09-04 07:13:57 +0000315 if (preg_match('#^'.preg_quote($key).'$#', $uri))
admind4e95072006-08-26 01:15:06 +0000316 {
admin45c872b2006-08-26 04:51:38 +0000317 // Do we have a back-reference?
admind4e95072006-08-26 01:15:06 +0000318 if (strpos($val, '$') !== FALSE AND strpos($key, '(') !== FALSE)
319 {
admin813711e2006-09-04 07:13:57 +0000320 $val = preg_replace('#^'.preg_quote($key).'$#', $val, $uri);
admind4e95072006-08-26 01:15:06 +0000321 }
322
admin45c872b2006-08-26 04:51:38 +0000323 $this->_compile_segments(explode('/', $val));
324 return;
adminb0dd10f2006-08-25 17:25:49 +0000325 }
admine07fbb32006-08-26 17:11:01 +0000326 }
327
328 // If we got this far it means we didn't encounter a
329 // matching route so we'll set the site default route
330 $this->_compile_segments($this->segments);
adminb0dd10f2006-08-25 17:25:49 +0000331 }
332 // END set_method()
admin45c872b2006-08-26 04:51:38 +0000333
334 // --------------------------------------------------------------------
335
336 /**
337 * Set the class name
338 *
339 * @access public
340 * @param string
341 * @return void
342 */
343 function set_class($class)
344 {
345 $this->class = $class;
346 }
347 // END set_class()
348
349 // --------------------------------------------------------------------
350
351 /**
352 * Fetch the current class
353 *
354 * @access public
355 * @return string
356 */
357 function fetch_class()
358 {
359 return $this->class;
360 }
361 // END fetch_class()
362
363 // --------------------------------------------------------------------
364
365 /**
366 * Set the method name
367 *
368 * @access public
369 * @param string
370 * @return void
371 */
372 function set_method($method)
373 {
374 $this->method = $method;
375 }
376 // END set_method()
377
378 // --------------------------------------------------------------------
379
380 /**
381 * Fetch the current method
382 *
383 * @access public
384 * @return string
385 */
386 function fetch_method()
387 {
388 return $this->method;
389 }
390 // END fetch_method()
391
392 // --------------------------------------------------------------------
393
394 /**
395 * Set the directory name
396 *
397 * @access public
398 * @param string
399 * @return void
400 */
401 function set_directory($dir)
402 {
403 $this->directory = $dir.'/';
404 }
405 // END set_directory()
406
407 // --------------------------------------------------------------------
408
409 /**
410 * Fetch the sub-directory (if any) that contains the requested controller class
411 *
412 * @access public
413 * @return string
414 */
415 function fetch_directory()
416 {
417 return $this->directory;
418 }
419 // END fetch_directory()
420
adminb0dd10f2006-08-25 17:25:49 +0000421}
422// END Router Class
423?>