blob: 7839de2d92ac57c57cd2b85b8050ba5d292cbe4e [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.
135 $i = 1;
136 foreach(explode("/", preg_replace("|/*(.+?)/*$|", "\\1", $this->uri_string)) as $val)
137 {
138 // Filter segments for security
139 $val = trim($this->_filter_uri($val));
140
141 if ($val != '')
142 $this->segments[$i++] = $val;
143 }
adminb0dd10f2006-08-25 17:25:49 +0000144
admin45c872b2006-08-26 04:51:38 +0000145 $this->_compile_segments($this->segments);
146
147 // Do we have any custom routing to deal with?
adminb0dd10f2006-08-25 17:25:49 +0000148 if (count($this->routes) > 0)
149 {
150 $this->_parse_routes();
151 }
152 }
153 // END _set_route_mapping()
154
155 // --------------------------------------------------------------------
156
157 /**
158 * Compile Segments
159 *
160 * This function takes an array of URI segments as
161 * input, and puts it into the $this->segments array.
162 * It also sets the current class/method
163 *
164 * @access private
165 * @param array
166 * @param bool
167 * @return void
168 */
admin45c872b2006-08-26 04:51:38 +0000169 function _compile_segments($segments = array())
170 {
171 $segments = $this->_validate_segments($segments);
adminb0dd10f2006-08-25 17:25:49 +0000172
admin45c872b2006-08-26 04:51:38 +0000173 if (count($segments) == 0)
174 {
175 return;
176 }
177
adminb0dd10f2006-08-25 17:25:49 +0000178 $this->set_class($segments['1']);
179
180 if (isset($segments['2']))
181 {
182 // A scaffolding request. No funny business with the URL
183 if ($this->routes['scaffolding_trigger'] == $segments['2'] AND $segments['2'] != '_ci_scaffolding')
184 {
185 $this->scaffolding_request = TRUE;
186 unset($this->routes['scaffolding_trigger']);
187 }
188 else
189 {
190 // A standard method request
191 $this->set_method($segments['2']);
192 }
193 }
adminb0dd10f2006-08-25 17:25:49 +0000194 }
195 // END _compile_segments()
196
197 // --------------------------------------------------------------------
198
199 /**
admin45c872b2006-08-26 04:51:38 +0000200 * Validates the supplied segments. Attempts to determine the path to
201 * the controller.
202 *
203 * @access private
204 * @param array
205 * @return array
206 */
207 function _validate_segments($segments)
208 {
209 // Does the requested controller exist?
210 if ( ! file_exists(APPPATH.'controllers/'.$segments['1'].EXT))
211 {
212 // Is it a directory? No? Smite them!
213 if ( ! is_dir(APPPATH.'controllers/'.$segments['1']))
214 {
215 show_404();
216 }
217 else
218 {
219 $this->set_directory($segments['1']);
220 $segs = array_slice($segments, 1);
221
222 if (count($segs) == 0)
223 {
224 $this->set_class($this->default_controller);
225 $this->set_method('index');
226 $this->directory = '';
227 return array();
228 }
229
230 $i = 1;
231 $segments = array();
232 foreach ($segs as $val)
233 {
234 $segments[$i++] = $val;
235 }
236 }
237 }
238
239 return $segments;
240 }
241 // END _validate_segments()
242
243 // --------------------------------------------------------------------
244
245 /**
adminb0dd10f2006-08-25 17:25:49 +0000246 * Filter segments for malicious characters
247 *
248 * @access private
249 * @param string
250 * @return string
251 */
252 function _filter_uri($str)
253 {
254 if ( ! preg_match("/^[a-z0-9~\s\%\.:_-]+$/i", $str))
255 {
256 exit('The URI you submitted has disallowed characters: '.$str);
257 }
258
259 return $str;
260 }
261 // END _filter_uri()
262
263 // --------------------------------------------------------------------
264
265 /**
adminb0dd10f2006-08-25 17:25:49 +0000266 * Parse Routes
267 *
268 * This function matches any routes that may exist in
269 * the config/routes.php file against the URI to
270 * determine if the class/method need to be remapped.
271 *
272 * @access private
273 * @return void
274 */
275 function _parse_routes()
276 {
277 // Turn the segment array into a URI string
278 $uri = implode('/', $this->segments);
279 $num = count($this->segments);
280
281 // Is there a literal match? If so we're done
282 if (isset($this->routes[$uri]))
283 {
admin45c872b2006-08-26 04:51:38 +0000284 $this->_compile_segments(explode('/', $this->routes[$uri]));
adminb0dd10f2006-08-25 17:25:49 +0000285 return;
286 }
287
288 // Loop through the route array looking for wildcards
admind4e95072006-08-26 01:15:06 +0000289 foreach (array_slice($this->routes, 1) as $key => $val)
adminb0dd10f2006-08-25 17:25:49 +0000290 {
291 if (count(explode('/', $key)) != $num)
292 continue;
admind4e95072006-08-26 01:15:06 +0000293
294 // Convert wildcards to RegEx
295 $key = str_replace(':any', '.+', str_replace(':num', '[0-9]+', $key));
296
admin45c872b2006-08-26 04:51:38 +0000297 // Does the RegEx match?
admind4e95072006-08-26 01:15:06 +0000298 if (preg_match('|^'.$key.'$|', $uri))
299 {
admin45c872b2006-08-26 04:51:38 +0000300 // Do we have a back-reference?
admind4e95072006-08-26 01:15:06 +0000301 if (strpos($val, '$') !== FALSE AND strpos($key, '(') !== FALSE)
302 {
303 $val = preg_replace('|^'.$key.'$|', $val, $uri);
304 }
305
admin45c872b2006-08-26 04:51:38 +0000306 $this->_compile_segments(explode('/', $val));
307 return;
adminb0dd10f2006-08-25 17:25:49 +0000308 }
adminb0dd10f2006-08-25 17:25:49 +0000309 }
310 }
311 // END set_method()
admin45c872b2006-08-26 04:51:38 +0000312
313 // --------------------------------------------------------------------
314
315 /**
316 * Set the class name
317 *
318 * @access public
319 * @param string
320 * @return void
321 */
322 function set_class($class)
323 {
324 $this->class = $class;
325 }
326 // END set_class()
327
328 // --------------------------------------------------------------------
329
330 /**
331 * Fetch the current class
332 *
333 * @access public
334 * @return string
335 */
336 function fetch_class()
337 {
338 return $this->class;
339 }
340 // END fetch_class()
341
342 // --------------------------------------------------------------------
343
344 /**
345 * Set the method name
346 *
347 * @access public
348 * @param string
349 * @return void
350 */
351 function set_method($method)
352 {
353 $this->method = $method;
354 }
355 // END set_method()
356
357 // --------------------------------------------------------------------
358
359 /**
360 * Fetch the current method
361 *
362 * @access public
363 * @return string
364 */
365 function fetch_method()
366 {
367 return $this->method;
368 }
369 // END fetch_method()
370
371 // --------------------------------------------------------------------
372
373 /**
374 * Set the directory name
375 *
376 * @access public
377 * @param string
378 * @return void
379 */
380 function set_directory($dir)
381 {
382 $this->directory = $dir.'/';
383 }
384 // END set_directory()
385
386 // --------------------------------------------------------------------
387
388 /**
389 * Fetch the sub-directory (if any) that contains the requested controller class
390 *
391 * @access public
392 * @return string
393 */
394 function fetch_directory()
395 {
396 return $this->directory;
397 }
398 // END fetch_directory()
399
adminb0dd10f2006-08-25 17:25:49 +0000400}
401// END Router Class
402?>