|
1 <?php |
|
2 /** |
|
3 * Zend Framework |
|
4 * |
|
5 * LICENSE |
|
6 * |
|
7 * This source file is subject to the new BSD license that is bundled |
|
8 * with this package in the file LICENSE.txt. |
|
9 * It is also available through the world-wide-web at this URL: |
|
10 * http://framework.zend.com/license/new-bsd |
|
11 * If you did not receive a copy of the license and are unable to |
|
12 * obtain it through the world-wide-web, please send an email |
|
13 * to license@zend.com so we can send you a copy immediately. |
|
14 * |
|
15 * @category Zend |
|
16 * @package Zend_Controller |
|
17 * @subpackage Router |
|
18 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) |
|
19 * @version $Id: Route.php 20096 2010-01-06 02:05:09Z bkarwin $ |
|
20 * @license http://framework.zend.com/license/new-bsd New BSD License |
|
21 */ |
|
22 |
|
23 /** Zend_Controller_Router_Route_Abstract */ |
|
24 require_once 'Zend/Controller/Router/Route/Abstract.php'; |
|
25 |
|
26 /** |
|
27 * Route |
|
28 * |
|
29 * @package Zend_Controller |
|
30 * @subpackage Router |
|
31 * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) |
|
32 * @license http://framework.zend.com/license/new-bsd New BSD License |
|
33 * @see http://manuals.rubyonrails.com/read/chapter/65 |
|
34 */ |
|
35 class Zend_Controller_Router_Route extends Zend_Controller_Router_Route_Abstract |
|
36 { |
|
37 /** |
|
38 * Default translator |
|
39 * |
|
40 * @var Zend_Translate |
|
41 */ |
|
42 protected static $_defaultTranslator; |
|
43 |
|
44 /** |
|
45 * Translator |
|
46 * |
|
47 * @var Zend_Translate |
|
48 */ |
|
49 protected $_translator; |
|
50 |
|
51 /** |
|
52 * Default locale |
|
53 * |
|
54 * @var mixed |
|
55 */ |
|
56 protected static $_defaultLocale; |
|
57 |
|
58 /** |
|
59 * Locale |
|
60 * |
|
61 * @var mixed |
|
62 */ |
|
63 protected $_locale; |
|
64 |
|
65 /** |
|
66 * Wether this is a translated route or not |
|
67 * |
|
68 * @var boolean |
|
69 */ |
|
70 protected $_isTranslated = false; |
|
71 |
|
72 /** |
|
73 * Translatable variables |
|
74 * |
|
75 * @var array |
|
76 */ |
|
77 protected $_translatable = array(); |
|
78 |
|
79 protected $_urlVariable = ':'; |
|
80 protected $_urlDelimiter = '/'; |
|
81 protected $_regexDelimiter = '#'; |
|
82 protected $_defaultRegex = null; |
|
83 |
|
84 /** |
|
85 * Holds names of all route's pattern variable names. Array index holds a position in URL. |
|
86 * @var array |
|
87 */ |
|
88 protected $_variables = array(); |
|
89 |
|
90 /** |
|
91 * Holds Route patterns for all URL parts. In case of a variable it stores it's regex |
|
92 * requirement or null. In case of a static part, it holds only it's direct value. |
|
93 * In case of a wildcard, it stores an asterisk (*) |
|
94 * @var array |
|
95 */ |
|
96 protected $_parts = array(); |
|
97 |
|
98 /** |
|
99 * Holds user submitted default values for route's variables. Name and value pairs. |
|
100 * @var array |
|
101 */ |
|
102 protected $_defaults = array(); |
|
103 |
|
104 /** |
|
105 * Holds user submitted regular expression patterns for route's variables' values. |
|
106 * Name and value pairs. |
|
107 * @var array |
|
108 */ |
|
109 protected $_requirements = array(); |
|
110 |
|
111 /** |
|
112 * Associative array filled on match() that holds matched path values |
|
113 * for given variable names. |
|
114 * @var array |
|
115 */ |
|
116 protected $_values = array(); |
|
117 |
|
118 /** |
|
119 * Associative array filled on match() that holds wildcard variable |
|
120 * names and values. |
|
121 * @var array |
|
122 */ |
|
123 protected $_wildcardData = array(); |
|
124 |
|
125 /** |
|
126 * Helper var that holds a count of route pattern's static parts |
|
127 * for validation |
|
128 * @var int |
|
129 */ |
|
130 protected $_staticCount = 0; |
|
131 |
|
132 public function getVersion() { |
|
133 return 1; |
|
134 } |
|
135 |
|
136 /** |
|
137 * Instantiates route based on passed Zend_Config structure |
|
138 * |
|
139 * @param Zend_Config $config Configuration object |
|
140 */ |
|
141 public static function getInstance(Zend_Config $config) |
|
142 { |
|
143 $reqs = ($config->reqs instanceof Zend_Config) ? $config->reqs->toArray() : array(); |
|
144 $defs = ($config->defaults instanceof Zend_Config) ? $config->defaults->toArray() : array(); |
|
145 return new self($config->route, $defs, $reqs); |
|
146 } |
|
147 |
|
148 /** |
|
149 * Prepares the route for mapping by splitting (exploding) it |
|
150 * to a corresponding atomic parts. These parts are assigned |
|
151 * a position which is later used for matching and preparing values. |
|
152 * |
|
153 * @param string $route Map used to match with later submitted URL path |
|
154 * @param array $defaults Defaults for map variables with keys as variable names |
|
155 * @param array $reqs Regular expression requirements for variables (keys as variable names) |
|
156 * @param Zend_Translate $translator Translator to use for this instance |
|
157 */ |
|
158 public function __construct($route, $defaults = array(), $reqs = array(), Zend_Translate $translator = null, $locale = null) |
|
159 { |
|
160 $route = trim($route, $this->_urlDelimiter); |
|
161 $this->_defaults = (array) $defaults; |
|
162 $this->_requirements = (array) $reqs; |
|
163 $this->_translator = $translator; |
|
164 $this->_locale = $locale; |
|
165 |
|
166 if ($route !== '') { |
|
167 foreach (explode($this->_urlDelimiter, $route) as $pos => $part) { |
|
168 if (substr($part, 0, 1) == $this->_urlVariable && substr($part, 1, 1) != $this->_urlVariable) { |
|
169 $name = substr($part, 1); |
|
170 |
|
171 if (substr($name, 0, 1) === '@' && substr($name, 1, 1) !== '@') { |
|
172 $name = substr($name, 1); |
|
173 $this->_translatable[] = $name; |
|
174 $this->_isTranslated = true; |
|
175 } |
|
176 |
|
177 $this->_parts[$pos] = (isset($reqs[$name]) ? $reqs[$name] : $this->_defaultRegex); |
|
178 $this->_variables[$pos] = $name; |
|
179 } else { |
|
180 if (substr($part, 0, 1) == $this->_urlVariable) { |
|
181 $part = substr($part, 1); |
|
182 } |
|
183 |
|
184 if (substr($part, 0, 1) === '@' && substr($part, 1, 1) !== '@') { |
|
185 $this->_isTranslated = true; |
|
186 } |
|
187 |
|
188 $this->_parts[$pos] = $part; |
|
189 |
|
190 if ($part !== '*') { |
|
191 $this->_staticCount++; |
|
192 } |
|
193 } |
|
194 } |
|
195 } |
|
196 } |
|
197 |
|
198 /** |
|
199 * Matches a user submitted path with parts defined by a map. Assigns and |
|
200 * returns an array of variables on a successful match. |
|
201 * |
|
202 * @param string $path Path used to match against this routing map |
|
203 * @return array|false An array of assigned values or a false on a mismatch |
|
204 */ |
|
205 public function match($path, $partial = false) |
|
206 { |
|
207 if ($this->_isTranslated) { |
|
208 $translateMessages = $this->getTranslator()->getMessages(); |
|
209 } |
|
210 |
|
211 $pathStaticCount = 0; |
|
212 $values = array(); |
|
213 $matchedPath = ''; |
|
214 |
|
215 if (!$partial) { |
|
216 $path = trim($path, $this->_urlDelimiter); |
|
217 } |
|
218 |
|
219 if ($path !== '') { |
|
220 $path = explode($this->_urlDelimiter, $path); |
|
221 |
|
222 foreach ($path as $pos => $pathPart) { |
|
223 // Path is longer than a route, it's not a match |
|
224 if (!array_key_exists($pos, $this->_parts)) { |
|
225 if ($partial) { |
|
226 break; |
|
227 } else { |
|
228 return false; |
|
229 } |
|
230 } |
|
231 |
|
232 $matchedPath .= $pathPart . $this->_urlDelimiter; |
|
233 |
|
234 // If it's a wildcard, get the rest of URL as wildcard data and stop matching |
|
235 if ($this->_parts[$pos] == '*') { |
|
236 $count = count($path); |
|
237 for($i = $pos; $i < $count; $i+=2) { |
|
238 $var = urldecode($path[$i]); |
|
239 if (!isset($this->_wildcardData[$var]) && !isset($this->_defaults[$var]) && !isset($values[$var])) { |
|
240 $this->_wildcardData[$var] = (isset($path[$i+1])) ? urldecode($path[$i+1]) : null; |
|
241 } |
|
242 } |
|
243 |
|
244 $matchedPath = implode($this->_urlDelimiter, $path); |
|
245 break; |
|
246 } |
|
247 |
|
248 $name = isset($this->_variables[$pos]) ? $this->_variables[$pos] : null; |
|
249 $pathPart = urldecode($pathPart); |
|
250 |
|
251 // Translate value if required |
|
252 $part = $this->_parts[$pos]; |
|
253 if ($this->_isTranslated && (substr($part, 0, 1) === '@' && substr($part, 1, 1) !== '@' && $name === null) || $name !== null && in_array($name, $this->_translatable)) { |
|
254 if (substr($part, 0, 1) === '@') { |
|
255 $part = substr($part, 1); |
|
256 } |
|
257 |
|
258 if (($originalPathPart = array_search($pathPart, $translateMessages)) !== false) { |
|
259 $pathPart = $originalPathPart; |
|
260 } |
|
261 } |
|
262 |
|
263 if (substr($part, 0, 2) === '@@') { |
|
264 $part = substr($part, 1); |
|
265 } |
|
266 |
|
267 // If it's a static part, match directly |
|
268 if ($name === null && $part != $pathPart) { |
|
269 return false; |
|
270 } |
|
271 |
|
272 // If it's a variable with requirement, match a regex. If not - everything matches |
|
273 if ($part !== null && !preg_match($this->_regexDelimiter . '^' . $part . '$' . $this->_regexDelimiter . 'iu', $pathPart)) { |
|
274 return false; |
|
275 } |
|
276 |
|
277 // If it's a variable store it's value for later |
|
278 if ($name !== null) { |
|
279 $values[$name] = $pathPart; |
|
280 } else { |
|
281 $pathStaticCount++; |
|
282 } |
|
283 } |
|
284 } |
|
285 |
|
286 // Check if all static mappings have been matched |
|
287 if ($this->_staticCount != $pathStaticCount) { |
|
288 return false; |
|
289 } |
|
290 |
|
291 $return = $values + $this->_wildcardData + $this->_defaults; |
|
292 |
|
293 // Check if all map variables have been initialized |
|
294 foreach ($this->_variables as $var) { |
|
295 if (!array_key_exists($var, $return)) { |
|
296 return false; |
|
297 } |
|
298 } |
|
299 |
|
300 $this->setMatchedPath(rtrim($matchedPath, $this->_urlDelimiter)); |
|
301 |
|
302 $this->_values = $values; |
|
303 |
|
304 return $return; |
|
305 |
|
306 } |
|
307 |
|
308 /** |
|
309 * Assembles user submitted parameters forming a URL path defined by this route |
|
310 * |
|
311 * @param array $data An array of variable and value pairs used as parameters |
|
312 * @param boolean $reset Whether or not to set route defaults with those provided in $data |
|
313 * @return string Route path with user submitted parameters |
|
314 */ |
|
315 public function assemble($data = array(), $reset = false, $encode = false, $partial = false) |
|
316 { |
|
317 if ($this->_isTranslated) { |
|
318 $translator = $this->getTranslator(); |
|
319 |
|
320 if (isset($data['@locale'])) { |
|
321 $locale = $data['@locale']; |
|
322 unset($data['@locale']); |
|
323 } else { |
|
324 $locale = $this->getLocale(); |
|
325 } |
|
326 } |
|
327 |
|
328 $url = array(); |
|
329 $flag = false; |
|
330 |
|
331 foreach ($this->_parts as $key => $part) { |
|
332 $name = isset($this->_variables[$key]) ? $this->_variables[$key] : null; |
|
333 |
|
334 $useDefault = false; |
|
335 if (isset($name) && array_key_exists($name, $data) && $data[$name] === null) { |
|
336 $useDefault = true; |
|
337 } |
|
338 |
|
339 if (isset($name)) { |
|
340 if (isset($data[$name]) && !$useDefault) { |
|
341 $value = $data[$name]; |
|
342 unset($data[$name]); |
|
343 } elseif (!$reset && !$useDefault && isset($this->_values[$name])) { |
|
344 $value = $this->_values[$name]; |
|
345 } elseif (!$reset && !$useDefault && isset($this->_wildcardData[$name])) { |
|
346 $value = $this->_wildcardData[$name]; |
|
347 } elseif (isset($this->_defaults[$name])) { |
|
348 $value = $this->_defaults[$name]; |
|
349 } else { |
|
350 require_once 'Zend/Controller/Router/Exception.php'; |
|
351 throw new Zend_Controller_Router_Exception($name . ' is not specified'); |
|
352 } |
|
353 |
|
354 if ($this->_isTranslated && in_array($name, $this->_translatable)) { |
|
355 $url[$key] = $translator->translate($value, $locale); |
|
356 } else { |
|
357 $url[$key] = $value; |
|
358 } |
|
359 } elseif ($part != '*') { |
|
360 if ($this->_isTranslated && substr($part, 0, 1) === '@') { |
|
361 if (substr($part, 1, 1) !== '@') { |
|
362 $url[$key] = $translator->translate(substr($part, 1), $locale); |
|
363 } else { |
|
364 $url[$key] = substr($part, 1); |
|
365 } |
|
366 } else { |
|
367 if (substr($part, 0, 2) === '@@') { |
|
368 $part = substr($part, 1); |
|
369 } |
|
370 |
|
371 $url[$key] = $part; |
|
372 } |
|
373 } else { |
|
374 if (!$reset) $data += $this->_wildcardData; |
|
375 $defaults = $this->getDefaults(); |
|
376 foreach ($data as $var => $value) { |
|
377 if ($value !== null && (!isset($defaults[$var]) || $value != $defaults[$var])) { |
|
378 $url[$key++] = $var; |
|
379 $url[$key++] = $value; |
|
380 $flag = true; |
|
381 } |
|
382 } |
|
383 } |
|
384 } |
|
385 |
|
386 $return = ''; |
|
387 |
|
388 foreach (array_reverse($url, true) as $key => $value) { |
|
389 $defaultValue = null; |
|
390 |
|
391 if (isset($this->_variables[$key])) { |
|
392 $defaultValue = $this->getDefault($this->_variables[$key]); |
|
393 |
|
394 if ($this->_isTranslated && $defaultValue !== null && isset($this->_translatable[$this->_variables[$key]])) { |
|
395 $defaultValue = $translator->translate($defaultValue, $locale); |
|
396 } |
|
397 } |
|
398 |
|
399 if ($flag || $value !== $defaultValue || $partial) { |
|
400 if ($encode) $value = urlencode($value); |
|
401 $return = $this->_urlDelimiter . $value . $return; |
|
402 $flag = true; |
|
403 } |
|
404 } |
|
405 |
|
406 return trim($return, $this->_urlDelimiter); |
|
407 |
|
408 } |
|
409 |
|
410 /** |
|
411 * Return a single parameter of route's defaults |
|
412 * |
|
413 * @param string $name Array key of the parameter |
|
414 * @return string Previously set default |
|
415 */ |
|
416 public function getDefault($name) { |
|
417 if (isset($this->_defaults[$name])) { |
|
418 return $this->_defaults[$name]; |
|
419 } |
|
420 return null; |
|
421 } |
|
422 |
|
423 /** |
|
424 * Return an array of defaults |
|
425 * |
|
426 * @return array Route defaults |
|
427 */ |
|
428 public function getDefaults() { |
|
429 return $this->_defaults; |
|
430 } |
|
431 |
|
432 /** |
|
433 * Get all variables which are used by the route |
|
434 * |
|
435 * @return array |
|
436 */ |
|
437 public function getVariables() |
|
438 { |
|
439 return $this->_variables; |
|
440 } |
|
441 |
|
442 /** |
|
443 * Set a default translator |
|
444 * |
|
445 * @param Zend_Translate $translator |
|
446 * @return void |
|
447 */ |
|
448 public static function setDefaultTranslator(Zend_Translate $translator = null) |
|
449 { |
|
450 self::$_defaultTranslator = $translator; |
|
451 } |
|
452 |
|
453 /** |
|
454 * Get the default translator |
|
455 * |
|
456 * @return Zend_Translate |
|
457 */ |
|
458 public static function getDefaultTranslator() |
|
459 { |
|
460 return self::$_defaultTranslator; |
|
461 } |
|
462 |
|
463 /** |
|
464 * Set a translator |
|
465 * |
|
466 * @param Zend_Translate $translator |
|
467 * @return void |
|
468 */ |
|
469 public function setTranslator(Zend_Translate $translator) |
|
470 { |
|
471 $this->_translator = $translator; |
|
472 } |
|
473 |
|
474 /** |
|
475 * Get the translator |
|
476 * |
|
477 * @throws Zend_Controller_Router_Exception When no translator can be found |
|
478 * @return Zend_Translate |
|
479 */ |
|
480 public function getTranslator() |
|
481 { |
|
482 if ($this->_translator !== null) { |
|
483 return $this->_translator; |
|
484 } else if (($translator = self::getDefaultTranslator()) !== null) { |
|
485 return $translator; |
|
486 } else { |
|
487 try { |
|
488 $translator = Zend_Registry::get('Zend_Translate'); |
|
489 } catch (Zend_Exception $e) { |
|
490 $translator = null; |
|
491 } |
|
492 |
|
493 if ($translator instanceof Zend_Translate) { |
|
494 return $translator; |
|
495 } |
|
496 } |
|
497 |
|
498 require_once 'Zend/Controller/Router/Exception.php'; |
|
499 throw new Zend_Controller_Router_Exception('Could not find a translator'); |
|
500 } |
|
501 |
|
502 /** |
|
503 * Set a default locale |
|
504 * |
|
505 * @param mixed $locale |
|
506 * @return void |
|
507 */ |
|
508 public static function setDefaultLocale($locale = null) |
|
509 { |
|
510 self::$_defaultLocale = $locale; |
|
511 } |
|
512 |
|
513 /** |
|
514 * Get the default locale |
|
515 * |
|
516 * @return mixed |
|
517 */ |
|
518 public static function getDefaultLocale() |
|
519 { |
|
520 return self::$_defaultLocale; |
|
521 } |
|
522 |
|
523 /** |
|
524 * Set a locale |
|
525 * |
|
526 * @param mixed $locale |
|
527 * @return void |
|
528 */ |
|
529 public function setLocale($locale) |
|
530 { |
|
531 $this->_locale = $locale; |
|
532 } |
|
533 |
|
534 /** |
|
535 * Get the locale |
|
536 * |
|
537 * @return mixed |
|
538 */ |
|
539 public function getLocale() |
|
540 { |
|
541 if ($this->_locale !== null) { |
|
542 return $this->_locale; |
|
543 } else if (($locale = self::getDefaultLocale()) !== null) { |
|
544 return $locale; |
|
545 } else { |
|
546 try { |
|
547 $locale = Zend_Registry::get('Zend_Locale'); |
|
548 } catch (Zend_Exception $e) { |
|
549 $locale = null; |
|
550 } |
|
551 |
|
552 if ($locale !== null) { |
|
553 return $locale; |
|
554 } |
|
555 } |
|
556 |
|
557 return null; |
|
558 } |
|
559 } |