PageRenderTime 34ms CodeModel.GetById 2ms app.highlight 24ms RepoModel.GetById 2ms app.codeStats 0ms

/components/com_router/router.php

https://bitbucket.org/oligriffiths/com_router
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    }