Namespaces

  • Nette
    • Application
    • Caching
    • Collections
    • Config
    • Forms
    • IO
    • Loaders
    • Mail
    • Reflection
    • Security
    • Templates
    • Web
  • None
  • PHP

Classes

  • AppForm
  • Application
  • CliRouter
  • Control
  • DownloadResponse
  • ForwardingResponse
  • JsonResponse
  • Link
  • MultiRouter
  • Presenter
  • PresenterComponent
  • PresenterLoader
  • PresenterRequest
  • RedirectingResponse
  • RenderResponse
  • Route
  • SimpleRouter

Interfaces

  • IPartiallyRenderable
  • IPresenter
  • IPresenterLoader
  • IPresenterResponse
  • IRenderable
  • IRouter
  • ISignalReceiver
  • IStatePersistent

Exceptions

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