diff options
Diffstat (limited to 'app')
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.'&action='.$action; + $values = array( + 'controller' => $controller, + 'action' => $action, + ); if ($csrf) { $params['csrf_token'] = Security::getCSRFToken(); } - foreach ($params as $key => $value) { - $html .= '&'.$key.'='.$value; - } + $values += $params; - return $html; + return '?'.http_build_query($values, '', '&'); } /** - * 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('← '.t('Previous'), $controller, $action, $params + compact('offset', 'order', 'direction')); - } - else { - $html .= '← '.t('Previous'); - } - - $html .= '</span>'; - $html .= '<span class="pagination-next">'; - - if (($total - $pagination['offset']) > $limit) { - $offset = $pagination['offset'] + $limit; - $html .= $this->a(t('Next').' →', $controller, $action, $params + compact('offset', 'order', 'direction')); - } - else { - $html .= t('Next').' →'; - } - - $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' ? '▼ ' : '▲ '; - $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( + '← '.t('Previous'), + $this->controller, + $this->action, + $this->getUrlParams($this->page - 1, $this->order, $this->direction) + ); + } + else { + $html .= '← '.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').' →', + $this->controller, + $this->action, + $this->getUrlParams($this->page + 1, $this->order, $this->direction) + ); + } + else { + $html .= t('Next').' →'; + } + + $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' ? '▼ ' : '▲ '; + $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')) ?> <?php endif ?> + + <?= $this->a('<i class="fa fa-calendar"></i>', 'calendar', 'show', array('project_id' => $project['id']), false, 'dashboard-table-link', t('Calendar')) ?> <?= $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 |