From 1c45690475f030de8bb54fb27bd581d5895d2a2e Mon Sep 17 00:00:00 2001 From: Alexander Ebert Date: Wed, 25 Nov 2015 13:39:43 +0100 Subject: [PATCH] Some changes for custom URL support --- .../PagePackageInstallationPlugin.class.php | 150 ++++++++++++++++++ .../system/request/ControllerMap.class.php | 14 ++ .../system/request/RequestHandler.class.php | 2 +- .../lib/system/request/RouteHandler.class.php | 17 +- .../route/DynamicRequestRoute.class.php | 7 +- .../request/route/IRequestRoute.class.php | 41 ++++- .../route/LookupRequestRoute.class.php | 96 +++++++++++ 7 files changed, 321 insertions(+), 6 deletions(-) create mode 100644 wcfsetup/install/files/lib/system/package/plugin/PagePackageInstallationPlugin.class.php create mode 100644 wcfsetup/install/files/lib/system/request/route/LookupRequestRoute.class.php diff --git a/wcfsetup/install/files/lib/system/package/plugin/PagePackageInstallationPlugin.class.php b/wcfsetup/install/files/lib/system/package/plugin/PagePackageInstallationPlugin.class.php new file mode 100644 index 0000000000..a52cae1ece --- /dev/null +++ b/wcfsetup/install/files/lib/system/package/plugin/PagePackageInstallationPlugin.class.php @@ -0,0 +1,150 @@ + + * @package com.woltlab.wcf + * @subpackage acp.package.plugin + * @category Community Framework + */ +class PagePackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin { + /** + * @see AbstractXMLPackageInstallationPlugin::$className + */ + public $className = 'wcf\data\clipboard\action\ClipboardActionEditor'; + + /** + * @see AbstractXMLPackageInstallationPlugin::$tagName + */ + public $tagName = 'page'; + + /** + * @see AbstractXMLPackageInstallationPlugin::handleDelete() + */ + protected function handleDelete(array $items) { + $sql = "DELETE FROM wcf".WCF_N."_".$this->tableName." + WHERE actionName = ? + AND actionClassName = ? + AND packageID = ?"; + $statement = WCF::getDB()->prepareStatement($sql); + foreach ($items as $item) { + $statement->execute(array( + $item['attributes']['name'], + $item['elements']['actionclassname'], + $this->installation->getPackageID() + )); + } + } + + /** + * @see AbstractXMLPackageInstallationPlugin::getElement() + */ + protected function getElement(\DOMXPath $xpath, array &$elements, \DOMElement $element) { + $nodeValue = $element->nodeValue; + + // read pages + if ($element->tagName == 'pages') { + $nodeValue = array(); + + $pages = $xpath->query('child::ns:page', $element); + foreach ($pages as $page) { + $nodeValue[] = $page->nodeValue; + } + } + + $elements[$element->tagName] = $nodeValue; + } + + /** + * @see AbstractXMLPackageInstallationPlugin::prepareImport() + */ + protected function prepareImport(array $data) { + $isStatic = (!empty($data['content'])); + + return [ + 'controller' => ($isStatic) ? '' : $data['elements']['controller'], + 'controllerCustomURL' => ($isStatic) ? '' : $data['elements']['customurl'], + 'displayName' => $data['elements']['displayname'], + 'name' => $data['attributes']['name'] + ]; + + $showOrder = (isset($data['elements']['showorder'])) ? intval($data['elements']['showorder']) : null; + $showOrder = $this->getShowOrder($showOrder, $data['elements']['actionclassname'], 'actionClassName'); + + return array( + 'actionClassName' => $data['elements']['actionclassname'], + 'actionName' => $data['attributes']['name'], + 'pages' => $data['elements']['pages'], + 'showOrder' => $showOrder + ); + } + + /** + * @see AbstractXMLPackageInstallationPlugin::findExistingItem() + */ + protected function findExistingItem(array $data) { + $sql = "SELECT * + FROM wcf".WCF_N."_".$this->tableName." + WHERE actionName = ? + AND actionClassName = ? + AND packageID = ?"; + $parameters = array( + $data['actionName'], + $data['actionClassName'], + $this->installation->getPackageID() + ); + + return array( + 'sql' => $sql, + 'parameters' => $parameters + ); + } + + /** + * @see AbstractXMLPackageInstallationPlugin::import() + */ + protected function import(array $row, array $data) { + // extract pages + $pages = $data['pages']; + unset($data['pages']); + + // import or update action + $object = parent::import($row, $data); + + // store pages for later import + $this->pages[$object->actionID] = $pages; + } + + /** + * @see AbstractXMLPackageInstallationPlugin::postImport() + */ + protected function postImport() { + // clear pages + $sql = "DELETE FROM wcf".WCF_N."_clipboard_page + WHERE packageID = ?"; + $statement = WCF::getDB()->prepareStatement($sql); + $statement->execute(array($this->installation->getPackageID())); + + if (!empty($this->pages)) { + // insert pages + $sql = "INSERT INTO wcf".WCF_N."_clipboard_page + (pageClassName, packageID, actionID) + VALUES (?, ?, ?)"; + $statement = WCF::getDB()->prepareStatement($sql); + foreach ($this->pages as $actionID => $pages) { + foreach ($pages as $pageClassName) { + $statement->execute(array( + $pageClassName, + $this->installation->getPackageID(), + $actionID + )); + } + } + } + } +} diff --git a/wcfsetup/install/files/lib/system/request/ControllerMap.class.php b/wcfsetup/install/files/lib/system/request/ControllerMap.class.php index c36abb030b..d247e83c99 100644 --- a/wcfsetup/install/files/lib/system/request/ControllerMap.class.php +++ b/wcfsetup/install/files/lib/system/request/ControllerMap.class.php @@ -60,6 +60,20 @@ class ControllerMap { return $classData; } + /** + * Attempts to resolve a custom controller, will return an empty array + * regardless if given controller would match an actual controller class. + * + * @param string $application application identifier + * @param string $controller url controller + * @return array empty array if there is no exact match + */ + public function resolveCustomController($application, $controller) { + // TODO: check custom controller mappings + + return []; + } + /** * Transforms given controller into its url representation. * diff --git a/wcfsetup/install/files/lib/system/request/RequestHandler.class.php b/wcfsetup/install/files/lib/system/request/RequestHandler.class.php index 59ad080c40..103b1c9b81 100644 --- a/wcfsetup/install/files/lib/system/request/RequestHandler.class.php +++ b/wcfsetup/install/files/lib/system/request/RequestHandler.class.php @@ -114,7 +114,7 @@ class RequestHandler extends SingletonFactory { $this->routeHandler->setRequestHandler($this); $this->routeHandler->setDefaultRoutes(); - if (!$this->routeHandler->matches()) { + if (!$this->routeHandler->matches($application)) { if (ENABLE_DEBUG_MODE) { throw new SystemException("Cannot handle request, no valid route provided."); } diff --git a/wcfsetup/install/files/lib/system/request/RouteHandler.class.php b/wcfsetup/install/files/lib/system/request/RouteHandler.class.php index 0a308b154a..107106b6d6 100644 --- a/wcfsetup/install/files/lib/system/request/RouteHandler.class.php +++ b/wcfsetup/install/files/lib/system/request/RouteHandler.class.php @@ -4,6 +4,7 @@ use wcf\system\application\ApplicationHandler; use wcf\system\event\EventHandler; use wcf\system\exception\SystemException; use wcf\system\request\route\DynamicRequestRoute; +use wcf\system\request\route\IRequestRoute; use wcf\system\SingletonFactory; use wcf\system\WCF; use wcf\util\FileUtil; @@ -149,18 +150,28 @@ class RouteHandler extends SingletonFactory { /** * Returns true if a route matches. Please bear in mind, that the - * first route which is able to consume all path components is used, + * first route that is able to consume all path components is used, * even if other routes may fit better. Route order is crucial! * + * @param string $application application identifier * @return boolean */ - public function matches() { + public function matches($application) { foreach ($this->routes as $route) { if ($this->requestHandler->isACPRequest() != $route->isACP()) { continue; } - if ($route->matches(self::getPathInfo())) { + $match = false; + if ($route instanceof IRequestRoute) { + $match = $route->matches($application, self::getPathInfo()); + } + else if ($route instanceof IRoute) { + // legacy route + $match = $route->matches(self::getPathInfo()); + } + + if ($match) { $this->routeData = $route->getRouteData(); $this->isDefaultController = $this->routeData['isDefaultController']; diff --git a/wcfsetup/install/files/lib/system/request/route/DynamicRequestRoute.class.php b/wcfsetup/install/files/lib/system/request/route/DynamicRequestRoute.class.php index d9b2bfb698..60b45f32b3 100644 --- a/wcfsetup/install/files/lib/system/request/route/DynamicRequestRoute.class.php +++ b/wcfsetup/install/files/lib/system/request/route/DynamicRequestRoute.class.php @@ -98,7 +98,12 @@ class DynamicRequestRoute implements IRequestRoute { $this->setPattern('~ /? (?: - (?P[A-Za-z0-9\-]+) + (?P + (?: + [a-z][a-z0-9]+ + (?:-[a-z][a-z0-9]+)* + )+ + ) (?: / (?P\d+) diff --git a/wcfsetup/install/files/lib/system/request/route/IRequestRoute.class.php b/wcfsetup/install/files/lib/system/request/route/IRequestRoute.class.php index d1a37eb487..e4360e48b7 100644 --- a/wcfsetup/install/files/lib/system/request/route/IRequestRoute.class.php +++ b/wcfsetup/install/files/lib/system/request/route/IRequestRoute.class.php @@ -12,7 +12,46 @@ use wcf\system\request\IRoute; * @subpackage system.request * @category Community Framework */ -interface IRequestRoute extends IRoute { +interface IRequestRoute { + /** + * Builds a link upon route components. + * + * @param array $components list of url components + * @return string + */ + public function buildLink(array $components); + + /** + * Returns true if current route can handle the build request. + * + * @param array $components list of url components + * @return boolean + */ + public function canHandle(array $components); + + /** + * Returns parsed route data. + * + * @return array + */ + public function getRouteData(); + + /** + * Returns true if route applies for ACP. + * + * @return boolean + */ + public function isACP(); + + /** + * Returns true if given request url matches this route. + * + * @param string $application application identifier + * @param string $requestURL request url + * @return boolean + */ + public function matches($application, $requestURL); + /** * Configures this route to handle either ACP or frontend requests. * diff --git a/wcfsetup/install/files/lib/system/request/route/LookupRequestRoute.class.php b/wcfsetup/install/files/lib/system/request/route/LookupRequestRoute.class.php new file mode 100644 index 0000000000..9cc69afda3 --- /dev/null +++ b/wcfsetup/install/files/lib/system/request/route/LookupRequestRoute.class.php @@ -0,0 +1,96 @@ +controllerMap = $controllerMap; + } + + /** + * @inheritDoc + */ + public function matches($application, $requestURL) { + $requestURL = FileUtil::addLeadingSlash($requestURL); + + if ($requestURL === '/') { + // ignore empty urls and let them be handled by regular routes + return false; + } + + $regex = '~^ + / + (?P.+?) + (?: + (?P[0-9]+) + (?: + - + (?P[^/]+) + )? + / + )? + $~x'; + + if (preg_match($regex, $requestURL, $matches)) { + if (!empty($matches['id'])) { + // check for static controller URLs + $this->routeData = $this->controllerMap->resolveCustomController($application, $matches['controller']); + } + + if (empty($this->routeData)) { + // try to match the entire url + $this->routeData = $this->controllerMap->resolveCustomController($application, $requestURL); + } + } + + return (!empty($this->routeData)); + } + + /** + * @inheritDoc + */ + public function getRouteData() { + return $this->getRouteData(); + } + + /** + * @inheritDoc + */ + public function setIsACP($isACP) { + // lookups are not supported for ACP requests + } + + /** + * @inheritDoc + * @throws SystemException + */ + public function buildLink(array $components) { + throw new SystemException('LookupRequestRoute cannot build links, please verify capabilities by calling canHandle() first.'); + } + + /** + * @inheritDoc + */ + public function canHandle(array $components) { + // this route cannot build routes, it is a one-way resolver + return false; + } + + /** + * @inheritDoc + */ + public function isACP() { + // lookups are not supported for ACP requests + return false; + } +} \ No newline at end of file -- 2.20.1