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
      • Table
    • DI
      • Config
        • Adapters
      • Extensions
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Loaders
    • Localization
    • Mail
    • Neon
    • PhpGenerator
      • Traits
    • Reflection
    • Security
    • Tokenizer
    • Utils
  • Tracy
    • Bridges
      • Nette
  • none

Classes

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