blob: b28ead95357403d0c4b156f1dd54fd0072ed8de4 [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 }
222
admine07fbb32006-08-26 17:11:01 +0000223 // Is the controller in a sub-folder?
224 if (is_dir(APPPATH.'controllers/'.$segments['0']))
225 {
226 // Set the directory and remove it from the segment array
227 $this->set_directory($segments['0']);
228 $segments = array_slice($segments, 1);
229
230 if (count($segments) == 0)
231 {
232 $this->set_class($this->default_controller);
233 $this->set_method('index');
234 $this->directory = '';
235 return array();
236 }
237
238 return $segments;
239 }
240
241 // Can't find the requested controller...
242 show_404();
admin45c872b2006-08-26 04:51:38 +0000243 }
244 // END _validate_segments()
245
246 // --------------------------------------------------------------------
247
248 /**
adminb0dd10f2006-08-25 17:25:49 +0000249 * Filter segments for malicious characters
250 *
251 * @access private
252 * @param string
253 * @return string
254 */
255 function _filter_uri($str)
256 {
257 if ( ! preg_match("/^[a-z0-9~\s\%\.:_-]+$/i", $str))
258 {
259 exit('The URI you submitted has disallowed characters: '.$str);
260 }
261
262 return $str;
263 }
264 // END _filter_uri()
265
266 // --------------------------------------------------------------------
267
268 /**
adminb0dd10f2006-08-25 17:25:49 +0000269 * Parse Routes
270 *
271 * This function matches any routes that may exist in
272 * the config/routes.php file against the URI to
273 * determine if the class/method need to be remapped.
274 *
275 * @access private
276 * @return void
277 */
278 function _parse_routes()
279 {
admine07fbb32006-08-26 17:11:01 +0000280 // Do we even have any custom routing to deal with?
281 if (count($this->routes) == 0)
282 {
283 $this->_compile_segments($this->segments);
284 return;
285 }
286
adminb0dd10f2006-08-25 17:25:49 +0000287 // Turn the segment array into a URI string
288 $uri = implode('/', $this->segments);
289 $num = count($this->segments);
290
291 // Is there a literal match? If so we're done
292 if (isset($this->routes[$uri]))
293 {
admin45c872b2006-08-26 04:51:38 +0000294 $this->_compile_segments(explode('/', $this->routes[$uri]));
adminb0dd10f2006-08-25 17:25:49 +0000295 return;
296 }
admine07fbb32006-08-26 17:11:01 +0000297
adminb0dd10f2006-08-25 17:25:49 +0000298 // Loop through the route array looking for wildcards
admind4e95072006-08-26 01:15:06 +0000299 foreach (array_slice($this->routes, 1) as $key => $val)
adminb071bb52006-08-26 19:28:37 +0000300 {
admind4e95072006-08-26 01:15:06 +0000301 // Convert wildcards to RegEx
302 $key = str_replace(':any', '.+', str_replace(':num', '[0-9]+', $key));
303
admin45c872b2006-08-26 04:51:38 +0000304 // Does the RegEx match?
admind4e95072006-08-26 01:15:06 +0000305 if (preg_match('|^'.$key.'$|', $uri))
306 {
admin45c872b2006-08-26 04:51:38 +0000307 // Do we have a back-reference?
admind4e95072006-08-26 01:15:06 +0000308 if (strpos($val, '$') !== FALSE AND strpos($key, '(') !== FALSE)
309 {
310 $val = preg_replace('|^'.$key.'$|', $val, $uri);
311 }
312
admin45c872b2006-08-26 04:51:38 +0000313 $this->_compile_segments(explode('/', $val));
314 return;
adminb0dd10f2006-08-25 17:25:49 +0000315 }
admine07fbb32006-08-26 17:11:01 +0000316 }
317
318 // If we got this far it means we didn't encounter a
319 // matching route so we'll set the site default route
320 $this->_compile_segments($this->segments);
adminb0dd10f2006-08-25 17:25:49 +0000321 }
322 // END set_method()
admin45c872b2006-08-26 04:51:38 +0000323
324 // --------------------------------------------------------------------
325
326 /**
327 * Set the class name
328 *
329 * @access public
330 * @param string
331 * @return void
332 */
333 function set_class($class)
334 {
335 $this->class = $class;
336 }
337 // END set_class()
338
339 // --------------------------------------------------------------------
340
341 /**
342 * Fetch the current class
343 *
344 * @access public
345 * @return string
346 */
347 function fetch_class()
348 {
349 return $this->class;
350 }
351 // END fetch_class()
352
353 // --------------------------------------------------------------------
354
355 /**
356 * Set the method name
357 *
358 * @access public
359 * @param string
360 * @return void
361 */
362 function set_method($method)
363 {
364 $this->method = $method;
365 }
366 // END set_method()
367
368 // --------------------------------------------------------------------
369
370 /**
371 * Fetch the current method
372 *
373 * @access public
374 * @return string
375 */
376 function fetch_method()
377 {
378 return $this->method;
379 }
380 // END fetch_method()
381
382 // --------------------------------------------------------------------
383
384 /**
385 * Set the directory name
386 *
387 * @access public
388 * @param string
389 * @return void
390 */
391 function set_directory($dir)
392 {
393 $this->directory = $dir.'/';
394 }
395 // END set_directory()
396
397 // --------------------------------------------------------------------
398
399 /**
400 * Fetch the sub-directory (if any) that contains the requested controller class
401 *
402 * @access public
403 * @return string
404 */
405 function fetch_directory()
406 {
407 return $this->directory;
408 }
409 // END fetch_directory()
410
adminb0dd10f2006-08-25 17:25:49 +0000411}
412// END Router Class
413?>