Packages

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

Classes

  • NAppForm
  • NApplication
  • NCliRouter
  • NControl
  • NDownloadResponse
  • NForwardingResponse
  • NJsonResponse
  • NLink
  • NMultiRouter
  • NPresenter
  • NPresenterComponent
  • NPresenterLoader
  • NPresenterRequest
  • NRedirectingResponse
  • NRenderResponse
  • NRoute
  • NSimpleRouter

Interfaces

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

Exceptions

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