diff --git a/code/site/components/com_pages/controller/behavior/breadcrumbable.php b/code/site/components/com_pages/controller/behavior/breadcrumbable.php index e42b50186..c52011a32 100644 --- a/code/site/components/com_pages/controller/behavior/breadcrumbable.php +++ b/code/site/components/com_pages/controller/behavior/breadcrumbable.php @@ -27,7 +27,7 @@ protected function _beforeRender(KControllerContextInterface $context) { $segments[] = $segment; - if($route = $router->generate('pages:'.implode('/', $segments))) + if($route = $router->generate('page:'.implode('/', $segments))) { $page = $route->getPage(); diff --git a/code/site/components/com_pages/dispatcher/behavior/redirectable.php b/code/site/components/com_pages/dispatcher/behavior/redirectable.php index ac04a56a2..f30f9d292 100644 --- a/code/site/components/com_pages/dispatcher/behavior/redirectable.php +++ b/code/site/components/com_pages/dispatcher/behavior/redirectable.php @@ -18,34 +18,6 @@ protected function _initialize(KObjectConfig $config) parent::_initialize($config); } - protected function _beforeDispatch(KDispatcherContextInterface $context) - { - $router = $this->getObject('com://site/pages.dispatcher.router.redirect', ['request' => $context->request]); - - if(false !== $route = $router->resolve()) - { - if($route->toString(KHttpUrl::AUTHORITY)) - { - //External redierct: 301 permanent - $status = KHttpResponse::MOVED_PERMANENTLY; - } - else - { - //Internal redirect: 307 temporary - $status = KHttpResponse::TEMPORARY_REDIRECT; - } - - //Qualify the route - $url = $router->qualify($route); - - //Set the location header - $context->getResponse()->getHeaders()->set('Location', $url); - $context->getResponse()->setStatus($status); - - $context->getSubject()->send(); - } - } - protected function _beforeSend(KDispatcherContextInterface $context) { $response = $context->response; diff --git a/code/site/components/com_pages/dispatcher/http.php b/code/site/components/com_pages/dispatcher/http.php index c14cfe4d3..5f2b9ee36 100644 --- a/code/site/components/com_pages/dispatcher/http.php +++ b/code/site/components/com_pages/dispatcher/http.php @@ -72,7 +72,7 @@ public function getRoute() $url = urldecode($this->getRequest()->getUrl()->getPath()); $path = trim(str_replace(array($base, '/index.php'), '', $url), '/'); - $this->__route = $this->getRouter()->resolve('pages:'.$path, $this->getRequest()->query->toArray()); + $this->__route = $this->getRouter()->resolve('page:'.$path, $this->getRequest()->query->toArray()); } if(is_object($this->__route)) { diff --git a/code/site/components/com_pages/dispatcher/router/file.php b/code/site/components/com_pages/dispatcher/router/file.php new file mode 100644 index 000000000..e254905f4 --- /dev/null +++ b/code/site/components/com_pages/dispatcher/router/file.php @@ -0,0 +1,60 @@ + + * @link https://github.com/joomlatools/joomlatools-pages for the canonical source repository + */ + +class ComPagesDispatcherRouterFile extends ComPagesDispatcherRouterAbstract +{ + protected function _initialize(KObjectConfig $config) + { + $config->append([ + 'route' => 'file', + 'routes' => [] + ])->append([ + 'resolvers' => [ + 'regex' => ['routes' => $config->routes] + ] + ]); + + parent::_initialize($config); + } + + public function resolve($route = null, array $parameters = array()) + { + $result = false; + if (count($this->getConfig()->routes)) + { + if (!$route) + { + $base = $this->getRequest()->getBasePath(); + $url = urldecode($this->getRequest()->getUrl()->getPath()); + $parameters = $this->getRequest()->getUrl()->getQuery(true); + + $route = trim(str_replace(array($base, '/index.php'), '', $url), '/'); + } + + $result = parent::resolve($route, $parameters); + } + + return $result; + } + + public function qualify(ComPagesDispatcherRouterRouteInterface $route, $replace = false) + { + $url = clone $route; + + if(!$url->isAbsolute()) + { + $base = $this->getRequest()->getBasePath(true); + $path = trim($url->getPath(), '/'); + + $url->setPath($base . '/' . $path); + } + + return $url; + } +} \ No newline at end of file diff --git a/code/site/components/com_pages/dispatcher/router/pages.php b/code/site/components/com_pages/dispatcher/router/pages.php index e4f038bb5..88f0d32be 100644 --- a/code/site/components/com_pages/dispatcher/router/pages.php +++ b/code/site/components/com_pages/dispatcher/router/pages.php @@ -27,7 +27,7 @@ protected function _initialize(KObjectConfig $config) public function getRoute($route, array $parameters = array()) { if($route instanceof ComPagesModelEntityPage) { - $route = 'pages:'.$route->path; + $route = 'page:'.$route->path; } return parent::getRoute($route, $parameters); diff --git a/code/site/components/com_pages/dispatcher/router/redirect.php b/code/site/components/com_pages/dispatcher/router/redirect.php index 51a752bca..0e1e36428 100644 --- a/code/site/components/com_pages/dispatcher/router/redirect.php +++ b/code/site/components/com_pages/dispatcher/router/redirect.php @@ -24,14 +24,21 @@ protected function _initialize(KObjectConfig $config) public function resolve($route = null, array $parameters = array()) { - if(!$route) + $result = false; + if(count($this->getConfig()->routes)) { - $base = $this->getRequest()->getBasePath(); - $url = urldecode( $this->getRequest()->getUrl()->getPath()); + if(!$route) + { + $base = $this->getRequest()->getBasePath(); + $url = urldecode( $this->getRequest()->getUrl()->getPath()); + $parameters = $this->getRequest()->getUrl()->getQuery(true); - $route = trim(str_replace(array($base, '/index.php'), '', $url), '/'); + $route = trim(str_replace(array($base, '/index.php'), '', $url), '/'); + } + + $result = parent::resolve($route, $parameters); } - return parent::resolve($route, $parameters); + return $result; } } \ No newline at end of file diff --git a/code/site/components/com_pages/dispatcher/router/resolver/regex.php b/code/site/components/com_pages/dispatcher/router/resolver/regex.php index 0fb7060b0..2e0575865 100644 --- a/code/site/components/com_pages/dispatcher/router/resolver/regex.php +++ b/code/site/components/com_pages/dispatcher/router/resolver/regex.php @@ -90,18 +90,21 @@ protected function _initialize(KObjectConfig $config) * Add a route for matching * * @param string $regex The route regex You can use multiple pre-set regex filters, like [digit:id] - * @param string $path The path this route should point to. + * @param string|callable $target The target this route points to * @return ComPagesDispatcherRouterResolverInterface */ - public function addRoute($regex, $path) + public function addRoute($regex, $target) { $regex = trim($regex, '/'); - $path = rtrim($path, '/'); + + if(is_string($target)) { + $path = rtrim($target, '/'); + } if(strpos($regex, '[') !== false) { - $this->__dynamic_routes[$regex] = $path; + $this->__dynamic_routes[$regex] = $target; } else { - $this->__static_routes[$regex] = $path; + $this->__static_routes[$regex] = $target; } return $this; @@ -115,16 +118,8 @@ public function addRoute($regex, $path) */ public function addRoutes($routes) { - foreach((array)KObjectConfig::unbox($routes) as $path => $routes) - { - foreach((array) $routes as $regex) - { - if (is_numeric($path)) { - $this->addRoute($regex, $regex); - } else { - $this->addRoute($regex, $path); - } - } + foreach((array)KObjectConfig::unbox($routes) as $regex => $target) { + $this->addRoute($regex, $target); } return $this; @@ -173,8 +168,13 @@ public function resolve(ComPagesDispatcherRouterRouteInterface $route) $this->__static_routes = array($path => $result) + $this->__static_routes; } - if($result !== false) { - $this->_buildRoute($result, $route); + if($result !== false) + { + if(isset($result['resolve']) && is_callable($result['resolve'])) { + $result = (bool) call_user_func($result['resolve'], $route); + } else { + $result = $this->_buildRoute($result, $route); + } } return $result !== false ? parent::resolve($route) : false; @@ -194,12 +194,21 @@ public function generate(ComPagesDispatcherRouterRouteInterface $route) $path = ltrim($route->getPath(), '/'); //Dynamic routes - if($routes = array_keys($this->__dynamic_routes, $path)) + $routes = $this->__dynamic_routes; + + foreach($routes as $regex => $target) { - foreach($routes as $regex) + if(isset($target['generate']) && is_callable($target['generate'])) { - //Generate the dynamic route - if($this->_buildRoute($regex, $route)) { + //Parse the route to match it + if($this->_parseRoute($regex, $route) && (bool) call_user_func($target['generate'], $route) == true) { + $generated = true; break; + } + } + else + { + //Parse the route to match it + if($this->_parseRoute($regex, $route) && $this->_buildRoute($target, $route)) { $generated = true; break; } } @@ -208,12 +217,23 @@ public function generate(ComPagesDispatcherRouterRouteInterface $route) //Static routes if(!$generated) { - $routes = array_flip(array_reverse($this->__static_routes, true)); + $routes = array_reverse($this->__static_routes, true); - if(isset($routes[$path])) + foreach($routes as $regex => $target) { - if($this->_buildRoute($routes[$path], $route)) { - $generated = true; + if(isset($target['generate']) && is_callable($target['generate'])) + { + //Compare the path to match it + if($regex == $path && (bool) call_user_func($target['generate'], $route) == true) { + $generated = true; break; + } + } + else + { + //Compare the path to match it + if($target == $path && $this->_buildRoute($regex, $route)) { + $generated = true; break; + } } } } @@ -338,7 +358,7 @@ protected function _buildRoute($regex, ComPagesDispatcherRouterRouteInterface $r if($optional) { $regex = str_replace($pre . $block, '', $regex); } else { - $result = false; break; + $result = false; break; } } } @@ -353,10 +373,10 @@ protected function _buildRoute($regex, ComPagesDispatcherRouterRouteInterface $r } if(strpos($regex, '://') === false) { - $route->setPath('/'.ltrim($regex, '/')); - } else { - $route->setUrl($regex); + $regex = '/' . ltrim($regex, '/'); } + + $route->setUrl($regex); } return $result; diff --git a/code/site/components/com_pages/dispatcher/router/route/file.php b/code/site/components/com_pages/dispatcher/router/route/file.php new file mode 100644 index 000000000..a7ecb8cf5 --- /dev/null +++ b/code/site/components/com_pages/dispatcher/router/route/file.php @@ -0,0 +1,22 @@ + + * @link https://github.com/joomlatools/joomlatools-pages for the canonical source repository + */ + +/** + * Router File Route + * + * @author Johan Janssens + * @package Koowa\Library\Dispatcher\Router\Route + */ +class ComPagesDispatcherRouterRouteFile extends ComPagesDispatcherRouterRouteAbstract +{ + public function isAbsolute() + { + return file_exists($this->getPath()); + } +} \ No newline at end of file diff --git a/code/site/components/com_pages/dispatcher/router/router.php b/code/site/components/com_pages/dispatcher/router/router.php index 1eabf46a9..96c0df12a 100644 --- a/code/site/components/com_pages/dispatcher/router/router.php +++ b/code/site/components/com_pages/dispatcher/router/router.php @@ -30,6 +30,29 @@ public function __construct(KObjectConfig $config) //Add a global object alias $this->getObject('manager')->registerAlias($this->getIdentifier(), 'router'); + + $this->__routers = KObjectConfig::unbox($config->routers); + } + + /** + * Initializes the options for the object + * + * Called from {@link __construct()} as a first step of object instantiation. + * + * @param KObjectConfig $config An optional ObjectConfig object with configuration options. + * @return void + */ + protected function _initialize(KObjectConfig $config) + { + $config->append(array( + 'routers' => [ + 'page' => 'com://site/pages.dispatcher.router.pages', + 'site' => 'com://site/pages.dispatcher.router.site', + 'redirect' => 'com://site/pages.dispatcher.router.redirect', + ], + )); + + parent::_initialize($config); } /** @@ -43,24 +66,38 @@ public function __construct(KObjectConfig $config) */ public function resolve($route, array $parameters = array()) { - $result = false; + $identifier = null; - //Find router package + //Find router identifier if($route instanceof KObjectInterface) { - if($route instanceof ComPagesDispatcherRouterRouteInterface) { + if($route instanceof ComPagesDispatcherRouterRouteInterface) + { $package = $route->getScheme(); - } else { - $package = $route->getIdentifier()->getPackage(); + + if(isset($this->__routers[$package])) { + $identifier = $this->__routers[$package]; + } + } + else $package = $route->getIdentifier()->getPackage(); + } + else + { + $package = parse_url($route, PHP_URL_SCHEME); + + if(isset($this->__routers[$package])) { + $identifier = $this->__routers[$package]; } } - else $package = parse_url($route, PHP_URL_SCHEME); + + //Identifier Fallback + if(!$identifier) { + $identifier = 'com://site/' . $package . '.dispatcher.router.' . $package; + } //Get router instance - if(!isset($this->__routers[$package])) + if(is_string($identifier)) { - $identifier = 'com://site/'.$package.'.dispatcher.router.'.$package; - $config = [ 'request' => $this->getRequest(), 'resolvers' => $this->getResolvers() @@ -70,7 +107,7 @@ public function resolve($route, array $parameters = array()) $this->__routers[$package] = $router; } - else $router = $this->__routers[$package]; + else $router = $identifier; return $router->resolve($route, $parameters); } @@ -86,24 +123,38 @@ public function resolve($route, array $parameters = array()) */ public function generate($route, array $parameters = array()) { - $result = false; + $identifier = null; - //Find router package + //Find router identifier if($route instanceof KObjectInterface) { - if($route instanceof ComPagesDispatcherRouterRouteInterface) { + if($route instanceof ComPagesDispatcherRouterRouteInterface) + { $package = $route->getScheme(); - } else { - $package = $route->getIdentifier()->getPackage(); + + if(isset($this->__routers[$package])) { + $identifier = $this->__routers[$package]; + } + } + else $package = $route->getIdentifier()->getPackage(); + } + else + { + $package = parse_url($route, PHP_URL_SCHEME); + + if(isset($this->__routers[$package])) { + $identifier = $this->__routers[$package]; } } - else $package = parse_url($route, PHP_URL_SCHEME); + + //Identifier Fallback + if(!$identifier) { + $identifier = 'com://site/' . $package . '.dispatcher.router.' . $package; + } //Get router instance - if(!isset($this->__routers[$package])) + if(is_string($identifier)) { - $identifier = 'com://site/'.$package.'.dispatcher.router.'.$package; - $config = [ 'request' => $this->getRequest(), 'resolvers' => $this->getResolvers() @@ -113,7 +164,7 @@ public function generate($route, array $parameters = array()) $this->__routers[$package] = $router; } - else $router = $this->__routers[$package]; + else $router = $identifier; return $router->generate($route, $parameters); } diff --git a/code/site/components/com_pages/event/subscriber/downloader.php b/code/site/components/com_pages/event/subscriber/downloader.php new file mode 100644 index 000000000..6dce7c016 --- /dev/null +++ b/code/site/components/com_pages/event/subscriber/downloader.php @@ -0,0 +1,67 @@ + + * @link https://github.com/joomlatools/joomlatools-pages for the canonical source repository + */ + +class ComPagesEventSubscriberDownloader extends ComPagesEventSubscriberAbstract +{ + protected function _initialize(KObjectConfig $config) + { + $config->append(array( + 'priority' => KEvent::PRIORITY_HIGH, + )); + + parent::_initialize($config); + } + + public function onAfterApplicationRoute(KEventInterface $event) + { + $request = $this->getObject('request'); + $router = $this->getObject('com://site/pages.dispatcher.router.file', ['request' => $request]); + + if(false !== $route = $router->resolve()) + { + //Qualify the route + $route = $router->qualify($route); + + //Get the file path + $path = $route->getPath(); + + if(isset($route->query['force-download'])) { + $request->query->set('force-download', true); + } + + //Set the location header + $dispatcher = $this->getObject('com://site/pages.dispatcher.http'); + + try + { + $response = $dispatcher->getResponse(); + + //Attach a different transport [stream or sendfile] + if(isset($route->query['transport'])) + { + $transport = $route->query['transport']; + + //Enable using the header + if($transport == 'sendfile') { + $response->getHeaders()->set('X-Sendfile', 1); + } + + $response->attachTransport($transport); + } + + $response->setContent($path, @mime_content_type($path) ?? 'application/octet-stream'); + + } catch (InvalidArgumentException $e) { + throw new KControllerExceptionResourceNotFound('File not found'); + } + + $dispatcher->send(); + } + } +} \ No newline at end of file diff --git a/code/site/components/com_pages/event/subscriber/filedownloader.php b/code/site/components/com_pages/event/subscriber/filedownloader.php new file mode 100644 index 000000000..51d68f07d --- /dev/null +++ b/code/site/components/com_pages/event/subscriber/filedownloader.php @@ -0,0 +1,76 @@ + + * @link https://github.com/joomlatools/joomlatools-pages for the canonical source repository + */ + +class ComPagesEventSubscriberFiledownloader extends ComPagesEventSubscriberAbstract +{ + protected function _initialize(KObjectConfig $config) + { + $config->append(array( + 'priority' => KEvent::PRIORITY_HIGH, + )); + + parent::_initialize($config); + } + + public function onAfterApplicationRoute(KEventInterface $event) + { + $request = $this->getObject('request'); + $router = $this->getObject('com://site/pages.dispatcher.router.file', ['request' => $request]); + + if(false !== $route = $router->resolve()) + { + //Set the location header + $dispatcher = $this->getObject('com://site/pages.dispatcher.http'); + $response = $dispatcher->getResponse(); + + //Force download the file + if(isset($route->query['force-download'])) + { + $request->query->set('force-download', true); + unset($route->query['force-download']); + } + + //Attach a different transport [stream or sendfile] + if(isset($route->query['transport'])) + { + $transport = $route->query['transport']; + + //Enable using the header + if($transport == 'sendfile') { + $response->getHeaders()->set('X-Sendfile', 1); + } + + $response->attachTransport($transport); + + unset($route->query['transport']); + } + + //Set the cache time + if(isset($route->query['cache'])) + { + $response->setMaxAge($route->query['cache']); + unset($route->query['cache']); + } + + //Qualify the route + $route = $router->qualify($route); + + //Get the file path + $path = $route->getPath(); + + try { + $response->setContent($path, @mime_content_type($path) ?? 'application/octet-stream'); + } catch (InvalidArgumentException $e) { + throw new KControllerExceptionResourceNotFound('File not found'); + } + + $dispatcher->send(); + } + } +} diff --git a/code/site/components/com_pages/event/subscriber/redirector.php b/code/site/components/com_pages/event/subscriber/redirector.php new file mode 100644 index 000000000..5381580f8 --- /dev/null +++ b/code/site/components/com_pages/event/subscriber/redirector.php @@ -0,0 +1,50 @@ + + * @link https://github.com/joomlatools/joomlatools-pages for the canonical source repository + */ + +class ComPagesEventSubscriberRedirector extends ComPagesEventSubscriberAbstract +{ + protected function _initialize(KObjectConfig $config) + { + $config->append(array( + 'priority' => KEvent::PRIORITY_HIGH, + )); + + parent::_initialize($config); + } + + public function onAfterApplicationRoute(KEventInterface $event) + { + $request = $this->getObject('request'); + $router = $this->getObject('com://site/pages.dispatcher.router.redirect', ['request' => $request]); + + if(false !== $route = $router->resolve()) + { + if($route->toString(KHttpUrl::AUTHORITY)) + { + //External redierct: 301 permanent + $status = KHttpResponse::MOVED_PERMANENTLY; + } + else + { + //Internal redirect: 307 temporary + $status = KHttpResponse::TEMPORARY_REDIRECT; + } + + //Qualify the route + $url = $router->qualify($route); + + //Set the location header + $dispatcher = $this->getObject('com://site/pages.dispatcher.http'); + $dispatcher->getResponse()->getHeaders()->set('Location', $url); + $dispatcher->getResponse()->setStatus($status); + + $dispatcher->send(); + } + } +} \ No newline at end of file diff --git a/code/site/components/com_pages/page/registry.php b/code/site/components/com_pages/page/registry.php index 16414784f..2a1f00cc1 100755 --- a/code/site/components/com_pages/page/registry.php +++ b/code/site/components/com_pages/page/registry.php @@ -239,10 +239,29 @@ public function getPageContent($path, $render = false) public function getRoutes($path = null) { - if(!is_null($path)) { - $result = $this->__data['routes'][$path]; - } else { - $result = $this->__data['routes']; + $result = array(); + if(is_null($path)) + { + foreach( $this->__data['routes'] as $path => $routes) + { + foreach((array) $routes as $regex) + { + if (is_numeric($path)) { + $result[$regex] = $regex; + } else { + $result[$regex] = $path; + } + } + } + } + else + { + if(isset($this->__data['routes'][$path])) + { + foreach((array) $this->__data['routes'][$path] as $regex) { + $result[$regex] = $path; + } + } } return $result; @@ -458,7 +477,7 @@ public function loadCache($basedir, $refresh = true) $result['pages'] = $pages; $result['routes'] = $routes; $result['collections'] = $collections; - $result['redirects'] = array_flip($redirects); + $result['redirects'] = $redirects; //Generate a checksum $result['hash'] = hash('crc32b', serialize($result)); diff --git a/code/site/components/com_pages/resources/config/bootstrapper.php b/code/site/components/com_pages/resources/config/bootstrapper.php index 0eab3b46b..d705030a5 100644 --- a/code/site/components/com_pages/resources/config/bootstrapper.php +++ b/code/site/components/com_pages/resources/config/bootstrapper.php @@ -63,6 +63,8 @@ 'event.subscriber.factory' => [ 'subscribers' => [ 'com://site/pages.event.subscriber.bootstrapper', + 'com://site/pages.event.subscriber.redirector', + 'com://site/pages.event.subscriber.filedownloader', 'com://site/pages.event.subscriber.dispatcher', 'com://site/pages.event.subscriber.pagedecorator', 'com://site/pages.event.subscriber.errorhandler', @@ -82,7 +84,7 @@ } ], 'com://site/pages.dispatcher.router.site' => [ - 'routes' => isset($config['sites']) ? array_flip($config['sites']) : array(JPATH_ROOT.'/joomlatools-pages' => '[*]'), + 'routes' => isset($config['sites']) ? $config['sites'] : array('[*]' => JPATH_ROOT.'/joomlatools-pages'), ], ] ]; \ No newline at end of file diff --git a/code/site/components/com_pages/resources/config/site.php b/code/site/components/com_pages/resources/config/site.php index 27375e7be..2e8b4f510 100644 --- a/code/site/components/com_pages/resources/config/site.php +++ b/code/site/components/com_pages/resources/config/site.php @@ -19,7 +19,7 @@ 'cache_path' => $config['page_cache_path'], 'cache_validation' => $config['page_cache_validation'], 'collections' => $config['collections'], - 'redirects' => array_flip($config['redirects']), + 'redirects' => $config['redirects'], 'properties' => $config['page'], ], 'data.registry' => [ @@ -48,9 +48,9 @@ 'cache_force' => $config['http_resource_cache_force'], 'debug' => $config['http_resource_cache_debug'], ], - 'com://site/pages.model.cache' => [ - 'cache_path' => $config['http_cache_path'], - ] + 'com://site/pages.dispatcher.router.file' => [ + 'routes' => isset($config['files']) ? $config['files'] : array(), + ], ], 'extensions' => $config['extensions'] ?? array(), ]; \ No newline at end of file