1 <?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
2 // ------------------------------------------------------------------------
3
4 /**
5 * URI Class
6 */
7 class CI_URI {
8
9 /**
10 * List of cached uri segments
11 */
12 var $keyval = array();
13
14
15 /**
16 * Current uri string
17 */
18 var $uri_string;
19
20
21 /**
22 * List of uri segments
23 */
24 var $segments = array();
25
26
27 /**
28 * Re-indexed list of uri segments
29 */
30 var $rsegments = array();
31
32 /**
33 * Constructor
34 */
35 function __construct()
36 {
37 $this->config =& load_class('Config', 'core');
38 log_message('debug', "URI Class Initialized");
39 }
40
41
42 // --------------------------------------------------------------------
43
44 /**
45 * Get the URI String
46 */
47 //URI组件里面有很多方法,大都是一些辅助作用的方法,而此方法是URI最主线的一个方法。
48 function _fetch_uri_string()
49 {
50 //下面的uri_protocol是在config.php里面的一个配置项,其实是问你用哪种方式去检测uri的信息的意思,
51 //默认是AUTO,自动检测,也就是通过各种方式检测,直至检测到,或者全部方式都检测完。。
52 if (strtoupper($this->config->item('uri_protocol')) == 'AUTO')
53 {
54 //开始尝试各种方式,主要有:命令行,REQUEST_URI, PATH_INFO, QUERY_STRING.
55
56 //下面会多次出现$this->_set_uri_string($str)这个方法,这个方法没别的,就是把$str经过
57 //过滤和修剪后值给$this->uri_string属性,在这里暂时可以理解为就是赋值。
58
59 //如果脚本是在命令行模式下运行的话,那么参数就是通过$_SERVER['argv']来传递。下面的
60 //$this->_parse_cli_args();就是拿到符合我们需要的路由相关的一些参数鸟~如果大部分
61 //情况你没用命令行执行脚本的话,下面这个if暂时可以不用管。
62 if (php_sapi_name() == 'cli' or defined('STDIN'))
63 {
64 $this->_set_uri_string($this->_parse_cli_args());
65 return;
66 }
67
68 //这种REQUEST_URI方式相对复杂一点,因此封装在$this->_detect_uri();里面。
69 //其实大多数情况下,利用REQUEST URI和SCRIPT NAME都会得到我们想要的路径信息了。
70 if ($uri = $this->_detect_uri())
71 {
72 $this->_set_uri_string($uri);
73 return;
74 }
75
76 //PATH_INFO方式,个人觉得这种方式最经济,只是不是每次请求都有$_SERVER['PATH_INFO']这个变量。
77 $path = (isset($_SERVER['PATH_INFO'])) ? $_SERVER['PATH_INFO'] : @getenv('PATH_INFO');
78 if (trim($path, '/') != '' && $path != "/".SELF)
79 {
80 $this->_set_uri_string($path);
81 return;
82 }
83
84 //如果是用QUERY_STRING的话,路径格式一般为index.php?/controller/method/xxx/xxx
85 $path = (isset($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : @getenv('QUERY_STRING');
86 if (trim($path, '/') != '')
87 {
88 $this->_set_uri_string($path);
89 return;
90 }
91
92 //上面的方法都不行,那真是奇怪了。。所以尝试最后一种奇葩的方法,就是从$_GET里面把那个键名拿出来。
93 if (is_array($_GET) && count($_GET) == 1 && trim(key($_GET), '/') != '')
94 {
95 $this->_set_uri_string(key($_GET));
96 return;
97 }
98
99 // We've exhausted all our options...
100 $this->uri_string = '';
101 return;
102 }
103
104 //厄,这里是因为上面那个获得uri_protocol配置的语句写在if里面,然后又没赋值到某个变量,所以这里要再写一次了
105 //可能是因为大多数情况下,我们都是选择AUTO方式吧。但是,这样写又何必呢。。
106 $uri = strtoupper($this->config->item('uri_protocol'));
107
108 //其实就是按规定的方式去找路径而已。。
109 if ($uri == 'REQUEST_URI')
110 {
111 $this->_set_uri_string($this->_detect_uri());
112 return;
113 }
114 elseif ($uri == 'CLI')
115 {
116 $this->_set_uri_string($this->_parse_cli_args());
117 return;
118 }
119
120 //如果你在配置文件config.php里面把这个uri_protocol定义成一种上面都没有的方式,那么就会执行下面的代码。
121 //意思是,就看$_SERVER有没有这个uri_protocol的变量了,有就给,没有就拉倒。
122 $path = (isset($_SERVER[$uri])) ? $_SERVER[$uri] : @getenv($uri);
123 $this->_set_uri_string($path);
124 }
125
126 // --------------------------------------------------------------------
127
128 /**
129 * Set the URI String
130 */
131 function _set_uri_string($str)
132 {
133 // Filter out control characters
134 $str = remove_invisible_characters($str, FALSE);
135
136 // If the URI contains only a slash we'll kill it
137 $this->uri_string = ($str == '/') ? '' : $str;
138 }
139
140 // --------------------------------------------------------------------
141
142 /**
143 * Detects the URI
144 */
145 private function _detect_uri()
146 {
147 //如果这两个值缺少其中一个,那么这种方法行不通。
148 if ( ! isset($_SERVER['REQUEST_URI']) OR ! isset($_SERVER['SCRIPT_NAME']))
149 {
150 return '';
151 }
152
153 $uri = $_SERVER['REQUEST_URI'];//取得request_uri
154
155 //注意下面这个是===0不是false! 接下来这个if 和下面的elseif分别是script_name有文件名和没有文件名(如
156 //http://abc.com/CI/或者http://abc.com/CI/?c=index&m=welcome等)的不同情况的处理。
157 if (strpos($uri, $_SERVER['SCRIPT_NAME']) === 0)
158 {
159 //去掉共同部分,取得对路由有用的部分。
160 $uri = substr($uri, strlen($_SERVER['SCRIPT_NAME']));
161 }
162 elseif (strpos($uri, dirname($_SERVER['SCRIPT_NAME'])) === 0)
163 {
164 //作用同上
165 $uri = substr($uri, strlen(dirname($_SERVER['SCRIPT_NAME'])));
166 }
167
168 //这里是为兼容?/abc/xx/的形式。
169 if (strncmp($uri, '?/', 2) === 0)
170 {
171 $uri = substr($uri, 2);
172 }
173
174 //在这里$uri可能是?xxx=xx的形式,也可能是直接xxx=xx,也可能是/
175 $parts = preg_split('#\?#i', $uri, 2);
176 $uri = $parts[0];
177 //如果是能通过上述的正则分割出两段,那么,是通过query_string即?的形式进行路由访问
178 if (isset($parts[1]))
179 {
180 $_SERVER['QUERY_STRING'] = $parts[1];
181 parse_str($_SERVER['QUERY_STRING'], $_GET);
182 }
183 else
184 {
185 $_SERVER['QUERY_STRING'] = '';
186 $_GET = array();
187 }
188
189 //如果为/,或者为空,有两种情况,要么就是通过query_string,所以此时$parts[0]就是等于下面两种可能,同时我们
190 //已经通过$parts[1]拿到要拿的信息,则可以返回。要么就是以段的形式,但是段的信息为空,即直接访问入口文件而没有
191 //任何路由信息的传递,也可以直接返回。
192 if ($uri == '/' || empty($uri))
193 {
194 return '/';
195 }
196
197 //这里我个人觉得是上面的strpos($uri, $_SERVER['SCRIPT_NAME']) === 0和elseif都无法匹配的时候,
198 //返回这个url的path部分。
199 $uri = parse_url($uri, PHP_URL_PATH);
200
201 // Do some final cleaning of the URI and return it
202 return str_replace(array('//', '../'), '/', trim($uri, '/'));
203 }
204
205 // --------------------------------------------------------------------
206
207 /**
208 * Parse cli arguments
209 */
210 private function _parse_cli_args()
211 {
212 //返回在命令行模式下运行时传递的参数。
213 $args = array_slice($_SERVER['argv'], 1);//因为第一个参数是当前文件名,所以从第二个开始才是我们要获取的。
214
215 //返回一个由'/'字符串拼接的字符串,因为$this->uri_string是一个字符串。
216 return $args ? '/' . implode('/', $args) : '';
217 }
218
219 // --------------------------------------------------------------------
220
221 /**
222 * Filter segments for malicious characters
223 */
224 //过滤不合法字符
225 function _filter_uri($str)
226 {
227 if ($str != '' && $this->config->item('permitted_uri_chars') != '' && $this->config->item('enable_query_strings') == FALSE)
228 {
229 // preg_quote() in PHP 5.3 escapes -, so the str_replace() and addition of - to preg_quote() is to maintain backwards
230 // compatibility as many are unaware of how characters in the permitted_uri_chars will be parsed as a regex pattern
231 if ( ! preg_match("|^[".str_replace(array('\\-', '\-'), '-', preg_quote($this->config->item('permitted_uri_chars'), '-'))."]+$|i", $str))
232 {
233 show_error('The URI you submitted has disallowed characters.', 400);
234 }
235 }
236
237 // Convert programatic characters to entities
238 $bad = array('$', '(', ')', '%28', '%29');
239 $good = array('$', '(', ')', '(', ')');
240
241 return str_replace($bad, $good, $str);
242 }
243
244 // --------------------------------------------------------------------
245
246 /**
247 * Remove the suffix from the URL if needed
248 */
249 //去掉url的我们自定义的后缀。
250 function _remove_url_suffix()
251 {
252
253 if ($this->config->item('url_suffix') != "")
254 {
255 $this->uri_string = preg_replace("|".preg_quote($this->config->item('url_suffix'))."$|", "", $this->uri_string);
256 }
257 }
258
259 // --------------------------------------------------------------------
260
261 /**
262 * Explode the URI Segments. The individual segments will
263 * be stored in the $this->segments array.
264 */
265 //把uri_string拆成段(同时对各段进行过滤),保存到URI::$segments中。
266 function _explode_segments()
267 {
268 foreach (explode("/", preg_replace("|/*(.+?)/*$|", "\\1", $this->uri_string)) as $val)
269 {
270 // Filter segments for security
271 $val = trim($this->_filter_uri($val));
272
273 if ($val != '')
274 {
275 $this->segments[] = $val;
276 }
277 }
278 }
279
280 // --------------------------------------------------------------------
281 /**
282 * Re-index Segments
283 */
284 //使得出来的段以下标1开始保存。
285 function _reindex_segments()
286 {
287 array_unshift($this->segments, NULL);
288 array_unshift($this->rsegments, NULL);
289 unset($this->segments[0]);
290 unset($this->rsegments[0]);
291 }
292
293 // --------------------------------------------------------------------
294
295 /**
296 * Fetch a URI Segment
297 */
298 //返回某一段
299 function segment($n, $no_result = FALSE)
300 {
301 return ( ! isset($this->segments[$n])) ? $no_result : $this->segments[$n];
302 }
303
304 // --------------------------------------------------------------------
305
306 /**
307 * Fetch a URI "routed" Segment
308 */
309 //返回确定路由后的某一段
310 function rsegment($n, $no_result = FALSE)
311 {
312 return ( ! isset($this->rsegments[$n])) ? $no_result : $this->rsegments[$n];
313 }
314
315 // --------------------------------------------------------------------
316
317 /**
318 * Generate a key value pair from the URI string
319 */
320 function uri_to_assoc($n = 3, $default = array())
321 {
322 return $this->_uri_to_assoc($n, $default, 'segment');
323 }
324 /**
325 * Identical to above only it uses the re-routed segment array
326 */
327 function ruri_to_assoc($n = 3, $default = array())
328 {
329 return $this->_uri_to_assoc($n, $default, 'rsegment');
330 }
331
332 // --------------------------------------------------------------------
333
334 /**
335 * Generate a key value pair from the URI string or Re-routed URI string
336 */
337 function _uri_to_assoc($n = 3, $default = array(), $which = 'segment')
338 {
339 if ($which == 'segment')
340 {
341 $total_segments = 'total_segments';
342 $segment_array = 'segment_array';
343 }
344 else
345 {
346 $total_segments = 'total_rsegments';
347 $segment_array = 'rsegment_array';
348 }
349
350 if ( ! is_numeric($n))
351 {
352 return $default;
353 }
354
355 if (isset($this->keyval[$n]))
356 {
357 return $this->keyval[$n];
358 }
359
360 if ($this->$total_segments() < $n)
361 {
362 if (count($default) == 0)
363 {
364 return array();
365 }
366
367 $retval = array();
368 foreach ($default as $val)
369 {
370 $retval[$val] = FALSE;
371 }
372 return $retval;
373 }
374
375 $segments = array_slice($this->$segment_array(), ($n - 1));
376
377 $i = 0;
378 $lastval = '';
379 $retval = array();
380 foreach ($segments as $seg)
381 {
382 if ($i % 2)
383 {
384 $retval[$lastval] = $seg;
385 }
386 else
387 {
388 $retval[$seg] = FALSE;
389 $lastval = $seg;
390 }
391
392 $i++;
393 }
394
395 if (count($default) > 0)
396 {
397 foreach ($default as $val)
398 {
399 if ( ! array_key_exists($val, $retval))
400 {
401 $retval[$val] = FALSE;
402 }
403 }
404 }
405
406 // Cache the array for reuse
407 $this->keyval[$n] = $retval;
408 return $retval;
409 }
410
411 // --------------------------------------------------------------------
412
413 /**
414 * Generate a URI string from an associative array
415 */
416 function assoc_to_uri($array)
417 {
418 $temp = array();
419 foreach ((array)$array as $key => $val)
420 {
421 $temp[] = $key;
422 $temp[] = $val;
423 }
424
425 return implode('/', $temp);
426 }
427
428 // --------------------------------------------------------------------
429
430 /**
431 * Fetch a URI Segment and add a trailing slash
432 */
433 function slash_segment($n, $where = 'trailing')
434 {
435 return $this->_slash_segment($n, $where, 'segment');
436 }
437
438 // --------------------------------------------------------------------
439
440 /**
441 * Fetch a URI Segment and add a trailing slash
442 */
443 function slash_rsegment($n, $where = 'trailing')
444 {
445 return $this->_slash_segment($n, $where, 'rsegment');
446 }
447
448 // --------------------------------------------------------------------
449
450 /**
451 * Fetch a URI Segment and add a trailing slash - helper function
452 */
453 function _slash_segment($n, $where = 'trailing', $which = 'segment')
454 {
455 $leading = '/';
456 $trailing = '/';
457
458 if ($where == 'trailing')
459 {
460 $leading = '';
461 }
462 elseif ($where == 'leading')
463 {
464 $trailing = '';
465 }
466
467 return $leading.$this->$which($n).$trailing;
468 }
469
470 // --------------------------------------------------------------------
471
472 /**
473 * Segment Array
474 */
475 function segment_array()
476 {
477 return $this->segments;
478 }
479
480 // --------------------------------------------------------------------
481
482 /**
483 * Routed Segment Array
484 */
485 function rsegment_array()
486 {
487 return $this->rsegments;
488 }
489
490 // --------------------------------------------------------------------
491
492 /**
493 * Total number of segments
494 */
495 function total_segments()
496 {
497 return count($this->segments);
498 }
499
500 // --------------------------------------------------------------------
501
502 /**
503 * Total number of routed segments
504 */
505 function total_rsegments()
506 {
507 return count($this->rsegments);
508 }
509
510 // --------------------------------------------------------------------
511
512 /**
513 * Fetch the entire URI string
514 */
515 function uri_string()
516 {
517 return $this->uri_string;
518 }
519
520
521 // --------------------------------------------------------------------
522
523 /**
524 * Fetch the entire Re-routed URI string
525 */
526 function ruri_string()
527 {
528 return '/'.implode('/', $this->rsegment_array());
529 }
530
531 }