PageRenderTime 66ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 1ms

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