diff --git a/components/com_tags/src/Helper/RouteHelper.php b/components/com_tags/src/Helper/RouteHelper.php index 0a130fb8d2b6b..68fbca89147c7 100644 --- a/components/com_tags/src/Helper/RouteHelper.php +++ b/components/com_tags/src/Helper/RouteHelper.php @@ -1,19 +1,16 @@ - * @license GNU General Public License version 2 or later; see LICENSE.txt + * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\Component\Tags\Site\Helper; -use Exception; -use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\Helper\RouteHelper as CMSRouteHelper; -use Joomla\CMS\Menu\AbstractMenu; // phpcs:disable PSR1.Files.SideEffects \defined('_JEXEC') or die; @@ -47,22 +44,32 @@ class RouteHelper extends CMSRouteHelper * * @since 3.1 */ - public static function getItemRoute($contentItemId, $contentItemAlias, $contentCatId, $language, $typeAlias, $routerName) - { - $link = ''; - $explodedAlias = explode('.', $typeAlias); + public static function getItemRoute( + $contentItemId, + $contentItemAlias, + $contentCatId, + $language, + $typeAlias, + $routerName + ) { + $link = ''; + $explodedAlias = explode('.', $typeAlias); $explodedRouter = explode('::', $routerName); if (file_exists($routerFile = JPATH_BASE . '/components/' . $explodedAlias[0] . '/helpers/route.php')) { \JLoader::register($explodedRouter[0], $routerFile); - $routerClass = $explodedRouter[0]; + $routerClass = $explodedRouter[0]; $routerMethod = $explodedRouter[1]; if (class_exists($routerClass) && method_exists($routerClass, $routerMethod)) { if ($routerMethod === 'getCategoryRoute') { $link = $routerClass::$routerMethod($contentItemId, $language); } else { - $link = $routerClass::$routerMethod($contentItemId . ':' . $contentItemAlias, $contentCatId, $language); + $link = $routerClass::$routerMethod( + $contentItemId . ':' . $contentItemAlias, + $contentCatId, + $language + ); } } } @@ -70,7 +77,7 @@ public static function getItemRoute($contentItemId, $contentItemAlias, $contentC if ($link === '') { // Create a fallback link in case we can't find the component router $router = new CMSRouteHelper(); - $link = $router->getRoute($contentItemId, $typeAlias, $link, $language, $contentCatId); + $link = $router->getRoute($contentItemId, $typeAlias, $link, $language, $contentCatId); } return $link; @@ -79,59 +86,19 @@ public static function getItemRoute($contentItemId, $contentItemAlias, $contentC /** * Tries to load the router for the component and calls it. Otherwise calls getRoute. * - * @param integer $id The ID of the tag + * @param integer $id The ID of the tag * * @return string URL link to pass to the router * - * @since 3.1 - * @throws Exception - * @deprecated 5.0.0 Use getComponentTagRoute() instead + * @since 3.1 */ public static function getTagRoute($id) { - @trigger_error('This function is replaced by the getComponentTagRoute()', E_USER_DEPRECATED); - - return self::getComponentTagRoute($id); - } - - /** - * Tries to load the router for the component and calls it. Otherwise calls getRoute. - * - * @param string $id The ID of the tag in the format TAG_ID:TAG_ALIAS - * @param string $language The language of the tag - * - * @return string URL link to pass to the router - * - * @since 4.2.0 - * @throws Exception - */ - public static function getComponentTagRoute(string $id, string $language = '*'): string - { - $needles = [ - 'tag' => [(int) $id], - 'language' => $language, - ]; - if ($id < 1) { - $link = ''; - } else { - $link = 'index.php?option=com_tags&view=tag&id=' . $id; - - if ($item = self::_findItem($needles)) { - $link .= '&Itemid=' . $item; - } else { - $needles = [ - 'tags' => [1, 0], - 'language' => $language, - ]; - - if ($item = self::_findItem($needles)) { - $link .= '&Itemid=' . $item; - } - } + return ''; } - return $link; + return 'index.php?option=com_tags&view=tag&id=' . $id; } /** @@ -139,112 +106,10 @@ public static function getComponentTagRoute(string $id, string $language = '*'): * * @return string URL link to pass to the router * - * @since 3.7 - * @throws Exception - * @deprecated 5.0.0 + * @since 3.7 */ public static function getTagsRoute() { - @trigger_error('This function is replaced by the getComponentTagsRoute()', E_USER_DEPRECATED); - - return self::getComponentTagsRoute(); - } - - /** - * Tries to load the router for the tags view. - * - * @param string $language The language of the tag - * - * @return string URL link to pass to the router - * - * @since 4.2.0 - * @throws Exception - */ - public static function getComponentTagsRoute(string $language = '*'): string - { - $needles = [ - 'tags' => [0], - 'language' => $language, - ]; - - $link = 'index.php?option=com_tags&view=tags'; - - if ($item = self::_findItem($needles)) { - $link .= '&Itemid=' . $item; - } - - return $link; - } - - /** - * Find Item static function - * - * @param array $needles Array used to get the language value - * - * @return null - * - * @throws Exception - */ - protected static function _findItem($needles = null) - { - $menus = AbstractMenu::getInstance('site'); - $language = $needles['language'] ?? '*'; - - // Prepare the reverse lookup array. - if (self::$lookup === null) { - self::$lookup = array(); - - $component = ComponentHelper::getComponent('com_tags'); - $items = $menus->getItems('component_id', $component->id); - - if ($items) { - foreach ($items as $item) { - if (isset($item->query, $item->query['view'])) { - $lang = ($item->language != '' ? $item->language : '*'); - - if (!isset(self::$lookup[$lang])) { - self::$lookup[$lang] = array(); - } - - $view = $item->query['view']; - - if (!isset(self::$lookup[$lang][$view])) { - self::$lookup[$lang][$view] = array(); - } - - // Only match menu items that list one tag - if (isset($item->query['id']) && is_array($item->query['id'])) { - foreach ($item->query['id'] as $position => $tagId) { - if (!isset(self::$lookup[$lang][$view][$item->query['id'][$position]]) || count($item->query['id']) == 1) { - self::$lookup[$lang][$view][$item->query['id'][$position]] = $item->id; - } - } - } elseif ($view == 'tags') { - self::$lookup[$lang]['tags'][] = $item->id; - } - } - } - } - } - - if ($needles) { - foreach ($needles as $view => $ids) { - if (isset(self::$lookup[$language][$view])) { - foreach ($ids as $id) { - if (isset(self::$lookup[$language][$view][(int) $id])) { - return self::$lookup[$language][$view][(int) $id]; - } - } - } - } - } else { - $active = $menus->getActive(); - - if ($active) { - return $active->id; - } - } - - return null; + return 'index.php?option=com_tags&view=tags'; } } diff --git a/components/com_tags/src/Service/Router.php b/components/com_tags/src/Service/Router.php index 545e1d1fdb483..f6caa703d47d8 100644 --- a/components/com_tags/src/Service/Router.php +++ b/components/com_tags/src/Service/Router.php @@ -1,11 +1,11 @@ - * @license GNU General Public License version 2 or later; see LICENSE.txt + * @license GNU General Public License version 2 or later; see LICENSE.txt */ namespace Joomla\Component\Tags\Site\Service; @@ -13,6 +13,7 @@ use Joomla\CMS\Application\SiteApplication; use Joomla\CMS\Categories\CategoryFactoryInterface; use Joomla\CMS\Component\Router\RouterBase; +use Joomla\CMS\Language\Multilanguage; use Joomla\CMS\Menu\AbstractMenu; use Joomla\Database\DatabaseInterface; use Joomla\Utilities\ArrayHelper; @@ -28,12 +29,20 @@ */ class Router extends RouterBase { + /** + * Lookup array of the menu items + * + * @var array + * @since __DEPLOY_VERSION__ + */ + protected $lookup = array(); + /** * The db * * @var DatabaseInterface * - * @since 4.0.0 + * @since __DEPLOY_VERSION__ */ private $db; @@ -47,11 +56,128 @@ class Router extends RouterBase * * @since 4.0.0 */ - public function __construct(SiteApplication $app, AbstractMenu $menu, ?CategoryFactoryInterface $categoryFactory, DatabaseInterface $db) - { + public function __construct( + SiteApplication $app, + AbstractMenu $menu, + ?CategoryFactoryInterface $categoryFactory, + DatabaseInterface $db + ) { + parent::__construct($app, $menu); + $this->db = $db; - parent::__construct($app, $menu); + $this->buildLookup(); + } + + /** + * Method to preprocess a URL + * + * @param array $query An associative array of URL arguments + * + * @return array The URL arguments to use to assemble the subsequent URL. + * + * @since 4.0 + */ + public function preprocess($query) + { + $active = $this->menu->getActive(); + + /** + * If the active item id is not the same as the supplied item id or we have a supplied item id and no active + * menu item then we just use the supplied menu item and continue + */ + if (isset($query['Itemid']) && ($active === null || $query['Itemid'] != $active->id)) { + return $query; + } + + // Get query language + $language = isset($query['lang']) ? $query['lang'] : '*'; + + // Set the language to the current one when multilang is enabled and item is tagged to ALL + if (Multilanguage::isEnabled() && $language === '*') { + $language = $this->app->get('language'); + } + + if (!isset($this->lookup[$language])) { + $this->buildLookup($language); + } + + // Check if the active menu item matches the requested query + if ($active !== null && isset($query['Itemid'])) { + // Check if active->query and supplied query are the same + $match = true; + + foreach ($active->query as $k => $v) { + if (isset($query[$k]) && $v !== $query[$k]) { + // Compare again without alias + if (\is_string($v) && $v == current(explode(':', $query[$k], 2))) { + continue; + } + + $match = false; + break; + } + } + + if ($match) { + // Just use the supplied menu item + return $query; + } + } + + $id = ''; + + if (isset($query['id'])) { + if (is_array($query['id'])) { + $ids = ArrayHelper::toInteger($query['id']); + $id = implode(':', $ids); + } else { + $id = (int)$query['id']; + } + } + + $view = $query['view']; + $layout = isset($query['layout']) && $query['layout'] !== 'default' ? ':' . $query['layout'] : ''; + + foreach (['tag', 'tags'] as $view) { + if (isset($this->lookup[$language][$view . $layout][$id])) { + $query['Itemid'] = $this->lookup[$language][$view . $layout][$id]; + + return $query; + } + + if (isset($this->lookup[$language][$view][$id])) { + $query['Itemid'] = $this->lookup[$language][$view][$id]; + + return $query; + } + } + + if (isset($this->lookup[$language][$view][''])) { + $query['Itemid'] = $this->lookup[$language][$view]['']; + + return $query; + } + + // Check if the active menuitem matches the requested language + if ( + $active && $active->component === 'com_tags' + && ($language === '*' || \in_array($active->language, array('*', $language)) || !Multilanguage::isEnabled( + )) + ) { + $query['Itemid'] = $active->id; + + return $query; + } + + // If not found, return language specific home link + $default = $this->menu->getDefault($language); + + if (!empty($default->id)) { + $query['Itemid'] = $default->id; + } + + return $query; } /** @@ -103,11 +229,13 @@ public function build(&$query) } if ($view === 'tag') { - $notActiveTag = is_array($mId) ? (count($mId) > 1 || $mId[0] != (int) $query['id']) : ($mId != (int) $query['id']); + $notActiveTag = is_array($mId) ? (count( + $mId + ) > 1 || $mId[0] != (int)$query['id']) : ($mId != (int)$query['id']); if ($notActiveTag || $mView != $view) { // ID in com_tags can be either an integer, a string or an array of IDs - $id = is_array($query['id']) ? implode(',', $query['id']) : $query['id']; + $id = is_array($query['id']) ? implode(',', $query['id']) : $query['id']; $segments[] = $id; } @@ -117,7 +245,7 @@ public function build(&$query) if (isset($query['layout'])) { if ( (!empty($query['Itemid']) && isset($menuItem->query['layout']) - && $query['layout'] == $menuItem->query['layout']) + && $query['layout'] == $menuItem->query['layout']) || $query['layout'] === 'default' ) { unset($query['layout']); @@ -151,7 +279,7 @@ public function build(&$query) public function parse(&$segments) { $total = count($segments); - $vars = array(); + $vars = array(); for ($i = 0; $i < $total; $i++) { $segments[$i] = preg_replace('/-/', ':', $segments[$i], 1); @@ -173,7 +301,7 @@ public function parse(&$segments) return $vars; } - $vars['id'] = $this->fixSegment($segments[0]); + $vars['id'] = $this->fixSegment($segments[0]); $vars['view'] = 'tag'; unset($segments[0]); @@ -208,4 +336,72 @@ protected function fixSegment($segment) return $segment; } + + /** + * Method to build the lookup array + * + * @param string $language The language that the lookup should be built up for + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + protected function buildLookup($language = '*') + { + // Prepare the reverse lookup array. + if (!isset($this->lookup[$language])) { + $this->lookup[$language] = array(); + + $component = ComponentHelper::getComponent('com_tags'); + $views = ['tags', 'tag']; + + $attributes = array('component_id'); + $values = array((int)$component->id); + + $attributes[] = 'language'; + $values[] = array($language, '*'); + + $items = $this->menu->getItems($attributes, $values); + + foreach ($items as $item) { + if (isset($item->query['view']) && in_array($item->query['view'], $views)) { + $view = $item->query['view']; + + $layout = ''; + + if (isset($item->query['layout'])) { + $layout = ':' . $item->query['layout']; + } + + if (!isset($this->lookup[$language][$view . $layout])) { + $this->lookup[$language][$view . $layout] = array(); + } + + if (!isset($this->lookup[$language][$view])) { + $this->lookup[$language][$view] = array(); + } + + $id = ''; + + if (isset($item->query['id'])) { + if (is_array($item->query['id'])) { + $id = implode(':', $item->query['id']); + } else { + $id = (string)$item->query['id']; + } + } + + /** + * Here it will become a bit tricky + * language != * can override existing entries + * language == * cannot override existing entries + */ + if (!isset($this->lookup[$language][$view . $layout][$id]) || $item->language !== '*') { + $this->lookup[$language][$view . $layout][$id] = $item->id; + $this->lookup[$language][$view][$id] = $item->id; + } + } + } + } + } }