/components/com_router/router.php
PHP | 1032 lines | 638 code | 194 blank | 200 comment | 159 complexity | 7287067c2065e35268827858cfa6a18d MD5 | raw file
1<?php 2 /** 3 * User: Oli Griffiths 4 * Date: 14/10/2012 5 * Time: 11:50 6 */ 7 8 class ComRouterRouter extends ComApplicationRouter implements KServiceInstantiatable 9 { 10 /** 11 * Holds the registry object 12 * @var ComRouterRegistry 13 */ 14 protected $_registry; 15 16 protected $_cache; 17 18 protected $_cache_identifier = 'koowa-route-cache'; 19 20 protected $_sef_suffix; 21 22 protected $_sef_rewrite; 23 24 protected $_build_modes = array(); 25 26 protected $_parse_modes = array(); 27 28 29 /** 30 * Force creation of a singleton 31 * 32 * @param object An optional KConfig object with configuration options 33 * @param object A KServiceInterface object 34 * @return KDispatcherDefault 35 */ 36 public static function getInstance( KConfigInterface $config, KServiceInterface $container ) 37 { 38 // Check if an instance with this identifier already exists or not 39 if (!$container->has($config->service_identifier)) { 40 //Create the singleton 41 $classname = $config->service_identifier->classname; 42 $instance = new $classname($config); 43 $container->set($config->service_identifier, $instance); 44 } 45 46 return $container->get($config->service_identifier); 47 } 48 49 50 /** 51 * Object constructor 52 * @param KConfig $config 53 */ 54 public function __construct( KConfig $config = null ) 55 { 56 parent::__construct($config); 57 58 $this->_registry = $config->registry; 59 $this->_cache = $config->cache; 60 $this->_build_modes = $config->build_modes; 61 $this->_parse_modes = $config->parse_modes; 62 63 $this->_sef_suffix = $config->sef_suffix; 64 $this->_sef_rewrite = $config->sef_rewrite; 65 } 66 67 68 /** 69 * Initializes the config 70 * @param KConfig $config 71 */ 72 protected function _initialize( KConfig $config ) 73 { 74 $config->append( 75 array( 76 'cache' => false, 77 'build_modes' => array( 78 'db' => true, 79 'registry' => true, 80 'component' => true, 81 'page' => true 82 ), 83 'parse_modes' => array( 84 'db' => true, 85 'registry' => true, 86 'component' => true, 87 'page' => true 88 ), 89 'sef_suffix' => false, 90 'sef_rewrite' => true, 91 'registry_auto_load' => true, 92 'registry_auto_create' => true 93 ) 94 )->append( 95 array( 96 'registry' => $this->getService( 97 'com://site/router.registry', array( 98 'auto_load' => $config->registry_auto_load, 99 'auto_create' => $config->registry_auto_create 100 ) 101 ) 102 ) 103 ); 104 105 parent::_initialize($config); 106 } 107 108 109 /** 110 * Returns the registry object 111 * @return ComRouterRegistry 112 */ 113 public function getRegistry() 114 { 115 return $this->_registry; 116 } 117 118 119 /** 120 * Cleans the APC cache 121 * @param bool $mode - one of, build, parse, template 122 * @param null $option 123 * @param null $view 124 */ 125 public function clearCache( $mode = 0, $option = null, $view = null ) 126 { 127 if (extension_loaded('apc')) { 128 if ($items = apc_cache_info('user')) { 129 130 $query = $this->_cache_identifier . '-' . ($mode ? $mode . '-' . http_build_query(array('option' => $option, 'view' => $view)) : ''); 131 $length = strlen($query); 132 133 foreach ($items['cache_list'] AS $item) { 134 if (substr($item['info'], 0, $length) == $query) { 135 apc_delete($item['info']); 136 } 137 } 138 } 139 } 140 } 141 142 143 /** 144 * Parse attempts to match url against a pre-defined route 145 * @param $url 146 * @return bool|KHttpUrl 147 */ 148 public function parse( KHttpUrl $url ) 149 { 150 // Get the path 151 $path = is_array($url->path) ? implode('/', array_filter($url->path)) : $url->path; 152 153 //Remove base path 154 $path = substr_replace($path, '', 0, strlen(KRequest::root()->getUrl(KHttpUrl::PATH))); 155 156 // Set the format 157 if (!empty($url->format)) { 158 $url->query['format'] = $url->format; 159 } 160 161 //Remove the filename 162 if (!$this->_sef_rewrite) { 163 $path = preg_replace('#^index\.php#', '', $path); 164 } 165 166 //Set the route 167 $url->path = trim($path, '/'); 168 $path = $url->getUrl(KHttpUrl::PATH + KHttpUrl::FORMAT); 169 170 return ($path == 'index.php' || empty($path)) && isset($url->query['option']) ? false : $this->_parseRoute($url); 171 } 172 173 174 /** 175 * Parses a route through 5 separate parsing methods in the following order 176 * 1. APC cache - all parsed urls are stored in the cache 177 * 2. DB routes - checks the DB for a matching route 178 * 3. Registry - checks the route against the registered route templates 179 * 4. Page - attempts to match the route against a page/menu item 180 * 5. View - if page matching was successful, attempt to load component router 181 * @param KHttpUrl $url 182 * @return bool 183 */ 184 protected function _parseRoute( KHttpUrl $url ) 185 { 186 if ($this->_parseCacheRoute($url)) { 187 return true; 188 } 189 190 $route = clone $url; 191 192 $this->_parseSiteRoute($url); 193 194 if (!$result = $this->_parseDbRoute($url)) { 195 if (!$result = $this->_parseRegistryRoute($url)) { 196 if ($result = $this->_parsePageRoute($url)) { 197 $result = $this->_parseComponentRoute($url); 198 } 199 } 200 } 201 202 //Cache result 203 if ($result) { 204 205 //Find itemid 206 if (!isset($url->query['Itemid'])) { 207 $itemid = $this->findPageId($url->query); 208 209 if ($itemid) { 210 $url->query['Itemid'] = $itemid; 211 } 212 } 213 214 //Set active page 215 if (isset($url->query['Itemid'])) { 216 $pages = $this->getService('application.pages'); 217 $pages->setActive($url->query['Itemid']); 218 } 219 220 $url->path = 'index'; 221 $url->format = 'php'; 222 223 $this->_storeRouteToCache($route, $url->query, 1); 224 } 225 226 return $result; 227 } 228 229 230 /** 231 * Parses a route against the APC cache. 232 * @param KHttpUrl $url 233 * @return bool 234 */ 235 protected function _parseCacheRoute( KHttpUrl $url ) 236 { 237 if (extension_loaded('apc') && $this->_cache) { 238 $route = $url->getUrl(KHttpUrl::PATH + KHttpUrl::FORMAT); 239 240 if ($query = apc_fetch($this->_cache_identifier . '-parse-' . $route)) { 241 if (!isset($query['format'])) { 242 $query['format'] = $url->format ? $url->format : 'html'; 243 } 244 $url->query = $query; 245 $url->path = 'index'; 246 $url->format = 'php'; 247 return true; 248 } 249 } 250 251 return false; 252 } 253 254 255 /** 256 * Attempts to match a route that's been cached in the DB 257 * @param KHttpUrl $url 258 * @return bool 259 */ 260 protected function _parseDbRoute( KHttpUrl $url ) 261 { 262 if (!$this->_parse_modes->db) { 263 return false; 264 } 265 266 $model = $this->getService('com://site/router.model.routes'); 267 if (!$model->getTable()) { 268 return false; 269 } 270 271 $path = $url->getUrl(KHttpUrl::PATH); 272 $item = $model->set('url', $path)->getItem(); 273 274 //If item was not found, 275 if ($item->isNew()) { 276 $alias = $this->getService('com://site/router.model.aliases')->set('url', $path)->getItem(); 277 if ($alias && !$alias->isNew()) { 278 $item = $model->set('url', $alias->target)->getItem(); 279 } 280 } 281 282 //Parse & set matching route 283 if (!$item->isNew()) { 284 parse_str($item->route, $query); 285 $url->query = array_merge($query, $url->query); 286 if ($item->itemid && !isset($url->query['Itemid'])) { 287 $url->query['Itemid'] = $item->itemid; 288 } 289 290 return true; 291 } 292 293 return false; 294 } 295 296 297 /** 298 * Attempts to match the route against any registered routes in the registry 299 * @param KHttpUrl $url 300 * @return bool 301 */ 302 protected function _parseRegistryRoute( KHttpUrl $url ) 303 { 304 if (!$this->_parse_modes->registry) { 305 return false; 306 } 307 308 //Check if a static route is defined for this url 309 if ($parsed_url = $this->_registry->parse($url)) { 310 if (isset($parsed_url->query['option'])) { 311 $url->query = $url->query + $parsed_url->query; 312 $url->path = $parsed_url->path; 313 $url->format = $parsed_url->format; 314 return true; 315 } 316 } 317 318 return false; 319 } 320 321 322 /** 323 * @param KHttpUrl $url 324 * @return bool 325 */ 326 protected function _parsePageRoute( KHttpUrl $url ) 327 { 328 if (!$this->_parse_modes->page) { 329 return false; 330 } 331 332 $route = $url->getUrl(KHttpUrl::PATH); 333 $pages = $this->getService('application.pages'); 334 335 $result = false; 336 337 if (substr($route, 0, 9) != 'component') { 338 //Need to reverse the array (highest sublevels first) 339 foreach (array_reverse($pages->id) as $id) { 340 $page = $pages->getPage($id); 341 $length = strlen($page->route); 342 343 if ($length > 0 && strpos($route . '/', $page->route . '/') === 0 && $page->type != 'pagelink') { 344 $route = substr($route, $length); 345 346 if ($page->type != 'redirect') { 347 $url->query += $page->link->query; 348 $url->query['Itemid'] = $page->id; 349 } 350 351 $pages->setActive($page->id); 352 $result = true; 353 break; 354 } 355 } 356 } else { 357 $segments = explode('/', $route); 358 $route = str_replace('component/' . $segments[1], '', $route); 359 360 if (!isset($url->query['Itemid'])) { 361 $url->query['Itemid'] = $pages->getHome()->id; 362 } 363 $url->query['option'] = 'com_' . $segments[1]; 364 $result = true; 365 } 366 367 $url->path = ltrim($route, '/'); 368 369 return $result; 370 } 371 372 373 /** 374 * Passes the URL parsing to the relevant component 375 * @param KHttpUrl $url 376 * @return bool 377 */ 378 protected function _parseComponentRoute( KHttpUrl $url ) 379 { 380 if (!$this->_parse_modes->component) { 381 return false; 382 } 383 384 $route = $url->path; 385 386 if (isset($url->query['option'])) { 387 if (!empty($route)) { 388 try { 389 //Get the router identifier 390 $identifier = 'com://site/' . substr($url->query['option'], 4) . '.router'; 391 392 //Parse the view route 393 $vars = KService::get($identifier)->parseRoute($route); 394 395 //Merge default and vars 396 $url->query = array_merge($url->query, $vars); 397 398 return true; 399 400 } catch ( KException $e ) { 401 return false; 402 } 403 } 404 } 405 406 return true; 407 } 408 409 410 /** 411 * Primary build method, routes are build according to 5 build methods. 412 * 1. APC cache - all built urls are stored in the cache 413 * 2. DB routes - checks the DB for an exact matching route 414 * 3. Registry - checks the route against the registered route templates and attempts to build 415 * 4. Component - if page matching was successful, attempt to load component router 416 * 5. Page - attempts to match the route against a page/menu item 417 * @param $url | KHttpUrl 418 * @return bool - true/false if the route was built 419 */ 420 public function build( KHttpUrl $url ) 421 { 422 $result = false; 423 if ($url->getUrl(KHttpUrl::PATH + KHttpUrl::FORMAT) == 'index.php') { 424 static $site; 425 426 $cached = false; 427 $query = $url->query; 428 if (!isset($site)) { 429 $site = $this->_buildSiteRoute($url); 430 } 431 432 //Check cache for route first, then build if no match 433 if (false !== $result = $this->_buildCacheRoute($url)) { 434 $cached = true; 435 } else { 436 $result = $this->_buildRoute($url); 437 } 438 439 if ($result) { 440 // Get the path data 441 $path = $url->path; 442 443 //Cache route 444 if (!$cached) { 445 $this->_storeRouteToCache($url, $query); 446 } 447 448 //Add site to route 449 if (count($site)) { 450 $path = array_merge($site, $url->path); 451 } 452 453 //Add root path 454 if ($root = ltrim(KRequest::root()->getUrl(KHttpUrl::PATH), '/')) { 455 array_unshift($path, $root); 456 } 457 458 //Ensure all routes start with / 459 array_unshift($path, ''); 460 461 //Set path 462 $url->path = $path; 463 464 } else { 465 if (!$cached) { 466 //Cache failed route 467 $this->_storeRouteToCache(null, $query); 468 } 469 } 470 } 471 472 return $result ? $url : false; 473 } 474 475 476 /** 477 * Builds the route from an internal querystring route 478 * @param KHttpUrl $url 479 * @return bool 480 */ 481 protected function _buildRoute( KHttpUrl $url ) 482 { 483 $query = $url->query; 484 $cache = false; 485 486 //Attempt to set the url from pages if no option set 487 if (!isset($url->query['option']) && isset($url->query['Itemid'])) { 488 if ($page = $this->getService('application.pages')->getPage($url->query['Itemid'])) { 489 $url->path = $page->link->path; 490 $url->query = $page->link->query; 491 $url->format = $page->link->format; 492 } 493 } 494 495 //Build 496 if (false === $segments = $this->_buildDbRoute($url)) { 497 if (false === $segments = $this->_buildRegistryRoute($url)) { 498 if (false !== $route = $this->_buildComponentRoute($url)) { 499 $page = $route['page'] ? $this->_buildPageRoute($url, $query) : array(); 500 $segments = array_merge($page, $route['segments']); 501 $cache = $route['cache']; 502 } else { 503 if ($route = $this->_buildPageRoute($url, $query)) { 504 $segments = $route; 505 } 506 } 507 } 508 } 509 510 $result = false; 511 if ($segments !== false) { 512 513 //Remove empty values 514 $segments = array_filter($segments); 515 516 //Format values 517 array_walk($segments, array($this, '_encodeSegments')); 518 519 //Set the path 520 $url->path = $segments; 521 522 //Remove Itemid if there is a DB override or it matches the id found from pages 523 //also ensure the itemid hasnt been set by any router methods 524 if (isset($url->query['Itemid']) && isset($query['Itemid']) && $url->query['Itemid'] == $query['Itemid']) { 525 unset($query['Itemid']); 526 if ($itemid = $this->findPageIdOverride($query)) { 527 unset($url->query['Itemid']); 528 } else { 529 if ($url->query['Itemid'] == $this->findPageId($query)) { 530 unset($url->query['Itemid']); 531 } 532 } 533 } 534 535 // Removed unused query variables 536 unset($url->query['option']); 537 538 //Add the format to the uri 539 $format = isset($url->query['format']) ? $url->query['format'] : ($url->format == 'php' ? 'html' : $url->format); 540 541 if ($this->_sef_suffix) { 542 unset($url->query['format']); 543 } else { 544 if ($format == 'html') { 545 unset($url->query['format']); 546 $format = null; 547 } 548 } 549 550 //Remove default layout 551 if (isset($url->query['layout']) && $url->query['layout'] == 'default') { 552 unset($url->query['layout']); 553 } 554 555 //Clear format for empty paths 556 if (empty($url->path)) { 557 $format = ''; 558 } 559 560 //Transform the route 561 if (!$this->_sef_rewrite) { 562 $url->path = array_merge(array('index.php'), $url->path); 563 $format = null; 564 } 565 566 $url->format = $format; 567 $result = true; 568 569 if ($cache && !empty($url->path)) { 570 $this->_storeRouteToDb($url, $query); 571 } 572 } 573 574 return $result; 575 } 576 577 578 /** 579 * Builds a route from the APC cache if found 580 * @param KHttpUrl $url 581 * @return bool|mixed 582 */ 583 protected function _buildCacheRoute( KHttpUrl $url ) 584 { 585 if (extension_loaded('apc') && $this->_cache) { 586 $query = $url->query; 587 588 //Ensure option and query come first, required for clean cache 589 $q = array(); 590 if (isset($query['option'])) { 591 $q['option'] = $query['option']; 592 unset($query['option']); 593 } 594 if (isset($query['view'])) { 595 $q['view'] = $query['view']; 596 unset($query['option']); 597 } 598 599 ksort($query); 600 $query = http_build_query(array_merge($q, $query)); 601 602 if ($route = apc_fetch($this->_cache_identifier . '-build-' . $query)) { 603 $url->query = array(); 604 $url->setUrl($route); 605 return true; 606 } else { 607 return $route; 608 } 609 } 610 611 return false; 612 } 613 614 615 /** 616 * Builds a route from the DB table (router_routes) 617 * @param KHttpUrl $url 618 * @return mixed 619 */ 620 protected function _buildDbRoute( KHttpUrl $url ) 621 { 622 static $routes = array(); 623 624 if (!$this->_build_modes->db) { 625 return false; 626 } 627 628 $query = $url->query; 629 630 //Ensure option and query come first, required for clean cache 631 $q = array(); 632 633 if (isset($query['option'])) { 634 $q['option'] = $query['option']; 635 unset($query['option']); 636 } 637 if (isset($query['view'])) { 638 $q['view'] = $query['view']; 639 unset($query['option']); 640 } 641 unset($query['Itemid']); 642 643 ksort($query); 644 $query = array_merge($q, $query); 645 $querystring = http_build_query($query); 646 647 if (!isset($routes[$querystring])) { 648 $routes[$querystring] = false; 649 650 if ($route = $this->getService('com://site/router.model.routes')->set('route', $querystring)->getItem()) { 651 652 if (!$route->isNew()) { 653 $tmp = $this->getService('koowa:http.url', array('url' => ltrim($route->url, '/'))); 654 $tmp->setPath($tmp->path); //forces path into an array 655 $tmp->setQuery($route->route); 656 if ($route->itemid) { 657 $tmp->query['Itemid'] = $route->itemid; 658 } 659 $routes[$querystring] = $tmp; 660 } 661 } 662 } 663 664 if ($routes[$querystring]) { 665 666 $route = $routes[$querystring]; 667 668 //Remove the querystring parts from the route 669 $url->query = array_diff_key($url->query, $route->query); 670 671 return $route->path; 672 } 673 674 return $routes[$querystring]; 675 } 676 677 678 /** 679 * Attempts to build a route from a connected route in the registry 680 * @param KHttpUrl $url 681 * @return mixed 682 */ 683 protected function _buildRegistryRoute( KHttpUrl $url ) 684 { 685 if (!$this->_build_modes->registry) { 686 return false; 687 } 688 689 $route = $this->_registry->match($url); 690 691 return $route ? $url->path : false; 692 } 693 694 695 /** 696 * Builds a route using a components router. 697 * If the router has a cache property that is set to true, this will cause the router to store the rout to the DB 698 * @param KHttpUrl $url 699 * @return array|bool 700 */ 701 protected function _buildComponentRoute( KHttpUrl $url ) 702 { 703 if (!$this->_build_modes->component) { 704 return false; 705 } 706 707 // Use the custom routing handler if it exists 708 if (isset($url->query['option'])) { 709 //Get the router identifier 710 $identifier = 'com://site/' . substr($url->query['option'], 4) . '.router'; 711 712 try { 713 //Build the view route 714 $router = KService::get($identifier, array('router' => $this)); 715 716 if (false !== $segments = $router->buildRoute($url->query, $url)) { 717 return array( 718 'segments' => $segments, 719 'cache' => isset($router->cache) ? $router->cache : false, 720 'page' => isset($router->page) ? $router->page : true 721 ); 722 } 723 } catch ( KException $e ) { 724 } 725 } 726 727 return false; 728 } 729 730 731 /** 732 * Builds a route based on pages 733 * @param KHttpUrl $url 734 * @return array 735 */ 736 protected function _buildPageRoute( KHttpUrl $url, &$query ) 737 { 738 if (!$this->_build_modes->page) { 739 return false; 740 } 741 742 $segments = ''; 743 744 if (!isset($url->query['Itemid'])) { 745 //Find itemid and set 746 if ($itemid = $this->findPageId($query)) { 747 $url->query['Itemid'] = $itemid; 748 $query['Itemid'] = $itemid; 749 } 750 751 //If not set, default to active menu item 752 if (!isset($url->query['Itemid'])) { 753 $page = $this->getService('application.pages')->getActive(); 754 if ($page) { 755 $url->query['Itemid'] = $page->id; 756 } 757 } 758 } 759 760 //Get page route 761 if (isset($url->query['Itemid'])) { 762 $page = $this->getService('application.pages')->getPage($url->query['Itemid']); 763 764 //@TODO: JPathway is currently modifying the url stored in the page, we need to reset it here 765 if (!isset($page->link->query['option']) && strpos($page->link_url, 'option=') !== false) { 766 $page->link = $this->getService('koowa:http.url', array('url' => $page->link_url)); 767 } 768 769 if ($page->link->query['option'] == $url->query['option']) { 770 $segments = $page->route; 771 772 // anonymous functions only work 5.3+ 773 if (version_compare(PHP_VERSION, '5.3.0') >= 0) { 774 // remove empty keys from $page->link->query which are not empty in $url->query 775 // otherwise stuff gets fucked up, like pagination on search loses query parameter 776 array_walk( 777 $page->link->query, function ( $value, $key ) use ( $url, $page ) { 778 if (empty($value) && !empty($url->query[$key])) { 779 unset($page->link->query[$key]); 780 } 781 } 782 ); 783 } 784 785 $url->query = array_diff_key($url->query, $page->link->query); 786 } else { 787 $segments = 'component/' . substr($url->query['option'], 4); 788 } 789 } else { 790 $segments = 'component/' . substr($url->query['option'], 4); 791 } 792 793 $segments = explode('/', $segments); 794 795 return $segments; 796 } 797 798 799 /** 800 * Stores a URL and query to the cache. 801 * @param KHttpUrl|null $url 802 * @param array $query 803 * @param bool $mode - 0 = build storage, 1 = parse storage 804 * @return bool 805 */ 806 protected function _storeRouteToCache( $url, array $query, $mode = 0 ) 807 { 808 if (extension_loaded('apc') && $this->_cache) { 809 810 if ($mode == 1) { 811 return apc_store($this->_cache_identifier . '-parse-' . $url->getUrl(KHttpUrl::PATH + KHttpUrl::FORMAT), $query); 812 } else { 813 //Ensure option and query come first, required for clean cache 814 $q = array(); 815 if (isset($query['option'])) { 816 $q['option'] = $query['option']; 817 unset($query['option']); 818 } 819 if (isset($query['view'])) { 820 $q['view'] = $query['view']; 821 unset($query['option']); 822 } 823 ksort($query); 824 $query = http_build_query(array_merge($q, $query)); 825 826 return apc_store($this->_cache_identifier . '-build-' . $query, $url instanceof KHttpUrl ? $url->getUrl(KHttpUrl::PATH + KHttpUrl::FORMAT + KHttpUrl::QUERY) : $url); 827 } 828 } 829 830 return false; 831 } 832 833 834 /** 835 * Stores a route in the DB 836 * @param KHttpUrl $url 837 * @param $query 838 * @return bool 839 */ 840 protected function _storeRouteToDb( KHttpUrl $url, $query ) 841 { 842 $extras = array_diff_key($url->query, $query); 843 $query = array_diff_key($query, $url->query); 844 $query = array_merge($query, $extras); 845 846 //Ensure option and query come first, required for clean cache 847 $q = array(); 848 $itemid = null; 849 if (isset($query['option'])) { 850 $q['option'] = $query['option']; 851 unset($query['option']); 852 unset($url->query['option']); 853 } 854 if (isset($query['view'])) { 855 $q['view'] = $query['view']; 856 unset($query['option']); 857 unset($url->query['option']); 858 } 859 if (isset($query['Itemid']) || isset($url->query['Itemid'])) { 860 $itemid = isset($url->query['Itemid']) ? $url->query['Itemid'] : $query['Itemid']; 861 unset($url->query['Itemid']); 862 } 863 unset($query['Itemid']); 864 865 ksort($query); 866 $query = array_merge($q, $query); 867 $querystring = http_build_query($query); 868 869 $route = $url->getUrl(KHttpUrl::PATH); 870 871 $item = $this->getService('com://site/router.model.routes')->set('url', $route)->getItem(); 872 if ($item && $item->isNew()) { 873 try { 874 $this->getService('com://site/router.controller.route')->save(array('url' => $route, 'route' => $querystring, 'itemid' => $itemid)); 875 return true; 876 } catch ( KException $e ) { 877 } 878 } 879 880 return true; 881 } 882 883 884 /** 885 * Finds the itemid for a component & view pair 886 * First looking in the DB table 887 * @param $component 888 * @param $view 889 * @return mixed 890 */ 891 public function findPageId( $query ) 892 { 893 static $ids = array(); 894 895 $querystring = http_build_query($query); 896 if (!isset($ids[$querystring])) { 897 898 if ($id = $this->findPageIdOverride($query)) { 899 $ids[$querystring] = $id; 900 } else { 901 $page = $this->findPage($query); 902 $ids[$querystring] = $page ? $page->id : false; 903 } 904 } 905 906 return $ids[$querystring]; 907 } 908 909 910 /** 911 * Finds the page for a component & view pair 912 * @param $component 913 * @param $view 914 * @return mixed 915 */ 916 public function findPage( $query ) 917 { 918 static $ids = array(); 919 920 ksort($query); 921 $querystring = http_build_query($query); 922 923 if (!isset($ids[$querystring])) { 924 925 $pages = $this->getService('application.pages'); 926 927 $view = isset($query['view']) ? $query['view'] : null; 928 $itemid = isset($query['Itemid']) ? $query['Itemid'] : null; 929 $match = null; 930 $view_plural = KInflector::pluralize($view); 931 932 if (!$match = $pages->find($itemid)) { 933 934 //Need to reverse the array (highest sublevels first) 935 foreach (array_reverse($pages->id) as $id) { 936 $page = $pages->getPage($id); 937 938 if (!$page->link) { 939 $page->link = $this->getService('koowa:http.url', array('url' => $page->link_url)); 940 } 941 942 if (count($page->link->query) && array_intersect_key($query, $page->link->query) == $page->link->query) { 943 $match = $page; 944 break; 945 } 946 } 947 948 //Try plural views 949 if (!$match && $view_plural != $view) { 950 foreach (array_reverse($pages->id) as $id) { 951 $page = $pages->getPage($id); 952 953 $q = $query; 954 $q['view'] = $view_plural; 955 if (count($page->link->query) && array_intersect_key($q, $page->link->query) == $page->link->query) { 956 $match = $page; 957 break; 958 } 959 } 960 } 961 } 962 963 $ids[$querystring] = $match; 964 } 965 966 return $ids[$querystring]; 967 } 968 969 /** 970 * Find an override Itemid. This can be set in the router_itemids table 971 * If set, all routes that return an ID from this method have their Itemid removed on build/added on parse 972 * @param $query 973 * @return mixed 974 */ 975 public function findPageIdOverride( $query ) 976 { 977 static $ids = array(); 978 979 ksort($query); 980 $option = isset($query['option']) ? $query['option'] : null; 981 $view = isset($query['view']) ? $query['view'] : null; 982 983 $querystring = http_build_query($query); 984 985 if (!isset($ids[$querystring])) { 986 $component = preg_replace('#^com_#', '', $option); 987 988 $list = $this->getService('com://site/router.model.itemids')->set( 989 array( 990 'component' => $component, 991 'view' => $view, 992 'sort' => 'view', 993 'direction' => 'DESC', 994 'limit' => 1 995 ) 996 )->getList(); 997 998 if ($list) { 999 1000 $match = false; 1001 foreach ($list AS $item) { 1002 parse_str($item->params, $q); 1003 if (array_intersect_key($query, $q) == $q) { 1004 $match = $item; 1005 } 1006 } 1007 1008 if (!$match && KInflector::isSingular($view)) { 1009 $query['view'] = KInflector::pluralize($view); 1010 if ($query['view'] != $view) { 1011 $ids[$querystring] = $this->findPageIdOverride($query); 1012 } else { 1013 $ids[$querystring] = false; 1014 } 1015 } else { 1016 $ids[$querystring] = $match ? $match->itemid : false; 1017 } 1018 1019 } else { 1020 $ids[$querystring] = false; 1021 } 1022 } 1023 1024 return $ids[$querystring]; 1025 } 1026 1027 1028 protected function _encodeSegments( &$segment ) 1029 { 1030 $segment = strtolower(rawurlencode(str_replace(' ', '-', $segment))); 1031 } 1032 }