Namespaces

  • Latte
    • Loaders
    • Macros
    • Runtime
  • Nette
    • Application
      • Responses
      • Routers
      • UI
    • Bridges
      • ApplicationLatte
      • ApplicationTracy
      • CacheLatte
      • DatabaseDI
      • DatabaseTracy
      • DITracy
      • FormsLatte
      • Framework
      • HttpTracy
      • SecurityTracy
    • Caching
      • Storages
    • ComponentModel
    • Database
      • 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

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:  * @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 string  parameter aliases in regular expression */
 97:     private $aliases;
 98: 
 99:     /** @var array of [value & fixity, filterIn, filterOut] */
100:     private $metadata = array();
101: 
102:     /** @var array  */
103:     private $xlat;
104: 
105:     /** @var int HOST, PATH, RELATIVE */
106:     private $type;
107: 
108:     /** @var int */
109:     private $flags;
110: 
111: 
112:     /**
113:      * @param  string  URL mask, e.g. '<presenter>/<action>/<id \d{1,3}>'
114:      * @param  array|string   default values or metadata
115:      * @param  int     flags
116:      */
117:     public function __construct($mask, $metadata = array(), $flags = 0)
118:     {
119:         if (is_string($metadata)) {
120:             $a = strrpos($metadata, ':');
121:             if (!$a) {
122:                 throw new Nette\InvalidArgumentException("Second argument must be array or string in format Presenter:action, '$metadata' given.");
123:             }
124:             $metadata = array(
125:                 self::PRESENTER_KEY => substr($metadata, 0, $a),
126:                 'action' => $a === strlen($metadata) - 1 ? NULL : substr($metadata, $a + 1),
127:             );
128:         } elseif ($metadata instanceof \Closure || $metadata instanceof Nette\Callback) {
129:             $metadata = array(
130:                 self::PRESENTER_KEY => 'Nette:Micro',
131:                 'callback' => $metadata,
132:             );
133:         }
134: 
135:         $this->flags = $flags | static::$defaultFlags;
136:         $this->setMask($mask, $metadata);
137:     }
138: 
139: 
140:     /**
141:      * Maps HTTP request to a Request object.
142:      * @return Nette\Application\Request|NULL
143:      */
144:     public function match(Nette\Http\IRequest $httpRequest)
145:     {
146:         // combine with precedence: mask (params in URL-path), fixity, query, (post,) defaults
147: 
148:         // 1) URL MASK
149:         $url = $httpRequest->getUrl();
150:         $re = $this->re;
151: 
152:         if ($this->type === self::HOST) {
153:             $path = '//' . $url->getHost() . $url->getPath();
154:             $host = array_reverse(explode('.', $url->getHost()));
155:             $re = strtr($re, array(
156:                 '/%basePath%/' => preg_quote($url->getBasePath(), '#'),
157:                 '%tld%' => $host[0],
158:                 '%domain%' => isset($host[1]) ? "$host[1]\\.$host[0]" : $host[0],
159:             ));
160: 
161:         } elseif ($this->type === self::RELATIVE) {
162:             $basePath = $url->getBasePath();
163:             if (strncmp($url->getPath(), $basePath, strlen($basePath)) !== 0) {
164:                 return NULL;
165:             }
166:             $path = (string) substr($url->getPath(), strlen($basePath));
167: 
168:         } else {
169:             $path = $url->getPath();
170:         }
171: 
172:         if ($path !== '') {
173:             $path = rtrim($path, '/') . '/';
174:         }
175: 
176:         if (!$matches = Strings::match($path, $re)) {
177:             // stop, not matched
178:             return NULL;
179:         }
180: 
181:         // assigns matched values to parameters
182:         $params = array();
183:         foreach ($matches as $k => $v) {
184:             if (is_string($k) && $v !== '') {
185:                 $params[$this->aliases[$k]] = $v;
186:             }
187:         }
188: 
189: 
190:         // 2) CONSTANT FIXITY
191:         foreach ($this->metadata as $name => $meta) {
192:             if (isset($params[$name])) {
193:                 //$params[$name] = $this->flags & self::CASE_SENSITIVE === 0 ? strtolower($params[$name]) : */$params[$name]; // strtolower damages UTF-8
194: 
195:             } elseif (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']) && strncasecmp($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:                 }
315:                 if (is_scalar($params[$name]) ? strcasecmp($params[$name], $meta[self::VALUE]) === 0
316:                     : $params[$name] === $meta[self::VALUE]
317:                 ) { // remove default values; NULL values are retain
318:                     unset($params[$name]);
319:                     continue;
320: 
321:                 } elseif ($meta['fixity'] === self::CONSTANT) {
322:                     return NULL; // missing or wrong parameter '$name'
323:                 }
324:             }
325: 
326:             if (is_scalar($params[$name]) && isset($meta['filterTable2'][$params[$name]])) {
327:                 $params[$name] = $meta['filterTable2'][$params[$name]];
328: 
329:             } elseif (isset($meta['filterTable2']) && !empty($meta[self::FILTER_STRICT])) {
330:                 return NULL;
331: 
332:             } elseif (isset($meta[self::FILTER_OUT])) {
333:                 $params[$name] = call_user_func($meta[self::FILTER_OUT], $params[$name]);
334:             }
335: 
336:             if (isset($meta[self::PATTERN]) && !preg_match($meta[self::PATTERN], rawurldecode($params[$name]))) {
337:                 return NULL; // pattern not match
338:             }
339:         }
340: 
341:         // compositing path
342:         $sequence = $this->sequence;
343:         $brackets = array();
344:         $required = NULL; // NULL for auto-optional
345:         $url = '';
346:         $i = count($sequence) - 1;
347:         do {
348:             $url = $sequence[$i] . $url;
349:             if ($i === 0) {
350:                 break;
351:             }
352:             $i--;
353: 
354:             $name = $sequence[$i]; $i--; // parameter name
355: 
356:             if ($name === ']') { // opening optional part
357:                 $brackets[] = $url;
358: 
359:             } elseif ($name[0] === '[') { // closing optional part
360:                 $tmp = array_pop($brackets);
361:                 if ($required < count($brackets) + 1) { // is this level optional?
362:                     if ($name !== '[!') { // and not "required"-optional
363:                         $url = $tmp;
364:                     }
365:                 } else {
366:                     $required = count($brackets);
367:                 }
368: 
369:             } elseif ($name[0] === '?') { // "foo" parameter
370:                 continue;
371: 
372:             } elseif (isset($params[$name]) && $params[$name] != '') { // intentionally ==
373:                 $required = count($brackets); // make this level required
374:                 $url = $params[$name] . $url;
375:                 unset($params[$name]);
376: 
377:             } elseif (isset($metadata[$name]['fixity'])) { // has default value?
378:                 if ($required === NULL && !$brackets) { // auto-optional
379:                     $url = '';
380:                 } else {
381:                     $url = $metadata[$name]['defOut'] . $url;
382:                 }
383: 
384:             } else {
385:                 return NULL; // missing parameter '$name'
386:             }
387:         } while (TRUE);
388: 
389: 
390:         // absolutize path
391:         if ($this->type === self::RELATIVE) {
392:             $url = '//' . $refUrl->getAuthority() . $refUrl->getBasePath() . $url;
393: 
394:         } elseif ($this->type === self::PATH) {
395:             $url = '//' . $refUrl->getAuthority() . $url;
396: 
397:         } else {
398:             $host = array_reverse(explode('.', $refUrl->getHost()));
399:             $url = strtr($url, array(
400:                 '/%basePath%/' => $refUrl->getBasePath(),
401:                 '%tld%' => $host[0],
402:                 '%domain%' => isset($host[1]) ? "$host[1].$host[0]" : $host[0],
403:             ));
404:         }
405: 
406:         if (strpos($url, '//', 2) !== FALSE) {
407:             return NULL; // TODO: implement counterpart in match() ?
408:         }
409: 
410:         $url = ($this->flags & self::SECURED ? 'https:' : 'http:') . $url;
411: 
412:         // build query string
413:         if ($this->xlat) {
414:             $params = self::renameKeys($params, $this->xlat);
415:         }
416: 
417:         $sep = ini_get('arg_separator.input');
418:         $query = http_build_query($params, '', $sep ? $sep[0] : '&');
419:         if ($query != '') { // intentionally ==
420:             $url .= '?' . $query;
421:         }
422: 
423:         return $url;
424:     }
425: 
426: 
427:     /**
428:      * Parse mask and array of default values; initializes object.
429:      * @param  string
430:      * @param  array
431:      * @return void
432:      */
433:     private function setMask($mask, array $metadata)
434:     {
435:         $this->mask = $mask;
436: 
437:         // detect '//host/path' vs. '/abs. path' vs. 'relative path'
438:         if (substr($mask, 0, 2) === '//') {
439:             $this->type = self::HOST;
440: 
441:         } elseif (substr($mask, 0, 1) === '/') {
442:             $this->type = self::PATH;
443: 
444:         } else {
445:             $this->type = self::RELATIVE;
446:         }
447: 
448:         foreach ($metadata as $name => $meta) {
449:             if (!is_array($meta)) {
450:                 $metadata[$name] = array(self::VALUE => $meta, 'fixity' => self::CONSTANT);
451: 
452:             } elseif (array_key_exists(self::VALUE, $meta)) {
453:                 $metadata[$name]['fixity'] = self::CONSTANT;
454:             }
455:         }
456: 
457:         // PARSE MASK
458:         // <parameter-name[=default] [pattern] [#class]> or [ or ] or ?...
459:         $parts = Strings::split($mask, '/<([^>#= ]+)(=[^># ]*)? *([^>#]*)(#?[^>\[\]]*)>|(\[!?|\]|\s*\?.*)/');
460: 
461:         $this->xlat = array();
462:         $i = count($parts) - 1;
463: 
464:         // PARSE QUERY PART OF MASK
465:         if (isset($parts[$i - 1]) && substr(ltrim($parts[$i - 1]), 0, 1) === '?') {
466:             // name=<parameter-name [pattern][#class]>
467:             $matches = Strings::matchAll($parts[$i - 1], '/(?:([a-zA-Z0-9_.-]+)=)?<([^># ]+) *([^>#]*)(#?[^>]*)>/');
468: 
469:             foreach ($matches as $match) {
470:                 list(, $param, $name, $pattern, $class) = $match;  // $pattern is not used
471: 
472:                 if ($class !== '') {
473:                     if (!isset(static::$styles[$class])) {
474:                         throw new Nette\InvalidStateException("Parameter '$name' has '$class' flag, but Route::\$styles['$class'] is not set.");
475:                     }
476:                     $meta = static::$styles[$class];
477: 
478:                 } elseif (isset(static::$styles['?' . $name])) {
479:                     $meta = static::$styles['?' . $name];
480: 
481:                 } else {
482:                     $meta = static::$styles['?#'];
483:                 }
484: 
485:                 if (isset($metadata[$name])) {
486:                     $meta = $metadata[$name] + $meta;
487:                 }
488: 
489:                 if (array_key_exists(self::VALUE, $meta)) {
490:                     $meta['fixity'] = self::OPTIONAL;
491:                 }
492: 
493:                 unset($meta['pattern']);
494:                 $meta['filterTable2'] = empty($meta[self::FILTER_TABLE]) ? NULL : array_flip($meta[self::FILTER_TABLE]);
495: 
496:                 $metadata[$name] = $meta;
497:                 if ($param !== '') {
498:                     $this->xlat[$name] = $param;
499:                 }
500:             }
501:             $i -= 6;
502:         }
503: 
504:         // PARSE PATH PART OF MASK
505:         $brackets = 0; // optional level
506:         $re = '';
507:         $sequence = array();
508:         $autoOptional = TRUE;
509:         $aliases = array();
510:         do {
511:             array_unshift($sequence, $parts[$i]);
512:             $re = preg_quote($parts[$i], '#') . $re;
513:             if ($i === 0) {
514:                 break;
515:             }
516:             $i--;
517: 
518:             $part = $parts[$i]; // [ or ]
519:             if ($part === '[' || $part === ']' || $part === '[!') {
520:                 $brackets += $part[0] === '[' ? -1 : 1;
521:                 if ($brackets < 0) {
522:                     throw new Nette\InvalidArgumentException("Unexpected '$part' in mask '$mask'.");
523:                 }
524:                 array_unshift($sequence, $part);
525:                 $re = ($part[0] === '[' ? '(?:' : ')?') . $re;
526:                 $i -= 5;
527:                 continue;
528:             }
529: 
530:             $class = $parts[$i]; $i--; // validation class
531:             $pattern = trim($parts[$i]); $i--; // validation condition (as regexp)
532:             $default = $parts[$i]; $i--; // default value
533:             $name = $parts[$i]; $i--; // parameter name
534:             array_unshift($sequence, $name);
535: 
536:             if ($name[0] === '?') { // "foo" parameter
537:                 $name = substr($name, 1);
538:                 $re = $pattern ? '(?:' . preg_quote($name, '#') . "|$pattern)$re" : preg_quote($name, '#') . $re;
539:                 $sequence[1] = $name . $sequence[1];
540:                 continue;
541:             }
542: 
543:             // check name (limitation by regexp)
544:             if (preg_match('#[^a-z0-9_-]#i', $name)) {
545:                 throw new Nette\InvalidArgumentException("Parameter name must be alphanumeric string due to limitations of PCRE, '$name' given.");
546:             }
547: 
548:             // pattern, condition & metadata
549:             if ($class !== '') {
550:                 if (!isset(static::$styles[$class])) {
551:                     throw new Nette\InvalidStateException("Parameter '$name' has '$class' flag, but Route::\$styles['$class'] is not set.");
552:                 }
553:                 $meta = static::$styles[$class];
554: 
555:             } elseif (isset(static::$styles[$name])) {
556:                 $meta = static::$styles[$name];
557: 
558:             } else {
559:                 $meta = static::$styles['#'];
560:             }
561: 
562:             if (isset($metadata[$name])) {
563:                 $meta = $metadata[$name] + $meta;
564:             }
565: 
566:             if ($pattern == '' && isset($meta[self::PATTERN])) {
567:                 $pattern = $meta[self::PATTERN];
568:             }
569: 
570:             if ($default !== '') {
571:                 $meta[self::VALUE] = (string) substr($default, 1);
572:                 $meta['fixity'] = self::PATH_OPTIONAL;
573:             }
574: 
575:             $meta['filterTable2'] = empty($meta[self::FILTER_TABLE]) ? NULL : array_flip($meta[self::FILTER_TABLE]);
576:             if (array_key_exists(self::VALUE, $meta)) {
577:                 if (isset($meta['filterTable2'][$meta[self::VALUE]])) {
578:                     $meta['defOut'] = $meta['filterTable2'][$meta[self::VALUE]];
579: 
580:                 } elseif (isset($meta[self::FILTER_OUT])) {
581:                     $meta['defOut'] = call_user_func($meta[self::FILTER_OUT], $meta[self::VALUE]);
582: 
583:                 } else {
584:                     $meta['defOut'] = $meta[self::VALUE];
585:                 }
586:             }
587:             $meta[self::PATTERN] = "#(?:$pattern)\\z#A" . ($this->flags & self::CASE_SENSITIVE ? '' : 'iu');
588: 
589:             // include in expression
590:             $aliases['p' . $i] = $name;
591:             $re = '(?P<p' . $i . '>(?U)' . $pattern . ')' . $re;
592:             if ($brackets) { // is in brackets?
593:                 if (!isset($meta[self::VALUE])) {
594:                     $meta[self::VALUE] = $meta['defOut'] = NULL;
595:                 }
596:                 $meta['fixity'] = self::PATH_OPTIONAL;
597: 
598:             } elseif (!$autoOptional) {
599:                 unset($meta['fixity']);
600: 
601:             } elseif (isset($meta['fixity'])) { // auto-optional
602:                 $re = '(?:' . $re . ')?';
603:                 $meta['fixity'] = self::PATH_OPTIONAL;
604: 
605:             } else {
606:                 $autoOptional = FALSE;
607:             }
608: 
609:             $metadata[$name] = $meta;
610:         } while (TRUE);
611: 
612:         if ($brackets) {
613:             throw new Nette\InvalidArgumentException("Missing closing ']' in mask '$mask'.");
614:         }
615: 
616:         $this->aliases = $aliases;
617:         $this->re = '#' . $re . '/?\z#A' . ($this->flags & self::CASE_SENSITIVE ? '' : 'iu');
618:         $this->metadata = $metadata;
619:         $this->sequence = $sequence;
620:     }
621: 
622: 
623:     /**
624:      * Returns mask.
625:      * @return string
626:      */
627:     public function getMask()
628:     {
629:         return $this->mask;
630:     }
631: 
632: 
633:     /**
634:      * Returns default values.
635:      * @return array
636:      */
637:     public function getDefaults()
638:     {
639:         $defaults = array();
640:         foreach ($this->metadata as $name => $meta) {
641:             if (isset($meta['fixity'])) {
642:                 $defaults[$name] = $meta[self::VALUE];
643:             }
644:         }
645:         return $defaults;
646:     }
647: 
648: 
649:     /**
650:      * Returns flags.
651:      * @return int
652:      */
653:     public function getFlags()
654:     {
655:         return $this->flags;
656:     }
657: 
658: 
659:     /********************* Utilities ****************d*g**/
660: 
661: 
662:     /**
663:      * Proprietary cache aim.
664:      * @internal
665:      * @return string|FALSE
666:      */
667:     public function getTargetPresenter()
668:     {
669:         if ($this->flags & self::ONE_WAY) {
670:             return FALSE;
671:         }
672: 
673:         $m = $this->metadata;
674:         $module = '';
675: 
676:         if (isset($m[self::MODULE_KEY])) {
677:             if (isset($m[self::MODULE_KEY]['fixity']) && $m[self::MODULE_KEY]['fixity'] === self::CONSTANT) {
678:                 $module = $m[self::MODULE_KEY][self::VALUE] . ':';
679:             } else {
680:                 return NULL;
681:             }
682:         }
683: 
684:         if (isset($m[self::PRESENTER_KEY]['fixity']) && $m[self::PRESENTER_KEY]['fixity'] === self::CONSTANT) {
685:             return $module . $m[self::PRESENTER_KEY][self::VALUE];
686:         }
687:         return NULL;
688:     }
689: 
690: 
691:     /**
692:      * Rename keys in array.
693:      * @param  array
694:      * @param  array
695:      * @return array
696:      */
697:     private static function renameKeys($arr, $xlat)
698:     {
699:         if (empty($xlat)) {
700:             return $arr;
701:         }
702: 
703:         $res = array();
704:         $occupied = array_flip($xlat);
705:         foreach ($arr as $k => $v) {
706:             if (isset($xlat[$k])) {
707:                 $res[$xlat[$k]] = $v;
708: 
709:             } elseif (!isset($occupied[$k])) {
710:                 $res[$k] = $v;
711:             }
712:         }
713:         return $res;
714:     }
715: 
716: 
717:     /********************* Inflectors ****************d*g**/
718: 
719: 
720:     /**
721:      * camelCaseAction name -> dash-separated.
722:      * @param  string
723:      * @return string
724:      */
725:     private static function action2path($s)
726:     {
727:         $s = preg_replace('#(.)(?=[A-Z])#', '$1-', $s);
728:         $s = strtolower($s);
729:         $s = rawurlencode($s);
730:         return $s;
731:     }
732: 
733: 
734:     /**
735:      * dash-separated -> camelCaseAction name.
736:      * @param  string
737:      * @return string
738:      */
739:     private static function path2action($s)
740:     {
741:         $s = strtolower($s);
742:         $s = preg_replace('#-(?=[a-z])#', ' ', $s);
743:         $s = substr(ucwords('x' . $s), 1);
744:         //$s = lcfirst(ucwords($s));
745:         $s = str_replace(' ', '', $s);
746:         return $s;
747:     }
748: 
749: 
750:     /**
751:      * PascalCase:Presenter name -> dash-and-dot-separated.
752:      * @param  string
753:      * @return string
754:      */
755:     private static function presenter2path($s)
756:     {
757:         $s = strtr($s, ':', '.');
758:         $s = preg_replace('#([^.])(?=[A-Z])#', '$1-', $s);
759:         $s = strtolower($s);
760:         $s = rawurlencode($s);
761:         return $s;
762:     }
763: 
764: 
765:     /**
766:      * dash-and-dot-separated -> PascalCase:Presenter name.
767:      * @param  string
768:      * @return string
769:      */
770:     private static function path2presenter($s)
771:     {
772:         $s = strtolower($s);
773:         $s = preg_replace('#([.-])(?=[a-z])#', '$1 ', $s);
774:         $s = ucwords($s);
775:         $s = str_replace('. ', ':', $s);
776:         $s = str_replace('- ', '', $s);
777:         return $s;
778:     }
779: 
780: 
781:     /**
782:      * Url encode.
783:      * @param  string
784:      * @return string
785:      */
786:     private static function param2path($s)
787:     {
788:         return str_replace('%2F', '/', rawurlencode($s));
789:     }
790: 
791: 
792:     /********************* Route::$styles manipulator ****************d*g**/
793: 
794: 
795:     /**
796:      * Creates new style.
797:      * @param  string  style name (#style, urlParameter, ?queryParameter)
798:      * @param  string  optional parent style name
799:      * @return void
800:      */
801:     public static function addStyle($style, $parent = '#')
802:     {
803:         if (isset(static::$styles[$style])) {
804:             throw new Nette\InvalidArgumentException("Style '$style' already exists.");
805:         }
806: 
807:         if ($parent !== NULL) {
808:             if (!isset(static::$styles[$parent])) {
809:                 throw new Nette\InvalidArgumentException("Parent style '$parent' doesn't exist.");
810:             }
811:             static::$styles[$style] = static::$styles[$parent];
812: 
813:         } else {
814:             static::$styles[$style] = array();
815:         }
816:     }
817: 
818: 
819:     /**
820:      * Changes style property value.
821:      * @param  string  style name (#style, urlParameter, ?queryParameter)
822:      * @param  string  property name (Route::PATTERN, Route::FILTER_IN, Route::FILTER_OUT, Route::FILTER_TABLE)
823:      * @param  mixed   property value
824:      * @return void
825:      */
826:     public static function setStyleProperty($style, $key, $value)
827:     {
828:         if (!isset(static::$styles[$style])) {
829:             throw new Nette\InvalidArgumentException("Style '$style' doesn't exist.");
830:         }
831:         static::$styles[$style][$key] = $value;
832:     }
833: 
834: }
835: 
Nette 2.2.2 API API documentation generated by ApiGen 2.8.0