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
  • PHP

Classes

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