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