summaryrefslogtreecommitdiff
path: root/app/Controller
diff options
context:
space:
mode:
Diffstat (limited to 'app/Controller')
-rw-r--r--app/Controller/Action.php30
-rw-r--r--app/Controller/Analytic.php170
-rw-r--r--app/Controller/App.php117
-rw-r--r--app/Controller/Auth.php67
-rw-r--r--app/Controller/Base.php263
-rw-r--r--app/Controller/Board.php501
-rw-r--r--app/Controller/Budget.php135
-rw-r--r--app/Controller/Calendar.php128
-rw-r--r--app/Controller/Category.php56
-rw-r--r--app/Controller/Column.php170
-rw-r--r--app/Controller/Comment.php76
-rw-r--r--app/Controller/Config.php84
-rw-r--r--app/Controller/Currency.php89
-rw-r--r--app/Controller/Export.php85
-rw-r--r--app/Controller/File.php79
-rw-r--r--app/Controller/Hourlyrate.php89
-rw-r--r--app/Controller/Ical.php99
-rw-r--r--app/Controller/Link.php162
-rw-r--r--app/Controller/Project.php333
-rw-r--r--app/Controller/Projectinfo.php97
-rw-r--r--app/Controller/Subtask.php184
-rw-r--r--app/Controller/Swimlane.php256
-rw-r--r--app/Controller/Task.php336
-rw-r--r--app/Controller/Tasklink.php179
-rw-r--r--app/Controller/Timetable.php39
-rw-r--r--app/Controller/Timetableday.php88
-rw-r--r--app/Controller/Timetableextra.php16
-rw-r--r--app/Controller/Timetableoff.php107
-rw-r--r--app/Controller/Timetableweek.php99
-rw-r--r--app/Controller/Twofactor.php167
-rw-r--r--app/Controller/User.php243
-rw-r--r--app/Controller/Webhook.php88
32 files changed, 3570 insertions, 1062 deletions
diff --git a/app/Controller/Action.php b/app/Controller/Action.php
index 714c87f3..cd24453a 100644
--- a/app/Controller/Action.php
+++ b/app/Controller/Action.php
@@ -17,9 +17,9 @@ class Action extends Base
*/
public function index()
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
- $this->response->html($this->projectLayout('action_index', array(
+ $this->response->html($this->projectLayout('action/index', array(
'values' => array('project_id' => $project['id']),
'project' => $project,
'actions' => $this->action->getAllByProject($project['id']),
@@ -27,11 +27,10 @@ class Action extends Base
'available_events' => $this->action->getAvailableEvents(),
'available_params' => $this->action->getAllActionParameters(),
'columns_list' => $this->board->getColumnsList($project['id']),
- 'users_list' => $this->projectPermission->getUsersList($project['id']),
+ 'users_list' => $this->projectPermission->getMemberList($project['id']),
'projects_list' => $this->project->getList(false),
'colors_list' => $this->color->getList(),
'categories_list' => $this->category->getList($project['id']),
- 'menu' => 'projects',
'title' => t('Automatic actions')
)));
}
@@ -43,18 +42,17 @@ class Action extends Base
*/
public function event()
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
$values = $this->request->getValues();
if (empty($values['action_name']) || empty($values['project_id'])) {
$this->response->redirect('?controller=action&action=index&project_id='.$project['id']);
}
- $this->response->html($this->projectLayout('action_event', array(
+ $this->response->html($this->projectLayout('action/event', array(
'values' => $values,
'project' => $project,
'events' => $this->action->getCompatibleEvents($values['action_name']),
- 'menu' => 'projects',
'title' => t('Automatic actions')
)));
}
@@ -66,7 +64,7 @@ class Action extends Base
*/
public function params()
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
$values = $this->request->getValues();
if (empty($values['action_name']) || empty($values['project_id']) || empty($values['event_name'])) {
@@ -83,16 +81,15 @@ class Action extends Base
$projects_list = $this->project->getList(false);
unset($projects_list[$project['id']]);
- $this->response->html($this->projectLayout('action_params', array(
+ $this->response->html($this->projectLayout('action/params', array(
'values' => $values,
'action_params' => $action_params,
'columns_list' => $this->board->getColumnsList($project['id']),
- 'users_list' => $this->projectPermission->getUsersList($project['id']),
+ 'users_list' => $this->projectPermission->getMemberList($project['id']),
'projects_list' => $projects_list,
'colors_list' => $this->color->getList(),
'categories_list' => $this->category->getList($project['id']),
'project' => $project,
- 'menu' => 'projects',
'title' => t('Automatic actions')
)));
}
@@ -104,7 +101,7 @@ class Action extends Base
*/
public function create()
{
- $this->doCreation($this->getProjectManagement(), $this->request->getValues());
+ $this->doCreation($this->getProject(), $this->request->getValues());
}
/**
@@ -138,14 +135,13 @@ class Action extends Base
*/
public function confirm()
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
- $this->response->html($this->projectLayout('action_remove', array(
+ $this->response->html($this->projectLayout('action/remove', array(
'action' => $this->action->getById($this->request->getIntegerParam('action_id')),
'available_events' => $this->action->getAvailableEvents(),
'available_actions' => $this->action->getAvailableActions(),
'project' => $project,
- 'menu' => 'projects',
'title' => t('Remove an action')
)));
}
@@ -158,10 +154,10 @@ class Action extends Base
public function remove()
{
$this->checkCSRFParam();
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
$action = $this->action->getById($this->request->getIntegerParam('action_id'));
- if ($action && $this->action->remove($action['id'])) {
+ if (! empty($action) && $this->action->remove($action['id'])) {
$this->session->flash(t('Action removed successfully.'));
} else {
$this->session->flashError(t('Unable to remove this action.'));
diff --git a/app/Controller/Analytic.php b/app/Controller/Analytic.php
new file mode 100644
index 00000000..f31870e0
--- /dev/null
+++ b/app/Controller/Analytic.php
@@ -0,0 +1,170 @@
+<?php
+
+namespace Controller;
+
+/**
+ * Project Anaytic controller
+ *
+ * @package controller
+ * @author Frederic Guillot
+ */
+class Analytic extends Base
+{
+ /**
+ * Common layout for analytic views
+ *
+ * @access private
+ * @param string $template Template name
+ * @param array $params Template parameters
+ * @return string
+ */
+ private function layout($template, array $params)
+ {
+ $params['board_selector'] = $this->projectPermission->getAllowedProjects($this->userSession->getId());
+ $params['content_for_sublayout'] = $this->template->render($template, $params);
+
+ return $this->template->layout('analytic/layout', $params);
+ }
+
+ /**
+ * Show tasks distribution graph
+ *
+ * @access public
+ */
+ public function tasks()
+ {
+ $project = $this->getProject();
+ $metrics = $this->projectAnalytic->getTaskRepartition($project['id']);
+
+ if ($this->request->isAjax()) {
+ $this->response->json(array(
+ 'metrics' => $metrics,
+ 'labels' => array(
+ 'column_title' => t('Column'),
+ 'nb_tasks' => t('Number of tasks'),
+ )
+ ));
+ }
+ else {
+ $this->response->html($this->layout('analytic/tasks', array(
+ 'project' => $project,
+ 'metrics' => $metrics,
+ 'title' => t('Task repartition for "%s"', $project['name']),
+ )));
+ }
+ }
+
+ /**
+ * Show users repartition
+ *
+ * @access public
+ */
+ public function users()
+ {
+ $project = $this->getProject();
+ $metrics = $this->projectAnalytic->getUserRepartition($project['id']);
+
+ if ($this->request->isAjax()) {
+ $this->response->json(array(
+ 'metrics' => $metrics,
+ 'labels' => array(
+ 'user' => t('User'),
+ 'nb_tasks' => t('Number of tasks'),
+ )
+ ));
+ }
+ else {
+ $this->response->html($this->layout('analytic/users', array(
+ 'project' => $project,
+ 'metrics' => $metrics,
+ 'title' => t('User repartition for "%s"', $project['name']),
+ )));
+ }
+ }
+
+ /**
+ * Show cumulative flow diagram
+ *
+ * @access public
+ */
+ public function cfd()
+ {
+ $project = $this->getProject();
+ $values = $this->request->getValues();
+
+ $from = $this->request->getStringParam('from', date('Y-m-d', strtotime('-1week')));
+ $to = $this->request->getStringParam('to', date('Y-m-d'));
+
+ if (! empty($values)) {
+ $from = $values['from'];
+ $to = $values['to'];
+ }
+
+ if ($this->request->isAjax()) {
+ $this->response->json(array(
+ 'columns' => array_values($this->board->getColumnsList($project['id'])),
+ 'metrics' => $this->projectDailySummary->getRawMetrics($project['id'], $from, $to),
+ 'labels' => array(
+ 'column' => t('Column'),
+ 'day' => t('Date'),
+ 'total' => t('Tasks'),
+ )
+ ));
+ }
+ else {
+ $this->response->html($this->layout('analytic/cfd', array(
+ 'values' => array(
+ 'from' => $from,
+ 'to' => $to,
+ ),
+ 'display_graph' => $this->projectDailySummary->countDays($project['id'], $from, $to) >= 2,
+ 'project' => $project,
+ 'date_format' => $this->config->get('application_date_format'),
+ 'date_formats' => $this->dateParser->getAvailableFormats(),
+ 'title' => t('Cumulative flow diagram for "%s"', $project['name']),
+ )));
+ }
+ }
+
+ /**
+ * Show burndown chart
+ *
+ * @access public
+ */
+ public function burndown()
+ {
+ $project = $this->getProject();
+ $values = $this->request->getValues();
+
+ $from = $this->request->getStringParam('from', date('Y-m-d', strtotime('-1week')));
+ $to = $this->request->getStringParam('to', date('Y-m-d'));
+
+ if (! empty($values)) {
+ $from = $values['from'];
+ $to = $values['to'];
+ }
+
+ if ($this->request->isAjax()) {
+ $this->response->json(array(
+ 'metrics' => $this->projectDailySummary->getRawMetricsByDay($project['id'], $from, $to),
+ 'labels' => array(
+ 'day' => t('Date'),
+ 'score' => t('Complexity'),
+ )
+ ));
+ }
+ else {
+ $this->response->html($this->layout('analytic/burndown', array(
+ 'values' => array(
+ 'from' => $from,
+ 'to' => $to,
+ ),
+ 'display_graph' => $this->projectDailySummary->countDays($project['id'], $from, $to) >= 2,
+ 'project' => $project,
+ 'date_format' => $this->config->get('application_date_format'),
+ 'date_formats' => $this->dateParser->getAvailableFormats(),
+ 'title' => t('Burndown chart for "%s"', $project['name']),
+ )));
+ }
+ }
+}
diff --git a/app/Controller/App.php b/app/Controller/App.php
index feec4221..8a97e8c7 100644
--- a/app/Controller/App.php
+++ b/app/Controller/App.php
@@ -2,7 +2,8 @@
namespace Controller;
-use Model\Project as ProjectModel;
+use Model\Subtask as SubtaskModel;
+use Model\Task as TaskModel;
/**
* Application controller
@@ -13,21 +14,117 @@ use Model\Project as ProjectModel;
class App extends Base
{
/**
+ * Check if the user is connected
+ *
+ * @access public
+ */
+ public function status()
+ {
+ $this->response->text('OK');
+ }
+
+ /**
+ * User dashboard view for admins
+ *
+ * @access public
+ */
+ public function dashboard()
+ {
+ $this->index($this->request->getIntegerParam('user_id'), 'dashboard');
+ }
+
+ /**
* Dashboard for the current user
*
* @access public
*/
- public function index()
+ public function index($user_id = 0, $action = 'index')
{
- $user_id = $this->acl->getUserId();
- $projects = $this->projectPermission->getAllowedProjects($user_id);
-
- $this->response->html($this->template->layout('app_index', array(
- 'board_selector' => $projects,
- 'events' => $this->projectActivity->getProjects(array_keys($projects), 10),
- 'tasks' => $this->taskFinder->getAllTasksByUser($user_id),
- 'menu' => 'dashboard',
+ $status = array(SubTaskModel::STATUS_TODO, SubtaskModel::STATUS_INPROGRESS);
+ $user_id = $user_id ?: $this->userSession->getId();
+ $projects = $this->projectPermission->getActiveMemberProjects($user_id);
+ $project_ids = array_keys($projects);
+
+ $task_paginator = $this->paginator
+ ->setUrl('app', $action, array('pagination' => 'tasks', 'user_id' => $user_id))
+ ->setMax(10)
+ ->setOrder('tasks.id')
+ ->setQuery($this->taskFinder->getUserQuery($user_id))
+ ->calculateOnlyIf($this->request->getStringParam('pagination') === 'tasks');
+
+ $subtask_paginator = $this->paginator
+ ->setUrl('app', $action, array('pagination' => 'subtasks', 'user_id' => $user_id))
+ ->setMax(10)
+ ->setOrder('tasks.id')
+ ->setQuery($this->subtask->getUserQuery($user_id, $status))
+ ->calculateOnlyIf($this->request->getStringParam('pagination') === 'subtasks');
+
+ $project_paginator = $this->paginator
+ ->setUrl('app', $action, array('pagination' => 'projects', 'user_id' => $user_id))
+ ->setMax(10)
+ ->setOrder('name')
+ ->setQuery($this->project->getQueryColumnStats($project_ids))
+ ->calculateOnlyIf($this->request->getStringParam('pagination') === 'projects');
+
+ $this->response->html($this->template->layout('app/dashboard', array(
'title' => t('Dashboard'),
+ 'board_selector' => $this->projectPermission->getAllowedProjects($user_id),
+ 'events' => $this->projectActivity->getProjects($project_ids, 5),
+ 'task_paginator' => $task_paginator,
+ 'subtask_paginator' => $subtask_paginator,
+ 'project_paginator' => $project_paginator,
+ 'user_id' => $user_id,
)));
}
+
+ /**
+ * Render Markdown text and reply with the HTML Code
+ *
+ * @access public
+ */
+ public function preview()
+ {
+ $payload = $this->request->getJson();
+
+ if (empty($payload['text'])) {
+ $this->response->html('<p>'.t('Nothing to preview...').'</p>');
+ }
+
+ $this->response->html($this->helper->text->markdown($payload['text']));
+ }
+
+ /**
+ * Colors stylesheet
+ *
+ * @access public
+ */
+ public function colors()
+ {
+ $this->response->css($this->color->getCss());
+ }
+
+ /**
+ * Task autocompletion (Ajax)
+ *
+ * @access public
+ */
+ public function autocomplete()
+ {
+ $search = $this->request->getStringParam('term');
+
+ $filter = $this->taskFilter
+ ->create()
+ ->filterByProjects($this->projectPermission->getActiveMemberProjectIds($this->userSession->getId()))
+ ->excludeTasks(array($this->request->getIntegerParam('exclude_task_id')));
+
+ // Search by task id or by title
+ if (ctype_digit($search)) {
+ $filter->filterById($search);
+ }
+ else {
+ $filter->filterByTitle($search);
+ }
+
+ $this->response->json($filter->toAutoCompletion());
+ }
}
diff --git a/app/Controller/Auth.php b/app/Controller/Auth.php
new file mode 100644
index 00000000..24e6e242
--- /dev/null
+++ b/app/Controller/Auth.php
@@ -0,0 +1,67 @@
+<?php
+
+namespace Controller;
+
+/**
+ * Authentication controller
+ *
+ * @package controller
+ * @author Frederic Guillot
+ */
+class Auth extends Base
+{
+ /**
+ * Display the form login
+ *
+ * @access public
+ */
+ public function login(array $values = array(), array $errors = array())
+ {
+ if ($this->userSession->isLogged()) {
+ $this->response->redirect($this->helper->url->to('app', 'index'));
+ }
+
+ $this->response->html($this->template->layout('auth/index', array(
+ 'errors' => $errors,
+ 'values' => $values,
+ 'no_layout' => true,
+ 'redirect_query' => $this->request->getStringParam('redirect_query'),
+ 'title' => t('Login')
+ )));
+ }
+
+ /**
+ * Check credentials
+ *
+ * @access public
+ */
+ public function check()
+ {
+ $redirect_query = $this->request->getStringParam('redirect_query');
+ $values = $this->request->getValues();
+ list($valid, $errors) = $this->authentication->validateForm($values);
+
+ if ($valid) {
+
+ if ($redirect_query !== '') {
+ $this->response->redirect('?'.urldecode($redirect_query));
+ }
+
+ $this->response->redirect($this->helper->url->to('app', 'index'));
+ }
+
+ $this->login($values, $errors);
+ }
+
+ /**
+ * Logout and destroy session
+ *
+ * @access public
+ */
+ public function logout()
+ {
+ $this->authentication->backend('rememberMe')->destroy($this->userSession->getId());
+ $this->session->close();
+ $this->response->redirect($this->helper->url->to('auth', 'login'));
+ }
+}
diff --git a/app/Controller/Base.php b/app/Controller/Base.php
index a8e22fd8..fcd07b99 100644
--- a/app/Controller/Base.php
+++ b/app/Controller/Base.php
@@ -2,166 +2,171 @@
namespace Controller;
-use Core\Tool;
-use Core\Registry;
+use Pimple\Container;
use Core\Security;
+use Core\Request;
+use Core\Response;
+use Core\Template;
+use Core\Session;
use Model\LastLogin;
+use Symfony\Component\EventDispatcher\Event;
/**
* Base controller
*
* @package controller
* @author Frederic Guillot
- *
- * @property \Model\Acl $acl
- * @property \Model\Authentication $authentication
- * @property \Model\Action $action
- * @property \Model\Board $board
- * @property \Model\Category $category
- * @property \Model\Color $color
- * @property \Model\Comment $comment
- * @property \Model\Config $config
- * @property \Model\File $file
- * @property \Model\LastLogin $lastLogin
- * @property \Model\Notification $notification
- * @property \Model\Project $project
- * @property \Model\ProjectPermission $projectPermission
- * @property \Model\SubTask $subTask
- * @property \Model\Task $task
- * @property \Model\TaskHistory $taskHistory
- * @property \Model\TaskExport $taskExport
- * @property \Model\TaskFinder $taskFinder
- * @property \Model\TaskPermission $taskPermission
- * @property \Model\TaskValidator $taskValidator
- * @property \Model\CommentHistory $commentHistory
- * @property \Model\SubtaskHistory $subtaskHistory
- * @property \Model\TimeTracking $timeTracking
- * @property \Model\User $user
- * @property \Model\Webhook $webhook
*/
-abstract class Base
+abstract class Base extends \Core\Base
{
/**
* Request instance
*
- * @accesss public
+ * @accesss protected
* @var \Core\Request
*/
- public $request;
+ protected $request;
/**
* Response instance
*
- * @accesss public
+ * @accesss protected
* @var \Core\Response
*/
- public $response;
-
- /**
- * Template instance
- *
- * @accesss public
- * @var \Core\Template
- */
- public $template;
-
- /**
- * Session instance
- *
- * @accesss public
- * @var \Core\Session
- */
- public $session;
-
- /**
- * Registry instance
- *
- * @access private
- * @var \Core\Registry
- */
- private $registry;
+ protected $response;
/**
* Constructor
*
* @access public
- * @param \Core\Registry $registry Registry instance
+ * @param \Pimple\Container $container
*/
- public function __construct(Registry $registry)
+ public function __construct(Container $container)
{
- $this->registry = $registry;
+ $this->container = $container;
+ $this->request = new Request;
+ $this->response = new Response;
+
+ if (DEBUG) {
+ $this->container['logger']->debug('START_REQUEST='.$_SERVER['REQUEST_URI']);
+ }
}
/**
- * Load automatically models
+ * Destructor
*
* @access public
- * @param string $name Model name
- * @return mixed
*/
- public function __get($name)
+ public function __destruct()
{
- return Tool::loadModel($this->registry, $name);
+ if (DEBUG) {
+
+ foreach ($this->container['db']->getLogMessages() as $message) {
+ $this->container['logger']->debug($message);
+ }
+
+ $this->container['logger']->debug('SQL_QUERIES={nb}', array('nb' => $this->container['db']->nb_queries));
+ $this->container['logger']->debug('RENDERING={time}', array('time' => microtime(true) - @$_SERVER['REQUEST_TIME_FLOAT']));
+ $this->container['logger']->debug('END_REQUEST='.$_SERVER['REQUEST_URI']);
+ }
}
/**
- * Method executed before each action
+ * Send HTTP headers
*
- * @access public
+ * @access private
*/
- public function beforeAction($controller, $action)
+ private function sendHeaders($action)
{
- // Start the session
- $this->session->open(BASE_URL_DIRECTORY, SESSION_SAVE_PATH);
-
// HTTP secure headers
- $this->response->csp(array('style-src' => "'self' 'unsafe-inline'"));
+ $this->response->csp(array('style-src' => "'self' 'unsafe-inline'", 'img-src' => '*'));
$this->response->nosniff();
$this->response->xss();
// Allow the public board iframe inclusion
- if ($action !== 'readonly') {
+ if (ENABLE_XFRAME && $action !== 'readonly') {
$this->response->xframe();
}
if (ENABLE_HSTS) {
$this->response->hsts();
}
+ }
+
+ /**
+ * Method executed before each action
+ *
+ * @access public
+ */
+ public function beforeAction($controller, $action)
+ {
+ // Start the session
+ $this->session->open(BASE_URL_DIRECTORY);
+ $this->sendHeaders($action);
+ $this->container['dispatcher']->dispatch('session.bootstrap', new Event);
- $this->config->setupTranslations();
- $this->config->setupTimezone();
+ if (! $this->acl->isPublicAction($controller, $action)) {
+ $this->handleAuthentication();
+ $this->handle2FA($controller, $action);
+ $this->handleAuthorization($controller, $action);
- // Authentication
- if (! $this->authentication->isAuthenticated($controller, $action)) {
- $this->response->redirect('?controller=user&action=login&redirect_query='.urlencode($this->request->getQueryString()));
+ $this->session['has_subtask_inprogress'] = $this->subtask->hasSubtaskInProgress($this->userSession->getId());
}
+ }
- // Check if the user is allowed to see this page
- if (! $this->acl->isPageAccessAllowed($controller, $action)) {
- $this->response->redirect('?controller=user&action=forbidden');
+ /**
+ * Check authentication
+ *
+ * @access public
+ */
+ public function handleAuthentication()
+ {
+ if (! $this->authentication->isAuthenticated()) {
+
+ if ($this->request->isAjax()) {
+ $this->response->text('Not Authorized', 401);
+ }
+
+ $this->response->redirect($this->helper->url->to('auth', 'login', array('redirect_query' => urlencode($this->request->getQueryString()))));
}
+ }
- // Attach events
- $this->attachEvents();
+ /**
+ * Check 2FA
+ *
+ * @access public
+ */
+ public function handle2FA($controller, $action)
+ {
+ $ignore = ($controller === 'twofactor' && in_array($action, array('code', 'check'))) || ($controller === 'auth' && $action === 'logout');
+
+ if ($ignore === false && $this->userSession->has2FA() && ! $this->userSession->check2FA()) {
+
+ if ($this->request->isAjax()) {
+ $this->response->text('Not Authorized', 401);
+ }
+
+ $this->response->redirect($this->helper->url->to('twofactor', 'code'));
+ }
}
/**
- * Attach events
+ * Check page access and authorization
*
- * @access private
+ * @access public
*/
- private function attachEvents()
+ public function handleAuthorization($controller, $action)
{
- $models = array(
- 'projectActivity', // Order is important
- 'action',
- 'project',
- 'webhook',
- 'notification',
- );
-
- foreach ($models as $model) {
- $this->$model->attachEvents();
+ $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 (! $this->acl->isAllowed($controller, $action, $project_id)) {
+ $this->forbidden();
}
}
@@ -173,7 +178,7 @@ abstract class Base
*/
public function notfound($no_layout = false)
{
- $this->response->html($this->template->layout('app_notfound', array(
+ $this->response->html($this->template->layout('app/notfound', array(
'title' => t('Page not found'),
'no_layout' => $no_layout,
)));
@@ -187,7 +192,7 @@ abstract class Base
*/
public function forbidden($no_layout = false)
{
- $this->response->html($this->template->layout('app_forbidden', array(
+ $this->response->html($this->template->layout('app/forbidden', array(
'title' => t('Access Forbidden'),
'no_layout' => $no_layout,
)));
@@ -206,19 +211,6 @@ abstract class Base
}
/**
- * Check if the current user have access to the given project
- *
- * @access protected
- * @param integer $project_id Project id
- */
- protected function checkProjectPermissions($project_id)
- {
- if ($this->acl->isRegularUser() && ! $this->projectPermission->isUserAllowed($project_id, $this->acl->getUserId())) {
- $this->forbidden();
- }
- }
-
- /**
* Redirection when there is no project in the database
*
* @access protected
@@ -239,14 +231,12 @@ abstract class Base
*/
protected function taskLayout($template, array $params)
{
- if (isset($params['task']) && $this->taskPermission->canRemoveTask($params['task']) === false) {
- $params['hide_remove_menu'] = true;
- }
-
- $content = $this->template->load($template, $params);
+ $content = $this->template->render($template, $params);
$params['task_content_for_layout'] = $content;
+ $params['title'] = $params['task']['project_name'].' &gt; '.$params['task']['title'];
+ $params['board_selector'] = $this->projectPermission->getAllowedProjects($this->userSession->getId());
- return $this->template->layout('task_layout', $params);
+ return $this->template->layout('task/layout', $params);
}
/**
@@ -257,13 +247,15 @@ abstract class Base
* @param array $params Template parameters
* @return string
*/
- protected function projectLayout($template, array $params)
+ protected function projectLayout($template, array $params, $sidebar_template = 'project/sidebar')
{
- $content = $this->template->load($template, $params);
+ $content = $this->template->render($template, $params);
$params['project_content_for_layout'] = $content;
- $params['menu'] = 'projects';
+ $params['title'] = $params['project']['name'] === $params['title'] ? $params['title'] : $params['project']['name'].' &gt; '.$params['title'];
+ $params['board_selector'] = $this->projectPermission->getAllowedProjects($this->userSession->getId());
+ $params['sidebar_template'] = $sidebar_template;
- return $this->template->layout('project_layout', $params);
+ return $this->template->layout('project/layout', $params);
}
/**
@@ -276,12 +268,10 @@ abstract class Base
{
$task = $this->taskFinder->getDetails($this->request->getIntegerParam('task_id'));
- if (! $task) {
+ if (empty($task)) {
$this->notfound();
}
- $this->checkProjectPermissions($task['project_id']);
-
return $task;
}
@@ -297,34 +287,11 @@ abstract class Base
$project_id = $this->request->getIntegerParam('project_id', $project_id);
$project = $this->project->getById($project_id);
- if (! $project) {
+ if (empty($project)) {
$this->session->flashError(t('Project not found.'));
$this->response->redirect('?controller=project');
}
- $this->checkProjectPermissions($project['id']);
-
- return $project;
- }
-
- /**
- * Common method to get a project with administration rights
- *
- * @access protected
- * @return array
- */
- protected function getProjectManagement()
- {
- $project = $this->project->getById($this->request->getIntegerParam('project_id'));
-
- if (! $project) {
- $this->notfound();
- }
-
- if ($this->acl->isRegularUser() && ! $this->projectPermission->adminAllowed($project['id'], $this->acl->getUserId())) {
- $this->forbidden();
- }
-
return $project;
}
}
diff --git a/app/Controller/Board.php b/app/Controller/Board.php
index d49ad021..2b633d82 100644
--- a/app/Controller/Board.php
+++ b/app/Controller/Board.php
@@ -2,10 +2,6 @@
namespace Controller;
-use Model\Project as ProjectModel;
-use Model\User as UserModel;
-use Core\Security;
-
/**
* Board controller
*
@@ -15,133 +11,6 @@ use Core\Security;
class Board extends Base
{
/**
- * Move a column down or up
- *
- * @access public
- */
- public function moveColumn()
- {
- $this->checkCSRFParam();
- $project = $this->getProjectManagement();
- $column_id = $this->request->getIntegerParam('column_id');
- $direction = $this->request->getStringParam('direction');
-
- if ($direction === 'up' || $direction === 'down') {
- $this->board->{'move'.$direction}($project['id'], $column_id);
- }
-
- $this->response->redirect('?controller=board&action=edit&project_id='.$project['id']);
- }
-
- /**
- * Change a task assignee directly from the board
- *
- * @access public
- */
- public function changeAssignee()
- {
- $task = $this->getTask();
- $project = $this->project->getById($task['project_id']);
- $projects = $this->projectPermission->getAllowedProjects($this->acl->getUserId());
- $params = array(
- 'errors' => array(),
- 'values' => $task,
- 'users_list' => $this->projectPermission->getUsersList($project['id']),
- 'projects' => $projects,
- 'current_project_id' => $project['id'],
- 'current_project_name' => $project['name'],
- );
-
- if ($this->request->isAjax()) {
-
- $this->response->html($this->template->load('board_assignee', $params));
- }
- else {
-
- $this->response->html($this->template->layout('board_assignee', $params + array(
- 'menu' => 'boards',
- 'title' => t('Change assignee').' - '.$task['title'],
- )));
- }
- }
-
- /**
- * Validate an assignee modification
- *
- * @access public
- */
- public function updateAssignee()
- {
- $values = $this->request->getValues();
- $this->checkProjectPermissions($values['project_id']);
-
- list($valid,) = $this->taskValidator->validateAssigneeModification($values);
-
- if ($valid && $this->task->update($values)) {
- $this->session->flash(t('Task updated successfully.'));
- }
- else {
- $this->session->flashError(t('Unable to update your task.'));
- }
-
- $this->response->redirect('?controller=board&action=show&project_id='.$values['project_id']);
- }
-
- /**
- * Change a task category directly from the board
- *
- * @access public
- */
- public function changeCategory()
- {
- $task = $this->getTask();
- $project = $this->project->getById($task['project_id']);
- $projects = $this->projectPermission->getAllowedProjects($this->acl->getUserId());
- $params = array(
- 'errors' => array(),
- 'values' => $task,
- 'categories_list' => $this->category->getList($project['id']),
- 'projects' => $projects,
- 'current_project_id' => $project['id'],
- 'current_project_name' => $project['name'],
- );
-
- if ($this->request->isAjax()) {
-
- $this->response->html($this->template->load('board_category', $params));
- }
- else {
-
- $this->response->html($this->template->layout('board_category', $params + array(
- 'menu' => 'boards',
- 'title' => t('Change category').' - '.$task['title'],
- )));
- }
- }
-
- /**
- * Validate a category modification
- *
- * @access public
- */
- public function updateCategory()
- {
- $values = $this->request->getValues();
- $this->checkProjectPermissions($values['project_id']);
-
- list($valid,) = $this->taskValidator->validateCategoryModification($values);
-
- if ($valid && $this->task->update($values)) {
- $this->session->flash(t('Task updated successfully.'));
- }
- else {
- $this->session->flashError(t('Unable to update your task.'));
- }
-
- $this->response->redirect('?controller=board&action=show&project_id='.$values['project_id']);
- }
-
- /**
* Display the public version of a board
* Access checked by a simple token, no user login, read only, auto-refresh
*
@@ -153,19 +22,25 @@ class Board extends Base
$project = $this->project->getByToken($token);
// Token verification
- if (! $project) {
+ if (empty($project)) {
$this->forbidden(true);
}
+ list($categories_listing, $categories_description) = $this->category->getBoardCategories($project['id']);
+
// Display the board with a specific layout
- $this->response->html($this->template->layout('board_public', array(
+ $this->response->html($this->template->layout('board/public', array(
'project' => $project,
- 'columns' => $this->board->get($project['id']),
- 'categories' => $this->category->getList($project['id'], false),
+ 'swimlanes' => $this->board->getBoard($project['id']),
+ 'categories_listing' => $categories_listing,
+ 'categories_description' => $categories_description,
'title' => $project['name'],
+ 'description' => $project['description'],
'no_layout' => true,
'not_editable' => true,
'board_public_refresh_interval' => $this->config->get('board_public_refresh_interval'),
+ 'board_private_refresh_interval' => $this->config->get('board_private_refresh_interval'),
+ 'board_highlight_period' => $this->config->get('board_highlight_period'),
)));
}
@@ -176,16 +51,16 @@ class Board extends Base
*/
public function index()
{
- $last_seen_project_id = $this->user->getLastSeenProjectId();
- $favorite_project_id = $this->user->getFavoriteProjectId();
+ $last_seen_project_id = $this->userSession->getLastSeenProjectId();
+ $favorite_project_id = $this->userSession->getFavoriteProjectId();
$project_id = $last_seen_project_id ?: $favorite_project_id;
if (! $project_id) {
- $projects = $this->projectPermission->getAllowedProjects($this->acl->getUserId());
+ $projects = $this->projectPermission->getAllowedProjects($this->userSession->getId());
if (empty($projects)) {
- if ($this->acl->isAdminUser()) {
+ if ($this->userSession->isAdmin()) {
$this->redirectNoProject();
}
@@ -207,23 +82,24 @@ class Board extends Base
public function show($project_id = 0)
{
$project = $this->getProject($project_id);
- $projects = $this->projectPermission->getAllowedProjects($this->acl->getUserId());
+ $projects = $this->projectPermission->getAllowedProjects($this->userSession->getId());
$board_selector = $projects;
unset($board_selector[$project['id']]);
- $this->user->storeLastSeenProjectId($project['id']);
+ $this->userSession->storeLastSeenProjectId($project['id']);
+
+ list($categories_listing, $categories_description) = $this->category->getBoardCategories($project['id']);
- $this->response->html($this->template->layout('board_index', array(
- 'users' => $this->projectPermission->getUsersList($project['id'], true, true),
- 'filters' => array('user_id' => UserModel::EVERYBODY_ID),
+ $this->response->html($this->template->layout('board/index', array(
+ 'users' => $this->projectPermission->getMemberList($project['id'], true, true),
'projects' => $projects,
- 'current_project_id' => $project['id'],
- 'current_project_name' => $project['name'],
- 'board' => $this->board->get($project['id']),
- 'categories' => $this->category->getList($project['id'], true, true),
- 'menu' => 'boards',
+ 'project' => $project,
+ 'swimlanes' => $this->board->getBoard($project['id']),
+ 'categories_listing' => $categories_listing,
+ 'categories_description' => $categories_description,
'title' => $project['name'],
+ 'description' => $project['description'],
'board_selector' => $board_selector,
'board_private_refresh_interval' => $this->config->get('board_private_refresh_interval'),
'board_highlight_period' => $this->config->get('board_highlight_period'),
@@ -231,215 +107,264 @@ class Board extends Base
}
/**
- * Display a form to edit a board
+ * Save the board (Ajax request made by the drag and drop)
*
* @access public
*/
- public function edit()
+ public function save()
{
- $project = $this->getProjectManagement();
- $columns = $this->board->getColumns($project['id']);
- $values = array();
+ $project_id = $this->request->getIntegerParam('project_id');
- foreach ($columns as $column) {
- $values['title['.$column['id'].']'] = $column['title'];
- $values['task_limit['.$column['id'].']'] = $column['task_limit'] ?: null;
+ if (! $project_id || ! $this->request->isAjax()) {
+ return $this->response->status(403);
}
- $this->response->html($this->projectLayout('board_edit', array(
- 'errors' => array(),
- 'values' => $values + array('project_id' => $project['id']),
- 'columns' => $columns,
- 'project' => $project,
- 'menu' => 'projects',
- 'title' => t('Edit board')
- )));
+ if (! $this->projectPermission->isUserAllowed($project_id, $this->userSession->getId())) {
+ $this->response->text('Forbidden', 403);
+ }
+
+ $values = $this->request->getJson();
+
+ $result =$this->taskPosition->movePosition(
+ $project_id,
+ $values['task_id'],
+ $values['column_id'],
+ $values['position'],
+ $values['swimlane_id']
+ );
+
+ if (! $result) {
+ return $this->response->status(400);
+ }
+
+ list($categories_listing, $categories_description) = $this->category->getBoardCategories($project_id);
+
+ $this->response->html(
+ $this->template->render('board/show', array(
+ 'project' => $this->project->getById($project_id),
+ 'swimlanes' => $this->board->getBoard($project_id),
+ 'categories_listing' => $categories_listing,
+ 'categories_description' => $categories_description,
+ 'board_private_refresh_interval' => $this->config->get('board_private_refresh_interval'),
+ 'board_highlight_period' => $this->config->get('board_highlight_period'),
+ )),
+ 201
+ );
}
/**
- * Validate and update a board
+ * Check if the board have been changed
*
* @access public
*/
- public function update()
+ public function check()
{
- $project = $this->getProjectManagement();
- $columns = $this->board->getColumns($project['id']);
- $data = $this->request->getValues();
- $values = $columns_list = array();
-
- foreach ($columns as $column) {
- $columns_list[$column['id']] = $column['title'];
- $values['title['.$column['id'].']'] = isset($data['title'][$column['id']]) ? $data['title'][$column['id']] : '';
- $values['task_limit['.$column['id'].']'] = isset($data['task_limit'][$column['id']]) ? $data['task_limit'][$column['id']] : 0;
+ if (! $this->request->isAjax()) {
+ return $this->response->status(403);
}
- list($valid, $errors) = $this->board->validateModification($columns_list, $values);
+ $project_id = $this->request->getIntegerParam('project_id');
+ $timestamp = $this->request->getIntegerParam('timestamp');
- if ($valid) {
+ if (! $this->projectPermission->isUserAllowed($project_id, $this->userSession->getId())) {
+ $this->response->text('Forbidden', 403);
+ }
- if ($this->board->update($data)) {
- $this->session->flash(t('Board updated successfully.'));
- $this->response->redirect('?controller=board&action=edit&project_id='.$project['id']);
- }
- else {
- $this->session->flashError(t('Unable to update this board.'));
- }
+ if (! $this->project->isModifiedSince($project_id, $timestamp)) {
+ return $this->response->status(304);
}
- $this->response->html($this->projectLayout('board_edit', array(
- 'errors' => $errors,
- 'values' => $values + array('project_id' => $project['id']),
- 'columns' => $columns,
- 'project' => $project,
- 'menu' => 'projects',
- 'title' => t('Edit board')
- )));
+ list($categories_listing, $categories_description) = $this->category->getBoardCategories($project_id);
+
+ $this->response->html(
+ $this->template->render('board/show', array(
+ 'project' => $this->project->getById($project_id),
+ 'swimlanes' => $this->board->getBoard($project_id),
+ 'categories_listing' => $categories_listing,
+ 'categories_description' => $categories_description,
+ 'board_private_refresh_interval' => $this->config->get('board_private_refresh_interval'),
+ 'board_highlight_period' => $this->config->get('board_highlight_period'),
+ ))
+ );
}
/**
- * Validate and add a new column
+ * Get links on mouseover
*
* @access public
*/
- public function add()
+ public function tasklinks()
{
- $project = $this->getProjectManagement();
- $columns = $this->board->getColumnsList($project['id']);
- $data = $this->request->getValues();
- $values = array();
-
- foreach ($columns as $column_id => $column_title) {
- $values['title['.$column_id.']'] = $column_title;
- }
-
- list($valid, $errors) = $this->board->validateCreation($data);
+ $task = $this->getTask();
+ $this->response->html($this->template->render('board/tasklinks', array(
+ 'links' => $this->taskLink->getAll($task['id']),
+ 'task' => $task,
+ )));
+ }
- if ($valid) {
+ /**
+ * Get subtasks on mouseover
+ *
+ * @access public
+ */
+ public function subtasks()
+ {
+ $task = $this->getTask();
+ $this->response->html($this->template->render('board/subtasks', array(
+ 'subtasks' => $this->subtask->getAll($task['id']),
+ 'task' => $task,
+ )));
+ }
- if ($this->board->addColumn($project['id'], $data['title'])) {
- $this->session->flash(t('Board updated successfully.'));
- $this->response->redirect('?controller=board&action=edit&project_id='.$project['id']);
- }
- else {
- $this->session->flashError(t('Unable to update this board.'));
- }
- }
+ /**
+ * Display all attachments during the task mouseover
+ *
+ * @access public
+ */
+ public function attachments()
+ {
+ $task = $this->getTask();
- $this->response->html($this->projectLayout('board_edit', array(
- 'errors' => $errors,
- 'values' => $values + $data,
- 'columns' => $columns,
- 'project' => $project,
- 'menu' => 'projects',
- 'title' => t('Edit board')
+ $this->response->html($this->template->render('board/files', array(
+ 'files' => $this->file->getAllDocuments($task['id']),
+ 'images' => $this->file->getAllImages($task['id']),
+ 'task' => $task,
)));
}
/**
- * Remove a column
+ * Display comments during a task mouseover
*
* @access public
*/
- public function remove()
+ public function comments()
{
- $project = $this->getProjectManagement();
+ $task = $this->getTask();
- if ($this->request->getStringParam('remove') === 'yes') {
+ $this->response->html($this->template->render('board/comments', array(
+ 'comments' => $this->comment->getAll($task['id'])
+ )));
+ }
- $this->checkCSRFParam();
- $column = $this->board->getColumn($this->request->getIntegerParam('column_id'));
+ /**
+ * Display task description
+ *
+ * @access public
+ */
+ public function description()
+ {
+ $task = $this->getTask();
- if ($column && $this->board->removeColumn($column['id'])) {
- $this->session->flash(t('Column removed successfully.'));
- } else {
- $this->session->flashError(t('Unable to remove this column.'));
- }
+ $this->response->html($this->template->render('board/description', array(
+ 'task' => $task
+ )));
+ }
- $this->response->redirect('?controller=board&action=edit&project_id='.$project['id']);
- }
+ /**
+ * Change a task assignee directly from the board
+ *
+ * @access public
+ */
+ public function changeAssignee()
+ {
+ $task = $this->getTask();
+ $project = $this->project->getById($task['project_id']);
- $this->response->html($this->projectLayout('board_remove', array(
- 'column' => $this->board->getColumn($this->request->getIntegerParam('column_id')),
+ $this->response->html($this->template->render('board/assignee', array(
+ 'values' => $task,
+ 'users_list' => $this->projectPermission->getMemberList($project['id']),
'project' => $project,
- 'menu' => 'projects',
- 'title' => t('Remove a column from a board')
)));
}
/**
- * Save the board (Ajax request made by the drag and drop)
+ * Validate an assignee modification
*
* @access public
*/
- public function save()
+ public function updateAssignee()
{
- $project_id = $this->request->getIntegerParam('project_id');
+ $values = $this->request->getValues();
- if ($project_id > 0 && $this->request->isAjax()) {
+ list($valid,) = $this->taskValidator->validateAssigneeModification($values);
- if (! $this->projectPermission->isUserAllowed($project_id, $this->acl->getUserId())) {
- $this->response->status(401);
- }
+ if ($valid && $this->taskModification->update($values)) {
+ $this->session->flash(t('Task updated successfully.'));
+ }
+ else {
+ $this->session->flashError(t('Unable to update your task.'));
+ }
- $values = $this->request->getValues();
+ $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $values['project_id'])));
+ }
- if ($this->task->movePosition($project_id, $values['task_id'], $values['column_id'], $values['position'])) {
+ /**
+ * Change a task category directly from the board
+ *
+ * @access public
+ */
+ public function changeCategory()
+ {
+ $task = $this->getTask();
+ $project = $this->project->getById($task['project_id']);
- $this->response->html(
- $this->template->load('board_show', array(
- 'current_project_id' => $project_id,
- 'board' => $this->board->get($project_id),
- 'categories' => $this->category->getList($project_id, false),
- 'board_private_refresh_interval' => $this->config->get('board_private_refresh_interval'),
- 'board_highlight_period' => $this->config->get('board_highlight_period'),
- )),
- 201
- );
- }
- else {
+ $this->response->html($this->template->render('board/category', array(
+ 'values' => $task,
+ 'categories_list' => $this->category->getList($project['id']),
+ 'project' => $project,
+ )));
+ }
- $this->response->status(400);
- }
+ /**
+ * Validate a category modification
+ *
+ * @access public
+ */
+ public function updateCategory()
+ {
+ $values = $this->request->getValues();
+
+ list($valid,) = $this->taskValidator->validateCategoryModification($values);
+
+ if ($valid && $this->taskModification->update($values)) {
+ $this->session->flash(t('Task updated successfully.'));
}
else {
- $this->response->status(401);
+ $this->session->flashError(t('Unable to update your task.'));
}
+
+ $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $values['project_id'])));
}
/**
- * Check if the board have been changed
+ * Screenshot popover
*
* @access public
*/
- public function check()
+ public function screenshot()
{
- if ($this->request->isAjax()) {
+ $task = $this->getTask();
- $project_id = $this->request->getIntegerParam('project_id');
- $timestamp = $this->request->getIntegerParam('timestamp');
+ $this->response->html($this->template->render('file/screenshot', array(
+ 'task' => $task,
+ 'redirect' => 'board',
+ )));
+ }
- if ($project_id > 0 && ! $this->projectPermission->isUserAllowed($project_id, $this->acl->getUserId())) {
- $this->response->text('Not Authorized', 401);
- }
+ /**
+ * Get recurrence information on mouseover
+ *
+ * @access public
+ */
+ public function recurrence()
+ {
+ $task = $this->getTask();
- if ($this->project->isModifiedSince($project_id, $timestamp)) {
- $this->response->html(
- $this->template->load('board_show', array(
- 'current_project_id' => $project_id,
- 'board' => $this->board->get($project_id),
- 'categories' => $this->category->getList($project_id, false),
- 'board_private_refresh_interval' => $this->config->get('board_private_refresh_interval'),
- 'board_highlight_period' => $this->config->get('board_highlight_period'),
- ))
- );
- }
- else {
- $this->response->status(304);
- }
- }
- else {
- $this->response->status(401);
- }
+ $this->response->html($this->template->render('task/recurring_info', array(
+ 'task' => $task,
+ 'recurrence_trigger_list' => $this->task->getRecurrenceTriggerList(),
+ 'recurrence_timeframe_list' => $this->task->getRecurrenceTimeframeList(),
+ 'recurrence_basedate_list' => $this->task->getRecurrenceBasedateList(),
+ )));
}
}
diff --git a/app/Controller/Budget.php b/app/Controller/Budget.php
new file mode 100644
index 00000000..a2f7e0db
--- /dev/null
+++ b/app/Controller/Budget.php
@@ -0,0 +1,135 @@
+<?php
+
+namespace Controller;
+
+/**
+ * Budget
+ *
+ * @package controller
+ * @author Frederic Guillot
+ */
+class Budget extends Base
+{
+ /**
+ * Budget index page
+ *
+ * @access public
+ */
+ public function index()
+ {
+ $project = $this->getProject();
+
+ $this->response->html($this->projectLayout('budget/index', array(
+ 'daily_budget' => $this->budget->getDailyBudgetBreakdown($project['id']),
+ 'project' => $project,
+ 'title' => t('Budget')
+ ), 'budget/sidebar'));
+ }
+
+ /**
+ * Cost breakdown by users/subtasks/tasks
+ *
+ * @access public
+ */
+ public function breakdown()
+ {
+ $project = $this->getProject();
+
+ $paginator = $this->paginator
+ ->setUrl('budget', 'breakdown', array('project_id' => $project['id']))
+ ->setMax(30)
+ ->setOrder('start')
+ ->setDirection('DESC')
+ ->setQuery($this->budget->getSubtaskBreakdown($project['id']))
+ ->calculate();
+
+ $this->response->html($this->projectLayout('budget/breakdown', array(
+ 'paginator' => $paginator,
+ 'project' => $project,
+ 'title' => t('Budget')
+ ), 'budget/sidebar'));
+ }
+
+ /**
+ * Create budget lines
+ *
+ * @access public
+ */
+ public function create(array $values = array(), array $errors = array())
+ {
+ $project = $this->getProject();
+
+ if (empty($values)) {
+ $values['date'] = date('Y-m-d');
+ }
+
+ $this->response->html($this->projectLayout('budget/create', array(
+ 'lines' => $this->budget->getAll($project['id']),
+ 'values' => $values + array('project_id' => $project['id']),
+ 'errors' => $errors,
+ 'project' => $project,
+ 'title' => t('Budget lines')
+ ), 'budget/sidebar'));
+ }
+
+ /**
+ * Validate and save a new budget
+ *
+ * @access public
+ */
+ public function save()
+ {
+ $project = $this->getProject();
+
+ $values = $this->request->getValues();
+ list($valid, $errors) = $this->budget->validateCreation($values);
+
+ if ($valid) {
+
+ if ($this->budget->create($values['project_id'], $values['amount'], $values['comment'], $values['date'])) {
+ $this->session->flash(t('The budget line have been created successfully.'));
+ $this->response->redirect($this->helper->url->to('budget', 'create', array('project_id' => $project['id'])));
+ }
+ else {
+ $this->session->flashError(t('Unable to create the budget line.'));
+ }
+ }
+
+ $this->create($values, $errors);
+ }
+
+ /**
+ * Confirmation dialog before removing a budget
+ *
+ * @access public
+ */
+ public function confirm()
+ {
+ $project = $this->getProject();
+
+ $this->response->html($this->projectLayout('budget/remove', array(
+ 'project' => $project,
+ 'budget_id' => $this->request->getIntegerParam('budget_id'),
+ 'title' => t('Remove a budget line'),
+ ), 'budget/sidebar'));
+ }
+
+ /**
+ * Remove a budget
+ *
+ * @access public
+ */
+ public function remove()
+ {
+ $this->checkCSRFParam();
+ $project = $this->getProject();
+
+ if ($this->budget->remove($this->request->getIntegerParam('budget_id'))) {
+ $this->session->flash(t('Budget line removed successfully.'));
+ } else {
+ $this->session->flashError(t('Unable to remove this budget line.'));
+ }
+
+ $this->response->redirect($this->helper->url->to('budget', 'create', array('project_id' => $project['id'])));
+ }
+}
diff --git a/app/Controller/Calendar.php b/app/Controller/Calendar.php
new file mode 100644
index 00000000..41642a59
--- /dev/null
+++ b/app/Controller/Calendar.php
@@ -0,0 +1,128 @@
+<?php
+
+namespace Controller;
+
+use Model\Task as TaskModel;
+
+/**
+ * Project Calendar controller
+ *
+ * @package controller
+ * @author Frederic Guillot
+ * @author Timo Litzbarski
+ */
+class Calendar extends Base
+{
+ /**
+ * Show calendar view for projects
+ *
+ * @access public
+ */
+ public function show()
+ {
+ $project = $this->getProject();
+
+ $this->response->html($this->template->layout('calendar/show', array(
+ 'check_interval' => $this->config->get('board_private_refresh_interval'),
+ 'users_list' => $this->projectPermission->getMemberList($project['id'], true, true),
+ 'categories_list' => $this->category->getList($project['id'], true, true),
+ 'columns_list' => $this->board->getColumnsList($project['id'], true),
+ 'swimlanes_list' => $this->swimlane->getList($project['id'], true),
+ 'colors_list' => $this->color->getList(true),
+ 'status_list' => $this->taskStatus->getList(true),
+ 'project' => $project,
+ 'title' => t('Calendar for "%s"', $project['name']),
+ 'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
+ )));
+ }
+
+ /**
+ * Get tasks to display on the calendar (project view)
+ *
+ * @access public
+ */
+ public function project()
+ {
+ $project_id = $this->request->getIntegerParam('project_id');
+ $start = $this->request->getStringParam('start');
+ $end = $this->request->getStringParam('end');
+
+ // Common filter
+ $filter = $this->taskFilter
+ ->create()
+ ->filterByProject($project_id)
+ ->filterByCategory($this->request->getIntegerParam('category_id', -1))
+ ->filterByOwner($this->request->getIntegerParam('owner_id', -1))
+ ->filterByColumn($this->request->getIntegerParam('column_id', -1))
+ ->filterBySwimlane($this->request->getIntegerParam('swimlane_id', -1))
+ ->filterByColor($this->request->getStringParam('color_id'))
+ ->filterByStatus($this->request->getIntegerParam('is_active', -1));
+
+ // Tasks
+ if ($this->config->get('calendar_project_tasks', 'date_started') === 'date_creation') {
+ $events = $filter->copy()->filterByCreationDateRange($start, $end)->toDateTimeCalendarEvents('date_creation', 'date_completed');
+ }
+ else {
+ $events = $filter->copy()->filterByStartDateRange($start, $end)->toDateTimeCalendarEvents('date_started', 'date_completed');
+ }
+
+ // Tasks with due date
+ $events = array_merge($events, $filter->copy()->filterByDueDateRange($start, $end)->toAllDayCalendarEvents());
+
+ $this->response->json($events);
+ }
+
+ /**
+ * Get tasks to display on the calendar (user view)
+ *
+ * @access public
+ */
+ public function user()
+ {
+ $user_id = $this->request->getIntegerParam('user_id');
+ $start = $this->request->getStringParam('start');
+ $end = $this->request->getStringParam('end');
+ $filter = $this->taskFilter->create()->filterByOwner($user_id)->filterByStatus(TaskModel::STATUS_OPEN);
+
+ // Task with due date
+ $events = $filter->copy()->filterByDueDateRange($start, $end)->toAllDayCalendarEvents();
+
+ // Tasks
+ if ($this->config->get('calendar_user_tasks', 'date_started') === 'date_creation') {
+ $events = array_merge($events, $filter->copy()->filterByCreationDateRange($start, $end)->toDateTimeCalendarEvents('date_creation', 'date_completed'));
+ }
+ else {
+ $events = array_merge($events, $filter->copy()->filterByStartDateRange($start, $end)->toDateTimeCalendarEvents('date_started', 'date_completed'));
+ }
+
+ // Subtasks time tracking
+ if ($this->config->get('calendar_user_subtasks_time_tracking') == 1) {
+ $events = array_merge($events, $this->subtaskTimeTracking->getUserCalendarEvents($user_id, $start, $end));
+ }
+
+ // Subtask estimates
+ if ($this->config->get('calendar_user_subtasks_forecast') == 1) {
+ $events = array_merge($events, $this->subtaskForecast->getCalendarEvents($user_id, $end));
+ }
+
+ $this->response->json($events);
+ }
+
+ /**
+ * Update task due date
+ *
+ * @access public
+ */
+ public function save()
+ {
+ if ($this->request->isAjax() && $this->request->isPost()) {
+
+ $values = $this->request->getJson();
+
+ $this->taskModification->update(array(
+ 'id' => $values['task_id'],
+ 'date_due' => substr($values['date_due'], 0, 10),
+ ));
+ }
+ }
+}
diff --git a/app/Controller/Category.php b/app/Controller/Category.php
index 38322294..515cc9c8 100644
--- a/app/Controller/Category.php
+++ b/app/Controller/Category.php
@@ -14,14 +14,14 @@ class Category extends Base
* Get the category (common method between actions)
*
* @access private
- * @param $project_id
+ * @param integer $project_id
* @return array
*/
private function getCategory($project_id)
{
$category = $this->category->getById($this->request->getIntegerParam('category_id'));
- if (! $category) {
+ if (empty($category)) {
$this->session->flashError(t('Category not found.'));
$this->response->redirect('?controller=category&action=index&project_id='.$project_id);
}
@@ -34,28 +34,27 @@ class Category extends Base
*
* @access public
*/
- public function index()
+ public function index(array $values = array(), array $errors = array())
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
- $this->response->html($this->projectLayout('category_index', array(
+ $this->response->html($this->projectLayout('category/index', array(
'categories' => $this->category->getList($project['id'], false),
- 'values' => array('project_id' => $project['id']),
- 'errors' => array(),
+ 'values' => $values + array('project_id' => $project['id']),
+ 'errors' => $errors,
'project' => $project,
- 'menu' => 'projects',
'title' => t('Categories')
)));
}
/**
- * Validate and save a new project
+ * Validate and save a new category
*
* @access public
*/
public function save()
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
$values = $this->request->getValues();
list($valid, $errors) = $this->category->validateCreation($values);
@@ -71,14 +70,7 @@ class Category extends Base
}
}
- $this->response->html($this->projectLayout('category_index', array(
- 'categories' => $this->category->getList($project['id'], false),
- 'values' => $values,
- 'errors' => $errors,
- 'project' => $project,
- 'menu' => 'projects',
- 'title' => t('Categories')
- )));
+ $this->index($values, $errors);
}
/**
@@ -86,16 +78,15 @@ class Category extends Base
*
* @access public
*/
- public function edit()
+ public function edit(array $values = array(), array $errors = array())
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
$category = $this->getCategory($project['id']);
- $this->response->html($this->projectLayout('category_edit', array(
- 'values' => $category,
- 'errors' => array(),
+ $this->response->html($this->projectLayout('category/edit', array(
+ 'values' => empty($values) ? $category : $values,
+ 'errors' => $errors,
'project' => $project,
- 'menu' => 'projects',
'title' => t('Categories')
)));
}
@@ -107,7 +98,7 @@ class Category extends Base
*/
public function update()
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
$values = $this->request->getValues();
list($valid, $errors) = $this->category->validateModification($values);
@@ -123,13 +114,7 @@ class Category extends Base
}
}
- $this->response->html($this->projectLayout('category_edit', array(
- 'values' => $values,
- 'errors' => $errors,
- 'project' => $project,
- 'menu' => 'projects',
- 'title' => t('Categories')
- )));
+ $this->edit($values, $errors);
}
/**
@@ -139,13 +124,12 @@ class Category extends Base
*/
public function confirm()
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
$category = $this->getCategory($project['id']);
- $this->response->html($this->projectLayout('category_remove', array(
+ $this->response->html($this->projectLayout('category/remove', array(
'project' => $project,
'category' => $category,
- 'menu' => 'projects',
'title' => t('Remove a category')
)));
}
@@ -158,7 +142,7 @@ class Category extends Base
public function remove()
{
$this->checkCSRFParam();
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
$category = $this->getCategory($project['id']);
if ($this->category->remove($category['id'])) {
diff --git a/app/Controller/Column.php b/app/Controller/Column.php
new file mode 100644
index 00000000..89c495a6
--- /dev/null
+++ b/app/Controller/Column.php
@@ -0,0 +1,170 @@
+<?php
+
+namespace Controller;
+
+/**
+ * Column controller
+ *
+ * @package controller
+ * @author Frederic Guillot
+ */
+class Column extends Base
+{
+ /**
+ * Display columns list
+ *
+ * @access public
+ */
+ public function index(array $values = array(), array $errors = array())
+ {
+ $project = $this->getProject();
+ $columns = $this->board->getColumns($project['id']);
+
+ foreach ($columns as $column) {
+ $values['title['.$column['id'].']'] = $column['title'];
+ $values['description['.$column['id'].']'] = $column['description'];
+ $values['task_limit['.$column['id'].']'] = $column['task_limit'] ?: null;
+ }
+
+ $this->response->html($this->projectLayout('column/index', array(
+ 'errors' => $errors,
+ 'values' => $values + array('project_id' => $project['id']),
+ 'columns' => $columns,
+ 'project' => $project,
+ 'title' => t('Edit board')
+ )));
+ }
+
+ /**
+ * Validate and add a new column
+ *
+ * @access public
+ */
+ public function create()
+ {
+ $project = $this->getProject();
+ $columns = $this->board->getColumnsList($project['id']);
+ $data = $this->request->getValues();
+ $values = array();
+
+ foreach ($columns as $column_id => $column_title) {
+ $values['title['.$column_id.']'] = $column_title;
+ }
+
+ list($valid, $errors) = $this->board->validateCreation($data);
+
+ if ($valid) {
+
+ if ($this->board->addColumn($project['id'], $data['title'], $data['task_limit'], $data['description'])) {
+ $this->session->flash(t('Board updated successfully.'));
+ $this->response->redirect($this->helper->url->to('column', 'index', array('project_id' => $project['id'])));
+ }
+ else {
+ $this->session->flashError(t('Unable to update this board.'));
+ }
+ }
+
+ $this->index($values, $errors);
+ }
+
+ /**
+ * Display a form to edit a column
+ *
+ * @access public
+ */
+ public function edit(array $values = array(), array $errors = array())
+ {
+ $project = $this->getProject();
+ $column = $this->board->getColumn($this->request->getIntegerParam('column_id'));
+
+ $this->response->html($this->projectLayout('column/edit', array(
+ 'errors' => $errors,
+ 'values' => $values ?: $column,
+ 'project' => $project,
+ 'column' => $column,
+ 'title' => t('Edit column "%s"', $column['title'])
+ )));
+ }
+
+ /**
+ * Validate and update a column
+ *
+ * @access public
+ */
+ public function update()
+ {
+ $project = $this->getProject();
+ $values = $this->request->getValues();
+
+ list($valid, $errors) = $this->board->validateModification($values);
+
+ if ($valid) {
+
+ if ($this->board->updateColumn($values['id'], $values['title'], $values['task_limit'], $values['description'])) {
+ $this->session->flash(t('Board updated successfully.'));
+ $this->response->redirect($this->helper->url->to('column', 'index', array('project_id' => $project['id'])));
+ }
+ else {
+ $this->session->flashError(t('Unable to update this board.'));
+ }
+ }
+
+ $this->edit($values, $errors);
+ }
+
+ /**
+ * Move a column up or down
+ *
+ * @access public
+ */
+ public function move()
+ {
+ $this->checkCSRFParam();
+ $project = $this->getProject();
+ $column_id = $this->request->getIntegerParam('column_id');
+ $direction = $this->request->getStringParam('direction');
+
+ if ($direction === 'up' || $direction === 'down') {
+ $this->board->{'move'.$direction}($project['id'], $column_id);
+ }
+
+ $this->response->redirect($this->helper->url->to('column', 'index', array('project_id' => $project['id'])));
+ }
+
+ /**
+ * Confirm column suppression
+ *
+ * @access public
+ */
+ public function confirm()
+ {
+ $project = $this->getProject();
+
+ $this->response->html($this->projectLayout('column/remove', array(
+ 'column' => $this->board->getColumn($this->request->getIntegerParam('column_id')),
+ 'project' => $project,
+ 'title' => t('Remove a column from a board')
+ )));
+ }
+
+ /**
+ * Remove a column
+ *
+ * @access public
+ */
+ public function remove()
+ {
+ $project = $this->getProject();
+ $this->checkCSRFParam();
+ $column = $this->board->getColumn($this->request->getIntegerParam('column_id'));
+
+ if (! empty($column) && $this->board->removeColumn($column['id'])) {
+ $this->session->flash(t('Column removed successfully.'));
+ }
+ else {
+ $this->session->flashError(t('Unable to remove this column.'));
+ }
+
+ $this->response->redirect($this->helper->url->to('column', 'index', array('project_id' => $project['id'])));
+ }
+}
diff --git a/app/Controller/Comment.php b/app/Controller/Comment.php
index a9032ed8..a5f6b1f8 100644
--- a/app/Controller/Comment.php
+++ b/app/Controller/Comment.php
@@ -20,13 +20,12 @@ class Comment extends Base
{
$comment = $this->comment->getById($this->request->getIntegerParam('comment_id'));
- if (! $comment) {
+ if (empty($comment)) {
$this->notfound();
}
- if (! $this->acl->isAdminUser() && $comment['user_id'] != $this->acl->getUserId()) {
- $this->response->html($this->template->layout('comment_forbidden', array(
- 'menu' => 'tasks',
+ if (! $this->userSession->isAdmin() && $comment['user_id'] != $this->userSession->getId()) {
+ $this->response->html($this->template->layout('comment/forbidden', array(
'title' => t('Access Forbidden')
)));
}
@@ -39,19 +38,32 @@ class Comment extends Base
*
* @access public
*/
- public function create()
+ public function create(array $values = array(), array $errors = array())
{
$task = $this->getTask();
+ $ajax = $this->request->isAjax() || $this->request->getIntegerParam('ajax');
- $this->response->html($this->taskLayout('comment_create', array(
- 'values' => array(
- 'user_id' => $this->acl->getUserId(),
+ if (empty($values)) {
+ $values = array(
+ 'user_id' => $this->userSession->getId(),
'task_id' => $task['id'],
- ),
- 'errors' => array(),
+ );
+ }
+
+ if ($ajax) {
+ $this->response->html($this->template->render('comment/create', array(
+ 'values' => $values,
+ 'errors' => $errors,
+ 'task' => $task,
+ 'ajax' => $ajax,
+ )));
+ }
+
+ $this->response->html($this->taskLayout('comment/create', array(
+ 'values' => $values,
+ 'errors' => $errors,
'task' => $task,
- 'menu' => 'tasks',
- 'title' => t('Add a comment')
+ 'title' => t('Add a comment'),
)));
}
@@ -64,6 +76,7 @@ class Comment extends Base
{
$task = $this->getTask();
$values = $this->request->getValues();
+ $ajax = $this->request->isAjax() || $this->request->getIntegerParam('ajax');
list($valid, $errors) = $this->comment->validateCreation($values);
@@ -76,16 +89,14 @@ class Comment extends Base
$this->session->flashError(t('Unable to create your comment.'));
}
- $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'#comments');
+ if ($ajax) {
+ $this->response->redirect('?controller=board&action=show&project_id='.$task['project_id']);
+ }
+
+ $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id'].'#comments');
}
- $this->response->html($this->taskLayout('comment_create', array(
- 'values' => $values,
- 'errors' => $errors,
- 'task' => $task,
- 'menu' => 'tasks',
- 'title' => t('Add a comment')
- )));
+ $this->create($values, $errors);
}
/**
@@ -93,17 +104,16 @@ class Comment extends Base
*
* @access public
*/
- public function edit()
+ public function edit(array $values = array(), array $errors = array())
{
$task = $this->getTask();
$comment = $this->getComment();
- $this->response->html($this->taskLayout('comment_edit', array(
- 'values' => $comment,
- 'errors' => array(),
+ $this->response->html($this->taskLayout('comment/edit', array(
+ 'values' => empty($values) ? $comment : $values,
+ 'errors' => $errors,
'comment' => $comment,
'task' => $task,
- 'menu' => 'tasks',
'title' => t('Edit a comment')
)));
}
@@ -130,17 +140,10 @@ class Comment extends Base
$this->session->flashError(t('Unable to update your comment.'));
}
- $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'#comment-'.$comment['id']);
+ $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id'].'#comment-'.$comment['id']);
}
- $this->response->html($this->taskLayout('comment_edit', array(
- 'values' => $values,
- 'errors' => $errors,
- 'comment' => $comment,
- 'task' => $task,
- 'menu' => 'tasks',
- 'title' => t('Edit a comment')
- )));
+ $this->edit($values, $errors);
}
/**
@@ -153,10 +156,9 @@ class Comment extends Base
$task = $this->getTask();
$comment = $this->getComment();
- $this->response->html($this->taskLayout('comment_remove', array(
+ $this->response->html($this->taskLayout('comment/remove', array(
'comment' => $comment,
'task' => $task,
- 'menu' => 'tasks',
'title' => t('Remove a comment')
)));
}
@@ -179,6 +181,6 @@ class Comment extends Base
$this->session->flashError(t('Unable to remove this comment.'));
}
- $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'#comments');
+ $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id'].'#comments');
}
}
diff --git a/app/Controller/Config.php b/app/Controller/Config.php
index 7c8373c3..fbd374ab 100644
--- a/app/Controller/Config.php
+++ b/app/Controller/Config.php
@@ -20,12 +20,12 @@ class Config extends Base
*/
private function layout($template, array $params)
{
+ $params['board_selector'] = $this->projectPermission->getAllowedProjects($this->userSession->getId());
$params['values'] = $this->config->getAll();
$params['errors'] = array();
- $params['menu'] = 'config';
- $params['config_content_for_layout'] = $this->template->load($template, $params);
+ $params['config_content_for_layout'] = $this->template->render($template, $params);
- return $this->template->layout('config_layout', $params);
+ return $this->template->layout('config/layout', $params);
}
/**
@@ -38,7 +38,19 @@ class Config extends Base
{
if ($this->request->isPost()) {
- $values = $this->request->getValues();
+ $values = $this->request->getValues();
+
+ switch ($redirect) {
+ case 'project':
+ $values += array('subtask_restriction' => 0, 'subtask_time_tracking' => 0);
+ break;
+ case 'integrations':
+ $values += array('integration_slack_webhook' => 0, 'integration_hipchat' => 0, 'integration_gravatar' => 0, 'integration_jabber' => 0);
+ break;
+ case 'calendar':
+ $values += array('calendar_user_subtasks_forecast' => 0, 'calendar_user_subtasks_time_tracking' => 0);
+ break;
+ }
if ($this->config->save($values)) {
$this->config->reload();
@@ -59,9 +71,9 @@ class Config extends Base
*/
public function index()
{
- $this->response->html($this->layout('config_about', array(
+ $this->response->html($this->layout('config/about', array(
'db_size' => $this->config->getDatabaseSize(),
- 'title' => t('About'),
+ 'title' => t('Settings').' &gt; '.t('About'),
)));
}
@@ -74,11 +86,26 @@ class Config extends Base
{
$this->common('application');
- $this->response->html($this->layout('config_application', array(
- 'title' => t('Application settings'),
+ $this->response->html($this->layout('config/application', array(
'languages' => $this->config->getLanguages(),
'timezones' => $this->config->getTimezones(),
'date_formats' => $this->dateParser->getAvailableFormats(),
+ 'title' => t('Settings').' &gt; '.t('Application settings'),
+ )));
+ }
+
+ /**
+ * Display the project settings page
+ *
+ * @access public
+ */
+ public function project()
+ {
+ $this->common('project');
+
+ $this->response->html($this->layout('config/project', array(
+ 'default_columns' => implode(', ', $this->board->getDefaultColumns()),
+ 'title' => t('Settings').' &gt; '.t('Project settings'),
)));
}
@@ -91,9 +118,36 @@ class Config extends Base
{
$this->common('board');
- $this->response->html($this->layout('config_board', array(
- 'title' => t('Board settings'),
- 'default_columns' => implode(', ', $this->board->getDefaultColumns()),
+ $this->response->html($this->layout('config/board', array(
+ 'title' => t('Settings').' &gt; '.t('Board settings'),
+ )));
+ }
+
+ /**
+ * Display the calendar settings page
+ *
+ * @access public
+ */
+ public function calendar()
+ {
+ $this->common('calendar');
+
+ $this->response->html($this->layout('config/calendar', array(
+ 'title' => t('Settings').' &gt; '.t('Calendar settings'),
+ )));
+ }
+
+ /**
+ * Display the integration settings page
+ *
+ * @access public
+ */
+ public function integrations()
+ {
+ $this->common('integrations');
+
+ $this->response->html($this->layout('config/integrations', array(
+ 'title' => t('Settings').' &gt; '.t('Integrations'),
)));
}
@@ -106,8 +160,8 @@ class Config extends Base
{
$this->common('webhook');
- $this->response->html($this->layout('config_webhook', array(
- 'title' => t('Webhook settings'),
+ $this->response->html($this->layout('config/webhook', array(
+ 'title' => t('Settings').' &gt; '.t('Webhook settings'),
)));
}
@@ -118,8 +172,8 @@ class Config extends Base
*/
public function api()
{
- $this->response->html($this->layout('config_api', array(
- 'title' => t('API'),
+ $this->response->html($this->layout('config/api', array(
+ 'title' => t('Settings').' &gt; '.t('API'),
)));
}
diff --git a/app/Controller/Currency.php b/app/Controller/Currency.php
new file mode 100644
index 00000000..10fb90da
--- /dev/null
+++ b/app/Controller/Currency.php
@@ -0,0 +1,89 @@
+<?php
+
+namespace Controller;
+
+/**
+ * Currency controller
+ *
+ * @package controller
+ * @author Frederic Guillot
+ */
+class Currency extends Base
+{
+ /**
+ * Common layout for config views
+ *
+ * @access private
+ * @param string $template Template name
+ * @param array $params Template parameters
+ * @return string
+ */
+ private function layout($template, array $params)
+ {
+ $params['board_selector'] = $this->projectPermission->getAllowedProjects($this->userSession->getId());
+ $params['config_content_for_layout'] = $this->template->render($template, $params);
+
+ return $this->template->layout('config/layout', $params);
+ }
+
+ /**
+ * Display all currency rates and form
+ *
+ * @access public
+ */
+ public function index(array $values = array(), array $errors = array())
+ {
+ $this->response->html($this->layout('currency/index', array(
+ 'config_values' => array('application_currency' => $this->config->get('application_currency')),
+ 'values' => $values,
+ 'errors' => $errors,
+ 'rates' => $this->currency->getAll(),
+ 'currencies' => $this->config->getCurrencies(),
+ 'title' => t('Settings').' &gt; '.t('Currency rates'),
+ )));
+ }
+
+ /**
+ * Validate and save a new currency rate
+ *
+ * @access public
+ */
+ public function create()
+ {
+ $values = $this->request->getValues();
+ list($valid, $errors) = $this->currency->validate($values);
+
+ if ($valid) {
+
+ if ($this->currency->create($values['currency'], $values['rate'])) {
+ $this->session->flash(t('The currency rate have been added successfully.'));
+ $this->response->redirect($this->helper->url->to('currency', 'index'));
+ }
+ else {
+ $this->session->flashError(t('Unable to add this currency rate.'));
+ }
+ }
+
+ $this->index($values, $errors);
+ }
+
+ /**
+ * Save reference currency
+ *
+ * @access public
+ */
+ public function reference()
+ {
+ $values = $this->request->getValues();
+
+ if ($this->config->save($values)) {
+ $this->config->reload();
+ $this->session->flash(t('Settings saved successfully.'));
+ }
+ else {
+ $this->session->flashError(t('Unable to save your settings.'));
+ }
+
+ $this->response->redirect($this->helper->url->to('currency', 'index'));
+ }
+}
diff --git a/app/Controller/Export.php b/app/Controller/Export.php
new file mode 100644
index 00000000..117fb5ee
--- /dev/null
+++ b/app/Controller/Export.php
@@ -0,0 +1,85 @@
+<?php
+
+namespace Controller;
+
+/**
+ * Export controller
+ *
+ * @package controller
+ * @author Frederic Guillot
+ */
+class Export extends Base
+{
+ /**
+ * Common export method
+ *
+ * @access private
+ */
+ private function common($model, $method, $filename, $action, $page_title)
+ {
+ $project = $this->getProject();
+ $from = $this->request->getStringParam('from');
+ $to = $this->request->getStringParam('to');
+
+ if ($from && $to) {
+ $data = $this->$model->$method($project['id'], $from, $to);
+ $this->response->forceDownload($filename.'.csv');
+ $this->response->csv($data);
+ }
+
+ $this->response->html($this->projectLayout('export/'.$action, array(
+ 'values' => array(
+ 'controller' => 'export',
+ 'action' => $action,
+ 'project_id' => $project['id'],
+ 'from' => $from,
+ 'to' => $to,
+ ),
+ 'errors' => array(),
+ 'date_format' => $this->config->get('application_date_format'),
+ 'date_formats' => $this->dateParser->getAvailableFormats(),
+ 'project' => $project,
+ 'title' => $page_title,
+ ), 'export/sidebar'));
+ }
+
+ /**
+ * Task export
+ *
+ * @access public
+ */
+ public function tasks()
+ {
+ $this->common('taskExport', 'export', t('Tasks'), 'tasks', t('Tasks Export'));
+ }
+
+ /**
+ * Subtask export
+ *
+ * @access public
+ */
+ public function subtasks()
+ {
+ $this->common('subtaskExport', 'export', t('Subtasks'), 'subtasks', t('Subtasks Export'));
+ }
+
+ /**
+ * Daily project summary export
+ *
+ * @access public
+ */
+ public function summary()
+ {
+ $this->common('projectDailySummary', 'getAggregatedMetrics', t('Summary'), 'summary', t('Daily project summary export'));
+ }
+
+ /**
+ * Transition export
+ *
+ * @access public
+ */
+ public function transitions()
+ {
+ $this->common('transition', 'export', t('Transitions'), 'transitions', t('Task transitions export'));
+ }
+}
diff --git a/app/Controller/File.php b/app/Controller/File.php
index 3c8c32d1..f0367537 100644
--- a/app/Controller/File.php
+++ b/app/Controller/File.php
@@ -2,8 +2,6 @@
namespace Controller;
-use Model\File as FileModel;
-
/**
* File controller
*
@@ -13,6 +11,32 @@ use Model\File as FileModel;
class File extends Base
{
/**
+ * Screenshot
+ *
+ * @access public
+ */
+ public function screenshot()
+ {
+ $task = $this->getTask();
+
+ if ($this->request->isPost() && $this->file->uploadScreenshot($task['project_id'], $task['id'], $this->request->getValue('screenshot'))) {
+
+ $this->session->flash(t('Screenshot uploaded successfully.'));
+
+ if ($this->request->getStringParam('redirect') === 'board') {
+ $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $task['project_id'])));
+ }
+
+ $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])));
+ }
+
+ $this->response->html($this->taskLayout('file/screenshot', array(
+ 'task' => $task,
+ 'redirect' => 'task',
+ )));
+ }
+
+ /**
* File upload form
*
* @access public
@@ -21,11 +45,9 @@ class File extends Base
{
$task = $this->getTask();
- $this->response->html($this->taskLayout('file_new', array(
+ $this->response->html($this->taskLayout('file/new', array(
'task' => $task,
- 'menu' => 'tasks',
'max_size' => ini_get('upload_max_filesize'),
- 'title' => t('Attach a document')
)));
}
@@ -38,13 +60,11 @@ class File extends Base
{
$task = $this->getTask();
- if ($this->file->upload($task['project_id'], $task['id'], 'files') === true) {
- $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'#attachments');
- }
- else {
+ if (! $this->file->upload($task['project_id'], $task['id'], 'files')) {
$this->session->flashError(t('Unable to upload the file.'));
- $this->response->redirect('?controller=file&action=create&task_id='.$task['id']);
}
+
+ $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])));
}
/**
@@ -56,14 +76,14 @@ class File extends Base
{
$task = $this->getTask();
$file = $this->file->getById($this->request->getIntegerParam('file_id'));
- $filename = FileModel::BASE_PATH.$file['path'];
+ $filename = FILES_DIR.$file['path'];
if ($file['task_id'] == $task['id'] && file_exists($filename)) {
$this->response->forceDownload($file['name']);
$this->response->binary(file_get_contents($filename));
}
- $this->response->redirect('?controller=task&action=show&task_id='.$task['id']);
+ $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])));
}
/**
@@ -77,8 +97,9 @@ class File extends Base
$file = $this->file->getById($this->request->getIntegerParam('file_id'));
if ($file['task_id'] == $task['id']) {
- $this->response->html($this->template->load('file_open', array(
- 'file' => $file
+ $this->response->html($this->template->render('file/open', array(
+ 'file' => $file,
+ 'task' => $task,
)));
}
}
@@ -92,7 +113,7 @@ class File extends Base
{
$task = $this->getTask();
$file = $this->file->getById($this->request->getIntegerParam('file_id'));
- $filename = FileModel::BASE_PATH.$file['path'];
+ $filename = FILES_DIR.$file['path'];
if ($file['task_id'] == $task['id'] && file_exists($filename)) {
$metadata = getimagesize($filename);
@@ -105,6 +126,28 @@ class File extends Base
}
/**
+ * Return image thumbnails
+ *
+ * @access public
+ */
+ public function thumbnail()
+ {
+ $task = $this->getTask();
+ $file = $this->file->getById($this->request->getIntegerParam('file_id'));
+ $filename = FILES_DIR.$file['path'];
+
+ if ($file['task_id'] == $task['id'] && file_exists($filename)) {
+
+ $this->response->contentType('image/jpeg');
+ $this->file->generateThumbnail(
+ $filename,
+ $this->request->getIntegerParam('width'),
+ $this->request->getIntegerParam('height')
+ );
+ }
+ }
+
+ /**
* Remove a file
*
* @access public
@@ -121,7 +164,7 @@ class File extends Base
$this->session->flashError(t('Unable to remove this file.'));
}
- $this->response->redirect('?controller=task&action=show&task_id='.$task['id']);
+ $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])));
}
/**
@@ -134,11 +177,9 @@ class File extends Base
$task = $this->getTask();
$file = $this->file->getById($this->request->getIntegerParam('file_id'));
- $this->response->html($this->taskLayout('file_remove', array(
+ $this->response->html($this->taskLayout('file/remove', array(
'task' => $task,
'file' => $file,
- 'menu' => 'tasks',
- 'title' => t('Remove a file')
)));
}
}
diff --git a/app/Controller/Hourlyrate.php b/app/Controller/Hourlyrate.php
new file mode 100644
index 00000000..19650ede
--- /dev/null
+++ b/app/Controller/Hourlyrate.php
@@ -0,0 +1,89 @@
+<?php
+
+namespace Controller;
+
+/**
+ * Hourly Rate controller
+ *
+ * @package controller
+ * @author Frederic Guillot
+ */
+class Hourlyrate extends User
+{
+ /**
+ * Display rate and form
+ *
+ * @access public
+ */
+ public function index(array $values = array(), array $errors = array())
+ {
+ $user = $this->getUser();
+
+ $this->response->html($this->layout('hourlyrate/index', array(
+ 'rates' => $this->hourlyRate->getAllByUser($user['id']),
+ 'currencies_list' => $this->config->getCurrencies(),
+ 'values' => $values + array('user_id' => $user['id']),
+ 'errors' => $errors,
+ 'user' => $user,
+ )));
+ }
+
+ /**
+ * Validate and save a new rate
+ *
+ * @access public
+ */
+ public function save()
+ {
+ $values = $this->request->getValues();
+ list($valid, $errors) = $this->hourlyRate->validateCreation($values);
+
+ if ($valid) {
+
+ if ($this->hourlyRate->create($values['user_id'], $values['rate'], $values['currency'], $values['date_effective'])) {
+ $this->session->flash(t('Hourly rate created successfully.'));
+ $this->response->redirect($this->helper->url->to('hourlyrate', 'index', array('user_id' => $values['user_id'])));
+ }
+ else {
+ $this->session->flashError(t('Unable to save the hourly rate.'));
+ }
+ }
+
+ $this->index($values, $errors);
+ }
+
+ /**
+ * Confirmation dialag box to remove a row
+ *
+ * @access public
+ */
+ public function confirm()
+ {
+ $user = $this->getUser();
+
+ $this->response->html($this->layout('hourlyrate/remove', array(
+ 'rate_id' => $this->request->getIntegerParam('rate_id'),
+ 'user' => $user,
+ )));
+ }
+
+ /**
+ * Remove a row
+ *
+ * @access public
+ */
+ public function remove()
+ {
+ $this->checkCSRFParam();
+ $user = $this->getUser();
+
+ if ($this->hourlyRate->remove($this->request->getIntegerParam('rate_id'))) {
+ $this->session->flash(t('Rate removed successfully.'));
+ }
+ else {
+ $this->session->flash(t('Unable to remove this rate.'));
+ }
+
+ $this->response->redirect($this->helper->url->to('hourlyrate', 'index', array('user_id' => $user['id'])));
+ }
+}
diff --git a/app/Controller/Ical.php b/app/Controller/Ical.php
new file mode 100644
index 00000000..52e10fa1
--- /dev/null
+++ b/app/Controller/Ical.php
@@ -0,0 +1,99 @@
+<?php
+
+namespace Controller;
+
+use Model\TaskFilter;
+use Model\Task as TaskModel;
+use Eluceo\iCal\Component\Calendar as iCalendar;
+
+/**
+ * iCalendar controller
+ *
+ * @package controller
+ * @author Frederic Guillot
+ */
+class Ical extends Base
+{
+ /**
+ * Get user iCalendar
+ *
+ * @access public
+ */
+ public function user()
+ {
+ $token = $this->request->getStringParam('token');
+ $user = $this->user->getByToken($token);
+
+ // Token verification
+ if (empty($user)) {
+ $this->forbidden(true);
+ }
+
+ // Common filter
+ $filter = $this->taskFilter
+ ->create()
+ ->filterByOwner($user['id']);
+
+ // Calendar properties
+ $calendar = new iCalendar('Kanboard');
+ $calendar->setName($user['name'] ?: $user['username']);
+ $calendar->setDescription($user['name'] ?: $user['username']);
+ $calendar->setPublishedTTL('PT1H');
+
+ $this->renderCalendar($filter, $calendar);
+ }
+
+ /**
+ * Get project iCalendar
+ *
+ * @access public
+ */
+ public function project()
+ {
+ $token = $this->request->getStringParam('token');
+ $project = $this->project->getByToken($token);
+
+ // Token verification
+ if (empty($project)) {
+ $this->forbidden(true);
+ }
+
+ // Common filter
+ $filter = $this->taskFilter
+ ->create()
+ ->filterByProject($project['id']);
+
+ // Calendar properties
+ $calendar = new iCalendar('Kanboard');
+ $calendar->setName($project['name']);
+ $calendar->setDescription($project['name']);
+ $calendar->setPublishedTTL('PT1H');
+
+ $this->renderCalendar($filter, $calendar);
+ }
+
+ /**
+ * Common method to render iCal events
+ *
+ * @access private
+ */
+ private function renderCalendar(TaskFilter $filter, iCalendar $calendar)
+ {
+ $start = $this->request->getStringParam('start', strtotime('-1 month'));
+ $end = $this->request->getStringParam('end', strtotime('+2 months'));
+
+ // Tasks
+ if ($this->config->get('calendar_project_tasks', 'date_started') === 'date_creation') {
+ $filter->copy()->filterByCreationDateRange($start, $end)->addDateTimeIcalEvents('date_creation', 'date_completed', $calendar);
+ }
+ else {
+ $filter->copy()->filterByStartDateRange($start, $end)->addDateTimeIcalEvents('date_started', 'date_completed', $calendar);
+ }
+
+ // Tasks with due date
+ $filter->copy()->filterByDueDateRange($start, $end)->addAllDayIcalEvents('date_due', $calendar);
+
+ $this->response->contentType('text/calendar; charset=utf-8');
+ echo $calendar->render();
+ }
+}
diff --git a/app/Controller/Link.php b/app/Controller/Link.php
new file mode 100644
index 00000000..482e415c
--- /dev/null
+++ b/app/Controller/Link.php
@@ -0,0 +1,162 @@
+<?php
+
+namespace Controller;
+
+/**
+ * Link controller
+ *
+ * @package controller
+ * @author Olivier Maridat
+ * @author Frederic Guillot
+ */
+class Link extends Base
+{
+ /**
+ * Common layout for config views
+ *
+ * @access private
+ * @param string $template Template name
+ * @param array $params Template parameters
+ * @return string
+ */
+ private function layout($template, array $params)
+ {
+ $params['board_selector'] = $this->projectPermission->getAllowedProjects($this->userSession->getId());
+ $params['config_content_for_layout'] = $this->template->render($template, $params);
+
+ return $this->template->layout('config/layout', $params);
+ }
+
+ /**
+ * Get the current link
+ *
+ * @access private
+ * @return array
+ */
+ private function getLink()
+ {
+ $link = $this->link->getById($this->request->getIntegerParam('link_id'));
+
+ if (empty($link)) {
+ $this->notfound();
+ }
+
+ return $link;
+ }
+
+ /**
+ * List of links
+ *
+ * @access public
+ */
+ public function index(array $values = array(), array $errors = array())
+ {
+ $this->response->html($this->layout('link/index', array(
+ 'links' => $this->link->getMergedList(),
+ 'values' => $values,
+ 'errors' => $errors,
+ 'title' => t('Settings').' &gt; '.t('Task\'s links'),
+ )));
+ }
+
+ /**
+ * Validate and save a new link
+ *
+ * @access public
+ */
+ public function save()
+ {
+ $values = $this->request->getValues();
+ list($valid, $errors) = $this->link->validateCreation($values);
+
+ if ($valid) {
+
+ if ($this->link->create($values['label'], $values['opposite_label']) !== false) {
+ $this->session->flash(t('Link added successfully.'));
+ $this->response->redirect($this->helper->url->to('link', 'index'));
+ }
+ else {
+ $this->session->flashError(t('Unable to create your link.'));
+ }
+ }
+
+ $this->index($values, $errors);
+ }
+
+ /**
+ * Edit form
+ *
+ * @access public
+ */
+ public function edit(array $values = array(), array $errors = array())
+ {
+ $link = $this->getLink();
+ $link['label'] = t($link['label']);
+
+ $this->response->html($this->layout('link/edit', array(
+ 'values' => $values ?: $link,
+ 'errors' => $errors,
+ 'labels' => $this->link->getList($link['id']),
+ 'link' => $link,
+ 'title' => t('Link modification')
+ )));
+ }
+
+ /**
+ * Edit a link (validate the form and update the database)
+ *
+ * @access public
+ */
+ public function update()
+ {
+ $values = $this->request->getValues();
+ list($valid, $errors) = $this->link->validateModification($values);
+
+ if ($valid) {
+ if ($this->link->update($values)) {
+ $this->session->flash(t('Link updated successfully.'));
+ $this->response->redirect($this->helper->url->to('link', 'index'));
+ }
+ else {
+ $this->session->flashError(t('Unable to update your link.'));
+ }
+ }
+
+ $this->edit($values, $errors);
+ }
+
+ /**
+ * Confirmation dialog before removing a link
+ *
+ * @access public
+ */
+ public function confirm()
+ {
+ $link = $this->getLink();
+
+ $this->response->html($this->layout('link/remove', array(
+ 'link' => $link,
+ 'title' => t('Remove a link')
+ )));
+ }
+
+ /**
+ * Remove a link
+ *
+ * @access public
+ */
+ public function remove()
+ {
+ $this->checkCSRFParam();
+ $link = $this->getLink();
+
+ if ($this->link->remove($link['id'])) {
+ $this->session->flash(t('Link removed successfully.'));
+ }
+ else {
+ $this->session->flashError(t('Unable to remove this link.'));
+ }
+
+ $this->response->redirect($this->helper->url->to('link', 'index'));
+ }
+}
diff --git a/app/Controller/Project.php b/app/Controller/Project.php
index d749ef53..ba039b7d 100644
--- a/app/Controller/Project.php
+++ b/app/Controller/Project.php
@@ -2,10 +2,8 @@
namespace Controller;
-use Model\Task as TaskModel;
-
/**
- * Project controller
+ * Project controller (Settings + creation/edition)
*
* @package controller
* @author Frederic Guillot
@@ -19,25 +17,26 @@ class Project extends Base
*/
public function index()
{
- $projects = $this->project->getAll($this->acl->isRegularUser());
- $nb_projects = count($projects);
- $active_projects = array();
- $inactive_projects = array();
-
- foreach ($projects as $project) {
- if ($project['is_active'] == 1) {
- $active_projects[] = $project;
- }
- else {
- $inactive_projects[] = $project;
- }
+ if ($this->userSession->isAdmin()) {
+ $project_ids = $this->project->getAllIds();
}
+ else {
+ $project_ids = $this->projectPermission->getMemberProjectIds($this->userSession->getId());
+ }
+
+ $nb_projects = count($project_ids);
- $this->response->html($this->template->layout('project_index', array(
- 'active_projects' => $active_projects,
- 'inactive_projects' => $inactive_projects,
+ $paginator = $this->paginator
+ ->setUrl('project', 'index')
+ ->setMax(20)
+ ->setOrder('name')
+ ->setQuery($this->project->getQueryColumnStats($project_ids))
+ ->calculate();
+
+ $this->response->html($this->template->layout('project/index', array(
+ 'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
+ 'paginator' => $paginator,
'nb_projects' => $nb_projects,
- 'menu' => 'projects',
'title' => t('Projects').' ('.$nb_projects.')'
)));
}
@@ -51,54 +50,21 @@ class Project extends Base
{
$project = $this->getProject();
- $this->response->html($this->projectLayout('project_show', array(
+ $this->response->html($this->projectLayout('project/show', array(
'project' => $project,
- 'stats' => $this->project->getStats($project['id']),
+ 'stats' => $this->project->getTaskStats($project['id']),
'title' => $project['name'],
)));
}
/**
- * Task export
- *
- * @access public
- */
- public function export()
- {
- $project = $this->getProjectManagement();
- $from = $this->request->getStringParam('from');
- $to = $this->request->getStringParam('to');
-
- if ($from && $to) {
- $data = $this->taskExport->export($project['id'], $from, $to);
- $this->response->forceDownload('Export_'.date('Y_m_d_H_i_S').'.csv');
- $this->response->csv($data);
- }
-
- $this->response->html($this->projectLayout('project_export', array(
- 'values' => array(
- 'controller' => 'project',
- 'action' => 'export',
- 'project_id' => $project['id'],
- 'from' => $from,
- 'to' => $to,
- ),
- 'errors' => array(),
- 'date_format' => $this->config->get('application_date_format'),
- 'date_formats' => $this->dateParser->getAvailableFormats(),
- 'project' => $project,
- 'title' => t('Tasks Export')
- )));
- }
-
- /**
* Public access management
*
* @access public
*/
public function share()
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
$switch = $this->request->getStringParam('switch');
if ($switch === 'enable' || $switch === 'disable') {
@@ -114,24 +80,51 @@ class Project extends Base
$this->response->redirect('?controller=project&action=share&project_id='.$project['id']);
}
- $this->response->html($this->projectLayout('project_share', array(
+ $this->response->html($this->projectLayout('project/share', array(
'project' => $project,
'title' => t('Public access'),
)));
}
/**
- * Display a form to edit a project
+ * Integrations page
*
* @access public
*/
- public function edit()
+ public function integration()
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
+
+ if ($this->request->isPost()) {
+ $params = $this->request->getValues();
+ $params += array('hipchat' => 0, 'slack' => 0, 'jabber' => 0);
+ $this->projectIntegration->saveParameters($project['id'], $params);
+ }
- $this->response->html($this->projectLayout('project_edit', array(
+ $values = $this->projectIntegration->getParameters($project['id']);
+ $values += array('hipchat_api_url' => 'https://api.hipchat.com');
+
+ $this->response->html($this->projectLayout('project/integrations', array(
+ 'project' => $project,
+ 'title' => t('Integrations'),
+ 'webhook_token' => $this->config->get('webhook_token'),
+ 'values' => $values,
'errors' => array(),
- 'values' => $project,
+ )));
+ }
+
+ /**
+ * Display a form to edit a project
+ *
+ * @access public
+ */
+ public function edit(array $values = array(), array $errors = array())
+ {
+ $project = $this->getProject();
+
+ $this->response->html($this->projectLayout('project/edit', array(
+ 'values' => empty($values) ? $project : $values,
+ 'errors' => $errors,
'project' => $project,
'title' => t('Edit project')
)));
@@ -144,8 +137,13 @@ class Project extends Base
*/
public function update()
{
- $project = $this->getProjectManagement();
- $values = $this->request->getValues() + array('is_active' => 0);
+ $project = $this->getProject();
+ $values = $this->request->getValues();
+
+ if ($project['is_private'] == 1 && $this->userSession->isAdmin() && ! isset($values['is_private'])) {
+ $values += array('is_private' => 0);
+ }
+
list($valid, $errors) = $this->project->validateModification($values);
if ($valid) {
@@ -159,12 +157,7 @@ class Project extends Base
}
}
- $this->response->html($this->projectLayout('project_edit', array(
- 'errors' => $errors,
- 'values' => $values,
- 'project' => $project,
- 'title' => t('Edit Project')
- )));
+ $this->edit($values, $errors);
}
/**
@@ -174,9 +167,9 @@ class Project extends Base
*/
public function users()
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
- $this->response->html($this->projectLayout('project_users', array(
+ $this->response->html($this->projectLayout('project/users', array(
'project' => $project,
'users' => $this->projectPermission->getAllUsers($project['id']),
'title' => t('Edit project access list')
@@ -190,7 +183,7 @@ class Project extends Base
*/
public function allowEverybody()
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
$values = $this->request->getValues() + array('is_everybody_allowed' => 0);
list($valid,) = $this->projectPermission->validateProjectModification($values);
@@ -219,7 +212,37 @@ class Project extends Base
if ($valid) {
- if ($this->projectPermission->allowUser($values['project_id'], $values['user_id'])) {
+ if ($this->projectPermission->addMember($values['project_id'], $values['user_id'])) {
+ $this->session->flash(t('Project updated successfully.'));
+ }
+ else {
+ $this->session->flashError(t('Unable to update this project.'));
+ }
+ }
+
+ $this->response->redirect('?controller=project&action=users&project_id='.$values['project_id']);
+ }
+
+ /**
+ * Change the role of a project member
+ *
+ * @access public
+ */
+ public function role()
+ {
+ $this->checkCSRFParam();
+
+ $values = array(
+ 'project_id' => $this->request->getIntegerParam('project_id'),
+ 'user_id' => $this->request->getIntegerParam('user_id'),
+ 'is_owner' => $this->request->getIntegerParam('is_owner'),
+ );
+
+ list($valid,) = $this->projectPermission->validateUserModification($values);
+
+ if ($valid) {
+
+ if ($this->projectPermission->changeRole($values['project_id'], $values['user_id'], $values['is_owner'])) {
$this->session->flash(t('Project updated successfully.'));
}
else {
@@ -248,7 +271,7 @@ class Project extends Base
if ($valid) {
- if ($this->projectPermission->revokeUser($values['project_id'], $values['user_id'])) {
+ if ($this->projectPermission->revokeMember($values['project_id'], $values['user_id'])) {
$this->session->flash(t('Project updated successfully.'));
}
else {
@@ -266,7 +289,7 @@ class Project extends Base
*/
public function remove()
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
if ($this->request->getStringParam('remove') === 'yes') {
@@ -281,7 +304,7 @@ class Project extends Base
$this->response->redirect('?controller=project');
}
- $this->response->html($this->projectLayout('project_remove', array(
+ $this->response->html($this->projectLayout('project/remove', array(
'project' => $project,
'title' => t('Remove project')
)));
@@ -291,17 +314,16 @@ class Project extends Base
* Duplicate a project
*
* @author Antonio Rabelo
+ * @author Michael Lüpkes
* @access public
*/
public function duplicate()
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
if ($this->request->getStringParam('duplicate') === 'yes') {
-
- $this->checkCSRFParam();
-
- if ($this->project->duplicate($project['id'])) {
+ $values = array_keys($this->request->getValues());
+ if ($this->projectDuplication->duplicate($project['id'], $values) !== false) {
$this->session->flash(t('Project cloned successfully.'));
} else {
$this->session->flashError(t('Unable to clone this project.'));
@@ -310,7 +332,7 @@ class Project extends Base
$this->response->redirect('?controller=project');
}
- $this->response->html($this->projectLayout('project_duplicate', array(
+ $this->response->html($this->projectLayout('project/duplicate', array(
'project' => $project,
'title' => t('Clone this project')
)));
@@ -323,7 +345,7 @@ class Project extends Base
*/
public function disable()
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
if ($this->request->getStringParam('disable') === 'yes') {
@@ -338,7 +360,7 @@ class Project extends Base
$this->response->redirect('?controller=project&action=show&project_id='.$project['id']);
}
- $this->response->html($this->projectLayout('project_disable', array(
+ $this->response->html($this->projectLayout('project/disable', array(
'project' => $project,
'title' => t('Project activation')
)));
@@ -351,7 +373,7 @@ class Project extends Base
*/
public function enable()
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
if ($this->request->getStringParam('enable') === 'yes') {
@@ -366,7 +388,7 @@ class Project extends Base
$this->response->redirect('?controller=project&action=show&project_id='.$project['id']);
}
- $this->response->html($this->projectLayout('project_enable', array(
+ $this->response->html($this->projectLayout('project/enable', array(
'project' => $project,
'title' => t('Project activation')
)));
@@ -383,115 +405,13 @@ class Project extends Base
$project = $this->project->getByToken($token);
// Token verification
- if (! $project) {
+ if (empty($project)) {
$this->forbidden(true);
}
- $this->response->xml($this->template->load('project_feed', array(
- 'events' => $this->projectActivity->getProject($project['id']),
- 'project' => $project,
- )));
- }
-
- /**
- * Activity page for a project
- *
- * @access public
- */
- public function activity()
- {
- $project = $this->getProject();
-
- $this->response->html($this->template->layout('project_activity', array(
+ $this->response->xml($this->template->render('project/feed', array(
'events' => $this->projectActivity->getProject($project['id']),
- 'menu' => 'projects',
'project' => $project,
- 'title' => t('%s\'s activity', $project['name'])
- )));
- }
-
- /**
- * Task search for a given project
- *
- * @access public
- */
- public function search()
- {
- $project = $this->getProject();
- $search = $this->request->getStringParam('search');
- $direction = $this->request->getStringParam('direction', 'DESC');
- $order = $this->request->getStringParam('order', 'tasks.id');
- $offset = $this->request->getIntegerParam('offset', 0);
- $tasks = array();
- $nb_tasks = 0;
- $limit = 25;
-
- if ($search !== '') {
- $tasks = $this->taskFinder->search($project['id'], $search, $offset, $limit, $order, $direction);
- $nb_tasks = $this->taskFinder->countSearch($project['id'], $search);
- }
-
- $this->response->html($this->template->layout('project_search', array(
- 'tasks' => $tasks,
- 'nb_tasks' => $nb_tasks,
- 'pagination' => array(
- 'controller' => 'project',
- 'action' => 'search',
- 'params' => array('search' => $search, 'project_id' => $project['id']),
- 'direction' => $direction,
- 'order' => $order,
- 'total' => $nb_tasks,
- 'offset' => $offset,
- 'limit' => $limit,
- ),
- 'values' => array(
- 'search' => $search,
- 'controller' => 'project',
- 'action' => 'search',
- 'project_id' => $project['id'],
- ),
- 'project' => $project,
- 'menu' => 'projects',
- 'columns' => $this->board->getColumnsList($project['id']),
- 'categories' => $this->category->getList($project['id'], false),
- 'title' => $project['name'].($nb_tasks > 0 ? ' ('.$nb_tasks.')' : '')
- )));
- }
-
- /**
- * List of completed tasks for a given project
- *
- * @access public
- */
- public function tasks()
- {
- $project = $this->getProject();
- $direction = $this->request->getStringParam('direction', 'DESC');
- $order = $this->request->getStringParam('order', 'tasks.date_completed');
- $offset = $this->request->getIntegerParam('offset', 0);
- $limit = 25;
-
- $tasks = $this->taskFinder->getClosedTasks($project['id'], $offset, $limit, $order, $direction);
- $nb_tasks = $this->taskFinder->countByProjectId($project['id'], array(TaskModel::STATUS_CLOSED));
-
- $this->response->html($this->template->layout('project_tasks', array(
- 'pagination' => array(
- 'controller' => 'project',
- 'action' => 'tasks',
- 'params' => array('project_id' => $project['id']),
- 'direction' => $direction,
- 'order' => $order,
- 'total' => $nb_tasks,
- 'offset' => $offset,
- 'limit' => $limit,
- ),
- 'project' => $project,
- 'menu' => 'projects',
- 'columns' => $this->board->getColumnsList($project['id']),
- 'categories' => $this->category->getList($project['id'], false),
- 'tasks' => $tasks,
- 'nb_tasks' => $nb_tasks,
- 'title' => $project['name'].' ('.$nb_tasks.')'
)));
}
@@ -500,14 +420,16 @@ class Project extends Base
*
* @access public
*/
- public function create()
+ public function create(array $values = array(), array $errors = array())
{
- $this->response->html($this->template->layout('project_new', array(
- 'errors' => array(),
- 'values' => array(
- 'is_private' => $this->request->getIntegerParam('private', $this->acl->isRegularUser()),
- ),
- 'title' => t('New project')
+ $is_private = $this->request->getIntegerParam('private', $this->userSession->isAdmin() ? 0 : 1);
+
+ $this->response->html($this->template->layout('project/new', array(
+ 'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
+ 'values' => empty($values) ? array('is_private' => $is_private) : $values,
+ 'errors' => $errors,
+ 'is_private' => $is_private,
+ 'title' => $is_private ? t('New private project') : t('New project'),
)));
}
@@ -523,19 +445,16 @@ class Project extends Base
if ($valid) {
- if ($this->project->create($values, $this->acl->getUserId())) {
+ $project_id = $this->project->create($values, $this->userSession->getId(), true);
+
+ if ($project_id > 0) {
$this->session->flash(t('Your project have been created successfully.'));
- $this->response->redirect('?controller=project');
- }
- else {
- $this->session->flashError(t('Unable to create your project.'));
+ $this->response->redirect('?controller=project&action=show&project_id='.$project_id);
}
+
+ $this->session->flashError(t('Unable to create your project.'));
}
- $this->response->html($this->template->layout('project_new', array(
- 'errors' => $errors,
- 'values' => $values,
- 'title' => t('New Project')
- )));
+ $this->create($values, $errors);
}
}
diff --git a/app/Controller/Projectinfo.php b/app/Controller/Projectinfo.php
new file mode 100644
index 00000000..a9498f43
--- /dev/null
+++ b/app/Controller/Projectinfo.php
@@ -0,0 +1,97 @@
+<?php
+
+namespace Controller;
+
+/**
+ * Project Info controller (ActivityStream + completed tasks)
+ *
+ * @package controller
+ * @author Frederic Guillot
+ */
+class Projectinfo extends Base
+{
+ /**
+ * Activity page for a project
+ *
+ * @access public
+ */
+ public function activity()
+ {
+ $project = $this->getProject();
+
+ $this->response->html($this->template->layout('projectinfo/activity', array(
+ 'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
+ 'events' => $this->projectActivity->getProject($project['id']),
+ 'project' => $project,
+ 'title' => t('%s\'s activity', $project['name'])
+ )));
+ }
+
+ /**
+ * Task search for a given project
+ *
+ * @access public
+ */
+ public function search()
+ {
+ $project = $this->getProject();
+ $search = $this->request->getStringParam('search');
+ $nb_tasks = 0;
+
+ $paginator = $this->paginator
+ ->setUrl('projectinfo', 'search', array('search' => $search, 'project_id' => $project['id']))
+ ->setMax(30)
+ ->setOrder('tasks.id')
+ ->setDirection('DESC');
+
+ if ($search !== '') {
+
+ $paginator
+ ->setQuery($this->taskFinder->getSearchQuery($project['id'], $search))
+ ->calculate();
+
+ $nb_tasks = $paginator->getTotal();
+ }
+
+ $this->response->html($this->template->layout('projectinfo/search', array(
+ 'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
+ 'values' => array(
+ 'search' => $search,
+ 'controller' => 'projectinfo',
+ 'action' => 'search',
+ 'project_id' => $project['id'],
+ ),
+ 'paginator' => $paginator,
+ 'project' => $project,
+ 'columns' => $this->board->getColumnsList($project['id']),
+ 'categories' => $this->category->getList($project['id'], false),
+ 'title' => t('Search in the project "%s"', $project['name']).($nb_tasks > 0 ? ' ('.$nb_tasks.')' : '')
+ )));
+ }
+
+ /**
+ * List of completed tasks for a given project
+ *
+ * @access public
+ */
+ public function tasks()
+ {
+ $project = $this->getProject();
+ $paginator = $this->paginator
+ ->setUrl('projectinfo', 'tasks', array('project_id' => $project['id']))
+ ->setMax(30)
+ ->setOrder('tasks.id')
+ ->setDirection('DESC')
+ ->setQuery($this->taskFinder->getClosedTaskQuery($project['id']))
+ ->calculate();
+
+ $this->response->html($this->template->layout('projectinfo/tasks', array(
+ 'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
+ 'project' => $project,
+ 'columns' => $this->board->getColumnsList($project['id']),
+ 'categories' => $this->category->getList($project['id'], false),
+ 'paginator' => $paginator,
+ 'title' => t('Completed tasks for "%s"', $project['name']).' ('.$paginator->getTotal().')'
+ )));
+ }
+}
diff --git a/app/Controller/Subtask.php b/app/Controller/Subtask.php
index 48f0d6e2..5baa6004 100644
--- a/app/Controller/Subtask.php
+++ b/app/Controller/Subtask.php
@@ -2,8 +2,10 @@
namespace Controller;
+use Model\Subtask as SubtaskModel;
+
/**
- * SubTask controller
+ * Subtask controller
*
* @package controller
* @author Frederic Guillot
@@ -18,9 +20,9 @@ class Subtask extends Base
*/
private function getSubtask()
{
- $subtask = $this->subTask->getById($this->request->getIntegerParam('subtask_id'));
+ $subtask = $this->subtask->getById($this->request->getIntegerParam('subtask_id'));
- if (! $subtask) {
+ if (empty($subtask)) {
$this->notfound();
}
@@ -32,20 +34,22 @@ class Subtask extends Base
*
* @access public
*/
- public function create()
+ public function create(array $values = array(), array $errors = array())
{
$task = $this->getTask();
- $this->response->html($this->taskLayout('subtask_create', array(
- 'values' => array(
+ if (empty($values)) {
+ $values = array(
'task_id' => $task['id'],
'another_subtask' => $this->request->getIntegerParam('another_subtask', 0)
- ),
- 'errors' => array(),
- 'users_list' => $this->projectPermission->getUsersList($task['project_id']),
+ );
+ }
+
+ $this->response->html($this->taskLayout('subtask/create', array(
+ 'values' => $values,
+ 'errors' => $errors,
+ 'users_list' => $this->projectPermission->getMemberList($task['project_id']),
'task' => $task,
- 'menu' => 'tasks',
- 'title' => t('Add a sub-task')
)));
}
@@ -59,11 +63,11 @@ class Subtask extends Base
$task = $this->getTask();
$values = $this->request->getValues();
- list($valid, $errors) = $this->subTask->validateCreation($values);
+ list($valid, $errors) = $this->subtask->validateCreation($values);
if ($valid) {
- if ($this->subTask->create($values)) {
+ if ($this->subtask->create($values)) {
$this->session->flash(t('Sub-task added successfully.'));
}
else {
@@ -71,20 +75,13 @@ class Subtask extends Base
}
if (isset($values['another_subtask']) && $values['another_subtask'] == 1) {
- $this->response->redirect('?controller=subtask&action=create&task_id='.$task['id'].'&another_subtask=1');
+ $this->response->redirect('?controller=subtask&action=create&task_id='.$task['id'].'&another_subtask=1&project_id='.$task['project_id']);
}
- $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'#subtasks');
+ $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id'].'#subtasks');
}
- $this->response->html($this->taskLayout('subtask_create', array(
- 'values' => $values,
- 'errors' => $errors,
- 'users_list' => $this->projectPermission->getUsersList($task['project_id']),
- 'task' => $task,
- 'menu' => 'tasks',
- 'title' => t('Add a sub-task')
- )));
+ $this->create($values, $errors);
}
/**
@@ -92,20 +89,18 @@ class Subtask extends Base
*
* @access public
*/
- public function edit()
+ public function edit(array $values = array(), array $errors = array())
{
$task = $this->getTask();
$subtask = $this->getSubTask();
- $this->response->html($this->taskLayout('subtask_edit', array(
- 'values' => $subtask,
- 'errors' => array(),
- 'users_list' => $this->projectPermission->getUsersList($task['project_id']),
- 'status_list' => $this->subTask->getStatusList(),
+ $this->response->html($this->taskLayout('subtask/edit', array(
+ 'values' => empty($values) ? $subtask : $values,
+ 'errors' => $errors,
+ 'users_list' => $this->projectPermission->getMemberList($task['project_id']),
+ 'status_list' => $this->subtask->getStatusList(),
'subtask' => $subtask,
'task' => $task,
- 'menu' => 'tasks',
- 'title' => t('Edit a sub-task')
)));
}
@@ -117,33 +112,24 @@ class Subtask extends Base
public function update()
{
$task = $this->getTask();
- $subtask = $this->getSubtask();
+ $this->getSubtask();
$values = $this->request->getValues();
- list($valid, $errors) = $this->subTask->validateModification($values);
+ list($valid, $errors) = $this->subtask->validateModification($values);
if ($valid) {
- if ($this->subTask->update($values)) {
+ if ($this->subtask->update($values)) {
$this->session->flash(t('Sub-task updated successfully.'));
}
else {
$this->session->flashError(t('Unable to update your sub-task.'));
}
- $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'#subtasks');
+ $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id'].'#subtasks');
}
- $this->response->html($this->taskLayout('subtask_edit', array(
- 'values' => $values,
- 'errors' => $errors,
- 'users_list' => $this->projectPermission->getUsersList($task['project_id']),
- 'status_list' => $this->subTask->getStatusList(),
- 'subtask' => $subtask,
- 'task' => $task,
- 'menu' => 'tasks',
- 'title' => t('Edit a sub-task')
- )));
+ $this->edit($values, $errors);
}
/**
@@ -156,11 +142,9 @@ class Subtask extends Base
$task = $this->getTask();
$subtask = $this->getSubtask();
- $this->response->html($this->taskLayout('subtask_remove', array(
+ $this->response->html($this->taskLayout('subtask/remove', array(
'subtask' => $subtask,
'task' => $task,
- 'menu' => 'tasks',
- 'title' => t('Remove a sub-task')
)));
}
@@ -175,14 +159,14 @@ class Subtask extends Base
$task = $this->getTask();
$subtask = $this->getSubtask();
- if ($this->subTask->remove($subtask['id'])) {
+ if ($this->subtask->remove($subtask['id'])) {
$this->session->flash(t('Sub-task removed successfully.'));
}
else {
$this->session->flashError(t('Unable to remove this sub-task.'));
}
- $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'#subtasks');
+ $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id'].'#subtasks');
}
/**
@@ -194,17 +178,103 @@ class Subtask extends Base
{
$task = $this->getTask();
$subtask = $this->getSubtask();
+ $redirect = $this->request->getStringParam('redirect', 'task');
+
+ $this->subtask->toggleStatus($subtask['id']);
+
+ if ($redirect === 'board') {
+
+ $this->session['has_subtask_inprogress'] = $this->subtask->hasSubtaskInProgress($this->userSession->getId());
+
+ $this->response->html($this->template->render('board/subtasks', array(
+ 'subtasks' => $this->subtask->getAll($task['id']),
+ 'task' => $task,
+ )));
+ }
+
+ $this->toggleRedirect($task, $redirect);
+ }
+
+ /**
+ * Handle subtask restriction (popover)
+ *
+ * @access public
+ */
+ public function subtaskRestriction()
+ {
+ $task = $this->getTask();
+ $subtask = $this->getSubtask();
- $value = array(
+ $this->response->html($this->template->render('subtask/restriction_change_status', array(
+ 'status_list' => array(
+ SubtaskModel::STATUS_TODO => t('Todo'),
+ SubtaskModel::STATUS_DONE => t('Done'),
+ ),
+ 'subtask_inprogress' => $this->subtask->getSubtaskInProgress($this->userSession->getId()),
+ 'subtask' => $subtask,
+ 'task' => $task,
+ 'redirect' => $this->request->getStringParam('redirect'),
+ )));
+ }
+
+ /**
+ * Change status of the in progress subtask and the other subtask
+ *
+ * @access public
+ */
+ public function changeRestrictionStatus()
+ {
+ $task = $this->getTask();
+ $subtask = $this->getSubtask();
+ $values = $this->request->getValues();
+
+ // Change status of the previous in progress subtask
+ $this->subtask->update(array(
+ 'id' => $values['id'],
+ 'status' => $values['status'],
+ ));
+
+ // Set the current subtask to in pogress
+ $this->subtask->update(array(
'id' => $subtask['id'],
- 'status' => ($subtask['status'] + 1) % 3,
- 'task_id' => $task['id'],
- );
+ 'status' => SubtaskModel::STATUS_INPROGRESS,
+ ));
- if (! $this->subTask->update($value)) {
- $this->session->flashError(t('Unable to update your sub-task.'));
+ $this->toggleRedirect($task, $values['redirect']);
+ }
+
+ /**
+ * Redirect to the right page
+ *
+ * @access private
+ */
+ private function toggleRedirect(array $task, $redirect)
+ {
+ switch ($redirect) {
+ case 'board':
+ $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $task['project_id'])));
+ case 'dashboard':
+ $this->response->redirect($this->helper->url->to('app', 'index'));
+ default:
+ $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])));
}
+ }
+
+ /**
+ * Move subtask position
+ *
+ * @access public
+ */
+ public function movePosition()
+ {
+ $this->checkCSRFParam();
+ $project_id = $this->request->getIntegerParam('project_id');
+ $task_id = $this->request->getIntegerParam('task_id');
+ $subtask_id = $this->request->getIntegerParam('subtask_id');
+ $direction = $this->request->getStringParam('direction');
+ $method = $direction === 'up' ? 'moveUp' : 'moveDown';
- $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'#subtasks');
+ $this->subtask->$method($task_id, $subtask_id);
+ $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $project_id, 'task_id' => $task_id)).'#subtasks');
}
}
diff --git a/app/Controller/Swimlane.php b/app/Controller/Swimlane.php
new file mode 100644
index 00000000..c6862d47
--- /dev/null
+++ b/app/Controller/Swimlane.php
@@ -0,0 +1,256 @@
+<?php
+
+namespace Controller;
+
+use Model\Swimlane as SwimlaneModel;
+
+/**
+ * Swimlanes
+ *
+ * @package controller
+ * @author Frederic Guillot
+ */
+class Swimlane extends Base
+{
+ /**
+ * Get the swimlane (common method between actions)
+ *
+ * @access private
+ * @param integer $project_id
+ * @return array
+ */
+ private function getSwimlane($project_id)
+ {
+ $swimlane = $this->swimlane->getById($this->request->getIntegerParam('swimlane_id'));
+
+ if (empty($swimlane)) {
+ $this->session->flashError(t('Swimlane not found.'));
+ $this->response->redirect('?controller=swimlane&action=index&project_id='.$project_id);
+ }
+
+ return $swimlane;
+ }
+
+ /**
+ * List of swimlanes for a given project
+ *
+ * @access public
+ */
+ public function index(array $values = array(), array $errors = array())
+ {
+ $project = $this->getProject();
+
+ $this->response->html($this->projectLayout('swimlane/index', array(
+ 'default_swimlane' => $this->swimlane->getDefault($project['id']),
+ 'active_swimlanes' => $this->swimlane->getAllByStatus($project['id'], SwimlaneModel::ACTIVE),
+ 'inactive_swimlanes' => $this->swimlane->getAllByStatus($project['id'], SwimlaneModel::INACTIVE),
+ 'values' => $values + array('project_id' => $project['id']),
+ 'errors' => $errors,
+ 'project' => $project,
+ 'title' => t('Swimlanes')
+ )));
+ }
+
+ /**
+ * Validate and save a new swimlane
+ *
+ * @access public
+ */
+ public function save()
+ {
+ $project = $this->getProject();
+
+ $values = $this->request->getValues();
+ list($valid, $errors) = $this->swimlane->validateCreation($values);
+
+ if ($valid) {
+
+ if ($this->swimlane->create($project['id'], $values['name'])) {
+ $this->session->flash(t('Your swimlane have been created successfully.'));
+ $this->response->redirect('?controller=swimlane&action=index&project_id='.$project['id']);
+ }
+ else {
+ $this->session->flashError(t('Unable to create your swimlane.'));
+ }
+ }
+
+ $this->index($values, $errors);
+ }
+
+ /**
+ * Change the default swimlane
+ *
+ * @access public
+ */
+ public function change()
+ {
+ $project = $this->getProject();
+
+ $values = $this->request->getValues() + array('show_default_swimlane' => 0);
+ list($valid,) = $this->swimlane->validateDefaultModification($values);
+
+ if ($valid) {
+
+ if ($this->swimlane->updateDefault($values)) {
+ $this->session->flash(t('The default swimlane have been updated successfully.'));
+ $this->response->redirect('?controller=swimlane&action=index&project_id='.$project['id']);
+ }
+ else {
+ $this->session->flashError(t('Unable to update this swimlane.'));
+ }
+ }
+
+ $this->index();
+ }
+
+ /**
+ * Edit a swimlane (display the form)
+ *
+ * @access public
+ */
+ public function edit(array $values = array(), array $errors = array())
+ {
+ $project = $this->getProject();
+ $swimlane = $this->getSwimlane($project['id']);
+
+ $this->response->html($this->projectLayout('swimlane/edit', array(
+ 'values' => empty($values) ? $swimlane : $values,
+ 'errors' => $errors,
+ 'project' => $project,
+ 'title' => t('Swimlanes')
+ )));
+ }
+
+ /**
+ * Edit a swimlane (validate the form and update the database)
+ *
+ * @access public
+ */
+ public function update()
+ {
+ $project = $this->getProject();
+
+ $values = $this->request->getValues();
+ list($valid, $errors) = $this->swimlane->validateModification($values);
+
+ if ($valid) {
+
+ if ($this->swimlane->rename($values['id'], $values['name'])) {
+ $this->session->flash(t('Swimlane updated successfully.'));
+ $this->response->redirect('?controller=swimlane&action=index&project_id='.$project['id']);
+ }
+ else {
+ $this->session->flashError(t('Unable to update this swimlane.'));
+ }
+ }
+
+ $this->edit($values, $errors);
+ }
+
+ /**
+ * Confirmation dialog before removing a swimlane
+ *
+ * @access public
+ */
+ public function confirm()
+ {
+ $project = $this->getProject();
+ $swimlane = $this->getSwimlane($project['id']);
+
+ $this->response->html($this->projectLayout('swimlane/remove', array(
+ 'project' => $project,
+ 'swimlane' => $swimlane,
+ 'title' => t('Remove a swimlane')
+ )));
+ }
+
+ /**
+ * Remove a swimlane
+ *
+ * @access public
+ */
+ public function remove()
+ {
+ $this->checkCSRFParam();
+ $project = $this->getProject();
+ $swimlane_id = $this->request->getIntegerParam('swimlane_id');
+
+ if ($this->swimlane->remove($project['id'], $swimlane_id)) {
+ $this->session->flash(t('Swimlane removed successfully.'));
+ } else {
+ $this->session->flashError(t('Unable to remove this swimlane.'));
+ }
+
+ $this->response->redirect('?controller=swimlane&action=index&project_id='.$project['id']);
+ }
+
+ /**
+ * Disable a swimlane
+ *
+ * @access public
+ */
+ public function disable()
+ {
+ $this->checkCSRFParam();
+ $project = $this->getProject();
+ $swimlane_id = $this->request->getIntegerParam('swimlane_id');
+
+ if ($this->swimlane->disable($project['id'], $swimlane_id)) {
+ $this->session->flash(t('Swimlane updated successfully.'));
+ } else {
+ $this->session->flashError(t('Unable to update this swimlane.'));
+ }
+
+ $this->response->redirect('?controller=swimlane&action=index&project_id='.$project['id']);
+ }
+
+ /**
+ * Enable a swimlane
+ *
+ * @access public
+ */
+ public function enable()
+ {
+ $this->checkCSRFParam();
+ $project = $this->getProject();
+ $swimlane_id = $this->request->getIntegerParam('swimlane_id');
+
+ if ($this->swimlane->enable($project['id'], $swimlane_id)) {
+ $this->session->flash(t('Swimlane updated successfully.'));
+ } else {
+ $this->session->flashError(t('Unable to update this swimlane.'));
+ }
+
+ $this->response->redirect('?controller=swimlane&action=index&project_id='.$project['id']);
+ }
+
+ /**
+ * Move up a swimlane
+ *
+ * @access public
+ */
+ public function moveup()
+ {
+ $this->checkCSRFParam();
+ $project = $this->getProject();
+ $swimlane_id = $this->request->getIntegerParam('swimlane_id');
+
+ $this->swimlane->moveUp($project['id'], $swimlane_id);
+ $this->response->redirect('?controller=swimlane&action=index&project_id='.$project['id']);
+ }
+
+ /**
+ * Move down a swimlane
+ *
+ * @access public
+ */
+ public function movedown()
+ {
+ $this->checkCSRFParam();
+ $project = $this->getProject();
+ $swimlane_id = $this->request->getIntegerParam('swimlane_id');
+
+ $this->swimlane->moveDown($project['id'], $swimlane_id);
+ $this->response->redirect('?controller=swimlane&action=index&project_id='.$project['id']);
+ }
+}
diff --git a/app/Controller/Task.php b/app/Controller/Task.php
index 1b20cf15..dc83f7b1 100644
--- a/app/Controller/Task.php
+++ b/app/Controller/Task.php
@@ -22,20 +22,21 @@ class Task extends Base
$project = $this->project->getByToken($this->request->getStringParam('token'));
// Token verification
- if (! $project) {
+ if (empty($project)) {
$this->forbidden(true);
}
$task = $this->taskFinder->getDetails($this->request->getIntegerParam('task_id'));
- if (! $task) {
+ if (empty($task)) {
$this->notfound(true);
}
- $this->response->html($this->template->layout('task_public', array(
+ $this->response->html($this->template->layout('task/public', array(
'project' => $project,
'comments' => $this->comment->getAll($task['id']),
- 'subtasks' => $this->subTask->getAll($task['id']),
+ 'subtasks' => $this->subtask->getAll($task['id']),
+ 'links' => $this->taskLink->getAllGroupedByLabel($task['id']),
'task' => $task,
'columns_list' => $this->board->getColumnsList($task['project_id']),
'colors_list' => $this->color->getList(),
@@ -54,7 +55,7 @@ class Task extends Base
public function show()
{
$task = $this->getTask();
- $subtasks = $this->subTask->getAll($task['id']);
+ $subtasks = $this->subtask->getAll($task['id']);
$values = array(
'id' => $task['id'],
@@ -65,20 +66,41 @@ class Task extends Base
$this->dateParser->format($values, array('date_started'));
- $this->response->html($this->taskLayout('task_show', array(
+ $this->response->html($this->taskLayout('task/show', array(
'project' => $this->project->getById($task['project_id']),
- 'files' => $this->file->getAll($task['id']),
+ 'files' => $this->file->getAllDocuments($task['id']),
+ 'images' => $this->file->getAllImages($task['id']),
'comments' => $this->comment->getAll($task['id']),
'subtasks' => $subtasks,
+ 'links' => $this->taskLink->getAllGroupedByLabel($task['id']),
'task' => $task,
'values' => $values,
- 'timesheet' => $this->timeTracking->getTaskTimesheet($task, $subtasks),
+ 'link_label_list' => $this->link->getList(0, false),
'columns_list' => $this->board->getColumnsList($task['project_id']),
'colors_list' => $this->color->getList(),
'date_format' => $this->config->get('application_date_format'),
'date_formats' => $this->dateParser->getAvailableFormats(),
- 'menu' => 'tasks',
+ 'title' => $task['project_name'].' &gt; '.$task['title'],
+ 'recurrence_trigger_list' => $this->task->getRecurrenceTriggerList(),
+ 'recurrence_timeframe_list' => $this->task->getRecurrenceTimeframeList(),
+ 'recurrence_basedate_list' => $this->task->getRecurrenceBasedateList(),
+ )));
+ }
+
+ /**
+ * Display task activities
+ *
+ * @access public
+ */
+ public function activites()
+ {
+ $task = $this->getTask();
+
+ $this->response->html($this->taskLayout('task/activity', array(
'title' => $task['title'],
+ 'task' => $task,
+ 'ajax' => $this->request->isAjax(),
+ 'events' => $this->projectActivity->getTask($task['id']),
)));
}
@@ -87,29 +109,36 @@ class Task extends Base
*
* @access public
*/
- public function create()
+ public function create(array $values = array(), array $errors = array())
{
- $project_id = $this->request->getIntegerParam('project_id');
- $this->checkProjectPermissions($project_id);
+ $project = $this->getProject();
+ $method = $this->request->isAjax() ? 'render' : 'layout';
+ $swimlanes_list = $this->swimlane->getList($project['id'], false, true);
- $this->response->html($this->template->layout('task_new', array(
- 'errors' => array(),
- 'values' => array(
- 'project_id' => $project_id,
+ if (empty($values)) {
+
+ $values = array(
+ 'swimlane_id' => $this->request->getIntegerParam('swimlane_id', key($swimlanes_list)),
'column_id' => $this->request->getIntegerParam('column_id'),
'color_id' => $this->request->getStringParam('color_id'),
'owner_id' => $this->request->getIntegerParam('owner_id'),
'another_task' => $this->request->getIntegerParam('another_task'),
- ),
+ );
+ }
+
+ $this->response->html($this->template->$method('task/new', array(
+ 'ajax' => $this->request->isAjax(),
+ 'errors' => $errors,
+ 'values' => $values + array('project_id' => $project['id']),
'projects_list' => $this->project->getListByStatus(ProjectModel::ACTIVE),
- 'columns_list' => $this->board->getColumnsList($project_id),
- 'users_list' => $this->projectPermission->getUsersList($project_id),
+ 'columns_list' => $this->board->getColumnsList($project['id']),
+ 'users_list' => $this->projectPermission->getMemberList($project['id'], true, false, true),
'colors_list' => $this->color->getList(),
- 'categories_list' => $this->category->getList($project_id),
+ 'categories_list' => $this->category->getList($project['id']),
+ 'swimlanes_list' => $swimlanes_list,
'date_format' => $this->config->get('application_date_format'),
'date_formats' => $this->dateParser->getAvailableFormats(),
- 'menu' => 'tasks',
- 'title' => t('New task')
+ 'title' => $project['name'].' &gt; '.t('New task')
)));
}
@@ -120,16 +149,15 @@ class Task extends Base
*/
public function save()
{
+ $project = $this->getProject();
$values = $this->request->getValues();
- $values['creator_id'] = $this->acl->getUserId();
-
- $this->checkProjectPermissions($values['project_id']);
+ $values['creator_id'] = $this->userSession->getId();
list($valid, $errors) = $this->taskValidator->validateCreation($values);
if ($valid) {
- if ($this->task->create($values)) {
+ if ($this->taskCreation->create($values)) {
$this->session->flash(t('Task created successfully.'));
if (isset($values['another_task']) && $values['another_task'] == 1) {
@@ -138,7 +166,7 @@ class Task extends Base
$this->response->redirect('?controller=task&action=create&'.http_build_query($values));
}
else {
- $this->response->redirect('?controller=board&action=show&project_id='.$values['project_id']);
+ $this->response->redirect('?controller=board&action=show&project_id='.$project['id']);
}
}
else {
@@ -146,19 +174,7 @@ class Task extends Base
}
}
- $this->response->html($this->template->layout('task_new', array(
- 'errors' => $errors,
- 'values' => $values,
- 'projects_list' => $this->project->getListByStatus(ProjectModel::ACTIVE),
- 'columns_list' => $this->board->getColumnsList($values['project_id']),
- 'users_list' => $this->projectPermission->getUsersList($values['project_id']),
- 'colors_list' => $this->color->getList(),
- 'categories_list' => $this->category->getList($values['project_id']),
- 'date_format' => $this->config->get('application_date_format'),
- 'date_formats' => $this->dateParser->getAvailableFormats(),
- 'menu' => 'tasks',
- 'title' => t('New task')
- )));
+ $this->create($values, $errors);
}
/**
@@ -166,32 +182,34 @@ class Task extends Base
*
* @access public
*/
- public function edit()
+ public function edit(array $values = array(), array $errors = array())
{
$task = $this->getTask();
$ajax = $this->request->isAjax();
- $this->dateParser->format($task, array('date_due'));
+ if (empty($values)) {
+ $values = $task;
+ }
+
+ $this->dateParser->format($values, array('date_due'));
$params = array(
- 'values' => $task,
- 'errors' => array(),
+ 'values' => $values,
+ 'errors' => $errors,
'task' => $task,
- 'users_list' => $this->projectPermission->getUsersList($task['project_id']),
+ 'users_list' => $this->projectPermission->getMemberList($task['project_id']),
'colors_list' => $this->color->getList(),
'categories_list' => $this->category->getList($task['project_id']),
'date_format' => $this->config->get('application_date_format'),
'date_formats' => $this->dateParser->getAvailableFormats(),
'ajax' => $ajax,
- 'menu' => 'tasks',
- 'title' => t('Edit a task')
);
if ($ajax) {
- $this->response->html($this->template->load('task_edit', $params));
+ $this->response->html($this->template->render('task/edit', $params));
}
else {
- $this->response->html($this->taskLayout('task_edit', $params));
+ $this->response->html($this->taskLayout('task/edit', $params));
}
}
@@ -209,14 +227,14 @@ class Task extends Base
if ($valid) {
- if ($this->task->update($values)) {
+ if ($this->taskModification->update($values)) {
$this->session->flash(t('Task updated successfully.'));
if ($this->request->getIntegerParam('ajax')) {
$this->response->redirect('?controller=board&action=show&project_id='.$task['project_id']);
}
else {
- $this->response->redirect('?controller=task&action=show&task_id='.$task['id']);
+ $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id']);
}
}
else {
@@ -224,20 +242,7 @@ class Task extends Base
}
}
- $this->response->html($this->taskLayout('task_edit', array(
- 'values' => $values,
- 'errors' => $errors,
- 'task' => $task,
- 'columns_list' => $this->board->getColumnsList($values['project_id']),
- 'users_list' => $this->projectPermission->getUsersList($values['project_id']),
- 'colors_list' => $this->color->getList(),
- 'categories_list' => $this->category->getList($values['project_id']),
- 'date_format' => $this->config->get('application_date_format'),
- 'date_formats' => $this->dateParser->getAvailableFormats(),
- 'menu' => 'tasks',
- 'title' => t('Edit a task'),
- 'ajax' => $this->request->isAjax(),
- )));
+ $this->edit($values, $errors);
}
/**
@@ -250,16 +255,16 @@ class Task extends Base
$task = $this->getTask();
$values = $this->request->getValues();
- list($valid, $errors) = $this->taskValidator->validateTimeModification($values);
+ list($valid,) = $this->taskValidator->validateTimeModification($values);
- if ($valid && $this->task->update($values)) {
+ if ($valid && $this->taskModification->update($values)) {
$this->session->flash(t('Task updated successfully.'));
}
else {
$this->session->flashError(t('Unable to update your task.'));
}
- $this->response->redirect('?controller=task&action=show&task_id='.$task['id']);
+ $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id']);
}
/**
@@ -270,24 +275,35 @@ class Task extends Base
public function close()
{
$task = $this->getTask();
+ $redirect = $this->request->getStringParam('redirect');
if ($this->request->getStringParam('confirmation') === 'yes') {
$this->checkCSRFParam();
- if ($this->task->close($task['id'])) {
+ if ($this->taskStatus->close($task['id'])) {
$this->session->flash(t('Task closed successfully.'));
} else {
$this->session->flashError(t('Unable to close this task.'));
}
- $this->response->redirect('?controller=task&action=show&task_id='.$task['id']);
+ if ($redirect === 'board') {
+ $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $task['project_id'])));
+ }
+
+ $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])));
}
- $this->response->html($this->taskLayout('task_close', array(
+ if ($this->request->isAjax()) {
+ $this->response->html($this->template->render('task/close', array(
+ 'task' => $task,
+ 'redirect' => $redirect,
+ )));
+ }
+
+ $this->response->html($this->taskLayout('task/close', array(
'task' => $task,
- 'menu' => 'tasks',
- 'title' => t('Close a task')
+ 'redirect' => $redirect,
)));
}
@@ -304,19 +320,17 @@ class Task extends Base
$this->checkCSRFParam();
- if ($this->task->open($task['id'])) {
+ if ($this->taskStatus->open($task['id'])) {
$this->session->flash(t('Task opened successfully.'));
} else {
$this->session->flashError(t('Unable to open this task.'));
}
- $this->response->redirect('?controller=task&action=show&task_id='.$task['id']);
+ $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id']);
}
- $this->response->html($this->taskLayout('task_open', array(
+ $this->response->html($this->taskLayout('task/open', array(
'task' => $task,
- 'menu' => 'tasks',
- 'title' => t('Open a task')
)));
}
@@ -346,10 +360,8 @@ class Task extends Base
$this->response->redirect('?controller=board&action=show&project_id='.$task['project_id']);
}
- $this->response->html($this->taskLayout('task_remove', array(
+ $this->response->html($this->taskLayout('task/remove', array(
'task' => $task,
- 'menu' => 'tasks',
- 'title' => t('Remove a task')
)));
}
@@ -365,21 +377,19 @@ class Task extends Base
if ($this->request->getStringParam('confirmation') === 'yes') {
$this->checkCSRFParam();
- $task_id = $this->task->duplicateToSameProject($task);
+ $task_id = $this->taskDuplication->duplicate($task['id']);
if ($task_id) {
$this->session->flash(t('Task created successfully.'));
- $this->response->redirect('?controller=task&action=show&task_id='.$task_id);
+ $this->response->redirect('?controller=task&action=show&task_id='.$task_id.'&project_id='.$task['project_id']);
} else {
$this->session->flashError(t('Unable to create this task.'));
- $this->response->redirect('?controller=task&action=duplicate&task_id='.$task['id']);
+ $this->response->redirect('?controller=task&action=duplicate&task_id='.$task['id'].'&project_id='.$task['project_id']);
}
}
- $this->response->html($this->taskLayout('task_duplicate', array(
+ $this->response->html($this->taskLayout('task/duplicate', array(
'task' => $task,
- 'menu' => 'tasks',
- 'title' => t('Duplicate a task')
)));
}
@@ -401,7 +411,7 @@ class Task extends Base
if ($valid) {
- if ($this->task->update($values)) {
+ if ($this->taskModification->update($values)) {
$this->session->flash(t('Task updated successfully.'));
}
else {
@@ -412,7 +422,7 @@ class Task extends Base
$this->response->redirect('?controller=board&action=show&project_id='.$task['project_id']);
}
else {
- $this->response->redirect('?controller=task&action=show&task_id='.$task['id']);
+ $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id']);
}
}
}
@@ -426,49 +436,123 @@ class Task extends Base
'errors' => $errors,
'task' => $task,
'ajax' => $ajax,
- 'menu' => 'tasks',
- 'title' => t('Edit the description'),
);
if ($ajax) {
- $this->response->html($this->template->load('task_edit_description', $params));
+ $this->response->html($this->template->render('task/edit_description', $params));
}
else {
- $this->response->html($this->taskLayout('task_edit_description', $params));
+ $this->response->html($this->taskLayout('task/edit_description', $params));
}
}
/**
- * Move a task to another project
+ * Edit recurrence form
*
* @access public
*/
- public function move()
+ public function recurrence()
{
- $this->toAnotherProject('move');
+ $task = $this->getTask();
+ $ajax = $this->request->isAjax() || $this->request->getIntegerParam('ajax');
+
+ if ($this->request->isPost()) {
+
+ $values = $this->request->getValues();
+
+ list($valid, $errors) = $this->taskValidator->validateEditRecurrence($values);
+
+ if ($valid) {
+
+ if ($this->taskModification->update($values)) {
+ $this->session->flash(t('Task updated successfully.'));
+ }
+ else {
+ $this->session->flashError(t('Unable to update your task.'));
+ }
+
+ if ($ajax) {
+ $this->response->redirect('?controller=board&action=show&project_id='.$task['project_id']);
+ }
+ else {
+ $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id']);
+ }
+ }
+ }
+ else {
+ $values = $task;
+ $errors = array();
+ }
+
+ $params = array(
+ 'values' => $values,
+ 'errors' => $errors,
+ 'task' => $task,
+ 'ajax' => $ajax,
+ 'recurrence_status_list' => $this->task->getRecurrenceStatusList(),
+ 'recurrence_trigger_list' => $this->task->getRecurrenceTriggerList(),
+ 'recurrence_timeframe_list' => $this->task->getRecurrenceTimeframeList(),
+ 'recurrence_basedate_list' => $this->task->getRecurrenceBasedateList(),
+ );
+
+ if ($ajax) {
+ $this->response->html($this->template->render('task/edit_recurrence', $params));
+ }
+ else {
+ $this->response->html($this->taskLayout('task/edit_recurrence', $params));
+ }
}
/**
- * Duplicate a task to another project
+ * Move a task to another project
*
* @access public
*/
- public function copy()
+ public function move()
{
- $this->toAnotherProject('duplicate');
+ $task = $this->getTask();
+ $values = $task;
+ $errors = array();
+ $projects_list = $this->projectPermission->getActiveMemberProjects($this->userSession->getId());
+
+ unset($projects_list[$task['project_id']]);
+
+ if ($this->request->isPost()) {
+
+ $values = $this->request->getValues();
+ list($valid, $errors) = $this->taskValidator->validateProjectModification($values);
+
+ if ($valid) {
+
+ if ($this->taskDuplication->moveToProject($task['id'], $values['project_id'])) {
+ $this->session->flash(t('Task updated successfully.'));
+ $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$values['project_id']);
+ }
+ else {
+ $this->session->flashError(t('Unable to update your task.'));
+ }
+ }
+ }
+
+ $this->response->html($this->taskLayout('task/move_project', array(
+ 'values' => $values,
+ 'errors' => $errors,
+ 'task' => $task,
+ 'projects_list' => $projects_list,
+ )));
}
/**
- * Common methods between the actions "move" and "copy"
+ * Duplicate a task to another project
*
- * @access private
+ * @access public
*/
- private function toAnotherProject($action)
+ public function copy()
{
$task = $this->getTask();
$values = $task;
$errors = array();
- $projects_list = $this->projectPermission->getAllowedProjects($this->acl->getUserId());
+ $projects_list = $this->projectPermission->getActiveMemberProjects($this->userSession->getId());
unset($projects_list[$task['project_id']]);
@@ -478,10 +562,10 @@ class Task extends Base
list($valid, $errors) = $this->taskValidator->validateProjectModification($values);
if ($valid) {
- $task_id = $this->task->{$action.'ToAnotherProject'}($values['project_id'], $task);
+ $task_id = $this->taskDuplication->duplicateToProject($task['id'], $values['project_id']);
if ($task_id) {
$this->session->flash(t('Task created successfully.'));
- $this->response->redirect('?controller=task&action=show&task_id='.$task_id);
+ $this->response->redirect('?controller=task&action=show&task_id='.$task_id.'&project_id='.$values['project_id']);
}
else {
$this->session->flashError(t('Unable to create your task.'));
@@ -489,13 +573,49 @@ class Task extends Base
}
}
- $this->response->html($this->taskLayout('task_'.$action.'_project', array(
+ $this->response->html($this->taskLayout('task/duplicate_project', array(
'values' => $values,
'errors' => $errors,
'task' => $task,
'projects_list' => $projects_list,
- 'menu' => 'tasks',
- 'title' => t(ucfirst($action).' the task to another project')
+ )));
+ }
+
+ /**
+ * Display the time tracking details
+ *
+ * @access public
+ */
+ public function timesheet()
+ {
+ $task = $this->getTask();
+
+ $subtask_paginator = $this->paginator
+ ->setUrl('task', 'timesheet', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'pagination' => 'subtasks'))
+ ->setMax(15)
+ ->setOrder('start')
+ ->setDirection('DESC')
+ ->setQuery($this->subtaskTimeTracking->getTaskQuery($task['id']))
+ ->calculateOnlyIf($this->request->getStringParam('pagination') === 'subtasks');
+
+ $this->response->html($this->taskLayout('task/time_tracking', array(
+ 'task' => $task,
+ 'subtask_paginator' => $subtask_paginator,
+ )));
+ }
+
+ /**
+ * Display the task transitions
+ *
+ * @access public
+ */
+ public function transitions()
+ {
+ $task = $this->getTask();
+
+ $this->response->html($this->taskLayout('task/transitions', array(
+ 'task' => $task,
+ 'transitions' => $this->transition->getAllByTask($task['id']),
)));
}
}
diff --git a/app/Controller/Tasklink.php b/app/Controller/Tasklink.php
new file mode 100644
index 00000000..dd076802
--- /dev/null
+++ b/app/Controller/Tasklink.php
@@ -0,0 +1,179 @@
+<?php
+
+namespace Controller;
+
+/**
+ * TaskLink controller
+ *
+ * @package controller
+ * @author Olivier Maridat
+ * @author Frederic Guillot
+ */
+class Tasklink extends Base
+{
+ /**
+ * Get the current link
+ *
+ * @access private
+ * @return array
+ */
+ private function getTaskLink()
+ {
+ $link = $this->taskLink->getById($this->request->getIntegerParam('link_id'));
+
+ if (empty($link)) {
+ $this->notfound();
+ }
+
+ return $link;
+ }
+
+ /**
+ * Creation form
+ *
+ * @access public
+ */
+ public function create(array $values = array(), array $errors = array())
+ {
+ $task = $this->getTask();
+ $ajax = $this->request->isAjax() || $this->request->getIntegerParam('ajax');
+
+ if ($ajax && empty($errors)) {
+ $this->response->html($this->template->render('tasklink/create', array(
+ 'values' => $values,
+ 'errors' => $errors,
+ 'task' => $task,
+ 'labels' => $this->link->getList(0, false),
+ 'title' => t('Add a new link'),
+ 'ajax' => $ajax,
+ )));
+ }
+
+ $this->response->html($this->taskLayout('tasklink/create', array(
+ 'values' => $values,
+ 'errors' => $errors,
+ 'task' => $task,
+ 'labels' => $this->link->getList(0, false),
+ 'title' => t('Add a new link')
+ )));
+ }
+
+ /**
+ * Validation and creation
+ *
+ * @access public
+ */
+ public function save()
+ {
+ $task = $this->getTask();
+ $values = $this->request->getValues();
+ $ajax = $this->request->isAjax() || $this->request->getIntegerParam('ajax');
+
+ list($valid, $errors) = $this->taskLink->validateCreation($values);
+
+ if ($valid) {
+
+ if ($this->taskLink->create($values['task_id'], $values['opposite_task_id'], $values['link_id'])) {
+ $this->session->flash(t('Link added successfully.'));
+
+ if ($ajax) {
+ $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $task['project_id'])));
+ }
+
+ $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])).'#links');
+ }
+
+ $errors = array('title' => array(t('The exact same link already exists')));
+ $this->session->flashError(t('Unable to create your link.'));
+ }
+
+ $this->create($values, $errors);
+ }
+
+ /**
+ * Edit form
+ *
+ * @access public
+ */
+ public function edit(array $values = array(), array $errors = array())
+ {
+ $task = $this->getTask();
+ $task_link = $this->getTaskLink();
+
+ if (empty($values)) {
+ $opposite_task = $this->taskFinder->getById($task_link['opposite_task_id']);
+ $values = $task_link;
+ $values['title'] = '#'.$opposite_task['id'].' - '.$opposite_task['title'];
+ }
+
+ $this->response->html($this->taskLayout('tasklink/edit', array(
+ 'values' => $values,
+ 'errors' => $errors,
+ 'task_link' => $task_link,
+ 'task' => $task,
+ 'labels' => $this->link->getList(0, false),
+ 'title' => t('Edit link')
+ )));
+ }
+
+ /**
+ * Validation and update
+ *
+ * @access public
+ */
+ public function update()
+ {
+ $task = $this->getTask();
+ $values = $this->request->getValues();
+
+ list($valid, $errors) = $this->taskLink->validateModification($values);
+
+ if ($valid) {
+
+ if ($this->taskLink->update($values['id'], $values['task_id'], $values['opposite_task_id'], $values['link_id'])) {
+ $this->session->flash(t('Link updated successfully.'));
+ $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])).'#links');
+ }
+
+ $this->session->flashError(t('Unable to update your link.'));
+ }
+
+ $this->edit($values, $errors);
+ }
+
+ /**
+ * Confirmation dialog before removing a link
+ *
+ * @access public
+ */
+ public function confirm()
+ {
+ $task = $this->getTask();
+ $link = $this->getTaskLink();
+
+ $this->response->html($this->taskLayout('tasklink/remove', array(
+ 'link' => $link,
+ 'task' => $task,
+ )));
+ }
+
+ /**
+ * Remove a link
+ *
+ * @access public
+ */
+ public function remove()
+ {
+ $this->checkCSRFParam();
+ $task = $this->getTask();
+
+ if ($this->taskLink->remove($this->request->getIntegerParam('link_id'))) {
+ $this->session->flash(t('Link removed successfully.'));
+ }
+ else {
+ $this->session->flashError(t('Unable to remove this link.'));
+ }
+
+ $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])).'#links');
+ }
+}
diff --git a/app/Controller/Timetable.php b/app/Controller/Timetable.php
new file mode 100644
index 00000000..65edb44c
--- /dev/null
+++ b/app/Controller/Timetable.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Controller;
+
+use DateTime;
+
+/**
+ * Timetable controller
+ *
+ * @package controller
+ * @author Frederic Guillot
+ */
+class Timetable extends User
+{
+ /**
+ * Display timetable for the user
+ *
+ * @access public
+ */
+ public function index()
+ {
+ $user = $this->getUser();
+ $from = $this->request->getStringParam('from', date('Y-m-d'));
+ $to = $this->request->getStringParam('to', date('Y-m-d', strtotime('next week')));
+ $timetable = $this->timetable->calculate($user['id'], new DateTime($from), new DateTime($to));
+
+ $this->response->html($this->layout('timetable/index', array(
+ 'user' => $user,
+ 'timetable' => $timetable,
+ 'values' => array(
+ 'from' => $from,
+ 'to' => $to,
+ 'controller' => 'timetable',
+ 'action' => 'index',
+ 'user_id' => $user['id'],
+ ),
+ )));
+ }
+}
diff --git a/app/Controller/Timetableday.php b/app/Controller/Timetableday.php
new file mode 100644
index 00000000..c8f7ac8a
--- /dev/null
+++ b/app/Controller/Timetableday.php
@@ -0,0 +1,88 @@
+<?php
+
+namespace Controller;
+
+/**
+ * Day Timetable controller
+ *
+ * @package controller
+ * @author Frederic Guillot
+ */
+class Timetableday extends User
+{
+ /**
+ * Display timetable for the user
+ *
+ * @access public
+ */
+ public function index(array $values = array(), array $errors = array())
+ {
+ $user = $this->getUser();
+
+ $this->response->html($this->layout('timetable_day/index', array(
+ 'timetable' => $this->timetableDay->getByUser($user['id']),
+ 'values' => $values + array('user_id' => $user['id']),
+ 'errors' => $errors,
+ 'user' => $user,
+ )));
+ }
+
+ /**
+ * Validate and save
+ *
+ * @access public
+ */
+ public function save()
+ {
+ $values = $this->request->getValues();
+ list($valid, $errors) = $this->timetableDay->validateCreation($values);
+
+ if ($valid) {
+
+ if ($this->timetableDay->create($values['user_id'], $values['start'], $values['end'])) {
+ $this->session->flash(t('Time slot created successfully.'));
+ $this->response->redirect($this->helper->url->to('timetableday', 'index', array('user_id' => $values['user_id'])));
+ }
+ else {
+ $this->session->flashError(t('Unable to save this time slot.'));
+ }
+ }
+
+ $this->index($values, $errors);
+ }
+
+ /**
+ * Confirmation dialag box to remove a row
+ *
+ * @access public
+ */
+ public function confirm()
+ {
+ $user = $this->getUser();
+
+ $this->response->html($this->layout('timetable_day/remove', array(
+ 'slot_id' => $this->request->getIntegerParam('slot_id'),
+ 'user' => $user,
+ )));
+ }
+
+ /**
+ * Remove a row
+ *
+ * @access public
+ */
+ public function remove()
+ {
+ $this->checkCSRFParam();
+ $user = $this->getUser();
+
+ if ($this->timetableDay->remove($this->request->getIntegerParam('slot_id'))) {
+ $this->session->flash(t('Time slot removed successfully.'));
+ }
+ else {
+ $this->session->flash(t('Unable to remove this time slot.'));
+ }
+
+ $this->response->redirect($this->helper->url->to('timetableday', 'index', array('user_id' => $user['id'])));
+ }
+}
diff --git a/app/Controller/Timetableextra.php b/app/Controller/Timetableextra.php
new file mode 100644
index 00000000..7c6fe265
--- /dev/null
+++ b/app/Controller/Timetableextra.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Controller;
+
+/**
+ * Over-time Timetable controller
+ *
+ * @package controller
+ * @author Frederic Guillot
+ */
+class Timetableextra extends Timetableoff
+{
+ protected $model = 'timetableExtra';
+ protected $controller_url = 'timetableextra';
+ protected $template_dir = 'timetable_extra';
+}
diff --git a/app/Controller/Timetableoff.php b/app/Controller/Timetableoff.php
new file mode 100644
index 00000000..585014a3
--- /dev/null
+++ b/app/Controller/Timetableoff.php
@@ -0,0 +1,107 @@
+<?php
+
+namespace Controller;
+
+/**
+ * Time-off Timetable controller
+ *
+ * @package controller
+ * @author Frederic Guillot
+ */
+class Timetableoff extends User
+{
+ protected $model = 'timetableOff';
+ protected $controller_url = 'timetableoff';
+ protected $template_dir = 'timetable_off';
+
+ /**
+ * Display timetable for the user
+ *
+ * @access public
+ */
+ public function index(array $values = array(), array $errors = array())
+ {
+ $user = $this->getUser();
+
+ $paginator = $this->paginator
+ ->setUrl($this->controller_url, 'index', array('user_id' => $user['id']))
+ ->setMax(10)
+ ->setOrder('date')
+ ->setDirection('desc')
+ ->setQuery($this->{$this->model}->getUserQuery($user['id']))
+ ->calculate();
+
+ $this->response->html($this->layout($this->template_dir.'/index', array(
+ 'values' => $values + array('user_id' => $user['id']),
+ 'errors' => $errors,
+ 'paginator' => $paginator,
+ 'user' => $user,
+ )));
+ }
+
+ /**
+ * Validate and save
+ *
+ * @access public
+ */
+ public function save()
+ {
+ $values = $this->request->getValues();
+ list($valid, $errors) = $this->{$this->model}->validateCreation($values);
+
+ if ($valid) {
+
+ if ($this->{$this->model}->create(
+ $values['user_id'],
+ $values['date'],
+ isset($values['all_day']) && $values['all_day'] == 1,
+ $values['start'],
+ $values['end'],
+ $values['comment'])) {
+
+ $this->session->flash(t('Time slot created successfully.'));
+ $this->response->redirect($this->helper->url->to($this->controller_url, 'index', array('user_id' => $values['user_id'])));
+ }
+ else {
+ $this->session->flashError(t('Unable to save this time slot.'));
+ }
+ }
+
+ $this->index($values, $errors);
+ }
+
+ /**
+ * Confirmation dialag box to remove a row
+ *
+ * @access public
+ */
+ public function confirm()
+ {
+ $user = $this->getUser();
+
+ $this->response->html($this->layout($this->template_dir.'/remove', array(
+ 'slot_id' => $this->request->getIntegerParam('slot_id'),
+ 'user' => $user,
+ )));
+ }
+
+ /**
+ * Remove a row
+ *
+ * @access public
+ */
+ public function remove()
+ {
+ $this->checkCSRFParam();
+ $user = $this->getUser();
+
+ if ($this->{$this->model}->remove($this->request->getIntegerParam('slot_id'))) {
+ $this->session->flash(t('Time slot removed successfully.'));
+ }
+ else {
+ $this->session->flash(t('Unable to remove this time slot.'));
+ }
+
+ $this->response->redirect($this->helper->url->to($this->controller_url, 'index', array('user_id' => $user['id'])));
+ }
+}
diff --git a/app/Controller/Timetableweek.php b/app/Controller/Timetableweek.php
new file mode 100644
index 00000000..b8ce00e7
--- /dev/null
+++ b/app/Controller/Timetableweek.php
@@ -0,0 +1,99 @@
+<?php
+
+namespace Controller;
+
+/**
+ * Week Timetable controller
+ *
+ * @package controller
+ * @author Frederic Guillot
+ */
+class Timetableweek extends User
+{
+ /**
+ * Display timetable for the user
+ *
+ * @access public
+ */
+ public function index(array $values = array(), array $errors = array())
+ {
+ $user = $this->getUser();
+
+ if (empty($values)) {
+
+ $day = $this->timetableDay->getByUser($user['id']);
+
+ $values = array(
+ 'user_id' => $user['id'],
+ 'start' => isset($day[0]['start']) ? $day[0]['start'] : null,
+ 'end' => isset($day[0]['end']) ? $day[0]['end'] : null,
+ );
+ }
+
+ $this->response->html($this->layout('timetable_week/index', array(
+ 'timetable' => $this->timetableWeek->getByUser($user['id']),
+ 'values' => $values,
+ 'errors' => $errors,
+ 'user' => $user,
+ )));
+ }
+
+ /**
+ * Validate and save
+ *
+ * @access public
+ */
+ public function save()
+ {
+ $values = $this->request->getValues();
+ list($valid, $errors) = $this->timetableWeek->validateCreation($values);
+
+ if ($valid) {
+
+ if ($this->timetableWeek->create($values['user_id'], $values['day'], $values['start'], $values['end'])) {
+ $this->session->flash(t('Time slot created successfully.'));
+ $this->response->redirect($this->helper->url->to('timetableweek', 'index', array('user_id' => $values['user_id'])));
+ }
+ else {
+ $this->session->flashError(t('Unable to save this time slot.'));
+ }
+ }
+
+ $this->index($values, $errors);
+ }
+
+ /**
+ * Confirmation dialag box to remove a row
+ *
+ * @access public
+ */
+ public function confirm()
+ {
+ $user = $this->getUser();
+
+ $this->response->html($this->layout('timetable_week/remove', array(
+ 'slot_id' => $this->request->getIntegerParam('slot_id'),
+ 'user' => $user,
+ )));
+ }
+
+ /**
+ * Remove a row
+ *
+ * @access public
+ */
+ public function remove()
+ {
+ $this->checkCSRFParam();
+ $user = $this->getUser();
+
+ if ($this->timetableWeek->remove($this->request->getIntegerParam('slot_id'))) {
+ $this->session->flash(t('Time slot removed successfully.'));
+ }
+ else {
+ $this->session->flash(t('Unable to remove this time slot.'));
+ }
+
+ $this->response->redirect($this->helper->url->to('timetableweek', 'index', array('user_id' => $user['id'])));
+ }
+}
diff --git a/app/Controller/Twofactor.php b/app/Controller/Twofactor.php
new file mode 100644
index 00000000..a8b0351f
--- /dev/null
+++ b/app/Controller/Twofactor.php
@@ -0,0 +1,167 @@
+<?php
+
+namespace Controller;
+
+use Otp\Otp;
+use Otp\GoogleAuthenticator;
+use Base32\Base32;
+
+/**
+ * Two Factor Auth controller
+ *
+ * @package controller
+ * @author Frederic Guillot
+ */
+class Twofactor extends User
+{
+ /**
+ * Only the current user can access to 2FA settings
+ *
+ * @access private
+ */
+ private function checkCurrentUser(array $user)
+ {
+ if ($user['id'] != $this->userSession->getId()) {
+ $this->forbidden();
+ }
+ }
+
+ /**
+ * Index
+ *
+ * @access public
+ */
+ public function index()
+ {
+ $user = $this->getUser();
+ $this->checkCurrentUser($user);
+
+ $label = $user['email'] ?: $user['username'];
+
+ $this->response->html($this->layout('twofactor/index', array(
+ 'user' => $user,
+ 'qrcode_url' => $user['twofactor_activated'] == 1 ? GoogleAuthenticator::getQrCodeUrl('totp', $label, $user['twofactor_secret']) : '',
+ 'key_url' => $user['twofactor_activated'] == 1 ? GoogleAuthenticator::getKeyUri('totp', $label, $user['twofactor_secret']) : '',
+ )));
+ }
+
+ /**
+ * Enable/disable 2FA
+ *
+ * @access public
+ */
+ public function save()
+ {
+ $user = $this->getUser();
+ $this->checkCurrentUser($user);
+
+ $values = $this->request->getValues();
+
+ if (isset($values['twofactor_activated']) && $values['twofactor_activated'] == 1) {
+ $this->user->update(array(
+ 'id' => $user['id'],
+ 'twofactor_activated' => 1,
+ 'twofactor_secret' => GoogleAuthenticator::generateRandom(),
+ ));
+ }
+ else {
+ $this->user->update(array(
+ 'id' => $user['id'],
+ 'twofactor_activated' => 0,
+ 'twofactor_secret' => '',
+ ));
+ }
+
+ // Allow the user to test or disable the feature
+ $_SESSION['user']['twofactor_activated'] = false;
+
+ $this->session->flash(t('User updated successfully.'));
+ $this->response->redirect($this->helper->url->to('twofactor', 'index', array('user_id' => $user['id'])));
+ }
+
+ /**
+ * Test 2FA
+ *
+ * @access public
+ */
+ public function test()
+ {
+ $user = $this->getUser();
+ $this->checkCurrentUser($user);
+
+ $otp = new Otp;
+ $values = $this->request->getValues();
+
+ if (! empty($values['code']) && $otp->checkTotp(Base32::decode($user['twofactor_secret']), $values['code'])) {
+ $this->session->flash(t('The two factor authentication code is valid.'));
+ }
+ else {
+ $this->session->flashError(t('The two factor authentication code is not valid.'));
+ }
+
+ $this->response->redirect($this->helper->url->to('twofactor', 'index', array('user_id' => $user['id'])));
+ }
+
+ /**
+ * Check 2FA
+ *
+ * @access public
+ */
+ public function check()
+ {
+ $user = $this->getUser();
+ $this->checkCurrentUser($user);
+
+ $otp = new Otp;
+ $values = $this->request->getValues();
+
+ if (! empty($values['code']) && $otp->checkTotp(Base32::decode($user['twofactor_secret']), $values['code'])) {
+ $this->session['2fa_validated'] = true;
+ $this->session->flash(t('The two factor authentication code is valid.'));
+ $this->response->redirect($this->helper->url->to('app', 'index'));
+ }
+ else {
+ $this->session->flashError(t('The two factor authentication code is not valid.'));
+ $this->response->redirect($this->helper->url->to('twofactor', 'code'));
+ }
+ }
+
+ /**
+ * Ask the 2FA code
+ *
+ * @access public
+ */
+ public function code()
+ {
+ $this->response->html($this->template->layout('twofactor/check', array(
+ 'title' => t('Check two factor authentication code'),
+ )));
+ }
+
+ /**
+ * Disable 2FA for a user
+ *
+ * @access public
+ */
+ public function disable()
+ {
+ $user = $this->getUser();
+
+ if ($this->request->getStringParam('disable') === 'yes') {
+
+ $this->checkCSRFParam();
+
+ $this->user->update(array(
+ 'id' => $user['id'],
+ 'twofactor_activated' => 0,
+ 'twofactor_secret' => '',
+ ));
+
+ $this->response->redirect($this->helper->url->to('user', 'show', array('user_id' => $user['id'])));
+ }
+
+ $this->response->html($this->layout('twofactor/disable', array(
+ 'user' => $user,
+ )));
+ }
+}
diff --git a/app/Controller/User.php b/app/Controller/User.php
index 834b2379..4cea06b1 100644
--- a/app/Controller/User.php
+++ b/app/Controller/User.php
@@ -11,103 +11,41 @@ namespace Controller;
class User extends Base
{
/**
- * Logout and destroy session
- *
- * @access public
- */
- public function logout()
- {
- $this->checkCSRFParam();
- $this->authentication->backend('rememberMe')->destroy($this->acl->getUserId());
- $this->session->close();
- $this->response->redirect('?controller=user&action=login');
- }
-
- /**
- * Display the form login
- *
- * @access public
- */
- public function login()
- {
- if ($this->acl->isLogged()) {
- $this->response->redirect('?controller=app');
- }
-
- $this->response->html($this->template->layout('user_login', array(
- 'errors' => array(),
- 'values' => array(),
- 'no_layout' => true,
- 'redirect_query' => $this->request->getStringParam('redirect_query'),
- 'title' => t('Login')
- )));
- }
-
- /**
- * Check credentials
- *
- * @access public
- */
- public function check()
- {
- $redirect_query = $this->request->getStringParam('redirect_query');
- $values = $this->request->getValues();
- list($valid, $errors) = $this->authentication->validateForm($values);
-
- if ($valid) {
- if ($redirect_query !== '') {
- $this->response->redirect('?'.$redirect_query);
- }
- else {
- $this->response->redirect('?controller=app');
- }
- }
-
- $this->response->html($this->template->layout('user_login', array(
- 'errors' => $errors,
- 'values' => $values,
- 'no_layout' => true,
- 'redirect_query' => $redirect_query,
- 'title' => t('Login')
- )));
- }
-
- /**
* Common layout for user views
*
- * @access private
+ * @access protected
* @param string $template Template name
* @param array $params Template parameters
* @return string
*/
- private function layout($template, array $params)
+ protected function layout($template, array $params)
{
- $content = $this->template->load($template, $params);
+ $content = $this->template->render($template, $params);
$params['user_content_for_layout'] = $content;
- $params['menu'] = 'users';
+ $params['board_selector'] = $this->projectPermission->getAllowedProjects($this->userSession->getId());
if (isset($params['user'])) {
- $params['title'] = $params['user']['name'] ?: $params['user']['username'];
+ $params['title'] = ($params['user']['name'] ?: $params['user']['username']).' (#'.$params['user']['id'].')';
}
- return $this->template->layout('user_layout', $params);
+ return $this->template->layout('user/layout', $params);
}
/**
* Common method to get the user
*
- * @access private
+ * @access protected
* @return array
*/
- private function getUser()
+ protected function getUser()
{
$user = $this->user->getById($this->request->getIntegerParam('user_id'));
- if (! $user) {
+ if (empty($user)) {
$this->notfound();
}
- if ($this->acl->isRegularUser() && $this->acl->getUserId() != $user['id']) {
+ if (! $this->userSession->isAdmin() && $this->userSession->getId() != $user['id']) {
$this->forbidden();
}
@@ -121,16 +59,19 @@ class User extends Base
*/
public function index()
{
- $users = $this->user->getAll();
- $nb_users = count($users);
+ $paginator = $this->paginator
+ ->setUrl('user', 'index')
+ ->setMax(30)
+ ->setOrder('username')
+ ->setQuery($this->user->getQuery())
+ ->calculate();
$this->response->html(
- $this->template->layout('user_index', array(
+ $this->template->layout('user/index', array(
+ 'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
'projects' => $this->project->getList(),
- 'users' => $users,
- 'nb_users' => $nb_users,
- 'menu' => 'users',
- 'title' => t('Users').' ('.$nb_users.')'
+ 'title' => t('Users').' ('.$paginator->getTotal().')',
+ 'paginator' => $paginator,
)));
}
@@ -139,13 +80,15 @@ class User extends Base
*
* @access public
*/
- public function create()
+ public function create(array $values = array(), array $errors = array())
{
- $this->response->html($this->template->layout('user_new', array(
+ $this->response->html($this->template->layout('user/new', array(
+ 'timezones' => $this->config->getTimezones(true),
+ 'languages' => $this->config->getLanguages(true),
+ 'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
'projects' => $this->project->getList(),
- 'errors' => array(),
- 'values' => array(),
- 'menu' => 'users',
+ 'errors' => $errors,
+ 'values' => $values,
'title' => t('New user')
)));
}
@@ -162,22 +105,18 @@ class User extends Base
if ($valid) {
- if ($this->user->create($values)) {
+ $user_id = $this->user->create($values);
+
+ if ($user_id !== false) {
$this->session->flash(t('User created successfully.'));
- $this->response->redirect('?controller=user');
+ $this->response->redirect($this->helper->url->to('user', 'show', array('user_id' => $user_id)));
}
else {
$this->session->flashError(t('Unable to create your user.'));
}
}
- $this->response->html($this->template->layout('user_new', array(
- 'projects' => $this->project->getList(),
- 'errors' => $errors,
- 'values' => $values,
- 'menu' => 'users',
- 'title' => t('New user')
- )));
+ $this->create($values, $errors);
}
/**
@@ -188,9 +127,48 @@ class User extends Base
public function show()
{
$user = $this->getUser();
- $this->response->html($this->layout('user_show', array(
+ $this->response->html($this->layout('user/show', array(
'projects' => $this->projectPermission->getAllowedProjects($user['id']),
'user' => $user,
+ 'timezones' => $this->config->getTimezones(true),
+ 'languages' => $this->config->getLanguages(true),
+ )));
+ }
+
+ /**
+ * Display user calendar
+ *
+ * @access public
+ */
+ public function calendar()
+ {
+ $user = $this->getUser();
+
+ $this->response->html($this->layout('user/calendar', array(
+ 'user' => $user,
+ )));
+ }
+
+ /**
+ * Display timesheet
+ *
+ * @access public
+ */
+ public function timesheet()
+ {
+ $user = $this->getUser();
+
+ $subtask_paginator = $this->paginator
+ ->setUrl('user', 'timesheet', array('user_id' => $user['id'], 'pagination' => 'subtasks'))
+ ->setMax(20)
+ ->setOrder('start')
+ ->setDirection('DESC')
+ ->setQuery($this->subtaskTimeTracking->getUserQuery($user['id']))
+ ->calculateOnlyIf($this->request->getStringParam('pagination') === 'subtasks');
+
+ $this->response->html($this->layout('user/timesheet', array(
+ 'subtask_paginator' => $subtask_paginator,
+ 'user' => $user,
)));
}
@@ -202,7 +180,7 @@ class User extends Base
public function last()
{
$user = $this->getUser();
- $this->response->html($this->layout('user_last', array(
+ $this->response->html($this->layout('user/last', array(
'last_logins' => $this->lastLogin->getAll($user['id']),
'user' => $user,
)));
@@ -216,7 +194,7 @@ class User extends Base
public function sessions()
{
$user = $this->getUser();
- $this->response->html($this->layout('user_sessions', array(
+ $this->response->html($this->layout('user/sessions', array(
'sessions' => $this->authentication->backend('rememberMe')->getAll($user['id']),
'user' => $user,
)));
@@ -251,7 +229,7 @@ class User extends Base
$this->response->redirect('?controller=user&action=notifications&user_id='.$user['id']);
}
- $this->response->html($this->layout('user_notifications', array(
+ $this->response->html($this->layout('user/notifications', array(
'projects' => $this->projectPermission->getAllowedProjects($user['id']),
'notifications' => $this->notification->readSettings($user['id']),
'user' => $user,
@@ -266,13 +244,42 @@ class User extends Base
public function external()
{
$user = $this->getUser();
- $this->response->html($this->layout('user_external', array(
+ $this->response->html($this->layout('user/external', array(
'last_logins' => $this->lastLogin->getAll($user['id']),
'user' => $user,
)));
}
/**
+ * Public access management
+ *
+ * @access public
+ */
+ public function share()
+ {
+ $user = $this->getUser();
+ $switch = $this->request->getStringParam('switch');
+
+ if ($switch === 'enable' || $switch === 'disable') {
+
+ $this->checkCSRFParam();
+
+ if ($this->user->{$switch.'PublicAccess'}($user['id'])) {
+ $this->session->flash(t('User updated successfully.'));
+ } else {
+ $this->session->flashError(t('Unable to update this user.'));
+ }
+
+ $this->response->redirect($this->helper->url->to('user', 'share', array('user_id' => $user['id'])));
+ }
+
+ $this->response->html($this->layout('user/share', array(
+ 'user' => $user,
+ 'title' => t('Public access'),
+ )));
+ }
+
+ /**
* Password modification
*
* @access public
@@ -301,7 +308,7 @@ class User extends Base
}
}
- $this->response->html($this->layout('user_password', array(
+ $this->response->html($this->layout('user/password', array(
'values' => $values,
'errors' => $errors,
'user' => $user,
@@ -323,9 +330,9 @@ class User extends Base
if ($this->request->isPost()) {
- $values = $this->request->getValues();
+ $values = $this->request->getValues() + array('disable_login_form' => 0);
- if ($this->acl->isAdminUser()) {
+ if ($this->userSession->isAdmin()) {
$values += array('is_admin' => 0);
}
else {
@@ -350,11 +357,13 @@ class User extends Base
}
}
- $this->response->html($this->layout('user_edit', array(
+ $this->response->html($this->layout('user/edit', array(
'values' => $values,
'errors' => $errors,
'projects' => $this->projectPermission->filterProjects($this->project->getList(), $user['id']),
'user' => $user,
+ 'timezones' => $this->config->getTimezones(true),
+ 'languages' => $this->config->getLanguages(true),
)));
}
@@ -380,7 +389,7 @@ class User extends Base
$this->response->redirect('?controller=user');
}
- $this->response->html($this->layout('user_remove', array(
+ $this->response->html($this->layout('user/remove', array(
'user' => $user,
)));
}
@@ -401,22 +410,22 @@ class User extends Base
if (is_array($profile)) {
// If the user is already logged, link the account otherwise authenticate
- if ($this->acl->isLogged()) {
+ if ($this->userSession->isLogged()) {
- if ($this->authentication->backend('google')->updateUser($this->acl->getUserId(), $profile)) {
+ if ($this->authentication->backend('google')->updateUser($this->userSession->getId(), $profile)) {
$this->session->flash(t('Your Google Account is linked to your profile successfully.'));
}
else {
$this->session->flashError(t('Unable to link your Google Account.'));
}
- $this->response->redirect('?controller=user&action=external&user_id='.$this->acl->getUserId());
+ $this->response->redirect('?controller=user&action=external&user_id='.$this->userSession->getId());
}
else if ($this->authentication->backend('google')->authenticate($profile['id'])) {
$this->response->redirect('?controller=app');
}
else {
- $this->response->html($this->template->layout('user_login', array(
+ $this->response->html($this->template->layout('auth/index', array(
'errors' => array('login' => t('Google authentication failed')),
'values' => array(),
'no_layout' => true,
@@ -438,14 +447,14 @@ class User extends Base
public function unlinkGoogle()
{
$this->checkCSRFParam();
- if ($this->authentication->backend('google')->unlink($this->acl->getUserId())) {
+ if ($this->authentication->backend('google')->unlink($this->userSession->getId())) {
$this->session->flash(t('Your Google Account is not linked anymore to your profile.'));
}
else {
$this->session->flashError(t('Unable to unlink your Google Account.'));
}
- $this->response->redirect('?controller=user&action=external&user_id='.$this->acl->getUserId());
+ $this->response->redirect('?controller=user&action=external&user_id='.$this->userSession->getId());
}
/**
@@ -453,7 +462,7 @@ class User extends Base
*
* @access public
*/
- public function gitHub()
+ public function github()
{
$code = $this->request->getStringParam('code');
@@ -463,22 +472,22 @@ class User extends Base
if (is_array($profile)) {
// If the user is already logged, link the account otherwise authenticate
- if ($this->acl->isLogged()) {
+ if ($this->userSession->isLogged()) {
- if ($this->authentication->backend('gitHub')->updateUser($this->acl->getUserId(), $profile)) {
+ if ($this->authentication->backend('gitHub')->updateUser($this->userSession->getId(), $profile)) {
$this->session->flash(t('Your GitHub account was successfully linked to your profile.'));
}
else {
$this->session->flashError(t('Unable to link your GitHub Account.'));
}
- $this->response->redirect('?controller=user&action=external&user_id='.$this->acl->getUserId());
+ $this->response->redirect('?controller=user&action=external&user_id='.$this->userSession->getId());
}
else if ($this->authentication->backend('gitHub')->authenticate($profile['id'])) {
$this->response->redirect('?controller=app');
}
else {
- $this->response->html($this->template->layout('user_login', array(
+ $this->response->html($this->template->layout('auth/index', array(
'errors' => array('login' => t('GitHub authentication failed')),
'values' => array(),
'no_layout' => true,
@@ -497,19 +506,19 @@ class User extends Base
*
* @access public
*/
- public function unlinkGitHub()
+ public function unlinkGithub()
{
$this->checkCSRFParam();
$this->authentication->backend('gitHub')->revokeGitHubAccess();
- if ($this->authentication->backend('gitHub')->unlink($this->acl->getUserId())) {
+ if ($this->authentication->backend('gitHub')->unlink($this->userSession->getId())) {
$this->session->flash(t('Your GitHub account is no longer linked to your profile.'));
}
else {
$this->session->flashError(t('Unable to unlink your GitHub Account.'));
}
- $this->response->redirect('?controller=user&action=external&user_id='.$this->acl->getUserId());
+ $this->response->redirect('?controller=user&action=external&user_id='.$this->userSession->getId());
}
}
diff --git a/app/Controller/Webhook.php b/app/Controller/Webhook.php
index 71acab08..10a24e47 100644
--- a/app/Controller/Webhook.php
+++ b/app/Controller/Webhook.php
@@ -35,7 +35,7 @@ class Webhook extends Base
list($valid,) = $this->taskValidator->validateCreation($values);
- if ($valid && $this->task->create($values)) {
+ if ($valid && $this->taskCreation->create($values)) {
$this->response->text('OK');
}
@@ -55,9 +55,91 @@ class Webhook extends Base
$this->githubWebhook->setProjectId($this->request->getIntegerParam('project_id'));
- $this->githubWebhook->parsePayload(
+ $result = $this->githubWebhook->parsePayload(
$this->request->getHeader('X-Github-Event'),
- $this->request->getBody()
+ $this->request->getJson() ?: array()
);
+
+ echo $result ? 'PARSED' : 'IGNORED';
+ }
+
+ /**
+ * Handle Gitlab webhooks
+ *
+ * @access public
+ */
+ public function gitlab()
+ {
+ if ($this->config->get('webhook_token') !== $this->request->getStringParam('token')) {
+ $this->response->text('Not Authorized', 401);
+ }
+
+ $this->gitlabWebhook->setProjectId($this->request->getIntegerParam('project_id'));
+
+ $result = $this->gitlabWebhook->parsePayload(
+ $this->request->getJson() ?: array()
+ );
+
+ echo $result ? 'PARSED' : 'IGNORED';
+ }
+
+ /**
+ * Handle Bitbucket webhooks
+ *
+ * @access public
+ */
+ public function bitbucket()
+ {
+ if ($this->config->get('webhook_token') !== $this->request->getStringParam('token')) {
+ $this->response->text('Not Authorized', 401);
+ }
+
+ $this->bitbucketWebhook->setProjectId($this->request->getIntegerParam('project_id'));
+
+ $result = $this->bitbucketWebhook->parsePayload(json_decode(@$_POST['payload'], true) ?: array());
+
+ echo $result ? 'PARSED' : 'IGNORED';
+ }
+
+ /**
+ * Handle Postmark webhooks
+ *
+ * @access public
+ */
+ public function postmark()
+ {
+ if ($this->config->get('webhook_token') !== $this->request->getStringParam('token')) {
+ $this->response->text('Not Authorized', 401);
+ }
+
+ echo $this->postmark->receiveEmail($this->request->getJson() ?: array()) ? 'PARSED' : 'IGNORED';
+ }
+
+ /**
+ * Handle Mailgun webhooks
+ *
+ * @access public
+ */
+ public function mailgun()
+ {
+ if ($this->config->get('webhook_token') !== $this->request->getStringParam('token')) {
+ $this->response->text('Not Authorized', 401);
+ }
+
+ echo $this->mailgun->receiveEmail($_POST) ? 'PARSED' : 'IGNORED';
+ }
+
+ /**
+ * Handle Sendgrid webhooks
+ *
+ * @access public
+ */
+ public function sendgrid()
+ {
+ if ($this->config->get('webhook_token') !== $this->request->getStringParam('token')) {
+ $this->response->text('Not Authorized', 401);
+ }
+
+ echo $this->sendgridWebhook->parsePayload($_POST) ? 'PARSED' : 'IGNORED';
}
}