diff options
author | Frederic Guillot <fred@kanboard.net> | 2015-12-22 19:06:03 +0100 |
---|---|---|
committer | Frederic Guillot <fred@kanboard.net> | 2015-12-22 19:06:03 +0100 |
commit | 6f9af3659c9146a2ac1b08d70610bf96398ec073 (patch) | |
tree | 21cbbca979a30d133d421881d6aa6e6af8096199 | |
parent | c83f589b22cd548c6de10bfb0c18f767ba7dffd8 (diff) |
Added the possiblity to define custom routes from plugins
-rw-r--r-- | ChangeLog | 3 | ||||
-rw-r--r-- | app/Core/Base.php | 1 | ||||
-rw-r--r-- | app/Core/Http/Request.php | 49 | ||||
-rw-r--r-- | app/Core/Http/Route.php | 188 | ||||
-rw-r--r-- | app/Core/Http/Router.php | 199 | ||||
-rw-r--r-- | app/Helper/App.php | 15 | ||||
-rw-r--r-- | app/Helper/Url.php | 14 | ||||
-rw-r--r-- | app/ServiceProvider/RouteProvider.php | 268 | ||||
-rw-r--r-- | doc/plugin-routes.markdown | 85 | ||||
-rw-r--r-- | doc/plugins.markdown | 1 | ||||
-rw-r--r-- | index.php | 2 | ||||
-rw-r--r-- | tests/units/Core/Http/RouteTest.php | 79 | ||||
-rw-r--r-- | tests/units/Core/Http/RouterTest.php | 250 | ||||
-rw-r--r-- | tests/units/Helper/UrlHelperTest.php | 77 |
14 files changed, 869 insertions, 362 deletions
@@ -3,7 +3,8 @@ Version 1.0.23 (unreleased) New features: -- Add report to compare working hours between open and closed tasks +- Added report to compare working hours between open and closed tasks +- Added the possiblity to define custom routes from plugins Bug fixes: diff --git a/app/Core/Base.php b/app/Core/Base.php index 2d00e52a..a4cf787a 100644 --- a/app/Core/Base.php +++ b/app/Core/Base.php @@ -18,6 +18,7 @@ use Pimple\Container; * @property \Kanboard\Core\Http\Request $request * @property \Kanboard\Core\Http\Response $response * @property \Kanboard\Core\Http\Router $router + * @property \Kanboard\Core\Http\Route $route * @property \Kanboard\Core\Mail\Client $emailClient * @property \Kanboard\Core\ObjectStorage\ObjectStorageInterface $objectStorage * @property \Kanboard\Core\Plugin\Hook $hook diff --git a/app/Core/Http/Request.php b/app/Core/Http/Request.php index c626f5b2..da95f2d4 100644 --- a/app/Core/Http/Request.php +++ b/app/Core/Http/Request.php @@ -42,6 +42,16 @@ class Request extends Base } /** + * Set GET parameters + * + * @param array $params + */ + public function setParams(array $params) + { + $this->get = array_merge($this->get, $params); + } + + /** * Get query string string parameter * * @access public @@ -147,6 +157,17 @@ class Request extends Base } /** + * Return HTTP method + * + * @access public + * @return bool + */ + public function getMethod() + { + return $this->getServerVariable('REQUEST_METHOD'); + } + + /** * Return true if the HTTP request is sent with the POST method * * @access public @@ -154,7 +175,7 @@ class Request extends Base */ public function isPost() { - return isset($this->server['REQUEST_METHOD']) && $this->server['REQUEST_METHOD'] === 'POST'; + return $this->getServerVariable('REQUEST_METHOD') === 'POST'; } /** @@ -203,7 +224,7 @@ class Request extends Base public function getHeader($name) { $name = 'HTTP_'.str_replace('-', '_', strtoupper($name)); - return isset($this->server[$name]) ? $this->server[$name] : ''; + return $this->getServerVariable($name); } /** @@ -214,18 +235,18 @@ class Request extends Base */ public function getRemoteUser() { - return isset($this->server[REVERSE_PROXY_USER_HEADER]) ? $this->server[REVERSE_PROXY_USER_HEADER] : ''; + return $this->getServerVariable(REVERSE_PROXY_USER_HEADER); } /** - * Returns current request's query string, useful for redirecting + * Returns query string * * @access public * @return string */ public function getQueryString() { - return isset($this->server['QUERY_STRING']) ? $this->server['QUERY_STRING'] : ''; + return $this->getServerVariable('QUERY_STRING'); } /** @@ -236,7 +257,7 @@ class Request extends Base */ public function getUri() { - return isset($this->server['REQUEST_URI']) ? $this->server['REQUEST_URI'] : ''; + return $this->getServerVariable('REQUEST_URI'); } /** @@ -269,7 +290,7 @@ class Request extends Base ); foreach ($keys as $key) { - if (! empty($this->server[$key])) { + if ($this->getServerVariable($key) !== '') { foreach (explode(',', $this->server[$key]) as $ipAddress) { return trim($ipAddress); } @@ -287,6 +308,18 @@ class Request extends Base */ public function getStartTime() { - return isset($this->server['REQUEST_TIME_FLOAT']) ? $this->server['REQUEST_TIME_FLOAT'] : 0; + return $this->getServerVariable('REQUEST_TIME_FLOAT') ?: 0; + } + + /** + * Get server variable + * + * @access public + * @param string $variable + * @return string + */ + public function getServerVariable($variable) + { + return isset($this->server[$variable]) ? $this->server[$variable] : ''; } } diff --git a/app/Core/Http/Route.php b/app/Core/Http/Route.php new file mode 100644 index 00000000..ed831467 --- /dev/null +++ b/app/Core/Http/Route.php @@ -0,0 +1,188 @@ +<?php + +namespace Kanboard\Core\Http; + +use RuntimeException; +use Kanboard\Core\Base; + +/** + * Route Handler + * + * @package http + * @author Frederic Guillot + */ +class Route extends Base +{ + /** + * Flag that enable the routing table + * + * @access private + * @var boolean + */ + private $activated = false; + + /** + * Store routes for path lookup + * + * @access private + * @var array + */ + private $paths = array(); + + /** + * Store routes for url lookup + * + * @access private + * @var array + */ + private $urls = array(); + + /** + * Enable routing table + * + * @access public + * @return Route + */ + public function enable() + { + $this->activated = true; + return $this; + } + + /** + * Add route + * + * @access public + * @param string $path + * @param string $controller + * @param string $action + * @param string $plugin + * @return Route + */ + public function addRoute($path, $controller, $action, $plugin = '') + { + if ($this->activated) { + $path = ltrim($path, '/'); + $items = explode('/', $path); + $params = $this->findParams($items); + + $this->paths[] = array( + 'items' => $items, + 'count' => count($items), + 'controller' => $controller, + 'action' => $action, + 'plugin' => $plugin, + ); + + $this->urls[$plugin][$controller][$action][] = array( + 'path' => $path, + 'params' => $params, + 'count' => count($params), + ); + } + + return $this; + } + + /** + * Find a route according to the given path + * + * @access public + * @param string $path + * @return array + */ + public function findRoute($path) + { + $items = explode('/', ltrim($path, '/')); + $count = count($items); + + foreach ($this->paths as $route) { + if ($count === $route['count']) { + $params = array(); + + for ($i = 0; $i < $count; $i++) { + if ($route['items'][$i]{0} === ':') { + $params[substr($route['items'][$i], 1)] = $items[$i]; + } elseif ($route['items'][$i] !== $items[$i]) { + break; + } + } + + if ($i === $count) { + $this->request->setParams($params); + return array( + 'controller' => $route['controller'], + 'action' => $route['action'], + 'plugin' => $route['plugin'], + ); + } + } + } + + return array( + 'controller' => 'app', + 'action' => 'index', + 'plugin' => '', + ); + } + + /** + * Find route url + * + * @access public + * @param string $controller + * @param string $action + * @param array $params + * @param string $plugin + * @return string + */ + public function findUrl($controller, $action, array $params = array(), $plugin = '') + { + if ($plugin === '' && isset($params['plugin'])) { + $plugin = $params['plugin']; + unset($params['plugin']); + } + + if (! isset($this->urls[$plugin][$controller][$action])) { + return ''; + } + + foreach ($this->urls[$plugin][$controller][$action] as $route) { + if (array_diff_key($params, $route['params']) === array()) { + $url = $route['path']; + $i = 0; + + foreach ($params as $variable => $value) { + $url = str_replace(':'.$variable, $value, $url); + $i++; + } + + if ($i === $route['count']) { + return $url; + } + } + } + + return ''; + } + + /** + * Find url params + * + * @access public + * @param array $items + * @return array + */ + public function findParams(array $items) + { + $params = array(); + + foreach ($items as $item) { + if ($item !== '' && $item{0} === ':') { + $params[substr($item, 1)] = true; + } + } + + return $params; + } +} diff --git a/app/Core/Http/Router.php b/app/Core/Http/Router.php index 0080b23a..8b58a947 100644 --- a/app/Core/Http/Router.php +++ b/app/Core/Http/Router.php @@ -6,7 +6,7 @@ use RuntimeException; use Kanboard\Core\Base; /** - * Router class + * Route Dispatcher * * @package http * @author Frederic Guillot @@ -14,46 +14,38 @@ use Kanboard\Core\Base; class Router extends Base { /** - * Controller + * Plugin name * * @access private * @var string */ - private $controller = ''; + private $plugin = ''; /** - * Action + * Controller * * @access private * @var string */ - private $action = ''; - - /** - * Store routes for path lookup - * - * @access private - * @var array - */ - private $paths = array(); + private $controller = ''; /** - * Store routes for url lookup + * Action * * @access private - * @var array + * @var string */ - private $urls = array(); + private $action = ''; /** - * Get action + * Get plugin name * * @access public * @return string */ - public function getAction() + public function getPlugin() { - return $this->action; + return $this->plugin; } /** @@ -68,163 +60,110 @@ class Router extends Base } /** - * Get the path to compare patterns + * Get action * * @access public - * @param string $uri - * @param string $query_string * @return string */ - public function getPath($uri, $query_string = '') + public function getAction() { - $path = substr($uri, strlen($this->helper->url->dir())); - - if (! empty($query_string)) { - $path = substr($path, 0, - strlen($query_string) - 1); - } - - if (! empty($path) && $path{0} === '/') { - $path = substr($path, 1); - } - - return $path; + return $this->action; } /** - * Add route + * Get the path to compare patterns * * @access public - * @param string $path - * @param string $controller - * @param string $action - * @param array $params + * @return string */ - public function addRoute($path, $controller, $action, array $params = array()) + public function getPath() { - $pattern = explode('/', $path); - - $this->paths[] = array( - 'pattern' => $pattern, - 'count' => count($pattern), - 'controller' => $controller, - 'action' => $action, - ); - - $this->urls[$controller][$action][] = array( - 'path' => $path, - 'params' => array_flip($params), - 'count' => count($params), - ); - } + $path = substr($this->request->getUri(), strlen($this->helper->url->dir())); - /** - * Find a route according to the given path - * - * @access public - * @param string $path - * @return array - */ - public function findRoute($path) - { - $parts = explode('/', $path); - $count = count($parts); - - foreach ($this->paths as $route) { - if ($count === $route['count']) { - $params = array(); - - for ($i = 0; $i < $count; $i++) { - if ($route['pattern'][$i]{0} === ':') { - $params[substr($route['pattern'][$i], 1)] = $parts[$i]; - } elseif ($route['pattern'][$i] !== $parts[$i]) { - break; - } - } - - if ($i === $count) { - $_GET = array_merge($_GET, $params); - return array($route['controller'], $route['action']); - } - } + if ($this->request->getQueryString() !== '') { + $path = substr($path, 0, - strlen($this->request->getQueryString()) - 1); } - return array('app', 'index'); + if ($path !== '' && $path{0} === '/') { + $path = substr($path, 1); + } + + return $path; } /** - * Find route url + * Find controller/action from the route table or from get arguments * * @access public - * @param string $controller - * @param string $action - * @param array $params - * @return string */ - public function findUrl($controller, $action, array $params = array()) + public function dispatch() { - if (! isset($this->urls[$controller][$action])) { - return ''; + $controller = $this->request->getStringParam('controller'); + $action = $this->request->getStringParam('action'); + $plugin = $this->request->getStringParam('plugin'); + + if ($controller === '') { + $route = $this->route->findRoute($this->getPath()); + $controller = $route['controller']; + $action = $route['action']; + $plugin = $route['plugin']; } - foreach ($this->urls[$controller][$action] as $pattern) { - if (array_diff_key($params, $pattern['params']) === array()) { - $url = $pattern['path']; - $i = 0; - - foreach ($params as $variable => $value) { - $url = str_replace(':'.$variable, $value, $url); - $i++; - } - - if ($i === $pattern['count']) { - return $url; - } - } - } + $this->controller = ucfirst($this->sanitize($controller, 'app')); + $this->action = $this->sanitize($action, 'index'); + $this->plugin = ucfirst($this->sanitize($plugin)); - return ''; + return $this->executeAction(); } /** * Check controller and action parameter * * @access public - * @param string $value Controller or action name - * @param string $default_value Default value if validation fail + * @param string $value + * @param string $default * @return string */ - public function sanitize($value, $default_value) + public function sanitize($value, $default = '') { - return ! preg_match('/^[a-zA-Z_0-9]+$/', $value) ? $default_value : $value; + return preg_match('/^[a-zA-Z_0-9]+$/', $value) ? $value : $default; } /** - * Find controller/action from the route table or from get arguments + * Execute controller action * - * @access public - * @param string $uri - * @param string $query_string + * @access private */ - public function dispatch($uri, $query_string = '') + private function executeAction() { - if (! empty($_GET['controller']) && ! empty($_GET['action'])) { - $this->controller = $this->sanitize($_GET['controller'], 'app'); - $this->action = $this->sanitize($_GET['action'], 'index'); - $plugin = ! empty($_GET['plugin']) ? $this->sanitize($_GET['plugin'], '') : ''; - } else { - list($this->controller, $this->action) = $this->findRoute($this->getPath($uri, $query_string)); // TODO: add plugin for routes - $plugin = ''; - } + $class = $this->getControllerClassName(); - $class = '\Kanboard\\'; - $class .= empty($plugin) ? 'Controller\\'.ucfirst($this->controller) : 'Plugin\\'.ucfirst($plugin).'\Controller\\'.ucfirst($this->controller); + if (! class_exists($class)) { + throw new RuntimeException('Controller not found'); + } - if (! class_exists($class) || ! method_exists($class, $this->action)) { - throw new RuntimeException('Controller or method not found for the given url!'); + if (! method_exists($class, $this->action)) { + throw new RuntimeException('Action not implemented'); } $instance = new $class($this->container); $instance->beforeAction($this->controller, $this->action); $instance->{$this->action}(); + return $instance; + } + + /** + * Get controller class name + * + * @access private + * @return string + */ + private function getControllerClassName() + { + if ($this->plugin !== '') { + return '\Kanboard\Plugin\\'.$this->plugin.'\Controller\\'.$this->controller; + } + + return '\Kanboard\Controller\\'.$this->controller; } } diff --git a/app/Helper/App.php b/app/Helper/App.php index 33729f2b..6e6028fc 100644 --- a/app/Helper/App.php +++ b/app/Helper/App.php @@ -2,15 +2,28 @@ namespace Kanboard\Helper; +use Kanboard\Core\Base; + /** * Application helpers * * @package helper * @author Frederic Guillot */ -class App extends \Kanboard\Core\Base +class App extends Base { /** + * Get plugin name from route + * + * @access public + * @return string + */ + public function getPluginName() + { + return $this->router->getPlugin(); + } + + /** * Get router controller * * @access public diff --git a/app/Helper/Url.php b/app/Helper/Url.php index 6ada8068..720297cf 100644 --- a/app/Helper/Url.php +++ b/app/Helper/Url.php @@ -103,8 +103,8 @@ class Url extends Base */ public function dir() { - if (empty($this->directory) && isset($_SERVER['REQUEST_METHOD'])) { - $this->directory = str_replace('\\', '/', dirname($_SERVER['PHP_SELF'])); + if ($this->directory === '' && $this->request->getMethod() !== '') { + $this->directory = str_replace('\\', '/', dirname($this->request->getServerVariable('PHP_SELF'))); $this->directory = $this->directory !== '/' ? $this->directory.'/' : '/'; $this->directory = str_replace('//', '/', $this->directory); } @@ -120,13 +120,13 @@ class Url extends Base */ public function server() { - if (empty($_SERVER['SERVER_NAME'])) { + if ($this->request->getServerVariable('SERVER_NAME') === '') { return 'http://localhost/'; } $url = $this->request->isHTTPS() ? 'https://' : 'http://'; - $url .= $_SERVER['SERVER_NAME']; - $url .= $_SERVER['SERVER_PORT'] == 80 || $_SERVER['SERVER_PORT'] == 443 ? '' : ':'.$_SERVER['SERVER_PORT']; + $url .= $this->request->getServerVariable('SERVER_NAME'); + $url .= $this->request->getServerVariable('SERVER_PORT') == 80 || $this->request->getServerVariable('SERVER_PORT') == 443 ? '' : ':'.$this->request->getServerVariable('SERVER_PORT'); $url .= $this->dir() ?: '/'; return $url; @@ -147,13 +147,15 @@ class Url extends Base */ private function build($separator, $controller, $action, array $params = array(), $csrf = false, $anchor = '', $absolute = false) { - $path = $this->router->findUrl($controller, $action, $params); + $path = $this->route->findUrl($controller, $action, $params); $qs = array(); if (empty($path)) { $qs['controller'] = $controller; $qs['action'] = $action; $qs += $params; + } else { + unset($params['plugin']); } if ($csrf) { diff --git a/app/ServiceProvider/RouteProvider.php b/app/ServiceProvider/RouteProvider.php index b7dba8e5..e58d50c7 100644 --- a/app/ServiceProvider/RouteProvider.php +++ b/app/ServiceProvider/RouteProvider.php @@ -4,6 +4,7 @@ namespace Kanboard\ServiceProvider; use Pimple\Container; use Pimple\ServiceProviderInterface; +use Kanboard\Core\Http\Route; use Kanboard\Core\Http\Router; /** @@ -24,183 +25,186 @@ class RouteProvider implements ServiceProviderInterface public function register(Container $container) { $container['router'] = new Router($container); + $container['route'] = new Route($container); if (ENABLE_URL_REWRITE) { + $container['route']->enable(); + // Dashboard - $container['router']->addRoute('dashboard', 'app', 'index'); - $container['router']->addRoute('dashboard/:user_id', 'app', 'index', array('user_id')); - $container['router']->addRoute('dashboard/:user_id/projects', 'app', 'projects', array('user_id')); - $container['router']->addRoute('dashboard/:user_id/tasks', 'app', 'tasks', array('user_id')); - $container['router']->addRoute('dashboard/:user_id/subtasks', 'app', 'subtasks', array('user_id')); - $container['router']->addRoute('dashboard/:user_id/calendar', 'app', 'calendar', array('user_id')); - $container['router']->addRoute('dashboard/:user_id/activity', 'app', 'activity', array('user_id')); + $container['route']->addRoute('dashboard', 'app', 'index'); + $container['route']->addRoute('dashboard/:user_id', 'app', 'index'); + $container['route']->addRoute('dashboard/:user_id/projects', 'app', 'projects'); + $container['route']->addRoute('dashboard/:user_id/tasks', 'app', 'tasks'); + $container['route']->addRoute('dashboard/:user_id/subtasks', 'app', 'subtasks'); + $container['route']->addRoute('dashboard/:user_id/calendar', 'app', 'calendar'); + $container['route']->addRoute('dashboard/:user_id/activity', 'app', 'activity'); // Search routes - $container['router']->addRoute('search', 'search', 'index'); - $container['router']->addRoute('search/:search', 'search', 'index', array('search')); + $container['route']->addRoute('search', 'search', 'index'); + $container['route']->addRoute('search/:search', 'search', 'index'); // Project routes - $container['router']->addRoute('projects', 'project', 'index'); - $container['router']->addRoute('project/create', 'project', 'create'); - $container['router']->addRoute('project/create/private', 'project', 'createPrivate'); - $container['router']->addRoute('project/:project_id', 'project', 'show', array('project_id')); - $container['router']->addRoute('p/:project_id', 'project', 'show', array('project_id')); - $container['router']->addRoute('project/:project_id/customer-filter', 'customfilter', 'index', array('project_id')); - $container['router']->addRoute('project/:project_id/share', 'project', 'share', array('project_id')); - $container['router']->addRoute('project/:project_id/notifications', 'project', 'notifications', array('project_id')); - $container['router']->addRoute('project/:project_id/edit', 'project', 'edit', array('project_id')); - $container['router']->addRoute('project/:project_id/integrations', 'project', 'integrations', array('project_id')); - $container['router']->addRoute('project/:project_id/duplicate', 'project', 'duplicate', array('project_id')); - $container['router']->addRoute('project/:project_id/remove', 'project', 'remove', array('project_id')); - $container['router']->addRoute('project/:project_id/disable', 'project', 'disable', array('project_id')); - $container['router']->addRoute('project/:project_id/enable', 'project', 'enable', array('project_id')); - $container['router']->addRoute('project/:project_id/permissions', 'ProjectPermission', 'index', array('project_id')); - $container['router']->addRoute('project/:project_id/import', 'taskImport', 'step1', array('project_id')); + $container['route']->addRoute('projects', 'project', 'index'); + $container['route']->addRoute('project/create', 'project', 'create'); + $container['route']->addRoute('project/create/private', 'project', 'createPrivate'); + $container['route']->addRoute('project/:project_id', 'project', 'show'); + $container['route']->addRoute('p/:project_id', 'project', 'show'); + $container['route']->addRoute('project/:project_id/customer-filter', 'customfilter', 'index'); + $container['route']->addRoute('project/:project_id/share', 'project', 'share'); + $container['route']->addRoute('project/:project_id/notifications', 'project', 'notifications'); + $container['route']->addRoute('project/:project_id/edit', 'project', 'edit'); + $container['route']->addRoute('project/:project_id/integrations', 'project', 'integrations'); + $container['route']->addRoute('project/:project_id/duplicate', 'project', 'duplicate'); + $container['route']->addRoute('project/:project_id/remove', 'project', 'remove'); + $container['route']->addRoute('project/:project_id/disable', 'project', 'disable'); + $container['route']->addRoute('project/:project_id/enable', 'project', 'enable'); + $container['route']->addRoute('project/:project_id/permissions', 'ProjectPermission', 'index'); + $container['route']->addRoute('project/:project_id/import', 'taskImport', 'step1'); // ProjectUser routes - $container['router']->addRoute('projects/managers/:user_id', 'projectuser', 'managers', array('user_id')); - $container['router']->addRoute('projects/members/:user_id', 'projectuser', 'members', array('user_id')); - $container['router']->addRoute('projects/tasks/:user_id/opens', 'projectuser', 'opens', array('user_id')); - $container['router']->addRoute('projects/tasks/:user_id/closed', 'projectuser', 'closed', array('user_id')); - $container['router']->addRoute('projects/managers', 'projectuser', 'managers'); + $container['route']->addRoute('projects/managers/:user_id', 'projectuser', 'managers'); + $container['route']->addRoute('projects/members/:user_id', 'projectuser', 'members'); + $container['route']->addRoute('projects/tasks/:user_id/opens', 'projectuser', 'opens'); + $container['route']->addRoute('projects/tasks/:user_id/closed', 'projectuser', 'closed'); + $container['route']->addRoute('projects/managers', 'projectuser', 'managers'); // Action routes - $container['router']->addRoute('project/:project_id/actions', 'action', 'index', array('project_id')); - $container['router']->addRoute('project/:project_id/action/:action_id/confirm', 'action', 'confirm', array('project_id', 'action_id')); + $container['route']->addRoute('project/:project_id/actions', 'action', 'index'); + $container['route']->addRoute('project/:project_id/action/:action_id/confirm', 'action', 'confirm'); // Column routes - $container['router']->addRoute('project/:project_id/columns', 'column', 'index', array('project_id')); - $container['router']->addRoute('project/:project_id/column/:column_id/edit', 'column', 'edit', array('project_id', 'column_id')); - $container['router']->addRoute('project/:project_id/column/:column_id/confirm', 'column', 'confirm', array('project_id', 'column_id')); - $container['router']->addRoute('project/:project_id/column/:column_id/move/:direction', 'column', 'move', array('project_id', 'column_id', 'direction')); + $container['route']->addRoute('project/:project_id/columns', 'column', 'index'); + $container['route']->addRoute('project/:project_id/column/:column_id/edit', 'column', 'edit'); + $container['route']->addRoute('project/:project_id/column/:column_id/confirm', 'column', 'confirm'); + $container['route']->addRoute('project/:project_id/column/:column_id/move/:direction', 'column', 'move'); // Swimlane routes - $container['router']->addRoute('project/:project_id/swimlanes', 'swimlane', 'index', array('project_id')); - $container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/edit', 'swimlane', 'edit', array('project_id', 'swimlane_id')); - $container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/confirm', 'swimlane', 'confirm', array('project_id', 'swimlane_id')); - $container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/disable', 'swimlane', 'disable', array('project_id', 'swimlane_id')); - $container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/enable', 'swimlane', 'enable', array('project_id', 'swimlane_id')); - $container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/up', 'swimlane', 'moveup', array('project_id', 'swimlane_id')); - $container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/down', 'swimlane', 'movedown', array('project_id', 'swimlane_id')); + $container['route']->addRoute('project/:project_id/swimlanes', 'swimlane', 'index'); + $container['route']->addRoute('project/:project_id/swimlane/:swimlane_id/edit', 'swimlane', 'edit'); + $container['route']->addRoute('project/:project_id/swimlane/:swimlane_id/confirm', 'swimlane', 'confirm'); + $container['route']->addRoute('project/:project_id/swimlane/:swimlane_id/disable', 'swimlane', 'disable'); + $container['route']->addRoute('project/:project_id/swimlane/:swimlane_id/enable', 'swimlane', 'enable'); + $container['route']->addRoute('project/:project_id/swimlane/:swimlane_id/up', 'swimlane', 'moveup'); + $container['route']->addRoute('project/:project_id/swimlane/:swimlane_id/down', 'swimlane', 'movedown'); // Category routes - $container['router']->addRoute('project/:project_id/categories', 'category', 'index', array('project_id')); - $container['router']->addRoute('project/:project_id/category/:category_id/edit', 'category', 'edit', array('project_id', 'category_id')); - $container['router']->addRoute('project/:project_id/category/:category_id/confirm', 'category', 'confirm', array('project_id', 'category_id')); + $container['route']->addRoute('project/:project_id/categories', 'category', 'index'); + $container['route']->addRoute('project/:project_id/category/:category_id/edit', 'category', 'edit'); + $container['route']->addRoute('project/:project_id/category/:category_id/confirm', 'category', 'confirm'); // Task routes - $container['router']->addRoute('project/:project_id/task/:task_id', 'task', 'show', array('project_id', 'task_id')); - $container['router']->addRoute('t/:task_id', 'task', 'show', array('task_id')); - $container['router']->addRoute('public/task/:task_id/:token', 'task', 'readonly', array('task_id', 'token')); - - $container['router']->addRoute('project/:project_id/task/:task_id/activity', 'activity', 'task', array('project_id', 'task_id')); - $container['router']->addRoute('project/:project_id/task/:task_id/screenshot', 'file', 'screenshot', array('project_id', 'task_id')); - $container['router']->addRoute('project/:project_id/task/:task_id/upload', 'file', 'create', array('project_id', 'task_id')); - $container['router']->addRoute('project/:project_id/task/:task_id/comment', 'comment', 'create', array('project_id', 'task_id')); - $container['router']->addRoute('project/:project_id/task/:task_id/link', 'tasklink', 'create', array('project_id', 'task_id')); - $container['router']->addRoute('project/:project_id/task/:task_id/transitions', 'task', 'transitions', array('project_id', 'task_id')); - $container['router']->addRoute('project/:project_id/task/:task_id/analytics', 'task', 'analytics', array('project_id', 'task_id')); - $container['router']->addRoute('project/:project_id/task/:task_id/remove', 'task', 'remove', array('project_id', 'task_id')); - - $container['router']->addRoute('project/:project_id/task/:task_id/edit', 'taskmodification', 'edit', array('project_id', 'task_id')); - $container['router']->addRoute('project/:project_id/task/:task_id/description', 'taskmodification', 'description', array('project_id', 'task_id')); - $container['router']->addRoute('project/:project_id/task/:task_id/recurrence', 'taskmodification', 'recurrence', array('project_id', 'task_id')); - - $container['router']->addRoute('project/:project_id/task/:task_id/close', 'taskstatus', 'close', array('task_id', 'project_id')); - $container['router']->addRoute('project/:project_id/task/:task_id/open', 'taskstatus', 'open', array('task_id', 'project_id')); - - $container['router']->addRoute('project/:project_id/task/:task_id/duplicate', 'taskduplication', 'duplicate', array('task_id', 'project_id')); - $container['router']->addRoute('project/:project_id/task/:task_id/copy', 'taskduplication', 'copy', array('task_id', 'project_id')); - $container['router']->addRoute('project/:project_id/task/:task_id/copy/:dst_project_id', 'taskduplication', 'copy', array('task_id', 'project_id', 'dst_project_id')); - $container['router']->addRoute('project/:project_id/task/:task_id/move', 'taskduplication', 'move', array('task_id', 'project_id')); - $container['router']->addRoute('project/:project_id/task/:task_id/move/:dst_project_id', 'taskduplication', 'move', array('task_id', 'project_id', 'dst_project_id')); + $container['route']->addRoute('project/:project_id/task/:task_id', 'task', 'show'); + $container['route']->addRoute('t/:task_id', 'task', 'show'); + $container['route']->addRoute('public/task/:task_id/:token', 'task', 'readonly'); + + $container['route']->addRoute('project/:project_id/task/:task_id/activity', 'activity', 'task'); + $container['route']->addRoute('project/:project_id/task/:task_id/screenshot', 'file', 'screenshot'); + $container['route']->addRoute('project/:project_id/task/:task_id/upload', 'file', 'create'); + $container['route']->addRoute('project/:project_id/task/:task_id/comment', 'comment', 'create'); + $container['route']->addRoute('project/:project_id/task/:task_id/link', 'tasklink', 'create'); + $container['route']->addRoute('project/:project_id/task/:task_id/transitions', 'task', 'transitions'); + $container['route']->addRoute('project/:project_id/task/:task_id/analytics', 'task', 'analytics'); + $container['route']->addRoute('project/:project_id/task/:task_id/remove', 'task', 'remove'); + + $container['route']->addRoute('project/:project_id/task/:task_id/edit', 'taskmodification', 'edit'); + $container['route']->addRoute('project/:project_id/task/:task_id/description', 'taskmodification', 'description'); + $container['route']->addRoute('project/:project_id/task/:task_id/recurrence', 'taskmodification', 'recurrence'); + + $container['route']->addRoute('project/:project_id/task/:task_id/close', 'taskstatus', 'close'); + $container['route']->addRoute('project/:project_id/task/:task_id/open', 'taskstatus', 'open'); + + $container['route']->addRoute('project/:project_id/task/:task_id/duplicate', 'taskduplication', 'duplicate'); + $container['route']->addRoute('project/:project_id/task/:task_id/copy', 'taskduplication', 'copy'); + $container['route']->addRoute('project/:project_id/task/:task_id/copy/:dst_project_id', 'taskduplication', 'copy'); + $container['route']->addRoute('project/:project_id/task/:task_id/move', 'taskduplication', 'move'); + $container['route']->addRoute('project/:project_id/task/:task_id/move/:dst_project_id', 'taskduplication', 'move'); // Exports - $container['router']->addRoute('export/tasks/:project_id', 'export', 'tasks', array('project_id')); - $container['router']->addRoute('export/subtasks/:project_id', 'export', 'subtasks', array('project_id')); - $container['router']->addRoute('export/transitions/:project_id', 'export', 'transitions', array('project_id')); - $container['router']->addRoute('export/summary/:project_id', 'export', 'summary', array('project_id')); + $container['route']->addRoute('export/tasks/:project_id', 'export', 'tasks'); + $container['route']->addRoute('export/subtasks/:project_id', 'export', 'subtasks'); + $container['route']->addRoute('export/transitions/:project_id', 'export', 'transitions'); + $container['route']->addRoute('export/summary/:project_id', 'export', 'summary'); // Board routes - $container['router']->addRoute('board/:project_id', 'board', 'show', array('project_id')); - $container['router']->addRoute('b/:project_id', 'board', 'show', array('project_id')); - $container['router']->addRoute('public/board/:token', 'board', 'readonly', array('token')); + $container['route']->addRoute('board/:project_id', 'board', 'show'); + $container['route']->addRoute('b/:project_id', 'board', 'show'); + $container['route']->addRoute('public/board/:token', 'board', 'readonly'); // Calendar routes - $container['router']->addRoute('calendar/:project_id', 'calendar', 'show', array('project_id')); - $container['router']->addRoute('c/:project_id', 'calendar', 'show', array('project_id')); + $container['route']->addRoute('calendar/:project_id', 'calendar', 'show'); + $container['route']->addRoute('c/:project_id', 'calendar', 'show'); // Listing routes - $container['router']->addRoute('list/:project_id', 'listing', 'show', array('project_id')); - $container['router']->addRoute('l/:project_id', 'listing', 'show', array('project_id')); + $container['route']->addRoute('list/:project_id', 'listing', 'show'); + $container['route']->addRoute('l/:project_id', 'listing', 'show'); // Gantt routes - $container['router']->addRoute('gantt/:project_id', 'gantt', 'project', array('project_id')); - $container['router']->addRoute('gantt/:project_id/sort/:sorting', 'gantt', 'project', array('project_id', 'sorting')); + $container['route']->addRoute('gantt/:project_id', 'gantt', 'project'); + $container['route']->addRoute('gantt/:project_id/sort/:sorting', 'gantt', 'project'); // Subtask routes - $container['router']->addRoute('project/:project_id/task/:task_id/subtask/create', 'subtask', 'create', array('project_id', 'task_id')); - $container['router']->addRoute('project/:project_id/task/:task_id/subtask/:subtask_id/remove', 'subtask', 'confirm', array('project_id', 'task_id', 'subtask_id')); - $container['router']->addRoute('project/:project_id/task/:task_id/subtask/:subtask_id/edit', 'subtask', 'edit', array('project_id', 'task_id', 'subtask_id')); + $container['route']->addRoute('project/:project_id/task/:task_id/subtask/create', 'subtask', 'create'); + $container['route']->addRoute('project/:project_id/task/:task_id/subtask/:subtask_id/remove', 'subtask', 'confirm'); + $container['route']->addRoute('project/:project_id/task/:task_id/subtask/:subtask_id/edit', 'subtask', 'edit'); // Feed routes - $container['router']->addRoute('feed/project/:token', 'feed', 'project', array('token')); - $container['router']->addRoute('feed/user/:token', 'feed', 'user', array('token')); + $container['route']->addRoute('feed/project/:token', 'feed', 'project'); + $container['route']->addRoute('feed/user/:token', 'feed', 'user'); // Ical routes - $container['router']->addRoute('ical/project/:token', 'ical', 'project', array('token')); - $container['router']->addRoute('ical/user/:token', 'ical', 'user', array('token')); + $container['route']->addRoute('ical/project/:token', 'ical', 'project'); + $container['route']->addRoute('ical/user/:token', 'ical', 'user'); // Users - $container['router']->addRoute('users', 'user', 'index'); - $container['router']->addRoute('user/profile/:user_id', 'user', 'profile', array('user_id')); - $container['router']->addRoute('user/show/:user_id', 'user', 'show', array('user_id')); - $container['router']->addRoute('user/show/:user_id/timesheet', 'user', 'timesheet', array('user_id')); - $container['router']->addRoute('user/show/:user_id/last-logins', 'user', 'last', array('user_id')); - $container['router']->addRoute('user/show/:user_id/sessions', 'user', 'sessions', array('user_id')); - $container['router']->addRoute('user/:user_id/edit', 'user', 'edit', array('user_id')); - $container['router']->addRoute('user/:user_id/password', 'user', 'password', array('user_id')); - $container['router']->addRoute('user/:user_id/share', 'user', 'share', array('user_id')); - $container['router']->addRoute('user/:user_id/notifications', 'user', 'notifications', array('user_id')); - $container['router']->addRoute('user/:user_id/accounts', 'user', 'external', array('user_id')); - $container['router']->addRoute('user/:user_id/integrations', 'user', 'integrations', array('user_id')); - $container['router']->addRoute('user/:user_id/authentication', 'user', 'authentication', array('user_id')); - $container['router']->addRoute('user/:user_id/remove', 'user', 'remove', array('user_id')); - $container['router']->addRoute('user/:user_id/2fa', 'twofactor', 'index', array('user_id')); + $container['route']->addRoute('users', 'user', 'index'); + $container['route']->addRoute('user/profile/:user_id', 'user', 'profile'); + $container['route']->addRoute('user/show/:user_id', 'user', 'show'); + $container['route']->addRoute('user/show/:user_id/timesheet', 'user', 'timesheet'); + $container['route']->addRoute('user/show/:user_id/last-logins', 'user', 'last'); + $container['route']->addRoute('user/show/:user_id/sessions', 'user', 'sessions'); + $container['route']->addRoute('user/:user_id/edit', 'user', 'edit'); + $container['route']->addRoute('user/:user_id/password', 'user', 'password'); + $container['route']->addRoute('user/:user_id/share', 'user', 'share'); + $container['route']->addRoute('user/:user_id/notifications', 'user', 'notifications'); + $container['route']->addRoute('user/:user_id/accounts', 'user', 'external'); + $container['route']->addRoute('user/:user_id/integrations', 'user', 'integrations'); + $container['route']->addRoute('user/:user_id/authentication', 'user', 'authentication'); + $container['route']->addRoute('user/:user_id/remove', 'user', 'remove'); + $container['route']->addRoute('user/:user_id/2fa', 'twofactor', 'index'); // Groups - $container['router']->addRoute('groups', 'group', 'index'); - $container['router']->addRoute('groups/create', 'group', 'create'); - $container['router']->addRoute('group/:group_id/associate', 'group', 'associate', array('group_id')); - $container['router']->addRoute('group/:group_id/dissociate/:user_id', 'group', 'dissociate', array('group_id', 'user_id')); - $container['router']->addRoute('group/:group_id/edit', 'group', 'edit', array('group_id')); - $container['router']->addRoute('group/:group_id/members', 'group', 'users', array('group_id')); - $container['router']->addRoute('group/:group_id/remove', 'group', 'confirm', array('group_id')); + $container['route']->addRoute('groups', 'group', 'index'); + $container['route']->addRoute('groups/create', 'group', 'create'); + $container['route']->addRoute('group/:group_id/associate', 'group', 'associate'); + $container['route']->addRoute('group/:group_id/dissociate/:user_id', 'group', 'dissociate'); + $container['route']->addRoute('group/:group_id/edit', 'group', 'edit'); + $container['route']->addRoute('group/:group_id/members', 'group', 'users'); + $container['route']->addRoute('group/:group_id/remove', 'group', 'confirm'); // Config - $container['router']->addRoute('settings', 'config', 'index'); - $container['router']->addRoute('settings/plugins', 'config', 'plugins'); - $container['router']->addRoute('settings/application', 'config', 'application'); - $container['router']->addRoute('settings/project', 'config', 'project'); - $container['router']->addRoute('settings/project', 'config', 'project'); - $container['router']->addRoute('settings/board', 'config', 'board'); - $container['router']->addRoute('settings/calendar', 'config', 'calendar'); - $container['router']->addRoute('settings/integrations', 'config', 'integrations'); - $container['router']->addRoute('settings/webhook', 'config', 'webhook'); - $container['router']->addRoute('settings/api', 'config', 'api'); - $container['router']->addRoute('settings/links', 'link', 'index'); - $container['router']->addRoute('settings/currencies', 'currency', 'index'); + $container['route']->addRoute('settings', 'config', 'index'); + $container['route']->addRoute('settings/plugins', 'config', 'plugins'); + $container['route']->addRoute('settings/application', 'config', 'application'); + $container['route']->addRoute('settings/project', 'config', 'project'); + $container['route']->addRoute('settings/project', 'config', 'project'); + $container['route']->addRoute('settings/board', 'config', 'board'); + $container['route']->addRoute('settings/calendar', 'config', 'calendar'); + $container['route']->addRoute('settings/integrations', 'config', 'integrations'); + $container['route']->addRoute('settings/webhook', 'config', 'webhook'); + $container['route']->addRoute('settings/api', 'config', 'api'); + $container['route']->addRoute('settings/links', 'link', 'index'); + $container['route']->addRoute('settings/currencies', 'currency', 'index'); // Doc - $container['router']->addRoute('documentation/:file', 'doc', 'show', array('file')); - $container['router']->addRoute('documentation', 'doc', 'show'); + $container['route']->addRoute('documentation/:file', 'doc', 'show'); + $container['route']->addRoute('documentation', 'doc', 'show'); // Auth routes - $container['router']->addRoute('oauth/google', 'oauth', 'google'); - $container['router']->addRoute('oauth/github', 'oauth', 'github'); - $container['router']->addRoute('oauth/gitlab', 'oauth', 'gitlab'); - $container['router']->addRoute('login', 'auth', 'login'); - $container['router']->addRoute('logout', 'auth', 'logout'); + $container['route']->addRoute('oauth/google', 'oauth', 'google'); + $container['route']->addRoute('oauth/github', 'oauth', 'github'); + $container['route']->addRoute('oauth/gitlab', 'oauth', 'gitlab'); + $container['route']->addRoute('login', 'auth', 'login'); + $container['route']->addRoute('logout', 'auth', 'logout'); } return $container; diff --git a/doc/plugin-routes.markdown b/doc/plugin-routes.markdown new file mode 100644 index 00000000..b943bb19 --- /dev/null +++ b/doc/plugin-routes.markdown @@ -0,0 +1,85 @@ +Custom Routes +============= + +When URL rewriting is enabled, you can define custom routes from your plugins. + +Define new routes +----------------- + +Routes are handled by the class `Kanboard\Core\Http\Route`. + +New routes can be added by using the method `addRoute($path, $controller, $action, $plugin)`, here an example: + +```php +$this->route->addRoute('/my/custom/route', 'myController', 'myAction', 'myplugin'); +``` + +When the end-user go to the URL `/my/custom/route`, the method `Kanboard\Plugin\Myplugin\Controller\MyController::myAction()` will be executed. + +The first character of the controller and the plugin name will converted in uppercase with the function `ucfirst()`. + +You can also define routes with variables: + +```php +$this->route->addRoute('/my/route/:my_variable', 'myController', 'myAction', 'myplugin'); +``` + +The colon prefix `:`, define a variable. +For example `:my_variable` declare a new variable named `my_variable`. + +To fetch the value of the variable you can use the method `getStringParam()` or `getIntegerParam()` from the class `Kanboard\Core\Http\Request`: + +If we have the URL `/my/route/foobar`, the value of `my_variable` is `foobar`: + +```php +$this->request->getStringParam('my_variable'); // Return foobar +``` + +Generate links based on the routing table +----------------------------------------- + +From templates, you have to use the helper `Kanboard\Helper\Url`. + +### Generate a HTML link + +```php +<?= $this->url->link('My link', 'mycontroller', 'myaction', array('plugin' => 'myplugin')) ?> +``` + +Will generate this HTML: + +```html +<a href="/my/custom/route">My link</a> +``` + +### Generate only the attribute `href`: + +```php +<?= $this->url->href('My link', 'mycontroller', 'myaction', array('plugin' => 'myplugin')) ?> +``` + +HTML output: + +```html +/my/custom/route +``` + +HTML output when URL rewriting is not enabled: + +```html +?controller=mycontroller&action=myaction&plugin=myplugin +``` + +### Generate redirect link: + +From a controller, if you need to perform a redirection: + +```php +$this->url->to('mycontroller', 'myaction', array('plugin' => 'myplugin')); +``` + +Generate: + +``` +?controller=mycontroller&action=myaction&plugin=myplugin +``` diff --git a/doc/plugins.markdown b/doc/plugins.markdown index 07447d4f..809ddd72 100644 --- a/doc/plugins.markdown +++ b/doc/plugins.markdown @@ -11,6 +11,7 @@ Plugin creators should specify explicitly the compatible versions of Kanboard. I - [Using plugin hooks](plugin-hooks.markdown) - [Override default application behaviors](plugin-overrides.markdown) - [Add schema migrations for plugins](plugin-schema-migrations.markdown) +- [Custom routes](plugin-routes.markdown) - [Add mail transports](plugin-mail-transports.markdown) - [Add notification types](plugin-notifications.markdown) - [Attach metadata to users, tasks and projects](plugin-metadata.markdown) @@ -2,7 +2,7 @@ try { require __DIR__.'/app/common.php'; - $container['router']->dispatch($_SERVER['REQUEST_URI'], isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : ''); + $container['router']->dispatch(); } catch (Exception $e) { echo 'Internal Error: '.$e->getMessage(); } diff --git a/tests/units/Core/Http/RouteTest.php b/tests/units/Core/Http/RouteTest.php new file mode 100644 index 00000000..5e44ad00 --- /dev/null +++ b/tests/units/Core/Http/RouteTest.php @@ -0,0 +1,79 @@ +<?php + +require_once __DIR__.'/../../Base.php'; + +use Kanboard\Core\Http\Route; + +class RouteTest extends Base +{ + public function testFindParams() + { + $route = new Route($this->container); + $route->enable(); + + $this->assertEquals(array('p1' => true, 'p2' => true), $route->findParams(array('something', ':p1', ':p2'))); + $this->assertEquals(array('p1' => true), $route->findParams(array('something', ':p1', ''))); + $this->assertEquals(array('p1' => true), $route->findParams(array('something', ':p1', 'something else'))); + } + + public function testFindRoute() + { + $route = new Route($this->container); + $route->enable(); + + $route->addRoute('/mycontroller/myaction', 'mycontroller', 'myaction'); + $this->assertEquals( + array('controller' => 'mycontroller', 'action' => 'myaction', 'plugin' => ''), + $route->findRoute('/mycontroller/myaction') + ); + + $route->addRoute('/a/b/c', 'mycontroller', 'myaction', 'myplugin'); + $this->assertEquals( + array('controller' => 'mycontroller', 'action' => 'myaction', 'plugin' => 'myplugin'), + $route->findRoute('/a/b/c') + ); + + $this->assertEquals( + array('controller' => 'app', 'action' => 'index', 'plugin' => ''), + $route->findRoute('/notfound') + ); + + $route->addRoute('/a/b/:c', 'mycontroller', 'myaction', 'myplugin'); + $this->assertEquals( + array('controller' => 'mycontroller', 'action' => 'myaction', 'plugin' => 'myplugin'), + $route->findRoute('/a/b/myvalue') + ); + + $this->assertEquals('myvalue', $this->container['request']->getStringParam('c')); + + $route->addRoute('/a/:p1/b/:p2', 'mycontroller', 'myaction'); + $this->assertEquals( + array('controller' => 'mycontroller', 'action' => 'myaction', 'plugin' => ''), + $route->findRoute('/a/v1/b/v2') + ); + + $this->assertEquals('v1', $this->container['request']->getStringParam('p1')); + $this->assertEquals('v2', $this->container['request']->getStringParam('p2')); + } + + public function testFindUrl() + { + $route = new Route($this->container); + $route->enable(); + $route->addRoute('a/b', 'controller1', 'action1'); + $route->addRoute('a/:myvar1/b/:myvar2', 'controller2', 'action2'); + $route->addRoute('/something', 'controller1', 'action1', 'myplugin'); + $route->addRoute('/myplugin/myroute', 'controller1', 'action2', 'myplugin'); + $route->addRoute('/foo/:myvar', 'controller1', 'action3', 'myplugin'); + + $this->assertEquals('a/1/b/2', $route->findUrl('controller2', 'action2', array('myvar1' => 1, 'myvar2' => 2))); + $this->assertEquals('', $route->findUrl('controller2', 'action2', array('myvar1' => 1))); + $this->assertEquals('a/b', $route->findUrl('controller1', 'action1')); + $this->assertEquals('', $route->findUrl('controller1', 'action2')); + + $this->assertEquals('myplugin/myroute', $route->findUrl('controller1', 'action2', array(), 'myplugin')); + $this->assertEquals('something', $route->findUrl('controller1', 'action1', array(), 'myplugin')); + $this->assertEquals('foo/123', $route->findUrl('controller1', 'action3', array('myvar' => 123), 'myplugin')); + $this->assertEquals('foo/123', $route->findUrl('controller1', 'action3', array('myvar' => 123, 'plugin' => 'myplugin'))); + } +} diff --git a/tests/units/Core/Http/RouterTest.php b/tests/units/Core/Http/RouterTest.php index c2380247..0b200ab5 100644 --- a/tests/units/Core/Http/RouterTest.php +++ b/tests/units/Core/Http/RouterTest.php @@ -1,81 +1,203 @@ <?php -require_once __DIR__.'/../../Base.php'; +namespace Kanboard\Controller { -use Kanboard\Core\Http\Router; + class FakeController { + public function beforeAction() {} + public function myAction() {} + } +} -class RouterTest extends Base -{ - public function testSanitize() - { - $r = new Router($this->container); - - $this->assertEquals('PloP', $r->sanitize('PloP', 'default')); - $this->assertEquals('default', $r->sanitize('', 'default')); - $this->assertEquals('default', $r->sanitize('123-AB', 'default')); - $this->assertEquals('default', $r->sanitize('R&D', 'default')); - $this->assertEquals('Test123', $r->sanitize('Test123', 'default')); - $this->assertEquals('Test_123', $r->sanitize('Test_123', 'default')); - $this->assertEquals('userImport', $r->sanitize('userImport', 'default')); +namespace Kanboard\Plugin\Myplugin\Controller { + + class FakeController { + public function beforeAction() {} + public function myAction() {} } +} - public function testPath() +namespace { + + require_once __DIR__.'/../../Base.php'; + + use Kanboard\Core\Helper; + use Kanboard\Core\Http\Route; + use Kanboard\Core\Http\Router; + use Kanboard\Core\Http\Request; + + class RouterTest extends Base { - $r = new Router($this->container); + public function testSanitize() + { + $dispatcher = new Router($this->container); - $this->assertEquals('a/b/c', $r->getPath('/a/b/c')); - $this->assertEquals('a/b/something', $r->getPath('/a/b/something?test=a', 'test=a')); + $this->assertEquals('PloP', $dispatcher->sanitize('PloP', 'default')); + $this->assertEquals('default', $dispatcher->sanitize('', 'default')); + $this->assertEquals('default', $dispatcher->sanitize('123-AB', 'default')); + $this->assertEquals('default', $dispatcher->sanitize('R&D', 'default')); + $this->assertEquals('Test123', $dispatcher->sanitize('Test123', 'default')); + $this->assertEquals('Test_123', $dispatcher->sanitize('Test_123', 'default')); + $this->assertEquals('userImport', $dispatcher->sanitize('userImport', 'default')); + } - $_SERVER['REQUEST_METHOD'] = 'GET'; - $_SERVER['PHP_SELF'] = '/a/index.php'; + public function testGetPath() + { + $dispatcher = new Router($this->container); - $this->assertEquals('b/c', $r->getPath('/a/b/c')); - $this->assertEquals('b/c', $r->getPath('/a/b/c?e=f', 'e=f')); - } + $this->container['helper'] = new Helper($this->container); + $this->container['request'] = new Request($this->container, array('PHP_SELF' => '/index.php', 'REQUEST_URI' => '/a/b/c', 'REQUEST_METHOD' => 'GET')); + $this->assertEquals('a/b/c', $dispatcher->getPath()); - public function testFindRouteWithEmptyTable() - { - $r = new Router($this->container); - $this->assertEquals(array('app', 'index'), $r->findRoute('')); - $this->assertEquals(array('app', 'index'), $r->findRoute('/')); - } + $this->container['helper'] = new Helper($this->container); + $this->container['request'] = new Request($this->container, array('PHP_SELF' => '/index.php', 'REQUEST_URI' => '/a/b/something?test=a', 'QUERY_STRING' => 'test=a', 'REQUEST_METHOD' => 'GET')); + $this->assertEquals('a/b/something', $dispatcher->getPath()); - public function testFindRouteWithoutPlaceholders() - { - $r = new Router($this->container); - $r->addRoute('a/b', 'controller', 'action'); - $this->assertEquals(array('app', 'index'), $r->findRoute('a/b/c')); - $this->assertEquals(array('controller', 'action'), $r->findRoute('a/b')); - } + $this->container['helper'] = new Helper($this->container); + $this->container['request'] = new Request($this->container, array('PHP_SELF' => '/a/index.php', 'REQUEST_URI' => '/a/b/something?test=a', 'QUERY_STRING' => 'test=a', 'REQUEST_METHOD' => 'GET')); + $this->assertEquals('b/something', $dispatcher->getPath()); + } - public function testFindRouteWithPlaceholders() - { - $r = new Router($this->container); - $r->addRoute('a/:myvar1/b/:myvar2', 'controller', 'action'); - $this->assertEquals(array('app', 'index'), $r->findRoute('a/123/b')); - $this->assertEquals(array('controller', 'action'), $r->findRoute('a/456/b/789')); - $this->assertEquals(array('myvar1' => 456, 'myvar2' => 789), $_GET); - } + public function testDispatcherWithControllerNotFound() + { + $this->container['request'] = new Request($this->container, array( + 'PHP_SELF' => '/kanboard/index.php', + 'REQUEST_URI' => '/kanboard/?controller=FakeControllerNotFound&action=myAction&myvar=value1', + 'QUERY_STRING' => 'controller=FakeControllerNotFound&action=myAction&myvar=value1', + 'REQUEST_METHOD' => 'GET' + ), + array( + 'controller' => 'FakeControllerNotFound', + 'action' => 'myAction', + 'myvar' => 'value1', + ) + ); - public function testFindMultipleRoutes() - { - $r = new Router($this->container); - $r->addRoute('a/b', 'controller1', 'action1'); - $r->addRoute('a/b', 'duplicate', 'duplicate'); - $r->addRoute('a', 'controller2', 'action2'); - $this->assertEquals(array('controller1', 'action1'), $r->findRoute('a/b')); - $this->assertEquals(array('controller2', 'action2'), $r->findRoute('a')); - } + $this->setExpectedException('RuntimeException', 'Controller not found'); - public function testFindUrl() - { - $r = new Router($this->container); - $r->addRoute('a/b', 'controller1', 'action1'); - $r->addRoute('a/:myvar1/b/:myvar2', 'controller2', 'action2', array('myvar1', 'myvar2')); - - $this->assertEquals('a/1/b/2', $r->findUrl('controller2', 'action2', array('myvar1' => 1, 'myvar2' => 2))); - $this->assertEquals('', $r->findUrl('controller2', 'action2', array('myvar1' => 1))); - $this->assertEquals('a/b', $r->findUrl('controller1', 'action1')); - $this->assertEquals('', $r->findUrl('controller1', 'action2')); + $dispatcher = new Router($this->container); + $dispatcher->dispatch(); + } + + public function testDispatcherWithActionNotFound() + { + $this->container['request'] = new Request($this->container, array( + 'PHP_SELF' => '/kanboard/index.php', + 'REQUEST_URI' => '/kanboard/?controller=FakeController&action=myActionNotFound&myvar=value1', + 'QUERY_STRING' => 'controller=FakeController&action=myActionNotFound&myvar=value1', + 'REQUEST_METHOD' => 'GET' + ), + array( + 'controller' => 'FakeController', + 'action' => 'myActionNotFound', + 'myvar' => 'value1', + ) + ); + + $this->setExpectedException('RuntimeException', 'Action not implemented'); + + $dispatcher = new Router($this->container); + $dispatcher->dispatch(); + } + + public function testDispatcherWithNoUrlRewrite() + { + $this->container['request'] = new Request($this->container, array( + 'PHP_SELF' => '/kanboard/index.php', + 'REQUEST_URI' => '/kanboard/?controller=FakeController&action=myAction&myvar=value1', + 'QUERY_STRING' => 'controller=FakeController&action=myAction&myvar=value1', + 'REQUEST_METHOD' => 'GET' + ), + array( + 'controller' => 'FakeController', + 'action' => 'myAction', + 'myvar' => 'value1', + ) + ); + + $dispatcher = new Router($this->container); + $this->assertInstanceOf('\Kanboard\Controller\FakeController', $dispatcher->dispatch()); + $this->assertEquals('FakeController', $dispatcher->getController()); + $this->assertEquals('myAction', $dispatcher->getAction()); + $this->assertEquals('', $dispatcher->getPlugin()); + $this->assertEquals('value1', $this->container['request']->getStringParam('myvar')); + } + + public function testDispatcherWithNoUrlRewriteAndPlugin() + { + $this->container['request'] = new Request($this->container, array( + 'PHP_SELF' => '/kanboard/index.php', + 'REQUEST_URI' => '/kanboard/?controller=FakeController&action=myAction&myvar=value1&plugin=myplugin', + 'QUERY_STRING' => 'controller=FakeController&action=myAction&myvar=value1&plugin=myplugin', + 'REQUEST_METHOD' => 'GET' + ), + array( + 'controller' => 'FakeController', + 'action' => 'myAction', + 'myvar' => 'value1', + 'plugin' => 'myplugin', + ) + ); + + $dispatcher = new Router($this->container); + $this->assertInstanceOf('\Kanboard\Plugin\Myplugin\Controller\FakeController', $dispatcher->dispatch()); + $this->assertEquals('FakeController', $dispatcher->getController()); + $this->assertEquals('myAction', $dispatcher->getAction()); + $this->assertEquals('Myplugin', $dispatcher->getPlugin()); + $this->assertEquals('value1', $this->container['request']->getStringParam('myvar')); + } + + public function testDispatcherWithUrlRewrite() + { + $this->container['request'] = new Request($this->container, array( + 'PHP_SELF' => '/kanboard/index.php', + 'REQUEST_URI' => '/kanboard/my/route/123?myvar=value1', + 'QUERY_STRING' => 'myvar=value1', + 'REQUEST_METHOD' => 'GET' + ), + array( + 'myvar' => 'value1', + ) + ); + + $this->container['route'] = new Route($this->container); + $this->container['route']->enable(); + $dispatcher = new Router($this->container); + + $this->container['route']->addRoute('/my/route/:param', 'FakeController', 'myAction'); + + $this->assertInstanceOf('\Kanboard\Controller\FakeController', $dispatcher->dispatch()); + $this->assertEquals('FakeController', $dispatcher->getController()); + $this->assertEquals('myAction', $dispatcher->getAction()); + $this->assertEquals('', $dispatcher->getPlugin()); + $this->assertEquals('value1', $this->container['request']->getStringParam('myvar')); + $this->assertEquals('123', $this->container['request']->getStringParam('param')); + } + + public function testDispatcherWithUrlRewriteWithPlugin() + { + $this->container['request'] = new Request($this->container, array( + 'PHP_SELF' => '/kanboard/index.php', + 'REQUEST_URI' => '/kanboard/my/plugin/route/123?myvar=value1', + 'QUERY_STRING' => 'myvar=value1', + 'REQUEST_METHOD' => 'GET' + ), + array( + 'myvar' => 'value1', + ) + ); + + $this->container['route'] = new Route($this->container); + $this->container['route']->enable(); + $dispatcher = new Router($this->container); + + $this->container['route']->addRoute('/my/plugin/route/:param', 'fakeController', 'myAction', 'Myplugin'); + + $this->assertInstanceOf('\Kanboard\Plugin\Myplugin\Controller\FakeController', $dispatcher->dispatch()); + $this->assertEquals('FakeController', $dispatcher->getController()); + $this->assertEquals('myAction', $dispatcher->getAction()); + $this->assertEquals('Myplugin', $dispatcher->getPlugin()); + $this->assertEquals('value1', $this->container['request']->getStringParam('myvar')); + $this->assertEquals('123', $this->container['request']->getStringParam('param')); + } } } diff --git a/tests/units/Helper/UrlHelperTest.php b/tests/units/Helper/UrlHelperTest.php index cbacbc73..405e9462 100644 --- a/tests/units/Helper/UrlHelperTest.php +++ b/tests/units/Helper/UrlHelperTest.php @@ -4,10 +4,32 @@ require_once __DIR__.'/../Base.php'; use Kanboard\Helper\Url; use Kanboard\Model\Config; +use Kanboard\Core\Http\Request; class UrlHelperTest extends Base { - public function testLink() + public function testPluginLink() + { + $h = new Url($this->container); + $this->assertEquals( + '<a href="?controller=a&action=b&d=e&plugin=something" class="f" title="g" target="_blank">label</a>', + $h->link('label', 'a', 'b', array('d' => 'e', 'plugin' => 'something'), false, 'f', 'g', true) + ); + } + + public function testPluginLinkWithRouteDefined() + { + $this->container['route']->enable(); + $this->container['route']->addRoute('/myplugin/something/:d', 'a', 'b', 'something'); + + $h = new Url($this->container); + $this->assertEquals( + '<a href="myplugin/something/e" class="f" title="g" target="_blank">label</a>', + $h->link('label', 'a', 'b', array('d' => 'e', 'plugin' => 'something'), false, 'f', 'g', true) + ); + } + + public function testAppLink() { $h = new Url($this->container); $this->assertEquals( @@ -36,42 +58,59 @@ class UrlHelperTest extends Base public function testDir() { - $h = new Url($this->container); - $this->assertEquals('', $h->dir()); + $this->container['request'] = new Request($this->container, array( + 'PHP_SELF' => '/kanboard/index.php', + 'REQUEST_METHOD' => 'GET' + ) + ); - $_SERVER['REQUEST_METHOD'] = 'GET'; - $_SERVER['PHP_SELF'] = '/plop/index.php'; $h = new Url($this->container); - $this->assertEquals('/plop/', $h->dir()); + $this->assertEquals('/kanboard/', $h->dir()); + + $this->container['request'] = new Request($this->container, array( + 'PHP_SELF' => '/index.php', + 'REQUEST_METHOD' => 'GET' + ) + ); - $_SERVER['REQUEST_METHOD'] = 'GET'; - $_SERVER['PHP_SELF'] = ''; $h = new Url($this->container); $this->assertEquals('/', $h->dir()); } public function testServer() { - $h = new Url($this->container); + $this->container['request'] = new Request($this->container, array( + 'PHP_SELF' => '/index.php', + 'REQUEST_METHOD' => 'GET', + 'SERVER_NAME' => 'localhost', + 'SERVER_PORT' => 80, + ) + ); + $h = new Url($this->container); $this->assertEquals('http://localhost/', $h->server()); - $_SERVER['PHP_SELF'] = '/'; - $_SERVER['SERVER_NAME'] = 'kb'; - $_SERVER['SERVER_PORT'] = 1234; + $this->container['request'] = new Request($this->container, array( + 'PHP_SELF' => '/index.php', + 'REQUEST_METHOD' => 'GET', + 'SERVER_NAME' => 'kb', + 'SERVER_PORT' => 1234, + ) + ); + $h = new Url($this->container); $this->assertEquals('http://kb:1234/', $h->server()); } public function testBase() { - $h = new Url($this->container); - - $this->assertEquals('http://localhost/', $h->base()); - - $_SERVER['PHP_SELF'] = '/'; - $_SERVER['SERVER_NAME'] = 'kb'; - $_SERVER['SERVER_PORT'] = 1234; + $this->container['request'] = new Request($this->container, array( + 'PHP_SELF' => '/index.php', + 'REQUEST_METHOD' => 'GET', + 'SERVER_NAME' => 'kb', + 'SERVER_PORT' => 1234, + ) + ); $h = new Url($this->container); $this->assertEquals('http://kb:1234/', $h->base()); |