Development

/branches/1.0/lib/controller/sfRouting.class.php

You must first sign up to be able to contribute.

root/branches/1.0/lib/controller/sfRouting.class.php

Revision 6614, 15.5 kB (checked in by noel, 6 years ago)

fixed magic_quotes_gpc() handling in sfRouting (closes #1801)

  • Property svn:mime-type set to text/x-php
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Rev Date
Line 
1 <?php
2
3 /*
4  * This file is part of the symfony package.
5  * (c) 2004-2006 Fabien Potencier <fabien.potencier@symfony-project.com>
6  *
7  * For the full copyright and license information, please view the LICENSE
8  * file that was distributed with this source code.
9  */
10
11 /**
12  * sfRouting class controls the creation of URLs and parses URLs. It maps an array of parameters to URLs definition.
13  * Each map is called a route.
14  * It implements the Singleton pattern.
15  *
16  * Routing can be disabled when [sf_routing] is set to false.
17  *
18  * This class is based on the Routes class of Cake framework.
19  *
20  * @package    symfony
21  * @subpackage controller
22  * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
23  * @version    SVN: $Id$
24  */
25 class sfRouting
26 {
27   protected static
28     $instance           = null;
29
30   protected
31     $current_route_name = '',
32     $routes             = array();
33
34   /**
35   * Retrieve the singleton instance of this class.
36    *
37    * @return  sfRouting The sfRouting implementation instance
38    */
39   public static function getInstance()
40   {
41     if (!isset(self::$instance))
42     {
43       self::$instance = new sfRouting();
44     }
45
46     return self::$instance;
47   }
48
49   /**
50    * Sets the current route name.
51    *
52    * @param string The route name
53    */
54   protected function setCurrentRouteName($name)
55   {
56     $this->current_route_name = $name;
57   }
58
59   /**
60    * Gets the current route name.
61    *
62    * @return string The route name
63    */
64   public function getCurrentRouteName()
65   {
66     return $this->current_route_name;
67   }
68
69   /**
70    * Gets the internal URI for the current request.
71    *
72    * @param boolean Whether to give an internal URI with the route name (@route)
73    *                or with the module/action pair
74    *
75    * @return string The current internal URI
76    */
77   public function getCurrentInternalUri($with_route_name = false)
78   {
79     if ($this->current_route_name)
80     {
81       list($url, $regexp, $names, $names_hash, $defaults, $requirements, $suffix) = $this->routes[$this->current_route_name];
82
83       $request = sfContext::getInstance()->getRequest();
84
85       if ($with_route_name)
86       {
87         $internal_uri = '@'.$this->current_route_name;
88       }
89       else
90       {
91         $internal_uri = $request->getParameter('module', isset($defaults['module']) ? $defaults['module'] : '').'/'.$request->getParameter('action', isset($defaults['action']) ? $defaults['action'] : '');
92       }
93
94       $params = array();
95
96       // add parameters
97       foreach ($names as $name)
98       {
99         if ($name == 'module' || $name == 'action') continue;
100
101         $params[] = $name.'='.$request->getParameter($name, isset($defaults[$name]) ? $defaults[$name] : '');
102       }
103
104       // add * parameters if needed
105       if (strpos($url, '*'))
106       {
107         foreach ($request->getParameterHolder()->getAll() as $key => $value)
108         {
109           if ($key == 'module' || $key == 'action' || in_array($key, $names))
110           {
111             continue;
112           }
113
114           $params[] = $key.'='.$value;
115         }
116       }
117
118       // sort to guaranty unicity
119       sort($params);
120
121       return $internal_uri.($params ? '?'.implode('&', $params) : '');
122     }
123   }
124
125   /**
126    * Gets the current compiled route array.
127    *
128    * @return array The route array
129    */
130   public function getRoutes()
131   {
132     return $this->routes;
133   }
134
135   /**
136    * Sets the compiled route array.
137    *
138    * @param array The route array
139    *
140    * @return array The route array
141    */
142   public function setRoutes($routes)
143   {
144     return $this->routes = $routes;
145   }
146
147   /**
148    * Returns true if this instance has some routes.
149    *
150    * @return  boolean
151    */
152   public function hasRoutes()
153   {
154     return count($this->routes) ? true : false;
155   }
156
157   /**
158    * Returns true if the route name given is defined.
159    *
160    * @param string The route name
161    *
162    * @return  boolean
163    */
164   public function hasRouteName($name)
165   {
166     return isset($this->routes[$name]) ? true : false;
167   }
168
169   /**
170    * Gets a route by its name.
171    *
172    * @param string The route name
173    *
174    * @return  array A route array
175    */
176   public function getRouteByName($name)
177   {
178     if ($name[0] == '@')
179     {
180       $name = substr($name, 1);
181     }
182
183     if (!isset($this->routes[$name]))
184     {
185       $error = 'The route "%s" does not exist';
186       $error = sprintf($error, $name);
187
188       throw new sfConfigurationException($error);
189     }
190
191     return $this->routes[$name];
192   }
193
194   /**
195    * Clears all current routes.
196    */
197   public function clearRoutes()
198   {
199     if (sfConfig::get('sf_logging_enabled'))
200     {
201       sfLogger::getInstance()->info('{sfRouting} clear all current routes');
202     }
203
204     $this->routes = array();
205   }
206
207   /**
208    * Adds a new route at the beginning of the current list of routes.
209    *
210    * @see connect
211    */
212   public function prependRoute($name, $route, $default = array(), $requirements = array())
213   {
214     $routes = $this->routes;
215     $this->routes = array();
216     $newroutes = $this->connect($name, $route, $default, $requirements);
217     $this->routes = array_merge($newroutes, $routes);
218
219     return $this->routes;
220   }
221
222   /**
223    * Adds a new route.
224    *
225    * Alias for the connect method.
226    *
227    * @see connect
228    */
229   public function appendRoute($name, $route, $default = array(), $requirements = array())
230   {
231     return $this->connect($name, $route, $default, $requirements);
232   }
233
234  /**
235   * Adds a new route at the end of the current list of routes.
236   *
237   * A route string is a string with 2 special constructions:
238   * - :string: :string denotes a named paramater (available later as $request->getParameter('string'))
239   * - *: * match an indefinite number of parameters in a route
240   *
241   * Here is a very common rule in a symfony project:
242   *
243   * <code>
244   * $r->connect('/:module/:action/*');
245   * </code>
246   *
247   * @param  string The route name
248   * @param  string The route string
249   * @param  array  The default parameter values
250   * @param  array  The regexps parameters must match
251   *
252   * @return array  current routes
253   */
254   public function connect($name, $route, $default = array(), $requirements = array())
255   {
256     // route already exists?
257     if (isset($this->routes[$name]))
258     {
259       $error = 'This named route already exists ("%s").';
260       $error = sprintf($error, $name);
261
262       throw new sfConfigurationException($error);
263     }
264
265     $parsed = array();
266     $names  = array();
267     $suffix = (($sf_suffix = sfConfig::get('sf_suffix')) == '.') ? '' : $sf_suffix;
268
269     // used for performance reasons
270     $names_hash = array();
271
272     $r = null;
273     if (($route == '') || ($route == '/'))
274     {
275       $regexp = '/^[\/]*$/';
276       $this->routes[$name] = array($route, $regexp, array(), array(), $default, $requirements, $suffix);
277     }
278     else
279     {
280       $elements = array();
281       foreach (explode('/', $route) as $element)
282       {
283         if (trim($element))
284         {
285           $elements[] = $element;
286         }
287       }
288
289       if (!isset($elements[0]))
290       {
291         return false;
292       }
293
294       // specific suffix for this route?
295       // or /$ directory
296       if (preg_match('/^(.+)(\.\w*)$/i', $elements[count($elements) - 1], $matches))
297       {
298         $suffix = ($matches[2] == '.') ? '' : $matches[2];
299         $elements[count($elements) - 1] = $matches[1];
300         $route = '/'.implode('/', $elements);
301       }
302       else if ($route{strlen($route) - 1} == '/')
303       {
304         $suffix = '/';
305       }
306
307       $regexp_suffix = preg_quote($suffix);
308
309       foreach ($elements as $element)
310       {
311         if (preg_match('/^:(.+)$/', $element, $r))
312         {
313           $element = $r[1];
314
315           // regex is [^\/]+ or the requirement regex
316           if (isset($requirements[$element]))
317           {
318             $regex = $requirements[$element];
319             if (0 === strpos($regex, '^'))
320             {
321               $regex = substr($regex, 1);
322             }
323             if (strlen($regex) - 1 === strpos($regex, '$'))
324             {
325               $regex = substr($regex, 0, -1);
326             }
327           }
328           else
329           {
330             $regex = '[^\/]+';
331           }
332
333           $parsed[] = '(?:\/('.$regex.'))?';
334           $names[] = $element;
335           $names_hash[$element] = 1;
336         }
337         elseif (preg_match('/^\*$/', $element, $r))
338         {
339           $parsed[] = '(?:\/(.*))?';
340         }
341         else
342         {
343           $parsed[] = '/'.$element;
344         }
345       }
346       $regexp = '#^'.join('', $parsed).$regexp_suffix.'$#';
347
348       $this->routes[$name] = array($route, $regexp, $names, $names_hash, $default, $requirements, $suffix);
349     }
350
351     if (sfConfig::get('sf_logging_enabled'))
352     {
353       sfLogger::getInstance()->info('{sfRouting} connect "'.$route.'"'.($suffix ? ' ("'.$suffix.'" suffix)' : ''));
354     }
355
356     return $this->routes;
357   }
358
359  /**
360   * Generates a valid URLs for parameters.
361   *
362   * @param  array  The parameter values
363   * @param  string The divider between key/value pairs
364   * @param  string The equal sign to use between key and value
365   *
366   * @return string The generated URL
367   */
368   public function generate($name, $params, $querydiv = '/', $divider = '/', $equals = '/')
369   {
370     $global_defaults = sfConfig::get('sf_routing_defaults', null);
371
372     // named route?
373     if ($name)
374     {
375       if (!isset($this->routes[$name]))
376       {
377         $error = 'The route "%s" does not exist.';
378         $error = sprintf($error, $name);
379
380         throw new sfConfigurationException($error);
381       }
382
383       list($url, $regexp, $names, $names_hash, $defaults, $requirements, $suffix) = $this->routes[$name];
384       if ($global_defaults !== null)
385       {
386         $defaults = array_merge($defaults, $global_defaults);
387       }
388
389       // all params must be given
390       foreach ($names as $tmp)
391       {
392         if (!isset($params[$tmp]) && !isset($defaults[$tmp]))
393         {
394           throw new sfException(sprintf('Route named "%s" have a mandatory "%s" parameter', $name, $tmp));
395         }
396       }
397     }
398     else
399     {
400       // find a matching route
401       $found = false;
402       foreach ($this->routes as $name => $route)
403       {
404         list($url, $regexp, $names, $names_hash, $defaults, $requirements, $suffix) = $route;
405         if ($global_defaults !== null)
406         {
407           $defaults = array_merge($defaults, $global_defaults);
408         }
409
410         $tparams = array_merge($defaults, $params);
411
412         // we must match all names (all $names keys must be in $params array)
413         foreach ($names as $key)
414         {
415           if (!isset($tparams[$key])) continue 2;
416         }
417
418         // we must match all defaults with value except if present in names
419         foreach ($defaults as $key => $value)
420         {
421           if (isset($names_hash[$key])) continue;
422
423           if (!isset($tparams[$key]) || $tparams[$key] != $value) continue 2;
424         }
425
426         // we must match all requirements for rule
427         foreach ($requirements as $req_param => $req_regexp)
428         {
429           if (!preg_match('/'.str_replace('/', '\\/', $req_regexp).'/', $tparams[$req_param]))
430           {
431             continue 2;
432           }
433         }
434
435         // we must have consumed all $params keys if there is no * in route
436         if (!strpos($url, '*'))
437         {
438           if (count(array_diff(array_keys($tparams), $names, array_keys($defaults))))
439           {
440             continue;
441           }
442         }
443
444         // match found
445         $found = true;
446         break;
447       }
448
449       if (!$found)
450       {
451         $error = 'Unable to find a matching routing rule to generate url for params "%s".';
452         $error = sprintf($error, var_export($params, true));
453
454         throw new sfConfigurationException($error);
455       }
456     }
457
458     $params = sfToolkit::arrayDeepMerge($defaults, $params);
459
460     $real_url = preg_replace('/\:([^\/]+)/e', 'urlencode($params["\\1"])', $url);
461
462     // we add all other params if *
463     if (strpos($real_url, '*'))
464     {
465       $tmp = array();
466       foreach ($params as $key => $value)
467       {
468         if (isset($names_hash[$key]) || isset($defaults[$key])) continue;
469
470         if (is_array($value))
471         {
472           foreach ($value as $v)
473           {
474             $tmp[] = $key.$equals.urlencode($v);
475           }
476         }
477         else
478         {
479           $tmp[] = urlencode($key).$equals.urlencode($value);
480         }
481       }
482       $tmp = implode($divider, $tmp);
483       if (strlen($tmp) > 0)
484       {
485         $tmp = $querydiv.$tmp;
486       }
487       $real_url = preg_replace('/\/\*(\/|$)/', "$tmp$1", $real_url);
488     }
489
490     // strip off last divider character
491     if (strlen($real_url) > 1)
492     {
493       $real_url = rtrim($real_url, $divider);
494     }
495
496     if ($real_url != '/')
497     {
498       $real_url .= $suffix;
499     }
500
501     return $real_url;
502   }
503
504  /**
505   * Parses a URL to find a matching route.
506   *
507   * Returns null if no route match the URL.
508   *
509   * @param  string URL to be parsed
510   *
511   * @return array  An array of parameters
512   */
513   public function parse($url)
514   {
515     // an URL should start with a '/', mod_rewrite doesn't respect that, but no-mod_rewrite version does.
516     if ($url && ('/' != $url[0]))
517     {
518       $url = '/'.$url;
519     }
520
521     // we remove the query string
522     if ($pos = strpos($url, '?'))
523     {
524       $url = substr($url, 0, $pos);
525     }
526
527     // we remove multiple /
528     $url = preg_replace('#/+#', '/', $url);
529     foreach ($this->routes as $route_name => $route)
530     {
531       $out = array();
532       $r = null;
533
534       list($route, $regexp, $names, $names_hash, $defaults, $requirements, $suffix) = $route;
535
536       $break = false;
537
538       if (preg_match($regexp, $url, $r))
539       {
540         $break = true;
541
542         // remove the first element, which is the url
543         array_shift($r);
544
545         // hack, pre-fill the default route names
546         foreach ($names as $name)
547         {
548           $out[$name] = null;
549         }
550
551         // defaults
552         foreach ($defaults as $name => $value)
553         {
554           if (preg_match('#[a-z_\-]#i', $name))
555           {
556             $out[$name] = urldecode($value);
557           }
558           else
559           {
560             $out[$value] = true;
561           }
562         }
563
564         $pos = 0;
565         foreach ($r as $found)
566         {
567           // if $found is a named url element (i.e. ':action')
568           if (isset($names[$pos]))
569           {
570             $out[$names[$pos]] = urldecode($found);
571           }
572           // unnamed elements go in as 'pass'
573           else
574           {
575             $pass = explode('/', $found);
576             $found = '';
577             for ($i = 0, $max = count($pass); $i < $max; $i += 2)
578             {
579               if (!isset($pass[$i + 1])) continue;
580
581               $found .= $pass[$i].'='.$pass[$i + 1].'&';
582             }
583
584             parse_str($found, $pass);
585
586             if (get_magic_quotes_gpc())
587             {
588               $pass = sfToolkit::stripslashesDeep((array) $pass);
589             }
590             
591             foreach ($pass as $key => $value)
592             {
593               // we add this parameters if not in conflict with named url element (i.e. ':action')
594               if (!isset($names_hash[$key]))
595               {
596                 $out[$key] = $value;
597               }
598             }
599           }
600           $pos++;
601         }
602
603         // we must have found all :var stuffs in url? except if default values exists
604         foreach ($names as $name)
605         {
606           if ($out[$name] == null)
607           {
608             $break = false;
609           }
610         }
611
612         if ($break)
613         {
614           // we store route name
615           $this->setCurrentRouteName($route_name);
616
617           if (sfConfig::get('sf_logging_enabled'))
618           {
619             sfLogger::getInstance()->info('{sfRouting} match route ['.$route_name.'] "'.$route.'"');
620           }
621
622           break;
623         }
624       }
625     }
626
627     // no route found
628     if (!$break)
629     {
630       if (sfConfig::get('sf_logging_enabled'))
631       {
632         sfLogger::getInstance()->info('{sfRouting} no matching route found');
633       }
634
635       return null;
636     }
637
638     return $out;
639   }
640 }
641
Note: See TracBrowser for help on using the browser.