diff options
Diffstat (limited to 'app/Helper')
-rw-r--r-- | app/Helper/AppHelper.php | 18 | ||||
-rw-r--r-- | app/Helper/BoardHelper.php | 3 | ||||
-rw-r--r-- | app/Helper/CalendarHelper.php | 26 | ||||
-rw-r--r-- | app/Helper/DateHelper.php | 2 | ||||
-rw-r--r-- | app/Helper/FileHelper.php | 29 | ||||
-rw-r--r-- | app/Helper/FormHelper.php | 115 | ||||
-rw-r--r-- | app/Helper/HookHelper.php | 55 | ||||
-rw-r--r-- | app/Helper/ICalHelper.php | 11 | ||||
-rw-r--r-- | app/Helper/LayoutHelper.php | 18 | ||||
-rw-r--r-- | app/Helper/ModalHelper.php | 83 | ||||
-rw-r--r-- | app/Helper/ProjectActivityHelper.php | 11 | ||||
-rw-r--r-- | app/Helper/ProjectRoleHelper.php | 286 | ||||
-rw-r--r-- | app/Helper/SubtaskHelper.php | 13 | ||||
-rw-r--r-- | app/Helper/TaskHelper.php | 119 | ||||
-rw-r--r-- | app/Helper/UrlHelper.php | 68 | ||||
-rw-r--r-- | app/Helper/UserHelper.php | 53 |
16 files changed, 736 insertions, 174 deletions
diff --git a/app/Helper/AppHelper.php b/app/Helper/AppHelper.php index 09f280cb..3b48d7d3 100644 --- a/app/Helper/AppHelper.php +++ b/app/Helper/AppHelper.php @@ -13,16 +13,28 @@ use Kanboard\Core\Base; class AppHelper extends Base { /** + * Render Javascript component + * + * @param string $name + * @param array $params + * @return string + */ + public function component($name, array $params = array()) + { + return '<div class="js-'.$name.'" data-params=\''.json_encode($params, JSON_HEX_APOS).'\'></div>'; + } + + /** * Get config variable * * @access public * @param string $param - * @param mixed $default_value + * @param mixed $default * @return mixed */ - public function config($param, $default_value = '') + public function config($param, $default = '') { - return $this->configModel->get($param, $default_value); + return $this->configModel->get($param, $default); } /** diff --git a/app/Helper/BoardHelper.php b/app/Helper/BoardHelper.php index a86a6c18..f5df3db2 100644 --- a/app/Helper/BoardHelper.php +++ b/app/Helper/BoardHelper.php @@ -3,6 +3,7 @@ namespace Kanboard\Helper; use Kanboard\Core\Base; +use Kanboard\Model\UserMetadataModel; /** * Board Helper @@ -21,6 +22,6 @@ class BoardHelper extends Base */ public function isCollapsed($project_id) { - return $this->userSession->isBoardCollapsed($project_id); + return $this->userMetadataCacheDecorator->get(UserMetadataModel::KEY_BOARD_COLLAPSED.$project_id, 0) == 1; } } diff --git a/app/Helper/CalendarHelper.php b/app/Helper/CalendarHelper.php index b35c40f7..0942177d 100644 --- a/app/Helper/CalendarHelper.php +++ b/app/Helper/CalendarHelper.php @@ -5,8 +5,6 @@ namespace Kanboard\Helper; use Kanboard\Core\Base; use Kanboard\Core\Filter\QueryBuilder; use Kanboard\Filter\TaskDueDateRangeFilter; -use Kanboard\Formatter\SubtaskTimeTrackingCalendarFormatter; -use Kanboard\Formatter\TaskCalendarFormatter; /** * Calendar Helper @@ -17,6 +15,23 @@ use Kanboard\Formatter\TaskCalendarFormatter; class CalendarHelper extends Base { /** + * Render calendar component + * + * @param string $checkUrl + * @param string $saveUrl + * @return string + */ + public function render($checkUrl, $saveUrl) + { + $params = array( + 'checkUrl' => $checkUrl, + 'saveUrl' => $saveUrl, + ); + + return '<div class="js-calendar" data-params=\''.json_encode($params, JSON_HEX_APOS).'\'></div>'; + } + + /** * Get formatted calendar task due events * * @access public @@ -27,7 +42,7 @@ class CalendarHelper extends Base */ public function getTaskDateDueEvents(QueryBuilder $queryBuilder, $start, $end) { - $formatter = new TaskCalendarFormatter($this->container); + $formatter = $this->taskCalendarFormatter; $formatter->setFullDay(); $formatter->setColumns('date_due'); @@ -56,7 +71,7 @@ class CalendarHelper extends Base 'date_due' )); - $formatter = new TaskCalendarFormatter($this->container); + $formatter = $this->taskCalendarFormatter; $formatter->setColumns($startColumn, 'date_due'); return $queryBuilder->format($formatter); @@ -73,8 +88,7 @@ class CalendarHelper extends Base */ public function getSubtaskTimeTrackingEvents($user_id, $start, $end) { - $formatter = new SubtaskTimeTrackingCalendarFormatter($this->container); - return $formatter + return $this->subtaskTimeTrackingCalendarFormatter ->withQuery($this->subtaskTimeTrackingModel->getUserQuery($user_id) ->addCondition($this->getCalendarCondition( $this->dateParser->getTimestampFromIsoFormat($start), diff --git a/app/Helper/DateHelper.php b/app/Helper/DateHelper.php index 7e2ec79c..3bc85b76 100644 --- a/app/Helper/DateHelper.php +++ b/app/Helper/DateHelper.php @@ -54,7 +54,7 @@ class DateHelper extends Base */ public function datetime($value) { - return date($this->configModel->get('application_datetime_format', 'm/d/Y H:i'), $value); + return date($this->dateParser->getUserDateTimeFormat(), $value); } /** diff --git a/app/Helper/FileHelper.php b/app/Helper/FileHelper.php index cabf371c..06589124 100644 --- a/app/Helper/FileHelper.php +++ b/app/Helper/FileHelper.php @@ -21,9 +21,7 @@ class FileHelper extends Base */ public function icon($filename) { - $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); - - switch ($extension) { + switch (get_file_extension($filename)) { case 'jpeg': case 'jpg': case 'png': @@ -70,9 +68,7 @@ class FileHelper extends Base */ public function getImageMimeType($filename) { - $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); - - switch ($extension) { + switch (get_file_extension($filename)) { case 'jpeg': case 'jpg': return 'image/jpeg'; @@ -94,9 +90,7 @@ class FileHelper extends Base */ public function getPreviewType($filename) { - $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); - - switch ($extension) { + switch (get_file_extension($filename)) { case 'md': case 'markdown': return 'markdown'; @@ -106,4 +100,21 @@ class FileHelper extends Base return null; } + + /** + * Return the browser view mime-type based on the file extension. + * + * @access public + * @param $filename + * @return string + */ + public function getBrowserViewType($filename) + { + switch (get_file_extension($filename)) { + case 'pdf': + return 'application/pdf'; + } + + return null; + } } diff --git a/app/Helper/FormHelper.php b/app/Helper/FormHelper.php index c2ea1d72..9eabd724 100644 --- a/app/Helper/FormHelper.php +++ b/app/Helper/FormHelper.php @@ -131,16 +131,34 @@ class FormHelper extends Base * Display a checkbox field * * @access public - * @param string $name Field name - * @param string $label Form label - * @param string $value Form value - * @param boolean $checked Field selected or not - * @param string $class CSS class + * @param string $name Field name + * @param string $label Form label + * @param string $value Form value + * @param boolean $checked Field selected or not + * @param string $class CSS class + * @param array $attributes * @return string */ - public function checkbox($name, $label, $value, $checked = false, $class = '') + public function checkbox($name, $label, $value, $checked = false, $class = '', array $attributes = array()) { - return '<label><input type="checkbox" name="'.$name.'" class="'.$class.'" value="'.$this->helper->text->e($value).'" '.($checked ? 'checked="checked"' : '').'> '.$this->helper->text->e($label).'</label>'; + $htmlAttributes = ''; + + if ($checked) { + $attributes['checked'] = 'checked'; + } + + foreach ($attributes as $attribute => $attributeValue) { + $htmlAttributes .= sprintf('%s="%s"', $attribute, $this->helper->text->e($attributeValue)); + } + + return sprintf( + '<label><input type="checkbox" name="%s" class="%s" value="%s" %s> %s</label>', + $name, + $class, + $this->helper->text->e($value), + $htmlAttributes, + $this->helper->text->e($label) + ); } /** @@ -174,7 +192,7 @@ class FormHelper extends Base $html = '<textarea name="'.$name.'" id="form-'.$name.'" class="'.$class.'" '; $html .= implode(' ', $attributes).'>'; - $html .= isset($values->$name) ? $this->helper->text->e($values->$name) : isset($values[$name]) ? $values[$name] : ''; + $html .= isset($values[$name]) ? $this->helper->text->e($values[$name]) : ''; $html .= '</textarea>'; $html .= $this->errorList($errors, $name); @@ -182,6 +200,45 @@ class FormHelper extends Base } /** + * Display a markdown editor + * + * @access public + * @param string $name Field name + * @param array $values Form values + * @param array $errors Form errors + * @param array $attributes + * @return string + */ + public function textEditor($name, $values = array(), array $errors = array(), array $attributes = array()) + { + $params = array( + 'name' => $name, + 'text' => isset($values[$name]) ? $values[$name] : '', + 'css' => $this->errorClass($errors, $name), + 'required' => isset($attributes['required']) && $attributes['required'], + 'tabindex' => isset($attributes['tabindex']) ? $attributes['tabindex'] : '-1', + 'labelPreview' => t('Preview'), + 'labelWrite' => t('Write'), + 'placeholder' => t('Write your text in Markdown'), + 'autofocus' => isset($attributes['autofocus']) && $attributes['autofocus'], + 'suggestOptions' => array( + 'triggers' => array( + '#' => $this->helper->url->to('TaskAjaxController', 'suggest', array('search' => 'SEARCH_TERM')), + ) + ), + ); + + if (isset($values['project_id'])) { + $params['suggestOptions']['triggers']['@'] = $this->helper->url->to('UserAjaxController', 'mention', array('project_id' => $values['project_id'], 'search' => 'SEARCH_TERM')); + } + + $html = '<div class="js-text-editor" data-params=\''.json_encode($params, JSON_HEX_APOS).'\'></div>'; + $html .= $this->errorList($errors, $name); + + return $html; + } + + /** * Display file field * * @access public @@ -307,6 +364,48 @@ class FormHelper extends Base } /** + * Date field + * + * @access public + * @param string $label + * @param string $name + * @param array $values + * @param array $errors + * @param array $attributes + * @return string + */ + public function date($label, $name, array $values, array $errors = array(), array $attributes = array()) + { + $userFormat = $this->dateParser->getUserDateFormat(); + $values = $this->dateParser->format($values, array($name), $userFormat); + $attributes = array_merge(array('placeholder="'.date($userFormat).'"'), $attributes); + + return $this->helper->form->label($label, $name) . + $this->helper->form->text($name, $values, $errors, $attributes, 'form-date'); + } + + /** + * Datetime field + * + * @access public + * @param string $label + * @param string $name + * @param array $values + * @param array $errors + * @param array $attributes + * @return string + */ + public function datetime($label, $name, array $values, array $errors = array(), array $attributes = array()) + { + $userFormat = $this->dateParser->getUserDateTimeFormat(); + $values = $this->dateParser->format($values, array($name), $userFormat); + $attributes = array_merge(array('placeholder="'.date($userFormat).'"'), $attributes); + + return $this->helper->form->label($label, $name) . + $this->helper->form->text($name, $values, $errors, $attributes, 'form-datetime'); + } + + /** * Display the form error class * * @access private diff --git a/app/Helper/HookHelper.php b/app/Helper/HookHelper.php index 2d13ebcc..24b7d00a 100644 --- a/app/Helper/HookHelper.php +++ b/app/Helper/HookHelper.php @@ -2,6 +2,7 @@ namespace Kanboard\Helper; +use Closure; use Kanboard\Core\Base; /** @@ -24,8 +25,8 @@ class HookHelper extends Base { $buffer = ''; - foreach ($this->hook->getListeners($hook) as $file) { - $buffer .= $this->helper->asset->$type($file); + foreach ($this->hook->getListeners($hook) as $params) { + $buffer .= $this->helper->asset->$type($params['template']); } return $buffer; @@ -43,8 +44,18 @@ class HookHelper extends Base { $buffer = ''; - foreach ($this->hook->getListeners($hook) as $template) { - $buffer .= $this->template->render($template, $variables); + foreach ($this->hook->getListeners($hook) as $params) { + if (! empty($params['variables'])) { + $variables = array_merge($variables, $params['variables']); + } elseif (! empty($params['callable'])) { + $result = call_user_func_array($params['callable'], $variables); + + if (is_array($result)) { + $variables = array_merge($variables, $result); + } + } + + $buffer .= $this->template->render($params['template'], $variables); } return $buffer; @@ -54,13 +65,39 @@ class HookHelper extends Base * Attach a template to a hook * * @access public - * @param string $hook - * @param string $template - * @return \Kanboard\Helper\Hook + * @param string $hook + * @param string $template + * @param array $variables + * @return $this + */ + public function attach($hook, $template, array $variables = array()) + { + $this->hook->on($hook, array( + 'template' => $template, + 'variables' => $variables, + )); + + return $this; + } + + /** + * Attach a template to a hook with a callable + * + * Arguments passed to the callback are the one passed to the hook + * + * @access public + * @param string $hook + * @param string $template + * @param Closure $callable + * @return $this */ - public function attach($hook, $template) + public function attachCallable($hook, $template, Closure $callable) { - $this->hook->on($hook, $template); + $this->hook->on($hook, array( + 'template' => $template, + 'callable' => $callable, + )); + return $this; } } diff --git a/app/Helper/ICalHelper.php b/app/Helper/ICalHelper.php index dc399bf8..95723417 100644 --- a/app/Helper/ICalHelper.php +++ b/app/Helper/ICalHelper.php @@ -5,7 +5,6 @@ namespace Kanboard\Helper; use Kanboard\Core\Base; use Kanboard\Core\Filter\QueryBuilder; use Kanboard\Filter\TaskDueDateRangeFilter; -use Kanboard\Formatter\TaskICalFormatter; use Eluceo\iCal\Component\Calendar as iCalendar; /** @@ -29,10 +28,10 @@ class ICalHelper extends Base { $queryBuilder->withFilter(new TaskDueDateRangeFilter(array($start, $end))); - $formatter = new TaskICalFormatter($this->container); - $formatter->setColumns('date_due'); - $formatter->setCalendar($calendar); - $formatter->withQuery($queryBuilder->getQuery()); - $formatter->addFullDayEvents(); + $this->taskICalFormatter + ->setColumns('date_due') + ->setCalendar($calendar) + ->withQuery($queryBuilder->getQuery()) + ->addFullDayEvents(); } } diff --git a/app/Helper/LayoutHelper.php b/app/Helper/LayoutHelper.php index 8ebb05d4..8be71757 100644 --- a/app/Helper/LayoutHelper.php +++ b/app/Helper/LayoutHelper.php @@ -22,7 +22,10 @@ class LayoutHelper extends Base */ public function app($template, array $params = array()) { - if ($this->request->isAjax()) { + $isAjax = $this->request->isAjax(); + $params['is_ajax'] = $isAjax; + + if ($isAjax) { return $this->template->render($template, $params); } @@ -156,7 +159,11 @@ class LayoutHelper extends Base */ public function analytic($template, array $params) { - return $this->subLayout('analytic/layout', 'analytic/sidebar', $template, $params); + if (isset($params['project']['name'])) { + $params['title'] = $params['project']['name'].' > '.$params['title']; + } + + return $this->subLayout('analytic/layout', 'analytic/sidebar', $template, $params, true); } /** @@ -184,13 +191,16 @@ class LayoutHelper extends Base * @param string $sidebar * @param string $template * @param array $params + * @param bool $ignoreAjax * @return string */ - public function subLayout($sublayout, $sidebar, $template, array $params = array()) + public function subLayout($sublayout, $sidebar, $template, array $params = array(), $ignoreAjax = false) { + $isAjax = $this->request->isAjax(); + $params['is_ajax'] = $isAjax; $content = $this->template->render($template, $params); - if ($this->request->isAjax()) { + if (!$ignoreAjax && $isAjax) { return $content; } diff --git a/app/Helper/ModalHelper.php b/app/Helper/ModalHelper.php new file mode 100644 index 00000000..efbe2c4d --- /dev/null +++ b/app/Helper/ModalHelper.php @@ -0,0 +1,83 @@ +<?php + +namespace Kanboard\Helper; + +use Kanboard\Core\Base; + +/** + * Class ModalHelper + * + * @package Kanboard\Helper + * @author Frederic Guillot + */ +class ModalHelper extends Base +{ + public function submitButtons(array $params = array()) + { + return $this->helper->app->component('submit-buttons', array( + 'submitLabel' => isset($params['submitLabel']) ? $params['submitLabel'] : t('Save'), + 'orLabel' => t('or'), + 'cancelLabel' => t('cancel'), + 'color' => isset($params['color']) ? $params['color'] : 'blue', + 'tabindex' => isset($params['tabindex']) ? $params['tabindex'] : null, + 'disabled' => isset($params['disabled']) ? true : false, + )); + } + + public function confirmButtons($controller, $action, array $params = array(), $submitLabel = '', $tabindex = null) + { + return $this->helper->app->component('confirm-buttons', array( + 'url' => $this->helper->url->href($controller, $action, $params, true), + 'submitLabel' => $submitLabel ?: t('Yes'), + 'orLabel' => t('or'), + 'cancelLabel' => t('cancel'), + 'tabindex' => $tabindex, + )); + } + + public function largeIcon($icon, $label, $controller, $action, array $params = array()) + { + $html = '<i class="fa fa-'.$icon.' fa-fw js-modal-large" aria-hidden="true"></i>'; + return $this->helper->url->link($html, $controller, $action, $params, false, 'js-modal-large', $label); + } + + public function large($icon, $label, $controller, $action, array $params = array()) + { + $html = '<i class="fa fa-'.$icon.' fa-fw js-modal-large" aria-hidden="true"></i>'.$label; + return $this->helper->url->link($html, $controller, $action, $params, false, 'js-modal-large'); + } + + public function medium($icon, $label, $controller, $action, array $params = array()) + { + $html = '<i class="fa fa-'.$icon.' fa-fw js-modal-medium" aria-hidden="true"></i>'.$label; + return $this->helper->url->link($html, $controller, $action, $params, false, 'js-modal-medium'); + } + + public function small($icon, $label, $controller, $action, array $params = array()) + { + $html = '<i class="fa fa-'.$icon.' fa-fw js-modal-small" aria-hidden="true"></i>'.$label; + return $this->helper->url->link($html, $controller, $action, $params, false, 'js-modal-small'); + } + + public function mediumButton($icon, $label, $controller, $action, array $params = array()) + { + $html = '<i class="fa fa-'.$icon.' fa-fw js-modal-medium" aria-hidden="true"></i>'.$label; + return $this->helper->url->link($html, $controller, $action, $params, false, 'js-modal-medium btn'); + } + + public function confirm($icon, $label, $controller, $action, array $params = array()) + { + $html = '<i class="fa fa-'.$icon.' fa-fw js-modal-confirm" aria-hidden="true"></i>'.$label; + return $this->helper->url->link($html, $controller, $action, $params, false, 'js-modal-confirm'); + } + + public function confirmLink($label, $controller, $action, array $params = array()) + { + return $this->helper->url->link($label, $controller, $action, $params, false, 'js-modal-confirm'); + } + + public function replaceLink($label, $controller, $action, array $params = array()) + { + return $this->helper->url->link($label, $controller, $action, $params, false, 'js-modal-replace'); + } +} diff --git a/app/Helper/ProjectActivityHelper.php b/app/Helper/ProjectActivityHelper.php index 40f386db..480db3d5 100644 --- a/app/Helper/ProjectActivityHelper.php +++ b/app/Helper/ProjectActivityHelper.php @@ -6,7 +6,6 @@ use Kanboard\Core\Base; use Kanboard\Filter\ProjectActivityProjectIdFilter; use Kanboard\Filter\ProjectActivityProjectIdsFilter; use Kanboard\Filter\ProjectActivityTaskIdFilter; -use Kanboard\Formatter\ProjectActivityEventFormatter; use Kanboard\Model\ProjectActivityModel; /** @@ -26,7 +25,7 @@ class ProjectActivityHelper extends Base */ public function searchEvents($search) { - $projects = $this->projectUserRoleModel->getProjectsByUser($this->userSession->getId()); + $projects = $this->projectUserRoleModel->getActiveProjectsByUser($this->userSession->getId()); $events = array(); if ($search !== '') { @@ -38,7 +37,7 @@ class ProjectActivityHelper extends Base ->limit(500) ; - $events = $queryBuilder->format(new ProjectActivityEventFormatter($this->container)); + $events = $queryBuilder->format($this->projectActivityEventFormatter); } return $events; @@ -62,7 +61,7 @@ class ProjectActivityHelper extends Base ->limit($limit) ; - return $queryBuilder->format(new ProjectActivityEventFormatter($this->container)); + return $queryBuilder->format($this->projectActivityEventFormatter); } /** @@ -83,7 +82,7 @@ class ProjectActivityHelper extends Base ->limit($limit) ; - return $queryBuilder->format(new ProjectActivityEventFormatter($this->container)); + return $queryBuilder->format($this->projectActivityEventFormatter); } /** @@ -100,6 +99,6 @@ class ProjectActivityHelper extends Base $queryBuilder->getQuery()->desc(ProjectActivityModel::TABLE.'.id'); - return $queryBuilder->format(new ProjectActivityEventFormatter($this->container)); + return $queryBuilder->format($this->projectActivityEventFormatter); } } diff --git a/app/Helper/ProjectRoleHelper.php b/app/Helper/ProjectRoleHelper.php new file mode 100644 index 00000000..6f9cf10c --- /dev/null +++ b/app/Helper/ProjectRoleHelper.php @@ -0,0 +1,286 @@ +<?php + +namespace Kanboard\Helper; + +use Kanboard\Core\Base; +use Kanboard\Core\Security\Role; +use Kanboard\Model\ColumnRestrictionModel; +use Kanboard\Model\ProjectRoleRestrictionModel; + +/** + * Class ProjectRoleHelper + * + * @package Kanboard\Helper + * @author Frederic Guillot + */ +class ProjectRoleHelper extends Base +{ + /** + * Get project role for the current user + * + * @access public + * @param integer $project_id + * @return string + */ + public function getProjectUserRole($project_id) + { + return $this->memoryCache->proxy($this->projectUserRoleModel, 'getUserRole', $project_id, $this->userSession->getId()); + } + + /** + * Return true if the task can be moved by the logged user + * + * @param array $task + * @return bool + */ + public function isDraggable(array &$task) + { + if ($task['is_active'] == 1 && $this->helper->user->hasProjectAccess('BoardAjaxController', 'save', $task['project_id'])) { + return $this->isSortableColumn($task['project_id'], $task['column_id']); + } + + return false; + } + + /** + * Return true is the column is sortable + * + * @param int $project_id + * @param int $column_id + * @return bool + */ + public function isSortableColumn($project_id, $column_id) + { + $role = $this->getProjectUserRole($project_id); + + if ($this->role->isCustomProjectRole($role)) { + $sortableColumns = $this->columnMoveRestrictionCacheDecorator->getSortableColumns($project_id, $role); + + foreach ($sortableColumns as $column) { + if ($column['src_column_id'] == $column_id || $column['dst_column_id'] == $column_id) { + return true; + } + } + + return empty($sortableColumns) && $this->isAllowedToMoveTask($project_id, $role); + } + + return true; + } + + /** + * Check if the user can move a task + * + * @param int $project_id + * @param int $src_column_id + * @param int $dst_column_id + * @return bool|int + */ + public function canMoveTask($project_id, $src_column_id, $dst_column_id) + { + $role = $this->getProjectUserRole($project_id); + + if ($this->role->isCustomProjectRole($role)) { + if ($src_column_id == $dst_column_id) { + return true; + } + + $sortableColumns = $this->columnMoveRestrictionCacheDecorator->getSortableColumns($project_id, $role); + + foreach ($sortableColumns as $column) { + if ($column['src_column_id'] == $src_column_id && $column['dst_column_id'] == $dst_column_id) { + return true; + } + + if ($column['dst_column_id'] == $src_column_id && $column['src_column_id'] == $dst_column_id) { + return true; + } + } + + return empty($sortableColumns) && $this->isAllowedToMoveTask($project_id, $role); + } + + return true; + } + + /** + * Return true if the user can create a task for the given column + * + * @param int $project_id + * @param int $column_id + * @return bool + */ + public function canCreateTaskInColumn($project_id, $column_id) + { + $role = $this->getProjectUserRole($project_id); + + if ($this->role->isCustomProjectRole($role)) { + if (! $this->isAllowedToCreateTask($project_id, $column_id, $role)) { + return false; + } + } + + return $this->helper->user->hasProjectAccess('TaskCreationController', 'show', $project_id); + } + + /** + * Return true if the user can create a task for the given column + * + * @param int $project_id + * @param int $column_id + * @return bool + */ + public function canChangeTaskStatusInColumn($project_id, $column_id) + { + $role = $this->getProjectUserRole($project_id); + + if ($this->role->isCustomProjectRole($role)) { + if (! $this->isAllowedToChangeTaskStatus($project_id, $column_id, $role)) { + return false; + } + } + + return $this->helper->user->hasProjectAccess('TaskStatusController', 'close', $project_id); + } + + /** + * Return true if the user can remove a task + * + * Regular users can't remove tasks from other people + * + * @public + * @param array $task + * @return bool + */ + public function canRemoveTask(array $task) + { + if (isset($task['creator_id']) && $task['creator_id'] == $this->userSession->getId()) { + return true; + } + + if ($this->userSession->isAdmin() || $this->getProjectUserRole($task['project_id']) === Role::PROJECT_MANAGER) { + return true; + } + + return false; + } + + /** + * Check project access + * + * @param string $controller + * @param string $action + * @param integer $project_id + * @return bool + */ + public function checkProjectAccess($controller, $action, $project_id) + { + if (! $this->userSession->isLogged()) { + return false; + } + + if ($this->userSession->isAdmin()) { + return true; + } + + if (! $this->helper->user->hasAccess($controller, $action)) { + return false; + } + + $role = $this->getProjectUserRole($project_id); + + if ($this->role->isCustomProjectRole($role)) { + $result = $this->projectAuthorization->isAllowed($controller, $action, Role::PROJECT_MEMBER); + } else { + $result = $this->projectAuthorization->isAllowed($controller, $action, $role); + } + + return $result; + } + + /** + * Check authorization for a custom project role to change the task status + * + * @param int $project_id + * @param int $column_id + * @param string $role + * @return bool + */ + protected function isAllowedToChangeTaskStatus($project_id, $column_id, $role) + { + $columnRestrictions = $this->columnRestrictionCacheDecorator->getAllByRole($project_id, $role); + + foreach ($columnRestrictions as $restriction) { + if ($restriction['column_id'] == $column_id) { + if ($restriction['rule'] == ColumnRestrictionModel::RULE_ALLOW_TASK_OPEN_CLOSE) { + return true; + } else if ($restriction['rule'] == ColumnRestrictionModel::RULE_BLOCK_TASK_OPEN_CLOSE) { + return false; + } + } + } + + $projectRestrictions = $this->projectRoleRestrictionCacheDecorator->getAllByRole($project_id, $role); + + foreach ($projectRestrictions as $restriction) { + if ($restriction['rule'] == ProjectRoleRestrictionModel::RULE_TASK_OPEN_CLOSE) { + return false; + } + } + + return true; + } + + /** + * Check authorization for a custom project role to create a task + * + * @param int $project_id + * @param int $column_id + * @param string $role + * @return bool + */ + protected function isAllowedToCreateTask($project_id, $column_id, $role) + { + $columnRestrictions = $this->columnRestrictionCacheDecorator->getAllByRole($project_id, $role); + + foreach ($columnRestrictions as $restriction) { + if ($restriction['column_id'] == $column_id) { + if ($restriction['rule'] == ColumnRestrictionModel::RULE_ALLOW_TASK_CREATION) { + return true; + } else if ($restriction['rule'] == ColumnRestrictionModel::RULE_BLOCK_TASK_CREATION) { + return false; + } + } + } + + $projectRestrictions = $this->projectRoleRestrictionCacheDecorator->getAllByRole($project_id, $role); + + foreach ($projectRestrictions as $restriction) { + if ($restriction['rule'] == ProjectRoleRestrictionModel::RULE_TASK_CREATION) { + return false; + } + } + + return true; + } + + /** + * Check if the role can move task in the given project + * + * @param int $project_id + * @param string $role + * @return bool + */ + protected function isAllowedToMoveTask($project_id, $role) + { + $projectRestrictions = $this->projectRoleRestrictionCacheDecorator->getAllByRole($project_id, $role); + + foreach ($projectRestrictions as $restriction) { + if ($restriction['rule'] == ProjectRoleRestrictionModel::RULE_TASK_MOVE) { + return false; + } + } + + return true; + } +} diff --git a/app/Helper/SubtaskHelper.php b/app/Helper/SubtaskHelper.php index dac71203..8e090f17 100644 --- a/app/Helper/SubtaskHelper.php +++ b/app/Helper/SubtaskHelper.php @@ -50,7 +50,7 @@ class SubtaskHelper extends Base return $this->helper->url->link($this->getTitle($subtask), 'SubtaskStatusController', 'change', $params, false, $class); } - public function selectTitle(array $values, array $errors = array(), array $attributes = array()) + public function renderTitleField(array $values, array $errors = array(), array $attributes = array()) { $attributes = array_merge(array('tabindex="1"', 'required', 'maxlength="255"'), $attributes); @@ -60,18 +60,21 @@ class SubtaskHelper extends Base return $html; } - public function selectAssignee(array $users, array $values, array $errors = array(), array $attributes = array()) + public function renderAssigneeField(array $users, array $values, array $errors = array(), array $attributes = array()) { $attributes = array_merge(array('tabindex="2"'), $attributes); $html = $this->helper->form->label(t('Assignee'), 'user_id'); $html .= $this->helper->form->select('user_id', $users, $values, $errors, $attributes); - $html .= ' <a href="#" class="assign-me" data-target-id="form-user_id" data-current-id="'.$this->userSession->getId().'" title="'.t('Assign to me').'">'.t('Me').'</a>'; + $html .= ' '; + $html .= '<small>'; + $html .= '<a href="#" class="assign-me" data-target-id="form-user_id" data-current-id="'.$this->userSession->getId().'" title="'.t('Assign to me').'">'.t('Me').'</a>'; + $html .= '</small>'; return $html; } - public function selectTimeEstimated(array $values, array $errors = array(), array $attributes = array()) + public function renderTimeEstimatedField(array $values, array $errors = array(), array $attributes = array()) { $attributes = array_merge(array('tabindex="3"'), $attributes); @@ -82,7 +85,7 @@ class SubtaskHelper extends Base return $html; } - public function selectTimeSpent(array $values, array $errors = array(), array $attributes = array()) + public function renderTimeSpentField(array $values, array $errors = array(), array $attributes = array()) { $attributes = array_merge(array('tabindex="4"'), $attributes); diff --git a/app/Helper/TaskHelper.php b/app/Helper/TaskHelper.php index e1d65cca..71596b60 100644 --- a/app/Helper/TaskHelper.php +++ b/app/Helper/TaskHelper.php @@ -40,32 +40,28 @@ class TaskHelper extends Base return $this->taskRecurrenceModel->getRecurrenceBasedateList(); } - public function selectTitle(array $values, array $errors) + public function renderTitleField(array $values, array $errors) { - $html = $this->helper->form->label(t('Title'), 'title'); - $html .= $this->helper->form->text('title', $values, $errors, array('autofocus', 'required', 'maxlength="200"', 'tabindex="1"'), 'form-input-large'); - return $html; - } - - public function selectDescription(array $values, array $errors) - { - $html = $this->helper->form->label(t('Description'), 'description'); - $html .= $this->helper->form->textarea( - 'description', + return $this->helper->form->text( + 'title', $values, $errors, array( - 'placeholder="'.t('Leave a description').'"', - 'tabindex="2"', - 'data-mention-search-url="'.$this->helper->url->href('UserAjaxController', 'mention', array('project_id' => $values['project_id'])).'"' - ), - 'markdown-editor' + 'autofocus', + 'required', + 'maxlength="200"', + 'tabindex="1"', + 'placeholder="'.t('Title').'"' + ) ); + } - return $html; + public function renderDescriptionField(array $values, array $errors) + { + return $this->helper->form->textEditor('description', $values, $errors, array('tabindex' => 2)); } - public function selectTags(array $project, array $tags = array()) + public function renderTagField(array $project, array $tags = array()) { $options = $this->tagModel->getAssignableList($project['id']); @@ -87,7 +83,7 @@ class TaskHelper extends Base return $html; } - public function selectColor(array $values) + public function renderColorField(array $values) { $colors = $this->colorModel->getList(); $html = $this->helper->form->label(t('Color'), 'color_id'); @@ -95,18 +91,21 @@ class TaskHelper extends Base return $html; } - public function selectAssignee(array $users, array $values, array $errors = array(), array $attributes = array()) + public function renderAssigneeField(array $users, array $values, array $errors = array(), array $attributes = array()) { $attributes = array_merge(array('tabindex="3"'), $attributes); $html = $this->helper->form->label(t('Assignee'), 'owner_id'); $html .= $this->helper->form->select('owner_id', $users, $values, $errors, $attributes); - $html .= ' <a href="#" class="assign-me" data-target-id="form-owner_id" data-current-id="'.$this->userSession->getId().'" title="'.t('Assign to me').'">'.t('Me').'</a>'; + $html .= ' '; + $html .= '<small>'; + $html .= '<a href="#" class="assign-me" data-target-id="form-owner_id" data-current-id="'.$this->userSession->getId().'" title="'.t('Assign to me').'">'.t('Me').'</a>'; + $html .= '</small>'; return $html; } - public function selectCategory(array $categories, array $values, array $errors = array(), array $attributes = array(), $allow_one_item = false) + public function renderCategoryField(array $categories, array $values, array $errors = array(), array $attributes = array(), $allow_one_item = false) { $attributes = array_merge(array('tabindex="4"'), $attributes); $html = ''; @@ -119,7 +118,7 @@ class TaskHelper extends Base return $html; } - public function selectSwimlane(array $swimlanes, array $values, array $errors = array(), array $attributes = array()) + public function renderSwimlaneField(array $swimlanes, array $values, array $errors = array(), array $attributes = array()) { $attributes = array_merge(array('tabindex="5"'), $attributes); $html = ''; @@ -132,7 +131,7 @@ class TaskHelper extends Base return $html; } - public function selectColumn(array $columns, array $values, array $errors = array(), array $attributes = array()) + public function renderColumnField(array $columns, array $values, array $errors = array(), array $attributes = array()) { $attributes = array_merge(array('tabindex="6"'), $attributes); @@ -142,11 +141,11 @@ class TaskHelper extends Base return $html; } - public function selectPriority(array $project, array $values) + public function renderPriorityField(array $project, array $values) { $html = ''; - if ($project['priority_end'] > $project['priority_start']) { + if ($project['priority_end'] != $project['priority_start']) { $range = range($project['priority_start'], $project['priority_end']); $options = array_combine($range, $range); $values += array('priority' => $project['priority_default']); @@ -158,9 +157,9 @@ class TaskHelper extends Base return $html; } - public function selectScore(array $values, array $errors = array(), array $attributes = array()) + public function renderScoreField(array $values, array $errors = array(), array $attributes = array()) { - $attributes = array_merge(array('tabindex="8"'), $attributes); + $attributes = array_merge(array('tabindex="13"'), $attributes); $html = $this->helper->form->label(t('Complexity'), 'score'); $html .= $this->helper->form->number('score', $values, $errors, $attributes); @@ -168,9 +167,9 @@ class TaskHelper extends Base return $html; } - public function selectReference(array $values, array $errors = array(), array $attributes = array()) + public function renderReferenceField(array $values, array $errors = array(), array $attributes = array()) { - $attributes = array_merge(array('tabindex="9"'), $attributes); + $attributes = array_merge(array('tabindex="14"'), $attributes); $html = $this->helper->form->label(t('Reference'), 'reference'); $html .= $this->helper->form->text('reference', $values, $errors, $attributes, 'form-input-small'); @@ -178,9 +177,9 @@ class TaskHelper extends Base return $html; } - public function selectTimeEstimated(array $values, array $errors = array(), array $attributes = array()) + public function renderTimeEstimatedField(array $values, array $errors = array(), array $attributes = array()) { - $attributes = array_merge(array('tabindex="10"'), $attributes); + $attributes = array_merge(array('tabindex="11"'), $attributes); $html = $this->helper->form->label(t('Original estimate'), 'time_estimated'); $html .= $this->helper->form->numeric('time_estimated', $values, $errors, $attributes); @@ -189,9 +188,9 @@ class TaskHelper extends Base return $html; } - public function selectTimeSpent(array $values, array $errors = array(), array $attributes = array()) + public function renderTimeSpentField(array $values, array $errors = array(), array $attributes = array()) { - $attributes = array_merge(array('tabindex="11"'), $attributes); + $attributes = array_merge(array('tabindex="12"'), $attributes); $html = $this->helper->form->label(t('Time spent'), 'time_spent'); $html .= $this->helper->form->numeric('time_spent', $values, $errors, $attributes); @@ -200,33 +199,23 @@ class TaskHelper extends Base return $html; } - public function selectStartDate(array $values, array $errors = array(), array $attributes = array()) + public function renderStartDateField(array $values, array $errors = array(), array $attributes = array()) { - $placeholder = date($this->configModel->get('application_date_format', 'm/d/Y H:i')); - $attributes = array_merge(array('tabindex="12"', 'placeholder="'.$placeholder.'"'), $attributes); - - $html = $this->helper->form->label(t('Start Date'), 'date_started'); - $html .= $this->helper->form->text('date_started', $values, $errors, $attributes, 'form-datetime'); - - return $html; + $attributes = array_merge(array('tabindex="10"'), $attributes); + return $this->helper->form->datetime(t('Start Date'), 'date_started', $values, $errors, $attributes); } - public function selectDueDate(array $values, array $errors = array(), array $attributes = array()) + public function renderDueDateField(array $values, array $errors = array(), array $attributes = array()) { - $placeholder = date($this->configModel->get('application_date_format', 'm/d/Y')); - $attributes = array_merge(array('tabindex="13"', 'placeholder="'.$placeholder.'"'), $attributes); - - $html = $this->helper->form->label(t('Due Date'), 'date_due'); - $html .= $this->helper->form->text('date_due', $values, $errors, $attributes, 'form-date'); - - return $html; + $attributes = array_merge(array('tabindex="9"'), $attributes); + return $this->helper->form->date(t('Due Date'), 'date_due', $values, $errors, $attributes); } public function formatPriority(array $project, array $task) { $html = ''; - if ($project['priority_end'] > $project['priority_start']) { + if ($project['priority_end'] != $project['priority_start']) { $html .= '<span class="task-board-priority" title="'.t('Task priority').'">'; $html .= $task['priority'] >= 0 ? 'P'.$task['priority'] : '-P'.abs($task['priority']); $html .= '</span>'; @@ -243,4 +232,32 @@ class TaskHelper extends Base return $this->taskModel->getProgress($task, $this->columns[$task['project_id']]); } + + public function getNewTaskDropdown($projectId, $swimlaneId, $columnId) + { + $providers = $this->externalTaskManager->getProvidersList(); + + if (empty($providers)) { + return ''; + } + + $html = '<small class="pull-right"><div class="dropdown">'; + $html .= '<a href="#" class="dropdown-menu"><i class="fa fa-cloud-download" aria-hidden="true"></i> <i class="fa fa-caret-down"></i></a><ul>'; + + foreach ($providers as $providerName) { + $link = $this->helper->url->link( + t('New External Task: %s', $providerName), + 'ExternalTaskCreationController', + 'step1', + array('project_id' => $projectId, 'swimlane_id' => $swimlaneId, 'column_id' => $columnId, 'provider_name' => $providerName), + false, + 'js-modal-replace' + ); + + $html .= '<li><i class="fa fa-fw fa-plus-square" aria-hidden="true"></i> '.$link.'</li>'; + } + + $html .= '</ul></div></small>'; + return $html; + } } diff --git a/app/Helper/UrlHelper.php b/app/Helper/UrlHelper.php index 2127c69e..94412cf5 100644 --- a/app/Helper/UrlHelper.php +++ b/app/Helper/UrlHelper.php @@ -42,29 +42,67 @@ class UrlHelper extends Base */ public function button($icon, $label, $controller, $action, array $params = array(), $class = '') { - $icon = '<i class="fa '.$icon.' fa-fw"></i> '; + $html = '<i class="fa fa-'.$icon.' fa-fw"></i> '.$label; $class = 'btn '.$class; - return $this->link($icon.$label, $controller, $action, $params, false, $class); + return $this->link($html, $controller, $action, $params, false, $class); + } + + /** + * Link element with icon + * + * @access public + * @param string $icon Icon name + * @param string $label Link label + * @param string $controller Controller name + * @param string $action Action name + * @param array $params Url parameters + * @param boolean $csrf Add a CSRF token + * @param string $class CSS class attribute + * @param string $title Link title + * @param boolean $newTab Open the link in a new tab + * @param string $anchor Link Anchor + * @param bool $absolute + * @return string + */ + public function icon($icon, $label, $controller, $action, array $params = array(), $csrf = false, $class = '', $title = '', $newTab = false, $anchor = '', $absolute = false) + { + $html = '<i class="fa fa-fw fa-'.$icon.'" aria-hidden="true"></i>'.$label; + return $this->helper->url->link($html, $controller, $action, $params, $csrf, $class, $title, $newTab, $anchor, $absolute); } /** * Link element * * @access public - * @param string $label Link label - * @param string $controller Controller name - * @param string $action Action name - * @param array $params Url parameters - * @param boolean $csrf Add a CSRF token - * @param string $class CSS class attribute - * @param string $title - * @param boolean $new_tab Open the link in a new tab - * @param string $anchor Link Anchor + * @param string $label Link label + * @param string $controller Controller name + * @param string $action Action name + * @param array $params Url parameters + * @param boolean $csrf Add a CSRF token + * @param string $class CSS class attribute + * @param string $title Link title + * @param boolean $newTab Open the link in a new tab + * @param string $anchor Link Anchor + * @param bool $absolute + * @return string + */ + public function link($label, $controller, $action, array $params = array(), $csrf = false, $class = '', $title = '', $newTab = false, $anchor = '', $absolute = false) + { + return '<a href="'.$this->href($controller, $action, $params, $csrf, $anchor, $absolute).'" class="'.$class.'" title=\''.$title.'\' '.($newTab ? 'target="_blank"' : '').'>'.$label.'</a>'; + } + + /** + * Absolute link + * + * @param string $label + * @param string $controller + * @param string $action + * @param array $params * @return string */ - public function link($label, $controller, $action, array $params = array(), $csrf = false, $class = '', $title = '', $new_tab = false, $anchor = '') + public function absoluteLink($label, $controller, $action, array $params = array()) { - return '<a href="'.$this->href($controller, $action, $params, $csrf, $anchor).'" class="'.$class.'" title=\''.$title.'\' '.($new_tab ? 'target="_blank"' : '').'>'.$label.'</a>'; + return $this->link($label, $controller, $action, $params, false, '', '', true, '', true); } /** @@ -155,7 +193,7 @@ class UrlHelper extends Base /** * Build relative url * - * @access private + * @access protected * @param string $separator Querystring argument separator * @param string $controller Controller name * @param string $action Action name @@ -165,7 +203,7 @@ class UrlHelper extends Base * @param boolean $absolute Absolute or relative link * @return string */ - private function build($separator, $controller, $action, array $params = array(), $csrf = false, $anchor = '', $absolute = false) + protected function build($separator, $controller, $action, array $params = array(), $csrf = false, $anchor = '', $absolute = false) { $path = $this->route->findUrl($controller, $action, $params); $qs = array(); diff --git a/app/Helper/UserHelper.php b/app/Helper/UserHelper.php index ab259a62..8c2567b9 100644 --- a/app/Helper/UserHelper.php +++ b/app/Helper/UserHelper.php @@ -3,7 +3,6 @@ namespace Kanboard\Helper; use Kanboard\Core\Base; -use Kanboard\Core\Security\Role; /** * User helpers @@ -50,7 +49,8 @@ class UserHelper extends Base */ public function getFullname(array $user = array()) { - return $this->userModel->getFullname(empty($user) ? $this->userSession->getAll() : $user); + $user = empty($user) ? $this->userSession->getAll() : $user; + return $user['name'] ?: $user['username']; } /** @@ -132,61 +132,14 @@ class UserHelper extends Base */ public function hasProjectAccess($controller, $action, $project_id) { - if (! $this->userSession->isLogged()) { - return false; - } - - if ($this->userSession->isAdmin()) { - return true; - } - - if (! $this->hasAccess($controller, $action)) { - return false; - } - $key = 'project_access:'.$controller.$action.$project_id; $result = $this->memoryCache->get($key); if ($result === null) { - $role = $this->getProjectUserRole($project_id); - $result = $this->projectAuthorization->isAllowed($controller, $action, $role); + $result = $this->helper->projectRole->checkProjectAccess($controller, $action, $project_id); $this->memoryCache->set($key, $result); } return $result; } - - /** - * Get project role for the current user - * - * @access public - * @param integer $project_id - * @return string - */ - public function getProjectUserRole($project_id) - { - return $this->memoryCache->proxy($this->projectUserRoleModel, 'getUserRole', $project_id, $this->userSession->getId()); - } - - /** - * Return true if the user can remove a task - * - * Regular users can't remove tasks from other people - * - * @public - * @param array $task - * @return bool - */ - public function canRemoveTask(array $task) - { - if (isset($task['creator_id']) && $task['creator_id'] == $this->userSession->getId()) { - return true; - } - - if ($this->userSession->isAdmin() || $this->getProjectUserRole($task['project_id']) === Role::PROJECT_MANAGER) { - return true; - } - - return false; - } } |