Namespaces

  • Latte
    • Loaders
    • Macros
    • Runtime
  • Nette
    • Application
      • Responses
      • Routers
      • UI
    • Bridges
      • ApplicationDI
      • ApplicationLatte
      • ApplicationTracy
      • CacheDI
      • CacheLatte
      • DatabaseDI
      • DatabaseTracy
      • DITracy
      • FormsDI
      • FormsLatte
      • Framework
      • HttpDI
      • HttpTracy
      • MailDI
      • ReflectionDI
      • SecurityDI
      • SecurityTracy
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Conventions
      • Drivers
      • Reflection
      • Table
    • DI
      • Config
        • Adapters
      • Extensions
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Loaders
    • Localization
    • Mail
    • Neon
    • PhpGenerator
    • Reflection
    • Security
    • Utils
  • none
  • Tracy
    • Bridges
      • Nette

Classes

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