From e3530f1090a99cc23e6690fdc1b249ff9594e18a Mon Sep 17 00:00:00 2001 From: Alexander Ebert Date: Tue, 17 Nov 2015 19:24:25 +0100 Subject: [PATCH] Using DI for certain classes --- .../lib/system/SingletonFactory.class.php | 3 +- .../application/ApplicationHandler.class.php | 31 +- .../lib/system/event/EventHandler.class.php | 45 ++- .../system/request/ControllerMap.class.php | 1 + .../system/request/FlexibleRoute.class.php | 1 + .../files/lib/system/request/IRoute.class.php | 1 + .../system/request/RequestHandler.class.php | 61 +++- .../lib/system/request/RouteHandler.class.php | 83 +++-- .../lib/system/request/StaticRoute.class.php | 1 + .../route/DynamicRequestRoute.class.php | 340 ++++++++++++++++++ .../request/route/IRequestRoute.class.php | 22 ++ .../route/StaticRequestRoute.class.php | 111 ++++++ 12 files changed, 636 insertions(+), 64 deletions(-) create mode 100644 wcfsetup/install/files/lib/system/request/route/DynamicRequestRoute.class.php create mode 100644 wcfsetup/install/files/lib/system/request/route/IRequestRoute.class.php create mode 100644 wcfsetup/install/files/lib/system/request/route/StaticRequestRoute.class.php diff --git a/wcfsetup/install/files/lib/system/SingletonFactory.class.php b/wcfsetup/install/files/lib/system/SingletonFactory.class.php index d15fc3f2f6..69ac610654 100644 --- a/wcfsetup/install/files/lib/system/SingletonFactory.class.php +++ b/wcfsetup/install/files/lib/system/SingletonFactory.class.php @@ -11,13 +11,14 @@ use wcf\system\exception\SystemException; * @package com.woltlab.wcf * @subpackage system * @category Community Framework + * @deprecated 2.2 */ abstract class SingletonFactory { /** * Singletons do not support a public constructor. Override init() if * your class needs to initialize components on creation. */ - public final function __construct() { + public function __construct() { $this->init(); } diff --git a/wcfsetup/install/files/lib/system/application/ApplicationHandler.class.php b/wcfsetup/install/files/lib/system/application/ApplicationHandler.class.php index d43aac53a8..b0043f54ea 100644 --- a/wcfsetup/install/files/lib/system/application/ApplicationHandler.class.php +++ b/wcfsetup/install/files/lib/system/application/ApplicationHandler.class.php @@ -17,6 +17,10 @@ use wcf\system\SingletonFactory; * @category Community Framework */ class ApplicationHandler extends SingletonFactory { + /** + * @var ApplicationCacheBuilder + */ + protected $applicationCacheBuilder; /** * application cache * @var array @@ -29,17 +33,28 @@ class ApplicationHandler extends SingletonFactory { */ protected $pageURLs = array(); + /** + * ApplicationHandler constructor. + * + * @param ApplicationCacheBuilder $applicationCacheBuilder + */ + public function __construct(ApplicationCacheBuilder $applicationCacheBuilder) { + $this->applicationCacheBuilder = $applicationCacheBuilder; + + parent::__construct(); + } + /** * Initializes cache. */ protected function init() { - $this->cache = ApplicationCacheBuilder::getInstance()->getData(); + $this->cache = $this->applicationCacheBuilder->getData(); } /** * Returns the primary application. * - * @return \wcf\data\application\Application + * @return Application */ public function getPrimaryApplication() { $packageID = ($this->cache['primary']) ?: PACKAGE_ID; @@ -55,7 +70,8 @@ class ApplicationHandler extends SingletonFactory { * Returns an application based upon it's abbreviation. Will return the * primary application if $abbreviation equals to 'wcf' * - * @return \wcf\data\application\Application + * @param string $abbreviation package abbreviation, e.g. `wbb` for `com.woltlab.wbb` + * @return Application */ public function getApplication($abbreviation) { if ($abbreviation == 'wcf') { @@ -77,7 +93,7 @@ class ApplicationHandler extends SingletonFactory { * Returns pseudo-application representing WCF used for special cases, * e.g. cross-domain files requestable through the webserver. * - * @return \wcf\data\application\Application + * @return Application */ public function getWCF() { return $this->cache['wcf']; @@ -86,7 +102,7 @@ class ApplicationHandler extends SingletonFactory { /** * Returns the currently active application. * - * @return \wcf\data\application\Application + * @return Application */ public function getActiveApplication() { // work-around during WCFSetup @@ -100,7 +116,7 @@ class ApplicationHandler extends SingletonFactory { /** * Returns a list of dependent applications. * - * @return array<\wcf\data\application\Application> + * @return Application[] */ public function getDependentApplications() { $applications = $this->getApplications(); @@ -117,7 +133,7 @@ class ApplicationHandler extends SingletonFactory { /** * Returns a list of all active applications. * - * @return array<\wcf\data\application\Application> + * @return Application[] */ public function getApplications() { return $this->cache['application']; @@ -126,6 +142,7 @@ class ApplicationHandler extends SingletonFactory { /** * Returns abbreviation for a given package id or null if application is unknown. * + * @param int $packageID unique package id * @return string */ public function getAbbreviation($packageID) { diff --git a/wcfsetup/install/files/lib/system/event/EventHandler.class.php b/wcfsetup/install/files/lib/system/event/EventHandler.class.php index f04a97b8c9..9a218134ab 100644 --- a/wcfsetup/install/files/lib/system/event/EventHandler.class.php +++ b/wcfsetup/install/files/lib/system/event/EventHandler.class.php @@ -23,6 +23,11 @@ class EventHandler extends SingletonFactory { */ protected $actions = null; + /** + * @var EventListenerCacheBuilder + */ + protected $eventListenerCacheBuilder; + /** * registered inherit actions * @var array @@ -33,26 +38,37 @@ class EventHandler extends SingletonFactory { * instances of registerd actions * @var array */ - protected $actionsObjects = array(); + protected $actionsObjects = []; /** * instances of registered inherit actions * @var array */ - protected $inheritedActionsObjects = array(); + protected $inheritedActionsObjects = []; /** * instances of listener objects - * @var array<\wcf\system\event\IEventListener> + * @var IEventListener[] */ - protected $listenerObjects = array(); + protected $listenerObjects = []; + + /** + * EventHandler constructor. + * + * @param EventListenerCacheBuilder $eventListenerCacheBuilder + */ + public function __construct(EventListenerCacheBuilder $eventListenerCacheBuilder) { + $this->eventListenerCacheBuilder = $eventListenerCacheBuilder; + + parent::__construct(); + } /** * Loads all registered actions of the active package. */ protected function loadActions() { $environment = ((class_exists('wcf\system\WCFACP', false) || class_exists('wcf\system\CLIWCF', false)) ? 'admin' : 'user'); - $cache = EventListenerCacheBuilder::getInstance()->getData(); + $cache = $this->eventListenerCacheBuilder->getData(); if (isset($cache['actions'][$environment])) { $this->actions = $cache['actions'][$environment]; @@ -63,10 +79,10 @@ class EventHandler extends SingletonFactory { unset($cache); if (!is_array($this->actions)) { - $this->actions = array(); + $this->actions = []; } if (!is_array($this->inheritedActions)) { - $this->inheritedActions = array(); + $this->inheritedActions = []; } } @@ -78,14 +94,15 @@ class EventHandler extends SingletonFactory { * @param string $className * @param string $name * @param array &$parameters + * @throws SystemException */ protected function executeInheritedActions($eventObj, $eventName, $className, $name, array &$parameters) { // create objects of the actions if (!isset($this->inheritedActionsObjects[$name]) || !is_array($this->inheritedActionsObjects[$name])) { - $this->inheritedActionsObjects[$name] = array(); + $this->inheritedActionsObjects[$name] = []; // get parent classes - $familyTree = array(); + $familyTree = []; $member = (is_object($eventObj) ? get_class($eventObj) : $eventObj); while ($member != false) { $familyTree[] = $member; @@ -155,6 +172,7 @@ class EventHandler extends SingletonFactory { * @param mixed $eventObj * @param string $eventName * @param array &$parameters + * @throws SystemException */ public function fireAction($eventObj, $eventName, array &$parameters = array()) { // get class name @@ -178,10 +196,10 @@ class EventHandler extends SingletonFactory { if (!isset($this->actionsObjects[$name]) || !is_array($this->actionsObjects[$name])) { if (!isset($this->actions[$name]) || !is_array($this->actions[$name])) { // no action registered - return false; + return; } - $this->actionsObjects[$name] = array(); + $this->actionsObjects[$name] = []; foreach ($this->actions[$name] as $eventListener) { if ($eventListener->validateOptions() && $eventListener->validatePermissions()) { if (isset($this->actionsObjects[$name][$eventListener->listenerClassName])) continue; @@ -229,8 +247,9 @@ class EventHandler extends SingletonFactory { /** * Generates an unique name for an action. * - * @param string $className - * @param string $eventName + * @param string $className + * @param string $eventName + * @return string unique action name */ public static function generateKey($className, $eventName) { return $eventName.'@'.$className; diff --git a/wcfsetup/install/files/lib/system/request/ControllerMap.class.php b/wcfsetup/install/files/lib/system/request/ControllerMap.class.php index 408fcf58fe..c36abb030b 100644 --- a/wcfsetup/install/files/lib/system/request/ControllerMap.class.php +++ b/wcfsetup/install/files/lib/system/request/ControllerMap.class.php @@ -30,6 +30,7 @@ class ControllerMap { * @param string $controller url controller * @param boolean $isAcpRequest true if this is an ACP request * @return array className, controller and pageType + * @throws SystemException */ public function resolve($application, $controller, $isAcpRequest) { // validate controller diff --git a/wcfsetup/install/files/lib/system/request/FlexibleRoute.class.php b/wcfsetup/install/files/lib/system/request/FlexibleRoute.class.php index 301c0bbade..664310fe68 100644 --- a/wcfsetup/install/files/lib/system/request/FlexibleRoute.class.php +++ b/wcfsetup/install/files/lib/system/request/FlexibleRoute.class.php @@ -15,6 +15,7 @@ use wcf\system\menu\page\PageMenu; * @package com.woltlab.wcf * @subpackage system.request * @category Community Framework + * @deprecated 2.2:2.3 Consider using \wcf\system\request\route\DynamicRequestRoute */ class FlexibleRoute implements IRoute { /** diff --git a/wcfsetup/install/files/lib/system/request/IRoute.class.php b/wcfsetup/install/files/lib/system/request/IRoute.class.php index 7ba99e62a1..2c92e1fe52 100644 --- a/wcfsetup/install/files/lib/system/request/IRoute.class.php +++ b/wcfsetup/install/files/lib/system/request/IRoute.class.php @@ -10,6 +10,7 @@ namespace wcf\system\request; * @package com.woltlab.wcf * @subpackage system.request * @category Community Framework + * @deprecated 2.2:2.3 will be replaced with \wcf\system\request\route\IRequestRoute */ interface IRoute { /** diff --git a/wcfsetup/install/files/lib/system/request/RequestHandler.class.php b/wcfsetup/install/files/lib/system/request/RequestHandler.class.php index 2d423e7e92..457cc89807 100644 --- a/wcfsetup/install/files/lib/system/request/RequestHandler.class.php +++ b/wcfsetup/install/files/lib/system/request/RequestHandler.class.php @@ -23,14 +23,19 @@ use wcf\util\HeaderUtil; class RequestHandler extends SingletonFactory { /** * active request object - * @var \wcf\system\request\Request + * @var Request */ protected $activeRequest = null; /** - * @var \wcf\system\request\ControllerMap + * @var ApplicationHandler */ - protected $controllerMap = null; + protected $applicationHandler; + + /** + * @var ControllerMap + */ + protected $controllerMap; /** * true, if current domain mismatch any known domain @@ -44,14 +49,32 @@ class RequestHandler extends SingletonFactory { */ protected $isACPRequest = false; + /** + * @var RouteHandler + */ + protected $routeHandler; + + /** + * RequestHandler constructor. + * + * @param ApplicationHandler $applicationHandler + * @param ControllerMap $controllerMap + * @param RouteHandler $routeHandler + */ + public function __construct(ApplicationHandler $applicationHandler, ControllerMap $controllerMap, RouteHandler $routeHandler) { + $this->applicationHandler = $applicationHandler; + $this->controllerMap = $controllerMap; + $this->routeHandler = $routeHandler; + + parent::__construct(); + } + /** * @see \wcf\system\SingletonFactory::init() */ protected function init() { - $this->controllerMap = new ControllerMap(); - if (isset($_SERVER['HTTP_HOST'])) { - foreach (ApplicationHandler::getInstance()->getApplications() as $application) { + foreach ($this->applicationHandler->getApplications() as $application) { if ($application->domainName == $_SERVER['HTTP_HOST']) { $this->inRescueMode = false; break; @@ -60,7 +83,7 @@ class RequestHandler extends SingletonFactory { // check if WCF is running as standalone if ($this->inRescueMode() && PACKAGE_ID == 1) { - if (ApplicationHandler::getInstance()->getWCF()->domainName == $_SERVER['HTTP_HOST']) { + if ($this->applicationHandler->getWCF()->domainName == $_SERVER['HTTP_HOST']) { $this->inRescueMode = false; } } @@ -77,14 +100,21 @@ class RequestHandler extends SingletonFactory { /** * Handles a http request. - * + * * @param string $application * @param boolean $isACPRequest + * @throws AJAXException + * @throws IllegalLinkException + * @throws SystemException */ public function handle($application = 'wcf', $isACPRequest = false) { $this->isACPRequest = $isACPRequest; - if (!RouteHandler::getInstance()->matches()) { + // initialize route handler + $this->routeHandler->setRequestHandler($this); + $this->routeHandler->setDefaultRoutes(); + + if (!$this->routeHandler->matches()) { if (ENABLE_DEBUG_MODE) { throw new SystemException("Cannot handle request, no valid route provided."); } @@ -123,10 +153,11 @@ class RequestHandler extends SingletonFactory { * Builds a new request. * * @param string $application + * @throws IllegalLinkException */ protected function buildRequest($application) { try { - $routeData = RouteHandler::getInstance()->getRouteData(); + $routeData = $this->routeHandler->getRouteData(); // handle landing page for frontend requests if (!$this->isACPRequest()) { @@ -134,7 +165,7 @@ class RequestHandler extends SingletonFactory { // check if accessing from the wrong domain (e.g. "www." omitted but domain was configured with) if (!defined('WCF_RUN_MODE') || WCF_RUN_MODE != 'embedded') { - $applicationObject = ApplicationHandler::getInstance()->getApplication($application); + $applicationObject = $this->applicationHandler->getApplication($application); if ($applicationObject->domainName != $_SERVER['HTTP_HOST']) { // build URL, e.g. http://example.net/forum/ $url = FileUtil::addTrailingSlash(RouteHandler::getProtocol() . $applicationObject->domainName . RouteHandler::getPath()); @@ -232,7 +263,7 @@ class RequestHandler extends SingletonFactory { * @param array $routeData */ protected function handleDefaultController($application, array &$routeData) { - if (!RouteHandler::getInstance()->isDefaultController()) { + if (!$this->routeHandler->isDefaultController()) { return; } @@ -245,8 +276,8 @@ class RequestHandler extends SingletonFactory { // resolve implicit application abbreviation for landing page controller $landingPageApplication = $landingPage->getApplication(); - $primaryApplication = ApplicationHandler::getInstance()->getPrimaryApplication(); - $primaryApplicationAbbr = ApplicationHandler::getInstance()->getAbbreviation($primaryApplication->packageID); + $primaryApplication = $this->applicationHandler->getPrimaryApplication(); + $primaryApplicationAbbr = $this->applicationHandler->getAbbreviation($primaryApplication->packageID); if ($landingPageApplication == 'wcf') { $landingPageApplication = $primaryApplicationAbbr; } @@ -266,7 +297,7 @@ class RequestHandler extends SingletonFactory { } // set default controller - $applicationObj = WCF::getApplicationObject(ApplicationHandler::getInstance()->getApplication($application)); + $applicationObj = WCF::getApplicationObject($this->applicationHandler->getApplication($application)); $routeData['controller'] = preg_replace('~^.*?\\\([^\\\]+)(?:Action|Form|Page)$~', '\\1', $applicationObj->getPrimaryController()); $routeData['controller'] = $this->getControllerMap()->lookup($routeData['controller']); } diff --git a/wcfsetup/install/files/lib/system/request/RouteHandler.class.php b/wcfsetup/install/files/lib/system/request/RouteHandler.class.php index e5cf7dfe96..3f9090d5f7 100644 --- a/wcfsetup/install/files/lib/system/request/RouteHandler.class.php +++ b/wcfsetup/install/files/lib/system/request/RouteHandler.class.php @@ -3,6 +3,7 @@ namespace wcf\system\request; use wcf\system\application\ApplicationHandler; use wcf\system\event\EventHandler; use wcf\system\exception\SystemException; +use wcf\system\request\route\DynamicRequestRoute; use wcf\system\SingletonFactory; use wcf\system\WCF; use wcf\util\FileUtil; @@ -51,21 +52,36 @@ class RouteHandler extends SingletonFactory { */ protected static $secure = null; + /** + * @var ApplicationHandler + */ + protected $applicationHandler; + /** * list of application abbreviation and default controller name * @var array */ protected $defaultControllers = null; + /** + * @var EventHandler + */ + protected $eventHandler; + /** * true, if default controller is used (support for custom landing page) * @var boolean */ protected $isDefaultController = false; + /** + * @var RequestHandler + */ + protected $requestHandler; + /** * list of available routes - * @var array<\wcf\system\request\IRoute> + * @var IRoute[] */ protected $routes = array(); @@ -76,39 +92,48 @@ class RouteHandler extends SingletonFactory { protected $routeData = null; /** - * @see \wcf\system\SingletonFactory::init() + * RouteHandler constructor. + * + * @param ApplicationHandler $applicationHandler + * @param EventHandler $eventHandler + */ + public function __construct(ApplicationHandler $applicationHandler, EventHandler $eventHandler) { + $this->applicationHandler = $applicationHandler; + $this->eventHandler = $eventHandler; + + parent::__construct(); + } + + /** + * Sets default routes. */ - protected function init() { - $this->addDefaultRoutes(); + public function setDefaultRoutes() { + /** @var \wcf\system\request\route\DynamicRequestRoute $route */ + $route = WCF::getDIContainer()->make(DynamicRequestRoute::class); + $route->setIsACP(true); + $this->addRoute($route); + + $route = WCF::getDIContainer()->make(DynamicRequestRoute::class); + $route->setIsACP(false); + $this->addRoute($route); // fire event - EventHandler::getInstance()->fireAction($this, 'didInit'); + $this->eventHandler->fireAction($this, 'didInit'); } /** - * Adds default routes. + * Sets the required request handler, setter function to avoid circular dependencies. + * + * @param RequestHandler $requestHandler */ - protected function addDefaultRoutes() { - $acpRoute = new FlexibleRoute(true); - $this->addRoute($acpRoute); - - if (URL_LEGACY_MODE) { - $defaultRoute = new Route('default'); - $defaultRoute->setSchema('/{controller}/{id}'); - $defaultRoute->setParameterOption('controller', null, null, true); - $defaultRoute->setParameterOption('id', null, '\d+', true); - $this->addRoute($defaultRoute); - } - else { - $defaultRoute = new FlexibleRoute(false); - $this->addRoute($defaultRoute); - } + public function setRequestHandler(RequestHandler $requestHandler) { + $this->requestHandler = $requestHandler; } /** * Adds a new route to the beginning of all routes. * - * @param \wcf\system\request\IRoute $route + * @param IRoute $route */ public function addRoute(IRoute $route) { array_unshift($this->routes, $route); @@ -117,7 +142,7 @@ class RouteHandler extends SingletonFactory { /** * Returns all registered routes. * - * @return array<\wcf\system\request\IRoute> + * @return IRoute[] **/ public function getRoutes() { return $this->routes; @@ -132,7 +157,7 @@ class RouteHandler extends SingletonFactory { */ public function matches() { foreach ($this->routes as $route) { - if (RequestHandler::getInstance()->isACPRequest() != $route->isACP()) { + if ($this->requestHandler->isACPRequest() != $route->isACP()) { continue; } @@ -185,9 +210,10 @@ class RouteHandler extends SingletonFactory { * @param array $components * @param boolean $isACP * @return string + * @throws SystemException */ public function buildRoute(array $components, $isACP = null) { - if ($isACP === null) $isACP = RequestHandler::getInstance()->isACPRequest(); + if ($isACP === null) $isACP = $this->requestHandler->isACPRequest(); foreach ($this->routes as $route) { if ($isACP != $route->isACP()) { @@ -282,8 +308,9 @@ class RouteHandler extends SingletonFactory { public static function getPathInfo() { if (self::$pathInfo === null) { self::$pathInfo = ''; + $requestHandler = WCF::getDIContainer()->get(RequestHandler::class); - if (!URL_LEGACY_MODE || RequestHandler::getInstance()->isACPRequest()) { + if (!URL_LEGACY_MODE || $requestHandler->isACPRequest()) { // WCF 2.1: ?Foo/Bar/ if (!empty($_SERVER['QUERY_STRING'])) { // don't use parse_str as it replaces dots with underscores @@ -306,7 +333,7 @@ class RouteHandler extends SingletonFactory { } // WCF 2.0: index.php/Foo/Bar/ - if ((URL_LEGACY_MODE && !RequestHandler::getInstance()->isACPRequest()) || (RequestHandler::getInstance()->isACPRequest() && empty(self::$pathInfo))) { + if ((URL_LEGACY_MODE && !$requestHandler->isACPRequest()) || ($requestHandler->isACPRequest() && empty(self::$pathInfo))) { if (isset($_SERVER['PATH_INFO'])) { self::$pathInfo = $_SERVER['PATH_INFO']; } @@ -358,7 +385,7 @@ class RouteHandler extends SingletonFactory { if ($this->defaultControllers === null) { $this->defaultControllers = array(); - foreach (ApplicationHandler::getInstance()->getApplications() as $application) { + foreach ($this->applicationHandler->getApplications() as $application) { $app = WCF::getApplicationObject($application); if (!$app) { diff --git a/wcfsetup/install/files/lib/system/request/StaticRoute.class.php b/wcfsetup/install/files/lib/system/request/StaticRoute.class.php index d014d840db..1e47a254fc 100644 --- a/wcfsetup/install/files/lib/system/request/StaticRoute.class.php +++ b/wcfsetup/install/files/lib/system/request/StaticRoute.class.php @@ -10,6 +10,7 @@ namespace wcf\system\request; * @package com.woltlab.wcf * @subpackage system.request * @category Community Framework + * @deprecated 2.2:2.3 Consider using \wcf\system\request\route\StaticRequestRoute */ class StaticRoute extends FlexibleRoute { /** diff --git a/wcfsetup/install/files/lib/system/request/route/DynamicRequestRoute.class.php b/wcfsetup/install/files/lib/system/request/route/DynamicRequestRoute.class.php new file mode 100644 index 0000000000..add7f76379 --- /dev/null +++ b/wcfsetup/install/files/lib/system/request/route/DynamicRequestRoute.class.php @@ -0,0 +1,340 @@ + + */ + protected $buildSchema = []; + + /** + * route is restricted to ACP + * @var boolean + */ + protected $isACP = false; + + /** + * @var \wcf\system\menu\page\PageMenu; + */ + protected $pageMenu; + + /** + * pattern for incoming requests + * @var string + */ + protected $pattern = ''; + + /** + * primary application's abbreviation (e.g. "wbb") + * @var string + */ + protected $primaryApplication = ''; + + /** + * @var \wcf\system\request\RequestHandler + */ + protected $requestHandler; + + /** + * list of required components + * @var array + */ + protected $requireComponents = []; + + /** + * parsed request data + * @var array + */ + protected $routeData = []; + + /** + * @var \wcf\system\request\RouteHandler; + */ + protected $routeHandler; + + /** + * DynamicRequestRoute constructor. + * + * @param \wcf\system\application\ApplicationHandler $applicationHandler + * @param \wcf\system\menu\page\PageMenu $pageMenu + * @param \wcf\system\request\RequestHandler $requestHandler + * @param \wcf\system\request\RouteHandler $routeHandler + */ + public function __construct(ApplicationHandler $applicationHandler, PageMenu $pageMenu, RequestHandler $requestHandler, RouteHandler $routeHandler) { + $this->applicationHandler = $applicationHandler; + $this->pageMenu = $pageMenu; + $this->requestHandler = $requestHandler; + $this->routeHandler = $routeHandler; + + $this->init(); + } + + /** + * Sets default routing information. + */ + protected function init() { + $this->setPattern('~ + /? + (?: + (?P[A-Za-z0-9\-]+) + (?: + / + (?P\d+) + (?: + - + (?P[^/]+) + )? + )? + )? + ~x'); + $this->setBuildSchema('/{controller}/{id}-{title}/'); + } + + /** + * {@inheritdoc} + */ + public function setIsACP($isACP) { + $this->isACP = $isACP; + } + + /** + * Sets the build schema used to build outgoing links. + * + * @param string $buildSchema + */ + public function setBuildSchema($buildSchema) { + $this->buildSchema = []; + + $buildSchema = ltrim($buildSchema, '/'); + $components = preg_split('~({(?:[a-z]+)})~', $buildSchema, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + $delimiters = ['/', '-', '.', '_']; + + foreach ($components as $component) { + $type = 'component'; + if (preg_match('~{([a-z]+)}~', $component, $matches)) { + $component = $matches[1]; + } + else { + $type = 'separator'; + } + + $this->buildSchema[] = [ + 'type' => $type, + 'value' => $component + ]; + } + } + + /** + * Sets the route pattern used to evaluate an incoming request. + * + * @param string $pattern + */ + public function setPattern($pattern) { + $this->pattern = $pattern; + } + + /** + * Sets the list of required components. + * + * @param array<string> $requiredComponents + */ + public function setRequiredComponents(array $requiredComponents) { + $this->requireComponents = $requiredComponents; + } + + /** + * @see IRoute::buildLink() + */ + public function buildLink(array $components) { + $application = (isset($components['application'])) ? $components['application'] : null; + + // drop application component to avoid being appended as query string + unset($components['application']); + + // handle default values for controller + $useBuildSchema = true; + if (count($components) == 1 && isset($components['controller'])) { + $ignoreController = false; + + if (!RequestHandler::getInstance()->isACPRequest()) { + $landingPage = PageMenu::getInstance()->getLandingPage(); + if ($this->primaryApplication === '') { + $primaryApplication = ApplicationHandler::getInstance()->getPrimaryApplication(); + $this->primaryApplication = ApplicationHandler::getInstance()->getAbbreviation($primaryApplication->packageID); + } + + // check if this is the default controller + if (strcasecmp(RouteHandler::getInstance()->getDefaultController($application), $components['controller']) === 0) { + // check if this matches the primary application + if ($this->primaryApplication === $application) { + if (strcasecmp($landingPage->getController(), $components['controller']) === 0) { + // skip controller if it matches the default controller + $ignoreController = true; + } + } + else { + // skip default controller + $ignoreController = true; + } + } + else if (strcasecmp($landingPage->getController(), $components['controller']) === 0) { + // landing page + $ignoreController = true; + } + } + + // drops controller from route + if ($ignoreController) { + $useBuildSchema = false; + + // unset the controller, since it would otherwise be added with http_build_query() + unset($components['controller']); + } + } + + return $this->buildRoute($components, $application, $useBuildSchema); + } + + /** + * Builds the actual link, the parameter $useBuildSchema can be set to false for + * empty routes, e.g. for the default page. + * + * @param array $components + * @param string $application + * @param boolean $useBuildSchema + * @return string + */ + protected function buildRoute(array $components, $application, $useBuildSchema) { + $link = ''; + + if ($useBuildSchema) { + $lastSeparator = null; + $skipToLastSeparator = false; + foreach ($this->buildSchema as $component) { + $value = $component['value']; + + if ($component['type'] === 'separator') { + $lastSeparator = $value; + } + else if ($skipToLastSeparator === false) { + // routes are build from left-to-right + if (empty($components[$value])) { + $skipToLastSeparator = true; + + // drop empty components to avoid them being appended as query string argument + unset($components[$value]); + + continue; + } + + if ($lastSeparator !== null) { + $link .= $lastSeparator; + $lastSeparator = null; + } + + // handle controller names + if ($value === 'controller') { + $components[$value] = $this->getControllerName($application, $components[$value]); + } + + $link .= $components[$value]; + unset($components[$value]); + } + } + + if (!empty($link) && $lastSeparator !== null) { + $link .= $lastSeparator; + } + } + + if ($this->isACP || !URL_OMIT_INDEX_PHP) { + if (!empty($link)) { + $link = 'index.php?' . $link; + } + } + + if (!empty($components)) { + if (strpos($link, '?') === false) $link .= '?'; + else $link .= '&'; + + $link .= http_build_query($components, '', '&'); + } + + return $link; + } + + /** + * @see IRoute::canHandle() + */ + public function canHandle(array $components) { + if (!empty($this->requireComponents)) { + foreach ($this->requireComponents as $component => $pattern) { + if (empty($components[$component])) { + return false; + } + + if ($pattern && !preg_match($pattern, $components[$component])) { + return false; + } + } + } + + return true; + } + + /** + * @see IRoute::getRouteData() + */ + public function getRouteData() { + return $this->routeData; + } + + /** + * @see IRoute::isACP() + */ + public function isACP() { + return $this->isACP; + } + + /** + * @see IRoute::matches() + */ + public function matches($requestURL) { + if (preg_match($this->pattern, $requestURL, $matches)) { + foreach ($matches as $key => $value) { + if (!is_numeric($key)) { + $this->routeData[$key] = $value; + } + } + + $this->routeData['isDefaultController'] = (!isset($this->routeData['controller'])); + + return true; + } + + return false; + } + + /** + * Returns the transformed controller name. + * + * @param string $application + * @param string $controller + * @return string + */ + protected function getControllerName($application, $controller) { + return RequestHandler::getInstance()->getControllerMap()->lookup($controller); + } +} diff --git a/wcfsetup/install/files/lib/system/request/route/IRequestRoute.class.php b/wcfsetup/install/files/lib/system/request/route/IRequestRoute.class.php new file mode 100644 index 0000000000..d1a37eb487 --- /dev/null +++ b/wcfsetup/install/files/lib/system/request/route/IRequestRoute.class.php @@ -0,0 +1,22 @@ +<?php +namespace wcf\system\request\route; +use wcf\system\request\IRoute; + +/** + * Default interface for route implementations. + * + * @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 system.request + * @category Community Framework + */ +interface IRequestRoute extends IRoute { + /** + * Configures this route to handle either ACP or frontend requests. + * + * @param boolean $isACP true if route handles ACP requests + */ + public function setIsACP($isACP); +} diff --git a/wcfsetup/install/files/lib/system/request/route/StaticRequestRoute.class.php b/wcfsetup/install/files/lib/system/request/route/StaticRequestRoute.class.php new file mode 100644 index 0000000000..3b16940da4 --- /dev/null +++ b/wcfsetup/install/files/lib/system/request/route/StaticRequestRoute.class.php @@ -0,0 +1,111 @@ +<?php +namespace wcf\system\request\route; +use wcf\system\application\ApplicationHandler; +use wcf\system\menu\page\PageMenu; +use wcf\system\request\ControllerMap; +use wcf\system\request\RequestHandler; +use wcf\system\request\RouteHandler; + +/** + * Static route implementation to resolve HTTP requests, handling a single controller. + * + * @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 system.request + * @category Community Framework + */ +class StaticRequestRoute extends DynamicRequestRoute { + /** + * @var \wcf\system\request\ControllerMap + */ + protected $controllerMap; + + /** + * static application identifier + * @var string + */ + protected $staticApplication = ''; + + /** + * static controller name, not the FQN + * @var string + */ + protected $staticController = ''; + + /** + * StaticRequestRoute constructor. + * + * @param \wcf\system\application\ApplicationHandler $applicationHandler + * @param \wcf\system\request\ControllerMap $controllerMap + * @param \wcf\system\menu\page\PageMenu $pageMenu + * @param \wcf\system\request\RequestHandler $requestHandler + * @param \wcf\system\request\RouteHandler $routeHandler + */ + public function __construct(ApplicationHandler $applicationHandler, ControllerMap $controllerMap, PageMenu $pageMenu, RequestHandler $requestHandler, RouteHandler $routeHandler) { + parent::__construct($applicationHandler, $pageMenu, $requestHandler, $routeHandler); + + $this->controllerMap = $controllerMap; + } + + /** + * @see \wcf\system\request\route\IRequestRoute::setIsACP() + */ + public function setIsACP($isACP) { + // static routes are disallowed for ACP + parent::setIsACP(false); + } + + /** + * Sets the static controller for this route. + * + * @param string $application + * @param string $controller + */ + public function setStaticController($application, $controller) { + $this->staticApplication = $application; + $this->staticController = $controller; + + $this->requireComponents['controller'] = '~^' . $this->staticController . '$~'; + } + + /** + * @see \wcf\system\request\IRoute::buildLink() + */ + public function buildLink(array $components) { + // static routes don't have these components + unset($components['application']); + unset($components['controller']); + + return $this->buildRoute($components, '', true); + } + + /** + * @see \wcf\system\request\IRoute::canHandle() + */ + public function canHandle(array $components) { + if (isset($components['application']) && $components['application'] == $this->staticApplication) { + if (isset($components['controller']) && $components['controller'] == $this->staticController) { + return parent::canHandle($components); + } + } + + return false; + } + + /** + * @see \wcf\system\request\IRoute::matches() + */ + public function matches($requestURL) { + if (parent::matches($requestURL)) { + $this->routeData['application'] = $this->staticApplication; + $this->routeData['controller'] = $this->controllerMap->lookup($this->staticController); + $this->routeData['isDefaultController'] = false; + + return true; + } + + return false; + } +} -- 2.20.1