Some changes for custom URL support
authorAlexander Ebert <ebert@woltlab.com>
Wed, 25 Nov 2015 12:39:43 +0000 (13:39 +0100)
committerAlexander Ebert <ebert@woltlab.com>
Wed, 25 Nov 2015 12:39:43 +0000 (13:39 +0100)
wcfsetup/install/files/lib/system/package/plugin/PagePackageInstallationPlugin.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/request/ControllerMap.class.php
wcfsetup/install/files/lib/system/request/RequestHandler.class.php
wcfsetup/install/files/lib/system/request/RouteHandler.class.php
wcfsetup/install/files/lib/system/request/route/DynamicRequestRoute.class.php
wcfsetup/install/files/lib/system/request/route/IRequestRoute.class.php
wcfsetup/install/files/lib/system/request/route/LookupRequestRoute.class.php [new file with mode: 0644]

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 (file)
index 0000000..a52cae1
--- /dev/null
@@ -0,0 +1,150 @@
+<?php
+namespace wcf\system\package\plugin;
+use wcf\system\WCF;
+
+/**
+ * Installs, updates and deletes CMS pages.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2015 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @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
+                                       ));
+                               }
+                       }
+               }
+       }
+}
index c36abb030bbf57458c35aa8cc93d35b58a5093d0..d247e83c993bede2afaf0504b763280286d00274 100644 (file)
@@ -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.
         * 
index 59ad080c40fd5a7035f0014b1037f06d89b14f92..103b1c9b81fbd3cab3c26f04047d8c39380e508b 100644 (file)
@@ -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.");
                        }
index 0a308b154aa5c83f8866789de6ba4f4d31c3fc5a..107106b6d6ab97d007362c474b15ae7a0150b778 100644 (file)
@@ -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'];
index d9b2bfb69867face4bb9731a770434ea710f2c89..60b45f32b3814e4ba11619dacf589d2663689466 100644 (file)
@@ -98,7 +98,12 @@ class DynamicRequestRoute implements IRequestRoute {
                $this->setPattern('~
                        /?
                        (?:
-                               (?P<controller>[A-Za-z0-9\-]+)
+                               (?P<controller>
+                                       (?:
+                                               [a-z][a-z0-9]+
+                                               (?:-[a-z][a-z0-9]+)*
+                                       )+
+                               )
                                (?:
                                        /
                                        (?P<id>\d+)
index d1a37eb4877bf438280265238487939e0e44cc5d..e4360e48b7ac739d616c20391907d2265f479bfa 100644 (file)
@@ -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 (file)
index 0000000..9cc69af
--- /dev/null
@@ -0,0 +1,96 @@
+<?php
+namespace wcf\system\request\route;
+
+use wcf\system\exception\SystemException;
+use wcf\system\request\ControllerMap;
+use wcf\util\FileUtil;
+
+class LookupRequestRoute implements IRequestRoute {
+       /**
+        * @var ControllerMap
+        */
+       protected $controllerMap;
+       
+       protected $routeData = [];
+       
+       public function __construct(ControllerMap $controllerMap) {
+               $this->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<controller>.+?)
+                       (?:
+                               (?P<id>[0-9]+)
+                               (?:
+                                       -
+                                       (?P<title>[^/]+)
+                               )?
+                               /
+                       )?
+               $~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