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
    • Reflection
    • Security
    • Utils
  • none
  • Tracy
    • Bridges
      • Nette

Classes

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