Namespaces

  • Nette
    • Application
      • Diagnostics
      • Responses
      • Routers
      • UI
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Diagnostics
      • Drivers
      • Reflection
      • Table
    • DI
      • Config
        • Adapters
      • Diagnostics
      • Extensions
    • Diagnostics
    • Forms
      • Controls
      • Rendering
    • Http
      • Diagnostics
    • Iterators
    • Latte
      • Macros
    • Loaders
    • Localization
    • Mail
    • PhpGenerator
    • Reflection
    • Security
      • Diagnostics
    • Templating
    • Utils
  • NetteModule
  • none

Classes

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