Namespaces

  • Nette
    • Application
      • Diagnostics
      • Responses
      • Routers
      • UI
    • Caching
      • Storages
    • ComponentModel
    • Config
      • Adapters
      • Extensions
    • Database
      • Diagnostics
      • Drivers
      • Reflection
      • Table
    • DI
      • Diagnostics
    • Diagnostics
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Latte
      • Macros
    • Loaders
    • Localization
    • Mail
    • Reflection
    • Security
      • Diagnostics
    • Templating
    • Utils
      • PhpGenerator
  • 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 (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 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($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(
122:                 self::PRESENTER_KEY => substr($metadata, 0, $a),
123:                 'action' => $a === strlen($metadata) - 1 ? NULL : substr($metadata, $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: 
148:         if ($this->type === self::HOST) {
149:             $path = '//' . $url->getHost() . $url->getPath();
150: 
151:         } elseif ($this->type === self::RELATIVE) {
152:             $basePath = $url->getBasePath();
153:             if (strncmp($url->getPath(), $basePath, strlen($basePath)) !== 0) {
154:                 return NULL;
155:             }
156:             $path = (string) substr($url->getPath(), strlen($basePath));
157: 
158:         } else {
159:             $path = $url->getPath();
160:         }
161: 
162:         if ($path !== '') {
163:             $path = rtrim($path, '/') . '/';
164:         }
165: 
166:         if (!$matches = Strings::match($path, $this->re)) {
167:             // stop, not matched
168:             return NULL;
169:         }
170: 
171:         // deletes numeric keys, restore '-' chars
172:         $params = array();
173:         foreach ($matches as $k => $v) {
174:             if (is_string($k) && $v !== '') {
175:                 $params[str_replace('___', '-', $k)] = $v; // trick
176:             }
177:         }
178: 
179: 
180:         // 2) CONSTANT FIXITY
181:         foreach ($this->metadata as $name => $meta) {
182:             if (isset($params[$name])) {
183:                 //$params[$name] = $this->flags & self::CASE_SENSITIVE === 0 ? strtolower($params[$name]) : */$params[$name]; // strtolower damages UTF-8
184: 
185:             } elseif (isset($meta['fixity']) && $meta['fixity'] !== self::OPTIONAL) {
186:                 $params[$name] = NULL; // cannot be overwriten in 3) and detected by isset() in 4)
187:             }
188:         }
189: 
190: 
191:         // 3) QUERY
192:         if ($this->xlat) {
193:             $params += self::renameKeys($httpRequest->getQuery(), array_flip($this->xlat));
194:         } else {
195:             $params += $httpRequest->getQuery();
196:         }
197: 
198: 
199:         // 4) APPLY FILTERS & FIXITY
200:         foreach ($this->metadata as $name => $meta) {
201:             if (isset($params[$name])) {
202:                 if (!is_scalar($params[$name])) {
203: 
204:                 } elseif (isset($meta[self::FILTER_TABLE][$params[$name]])) { // applies filterTable only to scalar parameters
205:                     $params[$name] = $meta[self::FILTER_TABLE][$params[$name]];
206: 
207:                 } elseif (isset($meta[self::FILTER_TABLE]) && !empty($meta[self::FILTER_STRICT])) {
208:                     return NULL; // rejected by filterTable
209: 
210:                 } elseif (isset($meta[self::FILTER_IN])) { // applies filterIn only to scalar parameters
211:                     $params[$name] = call_user_func($meta[self::FILTER_IN], (string) $params[$name]);
212:                     if ($params[$name] === NULL && !isset($meta['fixity'])) {
213:                         return NULL; // rejected by filter
214:                     }
215:                 }
216: 
217:             } elseif (isset($meta['fixity'])) {
218:                 $params[$name] = $meta[self::VALUE];
219:             }
220:         }
221: 
222: 
223:         // 5) BUILD Request
224:         if (!isset($params[self::PRESENTER_KEY])) {
225:             throw new Nette\InvalidStateException('Missing presenter in route definition.');
226:         } elseif (!is_string($params[self::PRESENTER_KEY])) {
227:             return NULL;
228:         }
229:         if (isset($this->metadata[self::MODULE_KEY])) {
230:             if (!isset($params[self::MODULE_KEY])) {
231:                 throw new Nette\InvalidStateException('Missing module in route definition.');
232:             }
233:             $presenter = $params[self::MODULE_KEY] . ':' . $params[self::PRESENTER_KEY];
234:             unset($params[self::MODULE_KEY], $params[self::PRESENTER_KEY]);
235: 
236:         } else {
237:             $presenter = $params[self::PRESENTER_KEY];
238:             unset($params[self::PRESENTER_KEY]);
239:         }
240: 
241:         return new Application\Request(
242:             $presenter,
243:             $httpRequest->getMethod(),
244:             $params,
245:             $httpRequest->getPost(),
246:             $httpRequest->getFiles(),
247:             array(Application\Request::SECURED => $httpRequest->isSecured())
248:         );
249:     }
250: 
251: 
252:     /**
253:      * Constructs absolute URL from Request object.
254:      * @return string|NULL
255:      */
256:     public function constructUrl(Application\Request $appRequest, Nette\Http\Url $refUrl)
257:     {
258:         if ($this->flags & self::ONE_WAY) {
259:             return NULL;
260:         }
261: 
262:         $params = $appRequest->getParameters();
263:         $metadata = $this->metadata;
264: 
265:         $presenter = $appRequest->getPresenterName();
266:         $params[self::PRESENTER_KEY] = $presenter;
267: 
268:         if (isset($metadata[self::MODULE_KEY])) { // try split into module and [submodule:]presenter parts
269:             $module = $metadata[self::MODULE_KEY];
270:             if (isset($module['fixity']) && strncasecmp($presenter, $module[self::VALUE] . ':', strlen($module[self::VALUE]) + 1) === 0) {
271:                 $a = strlen($module[self::VALUE]);
272:             } else {
273:                 $a = strrpos($presenter, ':');
274:             }
275:             if ($a === FALSE) {
276:                 $params[self::MODULE_KEY] = '';
277:             } else {
278:                 $params[self::MODULE_KEY] = substr($presenter, 0, $a);
279:                 $params[self::PRESENTER_KEY] = substr($presenter, $a + 1);
280:             }
281:         }
282: 
283:         foreach ($metadata as $name => $meta) {
284:             if (!isset($params[$name])) {
285:                 continue; // retains NULL values
286:             }
287: 
288:             if (isset($meta['fixity'])) {
289:                 if ($params[$name] === FALSE) {
290:                     $params[$name] = '0';
291:                 }
292:                 if (is_scalar($params[$name]) ? strcasecmp($params[$name], $meta[self::VALUE]) === 0
293:                     : $params[$name] === $meta[self::VALUE]
294:                 ) { // remove default values; NULL values are retain
295:                     unset($params[$name]);
296:                     continue;
297: 
298:                 } elseif ($meta['fixity'] === self::CONSTANT) {
299:                     return NULL; // missing or wrong parameter '$name'
300:                 }
301:             }
302: 
303:             if (is_scalar($params[$name]) && isset($meta['filterTable2'][$params[$name]])) {
304:                 $params[$name] = $meta['filterTable2'][$params[$name]];
305: 
306:             } elseif (isset($meta['filterTable2']) && !empty($meta[self::FILTER_STRICT])) {
307:                 return NULL;
308: 
309:             } elseif (isset($meta[self::FILTER_OUT])) {
310:                 $params[$name] = call_user_func($meta[self::FILTER_OUT], $params[$name]);
311:             }
312: 
313:             if (isset($meta[self::PATTERN]) && !preg_match($meta[self::PATTERN], rawurldecode($params[$name]))) {
314:                 return NULL; // pattern not match
315:             }
316:         }
317: 
318:         // compositing path
319:         $sequence = $this->sequence;
320:         $brackets = array();
321:         $required = NULL; // NULL for auto-optional
322:         $url = '';
323:         $i = count($sequence) - 1;
324:         do {
325:             $url = $sequence[$i] . $url;
326:             if ($i === 0) {
327:                 break;
328:             }
329:             $i--;
330: 
331:             $name = $sequence[$i]; $i--; // parameter name
332: 
333:             if ($name === ']') { // opening optional part
334:                 $brackets[] = $url;
335: 
336:             } elseif ($name[0] === '[') { // closing optional part
337:                 $tmp = array_pop($brackets);
338:                 if ($required < count($brackets) + 1) { // is this level optional?
339:                     if ($name !== '[!') { // and not "required"-optional
340:                         $url = $tmp;
341:                     }
342:                 } else {
343:                     $required = count($brackets);
344:                 }
345: 
346:             } elseif ($name[0] === '?') { // "foo" parameter
347:                 continue;
348: 
349:             } elseif (isset($params[$name]) && $params[$name] != '') { // intentionally ==
350:                 $required = count($brackets); // make this level required
351:                 $url = $params[$name] . $url;
352:                 unset($params[$name]);
353: 
354:             } elseif (isset($metadata[$name]['fixity'])) { // has default value?
355:                 if ($required === NULL && !$brackets) { // auto-optional
356:                     $url = '';
357:                 } else {
358:                     $url = $metadata[$name]['defOut'] . $url;
359:                 }
360: 
361:             } else {
362:                 return NULL; // missing parameter '$name'
363:             }
364:         } while (TRUE);
365: 
366: 
367:         // build query string
368:         if ($this->xlat) {
369:             $params = self::renameKeys($params, $this->xlat);
370:         }
371: 
372:         $sep = ini_get('arg_separator.input');
373:         $query = http_build_query($params, '', $sep ? $sep[0] : '&');
374:         if ($query != '') { // intentionally ==
375:             $url .= '?' . $query;
376:         }
377: 
378:         // absolutize path
379:         if ($this->type === self::RELATIVE) {
380:             $url = '//' . $refUrl->getAuthority() . $refUrl->getBasePath() . $url;
381: 
382:         } elseif ($this->type === self::PATH) {
383:             $url = '//' . $refUrl->getAuthority() . $url;
384:         }
385: 
386:         if (strpos($url, '//', 2) !== FALSE) {
387:             return NULL;
388:         }
389: 
390:         $url = ($this->flags & self::SECURED ? 'https:' : 'http:') . $url;
391: 
392:         return $url;
393:     }
394: 
395: 
396:     /**
397:      * Parse mask and array of default values; initializes object.
398:      * @param  string
399:      * @param  array
400:      * @return void
401:      */
402:     private function setMask($mask, array $metadata)
403:     {
404:         $this->mask = $mask;
405: 
406:         // detect '//host/path' vs. '/abs. path' vs. 'relative path'
407:         if (substr($mask, 0, 2) === '//') {
408:             $this->type = self::HOST;
409: 
410:         } elseif (substr($mask, 0, 1) === '/') {
411:             $this->type = self::PATH;
412: 
413:         } else {
414:             $this->type = self::RELATIVE;
415:         }
416: 
417:         foreach ($metadata as $name => $meta) {
418:             if (!is_array($meta)) {
419:                 $metadata[$name] = array(self::VALUE => $meta, 'fixity' => self::CONSTANT);
420: 
421:             } elseif (array_key_exists(self::VALUE, $meta)) {
422:                 $metadata[$name]['fixity'] = self::CONSTANT;
423:             }
424:         }
425: 
426:         // PARSE MASK
427:         // <parameter-name[=default] [pattern] [#class]> or [ or ] or ?...
428:         $parts = Strings::split($mask, '/<([^>#= ]+)(=[^># ]*)? *([^>#]*)(#?[^>\[\]]*)>|(\[!?|\]|\s*\?.*)/');
429: 
430:         $this->xlat = array();
431:         $i = count($parts) - 1;
432: 
433:         // PARSE QUERY PART OF MASK
434:         if (isset($parts[$i - 1]) && substr(ltrim($parts[$i - 1]), 0, 1) === '?') {
435:             // name=<parameter-name [pattern][#class]>
436:             $matches = Strings::matchAll($parts[$i - 1], '/(?:([a-zA-Z0-9_.-]+)=)?<([^># ]+) *([^>#]*)(#?[^>]*)>/');
437: 
438:             foreach ($matches as $match) {
439:                 list(, $param, $name, $pattern, $class) = $match;  // $pattern is not used
440: 
441:                 if ($class !== '') {
442:                     if (!isset(static::$styles[$class])) {
443:                         throw new Nette\InvalidStateException("Parameter '$name' has '$class' flag, but Route::\$styles['$class'] is not set.");
444:                     }
445:                     $meta = static::$styles[$class];
446: 
447:                 } elseif (isset(static::$styles['?' . $name])) {
448:                     $meta = static::$styles['?' . $name];
449: 
450:                 } else {
451:                     $meta = static::$styles['?#'];
452:                 }
453: 
454:                 if (isset($metadata[$name])) {
455:                     $meta = $metadata[$name] + $meta;
456:                 }
457: 
458:                 if (array_key_exists(self::VALUE, $meta)) {
459:                     $meta['fixity'] = self::OPTIONAL;
460:                 }
461: 
462:                 unset($meta['pattern']);
463:                 $meta['filterTable2'] = empty($meta[self::FILTER_TABLE]) ? NULL : array_flip($meta[self::FILTER_TABLE]);
464: 
465:                 $metadata[$name] = $meta;
466:                 if ($param !== '') {
467:                     $this->xlat[$name] = $param;
468:                 }
469:             }
470:             $i -= 6;
471:         }
472: 
473:         // PARSE PATH PART OF MASK
474:         $brackets = 0; // optional level
475:         $re = '';
476:         $sequence = array();
477:         $autoOptional = TRUE;
478:         do {
479:             array_unshift($sequence, $parts[$i]);
480:             $re = preg_quote($parts[$i], '#') . $re;
481:             if ($i === 0) {
482:                 break;
483:             }
484:             $i--;
485: 
486:             $part = $parts[$i]; // [ or ]
487:             if ($part === '[' || $part === ']' || $part === '[!') {
488:                 $brackets += $part[0] === '[' ? -1 : 1;
489:                 if ($brackets < 0) {
490:                     throw new Nette\InvalidArgumentException("Unexpected '$part' in mask '$mask'.");
491:                 }
492:                 array_unshift($sequence, $part);
493:                 $re = ($part[0] === '[' ? '(?:' : ')?') . $re;
494:                 $i -= 5;
495:                 continue;
496:             }
497: 
498:             $class = $parts[$i]; $i--; // validation class
499:             $pattern = trim($parts[$i]); $i--; // validation condition (as regexp)
500:             $default = $parts[$i]; $i--; // default value
501:             $name = $parts[$i]; $i--; // parameter name
502:             array_unshift($sequence, $name);
503: 
504:             if ($name[0] === '?') { // "foo" parameter
505:                 $re = '(?:' . preg_quote(substr($name, 1), '#') . '|' . $pattern . ')' . $re;
506:                 $sequence[1] = substr($name, 1) . $sequence[1];
507:                 continue;
508:             }
509: 
510:             // check name (limitation by regexp)
511:             if (preg_match('#[^a-z0-9_-]#i', $name)) {
512:                 throw new Nette\InvalidArgumentException("Parameter name must be alphanumeric string due to limitations of PCRE, '$name' given.");
513:             }
514: 
515:             // pattern, condition & metadata
516:             if ($class !== '') {
517:                 if (!isset(static::$styles[$class])) {
518:                     throw new Nette\InvalidStateException("Parameter '$name' has '$class' flag, but Route::\$styles['$class'] is not set.");
519:                 }
520:                 $meta = static::$styles[$class];
521: 
522:             } elseif (isset(static::$styles[$name])) {
523:                 $meta = static::$styles[$name];
524: 
525:             } else {
526:                 $meta = static::$styles['#'];
527:             }
528: 
529:             if (isset($metadata[$name])) {
530:                 $meta = $metadata[$name] + $meta;
531:             }
532: 
533:             if ($pattern == '' && isset($meta[self::PATTERN])) {
534:                 $pattern = $meta[self::PATTERN];
535:             }
536: 
537:             if ($default !== '') {
538:                 $meta[self::VALUE] = (string) substr($default, 1);
539:                 $meta['fixity'] = self::PATH_OPTIONAL;
540:             }
541: 
542:             $meta['filterTable2'] = empty($meta[self::FILTER_TABLE]) ? NULL : array_flip($meta[self::FILTER_TABLE]);
543:             if (array_key_exists(self::VALUE, $meta)) {
544:                 if (isset($meta['filterTable2'][$meta[self::VALUE]])) {
545:                     $meta['defOut'] = $meta['filterTable2'][$meta[self::VALUE]];
546: 
547:                 } elseif (isset($meta[self::FILTER_OUT])) {
548:                     $meta['defOut'] = call_user_func($meta[self::FILTER_OUT], $meta[self::VALUE]);
549: 
550:                 } else {
551:                     $meta['defOut'] = $meta[self::VALUE];
552:                 }
553:             }
554:             $meta[self::PATTERN] = "#(?:$pattern)\\z#A" . ($this->flags & self::CASE_SENSITIVE ? '' : 'iu');
555: 
556:             // include in expression
557:             $re = '(?P<' . str_replace('-', '___', $name) . '>(?U)' . $pattern . ')' . $re; // str_replace is dirty trick to enable '-' in parameter name
558:             if ($brackets) { // is in brackets?
559:                 if (!isset($meta[self::VALUE])) {
560:                     $meta[self::VALUE] = $meta['defOut'] = NULL;
561:                 }
562:                 $meta['fixity'] = self::PATH_OPTIONAL;
563: 
564:             } elseif (!$autoOptional) {
565:                 unset($meta['fixity']);
566: 
567:             } elseif (isset($meta['fixity'])) { // auto-optional
568:                 $re = '(?:' . $re . ')?';
569:                 $meta['fixity'] = self::PATH_OPTIONAL;
570: 
571:             } else {
572:                 $autoOptional = FALSE;
573:             }
574: 
575:             $metadata[$name] = $meta;
576:         } while (TRUE);
577: 
578:         if ($brackets) {
579:             throw new Nette\InvalidArgumentException("Missing closing ']' in mask '$mask'.");
580:         }
581: 
582:         $this->re = '#' . $re . '/?\z#A' . ($this->flags & self::CASE_SENSITIVE ? '' : 'iu');
583:         $this->metadata = $metadata;
584:         $this->sequence = $sequence;
585:     }
586: 
587: 
588:     /**
589:      * Returns mask.
590:      * @return string
591:      */
592:     public function getMask()
593:     {
594:         return $this->mask;
595:     }
596: 
597: 
598:     /**
599:      * Returns default values.
600:      * @return array
601:      */
602:     public function getDefaults()
603:     {
604:         $defaults = array();
605:         foreach ($this->metadata as $name => $meta) {
606:             if (isset($meta['fixity'])) {
607:                 $defaults[$name] = $meta[self::VALUE];
608:             }
609:         }
610:         return $defaults;
611:     }
612: 
613: 
614:     /**
615:      * Returns flags.
616:      * @return int
617:      */
618:     public function getFlags()
619:     {
620:         return $this->flags;
621:     }
622: 
623: 
624:     /********************* Utilities ****************d*g**/
625: 
626: 
627:     /**
628:      * Proprietary cache aim.
629:      * @return string|FALSE
630:      */
631:     public function getTargetPresenter()
632:     {
633:         if ($this->flags & self::ONE_WAY) {
634:             return FALSE;
635:         }
636: 
637:         $m = $this->metadata;
638:         $module = '';
639: 
640:         if (isset($m[self::MODULE_KEY])) {
641:             if (isset($m[self::MODULE_KEY]['fixity']) && $m[self::MODULE_KEY]['fixity'] === self::CONSTANT) {
642:                 $module = $m[self::MODULE_KEY][self::VALUE] . ':';
643:             } else {
644:                 return NULL;
645:             }
646:         }
647: 
648:         if (isset($m[self::PRESENTER_KEY]['fixity']) && $m[self::PRESENTER_KEY]['fixity'] === self::CONSTANT) {
649:             return $module . $m[self::PRESENTER_KEY][self::VALUE];
650:         }
651:         return NULL;
652:     }
653: 
654: 
655:     /**
656:      * Rename keys in array.
657:      * @param  array
658:      * @param  array
659:      * @return array
660:      */
661:     private static function renameKeys($arr, $xlat)
662:     {
663:         if (empty($xlat)) {
664:             return $arr;
665:         }
666: 
667:         $res = array();
668:         $occupied = array_flip($xlat);
669:         foreach ($arr as $k => $v) {
670:             if (isset($xlat[$k])) {
671:                 $res[$xlat[$k]] = $v;
672: 
673:             } elseif (!isset($occupied[$k])) {
674:                 $res[$k] = $v;
675:             }
676:         }
677:         return $res;
678:     }
679: 
680: 
681:     /********************* Inflectors ****************d*g**/
682: 
683: 
684:     /**
685:      * camelCaseAction name -> dash-separated.
686:      * @param  string
687:      * @return string
688:      */
689:     private static function action2path($s)
690:     {
691:         $s = preg_replace('#(.)(?=[A-Z])#', '$1-', $s);
692:         $s = strtolower($s);
693:         $s = rawurlencode($s);
694:         return $s;
695:     }
696: 
697: 
698:     /**
699:      * dash-separated -> camelCaseAction name.
700:      * @param  string
701:      * @return string
702:      */
703:     private static function path2action($s)
704:     {
705:         $s = strtolower($s);
706:         $s = preg_replace('#-(?=[a-z])#', ' ', $s);
707:         $s = substr(ucwords('x' . $s), 1);
708:         $s = str_replace(' ', '', $s);
709:         return $s;
710:     }
711: 
712: 
713:     /**
714:      * PascalCase:Presenter name -> dash-and-dot-separated.
715:      * @param  string
716:      * @return string
717:      */
718:     private static function presenter2path($s)
719:     {
720:         $s = strtr($s, ':', '.');
721:         $s = preg_replace('#([^.])(?=[A-Z])#', '$1-', $s);
722:         $s = strtolower($s);
723:         $s = rawurlencode($s);
724:         return $s;
725:     }
726: 
727: 
728:     /**
729:      * dash-and-dot-separated -> PascalCase:Presenter name.
730:      * @param  string
731:      * @return string
732:      */
733:     private static function path2presenter($s)
734:     {
735:         $s = strtolower($s);
736:         $s = preg_replace('#([.-])(?=[a-z])#', '$1 ', $s);
737:         $s = ucwords($s);
738:         $s = str_replace('. ', ':', $s);
739:         $s = str_replace('- ', '', $s);
740:         return $s;
741:     }
742: 
743: 
744:     /**
745:      * Url encode.
746:      * @param  string
747:      * @return string
748:      */
749:     private static function param2path($s)
750:     {
751:         return str_replace('%2F', '/', rawurlencode($s));
752:     }
753: 
754: 
755:     /********************* Route::$styles manipulator ****************d*g**/
756: 
757: 
758:     /**
759:      * Creates new style.
760:      * @param  string  style name (#style, urlParameter, ?queryParameter)
761:      * @param  string  optional parent style name
762:      * @return void
763:      */
764:     public static function addStyle($style, $parent = '#')
765:     {
766:         if (isset(static::$styles[$style])) {
767:             throw new Nette\InvalidArgumentException("Style '$style' already exists.");
768:         }
769: 
770:         if ($parent !== NULL) {
771:             if (!isset(static::$styles[$parent])) {
772:                 throw new Nette\InvalidArgumentException("Parent style '$parent' doesn't exist.");
773:             }
774:             static::$styles[$style] = static::$styles[$parent];
775: 
776:         } else {
777:             static::$styles[$style] = array();
778:         }
779:     }
780: 
781: 
782:     /**
783:      * Changes style property value.
784:      * @param  string  style name (#style, urlParameter, ?queryParameter)
785:      * @param  string  property name (Route::PATTERN, Route::FILTER_IN, Route::FILTER_OUT, Route::FILTER_TABLE)
786:      * @param  mixed   property value
787:      * @return void
788:      */
789:     public static function setStyleProperty($style, $key, $value)
790:     {
791:         if (!isset(static::$styles[$style])) {
792:             throw new Nette\InvalidArgumentException("Style '$style' doesn't exist.");
793:         }
794:         static::$styles[$style][$key] = $value;
795:     }
796: 
797: }
798: 
Nette 2.0 API documentation generated by ApiGen 2.8.0