Namespaces

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