Source for file Route.php

Documentation is available at Route.php

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