diff --git a/components/com_contact/src/Service/Router.php b/components/com_contact/src/Service/Router.php index c621c97b492ca..29e8d239617d2 100644 --- a/components/com_contact/src/Service/Router.php +++ b/components/com_contact/src/Service/Router.php @@ -18,6 +18,7 @@ use Joomla\CMS\Component\Router\RouterViewConfiguration; use Joomla\CMS\Component\Router\Rules\MenuRules; use Joomla\CMS\Component\Router\Rules\NomenuRules; +use Joomla\CMS\Component\Router\Rules\PreprocessRules; use Joomla\CMS\Component\Router\Rules\StandardRules; use Joomla\CMS\Menu\AbstractMenu; use Joomla\Database\DatabaseInterface; @@ -99,6 +100,9 @@ public function __construct(SiteApplication $app, AbstractMenu $menu, CategoryFa parent::__construct($app, $menu); + $preprocess = new PreprocessRules($contact, '#__contact_details', 'id', 'catid'); + $preprocess->setDatabase($this->db); + $this->attachRule($preprocess); $this->attachRule(new MenuRules($this)); $this->attachRule(new StandardRules($this)); $this->attachRule(new NomenuRules($this)); @@ -155,19 +159,7 @@ public function getCategoriesSegment($id, $query) */ public function getContactSegment($id, $query) { - if (!strpos($id, ':')) { - $id = (int) $id; - $dbquery = $this->db->getQuery(true); - $dbquery->select($this->db->quoteName('alias')) - ->from($this->db->quoteName('#__contact_details')) - ->where($this->db->quoteName('id') . ' = :id') - ->bind(':id', $id, ParameterType::INTEGER); - $this->db->setQuery($dbquery); - - $id .= ':' . $this->db->loadResult(); - } - - if ($this->noIDs) { + if ($this->noIDs && strpos($id, ':')) { list($void, $segment) = explode(':', $id, 2); return [$void => $segment]; diff --git a/components/com_content/src/Service/Router.php b/components/com_content/src/Service/Router.php index 6b334623ad58c..0ca21b7c77d27 100644 --- a/components/com_content/src/Service/Router.php +++ b/components/com_content/src/Service/Router.php @@ -18,6 +18,7 @@ use Joomla\CMS\Component\Router\RouterViewConfiguration; use Joomla\CMS\Component\Router\Rules\MenuRules; use Joomla\CMS\Component\Router\Rules\NomenuRules; +use Joomla\CMS\Component\Router\Rules\PreprocessRules; use Joomla\CMS\Component\Router\Rules\StandardRules; use Joomla\CMS\Menu\AbstractMenu; use Joomla\Database\DatabaseInterface; @@ -100,6 +101,9 @@ public function __construct(SiteApplication $app, AbstractMenu $menu, CategoryFa parent::__construct($app, $menu); + $preprocess = new PreprocessRules($article, '#__content', 'id', 'catid'); + $preprocess->setDatabase($this->db); + $this->attachRule($preprocess); $this->attachRule(new MenuRules($this)); $this->attachRule(new StandardRules($this)); $this->attachRule(new NomenuRules($this)); @@ -156,19 +160,7 @@ public function getCategoriesSegment($id, $query) */ public function getArticleSegment($id, $query) { - if (!strpos($id, ':')) { - $id = (int) $id; - $dbquery = $this->db->getQuery(true); - $dbquery->select($this->db->quoteName('alias')) - ->from($this->db->quoteName('#__content')) - ->where($this->db->quoteName('id') . ' = :id') - ->bind(':id', $id, ParameterType::INTEGER); - $this->db->setQuery($dbquery); - - $id .= ':' . $this->db->loadResult(); - } - - if ($this->noIDs) { + if ($this->noIDs && strpos($id, ':')) { list($void, $segment) = explode(':', $id, 2); return [$void => $segment]; diff --git a/components/com_newsfeeds/src/Service/Router.php b/components/com_newsfeeds/src/Service/Router.php index b9ae5505587fc..64b73557508d7 100644 --- a/components/com_newsfeeds/src/Service/Router.php +++ b/components/com_newsfeeds/src/Service/Router.php @@ -18,6 +18,7 @@ use Joomla\CMS\Component\Router\RouterViewConfiguration; use Joomla\CMS\Component\Router\Rules\MenuRules; use Joomla\CMS\Component\Router\Rules\NomenuRules; +use Joomla\CMS\Component\Router\Rules\PreprocessRules; use Joomla\CMS\Component\Router\Rules\StandardRules; use Joomla\CMS\Menu\AbstractMenu; use Joomla\Database\DatabaseInterface; @@ -95,6 +96,9 @@ public function __construct(SiteApplication $app, AbstractMenu $menu, CategoryFa parent::__construct($app, $menu); + $preprocess = new PreprocessRules($newsfeed, '#__newsfeeds', 'id', 'catid'); + $preprocess->setDatabase($this->db); + $this->attachRule($preprocess); $this->attachRule(new MenuRules($this)); $this->attachRule(new StandardRules($this)); $this->attachRule(new NomenuRules($this)); @@ -151,19 +155,7 @@ public function getCategoriesSegment($id, $query) */ public function getNewsfeedSegment($id, $query) { - if (!strpos($id, ':')) { - $id = (int) $id; - $dbquery = $this->db->getQuery(true); - $dbquery->select($this->db->quoteName('alias')) - ->from($this->db->quoteName('#__newsfeeds')) - ->where($this->db->quoteName('id') . ' = :id') - ->bind(':id', $id, ParameterType::INTEGER); - $this->db->setQuery($dbquery); - - $id .= ':' . $this->db->loadResult(); - } - - if ($this->noIDs) { + if ($this->noIDs && strpos($id, ':')) { list($void, $segment) = explode(':', $id, 2); return [$void => $segment]; diff --git a/libraries/src/Component/Router/Rules/PreprocessRules.php b/libraries/src/Component/Router/Rules/PreprocessRules.php new file mode 100644 index 0000000000000..5fe8720687420 --- /dev/null +++ b/libraries/src/Component/Router/Rules/PreprocessRules.php @@ -0,0 +1,169 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\CMS\Component\Router\Rules; + +use Joomla\CMS\Component\Router\RouterViewConfiguration; +use Joomla\Database\DatabaseAwareTrait; +use Joomla\Database\ParameterType; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * Rule to prepare the query and add missing information + * + * This rule adds the alias to an ID query parameter and the + * category ID if either of them is missing. This requires that + * the db table contains an alias column. + * This fixes sloppy URLs in the code, but doesn't mean you can + * simply drop the alias from the &id= in the future. Cleaning up + * every request with this would mean a significant performance impact + * + * @since __DEPLOY_VERSION__ + */ +class PreprocessRules implements RulesInterface +{ + use DatabaseAwareTrait; + + /** + * View to prepare + * + * @var RouterViewConfiguration + * @since __DEPLOY_VERSION__ + */ + protected $view; + + /** + * DB Table to read the information from + * + * @var string + * @since __DEPLOY_VERSION__ + */ + protected $table; + + /** + * ID column in the table to read the information from + * + * @var string + * @since __DEPLOY_VERSION__ + */ + protected $key; + + /** + * Parent ID column in the table to read the information from + * + * @var string + * @since __DEPLOY_VERSION__ + */ + protected $parent_key; + + /** + * Class constructor. + * + * @param RouterViewConfiguration $view View to act on + * @param string $table Table name for the views information + * @param string $key Key in the table to get the information + * @param string $parent_key Column name of the parent key + * + * @since __DEPLOY_VERSION__ + */ + public function __construct(RouterViewConfiguration $view, $table, $key, $parent_key = null) + { + $this->view = $view; + $this->table = $table; + $this->key = $key; + $this->parent_key = $parent_key; + } + + /** + * Finds the correct Itemid for this query + * + * @param array &$query The query array to process + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function preprocess(&$query) + { + // We only work for URLs with the view we have been setup for + if (!isset($query['view']) || $query['view'] != $this->view->name) { + return; + } + + $key = $this->view->key; + $parent_key = $this->view->parent_key; + + // We have to have at least the ID or something to repair + if (!isset($query[$key]) || (strpos($query[$key], ':') && isset($query[$parent_key]))) { + return; + } + + $dbquery = $this->getDatabase()->getQuery(true); + + $dbquery->select($dbquery->quoteName('alias')) + ->from($this->table) + ->where($dbquery->quoteName($this->key) . ' = :key') + ->bind(':key', $query[$key], ParameterType::INTEGER); + + // Do we have a parent key? + if ($parent_key && $this->parent_key) { + $dbquery->select($dbquery->quoteName($this->parent_key)); + } + + $obj = $this->getDatabase()->setQuery($dbquery)->loadObject(); + + // We haven't found the item in the database. Abort. + if (!$obj) { + return; + } + + // Lets fix the slug (id:alias) + if (!strpos($query[$key], ':')) { + $query[$key] .= ':' . $obj->alias; + } + + // If we have a parent key and it is missing, lets add it + if ($parent_key && $this->parent_key && !isset($query[$parent_key])) { + $query[$parent_key] = $obj->{$this->parent_key}; + } + } + + /** + * Dummy method to fulfil the interface requirements + * + * @param array &$segments The URL segments to parse + * @param array &$vars The vars that result from the segments + * + * @return void + * + * @since __DEPLOY_VERSION__ + * @codeCoverageIgnore + */ + public function parse(&$segments, &$vars) + { + } + + /** + * Dummy method to fulfil the interface requirements + * + * @param array &$query The vars that should be converted + * @param array &$segments The URL segments to create + * + * @return void + * + * @since __DEPLOY_VERSION__ + * @codeCoverageIgnore + */ + public function build(&$query, &$segments) + { + } +}