Overview

Namespaces

  • Budovy
  • Kdyby
    • BootstrapFormRenderer
      • DI
      • Latte
  • Nette
    • Application
      • Diagnostics
      • Responses
      • Routers
      • UI
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Diagnostics
      • Drivers
      • Reflection
      • Table
    • DI
      • Config
        • Adapters
      • Diagnostics
      • Extensions
    • Diagnostics
    • Forms
      • Controls
      • Rendering
    • Http
      • Diagnostics
    • Iterators
    • Latte
      • Macros
    • Loaders
    • Localization
    • Mail
    • PhpGenerator
    • Reflection
    • Security
      • Diagnostics
    • Templating
    • Utils
  • NetteModule
  • Nextras
    • Datagrid
  • None
  • PHP
  • Tester
    • CodeCoverage
    • Runner
      • Output
  • Vodacek
    • Forms
      • Controls
  • WebLoader
    • Filter
    • Nette

Classes

  • CliRouter
  • Route
  • RouteList
  • SimpleRouter
  • Overview
  • Namespace
  • Class
  • Tree
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Nette Framework (http://nette.org)
  5:  * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
  6:  */
  7: 
  8: namespace Nette\Application\Routers;
  9: 
 10: use Nette,
 11:     Nette\Application,
 12:     Nette\Utils\Strings;
 13: 
 14: 
 15: /**
 16:  * The bidirectional route is responsible for mapping
 17:  * HTTP request to a Request object for dispatch and vice-versa.
 18:  *
 19:  * @author     David Grudl
 20:  *
 21:  * @property-read string $mask
 22:  * @property-read array $defaults
 23:  * @property-read int $flags
 24:  * @property-read string|FALSE $targetPresenter
 25:  */
 26: class Route extends Nette\Object implements Application\IRouter
 27: {
 28:     const PRESENTER_KEY = 'presenter';
 29:     const MODULE_KEY = 'module';
 30: 
 31:     /** flag */
 32:     const CASE_SENSITIVE = 256;
 33: 
 34:     /** @internal url type */
 35:     const HOST = 1,
 36:         PATH = 2,
 37:         RELATIVE = 3;
 38: 
 39:     /** key used in {@link Route::$styles} or metadata {@link Route::__construct} */
 40:     const VALUE = 'value';
 41:     const PATTERN = 'pattern';
 42:     const FILTER_IN = 'filterIn';
 43:     const FILTER_OUT = 'filterOut';
 44:     const FILTER_TABLE = 'filterTable';
 45:     const FILTER_STRICT = 'filterStrict';
 46: 
 47:     /** @internal fixity types - how to handle default value? {@link Route::$metadata} */
 48:     const OPTIONAL = 0,
 49:         PATH_OPTIONAL = 1,
 50:         CONSTANT = 2;
 51: 
 52:     /** @var int */
 53:     public static $defaultFlags = 0;
 54: 
 55:     /** @var array */
 56:     public static $styles = array(
 57:         '#' => array( // default style for path parameters
 58:             self::PATTERN => '[^/]+',
 59:             self::FILTER_IN => 'rawurldecode',
 60:             self::FILTER_OUT => array(__CLASS__, 'param2path'),
 61:         ),
 62:         '?#' => array( // default style for query parameters
 63:         ),
 64:         'module' => array(
 65:             self::PATTERN => '[a-z][a-z0-9.-]*',
 66:             self::FILTER_IN => array(__CLASS__, 'path2presenter'),
 67:             self::FILTER_OUT => array(__CLASS__, 'presenter2path'),
 68:         ),
 69:         'presenter' => array(
 70:             self::PATTERN => '[a-z][a-z0-9.-]*',
 71:             self::FILTER_IN => array(__CLASS__, 'path2presenter'),
 72:             self::FILTER_OUT => array(__CLASS__, 'presenter2path'),
 73:         ),
 74:         'action' => array(
 75:             self::PATTERN => '[a-z][a-z0-9-]*',
 76:             self::FILTER_IN => array(__CLASS__, 'path2action'),
 77:             self::FILTER_OUT => array(__CLASS__, 'action2path'),
 78:         ),
 79:         '?module' => array(
 80:         ),
 81:         '?presenter' => array(
 82:         ),
 83:         '?action' => array(
 84:         ),
 85:     );
 86: 
 87:     /** @var string */
 88:     private $mask;
 89: 
 90:     /** @var array */
 91:     private $sequence;
 92: 
 93:     /** @var string  regular expression pattern */
 94:     private $re;
 95: 
 96:     /** @var array of [value & fixity, filterIn, filterOut] */
 97:     private $metadata = array();
 98: 
 99:     /** @var array  */
100:     private $xlat;
101: 
102:     /** @var int HOST, PATH, RELATIVE */
103:     private $type;
104: 
105:     /** @var int */
106:     private $flags;
107: 
108: 
109:     /**
110:      * @param  string  URL mask, e.g. '<presenter>/<action>/<id \d{1,3}>'
111:      * @param  array|string   default values or metadata
112:      * @param  int     flags
113:      */
114:     public function __construct($mask, $metadata = array(), $flags = 0)
115:     {
116:         if (is_string($metadata)) {
117:             $a = strrpos($metadata, ':');
118:             if (!$a) {
119:                 throw new Nette\InvalidArgumentException("Second argument must be array or string in format Presenter:action, '$metadata' given.");
120:             }
121:             $metadata = array(
122:                 self::PRESENTER_KEY => substr($metadata, 0, $a),
123:                 'action' => $a === strlen($metadata) - 1 ? NULL : substr($metadata, $a + 1),
124:             );
125:         } elseif ($metadata instanceof \Closure || $metadata instanceof Nette\Callback) {
126:             $metadata = array(
127:                 self::PRESENTER_KEY => 'Nette:Micro',
128:                 'callback' => $metadata,
129:             );
130:         }
131: 
132:         $this->flags = $flags | static::$defaultFlags;
133:         $this->setMask($mask, $metadata);
134:     }
135: 
136: 
137:     /**
138:      * Maps HTTP request to a Request object.
139:      * @return Nette\Application\Request|NULL
140:      */
141:     public function match(Nette\Http\IRequest $httpRequest)
142:     {
143:         // combine with precedence: mask (params in URL-path), fixity, query, (post,) defaults
144: 
145:         // 1) URL MASK
146:         $url = $httpRequest->getUrl();
147:         $re = $this->re;
148: 
149:         if ($this->type === self::HOST) {
150:             $path = '//' . $url->getHost() . $url->getPath();
151:             $host = array_reverse(explode('.', $url->getHost()));
152:             $re = strtr($re, array(
153:                 '/%basePath%/' => preg_quote($url->getBasePath(), '#'),
154:                 '%tld%' => $host[0],
155:                 '%domain%' => isset($host[1]) ? "$host[1]\\.$host[0]" : $host[0],
156:             ));
157: 
158:         } elseif ($this->type === self::RELATIVE) {
159:             $basePath = $url->getBasePath();
160:             if (strncmp($url->getPath(), $basePath, strlen($basePath)) !== 0) {
161:                 return NULL;
162:             }
163:             $path = (string) substr($url->getPath(), strlen($basePath));
164: 
165:         } else {
166:             $path = $url->getPath();
167:         }
168: 
169:         if ($path !== '') {
170:             $path = rtrim($path, '/') . '/';
171:         }
172: 
173:         if (!$matches = Strings::match($path, $re)) {
174:             // stop, not matched
175:             return NULL;
176:         }
177: 
178:         // deletes numeric keys, restore '-' chars
179:         $params = array();
180:         foreach ($matches as $k => $v) {
181:             if (is_string($k) && $v !== '') {
182:                 $params[str_replace('___', '-', $k)] = $v; // trick
183:             }
184:         }
185: 
186: 
187:         // 2) CONSTANT FIXITY
188:         foreach ($this->metadata as $name => $meta) {
189:             if (isset($params[$name])) {
190:                 //$params[$name] = $this->flags & self::CASE_SENSITIVE === 0 ? strtolower($params[$name]) : */$params[$name]; // strtolower damages UTF-8
191: 
192:             } elseif (isset($meta['fixity']) && $meta['fixity'] !== self::OPTIONAL) {
193:                 $params[$name] = NULL; // cannot be overwriten in 3) and detected by isset() in 4)
194:             }
195:         }
196: 
197: 
198:         // 3) QUERY
199:         if ($this->xlat) {
200:             $params += self::renameKeys($httpRequest->getQuery(), array_flip($this->xlat));
201:         } else {
202:             $params += $httpRequest->getQuery();
203:         }
204: 
205: 
206:         // 4) APPLY FILTERS & FIXITY
207:         foreach ($this->metadata as $name => $meta) {
208:             if (isset($params[$name])) {
209:                 if (!is_scalar($params[$name])) {
210: 
211:                 } elseif (isset($meta[self::FILTER_TABLE][$params[$name]])) { // applies filterTable only to scalar parameters
212:                     $params[$name] = $meta[self::FILTER_TABLE][$params[$name]];
213: 
214:                 } elseif (isset($meta[self::FILTER_TABLE]) && !empty($meta[self::FILTER_STRICT])) {
215:                     return NULL; // rejected by filterTable
216: 
217:                 } elseif (isset($meta[self::FILTER_IN])) { // applies filterIn only to scalar parameters
218:                     $params[$name] = call_user_func($meta[self::FILTER_IN], (string) $params[$name]);
219:                     if ($params[$name] === NULL && !isset($meta['fixity'])) {
220:                         return NULL; // rejected by filter
221:                     }
222:                 }
223: 
224:             } elseif (isset($meta['fixity'])) {
225:                 $params[$name] = $meta[self::VALUE];
226:             }
227:         }
228: 
229:         if (isset($this->metadata[NULL][self::FILTER_IN])) {
230:             $params = call_user_func($this->metadata[NULL][self::FILTER_IN], $params);
231:             if ($params === NULL) {
232:                 return NULL;
233:             }
234:         }
235: 
236:         // 5) BUILD Request
237:         if (!isset($params[self::PRESENTER_KEY])) {
238:             throw new Nette\InvalidStateException('Missing presenter in route definition.');
239:         } elseif (!is_string($params[self::PRESENTER_KEY])) {
240:             return NULL;
241:         }
242:         if (isset($this->metadata[self::MODULE_KEY])) {
243:             if (!isset($params[self::MODULE_KEY])) {
244:                 throw new Nette\InvalidStateException('Missing module in route definition.');
245:             }
246:             $presenter = $params[self::MODULE_KEY] . ':' . $params[self::PRESENTER_KEY];
247:             unset($params[self::MODULE_KEY], $params[self::PRESENTER_KEY]);
248: 
249:         } else {
250:             $presenter = $params[self::PRESENTER_KEY];
251:             unset($params[self::PRESENTER_KEY]);
252:         }
253: 
254:         return new Application\Request(
255:             $presenter,
256:             $httpRequest->getMethod(),
257:             $params,
258:             $httpRequest->getPost(),
259:             $httpRequest->getFiles(),
260:             array(Application\Request::SECURED => $httpRequest->isSecured())
261:         );
262:     }
263: 
264: 
265:     /**
266:      * Constructs absolute URL from Request object.
267:      * @return string|NULL
268:      */
269:     public function constructUrl(Application\Request $appRequest, Nette\Http\Url $refUrl)
270:     {
271:         if ($this->flags & self::ONE_WAY) {
272:             return NULL;
273:         }
274: 
275:         $params = $appRequest->getParameters();
276:         $metadata = $this->metadata;
277: 
278:         $presenter = $appRequest->getPresenterName();
279:         $params[self::PRESENTER_KEY] = $presenter;
280: 
281:         if (isset($metadata[NULL][self::FILTER_OUT])) {
282:             $params = call_user_func($metadata[NULL][self::FILTER_OUT], $params);
283:             if ($params === NULL) {
284:                 return NULL;
285:             }
286:         }
287: 
288:         if (isset($metadata[self::MODULE_KEY])) { // try split into module and [submodule:]presenter parts
289:             $module = $metadata[self::MODULE_KEY];
290:             if (isset($module['fixity']) && strncasecmp($presenter, $module[self::VALUE] . ':', strlen($module[self::VALUE]) + 1) === 0) {
291:                 $a = strlen($module[self::VALUE]);
292:             } else {
293:                 $a = strrpos($presenter, ':');
294:             }
295:             if ($a === FALSE) {
296:                 $params[self::MODULE_KEY] = '';
297:             } else {
298:                 $params[self::MODULE_KEY] = substr($presenter, 0, $a);
299:                 $params[self::PRESENTER_KEY] = substr($presenter, $a + 1);
300:             }
301:         }
302: 
303:         foreach ($metadata as $name => $meta) {
304:             if (!isset($params[$name])) {
305:                 continue; // retains NULL values
306:             }
307: 
308:             if (isset($meta['fixity'])) {
309:                 if ($params[$name] === FALSE) {
310:                     $params[$name] = '0';
311:                 }
312:                 if (is_scalar($params[$name]) ? strcasecmp($params[$name], $meta[self::VALUE]) === 0
313:                     : $params[$name] === $meta[self::VALUE]
314:                 ) { // remove default values; NULL values are retain
315:                     unset($params[$name]);
316:                     continue;
317: 
318:                 } elseif ($meta['fixity'] === self::CONSTANT) {
319:                     return NULL; // missing or wrong parameter '$name'
320:                 }
321:             }
322: 
323:             if (is_scalar($params[$name]) && isset($meta['filterTable2'][$params[$name]])) {
324:                 $params[$name] = $meta['filterTable2'][$params[$name]];
325: 
326:             } elseif (isset($meta['filterTable2']) && !empty($meta[self::FILTER_STRICT])) {
327:                 return NULL;
328: 
329:             } elseif (isset($meta[self::FILTER_OUT])) {
330:                 $params[$name] = call_user_func($meta[self::FILTER_OUT], $params[$name]);
331:             }
332: 
333:             if (isset($meta[self::PATTERN]) && !preg_match($meta[self::PATTERN], rawurldecode($params[$name]))) {
334:                 return NULL; // pattern not match
335:             }
336:         }
337: 
338:         // compositing path
339:         $sequence = $this->sequence;
340:         $brackets = array();
341:         $required = NULL; // NULL for auto-optional
342:         $url = '';
343:         $i = count($sequence) - 1;
344:         do {
345:             $url = $sequence[$i] . $url;
346:             if ($i === 0) {
347:                 break;
348:             }
349:             $i--;
350: 
351:             $name = $sequence[$i]; $i--; // parameter name
352: 
353:             if ($name === ']') { // opening optional part
354:                 $brackets[] = $url;
355: 
356:             } elseif ($name[0] === '[') { // closing optional part
357:                 $tmp = array_pop($brackets);
358:                 if ($required < count($brackets) + 1) { // is this level optional?
359:                     if ($name !== '[!') { // and not "required"-optional
360:                         $url = $tmp;
361:                     }
362:                 } else {
363:                     $required = count($brackets);
364:                 }
365: 
366:             } elseif ($name[0] === '?') { // "foo" parameter
367:                 continue;
368: 
369:             } elseif (isset($params[$name]) && $params[$name] != '') { // intentionally ==
370:                 $required = count($brackets); // make this level required
371:                 $url = $params[$name] . $url;
372:                 unset($params[$name]);
373: 
374:             } elseif (isset($metadata[$name]['fixity'])) { // has default value?
375:                 if ($required === NULL && !$brackets) { // auto-optional
376:                     $url = '';
377:                 } else {
378:                     $url = $metadata[$name]['defOut'] . $url;
379:                 }
380: 
381:             } else {
382:                 return NULL; // missing parameter '$name'
383:             }
384:         } while (TRUE);
385: 
386: 
387:         // absolutize path
388:         if ($this->type === self::RELATIVE) {
389:             $url = '//' . $refUrl->getAuthority() . $refUrl->getBasePath() . $url;
390: 
391:         } elseif ($this->type === self::PATH) {
392:             $url = '//' . $refUrl->getAuthority() . $url;
393: 
394:         } else {
395:             $host = array_reverse(explode('.', $refUrl->getHost()));
396:             $url = strtr($url, array(
397:                 '/%basePath%/' => $refUrl->getBasePath(),
398:                 '%tld%' => $host[0],
399:                 '%domain%' => isset($host[1]) ? "$host[1].$host[0]" : $host[0],
400:             ));
401:         }
402: 
403:         if (strpos($url, '//', 2) !== FALSE) {
404:             return NULL; // TODO: implement counterpart in match() ?
405:         }
406: 
407:         $url = ($this->flags & self::SECURED ? 'https:' : 'http:') . $url;
408: 
409:         // build query string
410:         if ($this->xlat) {
411:             $params = self::renameKeys($params, $this->xlat);
412:         }
413: 
414:         $sep = ini_get('arg_separator.input');
415:         $query = http_build_query($params, '', $sep ? $sep[0] : '&');
416:         if ($query != '') { // intentionally ==
417:             $url .= '?' . $query;
418:         }
419: 
420:         return $url;
421:     }
422: 
423: 
424:     /**
425:      * Parse mask and array of default values; initializes object.
426:      * @param  string
427:      * @param  array
428:      * @return void
429:      */
430:     private function setMask($mask, array $metadata)
431:     {
432:         $this->mask = $mask;
433: 
434:         // detect '//host/path' vs. '/abs. path' vs. 'relative path'
435:         if (substr($mask, 0, 2) === '//') {
436:             $this->type = self::HOST;
437: 
438:         } elseif (substr($mask, 0, 1) === '/') {
439:             $this->type = self::PATH;
440: 
441:         } else {
442:             $this->type = self::RELATIVE;
443:         }
444: 
445:         foreach ($metadata as $name => $meta) {
446:             if (!is_array($meta)) {
447:                 $metadata[$name] = array(self::VALUE => $meta, 'fixity' => self::CONSTANT);
448: 
449:             } elseif (array_key_exists(self::VALUE, $meta)) {
450:                 $metadata[$name]['fixity'] = self::CONSTANT;
451:             }
452:         }
453: 
454:         // PARSE MASK
455:         // <parameter-name[=default] [pattern] [#class]> or [ or ] or ?...
456:         $parts = Strings::split($mask, '/<([^>#= ]+)(=[^># ]*)? *([^>#]*)(#?[^>\[\]]*)>|(\[!?|\]|\s*\?.*)/');
457: 
458:         $this->xlat = array();
459:         $i = count($parts) - 1;
460: 
461:         // PARSE QUERY PART OF MASK
462:         if (isset($parts[$i - 1]) && substr(ltrim($parts[$i - 1]), 0, 1) === '?') {
463:             // name=<parameter-name [pattern][#class]>
464:             $matches = Strings::matchAll($parts[$i - 1], '/(?:([a-zA-Z0-9_.-]+)=)?<([^># ]+) *([^>#]*)(#?[^>]*)>/');
465: 
466:             foreach ($matches as $match) {
467:                 list(, $param, $name, $pattern, $class) = $match;  // $pattern is not used
468: 
469:                 if ($class !== '') {
470:                     if (!isset(static::$styles[$class])) {
471:                         throw new Nette\InvalidStateException("Parameter '$name' has '$class' flag, but Route::\$styles['$class'] is not set.");
472:                     }
473:                     $meta = static::$styles[$class];
474: 
475:                 } elseif (isset(static::$styles['?' . $name])) {
476:                     $meta = static::$styles['?' . $name];
477: 
478:                 } else {
479:                     $meta = static::$styles['?#'];
480:                 }
481: 
482:                 if (isset($metadata[$name])) {
483:                     $meta = $metadata[$name] + $meta;
484:                 }
485: 
486:                 if (array_key_exists(self::VALUE, $meta)) {
487:                     $meta['fixity'] = self::OPTIONAL;
488:                 }
489: 
490:                 unset($meta['pattern']);
491:                 $meta['filterTable2'] = empty($meta[self::FILTER_TABLE]) ? NULL : array_flip($meta[self::FILTER_TABLE]);
492: 
493:                 $metadata[$name] = $meta;
494:                 if ($param !== '') {
495:                     $this->xlat[$name] = $param;
496:                 }
497:             }
498:             $i -= 6;
499:         }
500: 
501:         // PARSE PATH PART OF MASK
502:         $brackets = 0; // optional level
503:         $re = '';
504:         $sequence = array();
505:         $autoOptional = TRUE;
506:         do {
507:             array_unshift($sequence, $parts[$i]);
508:             $re = preg_quote($parts[$i], '#') . $re;
509:             if ($i === 0) {
510:                 break;
511:             }
512:             $i--;
513: 
514:             $part = $parts[$i]; // [ or ]
515:             if ($part === '[' || $part === ']' || $part === '[!') {
516:                 $brackets += $part[0] === '[' ? -1 : 1;
517:                 if ($brackets < 0) {
518:                     throw new Nette\InvalidArgumentException("Unexpected '$part' in mask '$mask'.");
519:                 }
520:                 array_unshift($sequence, $part);
521:                 $re = ($part[0] === '[' ? '(?:' : ')?') . $re;
522:                 $i -= 5;
523:                 continue;
524:             }
525: 
526:             $class = $parts[$i]; $i--; // validation class
527:             $pattern = trim($parts[$i]); $i--; // validation condition (as regexp)
528:             $default = $parts[$i]; $i--; // default value
529:             $name = $parts[$i]; $i--; // parameter name
530:             array_unshift($sequence, $name);
531: 
532:             if ($name[0] === '?') { // "foo" parameter
533:                 $name = substr($name, 1);
534:                 $re = $pattern ? '(?:' . preg_quote($name, '#') . "|$pattern)$re" : preg_quote($name, '#') . $re;
535:                 $sequence[1] = $name . $sequence[1];
536:                 continue;
537:             }
538: 
539:             // check name (limitation by regexp)
540:             if (preg_match('#[^a-z0-9_-]#i', $name)) {
541:                 throw new Nette\InvalidArgumentException("Parameter name must be alphanumeric string due to limitations of PCRE, '$name' given.");
542:             }
543: 
544:             // pattern, condition & metadata
545:             if ($class !== '') {
546:                 if (!isset(static::$styles[$class])) {
547:                     throw new Nette\InvalidStateException("Parameter '$name' has '$class' flag, but Route::\$styles['$class'] is not set.");
548:                 }
549:                 $meta = static::$styles[$class];
550: 
551:             } elseif (isset(static::$styles[$name])) {
552:                 $meta = static::$styles[$name];
553: 
554:             } else {
555:                 $meta = static::$styles['#'];
556:             }
557: 
558:             if (isset($metadata[$name])) {
559:                 $meta = $metadata[$name] + $meta;
560:             }
561: 
562:             if ($pattern == '' && isset($meta[self::PATTERN])) {
563:                 $pattern = $meta[self::PATTERN];
564:             }
565: 
566:             if ($default !== '') {
567:                 $meta[self::VALUE] = (string) substr($default, 1);
568:                 $meta['fixity'] = self::PATH_OPTIONAL;
569:             }
570: 
571:             $meta['filterTable2'] = empty($meta[self::FILTER_TABLE]) ? NULL : array_flip($meta[self::FILTER_TABLE]);
572:             if (array_key_exists(self::VALUE, $meta)) {
573:                 if (isset($meta['filterTable2'][$meta[self::VALUE]])) {
574:                     $meta['defOut'] = $meta['filterTable2'][$meta[self::VALUE]];
575: 
576:                 } elseif (isset($meta[self::FILTER_OUT])) {
577:                     $meta['defOut'] = call_user_func($meta[self::FILTER_OUT], $meta[self::VALUE]);
578: 
579:                 } else {
580:                     $meta['defOut'] = $meta[self::VALUE];
581:                 }
582:             }
583:             $meta[self::PATTERN] = "#(?:$pattern)\\z#A" . ($this->flags & self::CASE_SENSITIVE ? '' : 'iu');
584: 
585:             // include in expression
586:             $re = '(?P<' . str_replace('-', '___', $name) . '>(?U)' . $pattern . ')' . $re; // str_replace is dirty trick to enable '-' in parameter name
587:             if ($brackets) { // is in brackets?
588:                 if (!isset($meta[self::VALUE])) {
589:                     $meta[self::VALUE] = $meta['defOut'] = NULL;
590:                 }
591:                 $meta['fixity'] = self::PATH_OPTIONAL;
592: 
593:             } elseif (!$autoOptional) {
594:                 unset($meta['fixity']);
595: 
596:             } elseif (isset($meta['fixity'])) { // auto-optional
597:                 $re = '(?:' . $re . ')?';
598:                 $meta['fixity'] = self::PATH_OPTIONAL;
599: 
600:             } else {
601:                 $autoOptional = FALSE;
602:             }
603: 
604:             $metadata[$name] = $meta;
605:         } while (TRUE);
606: 
607:         if ($brackets) {
608:             throw new Nette\InvalidArgumentException("Missing closing ']' in mask '$mask'.");
609:         }
610: 
611:         $this->re = '#' . $re . '/?\z#A' . ($this->flags & self::CASE_SENSITIVE ? '' : 'iu');
612:         $this->metadata = $metadata;
613:         $this->sequence = $sequence;
614:     }
615: 
616: 
617:     /**
618:      * Returns mask.
619:      * @return string
620:      */
621:     public function getMask()
622:     {
623:         return $this->mask;
624:     }
625: 
626: 
627:     /**
628:      * Returns default values.
629:      * @return array
630:      */
631:     public function getDefaults()
632:     {
633:         $defaults = array();
634:         foreach ($this->metadata as $name => $meta) {
635:             if (isset($meta['fixity'])) {
636:                 $defaults[$name] = $meta[self::VALUE];
637:             }
638:         }
639:         return $defaults;
640:     }
641: 
642: 
643:     /**
644:      * Returns flags.
645:      * @return int
646:      */
647:     public function getFlags()
648:     {
649:         return $this->flags;
650:     }
651: 
652: 
653:     /********************* Utilities ****************d*g**/
654: 
655: 
656:     /**
657:      * Proprietary cache aim.
658:      * @internal
659:      * @return string|FALSE
660:      */
661:     public function getTargetPresenter()
662:     {
663:         if ($this->flags & self::ONE_WAY) {
664:             return FALSE;
665:         }
666: 
667:         $m = $this->metadata;
668:         $module = '';
669: 
670:         if (isset($m[self::MODULE_KEY])) {
671:             if (isset($m[self::MODULE_KEY]['fixity']) && $m[self::MODULE_KEY]['fixity'] === self::CONSTANT) {
672:                 $module = $m[self::MODULE_KEY][self::VALUE] . ':';
673:             } else {
674:                 return NULL;
675:             }
676:         }
677: 
678:         if (isset($m[self::PRESENTER_KEY]['fixity']) && $m[self::PRESENTER_KEY]['fixity'] === self::CONSTANT) {
679:             return $module . $m[self::PRESENTER_KEY][self::VALUE];
680:         }
681:         return NULL;
682:     }
683: 
684: 
685:     /**
686:      * Rename keys in array.
687:      * @param  array
688:      * @param  array
689:      * @return array
690:      */
691:     private static function renameKeys($arr, $xlat)
692:     {
693:         if (empty($xlat)) {
694:             return $arr;
695:         }
696: 
697:         $res = array();
698:         $occupied = array_flip($xlat);
699:         foreach ($arr as $k => $v) {
700:             if (isset($xlat[$k])) {
701:                 $res[$xlat[$k]] = $v;
702: 
703:             } elseif (!isset($occupied[$k])) {
704:                 $res[$k] = $v;
705:             }
706:         }
707:         return $res;
708:     }
709: 
710: 
711:     /********************* Inflectors ****************d*g**/
712: 
713: 
714:     /**
715:      * camelCaseAction name -> dash-separated.
716:      * @param  string
717:      * @return string
718:      */
719:     private static function action2path($s)
720:     {
721:         $s = preg_replace('#(.)(?=[A-Z])#', '$1-', $s);
722:         $s = strtolower($s);
723:         $s = rawurlencode($s);
724:         return $s;
725:     }
726: 
727: 
728:     /**
729:      * dash-separated -> camelCaseAction name.
730:      * @param  string
731:      * @return string
732:      */
733:     private static function path2action($s)
734:     {
735:         $s = strtolower($s);
736:         $s = preg_replace('#-(?=[a-z])#', ' ', $s);
737:         $s = substr(ucwords('x' . $s), 1);
738:         //$s = lcfirst(ucwords($s));
739:         $s = str_replace(' ', '', $s);
740:         return $s;
741:     }
742: 
743: 
744:     /**
745:      * PascalCase:Presenter name -> dash-and-dot-separated.
746:      * @param  string
747:      * @return string
748:      */
749:     private static function presenter2path($s)
750:     {
751:         $s = strtr($s, ':', '.');
752:         $s = preg_replace('#([^.])(?=[A-Z])#', '$1-', $s);
753:         $s = strtolower($s);
754:         $s = rawurlencode($s);
755:         return $s;
756:     }
757: 
758: 
759:     /**
760:      * dash-and-dot-separated -> PascalCase:Presenter name.
761:      * @param  string
762:      * @return string
763:      */
764:     private static function path2presenter($s)
765:     {
766:         $s = strtolower($s);
767:         $s = preg_replace('#([.-])(?=[a-z])#', '$1 ', $s);
768:         $s = ucwords($s);
769:         $s = str_replace('. ', ':', $s);
770:         $s = str_replace('- ', '', $s);
771:         return $s;
772:     }
773: 
774: 
775:     /**
776:      * Url encode.
777:      * @param  string
778:      * @return string
779:      */
780:     private static function param2path($s)
781:     {
782:         return str_replace('%2F', '/', rawurlencode($s));
783:     }
784: 
785: 
786:     /********************* Route::$styles manipulator ****************d*g**/
787: 
788: 
789:     /**
790:      * Creates new style.
791:      * @param  string  style name (#style, urlParameter, ?queryParameter)
792:      * @param  string  optional parent style name
793:      * @return void
794:      */
795:     public static function addStyle($style, $parent = '#')
796:     {
797:         if (isset(static::$styles[$style])) {
798:             throw new Nette\InvalidArgumentException("Style '$style' already exists.");
799:         }
800: 
801:         if ($parent !== NULL) {
802:             if (!isset(static::$styles[$parent])) {
803:                 throw new Nette\InvalidArgumentException("Parent style '$parent' doesn't exist.");
804:             }
805:             static::$styles[$style] = static::$styles[$parent];
806: 
807:         } else {
808:             static::$styles[$style] = array();
809:         }
810:     }
811: 
812: 
813:     /**
814:      * Changes style property value.
815:      * @param  string  style name (#style, urlParameter, ?queryParameter)
816:      * @param  string  property name (Route::PATTERN, Route::FILTER_IN, Route::FILTER_OUT, Route::FILTER_TABLE)
817:      * @param  mixed   property value
818:      * @return void
819:      */
820:     public static function setStyleProperty($style, $key, $value)
821:     {
822:         if (!isset(static::$styles[$style])) {
823:             throw new Nette\InvalidArgumentException("Style '$style' doesn't exist.");
824:         }
825:         static::$styles[$style][$key] = $value;
826:     }
827: 
828: }
829: 
API documentation generated by ApiGen 2.8.0