Packages

  • 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

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