Packages

  • Nette
    • Application
      • Application\Diagnostics
      • Application\Responses
      • Application\Routers
      • Application\UI
    • Caching
      • Caching\Storages
    • ComponentModel
    • Config
    • Database
      • Database\Diagnostics
      • Database\Drivers
      • Database\Reflection
      • Database\Table
    • DI
    • Diagnostics
    • Forms
      • Forms\Controls
      • Forms\Rendering
    • Http
    • Iterators
    • Latte
      • Latte\Macros
    • Loaders
    • Localization
    • Mail
    • Reflection
    • Security
    • Templating
    • Utils
  • NetteModule
  • None
  • PHP

Classes

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