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

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