From b3dbdca609700a80be6f31b894a7d8e479ad16cb Mon Sep 17 00:00:00 2001 From: Eskiso Date: Thu, 17 Dec 2015 09:35:18 +0000 Subject: [PATCH 1/6] Add reference to the createComment parameters I've only seen the reference column being used by integrations (gitbucket,etc), would be nice that from the API we could also use that column for other references. --- app/Api/Comment.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Api/Comment.php b/app/Api/Comment.php index 26b632e97..4c4027c06 100644 --- a/app/Api/Comment.php +++ b/app/Api/Comment.php @@ -25,12 +25,13 @@ class Comment extends \Kanboard\Core\Base return $this->comment->remove($comment_id); } - public function createComment($task_id, $user_id, $content) + public function createComment($task_id, $user_id, $content, $reference = '') { $values = array( 'task_id' => $task_id, 'user_id' => $user_id, 'comment' => $content, + 'reference' => $reference, ); list($valid, ) = $this->comment->validateCreation($values); From 59367d5450ce3607ee7a6c56849e7b5f67b7e42b Mon Sep 17 00:00:00 2001 From: Eskiso Date: Thu, 17 Dec 2015 09:36:38 +0000 Subject: [PATCH 2/6] Added MaxLenght to 50 for use with API/Comment new parameter --- app/Model/Comment.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Model/Comment.php b/app/Model/Comment.php index c7125a252..f60a96e3a 100644 --- a/app/Model/Comment.php +++ b/app/Model/Comment.php @@ -203,6 +203,7 @@ class Comment extends Base new Validators\Integer('id', t('This value must be an integer')), new Validators\Integer('task_id', t('This value must be an integer')), new Validators\Integer('user_id', t('This value must be an integer')), + new Validators\MaxLength('reference', t('The maximum length is %d characters', 50), 50), new Validators\Required('comment', t('Comment is required')) ); } From c83f589b22cd548c6de10bfb0c18f767ba7dffd8 Mon Sep 17 00:00:00 2001 From: Frederic Guillot Date: Thu, 17 Dec 2015 21:38:13 -0500 Subject: [PATCH 3/6] Show only active projects in board selector --- app/Controller/Activity.php | 2 +- app/Controller/Analytic.php | 2 +- app/Controller/App.php | 2 +- app/Controller/Base.php | 6 +++--- app/Controller/Config.php | 2 +- app/Controller/Currency.php | 2 +- app/Controller/Doc.php | 2 +- app/Controller/Gantt.php | 2 +- app/Controller/Group.php | 10 +++++----- app/Controller/Link.php | 2 +- app/Controller/Project.php | 4 ++-- app/Controller/Projectuser.php | 2 +- app/Controller/User.php | 8 ++++---- app/Model/ProjectPermission.php | 2 +- app/Model/ProjectUserRole.php | 14 +++++++++++++- 15 files changed, 37 insertions(+), 25 deletions(-) diff --git a/app/Controller/Activity.php b/app/Controller/Activity.php index 71d5e94f7..386583458 100644 --- a/app/Controller/Activity.php +++ b/app/Controller/Activity.php @@ -20,7 +20,7 @@ class Activity extends Base $project = $this->getProject(); $this->response->html($this->template->layout('activity/project', array( - 'board_selector' => $this->projectUserRole->getProjectsByUser($this->userSession->getId()), + 'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()), 'events' => $this->projectActivity->getProject($project['id']), 'project' => $project, 'title' => t('%s\'s activity', $project['name']) diff --git a/app/Controller/Analytic.php b/app/Controller/Analytic.php index bebb13fac..b2e27c586 100644 --- a/app/Controller/Analytic.php +++ b/app/Controller/Analytic.php @@ -21,7 +21,7 @@ class Analytic extends Base */ private function layout($template, array $params) { - $params['board_selector'] = $this->projectUserRole->getProjectsByUser($this->userSession->getId()); + $params['board_selector'] = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()); $params['content_for_sublayout'] = $this->template->render($template, $params); return $this->template->layout('analytic/layout', $params); diff --git a/app/Controller/App.php b/app/Controller/App.php index c596b4a81..bdd7fbcff 100644 --- a/app/Controller/App.php +++ b/app/Controller/App.php @@ -22,7 +22,7 @@ class App extends Base */ private function layout($template, array $params) { - $params['board_selector'] = $this->projectUserRole->getProjectsByUser($this->userSession->getId()); + $params['board_selector'] = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()); $params['content_for_sublayout'] = $this->template->render($template, $params); return $this->template->layout('app/layout', $params); diff --git a/app/Controller/Base.php b/app/Controller/Base.php index 35ceee096..6d0ecae98 100644 --- a/app/Controller/Base.php +++ b/app/Controller/Base.php @@ -190,7 +190,7 @@ abstract class Base extends \Kanboard\Core\Base $content = $this->template->render($template, $params); $params['task_content_for_layout'] = $content; $params['title'] = $params['task']['project_name'].' > '.$params['task']['title']; - $params['board_selector'] = $this->projectUserRole->getProjectsByUser($this->userSession->getId()); + $params['board_selector'] = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()); return $this->template->layout('task/layout', $params); } @@ -208,7 +208,7 @@ abstract class Base extends \Kanboard\Core\Base $content = $this->template->render($template, $params); $params['project_content_for_layout'] = $content; $params['title'] = $params['project']['name'] === $params['title'] ? $params['title'] : $params['project']['name'].' > '.$params['title']; - $params['board_selector'] = $this->projectUserRole->getProjectsByUser($this->userSession->getId()); + $params['board_selector'] = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()); $params['sidebar_template'] = $sidebar_template; return $this->template->layout('project/layout', $params); @@ -289,7 +289,7 @@ abstract class Base extends \Kanboard\Core\Base { $project = $this->getProject(); $search = $this->request->getStringParam('search', $this->userSession->getFilters($project['id'])); - $board_selector = $this->projectUserRole->getProjectsByUser($this->userSession->getId()); + $board_selector = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()); unset($board_selector[$project['id']]); $filters = array( diff --git a/app/Controller/Config.php b/app/Controller/Config.php index c813c795e..c7097da3f 100644 --- a/app/Controller/Config.php +++ b/app/Controller/Config.php @@ -20,7 +20,7 @@ class Config extends Base */ private function layout($template, array $params) { - $params['board_selector'] = $this->projectUserRole->getProjectsByUser($this->userSession->getId()); + $params['board_selector'] = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()); $params['values'] = $this->config->getAll(); $params['errors'] = array(); $params['config_content_for_layout'] = $this->template->render($template, $params); diff --git a/app/Controller/Currency.php b/app/Controller/Currency.php index 89e385699..4c5b8ee81 100644 --- a/app/Controller/Currency.php +++ b/app/Controller/Currency.php @@ -20,7 +20,7 @@ class Currency extends Base */ private function layout($template, array $params) { - $params['board_selector'] = $this->projectUserRole->getProjectsByUser($this->userSession->getId()); + $params['board_selector'] = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()); $params['config_content_for_layout'] = $this->template->render($template, $params); return $this->template->layout('config/layout', $params); diff --git a/app/Controller/Doc.php b/app/Controller/Doc.php index 08561aa10..a233b120d 100644 --- a/app/Controller/Doc.php +++ b/app/Controller/Doc.php @@ -53,7 +53,7 @@ class Doc extends Base } $this->response->html($this->template->layout('doc/show', $this->readFile($filename) + array( - 'board_selector' => $this->projectUserRole->getProjectsByUser($this->userSession->getId()), + 'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()), ))); } } diff --git a/app/Controller/Gantt.php b/app/Controller/Gantt.php index f3954a25f..ac0e6fad2 100644 --- a/app/Controller/Gantt.php +++ b/app/Controller/Gantt.php @@ -26,7 +26,7 @@ class Gantt extends Base $this->response->html($this->template->layout('gantt/projects', array( 'projects' => $this->projectGanttFormatter->filter($project_ids)->format(), 'title' => t('Gantt chart for all projects'), - 'board_selector' => $this->projectUserRole->getProjectsByUser($this->userSession->getId()), + 'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()), ))); } diff --git a/app/Controller/Group.php b/app/Controller/Group.php index 3e6505e93..3c9c4a077 100644 --- a/app/Controller/Group.php +++ b/app/Controller/Group.php @@ -25,7 +25,7 @@ class Group extends Base ->calculate(); $this->response->html($this->template->layout('group/index', array( - 'board_selector' => $this->projectUserRole->getProjectsByUser($this->userSession->getId()), + 'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()), 'title' => t('Groups').' ('.$paginator->getTotal().')', 'paginator' => $paginator, ))); @@ -49,7 +49,7 @@ class Group extends Base ->calculate(); $this->response->html($this->template->layout('group/users', array( - 'board_selector' => $this->projectUserRole->getProjectsByUser($this->userSession->getId()), + 'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()), 'title' => t('Members of %s', $group['name']).' ('.$paginator->getTotal().')', 'paginator' => $paginator, 'group' => $group, @@ -64,7 +64,7 @@ class Group extends Base public function create(array $values = array(), array $errors = array()) { $this->response->html($this->template->layout('group/create', array( - 'board_selector' => $this->projectUserRole->getProjectsByUser($this->userSession->getId()), + 'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()), 'errors' => $errors, 'values' => $values, 'title' => t('New group') @@ -105,7 +105,7 @@ class Group extends Base } $this->response->html($this->template->layout('group/edit', array( - 'board_selector' => $this->projectUserRole->getProjectsByUser($this->userSession->getId()), + 'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()), 'errors' => $errors, 'values' => $values, 'title' => t('Edit group') @@ -149,7 +149,7 @@ class Group extends Base } $this->response->html($this->template->layout('group/associate', array( - 'board_selector' => $this->projectUserRole->getProjectsByUser($this->userSession->getId()), + 'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()), 'users' => $this->user->prepareList($this->groupMember->getNotMembers($group_id)), 'group' => $group, 'errors' => $errors, diff --git a/app/Controller/Link.php b/app/Controller/Link.php index 33ec66884..2ae57b1a3 100644 --- a/app/Controller/Link.php +++ b/app/Controller/Link.php @@ -21,7 +21,7 @@ class Link extends Base */ private function layout($template, array $params) { - $params['board_selector'] = $this->projectUserRole->getProjectsByUser($this->userSession->getId()); + $params['board_selector'] = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()); $params['config_content_for_layout'] = $this->template->render($template, $params); return $this->template->layout('config/layout', $params); diff --git a/app/Controller/Project.php b/app/Controller/Project.php index 80c95aa23..5e75db4ee 100644 --- a/app/Controller/Project.php +++ b/app/Controller/Project.php @@ -33,7 +33,7 @@ class Project extends Base ->calculate(); $this->response->html($this->template->layout('project/index', array( - 'board_selector' => $this->projectUserRole->getProjectsByUser($this->userSession->getId()), + 'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()), 'paginator' => $paginator, 'nb_projects' => $nb_projects, 'title' => t('Projects').' ('.$nb_projects.')' @@ -302,7 +302,7 @@ class Project extends Base $is_private = isset($values['is_private']) && $values['is_private'] == 1; $this->response->html($this->template->layout('project/new', array( - 'board_selector' => $this->projectUserRole->getProjectsByUser($this->userSession->getId()), + 'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()), 'values' => $values, 'errors' => $errors, 'is_private' => $is_private, diff --git a/app/Controller/Projectuser.php b/app/Controller/Projectuser.php index 345957643..806ede770 100644 --- a/app/Controller/Projectuser.php +++ b/app/Controller/Projectuser.php @@ -24,7 +24,7 @@ class Projectuser extends Base */ private function layout($template, array $params) { - $params['board_selector'] = $this->projectUserRole->getProjectsByUser($this->userSession->getId()); + $params['board_selector'] = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()); $params['content_for_sublayout'] = $this->template->render($template, $params); $params['filter'] = array('user_id' => $params['user_id']); diff --git a/app/Controller/User.php b/app/Controller/User.php index 0968d5a5e..8b6df44c9 100644 --- a/app/Controller/User.php +++ b/app/Controller/User.php @@ -26,7 +26,7 @@ class User extends Base { $content = $this->template->render($template, $params); $params['user_content_for_layout'] = $content; - $params['board_selector'] = $this->projectUserRole->getProjectsByUser($this->userSession->getId()); + $params['board_selector'] = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()); if (isset($params['user'])) { $params['title'] = ($params['user']['name'] ?: $params['user']['username']).' (#'.$params['user']['id'].')'; @@ -51,7 +51,7 @@ class User extends Base $this->response->html( $this->template->layout('user/index', array( - 'board_selector' => $this->projectUserRole->getProjectsByUser($this->userSession->getId()), + 'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()), 'title' => t('Users').' ('.$paginator->getTotal().')', 'paginator' => $paginator, ))); @@ -72,7 +72,7 @@ class User extends Base $this->response->html( $this->template->layout('user/profile', array( - 'board_selector' => $this->projectUserRole->getProjectsByUser($this->userSession->getId()), + 'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()), 'title' => $user['name'] ?: $user['username'], 'user' => $user, ) @@ -92,7 +92,7 @@ class User extends Base 'timezones' => $this->config->getTimezones(true), 'languages' => $this->config->getLanguages(true), 'roles' => $this->role->getApplicationRoles(), - 'board_selector' => $this->projectUserRole->getProjectsByUser($this->userSession->getId()), + 'board_selector' => $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()), 'projects' => $this->project->getList(), 'errors' => $errors, 'values' => $values + array('role' => Role::APP_USER), diff --git a/app/Model/ProjectPermission.php b/app/Model/ProjectPermission.php index f74b85877..4ad9bbf14 100644 --- a/app/Model/ProjectPermission.php +++ b/app/Model/ProjectPermission.php @@ -100,7 +100,7 @@ class ProjectPermission extends Base */ public function getActiveProjectIds($user_id) { - return array_keys($this->projectUserRole->getProjectsByUser($user_id, array(Project::ACTIVE))); + return array_keys($this->projectUserRole->getActiveProjectsByUser($user_id)); } /** diff --git a/app/Model/ProjectUserRole.php b/app/Model/ProjectUserRole.php index 28e6c8c64..b2c386227 100644 --- a/app/Model/ProjectUserRole.php +++ b/app/Model/ProjectUserRole.php @@ -20,7 +20,19 @@ class ProjectUserRole extends Base const TABLE = 'project_has_users'; /** - * Get the list of project visible by the given user + * Get the list of active project for the given user + * + * @access public + * @param integer $user_id + * @return array + */ + public function getActiveProjectsByUser($user_id) + { + return $this->getProjectsByUser($user_id, $status = array(Project::ACTIVE)); + } + + /** + * Get the list of project visible for the given user * * @access public * @param integer $user_id From 6f9af3659c9146a2ac1b08d70610bf96398ec073 Mon Sep 17 00:00:00 2001 From: Frederic Guillot Date: Tue, 22 Dec 2015 19:06:03 +0100 Subject: [PATCH 4/6] Added the possiblity to define custom routes from plugins --- ChangeLog | 3 +- app/Core/Base.php | 1 + app/Core/Http/Request.php | 49 ++++- app/Core/Http/Route.php | 188 ++++++++++++++++++ app/Core/Http/Router.php | 203 +++++++------------ app/Helper/App.php | 15 +- app/Helper/Url.php | 14 +- app/ServiceProvider/RouteProvider.php | 260 ++++++++++++------------ doc/plugin-routes.markdown | 85 ++++++++ doc/plugins.markdown | 1 + index.php | 2 +- tests/units/Core/Http/RouteTest.php | 79 ++++++++ tests/units/Core/Http/RouterTest.php | 274 +++++++++++++++++++------- tests/units/Helper/UrlHelperTest.php | 77 ++++++-- 14 files changed, 879 insertions(+), 372 deletions(-) create mode 100644 app/Core/Http/Route.php create mode 100644 doc/plugin-routes.markdown create mode 100644 tests/units/Core/Http/RouteTest.php diff --git a/ChangeLog b/ChangeLog index 62ff45c71..e49fdb19e 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 2d00e52aa..a4cf787a9 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 c626f5b24..da95f2d40 100644 --- a/app/Core/Http/Request.php +++ b/app/Core/Http/Request.php @@ -41,6 +41,16 @@ class Request extends Base $this->cookies = empty($cookies) ? $_COOKIE : $cookies; } + /** + * Set GET parameters + * + * @param array $params + */ + public function setParams(array $params) + { + $this->get = array_merge($this->get, $params); + } + /** * Get query string string parameter * @@ -146,6 +156,17 @@ class Request extends Base return isset($this->files[$name]['tmp_name']) ? $this->files[$name]['tmp_name'] : ''; } + /** + * 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 * @@ -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 000000000..ed8314675 --- /dev/null +++ b/app/Core/Http/Route.php @@ -0,0 +1,188 @@ +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 0080b23a9..8b58a947d 100644 --- a/app/Core/Http/Router.php +++ b/app/Core/Http/Router.php @@ -6,13 +6,21 @@ use RuntimeException; use Kanboard\Core\Base; /** - * Router class + * Route Dispatcher * * @package http * @author Frederic Guillot */ class Router extends Base { + /** + * Plugin name + * + * @access private + * @var string + */ + private $plugin = ''; + /** * Controller * @@ -30,30 +38,14 @@ class Router extends Base private $action = ''; /** - * Store routes for path lookup - * - * @access private - * @var array - */ - private $paths = array(); - - /** - * Store routes for url lookup - * - * @access private - * @var array - */ - private $urls = array(); - - /** - * Get action + * Get plugin name * * @access public * @return string */ - public function getAction() + public function getPlugin() { - return $this->action; + return $this->plugin; } /** @@ -67,23 +59,32 @@ class Router extends Base return $this->controller; } + /** + * Get action + * + * @access public + * @return string + */ + public function getAction() + { + return $this->action; + } + /** * Get the path to compare patterns * * @access public - * @param string $uri - * @param string $query_string * @return string */ - public function getPath($uri, $query_string = '') + public function getPath() { - $path = substr($uri, strlen($this->helper->url->dir())); + $path = substr($this->request->getUri(), strlen($this->helper->url->dir())); - if (! empty($query_string)) { - $path = substr($path, 0, - strlen($query_string) - 1); + if ($this->request->getQueryString() !== '') { + $path = substr($path, 0, - strlen($this->request->getQueryString()) - 1); } - if (! empty($path) && $path{0} === '/') { + if ($path !== '' && $path{0} === '/') { $path = substr($path, 1); } @@ -91,140 +92,78 @@ class Router extends Base } /** - * Add route + * Find controller/action from the route table or from get arguments * * @access public - * @param string $path - * @param string $controller - * @param string $action - * @param array $params */ - public function addRoute($path, $controller, $action, array $params = array()) + public function dispatch() { - $pattern = explode('/', $path); + $controller = $this->request->getStringParam('controller'); + $action = $this->request->getStringParam('action'); + $plugin = $this->request->getStringParam('plugin'); - $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), - ); - } - - /** - * 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 ($controller === '') { + $route = $this->route->findRoute($this->getPath()); + $controller = $route['controller']; + $action = $route['action']; + $plugin = $route['plugin']; } - return array('app', 'index'); - } + $this->controller = ucfirst($this->sanitize($controller, 'app')); + $this->action = $this->sanitize($action, 'index'); + $this->plugin = ucfirst($this->sanitize($plugin)); - /** - * Find route url - * - * @access public - * @param string $controller - * @param string $action - * @param array $params - * @return string - */ - public function findUrl($controller, $action, array $params = array()) - { - if (! isset($this->urls[$controller][$action])) { - return ''; - } - - 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; - } - } - } - - 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(); + + if (! class_exists($class)) { + throw new RuntimeException('Controller not found'); } - $class = '\Kanboard\\'; - $class .= empty($plugin) ? 'Controller\\'.ucfirst($this->controller) : 'Plugin\\'.ucfirst($plugin).'\Controller\\'.ucfirst($this->controller); - - 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 33729f2b1..6e6028fce 100644 --- a/app/Helper/App.php +++ b/app/Helper/App.php @@ -2,14 +2,27 @@ 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 * diff --git a/app/Helper/Url.php b/app/Helper/Url.php index 6ada80688..720297cff 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 b7dba8e57..e58d50c74 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['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['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['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['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['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['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['route']->addRoute('project/:project_id/task/:task_id/close', 'taskstatus', 'close'); + $container['route']->addRoute('project/:project_id/task/:task_id/open', 'taskstatus', 'open'); - $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/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 000000000..b943bb19c --- /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 +url->link('My link', 'mycontroller', 'myaction', array('plugin' => 'myplugin')) ?> +``` + +Will generate this HTML: + +```html +My link +``` + +### Generate only the attribute `href`: + +```php +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 07447d4f6..809ddd72e 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 2ca0731f4..5a8485c59 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 000000000..5e44ad005 --- /dev/null +++ b/tests/units/Core/Http/RouteTest.php @@ -0,0 +1,79 @@ +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 c23802479..0b200ab51 100644 --- a/tests/units/Core/Http/RouterTest.php +++ b/tests/units/Core/Http/RouterTest.php @@ -1,81 +1,203 @@ 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')); - } - - public function testPath() - { - $r = 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')); - - $_SERVER['REQUEST_METHOD'] = 'GET'; - $_SERVER['PHP_SELF'] = '/a/index.php'; - - $this->assertEquals('b/c', $r->getPath('/a/b/c')); - $this->assertEquals('b/c', $r->getPath('/a/b/c?e=f', 'e=f')); - } - - public function testFindRouteWithEmptyTable() - { - $r = new Router($this->container); - $this->assertEquals(array('app', 'index'), $r->findRoute('')); - $this->assertEquals(array('app', 'index'), $r->findRoute('/')); - } - - 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')); - } - - 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 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')); - } - - 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')); + class FakeController { + public function beforeAction() {} + public function myAction() {} + } +} + +namespace Kanboard\Plugin\Myplugin\Controller { + + class FakeController { + public function beforeAction() {} + public function myAction() {} + } +} + +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 + { + public function testSanitize() + { + $dispatcher = new Router($this->container); + + $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')); + } + + public function testGetPath() + { + $dispatcher = new Router($this->container); + + $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()); + + $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()); + + $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 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', + ) + ); + + $this->setExpectedException('RuntimeException', 'Controller not found'); + + $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 cbacbc738..405e9462e 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( + 'label', + $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( + 'label', + $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()); From a16f3adc1efc2198038325a78bc153daab6db213 Mon Sep 17 00:00:00 2001 From: Frederic Guillot Date: Tue, 22 Dec 2015 19:16:26 +0100 Subject: [PATCH 5/6] Add unit test for LdapBackendGroupProvider --- .../units/Group/LdapBackendGroupProviderTest.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 tests/units/Group/LdapBackendGroupProviderTest.php diff --git a/tests/units/Group/LdapBackendGroupProviderTest.php b/tests/units/Group/LdapBackendGroupProviderTest.php new file mode 100644 index 000000000..67b5ef351 --- /dev/null +++ b/tests/units/Group/LdapBackendGroupProviderTest.php @@ -0,0 +1,16 @@ +setExpectedException('LogicException', 'LDAP group filter empty, check the parameter LDAP_GROUP_FILTER'); + + $backend = new LdapBackendGroupProvider($this->container); + $backend->getLdapGroupPattern('test'); + } +} From 8ff2032ea3fa49972fe076166c831719131e829d Mon Sep 17 00:00:00 2001 From: Frederic Guillot Date: Tue, 22 Dec 2015 19:35:41 +0100 Subject: [PATCH 6/6] Avoid PHP error when enabling LDAP group provider with PHP < 5.5 --- ChangeLog | 1 + app/Group/LdapBackendGroupProvider.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index e49fdb19e..a062848e8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -10,6 +10,7 @@ Bug fixes: - Fix wrong constant name that cause a PHP error in project management section - Fix pagination in group members listing +- Avoid PHP error when enabling LDAP group provider with PHP < 5.5 Version 1.0.22 -------------- diff --git a/app/Group/LdapBackendGroupProvider.php b/app/Group/LdapBackendGroupProvider.php index d65a98657..cad732c46 100644 --- a/app/Group/LdapBackendGroupProvider.php +++ b/app/Group/LdapBackendGroupProvider.php @@ -45,7 +45,7 @@ class LdapBackendGroupProvider extends Base implements GroupBackendProviderInter */ public function getLdapGroupPattern($input) { - if (empty(LDAP_GROUP_FILTER)) { + if (LDAP_GROUP_FILTER === '') { throw new LogicException('LDAP group filter empty, check the parameter LDAP_GROUP_FILTER'); }