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