summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/Controller/App.php179
-rw-r--r--app/Controller/Base.php6
-rw-r--r--app/Controller/Calendar.php79
-rw-r--r--app/Controller/Project.php63
-rw-r--r--app/Controller/User.php28
-rw-r--r--app/Core/Helper.php136
-rw-r--r--app/Core/Paginator.php461
-rw-r--r--app/Core/Response.php17
-rw-r--r--app/Integration/GitlabWebhook.php6
-rw-r--r--app/Libs/password.php227
-rw-r--r--app/Locale/da_DK/translations.php49
-rw-r--r--app/Locale/de_DE/translations.php51
-rw-r--r--app/Locale/es_ES/translations.php49
-rw-r--r--app/Locale/fi_FI/translations.php49
-rw-r--r--app/Locale/fr_FR/translations.php49
-rw-r--r--app/Locale/hu_HU/translations.php49
-rw-r--r--app/Locale/it_IT/translations.php49
-rw-r--r--app/Locale/ja_JP/translations.php49
-rw-r--r--app/Locale/pl_PL/translations.php49
-rw-r--r--app/Locale/pt_BR/translations.php49
-rw-r--r--app/Locale/ru_RU/translations.php49
-rw-r--r--app/Locale/sv_SE/translations.php49
-rw-r--r--app/Locale/th_TH/translations.php49
-rw-r--r--app/Locale/zh_CN/translations.php49
-rw-r--r--app/Model/Acl.php1
-rw-r--r--app/Model/Base.php1
-rw-r--r--app/Model/Board.php23
-rw-r--r--app/Model/Color.php105
-rw-r--r--app/Model/DateParser.php12
-rw-r--r--app/Model/Project.php58
-rw-r--r--app/Model/ProjectPaginator.php50
-rw-r--r--app/Model/SubTask.php74
-rw-r--r--app/Model/SubtaskExport.php6
-rw-r--r--app/Model/SubtaskPaginator.php68
-rw-r--r--app/Model/Swimlane.php18
-rw-r--r--app/Model/TaskExport.php2
-rw-r--r--app/Model/TaskFilter.php117
-rw-r--r--app/Model/TaskFinder.php60
-rw-r--r--app/Model/TaskPaginator.php138
-rw-r--r--app/Model/TaskStatus.php17
-rw-r--r--app/Model/User.php73
-rw-r--r--app/ServiceProvider/ClassProvider.php11
-rw-r--r--app/Subscriber/Base.php4
-rw-r--r--app/Template/app/dashboard.php6
-rw-r--r--app/Template/app/projects.php12
-rw-r--r--app/Template/app/subtasks.php16
-rw-r--r--app/Template/app/tasks.php16
-rw-r--r--app/Template/board/files.php2
-rw-r--r--app/Template/board/filters.php10
-rw-r--r--app/Template/board/task.php4
-rw-r--r--app/Template/calendar/show.php44
-rw-r--r--app/Template/calendar/sidebar.php40
-rw-r--r--app/Template/layout.php1
-rw-r--r--app/Template/project/activity.php19
-rw-r--r--app/Template/project/layout.php10
-rw-r--r--app/Template/project/search.php26
-rw-r--r--app/Template/project/tasks.php24
-rw-r--r--app/Template/task/details.php2
-rw-r--r--app/Template/task/layout.php9
-rw-r--r--app/Template/task/table.php24
-rw-r--r--app/Template/user/index.php22
-rw-r--r--app/check_setup.php4
62 files changed, 2318 insertions, 701 deletions
diff --git a/app/Controller/App.php b/app/Controller/App.php
index aa2673a1..cf8d606f 100644
--- a/app/Controller/App.php
+++ b/app/Controller/App.php
@@ -29,157 +29,44 @@ class App extends Base
*/
public function index()
{
- $paginate = $this->request->getStringParam('paginate', 'userTasks');
- $offset = $this->request->getIntegerParam('offset', 0);
- $direction = $this->request->getStringParam('direction');
- $order = $this->request->getStringParam('order');
-
+ $status = array(SubTaskModel::STATUS_TODO, SubTaskModel::STATUS_INPROGRESS);
$user_id = $this->userSession->getId();
$projects = $this->projectPermission->getMemberProjects($user_id);
$project_ids = array_keys($projects);
- $params = array(
+ $task_paginator = $this->paginator
+ ->setUrl('app', 'index', array('pagination' => 'tasks'))
+ ->setMax(10)
+ ->setOrder('tasks.id')
+ ->setQuery($this->taskFinder->getUserQuery($user_id))
+ ->calculateOnlyIf($this->request->getStringParam('pagination') === 'tasks');
+
+ $subtask_paginator = $this->paginator
+ ->setUrl('app', 'index', array('pagination' => 'subtasks'))
+ ->setMax(10)
+ ->setOrder('tasks.id')
+ ->setQuery($this->subTask->getUserQuery($user_id, $status))
+ ->calculateOnlyIf($this->request->getStringParam('pagination') === 'subtasks');
+
+ $project_paginator = $this->paginator
+ ->setUrl('app', 'index', array('pagination' => 'projects'))
+ ->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, 10),
- );
-
- $params += $this->getTaskPagination($user_id, $paginate, $offset, $order, $direction);
- $params += $this->getSubtaskPagination($user_id, $paginate, $offset, $order, $direction);
- $params += $this->getProjectPagination($project_ids, $paginate, $offset, $order, $direction);
-
- $this->response->html($this->template->layout('app/dashboard', $params));
- }
-
- /**
- * Get tasks pagination
- *
- * @access public
- * @param integer $user_id
- * @param string $paginate
- * @param integer $offset
- * @param string $order
- * @param string $direction
- */
- private function getTaskPagination($user_id, $paginate, $offset, $order, $direction)
- {
- $limit = 10;
-
- if (! in_array($order, array('tasks.id', 'project_name', 'title', 'date_due'))) {
- $order = 'tasks.id';
- $direction = 'ASC';
- }
-
- if ($paginate === 'userTasks') {
- $tasks = $this->taskPaginator->userTasks($user_id, $offset, $limit, $order, $direction);
- }
- else {
- $offset = 0;
- $tasks = $this->taskPaginator->userTasks($user_id, $offset, $limit);
- }
-
- return array(
- 'tasks' => $tasks,
- 'task_pagination' => array(
- 'controller' => 'app',
- 'action' => 'index',
- 'params' => array('paginate' => 'userTasks'),
- 'direction' => $direction,
- 'order' => $order,
- 'total' => $this->taskPaginator->countUserTasks($user_id),
- 'offset' => $offset,
- 'limit' => $limit,
- )
- );
- }
-
- /**
- * Get subtasks pagination
- *
- * @access public
- * @param integer $user_id
- * @param string $paginate
- * @param integer $offset
- * @param string $order
- * @param string $direction
- */
- private function getSubtaskPagination($user_id, $paginate, $offset, $order, $direction)
- {
- $status = array(SubTaskModel::STATUS_TODO, SubTaskModel::STATUS_INPROGRESS);
- $limit = 10;
-
- if (! in_array($order, array('tasks.id', 'project_name', 'status', 'title'))) {
- $order = 'tasks.id';
- $direction = 'ASC';
- }
-
- if ($paginate === 'userSubtasks') {
- $subtasks = $this->subtaskPaginator->userSubtasks($user_id, $status, $offset, $limit, $order, $direction);
- }
- else {
- $offset = 0;
- $subtasks = $this->subtaskPaginator->userSubtasks($user_id, $status, $offset, $limit);
- }
-
- return array(
- 'subtasks' => $subtasks,
- 'subtask_pagination' => array(
- 'controller' => 'app',
- 'action' => 'index',
- 'params' => array('paginate' => 'userSubtasks'),
- 'direction' => $direction,
- 'order' => $order,
- 'total' => $this->subtaskPaginator->countUserSubtasks($user_id, $status),
- 'offset' => $offset,
- 'limit' => $limit,
- )
- );
+ 'task_paginator' => $task_paginator,
+ 'subtask_paginator' => $subtask_paginator,
+ 'project_paginator' => $project_paginator,
+ )));
}
/**
- * Get projects pagination
- *
- * @access public
- * @param array $project_ids
- * @param string $paginate
- * @param integer $offset
- * @param string $order
- * @param string $direction
- */
- private function getProjectPagination(array $project_ids, $paginate, $offset, $order, $direction)
- {
- $limit = 10;
-
- if (! in_array($order, array('id', 'name'))) {
- $order = 'name';
- $direction = 'ASC';
- }
-
- if ($paginate === 'projectSummaries') {
- $projects = $this->projectPaginator->projectSummaries($project_ids, $offset, $limit, $order, $direction);
- }
- else {
- $offset = 0;
- $projects = $this->projectPaginator->projectSummaries($project_ids, $offset, $limit);
- }
-
- return array(
- 'projects' => $projects,
- 'project_pagination' => array(
- 'controller' => 'app',
- 'action' => 'index',
- 'params' => array('paginate' => 'projectSummaries'),
- 'direction' => $direction,
- 'order' => $order,
- 'total' => count($project_ids),
- 'offset' => $offset,
- 'limit' => $limit,
- )
- );
- }
-
- /**
- * Render Markdown Text and reply with the HTML Code
+ * Render Markdown text and reply with the HTML Code
*
* @access public
*/
@@ -196,4 +83,14 @@ class App extends Base
);
}
}
+
+ /**
+ * Colors stylesheet
+ *
+ * @access public
+ */
+ public function colors()
+ {
+ $this->response->css($this->color->getCss());
+ }
}
diff --git a/app/Controller/Base.php b/app/Controller/Base.php
index 8a5354aa..1f8b243c 100644
--- a/app/Controller/Base.php
+++ b/app/Controller/Base.php
@@ -19,6 +19,9 @@ use Symfony\Component\EventDispatcher\Event;
*
* @property \Core\Session $session
* @property \Core\Template $template
+ * @property \Core\Paginator $paginator
+ * @property \Integration\GithubWebhook $githubWebhook
+ * @property \Integration\GitlabWebhook $gitlabWebhook
* @property \Model\Acl $acl
* @property \Model\Authentication $authentication
* @property \Model\Action $action
@@ -34,8 +37,10 @@ use Symfony\Component\EventDispatcher\Event;
* @property \Model\Project $project
* @property \Model\ProjectPermission $projectPermission
* @property \Model\ProjectAnalytic $projectAnalytic
+ * @property \Model\ProjectActivity $projectActivity
* @property \Model\ProjectDailySummary $projectDailySummary
* @property \Model\SubTask $subTask
+ * @property \Model\Swimlane $swimlane
* @property \Model\Task $task
* @property \Model\TaskCreation $taskCreation
* @property \Model\TaskModification $taskModification
@@ -43,6 +48,7 @@ use Symfony\Component\EventDispatcher\Event;
* @property \Model\TaskHistory $taskHistory
* @property \Model\TaskExport $taskExport
* @property \Model\TaskFinder $taskFinder
+ * @property \Model\TaskFilter $taskFilter
* @property \Model\TaskPosition $taskPosition
* @property \Model\TaskPermission $taskPermission
* @property \Model\TaskStatus $taskStatus
diff --git a/app/Controller/Calendar.php b/app/Controller/Calendar.php
new file mode 100644
index 00000000..6b0c8619
--- /dev/null
+++ b/app/Controller/Calendar.php
@@ -0,0 +1,79 @@
+<?php
+
+namespace Controller;
+
+/**
+ * Project Calendar controller
+ *
+ * @package controller
+ * @author Frederic Guillot
+ * @author Timo Litzbarski
+ */
+class Calendar extends Base
+{
+ /**
+ * Show calendar view
+ *
+ * @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
+ *
+ * @access public
+ */
+ public function events()
+ {
+ $this->response->json(
+ $this->taskFilter
+ ->create()
+ ->filterByProject($this->request->getIntegerParam('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))
+ ->filterByDueDateRange(
+ $this->request->getStringParam('start'),
+ $this->request->getStringParam('end')
+ )
+ ->toCalendarEvents()
+ );
+ }
+
+ /**
+ * 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' => $values['date_due'],
+ ));
+ }
+ }
+}
diff --git a/app/Controller/Project.php b/app/Controller/Project.php
index d0da53d0..a53c917c 100644
--- a/app/Controller/Project.php
+++ b/app/Controller/Project.php
@@ -51,7 +51,7 @@ class Project extends Base
$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'],
)));
}
@@ -425,38 +425,32 @@ class Project extends Base
{
$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;
+
+ $paginator = $this->paginator
+ ->setUrl('project', 'search', array('search' => $search, 'project_id' => $project['id']))
+ ->setMax(30)
+ ->setOrder('tasks.id')
+ ->setDirection('DESC');
if ($search !== '') {
- $tasks = $this->taskPaginator->searchTasks($project['id'], $search, $offset, $limit, $order, $direction);
- $nb_tasks = $this->taskPaginator->countSearchTasks($project['id'], $search);
+
+ $paginator
+ ->setQuery($this->taskFinder->getSearchQuery($project['id'], $search))
+ ->calculate();
+
+ $nb_tasks = $paginator->getTotal();
}
$this->response->html($this->template->layout('project/search', array(
'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
- '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'],
),
+ 'paginator' => $paginator,
'project' => $project,
'columns' => $this->board->getColumnsList($project['id']),
'categories' => $this->category->getList($project['id'], false),
@@ -472,32 +466,21 @@ class Project extends Base
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->taskPaginator->closedTasks($project['id'], $offset, $limit, $order, $direction);
- $nb_tasks = $this->taskPaginator->countClosedTasks($project['id']);
+ $paginator = $this->paginator
+ ->setUrl('project', '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('project/tasks', array(
'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
- '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,
'columns' => $this->board->getColumnsList($project['id']),
'categories' => $this->category->getList($project['id'], false),
- 'tasks' => $tasks,
- 'nb_tasks' => $nb_tasks,
- 'title' => t('Completed tasks for "%s"', $project['name']).' ('.$nb_tasks.')'
+ 'paginator' => $paginator,
+ 'title' => t('Completed tasks for "%s"', $project['name']).' ('.$paginator->getTotal().')'
)));
}
diff --git a/app/Controller/User.php b/app/Controller/User.php
index 7fddf705..a02da7a9 100644
--- a/app/Controller/User.php
+++ b/app/Controller/User.php
@@ -115,31 +115,19 @@ class User extends Base
*/
public function index()
{
- $direction = $this->request->getStringParam('direction', 'ASC');
- $order = $this->request->getStringParam('order', 'username');
- $offset = $this->request->getIntegerParam('offset', 0);
- $limit = 25;
-
- $users = $this->user->paginate($offset, $limit, $order, $direction);
- $nb_users = $this->user->count();
+ $paginator = $this->paginator
+ ->setUrl('user', 'index')
+ ->setMax(30)
+ ->setOrder('username')
+ ->setQuery($this->user->getQuery())
+ ->calculate();
$this->response->html(
$this->template->layout('user/index', array(
'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
'projects' => $this->project->getList(),
- 'nb_users' => $nb_users,
- 'users' => $users,
- 'title' => t('Users').' ('.$nb_users.')',
- 'pagination' => array(
- 'controller' => 'user',
- 'action' => 'index',
- 'direction' => $direction,
- 'order' => $order,
- 'total' => $nb_users,
- 'offset' => $offset,
- 'limit' => $limit,
- 'params' => array(),
- ),
+ 'title' => t('Users').' ('.$paginator->getTotal().')',
+ 'paginator' => $paginator,
)));
}
diff --git a/app/Core/Helper.php b/app/Core/Helper.php
index e9fa1868..dbe5a271 100644
--- a/app/Core/Helper.php
+++ b/app/Core/Helper.php
@@ -104,9 +104,9 @@ class Helper
* @param string $filename Filename
* @return string
*/
- public function css($filename)
+ public function css($filename, $is_file = true)
{
- return '<link rel="stylesheet" href="'.$filename.'?'.filemtime($filename).'" media="screen">';
+ return '<link rel="stylesheet" href="'.$filename.($is_file ? '?'.filemtime($filename) : '').'" media="screen">';
}
/**
@@ -417,7 +417,7 @@ class Helper
}
/**
- * URL query string
+ * Generate controller/action url for templates
*
* u('task', 'show', array('task_id' => $task_id))
*
@@ -429,83 +429,40 @@ class Helper
*/
public function u($controller, $action, array $params = array(), $csrf = false)
{
- $html = '?controller='.$controller.'&amp;action='.$action;
+ $values = array(
+ 'controller' => $controller,
+ 'action' => $action,
+ );
if ($csrf) {
$params['csrf_token'] = Security::getCSRFToken();
}
- foreach ($params as $key => $value) {
- $html .= '&amp;'.$key.'='.$value;
- }
+ $values += $params;
- return $html;
+ return '?'.http_build_query($values, '', '&amp;');
}
/**
- * Pagination links
+ * Generate controller/action url
*
- * @param array $pagination Pagination information
- * @return string
- */
- public function paginate(array $pagination)
- {
- extract($pagination);
-
- if ($pagination['offset'] === 0 && ($total - $pagination['offset']) <= $limit) {
- return '';
- }
-
- $html = '<div class="pagination">';
- $html .= '<span class="pagination-previous">';
-
- if ($pagination['offset'] > 0) {
- $offset = $pagination['offset'] - $limit;
- $html .= $this->a('&larr; '.t('Previous'), $controller, $action, $params + compact('offset', 'order', 'direction'));
- }
- else {
- $html .= '&larr; '.t('Previous');
- }
-
- $html .= '</span>';
- $html .= '<span class="pagination-next">';
-
- if (($total - $pagination['offset']) > $limit) {
- $offset = $pagination['offset'] + $limit;
- $html .= $this->a(t('Next').' &rarr;', $controller, $action, $params + compact('offset', 'order', 'direction'));
- }
- else {
- $html .= t('Next').' &rarr;';
- }
-
- $html .= '</span>';
- $html .= '</div>';
-
- return $html;
- }
-
- /**
- * Column sorting (work with pagination)
+ * l('task', 'show', array('task_id' => $task_id))
*
- * @param string $label Column title
- * @param string $column SQL column name
- * @param array $pagination Pagination information
+ * @param string $controller Controller name
+ * @param string $action Action name
+ * @param array $params Url parameters
* @return string
*/
- public function order($label, $column, array $pagination)
+ public function url($controller, $action, array $params = array())
{
- extract($pagination);
+ $values = array(
+ 'controller' => $controller,
+ 'action' => $action,
+ );
- $prefix = '';
-
- if ($order === $column) {
- $prefix = $direction === 'DESC' ? '&#9660; ' : '&#9650; ';
- $direction = $direction === 'DESC' ? 'ASC' : 'DESC';
- }
+ $values += $params;
- $order = $column;
-
- return $prefix.$this->a($label, $controller, $action, $params + compact('offset', 'order', 'direction'));
+ return '?'.http_build_query($values);
}
/**
@@ -656,4 +613,55 @@ class Helper
return $default_value;
}
+
+ /**
+ * Get calendar translations
+ *
+ * @access public
+ * @return string
+ */
+ public function getCalendarTranslations()
+ {
+ return json_encode(array(
+ 'Today' => t('Today'),
+ 'Jan' => t('Jan'),
+ 'Feb' => t('Feb'),
+ 'Mar' => t('Mar'),
+ 'Apr' => t('Apr'),
+ 'May' => t('May'),
+ 'Jun' => t('Jun'),
+ 'Jul' => t('Jul'),
+ 'Aug' => t('Aug'),
+ 'Sep' => t('Sep'),
+ 'Oct' => t('Oct'),
+ 'Nov' => t('Nov'),
+ 'Dec' => t('Dec'),
+ 'January' => t('January'),
+ 'February' => t('February'),
+ 'March' => t('March'),
+ 'April' => t('April'),
+ 'May' => t('May'),
+ 'June' => t('June'),
+ 'July' => t('July'),
+ 'August' => t('August'),
+ 'September' => t('September'),
+ 'October' => t('October'),
+ 'November' => t('November'),
+ 'December' => t('December'),
+ 'Sunday' => t('Sunday'),
+ 'Monday' => t('Monday'),
+ 'Tuesday' => t('Tuesday'),
+ 'Wednesday' => t('Wednesday'),
+ 'Thursday' => t('Thursday'),
+ 'Friday' => t('Friday'),
+ 'Saturday' => t('Saturday'),
+ 'Sun' => t('Sun'),
+ 'Mon' => t('Mon'),
+ 'Tue' => t('Tue'),
+ 'Wed' => t('Wed'),
+ 'Thu' => t('Thu'),
+ 'Fri' => t('Fri'),
+ 'Sat' => t('Sat'),
+ ));
+ }
}
diff --git a/app/Core/Paginator.php b/app/Core/Paginator.php
new file mode 100644
index 00000000..4d4364dd
--- /dev/null
+++ b/app/Core/Paginator.php
@@ -0,0 +1,461 @@
+<?php
+
+namespace Core;
+
+use Pimple\Container;
+use PicoDb\Table;
+
+/**
+ * Paginator helper
+ *
+ * @package core
+ * @author Frederic Guillot
+ */
+class Paginator
+{
+ /**
+ * Container instance
+ *
+ * @access private
+ * @var \Pimple\Container
+ */
+ private $container;
+
+ /**
+ * Total number of items
+ *
+ * @access private
+ * @var integer
+ */
+ private $total = 0;
+
+ /**
+ * Page number
+ *
+ * @access private
+ * @var integer
+ */
+ private $page = 1;
+
+ /**
+ * Offset
+ *
+ * @access private
+ * @var integer
+ */
+ private $offset = 0;
+
+ /**
+ * Limit
+ *
+ * @access private
+ * @var integer
+ */
+ private $limit = 0;
+
+ /**
+ * Sort by this column
+ *
+ * @access private
+ * @var string
+ */
+ private $order = '';
+
+ /**
+ * Sorting direction
+ *
+ * @access private
+ * @var string
+ */
+ private $direction = 'ASC';
+
+ /**
+ * Slice of items
+ *
+ * @access private
+ * @var array
+ */
+ private $items = array();
+
+ /**
+ * PicoDb Table instance
+ *
+ * @access private
+ * @var \Picodb\Table
+ */
+ private $query = null;
+
+ /**
+ * Controller name
+ *
+ * @access private
+ * @var string
+ */
+ private $controller = '';
+
+ /**
+ * Action name
+ *
+ * @access private
+ * @var string
+ */
+ private $action = '';
+
+ /**
+ * Url params
+ *
+ * @access private
+ * @var array
+ */
+ private $params = array();
+
+ /**
+ * Constructor
+ *
+ * @access public
+ * @param \Pimple\Container $container
+ */
+ public function __construct(Container $container)
+ {
+ $this->container = $container;
+ }
+
+ /**
+ * Set a PicoDb query
+ *
+ * @access public
+ * @param \PicoDb\Table
+ * @return Paginator
+ */
+ public function setQuery(Table $query)
+ {
+ $this->query = $query;
+ $this->total = $this->query->count();
+ return $this;
+ }
+
+ /**
+ * Execute a PicoDb query
+ *
+ * @access public
+ * @return array
+ */
+ public function executeQuery()
+ {
+ if ($this->query !== null) {
+ return $this->query
+ ->offset($this->offset)
+ ->limit($this->limit)
+ ->orderBy($this->order, $this->direction)
+ ->findAll();
+ }
+
+ return array();
+ }
+
+ /**
+ * Set url parameters
+ *
+ * @access public
+ * @param string $controller
+ * @param string $action
+ * @param array $params
+ * @return Paginator
+ */
+ public function setUrl($controller, $action, array $params = array())
+ {
+ $this->controller = $controller;
+ $this->action = $action;
+ $this->params = $params;
+ return $this;
+ }
+
+ /**
+ * Add manually items
+ *
+ * @access public
+ * @param array $items
+ * @return Paginator
+ */
+ public function setCollection(array $items)
+ {
+ $this->items = $items;
+ return $this;
+ }
+
+ /**
+ * Return the items
+ *
+ * @access public
+ * @return array
+ */
+ public function getCollection()
+ {
+ return $this->items ?: $this->executeQuery();
+ }
+
+ /**
+ * Set the total number of items
+ *
+ * @access public
+ * @param integer $total
+ * @return Paginator
+ */
+ public function setTotal($total)
+ {
+ $this->total = $total;
+ return $this;
+ }
+
+ /**
+ * Get the total number of items
+ *
+ * @access public
+ * @return integer
+ */
+ public function getTotal()
+ {
+ return $this->total;
+ }
+
+ /**
+ * Set the default page number
+ *
+ * @access public
+ * @param integer $page
+ * @return Paginator
+ */
+ public function setPage($page)
+ {
+ $this->page = $page;
+ return $this;
+ }
+
+ /**
+ * Set the default column order
+ *
+ * @access public
+ * @param string $order
+ * @return Paginator
+ */
+ public function setOrder($order)
+ {
+ $this->order = $order;
+ return $this;
+ }
+
+ /**
+ * Set the default sorting direction
+ *
+ * @access public
+ * @param string $direction
+ * @return Paginator
+ */
+ public function setDirection($direction)
+ {
+ $this->direction = $direction;
+ return $this;
+ }
+
+ /**
+ * Set the maximum number of items per page
+ *
+ * @access public
+ * @param integer $limit
+ * @return Paginator
+ */
+ public function setMax($limit)
+ {
+ $this->limit = $limit;
+ return $this;
+ }
+
+ /**
+ * Return true if the collection is empty
+ *
+ * @access public
+ * @return boolean
+ */
+ public function isEmpty()
+ {
+ return $this->total === 0;
+ }
+
+ /**
+ * Execute the offset calculation only if the $condition is true
+ *
+ * @access public
+ * @param boolean $condition
+ * @return Paginator
+ */
+ public function calculateOnlyIf($condition)
+ {
+ if ($condition) {
+ $this->calculate();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Calculate the offset value accoring to url params and the page number
+ *
+ * @access public
+ * @return Paginator
+ */
+ public function calculate()
+ {
+ $this->page = $this->container['request']->getIntegerParam('page', 1);
+ $this->direction = $this->container['request']->getStringParam('direction', $this->direction);
+ $this->order = $this->container['request']->getStringParam('order', $this->order);
+
+ if ($this->page < 1) {
+ $this->page = 1;
+ }
+
+ $this->offset = ($this->page - 1) * $this->limit;
+
+ return $this;
+ }
+
+ /**
+ * Get url params for link generation
+ *
+ * @access public
+ * @param integer $page
+ * @param string $order
+ * @param string $direction
+ * @return string
+ */
+ public function getUrlParams($page, $order, $direction)
+ {
+ $params = array(
+ 'page' => $page,
+ 'order' => $order,
+ 'direction' => $direction,
+ );
+
+ return array_merge($this->params, $params);
+ }
+
+ /**
+ * Generate the previous link
+ *
+ * @access public
+ * @return string
+ */
+ public function generatePreviousLink()
+ {
+ $html = '<span class="pagination-previous">';
+
+ if ($this->offset > 0) {
+ $html .= $this->container['helper']->a(
+ '&larr; '.t('Previous'),
+ $this->controller,
+ $this->action,
+ $this->getUrlParams($this->page - 1, $this->order, $this->direction)
+ );
+ }
+ else {
+ $html .= '&larr; '.t('Previous');
+ }
+
+ $html .= '</span>';
+
+ return $html;
+ }
+
+ /**
+ * Generate the next link
+ *
+ * @access public
+ * @return string
+ */
+ public function generateNextLink()
+ {
+ $html = '<span class="pagination-next">';
+
+ if (($this->total - $this->offset) > $this->limit) {
+ $html .= $this->container['helper']->a(
+ t('Next').' &rarr;',
+ $this->controller,
+ $this->action,
+ $this->getUrlParams($this->page + 1, $this->order, $this->direction)
+ );
+ }
+ else {
+ $html .= t('Next').' &rarr;';
+ }
+
+ $html .= '</span>';
+
+ return $html;
+ }
+
+ /**
+ * Return true if there is no pagination to show
+ *
+ * @access public
+ * @return boolean
+ */
+ public function hasNothingtoShow()
+ {
+ return $this->offset === 0 && ($this->total - $this->offset) <= $this->limit;
+ }
+
+ /**
+ * Generation pagination links
+ *
+ * @access public
+ * @return string
+ */
+ public function toHtml()
+ {
+ $html = '';
+
+ if (! $this->hasNothingtoShow()) {
+ $html .= '<div class="pagination">';
+ $html .= $this->generatePreviousLink();
+ $html .= $this->generateNextLink();
+ $html .= '</div>';
+ }
+
+ return $html;
+ }
+
+ /**
+ * Magic method to output pagination links
+ *
+ * @access public
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->toHtml();
+ }
+
+ /**
+ * Column sorting
+ *
+ * @param string $label Column title
+ * @param string $column SQL column name
+ * @return string
+ */
+ public function order($label, $column)
+ {
+ $prefix = '';
+ $direction = 'ASC';
+
+ if ($this->order === $column) {
+ $prefix = $this->direction === 'DESC' ? '&#9660; ' : '&#9650; ';
+ $direction = $this->direction === 'DESC' ? 'ASC' : 'DESC';
+ }
+
+ return $prefix.$this->container['helper']->a(
+ $label,
+ $this->controller,
+ $this->action,
+ $this->getUrlParams($this->page, $column, $direction)
+ );
+ }
+}
diff --git a/app/Core/Response.php b/app/Core/Response.php
index 6534d64f..1ce42ad3 100644
--- a/app/Core/Response.php
+++ b/app/Core/Response.php
@@ -168,6 +168,23 @@ class Response
}
/**
+ * Send a css response
+ *
+ * @access public
+ * @param string $data Raw data
+ * @param integer $status_code HTTP status code
+ */
+ public function css($data, $status_code = 200)
+ {
+ $this->status($status_code);
+
+ header('Content-Type: text/css; charset=utf-8');
+ echo $data;
+
+ exit;
+ }
+
+ /**
* Send a binary response
*
* @access public
diff --git a/app/Integration/GitlabWebhook.php b/app/Integration/GitlabWebhook.php
index f5df32a6..e920f33d 100644
--- a/app/Integration/GitlabWebhook.php
+++ b/app/Integration/GitlabWebhook.php
@@ -148,10 +148,10 @@ class GitlabWebhook extends Base
*/
public function handleIssueEvent(array $payload)
{
- switch ($payload['object_attributes']['state']) {
- case 'opened':
+ switch ($payload['object_attributes']['action']) {
+ case 'open':
return $this->handleIssueOpened($payload['object_attributes']);
- case 'closed':
+ case 'close':
return $this->handleIssueClosed($payload['object_attributes']);
}
diff --git a/app/Libs/password.php b/app/Libs/password.php
new file mode 100644
index 00000000..c6e84cbd
--- /dev/null
+++ b/app/Libs/password.php
@@ -0,0 +1,227 @@
+<?php
+/**
+ * A Compatibility library with PHP 5.5's simplified password hashing API.
+ *
+ * @author Anthony Ferrara <ircmaxell@php.net>
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @copyright 2012 The Authors
+ */
+
+if (!defined('PASSWORD_BCRYPT')) {
+
+ define('PASSWORD_BCRYPT', 1);
+ define('PASSWORD_DEFAULT', PASSWORD_BCRYPT);
+
+ if (version_compare(PHP_VERSION, '5.3.7', '<')) {
+
+ define('PASSWORD_PREFIX', '$2a$');
+ }
+ else {
+
+ define('PASSWORD_PREFIX', '$2y$');
+ }
+
+ /**
+ * Hash the password using the specified algorithm
+ *
+ * @param string $password The password to hash
+ * @param int $algo The algorithm to use (Defined by PASSWORD_* constants)
+ * @param array $options The options for the algorithm to use
+ *
+ * @return string|false The hashed password, or false on error.
+ */
+ function password_hash($password, $algo, array $options = array()) {
+ if (!function_exists('crypt')) {
+ trigger_error("Crypt must be loaded for password_hash to function", E_USER_WARNING);
+ return null;
+ }
+ if (!is_string($password)) {
+ trigger_error("password_hash(): Password must be a string", E_USER_WARNING);
+ return null;
+ }
+ if (!is_int($algo)) {
+ trigger_error("password_hash() expects parameter 2 to be long, " . gettype($algo) . " given", E_USER_WARNING);
+ return null;
+ }
+ switch ($algo) {
+ case PASSWORD_BCRYPT:
+ // Note that this is a C constant, but not exposed to PHP, so we don't define it here.
+ $cost = 10;
+ if (isset($options['cost'])) {
+ $cost = $options['cost'];
+ if ($cost < 4 || $cost > 31) {
+ trigger_error(sprintf("password_hash(): Invalid bcrypt cost parameter specified: %d", $cost), E_USER_WARNING);
+ return null;
+ }
+ }
+ $required_salt_len = 22;
+ $hash_format = sprintf("%s%02d$", PASSWORD_PREFIX, $cost);
+ break;
+ default:
+ trigger_error(sprintf("password_hash(): Unknown password hashing algorithm: %s", $algo), E_USER_WARNING);
+ return null;
+ }
+ if (isset($options['salt'])) {
+ switch (gettype($options['salt'])) {
+ case 'NULL':
+ case 'boolean':
+ case 'integer':
+ case 'double':
+ case 'string':
+ $salt = (string) $options['salt'];
+ break;
+ case 'object':
+ if (method_exists($options['salt'], '__tostring')) {
+ $salt = (string) $options['salt'];
+ break;
+ }
+ case 'array':
+ case 'resource':
+ default:
+ trigger_error('password_hash(): Non-string salt parameter supplied', E_USER_WARNING);
+ return null;
+ }
+ if (strlen($salt) < $required_salt_len) {
+ trigger_error(sprintf("password_hash(): Provided salt is too short: %d expecting %d", strlen($salt), $required_salt_len), E_USER_WARNING);
+ return null;
+ } elseif (0 == preg_match('#^[a-zA-Z0-9./]+$#D', $salt)) {
+ $salt = str_replace('+', '.', base64_encode($salt));
+ }
+ } else {
+ $buffer = '';
+ $raw_length = (int) ($required_salt_len * 3 / 4 + 1);
+ $buffer_valid = false;
+ if (function_exists('mcrypt_create_iv') && !defined('PHALANGER')) {
+ $buffer = mcrypt_create_iv($raw_length, MCRYPT_DEV_URANDOM);
+ if ($buffer) {
+ $buffer_valid = true;
+ }
+ }
+ if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes')) {
+ $buffer = openssl_random_pseudo_bytes($raw_length);
+ if ($buffer) {
+ $buffer_valid = true;
+ }
+ }
+ if (!$buffer_valid && is_readable('/dev/urandom')) {
+ $f = fopen('/dev/urandom', 'r');
+ $read = strlen($buffer);
+ while ($read < $raw_length) {
+ $buffer .= fread($f, $raw_length - $read);
+ $read = strlen($buffer);
+ }
+ fclose($f);
+ if ($read >= $raw_length) {
+ $buffer_valid = true;
+ }
+ }
+ if (!$buffer_valid || strlen($buffer) < $raw_length) {
+ $bl = strlen($buffer);
+ for ($i = 0; $i < $raw_length; $i++) {
+ if ($i < $bl) {
+ $buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255));
+ } else {
+ $buffer .= chr(mt_rand(0, 255));
+ }
+ }
+ }
+ $salt = str_replace('+', '.', base64_encode($buffer));
+
+ }
+ $salt = substr($salt, 0, $required_salt_len);
+
+ $hash = $hash_format . $salt;
+
+ $ret = crypt($password, $hash);
+
+ if (!is_string($ret) || strlen($ret) <= 13) {
+ return false;
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Get information about the password hash. Returns an array of the information
+ * that was used to generate the password hash.
+ *
+ * array(
+ * 'algo' => 1,
+ * 'algoName' => 'bcrypt',
+ * 'options' => array(
+ * 'cost' => 10,
+ * ),
+ * )
+ *
+ * @param string $hash The password hash to extract info from
+ *
+ * @return array The array of information about the hash.
+ */
+ function password_get_info($hash) {
+ $return = array(
+ 'algo' => 0,
+ 'algoName' => 'unknown',
+ 'options' => array(),
+ );
+ if (substr($hash, 0, 4) == PASSWORD_PREFIX && strlen($hash) == 60) {
+ $return['algo'] = PASSWORD_BCRYPT;
+ $return['algoName'] = 'bcrypt';
+ list($cost) = sscanf($hash, PASSWORD_PREFIX."%d$");
+ $return['options']['cost'] = $cost;
+ }
+ return $return;
+ }
+
+ /**
+ * Determine if the password hash needs to be rehashed according to the options provided
+ *
+ * If the answer is true, after validating the password using password_verify, rehash it.
+ *
+ * @param string $hash The hash to test
+ * @param int $algo The algorithm used for new password hashes
+ * @param array $options The options array passed to password_hash
+ *
+ * @return boolean True if the password needs to be rehashed.
+ */
+ function password_needs_rehash($hash, $algo, array $options = array()) {
+ $info = password_get_info($hash);
+ if ($info['algo'] != $algo) {
+ return true;
+ }
+ switch ($algo) {
+ case PASSWORD_BCRYPT:
+ $cost = isset($options['cost']) ? $options['cost'] : 10;
+ if ($cost != $info['options']['cost']) {
+ return true;
+ }
+ break;
+ }
+ return false;
+ }
+
+ /**
+ * Verify a password against a hash using a timing attack resistant approach
+ *
+ * @param string $password The password to verify
+ * @param string $hash The hash to verify against
+ *
+ * @return boolean If the password matches the hash
+ */
+ function password_verify($password, $hash) {
+ if (!function_exists('crypt')) {
+ trigger_error("Crypt must be loaded for password_verify to function", E_USER_WARNING);
+ return false;
+ }
+ $ret = crypt($password, $hash);
+ if (!is_string($ret) || strlen($ret) != strlen($hash) || strlen($ret) <= 13) {
+ return false;
+ }
+
+ $status = 0;
+ for ($i = 0; $i < strlen($ret); $i++) {
+ $status |= (ord($ret[$i]) ^ ord($hash[$i]));
+ }
+
+ return $status === 0;
+ }
+}
diff --git a/app/Locale/da_DK/translations.php b/app/Locale/da_DK/translations.php
index 3287aa51..5af587f4 100644
--- a/app/Locale/da_DK/translations.php
+++ b/app/Locale/da_DK/translations.php
@@ -647,5 +647,54 @@ return array(
// 'Application default' => '',
// 'Language:' => '',
// 'Timezone:' => '',
+ // 'All columns' => '',
+ // 'Calendar for "%s"' => '',
+ // 'Filter by column' => '',
+ // 'Filter by status' => '',
+ // 'Calendar' => '',
+ // 'Today' => '',
+ // 'Jan ' => '',
+ // 'Feb' => '',
+ // 'Mar' => '',
+ // 'Apr' => '',
+ // 'May' => '',
+ // 'Jun' => '',
+ // 'Jul' => '',
+ // 'Aug' => '',
+ // 'Sep' => '',
+ // 'Oct' => '',
+ // 'Nov' => '',
+ // 'Dec' => '',
+ // 'January' => '',
+ // 'February' => '',
+ // 'March' => '',
+ // 'April' => '',
+ // 'June' => '',
+ // 'July' => '',
+ // 'August' => '',
+ // 'September' => '',
+ // 'October' => '',
+ // 'November' => '',
+ // 'December' => '',
+ // 'Sunday' => '',
+ // 'Monday' => '',
+ // 'Tuesday' => '',
+ // 'Wednesday' => '',
+ // 'Thursday' => '',
+ // 'Friday' => '',
+ // 'Saturday' => '',
+ // 'Sun' => '',
+ // 'Mon' => '',
+ // 'Tue' => '',
+ // 'Wed' => '',
+ // 'Thu' => '',
+ // 'Fri' => '',
+ // 'Sat' => '',
// 'Next' => '',
+ // '#%d' => '',
+ // 'Filter by color' => '',
+ // 'Filter by swimlane' => '',
+ // 'All swimlanes' => '',
+ // 'All colors' => '',
+ // 'All status' => '',
);
diff --git a/app/Locale/de_DE/translations.php b/app/Locale/de_DE/translations.php
index c85c97c6..662046ce 100644
--- a/app/Locale/de_DE/translations.php
+++ b/app/Locale/de_DE/translations.php
@@ -647,5 +647,54 @@ return array(
'Application default' => 'Anwendungsstandard',
'Language:' => 'Sprache:',
'Timezone:' => 'Zeitzone:',
- // 'Next' => '',
+ 'All columns' => 'Alle Spalten',
+ 'Calendar for "%s"' => 'Kalender für "%s"',
+ 'Filter by column' => 'Spalte filtern',
+ 'Filter by status' => 'Status filtern',
+ 'Calendar' => 'Kalender',
+ 'Today' => 'Heute',
+ 'Jan ' => 'Jan',
+ 'Feb' => 'Feb',
+ 'Mar' => 'Mar',
+ 'Apr' => 'Apr',
+ 'May' => 'Mai',
+ 'Jun' => 'Jun',
+ 'Jul' => 'Jul',
+ 'Aug' => 'Aug',
+ 'Sep' => 'Sep',
+ 'Oct' => 'Okt',
+ 'Nov' => 'Nov',
+ 'Dec' => 'Dez',
+ 'January' => 'Januar',
+ 'February' => 'Februar',
+ 'March' => 'März',
+ 'April' => 'April',
+ 'June' => 'Juni',
+ 'July' => 'Juli',
+ 'August' => 'August',
+ 'September' => 'September',
+ 'October' => 'Oktober',
+ 'November' => 'November',
+ 'December' => 'Dezember',
+ 'Sunday' => 'Sonntag',
+ 'Monday' => 'Montag',
+ 'Tuesday' => 'Dienstag',
+ 'Wednesday' => 'Mittwoch',
+ 'Thursday' => 'Donnerstag',
+ 'Friday' => 'Freitag',
+ 'Saturday' => 'Samstag',
+ 'Sun' => 'So',
+ 'Mon' => 'Mo',
+ 'Tue' => 'Di',
+ 'Wed' => 'Mi',
+ 'Thu' => 'Do',
+ 'Fri' => 'Fr',
+ 'Sat' => 'Sa',
+ 'Next' => 'Nächste',
+ // '#%d' => '',
+ 'Filter by color' => 'Farbe filtern',
+ 'Filter by swimlane' => 'Swimlane filtern',
+ 'All swimlanes' => 'Alle Swimlanes',
+ 'All colors' => 'Alle Farben',
+ // 'All status' => '',
);
diff --git a/app/Locale/es_ES/translations.php b/app/Locale/es_ES/translations.php
index 5dafde9f..9c50e1a7 100644
--- a/app/Locale/es_ES/translations.php
+++ b/app/Locale/es_ES/translations.php
@@ -647,5 +647,54 @@ return array(
// 'Application default' => '',
// 'Language:' => '',
// 'Timezone:' => '',
+ // 'All columns' => '',
+ // 'Calendar for "%s"' => '',
+ // 'Filter by column' => '',
+ // 'Filter by status' => '',
+ // 'Calendar' => '',
+ // 'Today' => '',
+ // 'Jan ' => '',
+ // 'Feb' => '',
+ // 'Mar' => '',
+ // 'Apr' => '',
+ // 'May' => '',
+ // 'Jun' => '',
+ // 'Jul' => '',
+ // 'Aug' => '',
+ // 'Sep' => '',
+ // 'Oct' => '',
+ // 'Nov' => '',
+ // 'Dec' => '',
+ // 'January' => '',
+ // 'February' => '',
+ // 'March' => '',
+ // 'April' => '',
+ // 'June' => '',
+ // 'July' => '',
+ // 'August' => '',
+ // 'September' => '',
+ // 'October' => '',
+ // 'November' => '',
+ // 'December' => '',
+ // 'Sunday' => '',
+ // 'Monday' => '',
+ // 'Tuesday' => '',
+ // 'Wednesday' => '',
+ // 'Thursday' => '',
+ // 'Friday' => '',
+ // 'Saturday' => '',
+ // 'Sun' => '',
+ // 'Mon' => '',
+ // 'Tue' => '',
+ // 'Wed' => '',
+ // 'Thu' => '',
+ // 'Fri' => '',
+ // 'Sat' => '',
// 'Next' => '',
+ // '#%d' => '',
+ // 'Filter by color' => '',
+ // 'Filter by swimlane' => '',
+ // 'All swimlanes' => '',
+ // 'All colors' => '',
+ // 'All status' => '',
);
diff --git a/app/Locale/fi_FI/translations.php b/app/Locale/fi_FI/translations.php
index c3f1fbd1..b1dff1ad 100644
--- a/app/Locale/fi_FI/translations.php
+++ b/app/Locale/fi_FI/translations.php
@@ -647,5 +647,54 @@ return array(
// 'Application default' => '',
// 'Language:' => '',
// 'Timezone:' => '',
+ // 'All columns' => '',
+ // 'Calendar for "%s"' => '',
+ // 'Filter by column' => '',
+ // 'Filter by status' => '',
+ // 'Calendar' => '',
+ // 'Today' => '',
+ // 'Jan ' => '',
+ // 'Feb' => '',
+ // 'Mar' => '',
+ // 'Apr' => '',
+ // 'May' => '',
+ // 'Jun' => '',
+ // 'Jul' => '',
+ // 'Aug' => '',
+ // 'Sep' => '',
+ // 'Oct' => '',
+ // 'Nov' => '',
+ // 'Dec' => '',
+ // 'January' => '',
+ // 'February' => '',
+ // 'March' => '',
+ // 'April' => '',
+ // 'June' => '',
+ // 'July' => '',
+ // 'August' => '',
+ // 'September' => '',
+ // 'October' => '',
+ // 'November' => '',
+ // 'December' => '',
+ // 'Sunday' => '',
+ // 'Monday' => '',
+ // 'Tuesday' => '',
+ // 'Wednesday' => '',
+ // 'Thursday' => '',
+ // 'Friday' => '',
+ // 'Saturday' => '',
+ // 'Sun' => '',
+ // 'Mon' => '',
+ // 'Tue' => '',
+ // 'Wed' => '',
+ // 'Thu' => '',
+ // 'Fri' => '',
+ // 'Sat' => '',
// 'Next' => '',
+ // '#%d' => '',
+ // 'Filter by color' => '',
+ // 'Filter by swimlane' => '',
+ // 'All swimlanes' => '',
+ // 'All colors' => '',
+ // 'All status' => '',
);
diff --git a/app/Locale/fr_FR/translations.php b/app/Locale/fr_FR/translations.php
index d0673042..c40cee9e 100644
--- a/app/Locale/fr_FR/translations.php
+++ b/app/Locale/fr_FR/translations.php
@@ -647,6 +647,55 @@ return array(
'Application default' => 'Valeur par défaut de l\'application',
'Language:' => 'Langue :',
'Timezone:' => 'Fuseau horaire :',
+ 'All columns' => 'Toutes les colonnes',
+ 'Calendar for "%s"' => 'Agenda pour le projet « %s »',
+ 'Filter by column' => 'Filtrer par colonne',
+ 'Filter by status' => 'Filtrer par status',
+ 'Calendar' => 'Agenda',
+ 'Today' => 'Aujourd\'hui',
+ 'Jan ' => 'Janv',
+ 'Feb' => 'Fév',
+ 'Mar' => 'Mars',
+ 'Apr' => 'Avr',
+ 'May' => 'Mai',
+ 'Jun' => 'Juin',
+ 'Jul' => 'Juil',
+ 'Aug' => 'Août',
+ 'Sep' => 'Sept',
+ 'Oct' => 'Oct',
+ 'Nov' => 'Nov',
+ 'Dec' => 'Déc',
+ 'January' => 'Janvier',
+ 'February' => 'Février',
+ 'March' => 'Mars',
+ 'April' => 'Avril',
+ 'May' => 'Mai',
+ 'June' => 'Juin',
+ 'July' => 'Juillet',
+ 'August' => 'Août',
+ 'September' => 'Septembre',
+ 'October' => 'Octobre',
+ 'November' => 'Novembre',
+ 'December' => 'Décembre',
+ 'Sunday' => 'Dimanche',
+ 'Monday' => 'Lundi',
+ 'Tuesday' => 'Mardi',
+ 'Wednesday' => 'Mercredi',
+ 'Thursday' => 'Jeudi',
+ 'Friday' => 'Vendredi',
+ 'Saturday' => 'Samedi',
+ 'Sun' => 'Dim',
+ 'Mon' => 'Lun',
+ 'Tue' => 'Mar',
+ 'Wed' => 'Mer',
+ 'Thu' => 'Jeu',
+ 'Fri' => 'Ven',
+ 'Sat' => 'Sam',
'Next' => 'Suivant',
'#%d' => 'nËš%d',
+ 'Filter by color' => 'Filtrer par couleur',
+ 'Filter by swimlane' => 'Filtrer par swimlanes',
+ 'All swimlanes' => 'Toutes les swimlanes',
+ 'All colors' => 'Toutes les couleurs',
+ 'All status' => 'Tous les états',
);
diff --git a/app/Locale/hu_HU/translations.php b/app/Locale/hu_HU/translations.php
index b879b787..d011a0a4 100644
--- a/app/Locale/hu_HU/translations.php
+++ b/app/Locale/hu_HU/translations.php
@@ -647,5 +647,54 @@ return array(
// 'Application default' => '',
// 'Language:' => '',
// 'Timezone:' => '',
+ // 'All columns' => '',
+ // 'Calendar for "%s"' => '',
+ // 'Filter by column' => '',
+ // 'Filter by status' => '',
+ // 'Calendar' => '',
+ // 'Today' => '',
+ // 'Jan ' => '',
+ // 'Feb' => '',
+ // 'Mar' => '',
+ // 'Apr' => '',
+ // 'May' => '',
+ // 'Jun' => '',
+ // 'Jul' => '',
+ // 'Aug' => '',
+ // 'Sep' => '',
+ // 'Oct' => '',
+ // 'Nov' => '',
+ // 'Dec' => '',
+ // 'January' => '',
+ // 'February' => '',
+ // 'March' => '',
+ // 'April' => '',
+ // 'June' => '',
+ // 'July' => '',
+ // 'August' => '',
+ // 'September' => '',
+ // 'October' => '',
+ // 'November' => '',
+ // 'December' => '',
+ // 'Sunday' => '',
+ // 'Monday' => '',
+ // 'Tuesday' => '',
+ // 'Wednesday' => '',
+ // 'Thursday' => '',
+ // 'Friday' => '',
+ // 'Saturday' => '',
+ // 'Sun' => '',
+ // 'Mon' => '',
+ // 'Tue' => '',
+ // 'Wed' => '',
+ // 'Thu' => '',
+ // 'Fri' => '',
+ // 'Sat' => '',
// 'Next' => '',
+ // '#%d' => '',
+ // 'Filter by color' => '',
+ // 'Filter by swimlane' => '',
+ // 'All swimlanes' => '',
+ // 'All colors' => '',
+ // 'All status' => '',
);
diff --git a/app/Locale/it_IT/translations.php b/app/Locale/it_IT/translations.php
index 0777e171..a5350f71 100644
--- a/app/Locale/it_IT/translations.php
+++ b/app/Locale/it_IT/translations.php
@@ -647,5 +647,54 @@ return array(
// 'Application default' => '',
// 'Language:' => '',
// 'Timezone:' => '',
+ // 'All columns' => '',
+ // 'Calendar for "%s"' => '',
+ // 'Filter by column' => '',
+ // 'Filter by status' => '',
+ // 'Calendar' => '',
+ // 'Today' => '',
+ // 'Jan ' => '',
+ // 'Feb' => '',
+ // 'Mar' => '',
+ // 'Apr' => '',
+ // 'May' => '',
+ // 'Jun' => '',
+ // 'Jul' => '',
+ // 'Aug' => '',
+ // 'Sep' => '',
+ // 'Oct' => '',
+ // 'Nov' => '',
+ // 'Dec' => '',
+ // 'January' => '',
+ // 'February' => '',
+ // 'March' => '',
+ // 'April' => '',
+ // 'June' => '',
+ // 'July' => '',
+ // 'August' => '',
+ // 'September' => '',
+ // 'October' => '',
+ // 'November' => '',
+ // 'December' => '',
+ // 'Sunday' => '',
+ // 'Monday' => '',
+ // 'Tuesday' => '',
+ // 'Wednesday' => '',
+ // 'Thursday' => '',
+ // 'Friday' => '',
+ // 'Saturday' => '',
+ // 'Sun' => '',
+ // 'Mon' => '',
+ // 'Tue' => '',
+ // 'Wed' => '',
+ // 'Thu' => '',
+ // 'Fri' => '',
+ // 'Sat' => '',
// 'Next' => '',
+ // '#%d' => '',
+ // 'Filter by color' => '',
+ // 'Filter by swimlane' => '',
+ // 'All swimlanes' => '',
+ // 'All colors' => '',
+ // 'All status' => '',
);
diff --git a/app/Locale/ja_JP/translations.php b/app/Locale/ja_JP/translations.php
index f4f5215f..53b10212 100644
--- a/app/Locale/ja_JP/translations.php
+++ b/app/Locale/ja_JP/translations.php
@@ -647,5 +647,54 @@ return array(
// 'Application default' => '',
// 'Language:' => '',
// 'Timezone:' => '',
+ // 'All columns' => '',
+ // 'Calendar for "%s"' => '',
+ // 'Filter by column' => '',
+ // 'Filter by status' => '',
+ // 'Calendar' => '',
+ // 'Today' => '',
+ // 'Jan ' => '',
+ // 'Feb' => '',
+ // 'Mar' => '',
+ // 'Apr' => '',
+ // 'May' => '',
+ // 'Jun' => '',
+ // 'Jul' => '',
+ // 'Aug' => '',
+ // 'Sep' => '',
+ // 'Oct' => '',
+ // 'Nov' => '',
+ // 'Dec' => '',
+ // 'January' => '',
+ // 'February' => '',
+ // 'March' => '',
+ // 'April' => '',
+ // 'June' => '',
+ // 'July' => '',
+ // 'August' => '',
+ // 'September' => '',
+ // 'October' => '',
+ // 'November' => '',
+ // 'December' => '',
+ // 'Sunday' => '',
+ // 'Monday' => '',
+ // 'Tuesday' => '',
+ // 'Wednesday' => '',
+ // 'Thursday' => '',
+ // 'Friday' => '',
+ // 'Saturday' => '',
+ // 'Sun' => '',
+ // 'Mon' => '',
+ // 'Tue' => '',
+ // 'Wed' => '',
+ // 'Thu' => '',
+ // 'Fri' => '',
+ // 'Sat' => '',
// 'Next' => '',
+ // '#%d' => '',
+ // 'Filter by color' => '',
+ // 'Filter by swimlane' => '',
+ // 'All swimlanes' => '',
+ // 'All colors' => '',
+ // 'All status' => '',
);
diff --git a/app/Locale/pl_PL/translations.php b/app/Locale/pl_PL/translations.php
index 408828ae..6de3ff70 100644
--- a/app/Locale/pl_PL/translations.php
+++ b/app/Locale/pl_PL/translations.php
@@ -647,5 +647,54 @@ return array(
// 'Application default' => '',
// 'Language:' => '',
// 'Timezone:' => '',
+ // 'All columns' => '',
+ // 'Calendar for "%s"' => '',
+ // 'Filter by column' => '',
+ // 'Filter by status' => '',
+ // 'Calendar' => '',
+ // 'Today' => '',
+ // 'Jan ' => '',
+ // 'Feb' => '',
+ // 'Mar' => '',
+ // 'Apr' => '',
+ // 'May' => '',
+ // 'Jun' => '',
+ // 'Jul' => '',
+ // 'Aug' => '',
+ // 'Sep' => '',
+ // 'Oct' => '',
+ // 'Nov' => '',
+ // 'Dec' => '',
+ // 'January' => '',
+ // 'February' => '',
+ // 'March' => '',
+ // 'April' => '',
+ // 'June' => '',
+ // 'July' => '',
+ // 'August' => '',
+ // 'September' => '',
+ // 'October' => '',
+ // 'November' => '',
+ // 'December' => '',
+ // 'Sunday' => '',
+ // 'Monday' => '',
+ // 'Tuesday' => '',
+ // 'Wednesday' => '',
+ // 'Thursday' => '',
+ // 'Friday' => '',
+ // 'Saturday' => '',
+ // 'Sun' => '',
+ // 'Mon' => '',
+ // 'Tue' => '',
+ // 'Wed' => '',
+ // 'Thu' => '',
+ // 'Fri' => '',
+ // 'Sat' => '',
// 'Next' => '',
+ // '#%d' => '',
+ // 'Filter by color' => '',
+ // 'Filter by swimlane' => '',
+ // 'All swimlanes' => '',
+ // 'All colors' => '',
+ // 'All status' => '',
);
diff --git a/app/Locale/pt_BR/translations.php b/app/Locale/pt_BR/translations.php
index f155b045..e5dfcb25 100644
--- a/app/Locale/pt_BR/translations.php
+++ b/app/Locale/pt_BR/translations.php
@@ -647,5 +647,54 @@ return array(
'Application default' => 'Aplicação padrão',
'Language:' => 'Idioma',
'Timezone:' => 'Fuso horário',
+ // 'All columns' => '',
+ // 'Calendar for "%s"' => '',
+ // 'Filter by column' => '',
+ // 'Filter by status' => '',
+ // 'Calendar' => '',
+ // 'Today' => '',
+ // 'Jan ' => '',
+ // 'Feb' => '',
+ // 'Mar' => '',
+ // 'Apr' => '',
+ // 'May' => '',
+ // 'Jun' => '',
+ // 'Jul' => '',
+ // 'Aug' => '',
+ // 'Sep' => '',
+ // 'Oct' => '',
+ // 'Nov' => '',
+ // 'Dec' => '',
+ // 'January' => '',
+ // 'February' => '',
+ // 'March' => '',
+ // 'April' => '',
+ // 'June' => '',
+ // 'July' => '',
+ // 'August' => '',
+ // 'September' => '',
+ // 'October' => '',
+ // 'November' => '',
+ // 'December' => '',
+ // 'Sunday' => '',
+ // 'Monday' => '',
+ // 'Tuesday' => '',
+ // 'Wednesday' => '',
+ // 'Thursday' => '',
+ // 'Friday' => '',
+ // 'Saturday' => '',
+ // 'Sun' => '',
+ // 'Mon' => '',
+ // 'Tue' => '',
+ // 'Wed' => '',
+ // 'Thu' => '',
+ // 'Fri' => '',
+ // 'Sat' => '',
'Next' => 'Próximo',
+ // '#%d' => '',
+ // 'Filter by color' => '',
+ // 'Filter by swimlane' => '',
+ // 'All swimlanes' => '',
+ // 'All colors' => '',
+ // 'All status' => '',
);
diff --git a/app/Locale/ru_RU/translations.php b/app/Locale/ru_RU/translations.php
index a857de30..daf325df 100644
--- a/app/Locale/ru_RU/translations.php
+++ b/app/Locale/ru_RU/translations.php
@@ -647,5 +647,54 @@ return array(
// 'Application default' => '',
// 'Language:' => '',
// 'Timezone:' => '',
+ // 'All columns' => '',
+ // 'Calendar for "%s"' => '',
+ // 'Filter by column' => '',
+ // 'Filter by status' => '',
+ // 'Calendar' => '',
+ // 'Today' => '',
+ // 'Jan ' => '',
+ // 'Feb' => '',
+ // 'Mar' => '',
+ // 'Apr' => '',
+ // 'May' => '',
+ // 'Jun' => '',
+ // 'Jul' => '',
+ // 'Aug' => '',
+ // 'Sep' => '',
+ // 'Oct' => '',
+ // 'Nov' => '',
+ // 'Dec' => '',
+ // 'January' => '',
+ // 'February' => '',
+ // 'March' => '',
+ // 'April' => '',
+ // 'June' => '',
+ // 'July' => '',
+ // 'August' => '',
+ // 'September' => '',
+ // 'October' => '',
+ // 'November' => '',
+ // 'December' => '',
+ // 'Sunday' => '',
+ // 'Monday' => '',
+ // 'Tuesday' => '',
+ // 'Wednesday' => '',
+ // 'Thursday' => '',
+ // 'Friday' => '',
+ // 'Saturday' => '',
+ // 'Sun' => '',
+ // 'Mon' => '',
+ // 'Tue' => '',
+ // 'Wed' => '',
+ // 'Thu' => '',
+ // 'Fri' => '',
+ // 'Sat' => '',
// 'Next' => '',
+ // '#%d' => '',
+ // 'Filter by color' => '',
+ // 'Filter by swimlane' => '',
+ // 'All swimlanes' => '',
+ // 'All colors' => '',
+ // 'All status' => '',
);
diff --git a/app/Locale/sv_SE/translations.php b/app/Locale/sv_SE/translations.php
index ae805d4b..fb347dbd 100644
--- a/app/Locale/sv_SE/translations.php
+++ b/app/Locale/sv_SE/translations.php
@@ -647,5 +647,54 @@ return array(
'Application default' => 'Applikationsstandard',
'Language:' => 'Språk',
'Timezone:' => 'Tidszon',
+ // 'All columns' => '',
+ // 'Calendar for "%s"' => '',
+ // 'Filter by column' => '',
+ // 'Filter by status' => '',
+ // 'Calendar' => '',
+ // 'Today' => '',
+ // 'Jan ' => '',
+ // 'Feb' => '',
+ // 'Mar' => '',
+ // 'Apr' => '',
+ // 'May' => '',
+ // 'Jun' => '',
+ // 'Jul' => '',
+ // 'Aug' => '',
+ // 'Sep' => '',
+ // 'Oct' => '',
+ // 'Nov' => '',
+ // 'Dec' => '',
+ // 'January' => '',
+ // 'February' => '',
+ // 'March' => '',
+ // 'April' => '',
+ // 'June' => '',
+ // 'July' => '',
+ // 'August' => '',
+ // 'September' => '',
+ // 'October' => '',
+ // 'November' => '',
+ // 'December' => '',
+ // 'Sunday' => '',
+ // 'Monday' => '',
+ // 'Tuesday' => '',
+ // 'Wednesday' => '',
+ // 'Thursday' => '',
+ // 'Friday' => '',
+ // 'Saturday' => '',
+ // 'Sun' => '',
+ // 'Mon' => '',
+ // 'Tue' => '',
+ // 'Wed' => '',
+ // 'Thu' => '',
+ // 'Fri' => '',
+ // 'Sat' => '',
'Next' => 'Nästa',
+ // '#%d' => '',
+ // 'Filter by color' => '',
+ // 'Filter by swimlane' => '',
+ // 'All swimlanes' => '',
+ // 'All colors' => '',
+ // 'All status' => '',
);
diff --git a/app/Locale/th_TH/translations.php b/app/Locale/th_TH/translations.php
index f091020f..ac6b82f6 100644
--- a/app/Locale/th_TH/translations.php
+++ b/app/Locale/th_TH/translations.php
@@ -647,5 +647,54 @@ return array(
// 'Application default' => '',
// 'Language:' => '',
// 'Timezone:' => '',
+ // 'All columns' => '',
+ // 'Calendar for "%s"' => '',
+ // 'Filter by column' => '',
+ // 'Filter by status' => '',
+ // 'Calendar' => '',
+ // 'Today' => '',
+ // 'Jan ' => '',
+ // 'Feb' => '',
+ // 'Mar' => '',
+ // 'Apr' => '',
+ // 'May' => '',
+ // 'Jun' => '',
+ // 'Jul' => '',
+ // 'Aug' => '',
+ // 'Sep' => '',
+ // 'Oct' => '',
+ // 'Nov' => '',
+ // 'Dec' => '',
+ // 'January' => '',
+ // 'February' => '',
+ // 'March' => '',
+ // 'April' => '',
+ // 'June' => '',
+ // 'July' => '',
+ // 'August' => '',
+ // 'September' => '',
+ // 'October' => '',
+ // 'November' => '',
+ // 'December' => '',
+ // 'Sunday' => '',
+ // 'Monday' => '',
+ // 'Tuesday' => '',
+ // 'Wednesday' => '',
+ // 'Thursday' => '',
+ // 'Friday' => '',
+ // 'Saturday' => '',
+ // 'Sun' => '',
+ // 'Mon' => '',
+ // 'Tue' => '',
+ // 'Wed' => '',
+ // 'Thu' => '',
+ // 'Fri' => '',
+ // 'Sat' => '',
// 'Next' => '',
+ // '#%d' => '',
+ // 'Filter by color' => '',
+ // 'Filter by swimlane' => '',
+ // 'All swimlanes' => '',
+ // 'All colors' => '',
+ // 'All status' => '',
);
diff --git a/app/Locale/zh_CN/translations.php b/app/Locale/zh_CN/translations.php
index cd423d00..362ac825 100644
--- a/app/Locale/zh_CN/translations.php
+++ b/app/Locale/zh_CN/translations.php
@@ -647,5 +647,54 @@ return array(
// 'Application default' => '',
// 'Language:' => '',
// 'Timezone:' => '',
+ // 'All columns' => '',
+ // 'Calendar for "%s"' => '',
+ // 'Filter by column' => '',
+ // 'Filter by status' => '',
+ // 'Calendar' => '',
+ // 'Today' => '',
+ // 'Jan ' => '',
+ // 'Feb' => '',
+ // 'Mar' => '',
+ // 'Apr' => '',
+ // 'May' => '',
+ // 'Jun' => '',
+ // 'Jul' => '',
+ // 'Aug' => '',
+ // 'Sep' => '',
+ // 'Oct' => '',
+ // 'Nov' => '',
+ // 'Dec' => '',
+ // 'January' => '',
+ // 'February' => '',
+ // 'March' => '',
+ // 'April' => '',
+ // 'June' => '',
+ // 'July' => '',
+ // 'August' => '',
+ // 'September' => '',
+ // 'October' => '',
+ // 'November' => '',
+ // 'December' => '',
+ // 'Sunday' => '',
+ // 'Monday' => '',
+ // 'Tuesday' => '',
+ // 'Wednesday' => '',
+ // 'Thursday' => '',
+ // 'Friday' => '',
+ // 'Saturday' => '',
+ // 'Sun' => '',
+ // 'Mon' => '',
+ // 'Tue' => '',
+ // 'Wed' => '',
+ // 'Thu' => '',
+ // 'Fri' => '',
+ // 'Sat' => '',
// 'Next' => '',
+ // '#%d' => '',
+ // 'Filter by color' => '',
+ // 'Filter by swimlane' => '',
+ // 'All swimlanes' => '',
+ // 'All colors' => '',
+ // 'All status' => '',
);
diff --git a/app/Model/Acl.php b/app/Model/Acl.php
index 599ff055..f6c54814 100644
--- a/app/Model/Acl.php
+++ b/app/Model/Acl.php
@@ -37,6 +37,7 @@ class Acl extends Base
'project' => array('show', 'tasks', 'search', 'activity'),
'subtask' => '*',
'task' => '*',
+ 'calendar' => '*',
);
/**
diff --git a/app/Model/Base.php b/app/Model/Base.php
index 3f847c2e..a6333144 100644
--- a/app/Model/Base.php
+++ b/app/Model/Base.php
@@ -23,6 +23,7 @@ use Pimple\Container;
* @property \Model\Config $config
* @property \Model\DateParser $dateParser
* @property \Model\File $file
+ * @property \Model\Helper $helper
* @property \Model\LastLogin $lastLogin
* @property \Model\Notification $notification
* @property \Model\Project $project
diff --git a/app/Model/Board.php b/app/Model/Board.php
index 550009fa..d5b83283 100644
--- a/app/Model/Board.php
+++ b/app/Model/Board.php
@@ -258,16 +258,19 @@ class Board extends Base
*
* @access public
* @param integer $project_id
+ * @param boolean $prepend Prepend default value
* @return array
*/
- public function getColumnStats($project_id)
+ public function getColumnStats($project_id, $prepend = false)
{
- return $this->db
- ->table(Task::TABLE)
- ->eq('project_id', $project_id)
- ->eq('is_active', 1)
- ->groupBy('column_id')
- ->listing('column_id', 'COUNT(*) AS total');
+ $listing = $this->db
+ ->table(Task::TABLE)
+ ->eq('project_id', $project_id)
+ ->eq('is_active', 1)
+ ->groupBy('column_id')
+ ->listing('column_id', 'COUNT(*) AS total');
+
+ return $prepend ? array(-1 => t('All columns')) + $listing : $listing;
}
/**
@@ -287,11 +290,13 @@ class Board extends Base
*
* @access public
* @param integer $project_id Project id
+ * @param boolean $prepend Prepend a default value
* @return array
*/
- public function getColumnsList($project_id)
+ public function getColumnsList($project_id, $prepend = false)
{
- return $this->db->table(self::TABLE)->eq('project_id', $project_id)->asc('position')->listing('id', 'title');
+ $listing = $this->db->table(self::TABLE)->eq('project_id', $project_id)->asc('position')->listing('id', 'title');
+ return $prepend ? array(-1 => t('All columns')) + $listing : $listing;
}
/**
diff --git a/app/Model/Color.php b/app/Model/Color.php
index 8668cf0f..241a97c7 100644
--- a/app/Model/Color.php
+++ b/app/Model/Color.php
@@ -3,7 +3,7 @@
namespace Model;
/**
- * Color model (TODO: model for the future color picker)
+ * Color model
*
* @package model
* @author Frederic Guillot
@@ -11,14 +11,60 @@ namespace Model;
class Color extends Base
{
/**
+ * Default colors
+ *
+ * @access private
+ * @var array
+ */
+ private $default_colors = array(
+ 'yellow' => array(
+ 'name' => 'Yellow',
+ 'background' => 'rgb(245, 247, 196)',
+ 'border' => 'rgb(223, 227, 45)',
+ ),
+ 'blue' => array(
+ 'name' => 'Blue',
+ 'background' => 'rgb(219, 235, 255)',
+ 'border' => 'rgb(168, 207, 255)',
+ ),
+ 'green' => array(
+ 'name' => 'Green',
+ 'background' => 'rgb(189, 244, 203)',
+ 'border' => 'rgb(74, 227, 113)',
+ ),
+ 'purple' => array(
+ 'name' => 'Purple',
+ 'background' => 'rgb(223, 176, 255)',
+ 'border' => 'rgb(205, 133, 254)',
+ ),
+ 'red' => array(
+ 'name' => 'Red',
+ 'background' => 'rgb(255, 187, 187)',
+ 'border' => 'rgb(255, 151, 151)',
+ ),
+ 'orange' => array(
+ 'name' => 'Orange',
+ 'background' => 'rgb(255, 215, 179)',
+ 'border' => 'rgb(255, 172, 98)',
+ ),
+ 'grey' => array(
+ 'name' => 'Grey',
+ 'background' => 'rgb(238, 238, 238)',
+ 'border' => 'rgb(204, 204, 204)',
+ ),
+ );
+
+ /**
* Get available colors
*
* @access public
* @return array
*/
- public function getList()
+ public function getList($prepend = false)
{
- return array(
+ $listing = $prepend ? array('' => t('All colors')) : array();
+
+ return $listing + array(
'yellow' => t('Yellow'),
'blue' => t('Blue'),
'green' => t('Green'),
@@ -39,4 +85,57 @@ class Color extends Base
{
return 'yellow'; // TODO: make this parameter configurable
}
+
+ /**
+ * Get Bordercolor from string
+ *
+ * @access public
+ * @param string $color_id Color id
+ * @return string
+ */
+ public function getBorderColor($color_id)
+ {
+ if (isset($this->default_colors[$color_id])) {
+ return $this->default_colors[$color_id]['border'];
+ }
+
+ return $this->default_colors[$this->getDefaultColor()]['border'];
+ }
+
+ /**
+ * Get background color from the color_id
+ *
+ * @access public
+ * @param string $color_id Color id
+ * @return string
+ */
+ public function getBackgroundColor($color_id)
+ {
+ if (isset($this->default_colors[$color_id])) {
+ return $this->default_colors[$color_id]['background'];
+ }
+
+ return $this->default_colors[$this->getDefaultColor()]['background'];
+ }
+
+ /**
+ * Get CSS stylesheet of all colors
+ *
+ * @access public
+ * @return string
+ */
+ public function getCss()
+ {
+ $buffer = '';
+
+ foreach ($this->default_colors as $color => $values) {
+ $buffer .= 'td.color-'.$color.',';
+ $buffer .= 'div.color-'.$color.' {';
+ $buffer .= 'background-color: '.$values['background'].';';
+ $buffer .= 'border-color: '.$values['border'];
+ $buffer .= '}';
+ }
+
+ return $buffer;
+ }
}
diff --git a/app/Model/DateParser.php b/app/Model/DateParser.php
index 518a4f3f..53fc9b76 100644
--- a/app/Model/DateParser.php
+++ b/app/Model/DateParser.php
@@ -99,6 +99,18 @@ class DateParser extends Base
}
/**
+ * Get a timetstamp from an ISO date format
+ *
+ * @access public
+ * @param string $date Date format
+ * @return integer
+ */
+ public function getTimestampFromIsoFormat($date)
+ {
+ return $this->resetDateToMidnight(strtotime($date));
+ }
+
+ /**
* Format date (form display)
*
* @access public
diff --git a/app/Model/Project.php b/app/Model/Project.php
index f9c5c39c..a9693fbb 100644
--- a/app/Model/Project.php
+++ b/app/Model/Project.php
@@ -95,7 +95,7 @@ class Project extends Base
}
/**
- * Get all projects, optionaly fetch stats for each project and can check users permissions
+ * Get all projects
*
* @access public
* @param bool $filter_permissions If true, remove projects not allowed for the current user
@@ -188,7 +188,7 @@ class Project extends Base
* @param integer $project_id Project id
* @return array
*/
- public function getStats($project_id)
+ public function getTaskStats($project_id)
{
$stats = array();
$stats['nb_active_tasks'] = 0;
@@ -208,6 +208,60 @@ class Project extends Base
}
/**
+ * Get stats for each column of a project
+ *
+ * @access public
+ * @param array $project
+ * @return array
+ */
+ public function getColumnStats(array &$project)
+ {
+ $project['columns'] = $this->board->getColumns($project['id']);
+ $stats = $this->board->getColumnStats($project['id']);
+
+ foreach ($project['columns'] as &$column) {
+ $column['nb_tasks'] = isset($stats[$column['id']]) ? $stats[$column['id']] : 0;
+ }
+
+ return $project;
+ }
+
+ /**
+ * Apply column stats to a collection of projects (filter callback)
+ *
+ * @access public
+ * @param array $projects
+ * @return array
+ */
+ public function applyColumnStats(array $projects)
+ {
+ foreach ($projects as &$project) {
+ $this->getColumnStats($project);
+ }
+
+ return $projects;
+ }
+
+ /**
+ * Get project summary for a list of project
+ *
+ * @access public
+ * @param array $project_ids List of project id
+ * @return \PicoDb\Table
+ */
+ public function getQueryColumnStats(array $project_ids)
+ {
+ if (empty($project_ids)) {
+ return $this->db->table(Project::TABLE)->limit(0);
+ }
+
+ return $this->db
+ ->table(Project::TABLE)
+ ->in('id', $project_ids)
+ ->filter(array($this, 'applyColumnStats'));
+ }
+
+ /**
* Create a project from another one.
*
* @author Antonio Rabelo
diff --git a/app/Model/ProjectPaginator.php b/app/Model/ProjectPaginator.php
deleted file mode 100644
index 68b216b1..00000000
--- a/app/Model/ProjectPaginator.php
+++ /dev/null
@@ -1,50 +0,0 @@
-<?php
-
-namespace Model;
-
-/**
- * Project Paginator
- *
- * @package model
- * @author Frederic Guillot
- */
-class ProjectPaginator extends Base
-{
- /**
- * Get project summary for a list of project (number of tasks for each column)
- *
- * @access public
- * @param array $project_ids List of project id
- * @param integer $offset Offset
- * @param integer $limit Limit
- * @param string $column Sorting column
- * @param string $direction Sorting direction
- * @return array
- */
- public function projectSummaries(array $project_ids, $offset = 0, $limit = 25, $column = 'name', $direction = 'asc')
- {
- if (empty($project_ids)) {
- return array();
- }
-
- $projects = $this->db
- ->table(Project::TABLE)
- ->in('id', $project_ids)
- ->offset($offset)
- ->limit($limit)
- ->orderBy($column, $direction)
- ->findAll();
-
- foreach ($projects as &$project) {
-
- $project['columns'] = $this->board->getColumns($project['id']);
- $stats = $this->board->getColumnStats($project['id']);
-
- foreach ($project['columns'] as &$column) {
- $column['nb_tasks'] = isset($stats[$column['id']]) ? $stats[$column['id']] : 0;
- }
- }
-
- return $projects;
- }
-}
diff --git a/app/Model/SubTask.php b/app/Model/SubTask.php
index 1c5d1bf0..e5b03ab0 100644
--- a/app/Model/SubTask.php
+++ b/app/Model/SubTask.php
@@ -66,21 +66,15 @@ class SubTask extends Base
}
/**
- * Get all subtasks for a given task
+ * Add subtask status status to the resultset
*
* @access public
- * @param integer $task_id Task id
+ * @param array $subtasks Subtasks
* @return array
*/
- public function getAll($task_id)
+ public function addStatusName(array $subtasks)
{
$status = $this->getStatusList();
- $subtasks = $this->db->table(self::TABLE)
- ->eq('task_id', $task_id)
- ->columns(self::TABLE.'.*', User::TABLE.'.username', User::TABLE.'.name')
- ->join(User::TABLE, 'id', 'user_id')
- ->asc(self::TABLE.'.id')
- ->findAll();
foreach ($subtasks as &$subtask) {
$subtask['status_name'] = $status[$subtask['status']];
@@ -90,6 +84,49 @@ class SubTask extends Base
}
/**
+ * Get the query to fetch subtasks assigned to a user
+ *
+ * @access public
+ * @param integer $user_id User id
+ * @param array $status List of status
+ * @return \PicoDb\Table
+ */
+ public function getUserQuery($user_id, array $status)
+ {
+ return $this->db->table(SubTask::TABLE)
+ ->columns(
+ SubTask::TABLE.'.*',
+ Task::TABLE.'.project_id',
+ Task::TABLE.'.color_id',
+ Project::TABLE.'.name AS project_name'
+ )
+ ->eq('user_id', $user_id)
+ ->in(SubTask::TABLE.'.status', $status)
+ ->join(Task::TABLE, 'id', 'task_id')
+ ->join(Project::TABLE, 'id', 'project_id', Task::TABLE)
+ ->filter(array($this, 'addStatusName'));
+ }
+
+ /**
+ * Get all subtasks for a given task
+ *
+ * @access public
+ * @param integer $task_id Task id
+ * @return array
+ */
+ public function getAll($task_id)
+ {
+ return $this->db
+ ->table(self::TABLE)
+ ->eq('task_id', $task_id)
+ ->columns(self::TABLE.'.*', User::TABLE.'.username', User::TABLE.'.name')
+ ->join(User::TABLE, 'id', 'user_id')
+ ->asc(self::TABLE.'.id')
+ ->filter(array($this, 'addStatusName'))
+ ->findAll();
+ }
+
+ /**
* Get a subtask by the id
*
* @access public
@@ -101,18 +138,13 @@ class SubTask extends Base
{
if ($more) {
- $subtask = $this->db->table(self::TABLE)
- ->eq(self::TABLE.'.id', $subtask_id)
- ->columns(self::TABLE.'.*', User::TABLE.'.username', User::TABLE.'.name')
- ->join(User::TABLE, 'id', 'user_id')
- ->findOne();
-
- if ($subtask) {
- $status = $this->getStatusList();
- $subtask['status_name'] = $status[$subtask['status']];
- }
-
- return $subtask;
+ return $this->db
+ ->table(self::TABLE)
+ ->eq(self::TABLE.'.id', $subtask_id)
+ ->columns(self::TABLE.'.*', User::TABLE.'.username', User::TABLE.'.name')
+ ->join(User::TABLE, 'id', 'user_id')
+ ->filter(array($this, 'addStatusName'))
+ ->findOne();
}
return $this->db->table(self::TABLE)->eq('id', $subtask_id)->findOne();
diff --git a/app/Model/SubtaskExport.php b/app/Model/SubtaskExport.php
index 50b028e2..672c50c6 100644
--- a/app/Model/SubtaskExport.php
+++ b/app/Model/SubtaskExport.php
@@ -86,9 +86,9 @@ class SubtaskExport extends Base
* Get all subtasks for a given project
*
* @access public
- * @param integer $task_id Task id
- * @param mixed $from Start date (timestamp or user formatted date)
- * @param mixed $to End date (timestamp or user formatted date)
+ * @param integer $project_id Project id
+ * @param mixed $from Start date (timestamp or user formatted date)
+ * @param mixed $to End date (timestamp or user formatted date)
* @return array
*/
public function getSubtasks($project_id, $from, $to)
diff --git a/app/Model/SubtaskPaginator.php b/app/Model/SubtaskPaginator.php
deleted file mode 100644
index 8ccbd696..00000000
--- a/app/Model/SubtaskPaginator.php
+++ /dev/null
@@ -1,68 +0,0 @@
-<?php
-
-namespace Model;
-
-/**
- * Subtask Paginator
- *
- * @package model
- * @author Frederic Guillot
- */
-class SubtaskPaginator extends Base
-{
- /**
- * Get all subtasks assigned to a user
- *
- * @access public
- * @param integer $user_id User id
- * @param array $status List of status
- * @param integer $offset Offset
- * @param integer $limit Limit
- * @param string $column Sorting column
- * @param string $direction Sorting direction
- * @return array
- */
- public function userSubtasks($user_id, array $status, $offset = 0, $limit = 25, $column = 'tasks.id', $direction = 'asc')
- {
- $status_list = $this->subTask->getStatusList();
-
- $subtasks = $this->db->table(SubTask::TABLE)
- ->columns(
- SubTask::TABLE.'.*',
- Task::TABLE.'.project_id',
- Task::TABLE.'.color_id',
- Project::TABLE.'.name AS project_name'
- )
- ->eq('user_id', $user_id)
- ->in(SubTask::TABLE.'.status', $status)
- ->join(Task::TABLE, 'id', 'task_id')
- ->join(Project::TABLE, 'id', 'project_id', Task::TABLE)
- ->offset($offset)
- ->limit($limit)
- ->orderBy($column, $direction)
- ->findAll();
-
- foreach ($subtasks as &$subtask) {
- $subtask['status_name'] = $status_list[$subtask['status']];
- }
-
- return $subtasks;
- }
-
- /**
- * Count all subtasks assigned to the user
- *
- * @access public
- * @param integer $user_id User id
- * @param array $status List of status
- * @return integer
- */
- public function countUserSubtasks($user_id, array $status)
- {
- return $this->db
- ->table(SubTask::TABLE)
- ->eq('user_id', $user_id)
- ->in('status', $status)
- ->count();
- }
-}
diff --git a/app/Model/Swimlane.php b/app/Model/Swimlane.php
index 069f14b6..7a88cec1 100644
--- a/app/Model/Swimlane.php
+++ b/app/Model/Swimlane.php
@@ -161,20 +161,20 @@ class Swimlane extends Base
*
* @access public
* @param integer $project_id Project id
+ * @param boolean $prepend Prepend default value
* @return array
*/
- public function getSwimlanesList($project_id)
+ public function getList($project_id, $prepend = false)
{
- $swimlanes = $this->db->table(self::TABLE)
- ->eq('project_id', $project_id)
- ->orderBy('position', 'asc')
- ->listing('id', 'name');
+ $swimlanes = array();
+ $swimlanes[] = $this->db->table(Project::TABLE)->eq('id', $project_id)->findOneColumn('default_swimlane');
- $swimlanes[0] = $this->db->table(Project::TABLE)
- ->eq('id', $project_id)
- ->findOneColumn('default_swimlane');
+ $swimlanes = array_merge(
+ $swimlanes,
+ $this->db->table(self::TABLE)->eq('project_id', $project_id)->orderBy('name', 'asc')->listing('id', 'name')
+ );
- return $swimlanes;
+ return $prepend ? array(-1 => t('All swimlanes')) + $swimlanes : $swimlanes;
}
/**
diff --git a/app/Model/TaskExport.php b/app/Model/TaskExport.php
index 1592deb1..9545d062 100644
--- a/app/Model/TaskExport.php
+++ b/app/Model/TaskExport.php
@@ -24,7 +24,7 @@ class TaskExport extends Base
public function export($project_id, $from, $to)
{
$tasks = $this->getTasks($project_id, $from, $to);
- $swimlanes = $this->swimlane->getSwimlanesList($project_id);
+ $swimlanes = $this->swimlane->getList($project_id);
$results = array($this->getColumns());
foreach ($tasks as &$task) {
diff --git a/app/Model/TaskFilter.php b/app/Model/TaskFilter.php
new file mode 100644
index 00000000..eac90aab
--- /dev/null
+++ b/app/Model/TaskFilter.php
@@ -0,0 +1,117 @@
+<?php
+
+namespace Model;
+
+/**
+ * Task Filter
+ *
+ * @package model
+ * @author Frederic Guillot
+ */
+class TaskFilter extends Base
+{
+ private $query;
+
+ public function create()
+ {
+ $this->query = $this->db->table(Task::TABLE);
+ return $this;
+ }
+
+ public function filterByProject($project_id)
+ {
+ if ($project_id > 0) {
+ $this->query->eq('project_id', $project_id);
+ }
+
+ return $this;
+ }
+
+ public function filterByCategory($category_id)
+ {
+ if ($category_id >= 0) {
+ $this->query->eq('category_id', $category_id);
+ }
+
+ return $this;
+ }
+
+ public function filterByOwner($owner_id)
+ {
+ if ($owner_id >= 0) {
+ $this->query->eq('owner_id', $owner_id);
+ }
+
+ return $this;
+ }
+
+ public function filterByColor($color_id)
+ {
+ if ($color_id !== '') {
+ $this->query->eq('color_id', $color_id);
+ }
+
+ return $this;
+ }
+
+ public function filterByColumn($column_id)
+ {
+ if ($column_id >= 0) {
+ $this->query->eq('column_id', $column_id);
+ }
+
+ return $this;
+ }
+
+ public function filterBySwimlane($swimlane_id)
+ {
+ if ($swimlane_id >= 0) {
+ $this->query->eq('swimlane_id', $swimlane_id);
+ }
+
+ return $this;
+ }
+
+ public function filterByStatus($is_active)
+ {
+ if ($is_active >= 0) {
+ $this->query->eq('is_active', $is_active);
+ }
+
+ return $this;
+ }
+
+ public function filterByDueDateRange($start, $end)
+ {
+ $this->query->gte('date_due', $this->dateParser->getTimestampFromIsoFormat($start));
+ $this->query->lte('date_due', $this->dateParser->getTimestampFromIsoFormat($end));
+
+ return $this;
+ }
+
+ public function findAll()
+ {
+ return $this->query->findAll();
+ }
+
+ public function toCalendarEvents()
+ {
+ $events = array();
+
+ foreach ($this->query->findAll() as $task) {
+ $events[] = array(
+ 'id' => $task['id'],
+ 'title' => t('#%d', $task['id']).' '.$task['title'],
+ 'start' => date('Y-m-d', $task['date_due']),
+ 'end' => date('Y-m-d', $task['date_due']),
+ 'allday' => true,
+ 'backgroundColor' => $this->color->getBackgroundColor($task['color_id']),
+ 'borderColor' => $this->color->getBorderColor($task['color_id']),
+ 'textColor' => 'black',
+ 'url' => $this->helper->url('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])),
+ );
+ }
+
+ return $events;
+ }
+}
diff --git a/app/Model/TaskFinder.php b/app/Model/TaskFinder.php
index eb86fe3e..9d517ac5 100644
--- a/app/Model/TaskFinder.php
+++ b/app/Model/TaskFinder.php
@@ -13,12 +13,66 @@ use PDO;
class TaskFinder extends Base
{
/**
- * Common request to fetch a list of tasks
+ * Get query for closed tasks
*
* @access public
+ * @param integer $project_id Project id
* @return \PicoDb\Table
*/
- public function getQuery()
+ public function getClosedTaskQuery($project_id)
+ {
+ return $this->getExtendedQuery()
+ ->eq('project_id', $project_id)
+ ->eq('is_active', Task::STATUS_CLOSED);
+ }
+
+ /**
+ * Get query for task search
+ *
+ * @access public
+ * @param integer $project_id Project id
+ * @param string $search Search terms
+ * @return \PicoDb\Table
+ */
+ public function getSearchQuery($project_id, $search)
+ {
+ return $this->getExtendedQuery()
+ ->eq('project_id', $project_id)
+ ->ilike('title', '%'.$search.'%');
+ }
+
+ /**
+ * Get query for assigned user tasks
+ *
+ * @access public
+ * @param integer $user_id User id
+ * @return \PicoDb\Table
+ */
+ public function getUserQuery($user_id)
+ {
+ return $this->db
+ ->table(Task::TABLE)
+ ->columns(
+ 'tasks.id',
+ 'tasks.title',
+ 'tasks.date_due',
+ 'tasks.date_creation',
+ 'tasks.project_id',
+ 'tasks.color_id',
+ 'projects.name AS project_name'
+ )
+ ->join(Project::TABLE, 'id', 'project_id')
+ ->eq('tasks.owner_id', $user_id)
+ ->eq('tasks.is_active', Task::STATUS_OPEN);
+ }
+
+ /**
+ * Extended query
+ *
+ * @access public
+ * @return \PicoDb\Table
+ */
+ public function getExtendedQuery()
{
return $this->db
->table(Task::TABLE)
@@ -62,7 +116,7 @@ class TaskFinder extends Base
*/
public function getTasksByColumnAndSwimlane($project_id, $column_id, $swimlane_id = 0)
{
- return $this->getQuery()
+ return $this->getExtendedQuery()
->eq('project_id', $project_id)
->eq('column_id', $column_id)
->eq('swimlane_id', $swimlane_id)
diff --git a/app/Model/TaskPaginator.php b/app/Model/TaskPaginator.php
deleted file mode 100644
index e8109229..00000000
--- a/app/Model/TaskPaginator.php
+++ /dev/null
@@ -1,138 +0,0 @@
-<?php
-
-namespace Model;
-
-/**
- * Task Paginator model
- *
- * @package model
- * @author Frederic Guillot
- */
-class TaskPaginator extends Base
-{
- /**
- * Task search with pagination
- *
- * @access public
- * @param integer $project_id Project id
- * @param string $search Search terms
- * @param integer $offset Offset
- * @param integer $limit Limit
- * @param string $column Sorting column
- * @param string $direction Sorting direction
- * @return array
- */
- public function searchTasks($project_id, $search, $offset = 0, $limit = 25, $column = 'tasks.id', $direction = 'DESC')
- {
- return $this->taskFinder->getQuery()
- ->eq('project_id', $project_id)
- ->ilike('title', '%'.$search.'%')
- ->offset($offset)
- ->limit($limit)
- ->orderBy($column, $direction)
- ->findAll();
- }
-
- /**
- * Count the number of tasks for a custom search
- *
- * @access public
- * @param integer $project_id Project id
- * @param string $search Search terms
- * @return integer
- */
- public function countSearchTasks($project_id, $search)
- {
- return $this->db->table(Task::TABLE)
- ->eq('project_id', $project_id)
- ->ilike('title', '%'.$search.'%')
- ->count();
- }
-
- /**
- * Get all completed tasks with pagination
- *
- * @access public
- * @param integer $project_id Project id
- * @param integer $offset Offset
- * @param integer $limit Limit
- * @param string $column Sorting column
- * @param string $direction Sorting direction
- * @return array
- */
- public function closedTasks($project_id, $offset = 0, $limit = 25, $column = 'tasks.date_completed', $direction = 'DESC')
- {
- return $this->taskFinder->getQuery()
- ->eq('project_id', $project_id)
- ->eq('is_active', Task::STATUS_CLOSED)
- ->offset($offset)
- ->limit($limit)
- ->orderBy($column, $direction)
- ->findAll();
- }
-
- /**
- * Count all closed tasks
- *
- * @access public
- * @param integer $project_id Project id
- * @return integer
- */
- public function countClosedTasks($project_id)
- {
- return $this->db
- ->table(Task::TABLE)
- ->eq('project_id', $project_id)
- ->eq('is_active', Task::STATUS_CLOSED)
- ->count();
- }
-
- /**
- * Get all open tasks for a given user
- *
- * @access public
- * @param integer $user_id User id
- * @param integer $offset Offset
- * @param integer $limit Limit
- * @param string $column Sorting column
- * @param string $direction Sorting direction
- * @return array
- */
- public function userTasks($user_id, $offset = 0, $limit = 25, $column = 'tasks.id', $direction = 'ASC')
- {
- return $this->db
- ->table(Task::TABLE)
- ->columns(
- 'tasks.id',
- 'tasks.title',
- 'tasks.date_due',
- 'tasks.date_creation',
- 'tasks.project_id',
- 'tasks.color_id',
- 'projects.name AS project_name'
- )
- ->join(Project::TABLE, 'id', 'project_id')
- ->eq('tasks.owner_id', $user_id)
- ->eq('tasks.is_active', Task::STATUS_OPEN)
- ->offset($offset)
- ->limit($limit)
- ->orderBy($column, $direction)
- ->findAll();
- }
-
- /**
- * Count all tasks assigned to the user
- *
- * @access public
- * @param integer $user_id User id
- * @return integer
- */
- public function countUserTasks($user_id)
- {
- return $this->db
- ->table(Task::TABLE)
- ->eq('owner_id', $user_id)
- ->eq('is_active', Task::STATUS_OPEN)
- ->count();
- }
-}
diff --git a/app/Model/TaskStatus.php b/app/Model/TaskStatus.php
index 225b3933..30a65e1e 100644
--- a/app/Model/TaskStatus.php
+++ b/app/Model/TaskStatus.php
@@ -13,6 +13,23 @@ use Event\TaskEvent;
class TaskStatus extends Base
{
/**
+ * Return the list of statuses
+ *
+ * @access public
+ * @param boolean $prepend Prepend default value
+ * @return array
+ */
+ public function getList($prepend = false)
+ {
+ $listing = $prepend ? array(-1 => t('All status')) : array();
+
+ return $listing + array(
+ Task::STATUS_OPEN => t('Open'),
+ Task::STATUS_CLOSED => t('Closed'),
+ );
+ }
+
+ /**
* Return true if the task is closed
*
* @access public
diff --git a/app/Model/User.php b/app/Model/User.php
index 1bcc82b5..01be8597 100644
--- a/app/Model/User.php
+++ b/app/Model/User.php
@@ -29,6 +29,30 @@ class User extends Base
const EVERYBODY_ID = -1;
/**
+ * Get query to fetch all users
+ *
+ * @access public
+ * @return \PicoDb\Table
+ */
+ public function getQuery()
+ {
+ return $this->db
+ ->table(self::TABLE)
+ ->columns(
+ 'id',
+ 'username',
+ 'name',
+ 'email',
+ 'is_admin',
+ 'default_project_id',
+ 'is_ldap_user',
+ 'notifications_enabled',
+ 'google_id',
+ 'github_id'
+ );
+ }
+
+ /**
* Return the full name
*
* @param array $user User properties
@@ -112,54 +136,7 @@ class User extends Base
*/
public function getAll()
{
- return $this->db
- ->table(self::TABLE)
- ->asc('username')
- ->columns(
- 'id',
- 'username',
- 'name',
- 'email',
- 'is_admin',
- 'default_project_id',
- 'is_ldap_user',
- 'notifications_enabled',
- 'google_id',
- 'github_id'
- )
- ->findAll();
- }
-
- /**
- * Get all users with pagination
- *
- * @access public
- * @param integer $offset Offset
- * @param integer $limit Limit
- * @param string $column Sorting column
- * @param string $direction Sorting direction
- * @return array
- */
- public function paginate($offset = 0, $limit = 25, $column = 'username', $direction = 'ASC')
- {
- return $this->db
- ->table(self::TABLE)
- ->columns(
- 'id',
- 'username',
- 'name',
- 'email',
- 'is_admin',
- 'default_project_id',
- 'is_ldap_user',
- 'notifications_enabled',
- 'google_id',
- 'github_id'
- )
- ->offset($offset)
- ->limit($limit)
- ->orderBy($column, $direction)
- ->findAll();
+ return $this->getQuery()->asc('username')->findAll();
}
/**
diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php
index 3177276a..8ff00137 100644
--- a/app/ServiceProvider/ClassProvider.php
+++ b/app/ServiceProvider/ClassProvider.php
@@ -2,6 +2,7 @@
namespace ServiceProvider;
+use Core\Paginator;
use Model\Config;
use Model\Project;
use Model\Webhook;
@@ -28,10 +29,8 @@ class ClassProvider implements ServiceProviderInterface
'ProjectActivity',
'ProjectAnalytic',
'ProjectDailySummary',
- 'ProjectPaginator',
'ProjectPermission',
'SubTask',
- 'SubtaskPaginator',
'SubtaskExport',
'Swimlane',
'Task',
@@ -39,8 +38,8 @@ class ClassProvider implements ServiceProviderInterface
'TaskDuplication',
'TaskExport',
'TaskFinder',
+ 'TaskFilter',
'TaskModification',
- 'TaskPaginator',
'TaskPermission',
'TaskPosition',
'TaskStatus',
@@ -51,10 +50,12 @@ class ClassProvider implements ServiceProviderInterface
'Webhook',
),
'Core' => array(
+ 'Helper',
'Template',
'Session',
'MemoryCache',
'FileCache',
+ 'Request',
),
'Integration' => array(
'GitlabWebhook',
@@ -75,5 +76,9 @@ class ClassProvider implements ServiceProviderInterface
};
}
}
+
+ $container['paginator'] = $container->factory(function ($c) {
+ return new Paginator($c);
+ });
}
}
diff --git a/app/Subscriber/Base.php b/app/Subscriber/Base.php
index abc051b3..4616b179 100644
--- a/app/Subscriber/Base.php
+++ b/app/Subscriber/Base.php
@@ -11,15 +11,19 @@ use Pimple\Container;
* @author Frederic Guillot
*
* @property \Model\Config $config
+ * @property \Model\Comment $comment
+ * @property \Model\LastLogin $lastLogin
* @property \Model\Notification $notification
* @property \Model\Project $project
* @property \Model\ProjectPermission $projectPermission
* @property \Model\ProjectAnalytic $projectAnalytic
* @property \Model\ProjectDailySummary $projectDailySummary
+ * @property \Model\SubTask $subTask
* @property \Model\Task $task
* @property \Model\TaskExport $taskExport
* @property \Model\TaskFinder $taskFinder
* @property \Model\UserSession $userSession
+ * @property \Model\Webhook $webhook
*/
abstract class Base
{
diff --git a/app/Template/app/dashboard.php b/app/Template/app/dashboard.php
index 81305ed8..592621a6 100644
--- a/app/Template/app/dashboard.php
+++ b/app/Template/app/dashboard.php
@@ -14,9 +14,9 @@
</div>
<section id="dashboard">
<div class="dashboard-left-column">
- <?= $this->render('app/projects', array('projects' => $projects, 'pagination' => $project_pagination)) ?>
- <?= $this->render('app/tasks', array('tasks' => $tasks, 'pagination' => $task_pagination)) ?>
- <?= $this->render('app/subtasks', array('subtasks' => $subtasks, 'pagination' => $subtask_pagination)) ?>
+ <?= $this->render('app/projects', array('paginator' => $project_paginator)) ?>
+ <?= $this->render('app/tasks', array('paginator' => $task_paginator)) ?>
+ <?= $this->render('app/subtasks', array('paginator' => $subtask_paginator)) ?>
</div>
<div class="dashboard-right-column">
<h2><?= t('Activity stream') ?></h2>
diff --git a/app/Template/app/projects.php b/app/Template/app/projects.php
index 409697ac..4740c4b8 100644
--- a/app/Template/app/projects.php
+++ b/app/Template/app/projects.php
@@ -1,14 +1,14 @@
<h2><?= t('My projects') ?></h2>
-<?php if (empty($projects)): ?>
+<?php if ($paginator->isEmpty()): ?>
<p class="alert"><?= t('Your are not member of any project.') ?></p>
<?php else: ?>
<table class="table-fixed">
<tr>
- <th class="column-8"><?= $this->order('Id', 'id', $pagination) ?></th>
- <th class="column-20"><?= $this->order(t('Project'), 'name', $pagination) ?></th>
+ <th class="column-8"><?= $paginator->order('Id', 'id') ?></th>
+ <th class="column-20"><?= $paginator->order(t('Project'), 'name') ?></th>
<th><?= t('Columns') ?></th>
</tr>
- <?php foreach ($projects as $project): ?>
+ <?php foreach ($paginator->getCollection() as $project): ?>
<tr>
<td>
<?= $this->a('#'.$project['id'], 'board', 'show', array('project_id' => $project['id']), false, 'dashboard-table-link') ?>
@@ -17,6 +17,8 @@
<?php if ($this->isManager($project['id'])): ?>
<?= $this->a('<i class="fa fa-cog"></i>', 'project', 'show', array('project_id' => $project['id']), false, 'dashboard-table-link', t('Settings')) ?>&nbsp;
<?php endif ?>
+
+ <?= $this->a('<i class="fa fa-calendar"></i>', 'calendar', 'show', array('project_id' => $project['id']), false, 'dashboard-table-link', t('Calendar')) ?>&nbsp;
<?= $this->a($this->e($project['name']), 'board', 'show', array('project_id' => $project['id'])) ?>
</td>
<td class="dashboard-project-stats">
@@ -29,5 +31,5 @@
<?php endforeach ?>
</table>
- <?= $this->paginate($pagination) ?>
+ <?= $paginator ?>
<?php endif ?> \ No newline at end of file
diff --git a/app/Template/app/subtasks.php b/app/Template/app/subtasks.php
index 7081b174..75320027 100644
--- a/app/Template/app/subtasks.php
+++ b/app/Template/app/subtasks.php
@@ -1,17 +1,17 @@
<h2><?= t('My subtasks') ?></h2>
-<?php if (empty($subtasks)): ?>
+<?php if ($paginator->isEmpty()): ?>
<p class="alert"><?= t('There is nothing assigned to you.') ?></p>
<?php else: ?>
<table class="table-fixed">
<tr>
- <th class="column-10"><?= $this->order('Id', 'tasks.id', $pagination) ?></th>
- <th class="column-20"><?= $this->order(t('Project'), 'project_name', $pagination) ?></th>
- <th class="column-15"><?= $this->order(t('Status'), 'status', $pagination) ?></th>
- <th><?= $this->order(t('Subtask'), 'title', $pagination) ?></th>
+ <th class="column-10"><?= $paginator->order('Id', 'tasks.id') ?></th>
+ <th class="column-20"><?= $paginator->order(t('Project'), 'project_name') ?></th>
+ <th class="column-15"><?= $paginator->order(t('Status'), 'status') ?></th>
+ <th><?= $paginator->order(t('Subtask'), 'title') ?></th>
</tr>
- <?php foreach ($subtasks as $subtask): ?>
+ <?php foreach ($paginator->getCollection() as $subtask): ?>
<tr>
- <td class="task-table task-<?= $subtask['color_id'] ?>">
+ <td class="task-table color-<?= $subtask['color_id'] ?>">
<?= $this->a('#'.$subtask['task_id'], 'task', 'show', array('task_id' => $subtask['task_id'], 'project_id' => $subtask['project_id'])) ?>
</td>
<td>
@@ -27,5 +27,5 @@
<?php endforeach ?>
</table>
- <?= $this->paginate($pagination) ?>
+ <?= $paginator ?>
<?php endif ?> \ No newline at end of file
diff --git a/app/Template/app/tasks.php b/app/Template/app/tasks.php
index a3d78a9d..37843af5 100644
--- a/app/Template/app/tasks.php
+++ b/app/Template/app/tasks.php
@@ -1,17 +1,17 @@
<h2><?= t('My tasks') ?></h2>
-<?php if (empty($tasks)): ?>
+<?php if ($paginator->isEmpty()): ?>
<p class="alert"><?= t('There is nothing assigned to you.') ?></p>
<?php else: ?>
<table class="table-fixed">
<tr>
- <th class="column-8"><?= $this->order('Id', 'tasks.id', $pagination) ?></th>
- <th class="column-20"><?= $this->order(t('Project'), 'project_name', $pagination) ?></th>
- <th><?= $this->order(t('Task'), 'title', $pagination) ?></th>
- <th class="column-20"><?= $this->order(t('Due date'), 'date_due', $pagination) ?></th>
+ <th class="column-8"><?= $paginator->order('Id', 'tasks.id') ?></th>
+ <th class="column-20"><?= $paginator->order(t('Project'), 'project_name') ?></th>
+ <th><?= $paginator->order(t('Task'), 'title') ?></th>
+ <th class="column-20"><?= $paginator->order(t('Due date'), 'date_due') ?></th>
</tr>
- <?php foreach ($tasks as $task): ?>
+ <?php foreach ($paginator->getCollection() as $task): ?>
<tr>
- <td class="task-table task-<?= $task['color_id'] ?>">
+ <td class="task-table color-<?= $task['color_id'] ?>">
<?= $this->a('#'.$task['id'], 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
</td>
<td>
@@ -27,5 +27,5 @@
<?php endforeach ?>
</table>
- <?= $this->paginate($pagination) ?>
+ <?= $paginator ?>
<?php endif ?> \ No newline at end of file
diff --git a/app/Template/board/files.php b/app/Template/board/files.php
index fee0b63b..278b906b 100644
--- a/app/Template/board/files.php
+++ b/app/Template/board/files.php
@@ -6,7 +6,7 @@
$this->e($file['name']),
'file',
'download',
- array('file_id' => $file['id'], 'task_id' => $file['task_id'])
+ array('file_id' => $file['id'], 'task_id' => $file['task_id'], 'project_id' => $task['project_id'])
) ?>
<br/>
diff --git a/app/Template/board/filters.php b/app/Template/board/filters.php
index 36259820..45255231 100644
--- a/app/Template/board/filters.php
+++ b/app/Template/board/filters.php
@@ -12,7 +12,7 @@
<a href="#" id="filter-due-date"><?= t('Filter by due date') ?></a>
</li>
<li>
- <i class="fa fa-search"></i>
+ <i class="fa fa-search fa-fw"></i>
<?= $this->a(t('Search'), 'project', 'search', array('project_id' => $project['id'])) ?>
</li>
<li>
@@ -23,13 +23,19 @@
<i class="fa fa-dashboard fa-fw"></i>
<?= $this->a(t('Activity'), 'project', 'activity', array('project_id' => $project['id'])) ?>
</li>
+ <li>
+ <i class="fa fa-calendar fa-fw"></i>
+ <?= $this->a(t('Calendar'), 'calendar', 'show', array('project_id' => $project['id'])) ?>
+ </li>
<?php if ($this->acl->isManagerActionAllowed($project['id'])): ?>
<li>
<i class="fa fa-line-chart fa-fw"></i>
<?= $this->a(t('Analytics'), 'analytic', 'tasks', array('project_id' => $project['id'])) ?>
</li>
- <li><i class="fa fa-cog fa-fw"></i>
+ <li>
+ <i class="fa fa-cog fa-fw"></i>
<?= $this->a(t('Configure'), 'project', 'show', array('project_id' => $project['id'])) ?>
+ </li>
<?php endif ?>
</ul>
</div> \ No newline at end of file
diff --git a/app/Template/board/task.php b/app/Template/board/task.php
index 6700b693..66ddee60 100644
--- a/app/Template/board/task.php
+++ b/app/Template/board/task.php
@@ -1,6 +1,6 @@
<?php if ($not_editable): ?>
-<div class="task-board task-<?= $task['color_id'] ?> <?= $task['date_modification'] > time() - $board_highlight_period ? 'task-board-recent' : '' ?>">
+<div class="task-board color-<?= $task['color_id'] ?> <?= $task['date_modification'] > time() - $board_highlight_period ? 'task-board-recent' : '' ?>">
<?= $this->a('#'.$task['id'], 'task', 'readonly', array('task_id' => $task['id'], 'token' => $project['token'])) ?>
@@ -30,7 +30,7 @@
<?php else: ?>
-<div class="task-board draggable-item task-<?= $task['color_id'] ?> <?= $task['date_modification'] > time() - $board_highlight_period ? 'task-board-recent' : '' ?>"
+<div class="task-board draggable-item color-<?= $task['color_id'] ?> <?= $task['date_modification'] > time() - $board_highlight_period ? 'task-board-recent' : '' ?>"
data-task-id="<?= $task['id'] ?>"
data-owner-id="<?= $task['owner_id'] ?>"
data-category-id="<?= $task['category_id'] ?>"
diff --git a/app/Template/calendar/show.php b/app/Template/calendar/show.php
new file mode 100644
index 00000000..0a41fabe
--- /dev/null
+++ b/app/Template/calendar/show.php
@@ -0,0 +1,44 @@
+<section id="main">
+ <div class="page-header">
+ <ul>
+ <li>
+ <i class="fa fa-table fa-fw"></i>
+ <?= $this->a(t('Back to the board'), 'board', 'show', array('project_id' => $project['id'])) ?>
+ </li>
+ <li>
+ <i class="fa fa-search fa-fw"></i>
+ <?= $this->a(t('Search'), 'project', 'search', array('project_id' => $project['id'])) ?>
+ </li>
+ <li>
+ <i class="fa fa-check-square-o fa-fw"></i>
+ <?= $this->a(t('Completed tasks'), 'project', 'tasks', array('project_id' => $project['id'])) ?>
+ </li>
+ <li>
+ <i class="fa fa-dashboard fa-fw"></i>
+ <?= $this->a(t('Activity'), 'project', 'activity', array('project_id' => $project['id'])) ?>
+ </li>
+ </ul>
+ </div>
+ <section class="sidebar-container">
+
+ <?= $this->render('calendar/sidebar', array(
+ 'project' => $project,
+ 'users_list' => $users_list,
+ 'categories_list' => $categories_list,
+ 'columns_list' => $columns_list,
+ 'swimlanes_list' => $swimlanes_list,
+ 'colors_list' => $colors_list,
+ 'status_list' => $status_list
+ )) ?>
+
+ <div class="sidebar-content">
+ <div id="calendar"
+ data-save-url="<?= $this->u('calendar', 'save', array('project_id' => $project['id'])) ?>"
+ data-check-url="<?= $this->u('calendar', 'events', array('project_id' => $project['id'])) ?>"
+ data-check-interval="<?= $check_interval ?>"
+ data-translations='<?= $this->getCalendarTranslations() ?>'
+ >
+ </div>
+ </div>
+ </section>
+</section>
diff --git a/app/Template/calendar/sidebar.php b/app/Template/calendar/sidebar.php
new file mode 100644
index 00000000..599b2912
--- /dev/null
+++ b/app/Template/calendar/sidebar.php
@@ -0,0 +1,40 @@
+<div class="sidebar">
+ <ul class="no-bullet">
+ <li>
+ <?= t('Filter by user') ?>
+ </li>
+ <li>
+ <?= $this->formSelect('owner_id', $users_list, array(), array(), 'calendar-filter') ?>
+ </li>
+ <li>
+ <?= t('Filter by category') ?>
+ </li>
+ <li>
+ <?= $this->formSelect('category_id', $categories_list, array(), array(), 'calendar-filter') ?>
+ </li>
+ <li>
+ <?= t('Filter by column') ?>
+ </li>
+ <li>
+ <?= $this->formSelect('column_id', $columns_list, array(), array(), 'calendar-filter') ?>
+ </li>
+ <li>
+ <?= t('Filter by swimlane') ?>
+ </li>
+ <li>
+ <?= $this->formSelect('swimlane_id', $swimlanes_list, array(), array(), 'calendar-filter') ?>
+ </li>
+ <li>
+ <?= t('Filter by color') ?>
+ </li>
+ <li>
+ <?= $this->formSelect('color_id', $colors_list, array(), array(), 'calendar-filter') ?>
+ </li>
+ <li>
+ <?= t('Filter by status') ?>
+ </li>
+ <li>
+ <?= $this->formSelect('is_active', $status_list, array(), array(), 'calendar-filter') ?>
+ </li>
+ </ul>
+</div>
diff --git a/app/Template/layout.php b/app/Template/layout.php
index 9aa34905..283a85f8 100644
--- a/app/Template/layout.php
+++ b/app/Template/layout.php
@@ -14,6 +14,7 @@
<?= $this->js('assets/js/app.js') ?>
<?php endif ?>
+ <?= $this->css($this->u('app', 'colors'), false) ?>
<?= $this->css('assets/css/app.css') ?>
<link rel="icon" type="image/png" href="assets/img/favicon.png">
diff --git a/app/Template/project/activity.php b/app/Template/project/activity.php
index cb986658..bc4eb19c 100644
--- a/app/Template/project/activity.php
+++ b/app/Template/project/activity.php
@@ -1,9 +1,22 @@
<section id="main">
<div class="page-header">
<ul>
- <li><i class="fa fa-table fa-fw"></i><?= $this->a(t('Back to the board'), 'board', 'show', array('project_id' => $project['id'])) ?></li>
- <li><i class="fa fa-search fa-fw"></i><?= $this->a(t('Search'), 'project', 'search', array('project_id' => $project['id'])) ?></li>
- <li><i class="fa fa-check-square-o fa-fw"></i><?= $this->a(t('Completed tasks'), 'project', 'tasks', array('project_id' => $project['id'])) ?></li>
+ <li>
+ <i class="fa fa-table fa-fw"></i>
+ <?= $this->a(t('Back to the board'), 'board', 'show', array('project_id' => $project['id'])) ?>
+ </li>
+ <li>
+ <i class="fa fa-calendar fa-fw"></i>
+ <?= $this->a(t('Calendar'), 'calendar', 'show', array('project_id' => $project['id'])) ?>
+ </li>
+ <li>
+ <i class="fa fa-search fa-fw"></i>
+ <?= $this->a(t('Search'), 'project', 'search', array('project_id' => $project['id'])) ?>
+ </li>
+ <li>
+ <i class="fa fa-check-square-o fa-fw"></i>
+ <?= $this->a(t('Completed tasks'), 'project', 'tasks', array('project_id' => $project['id'])) ?>
+ </li>
<?php if ($project['is_public']): ?>
<li><i class="fa fa-rss-square fa-fw"></i><?= $this->a(t('RSS feed'), 'project', 'feed', array('token' => $project['token']), false, '', '', true) ?></li>
<?php endif ?>
diff --git a/app/Template/project/layout.php b/app/Template/project/layout.php
index ce154c19..8cd1fd3c 100644
--- a/app/Template/project/layout.php
+++ b/app/Template/project/layout.php
@@ -1,8 +1,14 @@
<section id="main">
<div class="page-header">
<ul>
- <li><i class="fa fa-table fa-fw"></i><?= $this->a(t('Back to the board'), 'board', 'show', array('project_id' => $project['id'])) ?></li>
- <li><i class="fa fa-folder fa-fw"></i><?= $this->a(t('All projects'), 'project', 'index') ?></li>
+ <li>
+ <i class="fa fa-table fa-fw"></i>
+ <?= $this->a(t('Back to the board'), 'board', 'show', array('project_id' => $project['id'])) ?>
+ </li>
+ <li>
+ <i class="fa fa-folder fa-fw"></i>
+ <?= $this->a(t('All projects'), 'project', 'index') ?>
+ </li>
</ul>
</div>
<section class="sidebar-container" id="project-section">
diff --git a/app/Template/project/search.php b/app/Template/project/search.php
index c8a3e410..47ba0f77 100644
--- a/app/Template/project/search.php
+++ b/app/Template/project/search.php
@@ -1,9 +1,22 @@
<section id="main">
<div class="page-header">
<ul>
- <li><i class="fa fa-table fa-fw"></i><?= $this->a(t('Back to the board'), 'board', 'show', array('project_id' => $project['id'])) ?></li>
- <li><i class="fa fa-check-square-o fa-fw"></i><?= $this->a(t('Completed tasks'), 'project', 'tasks', array('project_id' => $project['id'])) ?></li>
- <li><i class="fa fa-dashboard fa-fw"></i><?= $this->a(t('Activity'), 'project', 'activity', array('project_id' => $project['id'])) ?></li>
+ <li>
+ <i class="fa fa-table fa-fw"></i>
+ <?= $this->a(t('Back to the board'), 'board', 'show', array('project_id' => $project['id'])) ?>
+ </li>
+ <li>
+ <i class="fa fa-calendar fa-fw"></i>
+ <?= $this->a(t('Calendar'), 'calendar', 'show', array('project_id' => $project['id'])) ?>
+ </li>
+ <li>
+ <i class="fa fa-check-square-o fa-fw"></i>
+ <?= $this->a(t('Completed tasks'), 'project', 'tasks', array('project_id' => $project['id'])) ?>
+ </li>
+ <li>
+ <i class="fa fa-dashboard fa-fw"></i>
+ <?= $this->a(t('Activity'), 'project', 'activity', array('project_id' => $project['id'])) ?>
+ </li>
</ul>
</div>
<section>
@@ -15,14 +28,13 @@
<input type="submit" value="<?= t('Search') ?>" class="btn btn-blue"/>
</form>
- <?php if (empty($tasks) && ! empty($values['search'])): ?>
+ <?php if (! empty($values['search']) && $paginator->isEmpty()): ?>
<p class="alert"><?= t('Nothing found.') ?></p>
- <?php elseif (! empty($tasks)): ?>
+ <?php elseif (! $paginator->isEmpty()): ?>
<?= $this->render('task/table', array(
- 'tasks' => $tasks,
+ 'paginator' => $paginator,
'categories' => $categories,
'columns' => $columns,
- 'pagination' => $pagination,
)) ?>
<?php endif ?>
diff --git a/app/Template/project/tasks.php b/app/Template/project/tasks.php
index 6ca24acf..b22746f9 100644
--- a/app/Template/project/tasks.php
+++ b/app/Template/project/tasks.php
@@ -1,20 +1,32 @@
<section id="main">
<div class="page-header">
<ul>
- <li><i class="fa fa-table fa-fw"></i><?= $this->a(t('Back to the board'), 'board', 'show', array('project_id' => $project['id'])) ?></li>
- <li><i class="fa fa-search fa-fw"></i><?= $this->a(t('Search'), 'project', 'search', array('project_id' => $project['id'])) ?></li>
- <li><i class="fa fa-dashboard fa-fw"></i><?= $this->a(t('Activity'), 'project', 'activity', array('project_id' => $project['id'])) ?></li>
+ <li>
+ <i class="fa fa-table fa-fw"></i>
+ <?= $this->a(t('Back to the board'), 'board', 'show', array('project_id' => $project['id'])) ?>
+ </li>
+ <li>
+ <i class="fa fa-calendar fa-fw"></i>
+ <?= $this->a(t('Calendar'), 'calendar', 'show', array('project_id' => $project['id'])) ?>
+ </li>
+ <li>
+ <i class="fa fa-search fa-fw"></i>
+ <?= $this->a(t('Search'), 'project', 'search', array('project_id' => $project['id'])) ?>
+ </li>
+ <li>
+ <i class="fa fa-dashboard fa-fw"></i>
+ <?= $this->a(t('Activity'), 'project', 'activity', array('project_id' => $project['id'])) ?>
+ </li>
</ul>
</div>
<section>
- <?php if (empty($tasks)): ?>
+ <?php if ($paginator->isEmpty()): ?>
<p class="alert"><?= t('No task') ?></p>
<?php else: ?>
<?= $this->render('task/table', array(
- 'tasks' => $tasks,
+ 'paginator' => $paginator,
'categories' => $categories,
'columns' => $columns,
- 'pagination' => $pagination,
)) ?>
<?php endif ?>
</section>
diff --git a/app/Template/task/details.php b/app/Template/task/details.php
index 50145da4..3205514c 100644
--- a/app/Template/task/details.php
+++ b/app/Template/task/details.php
@@ -1,4 +1,4 @@
-<div class="task-<?= $task['color_id'] ?> task-show-details">
+<div class="color-<?= $task['color_id'] ?> task-show-details">
<h2><?= $this->e('#'.$task['id'].' '.$task['title']) ?></h2>
<?php if ($task['score']): ?>
<span class="task-score"><?= $this->e($task['score']) ?></span>
diff --git a/app/Template/task/layout.php b/app/Template/task/layout.php
index dd36903d..dd59a9fc 100644
--- a/app/Template/task/layout.php
+++ b/app/Template/task/layout.php
@@ -1,7 +1,14 @@
<section id="main">
<div class="page-header">
<ul>
- <li><i class="fa fa-table fa-fw"></i><?= $this->a(t('Back to the board'), 'board', 'show', array('project_id' => $task['project_id'])) ?></li>
+ <li>
+ <i class="fa fa-table fa-fw"></i>
+ <?= $this->a(t('Back to the board'), 'board', 'show', array('project_id' => $task['project_id'])) ?>
+ </li>
+ <li>
+ <i class="fa fa-calendar fa-fw"></i>
+ <?= $this->a(t('Calendar'), 'calendar', 'show', array('project_id' => $task['project_id'])) ?>
+ </li>
</ul>
</div>
<section class="sidebar-container" id="task-section">
diff --git a/app/Template/task/table.php b/app/Template/task/table.php
index 689cdcc4..4ccf83b9 100644
--- a/app/Template/task/table.php
+++ b/app/Template/task/table.php
@@ -1,18 +1,18 @@
<table class="table-fixed table-small">
<tr>
- <th class="column-8"><?= $this->order(t('Id'), 'tasks.id', $pagination) ?></th>
- <th class="column-8"><?= $this->order(t('Column'), 'tasks.column_id', $pagination) ?></th>
- <th class="column-8"><?= $this->order(t('Category'), 'tasks.category_id', $pagination) ?></th>
- <th><?= $this->order(t('Title'), 'tasks.title', $pagination) ?></th>
- <th class="column-10"><?= $this->order(t('Assignee'), 'users.username', $pagination) ?></th>
- <th class="column-10"><?= $this->order(t('Due date'), 'tasks.date_due', $pagination) ?></th>
- <th class="column-10"><?= $this->order(t('Date created'), 'tasks.date_creation', $pagination) ?></th>
- <th class="column-10"><?= $this->order(t('Date completed'), 'tasks.date_completed', $pagination) ?></th>
- <th class="column-5"><?= $this->order(t('Status'), 'tasks.is_active', $pagination) ?></th>
+ <th class="column-8"><?= $paginator->order(t('Id'), 'tasks.id') ?></th>
+ <th class="column-8"><?= $paginator->order(t('Column'), 'tasks.column_id') ?></th>
+ <th class="column-8"><?= $paginator->order(t('Category'), 'tasks.category_id') ?></th>
+ <th><?= $paginator->order(t('Title'), 'tasks.title') ?></th>
+ <th class="column-10"><?= $paginator->order(t('Assignee'), 'users.username') ?></th>
+ <th class="column-10"><?= $paginator->order(t('Due date'), 'tasks.date_due') ?></th>
+ <th class="column-10"><?= $paginator->order(t('Date created'), 'tasks.date_creation') ?></th>
+ <th class="column-10"><?= $paginator->order(t('Date completed'), 'tasks.date_completed') ?></th>
+ <th class="column-5"><?= $paginator->order(t('Status'), 'tasks.is_active') ?></th>
</tr>
- <?php foreach ($tasks as $task): ?>
+ <?php foreach ($paginator->getCollection() as $task): ?>
<tr>
- <td class="task-table task-<?= $task['color_id'] ?>">
+ <td class="task-table color-<?= $task['color_id'] ?>">
<?= $this->a('#'.$this->e($task['id']), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, '', t('View this task')) ?>
</td>
<td>
@@ -53,4 +53,4 @@
<?php endforeach ?>
</table>
-<?= $this->paginate($pagination) ?>
+<?= $paginator ?>
diff --git a/app/Template/user/index.php b/app/Template/user/index.php
index e4729501..41e205ba 100644
--- a/app/Template/user/index.php
+++ b/app/Template/user/index.php
@@ -7,22 +7,22 @@
<?php endif ?>
</div>
<section>
- <?php if (empty($users)): ?>
+ <?php if ($paginator->isEmpty()): ?>
<p class="alert"><?= t('No user') ?></p>
<?php else: ?>
<table>
<tr>
- <th><?= $this->order(t('Id'), 'id', $pagination) ?></th>
- <th><?= $this->order(t('Username'), 'username', $pagination) ?></th>
- <th><?= $this->order(t('Name'), 'name', $pagination) ?></th>
- <th><?= $this->order(t('Email'), 'email', $pagination) ?></th>
- <th><?= $this->order(t('Administrator'), 'is_admin', $pagination) ?></th>
- <th><?= $this->order(t('Default project'), 'default_project_id', $pagination) ?></th>
- <th><?= $this->order(t('Notifications'), 'notifications_enabled', $pagination) ?></th>
+ <th><?= $paginator->order(t('Id'), 'id') ?></th>
+ <th><?= $paginator->order(t('Username'), 'username') ?></th>
+ <th><?= $paginator->order(t('Name'), 'name') ?></th>
+ <th><?= $paginator->order(t('Email'), 'email') ?></th>
+ <th><?= $paginator->order(t('Administrator'), 'is_admin') ?></th>
+ <th><?= $paginator->order(t('Default project'), 'default_project_id') ?></th>
+ <th><?= $paginator->order(t('Notifications'), 'notifications_enabled') ?></th>
<th><?= t('External accounts') ?></th>
- <th><?= $this->order(t('Account type'), 'is_ldap_user', $pagination) ?></th>
+ <th><?= $paginator->order(t('Account type'), 'is_ldap_user') ?></th>
</tr>
- <?php foreach ($users as $user): ?>
+ <?php foreach ($paginator->getCollection() as $user): ?>
<tr>
<td>
<?= $this->a('#'.$user['id'], 'user', 'show', array('user_id' => $user['id'])) ?>
@@ -66,7 +66,7 @@
<?php endforeach ?>
</table>
- <?= $this->paginate($pagination) ?>
+ <?= $paginator ?>
<?php endif ?>
</section>
</section>
diff --git a/app/check_setup.php b/app/check_setup.php
index f0059215..afb08f6a 100644
--- a/app/check_setup.php
+++ b/app/check_setup.php
@@ -1,8 +1,8 @@
<?php
// PHP 5.3.3 minimum
-if (version_compare(PHP_VERSION, '5.3.7', '<')) {
- die('This software require PHP 5.3.7 minimum');
+if (version_compare(PHP_VERSION, '5.3.3', '<')) {
+ die('This software require PHP 5.3.3 minimum');
}
// Checks for PHP < 5.4