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

Classes

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