diff options
Diffstat (limited to 'app')
102 files changed, 1411 insertions, 917 deletions
diff --git a/app/Controller/Action.php b/app/Controller/Action.php index 8881e8ec..40497a62 100644 --- a/app/Controller/Action.php +++ b/app/Controller/Action.php @@ -8,7 +8,7 @@ namespace Kanboard\Controller; * @package controller * @author Frederic Guillot */ -class Action extends Base +class Action extends BaseController { /** * List of automatic actions for a given project diff --git a/app/Controller/ActionCreation.php b/app/Controller/ActionCreation.php index 61b7b5e2..388b30e2 100644 --- a/app/Controller/ActionCreation.php +++ b/app/Controller/ActionCreation.php @@ -8,7 +8,7 @@ namespace Kanboard\Controller; * @package controller * @author Frederic Guillot */ -class ActionCreation extends Base +class ActionCreation extends BaseController { /** * Show the form (step 1) diff --git a/app/Controller/ActionProject.php b/app/Controller/ActionProject.php index e5063f73..10b3c9d4 100644 --- a/app/Controller/ActionProject.php +++ b/app/Controller/ActionProject.php @@ -8,7 +8,7 @@ namespace Kanboard\Controller; * @package controller * @author Frederic Guillot */ -class ActionProject extends Base +class ActionProject extends BaseController { public function project() { diff --git a/app/Controller/Activity.php b/app/Controller/Activity.php index 47a66e0a..0c6aa3f3 100644 --- a/app/Controller/Activity.php +++ b/app/Controller/Activity.php @@ -8,7 +8,7 @@ namespace Kanboard\Controller; * @package controller * @author Frederic Guillot */ -class Activity extends Base +class Activity extends BaseController { /** * Activity page for a project diff --git a/app/Controller/Analytic.php b/app/Controller/Analytic.php index 35bc3048..ba73c15c 100644 --- a/app/Controller/Analytic.php +++ b/app/Controller/Analytic.php @@ -11,7 +11,7 @@ use Kanboard\Model\Task as TaskModel; * @package controller * @author Frederic Guillot */ -class Analytic extends Base +class Analytic extends BaseController { /** * Show average Lead and Cycle time diff --git a/app/Controller/AppController.php b/app/Controller/AppController.php new file mode 100644 index 00000000..60bc154a --- /dev/null +++ b/app/Controller/AppController.php @@ -0,0 +1,45 @@ +<?php + +namespace Kanboard\Controller; + +use Kanboard\Core\Base; + +/** + * Class AppController + * + * @package Kanboard\Controller + */ +class AppController extends Base +{ + /** + * Forbidden page + * + * @access public + * @param bool $withoutLayout + */ + public function accessForbidden($withoutLayout = false) + { + if ($this->request->isAjax()) { + $this->response->json(array('message' => 'Access Forbidden'), 403); + } + + $this->response->html($this->helper->layout->app('app/forbidden', array( + 'title' => t('Access Forbidden'), + 'no_layout' => $withoutLayout, + ))); + } + + /** + * Page not found + * + * @access public + * @param boolean $withoutLayout + */ + public function notFound($withoutLayout = false) + { + $this->response->html($this->helper->layout->app('app/notfound', array( + 'title' => t('Page not found'), + 'no_layout' => $withoutLayout, + ))); + } +} diff --git a/app/Controller/Auth.php b/app/Controller/Auth.php index b882a720..cad44a34 100644 --- a/app/Controller/Auth.php +++ b/app/Controller/Auth.php @@ -8,7 +8,7 @@ namespace Kanboard\Controller; * @package controller * @author Frederic Guillot */ -class Auth extends Base +class Auth extends BaseController { /** * Display the form login @@ -20,16 +20,16 @@ class Auth extends Base public function login(array $values = array(), array $errors = array()) { if ($this->userSession->isLogged()) { - $this->response->redirect($this->helper->url->to('app', 'index')); + $this->response->redirect($this->helper->url->to('DashboardController', 'show')); + } else { + $this->response->html($this->helper->layout->app('auth/index', array( + 'captcha' => ! empty($values['username']) && $this->userLocking->hasCaptcha($values['username']), + 'errors' => $errors, + 'values' => $values, + 'no_layout' => true, + 'title' => t('Login') + ))); } - - $this->response->html($this->helper->layout->app('auth/index', array( - 'captcha' => ! empty($values['username']) && $this->userLocking->hasCaptcha($values['username']), - 'errors' => $errors, - 'values' => $values, - 'no_layout' => true, - 'title' => t('Login') - ))); } /** @@ -45,9 +45,9 @@ class Auth extends Base if ($valid) { $this->redirectAfterLogin(); + } else { + $this->login($values, $errors); } - - $this->login($values, $errors); } /** @@ -76,8 +76,8 @@ class Auth extends Base $redirect = $this->sessionStorage->redirectAfterLogin; unset($this->sessionStorage->redirectAfterLogin); $this->response->redirect($redirect); + } else { + $this->response->redirect($this->helper->url->to('DashboardController', 'show')); } - - $this->response->redirect($this->helper->url->to('app', 'index')); } } diff --git a/app/Controller/AvatarFile.php b/app/Controller/AvatarFile.php index 45cb1615..67c3efe1 100644 --- a/app/Controller/AvatarFile.php +++ b/app/Controller/AvatarFile.php @@ -11,7 +11,7 @@ use Kanboard\Core\Thumbnail; * @package controller * @author Frederic Guillot */ -class AvatarFile extends Base +class AvatarFile extends BaseController { /** * Display avatar page @@ -61,8 +61,8 @@ class AvatarFile extends Base $filename = $this->avatarFile->getFilename($user_id); $etag = md5($filename.$size); - $this->response->cache(365 * 86400, $etag); - $this->response->contentType('image/jpeg'); + $this->response->withCache(365 * 86400, $etag); + $this->response->withContentType('image/jpeg'); if ($this->request->getHeader('If-None-Match') !== '"'.$etag.'"') { $this->render($filename, $size); diff --git a/app/Controller/Base.php b/app/Controller/Base.php deleted file mode 100644 index beb56909..00000000 --- a/app/Controller/Base.php +++ /dev/null @@ -1,290 +0,0 @@ -<?php - -namespace Kanboard\Controller; - -use Kanboard\Core\Security\Role; - -/** - * Base controller - * - * @package controller - * @author Frederic Guillot - */ -abstract class Base extends \Kanboard\Core\Base -{ - /** - * Method executed before each action - * - * @access public - */ - public function beforeAction() - { - $this->sessionManager->open(); - $this->dispatcher->dispatch('app.bootstrap'); - $this->sendHeaders(); - $this->authenticationManager->checkCurrentSession(); - - if (! $this->applicationAuthorization->isAllowed($this->router->getController(), $this->router->getAction(), Role::APP_PUBLIC)) { - $this->handleAuthentication(); - $this->handlePostAuthentication(); - $this->checkApplicationAuthorization(); - $this->checkProjectAuthorization(); - } - } - - /** - * Send HTTP headers - * - * @access private - */ - private function sendHeaders() - { - // HTTP secure headers - $this->response->csp($this->container['cspRules']); - $this->response->nosniff(); - $this->response->xss(); - - // Allow the public board iframe inclusion - if (ENABLE_XFRAME && $this->router->getAction() !== 'readonly') { - $this->response->xframe(); - } - - if (ENABLE_HSTS) { - $this->response->hsts(); - } - } - - /** - * Check authentication - * - * @access private - */ - private function handleAuthentication() - { - if (! $this->userSession->isLogged() && ! $this->authenticationManager->preAuthentication()) { - if ($this->request->isAjax()) { - $this->response->text('Not Authorized', 401); - } - - $this->sessionStorage->redirectAfterLogin = $this->request->getUri(); - $this->response->redirect($this->helper->url->to('auth', 'login')); - } - } - - /** - * Handle Post-Authentication (2FA) - * - * @access private - */ - private function handlePostAuthentication() - { - $controller = strtolower($this->router->getController()); - $action = strtolower($this->router->getAction()); - $ignore = ($controller === 'twofactor' && in_array($action, array('code', 'check'))) || ($controller === 'auth' && $action === 'logout'); - - if ($ignore === false && $this->userSession->hasPostAuthentication() && ! $this->userSession->isPostAuthenticationValidated()) { - if ($this->request->isAjax()) { - $this->response->text('Not Authorized', 401); - } - - $this->response->redirect($this->helper->url->to('twofactor', 'code')); - } - } - - /** - * Check application authorization - * - * @access private - */ - private function checkApplicationAuthorization() - { - if (! $this->helper->user->hasAccess($this->router->getController(), $this->router->getAction())) { - $this->forbidden(); - } - } - - /** - * Check project authorization - * - * @access private - */ - private function checkProjectAuthorization() - { - $project_id = $this->request->getIntegerParam('project_id'); - $task_id = $this->request->getIntegerParam('task_id'); - - // Allow urls without "project_id" - if ($task_id > 0 && $project_id === 0) { - $project_id = $this->taskFinder->getProjectId($task_id); - } - - if ($project_id > 0 && ! $this->helper->user->hasProjectAccess($this->router->getController(), $this->router->getAction(), $project_id)) { - $this->forbidden(); - } - } - - /** - * Application not found page (404 error) - * - * @access protected - * @param boolean $no_layout Display the layout or not - */ - protected function notfound($no_layout = false) - { - $this->response->html($this->helper->layout->app('app/notfound', array( - 'title' => t('Page not found'), - 'no_layout' => $no_layout, - ))); - } - - /** - * Application forbidden page - * - * @access protected - * @param boolean $no_layout Display the layout or not - */ - protected function forbidden($no_layout = false) - { - if ($this->request->isAjax()) { - $this->response->text('Access Forbidden', 403); - } - - $this->response->html($this->helper->layout->app('app/forbidden', array( - 'title' => t('Access Forbidden'), - 'no_layout' => $no_layout, - ))); - } - - /** - * Check if the CSRF token from the URL is correct - * - * @access protected - */ - protected function checkCSRFParam() - { - if (! $this->token->validateCSRFToken($this->request->getStringParam('csrf_token'))) { - $this->forbidden(); - } - } - - /** - * Check webhook token - * - * @access protected - */ - protected function checkWebhookToken() - { - if ($this->config->get('webhook_token') !== $this->request->getStringParam('token')) { - $this->response->text('Not Authorized', 401); - } - } - - /** - * Common method to get a task for task views - * - * @access protected - * @return array - */ - protected function getTask() - { - $project_id = $this->request->getIntegerParam('project_id'); - $task = $this->taskFinder->getDetails($this->request->getIntegerParam('task_id')); - - if (empty($task)) { - $this->notfound(); - } - - if ($project_id !== 0 && $project_id != $task['project_id']) { - $this->forbidden(); - } - - return $task; - } - - /** - * Get Task or Project file - * - * @access protected - */ - protected function getFile() - { - $task_id = $this->request->getIntegerParam('task_id'); - $file_id = $this->request->getIntegerParam('file_id'); - $model = 'projectFile'; - - if ($task_id > 0) { - $model = 'taskFile'; - $project_id = $this->taskFinder->getProjectId($task_id); - - if ($project_id !== $this->request->getIntegerParam('project_id')) { - $this->forbidden(); - } - } - - $file = $this->$model->getById($file_id); - - if (empty($file)) { - $this->notfound(); - } - - $file['model'] = $model; - return $file; - } - - /** - * Common method to get a project - * - * @access protected - * @param integer $project_id Default project id - * @return array - */ - protected function getProject($project_id = 0) - { - $project_id = $this->request->getIntegerParam('project_id', $project_id); - $project = $this->project->getByIdWithOwner($project_id); - - if (empty($project)) { - $this->notfound(); - } - - return $project; - } - - /** - * Common method to get the user - * - * @access protected - * @return array - */ - protected function getUser() - { - $user = $this->user->getById($this->request->getIntegerParam('user_id', $this->userSession->getId())); - - if (empty($user)) { - $this->notfound(); - } - - if (! $this->userSession->isAdmin() && $this->userSession->getId() != $user['id']) { - $this->forbidden(); - } - - return $user; - } - - /** - * Get the current subtask - * - * @access protected - * @return array - */ - protected function getSubtask() - { - $subtask = $this->subtask->getById($this->request->getIntegerParam('subtask_id')); - - if (empty($subtask)) { - $this->notfound(); - } - - return $subtask; - } -} diff --git a/app/Controller/BaseController.php b/app/Controller/BaseController.php new file mode 100644 index 00000000..ad02f708 --- /dev/null +++ b/app/Controller/BaseController.php @@ -0,0 +1,158 @@ +<?php + +namespace Kanboard\Controller; + +use Kanboard\Core\Base; +use Kanboard\Core\Controller\AccessForbiddenException; +use Kanboard\Core\Controller\PageNotFoundException; + +/** + * Base Controller + * + * @package Kanboard\Controller + * @author Frederic Guillot + */ +abstract class BaseController extends Base +{ + /** + * Check if the CSRF token from the URL is correct + * + * @access protected + */ + protected function checkCSRFParam() + { + if (! $this->token->validateCSRFToken($this->request->getStringParam('csrf_token'))) { + throw new AccessForbiddenException(); + } + } + + /** + * Check webhook token + * + * @access protected + */ + protected function checkWebhookToken() + { + if ($this->config->get('webhook_token') !== $this->request->getStringParam('token')) { + $this->response->text('Not Authorized', 401); + } + } + + /** + * Common method to get a task for task views + * + * @access protected + * @return array + * @throws PageNotFoundException + * @throws AccessForbiddenException + */ + protected function getTask() + { + $project_id = $this->request->getIntegerParam('project_id'); + $task = $this->taskFinder->getDetails($this->request->getIntegerParam('task_id')); + + if (empty($task)) { + throw new PageNotFoundException(); + } + + if ($project_id !== 0 && $project_id != $task['project_id']) { + throw new AccessForbiddenException(); + } + + return $task; + } + + /** + * Get Task or Project file + * + * @access protected + * @return array + * @throws PageNotFoundException + * @throws AccessForbiddenException + */ + protected function getFile() + { + $task_id = $this->request->getIntegerParam('task_id'); + $file_id = $this->request->getIntegerParam('file_id'); + $model = 'projectFile'; + + if ($task_id > 0) { + $model = 'taskFile'; + $project_id = $this->taskFinder->getProjectId($task_id); + + if ($project_id !== $this->request->getIntegerParam('project_id')) { + throw new AccessForbiddenException(); + } + } + + $file = $this->$model->getById($file_id); + + if (empty($file)) { + throw new PageNotFoundException(); + } + + $file['model'] = $model; + return $file; + } + + /** + * Common method to get a project + * + * @access protected + * @param integer $project_id Default project id + * @return array + * @throws PageNotFoundException + */ + protected function getProject($project_id = 0) + { + $project_id = $this->request->getIntegerParam('project_id', $project_id); + $project = $this->project->getByIdWithOwner($project_id); + + if (empty($project)) { + throw new PageNotFoundException(); + } + + return $project; + } + + /** + * Common method to get the user + * + * @access protected + * @return array + * @throws PageNotFoundException + * @throws AccessForbiddenException + */ + protected function getUser() + { + $user = $this->user->getById($this->request->getIntegerParam('user_id', $this->userSession->getId())); + + if (empty($user)) { + throw new PageNotFoundException(); + } + + if (! $this->userSession->isAdmin() && $this->userSession->getId() != $user['id']) { + throw new AccessForbiddenException(); + } + + return $user; + } + + /** + * Get the current subtask + * + * @access protected + * @return array + * @throws PageNotFoundException + */ + protected function getSubtask() + { + $subtask = $this->subtask->getById($this->request->getIntegerParam('subtask_id')); + + if (empty($subtask)) { + throw new PageNotFoundException(); + } + + return $subtask; + } +} diff --git a/app/Controller/Board.php b/app/Controller/Board.php index 67e99b81..0f6b3b14 100644 --- a/app/Controller/Board.php +++ b/app/Controller/Board.php @@ -2,6 +2,7 @@ namespace Kanboard\Controller; +use Kanboard\Core\Controller\AccessForbiddenException; use Kanboard\Formatter\BoardFormatter; /** @@ -10,7 +11,7 @@ use Kanboard\Formatter\BoardFormatter; * @package controller * @author Frederic Guillot */ -class Board extends Base +class Board extends BaseController { /** * Display the public version of a board @@ -25,7 +26,7 @@ class Board extends Base // Token verification if (empty($project)) { - $this->forbidden(true); + throw AccessForbiddenException::getInstance()->withoutLayout(); } // Display the board with a specific layout @@ -74,7 +75,7 @@ class Board extends Base $project_id = $this->request->getIntegerParam('project_id'); if (! $project_id || ! $this->request->isAjax()) { - return $this->response->status(403); + throw new AccessForbiddenException(); } $values = $this->request->getJson(); @@ -88,10 +89,10 @@ class Board extends Base ); if (! $result) { - return $this->response->status(400); + $this->response->status(400); + } else { + $this->response->html($this->renderBoard($project_id), 201); } - - $this->response->html($this->renderBoard($project_id), 201); } /** @@ -105,14 +106,12 @@ class Board extends Base $timestamp = $this->request->getIntegerParam('timestamp'); if (! $project_id || ! $this->request->isAjax()) { - return $this->response->status(403); - } - - if (! $this->project->isModifiedSince($project_id, $timestamp)) { - return $this->response->status(304); + $this->response->status(403); + } elseif (! $this->project->isModifiedSince($project_id, $timestamp)) { + $this->response->status(304); + } else { + $this->response->html($this->renderBoard($project_id)); } - - return $this->response->html($this->renderBoard($project_id)); } /** @@ -125,7 +124,7 @@ class Board extends Base $project_id = $this->request->getIntegerParam('project_id'); if (! $project_id || ! $this->request->isAjax()) { - return $this->response->status(403); + throw new AccessForbiddenException(); } $values = $this->request->getJson(); @@ -177,6 +176,7 @@ class Board extends Base * * @access private * @param integer $project_id + * @return string */ private function renderBoard($project_id) { diff --git a/app/Controller/BoardPopover.php b/app/Controller/BoardPopover.php index b1a017f4..d3117f78 100644 --- a/app/Controller/BoardPopover.php +++ b/app/Controller/BoardPopover.php @@ -8,7 +8,7 @@ namespace Kanboard\Controller; * @package controller * @author Frederic Guillot */ -class BoardPopover extends Base +class BoardPopover extends BaseController { /** * Confirmation before to close all column tasks diff --git a/app/Controller/BoardTooltip.php b/app/Controller/BoardTooltip.php index c7819bc1..49d02ced 100644 --- a/app/Controller/BoardTooltip.php +++ b/app/Controller/BoardTooltip.php @@ -8,7 +8,7 @@ namespace Kanboard\Controller; * @package controller * @author Frederic Guillot */ -class BoardTooltip extends Base +class BoardTooltip extends BaseController { /** * Get links on mouseover diff --git a/app/Controller/Calendar.php b/app/Controller/Calendar.php index 2517286d..706c1d3b 100644 --- a/app/Controller/Calendar.php +++ b/app/Controller/Calendar.php @@ -14,7 +14,7 @@ use Kanboard\Model\Task as TaskModel; * @author Frederic Guillot * @author Timo Litzbarski */ -class Calendar extends Base +class Calendar extends BaseController { /** * Show calendar view for projects diff --git a/app/Controller/Captcha.php b/app/Controller/Captcha.php index fcf081ea..fb9d6fe3 100644 --- a/app/Controller/Captcha.php +++ b/app/Controller/Captcha.php @@ -10,7 +10,7 @@ use Gregwar\Captcha\CaptchaBuilder; * @package controller * @author Frederic Guillot */ -class Captcha extends Base +class Captcha extends BaseController { /** * Display captcha image @@ -19,7 +19,7 @@ class Captcha extends Base */ public function image() { - $this->response->contentType('image/jpeg'); + $this->response->withContentType('image/jpeg'); $builder = new CaptchaBuilder; $builder->build(); diff --git a/app/Controller/Category.php b/app/Controller/Category.php index 258a3b78..954d92cc 100644 --- a/app/Controller/Category.php +++ b/app/Controller/Category.php @@ -2,28 +2,29 @@ namespace Kanboard\Controller; +use Kanboard\Core\Controller\PageNotFoundException; + /** * Category management * * @package controller * @author Frederic Guillot */ -class Category extends Base +class Category extends BaseController { /** * Get the category (common method between actions) * * @access private - * @param integer $project_id * @return array + * @throws PageNotFoundException */ - private function getCategory($project_id) + private function getCategory() { $category = $this->category->getById($this->request->getIntegerParam('category_id')); if (empty($category)) { - $this->flash->failure(t('Category not found.')); - $this->response->redirect($this->helper->url->to('category', 'index', array('project_id' => $project_id))); + throw new PageNotFoundException(); } return $category; @@ -33,6 +34,9 @@ class Category extends Base * List of categories for a given project * * @access public + * @param array $values + * @param array $errors + * @throws PageNotFoundException */ public function index(array $values = array(), array $errors = array()) { @@ -62,24 +66,27 @@ class Category extends Base if ($valid) { if ($this->category->create($values)) { $this->flash->success(t('Your category have been created successfully.')); - $this->response->redirect($this->helper->url->to('category', 'index', array('project_id' => $project['id']))); + return $this->response->redirect($this->helper->url->to('category', 'index', array('project_id' => $project['id']))); } else { $this->flash->failure(t('Unable to create your category.')); } } - $this->index($values, $errors); + return $this->index($values, $errors); } /** * Edit a category (display the form) * * @access public + * @param array $values + * @param array $errors + * @throws PageNotFoundException */ public function edit(array $values = array(), array $errors = array()) { $project = $this->getProject(); - $category = $this->getCategory($project['id']); + $category = $this->getCategory(); $this->response->html($this->helper->layout->project('category/edit', array( 'values' => empty($values) ? $category : $values, @@ -104,13 +111,13 @@ class Category extends Base if ($valid) { if ($this->category->update($values)) { $this->flash->success(t('Your category have been updated successfully.')); - $this->response->redirect($this->helper->url->to('category', 'index', array('project_id' => $project['id']))); + return $this->response->redirect($this->helper->url->to('category', 'index', array('project_id' => $project['id']))); } else { $this->flash->failure(t('Unable to update your category.')); } } - $this->edit($values, $errors); + return $this->edit($values, $errors); } /** @@ -121,7 +128,7 @@ class Category extends Base public function confirm() { $project = $this->getProject(); - $category = $this->getCategory($project['id']); + $category = $this->getCategory(); $this->response->html($this->helper->layout->project('category/remove', array( 'project' => $project, @@ -139,7 +146,7 @@ class Category extends Base { $this->checkCSRFParam(); $project = $this->getProject(); - $category = $this->getCategory($project['id']); + $category = $this->getCategory(); if ($this->category->remove($category['id'])) { $this->flash->success(t('Category removed successfully.')); diff --git a/app/Controller/Column.php b/app/Controller/Column.php index bbe12fe1..294c31d8 100644 --- a/app/Controller/Column.php +++ b/app/Controller/Column.php @@ -2,13 +2,15 @@ namespace Kanboard\Controller; +use Kanboard\Core\Controller\AccessForbiddenException; + /** * Column controller * * @package controller * @author Frederic Guillot */ -class Column extends Base +class Column extends BaseController { /** * Display columns list @@ -31,6 +33,9 @@ class Column extends Base * Show form to create a new column * * @access public + * @param array $values + * @param array $errors + * @throws \Kanboard\Core\Controller\PageNotFoundException */ public function create(array $values = array(), array $errors = array()) { @@ -69,7 +74,7 @@ class Column extends Base } } - $this->create($values, $errors); + return $this->create($values, $errors); } /** @@ -108,13 +113,13 @@ class Column extends Base if ($valid) { if ($this->column->update($values['id'], $values['title'], $values['task_limit'], $values['description'])) { $this->flash->success(t('Board updated successfully.')); - $this->response->redirect($this->helper->url->to('column', 'index', array('project_id' => $project['id']))); + return $this->response->redirect($this->helper->url->to('column', 'index', array('project_id' => $project['id']))); } else { $this->flash->failure(t('Unable to update this board.')); } } - $this->edit($values, $errors); + return $this->edit($values, $errors); } /** @@ -129,10 +134,10 @@ class Column extends Base if (! empty($values) && isset($values['column_id']) && isset($values['position'])) { $result = $this->column->changePosition($project['id'], $values['column_id'], $values['position']); - return $this->response->json(array('result' => $result)); + $this->response->json(array('result' => $result)); + } else { + throw new AccessForbiddenException(); } - - $this->forbidden(); } /** diff --git a/app/Controller/Comment.php b/app/Controller/Comment.php index 0b39f390..83a67b41 100644 --- a/app/Controller/Comment.php +++ b/app/Controller/Comment.php @@ -2,30 +2,35 @@ namespace Kanboard\Controller; +use Kanboard\Core\Controller\AccessForbiddenException; +use Kanboard\Core\Controller\PageNotFoundException; + /** * Comment controller * * @package controller * @author Frederic Guillot */ -class Comment extends Base +class Comment extends BaseController { /** * Get the current comment * * @access private * @return array + * @throws PageNotFoundException + * @throws AccessForbiddenException */ private function getComment() { $comment = $this->comment->getById($this->request->getIntegerParam('comment_id')); if (empty($comment)) { - return $this->notfound(); + throw new PageNotFoundException(); } if (! $this->userSession->isAdmin() && $comment['user_id'] != $this->userSession->getId()) { - return $this->forbidden(); + throw new AccessForbiddenException(); } return $comment; @@ -35,6 +40,10 @@ class Comment extends Base * Add comment form * * @access public + * @param array $values + * @param array $errors + * @throws AccessForbiddenException + * @throws PageNotFoundException */ public function create(array $values = array(), array $errors = array()) { @@ -76,13 +85,17 @@ class Comment extends Base return $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), 'comments'), true); } - $this->create($values, $errors); + return $this->create($values, $errors); } /** * Edit a comment * * @access public + * @param array $values + * @param array $errors + * @throws AccessForbiddenException + * @throws PageNotFoundException */ public function edit(array $values = array(), array $errors = array()) { @@ -121,7 +134,7 @@ class Comment extends Base return $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])), false); } - $this->edit($values, $errors); + return $this->edit($values, $errors); } /** diff --git a/app/Controller/Config.php b/app/Controller/Config.php index 021534cf..ebb541d2 100644 --- a/app/Controller/Config.php +++ b/app/Controller/Config.php @@ -8,51 +8,9 @@ namespace Kanboard\Controller; * @package controller * @author Frederic Guillot */ -class Config extends Base +class Config extends BaseController { /** - * Common method between pages - * - * @access private - * @param string $redirect Action to redirect after saving the form - */ - private function common($redirect) - { - if ($this->request->isPost()) { - $values = $this->request->getValues(); - - switch ($redirect) { - case 'application': - $values += array('password_reset' => 0); - break; - case 'project': - $values += array( - 'subtask_restriction' => 0, - 'subtask_time_tracking' => 0, - 'cfd_include_closed_tasks' => 0, - 'disable_private_project' => 0, - ); - break; - case 'integrations': - $values += array('integration_gravatar' => 0); - break; - case 'calendar': - $values += array('calendar_user_subtasks_time_tracking' => 0); - break; - } - - if ($this->config->save($values)) { - $this->language->loadCurrentLanguage(); - $this->flash->success(t('Settings saved successfully.')); - } else { - $this->flash->failure(t('Unable to save your settings.')); - } - - $this->response->redirect($this->helper->url->to('config', $redirect)); - } - } - - /** * Display the about page * * @access public @@ -68,6 +26,45 @@ class Config extends Base } /** + * Save settings + * + */ + public function save() + { + $values = $this->request->getValues(); + $redirect = $this->request->getStringParam('redirect', 'application'); + + switch ($redirect) { + case 'application': + $values += array('password_reset' => 0); + break; + case 'project': + $values += array( + 'subtask_restriction' => 0, + 'subtask_time_tracking' => 0, + 'cfd_include_closed_tasks' => 0, + 'disable_private_project' => 0, + ); + break; + case 'integrations': + $values += array('integration_gravatar' => 0); + break; + case 'calendar': + $values += array('calendar_user_subtasks_time_tracking' => 0); + break; + } + + if ($this->config->save($values)) { + $this->language->loadCurrentLanguage(); + $this->flash->success(t('Settings saved successfully.')); + } else { + $this->flash->failure(t('Unable to save your settings.')); + } + + $this->response->redirect($this->helper->url->to('config', $redirect)); + } + + /** * Display the plugin page * * @access public @@ -87,8 +84,6 @@ class Config extends Base */ public function application() { - $this->common('application'); - $this->response->html($this->helper->layout->config('config/application', array( 'languages' => $this->language->getLanguages(), 'timezones' => $this->timezone->getTimezones(), @@ -106,8 +101,6 @@ class Config extends Base */ public function project() { - $this->common('project'); - $this->response->html($this->helper->layout->config('config/project', array( 'colors' => $this->color->getList(), 'default_columns' => implode(', ', $this->board->getDefaultColumns()), @@ -122,8 +115,6 @@ class Config extends Base */ public function board() { - $this->common('board'); - $this->response->html($this->helper->layout->config('config/board', array( 'title' => t('Settings').' > '.t('Board settings'), ))); @@ -136,8 +127,6 @@ class Config extends Base */ public function calendar() { - $this->common('calendar'); - $this->response->html($this->helper->layout->config('config/calendar', array( 'title' => t('Settings').' > '.t('Calendar settings'), ))); @@ -150,8 +139,6 @@ class Config extends Base */ public function integrations() { - $this->common('integrations'); - $this->response->html($this->helper->layout->config('config/integrations', array( 'title' => t('Settings').' > '.t('Integrations'), ))); @@ -164,8 +151,6 @@ class Config extends Base */ public function webhook() { - $this->common('webhook'); - $this->response->html($this->helper->layout->config('config/webhook', array( 'title' => t('Settings').' > '.t('Webhook settings'), ))); @@ -191,7 +176,7 @@ class Config extends Base public function downloadDb() { $this->checkCSRFParam(); - $this->response->forceDownload('db.sqlite.gz'); + $this->response->withDownload('db.sqlite.gz'); $this->response->binary($this->config->downloadDatabase()); } diff --git a/app/Controller/Currency.php b/app/Controller/Currency.php index 3e0de905..872d6929 100644 --- a/app/Controller/Currency.php +++ b/app/Controller/Currency.php @@ -8,12 +8,14 @@ namespace Kanboard\Controller; * @package controller * @author Frederic Guillot */ -class Currency extends Base +class Currency extends BaseController { /** * Display all currency rates and form * * @access public + * @param array $values + * @param array $errors */ public function index(array $values = array(), array $errors = array()) { @@ -40,13 +42,13 @@ class Currency extends Base if ($valid) { if ($this->currency->create($values['currency'], $values['rate'])) { $this->flash->success(t('The currency rate have been added successfully.')); - $this->response->redirect($this->helper->url->to('currency', 'index')); + return $this->response->redirect($this->helper->url->to('currency', 'index')); } else { $this->flash->failure(t('Unable to add this currency rate.')); } } - $this->index($values, $errors); + return $this->index($values, $errors); } /** diff --git a/app/Controller/Customfilter.php b/app/Controller/Customfilter.php index 41da0b11..d0794366 100644 --- a/app/Controller/Customfilter.php +++ b/app/Controller/Customfilter.php @@ -2,6 +2,7 @@ namespace Kanboard\Controller; +use Kanboard\Core\Controller\AccessForbiddenException; use Kanboard\Core\Security\Role; /** @@ -10,7 +11,7 @@ use Kanboard\Core\Security\Role; * @package controller * @author Timo Litzbarski */ -class Customfilter extends Base +class Customfilter extends BaseController { /** * Display list of filters @@ -47,13 +48,13 @@ class Customfilter extends Base if ($valid) { if ($this->customFilter->create($values)) { $this->flash->success(t('Your custom filter have been created successfully.')); - $this->response->redirect($this->helper->url->to('customfilter', 'index', array('project_id' => $project['id']))); + return $this->response->redirect($this->helper->url->to('customfilter', 'index', array('project_id' => $project['id']))); } else { $this->flash->failure(t('Unable to create your custom filter.')); } } - $this->index($values, $errors); + return $this->index($values, $errors); } /** @@ -143,13 +144,13 @@ class Customfilter extends Base if ($valid) { if ($this->customFilter->update($values)) { $this->flash->success(t('Your custom filter have been updated successfully.')); - $this->response->redirect($this->helper->url->to('customfilter', 'index', array('project_id' => $project['id']))); + return $this->response->redirect($this->helper->url->to('customfilter', 'index', array('project_id' => $project['id']))); } else { $this->flash->failure(t('Unable to update custom filter.')); } } - $this->edit($values, $errors); + return $this->edit($values, $errors); } private function checkPermission(array $project, array $filter) @@ -157,7 +158,7 @@ class Customfilter extends Base $user_id = $this->userSession->getId(); if ($filter['user_id'] != $user_id && ($this->projectUserRole->getUserRole($project['id'], $user_id) === Role::PROJECT_MANAGER || ! $this->userSession->isAdmin())) { - $this->forbidden(); + throw new AccessForbiddenException(); } } } diff --git a/app/Controller/App.php b/app/Controller/DashboardController.php index 01f733ff..b05cd209 100644 --- a/app/Controller/App.php +++ b/app/Controller/DashboardController.php @@ -6,12 +6,12 @@ use Kanboard\Model\Project as ProjectModel; use Kanboard\Model\Subtask as SubtaskModel; /** - * Application controller + * Dashboard Controller * - * @package controller + * @package Kanboard\Controller * @author Frederic Guillot */ -class App extends Base +class DashboardController extends BaseController { /** * Get project pagination @@ -25,7 +25,7 @@ class App extends Base private function getProjectPaginator($user_id, $action, $max) { return $this->paginator - ->setUrl('app', $action, array('pagination' => 'projects', 'user_id' => $user_id)) + ->setUrl('DashboardController', $action, array('pagination' => 'projects', 'user_id' => $user_id)) ->setMax($max) ->setOrder(ProjectModel::TABLE.'.name') ->setQuery($this->project->getQueryColumnStats($this->projectPermission->getActiveProjectIds($user_id))) @@ -44,7 +44,7 @@ class App extends Base private function getTaskPaginator($user_id, $action, $max) { return $this->paginator - ->setUrl('app', $action, array('pagination' => 'tasks', 'user_id' => $user_id)) + ->setUrl('DashboardController', $action, array('pagination' => 'tasks', 'user_id' => $user_id)) ->setMax($max) ->setOrder('tasks.id') ->setQuery($this->taskFinder->getUserQuery($user_id)) @@ -63,7 +63,7 @@ class App extends Base private function getSubtaskPaginator($user_id, $action, $max) { return $this->paginator - ->setUrl('app', $action, array('pagination' => 'subtasks', 'user_id' => $user_id)) + ->setUrl('DashboardController', $action, array('pagination' => 'subtasks', 'user_id' => $user_id)) ->setMax($max) ->setOrder('tasks.id') ->setQuery($this->subtask->getUserQuery($user_id, array(SubTaskModel::STATUS_TODO, SubtaskModel::STATUS_INPROGRESS))) @@ -71,25 +71,15 @@ class App extends Base } /** - * Check if the user is connected - * - * @access public - */ - public function status() - { - $this->response->text('OK'); - } - - /** * Dashboard overview * * @access public */ - public function index() + public function show() { $user = $this->getUser(); - $this->response->html($this->helper->layout->dashboard('app/overview', array( + $this->response->html($this->helper->layout->dashboard('dashboard/show', array( 'title' => t('Dashboard'), 'project_paginator' => $this->getProjectPaginator($user['id'], 'index', 10), 'task_paginator' => $this->getTaskPaginator($user['id'], 'index', 10), @@ -107,7 +97,7 @@ class App extends Base { $user = $this->getUser(); - $this->response->html($this->helper->layout->dashboard('app/tasks', array( + $this->response->html($this->helper->layout->dashboard('dashboard/tasks', array( 'title' => t('My tasks'), 'paginator' => $this->getTaskPaginator($user['id'], 'tasks', 50), 'user' => $user, @@ -123,7 +113,7 @@ class App extends Base { $user = $this->getUser(); - $this->response->html($this->helper->layout->dashboard('app/subtasks', array( + $this->response->html($this->helper->layout->dashboard('dashboard/subtasks', array( 'title' => t('My subtasks'), 'paginator' => $this->getSubtaskPaginator($user['id'], 'subtasks', 50), 'user' => $user, @@ -139,7 +129,7 @@ class App extends Base { $user = $this->getUser(); - $this->response->html($this->helper->layout->dashboard('app/projects', array( + $this->response->html($this->helper->layout->dashboard('dashboard/projects', array( 'title' => t('My projects'), 'paginator' => $this->getProjectPaginator($user['id'], 'projects', 25), 'user' => $user, @@ -155,7 +145,7 @@ class App extends Base { $user = $this->getUser(); - $this->response->html($this->helper->layout->dashboard('app/activity', array( + $this->response->html($this->helper->layout->dashboard('dashboard/activity', array( 'title' => t('My activity stream'), 'events' => $this->helper->projectActivity->getProjectsEvents($this->projectPermission->getActiveProjectIds($user['id']), 100), 'user' => $user, @@ -169,7 +159,7 @@ class App extends Base */ public function calendar() { - $this->response->html($this->helper->layout->dashboard('app/calendar', array( + $this->response->html($this->helper->layout->dashboard('dashboard/calendar', array( 'title' => t('My calendar'), 'user' => $this->getUser(), ))); @@ -184,7 +174,7 @@ class App extends Base { $user = $this->getUser(); - $this->response->html($this->helper->layout->dashboard('app/notifications', array( + $this->response->html($this->helper->layout->dashboard('dashboard/notifications', array( 'title' => t('My notifications'), 'notifications' => $this->userUnreadNotification->getAll($user['id']), 'user' => $user, diff --git a/app/Controller/Doc.php b/app/Controller/Doc.php index 219ef8ad..5caf5f5f 100644 --- a/app/Controller/Doc.php +++ b/app/Controller/Doc.php @@ -10,7 +10,7 @@ use Parsedown; * @package controller * @author Frederic Guillot */ -class Doc extends Base +class Doc extends BaseController { public function show() { diff --git a/app/Controller/Export.php b/app/Controller/Export.php index c2ff652e..f5783b72 100644 --- a/app/Controller/Export.php +++ b/app/Controller/Export.php @@ -8,12 +8,18 @@ namespace Kanboard\Controller; * @package controller * @author Frederic Guillot */ -class Export extends Base +class Export extends BaseController { /** * Common export method * * @access private + * @param string $model + * @param string $method + * @param string $filename + * @param string $action + * @param string $page_title + * @throws \Kanboard\Core\Controller\PageNotFoundException */ private function common($model, $method, $filename, $action, $page_title) { @@ -23,7 +29,7 @@ class Export extends Base if ($from && $to) { $data = $this->$model->$method($project['id'], $from, $to); - $this->response->forceDownload($filename.'.csv'); + $this->response->withDownload($filename.'.csv'); $this->response->csv($data); } diff --git a/app/Controller/Feed.php b/app/Controller/Feed.php index f8b3d320..7554a499 100644 --- a/app/Controller/Feed.php +++ b/app/Controller/Feed.php @@ -2,13 +2,15 @@ namespace Kanboard\Controller; +use Kanboard\Core\Controller\AccessForbiddenException; + /** * Atom/RSS Feed controller * * @package controller * @author Frederic Guillot */ -class Feed extends Base +class Feed extends BaseController { /** * RSS feed for a user @@ -22,7 +24,7 @@ class Feed extends Base // Token verification if (empty($user)) { - $this->forbidden(true); + throw AccessForbiddenException::getInstance()->withoutLayout(); } $this->response->xml($this->template->render('feed/user', array( @@ -41,9 +43,8 @@ class Feed extends Base $token = $this->request->getStringParam('token'); $project = $this->project->getByToken($token); - // Token verification if (empty($project)) { - $this->forbidden(true); + throw AccessForbiddenException::getInstance()->withoutLayout(); } $this->response->xml($this->template->render('feed/project', array( diff --git a/app/Controller/FileViewer.php b/app/Controller/FileViewer.php index 3be4ea14..a990e12a 100644 --- a/app/Controller/FileViewer.php +++ b/app/Controller/FileViewer.php @@ -10,7 +10,7 @@ use Kanboard\Core\ObjectStorage\ObjectStorageException; * @package controller * @author Frederic Guillot */ -class FileViewer extends Base +class FileViewer extends BaseController { /** * Get file content from object storage @@ -24,11 +24,9 @@ class FileViewer extends Base $content = ''; try { - if ($file['is_image'] == 0) { $content = $this->objectStorage->get($file['path']); } - } catch (ObjectStorageException $e) { $this->logger->error($e->getMessage()); } @@ -68,17 +66,18 @@ class FileViewer extends Base { $file = $this->getFile(); $etag = md5($file['path']); - $this->response->contentType($this->helper->file->getImageMimeType($file['name'])); - $this->response->cache(5 * 86400, $etag); + $this->response->withContentType($this->helper->file->getImageMimeType($file['name'])); + $this->response->withCache(5 * 86400, $etag); if ($this->request->getHeader('If-None-Match') === '"'.$etag.'"') { - return $this->response->status(304); - } + $this->response->status(304); + } else { - try { - $this->objectStorage->output($file['path']); - } catch (ObjectStorageException $e) { - $this->logger->error($e->getMessage()); + try { + $this->objectStorage->output($file['path']); + } catch (ObjectStorageException $e) { + $this->logger->error($e->getMessage()); + } } } @@ -94,23 +93,24 @@ class FileViewer extends Base $filename = $this->$model->getThumbnailPath($file['path']); $etag = md5($filename); - $this->response->cache(5 * 86400, $etag); - $this->response->contentType('image/jpeg'); + $this->response->withCache(5 * 86400, $etag); + $this->response->withContentType('image/jpeg'); if ($this->request->getHeader('If-None-Match') === '"'.$etag.'"') { - return $this->response->status(304); - } + $this->response->status(304); + } else { - try { + try { - $this->objectStorage->output($filename); - } catch (ObjectStorageException $e) { - $this->logger->error($e->getMessage()); + $this->objectStorage->output($filename); + } catch (ObjectStorageException $e) { + $this->logger->error($e->getMessage()); - // Try to generate thumbnail on the fly for images uploaded before Kanboard < 1.0.19 - $data = $this->objectStorage->get($file['path']); - $this->$model->generateThumbnailFromData($file['path'], $data); - $this->objectStorage->output($this->$model->getThumbnailPath($file['path'])); + // Try to generate thumbnail on the fly for images uploaded before Kanboard < 1.0.19 + $data = $this->objectStorage->get($file['path']); + $this->$model->generateThumbnailFromData($file['path'], $data); + $this->objectStorage->output($this->$model->getThumbnailPath($file['path'])); + } } } @@ -123,7 +123,7 @@ class FileViewer extends Base { try { $file = $this->getFile(); - $this->response->forceDownload($file['name']); + $this->response->withDownload($file['name']); $this->objectStorage->output($file['path']); } catch (ObjectStorageException $e) { $this->logger->error($e->getMessage()); diff --git a/app/Controller/Gantt.php b/app/Controller/Gantt.php index 5e9ad55e..d062b2fe 100644 --- a/app/Controller/Gantt.php +++ b/app/Controller/Gantt.php @@ -17,7 +17,7 @@ use Kanboard\Model\Project as ProjectModel; * @package controller * @author Frederic Guillot */ -class Gantt extends Base +class Gantt extends BaseController { /** * Show Gantt chart for all projects @@ -53,9 +53,9 @@ class Gantt extends Base if (! $result) { $this->response->json(array('message' => 'Unable to save project'), 400); + } else { + $this->response->json(array('message' => 'OK'), 201); } - - $this->response->json(array('message' => 'OK'), 201); } /** @@ -99,15 +99,18 @@ class Gantt extends Base if (! $result) { $this->response->json(array('message' => 'Unable to save task'), 400); + } else { + $this->response->json(array('message' => 'OK'), 201); } - - $this->response->json(array('message' => 'OK'), 201); } /** * Simplified form to create a new task * * @access public + * @param array $values + * @param array $errors + * @throws \Kanboard\Core\Controller\PageNotFoundException */ public function task(array $values = array(), array $errors = array()) { @@ -151,12 +154,12 @@ class Gantt extends Base if ($task_id !== false) { $this->flash->success(t('Task created successfully.')); - $this->response->redirect($this->helper->url->to('gantt', 'project', array('project_id' => $project['id']))); + return $this->response->redirect($this->helper->url->to('gantt', 'project', array('project_id' => $project['id']))); } else { $this->flash->failure(t('Unable to create your task.')); } } - $this->task($values, $errors); + return $this->task($values, $errors); } } diff --git a/app/Controller/Group.php b/app/Controller/Group.php index fa47f428..2f91e72a 100644 --- a/app/Controller/Group.php +++ b/app/Controller/Group.php @@ -8,7 +8,7 @@ namespace Kanboard\Controller; * @package controller * @author Frederic Guillot */ -class Group extends Base +class Group extends BaseController { /** * List all groups @@ -58,6 +58,8 @@ class Group extends Base * Display a form to create a new group * * @access public + * @param array $values + * @param array $errors */ public function create(array $values = array(), array $errors = array()) { @@ -81,19 +83,21 @@ class Group extends Base if ($valid) { if ($this->group->create($values['name']) !== false) { $this->flash->success(t('Group created successfully.')); - $this->response->redirect($this->helper->url->to('group', 'index')); + return $this->response->redirect($this->helper->url->to('group', 'index')); } else { $this->flash->failure(t('Unable to create your group.')); } } - $this->create($values, $errors); + return $this->create($values, $errors); } /** * Display a form to update a group * * @access public + * @param array $values + * @param array $errors */ public function edit(array $values = array(), array $errors = array()) { @@ -121,24 +125,26 @@ class Group extends Base if ($valid) { if ($this->group->update($values) !== false) { $this->flash->success(t('Group updated successfully.')); - $this->response->redirect($this->helper->url->to('group', 'index')); + return $this->response->redirect($this->helper->url->to('group', 'index')); } else { $this->flash->failure(t('Unable to update your group.')); } } - $this->edit($values, $errors); + return $this->edit($values, $errors); } /** * Form to associate a user to a group * * @access public + * @param array $values + * @param array $errors */ public function associate(array $values = array(), array $errors = array()) { $group_id = $this->request->getIntegerParam('group_id'); - $group = $this->group->getbyId($group_id); + $group = $this->group->getById($group_id); if (empty($values)) { $values['group_id'] = $group_id; @@ -165,13 +171,13 @@ class Group extends Base if (isset($values['group_id']) && isset($values['user_id'])) { if ($this->groupMember->addUser($values['group_id'], $values['user_id'])) { $this->flash->success(t('Group member added successfully.')); - $this->response->redirect($this->helper->url->to('group', 'users', array('group_id' => $values['group_id']))); + return $this->response->redirect($this->helper->url->to('group', 'users', array('group_id' => $values['group_id']))); } else { $this->flash->failure(t('Unable to add group member.')); } } - $this->associate($values); + return $this->associate($values); } /** diff --git a/app/Controller/GroupHelper.php b/app/Controller/GroupHelper.php index 429614c2..06e5ec71 100644 --- a/app/Controller/GroupHelper.php +++ b/app/Controller/GroupHelper.php @@ -10,7 +10,7 @@ use Kanboard\Formatter\GroupAutoCompleteFormatter; * @package controller * @author Frederic Guillot */ -class GroupHelper extends Base +class GroupHelper extends BaseController { /** * Group auto-completion (Ajax) diff --git a/app/Controller/Ical.php b/app/Controller/Ical.php index 8fe97b46..091ea5f4 100644 --- a/app/Controller/Ical.php +++ b/app/Controller/Ical.php @@ -2,6 +2,7 @@ namespace Kanboard\Controller; +use Kanboard\Core\Controller\AccessForbiddenException; use Kanboard\Core\Filter\QueryBuilder; use Kanboard\Filter\TaskAssigneeFilter; use Kanboard\Filter\TaskProjectFilter; @@ -16,7 +17,7 @@ use Eluceo\iCal\Component\Calendar as iCalendar; * @package controller * @author Frederic Guillot */ -class Ical extends Base +class Ical extends BaseController { /** * Get user iCalendar @@ -30,7 +31,7 @@ class Ical extends Base // Token verification if (empty($user)) { - $this->forbidden(true); + throw AccessForbiddenException::getInstance()->withoutLayout(); } // Common filter @@ -61,7 +62,7 @@ class Ical extends Base // Token verification if (empty($project)) { - $this->forbidden(true); + throw AccessForbiddenException::getInstance()->withoutLayout(); } // Common filter @@ -84,6 +85,8 @@ class Ical extends Base * Common method to render iCal events * * @access private + * @param QueryBuilder $queryBuilder + * @param iCalendar $calendar */ private function renderCalendar(QueryBuilder $queryBuilder, iCalendar $calendar) { diff --git a/app/Controller/Link.php b/app/Controller/Link.php index ec7ab1af..d28f5e4e 100644 --- a/app/Controller/Link.php +++ b/app/Controller/Link.php @@ -2,6 +2,8 @@ namespace Kanboard\Controller; +use Kanboard\Core\Controller\PageNotFoundException; + /** * Link controller * @@ -9,20 +11,21 @@ namespace Kanboard\Controller; * @author Olivier Maridat * @author Frederic Guillot */ -class Link extends Base +class Link extends BaseController { /** * Get the current link * * @access private * @return array + * @throws PageNotFoundException */ private function getLink() { $link = $this->link->getById($this->request->getIntegerParam('link_id')); if (empty($link)) { - $this->notfound(); + throw new PageNotFoundException(); } return $link; @@ -32,6 +35,8 @@ class Link extends Base * List of links * * @access public + * @param array $values + * @param array $errors */ public function index(array $values = array(), array $errors = array()) { @@ -56,19 +61,22 @@ class Link extends Base if ($valid) { if ($this->link->create($values['label'], $values['opposite_label']) !== false) { $this->flash->success(t('Link added successfully.')); - $this->response->redirect($this->helper->url->to('link', 'index')); + return $this->response->redirect($this->helper->url->to('link', 'index')); } else { $this->flash->failure(t('Unable to create your link.')); } } - $this->index($values, $errors); + return $this->index($values, $errors); } /** * Edit form * * @access public + * @param array $values + * @param array $errors + * @throws PageNotFoundException */ public function edit(array $values = array(), array $errors = array()) { @@ -97,13 +105,13 @@ class Link extends Base if ($valid) { if ($this->link->update($values)) { $this->flash->success(t('Link updated successfully.')); - $this->response->redirect($this->helper->url->to('link', 'index')); + return $this->response->redirect($this->helper->url->to('link', 'index')); } else { $this->flash->failure(t('Unable to update your link.')); } } - $this->edit($values, $errors); + return $this->edit($values, $errors); } /** diff --git a/app/Controller/Listing.php b/app/Controller/Listing.php index 2024ff03..93a7b836 100644 --- a/app/Controller/Listing.php +++ b/app/Controller/Listing.php @@ -11,7 +11,7 @@ use Kanboard\Model\Task as TaskModel; * @package controller * @author Frederic Guillot */ -class Listing extends Base +class Listing extends BaseController { /** * Show list view for projects diff --git a/app/Controller/Oauth.php b/app/Controller/Oauth.php index 12b91144..c38654be 100644 --- a/app/Controller/Oauth.php +++ b/app/Controller/Oauth.php @@ -10,7 +10,7 @@ use Kanboard\Core\Security\OAuthAuthenticationProviderInterface; * @package controller * @author Frederic Guillot */ -class Oauth extends Base +class Oauth extends BaseController { /** * Redirect to the provider if no code received @@ -106,7 +106,7 @@ class Oauth extends Base protected function authenticate($providerName) { if ($this->authenticationManager->oauthAuthentication($providerName)) { - $this->response->redirect($this->helper->url->to('app', 'index')); + $this->response->redirect($this->helper->url->to('DashboardController', 'show')); } else { $this->authenticationFailure(t('External authentication failed')); } diff --git a/app/Controller/PasswordReset.php b/app/Controller/PasswordReset.php index f6a0eb8e..7050d6d2 100644 --- a/app/Controller/PasswordReset.php +++ b/app/Controller/PasswordReset.php @@ -2,13 +2,15 @@ namespace Kanboard\Controller; +use Kanboard\Core\Controller\AccessForbiddenException; + /** * Password Reset Controller * * @package controller * @author Frederic Guillot */ -class PasswordReset extends Base +class PasswordReset extends BaseController { /** * Show the form to reset the password @@ -37,9 +39,9 @@ class PasswordReset extends Base if ($valid) { $this->sendEmail($values['username']); $this->response->redirect($this->helper->url->to('auth', 'login')); + } else { + $this->create($values, $errors); } - - $this->create($values, $errors); } /** @@ -59,9 +61,9 @@ class PasswordReset extends Base 'values' => $values, 'no_layout' => true, ))); + } else { + $this->response->redirect($this->helper->url->to('auth', 'login')); } - - $this->response->redirect($this->helper->url->to('auth', 'login')); } /** @@ -83,10 +85,10 @@ class PasswordReset extends Base $this->passwordReset->disable($user_id); } - $this->response->redirect($this->helper->url->to('auth', 'login')); + return $this->response->redirect($this->helper->url->to('auth', 'login')); } - $this->change($values, $errors); + return $this->change($values, $errors); } /** @@ -114,7 +116,7 @@ class PasswordReset extends Base private function checkActivation() { if ($this->config->get('password_reset', 0) == 0) { - $this->response->redirect($this->helper->url->to('auth', 'login')); + throw AccessForbiddenException::getInstance()->withoutLayout(); } } } diff --git a/app/Controller/Project.php b/app/Controller/Project.php index cdfbd94a..22a9ad30 100644 --- a/app/Controller/Project.php +++ b/app/Controller/Project.php @@ -8,7 +8,7 @@ namespace Kanboard\Controller; * @package controller * @author Frederic Guillot */ -class Project extends Base +class Project extends BaseController { /** * List of projects @@ -75,12 +75,9 @@ class Project extends Base } $this->response->redirect($this->helper->url->to('project', 'share', array('project_id' => $project['id']))); + } else { + $this->show(); } - - $this->response->html($this->helper->layout->project('project/share', array( - 'project' => $project, - 'title' => t('Public access'), - ))); } /** @@ -96,15 +93,15 @@ class Project extends Base $this->projectMetadata->save($project['id'], $this->request->getValues()); $this->flash->success(t('Project updated successfully.')); $this->response->redirect($this->helper->url->to('project', 'integrations', array('project_id' => $project['id']))); + } else { + $this->response->html($this->helper->layout->project('project/integrations', array( + 'project' => $project, + 'title' => t('Integrations'), + 'webhook_token' => $this->config->get('webhook_token'), + 'values' => $this->projectMetadata->getAll($project['id']), + 'errors' => array(), + ))); } - - $this->response->html($this->helper->layout->project('project/integrations', array( - 'project' => $project, - 'title' => t('Integrations'), - 'webhook_token' => $this->config->get('webhook_token'), - 'values' => $this->projectMetadata->getAll($project['id']), - 'errors' => array(), - ))); } /** @@ -120,10 +117,10 @@ class Project extends Base $values = $this->request->getValues(); $this->projectNotification->saveSettings($project['id'], $values); $this->flash->success(t('Project updated successfully.')); - $this->response->redirect($this->helper->url->to('project', 'notifications', array('project_id' => $project['id']))); + return $this->response->redirect($this->helper->url->to('project', 'notifications', array('project_id' => $project['id']))); } - $this->response->html($this->helper->layout->project('project/notifications', array( + return $this->response->html($this->helper->layout->project('project/notifications', array( 'notifications' => $this->projectNotification->readSettings($project['id']), 'types' => $this->projectNotificationType->getTypes(), 'project' => $project, @@ -149,10 +146,10 @@ class Project extends Base $this->flash->failure(t('Unable to remove this project.')); } - $this->response->redirect($this->helper->url->to('project', 'index')); + return $this->response->redirect($this->helper->url->to('project', 'index')); } - $this->response->html($this->helper->layout->project('project/remove', array( + return $this->response->html($this->helper->layout->project('project/remove', array( 'project' => $project, 'title' => t('Remove project') ))); @@ -178,10 +175,10 @@ class Project extends Base $this->flash->failure(t('Unable to clone this project.')); } - $this->response->redirect($this->helper->url->to('project', 'show', array('project_id' => $project_id))); + return $this->response->redirect($this->helper->url->to('project', 'show', array('project_id' => $project_id))); } - $this->response->html($this->helper->layout->project('project/duplicate', array( + return $this->response->html($this->helper->layout->project('project/duplicate', array( 'project' => $project, 'title' => t('Clone this project') ))); @@ -205,10 +202,10 @@ class Project extends Base $this->flash->failure(t('Unable to disable this project.')); } - $this->response->redirect($this->helper->url->to('project', 'show', array('project_id' => $project['id']))); + return $this->response->redirect($this->helper->url->to('project', 'show', array('project_id' => $project['id']))); } - $this->response->html($this->helper->layout->project('project/disable', array( + return $this->response->html($this->helper->layout->project('project/disable', array( 'project' => $project, 'title' => t('Project activation') ))); @@ -232,10 +229,10 @@ class Project extends Base $this->flash->failure(t('Unable to activate this project.')); } - $this->response->redirect($this->helper->url->to('project', 'show', array('project_id' => $project['id']))); + return $this->response->redirect($this->helper->url->to('project', 'show', array('project_id' => $project['id']))); } - $this->response->html($this->helper->layout->project('project/enable', array( + return $this->response->html($this->helper->layout->project('project/enable', array( 'project' => $project, 'title' => t('Project activation') ))); diff --git a/app/Controller/ProjectCreation.php b/app/Controller/ProjectCreation.php index 88f41fcd..ed997fea 100644 --- a/app/Controller/ProjectCreation.php +++ b/app/Controller/ProjectCreation.php @@ -8,12 +8,14 @@ namespace Kanboard\Controller; * @package controller * @author Frederic Guillot */ -class ProjectCreation extends Base +class ProjectCreation extends BaseController { /** * Display a form to create a new project * * @access public + * @param array $values + * @param array $errors */ public function create(array $values = array(), array $errors = array()) { @@ -33,6 +35,8 @@ class ProjectCreation extends Base * Display a form to create a private project * * @access public + * @param array $values + * @param array $errors */ public function createPrivate(array $values = array(), array $errors = array()) { @@ -61,7 +65,7 @@ class ProjectCreation extends Base $this->flash->failure(t('Unable to create your project.')); } - $this->create($values, $errors); + return $this->create($values, $errors); } /** diff --git a/app/Controller/ProjectEdit.php b/app/Controller/ProjectEdit.php index 94be0206..e3ba74c3 100644 --- a/app/Controller/ProjectEdit.php +++ b/app/Controller/ProjectEdit.php @@ -8,12 +8,14 @@ namespace Kanboard\Controller; * @package controller * @author Frederic Guillot */ -class ProjectEdit extends Base +class ProjectEdit extends BaseController { /** * General edition (most common operations) * * @access public + * @param array $values + * @param array $errors */ public function edit(array $values = array(), array $errors = array()) { @@ -24,6 +26,8 @@ class ProjectEdit extends Base * Change start and end dates * * @access public + * @param array $values + * @param array $errors */ public function dates(array $values = array(), array $errors = array()) { @@ -34,6 +38,8 @@ class ProjectEdit extends Base * Change project description * * @access public + * @param array $values + * @param array $errors */ public function description(array $values = array(), array $errors = array()) { @@ -44,6 +50,8 @@ class ProjectEdit extends Base * Change task priority * * @access public + * @param array $values + * @param array $errors */ public function priority(array $values = array(), array $errors = array()) { @@ -67,13 +75,13 @@ class ProjectEdit extends Base if ($valid) { if ($this->project->update($values)) { $this->flash->success(t('Project updated successfully.')); - $this->response->redirect($this->helper->url->to('ProjectEdit', $redirect, array('project_id' => $project['id'])), true); + return $this->response->redirect($this->helper->url->to('ProjectEdit', $redirect, array('project_id' => $project['id'])), true); } else { $this->flash->failure(t('Unable to update this project.')); } } - $this->$redirect($values, $errors); + return $this->$redirect($values, $errors); } /** diff --git a/app/Controller/ProjectFile.php b/app/Controller/ProjectFile.php index 96764a92..6ec5ff27 100644 --- a/app/Controller/ProjectFile.php +++ b/app/Controller/ProjectFile.php @@ -8,7 +8,7 @@ namespace Kanboard\Controller; * @package controller * @author Frederic Guillot */ -class ProjectFile extends Base +class ProjectFile extends BaseController { /** * File upload form diff --git a/app/Controller/ProjectOverview.php b/app/Controller/ProjectOverview.php index b2bb33d6..f8837f95 100644 --- a/app/Controller/ProjectOverview.php +++ b/app/Controller/ProjectOverview.php @@ -8,7 +8,7 @@ namespace Kanboard\Controller; * @package controller * @author Frederic Guillot */ -class ProjectOverview extends Base +class ProjectOverview extends BaseController { /** * Show project overview diff --git a/app/Controller/ProjectPermission.php b/app/Controller/ProjectPermission.php index e203c0db..f50a96b8 100644 --- a/app/Controller/ProjectPermission.php +++ b/app/Controller/ProjectPermission.php @@ -2,6 +2,7 @@ namespace Kanboard\Controller; +use Kanboard\Core\Controller\AccessForbiddenException; use Kanboard\Core\Security\Role; /** @@ -10,7 +11,7 @@ use Kanboard\Core\Security\Role; * @package controller * @author Frederic Guillot */ -class ProjectPermission extends Base +class ProjectPermission extends BaseController { /** * Permissions are only available for team projects @@ -18,13 +19,14 @@ class ProjectPermission extends Base * @access protected * @param integer $project_id Default project id * @return array + * @throws AccessForbiddenException */ protected function getProject($project_id = 0) { $project = parent::getProject($project_id); if ($project['is_private'] == 1) { - $this->forbidden(); + throw new AccessForbiddenException(); } return $project; @@ -34,6 +36,9 @@ class ProjectPermission extends Base * Show all permissions * * @access public + * @param array $values + * @param array $errors + * @throws AccessForbiddenException */ public function index(array $values = array(), array $errors = array()) { diff --git a/app/Controller/Projectuser.php b/app/Controller/Projectuser.php index a6d4fe4e..fe1fe0f1 100644 --- a/app/Controller/Projectuser.php +++ b/app/Controller/Projectuser.php @@ -12,7 +12,7 @@ use Kanboard\Core\Security\Role; * @package controller * @author Frederic Guillot */ -class Projectuser extends Base +class Projectuser extends BaseController { private function common() { @@ -94,7 +94,7 @@ class Projectuser extends Base */ public function members() { - $this->role(ROLE::PROJECT_MEMBER, 'members', t('People who are project members'), 'Projects where "%s" is member'); + $this->role(Role::PROJECT_MEMBER, 'members', t('People who are project members'), 'Projects where "%s" is member'); } /** diff --git a/app/Controller/Search.php b/app/Controller/Search.php index a42e9d3d..a092cba6 100644 --- a/app/Controller/Search.php +++ b/app/Controller/Search.php @@ -10,7 +10,7 @@ use Kanboard\Filter\TaskProjectsFilter; * @package controller * @author Frederic Guillot */ -class Search extends Base +class Search extends BaseController { public function index() { diff --git a/app/Controller/Subtask.php b/app/Controller/Subtask.php index dea2b08e..dfe4415e 100644 --- a/app/Controller/Subtask.php +++ b/app/Controller/Subtask.php @@ -2,18 +2,24 @@ namespace Kanboard\Controller; +use Kanboard\Core\Controller\AccessForbiddenException; + /** * Subtask controller * * @package controller * @author Frederic Guillot */ -class Subtask extends Base +class Subtask extends BaseController { /** * Creation form * * @access public + * @param array $values + * @param array $errors + * @throws AccessForbiddenException + * @throws \Kanboard\Core\Controller\PageNotFoundException */ public function create(array $values = array(), array $errors = array()) { @@ -60,18 +66,22 @@ class Subtask extends Base return $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id']), 'subtasks'), true); } - $this->create($values, $errors); + return $this->create($values, $errors); } /** * Edit form * * @access public + * @param array $values + * @param array $errors + * @throws AccessForbiddenException + * @throws \Kanboard\Core\Controller\PageNotFoundException */ public function edit(array $values = array(), array $errors = array()) { $task = $this->getTask(); - $subtask = $this->getSubTask(); + $subtask = $this->getSubtask(); $this->response->html($this->template->render('subtask/edit', array( 'values' => empty($values) ? $subtask : $values, @@ -106,7 +116,7 @@ class Subtask extends Base return $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id'])), true); } - $this->edit($values, $errors); + return $this->edit($values, $errors); } /** @@ -158,9 +168,9 @@ class Subtask extends Base if (! empty($values) && $this->helper->user->hasProjectAccess('Subtask', 'movePosition', $project_id)) { $result = $this->subtask->changePosition($task_id, $values['subtask_id'], $values['position']); - return $this->response->json(array('result' => $result)); + $this->response->json(array('result' => $result)); + } else { + throw new AccessForbiddenException(); } - - $this->forbidden(); } } diff --git a/app/Controller/SubtaskRestriction.php b/app/Controller/SubtaskRestriction.php index 56024867..bfa3031c 100644 --- a/app/Controller/SubtaskRestriction.php +++ b/app/Controller/SubtaskRestriction.php @@ -10,7 +10,7 @@ use Kanboard\Model\Subtask as SubtaskModel; * @package controller * @author Frederic Guillot */ -class SubtaskRestriction extends Base +class SubtaskRestriction extends BaseController { /** * Show popup diff --git a/app/Controller/SubtaskStatus.php b/app/Controller/SubtaskStatus.php index 4fb82fc0..e22e825e 100644 --- a/app/Controller/SubtaskStatus.php +++ b/app/Controller/SubtaskStatus.php @@ -8,7 +8,7 @@ namespace Kanboard\Controller; * @package controller * @author Frederic Guillot */ -class SubtaskStatus extends Base +class SubtaskStatus extends BaseController { /** * Change status to the next status: Toto -> In Progress -> Done diff --git a/app/Controller/Swimlane.php b/app/Controller/Swimlane.php index 8270a16f..4575e909 100644 --- a/app/Controller/Swimlane.php +++ b/app/Controller/Swimlane.php @@ -2,6 +2,8 @@ namespace Kanboard\Controller; +use Kanboard\Core\Controller\AccessForbiddenException; +use Kanboard\Core\Controller\PageNotFoundException; use Kanboard\Model\Swimlane as SwimlaneModel; /** @@ -10,22 +12,21 @@ use Kanboard\Model\Swimlane as SwimlaneModel; * @package controller * @author Frederic Guillot */ -class Swimlane extends Base +class Swimlane extends BaseController { /** * Get the swimlane (common method between actions) * * @access private - * @param integer $project_id * @return array + * @throws PageNotFoundException */ - private function getSwimlane($project_id) + private function getSwimlane() { $swimlane = $this->swimlane->getById($this->request->getIntegerParam('swimlane_id')); if (empty($swimlane)) { - $this->flash->failure(t('Swimlane not found.')); - $this->response->redirect($this->helper->url->to('swimlane', 'index', array('project_id' => $project_id))); + throw new PageNotFoundException(); } return $swimlane; @@ -53,6 +54,9 @@ class Swimlane extends Base * Create a new swimlane * * @access public + * @param array $values + * @param array $errors + * @throws \Kanboard\Core\Controller\PageNotFoundException */ public function create(array $values = array(), array $errors = array()) { @@ -79,19 +83,22 @@ class Swimlane extends Base if ($valid) { if ($this->swimlane->create($values)) { $this->flash->success(t('Your swimlane have been created successfully.')); - $this->response->redirect($this->helper->url->to('swimlane', 'index', array('project_id' => $project['id']))); + return $this->response->redirect($this->helper->url->to('swimlane', 'index', array('project_id' => $project['id']))); } else { $errors = array('name' => array(t('Another swimlane with the same name exists in the project'))); } } - $this->create($values, $errors); + return $this->create($values, $errors); } /** * Edit default swimlane (display the form) * * @access public + * @param array $values + * @param array $errors + * @throws \Kanboard\Core\Controller\PageNotFoundException */ public function editDefault(array $values = array(), array $errors = array()) { @@ -120,24 +127,27 @@ class Swimlane extends Base if ($valid) { if ($this->swimlane->updateDefault($values)) { $this->flash->success(t('The default swimlane have been updated successfully.')); - $this->response->redirect($this->helper->url->to('swimlane', 'index', array('project_id' => $project['id'])), true); + return $this->response->redirect($this->helper->url->to('swimlane', 'index', array('project_id' => $project['id'])), true); } else { $this->flash->failure(t('Unable to update this swimlane.')); } } - $this->editDefault($values, $errors); + return $this->editDefault($values, $errors); } /** * Edit a swimlane (display the form) * * @access public + * @param array $values + * @param array $errors + * @throws \Kanboard\Core\Controller\PageNotFoundException */ public function edit(array $values = array(), array $errors = array()) { $project = $this->getProject(); - $swimlane = $this->getSwimlane($project['id']); + $swimlane = $this->getSwimlane(); $this->response->html($this->helper->layout->project('swimlane/edit', array( 'values' => empty($values) ? $swimlane : $values, @@ -161,13 +171,13 @@ class Swimlane extends Base if ($valid) { if ($this->swimlane->update($values)) { $this->flash->success(t('Swimlane updated successfully.')); - $this->response->redirect($this->helper->url->to('swimlane', 'index', array('project_id' => $project['id']))); + return $this->response->redirect($this->helper->url->to('swimlane', 'index', array('project_id' => $project['id']))); } else { $errors = array('name' => array(t('Another swimlane with the same name exists in the project'))); } } - $this->edit($values, $errors); + return $this->edit($values, $errors); } /** @@ -178,7 +188,7 @@ class Swimlane extends Base public function confirm() { $project = $this->getProject(); - $swimlane = $this->getSwimlane($project['id']); + $swimlane = $this->getSwimlane(); $this->response->html($this->helper->layout->project('swimlane/remove', array( 'project' => $project, @@ -296,9 +306,9 @@ class Swimlane extends Base if (! empty($values) && isset($values['swimlane_id']) && isset($values['position'])) { $result = $this->swimlane->changePosition($project['id'], $values['swimlane_id'], $values['position']); - return $this->response->json(array('result' => $result)); + $this->response->json(array('result' => $result)); + } else { + throw new AccessForbiddenException(); } - - $this->forbidden(); } } diff --git a/app/Controller/Task.php b/app/Controller/Task.php index 072df87b..1ce13f69 100644 --- a/app/Controller/Task.php +++ b/app/Controller/Task.php @@ -2,6 +2,8 @@ namespace Kanboard\Controller; +use Kanboard\Core\Controller\AccessForbiddenException; +use Kanboard\Core\Controller\PageNotFoundException; use Kanboard\Core\DateParser; /** @@ -10,7 +12,7 @@ use Kanboard\Core\DateParser; * @package controller * @author Frederic Guillot */ -class Task extends Base +class Task extends BaseController { /** * Public access (display a task) @@ -23,17 +25,17 @@ class Task extends Base // Token verification if (empty($project)) { - return $this->forbidden(true); + throw AccessForbiddenException::getInstance()->withoutLayout(); } $task = $this->taskFinder->getDetails($this->request->getIntegerParam('task_id')); if (empty($task)) { - return $this->notfound(true); + throw PageNotFoundException::getInstance()->withoutLayout(); } if ($task['project_id'] != $project['id']) { - return $this->forbidden(true); + throw AccessForbiddenException::getInstance()->withoutLayout(); } $this->response->html($this->helper->layout->app('task/public', array( @@ -152,7 +154,7 @@ class Task extends Base $task = $this->getTask(); if (! $this->helper->user->canRemoveTask($task)) { - $this->forbidden(); + throw new AccessForbiddenException(); } if ($this->request->getStringParam('confirmation') === 'yes') { @@ -164,10 +166,10 @@ class Task extends Base $this->flash->failure(t('Unable to remove this task.')); } - $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $task['project_id'])), true); + return $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $task['project_id'])), true); } - $this->response->html($this->template->render('task/remove', array( + return $this->response->html($this->template->render('task/remove', array( 'task' => $task, ))); } diff --git a/app/Controller/TaskBulk.php b/app/Controller/TaskBulk.php index 4e9d4443..d0a1b276 100644 --- a/app/Controller/TaskBulk.php +++ b/app/Controller/TaskBulk.php @@ -7,7 +7,7 @@ namespace Kanboard\Controller; * * @package Kanboard\Controller */ -class TaskBulk extends Base +class TaskBulk extends BaseController { /** * Show the form diff --git a/app/Controller/TaskExternalLink.php b/app/Controller/TaskExternalLink.php index 0db8ec37..9f040957 100644 --- a/app/Controller/TaskExternalLink.php +++ b/app/Controller/TaskExternalLink.php @@ -2,6 +2,7 @@ namespace Kanboard\Controller; +use Kanboard\Core\Controller\PageNotFoundException; use Kanboard\Core\ExternalLink\ExternalLinkProviderNotFound; /** @@ -10,12 +11,16 @@ use Kanboard\Core\ExternalLink\ExternalLinkProviderNotFound; * @package controller * @author Frederic Guillot */ -class TaskExternalLink extends Base +class TaskExternalLink extends BaseController { /** * First creation form * * @access public + * @param array $values + * @param array $errors + * @throws PageNotFoundException + * @throws \Kanboard\Core\Controller\AccessForbiddenException */ public function find(array $values = array(), array $errors = array()) { @@ -36,11 +41,10 @@ class TaskExternalLink extends Base */ public function create() { - try { - - $task = $this->getTask(); - $values = $this->request->getValues(); + $task = $this->getTask(); + $values = $this->request->getValues(); + try { $provider = $this->externalLinkManager->setUserInput($values)->find(); $link = $provider->getLink(); @@ -77,13 +81,18 @@ class TaskExternalLink extends Base return $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])), true); } - $this->edit($values, $errors); + return $this->edit($values, $errors); } /** * Edit form * * @access public + * @param array $values + * @param array $errors + * @throws ExternalLinkProviderNotFound + * @throws PageNotFoundException + * @throws \Kanboard\Core\Controller\AccessForbiddenException */ public function edit(array $values = array(), array $errors = array()) { @@ -95,7 +104,7 @@ class TaskExternalLink extends Base } if (empty($values)) { - return $this->notfound(); + throw new PageNotFoundException(); } $provider = $this->externalLinkManager->getProvider($values['link_type']); @@ -124,7 +133,7 @@ class TaskExternalLink extends Base return $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])), true); } - $this->edit($values, $errors); + return $this->edit($values, $errors); } /** @@ -139,7 +148,7 @@ class TaskExternalLink extends Base $link = $this->taskExternalLink->getById($link_id); if (empty($link)) { - return $this->notfound(); + throw new PageNotFoundException(); } $this->response->html($this->template->render('task_external_link/remove', array( diff --git a/app/Controller/TaskFile.php b/app/Controller/TaskFile.php index 2b0152a7..0fcd2d69 100644 --- a/app/Controller/TaskFile.php +++ b/app/Controller/TaskFile.php @@ -8,7 +8,7 @@ namespace Kanboard\Controller; * @package controller * @author Frederic Guillot */ -class TaskFile extends Base +class TaskFile extends BaseController { /** * Screenshot @@ -24,7 +24,7 @@ class TaskFile extends Base return $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])), true); } - $this->response->html($this->template->render('task_file/screenshot', array( + return $this->response->html($this->template->render('task_file/screenshot', array( 'task' => $task, ))); } diff --git a/app/Controller/TaskHelper.php b/app/Controller/TaskHelper.php index 6835ab2b..2f14c0eb 100644 --- a/app/Controller/TaskHelper.php +++ b/app/Controller/TaskHelper.php @@ -14,7 +14,7 @@ use Kanboard\Formatter\TaskAutoCompleteFormatter; * @package controller * @author Frederic Guillot */ -class TaskHelper extends Base +class TaskHelper extends BaseController { /** * Task auto-completion (Ajax) diff --git a/app/Controller/TaskImport.php b/app/Controller/TaskImport.php index 460c608c..5dbf8678 100644 --- a/app/Controller/TaskImport.php +++ b/app/Controller/TaskImport.php @@ -10,11 +10,14 @@ use Kanboard\Core\Csv; * @package controller * @author Frederic Guillot */ -class TaskImport extends Base +class TaskImport extends BaseController { /** * Upload the file and ask settings * + * @param array $values + * @param array $errors + * @throws \Kanboard\Core\Controller\PageNotFoundException */ public function step1(array $values = array(), array $errors = array()) { @@ -66,7 +69,7 @@ class TaskImport extends Base */ public function template() { - $this->response->forceDownload('tasks.csv'); + $this->response->withDownload('tasks.csv'); $this->response->csv(array($this->taskImport->getColumnMapping())); } } diff --git a/app/Controller/TaskInternalLink.php b/app/Controller/TaskInternalLink.php index ac5e04b7..6ff20252 100644 --- a/app/Controller/TaskInternalLink.php +++ b/app/Controller/TaskInternalLink.php @@ -2,6 +2,8 @@ namespace Kanboard\Controller; +use Kanboard\Core\Controller\PageNotFoundException; + /** * TaskInternalLink Controller * @@ -9,20 +11,21 @@ namespace Kanboard\Controller; * @author Olivier Maridat * @author Frederic Guillot */ -class TaskInternalLink extends Base +class TaskInternalLink extends BaseController { /** * Get the current link * * @access private * @return array + * @throws PageNotFoundException */ private function getTaskLink() { $link = $this->taskLink->getById($this->request->getIntegerParam('link_id')); if (empty($link)) { - return $this->notfound(); + throw new PageNotFoundException(); } return $link; @@ -32,6 +35,10 @@ class TaskInternalLink extends Base * Creation form * * @access public + * @param array $values + * @param array $errors + * @throws PageNotFoundException + * @throws \Kanboard\Core\Controller\AccessForbiddenException */ public function create(array $values = array(), array $errors = array()) { @@ -67,13 +74,17 @@ class TaskInternalLink extends Base $this->flash->failure(t('Unable to create your link.')); } - $this->create($values, $errors); + return $this->create($values, $errors); } /** * Edit form * * @access public + * @param array $values + * @param array $errors + * @throws PageNotFoundException + * @throws \Kanboard\Core\Controller\AccessForbiddenException */ public function edit(array $values = array(), array $errors = array()) { @@ -116,7 +127,7 @@ class TaskInternalLink extends Base $this->flash->failure(t('Unable to update your link.')); } - $this->edit($values, $errors); + return $this->edit($values, $errors); } /** diff --git a/app/Controller/TaskPopover.php b/app/Controller/TaskPopover.php index 422a99c7..0e47cffe 100644 --- a/app/Controller/TaskPopover.php +++ b/app/Controller/TaskPopover.php @@ -8,7 +8,7 @@ namespace Kanboard\Controller; * @package controller * @author Frederic Guillot */ -class TaskPopover extends Base +class TaskPopover extends BaseController { /** * Change a task assignee directly from the board diff --git a/app/Controller/TaskRecurrence.php b/app/Controller/TaskRecurrence.php index 569ef8d9..72dce3a5 100644 --- a/app/Controller/TaskRecurrence.php +++ b/app/Controller/TaskRecurrence.php @@ -8,12 +8,16 @@ namespace Kanboard\Controller; * @package controller * @author Frederic Guillot */ -class TaskRecurrence extends Base +class TaskRecurrence extends BaseController { /** * Edit recurrence form * * @access public + * @param array $values + * @param array $errors + * @throws \Kanboard\Core\Controller\AccessForbiddenException + * @throws \Kanboard\Core\Controller\PageNotFoundException */ public function edit(array $values = array(), array $errors = array()) { @@ -53,9 +57,9 @@ class TaskRecurrence extends Base $this->flash->failure(t('Unable to update your task.')); } - $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id'])), true); + return $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id'])), true); } - $this->edit($values, $errors); + return $this->edit($values, $errors); } } diff --git a/app/Controller/Taskcreation.php b/app/Controller/Taskcreation.php index 1c9e0d82..af7d0c80 100644 --- a/app/Controller/Taskcreation.php +++ b/app/Controller/Taskcreation.php @@ -8,12 +8,15 @@ namespace Kanboard\Controller; * @package controller * @author Frederic Guillot */ -class Taskcreation extends Base +class Taskcreation extends BaseController { /** * Display a form to create a new task * * @access public + * @param array $values + * @param array $errors + * @throws \Kanboard\Core\Controller\PageNotFoundException */ public function create(array $values = array(), array $errors = array()) { @@ -63,7 +66,7 @@ class Taskcreation extends Base } $this->flash->failure(t('Unable to create your task.')); - $this->create($values, $errors); + return $this->create($values, $errors); } private function afterSave(array $project, array &$values) @@ -79,6 +82,6 @@ class Taskcreation extends Base )); } - $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $project['id'])), true); + return $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $project['id'])), true); } } diff --git a/app/Controller/Taskduplication.php b/app/Controller/Taskduplication.php index 8fca930d..ff60228e 100644 --- a/app/Controller/Taskduplication.php +++ b/app/Controller/Taskduplication.php @@ -8,7 +8,7 @@ namespace Kanboard\Controller; * @package controller * @author Frederic Guillot */ -class Taskduplication extends Base +class Taskduplication extends BaseController { /** * Duplicate a task @@ -25,14 +25,14 @@ class Taskduplication extends Base if ($task_id > 0) { $this->flash->success(t('Task created successfully.')); - $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task_id))); + return $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task_id))); } else { $this->flash->failure(t('Unable to create this task.')); - $this->response->redirect($this->helper->url->to('taskduplication', 'duplicate', array('project_id' => $task['project_id'], 'task_id' => $task['id'])), true); + return $this->response->redirect($this->helper->url->to('taskduplication', 'duplicate', array('project_id' => $task['project_id'], 'task_id' => $task['id'])), true); } } - $this->response->html($this->template->render('task_duplication/duplicate', array( + return $this->response->html($this->template->render('task_duplication/duplicate', array( 'task' => $task, ))); } @@ -57,13 +57,13 @@ class Taskduplication extends Base $values['category_id'], $values['owner_id'])) { $this->flash->success(t('Task updated successfully.')); - $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $values['project_id'], 'task_id' => $task['id']))); + return $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $values['project_id'], 'task_id' => $task['id']))); } $this->flash->failure(t('Unable to update your task.')); } - $this->chooseDestination($task, 'task_duplication/move'); + return $this->chooseDestination($task, 'task_duplication/move'); } /** @@ -87,14 +87,14 @@ class Taskduplication extends Base if ($task_id > 0) { $this->flash->success(t('Task created successfully.')); - $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $values['project_id'], 'task_id' => $task_id))); + return $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $values['project_id'], 'task_id' => $task_id))); } } $this->flash->failure(t('Unable to create your task.')); } - $this->chooseDestination($task, 'task_duplication/copy'); + return $this->chooseDestination($task, 'task_duplication/copy'); } /** diff --git a/app/Controller/Taskmodification.php b/app/Controller/Taskmodification.php index 6b945f37..e8eafe20 100644 --- a/app/Controller/Taskmodification.php +++ b/app/Controller/Taskmodification.php @@ -10,7 +10,7 @@ use Kanboard\Core\DateParser; * @package controller * @author Frederic Guillot */ -class Taskmodification extends Base +class Taskmodification extends BaseController { /** * Set automatically the start date @@ -28,6 +28,10 @@ class Taskmodification extends Base * Edit description form * * @access public + * @param array $values + * @param array $errors + * @throws \Kanboard\Core\Controller\AccessForbiddenException + * @throws \Kanboard\Core\Controller\PageNotFoundException */ public function description(array $values = array(), array $errors = array()) { @@ -66,13 +70,17 @@ class Taskmodification extends Base return $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id'])), true); } - $this->description($values, $errors); + return $this->description($values, $errors); } /** * Display a form to edit a task * * @access public + * @param array $values + * @param array $errors + * @throws \Kanboard\Core\Controller\AccessForbiddenException + * @throws \Kanboard\Core\Controller\PageNotFoundException */ public function edit(array $values = array(), array $errors = array()) { @@ -113,7 +121,7 @@ class Taskmodification extends Base if ($valid && $this->taskModification->update($values)) { $this->flash->success(t('Task updated successfully.')); - return $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id'])), true); + $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id'])), true); } else { $this->flash->failure(t('Unable to update your task.')); $this->edit($values, $errors); diff --git a/app/Controller/Taskstatus.php b/app/Controller/Taskstatus.php index a67459c9..eeaf8513 100644 --- a/app/Controller/Taskstatus.php +++ b/app/Controller/Taskstatus.php @@ -8,7 +8,7 @@ namespace Kanboard\Controller; * @package controller * @author Frederic Guillot */ -class Taskstatus extends Base +class Taskstatus extends BaseController { /** * Close a task @@ -55,7 +55,7 @@ class Taskstatus extends Base return $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])), true); } - $this->response->html($this->template->render($template, array( + return $this->response->html($this->template->render($template, array( 'task' => $task, ))); } diff --git a/app/Controller/Twofactor.php b/app/Controller/Twofactor.php index 10292261..2eb61594 100644 --- a/app/Controller/Twofactor.php +++ b/app/Controller/Twofactor.php @@ -2,6 +2,8 @@ namespace Kanboard\Controller; +use Kanboard\Core\Controller\AccessForbiddenException; + /** * Two Factor Auth controller * @@ -14,11 +16,13 @@ class Twofactor extends User * Only the current user can access to 2FA settings * * @access private + * @param array $user + * @throws AccessForbiddenException */ private function checkCurrentUser(array $user) { if ($user['id'] != $this->userSession->getId()) { - $this->forbidden(); + throw new AccessForbiddenException(); } } @@ -145,7 +149,7 @@ class Twofactor extends User if ($provider->authenticate()) { $this->userSession->validatePostAuthentication(); $this->flash->success(t('The two factor authentication code is valid.')); - $this->response->redirect($this->helper->url->to('app', 'index')); + $this->response->redirect($this->helper->url->to('DashboardController', 'show')); } else { $this->flash->failure(t('The two factor authentication code is not valid.')); $this->response->redirect($this->helper->url->to('twofactor', 'code')); @@ -188,10 +192,10 @@ class Twofactor extends User 'twofactor_secret' => '', )); - $this->response->redirect($this->helper->url->to('user', 'show', array('user_id' => $user['id']))); + return $this->response->redirect($this->helper->url->to('user', 'show', array('user_id' => $user['id']))); } - $this->response->html($this->helper->layout->user('twofactor/disable', array( + return $this->response->html($this->helper->layout->user('twofactor/disable', array( 'user' => $user, ))); } diff --git a/app/Controller/User.php b/app/Controller/User.php index 4caed1e6..11a7a01e 100644 --- a/app/Controller/User.php +++ b/app/Controller/User.php @@ -2,6 +2,7 @@ namespace Kanboard\Controller; +use Kanboard\Core\Controller\PageNotFoundException; use Kanboard\Notification\Mail as MailNotification; use Kanboard\Model\Project as ProjectModel; use Kanboard\Core\Security\Role; @@ -12,7 +13,7 @@ use Kanboard\Core\Security\Role; * @package controller * @author Frederic Guillot */ -class User extends Base +class User extends BaseController { /** * List all users @@ -28,39 +29,38 @@ class User extends Base ->setQuery($this->user->getQuery()) ->calculate(); - $this->response->html( - $this->helper->layout->app('user/index', array( - 'title' => t('Users').' ('.$paginator->getTotal().')', - 'paginator' => $paginator, - ) - )); + $this->response->html($this->helper->layout->app('user/index', array( + 'title' => t('Users').' ('.$paginator->getTotal().')', + 'paginator' => $paginator, + ))); } /** * Public user profile * * @access public + * @throws PageNotFoundException */ public function profile() { $user = $this->user->getById($this->request->getIntegerParam('user_id')); if (empty($user)) { - $this->notfound(); + throw new PageNotFoundException(); } - $this->response->html( - $this->helper->layout->app('user/profile', array( - 'title' => $user['name'] ?: $user['username'], - 'user' => $user, - ) - )); + $this->response->html($this->helper->layout->app('user/profile', array( + 'title' => $user['name'] ?: $user['username'], + 'user' => $user, + ))); } /** * Display a form to create a new user * * @access public + * @param array $values + * @param array $errors */ public function create(array $values = array(), array $errors = array()) { @@ -101,14 +101,14 @@ class User extends Base } $this->flash->success(t('User created successfully.')); - $this->response->redirect($this->helper->url->to('user', 'show', array('user_id' => $user_id))); + return $this->response->redirect($this->helper->url->to('user', 'show', array('user_id' => $user_id))); } else { $this->flash->failure(t('Unable to create your user.')); $values['project_id'] = $project_id; } } - $this->create($values, $errors); + return $this->create($values, $errors); } /** @@ -217,10 +217,10 @@ class User extends Base $values = $this->request->getValues(); $this->userNotification->saveSettings($user['id'], $values); $this->flash->success(t('User updated successfully.')); - $this->response->redirect($this->helper->url->to('user', 'notifications', array('user_id' => $user['id']))); + return $this->response->redirect($this->helper->url->to('user', 'notifications', array('user_id' => $user['id']))); } - $this->response->html($this->helper->layout->user('user/notifications', array( + return $this->response->html($this->helper->layout->user('user/notifications', array( 'projects' => $this->projectUserRole->getProjectsByUser($user['id'], array(ProjectModel::ACTIVE)), 'notifications' => $this->userNotification->readSettings($user['id']), 'types' => $this->userNotificationType->getTypes(), @@ -284,10 +284,10 @@ class User extends Base $this->flash->failure(t('Unable to update this user.')); } - $this->response->redirect($this->helper->url->to('user', 'share', array('user_id' => $user['id']))); + return $this->response->redirect($this->helper->url->to('user', 'share', array('user_id' => $user['id']))); } - $this->response->html($this->helper->layout->user('user/share', array( + return $this->response->html($this->helper->layout->user('user/share', array( 'user' => $user, 'title' => t('Public access'), ))); @@ -315,11 +315,11 @@ class User extends Base $this->flash->failure(t('Unable to change the password.')); } - $this->response->redirect($this->helper->url->to('user', 'show', array('user_id' => $user['id']))); + return $this->response->redirect($this->helper->url->to('user', 'show', array('user_id' => $user['id']))); } } - $this->response->html($this->helper->layout->user('user/password', array( + return $this->response->html($this->helper->layout->user('user/password', array( 'values' => $values, 'errors' => $errors, 'user' => $user, @@ -357,11 +357,11 @@ class User extends Base $this->flash->failure(t('Unable to update your user.')); } - $this->response->redirect($this->helper->url->to('user', 'show', array('user_id' => $user['id']))); + return $this->response->redirect($this->helper->url->to('user', 'show', array('user_id' => $user['id']))); } } - $this->response->html($this->helper->layout->user('user/edit', array( + return $this->response->html($this->helper->layout->user('user/edit', array( 'values' => $values, 'errors' => $errors, 'user' => $user, @@ -395,11 +395,11 @@ class User extends Base $this->flash->failure(t('Unable to update your user.')); } - $this->response->redirect($this->helper->url->to('user', 'authentication', array('user_id' => $user['id']))); + return $this->response->redirect($this->helper->url->to('user', 'authentication', array('user_id' => $user['id']))); } } - $this->response->html($this->helper->layout->user('user/authentication', array( + return $this->response->html($this->helper->layout->user('user/authentication', array( 'values' => $values, 'errors' => $errors, 'user' => $user, diff --git a/app/Controller/UserHelper.php b/app/Controller/UserHelper.php index 47bbe554..d5e0920d 100644 --- a/app/Controller/UserHelper.php +++ b/app/Controller/UserHelper.php @@ -12,7 +12,7 @@ use Kanboard\Model\User as UserModel; * @package controller * @author Frederic Guillot */ -class UserHelper extends Base +class UserHelper extends BaseController { /** * User auto-completion (Ajax) @@ -39,4 +39,14 @@ class UserHelper extends Base $users = $this->projectPermission->findUsernames($project_id, $query); $this->response->json($users); } + + /** + * Check if the user is connected + * + * @access public + */ + public function status() + { + $this->response->text('OK'); + } } diff --git a/app/Controller/UserImport.php b/app/Controller/UserImport.php index debd69e5..b99e56a0 100644 --- a/app/Controller/UserImport.php +++ b/app/Controller/UserImport.php @@ -10,7 +10,7 @@ use Kanboard\Core\Csv; * @package controller * @author Frederic Guillot */ -class UserImport extends Base +class UserImport extends BaseController { /** * Upload the file and ask settings @@ -60,7 +60,7 @@ class UserImport extends Base */ public function template() { - $this->response->forceDownload('users.csv'); + $this->response->withDownload('users.csv'); $this->response->csv(array($this->userImport->getColumnMapping())); } } diff --git a/app/Controller/UserStatus.php b/app/Controller/UserStatus.php index b8ee5c91..6f93e953 100644 --- a/app/Controller/UserStatus.php +++ b/app/Controller/UserStatus.php @@ -8,7 +8,7 @@ namespace Kanboard\Controller; * @package controller * @author Frederic Guillot */ -class UserStatus extends Base +class UserStatus extends BaseController { /** * Confirm remove a user diff --git a/app/Controller/WebNotification.php b/app/Controller/WebNotification.php index dca5cb46..a62da0e3 100644 --- a/app/Controller/WebNotification.php +++ b/app/Controller/WebNotification.php @@ -8,7 +8,7 @@ namespace Kanboard\Controller; * @package controller * @author Frederic Guillot */ -class WebNotification extends Base +class WebNotification extends BaseController { /** * Mark all notifications as read @@ -20,7 +20,7 @@ class WebNotification extends Base $user_id = $this->getUserId(); $this->userUnreadNotification->markAllAsRead($user_id); - $this->response->redirect($this->helper->url->to('app', 'notifications', array('user_id' => $user_id))); + $this->response->redirect($this->helper->url->to('DashboardController', 'notifications', array('user_id' => $user_id))); } /** @@ -34,7 +34,7 @@ class WebNotification extends Base $notification_id = $this->request->getIntegerParam('notification_id'); $this->userUnreadNotification->markAsRead($user_id, $notification_id); - $this->response->redirect($this->helper->url->to('app', 'notifications', array('user_id' => $user_id))); + $this->response->redirect($this->helper->url->to('DashboardController', 'notifications', array('user_id' => $user_id))); } private function getUserId() diff --git a/app/Controller/Webhook.php b/app/Controller/Webhook.php index 0eafe3e5..6e172aeb 100644 --- a/app/Controller/Webhook.php +++ b/app/Controller/Webhook.php @@ -8,7 +8,7 @@ namespace Kanboard\Controller; * @package controller * @author Frederic Guillot */ -class Webhook extends Base +class Webhook extends BaseController { /** * Webhook to create a task @@ -34,9 +34,9 @@ class Webhook extends Base list($valid, ) = $this->taskValidator->validateCreation($values); if ($valid && $this->taskCreation->create($values)) { - $this->response->text('OK'); + return $this->response->text('OK'); } - $this->response->text('FAILED'); + return $this->response->text('FAILED'); } } diff --git a/app/Core/Controller/AccessForbiddenException.php b/app/Core/Controller/AccessForbiddenException.php new file mode 100644 index 00000000..b5dccb78 --- /dev/null +++ b/app/Core/Controller/AccessForbiddenException.php @@ -0,0 +1,14 @@ +<?php + +namespace Kanboard\Core\Controller; + +/** + * Class AccessForbiddenException + * + * @package Kanboard\Core\Controller + * @author Frederic Guillot + */ +class AccessForbiddenException extends BaseException +{ + +} diff --git a/app/Core/Controller/BaseException.php b/app/Core/Controller/BaseException.php new file mode 100644 index 00000000..13836d2c --- /dev/null +++ b/app/Core/Controller/BaseException.php @@ -0,0 +1,52 @@ +<?php + +namespace Kanboard\Core\Controller; + +use Exception; + +/** + * Class AccessForbiddenException + * + * @package Kanboard\Core\Controller + * @author Frederic Guillot + */ +class BaseException extends Exception +{ + protected $withoutLayout = false; + + /** + * Get object instance + * + * @static + * @access public + * @param string $message + * @return static + */ + public static function getInstance($message = '') + { + return new static($message); + } + + /** + * There is no layout + * + * @access public + * @return BaseException + */ + public function withoutLayout() + { + $this->withoutLayout = true; + return $this; + } + + /** + * Return true if no layout + * + * @access public + * @return boolean + */ + public function hasLayout() + { + return $this->withoutLayout; + } +} diff --git a/app/Core/Controller/BaseMiddleware.php b/app/Core/Controller/BaseMiddleware.php new file mode 100644 index 00000000..f2862d13 --- /dev/null +++ b/app/Core/Controller/BaseMiddleware.php @@ -0,0 +1,58 @@ +<?php + +namespace Kanboard\Core\Controller; + +use Kanboard\Core\Base; + +/** + * Class BaseMiddleware + * + * @package Kanboard\Core\Controller + * @author Frederic Guillot + */ +abstract class BaseMiddleware extends Base +{ + /** + * @var BaseMiddleware + */ + protected $nextMiddleware = null; + + /** + * Execute middleware + */ + abstract public function execute(); + + /** + * Set next middleware + * + * @param BaseMiddleware $nextMiddleware + * @return BaseMiddleware + */ + public function setNextMiddleware($nextMiddleware) + { + $this->nextMiddleware = $nextMiddleware; + return $this; + } + + /** + * @return BaseMiddleware + */ + public function getNextMiddleware() + { + return $this->nextMiddleware; + } + + /** + * Move to next middleware + */ + public function next() + { + if ($this->nextMiddleware !== null) { + if (DEBUG) { + $this->logger->debug(__METHOD__.' => ' . get_class($this->nextMiddleware)); + } + + $this->nextMiddleware->execute(); + } + } +} diff --git a/app/Core/Controller/PageNotFoundException.php b/app/Core/Controller/PageNotFoundException.php new file mode 100644 index 00000000..e96a2057 --- /dev/null +++ b/app/Core/Controller/PageNotFoundException.php @@ -0,0 +1,14 @@ +<?php + +namespace Kanboard\Core\Controller; + +/** + * Class PageNotFoundException + * + * @package Kanboard\Core\Controller + * @author Frederic Guillot + */ +class PageNotFoundException extends BaseException +{ + +} diff --git a/app/Core/Controller/Runner.php b/app/Core/Controller/Runner.php new file mode 100644 index 00000000..b973c098 --- /dev/null +++ b/app/Core/Controller/Runner.php @@ -0,0 +1,102 @@ +<?php + +namespace Kanboard\Core\Controller; + +use Kanboard\Controller\AppController; +use Kanboard\Core\Base; +use Kanboard\Middleware\ApplicationAuthorizationMiddleware; +use Kanboard\Middleware\AuthenticationMiddleware; +use Kanboard\Middleware\BootstrapMiddleware; +use Kanboard\Middleware\PostAuthenticationMiddleware; +use Kanboard\Middleware\ProjectAuthorizationMiddleware; +use RuntimeException; + +/** + * Class Runner + * + * @package Kanboard\Core\Controller + * @author Frederic Guillot + */ +class Runner extends Base +{ + /** + * Execute middleware and controller + */ + public function execute() + { + try { + $this->executeMiddleware(); + $this->executeController(); + } catch (PageNotFoundException $e) { + $controllerObject = new AppController($this->container); + $controllerObject->notFound($e->hasLayout()); + } catch (AccessForbiddenException $e) { + $controllerObject = new AppController($this->container); + $controllerObject->accessForbidden($e->hasLayout()); + } + } + + /** + * Execute all middleware + */ + protected function executeMiddleware() + { + if (DEBUG) { + $this->logger->debug(__METHOD__); + } + + $bootstrapMiddleware = new BootstrapMiddleware($this->container); + $authenticationMiddleware = new AuthenticationMiddleware($this->container); + $postAuthenticationMiddleware = new PostAuthenticationMiddleware($this->container); + $appAuthorizationMiddleware = new ApplicationAuthorizationMiddleware($this->container); + $projectAuthorizationMiddleware = new ProjectAuthorizationMiddleware($this->container); + + $bootstrapMiddleware->setNextMiddleware($authenticationMiddleware); + $authenticationMiddleware->setNextMiddleware($postAuthenticationMiddleware); + $postAuthenticationMiddleware->setNextMiddleware($appAuthorizationMiddleware); + $appAuthorizationMiddleware->setNextMiddleware($projectAuthorizationMiddleware); + + $bootstrapMiddleware->execute(); + } + + /** + * Execute the controller + */ + protected function executeController() + { + $className = $this->getControllerClassName(); + + if (DEBUG) { + $this->logger->debug(__METHOD__.' => '.$className.'::'.$this->router->getAction()); + } + + $controllerObject = new $className($this->container); + $controllerObject->{$this->router->getAction()}(); + } + + /** + * Get controller class name + * + * @access protected + * @return string + * @throws RuntimeException + */ + protected function getControllerClassName() + { + if ($this->router->getPlugin() !== '') { + $className = '\Kanboard\Plugin\\'.$this->router->getPlugin().'\Controller\\'.$this->router->getController(); + } else { + $className = '\Kanboard\Controller\\'.$this->router->getController(); + } + + if (! class_exists($className)) { + throw new RuntimeException('Controller not found'); + } + + if (! method_exists($className, $this->router->getAction())) { + throw new RuntimeException('Action not implemented'); + } + + return $className; + } +} diff --git a/app/Core/Http/Response.php b/app/Core/Http/Response.php index 996fc58d..fd67ec95 100644 --- a/app/Core/Http/Response.php +++ b/app/Core/Http/Response.php @@ -13,296 +13,359 @@ use Kanboard\Core\Csv; */ class Response extends Base { + private $httpStatusCode = 200; + private $httpHeaders = array(); + private $httpBody = ''; + /** - * Send headers to cache a resource + * Set HTTP status code * * @access public - * @param integer $duration - * @param string $etag + * @param integer $statusCode + * @return $this */ - public function cache($duration, $etag = '') + public function withStatusCode($statusCode) { - header('Pragma: cache'); - header('Expires: ' . gmdate('D, d M Y H:i:s', time() + $duration) . ' GMT'); - header('Cache-Control: public, max-age=' . $duration); - - if ($etag) { - header('ETag: "' . $etag . '"'); - } + $this->httpStatusCode = $statusCode; + return $this; } /** - * Send no cache headers + * Set HTTP header * * @access public + * @param string $header + * @param string $value + * @return $this */ - public function nocache() + public function withHeader($header, $value) { - header('Pragma: no-cache'); - header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); - - // Use no-store due to a Chrome bug: https://code.google.com/p/chromium/issues/detail?id=28035 - header('Cache-Control: no-store, must-revalidate'); + $this->httpHeaders[$header] = $value; + return $this; } /** - * Send a custom Content-Type header + * Set content type header * * @access public - * @param string $mimetype Mime-type + * @param string $value + * @return $this */ - public function contentType($mimetype) + public function withContentType($value) { - header('Content-Type: '.$mimetype); + $this->httpHeaders['Content-Type'] = $value; + return $this; } /** - * Force the browser to download an attachment + * Set default security headers * * @access public - * @param string $filename File name + * @return $this */ - public function forceDownload($filename) + public function withSecurityHeaders() { - header('Content-Disposition: attachment; filename="'.$filename.'"'); - header('Content-Transfer-Encoding: binary'); - header('Content-Type: application/octet-stream'); + $this->httpHeaders['X-Content-Type-Options'] = 'nosniff'; + $this->httpHeaders['X-XSS-Protection'] = '1; mode=block'; + return $this; } /** - * Send a custom HTTP status code + * Set header Content-Security-Policy * * @access public - * @param integer $status_code HTTP status code + * @param array $policies + * @return $this */ - public function status($status_code) + public function withContentSecurityPolicy(array $policies = array()) { - header('Status: '.$status_code); - header($this->request->getServerVariable('SERVER_PROTOCOL').' '.$status_code); + $values = ''; + + foreach ($policies as $policy => $acl) { + $values .= $policy.' '.trim($acl).'; '; + } + + $this->withHeader('Content-Security-Policy', $values); + return $this; } /** - * Redirect to another URL + * Set header X-Frame-Options * * @access public - * @param string $url Redirection URL - * @param boolean $self If Ajax request and true: refresh the current page + * @return $this */ - public function redirect($url, $self = false) + public function withXframe() { - if ($this->request->isAjax()) { - header('X-Ajax-Redirect: '.($self ? 'self' : $url)); - } else { - header('Location: '.$url); - } - - exit; + $this->withHeader('X-Frame-Options', 'DENY'); + return $this; } /** - * Send a CSV response + * Set header Strict-Transport-Security (only if we use HTTPS) * * @access public - * @param array $data Data to serialize in csv - * @param integer $status_code HTTP status code + * @return $this */ - public function csv(array $data, $status_code = 200) + public function withStrictTransportSecurity() { - $this->status($status_code); - $this->nocache(); + if ($this->request->isHTTPS()) { + $this->withHeader('Strict-Transport-Security', 'max-age=31536000'); + } - header('Content-Type: text/csv'); - Csv::output($data); - exit; + return $this; } /** - * Send a Json response + * Set HTTP response body * * @access public - * @param array $data Data to serialize in json - * @param integer $status_code HTTP status code + * @param string $body + * @return $this */ - public function json(array $data, $status_code = 200) + public function withBody($body) { - $this->status($status_code); - $this->nocache(); - header('Content-Type: application/json'); - echo json_encode($data); - exit; + $this->httpBody = $body; + return $this; } /** - * Send a text response + * Send headers to cache a resource * * @access public - * @param string $data Raw data - * @param integer $status_code HTTP status code + * @param integer $duration + * @param string $etag + * @return $this */ - public function text($data, $status_code = 200) + public function withCache($duration, $etag = '') { - $this->status($status_code); - $this->nocache(); - header('Content-Type: text/plain; charset=utf-8'); - echo $data; - exit; + $this + ->withHeader('Pragma', 'cache') + ->withHeader('Expires', gmdate('D, d M Y H:i:s', time() + $duration) . ' GMT') + ->withHeader('Cache-Control', 'public, max-age=' . $duration) + ; + + if ($etag) { + $this->withHeader('ETag', '"' . $etag . '"'); + } + + return $this; } /** - * Send a HTML response + * Send no cache headers * * @access public - * @param string $data Raw data - * @param integer $status_code HTTP status code + * @return $this */ - public function html($data, $status_code = 200) + public function withoutCache() { - $this->status($status_code); - $this->nocache(); - header('Content-Type: text/html; charset=utf-8'); - echo $data; - exit; + $this->withHeader('Pragma', 'no-cache'); + $this->withHeader('Expires', 'Sat, 26 Jul 1997 05:00:00 GMT'); + return $this; } /** - * Send a XML response + * Force the browser to download an attachment * * @access public - * @param string $data Raw data - * @param integer $status_code HTTP status code + * @param string $filename + * @return $this */ - public function xml($data, $status_code = 200) + public function withDownload($filename) { - $this->status($status_code); - $this->nocache(); - header('Content-Type: text/xml; charset=utf-8'); - echo $data; - exit; + $this->withHeader('Content-Disposition', 'attachment; filename="'.$filename.'"'); + $this->withHeader('Content-Transfer-Encoding', 'binary'); + $this->withHeader('Content-Type', 'application/octet-stream'); + return $this; } /** - * Send a javascript response + * Send headers and body * * @access public - * @param string $data Raw data - * @param integer $status_code HTTP status code */ - public function js($data, $status_code = 200) + public function send() { - $this->status($status_code); + if ($this->httpStatusCode !== 200) { + header('Status: '.$this->httpStatusCode); + header($this->request->getServerVariable('SERVER_PROTOCOL').' '.$this->httpStatusCode); + } - header('Content-Type: text/javascript; charset=utf-8'); - echo $data; + foreach ($this->httpHeaders as $header => $value) { + header($header.': '.$value); + } - exit; + if (! empty($this->httpBody)) { + echo $this->httpBody; + } } /** - * Send a css response + * Send a custom HTTP status code * * @access public - * @param string $data Raw data - * @param integer $status_code HTTP status code + * @param integer $statusCode */ - public function css($data, $status_code = 200) + public function status($statusCode) { - $this->status($status_code); + $this->withStatusCode($statusCode); + $this->send(); + } - header('Content-Type: text/css; charset=utf-8'); - echo $data; + /** + * Redirect to another URL + * + * @access public + * @param string $url Redirection URL + * @param boolean $self If Ajax request and true: refresh the current page + */ + public function redirect($url, $self = false) + { + if ($this->request->isAjax()) { + $this->withHeader('X-Ajax-Redirect', $self ? 'self' : $url); + } else { + $this->withHeader('Location', $url); + } - exit; + $this->send(); } /** - * Send a binary response + * Send a HTML response * * @access public - * @param string $data Raw data - * @param integer $status_code HTTP status code + * @param string $data + * @param integer $statusCode */ - public function binary($data, $status_code = 200) + public function html($data, $statusCode = 200) { - $this->status($status_code); - $this->nocache(); - header('Content-Transfer-Encoding: binary'); - header('Content-Type: application/octet-stream'); - echo $data; - exit; + $this->withStatusCode($statusCode); + $this->withContentType('text/html; charset=utf-8'); + $this->withBody($data); + $this->send(); } /** - * Send a iCal response + * Send a text response * * @access public - * @param string $data Raw data - * @param integer $status_code HTTP status code + * @param string $data + * @param integer $statusCode */ - public function ical($data, $status_code = 200) + public function text($data, $statusCode = 200) { - $this->status($status_code); - $this->contentType('text/calendar; charset=utf-8'); - echo $data; + $this->withStatusCode($statusCode); + $this->withContentType('text/plain; charset=utf-8'); + $this->withBody($data); + $this->send(); } /** - * Send the security header: Content-Security-Policy + * Send a CSV response * * @access public - * @param array $policies CSP rules + * @param array $data Data to serialize in csv */ - public function csp(array $policies = array()) + public function csv(array $data) { - $values = ''; + $this->withoutCache(); + $this->withContentType('text/csv; charset=utf-8'); + $this->send(); + Csv::output($data); + } - foreach ($policies as $policy => $acl) { - $values .= $policy.' '.trim($acl).'; '; - } + /** + * Send a Json response + * + * @access public + * @param array $data Data to serialize in json + * @param integer $statusCode HTTP status code + */ + public function json(array $data, $statusCode = 200) + { + $this->withStatusCode($statusCode); + $this->withContentType('application/json'); + $this->withoutCache(); + $this->withBody(json_encode($data)); + $this->send(); + } - header('Content-Security-Policy: '.$values); + /** + * Send a XML response + * + * @access public + * @param string $data + * @param integer $statusCode + */ + public function xml($data, $statusCode = 200) + { + $this->withStatusCode($statusCode); + $this->withContentType('text/xml; charset=utf-8'); + $this->withoutCache(); + $this->withBody($data); + $this->send(); } /** - * Send the security header: X-Content-Type-Options + * Send a javascript response * * @access public + * @param string $data + * @param integer $statusCode */ - public function nosniff() + public function js($data, $statusCode = 200) { - header('X-Content-Type-Options: nosniff'); + $this->withStatusCode($statusCode); + $this->withContentType('text/javascript; charset=utf-8'); + $this->withBody($data); + $this->send(); } /** - * Send the security header: X-XSS-Protection + * Send a css response * * @access public + * @param string $data + * @param integer $statusCode */ - public function xss() + public function css($data, $statusCode = 200) { - header('X-XSS-Protection: 1; mode=block'); + $this->withStatusCode($statusCode); + $this->withContentType('text/css; charset=utf-8'); + $this->withBody($data); + $this->send(); } /** - * Send the security header: Strict-Transport-Security (only if we use HTTPS) + * Send a binary response * * @access public + * @param string $data + * @param integer $statusCode */ - public function hsts() + public function binary($data, $statusCode = 200) { - if ($this->request->isHTTPS()) { - header('Strict-Transport-Security: max-age=31536000'); - } + $this->withStatusCode($statusCode); + $this->withoutCache(); + $this->withHeader('Content-Transfer-Encoding', 'binary'); + $this->withContentType('application/octet-stream'); + $this->withBody($data); + $this->send(); } /** - * Send the security header: X-Frame-Options (deny by default) + * Send a iCal response * * @access public - * @param string $mode Frame option mode - * @param array $urls Allowed urls for the given mode + * @param string $data + * @param integer $statusCode */ - public function xframe($mode = 'DENY', array $urls = array()) + public function ical($data, $statusCode = 200) { - header('X-Frame-Options: '.$mode.' '.implode(' ', $urls)); + $this->withStatusCode($statusCode); + $this->withContentType('text/calendar; charset=utf-8'); + $this->withBody($data); + $this->send(); } } diff --git a/app/Core/Http/Route.php b/app/Core/Http/Route.php index 7836146d..9b45b725 100644 --- a/app/Core/Http/Route.php +++ b/app/Core/Http/Route.php @@ -119,8 +119,8 @@ class Route extends Base } return array( - 'controller' => 'app', - 'action' => 'index', + 'controller' => 'DashboardController', + 'action' => 'show', 'plugin' => '', ); } diff --git a/app/Core/Http/Router.php b/app/Core/Http/Router.php index 0fe80ecc..4de276a0 100644 --- a/app/Core/Http/Router.php +++ b/app/Core/Http/Router.php @@ -2,7 +2,6 @@ namespace Kanboard\Core\Http; -use RuntimeException; use Kanboard\Core\Base; /** @@ -13,13 +12,16 @@ use Kanboard\Core\Base; */ class Router extends Base { + const DEFAULT_CONTROLLER = 'DashboardController'; + const DEFAULT_METHOD = 'show'; + /** * Plugin name * * @access private * @var string */ - private $plugin = ''; + private $currentPluginName = ''; /** * Controller @@ -27,7 +29,7 @@ class Router extends Base * @access private * @var string */ - private $controller = ''; + private $currentControllerName = ''; /** * Action @@ -35,7 +37,7 @@ class Router extends Base * @access private * @var string */ - private $action = ''; + private $currentActionName = ''; /** * Get plugin name @@ -45,7 +47,7 @@ class Router extends Base */ public function getPlugin() { - return $this->plugin; + return $this->currentPluginName; } /** @@ -56,7 +58,7 @@ class Router extends Base */ public function getController() { - return $this->controller; + return $this->currentControllerName; } /** @@ -67,7 +69,7 @@ class Router extends Base */ public function getAction() { - return $this->action; + return $this->currentActionName; } /** @@ -109,11 +111,9 @@ class Router extends Base $plugin = $route['plugin']; } - $this->controller = ucfirst($this->sanitize($controller, 'app')); - $this->action = $this->sanitize($action, 'index'); - $this->plugin = ucfirst($this->sanitize($plugin)); - - return $this->executeAction(); + $this->currentControllerName = ucfirst($this->sanitize($controller, self::DEFAULT_CONTROLLER)); + $this->currentActionName = $this->sanitize($action, self::DEFAULT_METHOD); + $this->currentPluginName = ucfirst($this->sanitize($plugin)); } /** @@ -128,42 +128,4 @@ class Router extends Base { return preg_match('/^[a-zA-Z_0-9]+$/', $value) ? $value : $default; } - - /** - * Execute controller action - * - * @access private - */ - private function executeAction() - { - $class = $this->getControllerClassName(); - - if (! class_exists($class)) { - throw new RuntimeException('Controller not found'); - } - - if (! method_exists($class, $this->action)) { - throw new RuntimeException('Action not implemented'); - } - - $instance = new $class($this->container); - $instance->beforeAction(); - $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/LayoutHelper.php b/app/Helper/LayoutHelper.php index cbda85a3..39defc88 100644 --- a/app/Helper/LayoutHelper.php +++ b/app/Helper/LayoutHelper.php @@ -130,7 +130,7 @@ class LayoutHelper extends Base */ public function dashboard($template, array $params) { - return $this->subLayout('app/layout', 'app/sidebar', $template, $params); + return $this->subLayout('dashboard/layout', 'dashboard/sidebar', $template, $params); } /** diff --git a/app/Middleware/ApplicationAuthorizationMiddleware.php b/app/Middleware/ApplicationAuthorizationMiddleware.php new file mode 100644 index 00000000..faca2d6a --- /dev/null +++ b/app/Middleware/ApplicationAuthorizationMiddleware.php @@ -0,0 +1,27 @@ +<?php + +namespace Kanboard\Middleware; + +use Kanboard\Core\Controller\AccessForbiddenException; +use Kanboard\Core\Controller\BaseMiddleware; + +/** + * Class ApplicationAuthorizationMiddleware + * + * @package Kanboard\Middleware + * @author Frederic Guillot + */ +class ApplicationAuthorizationMiddleware extends BaseMiddleware +{ + /** + * Execute middleware + */ + public function execute() + { + if (! $this->helper->user->hasAccess($this->router->getController(), $this->router->getAction())) { + throw new AccessForbiddenException(); + } + + $this->next(); + } +} diff --git a/app/Middleware/AuthenticationMiddleware.php b/app/Middleware/AuthenticationMiddleware.php new file mode 100644 index 00000000..a31198a5 --- /dev/null +++ b/app/Middleware/AuthenticationMiddleware.php @@ -0,0 +1,56 @@ +<?php + +namespace Kanboard\Middleware; + +use Kanboard\Core\Controller\AccessForbiddenException; +use Kanboard\Core\Controller\BaseMiddleware; +use Kanboard\Core\Security\Role; + +/** + * Class AuthenticationMiddleware + * + * @package Kanboard\Middleware + * @author Frederic Guillot + */ +class AuthenticationMiddleware extends BaseMiddleware +{ + /** + * Execute middleware + */ + public function execute() + { + if (! $this->authenticationManager->checkCurrentSession()) { + throw AccessForbiddenException::getInstance()->withoutLayout(); + } + + if (! $this->isPublicAccess()) { + $this->handleAuthentication(); + } + + $this->next(); + } + + protected function handleAuthentication() + { + if (! $this->userSession->isLogged() && ! $this->authenticationManager->preAuthentication()) { + $this->setNextMiddleware(null); + + if ($this->request->isAjax()) { + $this->response->text('Not Authorized', 401); + } else { + $this->sessionStorage->redirectAfterLogin = $this->request->getUri(); + $this->response->redirect($this->helper->url->to('auth', 'login')); + } + } + } + + private function isPublicAccess() + { + if ($this->applicationAuthorization->isAllowed($this->router->getController(), $this->router->getAction(), Role::APP_PUBLIC)) { + $this->setNextMiddleware(null); + return true; + } + + return false; + } +} diff --git a/app/Middleware/BootstrapMiddleware.php b/app/Middleware/BootstrapMiddleware.php new file mode 100644 index 00000000..c9de1de9 --- /dev/null +++ b/app/Middleware/BootstrapMiddleware.php @@ -0,0 +1,44 @@ +<?php + +namespace Kanboard\Middleware; + +use Kanboard\Core\Controller\BaseMiddleware; + +/** + * Class BootstrapMiddleware + * + * @package Kanboard\Middleware + * @author Frederic Guillot + */ +class BootstrapMiddleware extends BaseMiddleware +{ + /** + * Execute middleware + */ + public function execute() + { + $this->sessionManager->open(); + $this->dispatcher->dispatch('app.bootstrap'); + $this->sendHeaders(); + $this->next(); + } + + /** + * Send HTTP headers + * + * @access private + */ + private function sendHeaders() + { + $this->response->withContentSecurityPolicy($this->container['cspRules']); + $this->response->withSecurityHeaders(); + + if (ENABLE_XFRAME && $this->router->getAction() !== 'readonly') { + $this->response->withXframe(); + } + + if (ENABLE_HSTS) { + $this->response->withStrictTransportSecurity(); + } + } +} diff --git a/app/Middleware/PostAuthenticationMiddleware.php b/app/Middleware/PostAuthenticationMiddleware.php new file mode 100644 index 00000000..8287c10e --- /dev/null +++ b/app/Middleware/PostAuthenticationMiddleware.php @@ -0,0 +1,36 @@ +<?php + +namespace Kanboard\Middleware; + +use Kanboard\Core\Controller\BaseMiddleware; + +/** + * Class PostAuthenticationMiddleware + * + * @package Kanboard\Middleware + * @author Frederic Guillot + */ +class PostAuthenticationMiddleware extends BaseMiddleware +{ + /** + * Execute middleware + */ + public function execute() + { + $controller = strtolower($this->router->getController()); + $action = strtolower($this->router->getAction()); + $ignore = ($controller === 'twofactor' && in_array($action, array('code', 'check'))) || ($controller === 'auth' && $action === 'logout'); + + if ($ignore === false && $this->userSession->hasPostAuthentication() && ! $this->userSession->isPostAuthenticationValidated()) { + $this->setNextMiddleware(null); + + if ($this->request->isAjax()) { + $this->response->text('Not Authorized', 401); + } + + $this->response->redirect($this->helper->url->to('twofactor', 'code')); + } + + $this->next(); + } +} diff --git a/app/Middleware/ProjectAuthorizationMiddleware.php b/app/Middleware/ProjectAuthorizationMiddleware.php new file mode 100644 index 00000000..6000ee0e --- /dev/null +++ b/app/Middleware/ProjectAuthorizationMiddleware.php @@ -0,0 +1,34 @@ +<?php + +namespace Kanboard\Middleware; + +use Kanboard\Core\Controller\AccessForbiddenException; +use Kanboard\Core\Controller\BaseMiddleware; + +/** + * Class ProjectAuthorizationMiddleware + * + * @package Kanboard\Middleware + * @author Frederic Guillot + */ +class ProjectAuthorizationMiddleware extends BaseMiddleware +{ + /** + * Execute middleware + */ + public function execute() + { + $project_id = $this->request->getIntegerParam('project_id'); + $task_id = $this->request->getIntegerParam('task_id'); + + if ($task_id > 0 && $project_id === 0) { + $project_id = $this->taskFinder->getProjectId($task_id); + } + + if ($project_id > 0 && ! $this->helper->user->hasProjectAccess($this->router->getController(), $this->router->getAction(), $project_id)) { + throw new AccessForbiddenException(); + } + + $this->next(); + } +} diff --git a/app/ServiceProvider/RouteProvider.php b/app/ServiceProvider/RouteProvider.php index 30e7b648..2788e3a7 100644 --- a/app/ServiceProvider/RouteProvider.php +++ b/app/ServiceProvider/RouteProvider.php @@ -31,14 +31,14 @@ class RouteProvider implements ServiceProviderInterface $container['route']->enable(); // Dashboard - $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'); - $container['route']->addRoute('dashboard/:user_id/notifications', 'app', 'notifications'); + $container['route']->addRoute('dashboard', 'DashboardController', 'show'); + $container['route']->addRoute('dashboard/:user_id', 'DashboardController', 'show'); + $container['route']->addRoute('dashboard/:user_id/projects', 'DashboardController', 'projects'); + $container['route']->addRoute('dashboard/:user_id/tasks', 'DashboardController', 'tasks'); + $container['route']->addRoute('dashboard/:user_id/subtasks', 'DashboardController', 'subtasks'); + $container['route']->addRoute('dashboard/:user_id/calendar', 'DashboardController', 'calendar'); + $container['route']->addRoute('dashboard/:user_id/activity', 'DashboardController', 'activity'); + $container['route']->addRoute('dashboard/:user_id/notifications', 'DashboardController', 'notifications'); // Search routes $container['route']->addRoute('search', 'search', 'index'); diff --git a/app/Subscriber/BootstrapSubscriber.php b/app/Subscriber/BootstrapSubscriber.php index a376a935..b82405f8 100644 --- a/app/Subscriber/BootstrapSubscriber.php +++ b/app/Subscriber/BootstrapSubscriber.php @@ -29,7 +29,7 @@ class BootstrapSubscriber extends BaseSubscriber implements EventSubscriberInter { if (DEBUG) { foreach ($this->db->getLogMessages() as $message) { - $this->logger->debug($message); + $this->logger->debug('SQL: ' . $message); } $this->logger->debug('nb_queries={nb}', array('nb' => $this->db->getStatementHandler()->getNbQueries())); diff --git a/app/Template/app/sidebar.php b/app/Template/app/sidebar.php deleted file mode 100644 index 66d15b14..00000000 --- a/app/Template/app/sidebar.php +++ /dev/null @@ -1,27 +0,0 @@ -<div class="sidebar"> - <h2><?= $this->text->e($user['name'] ?: $user['username']) ?></h2> - <ul> - <li <?= $this->app->checkMenuSelection('app', 'index') ?>> - <?= $this->url->link(t('Overview'), 'app', 'index', array('user_id' => $user['id'])) ?> - </li> - <li <?= $this->app->checkMenuSelection('app', 'projects') ?>> - <?= $this->url->link(t('My projects'), 'app', 'projects', array('user_id' => $user['id'])) ?> - </li> - <li <?= $this->app->checkMenuSelection('app', 'tasks') ?>> - <?= $this->url->link(t('My tasks'), 'app', 'tasks', array('user_id' => $user['id'])) ?> - </li> - <li <?= $this->app->checkMenuSelection('app', 'subtasks') ?>> - <?= $this->url->link(t('My subtasks'), 'app', 'subtasks', array('user_id' => $user['id'])) ?> - </li> - <li <?= $this->app->checkMenuSelection('app', 'calendar') ?>> - <?= $this->url->link(t('My calendar'), 'app', 'calendar', array('user_id' => $user['id'])) ?> - </li> - <li <?= $this->app->checkMenuSelection('app', 'activity') ?>> - <?= $this->url->link(t('My activity stream'), 'app', 'activity', array('user_id' => $user['id'])) ?> - </li> - <li <?= $this->app->checkMenuSelection('app', 'notifications') ?>> - <?= $this->url->link(t('My notifications'), 'app', 'notifications', array('user_id' => $user['id'])) ?> - </li> - <?= $this->hook->render('template:dashboard:sidebar') ?> - </ul> -</div>
\ No newline at end of file diff --git a/app/Template/config/application.php b/app/Template/config/application.php index 259756bc..ee0e147b 100644 --- a/app/Template/config/application.php +++ b/app/Template/config/application.php @@ -1,7 +1,7 @@ <div class="page-header"> <h2><?= t('Application settings') ?></h2> </div> -<form method="post" action="<?= $this->url->href('config', 'application') ?>" autocomplete="off"> +<form method="post" action="<?= $this->url->href('config', 'save', array('redirect' => 'application')) ?>" autocomplete="off"> <?= $this->form->csrf() ?> diff --git a/app/Template/config/board.php b/app/Template/config/board.php index ba1bab59..75cd40ef 100644 --- a/app/Template/config/board.php +++ b/app/Template/config/board.php @@ -1,7 +1,7 @@ <div class="page-header"> <h2><?= t('Board settings') ?></h2> </div> -<form method="post" action="<?= $this->url->href('config', 'board') ?>" autocomplete="off"> +<form method="post" action="<?= $this->url->href('config', 'save', array('redirect' => 'board')) ?>" autocomplete="off"> <?= $this->form->csrf() ?> diff --git a/app/Template/config/calendar.php b/app/Template/config/calendar.php index b7b230df..37084a8b 100644 --- a/app/Template/config/calendar.php +++ b/app/Template/config/calendar.php @@ -2,7 +2,7 @@ <h2><?= t('Calendar settings') ?></h2> </div> <section> -<form method="post" action="<?= $this->url->href('config', 'calendar') ?>" autocomplete="off"> +<form method="post" action="<?= $this->url->href('config', 'save', array('redirect' => 'calendar')) ?>" autocomplete="off"> <?= $this->form->csrf() ?> @@ -31,4 +31,4 @@ <button type="submit" class="btn btn-blue"><?= t('Save') ?></button> </div> </form> -</section>
\ No newline at end of file +</section> diff --git a/app/Template/config/integrations.php b/app/Template/config/integrations.php index e404c52e..2a29b358 100644 --- a/app/Template/config/integrations.php +++ b/app/Template/config/integrations.php @@ -2,7 +2,7 @@ <h2><?= t('Integration with third-party services') ?></h2> </div> -<form method="post" action="<?= $this->url->href('config', 'integrations') ?>" autocomplete="off"> +<form method="post" action="<?= $this->url->href('config', 'save', array('redirect' => 'integrations')) ?>" autocomplete="off"> <?= $this->form->csrf() ?> <?= $this->hook->render('template:config:integrations', array('values' => $values)) ?> @@ -14,4 +14,4 @@ <div class="form-actions"> <button type="submit" class="btn btn-blue"><?= t('Save') ?></button> </div> -</form>
\ No newline at end of file +</form> diff --git a/app/Template/config/layout.php b/app/Template/config/layout.php index f34caaab..6eafa593 100644 --- a/app/Template/config/layout.php +++ b/app/Template/config/layout.php @@ -1,10 +1,9 @@ <section id="main"> <section class="sidebar-container" id="config-section"> - <?= $this->render($sidebar_template) ?> <div class="sidebar-content"> <?= $content_for_sublayout ?> </div> </section> -</section>
\ No newline at end of file +</section> diff --git a/app/Template/config/project.php b/app/Template/config/project.php index b6b7ec25..b0112773 100644 --- a/app/Template/config/project.php +++ b/app/Template/config/project.php @@ -1,7 +1,7 @@ <div class="page-header"> <h2><?= t('Project settings') ?></h2> </div> -<form method="post" action="<?= $this->url->href('config', 'project') ?>" autocomplete="off"> +<form method="post" action="<?= $this->url->href('config', 'save', array('redirect' => 'project')) ?>" autocomplete="off"> <?= $this->form->csrf() ?> diff --git a/app/Template/config/webhook.php b/app/Template/config/webhook.php index b96979a0..5db1fa6e 100644 --- a/app/Template/config/webhook.php +++ b/app/Template/config/webhook.php @@ -2,7 +2,7 @@ <h2><?= t('Webhook settings') ?></h2> </div> <section> -<form method="post" action="<?= $this->url->href('config', 'webhook') ?>" autocomplete="off"> +<form method="post" action="<?= $this->url->href('config', 'save', array('redirect' => 'webhook')) ?>" autocomplete="off"> <?= $this->form->csrf() ?> @@ -32,4 +32,4 @@ <?= $this->url->link(t('Reset token'), 'config', 'token', array('type' => 'webhook'), true) ?> </li> </ul> -</section>
\ No newline at end of file +</section> diff --git a/app/Template/app/activity.php b/app/Template/dashboard/activity.php index 71a67fb2..71a67fb2 100644 --- a/app/Template/app/activity.php +++ b/app/Template/dashboard/activity.php diff --git a/app/Template/app/calendar.php b/app/Template/dashboard/calendar.php index a154203b..a154203b 100644 --- a/app/Template/app/calendar.php +++ b/app/Template/dashboard/calendar.php diff --git a/app/Template/app/layout.php b/app/Template/dashboard/layout.php index 2a32ac02..2a32ac02 100644 --- a/app/Template/app/layout.php +++ b/app/Template/dashboard/layout.php diff --git a/app/Template/app/notifications.php b/app/Template/dashboard/notifications.php index b64eb0b7..b64eb0b7 100644 --- a/app/Template/app/notifications.php +++ b/app/Template/dashboard/notifications.php diff --git a/app/Template/app/projects.php b/app/Template/dashboard/projects.php index c0110b07..cdf19bdf 100644 --- a/app/Template/app/projects.php +++ b/app/Template/dashboard/projects.php @@ -1,5 +1,5 @@ <div class="page-header"> - <h2><?= $this->url->link(t('My projects'), 'app', 'projects', array('user_id' => $user['id'])) ?> (<?= $paginator->getTotal() ?>)</h2> + <h2><?= $this->url->link(t('My projects'), 'DashboardController', 'projects', array('user_id' => $user['id'])) ?> (<?= $paginator->getTotal() ?>)</h2> </div> <?php if ($paginator->isEmpty()): ?> <p class="alert"><?= t('Your are not member of any project.') ?></p> diff --git a/app/Template/app/overview.php b/app/Template/dashboard/show.php index 0b354791..bc4e2952 100644 --- a/app/Template/app/overview.php +++ b/app/Template/dashboard/show.php @@ -7,6 +7,6 @@ </form> </div> -<?= $this->render('app/projects', array('paginator' => $project_paginator, 'user' => $user)) ?> -<?= $this->render('app/tasks', array('paginator' => $task_paginator, 'user' => $user)) ?> -<?= $this->render('app/subtasks', array('paginator' => $subtask_paginator, 'user' => $user)) ?>
\ No newline at end of file +<?= $this->render('dashboard/projects', array('paginator' => $project_paginator, 'user' => $user)) ?> +<?= $this->render('dashboard/tasks', array('paginator' => $task_paginator, 'user' => $user)) ?> +<?= $this->render('dashboard/subtasks', array('paginator' => $subtask_paginator, 'user' => $user)) ?> diff --git a/app/Template/dashboard/sidebar.php b/app/Template/dashboard/sidebar.php new file mode 100644 index 00000000..86cc20f8 --- /dev/null +++ b/app/Template/dashboard/sidebar.php @@ -0,0 +1,27 @@ +<div class="sidebar"> + <h2><?= $this->text->e($user['name'] ?: $user['username']) ?></h2> + <ul> + <li <?= $this->app->checkMenuSelection('DashboardController', 'show') ?>> + <?= $this->url->link(t('Overview'), 'DashboardController', 'show', array('user_id' => $user['id'])) ?> + </li> + <li <?= $this->app->checkMenuSelection('DashboardController', 'projects') ?>> + <?= $this->url->link(t('My projects'), 'DashboardController', 'projects', array('user_id' => $user['id'])) ?> + </li> + <li <?= $this->app->checkMenuSelection('DashboardController', 'tasks') ?>> + <?= $this->url->link(t('My tasks'), 'DashboardController', 'tasks', array('user_id' => $user['id'])) ?> + </li> + <li <?= $this->app->checkMenuSelection('DashboardController', 'subtasks') ?>> + <?= $this->url->link(t('My subtasks'), 'DashboardController', 'subtasks', array('user_id' => $user['id'])) ?> + </li> + <li <?= $this->app->checkMenuSelection('DashboardController', 'calendar') ?>> + <?= $this->url->link(t('My calendar'), 'DashboardController', 'calendar', array('user_id' => $user['id'])) ?> + </li> + <li <?= $this->app->checkMenuSelection('DashboardController', 'activity') ?>> + <?= $this->url->link(t('My activity stream'), 'DashboardController', 'activity', array('user_id' => $user['id'])) ?> + </li> + <li <?= $this->app->checkMenuSelection('DashboardController', 'notifications') ?>> + <?= $this->url->link(t('My notifications'), 'DashboardController', 'notifications', array('user_id' => $user['id'])) ?> + </li> + <?= $this->hook->render('template:dashboard:sidebar') ?> + </ul> +</div> diff --git a/app/Template/app/subtasks.php b/app/Template/dashboard/subtasks.php index cca09481..ee6caf02 100644 --- a/app/Template/app/subtasks.php +++ b/app/Template/dashboard/subtasks.php @@ -1,5 +1,5 @@ <div class="page-header"> - <h2><?= $this->url->link(t('My subtasks'), 'app', 'subtasks', array('user_id' => $user['id'])) ?> (<?= $paginator->getTotal() ?>)</h2> + <h2><?= $this->url->link(t('My subtasks'), 'DashboardController', 'subtasks', array('user_id' => $user['id'])) ?> (<?= $paginator->getTotal() ?>)</h2> </div> <?php if ($paginator->isEmpty()): ?> <p class="alert"><?= t('There is nothing assigned to you.') ?></p> @@ -40,4 +40,4 @@ </table> <?= $paginator ?> -<?php endif ?>
\ No newline at end of file +<?php endif ?> diff --git a/app/Template/app/tasks.php b/app/Template/dashboard/tasks.php index f0ed61e0..71b62572 100644 --- a/app/Template/app/tasks.php +++ b/app/Template/dashboard/tasks.php @@ -1,5 +1,5 @@ <div class="page-header"> - <h2><?= $this->url->link(t('My tasks'), 'app', 'tasks', array('user_id' => $user['id'])) ?> (<?= $paginator->getTotal() ?>)</h2> + <h2><?= $this->url->link(t('My tasks'), 'DashboardController', 'tasks', array('user_id' => $user['id'])) ?> (<?= $paginator->getTotal() ?>)</h2> </div> <?php if ($paginator->isEmpty()): ?> <p class="alert"><?= t('There is nothing assigned to you.') ?></p> diff --git a/app/Template/header.php b/app/Template/header.php index 3885e671..f47b270f 100644 --- a/app/Template/header.php +++ b/app/Template/header.php @@ -2,7 +2,7 @@ <nav> <h1> <span class="logo"> - <?= $this->url->link('K<span>B</span>', 'app', 'index', array(), false, '', t('Dashboard')) ?> + <?= $this->url->link('K<span>B</span>', 'DashboardController', 'show', array(), false, '', t('Dashboard')) ?> </span> <span class="title"> <?= $this->text->e($title) ?> @@ -34,7 +34,7 @@ <li class="user-links"> <?php if ($this->user->hasNotifications()): ?> <span class="notification"> - <?= $this->url->link('<i class="fa fa-bell web-notification-icon"></i>', 'app', 'notifications', array('user_id' => $this->user->getId()), false, '', t('Unread notifications')) ?> + <?= $this->url->link('<i class="fa fa-bell web-notification-icon"></i>', 'DashboardController', 'notifications', array('user_id' => $this->user->getId()), false, '', t('Unread notifications')) ?> </span> <?php endif ?> @@ -63,7 +63,7 @@ <li class="no-hover"><strong><?= $this->text->e($this->user->getFullname()) ?></strong></li> <li> <i class="fa fa-tachometer fa-fw"></i> - <?= $this->url->link(t('My dashboard'), 'app', 'index', array('user_id' => $this->user->getId())) ?> + <?= $this->url->link(t('My dashboard'), 'DashboardController', 'show', array('user_id' => $this->user->getId())) ?> </li> <li> <i class="fa fa-home fa-fw"></i> diff --git a/app/Template/layout.php b/app/Template/layout.php index a80fc288..701b297d 100644 --- a/app/Template/layout.php +++ b/app/Template/layout.php @@ -44,7 +44,7 @@ <?= $this->hook->render('template:layout:head') ?> </head> - <body data-status-url="<?= $this->url->href('app', 'status') ?>" + <body data-status-url="<?= $this->url->href('UserHelper', 'status') ?>" data-login-url="<?= $this->url->href('auth', 'login') ?>" data-keyboard-shortcut-url="<?= $this->url->href('Doc', 'shortcuts') ?>" data-timezone="<?= $this->app->getTimezone() ?>" diff --git a/app/Template/user/sidebar.php b/app/Template/user/sidebar.php index 5ea2e355..9da43ced 100644 --- a/app/Template/user/sidebar.php +++ b/app/Template/user/sidebar.php @@ -8,7 +8,7 @@ <?php endif ?> <?php if ($this->user->isAdmin()): ?> <li> - <?= $this->url->link(t('User dashboard'), 'app', 'index', array('user_id' => $user['id'])) ?> + <?= $this->url->link(t('User dashboard'), 'DashboardController', 'show', array('user_id' => $user['id'])) ?> </li> <?php endif ?> <?php if ($this->user->isAdmin() || $this->user->isCurrentUser($user['id'])): ?> @@ -80,4 +80,4 @@ <?= $this->hook->render('template:user:sidebar:actions', array('user' => $user)) ?> </ul> -</div>
\ No newline at end of file +</div> |