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