summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFrederic Guillot <fred@kanboard.net>2015-12-22 19:06:03 +0100
committerFrederic Guillot <fred@kanboard.net>2015-12-22 19:06:03 +0100
commit6f9af3659c9146a2ac1b08d70610bf96398ec073 (patch)
tree21cbbca979a30d133d421881d6aa6e6af8096199
parentc83f589b22cd548c6de10bfb0c18f767ba7dffd8 (diff)
Added the possiblity to define custom routes from plugins
-rw-r--r--ChangeLog3
-rw-r--r--app/Core/Base.php1
-rw-r--r--app/Core/Http/Request.php49
-rw-r--r--app/Core/Http/Route.php188
-rw-r--r--app/Core/Http/Router.php199
-rw-r--r--app/Helper/App.php15
-rw-r--r--app/Helper/Url.php14
-rw-r--r--app/ServiceProvider/RouteProvider.php268
-rw-r--r--doc/plugin-routes.markdown85
-rw-r--r--doc/plugins.markdown1
-rw-r--r--index.php2
-rw-r--r--tests/units/Core/Http/RouteTest.php79
-rw-r--r--tests/units/Core/Http/RouterTest.php250
-rw-r--r--tests/units/Helper/UrlHelperTest.php77
14 files changed, 869 insertions, 362 deletions
diff --git a/ChangeLog b/ChangeLog
index 62ff45c7..e49fdb19 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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&amp;action=myaction&amp;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)
diff --git a/index.php b/index.php
index 2ca0731f..5a8485c5 100644
--- a/index.php
+++ b/index.php
@@ -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&amp;action=b&amp;d=e&amp;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());