From d05b83fa61d3f84d457e7c5744dc50f0f5dda050 Mon Sep 17 00:00:00 2001 From: Hannes Papenberg Date: Fri, 24 Mar 2017 01:13:14 +0100 Subject: [PATCH] Initial commit --- README.md | 3 +- classes/contactrouter.php | 237 ++++++ classes/contentrouter.php | 199 +++++ classes/newsfeedrouter.php | 195 +++++ classes/router/rules/interface.php | 60 ++ classes/router/rules/menu.php | 250 ++++++ classes/router/rules/nomenu.php | 132 +++ classes/router/rules/standard.php | 300 +++++++ classes/router/view.php | 292 +++++++ classes/router/viewconfiguration.php | 236 ++++++ classes/site.php | 798 ++++++++++++++++++ jlrouter.php | 89 ++ jlrouter.xml | 100 +++ language/en-GB/en-GB.plg_system_jlrouter.ini | 19 + .../en-GB/en-GB.plg_system_jlrouter.sys.ini | 19 + 15 files changed, 2928 insertions(+), 1 deletion(-) create mode 100644 classes/contactrouter.php create mode 100644 classes/contentrouter.php create mode 100644 classes/newsfeedrouter.php create mode 100644 classes/router/rules/interface.php create mode 100644 classes/router/rules/menu.php create mode 100644 classes/router/rules/nomenu.php create mode 100644 classes/router/rules/standard.php create mode 100644 classes/router/view.php create mode 100644 classes/router/viewconfiguration.php create mode 100644 classes/site.php create mode 100644 jlrouter.php create mode 100644 jlrouter.xml create mode 100644 language/en-GB/en-GB.plg_system_jlrouter.ini create mode 100644 language/en-GB/en-GB.plg_system_jlrouter.sys.ini diff --git a/README.md b/README.md index 3f73f28..ca49891 100644 --- a/README.md +++ b/README.md @@ -1 +1,2 @@ -# jlrouter \ No newline at end of file +# Joomlager Router +This plugin backports the modern router that was supposed to be in 3.7 from Joomla 3.8 to Joomla 3.6.5 and Joomla 3.7. Further documentation and an update server are coming soon. \ No newline at end of file diff --git a/classes/contactrouter.php b/classes/contactrouter.php new file mode 100644 index 0000000..2d8113d --- /dev/null +++ b/classes/contactrouter.php @@ -0,0 +1,237 @@ +setKey('id'); + $this->registerView($categories); + $category = new JComponentRouterViewconfiguration('category'); + $category->setKey('id')->setParent($categories, 'catid')->setNestable(); + $this->registerView($category); + $contact = new JComponentRouterViewconfiguration('contact'); + $contact->setKey('id')->setParent($category, 'catid'); + $this->registerView($contact); + $this->registerView(new JComponentRouterViewconfiguration('featured')); + + parent::__construct($app, $menu); + + $this->attachRule(new JComponentRouterRulesMenu($this)); + $this->attachRule(new JComponentRouterRulesStandard($this)); + $this->attachRule(new JComponentRouterRulesNomenu($this)); + } + + /** + * Method to get the segment(s) for a category + * + * @param string $id ID of the category to retrieve the segments for + * @param array $query The request that is build right now + * + * @return array|string The segments of this item + */ + public function getCategorySegment($id, $query) + { + $category = JCategories::getInstance($this->getName())->get($id); + + if ($category) + { + $path = array_reverse($category->getPath(), true); + $path[0] = '1:root'; + + if ($this->noIDs) + { + foreach ($path as &$segment) + { + list($id, $segment) = explode(':', $segment, 2); + } + } + + return $path; + } + + return array(); + } + + /** + * Method to get the segment(s) for a category + * + * @param string $id ID of the category to retrieve the segments for + * @param array $query The request that is build right now + * + * @return array|string The segments of this item + */ + public function getCategoriesSegment($id, $query) + { + return $this->getCategorySegment($id, $query); + } + + /** + * Method to get the segment(s) for a contact + * + * @param string $id ID of the contact to retrieve the segments for + * @param array $query The request that is build right now + * + * @return array|string The segments of this item + */ + public function getContactSegment($id, $query) + { + if (!strpos($id, ':')) + { + $db = JFactory::getDbo(); + $dbquery = $db->getQuery(true); + $dbquery->select($dbquery->qn('alias')) + ->from($dbquery->qn('#__contact_details')) + ->where('id = ' . $dbquery->q((int) $id)); + $db->setQuery($dbquery); + + $id .= ':' . $db->loadResult(); + } + + if ($this->noIDs) + { + list($void, $segment) = explode(':', $id, 2); + + return array($void => $segment); + } + + return array((int) $id => $id); + } + + /** + * Method to get the id for a category + * + * @param string $segment Segment to retrieve the ID for + * @param array $query The request that is parsed right now + * + * @return mixed The id of this item or false + */ + public function getCategoryId($segment, $query) + { + if (isset($query['id'])) + { + $category = JCategories::getInstance($this->getName())->get($query['id']); + + foreach ($category->getChildren() as $child) + { + if ($this->noIDs) + { + if ($child->alias == $segment) + { + return $child->id; + } + } + else + { + if ($child->id == (int) $segment) + { + return $child->id; + } + } + } + } + + return false; + } + + /** + * Method to get the segment(s) for a category + * + * @param string $segment Segment to retrieve the ID for + * @param array $query The request that is parsed right now + * + * @return mixed The id of this item or false + */ + public function getCategoriesId($segment, $query) + { + return $this->getCategoryId($segment, $query); + } + + /** + * Method to get the segment(s) for a contact + * + * @param string $segment Segment of the contact to retrieve the ID for + * @param array $query The request that is parsed right now + * + * @return mixed The id of this item or false + */ + public function getContactId($segment, $query) + { + if ($this->noIDs) + { + $db = JFactory::getDbo(); + $dbquery = $db->getQuery(true); + $dbquery->select($dbquery->qn('id')) + ->from($dbquery->qn('#__contact_details')) + ->where('alias = ' . $dbquery->q($segment)) + ->where('catid = ' . $dbquery->q($query['id'])); + $db->setQuery($dbquery); + + return (int) $db->loadResult(); + } + + return (int) $segment; + } +} + +/** + * Contact router functions + * + * These functions are proxys for the new router interface + * for old SEF extensions. + * + * @param array &$query An array of URL arguments + * + * @return array The URL arguments to use to assemble the subsequent URL. + * + * @deprecated 4.0 Use Class based routers instead + */ +function ContactBuildRoute(&$query) +{ + $app = JFactory::getApplication(); + $router = new ContactRouter($app, $app->getMenu()); + + return $router->build($query); +} + +/** + * Contact router functions + * + * These functions are proxys for the new router interface + * for old SEF extensions. + * + * @param array $segments The segments of the URL to parse. + * + * @return array The URL attributes to be used by the application. + * + * @deprecated 4.0 Use Class based routers instead + */ +function ContactParseRoute($segments) +{ + $app = JFactory::getApplication(); + $router = new ContactRouter($app, $app->getMenu()); + + return $router->parse($segments); +} diff --git a/classes/contentrouter.php b/classes/contentrouter.php new file mode 100644 index 0000000..707c1b7 --- /dev/null +++ b/classes/contentrouter.php @@ -0,0 +1,199 @@ +setKey('id'); + $this->registerView($categories); + $category = new JComponentRouterViewconfiguration('category'); + $category->setKey('id')->setParent($categories, 'catid')->setNestable()->addLayout('blog'); + $this->registerView($category); + $article = new JComponentRouterViewconfiguration('article'); + $article->setKey('id')->setParent($category, 'catid'); + $this->registerView($article); + $this->registerView(new JComponentRouterViewconfiguration('archive')); + $this->registerView(new JComponentRouterViewconfiguration('featured')); + $this->registerView(new JComponentRouterViewconfiguration('form')); + + parent::__construct($app, $menu); + + $this->attachRule(new JComponentRouterRulesMenu($this)); + $this->attachRule(new JComponentRouterRulesStandard($this)); + $this->attachRule(new JComponentRouterRulesNomenu($this)); + } + + /** + * Method to get the segment(s) for a category + * + * @param string $id ID of the category to retrieve the segments for + * @param array $query The request that is build right now + * + * @return array|string The segments of this item + */ + public function getCategorySegment($id, $query) + { + $category = JCategories::getInstance($this->getName())->get($id); + + if ($category) + { + $path = array_reverse($category->getPath(), true); + $path[0] = '1:root'; + + if ($this->noIDs) + { + foreach ($path as &$segment) + { + list($id, $segment) = explode(':', $segment, 2); + } + } + + return $path; + } + + return array(); + } + + /** + * Method to get the segment(s) for a category + * + * @param string $id ID of the category to retrieve the segments for + * @param array $query The request that is build right now + * + * @return array|string The segments of this item + */ + public function getCategoriesSegment($id, $query) + { + return $this->getCategorySegment($id, $query); + } + + /** + * Method to get the segment(s) for an article + * + * @param string $id ID of the article to retrieve the segments for + * @param array $query The request that is build right now + * + * @return array|string The segments of this item + */ + public function getArticleSegment($id, $query) + { + if (!strpos($id, ':')) + { + $db = JFactory::getDbo(); + $dbquery = $db->getQuery(true); + $dbquery->select($dbquery->qn('alias')) + ->from($dbquery->qn('#__content')) + ->where('id = ' . $dbquery->q($id)); + $db->setQuery($dbquery); + + $id .= ':' . $db->loadResult(); + } + + if ($this->noIDs) + { + list($void, $segment) = explode(':', $id, 2); + + return array($void => $segment); + } + + return array((int) $id => $id); + } + + /** + * Method to get the id for a category + * + * @param string $segment Segment to retrieve the ID for + * @param array $query The request that is parsed right now + * + * @return mixed The id of this item or false + */ + public function getCategoryId($segment, $query) + { + if (isset($query['id'])) + { + $category = JCategories::getInstance($this->getName())->get($query['id']); + + foreach ($category->getChildren() as $child) + { + if ($this->noIDs) + { + if ($child->alias == $segment) + { + return $child->id; + } + } + else + { + if ($child->id == (int) $segment) + { + return $child->id; + } + } + } + } + + return false; + } + + /** + * Method to get the segment(s) for a category + * + * @param string $segment Segment to retrieve the ID for + * @param array $query The request that is parsed right now + * + * @return mixed The id of this item or false + */ + public function getCategoriesId($segment, $query) + { + return $this->getCategoryId($segment, $query); + } + + /** + * Method to get the segment(s) for an article + * + * @param string $segment Segment of the article to retrieve the ID for + * @param array $query The request that is parsed right now + * + * @return mixed The id of this item or false + */ + public function getArticleId($segment, $query) + { + if ($this->noIDs) + { + $db = JFactory::getDbo(); + $dbquery = $db->getQuery(true); + $dbquery->select($dbquery->qn('id')) + ->from($dbquery->qn('#__content')) + ->where('alias = ' . $dbquery->q($segment)) + ->where('catid = ' . $dbquery->q($query['id'])); + $db->setQuery($dbquery); + + return (int) $db->loadResult(); + } + + return (int) $segment; + } +} diff --git a/classes/newsfeedrouter.php b/classes/newsfeedrouter.php new file mode 100644 index 0000000..17c80fb --- /dev/null +++ b/classes/newsfeedrouter.php @@ -0,0 +1,195 @@ +setKey('id'); + $this->registerView($categories); + $category = new JComponentRouterViewconfiguration('category'); + $category->setKey('id')->setParent($categories, 'catid')->setNestable(); + $this->registerView($category); + $newsfeed = new JComponentRouterViewconfiguration('newsfeed'); + $newsfeed->setKey('id')->setParent($category, 'catid'); + $this->registerView($newsfeed); + + parent::__construct($app, $menu); + + $this->attachRule(new JComponentRouterRulesMenu($this)); + $this->attachRule(new JComponentRouterRulesStandard($this)); + $this->attachRule(new JComponentRouterRulesNomenu($this)); + } + + /** + * Method to get the segment(s) for a category + * + * @param string $id ID of the category to retrieve the segments for + * @param array $query The request that is build right now + * + * @return array|string The segments of this item + */ + public function getCategorySegment($id, $query) + { + $category = JCategories::getInstance($this->getName())->get($id); + if ($category) + { + $path = array_reverse($category->getPath(), true); + $path[0] = '1:root'; + + if ($this->noIDs) + { + foreach ($path as &$segment) + { + list($id, $segment) = explode(':', $segment, 2); + } + } + + return $path; + } + + return array(); + } + + /** + * Method to get the segment(s) for a category + * + * @param string $id ID of the category to retrieve the segments for + * @param array $query The request that is build right now + * + * @return array|string The segments of this item + */ + public function getCategoriesSegment($id, $query) + { + return $this->getCategorySegment($id, $query); + } + + /** + * Method to get the segment(s) for a newsfeed + * + * @param string $id ID of the newsfeed to retrieve the segments for + * @param array $query The request that is build right now + * + * @return array|string The segments of this item + */ + public function getNewsfeedSegment($id, $query) + { + if (!strpos($id, ':')) + { + $db = JFactory::getDbo(); + $dbquery = $db->getQuery(true); + $dbquery->select($dbquery->qn('alias')) + ->from($dbquery->qn('#__newsfeeds')) + ->where('id = ' . $dbquery->q((int) $id)); + $db->setQuery($dbquery); + + $id .= ':' . $db->loadResult(); + } + + if ($this->noIDs) + { + list($void, $segment) = explode(':', $id, 2); + + return array($void => $segment); + } + + return array((int) $id => $id); + } + + /** + * Method to get the id for a category + * + * @param string $segment Segment to retrieve the ID for + * @param array $query The request that is parsed right now + * + * @return mixed The id of this item or false + */ + public function getCategoryId($segment, $query) + { + if (isset($query['id'])) + { + $category = JCategories::getInstance($this->getName())->get($query['id']); + + foreach ($category->getChildren() as $child) + { + if ($this->noIDs) + { + if ($child->alias == $segment) + { + return $child->id; + } + } + else + { + if ($child->id == (int) $segment) + { + return $child->id; + } + } + } + } + + return false; + } + + /** + * Method to get the segment(s) for a category + * + * @param string $segment Segment to retrieve the ID for + * @param array $query The request that is parsed right now + * + * @return mixed The id of this item or false + */ + public function getCategoriesId($segment, $query) + { + return $this->getCategoryId($segment, $query); + } + + /** + * Method to get the segment(s) for a newsfeed + * + * @param string $segment Segment of the newsfeed to retrieve the ID for + * @param array $query The request that is parsed right now + * + * @return mixed The id of this item or false + */ + public function getNewsfeedId($segment, $query) + { + if ($this->noIDs) + { + $db = JFactory::getDbo(); + $dbquery = $db->getQuery(true); + $dbquery->select($dbquery->qn('id')) + ->from($dbquery->qn('#__newsfeeds')) + ->where('alias = ' . $dbquery->q($segment)) + ->where('catid = ' . $dbquery->q($query['id'])); + $db->setQuery($dbquery); + + return (int) $db->loadResult(); + } + + return (int) $segment; + } +} diff --git a/classes/router/rules/interface.php b/classes/router/rules/interface.php new file mode 100644 index 0000000..d612c85 --- /dev/null +++ b/classes/router/rules/interface.php @@ -0,0 +1,60 @@ +router = $router; + + $this->buildLookup(); + } + + /** + * Finds the right Itemid for this query + * + * @param array &$query The query array to process + * + * @return void + * + * @since 3.4 + */ + public function preprocess(&$query) + { + /** + * 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']) + && (($this->router->menu->getActive() && $query['Itemid'] != $this->router->menu->getActive()->id) + || ($this->router->menu->getActive() === null))) + { + return; + } + + $language = '*'; + if (isset($query['lang'])) + { + $language = $query['lang']; + + if (!isset($this->lookup[$query['lang']])) + { + $this->buildLookup($query['lang']); + } + } + + $needles = $this->router->getPath($query); + + $layout = ''; + + if (isset($query['layout'])) + { + $layout = ':' . $query['layout']; + } + + if ($needles) + { + foreach ($needles as $view => $ids) + { + if (isset($this->lookup[$language][$view . $layout])) + { + if (is_bool($ids)) + { + $query['Itemid'] = $this->lookup[$language][$view . $layout]; + return; + } + foreach ($ids as $id => $segment) + { + if (isset($this->lookup[$language][$view . $layout][(int) $id])) + { + $query['Itemid'] = $this->lookup[$language][$view . $layout][(int) $id]; + return; + } + + if (isset($this->lookup[$language][$view][(int) $id])) + { + $query['Itemid'] = $this->lookup[$language][$view][(int) $id]; + return; + } + } + } + } + } + + // Check if the active menuitem matches the requested language + $active = $this->router->menu->getActive(); + + if ($active && $active->component == 'com_' . $this->router->getName() + && ($language == '*' || in_array($active->language, array('*', $language)) || !JLanguageMultilang::isEnabled())) + { + $query['Itemid'] = $active->id; + return; + } + + // If not found, return language specific home link + $default = $this->router->menu->getDefault($language); + + if (!empty($default->id)) + { + $query['Itemid'] = $default->id; + } + } + + /** + * Method to build the lookup array + * + * @param string $language The language that the lookup should be built up for + * + * @return void + * + * @since 3.4 + */ + protected function buildLookup($language = '*') + { + // Prepare the reverse lookup array. + if (!isset($this->lookup[$language])) + { + $this->lookup[$language] = array(); + + $component = JComponentHelper::getComponent('com_' . $this->router->getName()); + $views = $this->router->getViews(); + + $attributes = array('component_id'); + $values = array((int) $component->id); + + $attributes[] = 'language'; + $values[] = array($language, '*'); + + $items = $this->router->menu->getItems($attributes, $values); + + foreach ($items as $item) + { + if (isset($item->query) && isset($item->query['view'])) + { + $view = $item->query['view']; + + $layout = ''; + + if (isset($item->query['layout'])) + { + $layout = ':' . $item->query['layout']; + } + + if ($views[$view]->key) + { + if (!isset($this->lookup[$language][$view . $layout])) + { + $this->lookup[$language][$view . $layout] = array(); + } + + if (!isset($this->lookup[$language][$view])) + { + $this->lookup[$language][$view] = array(); + } + + /** + * Here it will become a bit tricky + * language != * can override existing entries + * language == * cannot override existing entries + */ + if (isset($item->query[$views[$view]->key]) + && (!isset($this->lookup[$language][$view . $layout][$item->query[$views[$view]->key]]) || $item->language != '*')) + { + $this->lookup[$language][$view . $layout][$item->query[$views[$view]->key]] = $item->id; + $this->lookup[$language][$view][$item->query[$views[$view]->key]] = $item->id; + } + } + else + { + /** + * Here it will become a bit tricky + * language != * can override existing entries + * language == * cannot override existing entries + */ + if (!isset($this->lookup[$language][$view . $layout]) || $item->language != '*') + { + $this->lookup[$language][$view . $layout] = $item->id; + $this->lookup[$language][$view] = $item->id; + } + } + } + } + } + } + + /** + * Dummymethod to fullfill the interface requirements + * + * @param array &$segments The URL segments to parse + * @param array &$vars The vars that result from the segments + * + * @return void + * + * @since 3.4 + * @codeCoverageIgnore + */ + public function parse(&$segments, &$vars) + { + } + + /** + * Dummymethod to fullfill the interface requirements + * + * @param array &$query The vars that should be converted + * @param array &$segments The URL segments to create + * + * @return void + * + * @since 3.4 + * @codeCoverageIgnore + */ + public function build(&$query, &$segments) + { + } +} diff --git a/classes/router/rules/nomenu.php b/classes/router/rules/nomenu.php new file mode 100644 index 0000000..837ff85 --- /dev/null +++ b/classes/router/rules/nomenu.php @@ -0,0 +1,132 @@ +router = $router; + } + + /** + * Dummymethod to fullfill the interface requirements + * + * @param array &$query The query array to process + * + * @return void + * + * @since 3.4 + * @codeCoverageIgnore + */ + public function preprocess(&$query) + { + } + + /** + * Parse a menu-less URL + * + * @param array &$segments The URL segments to parse + * @param array &$vars The vars that result from the segments + * + * @return void + * + * @since 3.4 + */ + public function parse(&$segments, &$vars) + { + $active = $this->router->menu->getActive(); + + if (!is_object($active)) + { + $views = $this->router->getViews(); + + if (isset($views[$segments[0]])) + { + $vars['view'] = array_shift($segments); + + if (isset($views[$vars['view']]->key) && isset($segments[0])) + { + $vars[$views[$vars['view']]->key] = preg_replace('/-/', ':', array_shift($segments), 1); + } + } + } + } + + /** + * Build a menu-less URL + * + * @param array &$query The vars that should be converted + * @param array &$segments The URL segments to create + * + * @return void + * + * @since 3.4 + */ + public function build(&$query, &$segments) + { + $menu_found = false; + + if (isset($query['Itemid'])) + { + $item = $this->router->menu->getItem($query['Itemid']); + + if (!isset($query['option']) || ($item && $item->query['option'] == $query['option'])) + { + $menu_found = true; + } + } + + if (!$menu_found && isset($query['view'])) + { + $views = $this->router->getViews(); + if (isset($views[$query['view']])) + { + $view = $views[$query['view']]; + $segments[] = $query['view']; + + if ($view->key && isset($query[$view->key])) + { + if (is_callable(array($this->router, 'get' . ucfirst($view->name) . 'Segment'))) + { + $result = call_user_func_array(array($this->router, 'get' . ucfirst($view->name) . 'Segment'), array($query[$view->key], $query)); + $segments[] = str_replace(':', '-', array_shift($result)); + } + else + { + $segments[] = str_replace(':', '-', $query[$view->key]); + } + unset($query[$views[$query['view']]->key]); + } + unset($query['view']); + } + } + } +} diff --git a/classes/router/rules/standard.php b/classes/router/rules/standard.php new file mode 100644 index 0000000..2542a0c --- /dev/null +++ b/classes/router/rules/standard.php @@ -0,0 +1,300 @@ +router = $router; + } + + /** + * Dummymethod to fullfill the interface requirements + * + * @param array &$query The query array to process + * + * @return void + * + * @since 3.4 + */ + public function preprocess(&$query) + { + } + + /** + * Parse the URL + * + * @param array &$segments The URL segments to parse + * @param array &$vars The vars that result from the segments + * + * @return void + * + * @since 3.4 + */ + public function parse(&$segments, &$vars) + { + // Get the views and the currently active query vars + $views = $this->router->getViews(); + $active = $this->router->menu->getActive(); + + if ($active) + { + $vars = array_merge($active->query, $vars); + } + + // We don't have a view or its not a view of this component! We stop here + if (!isset($vars['view']) || !isset($views[$vars['view']])) + { + return; + } + + // Copy the segments, so that we can iterate over all of them and at the same time modify the original segments + $tempSegments = $segments; + + // Iterate over the segments as long as a segment fits + foreach ($tempSegments as $segment) + { + // Our current view is nestable. We need to check first if the segment fits to that + if ($views[$vars['view']]->nestable) + { + if (is_callable(array($this->router, 'get' . ucfirst($views[$vars['view']]->name) . 'Id'))) + { + $key = call_user_func_array(array($this->router, 'get' . ucfirst($views[$vars['view']]->name) . 'Id'), array($segment, $vars)); + + // Did we get a proper key? If not, we need to look in the child-views + if ($key) + { + $vars[$views[$vars['view']]->key] = $key; + + array_shift($segments); + + continue; + } + } + else + { + // The router is not complete. The getKey() method is missing. + return; + } + } + + // Lets find the right view that belongs to this segment + $found = false; + + foreach ($views[$vars['view']]->children as $view) + { + if (!$view->key) + { + if ($view->name == $segment) + { + // The segment is a view name + $parent = $views[$vars['view']]; + $vars['view'] = $view->name; + $found = true; + + if ($view->parent_key && isset($vars[$parent->key])) + { + $parent_key = $vars[$parent->key]; + $vars[$view->parent_key] = $parent_key; + + unset($vars[$parent->key]); + } + + break; + } + } + elseif (is_callable(array($this->router, 'get' . ucfirst($view->name) . 'Id'))) + { + // Hand the data over to the router specific method and see if there is a content item that fits + $key = call_user_func_array(array($this->router, 'get' . ucfirst($view->name) . 'Id'), array($segment, $vars)); + + if ($key) + { + // Found the right view and the right item + $parent = $views[$vars['view']]; + $vars['view'] = $view->name; + $found = true; + + if ($view->parent_key && isset($vars[$parent->key])) + { + $parent_key = $vars[$parent->key]; + $vars[$view->parent_key] = $parent_key; + + unset($vars[$parent->key]); + } + + $vars[$view->key] = $key; + + break; + } + } + } + + if (!$found) + { + return; + } + + array_shift($segments); + } + } + + /** + * Build a standard URL + * + * @param array &$query The vars that should be converted + * @param array &$segments The URL segments to create + * + * @return void + * + * @since 3.4 + */ + public function build(&$query, &$segments) + { + // Get the menu item belonging to the Itemid that has been found + $item = $this->router->menu->getItem($query['Itemid']); + + if (!isset($query['view'])) + { + return; + } + + // Get all views for this component + $views = $this->router->getViews(); + + // Return directly when the URL of the Itemid is identical with the URL to build + if (isset($item->query['view']) && $item->query['view'] == $query['view']) + { + $view = $views[$query['view']]; + + if (isset($item->query[$view->key]) && $item->query[$view->key] == (int) $query[$view->key]) + { + unset($query[$view->key]); + + while ($view) + { + unset($query[$view->parent_key]); + + $view = $view->parent; + } + + unset($query['view']); + + if (isset($item->query['layout']) && isset($query['layout']) && $item->query['layout'] == $query['layout']) + { + unset($query['layout']); + } + + return; + } + + if (!$view->key) + { + if (isset($item->query['layout']) && isset($query['layout']) && $item->query['layout'] == $query['layout']) + { + unset($query['view']); + unset($query['layout']); + + return; + } + } + } + + // Get the path from the view of the current URL and parse it to the menu item + $path = array_reverse($this->router->getPath($query), true); + $found = false; + $found2 = false; + + for ($i = 0, $j = count($path); $i < $j; $i++) + { + reset($path); + $view = key($path); + + if ($found) + { + $ids = array_shift($path); + + if ($views[$view]->nestable) + { + foreach (array_reverse($ids, true) as $id => $segment) + { + if ($found2) + { + $segments[] = str_replace(':', '-', $segment); + } + elseif ((int) $item->query[$views[$view]->key] == (int) $id) + { + $found2 = true; + } + } + } + elseif (is_bool($ids)) + { + $segments[] = $views[$view]->name; + } + else + { + $segments[] = str_replace(':', '-', array_shift($ids)); + } + } + elseif ($item->query['view'] != $view) + { + array_shift($path); + } + else + { + if (!$views[$view]->nestable) + { + array_shift($path); + } + else + { + $i--; + $found2 = false; + } + + if (count($views[$view]->children)) + { + $found = true; + } + } + + unset($query[$views[$view]->parent_key]); + } + + if ($found) + { + unset($query['layout']); + unset($query[$views[$query['view']]->key]); + unset($query['view']); + } + } +} diff --git a/classes/router/view.php b/classes/router/view.php new file mode 100644 index 0000000..1a786db --- /dev/null +++ b/classes/router/view.php @@ -0,0 +1,292 @@ +views[$view->name] = $view; + } + + /** + * Return an array of registered view objects + * + * @return JComponentRouterViewconfiguration[] Array of registered view objects + * + * @since 3.5 + */ + public function getViews() + { + return $this->views; + } + + /** + * Get the path of views from target view to root view + * including content items of a nestable view + * + * @param array $query Array of query elements + * + * @return array List of views including IDs of content items + * + * @since 3.5 + */ + public function getPath($query) + { + $views = $this->getViews(); + $result = array(); + + // Get the right view object + if (isset($query['view']) && isset($views[$query['view']])) + { + $viewobj = $views[$query['view']]; + } + + // Get the path from the current item to the root view with all IDs + if (isset($viewobj)) + { + $path = array_reverse($viewobj->path); + $start = true; + $childkey = false; + + foreach ($path as $element) + { + $view = $views[$element]; + + if ($start) + { + $key = $view->key; + $start = false; + } + else + { + $key = $childkey; + } + + $childkey = $view->parent_key; + + if (($key || $view->key) && is_callable(array($this, 'get' . ucfirst($view->name) . 'Segment'))) + { + if (isset($query[$key])) + { + $result[$view->name] = call_user_func_array(array($this, 'get' . ucfirst($view->name) . 'Segment'), array($query[$key], $query)); + } + elseif (isset($query[$view->key])) + { + $result[$view->name] = call_user_func_array(array($this, 'get' . ucfirst($view->name) . 'Segment'), array($query[$view->key], $query)); + } + else + { + $result[$view->name] = array(); + } + } + else + { + $result[$view->name] = true; + } + } + } + + return $result; + } + + /** + * Get all currently attached rules + * + * @return JComponentRouterRulesInterface[] All currently attached rules in an array + * + * @since 3.5 + */ + public function getRules() + { + return $this->rules; + } + + /** + * Add a number of router rules to the object + * + * @param JComponentRouterRulesInterface[] $rules Array of JComponentRouterRulesInterface objects + * + * @return void + * + * @since 3.5 + */ + public function attachRules($rules) + { + foreach ($rules as $rule) + { + $this->attachRule($rule); + } + } + + /** + * Attach a build rule + * + * @param JComponentRouterRulesInterface $rule The function to be called. + * + * @return void + * + * @since 3.5 + */ + public function attachRule(JComponentRouterRulesInterface $rule) + { + $this->rules[] = $rule; + } + + /** + * Remove a build rule + * + * @param JComponentRouterRulesInterface $rule The rule to be removed. + * + * @return boolean Was a rule removed? + * + * @since 3.5 + */ + public function detachRule(JComponentRouterRulesInterface $rule) + { + foreach ($this->rules as $id => $r) + { + if ($r == $rule) + { + unset($this->rules[$id]); + + return true; + } + } + + return false; + } + + /** + * Generic 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 3.5 + */ + public function preprocess($query) + { + // Process the parsed variables based on custom defined rules + foreach ($this->rules as $rule) + { + $rule->preprocess($query); + } + + return $query; + } + + /** + * Build method for URLs + * + * @param array &$query Array of query elements + * + * @return array Array of URL segments + * + * @since 3.5 + */ + public function build(&$query) + { + $segments = array(); + + // Process the parsed variables based on custom defined rules + foreach ($this->rules as $rule) + { + $rule->build($query, $segments); + } + + return $segments; + } + + /** + * Parse method for URLs + * + * @param array &$segments Array of URL string-segments + * + * @return array Associative array of query values + * + * @since 3.5 + */ + public function parse(&$segments) + { + $vars = array(); + + // Process the parsed variables based on custom defined rules + foreach ($this->rules as $rule) + { + $rule->parse($segments, $vars); + } + + return $vars; + } + + /** + * Method to return the name of the router + * + * @return string Name of the router + * + * @since 3.5 + */ + public function getName() + { + if (empty($this->name)) + { + $r = null; + + if (!preg_match('/(.*)Router/i', get_class($this), $r)) + { + throw new Exception('JLIB_APPLICATION_ERROR_ROUTER_GET_NAME', 500); + } + + $this->name = strtolower($r[1]); + } + + return $this->name; + } +} diff --git a/classes/router/viewconfiguration.php b/classes/router/viewconfiguration.php new file mode 100644 index 0000000..da4fb06 --- /dev/null +++ b/classes/router/viewconfiguration.php @@ -0,0 +1,236 @@ +name = $name; + $this->path[] = $name; + } + + /** + * Set the name of the view + * + * @param string $name Name of the view + * + * @return JComponentRouterViewconfiguration This object for chaining + * + * @since 3.5 + */ + public function setName($name) + { + $this->name = $name; + + array_pop($this->path); + $this->path[] = $name; + + return $this; + } + + /** + * Set the key-identifier for the view + * + * @param string $key Key of the view + * + * @return JComponentRouterViewconfiguration This object for chaining + * + * @since 3.5 + */ + public function setKey($key) + { + $this->key = $key; + + return $this; + } + + /** + * Set the parent view of this view + * + * @param JComponentRouterViewconfiguration $parent Parent view object + * @param string $parent_key Key of the parent view in this context + * + * @return JComponentRouterViewconfiguration This object for chaining + * + * @since 3.5 + */ + public function setParent(JComponentRouterViewconfiguration $parent, $parent_key = false) + { + if ($this->parent) + { + $key = array_search($this, $this->parent->children); + + if ($key !== false) + { + unset($this->parent->children[$key]); + } + + if ($this->parent_key) + { + $child_key = array_search($this->parent_key, $this->parent->child_keys); + unset($this->parent->child_keys[$child_key]); + } + } + + $this->parent = $parent; + $parent->children[] = $this; + + $this->path = $parent->path; + $this->path[] = $this->name; + + $this->parent_key = $parent_key; + + if ($parent_key) + { + $parent->child_keys[] = $parent_key; + } + + return $this; + } + + /** + * Set if this view is nestable or not + * + * @param bool $isNestable If set to true, the view is nestable + * + * @return JComponentRouterViewconfiguration This object for chaining + * + * @since 3.5 + */ + public function setNestable($isNestable = true) + { + $this->nestable = (bool) $isNestable; + + return $this; + } + + /** + * Add a layout to this view + * + * @param string $layout Layouts that this view supports + * + * @return JComponentRouterViewconfiguration This object for chaining + * + * @since 3.5 + */ + public function addLayout($layout) + { + $this->layouts[] = $layout; + $this->layouts = array_unique($this->layouts); + + return $this; + } + + /** + * Remove a layout from this view + * + * @param string $layout Layouts that this view supports + * + * @return JComponentRouterViewconfiguration This object for chaining + * + * @since 3.5 + */ + public function removeLayout($layout) + { + $key = array_search($layout, $this->layouts); + + if ($key !== false) + { + unset($this->layouts[$key]); + } + + return $this; + } +} diff --git a/classes/site.php b/classes/site.php new file mode 100644 index 0000000..57fb099 --- /dev/null +++ b/classes/site.php @@ -0,0 +1,798 @@ +app = $app ? $app : JApplicationCms::getInstance('site'); + $this->menu = $menu ? $menu : $this->app->getMenu(); + } + + /** + * Function to convert a route to an internal URI + * + * @param JUri &$uri The uri. + * + * @return array + * + * @since 1.5 + */ + public function parse(&$uri) + { + $vars = array(); + + if ($this->app->get('force_ssl') == 2 && strtolower($uri->getScheme()) != 'https') + { + // Forward to https + $uri->setScheme('https'); + $this->app->redirect((string) $uri, 301); + } + + // Get the path + // Decode URL to convert percent-encoding to unicode so that strings match when routing. + $path = urldecode($uri->getPath()); + + // Remove the base URI path. + $path = substr_replace($path, '', 0, strlen(JUri::base(true))); + + // Check to see if a request to a specific entry point has been made. + if (preg_match("#.*?\.php#u", $path, $matches)) + { + // Get the current entry point path relative to the site path. + $scriptPath = realpath($_SERVER['SCRIPT_FILENAME'] ? $_SERVER['SCRIPT_FILENAME'] : str_replace('\\\\', '\\', $_SERVER['PATH_TRANSLATED'])); + $relativeScriptPath = str_replace('\\', '/', str_replace(JPATH_SITE, '', $scriptPath)); + + // If a php file has been found in the request path, check to see if it is a valid file. + // Also verify that it represents the same file from the server variable for entry script. + if (file_exists(JPATH_SITE . $matches[0]) && ($matches[0] == $relativeScriptPath)) + { + // Remove the entry point segments from the request path for proper routing. + $path = str_replace($matches[0], '', $path); + } + } + + // Identify format + if ($this->_mode == JROUTER_MODE_SEF) + { + if ($this->app->get('sef_suffix') && !(substr($path, -9) == 'index.php' || substr($path, -1) == '/')) + { + if ($suffix = pathinfo($path, PATHINFO_EXTENSION)) + { + $vars['format'] = $suffix; + } + } + } + + // Set the route + $uri->setPath(trim($path, '/')); + + // Set the parsepreprocess components methods + $components = JComponentHelper::getComponents(); + + foreach ($components as $component) + { + $componentRouter = $this->getComponentRouter($component->option); + + if (method_exists($componentRouter, 'parsepreprocess')) + { + $this->attachParseRule(array($componentRouter, 'parsepreprocess'), static::PROCESS_BEFORE); + } + } + + $vars += parent::parse($uri); + + if (is_callable($this->handling404)) + { + call_user_func_array($this->handling404, array($this, $vars, $uri)); + } + + return $vars; + } + + /** + * Function to convert an internal URI to a route + * + * @param string $url The internal URL + * + * @return string The absolute search engine friendly URL + * + * @since 1.5 + */ + public function build($url) + { + $uri = parent::build($url); + + // Get the path data + $route = $uri->getPath(); + + // Add the suffix to the uri + if ($this->_mode == JROUTER_MODE_SEF && $route) + { + if ($this->app->get('sef_suffix') && !(substr($route, -9) == 'index.php' || substr($route, -1) == '/')) + { + if ($format = $uri->getVar('format', 'html')) + { + $route .= '.' . $format; + $uri->delVar('format'); + } + } + + if ($this->app->get('sef_rewrite')) + { + // Transform the route + if ($route == 'index.php') + { + $route = ''; + } + else + { + $route = str_replace('index.php/', '', $route); + } + } + } + + // Add basepath to the uri + $uri->setPath(JUri::base(true) . '/' . $route); + + return $uri; + } + + /** + * Function to convert a raw route to an internal URI + * + * @param JUri &$uri The raw route + * + * @return array + * + * @since 3.2 + * @deprecated 4.0 Attach your logic as rule to the main parse stage + */ + protected function parseRawRoute(&$uri) + { + $vars = array(); + + // Handle an empty URL (special case) + if (!$uri->getVar('Itemid') && !$uri->getVar('option')) + { + $item = $this->menu->getDefault($this->app->getLanguage()->getTag()); + + if (!is_object($item)) + { + // No default item set + return $vars; + } + + // Set the information in the request + $vars = $item->query; + + // Get the itemid + $vars['Itemid'] = $item->id; + + // Set the active menu item + $this->menu->setActive($vars['Itemid']); + + return $vars; + } + + // Get the variables from the uri + $this->setVars($uri->getQuery(true)); + + // Get the itemid, if it hasn't been set force it to null + $this->setVar('Itemid', $this->app->input->getInt('Itemid', null)); + + // Only an Itemid OR if filter language plugin set? Get the full information from the itemid + if (count($this->getVars()) == 1 || ($this->app->getLanguageFilter() && count($this->getVars()) == 2)) + { + $item = $this->menu->getItem($this->getVar('Itemid')); + + if ($item !== null && is_array($item->query)) + { + $vars = $vars + $item->query; + } + } + + // Set the active menu item + $this->menu->setActive($this->getVar('Itemid')); + + return $vars; + } + + /** + * Function to convert a sef route to an internal URI + * + * @param JUri &$uri The sef URI + * + * @return string Internal URI + * + * @since 3.2 + * @deprecated 4.0 Attach your logic as rule to the main parse stage + */ + protected function parseSefRoute(&$uri) + { + $route = $uri->getPath(); + + // Remove the suffix + if ($this->app->get('sef_suffix')) + { + if ($suffix = pathinfo($route, PATHINFO_EXTENSION)) + { + $route = str_replace('.' . $suffix, '', $route); + } + } + + // Get the variables from the uri + $vars = $uri->getQuery(true); + + // Handle an empty URL (special case) + if (empty($route)) + { + // If route is empty AND option is set in the query, assume it's non-sef url, and parse apropriately + if (isset($vars['option']) || isset($vars['Itemid'])) + { + return $this->parseRawRoute($uri); + } + + $item = $this->menu->getDefault($this->app->getLanguage()->getTag()); + + // If user not allowed to see default menu item then avoid notices + if (is_object($item)) + { + // Set the information in the request + $vars = $item->query; + + // Get the itemid + $vars['Itemid'] = $item->id; + + // Set the active menu item + $this->menu->setActive($vars['Itemid']); + + $this->setVars($vars); + } + + return $vars; + } + + // Parse the application route + $segments = explode('/', $route); + + if (count($segments) > 1 && $segments[0] == 'component') + { + $vars['option'] = 'com_' . $segments[1]; + $vars['Itemid'] = null; + $route = implode('/', array_slice($segments, 2)); + } + else + { + // Get menu items. + $items = $this->menu->getMenu(); + + $found = false; + $route_lowercase = JString::strtolower($route); + $lang_tag = $this->app->getLanguage()->getTag(); + + // Iterate through all items and check route matches. + foreach ($items as $item) + { + if ($item->route && JString::strpos($route_lowercase . '/', $item->route . '/') === 0 && $item->type != 'menulink') + { + // Usual method for non-multilingual site. + if (!$this->app->getLanguageFilter()) + { + // Exact route match. We can break iteration because exact item was found. + if ($item->route == $route_lowercase) + { + $found = $item; + break; + } + + // Partial route match. Item with highest level takes priority. + if (!$found || $found->level < $item->level) + { + $found = $item; + } + } + // Multilingual site. + elseif ($item->language == '*' || $item->language == $lang_tag) + { + // Exact route match. + if ($item->route == $route_lowercase) + { + $found = $item; + + // Break iteration only if language is matched. + if ($item->language == $lang_tag) + { + break; + } + } + + // Partial route match. Item with highest level or same language takes priority. + if (!$found || $found->level < $item->level || $item->language == $lang_tag) + { + $found = $item; + } + } + } + } + + if (!$found) + { + $found = $this->menu->getDefault($lang_tag); + } + else + { + $route = substr($route, strlen($found->route)); + + if ($route) + { + $route = substr($route, 1); + } + } + + if ($found) + { + $vars['Itemid'] = $found->id; + $vars['option'] = $found->component; + } + } + + // Set the active menu item + if (isset($vars['Itemid'])) + { + $this->menu->setActive($vars['Itemid']); + } + + // Set the variables + $this->setVars($vars); + + // Parse the component route + if (!empty($route) && isset($this->_vars['option'])) + { + $segments = explode('/', $route); + + if (empty($segments[0])) + { + array_shift($segments); + } + + // Handle component route + $component = preg_replace('/[^A-Z0-9_\.-]/i', '', $this->_vars['option']); + + if (count($segments)) + { + $crouter = $this->getComponentRouter($component); + $vars = $crouter->parse($segments); + + $this->setVars($vars); + } + + $route = implode('/', $segments); + } + else + { + // Set active menu item + if ($item = $this->menu->getActive()) + { + $vars = $item->query; + } + } + + $uri->setPath($route); + + return $vars; + + } + + /** + * Function to build a raw route + * + * @param JUri &$uri The internal URL + * + * @return string Raw Route + * + * @since 3.2 + * @deprecated 4.0 Attach your logic as rule to the main build stage + */ + protected function buildRawRoute(&$uri) + { + // Get the query data + $query = $uri->getQuery(true); + + if (!isset($query['option'])) + { + return; + } + + $component = preg_replace('/[^A-Z0-9_\.-]/i', '', $query['option']); + $crouter = $this->getComponentRouter($component); + $query = $crouter->preprocess($query); + + $uri->setQuery($query); + } + + /** + * Function to build a sef route + * + * @param JUri &$uri The internal URL + * + * @return void + * + * @since 1.5 + * @deprecated 4.0 Attach your logic as rule to the main build stage + * @codeCoverageIgnore + */ + protected function _buildSefRoute(&$uri) + { + $this->buildSefRoute($uri); + } + + /** + * Function to build a sef route + * + * @param JUri &$uri The uri + * + * @return void + * + * @since 3.2 + * @deprecated 4.0 Attach your logic as rule to the main build stage + */ + protected function buildSefRoute(&$uri) + { + // Get the route + $route = $uri->getPath(); + + // Get the query data + $query = $uri->getQuery(true); + + if (!isset($query['option'])) + { + return; + } + + // Build the component route + $component = preg_replace('/[^A-Z0-9_\.-]/i', '', $query['option']); + $tmp = ''; + $itemID = !empty($query['Itemid']) ? $query['Itemid'] : null; + $crouter = $this->getComponentRouter($component); + $parts = $crouter->build($query); + $result = implode('/', $parts); + $tmp = ($result != "") ? $result : ''; + + // Build the application route + $built = false; + + if (!empty($query['Itemid'])) + { + $item = $this->menu->getItem($query['Itemid']); + + if (is_object($item) && $query['option'] == $item->component) + { + if (!$item->home) + { + $tmp = !empty($tmp) ? $item->route . '/' . $tmp : $item->route; + } + + $built = true; + } + } + + if (empty($query['Itemid']) && !empty($itemID)) + { + $query['Itemid'] = $itemID; + } + + if (!$built) + { + $tmp = 'component/' . substr($query['option'], 4) . '/' . $tmp; + } + + if ($tmp) + { + $route .= '/' . $tmp; + } + + // Unset unneeded query information + if (isset($item) && $query['option'] == $item->component) + { + unset($query['Itemid']); + } + + unset($query['option']); + + // Set query again in the URI + $uri->setQuery($query); + $uri->setPath($route); + } + + /** + * Process the parsed router variables based on custom defined rules + * + * @param JUri &$uri The URI to parse + * @param string $stage The stage that should be processed. + * Possible values: 'preprocess', 'postprocess' + * and '' for the main parse stage + * + * @return array The array of processed URI variables + * + * @since 3.2 + */ + protected function processParseRules(&$uri, $stage = self::PROCESS_DURING) + { + // Process the attached parse rules + $vars = parent::processParseRules($uri, $stage); + + if ($stage == self::PROCESS_DURING) + { + // Process the pagination support + if ($this->_mode == JROUTER_MODE_SEF) + { + if ($start = $uri->getVar('start')) + { + $uri->delVar('start'); + $vars['limitstart'] = $start; + } + } + } + + return $vars; + } + + /** + * Process the build uri query data based on custom defined rules + * + * @param JUri &$uri The URI + * @param string $stage The stage that should be processed. + * Possible values: 'preprocess', 'postprocess' + * and '' for the main build stage + * + * @return void + * + * @since 3.2 + * @deprecated 4.0 The special logic should be implemented as rule + */ + protected function processBuildRules(&$uri, $stage = self::PROCESS_DURING) + { + if ($stage == self::PROCESS_DURING) + { + // Make sure any menu vars are used if no others are specified + $query = $uri->getQuery(true); + if ($this->_mode != 1 + && isset($query['Itemid']) + && (count($query) == 2 || (count($query) == 3 && isset($query['lang'])))) + { + // Get the active menu item + $itemid = $uri->getVar('Itemid'); + $lang = $uri->getVar('lang'); + $item = $this->menu->getItem($itemid); + + if ($item) + { + $uri->setQuery($item->query); + } + + $uri->setVar('Itemid', $itemid); + + if ($lang) + { + $uri->setVar('lang', $lang); + } + } + } + + // Process the attached build rules + parent::processBuildRules($uri, $stage); + + if ($stage == self::PROCESS_BEFORE) + { + // Get the query data + $query = $uri->getQuery(true); + + if (!isset($query['option'])) + { + return; + } + + // Build the component route + $component = preg_replace('/[^A-Z0-9_\.-]/i', '', $query['option']); + $router = $this->getComponentRouter($component); + $query = $router->preprocess($query); + $uri->setQuery($query); + } + + if ($stage == self::PROCESS_DURING) + { + // Get the path data + $route = $uri->getPath(); + + if ($this->_mode == JROUTER_MODE_SEF && $route) + { + if ($limitstart = $uri->getVar('limitstart')) + { + $uri->setVar('start', (int) $limitstart); + $uri->delVar('limitstart'); + } + } + + $uri->setPath($route); + } + } + + /** + * Create a uri based on a full or partial url string + * + * @param string $url The URI + * + * @return JUri + * + * @since 3.2 + */ + protected function createUri($url) + { + // Create the URI + $uri = parent::createUri($url); + + // Get the itemid form the URI + $itemid = $uri->getVar('Itemid'); + + if (is_null($itemid)) + { + if ($option = $uri->getVar('option')) + { + $item = $this->menu->getItem($this->getVar('Itemid')); + + if (isset($item) && $item->component == $option) + { + $uri->setVar('Itemid', $item->id); + } + } + else + { + if ($option = $this->getVar('option')) + { + $uri->setVar('option', $option); + } + + if ($itemid = $this->getVar('Itemid')) + { + $uri->setVar('Itemid', $itemid); + } + } + } + else + { + if (!$uri->getVar('option')) + { + if ($item = $this->menu->getItem($itemid)) + { + $uri->setVar('option', $item->component); + } + } + } + + return $uri; + } + + /** + * Get component router + * + * @param string $component Name of the component including com_ prefix + * + * @return JComponentRouterInterface Component router + * + * @since 3.3 + */ + public function getComponentRouter($component) + { + if (!isset($this->componentRouters[$component])) + { + $compname = ucfirst(substr($component, 4)); + $class = $compname . 'Router'; + + if (!class_exists($class)) + { + // Use the component routing handler if it exists + $path = JPATH_SITE . '/components/' . $component . '/router.php'; + + // Use the custom routing handler if it exists + if (file_exists($path)) + { + require_once $path; + } + } + + if (class_exists($class)) + { + $reflection = new ReflectionClass($class); + + if (in_array('JComponentRouterInterface', $reflection->getInterfaceNames())) + { + $this->componentRouters[$component] = new $class($this->app, $this->menu); + } + } + + if (!isset($this->componentRouters[$component])) + { + $this->componentRouters[$component] = new JComponentRouterLegacy($compname); + } + } + + return $this->componentRouters[$component]; + } + + /** + * Set a router for a component + * + * @param string $component Component name with com_ prefix + * @param object $router Component router + * + * @return boolean True if the router was accepted, false if not + * + * @since 3.3 + */ + public function setComponentRouter($component, $router) + { + $reflection = new ReflectionClass($router); + + if (in_array('JComponentRouterInterface', $reflection->getInterfaceNames())) + { + $this->componentRouters[$component] = $router; + + return true; + } + else + { + return false; + } + } +} diff --git a/jlrouter.php b/jlrouter.php new file mode 100644 index 0000000..9c6aed1 --- /dev/null +++ b/jlrouter.php @@ -0,0 +1,89 @@ +isAdmin()) + { + return true; + } + + require_once dirname(__FILE__) . '/classes/site.php'; + $router = $app->getRouter(); + $menu = $app->getMenu(); + + if ($this->params->get('router_contact') || $this->params->get('router_content') || $this->params->get('router_newsfeed')) + { + require_once dirname(__FILE__) . '/classes/router/viewconfiguration.php'; + require_once dirname(__FILE__) . '/classes/router/view.php'; + require_once dirname(__FILE__) . '/classes/router/rules/interface.php'; + require_once dirname(__FILE__) . '/classes/router/rules/menu.php'; + require_once dirname(__FILE__) . '/classes/router/rules/nomenu.php'; + require_once dirname(__FILE__) . '/classes/router/rules/standard.php'; + } + + if ($this->params->get('router_contact')) + { + require_once dirname(__FILE__) . '/classes/contactrouter.php'; + $crouter = new ContactRouter($app, $menu); + $crouter->noIDs = $this->params->get('contact_noids'); + $router->setComponentRouter('com_contact', $crouter); + } + + if ($this->params->get('router_content')) + { + require_once dirname(__FILE__) . '/classes/contentrouter.php'; + $crouter = new ContentRouter($app, $menu); + $crouter->noIDs = $this->params->get('content_noids'); + $router->setComponentRouter('com_content', $crouter); + } + + if ($this->params->get('router_newsfeed')) + { + require_once dirname(__FILE__) . '/classes/newsfeedrouter.php'; + $crouter = new NewsfeedsRouter($app, $menu); + $crouter->noIDs = $this->params->get('newsfeed_noids'); + $router->setComponentRouter('com_newsfeeds', $crouter); + } + + if ($this->params->get('404handling') == '1') + { + $router->handling404 = array($this, 'modern404handling'); + } + + if ($this->params->get('404handling') == '2') + { + $router->handling404 = array($this, 'strict404handling'); + } + } + + public function modern404handling($router, $vars, $uri) + { + if (strlen($uri->getPath()) > 0) + { + if (isset($vars['option']) && is_a($router->getComponentRouter($vars['option']), 'JComponentRouterView')) + { + throw new Exception('URL invalid', 404); + } + } + } + + public function strict404handling($router, $vars, $uri) + { + if (strlen($uri->getPath()) > 0) + { + throw new Exception('URL invalid', 404); + } + } +} diff --git a/jlrouter.xml b/jlrouter.xml new file mode 100644 index 0000000..b9cfc3e --- /dev/null +++ b/jlrouter.xml @@ -0,0 +1,100 @@ + + + plg_system_jlrouter + Hannes Papenberg + March 2017 + Copyright (C) 2017 Hannes Papenberg. All rights reserved. + GNU General Public License version 2 or later; see LICENSE.txt + info@joomlager.de + www.joomlager.de + 1.0.0 + PLG_SYSTEM_JLROUTER_XML_DESCRIPTION + + jlrouter.php + classes + language + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
diff --git a/language/en-GB/en-GB.plg_system_jlrouter.ini b/language/en-GB/en-GB.plg_system_jlrouter.ini new file mode 100644 index 0000000..189899b --- /dev/null +++ b/language/en-GB/en-GB.plg_system_jlrouter.ini @@ -0,0 +1,19 @@ +PLG_SYSTEM_JLROUTER="Joomlager Router" +PLG_SYSTEM_JLROUTER_XML_DESCRIPTION="This plugin backports the router that was initially planned for Joomla 3.7 into Joomla 3.6 and 3.7. Enable the modern routing for every component that you want this to use. To get proper 404 handling, set the option that best fits you. CAREFULL: Read the hints to that option!" +PLG_SYSTEM_JLROUTER_FIELD_ROUTER_CONTACT_LABEL="Router for Contact component" +PLG_SYSTEM_JLROUTER_FIELD_ROUTER_CONTACT_DESC="Enables the modern routing for the Contact component." +PLG_SYSTEM_JLROUTER_FIELD_ROUTER_CONTENT_LABEL="Router for Content component" +PLG_SYSTEM_JLROUTER_FIELD_ROUTER_CONTENT_DESC="Enables the modern routing for the Content component." +PLG_SYSTEM_JLROUTER_FIELD_ROUTER_NEWSFEED_LABEL="Router for Newsfeed component" +PLG_SYSTEM_JLROUTER_FIELD_ROUTER_NEWSFEED_DESC="Enables the modern routing for the Newsfeed component." +PLG_SYSTEM_JLROUTER_FIELD_CONTACT_NOIDS_LABEL="Remove IDs from Contact URLs" +PLG_SYSTEM_JLROUTER_FIELD_CONTACT_NOIDS_DESC="" +PLG_SYSTEM_JLROUTER_FIELD_CONTENT_NOIDS_LABEL="Remove IDs from Content URLs" +PLG_SYSTEM_JLROUTER_FIELD_CONTACT_NOIDS_DESC="" +PLG_SYSTEM_JLROUTER_FIELD_NEWSFEED_NOIDS_LABEL="Remove IDs from Newsfeeds URLs" +PLG_SYSTEM_JLROUTER_FIELD_NEWSFEED_NOIDS_DESC="" +PLG_SYSTEM_JLROUTER_FIELD_404HANDLING_LABEL="404 handling" +PLG_SYSTEM_JLROUTER_FIELD_404HANDLING_DESC="The new router can throw proper 404 errors, but that requires that your extensions support it. The second option only enables this feature for modern routers, the third does this for all components. You have to test that your site still works properly after this!" +PLG_SYSTEM_JLROUTER_FIELD_VALUE_NO_404_HANDLING="No special 404 handling" +PLG_SYSTEM_JLROUTER_FIELD_VALUE_MODERN_404_HANDLING="Strict 404 handling for modern routers" +PLG_SYSTEM_JLROUTER_VALUE_STRICT_404_HANDLING="Strict 404 handling for all components" diff --git a/language/en-GB/en-GB.plg_system_jlrouter.sys.ini b/language/en-GB/en-GB.plg_system_jlrouter.sys.ini new file mode 100644 index 0000000..0534133 --- /dev/null +++ b/language/en-GB/en-GB.plg_system_jlrouter.sys.ini @@ -0,0 +1,19 @@ +PLG_SYSTEM_JLROUTER="Joomlager Router" +PLG_SYSTEM_JLROUTER_XML_DESCRIPTION="This plugin backports the router that was initially planned for Joomla 3.7 into Joomla 3.6 and 3.7. Enable the modern routing for every component that you want this to use. To get proper 404 handling, set the option that best fits you. CAREFULL: Read the hints to that option!" +PLG_SYSTEM_JLROUTER_FIELD_ROUTER_CONTACT_LABEL="Router for Contact component" +PLG_SYSTEM_JLROUTER_FIELD_ROUTER_CONTACT_DESC="Enables the modern routing for the Contact component." +PLG_SYSTEM_JLROUTER_FIELD_ROUTER_CONTENT_LABEL="Router for Content component" +PLG_SYSTEM_JLROUTER_FIELD_ROUTER_CONTENT_DESC="Enables the modern routing for the Content component." +PLG_SYSTEM_JLROUTER_FIELD_ROUTER_NEWSFEED_LABEL="Router for Newsfeed component" +PLG_SYSTEM_JLROUTER_FIELD_ROUTER_NEWSFEED_DESC="Enables the modern routing for the Newsfeed component." +PLG_SYSTEM_JLROUTER_FIELD_CONTACT_NOIDS_LABEL="Remove IDs from Contact URLs" +PLG_SYSTEM_JLROUTER_FIELD_CONTACT_NOIDS_DESC="" +PLG_SYSTEM_JLROUTER_FIELD_CONTENT_NOIDS_LABEL="Remove IDs from Content URLs" +PLG_SYSTEM_JLROUTER_FIELD_CONTACT_NOIDS_DESC="" +PLG_SYSTEM_JLROUTER_FIELD_NEWSFEED_NOIDS_LABEL="Remove IDs from Newsfeeds URLs" +PLG_SYSTEM_JLROUTER_FIELD_NEWSFEED_NOIDS_DESC="" +PLG_SYSTEM_JLROUTER_FIELD_404HANDLING_LABEL="404 handling" +PLG_SYSTEM_JLROUTER_FIELD_404HANDLING_DESC="The new router can throw proper 404 errors, but that requires that your extensions support it. The second option only enables this feature for modern routers, the third does this for all components. You have to test that your site still works properly after this!" +PLG_SYSTEM_JLROUTER_FIELD_VALUE_NO_404_HANDLING="No special 404 handling" +PLG_SYSTEM_JLROUTER_FIELD_VALUE_NO_404_HANDLING="Strict 404 handling for modern routers" +PLG_SYSTEM_JLROUTER_VALUE_STRICT_404_HANDLING="Strict 404 handling for all components"