diff options
author | JLGC @monolinux <monolinux@junglacode.org> | 2016-08-15 23:13:16 -0500 |
---|---|---|
committer | JLGC @monolinux <monolinux@junglacode.org> | 2016-08-15 23:13:16 -0500 |
commit | 683c0464093f6a7976236c68653c2a2cc5dae280 (patch) | |
tree | bf176ecd82415cc4952eea071b7d264dd5fd68b4 /app | |
parent | b1e795fc5b45369f7b9b565b1e106d2673361977 (diff) | |
parent | 5f82a942c0011bf91947b2c1d627c0907bda0c92 (diff) |
Merge https://github.com/kanboard/kanboard
Diffstat (limited to 'app')
374 files changed, 9157 insertions, 4441 deletions
diff --git a/app/Action/Base.php b/app/Action/Base.php index e5c65a17..9a502a08 100644 --- a/app/Action/Base.php +++ b/app/Action/Base.php @@ -7,7 +7,7 @@ use Kanboard\Event\GenericEvent; /** * Base class for automatic actions * - * @package action + * @package Kanboard\Action * @author Frederic Guillot */ abstract class Base extends \Kanboard\Core\Base @@ -216,7 +216,8 @@ abstract class Base extends \Kanboard\Core\Base */ public function hasRequiredProject(array $data) { - return isset($data['project_id']) && $data['project_id'] == $this->getProjectId(); + return (isset($data['project_id']) && $data['project_id'] == $this->getProjectId()) || + (isset($data['task']['project_id']) && $data['task']['project_id'] == $this->getProjectId()); } /** @@ -226,10 +227,14 @@ abstract class Base extends \Kanboard\Core\Base * @param array $data Event data dictionary * @return bool True if all keys are there */ - public function hasRequiredParameters(array $data) + public function hasRequiredParameters(array $data, array $parameters = array()) { - foreach ($this->getEventRequiredParameters() as $parameter) { - if (! isset($data[$parameter])) { + $parameters = $parameters ?: $this->getEventRequiredParameters(); + + foreach ($parameters as $key => $value) { + if (is_array($value)) { + return isset($data[$key]) && $this->hasRequiredParameters($data[$key], $value); + } else if (! isset($data[$value])) { return false; } } diff --git a/app/Action/CommentCreation.php b/app/Action/CommentCreation.php index 60ca24f7..301d2cf9 100644 --- a/app/Action/CommentCreation.php +++ b/app/Action/CommentCreation.php @@ -5,7 +5,7 @@ namespace Kanboard\Action; /** * Create automatically a comment from a webhook * - * @package action + * @package Kanboard\Action * @author Frederic Guillot */ class CommentCreation extends Base diff --git a/app/Action/CommentCreationMoveTaskColumn.php b/app/Action/CommentCreationMoveTaskColumn.php index 1b16f481..d5bdd807 100644 --- a/app/Action/CommentCreationMoveTaskColumn.php +++ b/app/Action/CommentCreationMoveTaskColumn.php @@ -7,7 +7,7 @@ use Kanboard\Model\TaskModel; /** * Add a comment of the triggering event to the task description. * - * @package action + * @package Kanboard\Action * @author Oren Ben-Kiki */ class CommentCreationMoveTaskColumn extends Base @@ -55,7 +55,13 @@ class CommentCreationMoveTaskColumn extends Base */ public function getEventRequiredParameters() { - return array('task_id', 'column_id'); + return array( + 'task_id', + 'task' => array( + 'column_id', + 'project_id', + ), + ); } /** @@ -71,7 +77,7 @@ class CommentCreationMoveTaskColumn extends Base return false; } - $column = $this->columnModel->getById($data['column_id']); + $column = $this->columnModel->getById($data['task']['column_id']); return (bool) $this->commentModel->create(array( 'comment' => t('Moved to column %s', $column['title']), @@ -89,6 +95,6 @@ class CommentCreationMoveTaskColumn extends Base */ public function hasRequiredCondition(array $data) { - return $data['column_id'] == $this->getParam('column_id'); + return $data['task']['column_id'] == $this->getParam('column_id'); } } diff --git a/app/Action/TaskAssignCategoryColor.php b/app/Action/TaskAssignCategoryColor.php index fc486870..9228e1ff 100644 --- a/app/Action/TaskAssignCategoryColor.php +++ b/app/Action/TaskAssignCategoryColor.php @@ -7,7 +7,7 @@ use Kanboard\Model\TaskModel; /** * Set a category automatically according to the color * - * @package action + * @package Kanboard\Action * @author Frederic Guillot */ class TaskAssignCategoryColor extends Base @@ -60,7 +60,10 @@ class TaskAssignCategoryColor extends Base { return array( 'task_id', - 'color_id', + 'task' => array( + 'project_id', + 'color_id', + ), ); } @@ -90,6 +93,6 @@ class TaskAssignCategoryColor extends Base */ public function hasRequiredCondition(array $data) { - return $data['color_id'] == $this->getParam('color_id'); + return $data['task']['color_id'] == $this->getParam('color_id'); } } diff --git a/app/Action/TaskAssignCategoryLabel.php b/app/Action/TaskAssignCategoryLabel.php index 48299010..c390414e 100644 --- a/app/Action/TaskAssignCategoryLabel.php +++ b/app/Action/TaskAssignCategoryLabel.php @@ -5,7 +5,7 @@ namespace Kanboard\Action; /** * Set a category automatically according to a label * - * @package action + * @package Kanboard\Action * @author Frederic Guillot */ class TaskAssignCategoryLabel extends Base diff --git a/app/Action/TaskAssignCategoryLink.php b/app/Action/TaskAssignCategoryLink.php index 6937edd1..6c4b6c96 100644 --- a/app/Action/TaskAssignCategoryLink.php +++ b/app/Action/TaskAssignCategoryLink.php @@ -7,7 +7,7 @@ use Kanboard\Model\TaskLinkModel; /** * Set a category automatically according to a task link * - * @package action + * @package Kanboard\Action * @author Olivier Maridat * @author Frederic Guillot */ @@ -60,8 +60,10 @@ class TaskAssignCategoryLink extends Base public function getEventRequiredParameters() { return array( - 'task_id', - 'link_id', + 'task_link' => array( + 'task_id', + 'link_id', + ) ); } @@ -75,7 +77,7 @@ class TaskAssignCategoryLink extends Base public function doAction(array $data) { $values = array( - 'id' => $data['task_id'], + 'id' => $data['task_link']['task_id'], 'category_id' => $this->getParam('category_id'), ); @@ -91,9 +93,8 @@ class TaskAssignCategoryLink extends Base */ public function hasRequiredCondition(array $data) { - if ($data['link_id'] == $this->getParam('link_id')) { - $task = $this->taskFinderModel->getById($data['task_id']); - return empty($task['category_id']); + if ($data['task_link']['link_id'] == $this->getParam('link_id')) { + return empty($data['task']['category_id']); } return false; diff --git a/app/Action/TaskAssignColorCategory.php b/app/Action/TaskAssignColorCategory.php index 284b8f40..a136ffd2 100644 --- a/app/Action/TaskAssignColorCategory.php +++ b/app/Action/TaskAssignColorCategory.php @@ -7,7 +7,7 @@ use Kanboard\Model\TaskModel; /** * Assign a color to a specific category * - * @package action + * @package Kanboard\Action * @author Frederic Guillot */ class TaskAssignColorCategory extends Base @@ -60,7 +60,10 @@ class TaskAssignColorCategory extends Base { return array( 'task_id', - 'category_id', + 'task' => array( + 'project_id', + 'category_id', + ), ); } @@ -90,6 +93,6 @@ class TaskAssignColorCategory extends Base */ public function hasRequiredCondition(array $data) { - return $data['category_id'] == $this->getParam('category_id'); + return $data['task']['category_id'] == $this->getParam('category_id'); } } diff --git a/app/Action/TaskAssignColorColumn.php b/app/Action/TaskAssignColorColumn.php index 57fd6f44..da6e3aed 100644 --- a/app/Action/TaskAssignColorColumn.php +++ b/app/Action/TaskAssignColorColumn.php @@ -7,7 +7,7 @@ use Kanboard\Model\TaskModel; /** * Assign a color to a task * - * @package action + * @package Kanboard\Action * @author Frederic Guillot */ class TaskAssignColorColumn extends Base @@ -61,7 +61,10 @@ class TaskAssignColorColumn extends Base { return array( 'task_id', - 'column_id', + 'task' => array( + 'project_id', + 'column_id', + ), ); } @@ -91,6 +94,6 @@ class TaskAssignColorColumn extends Base */ public function hasRequiredCondition(array $data) { - return $data['column_id'] == $this->getParam('column_id'); + return $data['task']['column_id'] == $this->getParam('column_id'); } } diff --git a/app/Action/TaskAssignColorLink.php b/app/Action/TaskAssignColorLink.php index 9ab5458b..19c37afe 100644 --- a/app/Action/TaskAssignColorLink.php +++ b/app/Action/TaskAssignColorLink.php @@ -7,7 +7,7 @@ use Kanboard\Model\TaskLinkModel; /** * Assign a color to a specific task link * - * @package action + * @package Kanboard\Action * @author Frederic Guillot */ class TaskAssignColorLink extends Base @@ -59,8 +59,10 @@ class TaskAssignColorLink extends Base public function getEventRequiredParameters() { return array( - 'task_id', - 'link_id', + 'task_link' => array( + 'task_id', + 'link_id', + ) ); } @@ -74,7 +76,7 @@ class TaskAssignColorLink extends Base public function doAction(array $data) { $values = array( - 'id' => $data['task_id'], + 'id' => $data['task_link']['task_id'], 'color_id' => $this->getParam('color_id'), ); @@ -90,6 +92,6 @@ class TaskAssignColorLink extends Base */ public function hasRequiredCondition(array $data) { - return $data['link_id'] == $this->getParam('link_id'); + return $data['task_link']['link_id'] == $this->getParam('link_id'); } } diff --git a/app/Action/TaskAssignColorPriority.php b/app/Action/TaskAssignColorPriority.php index eae1b771..37f7ffed 100644 --- a/app/Action/TaskAssignColorPriority.php +++ b/app/Action/TaskAssignColorPriority.php @@ -7,7 +7,7 @@ use Kanboard\Model\TaskModel; /** * Assign a color to a priority * - * @package action + * @package Kanboard\Action * @author Frederic Guillot */ class TaskAssignColorPriority extends Base @@ -60,7 +60,10 @@ class TaskAssignColorPriority extends Base { return array( 'task_id', - 'priority', + 'task' => array( + 'project_id', + 'priority', + ), ); } @@ -90,6 +93,6 @@ class TaskAssignColorPriority extends Base */ public function hasRequiredCondition(array $data) { - return $data['priority'] == $this->getParam('priority'); + return $data['task']['priority'] == $this->getParam('priority'); } } diff --git a/app/Action/TaskAssignColorUser.php b/app/Action/TaskAssignColorUser.php index 4bcf7a5c..468d0198 100644 --- a/app/Action/TaskAssignColorUser.php +++ b/app/Action/TaskAssignColorUser.php @@ -7,7 +7,7 @@ use Kanboard\Model\TaskModel; /** * Assign a color to a specific user * - * @package action + * @package Kanboard\Action * @author Frederic Guillot */ class TaskAssignColorUser extends Base @@ -61,7 +61,10 @@ class TaskAssignColorUser extends Base { return array( 'task_id', - 'owner_id', + 'task' => array( + 'project_id', + 'owner_id', + ), ); } @@ -91,6 +94,6 @@ class TaskAssignColorUser extends Base */ public function hasRequiredCondition(array $data) { - return $data['owner_id'] == $this->getParam('user_id'); + return $data['task']['owner_id'] == $this->getParam('user_id'); } } diff --git a/app/Action/TaskAssignCurrentUser.php b/app/Action/TaskAssignCurrentUser.php index 997aa98f..dee5e7db 100644 --- a/app/Action/TaskAssignCurrentUser.php +++ b/app/Action/TaskAssignCurrentUser.php @@ -7,7 +7,7 @@ use Kanboard\Model\TaskModel; /** * Assign a task to the logged user * - * @package action + * @package Kanboard\Action * @author Frederic Guillot */ class TaskAssignCurrentUser extends Base diff --git a/app/Action/TaskAssignCurrentUserColumn.php b/app/Action/TaskAssignCurrentUserColumn.php index bc28a90b..60ada7ef 100644 --- a/app/Action/TaskAssignCurrentUserColumn.php +++ b/app/Action/TaskAssignCurrentUserColumn.php @@ -7,7 +7,7 @@ use Kanboard\Model\TaskModel; /** * Assign a task to the logged user on column change * - * @package action + * @package Kanboard\Action * @author Frederic Guillot */ class TaskAssignCurrentUserColumn extends Base @@ -59,7 +59,10 @@ class TaskAssignCurrentUserColumn extends Base { return array( 'task_id', - 'column_id', + 'task' => array( + 'project_id', + 'column_id', + ), ); } @@ -93,6 +96,6 @@ class TaskAssignCurrentUserColumn extends Base */ public function hasRequiredCondition(array $data) { - return $data['column_id'] == $this->getParam('column_id'); + return $data['task']['column_id'] == $this->getParam('column_id'); } } diff --git a/app/Action/TaskAssignDueDateOnCreation.php b/app/Action/TaskAssignDueDateOnCreation.php new file mode 100644 index 00000000..5c6e2b61 --- /dev/null +++ b/app/Action/TaskAssignDueDateOnCreation.php @@ -0,0 +1,96 @@ +<?php + +namespace Kanboard\Action; + +use Kanboard\Model\TaskModel; + +/** + * Set the due date of task + * + * @package Kanboard\Action + * @author Frederic Guillot + */ +class TaskAssignDueDateOnCreation extends Base +{ + /** + * Get automatic action description + * + * @access public + * @return string + */ + public function getDescription() + { + return t('Automatically set the due date on task creation'); + } + + /** + * Get the list of compatible events + * + * @access public + * @return array + */ + public function getCompatibleEvents() + { + return array( + TaskModel::EVENT_CREATE, + ); + } + + /** + * Get the required parameter for the action (defined by the user) + * + * @access public + * @return array + */ + public function getActionRequiredParameters() + { + return array( + 'duration' => t('Duration in days') + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'task' => array( + 'project_id', + ), + ); + } + + /** + * Execute the action (set the task color) + * + * @access public + * @param array $data Event data dictionary + * @return bool True if the action was executed or false when not executed + */ + public function doAction(array $data) + { + $values = array( + 'id' => $data['task_id'], + 'date_due' => strtotime('+'.$this->getParam('duration').'days'), + ); + + return $this->taskModificationModel->update($values, false); + } + + /** + * Check if the event data meet the action condition + * + * @access public + * @param array $data Event data dictionary + * @return bool + */ + public function hasRequiredCondition(array $data) + { + return true; + } +} diff --git a/app/Action/TaskAssignSpecificUser.php b/app/Action/TaskAssignSpecificUser.php index 50a2b2ae..daf9e1df 100644 --- a/app/Action/TaskAssignSpecificUser.php +++ b/app/Action/TaskAssignSpecificUser.php @@ -7,7 +7,7 @@ use Kanboard\Model\TaskModel; /** * Assign a task to a specific user * - * @package action + * @package Kanboard\Action * @author Frederic Guillot */ class TaskAssignSpecificUser extends Base @@ -61,7 +61,10 @@ class TaskAssignSpecificUser extends Base { return array( 'task_id', - 'column_id', + 'task' => array( + 'project_id', + 'column_id', + ), ); } @@ -91,6 +94,6 @@ class TaskAssignSpecificUser extends Base */ public function hasRequiredCondition(array $data) { - return $data['column_id'] == $this->getParam('column_id'); + return $data['task']['column_id'] == $this->getParam('column_id'); } } diff --git a/app/Action/TaskAssignUser.php b/app/Action/TaskAssignUser.php index 9ea22986..8727b672 100644 --- a/app/Action/TaskAssignUser.php +++ b/app/Action/TaskAssignUser.php @@ -5,7 +5,7 @@ namespace Kanboard\Action; /** * Assign a task to someone * - * @package action + * @package Kanboard\Actionv * @author Frederic Guillot */ class TaskAssignUser extends Base diff --git a/app/Action/TaskClose.php b/app/Action/TaskClose.php index 91e8cf43..e476e9ba 100644 --- a/app/Action/TaskClose.php +++ b/app/Action/TaskClose.php @@ -5,7 +5,7 @@ namespace Kanboard\Action; /** * Close automatically a task * - * @package action + * @package Kanboard\Action * @author Frederic Guillot */ class TaskClose extends Base diff --git a/app/Action/TaskCloseColumn.php b/app/Action/TaskCloseColumn.php index 1edce8fa..523996f4 100644 --- a/app/Action/TaskCloseColumn.php +++ b/app/Action/TaskCloseColumn.php @@ -7,7 +7,7 @@ use Kanboard\Model\TaskModel; /** * Close automatically a task in a specific column * - * @package action + * @package Kanboard\Action * @author Frederic Guillot */ class TaskCloseColumn extends Base @@ -55,7 +55,13 @@ class TaskCloseColumn extends Base */ public function getEventRequiredParameters() { - return array('task_id', 'column_id'); + return array( + 'task_id', + 'task' => array( + 'project_id', + 'column_id', + ) + ); } /** @@ -79,6 +85,6 @@ class TaskCloseColumn extends Base */ public function hasRequiredCondition(array $data) { - return $data['column_id'] == $this->getParam('column_id'); + return $data['task']['column_id'] == $this->getParam('column_id'); } } diff --git a/app/Action/TaskCloseNoActivity.php b/app/Action/TaskCloseNoActivity.php index 5a10510f..ea724d8c 100644 --- a/app/Action/TaskCloseNoActivity.php +++ b/app/Action/TaskCloseNoActivity.php @@ -7,7 +7,7 @@ use Kanboard\Model\TaskModel; /** * Close automatically a task after when inactive * - * @package action + * @package Kanboard\Action * @author Frederic Guillot */ class TaskCloseNoActivity extends Base diff --git a/app/Action/TaskCloseNoActivityColumn.php b/app/Action/TaskCloseNoActivityColumn.php new file mode 100644 index 00000000..b2ee5224 --- /dev/null +++ b/app/Action/TaskCloseNoActivityColumn.php @@ -0,0 +1,96 @@ +<?php + +namespace Kanboard\Action; + +use Kanboard\Model\TaskModel; + +/** + * Close automatically a task after inactive and in an defined column + * + * @package Kanboard\Action + * @author Frederic Guillot + */ +class TaskCloseNoActivityColumn extends Base +{ + /** + * Get automatic action description + * + * @access public + * @return string + */ + public function getDescription() + { + return t('Close a task when there is no activity in an specific column'); + } + + /** + * Get the list of compatible events + * + * @access public + * @return array + */ + public function getCompatibleEvents() + { + return array(TaskModel::EVENT_DAILY_CRONJOB); + } + + /** + * Get the required parameter for the action (defined by the user) + * + * @access public + * @return array + */ + public function getActionRequiredParameters() + { + return array( + 'duration' => t('Duration in days'), + 'column_id' => t('Column') + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array('tasks'); + } + + /** + * Execute the action (close the task) + * + * @access public + * @param array $data Event data dictionary + * @return bool True if the action was executed or false when not executed + */ + public function doAction(array $data) + { + $results = array(); + $max = $this->getParam('duration') * 86400; + + foreach ($data['tasks'] as $task) { + $duration = time() - $task['date_modification']; + + if ($duration > $max && $task['column_id'] == $this->getParam('column_id')) { + $results[] = $this->taskStatusModel->close($task['id']); + } + } + + return in_array(true, $results, true); + } + + /** + * Check if the event data meet the action condition + * + * @access public + * @param array $data Event data dictionary + * @return bool + */ + public function hasRequiredCondition(array $data) + { + return count($data['tasks']) > 0; + } +} diff --git a/app/Action/TaskCreation.php b/app/Action/TaskCreation.php index e9e5c5f3..01d91228 100644 --- a/app/Action/TaskCreation.php +++ b/app/Action/TaskCreation.php @@ -5,7 +5,7 @@ namespace Kanboard\Action; /** * Create automatically a task from a webhook * - * @package action + * @package Kanboard\Action * @author Frederic Guillot */ class TaskCreation extends Base @@ -52,6 +52,7 @@ class TaskCreation extends Base public function getEventRequiredParameters() { return array( + 'project_id', 'reference', 'title', ); diff --git a/app/Action/TaskDuplicateAnotherProject.php b/app/Action/TaskDuplicateAnotherProject.php index 1d4a2f13..0ad7713c 100644 --- a/app/Action/TaskDuplicateAnotherProject.php +++ b/app/Action/TaskDuplicateAnotherProject.php @@ -7,7 +7,7 @@ use Kanboard\Model\TaskModel; /** * Duplicate a task to another project * - * @package action + * @package Kanboard\Action * @author Frederic Guillot */ class TaskDuplicateAnotherProject extends Base @@ -62,7 +62,10 @@ class TaskDuplicateAnotherProject extends Base { return array( 'task_id', - 'column_id', + 'task' => array( + 'project_id', + 'column_id', + ) ); } @@ -76,7 +79,12 @@ class TaskDuplicateAnotherProject extends Base public function doAction(array $data) { $destination_column_id = $this->columnModel->getFirstColumnId($this->getParam('project_id')); - return (bool) $this->taskDuplicationModel->duplicateToProject($data['task_id'], $this->getParam('project_id'), null, $destination_column_id); + return (bool) $this->taskProjectDuplicationModel->duplicateToProject( + $data['task_id'], + $this->getParam('project_id'), + null, + $destination_column_id + ); } /** @@ -88,6 +96,6 @@ class TaskDuplicateAnotherProject extends Base */ public function hasRequiredCondition(array $data) { - return $data['column_id'] == $this->getParam('column_id') && $data['project_id'] != $this->getParam('project_id'); + return $data['task']['column_id'] == $this->getParam('column_id') && $data['task']['project_id'] != $this->getParam('project_id'); } } diff --git a/app/Action/TaskEmail.php b/app/Action/TaskEmail.php index 7f9ba416..fdfe7987 100644 --- a/app/Action/TaskEmail.php +++ b/app/Action/TaskEmail.php @@ -7,7 +7,7 @@ use Kanboard\Model\TaskModel; /** * Email a task to someone * - * @package action + * @package Kanboard\Action * @author Frederic Guillot */ class TaskEmail extends Base @@ -62,7 +62,10 @@ class TaskEmail extends Base { return array( 'task_id', - 'column_id', + 'task' => array( + 'project_id', + 'column_id', + ), ); } @@ -78,13 +81,14 @@ class TaskEmail extends Base $user = $this->userModel->getById($this->getParam('user_id')); if (! empty($user['email'])) { - $task = $this->taskFinderModel->getDetails($data['task_id']); - $this->emailClient->send( $user['email'], $user['name'] ?: $user['username'], $this->getParam('subject'), - $this->template->render('notification/task_create', array('task' => $task, 'application_url' => $this->configModel->get('application_url'))) + $this->template->render('notification/task_create', array( + 'task' => $data['task'], + 'application_url' => $this->configModel->get('application_url'), + )) ); return true; @@ -102,6 +106,6 @@ class TaskEmail extends Base */ public function hasRequiredCondition(array $data) { - return $data['column_id'] == $this->getParam('column_id'); + return $data['task']['column_id'] == $this->getParam('column_id'); } } diff --git a/app/Action/TaskEmailNoActivity.php b/app/Action/TaskEmailNoActivity.php index c60702fb..cac4281e 100644 --- a/app/Action/TaskEmailNoActivity.php +++ b/app/Action/TaskEmailNoActivity.php @@ -7,7 +7,7 @@ use Kanboard\Model\TaskModel; /** * Email a task with no activity * - * @package action + * @package Kanboard\Action * @author Frederic Guillot */ class TaskEmailNoActivity extends Base diff --git a/app/Action/TaskMoveAnotherProject.php b/app/Action/TaskMoveAnotherProject.php index 73ad4b69..0fa22b1b 100644 --- a/app/Action/TaskMoveAnotherProject.php +++ b/app/Action/TaskMoveAnotherProject.php @@ -7,7 +7,7 @@ use Kanboard\Model\TaskModel; /** * Move a task to another project * - * @package action + * @package Kanboard\Action * @author Frederic Guillot */ class TaskMoveAnotherProject extends Base @@ -61,8 +61,10 @@ class TaskMoveAnotherProject extends Base { return array( 'task_id', - 'column_id', - 'project_id', + 'task' => array( + 'project_id', + 'column_id', + ) ); } @@ -75,7 +77,7 @@ class TaskMoveAnotherProject extends Base */ public function doAction(array $data) { - return $this->taskDuplicationModel->moveToProject($data['task_id'], $this->getParam('project_id')); + return $this->taskProjectMoveModel->moveToProject($data['task_id'], $this->getParam('project_id')); } /** @@ -87,6 +89,6 @@ class TaskMoveAnotherProject extends Base */ public function hasRequiredCondition(array $data) { - return $data['column_id'] == $this->getParam('column_id') && $data['project_id'] != $this->getParam('project_id'); + return $data['task']['column_id'] == $this->getParam('column_id') && $data['task']['project_id'] != $this->getParam('project_id'); } } diff --git a/app/Action/TaskMoveColumnAssigned.php b/app/Action/TaskMoveColumnAssigned.php index 7e3db9c5..1cfe6743 100644 --- a/app/Action/TaskMoveColumnAssigned.php +++ b/app/Action/TaskMoveColumnAssigned.php @@ -7,7 +7,7 @@ use Kanboard\Model\TaskModel; /** * Move a task to another column when an assignee is set * - * @package action + * @package Kanboard\Action * @author Francois Ferrand */ class TaskMoveColumnAssigned extends Base @@ -61,8 +61,13 @@ class TaskMoveColumnAssigned extends Base { return array( 'task_id', - 'column_id', - 'owner_id' + 'task' => array( + 'project_id', + 'column_id', + 'owner_id', + 'position', + 'swimlane_id', + ) ); } @@ -75,14 +80,12 @@ class TaskMoveColumnAssigned extends Base */ public function doAction(array $data) { - $original_task = $this->taskFinderModel->getById($data['task_id']); - return $this->taskPositionModel->movePosition( - $data['project_id'], + $data['task']['project_id'], $data['task_id'], $this->getParam('dest_column_id'), - $original_task['position'], - $original_task['swimlane_id'], + $data['task']['position'], + $data['task']['swimlane_id'], false ); } @@ -96,6 +99,6 @@ class TaskMoveColumnAssigned extends Base */ public function hasRequiredCondition(array $data) { - return $data['column_id'] == $this->getParam('src_column_id') && $data['owner_id'] > 0; + return $data['task']['column_id'] == $this->getParam('src_column_id') && $data['task']['owner_id'] > 0; } } diff --git a/app/Action/TaskMoveColumnCategoryChange.php b/app/Action/TaskMoveColumnCategoryChange.php index e4f88760..13d6ee4f 100644 --- a/app/Action/TaskMoveColumnCategoryChange.php +++ b/app/Action/TaskMoveColumnCategoryChange.php @@ -7,7 +7,7 @@ use Kanboard\Model\TaskModel; /** * Move a task to another column when the category is changed * - * @package action + * @package Kanboard\Action * @author Francois Ferrand */ class TaskMoveColumnCategoryChange extends Base @@ -60,8 +60,13 @@ class TaskMoveColumnCategoryChange extends Base { return array( 'task_id', - 'column_id', - 'category_id', + 'task' => array( + 'project_id', + 'column_id', + 'category_id', + 'position', + 'swimlane_id', + ) ); } @@ -74,14 +79,12 @@ class TaskMoveColumnCategoryChange extends Base */ public function doAction(array $data) { - $original_task = $this->taskFinderModel->getById($data['task_id']); - return $this->taskPositionModel->movePosition( - $data['project_id'], + $data['task']['project_id'], $data['task_id'], $this->getParam('dest_column_id'), - $original_task['position'], - $original_task['swimlane_id'], + $data['task']['position'], + $data['task']['swimlane_id'], false ); } @@ -95,6 +98,6 @@ class TaskMoveColumnCategoryChange extends Base */ public function hasRequiredCondition(array $data) { - return $data['column_id'] != $this->getParam('dest_column_id') && $data['category_id'] == $this->getParam('category_id'); + return $data['task']['column_id'] != $this->getParam('dest_column_id') && $data['task']['category_id'] == $this->getParam('category_id'); } } diff --git a/app/Action/TaskMoveColumnClosed.php b/app/Action/TaskMoveColumnClosed.php new file mode 100644 index 00000000..3f3e2124 --- /dev/null +++ b/app/Action/TaskMoveColumnClosed.php @@ -0,0 +1,102 @@ +<?php + +namespace Kanboard\Action; + +use Kanboard\Model\TaskModel; + +/** + * Move a task to another column when the task is closed + * + * @package Kanboard\Action + * @author Frederic Guillot + */ +class TaskMoveColumnClosed extends Base +{ + /** + * Get automatic action description + * + * @access public + * @return string + */ + public function getDescription() + { + return t('Move the task to another column when closed'); + } + + /** + * Get the list of compatible events + * + * @access public + * @return array + */ + public function getCompatibleEvents() + { + return array( + TaskModel::EVENT_CLOSE, + ); + } + + /** + * Get the required parameter for the action (defined by the user) + * + * @access public + * @return array + */ + public function getActionRequiredParameters() + { + return array( + 'dest_column_id' => t('Destination column'), + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array( + 'task_id', + 'task' => array( + 'project_id', + 'column_id', + 'swimlane_id', + 'is_active', + ) + ); + } + + /** + * Execute the action (move the task to another column) + * + * @access public + * @param array $data Event data dictionary + * @return bool True if the action was executed or false when not executed + */ + public function doAction(array $data) + { + return $this->taskPositionModel->movePosition( + $data['task']['project_id'], + $data['task']['id'], + $this->getParam('dest_column_id'), + 1, + $data['task']['swimlane_id'], + false, + false + ); + } + + /** + * Check if the event data meet the action condition + * + * @access public + * @param array $data Event data dictionary + * @return bool + */ + public function hasRequiredCondition(array $data) + { + return $data['task']['column_id'] != $this->getParam('dest_column_id') && $data['task']['is_active'] == 0; + } +} diff --git a/app/Action/TaskMoveColumnNotMovedPeriod.php b/app/Action/TaskMoveColumnNotMovedPeriod.php new file mode 100644 index 00000000..87e7e405 --- /dev/null +++ b/app/Action/TaskMoveColumnNotMovedPeriod.php @@ -0,0 +1,104 @@ +<?php + +namespace Kanboard\Action; + +use Kanboard\Model\TaskModel; + +/** + * Move a task to another column when not moved during a given period + * + * @package Kanboard\Action + * @author Frederic Guillot + */ +class TaskMoveColumnNotMovedPeriod extends Base +{ + /** + * Get automatic action description + * + * @access public + * @return string + */ + public function getDescription() + { + return t('Move the task to another column when not moved during a given period'); + } + + /** + * Get the list of compatible events + * + * @access public + * @return array + */ + public function getCompatibleEvents() + { + return array(TaskModel::EVENT_DAILY_CRONJOB); + } + + /** + * Get the required parameter for the action (defined by the user) + * + * @access public + * @return array + */ + public function getActionRequiredParameters() + { + return array( + 'duration' => t('Duration in days'), + 'src_column_id' => t('Source column'), + 'dest_column_id' => t('Destination column'), + ); + } + + /** + * Get the required parameter for the event + * + * @access public + * @return string[] + */ + public function getEventRequiredParameters() + { + return array('tasks'); + } + + /** + * Execute the action (close the task) + * + * @access public + * @param array $data Event data dictionary + * @return bool True if the action was executed or false when not executed + */ + public function doAction(array $data) + { + $results = array(); + $max = $this->getParam('duration') * 86400; + + foreach ($data['tasks'] as $task) { + $duration = time() - $task['date_moved']; + + if ($duration > $max && $task['column_id'] == $this->getParam('src_column_id')) { + $results[] = $this->taskPositionModel->movePosition( + $task['project_id'], + $task['id'], + $this->getParam('dest_column_id'), + 1, + $task['swimlane_id'], + false + ); + } + } + + return in_array(true, $results, true); + } + + /** + * Check if the event data meet the action condition + * + * @access public + * @param array $data Event data dictionary + * @return bool + */ + public function hasRequiredCondition(array $data) + { + return count($data['tasks']) > 0; + } +} diff --git a/app/Action/TaskMoveColumnUnAssigned.php b/app/Action/TaskMoveColumnUnAssigned.php index c3ae9e1d..ab63d624 100644 --- a/app/Action/TaskMoveColumnUnAssigned.php +++ b/app/Action/TaskMoveColumnUnAssigned.php @@ -7,7 +7,7 @@ use Kanboard\Model\TaskModel; /** * Move a task to another column when an assignee is cleared * - * @package action + * @package Kanboard\Action * @author Francois Ferrand */ class TaskMoveColumnUnAssigned extends Base @@ -61,8 +61,13 @@ class TaskMoveColumnUnAssigned extends Base { return array( 'task_id', - 'column_id', - 'owner_id' + 'task' => array( + 'project_id', + 'column_id', + 'owner_id', + 'position', + 'swimlane_id', + ) ); } @@ -75,14 +80,12 @@ class TaskMoveColumnUnAssigned extends Base */ public function doAction(array $data) { - $original_task = $this->taskFinderModel->getById($data['task_id']); - return $this->taskPositionModel->movePosition( - $data['project_id'], + $data['task']['project_id'], $data['task_id'], $this->getParam('dest_column_id'), - $original_task['position'], - $original_task['swimlane_id'], + $data['task']['position'], + $data['task']['swimlane_id'], false ); } @@ -96,6 +99,6 @@ class TaskMoveColumnUnAssigned extends Base */ public function hasRequiredCondition(array $data) { - return $data['column_id'] == $this->getParam('src_column_id') && $data['owner_id'] == 0; + return $data['task']['column_id'] == $this->getParam('src_column_id') && $data['task']['owner_id'] == 0; } } diff --git a/app/Action/TaskOpen.php b/app/Action/TaskOpen.php index 8e847b8e..49017831 100644 --- a/app/Action/TaskOpen.php +++ b/app/Action/TaskOpen.php @@ -5,7 +5,7 @@ namespace Kanboard\Action; /** * Open automatically a task * - * @package action + * @package Kanboard\Action * @author Frederic Guillot */ class TaskOpen extends Base diff --git a/app/Action/TaskUpdateStartDate.php b/app/Action/TaskUpdateStartDate.php index e5410a87..160f6ee5 100644 --- a/app/Action/TaskUpdateStartDate.php +++ b/app/Action/TaskUpdateStartDate.php @@ -7,7 +7,7 @@ use Kanboard\Model\TaskModel; /** * Set the start date of task * - * @package action + * @package Kanboard\Action * @author Frederic Guillot */ class TaskUpdateStartDate extends Base @@ -59,7 +59,10 @@ class TaskUpdateStartDate extends Base { return array( 'task_id', - 'column_id', + 'task' => array( + 'project_id', + 'column_id', + ), ); } @@ -89,6 +92,6 @@ class TaskUpdateStartDate extends Base */ public function hasRequiredCondition(array $data) { - return $data['column_id'] == $this->getParam('column_id'); + return $data['task']['column_id'] == $this->getParam('column_id'); } } diff --git a/app/Api/Authorization/ActionAuthorization.php b/app/Api/Authorization/ActionAuthorization.php new file mode 100644 index 00000000..4b41ad82 --- /dev/null +++ b/app/Api/Authorization/ActionAuthorization.php @@ -0,0 +1,19 @@ +<?php + +namespace Kanboard\Api\Authorization; + +/** + * Class ActionAuthorization + * + * @package Kanboard\Api\Authorization + * @author Frederic Guillot + */ +class ActionAuthorization extends ProjectAuthorization +{ + public function check($class, $method, $action_id) + { + if ($this->userSession->isLogged()) { + $this->checkProjectPermission($class, $method, $this->actionModel->getProjectId($action_id)); + } + } +} diff --git a/app/Api/Authorization/CategoryAuthorization.php b/app/Api/Authorization/CategoryAuthorization.php new file mode 100644 index 00000000..f17265a2 --- /dev/null +++ b/app/Api/Authorization/CategoryAuthorization.php @@ -0,0 +1,19 @@ +<?php + +namespace Kanboard\Api\Authorization; + +/** + * Class CategoryAuthorization + * + * @package Kanboard\Api\Authorization + * @author Frederic Guillot + */ +class CategoryAuthorization extends ProjectAuthorization +{ + public function check($class, $method, $category_id) + { + if ($this->userSession->isLogged()) { + $this->checkProjectPermission($class, $method, $this->categoryModel->getProjectId($category_id)); + } + } +} diff --git a/app/Api/Authorization/ColumnAuthorization.php b/app/Api/Authorization/ColumnAuthorization.php new file mode 100644 index 00000000..37aecda2 --- /dev/null +++ b/app/Api/Authorization/ColumnAuthorization.php @@ -0,0 +1,19 @@ +<?php + +namespace Kanboard\Api\Authorization; + +/** + * Class ColumnAuthorization + * + * @package Kanboard\Api\Authorization + * @author Frederic Guillot + */ +class ColumnAuthorization extends ProjectAuthorization +{ + public function check($class, $method, $column_id) + { + if ($this->userSession->isLogged()) { + $this->checkProjectPermission($class, $method, $this->columnModel->getProjectId($column_id)); + } + } +} diff --git a/app/Api/Authorization/CommentAuthorization.php b/app/Api/Authorization/CommentAuthorization.php new file mode 100644 index 00000000..ed15512e --- /dev/null +++ b/app/Api/Authorization/CommentAuthorization.php @@ -0,0 +1,19 @@ +<?php + +namespace Kanboard\Api\Authorization; + +/** + * Class CommentAuthorization + * + * @package Kanboard\Api\Authorization + * @author Frederic Guillot + */ +class CommentAuthorization extends ProjectAuthorization +{ + public function check($class, $method, $comment_id) + { + if ($this->userSession->isLogged()) { + $this->checkProjectPermission($class, $method, $this->commentModel->getProjectId($comment_id)); + } + } +} diff --git a/app/Api/Authorization/ProcedureAuthorization.php b/app/Api/Authorization/ProcedureAuthorization.php new file mode 100644 index 00000000..070a6371 --- /dev/null +++ b/app/Api/Authorization/ProcedureAuthorization.php @@ -0,0 +1,32 @@ +<?php + +namespace Kanboard\Api\Authorization; + +use JsonRPC\Exception\AccessDeniedException; +use Kanboard\Core\Base; + +/** + * Class ProcedureAuthorization + * + * @package Kanboard\Api\Authorization + * @author Frederic Guillot + */ +class ProcedureAuthorization extends Base +{ + private $userSpecificProcedures = array( + 'getMe', + 'getMyDashboard', + 'getMyActivityStream', + 'createMyPrivateProject', + 'getMyProjectsList', + 'getMyProjects', + 'getMyOverdueTasks', + ); + + public function check($procedure) + { + if (! $this->userSession->isLogged() && in_array($procedure, $this->userSpecificProcedures)) { + throw new AccessDeniedException('This procedure is not available with the API credentials'); + } + } +} diff --git a/app/Api/Authorization/ProjectAuthorization.php b/app/Api/Authorization/ProjectAuthorization.php new file mode 100644 index 00000000..21ecf311 --- /dev/null +++ b/app/Api/Authorization/ProjectAuthorization.php @@ -0,0 +1,35 @@ +<?php + +namespace Kanboard\Api\Authorization; + +use JsonRPC\Exception\AccessDeniedException; +use Kanboard\Core\Base; + +/** + * Class ProjectAuthorization + * + * @package Kanboard\Api\Authorization + * @author Frederic Guillot + */ +class ProjectAuthorization extends Base +{ + public function check($class, $method, $project_id) + { + if ($this->userSession->isLogged()) { + $this->checkProjectPermission($class, $method, $project_id); + } + } + + protected function checkProjectPermission($class, $method, $project_id) + { + if (empty($project_id)) { + throw new AccessDeniedException('Project not found'); + } + + $role = $this->projectUserRoleModel->getUserRole($project_id, $this->userSession->getId()); + + if (! $this->apiProjectAuthorization->isAllowed($class, $method, $role)) { + throw new AccessDeniedException('Project access denied'); + } + } +} diff --git a/app/Api/Authorization/SubtaskAuthorization.php b/app/Api/Authorization/SubtaskAuthorization.php new file mode 100644 index 00000000..fcb57929 --- /dev/null +++ b/app/Api/Authorization/SubtaskAuthorization.php @@ -0,0 +1,19 @@ +<?php + +namespace Kanboard\Api\Authorization; + +/** + * Class SubtaskAuthorization + * + * @package Kanboard\Api\Authorization + * @author Frederic Guillot + */ +class SubtaskAuthorization extends ProjectAuthorization +{ + public function check($class, $method, $subtask_id) + { + if ($this->userSession->isLogged()) { + $this->checkProjectPermission($class, $method, $this->subtaskModel->getProjectId($subtask_id)); + } + } +} diff --git a/app/Api/Authorization/TaskAuthorization.php b/app/Api/Authorization/TaskAuthorization.php new file mode 100644 index 00000000..db93b76b --- /dev/null +++ b/app/Api/Authorization/TaskAuthorization.php @@ -0,0 +1,19 @@ +<?php + +namespace Kanboard\Api\Authorization; + +/** + * Class TaskAuthorization + * + * @package Kanboard\Api\Authorization + * @author Frederic Guillot + */ +class TaskAuthorization extends ProjectAuthorization +{ + public function check($class, $method, $category_id) + { + if ($this->userSession->isLogged()) { + $this->checkProjectPermission($class, $method, $this->taskFinderModel->getProjectId($category_id)); + } + } +} diff --git a/app/Api/Authorization/TaskFileAuthorization.php b/app/Api/Authorization/TaskFileAuthorization.php new file mode 100644 index 00000000..e40783eb --- /dev/null +++ b/app/Api/Authorization/TaskFileAuthorization.php @@ -0,0 +1,19 @@ +<?php + +namespace Kanboard\Api\Authorization; + +/** + * Class TaskFileAuthorization + * + * @package Kanboard\Api\Authorization + * @author Frederic Guillot + */ +class TaskFileAuthorization extends ProjectAuthorization +{ + public function check($class, $method, $file_id) + { + if ($this->userSession->isLogged()) { + $this->checkProjectPermission($class, $method, $this->taskFileModel->getProjectId($file_id)); + } + } +} diff --git a/app/Api/Authorization/TaskLinkAuthorization.php b/app/Api/Authorization/TaskLinkAuthorization.php new file mode 100644 index 00000000..2f5fc8d5 --- /dev/null +++ b/app/Api/Authorization/TaskLinkAuthorization.php @@ -0,0 +1,19 @@ +<?php + +namespace Kanboard\Api\Authorization; + +/** + * Class TaskLinkAuthorization + * + * @package Kanboard\Api\Authorization + * @author Frederic Guillot + */ +class TaskLinkAuthorization extends ProjectAuthorization +{ + public function check($class, $method, $task_link_id) + { + if ($this->userSession->isLogged()) { + $this->checkProjectPermission($class, $method, $this->taskLinkModel->getProjectId($task_link_id)); + } + } +} diff --git a/app/Api/Authorization/UserAuthorization.php b/app/Api/Authorization/UserAuthorization.php new file mode 100644 index 00000000..3fd6865c --- /dev/null +++ b/app/Api/Authorization/UserAuthorization.php @@ -0,0 +1,22 @@ +<?php + +namespace Kanboard\Api\Authorization; + +use JsonRPC\Exception\AccessDeniedException; +use Kanboard\Core\Base; + +/** + * Class UserAuthorization + * + * @package Kanboard\Api\Authorization + * @author Frederic Guillot + */ +class UserAuthorization extends Base +{ + public function check($class, $method) + { + if ($this->userSession->isLogged() && ! $this->apiAuthorization->isAllowed($class, $method, $this->userSession->getRole())) { + throw new AccessDeniedException('You are not allowed to access to this resource'); + } + } +} diff --git a/app/Api/BoardApi.php b/app/Api/BoardApi.php deleted file mode 100644 index aa5942af..00000000 --- a/app/Api/BoardApi.php +++ /dev/null @@ -1,18 +0,0 @@ -<?php - -namespace Kanboard\Api; - -/** - * Board API controller - * - * @package Kanboard\Api - * @author Frederic Guillot - */ -class BoardApi extends BaseApi -{ - public function getBoard($project_id) - { - $this->checkProjectPermission($project_id); - return $this->boardModel->getBoard($project_id); - } -} diff --git a/app/Api/Middleware/AuthenticationApiMiddleware.php b/app/Api/Middleware/AuthenticationMiddleware.php index b16e10b8..8e309593 100644 --- a/app/Api/Middleware/AuthenticationApiMiddleware.php +++ b/app/Api/Middleware/AuthenticationMiddleware.php @@ -13,46 +13,8 @@ use Kanboard\Core\Base; * @package Kanboard\Api\Middleware * @author Frederic Guillot */ -class AuthenticationApiMiddleware extends Base implements MiddlewareInterface +class AuthenticationMiddleware extends Base implements MiddlewareInterface { - private $user_allowed_procedures = array( - 'getMe', - 'getMyDashboard', - 'getMyActivityStream', - 'createMyPrivateProject', - 'getMyProjectsList', - 'getMyProjects', - 'getMyOverdueTasks', - ); - - private $both_allowed_procedures = array( - 'getTimezone', - 'getVersion', - 'getDefaultTaskColor', - 'getDefaultTaskColors', - 'getColorList', - 'getProjectById', - 'getSubTask', - 'getTask', - 'getTaskByReference', - 'getTimeSpent', - 'getAllTasks', - 'getAllSubTasks', - 'hasTimer', - 'logStartTime', - 'logEndTime', - 'openTask', - 'closeTask', - 'moveTaskPosition', - 'createTask', - 'createSubtask', - 'updateTask', - 'getBoard', - 'getProjectActivity', - 'getOverdueTasksByProject', - 'searchTasks', - ); - /** * Execute Middleware * @@ -68,11 +30,8 @@ class AuthenticationApiMiddleware extends Base implements MiddlewareInterface $this->dispatcher->dispatch('app.bootstrap'); if ($this->isUserAuthenticated($username, $password)) { - $this->checkProcedurePermission(true, $procedureName); $this->userSession->initialize($this->userModel->getByUsername($username)); - } elseif ($this->isAppAuthenticated($username, $password)) { - $this->checkProcedurePermission(false, $procedureName); - } else { + } elseif (! $this->isAppAuthenticated($username, $password)) { $this->logger->error('API authentication failure for '.$username); throw new AuthenticationFailureException('Wrong credentials'); } @@ -120,18 +79,4 @@ class AuthenticationApiMiddleware extends Base implements MiddlewareInterface return $this->configModel->get('api_token'); } - - public function checkProcedurePermission($is_user, $procedure) - { - $is_both_procedure = in_array($procedure, $this->both_allowed_procedures); - $is_user_procedure = in_array($procedure, $this->user_allowed_procedures); - - if ($is_user && ! $is_both_procedure && ! $is_user_procedure) { - throw new AccessDeniedException('Permission denied'); - } elseif (! $is_user && ! $is_both_procedure && $is_user_procedure) { - throw new AccessDeniedException('Permission denied'); - } - - $this->logger->debug('API call: '.$procedure); - } } diff --git a/app/Api/ActionApi.php b/app/Api/Procedure/ActionProcedure.php index 116742d8..4043dbb9 100644 --- a/app/Api/ActionApi.php +++ b/app/Api/Procedure/ActionProcedure.php @@ -1,16 +1,17 @@ <?php -namespace Kanboard\Api; +namespace Kanboard\Api\Procedure; -use Kanboard\Core\Base; +use Kanboard\Api\Authorization\ActionAuthorization; +use Kanboard\Api\Authorization\ProjectAuthorization; /** * Action API controller * - * @package Kanboard\Api + * @package Kanboard\Api\Procedure * @author Frederic Guillot */ -class ActionApi extends Base +class ActionProcedure extends BaseProcedure { public function getAvailableActions() { @@ -29,16 +30,19 @@ class ActionApi extends Base public function removeAction($action_id) { + ActionAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeAction', $action_id); return $this->actionModel->remove($action_id); } public function getActions($project_id) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getActions', $project_id); return $this->actionModel->getAllByProject($project_id); } public function createAction($project_id, $event_name, $action_name, array $params) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'createAction', $project_id); $values = array( 'project_id' => $project_id, 'event_name' => $event_name, diff --git a/app/Api/AppApi.php b/app/Api/Procedure/AppProcedure.php index 637de5c5..60af4a60 100644 --- a/app/Api/AppApi.php +++ b/app/Api/Procedure/AppProcedure.php @@ -1,16 +1,14 @@ <?php -namespace Kanboard\Api; - -use Kanboard\Core\Base; +namespace Kanboard\Api\Procedure; /** * App API controller * - * @package Kanboard\Api + * @package Kanboard\Api\Procedure * @author Frederic Guillot */ -class AppApi extends Base +class AppProcedure extends BaseProcedure { public function getTimezone() { diff --git a/app/Api/BaseApi.php b/app/Api/Procedure/BaseProcedure.php index 9f69aa65..e31b3027 100644 --- a/app/Api/BaseApi.php +++ b/app/Api/Procedure/BaseProcedure.php @@ -1,30 +1,24 @@ <?php -namespace Kanboard\Api; +namespace Kanboard\Api\Procedure; -use JsonRPC\Exception\AccessDeniedException; +use Kanboard\Api\Authorization\ProcedureAuthorization; +use Kanboard\Api\Authorization\UserAuthorization; use Kanboard\Core\Base; +use ReflectionClass; /** * Base class * - * @package Kanboard\Api + * @package Kanboard\Api\Procedure * @author Frederic Guillot */ -abstract class BaseApi extends Base +abstract class BaseProcedure extends Base { - public function checkProjectPermission($project_id) + public function beforeProcedure($procedure) { - if ($this->userSession->isLogged() && ! $this->projectPermissionModel->isUserAllowed($project_id, $this->userSession->getId())) { - throw new AccessDeniedException('Permission denied'); - } - } - - public function checkTaskPermission($task_id) - { - if ($this->userSession->isLogged()) { - $this->checkProjectPermission($this->taskFinderModel->getProjectId($task_id)); - } + ProcedureAuthorization::getInstance($this->container)->check($procedure); + UserAuthorization::getInstance($this->container)->check($this->getClassName(), $procedure); } protected function formatTask($task) @@ -71,4 +65,21 @@ abstract class BaseApi extends Base return $projects; } + + protected function filterValues(array $values) + { + foreach ($values as $key => $value) { + if (is_null($value)) { + unset($values[$key]); + } + } + + return $values; + } + + protected function getClassName() + { + $reflection = new ReflectionClass(get_called_class()); + return $reflection->getShortName(); + } } diff --git a/app/Api/Procedure/BoardProcedure.php b/app/Api/Procedure/BoardProcedure.php new file mode 100644 index 00000000..674b5466 --- /dev/null +++ b/app/Api/Procedure/BoardProcedure.php @@ -0,0 +1,25 @@ +<?php + +namespace Kanboard\Api\Procedure; + +use Kanboard\Api\Authorization\ProjectAuthorization; +use Kanboard\Formatter\BoardFormatter; + +/** + * Board API controller + * + * @package Kanboard\Api\Procedure + * @author Frederic Guillot + */ +class BoardProcedure extends BaseProcedure +{ + public function getBoard($project_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getBoard', $project_id); + + return BoardFormatter::getInstance($this->container) + ->withProjectId($project_id) + ->withQuery($this->taskFinderModel->getExtendedQuery()) + ->format(); + } +} diff --git a/app/Api/CategoryApi.php b/app/Api/Procedure/CategoryProcedure.php index c56cfb35..3ebbd908 100644 --- a/app/Api/CategoryApi.php +++ b/app/Api/Procedure/CategoryProcedure.php @@ -1,34 +1,40 @@ <?php -namespace Kanboard\Api; +namespace Kanboard\Api\Procedure; -use Kanboard\Core\Base; +use Kanboard\Api\Authorization\CategoryAuthorization; +use Kanboard\Api\Authorization\ProjectAuthorization; /** * Category API controller * - * @package Kanboard\Api + * @package Kanboard\Api\Procedure * @author Frederic Guillot */ -class CategoryApi extends Base +class CategoryProcedure extends BaseProcedure { public function getCategory($category_id) { + CategoryAuthorization::getInstance($this->container)->check($this->getClassName(), 'getCategory', $category_id); return $this->categoryModel->getById($category_id); } public function getAllCategories($project_id) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getAllCategories', $project_id); return $this->categoryModel->getAll($project_id); } public function removeCategory($category_id) { + CategoryAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeCategory', $category_id); return $this->categoryModel->remove($category_id); } public function createCategory($project_id, $name) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'createCategory', $project_id); + $values = array( 'project_id' => $project_id, 'name' => $name, @@ -40,6 +46,8 @@ class CategoryApi extends Base public function updateCategory($id, $name) { + CategoryAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateCategory', $id); + $values = array( 'id' => $id, 'name' => $name, diff --git a/app/Api/ColumnApi.php b/app/Api/Procedure/ColumnProcedure.php index aa4026f6..ab9d173b 100644 --- a/app/Api/ColumnApi.php +++ b/app/Api/Procedure/ColumnProcedure.php @@ -1,42 +1,51 @@ <?php -namespace Kanboard\Api; +namespace Kanboard\Api\Procedure; + +use Kanboard\Api\Authorization\ColumnAuthorization; +use Kanboard\Api\Authorization\ProjectAuthorization; /** * Column API controller * - * @package Kanboard\Api + * @package Kanboard\Api\Procedure * @author Frederic Guillot */ -class ColumnApi extends BaseApi +class ColumnProcedure extends BaseProcedure { public function getColumns($project_id) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getColumns', $project_id); return $this->columnModel->getAll($project_id); } public function getColumn($column_id) { + ColumnAuthorization::getInstance($this->container)->check($this->getClassName(), 'getColumn', $column_id); return $this->columnModel->getById($column_id); } public function updateColumn($column_id, $title, $task_limit = 0, $description = '') { + ColumnAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateColumn', $column_id); return $this->columnModel->update($column_id, $title, $task_limit, $description); } public function addColumn($project_id, $title, $task_limit = 0, $description = '') { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'addColumn', $project_id); return $this->columnModel->create($project_id, $title, $task_limit, $description); } public function removeColumn($column_id) { + ColumnAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeColumn', $column_id); return $this->columnModel->remove($column_id); } public function changeColumnPosition($project_id, $column_id, $position) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'changeColumnPosition', $project_id); return $this->columnModel->changePosition($project_id, $column_id, $position); } } diff --git a/app/Api/CommentApi.php b/app/Api/Procedure/CommentProcedure.php index 8358efee..019a49bb 100644 --- a/app/Api/CommentApi.php +++ b/app/Api/Procedure/CommentProcedure.php @@ -1,34 +1,40 @@ <?php -namespace Kanboard\Api; +namespace Kanboard\Api\Procedure; -use Kanboard\Core\Base; +use Kanboard\Api\Authorization\CommentAuthorization; +use Kanboard\Api\Authorization\TaskAuthorization; /** * Comment API controller * - * @package Kanboard\Api + * @package Kanboard\Api\Procedure * @author Frederic Guillot */ -class CommentApi extends Base +class CommentProcedure extends BaseProcedure { public function getComment($comment_id) { + CommentAuthorization::getInstance($this->container)->check($this->getClassName(), 'getComment', $comment_id); return $this->commentModel->getById($comment_id); } public function getAllComments($task_id) { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getAllComments', $task_id); return $this->commentModel->getAll($task_id); } public function removeComment($comment_id) { + CommentAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeComment', $comment_id); return $this->commentModel->remove($comment_id); } public function createComment($task_id, $user_id, $content, $reference = '') { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'createComment', $task_id); + $values = array( 'task_id' => $task_id, 'user_id' => $user_id, @@ -43,6 +49,8 @@ class CommentApi extends Base public function updateComment($id, $content) { + CommentAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateComment', $id); + $values = array( 'id' => $id, 'comment' => $content, diff --git a/app/Api/GroupMemberApi.php b/app/Api/Procedure/GroupMemberProcedure.php index e09f6975..081d6ac8 100644 --- a/app/Api/GroupMemberApi.php +++ b/app/Api/Procedure/GroupMemberProcedure.php @@ -1,16 +1,14 @@ <?php -namespace Kanboard\Api; - -use Kanboard\Core\Base; +namespace Kanboard\Api\Procedure; /** * Group Member API controller * - * @package Kanboard\Api + * @package Kanboard\Api\Procedure * @author Frederic Guillot */ -class GroupMemberApi extends Base +class GroupMemberProcedure extends BaseProcedure { public function getMemberGroups($user_id) { diff --git a/app/Api/GroupApi.php b/app/Api/Procedure/GroupProcedure.php index 1701edc3..804940a2 100644 --- a/app/Api/GroupApi.php +++ b/app/Api/Procedure/GroupProcedure.php @@ -1,16 +1,14 @@ <?php -namespace Kanboard\Api; - -use Kanboard\Core\Base; +namespace Kanboard\Api\Procedure; /** * Group API controller * - * @package Kanboard\Api + * @package Kanboard\Api\Procedure * @author Frederic Guillot */ -class GroupApi extends Base +class GroupProcedure extends BaseProcedure { public function createGroup($name, $external_id = '') { diff --git a/app/Api/LinkApi.php b/app/Api/Procedure/LinkProcedure.php index d8e525e4..b4cecf3a 100644 --- a/app/Api/LinkApi.php +++ b/app/Api/Procedure/LinkProcedure.php @@ -1,16 +1,14 @@ <?php -namespace Kanboard\Api; - -use Kanboard\Core\Base; +namespace Kanboard\Api\Procedure; /** * Link API controller * - * @package Kanboard\Api + * @package Kanboard\Api\Procedure * @author Frederic Guillot */ -class LinkApi extends Base +class LinkProcedure extends BaseProcedure { /** * Get a link by id diff --git a/app/Api/MeApi.php b/app/Api/Procedure/MeProcedure.php index 497749b6..e59e6522 100644 --- a/app/Api/MeApi.php +++ b/app/Api/Procedure/MeProcedure.php @@ -1,16 +1,16 @@ <?php -namespace Kanboard\Api; +namespace Kanboard\Api\Procedure; use Kanboard\Model\SubtaskModel; /** * Me API controller * - * @package Kanboard\Api + * @package Kanboard\Api\Procedure * @author Frederic Guillot */ -class MeApi extends BaseApi +class MeProcedure extends BaseProcedure { public function getMe() { diff --git a/app/Api/Procedure/ProjectFileProcedure.php b/app/Api/Procedure/ProjectFileProcedure.php new file mode 100644 index 00000000..48466ce3 --- /dev/null +++ b/app/Api/Procedure/ProjectFileProcedure.php @@ -0,0 +1,68 @@ +<?php + +namespace Kanboard\Api\Procedure; + +use Kanboard\Api\Authorization\ProjectAuthorization; +use Kanboard\Core\ObjectStorage\ObjectStorageException; + +/** + * Project File API controller + * + * @package Kanboard\Api\Procedure + * @author Frederic Guillot + */ +class ProjectFileProcedure extends BaseProcedure +{ + public function getProjectFile($project_id, $file_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getProjectFile', $project_id); + return $this->projectFileModel->getById($file_id); + } + + public function getAllProjectFiles($project_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getAllProjectFiles', $project_id); + return $this->projectFileModel->getAll($project_id); + } + + public function downloadProjectFile($project_id, $file_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'downloadProjectFile', $project_id); + + try { + $file = $this->projectFileModel->getById($file_id); + + if (! empty($file)) { + return base64_encode($this->objectStorage->get($file['path'])); + } + } catch (ObjectStorageException $e) { + $this->logger->error($e->getMessage()); + } + + return ''; + } + + public function createProjectFile($project_id, $filename, $blob) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'createProjectFile', $project_id); + + try { + return $this->projectFileModel->uploadContent($project_id, $filename, $blob); + } catch (ObjectStorageException $e) { + $this->logger->error(__METHOD__.': '.$e->getMessage()); + return false; + } + } + + public function removeProjectFile($project_id, $file_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeProjectFile', $project_id); + return $this->projectFileModel->remove($file_id); + } + + public function removeAllProjectFiles($project_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeAllProjectFiles', $project_id); + return $this->projectFileModel->removeAll($project_id); + } +} diff --git a/app/Api/ProjectPermissionApi.php b/app/Api/Procedure/ProjectPermissionProcedure.php index 703cd0f3..e22e1d62 100644 --- a/app/Api/ProjectPermissionApi.php +++ b/app/Api/Procedure/ProjectPermissionProcedure.php @@ -1,73 +1,69 @@ <?php -namespace Kanboard\Api; +namespace Kanboard\Api\Procedure; -use Kanboard\Core\Base; +use Kanboard\Api\Authorization\ProjectAuthorization; use Kanboard\Core\Security\Role; /** * Project Permission API controller * - * @package Kanboard\Api + * @package Kanboard\Api\Procedure * @author Frederic Guillot */ -class ProjectPermissionApi extends Base +class ProjectPermissionProcedure extends BaseProcedure { public function getProjectUsers($project_id) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getProjectUsers', $project_id); return $this->projectUserRoleModel->getAllUsers($project_id); } public function getAssignableUsers($project_id, $prepend_unassigned = false) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getAssignableUsers', $project_id); return $this->projectUserRoleModel->getAssignableUsersList($project_id, $prepend_unassigned); } public function addProjectUser($project_id, $user_id, $role = Role::PROJECT_MEMBER) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'addProjectUser', $project_id); return $this->projectUserRoleModel->addUser($project_id, $user_id, $role); } public function addProjectGroup($project_id, $group_id, $role = Role::PROJECT_MEMBER) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'addProjectGroup', $project_id); return $this->projectGroupRoleModel->addGroup($project_id, $group_id, $role); } public function removeProjectUser($project_id, $user_id) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeProjectUser', $project_id); return $this->projectUserRoleModel->removeUser($project_id, $user_id); } public function removeProjectGroup($project_id, $group_id) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeProjectGroup', $project_id); return $this->projectGroupRoleModel->removeGroup($project_id, $group_id); } public function changeProjectUserRole($project_id, $user_id, $role) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'changeProjectUserRole', $project_id); return $this->projectUserRoleModel->changeUserRole($project_id, $user_id, $role); } public function changeProjectGroupRole($project_id, $group_id, $role) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'changeProjectGroupRole', $project_id); return $this->projectGroupRoleModel->changeGroupRole($project_id, $group_id, $role); } - // Deprecated - public function getMembers($project_id) + public function getProjectUserRole($project_id, $user_id) { - return $this->getProjectUsers($project_id); - } - - // Deprecated - public function revokeUser($project_id, $user_id) - { - return $this->removeProjectUser($project_id, $user_id); - } - - // Deprecated - public function allowUser($project_id, $user_id) - { - return $this->addProjectUser($project_id, $user_id); + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getProjectUserRole', $project_id); + return $this->projectUserRoleModel->getUserRole($project_id, $user_id); } } diff --git a/app/Api/Procedure/ProjectProcedure.php b/app/Api/Procedure/ProjectProcedure.php new file mode 100644 index 00000000..a580c8d9 --- /dev/null +++ b/app/Api/Procedure/ProjectProcedure.php @@ -0,0 +1,113 @@ +<?php + +namespace Kanboard\Api\Procedure; + +use Kanboard\Api\Authorization\ProjectAuthorization; + +/** + * Project API controller + * + * @package Kanboard\Api\Procedure + * @author Frederic Guillot + */ +class ProjectProcedure extends BaseProcedure +{ + public function getProjectById($project_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getProjectById', $project_id); + return $this->formatProject($this->projectModel->getById($project_id)); + } + + public function getProjectByName($name) + { + $project = $this->projectModel->getByName($name); + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getProjectByName', $project['id']); + return $this->formatProject($project); + } + + public function getProjectByIdentifier($identifier) + { + $project = $this->formatProject($this->projectModel->getByIdentifier($identifier)); + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getProjectByIdentifier', $project['id']); + return $this->formatProject($project); + } + + public function getAllProjects() + { + return $this->formatProjects($this->projectModel->getAll()); + } + + public function removeProject($project_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeProject', $project_id); + return $this->projectModel->remove($project_id); + } + + public function enableProject($project_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'enableProject', $project_id); + return $this->projectModel->enable($project_id); + } + + public function disableProject($project_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'disableProject', $project_id); + return $this->projectModel->disable($project_id); + } + + public function enableProjectPublicAccess($project_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'enableProjectPublicAccess', $project_id); + return $this->projectModel->enablePublicAccess($project_id); + } + + public function disableProjectPublicAccess($project_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'disableProjectPublicAccess', $project_id); + return $this->projectModel->disablePublicAccess($project_id); + } + + public function getProjectActivities(array $project_ids) + { + foreach ($project_ids as $project_id) { + ProjectAuthorization::getInstance($this->container) + ->check($this->getClassName(), 'getProjectActivities', $project_id); + } + + return $this->helper->projectActivity->getProjectsEvents($project_ids); + } + + public function getProjectActivity($project_id) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getProjectActivity', $project_id); + return $this->helper->projectActivity->getProjectEvents($project_id); + } + + public function createProject($name, $description = null, $owner_id = 0, $identifier = null) + { + $values = $this->filterValues(array( + 'name' => $name, + 'description' => $description, + 'identifier' => $identifier, + )); + + list($valid, ) = $this->projectValidator->validateCreation($values); + return $valid ? $this->projectModel->create($values, $owner_id, $this->userSession->isLogged()) : false; + } + + public function updateProject($project_id, $name = null, $description = null, $owner_id = null, $identifier = null) + { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateProject', $project_id); + + $values = $this->filterValues(array( + 'id' => $project_id, + 'name' => $name, + 'description' => $description, + 'owner_id' => $owner_id, + 'identifier' => $identifier, + )); + + list($valid, ) = $this->projectValidator->validateModification($values); + return $valid && $this->projectModel->update($values); + } +} diff --git a/app/Api/SubtaskApi.php b/app/Api/Procedure/SubtaskProcedure.php index 5764ff7d..e2400912 100644 --- a/app/Api/SubtaskApi.php +++ b/app/Api/Procedure/SubtaskProcedure.php @@ -1,34 +1,40 @@ <?php -namespace Kanboard\Api; +namespace Kanboard\Api\Procedure; -use Kanboard\Core\Base; +use Kanboard\Api\Authorization\SubtaskAuthorization; +use Kanboard\Api\Authorization\TaskAuthorization; /** * Subtask API controller * - * @package Kanboard\Api + * @package Kanboard\Api\Procedure * @author Frederic Guillot */ -class SubtaskApi extends Base +class SubtaskProcedure extends BaseProcedure { public function getSubtask($subtask_id) { + SubtaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getSubtask', $subtask_id); return $this->subtaskModel->getById($subtask_id); } public function getAllSubtasks($task_id) { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getAllSubtasks', $task_id); return $this->subtaskModel->getAll($task_id); } public function removeSubtask($subtask_id) { + SubtaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeSubtask', $subtask_id); return $this->subtaskModel->remove($subtask_id); } public function createSubtask($task_id, $title, $user_id = 0, $time_estimated = 0, $time_spent = 0, $status = 0) { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'createSubtask', $task_id); + $values = array( 'title' => $title, 'task_id' => $task_id, @@ -44,6 +50,8 @@ class SubtaskApi extends Base public function updateSubtask($id, $task_id, $title = null, $user_id = null, $time_estimated = null, $time_spent = null, $status = null) { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateSubtask', $task_id); + $values = array( 'id' => $id, 'task_id' => $task_id, diff --git a/app/Api/Procedure/SubtaskTimeTrackingProcedure.php b/app/Api/Procedure/SubtaskTimeTrackingProcedure.php new file mode 100644 index 00000000..5ceaa08d --- /dev/null +++ b/app/Api/Procedure/SubtaskTimeTrackingProcedure.php @@ -0,0 +1,39 @@ +<?php + +namespace Kanboard\Api\Procedure; + +use Kanboard\Api\Authorization\SubtaskAuthorization; + +/** + * Subtask Time Tracking API controller + * + * @package Kanboard\Api\Procedure + * @author Frederic Guillot + * @author Nikolaos Georgakis + */ +class SubtaskTimeTrackingProcedure extends BaseProcedure +{ + public function hasSubtaskTimer($subtask_id, $user_id) + { + SubtaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'hasSubtaskTimer', $subtask_id); + return $this->subtaskTimeTrackingModel->hasTimer($subtask_id, $user_id); + } + + public function setSubtaskStartTime($subtask_id, $user_id) + { + SubtaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'setSubtaskStartTime', $subtask_id); + return $this->subtaskTimeTrackingModel->logStartTime($subtask_id, $user_id); + } + + public function setSubtaskEndTime($subtask_id, $user_id) + { + SubtaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'setSubtaskEndTime', $subtask_id); + return $this->subtaskTimeTrackingModel->logEndTime($subtask_id, $user_id); + } + + public function getSubtaskTimeSpent($subtask_id, $user_id) + { + SubtaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getSubtaskTimeSpent', $subtask_id); + return $this->subtaskTimeTrackingModel->getTimeSpent($subtask_id, $user_id); + } +} diff --git a/app/Api/SwimlaneApi.php b/app/Api/Procedure/SwimlaneProcedure.php index c3c56a71..9b7d181d 100644 --- a/app/Api/SwimlaneApi.php +++ b/app/Api/Procedure/SwimlaneProcedure.php @@ -1,34 +1,39 @@ <?php -namespace Kanboard\Api; +namespace Kanboard\Api\Procedure; -use Kanboard\Core\Base; +use Kanboard\Api\Authorization\ProjectAuthorization; /** * Swimlane API controller * - * @package Kanboard\Api + * @package Kanboard\Api\Procedure * @author Frederic Guillot */ -class SwimlaneApi extends Base +class SwimlaneProcedure extends BaseProcedure { public function getActiveSwimlanes($project_id) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getActiveSwimlanes', $project_id); return $this->swimlaneModel->getSwimlanes($project_id); } public function getAllSwimlanes($project_id) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getAllSwimlanes', $project_id); return $this->swimlaneModel->getAll($project_id); } public function getSwimlaneById($swimlane_id) { - return $this->swimlaneModel->getById($swimlane_id); + $swimlane = $this->swimlaneModel->getById($swimlane_id); + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getSwimlaneById', $swimlane['project_id']); + return $swimlane; } public function getSwimlaneByName($project_id, $name) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getSwimlaneByName', $project_id); return $this->swimlaneModel->getByName($project_id, $name); } @@ -39,11 +44,13 @@ class SwimlaneApi extends Base public function getDefaultSwimlane($project_id) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getDefaultSwimlane', $project_id); return $this->swimlaneModel->getDefault($project_id); } public function addSwimlane($project_id, $name, $description = '') { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'addSwimlane', $project_id); return $this->swimlaneModel->create(array('project_id' => $project_id, 'name' => $name, 'description' => $description)); } @@ -60,21 +67,25 @@ class SwimlaneApi extends Base public function removeSwimlane($project_id, $swimlane_id) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeSwimlane', $project_id); return $this->swimlaneModel->remove($project_id, $swimlane_id); } public function disableSwimlane($project_id, $swimlane_id) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'disableSwimlane', $project_id); return $this->swimlaneModel->disable($project_id, $swimlane_id); } public function enableSwimlane($project_id, $swimlane_id) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'enableSwimlane', $project_id); return $this->swimlaneModel->enable($project_id, $swimlane_id); } public function changeSwimlanePosition($project_id, $swimlane_id, $position) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'changeSwimlanePosition', $project_id); return $this->swimlaneModel->changePosition($project_id, $swimlane_id, $position); } } diff --git a/app/Api/Procedure/TaskExternalLinkProcedure.php b/app/Api/Procedure/TaskExternalLinkProcedure.php new file mode 100644 index 00000000..05ec6906 --- /dev/null +++ b/app/Api/Procedure/TaskExternalLinkProcedure.php @@ -0,0 +1,106 @@ +<?php + +namespace Kanboard\Api\Procedure; + +use Kanboard\Api\Authorization\TaskAuthorization; +use Kanboard\Core\ExternalLink\ExternalLinkManager; +use Kanboard\Core\ExternalLink\ExternalLinkProviderNotFound; + +/** + * Task External Link API controller + * + * @package Kanboard\Api\Procedure + * @author Frederic Guillot + */ +class TaskExternalLinkProcedure extends BaseProcedure +{ + public function getExternalTaskLinkTypes() + { + return $this->externalLinkManager->getTypes(); + } + + public function getExternalTaskLinkProviderDependencies($providerName) + { + try { + return $this->externalLinkManager->getProvider($providerName)->getDependencies(); + } catch (ExternalLinkProviderNotFound $e) { + $this->logger->error(__METHOD__.': '.$e->getMessage()); + return false; + } + } + + public function getExternalTaskLinkById($task_id, $link_id) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getExternalTaskLink', $task_id); + return $this->taskExternalLinkModel->getById($link_id); + } + + public function getAllExternalTaskLinks($task_id) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getExternalTaskLinks', $task_id); + return $this->taskExternalLinkModel->getAll($task_id); + } + + public function createExternalTaskLink($task_id, $url, $dependency, $type = ExternalLinkManager::TYPE_AUTO, $title = '') + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'createExternalTaskLink', $task_id); + + try { + $provider = $this->externalLinkManager + ->setUserInputText($url) + ->setUserInputType($type) + ->find(); + + $link = $provider->getLink(); + + $values = array( + 'task_id' => $task_id, + 'title' => $title ?: $link->getTitle(), + 'url' => $link->getUrl(), + 'link_type' => $provider->getType(), + 'dependency' => $dependency, + ); + + list($valid, $errors) = $this->externalLinkValidator->validateCreation($values); + + if (! $valid) { + $this->logger->error(__METHOD__.': '.var_export($errors)); + return false; + } + + return $this->taskExternalLinkModel->create($values); + } catch (ExternalLinkProviderNotFound $e) { + $this->logger->error(__METHOD__.': '.$e->getMessage()); + } + + return false; + } + + public function updateExternalTaskLink($task_id, $link_id, $title = null, $url = null, $dependency = null) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateExternalTaskLink', $task_id); + + $link = $this->taskExternalLinkModel->getById($link_id); + $values = $this->filterValues(array( + 'title' => $title, + 'url' => $url, + 'dependency' => $dependency, + )); + + $values = array_merge($link, $values); + list($valid, $errors) = $this->externalLinkValidator->validateModification($values); + + if (! $valid) { + $this->logger->error(__METHOD__.': '.var_export($errors)); + return false; + } + + return $this->taskExternalLinkModel->update($values); + } + + public function removeExternalTaskLink($task_id, $link_id) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeExternalTaskLink', $task_id); + return $this->taskExternalLinkModel->remove($link_id); + } +} diff --git a/app/Api/FileApi.php b/app/Api/Procedure/TaskFileProcedure.php index 1ed3aeb9..bd006578 100644 --- a/app/Api/FileApi.php +++ b/app/Api/Procedure/TaskFileProcedure.php @@ -1,29 +1,36 @@ <?php -namespace Kanboard\Api; +namespace Kanboard\Api\Procedure; +use Kanboard\Api\Authorization\ProjectAuthorization; +use Kanboard\Api\Authorization\TaskAuthorization; +use Kanboard\Api\Authorization\TaskFileAuthorization; use Kanboard\Core\ObjectStorage\ObjectStorageException; /** - * File API controller + * Task File API controller * - * @package Kanboard\Api + * @package Kanboard\Api\Procedure * @author Frederic Guillot */ -class FileApi extends BaseApi +class TaskFileProcedure extends BaseProcedure { public function getTaskFile($file_id) { + TaskFileAuthorization::getInstance($this->container)->check($this->getClassName(), 'getTaskFile', $file_id); return $this->taskFileModel->getById($file_id); } public function getAllTaskFiles($task_id) { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getAllTaskFiles', $task_id); return $this->taskFileModel->getAll($task_id); } public function downloadTaskFile($file_id) { + TaskFileAuthorization::getInstance($this->container)->check($this->getClassName(), 'downloadTaskFile', $file_id); + try { $file = $this->taskFileModel->getById($file_id); @@ -33,59 +40,31 @@ class FileApi extends BaseApi } catch (ObjectStorageException $e) { $this->logger->error($e->getMessage()); } - + return ''; } public function createTaskFile($project_id, $task_id, $filename, $blob) { + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'createTaskFile', $project_id); + try { return $this->taskFileModel->uploadContent($task_id, $filename, $blob); } catch (ObjectStorageException $e) { - $this->logger->error($e->getMessage()); + $this->logger->error(__METHOD__.': '.$e->getMessage()); return false; } } public function removeTaskFile($file_id) { + TaskFileAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeTaskFile', $file_id); return $this->taskFileModel->remove($file_id); } public function removeAllTaskFiles($task_id) { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeAllTaskFiles', $task_id); return $this->taskFileModel->removeAll($task_id); } - - // Deprecated procedures - - public function getFile($file_id) - { - return $this->getTaskFile($file_id); - } - - public function getAllFiles($task_id) - { - return $this->getAllTaskFiles($task_id); - } - - public function downloadFile($file_id) - { - return $this->downloadTaskFile($file_id); - } - - public function createFile($project_id, $task_id, $filename, $blob) - { - return $this->createTaskFile($project_id, $task_id, $filename, $blob); - } - - public function removeFile($file_id) - { - return $this->removeTaskFile($file_id); - } - - public function removeAllFiles($task_id) - { - return $this->removeAllTaskFiles($task_id); - } } diff --git a/app/Api/TaskLinkApi.php b/app/Api/Procedure/TaskLinkProcedure.php index bb809133..375266fb 100644 --- a/app/Api/TaskLinkApi.php +++ b/app/Api/Procedure/TaskLinkProcedure.php @@ -1,16 +1,17 @@ <?php -namespace Kanboard\Api; +namespace Kanboard\Api\Procedure; -use Kanboard\Core\Base; +use Kanboard\Api\Authorization\TaskAuthorization; +use Kanboard\Api\Authorization\TaskLinkAuthorization; /** * TaskLink API controller * - * @package Kanboard\Api + * @package Kanboard\Api\Procedure * @author Frederic Guillot */ -class TaskLinkApi extends Base +class TaskLinkProcedure extends BaseProcedure { /** * Get a task link @@ -21,6 +22,7 @@ class TaskLinkApi extends Base */ public function getTaskLinkById($task_link_id) { + TaskLinkAuthorization::getInstance($this->container)->check($this->getClassName(), 'getTaskLinkById', $task_link_id); return $this->taskLinkModel->getById($task_link_id); } @@ -33,6 +35,7 @@ class TaskLinkApi extends Base */ public function getAllTaskLinks($task_id) { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getAllTaskLinks', $task_id); return $this->taskLinkModel->getAll($task_id); } @@ -47,6 +50,7 @@ class TaskLinkApi extends Base */ public function createTaskLink($task_id, $opposite_task_id, $link_id) { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'createTaskLink', $task_id); return $this->taskLinkModel->create($task_id, $opposite_task_id, $link_id); } @@ -62,6 +66,7 @@ class TaskLinkApi extends Base */ public function updateTaskLink($task_link_id, $task_id, $opposite_task_id, $link_id) { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateTaskLink', $task_id); return $this->taskLinkModel->update($task_link_id, $task_id, $opposite_task_id, $link_id); } @@ -74,6 +79,7 @@ class TaskLinkApi extends Base */ public function removeTaskLink($task_link_id) { + TaskLinkAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeTaskLink', $task_link_id); return $this->taskLinkModel->remove($task_link_id); } } diff --git a/app/Api/Procedure/TaskMetadataProcedure.php b/app/Api/Procedure/TaskMetadataProcedure.php new file mode 100644 index 00000000..169482f5 --- /dev/null +++ b/app/Api/Procedure/TaskMetadataProcedure.php @@ -0,0 +1,38 @@ +<?php + +namespace Kanboard\Api\Procedure; + +use Kanboard\Api\Authorization\TaskAuthorization; + +/** + * Class TaskMetadataProcedure + * + * @package Kanboard\Api\Procedure + * @author Frederic Guillot + */ +class TaskMetadataProcedure extends BaseProcedure +{ + public function getTaskMetadata($task_id) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getTask', $task_id); + return $this->taskMetadataModel->getAll($task_id); + } + + public function getTaskMetadataByName($task_id, $name) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getTask', $task_id); + return $this->taskMetadataModel->get($task_id, $name); + } + + public function saveTaskMetadata($task_id, array $values) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateTask', $task_id); + return $this->taskMetadataModel->save($task_id, $values); + } + + public function removeTaskMetadata($task_id, $name) + { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateTask', $task_id); + return $this->taskMetadataModel->remove($task_id, $name); + } +} diff --git a/app/Api/TaskApi.php b/app/Api/Procedure/TaskProcedure.php index ddb3ac54..ee9242d1 100644 --- a/app/Api/TaskApi.php +++ b/app/Api/Procedure/TaskProcedure.php @@ -1,39 +1,41 @@ <?php -namespace Kanboard\Api; +namespace Kanboard\Api\Procedure; +use Kanboard\Api\Authorization\ProjectAuthorization; +use Kanboard\Api\Authorization\TaskAuthorization; use Kanboard\Filter\TaskProjectFilter; use Kanboard\Model\TaskModel; /** * Task API controller * - * @package Kanboard\Api + * @package Kanboard\Api\Procedure * @author Frederic Guillot */ -class TaskApi extends BaseApi +class TaskProcedure extends BaseProcedure { public function searchTasks($project_id, $query) { - $this->checkProjectPermission($project_id); + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'searchTasks', $project_id); return $this->taskLexer->build($query)->withFilter(new TaskProjectFilter($project_id))->toArray(); } public function getTask($task_id) { - $this->checkTaskPermission($task_id); + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'getTask', $task_id); return $this->formatTask($this->taskFinderModel->getById($task_id)); } public function getTaskByReference($project_id, $reference) { - $this->checkProjectPermission($project_id); + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getTaskByReference', $project_id); return $this->formatTask($this->taskFinderModel->getByReference($project_id, $reference)); } public function getAllTasks($project_id, $status_id = TaskModel::STATUS_OPEN) { - $this->checkProjectPermission($project_id); + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getAllTasks', $project_id); return $this->formatTasks($this->taskFinderModel->getAll($project_id, $status_id)); } @@ -44,41 +46,44 @@ class TaskApi extends BaseApi public function getOverdueTasksByProject($project_id) { - $this->checkProjectPermission($project_id); + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'getOverdueTasksByProject', $project_id); return $this->taskFinderModel->getOverdueTasksByProject($project_id); } public function openTask($task_id) { - $this->checkTaskPermission($task_id); + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'openTask', $task_id); return $this->taskStatusModel->open($task_id); } public function closeTask($task_id) { - $this->checkTaskPermission($task_id); + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'closeTask', $task_id); return $this->taskStatusModel->close($task_id); } public function removeTask($task_id) { + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'removeTask', $task_id); return $this->taskModel->remove($task_id); } public function moveTaskPosition($project_id, $task_id, $column_id, $position, $swimlane_id = 0) { - $this->checkProjectPermission($project_id); + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'moveTaskPosition', $project_id); return $this->taskPositionModel->movePosition($project_id, $task_id, $column_id, $position, $swimlane_id); } public function moveTaskToProject($task_id, $project_id, $swimlane_id = null, $column_id = null, $category_id = null, $owner_id = null) { - return $this->taskDuplicationModel->moveToProject($task_id, $project_id, $swimlane_id, $column_id, $category_id, $owner_id); + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'moveTaskToProject', $project_id); + return $this->taskProjectMoveModel->moveToProject($task_id, $project_id, $swimlane_id, $column_id, $category_id, $owner_id); } public function duplicateTaskToProject($task_id, $project_id, $swimlane_id = null, $column_id = null, $category_id = null, $owner_id = null) { - return $this->taskDuplicationModel->duplicateToProject($task_id, $project_id, $swimlane_id, $column_id, $category_id, $owner_id); + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'duplicateTaskToProject', $project_id); + return $this->taskProjectDuplicationModel->duplicateToProject($task_id, $project_id, $swimlane_id, $column_id, $category_id, $owner_id); } public function createTask($title, $project_id, $color_id = '', $column_id = 0, $owner_id = 0, $creator_id = 0, @@ -86,7 +91,7 @@ class TaskApi extends BaseApi $recurrence_status = 0, $recurrence_trigger = 0, $recurrence_factor = 0, $recurrence_timeframe = 0, $recurrence_basedate = 0, $reference = '') { - $this->checkProjectPermission($project_id); + ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'createTask', $project_id); if ($owner_id !== 0 && ! $this->projectPermissionModel->isAssignable($project_id, $owner_id)) { return false; @@ -127,8 +132,7 @@ class TaskApi extends BaseApi $recurrence_status = null, $recurrence_trigger = null, $recurrence_factor = null, $recurrence_timeframe = null, $recurrence_basedate = null, $reference = null) { - $this->checkTaskPermission($id); - + TaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'updateTask', $id); $project_id = $this->taskFinderModel->getProjectId($id); if ($project_id === 0) { @@ -139,7 +143,7 @@ class TaskApi extends BaseApi return false; } - $values = array( + $values = $this->filterValues(array( 'id' => $id, 'title' => $title, 'color_id' => $color_id, @@ -155,13 +159,7 @@ class TaskApi extends BaseApi 'recurrence_basedate' => $recurrence_basedate, 'reference' => $reference, 'priority' => $priority, - ); - - foreach ($values as $key => $value) { - if (is_null($value)) { - unset($values[$key]); - } - } + )); list($valid) = $this->taskValidator->validateApiModification($values); return $valid && $this->taskModificationModel->update($values); diff --git a/app/Api/UserApi.php b/app/Api/Procedure/UserProcedure.php index 88d75527..145f85bf 100644 --- a/app/Api/UserApi.php +++ b/app/Api/Procedure/UserProcedure.php @@ -1,8 +1,7 @@ <?php -namespace Kanboard\Api; +namespace Kanboard\Api\Procedure; -use Kanboard\Core\Base; use LogicException; use Kanboard\Core\Security\Role; use Kanboard\Core\Ldap\Client as LdapClient; @@ -12,10 +11,10 @@ use Kanboard\Core\Ldap\User as LdapUser; /** * User API controller * - * @package Kanboard\Api + * @package Kanboard\Api\Procedure * @author Frederic Guillot */ -class UserApi extends Base +class UserProcedure extends BaseProcedure { public function getUser($user_id) { @@ -118,19 +117,13 @@ class UserApi extends Base public function updateUser($id, $username = null, $name = null, $email = null, $role = null) { - $values = array( + $values = $this->filterValues(array( 'id' => $id, 'username' => $username, 'name' => $name, 'email' => $email, 'role' => $role, - ); - - foreach ($values as $key => $value) { - if (is_null($value)) { - unset($values[$key]); - } - } + )); list($valid, ) = $this->userValidator->validateApiModification($values); return $valid && $this->userModel->update($values); diff --git a/app/Api/ProjectApi.php b/app/Api/ProjectApi.php deleted file mode 100644 index 29a9cd79..00000000 --- a/app/Api/ProjectApi.php +++ /dev/null @@ -1,87 +0,0 @@ -<?php - -namespace Kanboard\Api; - -/** - * Project API controller - * - * @package Kanboard\Api - * @author Frederic Guillot - */ -class ProjectApi extends BaseApi -{ - public function getProjectById($project_id) - { - $this->checkProjectPermission($project_id); - return $this->formatProject($this->projectModel->getById($project_id)); - } - - public function getProjectByName($name) - { - return $this->formatProject($this->projectModel->getByName($name)); - } - - public function getAllProjects() - { - return $this->formatProjects($this->projectModel->getAll()); - } - - public function removeProject($project_id) - { - return $this->projectModel->remove($project_id); - } - - public function enableProject($project_id) - { - return $this->projectModel->enable($project_id); - } - - public function disableProject($project_id) - { - return $this->projectModel->disable($project_id); - } - - public function enableProjectPublicAccess($project_id) - { - return $this->projectModel->enablePublicAccess($project_id); - } - - public function disableProjectPublicAccess($project_id) - { - return $this->projectModel->disablePublicAccess($project_id); - } - - public function getProjectActivities(array $project_ids) - { - return $this->helper->projectActivity->getProjectsEvents($project_ids); - } - - public function getProjectActivity($project_id) - { - $this->checkProjectPermission($project_id); - return $this->helper->projectActivity->getProjectEvents($project_id); - } - - public function createProject($name, $description = null) - { - $values = array( - 'name' => $name, - 'description' => $description - ); - - list($valid, ) = $this->projectValidator->validateCreation($values); - return $valid ? $this->projectModel->create($values) : false; - } - - public function updateProject($id, $name, $description = null) - { - $values = array( - 'id' => $id, - 'name' => $name, - 'description' => $description - ); - - list($valid, ) = $this->projectValidator->validateModification($values); - return $valid && $this->projectModel->update($values); - } -} diff --git a/app/Api/SubtaskTimeTrackingApi.php b/app/Api/SubtaskTimeTrackingApi.php deleted file mode 100644 index 0e700b31..00000000 --- a/app/Api/SubtaskTimeTrackingApi.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php - -namespace Kanboard\Api; - -use Kanboard\Core\Base; - -/** - * Subtask Time Tracking API controller - * - * @package api - * @author Nikolaos Georgakis - */ -class SubtaskTimeTrackingApi extends Base -{ - public function hasTimer($subtask_id,$user_id) - { - return $this->subtaskTimeTrackingModel->hasTimer($subtask_id,$user_id); - } - - public function logStartTime($subtask_id,$user_id) - { - return $this->subtaskTimeTrackingModel->logStartTime($subtask_id,$user_id); - } - - public function logEndTime($subtask_id,$user_id) - { - return $this->subtaskTimeTrackingModel->logEndTime($subtask_id,$user_id); - } - - public function getTimeSpent($subtask_id,$user_id) - { - return $this->subtaskTimeTrackingModel->getTimeSpent($subtask_id,$user_id); - } -} diff --git a/app/Auth/ReverseProxyAuth.php b/app/Auth/ReverseProxyAuth.php index b9730c5c..fdf936b1 100644 --- a/app/Auth/ReverseProxyAuth.php +++ b/app/Auth/ReverseProxyAuth.php @@ -45,7 +45,8 @@ class ReverseProxyAuth extends Base implements PreAuthenticationProviderInterfac $username = $this->request->getRemoteUser(); if (! empty($username)) { - $this->userInfo = new ReverseProxyUserProvider($username); + $userProfile = $this->userModel->getByUsername($username); + $this->userInfo = new ReverseProxyUserProvider($username, $userProfile ?: array()); return true; } diff --git a/app/Auth/TotpAuth.php b/app/Auth/TotpAuth.php index f4304930..8e1ebe35 100644 --- a/app/Auth/TotpAuth.php +++ b/app/Auth/TotpAuth.php @@ -123,7 +123,8 @@ class TotpAuth extends Base implements PostAuthenticationProviderInterface return ''; } - return GoogleAuthenticator::getQrCodeUrl('totp', $label, $this->secret); + $options = array('issuer' => TOTP_ISSUER); + return GoogleAuthenticator::getQrCodeUrl('totp', $label, $this->secret, null, $options); } /** @@ -139,6 +140,7 @@ class TotpAuth extends Base implements PostAuthenticationProviderInterface return ''; } - return GoogleAuthenticator::getKeyUri('totp', $label, $this->secret); + $options = array('issuer' => TOTP_ISSUER); + return GoogleAuthenticator::getKeyUri('totp', $label, $this->secret, null, $options); } } diff --git a/app/Console/TaskOverdueNotificationCommand.php b/app/Console/TaskOverdueNotificationCommand.php index 225a6a1a..36276615 100644 --- a/app/Console/TaskOverdueNotificationCommand.php +++ b/app/Console/TaskOverdueNotificationCommand.php @@ -149,7 +149,7 @@ class TaskOverdueNotificationCommand extends BaseCommand $this->userNotificationModel->sendUserNotification( $user, TaskModel::EVENT_OVERDUE, - array('tasks' => $user_tasks, 'project_name' => implode(", ", $project_names)) + array('tasks' => $user_tasks, 'project_name' => implode(', ', $project_names)) ); } } diff --git a/app/Controller/ActionCreationController.php b/app/Controller/ActionCreationController.php index abd8abd3..9b228f28 100644 --- a/app/Controller/ActionCreationController.php +++ b/app/Controller/ActionCreationController.php @@ -81,7 +81,7 @@ class ActionCreationController extends BaseController 'colors_list' => $this->colorModel->getList(), 'categories_list' => $this->categoryModel->getList($project['id']), 'links_list' => $this->linkModel->getList(0, false), - 'priorities_list' => $this->projectModel->getPriorities($project), + 'priorities_list' => $this->projectTaskPriorityModel->getPriorities($project), 'project' => $project, 'available_actions' => $this->actionManager->getAvailableActions(), 'events' => $this->actionManager->getCompatibleEvents($values['action_name']), diff --git a/app/Controller/ActivityController.php b/app/Controller/ActivityController.php index 9f9841af..476e4aac 100644 --- a/app/Controller/ActivityController.php +++ b/app/Controller/ActivityController.php @@ -40,6 +40,7 @@ class ActivityController extends BaseController 'task' => $task, 'project' => $this->projectModel->getById($task['project_id']), 'events' => $this->helper->projectActivity->getTaskEvents($task['id']), + 'tags' => $this->taskTagModel->getList($task['id']), ))); } } diff --git a/app/Controller/AnalyticController.php b/app/Controller/AnalyticController.php index cf3ba034..ab0646a2 100644 --- a/app/Controller/AnalyticController.php +++ b/app/Controller/AnalyticController.php @@ -33,7 +33,7 @@ class AnalyticController extends BaseController 'metrics' => $this->projectDailyStatsModel->getRawMetrics($project['id'], $from, $to), 'date_format' => $this->configModel->get('application_date_format'), 'date_formats' => $this->dateParser->getAvailableFormats($this->dateParser->getDateFormats()), - 'title' => t('Lead and Cycle time for "%s"', $project['name']), + 'title' => t('Lead and cycle time'), ))); } @@ -60,7 +60,7 @@ class AnalyticController extends BaseController 'project' => $project, 'paginator' => $paginator, 'metrics' => $this->estimatedTimeComparisonAnalytic->build($project['id']), - 'title' => t('Compare hours for "%s"', $project['name']), + 'title' => t('Estimated vs actual time'), ))); } @@ -76,7 +76,7 @@ class AnalyticController extends BaseController $this->response->html($this->helper->layout->analytic('analytic/avg_time_columns', array( 'project' => $project, 'metrics' => $this->averageTimeSpentColumnAnalytic->build($project['id']), - 'title' => t('Average time spent into each column for "%s"', $project['name']), + 'title' => t('Average time into each column'), ))); } @@ -92,7 +92,7 @@ class AnalyticController extends BaseController $this->response->html($this->helper->layout->analytic('analytic/tasks', array( 'project' => $project, 'metrics' => $this->taskDistributionAnalytic->build($project['id']), - 'title' => t('Task repartition for "%s"', $project['name']), + 'title' => t('Task distribution'), ))); } @@ -108,7 +108,7 @@ class AnalyticController extends BaseController $this->response->html($this->helper->layout->analytic('analytic/users', array( 'project' => $project, 'metrics' => $this->userDistributionAnalytic->build($project['id']), - 'title' => t('User repartition for "%s"', $project['name']), + 'title' => t('User repartition'), ))); } @@ -119,7 +119,7 @@ class AnalyticController extends BaseController */ public function cfd() { - $this->commonAggregateMetrics('analytic/cfd', 'total', 'Cumulative flow diagram for "%s"'); + $this->commonAggregateMetrics('analytic/cfd', 'total', t('Cumulative flow diagram')); } /** @@ -129,7 +129,7 @@ class AnalyticController extends BaseController */ public function burndown() { - $this->commonAggregateMetrics('analytic/burndown', 'score', 'Burndown chart for "%s"'); + $this->commonAggregateMetrics('analytic/burndown', 'score', t('Burndown chart')); } /** @@ -157,7 +157,7 @@ class AnalyticController extends BaseController 'project' => $project, 'date_format' => $this->configModel->get('application_date_format'), 'date_formats' => $this->dateParser->getAvailableFormats($this->dateParser->getDateFormats()), - 'title' => t($title, $project['name']), + 'title' => $title, ))); } diff --git a/app/Controller/BoardAjaxController.php b/app/Controller/BoardAjaxController.php index 24914671..9b721f06 100644 --- a/app/Controller/BoardAjaxController.php +++ b/app/Controller/BoardAjaxController.php @@ -134,7 +134,7 @@ class BoardAjaxController extends BaseController 'board_highlight_period' => $this->configModel->get('board_highlight_period'), 'swimlanes' => $this->taskLexer ->build($this->userSession->getFilters($project_id)) - ->format(BoardFormatter::getInstance($this->container)->setProjectId($project_id)) + ->format(BoardFormatter::getInstance($this->container)->withProjectId($project_id)) )); } } diff --git a/app/Controller/BoardTooltipController.php b/app/Controller/BoardTooltipController.php index 2a947027..134d728e 100644 --- a/app/Controller/BoardTooltipController.php +++ b/app/Controller/BoardTooltipController.php @@ -107,9 +107,9 @@ class BoardTooltipController extends BaseController $this->response->html($this->template->render('task_recurrence/info', array( 'task' => $task, - 'recurrence_trigger_list' => $this->taskModel->getRecurrenceTriggerList(), - 'recurrence_timeframe_list' => $this->taskModel->getRecurrenceTimeframeList(), - 'recurrence_basedate_list' => $this->taskModel->getRecurrenceBasedateList(), + 'recurrence_trigger_list' => $this->taskRecurrenceModel->getRecurrenceTriggerList(), + 'recurrence_timeframe_list' => $this->taskRecurrenceModel->getRecurrenceTimeframeList(), + 'recurrence_basedate_list' => $this->taskRecurrenceModel->getRecurrenceBasedateList(), ))); } diff --git a/app/Controller/BoardViewController.php b/app/Controller/BoardViewController.php index 496fa995..97c99d11 100644 --- a/app/Controller/BoardViewController.php +++ b/app/Controller/BoardViewController.php @@ -30,7 +30,11 @@ class BoardViewController extends BaseController $this->response->html($this->helper->layout->app('board/view_public', array( 'project' => $project, - 'swimlanes' => $this->boardModel->getBoard($project['id']), + 'swimlanes' => BoardFormatter::getInstance($this->container) + ->withProjectId($project['id']) + ->withQuery($this->taskFinderModel->getExtendedQuery()) + ->format() + , 'title' => $project['name'], 'description' => $project['description'], 'no_layout' => true, @@ -59,7 +63,7 @@ class BoardViewController extends BaseController 'board_highlight_period' => $this->configModel->get('board_highlight_period'), 'swimlanes' => $this->taskLexer ->build($search) - ->format(BoardFormatter::getInstance($this->container)->setProjectId($project['id'])) + ->format(BoardFormatter::getInstance($this->container)->withProjectId($project['id'])) ))); } } diff --git a/app/Controller/ColumnController.php b/app/Controller/ColumnController.php index 95fbcaaa..d3f0e36e 100644 --- a/app/Controller/ColumnController.php +++ b/app/Controller/ColumnController.php @@ -66,7 +66,15 @@ class ColumnController extends BaseController list($valid, $errors) = $this->columnValidator->validateCreation($values); if ($valid) { - if ($this->columnModel->create($project['id'], $values['title'], $values['task_limit'], $values['description']) !== false) { + $result = $this->columnModel->create( + $project['id'], + $values['title'], + $values['task_limit'], + $values['description'], + isset($values['hide_in_dashboard']) ? $values['hide_in_dashboard'] : 0 + ); + + if ($result !== false) { $this->flash->success(t('Column created successfully.')); return $this->response->redirect($this->helper->url->to('ColumnController', 'index', array('project_id' => $project['id'])), true); } else { @@ -111,7 +119,15 @@ class ColumnController extends BaseController list($valid, $errors) = $this->columnValidator->validateModification($values); if ($valid) { - if ($this->columnModel->update($values['id'], $values['title'], $values['task_limit'], $values['description'])) { + $result = $this->columnModel->update( + $values['id'], + $values['title'], + $values['task_limit'], + $values['description'], + isset($values['hide_in_dashboard']) ? $values['hide_in_dashboard'] : 0 + ); + + if ($result) { $this->flash->success(t('Board updated successfully.')); return $this->response->redirect($this->helper->url->to('ColumnController', 'index', array('project_id' => $project['id']))); } else { diff --git a/app/Controller/DashboardController.php b/app/Controller/DashboardController.php index 44874546..f32f8552 100644 --- a/app/Controller/DashboardController.php +++ b/app/Controller/DashboardController.php @@ -2,9 +2,6 @@ namespace Kanboard\Controller; -use Kanboard\Model\ProjectModel; -use Kanboard\Model\SubtaskModel; - /** * Dashboard Controller * @@ -14,63 +11,6 @@ use Kanboard\Model\SubtaskModel; class DashboardController extends BaseController { /** - * Get project pagination - * - * @access private - * @param integer $user_id - * @param string $action - * @param integer $max - * @return \Kanboard\Core\Paginator - */ - private function getProjectPaginator($user_id, $action, $max) - { - return $this->paginator - ->setUrl('DashboardController', $action, array('pagination' => 'projects', 'user_id' => $user_id)) - ->setMax($max) - ->setOrder(ProjectModel::TABLE.'.name') - ->setQuery($this->projectModel->getQueryColumnStats($this->projectPermissionModel->getActiveProjectIds($user_id))) - ->calculateOnlyIf($this->request->getStringParam('pagination') === 'projects'); - } - - /** - * Get task pagination - * - * @access private - * @param integer $user_id - * @param string $action - * @param integer $max - * @return \Kanboard\Core\Paginator - */ - private function getTaskPaginator($user_id, $action, $max) - { - return $this->paginator - ->setUrl('DashboardController', $action, array('pagination' => 'tasks', 'user_id' => $user_id)) - ->setMax($max) - ->setOrder('tasks.id') - ->setQuery($this->taskFinderModel->getUserQuery($user_id)) - ->calculateOnlyIf($this->request->getStringParam('pagination') === 'tasks'); - } - - /** - * Get subtask pagination - * - * @access private - * @param integer $user_id - * @param string $action - * @param integer $max - * @return \Kanboard\Core\Paginator - */ - private function getSubtaskPaginator($user_id, $action, $max) - { - return $this->paginator - ->setUrl('DashboardController', $action, array('pagination' => 'subtasks', 'user_id' => $user_id)) - ->setMax($max) - ->setOrder('tasks.id') - ->setQuery($this->subtaskModel->getUserQuery($user_id, array(SubTaskModel::STATUS_TODO, SubtaskModel::STATUS_INPROGRESS))) - ->calculateOnlyIf($this->request->getStringParam('pagination') === 'subtasks'); - } - - /** * Dashboard overview * * @access public @@ -80,10 +20,10 @@ class DashboardController extends BaseController $user = $this->getUser(); $this->response->html($this->helper->layout->dashboard('dashboard/show', array( - 'title' => t('Dashboard'), - 'project_paginator' => $this->getProjectPaginator($user['id'], 'show', 10), - 'task_paginator' => $this->getTaskPaginator($user['id'], 'show', 10), - 'subtask_paginator' => $this->getSubtaskPaginator($user['id'], 'show', 10), + 'title' => t('Dashboard for %s', $this->helper->user->getFullname($user)), + 'project_paginator' => $this->projectPagination->getDashboardPaginator($user['id'], 'show', 10), + 'task_paginator' => $this->taskPagination->getDashboardPaginator($user['id'], 'show', 10), + 'subtask_paginator' => $this->subtaskPagination->getDashboardPaginator($user['id'], 'show', 10), 'user' => $user, ))); } @@ -98,8 +38,8 @@ class DashboardController extends BaseController $user = $this->getUser(); $this->response->html($this->helper->layout->dashboard('dashboard/tasks', array( - 'title' => t('My tasks'), - 'paginator' => $this->getTaskPaginator($user['id'], 'tasks', 50), + 'title' => t('Tasks overview for %s', $this->helper->user->getFullname($user)), + 'paginator' => $this->taskPagination->getDashboardPaginator($user['id'], 'tasks', 50), 'user' => $user, ))); } @@ -114,8 +54,8 @@ class DashboardController extends BaseController $user = $this->getUser(); $this->response->html($this->helper->layout->dashboard('dashboard/subtasks', array( - 'title' => t('My subtasks'), - 'paginator' => $this->getSubtaskPaginator($user['id'], 'subtasks', 50), + 'title' => t('Subtasks overview for %s', $this->helper->user->getFullname($user)), + 'paginator' => $this->subtaskPagination->getDashboardPaginator($user['id'], 'subtasks', 50), 'user' => $user, ))); } @@ -130,8 +70,8 @@ class DashboardController extends BaseController $user = $this->getUser(); $this->response->html($this->helper->layout->dashboard('dashboard/projects', array( - 'title' => t('My projects'), - 'paginator' => $this->getProjectPaginator($user['id'], 'projects', 25), + 'title' => t('Projects overview for %s', $this->helper->user->getFullname($user)), + 'paginator' => $this->projectPagination->getDashboardPaginator($user['id'], 'projects', 25), 'user' => $user, ))); } @@ -146,7 +86,7 @@ class DashboardController extends BaseController $user = $this->getUser(); $this->response->html($this->helper->layout->dashboard('dashboard/activity', array( - 'title' => t('My activity stream'), + 'title' => t('Activity stream for %s', $this->helper->user->getFullname($user)), 'events' => $this->helper->projectActivity->getProjectsEvents($this->projectPermissionModel->getActiveProjectIds($user['id']), 100), 'user' => $user, ))); @@ -159,9 +99,11 @@ class DashboardController extends BaseController */ public function calendar() { + $user = $this->getUser(); + $this->response->html($this->helper->layout->dashboard('dashboard/calendar', array( - 'title' => t('My calendar'), - 'user' => $this->getUser(), + 'title' => t('Calendar for %s', $this->helper->user->getFullname($user)), + 'user' => $user, ))); } @@ -175,7 +117,7 @@ class DashboardController extends BaseController $user = $this->getUser(); $this->response->html($this->helper->layout->dashboard('dashboard/notifications', array( - 'title' => t('My notifications'), + 'title' => t('Notifications for %s', $this->helper->user->getFullname($user)), 'notifications' => $this->userUnreadNotificationModel->getAll($user['id']), 'user' => $user, ))); diff --git a/app/Controller/DocumentationController.php b/app/Controller/DocumentationController.php index d86fb3c8..0d02ebda 100644 --- a/app/Controller/DocumentationController.php +++ b/app/Controller/DocumentationController.php @@ -20,16 +20,7 @@ class DocumentationController extends BaseController $page = 'index'; } - if ($this->languageModel->getCurrentLanguage() === 'fr_FR') { - $filename = __DIR__.'/../../doc/fr/' . $page . '.markdown'; - } else { - $filename = __DIR__ . '/../../doc/' . $page . '.markdown'; - } - - if (!file_exists($filename)) { - $filename = __DIR__.'/../../doc/index.markdown'; - } - + $filename = $this->getPageFilename($page); $this->response->html($this->helper->layout->app('doc/show', $this->render($filename))); } @@ -83,10 +74,63 @@ class DocumentationController extends BaseController */ public function replaceImageUrl(array $matches) { - if ($this->languageModel->getCurrentLanguage() === 'fr_FR') { - return '('.$this->helper->url->base().'doc/fr/'.$matches[1].')'; + return '('.$this->getFileBaseUrl($matches[1]).')'; + } + + /** + * Get Markdown file according to the current language + * + * @access private + * @param string $page + * @return string + */ + private function getPageFilename($page) + { + return $this->getFileLocation($page . '.markdown') ?: + implode(DIRECTORY_SEPARATOR, array(ROOT_DIR, 'doc', 'index.markdown')); + } + + /** + * Get base URL for Markdown links + * + * @access private + * @param string $filename + * @return string + */ + private function getFileBaseUrl($filename) + { + $language = $this->languageModel->getCurrentLanguage(); + $path = $this->getFileLocation($filename); + + if (strpos($path, $language) !== false) { + $url = implode('/', array('doc', $language, $filename)); + } else { + $url = implode('/', array('doc', $filename)); + } + + return $this->helper->url->base().$url; + } + + /** + * Get file location according to the current language + * + * @access private + * @param string $filename + * @return string + */ + private function getFileLocation($filename) + { + $files = array( + implode(DIRECTORY_SEPARATOR, array(ROOT_DIR, 'doc', $this->languageModel->getCurrentLanguage(), $filename)), + implode(DIRECTORY_SEPARATOR, array(ROOT_DIR, 'doc', $filename)), + ); + + foreach ($files as $filename) { + if (file_exists($filename)) { + return $filename; + } } - return '('.$this->helper->url->base().'doc/'.$matches[1].')'; + return ''; } } diff --git a/app/Controller/ExportController.php b/app/Controller/ExportController.php index b2fe0ebd..27046c76 100644 --- a/app/Controller/ExportController.php +++ b/app/Controller/ExportController.php @@ -31,22 +31,23 @@ class ExportController extends BaseController $data = $this->$model->$method($project['id'], $from, $to); $this->response->withFileDownload($filename.'.csv'); $this->response->csv($data); - } + } else { - $this->response->html($this->helper->layout->project('export/'.$action, array( - 'values' => array( - 'controller' => 'ExportController', - 'action' => $action, - 'project_id' => $project['id'], - 'from' => $from, - 'to' => $to, - ), - 'errors' => array(), - 'date_format' => $this->configModel->get('application_date_format'), - 'date_formats' => $this->dateParser->getAvailableFormats($this->dateParser->getDateFormats()), - 'project' => $project, - 'title' => $page_title, - ), 'export/sidebar')); + $this->response->html($this->helper->layout->project('export/'.$action, array( + 'values' => array( + 'controller' => 'ExportController', + 'action' => $action, + 'project_id' => $project['id'], + 'from' => $from, + 'to' => $to, + ), + 'errors' => array(), + 'date_format' => $this->configModel->get('application_date_format'), + 'date_formats' => $this->dateParser->getAvailableFormats($this->dateParser->getDateFormats()), + 'project' => $project, + 'title' => $page_title, + ), 'export/sidebar')); + } } /** @@ -76,7 +77,7 @@ class ExportController extends BaseController */ public function summary() { - $this->common('projectDailyColumnStats', 'getAggregatedMetrics', t('Summary'), 'summary', t('Daily project summary export')); + $this->common('projectDailyColumnStatsModel', 'getAggregatedMetrics', t('Summary'), 'summary', t('Daily project summary export')); } /** diff --git a/app/Controller/FeedController.php b/app/Controller/FeedController.php index cf2b1088..f9b0ed7c 100644 --- a/app/Controller/FeedController.php +++ b/app/Controller/FeedController.php @@ -2,7 +2,11 @@ namespace Kanboard\Controller; +use DateTime; use Kanboard\Core\Controller\AccessForbiddenException; +use PicoFeed\Syndication\AtomFeedBuilder; +use PicoFeed\Syndication\AtomItemBuilder; +use PicoFeed\Syndication\FeedBuilder; /** * Atom/RSS Feed controller @@ -27,10 +31,15 @@ class FeedController extends BaseController throw AccessForbiddenException::getInstance()->withoutLayout(); } - $this->response->xml($this->template->render('feed/user', array( - 'events' => $this->helper->projectActivity->getProjectsEvents($this->projectPermissionModel->getActiveProjectIds($user['id'])), - 'user' => $user, - ))); + $events = $this->helper->projectActivity->getProjectsEvents($this->projectPermissionModel->getActiveProjectIds($user['id'])); + + $feedBuilder = AtomFeedBuilder::create() + ->withTitle(e('Project activities for %s', $this->helper->user->getFullname($user))) + ->withFeedUrl($this->helper->url->to('FeedController', 'user', array('token' => $user['token']), '', true)) + ->withSiteUrl($this->helper->url->base()) + ->withDate(new DateTime()); + + $this->response->xml($this->buildFeedItems($events, $feedBuilder)->build()); } /** @@ -47,9 +56,44 @@ class FeedController extends BaseController throw AccessForbiddenException::getInstance()->withoutLayout(); } - $this->response->xml($this->template->render('feed/project', array( - 'events' => $this->helper->projectActivity->getProjectEvents($project['id']), - 'project' => $project, - ))); + $events = $this->helper->projectActivity->getProjectEvents($project['id']); + + $feedBuilder = AtomFeedBuilder::create() + ->withTitle(e('%s\'s activity', $project['name'])) + ->withFeedUrl($this->helper->url->to('FeedController', 'project', array('token' => $project['token']), '', true)) + ->withSiteUrl($this->helper->url->base()) + ->withDate(new DateTime()); + + $this->response->xml($this->buildFeedItems($events, $feedBuilder)->build()); + } + + /** + * Build feed items + * + * @access protected + * @param array $events + * @param FeedBuilder $feedBuilder + * @return FeedBuilder + */ + protected function buildFeedItems(array $events, FeedBuilder $feedBuilder) + { + foreach ($events as $event) { + $itemDate = new DateTime(); + $itemDate->setTimestamp($event['date_creation']); + + $itemUrl = $this->helper->url->to('TaskViewController', 'show', array('task_id' => $event['task_id']), '', true); + + $feedBuilder + ->withItem(AtomItemBuilder::create($feedBuilder) + ->withTitle($event['event_title']) + ->withUrl($itemUrl.'#event-'.$event['id']) + ->withAuthor($event['author']) + ->withPublishedDate($itemDate) + ->withUpdatedDate($itemDate) + ->withContent($event['event_content']) + ); + } + + return $feedBuilder; } } diff --git a/app/Controller/ProjectListController.php b/app/Controller/ProjectListController.php index e1172400..4de73c97 100644 --- a/app/Controller/ProjectListController.php +++ b/app/Controller/ProjectListController.php @@ -20,7 +20,7 @@ class ProjectListController extends BaseController if ($this->userSession->isAdmin()) { $project_ids = $this->projectModel->getAllIds(); } else { - $project_ids = $this->projectPermissionModel->getActiveProjectIds($this->userSession->getId()); + $project_ids = $this->projectPermissionModel->getProjectIds($this->userSession->getId()); } $nb_projects = count($project_ids); diff --git a/app/Controller/ProjectPermissionController.php b/app/Controller/ProjectPermissionController.php index f3ca6ed9..99f556e8 100644 --- a/app/Controller/ProjectPermissionController.php +++ b/app/Controller/ProjectPermissionController.php @@ -147,7 +147,7 @@ class ProjectPermissionController extends BaseController $values = $this->request->getValues(); if (empty($values['group_id']) && ! empty($values['external_id'])) { - $values['group_id'] = $this->groupModel->create($values['name'], $values['external_id']); + $values['group_id'] = $this->groupModel->getOrCreateExternalGroupId($values['name'], $values['external_id']); } if ($this->projectGroupRoleModel->addGroup($project['id'], $values['group_id'], $values['role'])) { diff --git a/app/Controller/ProjectTagController.php b/app/Controller/ProjectTagController.php new file mode 100644 index 00000000..acf514d3 --- /dev/null +++ b/app/Controller/ProjectTagController.php @@ -0,0 +1,134 @@ +<?php + +namespace Kanboard\Controller; + +use Kanboard\Core\Controller\AccessForbiddenException; + +/** + * Class ProjectTagController + * + * @package Kanboard\Controller + * @author Frederic Guillot + */ +class ProjectTagController extends BaseController +{ + public function index() + { + $project = $this->getProject(); + + $this->response->html($this->helper->layout->project('project_tag/index', array( + 'project' => $project, + 'tags' => $this->tagModel->getAllByProject($project['id']), + 'title' => t('Project tags management'), + ))); + } + + public function create(array $values = array(), array $errors = array()) + { + $project = $this->getProject(); + + if (empty($values)) { + $values['project_id'] = $project['id']; + } + + $this->response->html($this->template->render('project_tag/create', array( + 'project' => $project, + 'values' => $values, + 'errors' => $errors, + ))); + } + + public function save() + { + $project = $this->getProject(); + $values = $this->request->getValues(); + list($valid, $errors) = $this->tagValidator->validateCreation($values); + + if ($valid) { + if ($this->tagModel->create($project['id'], $values['name']) > 0) { + $this->flash->success(t('Tag created successfully.')); + } else { + $this->flash->failure(t('Unable to create this tag.')); + } + + $this->response->redirect($this->helper->url->to('ProjectTagController', 'index', array('project_id' => $project['id']))); + } else { + $this->create($values, $errors); + } + } + + public function edit(array $values = array(), array $errors = array()) + { + $project = $this->getProject(); + $tag_id = $this->request->getIntegerParam('tag_id'); + $tag = $this->tagModel->getById($tag_id); + + if (empty($values)) { + $values = $tag; + } + + $this->response->html($this->template->render('project_tag/edit', array( + 'project' => $project, + 'tag' => $tag, + 'values' => $values, + 'errors' => $errors, + ))); + } + + public function update() + { + $project = $this->getProject(); + $tag_id = $this->request->getIntegerParam('tag_id'); + $tag = $this->tagModel->getById($tag_id); + $values = $this->request->getValues(); + list($valid, $errors) = $this->tagValidator->validateModification($values); + + if ($tag['project_id'] != $project['id']) { + throw new AccessForbiddenException(); + } + + if ($valid) { + if ($this->tagModel->update($values['id'], $values['name'])) { + $this->flash->success(t('Tag updated successfully.')); + } else { + $this->flash->failure(t('Unable to update this tag.')); + } + + $this->response->redirect($this->helper->url->to('ProjectTagController', 'index', array('project_id' => $project['id']))); + } else { + $this->edit($values, $errors); + } + } + + public function confirm() + { + $project = $this->getProject(); + $tag_id = $this->request->getIntegerParam('tag_id'); + $tag = $this->tagModel->getById($tag_id); + + $this->response->html($this->template->render('project_tag/remove', array( + 'tag' => $tag, + 'project' => $project, + ))); + } + + public function remove() + { + $this->checkCSRFParam(); + $project = $this->getProject(); + $tag_id = $this->request->getIntegerParam('tag_id'); + $tag = $this->tagModel->getById($tag_id); + + if ($tag['project_id'] != $project['id']) { + throw new AccessForbiddenException(); + } + + if ($this->tagModel->remove($tag_id)) { + $this->flash->success(t('Tag removed successfully.')); + } else { + $this->flash->failure(t('Unable to remove this tag.')); + } + + $this->response->redirect($this->helper->url->to('ProjectTagController', 'index', array('project_id' => $project['id']))); + } +} diff --git a/app/Controller/SubtaskController.php b/app/Controller/SubtaskController.php index 93dab5cd..7502d84f 100644 --- a/app/Controller/SubtaskController.php +++ b/app/Controller/SubtaskController.php @@ -168,7 +168,7 @@ class SubtaskController extends BaseController $values = $this->request->getJson(); if (! empty($values) && $this->helper->user->hasProjectAccess('SubtaskController', 'movePosition', $project_id)) { - $result = $this->subtaskModel->changePosition($task_id, $values['subtask_id'], $values['position']); + $result = $this->subtaskPositionModel->changePosition($task_id, $values['subtask_id'], $values['position']); $this->response->json(array('result' => $result)); } else { throw new AccessForbiddenException(); diff --git a/app/Controller/SubtaskConverterController.php b/app/Controller/SubtaskConverterController.php index 65bcd2da..404c50d0 100644 --- a/app/Controller/SubtaskConverterController.php +++ b/app/Controller/SubtaskConverterController.php @@ -26,7 +26,7 @@ class SubtaskConverterController extends BaseController $project = $this->getProject(); $subtask = $this->getSubtask(); - $task_id = $this->subtaskModel->convertToTask($project['id'], $subtask['id']); + $task_id = $this->subtaskTaskConversionModel->convertToTask($project['id'], $subtask['id']); if ($task_id !== false) { $this->flash->success(t('Subtask converted to task successfully.')); diff --git a/app/Controller/SubtaskRestrictionController.php b/app/Controller/SubtaskRestrictionController.php index 084fc0d9..cb642e1c 100644 --- a/app/Controller/SubtaskRestrictionController.php +++ b/app/Controller/SubtaskRestrictionController.php @@ -27,7 +27,7 @@ class SubtaskRestrictionController extends BaseController SubtaskModel::STATUS_TODO => t('Todo'), SubtaskModel::STATUS_DONE => t('Done'), ), - 'subtask_inprogress' => $this->subtaskModel->getSubtaskInProgress($this->userSession->getId()), + 'subtask_inprogress' => $this->subtaskStatusModel->getSubtaskInProgress($this->userSession->getId()), 'subtask' => $subtask, 'task' => $task, ))); diff --git a/app/Controller/SubtaskStatusController.php b/app/Controller/SubtaskStatusController.php index 699951fe..d4d356c3 100644 --- a/app/Controller/SubtaskStatusController.php +++ b/app/Controller/SubtaskStatusController.php @@ -20,7 +20,7 @@ class SubtaskStatusController extends BaseController $task = $this->getTask(); $subtask = $this->getSubtask(); - $status = $this->subtaskModel->toggleStatus($subtask['id']); + $status = $this->subtaskStatusModel->toggleStatus($subtask['id']); if ($this->request->getIntegerParam('refresh-table') === 0) { $subtask['status'] = $status; diff --git a/app/Controller/TagController.php b/app/Controller/TagController.php new file mode 100644 index 00000000..b8389910 --- /dev/null +++ b/app/Controller/TagController.php @@ -0,0 +1,121 @@ +<?php + +namespace Kanboard\Controller; + +use Kanboard\Core\Controller\AccessForbiddenException; + +/** + * Class TagController + * + * @package Kanboard\Controller + * @author Frederic Guillot + */ +class TagController extends BaseController +{ + public function index() + { + $this->response->html($this->helper->layout->config('tag/index', array( + 'tags' => $this->tagModel->getAllByProject(0), + 'title' => t('Settings').' > '.t('Global tags management'), + ))); + } + + public function create(array $values = array(), array $errors = array()) + { + if (empty($values)) { + $values['project_id'] = 0; + } + + $this->response->html($this->template->render('tag/create', array( + 'values' => $values, + 'errors' => $errors, + ))); + } + + public function save() + { + $values = $this->request->getValues(); + list($valid, $errors) = $this->tagValidator->validateCreation($values); + + if ($valid) { + if ($this->tagModel->create(0, $values['name']) > 0) { + $this->flash->success(t('Tag created successfully.')); + } else { + $this->flash->failure(t('Unable to create this tag.')); + } + + $this->response->redirect($this->helper->url->to('TagController', 'index')); + } else { + $this->create($values, $errors); + } + } + + public function edit(array $values = array(), array $errors = array()) + { + $tag_id = $this->request->getIntegerParam('tag_id'); + $tag = $this->tagModel->getById($tag_id); + + if (empty($values)) { + $values = $tag; + } + + $this->response->html($this->template->render('tag/edit', array( + 'tag' => $tag, + 'values' => $values, + 'errors' => $errors, + ))); + } + + public function update() + { + $tag_id = $this->request->getIntegerParam('tag_id'); + $tag = $this->tagModel->getById($tag_id); + $values = $this->request->getValues(); + list($valid, $errors) = $this->tagValidator->validateModification($values); + + if ($tag['project_id'] != 0) { + throw new AccessForbiddenException(); + } + + if ($valid) { + if ($this->tagModel->update($values['id'], $values['name'])) { + $this->flash->success(t('Tag updated successfully.')); + } else { + $this->flash->failure(t('Unable to update this tag.')); + } + + $this->response->redirect($this->helper->url->to('TagController', 'index')); + } else { + $this->edit($values, $errors); + } + } + + public function confirm() + { + $tag_id = $this->request->getIntegerParam('tag_id'); + $tag = $this->tagModel->getById($tag_id); + + $this->response->html($this->template->render('tag/remove', array( + 'tag' => $tag, + ))); + } + + public function remove() + { + $this->checkCSRFParam(); + $tag_id = $this->request->getIntegerParam('tag_id'); + $tag = $this->tagModel->getById($tag_id); + + if ($tag['project_id'] != 0) { + throw new AccessForbiddenException(); + } + + if ($this->tagModel->remove($tag_id)) { + $this->flash->success(t('Tag removed successfully.')); + } else { + $this->flash->failure(t('Unable to remove this tag.')); + } + + $this->response->redirect($this->helper->url->to('TagController', 'index')); + } +} diff --git a/app/Controller/TaskCreationController.php b/app/Controller/TaskCreationController.php index 819de96e..5f1337e5 100644 --- a/app/Controller/TaskCreationController.php +++ b/app/Controller/TaskCreationController.php @@ -2,6 +2,8 @@ namespace Kanboard\Controller; +use Kanboard\Core\Controller\PageNotFoundException; + /** * Task Creation Controller * @@ -14,9 +16,9 @@ class TaskCreationController extends BaseController * Display a form to create a new task * * @access public - * @param array $values - * @param array $errors - * @throws \Kanboard\Core\Controller\PageNotFoundException + * @param array $values + * @param array $errors + * @throws PageNotFoundException */ public function show(array $values = array(), array $errors = array()) { @@ -24,15 +26,7 @@ class TaskCreationController extends BaseController $swimlanes_list = $this->swimlaneModel->getList($project['id'], false, true); if (empty($values)) { - $values = array( - 'swimlane_id' => $this->request->getIntegerParam('swimlane_id', key($swimlanes_list)), - 'column_id' => $this->request->getIntegerParam('column_id'), - 'color_id' => $this->colorModel->getDefaultColor(), - 'owner_id' => $this->userSession->getId(), - ); - - $values = $this->hook->merge('controller:task:form:default', $values, array('default_values' => $values)); - $values = $this->hook->merge('controller:task-creation:form:default', $values, array('default_values' => $values)); + $values = $this->prepareValues($swimlanes_list); } $this->response->html($this->template->render('task_creation/show', array( @@ -41,10 +35,8 @@ class TaskCreationController extends BaseController 'values' => $values + array('project_id' => $project['id']), 'columns_list' => $this->columnModel->getList($project['id']), 'users_list' => $this->projectUserRoleModel->getAssignableUsersList($project['id'], true, false, true), - 'colors_list' => $this->colorModel->getList(), 'categories_list' => $this->categoryModel->getList($project['id']), 'swimlanes_list' => $swimlanes_list, - 'title' => $project['name'].' > '.t('New task') ))); } @@ -62,17 +54,17 @@ class TaskCreationController extends BaseController if ($valid && $this->taskCreationModel->create($values)) { $this->flash->success(t('Task created successfully.')); - return $this->afterSave($project, $values); + $this->afterSave($project, $values); + } else { + $this->flash->failure(t('Unable to create your task.')); + $this->show($values, $errors); } - - $this->flash->failure(t('Unable to create your task.')); - return $this->show($values, $errors); } private function afterSave(array $project, array &$values) { if (isset($values['another_task']) && $values['another_task'] == 1) { - return $this->show(array( + $this->show(array( 'owner_id' => $values['owner_id'], 'color_id' => $values['color_id'], 'category_id' => isset($values['category_id']) ? $values['category_id'] : 0, @@ -80,8 +72,29 @@ class TaskCreationController extends BaseController 'swimlane_id' => isset($values['swimlane_id']) ? $values['swimlane_id'] : 0, 'another_task' => 1, )); + } else { + $this->response->redirect($this->helper->url->to('BoardViewController', 'show', array('project_id' => $project['id'])), true); } + } + + /** + * Prepare form values + * + * @access protected + * @param array $swimlanes_list + * @return array + */ + protected function prepareValues(array $swimlanes_list) + { + $values = array( + 'swimlane_id' => $this->request->getIntegerParam('swimlane_id', key($swimlanes_list)), + 'column_id' => $this->request->getIntegerParam('column_id'), + 'color_id' => $this->colorModel->getDefaultColor(), + 'owner_id' => $this->userSession->getId(), + ); - return $this->response->redirect($this->helper->url->to('BoardViewController', 'show', array('project_id' => $project['id'])), true); + $values = $this->hook->merge('controller:task:form:default', $values, array('default_values' => $values)); + $values = $this->hook->merge('controller:task-creation:form:default', $values, array('default_values' => $values)); + return $values; } } diff --git a/app/Controller/TaskDuplicationController.php b/app/Controller/TaskDuplicationController.php index 6a475374..915bf8f8 100644 --- a/app/Controller/TaskDuplicationController.php +++ b/app/Controller/TaskDuplicationController.php @@ -50,7 +50,7 @@ class TaskDuplicationController extends BaseController $values = $this->request->getValues(); list($valid, ) = $this->taskValidator->validateProjectModification($values); - if ($valid && $this->taskDuplicationModel->moveToProject($task['id'], + if ($valid && $this->taskProjectMoveModel->moveToProject($task['id'], $values['project_id'], $values['swimlane_id'], $values['column_id'], @@ -80,7 +80,7 @@ class TaskDuplicationController extends BaseController list($valid, ) = $this->taskValidator->validateProjectModification($values); if ($valid) { - $task_id = $this->taskDuplicationModel->duplicateToProject( + $task_id = $this->taskProjectDuplicationModel->duplicateToProject( $task['id'], $values['project_id'], $values['swimlane_id'], $values['column_id'], $values['category_id'], $values['owner_id'] ); diff --git a/app/Controller/TaskGanttCreationController.php b/app/Controller/TaskGanttCreationController.php index c2998a3e..0fbac8bb 100644 --- a/app/Controller/TaskGanttCreationController.php +++ b/app/Controller/TaskGanttCreationController.php @@ -36,10 +36,8 @@ class TaskGanttCreationController extends BaseController 'errors' => $errors, 'values' => $values, 'users_list' => $this->projectUserRoleModel->getAssignableUsersList($project['id'], true, false, true), - 'colors_list' => $this->colorModel->getList(), 'categories_list' => $this->categoryModel->getList($project['id']), 'swimlanes_list' => $this->swimlaneModel->getList($project['id'], false, true), - 'title' => $project['name'].' > '.t('New task') ))); } @@ -55,17 +53,12 @@ class TaskGanttCreationController extends BaseController list($valid, $errors) = $this->taskValidator->validateCreation($values); - if ($valid) { - $task_id = $this->taskCreationModel->create($values); - - if ($task_id !== false) { - $this->flash->success(t('Task created successfully.')); - return $this->response->redirect($this->helper->url->to('TaskGanttController', 'show', array('project_id' => $project['id']))); - } else { - $this->flash->failure(t('Unable to create your task.')); - } + if ($valid && $this->taskCreationModel->create($values)) { + $this->flash->success(t('Task created successfully.')); + $this->response->redirect($this->helper->url->to('TaskGanttController', 'show', array('project_id' => $project['id']))); + } else { + $this->flash->failure(t('Unable to create your task.')); + $this->show($values, $errors); } - - return $this->show($values, $errors); } } diff --git a/app/Controller/TaskModificationController.php b/app/Controller/TaskModificationController.php index f9c63c12..cbc3777a 100644 --- a/app/Controller/TaskModificationController.php +++ b/app/Controller/TaskModificationController.php @@ -23,55 +23,6 @@ class TaskModificationController extends BaseController } /** - * Edit description form - * - * @access public - * @param array $values - * @param array $errors - * @throws \Kanboard\Core\Controller\AccessForbiddenException - * @throws \Kanboard\Core\Controller\PageNotFoundException - */ - public function description(array $values = array(), array $errors = array()) - { - $task = $this->getTask(); - - if (empty($values)) { - $values = array('id' => $task['id'], 'description' => $task['description']); - } - - $this->response->html($this->template->render('task_modification/edit_description', array( - 'values' => $values, - 'errors' => $errors, - 'task' => $task, - ))); - } - - /** - * Update description - * - * @access public - */ - public function updateDescription() - { - $task = $this->getTask(); - $values = $this->request->getValues(); - - list($valid, $errors) = $this->taskValidator->validateDescriptionCreation($values); - - if ($valid) { - if ($this->taskModificationModel->update($values)) { - $this->flash->success(t('Task updated successfully.')); - } else { - $this->flash->failure(t('Unable to update your task.')); - } - - return $this->response->redirect($this->helper->url->to('TaskViewController', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id'])), true); - } - - return $this->description($values, $errors); - } - - /** * Display a form to edit a task * * @access public @@ -86,20 +37,16 @@ class TaskModificationController extends BaseController $project = $this->projectModel->getById($task['project_id']); if (empty($values)) { - $values = $task; - $values = $this->hook->merge('controller:task:form:default', $values, array('default_values' => $values)); - $values = $this->hook->merge('controller:task-modification:form:default', $values, array('default_values' => $values)); - $values = $this->dateParser->format($values, array('date_due'), $this->dateParser->getUserDateFormat()); - $values = $this->dateParser->format($values, array('date_started'), $this->dateParser->getUserDateTimeFormat()); + $values = $this->prepareValues($task); } - $this->response->html($this->template->render('task_modification/edit_task', array( + $this->response->html($this->template->render('task_modification/show', array( 'project' => $project, 'values' => $values, 'errors' => $errors, 'task' => $task, + 'tags' => $this->taskTagModel->getList($task['id']), 'users_list' => $this->projectUserRoleModel->getAssignableUsersList($task['project_id']), - 'colors_list' => $this->colorModel->getList(), 'categories_list' => $this->categoryModel->getList($task['project_id']), ))); } @@ -124,4 +71,19 @@ class TaskModificationController extends BaseController $this->edit($values, $errors); } } + + /** + * Prepare form values + * + * @access protected + * @param array $task + * @return array + */ + protected function prepareValues(array $task) + { + $values = $task; + $values = $this->hook->merge('controller:task:form:default', $values, array('default_values' => $values)); + $values = $this->hook->merge('controller:task-modification:form:default', $values, array('default_values' => $values)); + return $values; + } } diff --git a/app/Controller/TaskPopoverController.php b/app/Controller/TaskPopoverController.php index bf4e23d5..4e3c11a3 100644 --- a/app/Controller/TaskPopoverController.php +++ b/app/Controller/TaskPopoverController.php @@ -11,80 +11,6 @@ namespace Kanboard\Controller; class TaskPopoverController extends BaseController { /** - * Change a task assignee directly from the board - * - * @access public - */ - public function changeAssignee() - { - $task = $this->getTask(); - $project = $this->projectModel->getById($task['project_id']); - - $this->response->html($this->template->render('task_popover/change_assignee', array( - 'values' => $task, - 'users_list' => $this->projectUserRoleModel->getAssignableUsersList($project['id']), - 'project' => $project, - ))); - } - - /** - * Validate an assignee modification - * - * @access public - */ - public function updateAssignee() - { - $values = $this->request->getValues(); - - list($valid,) = $this->taskValidator->validateAssigneeModification($values); - - if ($valid && $this->taskModificationModel->update($values)) { - $this->flash->success(t('Task updated successfully.')); - } else { - $this->flash->failure(t('Unable to update your task.')); - } - - $this->response->redirect($this->helper->url->to('BoardViewController', 'show', array('project_id' => $values['project_id'])), true); - } - - /** - * Change a task category directly from the board - * - * @access public - */ - public function changeCategory() - { - $task = $this->getTask(); - $project = $this->projectModel->getById($task['project_id']); - - $this->response->html($this->template->render('task_popover/change_category', array( - 'values' => $task, - 'categories_list' => $this->categoryModel->getList($project['id']), - 'project' => $project, - ))); - } - - /** - * Validate a category modification - * - * @access public - */ - public function updateCategory() - { - $values = $this->request->getValues(); - - list($valid,) = $this->taskValidator->validateCategoryModification($values); - - if ($valid && $this->taskModificationModel->update($values)) { - $this->flash->success(t('Task updated successfully.')); - } else { - $this->flash->failure(t('Unable to update your task.')); - } - - $this->response->redirect($this->helper->url->to('BoardViewController', 'show', array('project_id' => $values['project_id'])), true); - } - - /** * Screenshot popover * * @access public diff --git a/app/Controller/TaskRecurrenceController.php b/app/Controller/TaskRecurrenceController.php index dc7a0e1b..c6fdfa37 100644 --- a/app/Controller/TaskRecurrenceController.php +++ b/app/Controller/TaskRecurrenceController.php @@ -31,10 +31,10 @@ class TaskRecurrenceController extends BaseController 'values' => $values, 'errors' => $errors, 'task' => $task, - 'recurrence_status_list' => $this->taskModel->getRecurrenceStatusList(), - 'recurrence_trigger_list' => $this->taskModel->getRecurrenceTriggerList(), - 'recurrence_timeframe_list' => $this->taskModel->getRecurrenceTimeframeList(), - 'recurrence_basedate_list' => $this->taskModel->getRecurrenceBasedateList(), + 'recurrence_status_list' => $this->taskRecurrenceModel->getRecurrenceStatusList(), + 'recurrence_trigger_list' => $this->taskRecurrenceModel->getRecurrenceTriggerList(), + 'recurrence_timeframe_list' => $this->taskRecurrenceModel->getRecurrenceTimeframeList(), + 'recurrence_basedate_list' => $this->taskRecurrenceModel->getRecurrenceBasedateList(), ))); } diff --git a/app/Controller/TaskViewController.php b/app/Controller/TaskViewController.php index bd1e86ae..36597457 100644 --- a/app/Controller/TaskViewController.php +++ b/app/Controller/TaskViewController.php @@ -22,7 +22,6 @@ class TaskViewController extends BaseController { $project = $this->projectModel->getByToken($this->request->getStringParam('token')); - // Token verification if (empty($project)) { throw AccessForbiddenException::getInstance()->withoutLayout(); } @@ -45,6 +44,7 @@ class TaskViewController extends BaseController 'task' => $task, 'columns_list' => $this->columnModel->getList($task['project_id']), 'colors_list' => $this->colorModel->getList(), + 'tags' => $this->taskTagModel->getList($task['id']), 'title' => $task['title'], 'no_layout' => true, 'auto_refresh' => true, @@ -62,19 +62,9 @@ class TaskViewController extends BaseController $task = $this->getTask(); $subtasks = $this->subtaskModel->getAll($task['id']); - $values = array( - 'id' => $task['id'], - 'date_started' => $task['date_started'], - 'time_estimated' => $task['time_estimated'] ?: '', - 'time_spent' => $task['time_spent'] ?: '', - ); - - $values = $this->dateParser->format($values, array('date_started'), $this->dateParser->getUserDateTimeFormat()); - $this->response->html($this->helper->layout->task('task/show', array( 'task' => $task, 'project' => $this->projectModel->getById($task['project_id']), - 'values' => $values, 'files' => $this->taskFileModel->getAllDocuments($task['id']), 'images' => $this->taskFileModel->getAllImages($task['id']), 'comments' => $this->commentModel->getAll($task['id'], $this->userSession->getCommentSorting()), @@ -82,6 +72,7 @@ class TaskViewController extends BaseController 'internal_links' => $this->taskLinkModel->getAllGroupedByLabel($task['id']), 'external_links' => $this->taskExternalLinkModel->getAll($task['id']), 'link_label_list' => $this->linkModel->getList(0, false), + 'tags' => $this->taskTagModel->getList($task['id']), ))); } @@ -100,6 +91,7 @@ class TaskViewController extends BaseController 'lead_time' => $this->taskAnalyticModel->getLeadTime($task), 'cycle_time' => $this->taskAnalyticModel->getCycleTime($task), 'time_spent_columns' => $this->taskAnalyticModel->getTimeSpentByColumn($task), + 'tags' => $this->taskTagModel->getList($task['id']), ))); } @@ -124,6 +116,7 @@ class TaskViewController extends BaseController 'task' => $task, 'project' => $this->projectModel->getById($task['project_id']), 'subtask_paginator' => $subtask_paginator, + 'tags' => $this->taskTagModel->getList($task['id']), ))); } @@ -140,6 +133,7 @@ class TaskViewController extends BaseController 'task' => $task, 'project' => $this->projectModel->getById($task['project_id']), 'transitions' => $this->transitionModel->getAllByTask($task['id']), + 'tags' => $this->taskTagModel->getList($task['id']), ))); } } diff --git a/app/Controller/UserListController.php b/app/Controller/UserListController.php index 31fcdd44..888583fa 100644 --- a/app/Controller/UserListController.php +++ b/app/Controller/UserListController.php @@ -17,12 +17,7 @@ class UserListController extends BaseController */ public function show() { - $paginator = $this->paginator - ->setUrl('UserListController', 'show') - ->setMax(30) - ->setOrder('username') - ->setQuery($this->userModel->getQuery()) - ->calculate(); + $paginator = $this->userPagination->getListingPaginator(); $this->response->html($this->helper->layout->app('user_list/show', array( 'title' => t('Users').' ('.$paginator->getTotal().')', diff --git a/app/Controller/WebNotificationController.php b/app/Controller/WebNotificationController.php index 46a42063..30e317f8 100644 --- a/app/Controller/WebNotificationController.php +++ b/app/Controller/WebNotificationController.php @@ -54,14 +54,14 @@ class WebNotificationController extends BaseController $this->response->redirect($this->helper->url->to( 'TaskViewController', 'show', - array('task_id' => $notification['event_data']['task']['id'], 'project_id' => $notification['event_data']['task']['project_id']), + array('task_id' => $this->notificationModel->getTaskIdFromEvent($notification['event_name'], $notification['event_data'])), 'comment-'.$notification['event_data']['comment']['id'] )); } else { $this->response->redirect($this->helper->url->to( 'TaskViewController', 'show', - array('task_id' => $notification['event_data']['task']['id'], 'project_id' => $notification['event_data']['task']['project_id']) + array('task_id' => $this->notificationModel->getTaskIdFromEvent($notification['event_name'], $notification['event_data'])) )); } } diff --git a/app/Core/Action/ActionManager.php b/app/Core/Action/ActionManager.php index 1dfd820c..aec9ef02 100644 --- a/app/Core/Action/ActionManager.php +++ b/app/Core/Action/ActionManager.php @@ -139,4 +139,20 @@ class ActionManager extends Base return $this; } + + /** + * Remove all listeners for automated actions + * + * @access public + */ + public function removeEvents() + { + foreach ($this->dispatcher->getListeners() as $eventName => $listeners) { + foreach ($listeners as $listener) { + if (is_array($listener) && $listener[0] instanceof ActionBase) { + $this->dispatcher->removeListener($eventName, $listener); + } + } + } + } } diff --git a/app/Core/Base.php b/app/Core/Base.php index 7b4462e2..68604785 100644 --- a/app/Core/Base.php +++ b/app/Core/Base.php @@ -35,8 +35,12 @@ use Pimple\Container; * @property \Kanboard\Core\Security\AuthenticationManager $authenticationManager * @property \Kanboard\Core\Security\AccessMap $applicationAccessMap * @property \Kanboard\Core\Security\AccessMap $projectAccessMap + * @property \Kanboard\Core\Security\AccessMap $apiAccessMap + * @property \Kanboard\Core\Security\AccessMap $apiProjectAccessMap * @property \Kanboard\Core\Security\Authorization $applicationAuthorization * @property \Kanboard\Core\Security\Authorization $projectAuthorization + * @property \Kanboard\Core\Security\Authorization $apiAuthorization + * @property \Kanboard\Core\Security\Authorization $apiProjectAuthorization * @property \Kanboard\Core\Security\Role $role * @property \Kanboard\Core\Security\Token $token * @property \Kanboard\Core\Session\FlashMessage $flash @@ -82,20 +86,31 @@ use Pimple\Container; * @property \Kanboard\Model\ProjectGroupRoleModel $projectGroupRoleModel * @property \Kanboard\Model\ProjectNotificationModel $projectNotificationModel * @property \Kanboard\Model\ProjectNotificationTypeModel $projectNotificationTypeModel + * @property \Kanboard\Model\ProjectTaskDuplicationModel $projectTaskDuplicationModel + * @property \Kanboard\Model\ProjectTaskPriorityModel $projectTaskPriorityModel * @property \Kanboard\Model\RememberMeSessionModel $rememberMeSessionModel * @property \Kanboard\Model\SubtaskModel $subtaskModel + * @property \Kanboard\Model\SubtaskPositionModel $subtaskPositionModel + * @property \Kanboard\Model\SubtaskStatusModel $subtaskStatusModel + * @property \Kanboard\Model\SubtaskTaskConversionModel $subtaskTaskConversionModel * @property \Kanboard\Model\SubtaskTimeTrackingModel $subtaskTimeTrackingModel * @property \Kanboard\Model\SwimlaneModel $swimlaneModel + * @property \Kanboard\Model\TagDuplicationModel $tagDuplicationModel + * @property \Kanboard\Model\TagModel $tagModel * @property \Kanboard\Model\TaskModel $taskModel * @property \Kanboard\Model\TaskAnalyticModel $taskAnalyticModel * @property \Kanboard\Model\TaskCreationModel $taskCreationModel * @property \Kanboard\Model\TaskDuplicationModel $taskDuplicationModel + * @property \Kanboard\Model\TaskProjectDuplicationModel $taskProjectDuplicationModel + * @property \Kanboard\Model\TaskProjectMoveModel $taskProjectMoveModel + * @property \Kanboard\Model\TaskRecurrenceModel $taskRecurrenceModel * @property \Kanboard\Model\TaskExternalLinkModel $taskExternalLinkModel * @property \Kanboard\Model\TaskFinderModel $taskFinderModel * @property \Kanboard\Model\TaskLinkModel $taskLinkModel * @property \Kanboard\Model\TaskModificationModel $taskModificationModel * @property \Kanboard\Model\TaskPositionModel $taskPositionModel * @property \Kanboard\Model\TaskStatusModel $taskStatusModel + * @property \Kanboard\Model\TaskTagModel $taskTagModel * @property \Kanboard\Model\TaskMetadataModel $taskMetadataModel * @property \Kanboard\Model\TimezoneModel $timezoneModel * @property \Kanboard\Model\TransitionModel $transitionModel @@ -107,6 +122,10 @@ use Pimple\Container; * @property \Kanboard\Model\UserNotificationFilterModel $userNotificationFilterModel * @property \Kanboard\Model\UserUnreadNotificationModel $userUnreadNotificationModel * @property \Kanboard\Model\UserMetadataModel $userMetadataModel + * @property \Kanboard\Pagination\TaskPagination $taskPagination + * @property \Kanboard\Pagination\SubtaskPagination $subtaskPagination + * @property \Kanboard\Pagination\ProjectPagination $projectPagination + * @property \Kanboard\Pagination\UserPagination $userPagination * @property \Kanboard\Validator\ActionValidator $actionValidator * @property \Kanboard\Validator\AuthValidator $authValidator * @property \Kanboard\Validator\ColumnValidator $columnValidator @@ -114,14 +133,15 @@ use Pimple\Container; * @property \Kanboard\Validator\CommentValidator $commentValidator * @property \Kanboard\Validator\CurrencyValidator $currencyValidator * @property \Kanboard\Validator\CustomFilterValidator $customFilterValidator + * @property \Kanboard\Validator\ExternalLinkValidator $externalLinkValidator * @property \Kanboard\Validator\GroupValidator $groupValidator * @property \Kanboard\Validator\LinkValidator $linkValidator * @property \Kanboard\Validator\PasswordResetValidator $passwordResetValidator * @property \Kanboard\Validator\ProjectValidator $projectValidator * @property \Kanboard\Validator\SubtaskValidator $subtaskValidator * @property \Kanboard\Validator\SwimlaneValidator $swimlaneValidator + * @property \Kanboard\Validator\TagValidator $tagValidator * @property \Kanboard\Validator\TaskLinkValidator $taskLinkValidator - * @property \Kanboard\Validator\ExternalLinkValidator $externalLinkValidator * @property \Kanboard\Validator\TaskValidator $taskValidator * @property \Kanboard\Validator\UserValidator $userValidator * @property \Kanboard\Import\TaskImport $taskImport @@ -137,6 +157,14 @@ use Pimple\Container; * @property \Kanboard\Core\Filter\QueryBuilder $taskQuery * @property \Kanboard\Core\Filter\LexerBuilder $taskLexer * @property \Kanboard\Core\Filter\LexerBuilder $projectActivityLexer + * @property \Kanboard\Job\CommentEventJob $commentEventJob + * @property \Kanboard\Job\SubtaskEventJob $subtaskEventJob + * @property \Kanboard\Job\TaskEventJob $taskEventJob + * @property \Kanboard\Job\TaskFileEventJob $taskFileEventJob + * @property \Kanboard\Job\TaskLinkEventJob $taskLinkEventJob + * @property \Kanboard\Job\ProjectFileEventJob $projectFileEventJob + * @property \Kanboard\Job\NotificationJob $notificationJob + * @property \Kanboard\Job\ProjectMetricJob $projectMetricJob * @property \Psr\Log\LoggerInterface $logger * @property \PicoDb\Database $db * @property \Symfony\Component\EventDispatcher\EventDispatcher $dispatcher @@ -165,10 +193,10 @@ abstract class Base } /** - * Load automatically models + * Load automatically dependencies * * @access public - * @param string $name Model name + * @param string $name Class name * @return mixed */ public function __get($name) @@ -186,7 +214,6 @@ abstract class Base */ public static function getInstance(Container $container) { - $self = new static($container); - return $self; + return new static($container); } } diff --git a/app/Core/ExternalLink/ExternalLinkManager.php b/app/Core/ExternalLink/ExternalLinkManager.php index 804e6b34..5a037999 100644 --- a/app/Core/ExternalLink/ExternalLinkManager.php +++ b/app/Core/ExternalLink/ExternalLinkManager.php @@ -153,6 +153,30 @@ class ExternalLinkManager extends Base } /** + * Set provider type + * + * @access public + * @param string $userInputType + * @return ExternalLinkManager + */ + public function setUserInputType($userInputType) + { + $this->userInputType = $userInputType; + return $this; + } + + /** + * Set external link + * @param string $userInputText + * @return ExternalLinkManager + */ + public function setUserInputText($userInputText) + { + $this->userInputText = $userInputText; + return $this; + } + + /** * Find a provider that user input * * @access private diff --git a/app/Core/Filter/Lexer.php b/app/Core/Filter/Lexer.php index fa5b8d2d..3ff57641 100644 --- a/app/Core/Filter/Lexer.php +++ b/app/Core/Filter/Lexer.php @@ -30,7 +30,7 @@ class Lexer '/^([<=>]{1,2}\w+)/u' => 'T_STRING', '/^([<=>]{1,2}".+")/' => 'T_STRING', '/^("(.+)")/' => 'T_STRING', - '/^(\w+)/u' => 'T_STRING', + '/^(\S+)/u' => 'T_STRING', '/^(#\d+)/' => 'T_STRING', ); diff --git a/app/Core/Filter/LexerBuilder.php b/app/Core/Filter/LexerBuilder.php index 7a9a714f..e3ab725b 100644 --- a/app/Core/Filter/LexerBuilder.php +++ b/app/Core/Filter/LexerBuilder.php @@ -51,7 +51,7 @@ class LexerBuilder */ public function __construct() { - $this->lexer = new Lexer; + $this->lexer = new Lexer(); $this->queryBuilder = new QueryBuilder(); } @@ -69,7 +69,7 @@ class LexerBuilder foreach ($attributes as $attribute) { $this->filters[$attribute] = $filter; - $this->lexer->addToken(sprintf("/^(%s:)/", $attribute), $attribute); + $this->lexer->addToken(sprintf("/^(%s:)/i", $attribute), $attribute); if ($default) { $this->lexer->setDefaultToken($attribute); diff --git a/app/Core/Http/Request.php b/app/Core/Http/Request.php index e0df2d3c..2e84958d 100644 --- a/app/Core/Http/Request.php +++ b/app/Core/Http/Request.php @@ -301,6 +301,7 @@ class Request extends Base public function getIpAddress() { $keys = array( + 'HTTP_X_REAL_IP', 'HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', diff --git a/app/Core/Ldap/User.php b/app/Core/Ldap/User.php index 91b48530..4bc1f5f9 100644 --- a/app/Core/Ldap/User.php +++ b/app/Core/Ldap/User.php @@ -116,7 +116,7 @@ class User */ protected function getRole(array $groupIds) { - if ($this->hasGroupsNotConfigured()) { + if (! $this->hasGroupsConfigured()) { return null; } @@ -278,14 +278,14 @@ class User } /** - * Return true if LDAP Group mapping is not configured + * Return true if LDAP Group mapping are configured * * @access public * @return boolean */ - public function hasGroupsNotConfigured() + public function hasGroupsConfigured() { - return !$this->getGroupAdminDn() && !$this->getGroupManagerDn(); + return $this->getGroupAdminDn() || $this->getGroupManagerDn(); } /** diff --git a/app/Core/Mail/Transport/Smtp.php b/app/Core/Mail/Transport/Smtp.php index 66f0a3aa..1f4e54ce 100644 --- a/app/Core/Mail/Transport/Smtp.php +++ b/app/Core/Mail/Transport/Smtp.php @@ -24,6 +24,15 @@ class Smtp extends Mail $transport->setUsername(MAIL_SMTP_USERNAME); $transport->setPassword(MAIL_SMTP_PASSWORD); $transport->setEncryption(MAIL_SMTP_ENCRYPTION); + if (HTTP_VERIFY_SSL_CERTIFICATE === false) { + $transport->setStreamOptions(array( + 'ssl' => array( + 'allow_self_signed' => true, + 'verify_peer' => false, + 'verify_peer_name' => false, + ) + )); + } return $transport; } diff --git a/app/Core/Plugin/Hook.php b/app/Core/Plugin/Hook.php index ade69150..ca197937 100644 --- a/app/Core/Plugin/Hook.php +++ b/app/Core/Plugin/Hook.php @@ -96,4 +96,21 @@ class Hook return null; } + + /** + * Hook with reference + * + * @access public + * @param string $hook + * @param mixed $param + * @return mixed + */ + public function reference($hook, &$param) + { + foreach ($this->getListeners($hook) as $listener) { + $listener($param); + } + + return $param; + } } diff --git a/app/Core/Queue/JobHandler.php b/app/Core/Queue/JobHandler.php index f8736cce..11c1fb69 100644 --- a/app/Core/Queue/JobHandler.php +++ b/app/Core/Queue/JobHandler.php @@ -2,6 +2,7 @@ namespace Kanboard\Core\Queue; +use Exception; use Kanboard\Core\Base; use Kanboard\Job\BaseJob; use SimpleQueue\Job; @@ -39,15 +40,23 @@ class JobHandler extends Base public function executeJob(Job $job) { $payload = $job->getBody(); - $className = $payload['class']; - $this->prepareJobSession($payload['user_id']); - if (DEBUG) { - $this->logger->debug(__METHOD__.' Received job => '.$className.' ('.getmypid().')'); - } + try { + $className = $payload['class']; + $this->prepareJobSession($payload['user_id']); + $this->prepareJobEnvironment(); + + if (DEBUG) { + $this->logger->debug(__METHOD__.' Received job => '.$className.' ('.getmypid().')'); + $this->logger->debug(__METHOD__.' => '.json_encode($payload)); + } - $worker = new $className($this->container); - call_user_func_array(array($worker, 'execute'), $payload['params']); + $worker = new $className($this->container); + call_user_func_array(array($worker, 'execute'), $payload['params']); + } catch (Exception $e) { + $this->logger->error(__METHOD__.': Error during job execution: '.$e->getMessage()); + $this->logger->error(__METHOD__ .' => '.json_encode($payload)); + } } /** @@ -66,4 +75,16 @@ class JobHandler extends Base $this->userSession->initialize($user); } } + + /** + * Flush in-memory caching and specific events + * + * @access protected + */ + protected function prepareJobEnvironment() + { + $this->memoryCache->flush(); + $this->actionManager->removeEvents(); + $this->dispatcher->dispatch('app.bootstrap'); + } } diff --git a/app/Core/Queue/QueueManager.php b/app/Core/Queue/QueueManager.php index f34cb220..dcf0ebf5 100644 --- a/app/Core/Queue/QueueManager.php +++ b/app/Core/Queue/QueueManager.php @@ -42,9 +42,13 @@ class QueueManager extends Base */ public function push(BaseJob $job) { + $jobClassName = get_class($job); + if ($this->queue !== null) { + $this->logger->debug(__METHOD__.': Job pushed in queue: '.$jobClassName); $this->queue->push(JobHandler::getInstance($this->container)->serializeJob($job)); } else { + $this->logger->debug(__METHOD__.': Job executed synchronously: '.$jobClassName); call_user_func_array(array($job, 'execute'), $job->getJobParams()); } @@ -60,7 +64,7 @@ class QueueManager extends Base public function listen() { if ($this->queue === null) { - throw new LogicException('No Queue Driver defined!'); + throw new LogicException('No queue driver defined!'); } while ($job = $this->queue->pull()) { diff --git a/app/Event/FileEvent.php b/app/Event/FileEvent.php deleted file mode 100644 index 482a4eab..00000000 --- a/app/Event/FileEvent.php +++ /dev/null @@ -1,7 +0,0 @@ -<?php - -namespace Kanboard\Event; - -class FileEvent extends GenericEvent -{ -} diff --git a/app/Event/ProjectFileEvent.php b/app/Event/ProjectFileEvent.php new file mode 100644 index 00000000..5d57e463 --- /dev/null +++ b/app/Event/ProjectFileEvent.php @@ -0,0 +1,7 @@ +<?php + +namespace Kanboard\Event; + +class ProjectFileEvent extends GenericEvent +{ +} diff --git a/app/Event/TaskFileEvent.php b/app/Event/TaskFileEvent.php new file mode 100644 index 00000000..fa3bdde9 --- /dev/null +++ b/app/Event/TaskFileEvent.php @@ -0,0 +1,7 @@ +<?php + +namespace Kanboard\Event; + +class TaskFileEvent extends GenericEvent +{ +} diff --git a/app/EventBuilder/BaseEventBuilder.php b/app/EventBuilder/BaseEventBuilder.php new file mode 100644 index 00000000..5aa777a0 --- /dev/null +++ b/app/EventBuilder/BaseEventBuilder.php @@ -0,0 +1,44 @@ +<?php + +namespace Kanboard\EventBuilder; + +use Kanboard\Core\Base; +use Kanboard\Event\GenericEvent; + +/** + * Class BaseEventBuilder + * + * @package Kanboard\EventBuilder + * @author Frederic Guillot + */ +abstract class BaseEventBuilder extends Base +{ + /** + * Build event data + * + * @access public + * @return GenericEvent|null + */ + abstract public function buildEvent(); + + /** + * Get event title with author + * + * @access public + * @param string $author + * @param string $eventName + * @param array $eventData + * @return string + */ + abstract public function buildTitleWithAuthor($author, $eventName, array $eventData); + + /** + * Get event title without author + * + * @access public + * @param string $eventName + * @param array $eventData + * @return string + */ + abstract public function buildTitleWithoutAuthor($eventName, array $eventData); +} diff --git a/app/EventBuilder/CommentEventBuilder.php b/app/EventBuilder/CommentEventBuilder.php new file mode 100644 index 00000000..ba5842a4 --- /dev/null +++ b/app/EventBuilder/CommentEventBuilder.php @@ -0,0 +1,98 @@ +<?php + +namespace Kanboard\EventBuilder; + +use Kanboard\Event\CommentEvent; +use Kanboard\Model\CommentModel; + +/** + * Class CommentEventBuilder + * + * @package Kanboard\EventBuilder + * @author Frederic Guillot + */ +class CommentEventBuilder extends BaseEventBuilder +{ + protected $commentId = 0; + + /** + * Set commentId + * + * @param int $commentId + * @return $this + */ + public function withCommentId($commentId) + { + $this->commentId = $commentId; + return $this; + } + + /** + * Build event data + * + * @access public + * @return CommentEvent|null + */ + public function buildEvent() + { + $comment = $this->commentModel->getById($this->commentId); + + if (empty($comment)) { + return null; + } + + return new CommentEvent(array( + 'comment' => $comment, + 'task' => $this->taskFinderModel->getDetails($comment['task_id']), + )); + } + + /** + * Get event title with author + * + * @access public + * @param string $author + * @param string $eventName + * @param array $eventData + * @return string + */ + public function buildTitleWithAuthor($author, $eventName, array $eventData) + { + switch ($eventName) { + case CommentModel::EVENT_UPDATE: + return e('%s updated a comment on the task #%d', $author, $eventData['task']['id']); + case CommentModel::EVENT_CREATE: + return e('%s commented on the task #%d', $author, $eventData['task']['id']); + case CommentModel::EVENT_DELETE: + return e('%s removed a comment on the task #%d', $author, $eventData['task']['id']); + case CommentModel::EVENT_USER_MENTION: + return e('%s mentioned you in a comment on the task #%d', $author, $eventData['task']['id']); + default: + return ''; + } + } + + /** + * Get event title without author + * + * @access public + * @param string $eventName + * @param array $eventData + * @return string + */ + public function buildTitleWithoutAuthor($eventName, array $eventData) + { + switch ($eventName) { + case CommentModel::EVENT_CREATE: + return e('New comment on task #%d', $eventData['comment']['task_id']); + case CommentModel::EVENT_UPDATE: + return e('Comment updated on task #%d', $eventData['comment']['task_id']); + case CommentModel::EVENT_DELETE: + return e('Comment removed on task #%d', $eventData['comment']['task_id']); + case CommentModel::EVENT_USER_MENTION: + return e('You were mentioned in a comment on the task #%d', $eventData['task']['id']); + default: + return ''; + } + } +} diff --git a/app/EventBuilder/EventIteratorBuilder.php b/app/EventBuilder/EventIteratorBuilder.php new file mode 100644 index 00000000..ba821753 --- /dev/null +++ b/app/EventBuilder/EventIteratorBuilder.php @@ -0,0 +1,52 @@ +<?php + +namespace Kanboard\EventBuilder; + +use Iterator; + +/** + * Class EventIteratorBuilder + * + * @package Kanboard\EventBuilder + * @author Frederic Guillot + */ +class EventIteratorBuilder implements Iterator { + private $position = 0; + private $builders = array(); + + /** + * Set builder + * + * @access public + * @param BaseEventBuilder $builder + * @return $this + */ + public function withBuilder(BaseEventBuilder $builder) + { + $this->builders[] = $builder; + return $this; + } + + public function rewind() { + $this->position = 0; + } + + /** + * @return BaseEventBuilder + */ + public function current() { + return $this->builders[$this->position]; + } + + public function key() { + return $this->position; + } + + public function next() { + ++$this->position; + } + + public function valid() { + return isset($this->builders[$this->position]); + } +} diff --git a/app/EventBuilder/ProjectFileEventBuilder.php b/app/EventBuilder/ProjectFileEventBuilder.php new file mode 100644 index 00000000..6698f78a --- /dev/null +++ b/app/EventBuilder/ProjectFileEventBuilder.php @@ -0,0 +1,77 @@ +<?php + +namespace Kanboard\EventBuilder; + +use Kanboard\Event\ProjectFileEvent; +use Kanboard\Event\GenericEvent; + +/** + * Class ProjectFileEventBuilder + * + * @package Kanboard\EventBuilder + * @author Frederic Guillot + */ +class ProjectFileEventBuilder extends BaseEventBuilder +{ + protected $fileId = 0; + + /** + * Set fileId + * + * @param int $fileId + * @return $this + */ + public function withFileId($fileId) + { + $this->fileId = $fileId; + return $this; + } + + /** + * Build event data + * + * @access public + * @return GenericEvent|null + */ + public function buildEvent() + { + $file = $this->projectFileModel->getById($this->fileId); + + if (empty($file)) { + $this->logger->debug(__METHOD__.': File not found'); + return null; + } + + return new ProjectFileEvent(array( + 'file' => $file, + 'project' => $this->projectModel->getById($file['project_id']), + )); + } + + /** + * Get event title with author + * + * @access public + * @param string $author + * @param string $eventName + * @param array $eventData + * @return string + */ + public function buildTitleWithAuthor($author, $eventName, array $eventData) + { + return ''; + } + + /** + * Get event title without author + * + * @access public + * @param string $eventName + * @param array $eventData + * @return string + */ + public function buildTitleWithoutAuthor($eventName, array $eventData) + { + return ''; + } +} diff --git a/app/EventBuilder/SubtaskEventBuilder.php b/app/EventBuilder/SubtaskEventBuilder.php new file mode 100644 index 00000000..5f7e831d --- /dev/null +++ b/app/EventBuilder/SubtaskEventBuilder.php @@ -0,0 +1,125 @@ +<?php + +namespace Kanboard\EventBuilder; + +use Kanboard\Event\SubtaskEvent; +use Kanboard\Event\GenericEvent; +use Kanboard\Model\SubtaskModel; + +/** + * Class SubtaskEventBuilder + * + * @package Kanboard\EventBuilder + * @author Frederic Guillot + */ +class SubtaskEventBuilder extends BaseEventBuilder +{ + /** + * SubtaskId + * + * @access protected + * @var int + */ + protected $subtaskId = 0; + + /** + * Changed values + * + * @access protected + * @var array + */ + protected $values = array(); + + /** + * Set SubtaskId + * + * @param int $subtaskId + * @return $this + */ + public function withSubtaskId($subtaskId) + { + $this->subtaskId = $subtaskId; + return $this; + } + + /** + * Set values + * + * @param array $values + * @return $this + */ + public function withValues(array $values) + { + $this->values = $values; + return $this; + } + + /** + * Build event data + * + * @access public + * @return GenericEvent|null + */ + public function buildEvent() + { + $eventData = array(); + $eventData['subtask'] = $this->subtaskModel->getById($this->subtaskId, true); + + if (empty($eventData['subtask'])) { + $this->logger->debug(__METHOD__.': Subtask not found'); + return null; + } + + if (! empty($this->values)) { + $eventData['changes'] = array_diff_assoc($this->values, $eventData['subtask']); + } + + $eventData['task'] = $this->taskFinderModel->getDetails($eventData['subtask']['task_id']); + return new SubtaskEvent($eventData); + } + + /** + * Get event title with author + * + * @access public + * @param string $author + * @param string $eventName + * @param array $eventData + * @return string + */ + public function buildTitleWithAuthor($author, $eventName, array $eventData) + { + switch ($eventName) { + case SubtaskModel::EVENT_UPDATE: + return e('%s updated a subtask for the task #%d', $author, $eventData['task']['id']); + case SubtaskModel::EVENT_CREATE: + return e('%s created a subtask for the task #%d', $author, $eventData['task']['id']); + case SubtaskModel::EVENT_DELETE: + return e('%s removed a subtask for the task #%d', $author, $eventData['task']['id']); + default: + return ''; + } + } + + /** + * Get event title without author + * + * @access public + * @param string $eventName + * @param array $eventData + * @return string + */ + public function buildTitleWithoutAuthor($eventName, array $eventData) + { + switch ($eventName) { + case SubtaskModel::EVENT_CREATE: + return e('New subtask on task #%d', $eventData['subtask']['task_id']); + case SubtaskModel::EVENT_UPDATE: + return e('Subtask updated on task #%d', $eventData['subtask']['task_id']); + case SubtaskModel::EVENT_DELETE: + return e('Subtask removed on task #%d', $eventData['subtask']['task_id']); + default: + return ''; + } + } +} diff --git a/app/EventBuilder/TaskEventBuilder.php b/app/EventBuilder/TaskEventBuilder.php new file mode 100644 index 00000000..aa897632 --- /dev/null +++ b/app/EventBuilder/TaskEventBuilder.php @@ -0,0 +1,223 @@ +<?php + +namespace Kanboard\EventBuilder; + +use Kanboard\Event\TaskEvent; +use Kanboard\Model\TaskModel; + +/** + * Class TaskEventBuilder + * + * @package Kanboard\EventBuilder + * @author Frederic Guillot + */ +class TaskEventBuilder extends BaseEventBuilder +{ + /** + * TaskId + * + * @access protected + * @var int + */ + protected $taskId = 0; + + /** + * Task + * + * @access protected + * @var array + */ + protected $task = array(); + + /** + * Extra values + * + * @access protected + * @var array + */ + protected $values = array(); + + /** + * Changed values + * + * @access protected + * @var array + */ + protected $changes = array(); + + /** + * Set TaskId + * + * @param int $taskId + * @return $this + */ + public function withTaskId($taskId) + { + $this->taskId = $taskId; + return $this; + } + + /** + * Set task + * + * @param array $task + * @return $this + */ + public function withTask(array $task) + { + $this->task = $task; + return $this; + } + + /** + * Set values + * + * @param array $values + * @return $this + */ + public function withValues(array $values) + { + $this->values = $values; + return $this; + } + + /** + * Set changes + * + * @param array $changes + * @return $this + */ + public function withChanges(array $changes) + { + $this->changes = $changes; + return $this; + } + + /** + * Build event data + * + * @access public + * @return TaskEvent|null + */ + public function buildEvent() + { + $eventData = array(); + $eventData['task_id'] = $this->taskId; + $eventData['task'] = $this->taskFinderModel->getDetails($this->taskId); + + if (empty($eventData['task'])) { + $this->logger->debug(__METHOD__.': Task not found'); + return null; + } + + if (! empty($this->changes)) { + if (empty($this->task)) { + $this->task = $eventData['task']; + } + + $eventData['changes'] = array_diff_assoc($this->changes, $this->task); + unset($eventData['changes']['date_modification']); + } + + return new TaskEvent(array_merge($eventData, $this->values)); + } + + /** + * Get event title with author + * + * @access public + * @param string $author + * @param string $eventName + * @param array $eventData + * @return string + */ + public function buildTitleWithAuthor($author, $eventName, array $eventData) + { + switch ($eventName) { + case TaskModel::EVENT_ASSIGNEE_CHANGE: + $assignee = $eventData['task']['assignee_name'] ?: $eventData['task']['assignee_username']; + + if (! empty($assignee)) { + return e('%s changed the assignee of the task #%d to %s', $author, $eventData['task']['id'], $assignee); + } + + return e('%s removed the assignee of the task %s', $author, e('#%d', $eventData['task']['id'])); + case TaskModel::EVENT_UPDATE: + return e('%s updated the task #%d', $author, $eventData['task']['id']); + case TaskModel::EVENT_CREATE: + return e('%s created the task #%d', $author, $eventData['task']['id']); + case TaskModel::EVENT_CLOSE: + return e('%s closed the task #%d', $author, $eventData['task']['id']); + case TaskModel::EVENT_OPEN: + return e('%s opened the task #%d', $author, $eventData['task']['id']); + case TaskModel::EVENT_MOVE_COLUMN: + return e( + '%s moved the task #%d to the column "%s"', + $author, + $eventData['task']['id'], + $eventData['task']['column_title'] + ); + case TaskModel::EVENT_MOVE_POSITION: + return e( + '%s moved the task #%d to the position %d in the column "%s"', + $author, + $eventData['task']['id'], + $eventData['task']['position'], + $eventData['task']['column_title'] + ); + case TaskModel::EVENT_MOVE_SWIMLANE: + if ($eventData['task']['swimlane_id'] == 0) { + return e('%s moved the task #%d to the first swimlane', $author, $eventData['task']['id']); + } + + return e( + '%s moved the task #%d to the swimlane "%s"', + $author, + $eventData['task']['id'], + $eventData['task']['swimlane_name'] + ); + + case TaskModel::EVENT_USER_MENTION: + return e('%s mentioned you in the task #%d', $author, $eventData['task']['id']); + default: + return ''; + } + } + + /** + * Get event title without author + * + * @access public + * @param string $eventName + * @param array $eventData + * @return string + */ + public function buildTitleWithoutAuthor($eventName, array $eventData) + { + switch ($eventName) { + case TaskModel::EVENT_CREATE: + return e('New task #%d: %s', $eventData['task']['id'], $eventData['task']['title']); + case TaskModel::EVENT_UPDATE: + return e('Task updated #%d', $eventData['task']['id']); + case TaskModel::EVENT_CLOSE: + return e('Task #%d closed', $eventData['task']['id']); + case TaskModel::EVENT_OPEN: + return e('Task #%d opened', $eventData['task']['id']); + case TaskModel::EVENT_MOVE_COLUMN: + return e('Column changed for task #%d', $eventData['task']['id']); + case TaskModel::EVENT_MOVE_POSITION: + return e('New position for task #%d', $eventData['task']['id']); + case TaskModel::EVENT_MOVE_SWIMLANE: + return e('Swimlane changed for task #%d', $eventData['task']['id']); + case TaskModel::EVENT_ASSIGNEE_CHANGE: + return e('Assignee changed on task #%d', $eventData['task']['id']); + case TaskModel::EVENT_OVERDUE: + $nb = count($eventData['tasks']); + return $nb > 1 ? e('%d overdue tasks', $nb) : e('Task #%d is overdue', $eventData['tasks'][0]['id']); + case TaskModel::EVENT_USER_MENTION: + return e('You were mentioned in the task #%d', $eventData['task']['id']); + default: + return ''; + } + } +} diff --git a/app/EventBuilder/TaskFileEventBuilder.php b/app/EventBuilder/TaskFileEventBuilder.php new file mode 100644 index 00000000..8c985cc0 --- /dev/null +++ b/app/EventBuilder/TaskFileEventBuilder.php @@ -0,0 +1,86 @@ +<?php + +namespace Kanboard\EventBuilder; + +use Kanboard\Event\TaskFileEvent; +use Kanboard\Event\GenericEvent; +use Kanboard\Model\TaskFileModel; + +/** + * Class TaskFileEventBuilder + * + * @package Kanboard\EventBuilder + * @author Frederic Guillot + */ +class TaskFileEventBuilder extends BaseEventBuilder +{ + protected $fileId = 0; + + /** + * Set fileId + * + * @param int $fileId + * @return $this + */ + public function withFileId($fileId) + { + $this->fileId = $fileId; + return $this; + } + + /** + * Build event data + * + * @access public + * @return GenericEvent|null + */ + public function buildEvent() + { + $file = $this->taskFileModel->getById($this->fileId); + + if (empty($file)) { + $this->logger->debug(__METHOD__.': File not found'); + return null; + } + + return new TaskFileEvent(array( + 'file' => $file, + 'task' => $this->taskFinderModel->getDetails($file['task_id']), + )); + } + + /** + * Get event title with author + * + * @access public + * @param string $author + * @param string $eventName + * @param array $eventData + * @return string + */ + public function buildTitleWithAuthor($author, $eventName, array $eventData) + { + if ($eventName === TaskFileModel::EVENT_CREATE) { + return e('%s attached a file to the task #%d', $author, $eventData['task']['id']); + } + + return ''; + } + + /** + * Get event title without author + * + * @access public + * @param string $eventName + * @param array $eventData + * @return string + */ + public function buildTitleWithoutAuthor($eventName, array $eventData) + { + if ($eventName === TaskFileModel::EVENT_CREATE) { + return e('New attachment on task #%d: %s', $eventData['file']['task_id'], $eventData['file']['name']); + } + + return ''; + } +} diff --git a/app/EventBuilder/TaskLinkEventBuilder.php b/app/EventBuilder/TaskLinkEventBuilder.php new file mode 100644 index 00000000..f1a3fba2 --- /dev/null +++ b/app/EventBuilder/TaskLinkEventBuilder.php @@ -0,0 +1,89 @@ +<?php + +namespace Kanboard\EventBuilder; + +use Kanboard\Event\TaskLinkEvent; +use Kanboard\Model\TaskLinkModel; + +/** + * Class TaskLinkEventBuilder + * + * @package Kanboard\EventBuilder + * @author Frederic Guillot + */ +class TaskLinkEventBuilder extends BaseEventBuilder +{ + protected $taskLinkId = 0; + + /** + * Set taskLinkId + * + * @param int $taskLinkId + * @return $this + */ + public function withTaskLinkId($taskLinkId) + { + $this->taskLinkId = $taskLinkId; + return $this; + } + + /** + * Build event data + * + * @access public + * @return TaskLinkEvent|null + */ + public function buildEvent() + { + $taskLink = $this->taskLinkModel->getById($this->taskLinkId); + + if (empty($taskLink)) { + $this->logger->debug(__METHOD__.': TaskLink not found'); + return null; + } + + return new TaskLinkEvent(array( + 'task_link' => $taskLink, + 'task' => $this->taskFinderModel->getDetails($taskLink['task_id']), + )); + } + + /** + * Get event title with author + * + * @access public + * @param string $author + * @param string $eventName + * @param array $eventData + * @return string + */ + public function buildTitleWithAuthor($author, $eventName, array $eventData) + { + if ($eventName === TaskLinkModel::EVENT_CREATE_UPDATE) { + return e('%s set a new internal link for the task #%d', $author, $eventData['task']['id']); + } elseif ($eventName === TaskLinkModel::EVENT_DELETE) { + return e('%s removed an internal link for the task #%d', $author, $eventData['task']['id']); + } + + return ''; + } + + /** + * Get event title without author + * + * @access public + * @param string $eventName + * @param array $eventData + * @return string + */ + public function buildTitleWithoutAuthor($eventName, array $eventData) + { + if ($eventName === TaskLinkModel::EVENT_CREATE_UPDATE) { + return e('A new internal link for the task #%d have been defined', $eventData['task']['id']); + } elseif ($eventName === TaskLinkModel::EVENT_DELETE) { + return e('Internal link removed for the task #%d', $eventData['task']['id']); + } + + return ''; + } +} diff --git a/app/Filter/BaseFilter.php b/app/Filter/BaseFilter.php index 79a664be..e029f4e1 100644 --- a/app/Filter/BaseFilter.php +++ b/app/Filter/BaseFilter.php @@ -43,8 +43,7 @@ abstract class BaseFilter */ public static function getInstance($value = null) { - $self = new static($value); - return $self; + return new static($value); } /** diff --git a/app/Filter/TaskMovedDateFilter.php b/app/Filter/TaskMovedDateFilter.php new file mode 100644 index 00000000..d57b7d23 --- /dev/null +++ b/app/Filter/TaskMovedDateFilter.php @@ -0,0 +1,38 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\TaskModel; + +/** + * Filter tasks by modification date + * + * @package filter + * @author Frederic Guillot + */ +class TaskMovedDateFilter extends BaseDateFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('moved'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + $this->applyDateFilter(TaskModel::TABLE.'.date_moved'); + return $this; + } +} diff --git a/app/Filter/TaskPriorityFilter.php b/app/Filter/TaskPriorityFilter.php new file mode 100644 index 00000000..75f6ae3d --- /dev/null +++ b/app/Filter/TaskPriorityFilter.php @@ -0,0 +1,38 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\TaskModel; + +/** + * Class TaskPriorityFilter + * + * @package Kanboard\Filter + * @author Frederic Guillot + */ +class TaskPriorityFilter extends BaseFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('priority'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + $this->query->eq(TaskModel::TABLE.'.priority', $this->value); + return $this; + } +} diff --git a/app/Filter/TaskTagFilter.php b/app/Filter/TaskTagFilter.php new file mode 100644 index 00000000..01b6f625 --- /dev/null +++ b/app/Filter/TaskTagFilter.php @@ -0,0 +1,74 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\TagModel; +use Kanboard\Model\TaskModel; +use Kanboard\Model\TaskTagModel; +use PicoDb\Database; + +/** + * Class TaskTagFilter + * + * @package Kanboard\Filter + * @author Frederic Guillot + */ +class TaskTagFilter extends BaseFilter implements FilterInterface +{ + /** + * Database object + * + * @access private + * @var Database + */ + private $db; + + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('tag'); + } + + /** + * Set database object + * + * @access public + * @param Database $db + * @return $this + */ + public function setDatabase(Database $db) + { + $this->db = $db; + return $this; + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + $task_ids = $this->db + ->table(TagModel::TABLE) + ->ilike(TagModel::TABLE.'.name', $this->value) + ->asc(TagModel::TABLE.'.project_id') + ->join(TaskTagModel::TABLE, 'tag_id', 'id') + ->findAllByColumn(TaskTagModel::TABLE.'.task_id'); + + if (empty($task_ids)) { + $task_ids = array(-1); + } + + $this->query->in(TaskModel::TABLE.'.id', $task_ids); + + return $this; + } +} diff --git a/app/Formatter/BaseFormatter.php b/app/Formatter/BaseFormatter.php index a9f0ad15..89c48437 100644 --- a/app/Formatter/BaseFormatter.php +++ b/app/Formatter/BaseFormatter.php @@ -3,8 +3,8 @@ namespace Kanboard\Formatter; use Kanboard\Core\Base; -use Kanboard\Core\Filter\FormatterInterface; use PicoDb\Table; +use Pimple\Container; /** * Class BaseFormatter @@ -23,11 +23,24 @@ abstract class BaseFormatter extends Base protected $query; /** + * Get object instance + * + * @static + * @access public + * @param Container $container + * @return static + */ + public static function getInstance(Container $container) + { + return new static($container); + } + + /** * Set query * * @access public * @param Table $query - * @return FormatterInterface + * @return $this */ public function withQuery(Table $query) { diff --git a/app/Formatter/BoardColumnFormatter.php b/app/Formatter/BoardColumnFormatter.php new file mode 100644 index 00000000..d49a577a --- /dev/null +++ b/app/Formatter/BoardColumnFormatter.php @@ -0,0 +1,94 @@ +<?php + +namespace Kanboard\Formatter; + +use Kanboard\Core\Filter\FormatterInterface; + +/** + * Board Column Formatter + * + * @package formatter + * @author Frederic Guillot + */ +class BoardColumnFormatter extends BaseFormatter implements FormatterInterface +{ + protected $swimlaneId = 0; + protected $columns = array(); + protected $tasks = array(); + protected $tags = array(); + + /** + * Set swimlaneId + * + * @access public + * @param integer $swimlaneId + * @return $this + */ + public function withSwimlaneId($swimlaneId) + { + $this->swimlaneId = $swimlaneId; + return $this; + } + + /** + * Set columns + * + * @access public + * @param array $columns + * @return $this + */ + public function withColumns(array $columns) + { + $this->columns = $columns; + return $this; + } + + /** + * Set tasks + * + * @access public + * @param array $tasks + * @return $this + */ + public function withTasks(array $tasks) + { + $this->tasks = $tasks; + return $this; + } + + /** + * Set tags + * + * @access public + * @param array $tags + * @return $this + */ + public function withTags(array $tags) + { + $this->tags = $tags; + return $this; + } + + /** + * Apply formatter + * + * @access public + * @return array + */ + public function format() + { + foreach ($this->columns as &$column) { + $column['tasks'] = BoardTaskFormatter::getInstance($this->container) + ->withTasks($this->tasks) + ->withTags($this->tags) + ->withSwimlaneId($this->swimlaneId) + ->withColumnId($column['id']) + ->format(); + + $column['nb_tasks'] = count($column['tasks']); + $column['score'] = (int) array_column_sum($column['tasks'], 'score'); + } + + return $this->columns; + } +} diff --git a/app/Formatter/BoardFormatter.php b/app/Formatter/BoardFormatter.php index dbc7cf21..df443a52 100644 --- a/app/Formatter/BoardFormatter.php +++ b/app/Formatter/BoardFormatter.php @@ -28,7 +28,7 @@ class BoardFormatter extends BaseFormatter implements FormatterInterface * @param integer $projectId * @return $this */ - public function setProjectId($projectId) + public function withProjectId($projectId) { $this->projectId = $projectId; return $this; @@ -42,15 +42,28 @@ class BoardFormatter extends BaseFormatter implements FormatterInterface */ public function format() { + $swimlanes = $this->swimlaneModel->getSwimlanes($this->projectId); + $columns = $this->columnModel->getAll($this->projectId); + + if (empty($swimlanes) || empty($columns)) { + return array(); + } + + $this->hook->reference('formatter:board:query', $this->query); + $tasks = $this->query ->eq(TaskModel::TABLE.'.project_id', $this->projectId) ->asc(TaskModel::TABLE.'.position') ->findAll(); - return $this->boardModel->getBoard($this->projectId, function ($project_id, $column_id, $swimlane_id) use ($tasks) { - return array_filter($tasks, function (array $task) use ($column_id, $swimlane_id) { - return $task['column_id'] == $column_id && $task['swimlane_id'] == $swimlane_id; - }); - }); + $task_ids = array_column($tasks, 'id'); + $tags = $this->taskTagModel->getTagsByTasks($task_ids); + + return BoardSwimlaneFormatter::getInstance($this->container) + ->withSwimlanes($swimlanes) + ->withColumns($columns) + ->withTasks($tasks) + ->withTags($tags) + ->format(); } } diff --git a/app/Formatter/BoardSwimlaneFormatter.php b/app/Formatter/BoardSwimlaneFormatter.php new file mode 100644 index 00000000..c2abb444 --- /dev/null +++ b/app/Formatter/BoardSwimlaneFormatter.php @@ -0,0 +1,120 @@ +<?php + +namespace Kanboard\Formatter; + +use Kanboard\Core\Filter\FormatterInterface; + +/** + * Board Swimlane Formatter + * + * @package formatter + * @author Frederic Guillot + */ +class BoardSwimlaneFormatter extends BaseFormatter implements FormatterInterface +{ + protected $swimlanes = array(); + protected $columns = array(); + protected $tasks = array(); + protected $tags = array(); + + /** + * Set swimlanes + * + * @access public + * @param array $swimlanes + * @return $this + */ + public function withSwimlanes($swimlanes) + { + $this->swimlanes = $swimlanes; + return $this; + } + + /** + * Set columns + * + * @access public + * @param array $columns + * @return $this + */ + public function withColumns($columns) + { + $this->columns = $columns; + return $this; + } + + /** + * Set tasks + * + * @access public + * @param array $tasks + * @return $this + */ + public function withTasks(array $tasks) + { + $this->tasks = $tasks; + return $this; + } + + /** + * Set tags + * + * @access public + * @param array $tags + * @return $this + */ + public function withTags(array $tags) + { + $this->tags = $tags; + return $this; + } + + /** + * Apply formatter + * + * @access public + * @return array + */ + public function format() + { + $nb_swimlanes = count($this->swimlanes); + $nb_columns = count($this->columns); + + foreach ($this->swimlanes as &$swimlane) { + $swimlane['columns'] = BoardColumnFormatter::getInstance($this->container) + ->withSwimlaneId($swimlane['id']) + ->withColumns($this->columns) + ->withTasks($this->tasks) + ->withTags($this->tags) + ->format(); + + $swimlane['nb_swimlanes'] = $nb_swimlanes; + $swimlane['nb_columns'] = $nb_columns; + $swimlane['nb_tasks'] = array_column_sum($swimlane['columns'], 'nb_tasks'); + $swimlane['score'] = array_column_sum($swimlane['columns'], 'score'); + + $this->calculateStatsByColumnAcrossSwimlanes($swimlane['columns']); + } + + return $this->swimlanes; + } + + /** + * Calculate stats for each column acrosss all swimlanes + * + * @access protected + * @param array $columns + */ + protected function calculateStatsByColumnAcrossSwimlanes(array $columns) + { + foreach ($columns as $columnIndex => $column) { + if (! isset($this->swimlanes[0]['columns'][$columnIndex]['column_nb_tasks'])) { + $this->swimlanes[0]['columns'][$columnIndex]['column_nb_tasks'] = 0; + $this->swimlanes[0]['columns'][$columnIndex]['column_score'] = 0; + } + + $this->swimlanes[0]['columns'][$columnIndex]['column_nb_tasks'] += $column['nb_tasks']; + $this->swimlanes[0]['columns'][$columnIndex]['column_score'] += $column['score']; + } + } +} diff --git a/app/Formatter/BoardTaskFormatter.php b/app/Formatter/BoardTaskFormatter.php new file mode 100644 index 00000000..3bf171b1 --- /dev/null +++ b/app/Formatter/BoardTaskFormatter.php @@ -0,0 +1,96 @@ +<?php + +namespace Kanboard\Formatter; + +use Kanboard\Core\Filter\FormatterInterface; + +/** + * Board Task Formatter + * + * @package formatter + * @author Frederic Guillot + */ +class BoardTaskFormatter extends BaseFormatter implements FormatterInterface +{ + protected $tasks = array(); + protected $tags = array(); + protected $columnId = 0; + protected $swimlaneId = 0; + + /** + * Set tags + * + * @access public + * @param array $tags + * @return $this + */ + public function withTags(array $tags) + { + $this->tags = $tags; + return $this; + } + + /** + * Set tasks + * + * @access public + * @param array $tasks + * @return $this + */ + public function withTasks(array $tasks) + { + $this->tasks = $tasks; + return $this; + } + + /** + * Set columnId + * + * @access public + * @param integer $columnId + * @return $this + */ + public function withColumnId($columnId) + { + $this->columnId = $columnId; + return $this; + } + + /** + * Set swimlaneId + * + * @access public + * @param integer $swimlaneId + * @return $this + */ + public function withSwimlaneId($swimlaneId) + { + $this->swimlaneId = $swimlaneId; + return $this; + } + + /** + * Apply formatter + * + * @access public + * @return array + */ + public function format() + { + $tasks = array_values(array_filter($this->tasks, array($this, 'filterTasks'))); + array_merge_relation($tasks, $this->tags, 'tags', 'id'); + return $tasks; + } + + /** + * Keep only tasks of the given column and swimlane + * + * @access protected + * @param array $task + * @return bool + */ + protected function filterTasks(array $task) + { + return $task['column_id'] == $this->columnId && $task['swimlane_id'] == $this->swimlaneId; + } +} diff --git a/app/Formatter/TaskAutoCompleteFormatter.php b/app/Formatter/TaskAutoCompleteFormatter.php index 4f1c4c69..2d9f7341 100644 --- a/app/Formatter/TaskAutoCompleteFormatter.php +++ b/app/Formatter/TaskAutoCompleteFormatter.php @@ -3,6 +3,7 @@ namespace Kanboard\Formatter; use Kanboard\Core\Filter\FormatterInterface; +use Kanboard\Model\ProjectModel; use Kanboard\Model\TaskModel; /** @@ -21,11 +22,15 @@ class TaskAutoCompleteFormatter extends BaseFormatter implements FormatterInterf */ public function format() { - $tasks = $this->query->columns(TaskModel::TABLE.'.id', TaskModel::TABLE.'.title')->findAll(); + $tasks = $this->query->columns( + TaskModel::TABLE.'.id', + TaskModel::TABLE.'.title', + ProjectModel::TABLE.'.name AS project_name' + )->asc(TaskModel::TABLE.'.id')->findAll(); foreach ($tasks as &$task) { $task['value'] = $task['title']; - $task['label'] = '#'.$task['id'].' - '.$task['title']; + $task['label'] = $task['project_name'].' > #'.$task['id'].' '.$task['title']; } return $tasks; diff --git a/app/Formatter/TaskICalFormatter.php b/app/Formatter/TaskICalFormatter.php index 890674c7..ad2a4449 100644 --- a/app/Formatter/TaskICalFormatter.php +++ b/app/Formatter/TaskICalFormatter.php @@ -6,6 +6,7 @@ use DateTime; use Eluceo\iCal\Component\Calendar; use Eluceo\iCal\Component\Event; use Eluceo\iCal\Property\Event\Attendees; +use Eluceo\iCal\Property\Event\Organizer; use Kanboard\Core\Filter\FormatterInterface; /** @@ -117,16 +118,24 @@ class TaskICalFormatter extends BaseTaskCalendarFormatter implements FormatterIn $vEvent->setModified($dateModif); $vEvent->setUseTimezone(true); $vEvent->setSummary(t('#%d', $task['id']).' '.$task['title']); + $vEvent->setDescription($task['description']); + $vEvent->setDescriptionHTML($this->helper->text->markdown($task['description'])); $vEvent->setUrl($this->helper->url->base().$this->helper->url->to('TaskViewController', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']))); if (! empty($task['owner_id'])) { - $vEvent->setOrganizer($task['assignee_name'] ?: $task['assignee_username'], $task['assignee_email']); + $attendees = new Attendees; + $attendees->add( + 'MAILTO:'.($task['assignee_email'] ?: $task['assignee_username'].'@kanboard.local'), + array('CN' => $task['assignee_name'] ?: $task['assignee_username']) + ); + $vEvent->setAttendees($attendees); } if (! empty($task['creator_id'])) { - $attendees = new Attendees; - $attendees->add('MAILTO:'.($task['creator_email'] ?: $task['creator_username'].'@kanboard.local')); - $vEvent->setAttendees($attendees); + $vEvent->setOrganizer(new Organizer( + 'MAILTO:' . $task['creator_email'] ?: $task['creator_username'].'@kanboard.local', + array('CN' => $task['creator_name'] ?: $task['creator_username']) + )); } return $vEvent; diff --git a/app/Helper/FormHelper.php b/app/Helper/FormHelper.php index c2ea1d72..0bb94d39 100644 --- a/app/Helper/FormHelper.php +++ b/app/Helper/FormHelper.php @@ -307,6 +307,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/LayoutHelper.php b/app/Helper/LayoutHelper.php index 8ebb05d4..8d2e7e00 100644 --- a/app/Helper/LayoutHelper.php +++ b/app/Helper/LayoutHelper.php @@ -156,6 +156,10 @@ class LayoutHelper extends Base */ public function analytic($template, array $params) { + if (isset($params['project']['name'])) { + $params['title'] = $params['project']['name'].' > '.$params['title']; + } + return $this->subLayout('analytic/layout', 'analytic/sidebar', $template, $params); } diff --git a/app/Helper/SubtaskHelper.php b/app/Helper/SubtaskHelper.php index dac71203..833544a7 100644 --- a/app/Helper/SubtaskHelper.php +++ b/app/Helper/SubtaskHelper.php @@ -66,7 +66,10 @@ class SubtaskHelper extends Base $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; } diff --git a/app/Helper/TaskHelper.php b/app/Helper/TaskHelper.php index e33438d6..678b4bed 100644 --- a/app/Helper/TaskHelper.php +++ b/app/Helper/TaskHelper.php @@ -27,17 +27,74 @@ class TaskHelper extends Base public function recurrenceTriggers() { - return $this->taskModel->getRecurrenceTriggerList(); + return $this->taskRecurrenceModel->getRecurrenceTriggerList(); } public function recurrenceTimeframes() { - return $this->taskModel->getRecurrenceTimeframeList(); + return $this->taskRecurrenceModel->getRecurrenceTimeframeList(); } public function recurrenceBasedates() { - return $this->taskModel->getRecurrenceBasedateList(); + return $this->taskRecurrenceModel->getRecurrenceBasedateList(); + } + + public function selectTitle(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 .= '<div class="markdown-editor-container">'; + $html .= $this->helper->form->textarea( + 'description', + $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' + ); + + $html .= '</div>'; + return $html; + } + + public function selectTags(array $project, array $tags = array()) + { + $options = $this->tagModel->getAssignableList($project['id']); + + $html = $this->helper->form->label(t('Tags'), 'tags[]'); + $html .= '<input type="hidden" name="tags[]" value="">'; + $html .= '<select name="tags[]" id="form-tags" class="tag-autocomplete" multiple>'; + + foreach ($options as $tag) { + $html .= sprintf( + '<option value="%s" %s>%s</option>', + $this->helper->text->e($tag), + in_array($tag, $tags) ? 'selected="selected"' : '', + $this->helper->text->e($tag) + ); + } + + $html .= '</select>'; + + return $html; + } + + public function selectColor(array $values) + { + $colors = $this->colorModel->getList(); + $html = $this->helper->form->label(t('Color'), 'color_id'); + $html .= $this->helper->form->select('color_id', $colors, $values, array(), array(), 'color-picker'); + return $html; } public function selectAssignee(array $users, array $values, array $errors = array(), array $attributes = array()) @@ -46,7 +103,10 @@ class TaskHelper extends Base $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; } @@ -91,7 +151,7 @@ class TaskHelper extends Base { $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']); @@ -113,10 +173,20 @@ class TaskHelper extends Base return $html; } - public function selectTimeEstimated(array $values, array $errors = array(), array $attributes = array()) + public function selectReference(array $values, array $errors = array(), array $attributes = array()) { $attributes = array_merge(array('tabindex="9"'), $attributes); + $html = $this->helper->form->label(t('Reference'), 'reference'); + $html .= $this->helper->form->text('reference', $values, $errors, $attributes, 'form-input-small'); + + return $html; + } + + public function selectTimeEstimated(array $values, array $errors = array(), array $attributes = array()) + { + $attributes = array_merge(array('tabindex="10"'), $attributes); + $html = $this->helper->form->label(t('Original estimate'), 'time_estimated'); $html .= $this->helper->form->numeric('time_estimated', $values, $errors, $attributes); $html .= ' '.t('hours'); @@ -126,7 +196,7 @@ class TaskHelper extends Base public function selectTimeSpent(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('Time spent'), 'time_spent'); $html .= $this->helper->form->numeric('time_spent', $values, $errors, $attributes); @@ -137,31 +207,21 @@ class TaskHelper extends Base public function selectStartDate(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="11"', '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="12"'), $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()) { - $placeholder = date($this->configModel->get('application_date_format', 'm/d/Y')); - $attributes = array_merge(array('tabindex="12"', '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="13"'), $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>'; diff --git a/app/Helper/UserHelper.php b/app/Helper/UserHelper.php index ae3efe1d..e42bafe4 100644 --- a/app/Helper/UserHelper.php +++ b/app/Helper/UserHelper.php @@ -50,7 +50,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']; } /** @@ -107,6 +108,10 @@ class UserHelper extends Base */ public function hasAccess($controller, $action) { + if (! $this->userSession->isLogged()) { + return false; + } + $key = 'app_access:'.$controller.$action; $result = $this->memoryCache->get($key); @@ -128,6 +133,10 @@ class UserHelper extends Base */ public function hasProjectAccess($controller, $action, $project_id) { + if (! $this->userSession->isLogged()) { + return false; + } + if ($this->userSession->isAdmin()) { return true; } diff --git a/app/Job/CommentEventJob.php b/app/Job/CommentEventJob.php new file mode 100644 index 00000000..47cf8020 --- /dev/null +++ b/app/Job/CommentEventJob.php @@ -0,0 +1,50 @@ +<?php + +namespace Kanboard\Job; + +use Kanboard\EventBuilder\CommentEventBuilder; +use Kanboard\Model\CommentModel; + +/** + * Class CommentEventJob + * + * @package Kanboard\Job + * @author Frederic Guillot + */ +class CommentEventJob extends BaseJob +{ + /** + * Set job params + * + * @param int $commentId + * @param string $eventName + * @return $this + */ + public function withParams($commentId, $eventName) + { + $this->jobParams = array($commentId, $eventName); + return $this; + } + + /** + * Execute job + * + * @param int $commentId + * @param string $eventName + * @return $this + */ + public function execute($commentId, $eventName) + { + $event = CommentEventBuilder::getInstance($this->container) + ->withCommentId($commentId) + ->buildEvent(); + + if ($event !== null) { + $this->dispatcher->dispatch($eventName, $event); + + if ($eventName === CommentModel::EVENT_CREATE) { + $this->userMentionModel->fireEvents($event['comment']['comment'], CommentModel::EVENT_USER_MENTION, $event); + } + } + } +} diff --git a/app/Job/NotificationJob.php b/app/Job/NotificationJob.php index 904a9273..8fb260e8 100644 --- a/app/Job/NotificationJob.php +++ b/app/Job/NotificationJob.php @@ -17,69 +17,27 @@ class NotificationJob extends BaseJob * * @param GenericEvent $event * @param string $eventName - * @param string $eventObjectName * @return $this */ - public function withParams(GenericEvent $event, $eventName, $eventObjectName) + public function withParams(GenericEvent $event, $eventName) { - $this->jobParams = array($event->getAll(), $eventName, $eventObjectName); + $this->jobParams = array($event->getAll(), $eventName); return $this; } /** * Execute job * - * @param array $event + * @param array $eventData * @param string $eventName - * @param string $eventObjectName */ - public function execute(array $event, $eventName, $eventObjectName) + public function execute(array $eventData, $eventName) { - $eventData = $this->getEventData($event, $eventObjectName); - - if (! empty($eventData)) { - if (! empty($event['mention'])) { - $this->userNotificationModel->sendUserNotification($event['mention'], $eventName, $eventData); - } else { - $this->userNotificationModel->sendNotifications($eventName, $eventData); - $this->projectNotificationModel->sendNotifications($eventData['task']['project_id'], $eventName, $eventData); - } - } - } - - /** - * Get event data - * - * @param array $event - * @param string $eventObjectName - * @return array - */ - public function getEventData(array $event, $eventObjectName) - { - $values = array(); - - if (! empty($event['changes'])) { - $values['changes'] = $event['changes']; + if (! empty($eventData['mention'])) { + $this->userNotificationModel->sendUserNotification($eventData['mention'], $eventName, $eventData); + } else { + $this->userNotificationModel->sendNotifications($eventName, $eventData); + $this->projectNotificationModel->sendNotifications($eventData['task']['project_id'], $eventName, $eventData); } - - switch ($eventObjectName) { - case 'Kanboard\Event\TaskEvent': - $values['task'] = $this->taskFinderModel->getDetails($event['task_id']); - break; - case 'Kanboard\Event\SubtaskEvent': - $values['subtask'] = $this->subtaskModel->getById($event['id'], true); - $values['task'] = $this->taskFinderModel->getDetails($values['subtask']['task_id']); - break; - case 'Kanboard\Event\FileEvent': - $values['file'] = $event; - $values['task'] = $this->taskFinderModel->getDetails($values['file']['task_id']); - break; - case 'Kanboard\Event\CommentEvent': - $values['comment'] = $this->commentModel->getById($event['id']); - $values['task'] = $this->taskFinderModel->getDetails($values['comment']['task_id']); - break; - } - - return $values; } } diff --git a/app/Job/ProjectFileEventJob.php b/app/Job/ProjectFileEventJob.php new file mode 100644 index 00000000..45e6ece3 --- /dev/null +++ b/app/Job/ProjectFileEventJob.php @@ -0,0 +1,45 @@ +<?php + +namespace Kanboard\Job; + +use Kanboard\EventBuilder\ProjectFileEventBuilder; + +/** + * Class ProjectFileEventJob + * + * @package Kanboard\Job + * @author Frederic Guillot + */ +class ProjectFileEventJob extends BaseJob +{ + /** + * Set job params + * + * @param int $fileId + * @param string $eventName + * @return $this + */ + public function withParams($fileId, $eventName) + { + $this->jobParams = array($fileId, $eventName); + return $this; + } + + /** + * Execute job + * + * @param int $fileId + * @param string $eventName + * @return $this + */ + public function execute($fileId, $eventName) + { + $event = ProjectFileEventBuilder::getInstance($this->container) + ->withFileId($fileId) + ->buildEvent(); + + if ($event !== null) { + $this->dispatcher->dispatch($eventName, $event); + } + } +} diff --git a/app/Job/SubtaskEventJob.php b/app/Job/SubtaskEventJob.php new file mode 100644 index 00000000..85c4d73e --- /dev/null +++ b/app/Job/SubtaskEventJob.php @@ -0,0 +1,48 @@ +<?php + +namespace Kanboard\Job; + +use Kanboard\EventBuilder\SubtaskEventBuilder; + +/** + * Class SubtaskEventJob + * + * @package Kanboard\Job + * @author Frederic Guillot + */ +class SubtaskEventJob extends BaseJob +{ + /** + * Set job params + * + * @param int $subtaskId + * @param string $eventName + * @param array $values + * @return $this + */ + public function withParams($subtaskId, $eventName, array $values = array()) + { + $this->jobParams = array($subtaskId, $eventName, $values); + return $this; + } + + /** + * Execute job + * + * @param int $subtaskId + * @param string $eventName + * @param array $values + * @return $this + */ + public function execute($subtaskId, $eventName, array $values = array()) + { + $event = SubtaskEventBuilder::getInstance($this->container) + ->withSubtaskId($subtaskId) + ->withValues($values) + ->buildEvent(); + + if ($event !== null) { + $this->dispatcher->dispatch($eventName, $event); + } + } +} diff --git a/app/Job/TaskEventJob.php b/app/Job/TaskEventJob.php new file mode 100644 index 00000000..7d026a68 --- /dev/null +++ b/app/Job/TaskEventJob.php @@ -0,0 +1,75 @@ +<?php + +namespace Kanboard\Job; + +use Kanboard\Event\TaskEvent; +use Kanboard\EventBuilder\TaskEventBuilder; +use Kanboard\Model\TaskModel; + +/** + * Class TaskEventJob + * + * @package Kanboard\Job + * @author Frederic Guillot + */ +class TaskEventJob extends BaseJob +{ + /** + * Set job params + * + * @param int $taskId + * @param array $eventNames + * @param array $changes + * @param array $values + * @param array $task + * @return $this + */ + public function withParams($taskId, array $eventNames, array $changes = array(), array $values = array(), array $task = array()) + { + $this->jobParams = array($taskId, $eventNames, $changes, $values, $task); + return $this; + } + + /** + * Execute job + * + * @param int $taskId + * @param array $eventNames + * @param array $changes + * @param array $values + * @param array $task + * @return $this + */ + public function execute($taskId, array $eventNames, array $changes = array(), array $values = array(), array $task = array()) + { + $event = TaskEventBuilder::getInstance($this->container) + ->withTaskId($taskId) + ->withChanges($changes) + ->withValues($values) + ->withTask($task) + ->buildEvent(); + + if ($event !== null) { + foreach ($eventNames as $eventName) { + $this->fireEvent($eventName, $event); + } + } + } + + /** + * Trigger event + * + * @access protected + * @param string $eventName + * @param TaskEvent $event + */ + protected function fireEvent($eventName, TaskEvent $event) + { + $this->logger->debug(__METHOD__.' Event fired: '.$eventName); + $this->dispatcher->dispatch($eventName, $event); + + if ($eventName === TaskModel::EVENT_CREATE) { + $this->userMentionModel->fireEvents($event['task']['description'], TaskModel::EVENT_USER_MENTION, $event); + } + } +} diff --git a/app/Job/TaskFileEventJob.php b/app/Job/TaskFileEventJob.php new file mode 100644 index 00000000..293dbf27 --- /dev/null +++ b/app/Job/TaskFileEventJob.php @@ -0,0 +1,45 @@ +<?php + +namespace Kanboard\Job; + +use Kanboard\EventBuilder\TaskFileEventBuilder; + +/** + * Class TaskFileEventJob + * + * @package Kanboard\Job + * @author Frederic Guillot + */ +class TaskFileEventJob extends BaseJob +{ + /** + * Set job params + * + * @param int $fileId + * @param string $eventName + * @return $this + */ + public function withParams($fileId, $eventName) + { + $this->jobParams = array($fileId, $eventName); + return $this; + } + + /** + * Execute job + * + * @param int $fileId + * @param string $eventName + * @return $this + */ + public function execute($fileId, $eventName) + { + $event = TaskFileEventBuilder::getInstance($this->container) + ->withFileId($fileId) + ->buildEvent(); + + if ($event !== null) { + $this->dispatcher->dispatch($eventName, $event); + } + } +} diff --git a/app/Job/TaskLinkEventJob.php b/app/Job/TaskLinkEventJob.php new file mode 100644 index 00000000..31f62f07 --- /dev/null +++ b/app/Job/TaskLinkEventJob.php @@ -0,0 +1,45 @@ +<?php + +namespace Kanboard\Job; + +use Kanboard\EventBuilder\TaskLinkEventBuilder; + +/** + * Class TaskLinkEventJob + * + * @package Kanboard\Job + * @author Frederic Guillot + */ +class TaskLinkEventJob extends BaseJob +{ + /** + * Set job params + * + * @param int $taskLinkId + * @param string $eventName + * @return $this + */ + public function withParams($taskLinkId, $eventName) + { + $this->jobParams = array($taskLinkId, $eventName); + return $this; + } + + /** + * Execute job + * + * @param int $taskLinkId + * @param string $eventName + * @return $this + */ + public function execute($taskLinkId, $eventName) + { + $event = TaskLinkEventBuilder::getInstance($this->container) + ->withTaskLinkId($taskLinkId) + ->buildEvent(); + + if ($event !== null) { + $this->dispatcher->dispatch($eventName, $event); + } + } +} diff --git a/app/Locale/bs_BA/translations.php b/app/Locale/bs_BA/translations.php index b6b0f534..898e4d66 100644 --- a/app/Locale/bs_BA/translations.php +++ b/app/Locale/bs_BA/translations.php @@ -154,8 +154,6 @@ return array( '%d closed tasks' => '%d zatvorenih zadataka', 'No task for this project' => 'Nema dodijeljenih zadataka ovom projektu', 'Public link' => 'Javni link', - 'Change assignee' => 'Promjena izvršioca', - 'Change assignee for the task "%s"' => 'Promjena izvršioca za zadatak "%s"', 'Timezone' => 'Vremenska zona', 'Sorry, I didn\'t find this information in my database!' => 'Na žalost, nije pronađena informacija u bazi', 'Page not found' => 'Strana nije pronađena', @@ -248,7 +246,6 @@ return array( 'Category' => 'Kategorija', 'Category:' => 'Kategorija:', 'Categories' => 'Kategorije', - 'Category not found.' => 'Kategorija nije pronađena', 'Your category have been created successfully.' => 'Uspješno kreirana kategorija.', 'Unable to create your category.' => 'Nije moguće kreirati kategoriju.', 'Your category have been updated successfully.' => 'Kategorija je uspješno ažurirana', @@ -270,7 +267,6 @@ return array( 'Do you really want to remove this file: "%s"?' => 'Da li da uklonim fajl: "%s"?', 'Attachments' => 'Prilozi', 'Edit the task' => 'Uredi zadatak', - 'Edit the description' => 'Uredi opis zadatka', 'Add a comment' => 'Dodaj komentar', 'Edit a comment' => 'Izmijeni komentar', 'Summary' => 'Pregled', @@ -303,7 +299,6 @@ return array( 'Display another project' => 'Prikaži drugi projekat', 'Created by %s' => 'Kreirao %s', 'Tasks Export' => 'Izvoz zadataka', - 'Tasks exportation for "%s"' => 'Izvoz zadataka za "%s"', 'Start Date' => 'Početni datum', 'End Date' => 'Datum završetka', 'Execute' => 'Izvrši', @@ -326,14 +321,9 @@ return array( 'New sub-task' => 'Novi pod-zadatak', 'New attachment added "%s"' => 'Ubačen novi prilog "%s"', 'New comment posted by %s' => '%s ostavio novi komentar', - 'New attachment' => 'Novi prilog', 'New comment' => 'Novi komentar', 'Comment updated' => 'Komentar ažuriran', 'New subtask' => 'Novi pod-zadatak', - 'Subtask updated' => 'Pod-zadatak ažuriran', - 'Task updated' => 'Zadatak ažuriran', - 'Task closed' => 'Zadatak je zatvoren', - 'Task opened' => 'Zadatak je otvoren', 'I want to receive notifications only for those projects:' => 'Želim obavještenja samo za ove projekte:', 'view the task on Kanboard' => 'Pregledaj zadatke', 'Public access' => 'Javni pristup', @@ -368,7 +358,6 @@ return array( 'No external authentication enabled.' => 'Bez omogućenih vanjskih autentikacija.', 'Password modified successfully.' => 'Uspješna izmjena šifre.', 'Unable to change the password.' => 'Nije moguće izmijeniti šifru.', - 'Change category for the task "%s"' => 'Izijmeni kategoriju zadatka "%s"', 'Change category' => 'Izmijeni kategoriju', '%s updated the task %s' => '%s izmijenio zadatak %s', '%s opened the task %s' => '%s otvorio zadatak %s', @@ -391,14 +380,12 @@ return array( '%s updated the task #%d' => '%s ažurirao zadatak #%d', '%s created the task #%d' => '%s kreirao zadatak #%d', '%s closed the task #%d' => '%s zatvorio zadatak #%d', - '%s open the task #%d' => '%s otvorio zadatak #%d', - '%s moved the task #%d to the column "%s"' => '%s premjestio zadatak #%d u kolonu "%s"', - '%s moved the task #%d to the position %d in the column "%s"' => '%s premjestio zadatak #%d na poziciju %d u koloni "%s"', + '%s opened the task #%d' => '%s otvorio zadatak #%d', 'Activity' => 'Aktivnosti', 'Default values are "%s"' => 'Podrazumijevane vrijednosti su: "%s"', 'Default columns for new projects (Comma-separated)' => 'Podrazumijevane kolone za novi projekat (Odvojene zarezom)', 'Task assignee change' => 'Promijena izvršioca zadatka', - '%s change the assignee of the task #%d to %s' => '%s zamijeni izvršioca za zadatak #%d u %s', + '%s changed the assignee of the task #%d to %s' => '%s zamijeni izvršioca za zadatak #%d u %s', '%s changed the assignee of the task %s to %s' => '%s promijenio izvršioca za zadatak %s u %s', 'New password for the user "%s"' => 'Nova šifra korisnika "%s"', 'Choose an event' => 'Izaberi događaj', @@ -447,13 +434,10 @@ return array( 'Percentage' => 'Procenat', 'Number of tasks' => 'Broj zadataka', 'Task distribution' => 'Podjela zadataka', - 'Reportings' => 'Izveštaji', - 'Task repartition for "%s"' => 'Zaduženja zadataka za "%s"', 'Analytics' => 'Analiza', 'Subtask' => 'Pod-zadatak', 'My subtasks' => 'Moji pod-zadaci', 'User repartition' => 'Zaduženja korisnika', - 'User repartition for "%s"' => 'Zaduženja korisnika za "%s"', 'Clone this project' => 'Kloniraj ovaj projekat', 'Column removed successfully.' => 'Kolona uspješno uklonjena.', 'Not enough data to show the graph.' => 'Nedovoljno podataka za prikaz na grafikonu.', @@ -470,10 +454,8 @@ return array( 'This value must be numeric' => 'Vrijednost mora biti broj', 'Unable to create this task.' => 'Nije moguće kreirati zadatak.', 'Cumulative flow diagram' => 'Zbirni dijagram toka', - 'Cumulative flow diagram for "%s"' => 'Zbirni dijagram toka za "%s"', 'Daily project summary' => 'Zbirni pregled po danima', 'Daily project summary export' => 'Izvoz zbirnog pregleda po danima', - 'Daily project summary export for "%s"' => 'Izvoz zbirnog pregleda po danima za "%s"', 'Exports' => 'Izvozi', 'This export contains the number of tasks per column grouped per day.' => 'Ovaj izvoz sadržava broj zadataka po koloni grupisanih po danima.', 'Active swimlanes' => 'Aktivne swimline trake', @@ -485,7 +467,6 @@ return array( 'Remove a swimlane' => 'Ukloni swimline traku', 'Show default swimlane' => 'Prikaži podrazumijevanu swimline traku', 'Swimlane modification for the project "%s"' => 'Izmjene swimline trake za projekat "%s"', - 'Swimlane not found.' => 'Swimline traka nije pronađena.', 'Swimlane removed successfully.' => 'Swimline traka uspješno uklonjena.', 'Swimlanes' => 'Swimline trake', 'Swimlane updated successfully.' => 'Swimline traka uspjeno ažurirana.', @@ -500,7 +481,6 @@ return array( 'Subtask Id' => 'ID pod-zadatka', 'Subtasks' => 'Pod-zadaci', 'Subtasks Export' => 'Izvoz pod-zadataka', - 'Subtasks exportation for "%s"' => 'Izvoz pod-zadataka za "%s"', 'Task Title' => 'Naslov zadatka', 'Untitled' => 'Bez naslova', 'Application default' => 'Podrazumijevano od aplikacije', @@ -607,7 +587,7 @@ return array( 'The currency rate have been added successfully.' => 'Stopa valute je uspješno dodana.', 'Unable to add this currency rate.' => 'Nemoguće dodati stopu valute.', 'Webhook URL' => 'Webhook URL', - '%s remove the assignee of the task %s' => '%s je uklonio izvršioca zadatka %s', + '%s removed the assignee of the task %s' => '%s je uklonio izvršioca zadatka %s', 'Enable Gravatar images' => 'Omogući Gravatar slike', 'Information' => 'Informacije', 'Check two factor authentication code' => 'Provjera faktor-dva autentifikacionog koda', @@ -621,7 +601,6 @@ return array( 'Test your device' => 'Testiraj svoj uređaj', 'Assign a color when the task is moved to a specific column' => 'Dodijeli boju kada je zadatak pomjeren u odabranu kolonu', '%s via Kanboard' => '%s uz pomoć Kanboard-a', - 'Burndown chart for "%s"' => 'Grafikon izgaranja za "%s"', 'Burndown chart' => 'Grafikon izgaranja', 'This chart show the task complexity over the time (Work Remaining).' => 'Ovaj grafikon pokazuje kompleksnost zadatka u vremenu (Preostalo vremena)', 'Screenshot taken %s' => 'Slika ekrana uzeta %s', @@ -686,14 +665,8 @@ return array( 'Move the task to another column when the category is changed' => 'Pomjeri zadatak u drugu kolonu kada je kategorija promijenjena', 'Send a task by email to someone' => 'Pošalji zadatak nekome emailom', 'Reopen a task' => 'Ponovo otvori zadatak', - 'Column change' => 'Promijena kolone', - 'Position change' => 'Promjena pozicije', - 'Swimlane change' => 'Promjena swimline trake', - 'Assignee change' => 'Promijenjen izvršilac', - '[%s] Overdue tasks' => '[%s] Zaostali zadaci', 'Notification' => 'Obavještenja', '%s moved the task #%d to the first swimlane' => '%s je premjestio zadatak #%d u prvu swimline traku', - '%s moved the task #%d to the swimlane "%s"' => '%s je premjestio zadatak #%d u swimline traku "%s"', 'Swimlane' => 'Swimline traka', 'Gravatar' => 'Gravatar', '%s moved the task %s to the first swimlane' => '%s je premjestio zadatak %s u prvi swimline traku', @@ -764,8 +737,6 @@ return array( 'Search by category: ' => 'Pretraga po kategoriji: ', 'Search by description: ' => 'Pretraga po opisu: ', 'Search by due date: ' => 'Pretraga po datumu završetka: ', - 'Lead and Cycle time for "%s"' => 'Vrijeme upravljanje i vremenski ciklus za "%s"', - 'Average time spent into each column for "%s"' => 'Prosjek utrošenog vremena u svakoj koloni za "%s"', 'Average time spent into each column' => 'Prosjek utrošenog vrmena u svakoj koloni', 'Average time spent' => 'Prosjek utrošenog vremena', 'This chart show the average time spent into each column for the last %d tasks.' => 'Ovaj grafik pokazuje prosjek utrošenog vremena u svakoj koloni za posljednjih %d zadataka.', @@ -806,7 +777,6 @@ return array( 'License:' => 'Licenca:', 'License' => 'Licenca', 'Enter the text below' => 'Unesi tekst ispod', - 'Gantt chart for %s' => 'Gantogram za %s', 'Sort by position' => 'Sortiraj po poziciji', 'Sort by date' => 'Sortiraj po datumu', 'Add task' => 'Dodaj zadatak', @@ -847,8 +817,6 @@ return array( 'Version' => 'Verzija', 'Plugins' => 'Dodaci', 'There is no plugin loaded.' => 'Nema učitanih dodataka.', - 'Set maximum column height' => 'Postavi maksimalnu visinu kolone', - 'Remove maximum column height' => 'Ukloni maksimalnu visinu kolone', 'My notifications' => 'Moja obavještenja', 'Custom filters' => 'Prilagođeni filteri', 'Your custom filter have been created successfully.' => 'Tvoj prilagođeni filter je uspješno napravljen.', @@ -953,7 +921,6 @@ return array( 'Invalid captcha' => 'Pogrešna captcha', 'The name must be unique' => 'Ime mora biti jedinstveno', 'View all groups' => 'Pregledaj sve grupe', - 'View group members' => 'Pregledaj članove grupe', 'There is no user available.' => 'Trenutno nema dostupnih korisnika.', 'Do you really want to remove the user "%s" from the group "%s"?' => 'Da li zaista želiš ukloniti korisnika "%s" iz grupe "%s"?', 'There is no group.' => 'Trenutno nema grupa.', @@ -974,13 +941,10 @@ return array( 'Enter group name...' => 'Unesi ime grupe...', 'Role:' => 'Uloga:', 'Project members' => 'Članovi projekta', - 'Compare hours for "%s"' => 'Poredi sate za "%s"', '%s mentioned you in the task #%d' => '%s te spomenuo u zadatku #%d', '%s mentioned you in a comment on the task #%d' => '%s te spomenuo u komentaru zadatka #%d', 'You were mentioned in the task #%d' => 'Spomenut si u zadatku #%d', 'You were mentioned in a comment on the task #%d' => 'Spomenut si u komentaru zadatka #%d', - 'Mentioned' => 'Spominjanja', - 'Compare Estimated Time vs Actual Time' => 'Poređenje očekivanog i aktuelnog vremena', 'Estimated hours: ' => 'Očekivani sati:', 'Actual hours: ' => 'Aktuelni sati:', 'Hours Spent' => 'Utrošeni sati:', @@ -1202,4 +1166,52 @@ return array( // 'Email transport' => '', // 'Webhook token' => '', // 'Imports' => '', + // 'Project tags management' => '', + // 'Tag created successfully.' => '', + // 'Unable to create this tag.' => '', + // 'Tag updated successfully.' => '', + // 'Unable to update this tag.' => '', + // 'Tag removed successfully.' => '', + // 'Unable to remove this tag.' => '', + // 'Global tags management' => '', + // 'Tags' => '', + // 'Tags management' => '', + // 'Add new tag' => '', + // 'Edit a tag' => '', + // 'Project tags' => '', + // 'There is no specific tag for this project at the moment.' => '', + // 'Tag' => '', + // 'Remove a tag' => '', + // 'Do you really want to remove this tag: "%s"?' => '', + // 'Global tags' => '', + // 'There is no global tag at the moment.' => '', + // 'This field cannot be empty' => '', + // 'Close a task when there is no activity in an specific column' => '', + // '%s removed a subtask for the task #%d' => '', + // '%s removed a comment on the task #%d' => '', + // 'Comment removed on task #%d' => '', + // 'Subtask removed on task #%d' => '', + // 'Hide tasks in this column in the dashboard' => '', + // '%s removed a comment on the task %s' => '', + // '%s removed a subtask for the task %s' => '', + // 'Comment removed' => '', + // 'Subtask removed' => '', + // '%s set a new internal link for the task #%d' => '', + // '%s removed an internal link for the task #%d' => '', + // 'A new internal link for the task #%d have been defined' => '', + // 'Internal link removed for the task #%d' => '', + // '%s set a new internal link for the task %s' => '', + // '%s removed an internal link for the task %s' => '', + // 'Automatically set the due date on task creation' => '', + // 'Move the task to another column when closed' => '', + // 'Move the task to another column when not moved during a given period' => '', + // 'Dashboard for %s' => '', + // 'Tasks overview for %s' => '', + // 'Subtasks overview for %s' => '', + // 'Projects overview for %s' => '', + // 'Activity stream for %s' => '', + // 'Calendar for %s' => '', + // 'Notifications for %s' => '', + // 'Subtasks export' => '', + // 'Tasks exportation' => '', ); diff --git a/app/Locale/cs_CZ/translations.php b/app/Locale/cs_CZ/translations.php index f24c79ab..565f92f5 100644 --- a/app/Locale/cs_CZ/translations.php +++ b/app/Locale/cs_CZ/translations.php @@ -100,7 +100,7 @@ return array( 'There is nobody assigned' => 'Není přiřazeno žádnému uživateli', 'Column on the board:' => 'Sloupec:', 'Close this task' => 'Uzavřít úkol', - 'Open this task' => 'Aufgabe wieder öffnen', + 'Open this task' => 'Otevřít tento úkol', 'There is no description.' => 'Bez popisu', 'Add a new task' => 'Přidat nový úkol', 'The username is required' => 'Uživatelské jméno je vyžadováno', @@ -154,8 +154,6 @@ return array( '%d closed tasks' => '%d dokončených úkolů', 'No task for this project' => 'Tento projekt nemá žádné úkoly', 'Public link' => 'Veřejný odkaz', - 'Change assignee' => 'Změna přiřazení k uživatelům', - 'Change assignee for the task "%s"' => 'Změna přiřazení uživatele pro úkol "%s"', 'Timezone' => 'Časová zóna', 'Sorry, I didn\'t find this information in my database!' => 'Omlouváme se, tuto informaci nelze nalézt!', 'Page not found' => 'Stránka nenalezena', @@ -248,7 +246,6 @@ return array( 'Category' => 'Kategorie', 'Category:' => 'Kategorie:', 'Categories' => 'Kategorie', - 'Category not found.' => 'Kategorie není nalezena.', 'Your category have been created successfully.' => 'Kategorie byla úspěšně vytvořena.', 'Unable to create your category.' => 'Kategorii nelze vytvořit.', 'Your category have been updated successfully.' => 'Kategorie byla úspěšně aktualizována.', @@ -270,7 +267,6 @@ return array( 'Do you really want to remove this file: "%s"?' => 'Skutečně chcete odebrat soubor: "%s"?', 'Attachments' => 'Přílohy', 'Edit the task' => 'Upravit úkol', - 'Edit the description' => 'Upravit popis', 'Add a comment' => 'Přidat komentář', 'Edit a comment' => 'Upravit komentář', 'Summary' => 'Souhrn', @@ -303,7 +299,6 @@ return array( 'Display another project' => 'Zobrazit jiný projekt', 'Created by %s' => 'Vytvořeno uživatelem %s', 'Tasks Export' => 'Export úkolů', - 'Tasks exportation for "%s"' => 'Export úkolů pro "%s"', 'Start Date' => 'Počáteční datum', 'End Date' => 'Konečné datum', 'Execute' => 'Spustit', @@ -326,14 +321,9 @@ return array( 'New sub-task' => 'Nový dílčí úkol', 'New attachment added "%s"' => 'Byla přidána nová příloha "%s".', 'New comment posted by %s' => 'Nový komentář publikovaný uživatelem %s', - 'New attachment' => 'Nová příloha', 'New comment' => 'Nový komentář', 'Comment updated' => 'Komentář byl aktualizován.', 'New subtask' => 'Nový dílčí úkol', - 'Subtask updated' => 'Dílčí úkol byl aktualizován', - 'Task updated' => 'Úkol byl aktualizován', - 'Task closed' => 'Úkol byl uzavřen', - 'Task opened' => 'Úkol byl otevřen', 'I want to receive notifications only for those projects:' => 'Přeji si dostávat upozornění pouze pro následující projekty:', 'view the task on Kanboard' => 'Zobrazit úkol na Kanboard', 'Public access' => 'Veřejný přístup', @@ -368,7 +358,6 @@ return array( 'No external authentication enabled.' => 'Není povolena žádná vzdálená autorizace.', 'Password modified successfully.' => 'Heslo bylo úspěšně změněno.', 'Unable to change the password.' => 'Nelze změnit heslo.', - 'Change category for the task "%s"' => 'Změna kategorie pro úkol "%s" ', 'Change category' => 'Změna kategorie', '%s updated the task %s' => '%s aktualizoval úkol %s ', '%s opened the task %s' => '%s znovu otevřel úkol %s ', @@ -391,15 +380,13 @@ return array( '%s updated the task #%d' => '%s aktualizoval úkol #%d ', '%s created the task #%d' => '%s vytvořil úkol #%d ', '%s closed the task #%d' => '%s uzavřel úkol #%d ', - '%s open the task #%d' => '%s znovu otevřel úkol #%d ', - '%s moved the task #%d to the column "%s"' => '%s přesunul úkol #%d do sloupce "%s" ', - '%s moved the task #%d to the position %d in the column "%s"' => '%s přesunul úkol #%d na pozici %d ve sloupci "%s" ', + '%s opened the task #%d' => '%s znovu otevřel úkol #%d ', 'Activity' => 'Aktivity', 'Default values are "%s"' => 'Standardní hodnoty jsou: "%s"', 'Default columns for new projects (Comma-separated)' => 'Výchozí sloupce pro nové projekty (odděleny čárkou)', 'Task assignee change' => 'Změna přiřazení uživatelů', - '%s change the assignee of the task #%d to %s' => '%s hat die Zusständigkeit der Aufgabe #%d geändert um %s', - '%s changed the assignee of the task %s to %s' => '%s změnil řešitele úkolu %s na uživatele %s', + '%s changed the assignee of the task #%d to %s' => '%s změnil přidělení úkolu #%d na uživatele %s', + '%s changed the assignee of the task %s to %s' => '%s změnil přidělení úkolu %s na uživatele %s', 'New password for the user "%s"' => 'Nové heslo pro uživatele "%s"', 'Choose an event' => 'Vybrat událost', 'Create a task from an external provider' => 'Vytvořit úkol externím poskytovatelem', @@ -447,13 +434,10 @@ return array( 'Percentage' => 'Procenta', 'Number of tasks' => 'Počet úkolů', 'Task distribution' => 'Rozdělení úkolů', - 'Reportings' => 'Reporty', - 'Task repartition for "%s"' => 'Rozdělení úkolů pro "%s"', 'Analytics' => 'Analýza', 'Subtask' => 'Dílčí úkoly', 'My subtasks' => 'Moje dílčí úkoly', 'User repartition' => 'Rozdělení podle uživatelů', - 'User repartition for "%s"' => 'Rozdělení podle uživatelů pro "%s"', 'Clone this project' => 'Duplokovat projekt', 'Column removed successfully.' => 'Sloupec byl odstraněn.', 'Not enough data to show the graph.' => 'Pro zobrazení grafu není dostatek dat.', @@ -462,45 +446,41 @@ return array( 'The project id must be an integer' => 'ID projektu musí být celé číslo', 'The status must be an integer' => 'Status musí být celé číslo', 'The subtask id is required' => 'Je požadováno id dílčího úkolu', - 'The subtask id must be an integer' => 'Die Teilaufgabenid muss eine ganze Zahl sein', - 'The task id is required' => 'Die Aufgabenid ist benötigt', - 'The task id must be an integer' => 'Die Aufgabenid muss eine ganze Zahl sein', - 'The user id must be an integer' => 'Die Userid muss eine ganze Zahl sein', - 'This value is required' => 'Dieser Wert ist erforderlich', - 'This value must be numeric' => 'Dieser Wert muss numerisch sein', - 'Unable to create this task.' => 'Diese Aufgabe kann nicht erstellt werden', + 'The subtask id must be an integer' => 'ID dílčího úkolu musí být číslo', + 'The task id is required' => 'ID úkolu je povinné', + 'The task id must be an integer' => 'ID úkolu musí být číslo', + 'The user id must be an integer' => 'ID uživatele musí být číslo', + 'This value is required' => 'Hodnota je povinná', + 'This value must be numeric' => 'Hodnota musí být číselná', + 'Unable to create this task.' => 'Nelze vytvořit tento úkol', 'Cumulative flow diagram' => 'Kumulativní diagram', - 'Cumulative flow diagram for "%s"' => 'Kumulativní diagram pro "%s"', 'Daily project summary' => 'Denní přehledy', 'Daily project summary export' => 'Export denních přehledů', - 'Daily project summary export for "%s"' => 'Export denních přehledů pro "%s"', 'Exports' => 'Exporty', 'This export contains the number of tasks per column grouped per day.' => 'Tento export obsahuje počet úkolů pro jednotlivé sloupce seskupených podle dní.', - 'Active swimlanes' => 'Aktive Swimlane', - 'Add a new swimlane' => 'Přidat nový řádek', - 'Change default swimlane' => 'Standard Swimlane ändern', - 'Default swimlane' => 'Výchozí Swimlane', - 'Do you really want to remove this swimlane: "%s"?' => 'Diese Swimlane wirklich ändern: "%s"?', - 'Inactive swimlanes' => 'Inaktive Swimlane', - 'Remove a swimlane' => 'Odstranit swimlane', - 'Show default swimlane' => 'Standard Swimlane anzeigen', - 'Swimlane modification for the project "%s"' => 'Swimlane Änderung für das Projekt "%s"', - 'Swimlane not found.' => 'Swimlane nicht gefunden', - 'Swimlane removed successfully.' => 'Swimlane erfolgreich entfernt.', - 'Swimlanes' => 'Swimlanes', - 'Swimlane updated successfully.' => 'Swimlane erfolgreich geändert.', - 'The default swimlane have been updated successfully.' => 'Die standard Swimlane wurden erfolgreich aktualisiert. Die standard Swimlane wurden erfolgreich aktualisiert.', - 'Unable to remove this swimlane.' => 'Es ist nicht möglich die Swimlane zu entfernen.', - 'Unable to update this swimlane.' => 'Es ist nicht möglich die Swimöane zu ändern.', - 'Your swimlane have been created successfully.' => 'Die Swimlane wurde erfolgreich angelegt.', - 'Example: "Bug, Feature Request, Improvement"' => 'Beispiel: "Bug, Funktionswünsche, Verbesserung"', + 'Active swimlanes' => 'Aktivní dráhy', + 'Add a new swimlane' => 'Přidat novou dráhu', + 'Change default swimlane' => 'Změnit výchozí dráhu', + 'Default swimlane' => 'Výchozí dráha', + 'Do you really want to remove this swimlane: "%s"?' => 'Opravdu si přejete odstranit tuto dráhu: "%s"?', + 'Inactive swimlanes' => 'Neaktivní dráha', + 'Remove a swimlane' => 'Odstranit dráhu', + 'Show default swimlane' => 'Zobrazit výchozí dráhu', + 'Swimlane modification for the project "%s"' => 'Změny dráhy pro projekt "%s"', + 'Swimlane removed successfully.' => 'Dráha byla odstraněna.', + 'Swimlanes' => 'Dráhy', + 'Swimlane updated successfully.' => 'Dráha byla upravena.', + 'The default swimlane have been updated successfully.' => 'Výchozí dráha byla upravena', + 'Unable to remove this swimlane.' => 'Tuto dráhu nelze odstranit.', + 'Unable to update this swimlane.' => 'Tuto dráhu nelze upravit.', + 'Your swimlane have been created successfully.' => 'Dráha byla vytvořena.', + 'Example: "Bug, Feature Request, Improvement"' => 'Například: "Chyba", "Nápad", "Požadavek"...', 'Default categories for new projects (Comma-separated)' => 'Výchozí kategorie pro nové projekty (oddělené čárkou)', 'Integrations' => 'Integrace', - 'Integration with third-party services' => 'Integration von Fremdleistungen', + 'Integration with third-party services' => 'Integrace se službami třetích stran', 'Subtask Id' => 'Dílčí úkol Id', 'Subtasks' => 'Dílčí úkoly', 'Subtasks Export' => 'Export dílčích úkolů', - 'Subtasks exportation for "%s"' => 'Export dílčích úkolů pro "%s"', 'Task Title' => 'Název úkolu', 'Untitled' => 'bez názvu', 'Application default' => 'Standardní hodnoty', @@ -516,7 +496,7 @@ return array( 'User dashboard' => 'Nástěnka uživatele', 'Allow only one subtask in progress at the same time for a user' => 'Umožnit uživateli práci pouze na jednom dílčím úkolu ve stejném čase', 'Edit column "%s"' => 'Upravit sloupec "%s" ', - 'Select the new status of the subtask: "%s"' => 'Wähle einen neuen Status für Teilaufgabe: "%s"', + 'Select the new status of the subtask: "%s"' => 'Vyberte nový stav pro podúkol: "%s"', 'Subtask timesheet' => 'Časový rozvrh dílčích úkolů', 'There is nothing to show.' => 'Žádná položka k zobrazení', 'Time Tracking' => 'Sledování času', @@ -529,9 +509,9 @@ return array( 'Days in this column' => 'Dní v tomto sloupci', '%dd' => '%d d', 'Add a new link' => 'Přidat nový odkaz', - 'Do you really want to remove this link: "%s"?' => 'Die Verbindung "%s" wirklich löschen?', - 'Do you really want to remove this link with task #%d?' => 'Die Verbindung mit der Aufgabe #%d wirklich löschen?', - 'Field required' => 'Feld erforderlich', + 'Do you really want to remove this link: "%s"?' => 'Opravdu chcete odstranit odkaz "%s"?', + 'Do you really want to remove this link with task #%d?' => 'Opravdu chcete odstranit odkaz na úkol #%d ?', + 'Field required' => 'Povinné pole', 'Link added successfully.' => 'Propojení bylo úspěšně přidáno.', 'Link updated successfully.' => 'Propojení bylo úspěšně aktualizováno.', 'Link removed successfully.' => 'Propojení bylo úspěšně odebráno.', @@ -555,8 +535,8 @@ return array( 'is duplicated by' => 'je duplikován', 'is a child of' => 'je podřízený', 'is a parent of' => 'je nadřízený', - 'targets milestone' => 'targets milestone', - 'is a milestone of' => 'is a milestone of', + 'targets milestone' => 'patří k milníku', + 'is a milestone of' => 'je milníkem', 'fixes' => 'nahrazuje', 'is fixed by' => 'je nahrazen', 'This task' => 'Tento úkol', @@ -598,7 +578,7 @@ return array( 'Time spent in the column' => 'Trvání jednotlivých etap', 'Task transitions' => 'Přesuny úkolů', 'Task transitions export' => 'Export přesunů mezi sloupci', - 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => 'Diese Auswertung enthält alle Spaltenbewegungen für jede Aufgabe mit Datum, Benutzer und Zeit vor jedem Wechsel.', + 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => 'Tento seznam obsahuje všechny pohyby úkolů s daty, uživateli a časy strávenými na úkolu.', 'Currency rates' => 'Aktuální kurzy', 'Rate' => 'Kurz', 'Change reference currency' => 'Změnit referenční měnu', @@ -607,31 +587,30 @@ return array( 'The currency rate have been added successfully.' => 'Směnný kurz byl úspěšně přidán.', 'Unable to add this currency rate.' => 'Nelze přidat tento směnný kurz', 'Webhook URL' => 'Webhook URL', - '%s remove the assignee of the task %s' => '%s odstranil přiřazení úkolu %s ', + '%s removed the assignee of the task %s' => '%s odstranil přiřazení úkolu %s ', 'Enable Gravatar images' => 'Aktiviere Gravatar Bilder', 'Information' => 'Informace', - 'Check two factor authentication code' => 'Prüfe Zwei-Faktor-Authentifizierungscode', - 'The two factor authentication code is not valid.' => 'Der Zwei-Faktor-Authentifizierungscode ist ungültig.', - 'The two factor authentication code is valid.' => 'Der Zwei-Faktor-Authentifizierungscode ist gültig.', - 'Code' => 'Code', + 'Check two factor authentication code' => 'Zkontrolujte dvouúrovňový autentifikační klíč', + 'The two factor authentication code is not valid.' => 'Dvouúrovňový autentifikační klíč není platný.', + 'The two factor authentication code is valid.' => 'Dvouúrovňový autentifikační klíč je platný.', + 'Code' => 'Klíč', 'Two factor authentication' => 'Dvouúrovňová autorizace', - 'This QR code contains the key URI: ' => 'Dieser QR-Code beinhaltet die Schlüssel-URI', + 'This QR code contains the key URI: ' => 'Tento QR kód obsahuje adresu s klíčem', 'Check my code' => 'Kontrola mého kódu', 'Secret key: ' => 'Tajný klíč', 'Test your device' => 'Test Vašeho zařízení', 'Assign a color when the task is moved to a specific column' => 'Přiřadit barvu, když je úkol přesunut do konkrétního sloupce', '%s via Kanboard' => '%s via Kanboard', - 'Burndown chart for "%s"' => 'Burndown-Chart für "%s"', 'Burndown chart' => 'Burndown-Chart', 'This chart show the task complexity over the time (Work Remaining).' => 'Graf zobrazuje složitost úkolů v čase (Zbývající práce).', 'Screenshot taken %s' => 'Screenshot aufgenommen %s ', 'Add a screenshot' => 'Přidat snímek obrazovky', - 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Nimm einen Screenshot auf und drücke STRG+V oder ⌘+V um ihn hier einzufügen.', - 'Screenshot uploaded successfully.' => 'Screenshot erfolgreich hochgeladen.', + 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Pořiďte snímek obrazovky a v tomto poli stiskněte Ctrl+V nebo ⌘+V ', + 'Screenshot uploaded successfully.' => 'Snímek obrazovky byl úspěšně nahrán.', 'SEK - Swedish Krona' => 'SEK - Schwedische Kronen', - 'Identifier' => 'Identifikator', - 'Disable two factor authentication' => 'Zrušit dvou stupňovou autorizaci', - 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Willst du wirklich für folgenden Nutzer die Zwei-Faktor-Authentifizierung deaktivieren: "%s"?', + 'Identifier' => 'Identifikátor', + 'Disable two factor authentication' => 'Zrušit dvouúrovňovou autorizaci', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Opravdu chcete vypnout dvouúrovňovou autentifikaci pro uživatele: "%s"?', // 'Edit link' => '', // 'Start to type task title...' => '', // 'A task cannot be linked to itself' => '', @@ -686,27 +665,21 @@ return array( 'Move the task to another column when the category is changed' => 'Přesun úkolu do jiného sloupce když je změněna kategorie', 'Send a task by email to someone' => 'Poslat někomu úkol poštou', 'Reopen a task' => 'Znovu otevřít úkol', - 'Column change' => 'Spalte geändert', - 'Position change' => 'Position geändert', - 'Swimlane change' => 'Swimlane geändert', - 'Assignee change' => 'Zuordnung geändert', - '[%s] Overdue tasks' => '[%s] überfallige Aufgaben', - 'Notification' => 'Benachrichtigungen', - '%s moved the task #%d to the first swimlane' => '%s hat die Aufgabe #%d in die erste Swimlane verschoben', - '%s moved the task #%d to the swimlane "%s"' => '%s hat die Aufgabe #%d in die Swimlane "%s" verschoben', + 'Notification' => 'Upozornění', + '%s moved the task #%d to the first swimlane' => '%s přesunul úkol #%d do první dráhy', // 'Swimlane' => '', // 'Gravatar' => '', - '%s moved the task %s to the first swimlane' => '%s hat die Aufgabe %s in die erste Swimlane verschoben', - '%s moved the task %s to the swimlane "%s"' => '%s hat die Aufgaben %s in die Swimlane "%s" verschoben', + '%s moved the task %s to the first swimlane' => '%s přesunul úkol %s do první dráhy', + '%s moved the task %s to the swimlane "%s"' => '%s přesunul úkol %s do dráhy "%s"', 'This report contains all subtasks information for the given date range.' => 'Report obsahuje všechny informace o dílčích úkolech pro daný časový úsek', 'This report contains all tasks information for the given date range.' => 'Report obsahuje informace o všech úkolech pro daný časový úsek.', 'Project activities for %s' => 'Aktivity projektu %s', - 'view the board on Kanboard' => 'Pinnwand in Kanboard anzeigen', - 'The task have been moved to the first swimlane' => 'Die Aufgabe wurde in die erste Swimlane verschoben', - 'The task have been moved to another swimlane:' => 'Die Aufgaben wurde in ene andere Swimlane verschoben', - 'New title: %s' => 'Neuer Titel: %s', - 'The task is not assigned anymore' => 'Die Aufgabe ist nicht mehr zugewiesen', - 'New assignee: %s' => 'Neue Zuordnung: %s', + 'view the board on Kanboard' => 'Zobrazit nástěnku', + 'The task have been moved to the first swimlane' => 'Úkol byl přesunut do první dráhy', + 'The task have been moved to another swimlane:' => 'Úkol byl přesunut do další dráhy', + 'New title: %s' => 'Nový název: %s', + 'The task is not assigned anymore' => 'Úkol již není přidělen', + 'New assignee: %s' => 'přidělení: %s', 'There is no category now' => 'Nyní neexistuje žádná kategorie', 'New category: %s' => 'Nová kategorie: %s', 'New color: %s' => 'Nová barva: %s', @@ -714,11 +687,11 @@ return array( 'The due date have been removed' => 'Datum dokončení byl odstraněn', 'There is no description anymore' => 'Ještě neexistuje žádný popis', 'Recurrence settings have been modified' => 'Nastavení opakování bylo změněno', - 'Time spent changed: %sh' => 'Verbrauchte Zeit geändert: %sh', - 'Time estimated changed: %sh' => 'Geschätzte Zeit geändert: %sh', - 'The field "%s" have been updated' => 'Das Feld "%s" wurde verändert', - 'The description has been modified:' => 'Die Beschreibung wurde geändert:', - 'Do you really want to close the task "%s" as well as all subtasks?' => 'Soll die Aufgabe "%s" wirklich geschlossen werden? (einschließlich Teilaufgaben)', + 'Time spent changed: %sh' => 'Strávený čas se změnil: %sh', + 'Time estimated changed: %sh' => 'Odhadovaný čas se změnil: %sh', + 'The field "%s" have been updated' => 'Sloupec "%s" byl upraven', + 'The description has been modified:' => 'Popis byl upraven:', + 'Do you really want to close the task "%s" as well as all subtasks?' => 'Opravdu si přejete úkol "%s" uzavřít? (včetně podúkolů)', 'I want to receive notifications for:' => 'Chci dostávat upozornění na:', 'All tasks' => 'Všechny úkoly', 'Only for tasks assigned to me' => 'pouze pro moje úkoly', @@ -764,8 +737,6 @@ return array( 'Search by category: ' => 'Hledat podle kategorie: ', 'Search by description: ' => 'Hledat podle popisu: ', 'Search by due date: ' => 'Hledat podle termínu: ', - 'Lead and Cycle time for "%s"' => 'Dodací lhůta a doba cyklu pro "%s"', - 'Average time spent into each column for "%s"' => 'Průměrná doba strávená v každé fázi pro "%s"', 'Average time spent into each column' => 'Průměrná doba strávená v každé fázi', 'Average time spent' => 'Průměrná strávená doba', // 'This chart show the average time spent into each column for the last %d tasks.' => '', @@ -806,7 +777,6 @@ return array( 'License:' => 'Licence:', 'License' => 'Licence', 'Enter the text below' => 'Zadejte text níže', - 'Gantt chart for %s' => 'Gantt graf pro %s', 'Sort by position' => 'Třídit podle pozice', 'Sort by date' => 'Třídit podle datumu', 'Add task' => 'Přidat úkol', @@ -847,8 +817,6 @@ return array( // 'Version' => '', // 'Plugins' => '', // 'There is no plugin loaded.' => '', - // 'Set maximum column height' => '', - // 'Remove maximum column height' => '', // 'My notifications' => '', // 'Custom filters' => '', // 'Your custom filter have been created successfully.' => '', @@ -953,7 +921,6 @@ return array( // 'Invalid captcha' => '', // 'The name must be unique' => '', // 'View all groups' => '', - // 'View group members' => '', // 'There is no user available.' => '', // 'Do you really want to remove the user "%s" from the group "%s"?' => '', // 'There is no group.' => '', @@ -974,13 +941,10 @@ return array( // 'Enter group name...' => '', // 'Role:' => '', // 'Project members' => '', - // 'Compare hours for "%s"' => '', // '%s mentioned you in the task #%d' => '', // '%s mentioned you in a comment on the task #%d' => '', // 'You were mentioned in the task #%d' => '', // 'You were mentioned in a comment on the task #%d' => '', - // 'Mentioned' => '', - // 'Compare Estimated Time vs Actual Time' => '', // 'Estimated hours: ' => '', // 'Actual hours: ' => '', // 'Hours Spent' => '', @@ -1202,4 +1166,52 @@ return array( // 'Email transport' => '', // 'Webhook token' => '', // 'Imports' => '', + // 'Project tags management' => '', + // 'Tag created successfully.' => '', + // 'Unable to create this tag.' => '', + // 'Tag updated successfully.' => '', + // 'Unable to update this tag.' => '', + // 'Tag removed successfully.' => '', + // 'Unable to remove this tag.' => '', + // 'Global tags management' => '', + // 'Tags' => '', + // 'Tags management' => '', + // 'Add new tag' => '', + // 'Edit a tag' => '', + // 'Project tags' => '', + // 'There is no specific tag for this project at the moment.' => '', + // 'Tag' => '', + // 'Remove a tag' => '', + // 'Do you really want to remove this tag: "%s"?' => '', + // 'Global tags' => '', + // 'There is no global tag at the moment.' => '', + // 'This field cannot be empty' => '', + // 'Close a task when there is no activity in an specific column' => '', + // '%s removed a subtask for the task #%d' => '', + // '%s removed a comment on the task #%d' => '', + // 'Comment removed on task #%d' => '', + // 'Subtask removed on task #%d' => '', + // 'Hide tasks in this column in the dashboard' => '', + // '%s removed a comment on the task %s' => '', + // '%s removed a subtask for the task %s' => '', + // 'Comment removed' => '', + // 'Subtask removed' => '', + // '%s set a new internal link for the task #%d' => '', + // '%s removed an internal link for the task #%d' => '', + // 'A new internal link for the task #%d have been defined' => '', + // 'Internal link removed for the task #%d' => '', + // '%s set a new internal link for the task %s' => '', + // '%s removed an internal link for the task %s' => '', + // 'Automatically set the due date on task creation' => '', + // 'Move the task to another column when closed' => '', + // 'Move the task to another column when not moved during a given period' => '', + // 'Dashboard for %s' => '', + // 'Tasks overview for %s' => '', + // 'Subtasks overview for %s' => '', + // 'Projects overview for %s' => '', + // 'Activity stream for %s' => '', + // 'Calendar for %s' => '', + // 'Notifications for %s' => '', + // 'Subtasks export' => '', + // 'Tasks exportation' => '', ); diff --git a/app/Locale/da_DK/translations.php b/app/Locale/da_DK/translations.php index 280ee997..0ff4253e 100644 --- a/app/Locale/da_DK/translations.php +++ b/app/Locale/da_DK/translations.php @@ -154,8 +154,6 @@ return array( '%d closed tasks' => '%d lukket opgavet', 'No task for this project' => 'Ingen opgaver i dette projekt', 'Public link' => 'Offentligt link', - 'Change assignee' => 'Ændre ansvarlig', - 'Change assignee for the task "%s"' => 'Ændre ansvarlig for opgaven: "%s"', 'Timezone' => 'Tidszone', 'Sorry, I didn\'t find this information in my database!' => 'Denne information kunne ikke findes i databasen!', 'Page not found' => 'Siden er ikke fundet', @@ -248,7 +246,6 @@ return array( 'Category' => 'Kategori', 'Category:' => 'Kategori:', 'Categories' => 'Kategorier', - 'Category not found.' => 'Kategori ikke fundet.', 'Your category have been created successfully.' => 'Kategorien er oprettet.', 'Unable to create your category.' => 'Kategorien kunne ikke oprettes.', 'Your category have been updated successfully.' => 'Kategorien er opdateret.', @@ -270,7 +267,6 @@ return array( 'Do you really want to remove this file: "%s"?' => 'Vil du virkelig fjerne denne fil: "%s"?', 'Attachments' => 'Vedhæftninger', 'Edit the task' => 'Rediger opgaven', - 'Edit the description' => 'Rediger beskrivelsen', 'Add a comment' => 'Tilføj en kommentar', 'Edit a comment' => 'Rediger en kommentar', 'Summary' => 'Resumé', @@ -303,7 +299,6 @@ return array( 'Display another project' => 'Vis et andet projekt...', 'Created by %s' => 'Oprettet af %s', 'Tasks Export' => 'Opgave eksport', - 'Tasks exportation for "%s"' => 'Opgave eksport for "%s"', 'Start Date' => 'Start-dato', 'End Date' => 'Slut-dato', 'Execute' => 'Udfør', @@ -326,14 +321,9 @@ return array( 'New sub-task' => 'Ny under-opgave', 'New attachment added "%s"' => 'Ny vedhæftning tilføjet "%s"', 'New comment posted by %s' => 'Ny kommentar af %s', - // 'New attachment' => '', // 'New comment' => '', 'Comment updated' => 'Kommentar opdateret', // 'New subtask' => '', - // 'Subtask updated' => '', - // 'Task updated' => '', - // 'Task closed' => '', - // 'Task opened' => '', 'I want to receive notifications only for those projects:' => 'Jeg vil kun have notifikationer for disse projekter:', 'view the task on Kanboard' => 'se opgaven på Kanboard', 'Public access' => 'Offentlig adgang', @@ -368,7 +358,6 @@ return array( 'No external authentication enabled.' => 'Ingen eksterne autentificering aktiveret.', 'Password modified successfully.' => 'Adgangskode ændret.', 'Unable to change the password.' => 'Adgangskoden kunne ikke ændres.', - 'Change category for the task "%s"' => 'Skift kategori for opgaven "%s"', 'Change category' => 'Skift kategori', '%s updated the task %s' => '%s opdatert opgaven %s', '%s opened the task %s' => '%s åben opgaven %s', @@ -391,14 +380,12 @@ return array( '%s updated the task #%d' => '%s opdaterede opgaven #%d', '%s created the task #%d' => '%s oprettede opgaven #%d', '%s closed the task #%d' => '%s lukkede opgaven #%d', - '%s open the task #%d' => '%s åbnede opgaven #%d', - '%s moved the task #%d to the column "%s"' => '%s flyttede opgaven #%d til kolonnen "%s"', - '%s moved the task #%d to the position %d in the column "%s"' => '%s flyttede opgaven #%d til position %d i kolonnen "%s"', + '%s opened the task #%d' => '%s åbnede opgaven #%d', 'Activity' => 'Aktivitet', 'Default values are "%s"' => 'Standard værdier er "%s"', 'Default columns for new projects (Comma-separated)' => 'Standard kolonne for nye projekter (kommasepareret)', 'Task assignee change' => 'Opgaven ansvarlig ændring', - '%s change the assignee of the task #%d to %s' => '%s skrift ansvarlig for opgaven #%d til %s', + '%s changed the assignee of the task #%d to %s' => '%s skrift ansvarlig for opgaven #%d til %s', '%s changed the assignee of the task %s to %s' => '%s skift ansvarlig for opgaven %s til %s', 'New password for the user "%s"' => 'Ny adgangskode for brugeren "%s"', 'Choose an event' => 'Vælg et event', @@ -447,13 +434,10 @@ return array( // 'Percentage' => '', // 'Number of tasks' => '', // 'Task distribution' => '', - // 'Reportings' => '', - // 'Task repartition for "%s"' => '', // 'Analytics' => '', // 'Subtask' => '', // 'My subtasks' => '', // 'User repartition' => '', - // 'User repartition for "%s"' => '', // 'Clone this project' => '', // 'Column removed successfully.' => '', // 'Not enough data to show the graph.' => '', @@ -470,10 +454,8 @@ return array( // 'This value must be numeric' => '', // 'Unable to create this task.' => '', // 'Cumulative flow diagram' => '', - // 'Cumulative flow diagram for "%s"' => '', // 'Daily project summary' => '', // 'Daily project summary export' => '', - // 'Daily project summary export for "%s"' => '', // 'Exports' => '', // 'This export contains the number of tasks per column grouped per day.' => '', // 'Active swimlanes' => '', @@ -485,7 +467,6 @@ return array( // 'Remove a swimlane' => '', // 'Show default swimlane' => '', // 'Swimlane modification for the project "%s"' => '', - // 'Swimlane not found.' => '', // 'Swimlane removed successfully.' => '', // 'Swimlanes' => '', // 'Swimlane updated successfully.' => '', @@ -500,7 +481,6 @@ return array( // 'Subtask Id' => '', // 'Subtasks' => '', // 'Subtasks Export' => '', - // 'Subtasks exportation for "%s"' => '', // 'Task Title' => '', // 'Untitled' => '', // 'Application default' => '', @@ -607,7 +587,7 @@ return array( // 'The currency rate have been added successfully.' => '', // 'Unable to add this currency rate.' => '', // 'Webhook URL' => '', - // '%s remove the assignee of the task %s' => '', + // '%s removed the assignee of the task %s' => '', // 'Enable Gravatar images' => '', // 'Information' => '', // 'Check two factor authentication code' => '', @@ -621,7 +601,6 @@ return array( // 'Test your device' => '', // 'Assign a color when the task is moved to a specific column' => '', // '%s via Kanboard' => '', - // 'Burndown chart for "%s"' => '', // 'Burndown chart' => '', // 'This chart show the task complexity over the time (Work Remaining).' => '', // 'Screenshot taken %s' => '', @@ -686,14 +665,8 @@ return array( // 'Move the task to another column when the category is changed' => '', // 'Send a task by email to someone' => '', // 'Reopen a task' => '', - // 'Column change' => '', - // 'Position change' => '', - // 'Swimlane change' => '', - // 'Assignee change' => '', - // '[%s] Overdue tasks' => '', // 'Notification' => '', // '%s moved the task #%d to the first swimlane' => '', - // '%s moved the task #%d to the swimlane "%s"' => '', // 'Swimlane' => '', // 'Gravatar' => '', // '%s moved the task %s to the first swimlane' => '', @@ -764,8 +737,6 @@ return array( // 'Search by category: ' => '', // 'Search by description: ' => '', // 'Search by due date: ' => '', - // 'Lead and Cycle time for "%s"' => '', - // 'Average time spent into each column for "%s"' => '', // 'Average time spent into each column' => '', // 'Average time spent' => '', // 'This chart show the average time spent into each column for the last %d tasks.' => '', @@ -806,7 +777,6 @@ return array( // 'License:' => '', // 'License' => '', // 'Enter the text below' => '', - // 'Gantt chart for %s' => '', // 'Sort by position' => '', // 'Sort by date' => '', // 'Add task' => '', @@ -847,8 +817,6 @@ return array( // 'Version' => '', // 'Plugins' => '', // 'There is no plugin loaded.' => '', - // 'Set maximum column height' => '', - // 'Remove maximum column height' => '', // 'My notifications' => '', // 'Custom filters' => '', // 'Your custom filter have been created successfully.' => '', @@ -953,7 +921,6 @@ return array( // 'Invalid captcha' => '', // 'The name must be unique' => '', // 'View all groups' => '', - // 'View group members' => '', // 'There is no user available.' => '', // 'Do you really want to remove the user "%s" from the group "%s"?' => '', // 'There is no group.' => '', @@ -974,13 +941,10 @@ return array( // 'Enter group name...' => '', // 'Role:' => '', // 'Project members' => '', - // 'Compare hours for "%s"' => '', // '%s mentioned you in the task #%d' => '', // '%s mentioned you in a comment on the task #%d' => '', // 'You were mentioned in the task #%d' => '', // 'You were mentioned in a comment on the task #%d' => '', - // 'Mentioned' => '', - // 'Compare Estimated Time vs Actual Time' => '', // 'Estimated hours: ' => '', // 'Actual hours: ' => '', // 'Hours Spent' => '', @@ -1202,4 +1166,52 @@ return array( // 'Email transport' => '', // 'Webhook token' => '', // 'Imports' => '', + // 'Project tags management' => '', + // 'Tag created successfully.' => '', + // 'Unable to create this tag.' => '', + // 'Tag updated successfully.' => '', + // 'Unable to update this tag.' => '', + // 'Tag removed successfully.' => '', + // 'Unable to remove this tag.' => '', + // 'Global tags management' => '', + // 'Tags' => '', + // 'Tags management' => '', + // 'Add new tag' => '', + // 'Edit a tag' => '', + // 'Project tags' => '', + // 'There is no specific tag for this project at the moment.' => '', + // 'Tag' => '', + // 'Remove a tag' => '', + // 'Do you really want to remove this tag: "%s"?' => '', + // 'Global tags' => '', + // 'There is no global tag at the moment.' => '', + // 'This field cannot be empty' => '', + // 'Close a task when there is no activity in an specific column' => '', + // '%s removed a subtask for the task #%d' => '', + // '%s removed a comment on the task #%d' => '', + // 'Comment removed on task #%d' => '', + // 'Subtask removed on task #%d' => '', + // 'Hide tasks in this column in the dashboard' => '', + // '%s removed a comment on the task %s' => '', + // '%s removed a subtask for the task %s' => '', + // 'Comment removed' => '', + // 'Subtask removed' => '', + // '%s set a new internal link for the task #%d' => '', + // '%s removed an internal link for the task #%d' => '', + // 'A new internal link for the task #%d have been defined' => '', + // 'Internal link removed for the task #%d' => '', + // '%s set a new internal link for the task %s' => '', + // '%s removed an internal link for the task %s' => '', + // 'Automatically set the due date on task creation' => '', + // 'Move the task to another column when closed' => '', + // 'Move the task to another column when not moved during a given period' => '', + // 'Dashboard for %s' => '', + // 'Tasks overview for %s' => '', + // 'Subtasks overview for %s' => '', + // 'Projects overview for %s' => '', + // 'Activity stream for %s' => '', + // 'Calendar for %s' => '', + // 'Notifications for %s' => '', + // 'Subtasks export' => '', + // 'Tasks exportation' => '', ); diff --git a/app/Locale/de_DE/translations.php b/app/Locale/de_DE/translations.php index e76100c1..59de2956 100644 --- a/app/Locale/de_DE/translations.php +++ b/app/Locale/de_DE/translations.php @@ -154,8 +154,6 @@ return array( '%d closed tasks' => '%d abgeschlossene Aufgaben', 'No task for this project' => 'Keine Aufgaben in diesem Projekt', 'Public link' => 'Öffentlicher Link', - 'Change assignee' => 'Zuständigkeit ändern', - 'Change assignee for the task "%s"' => 'Zuständigkeit für diese Aufgabe ändern: "%s"', 'Timezone' => 'Zeitzone', 'Sorry, I didn\'t find this information in my database!' => 'Diese Information wurde in der Datenbank nicht gefunden!', 'Page not found' => 'Seite nicht gefunden', @@ -248,7 +246,6 @@ return array( 'Category' => 'Kategorie', 'Category:' => 'Kategorie:', 'Categories' => 'Kategorien', - 'Category not found.' => 'Kategorie nicht gefunden.', 'Your category have been created successfully.' => 'Kategorie erfolgreich erstellt.', 'Unable to create your category.' => 'Erstellung der Kategorie nicht möglich.', 'Your category have been updated successfully.' => 'Kategorie erfolgreich aktualisiert.', @@ -270,7 +267,6 @@ return array( 'Do you really want to remove this file: "%s"?' => 'Soll diese Datei wirklich gelöscht werden: "%s"?', 'Attachments' => 'Anhänge', 'Edit the task' => 'Aufgabe bearbeiten', - 'Edit the description' => 'Beschreibung bearbeiten', 'Add a comment' => 'Kommentar hinzufügen', 'Edit a comment' => 'Kommentar bearbeiten', 'Summary' => 'Zusammenfassung', @@ -303,7 +299,6 @@ return array( 'Display another project' => 'Zu Projekt wechseln', 'Created by %s' => 'Erstellt durch %s', 'Tasks Export' => 'Aufgaben exportieren', - 'Tasks exportation for "%s"' => 'Aufgaben exportieren für "%s"', 'Start Date' => 'Anfangsdatum', 'End Date' => 'Enddatum', 'Execute' => 'Ausführen', @@ -326,14 +321,9 @@ return array( 'New sub-task' => 'Neue Teilaufgabe', 'New attachment added "%s"' => 'Neuer Anhang "%s" wurde hinzugefügt.', 'New comment posted by %s' => 'Neuer Kommentar verfasst durch %s', - 'New attachment' => 'Neuer Anhang', 'New comment' => 'Neuer Kommentar', 'Comment updated' => 'Kommentar wurde aktualisiert', 'New subtask' => 'Neue Teilaufgabe', - 'Subtask updated' => 'Teilaufgabe aktualisiert', - 'Task updated' => 'Aufgabe aktualisiert', - 'Task closed' => 'Aufgabe geschlossen', - 'Task opened' => 'Aufgabe geöffnet', 'I want to receive notifications only for those projects:' => 'Ich möchte nur für diese Projekte Benachrichtigungen erhalten:', 'view the task on Kanboard' => 'diese Aufgabe auf dem Kanboard zeigen', 'Public access' => 'Öffentlicher Zugriff', @@ -368,7 +358,6 @@ return array( 'No external authentication enabled.' => 'Es sind keine externen Authentisierungsmethoden aktiv.', 'Password modified successfully.' => 'Passwort wurde erfolgreich geändert.', 'Unable to change the password.' => 'Passwort konnte nicht geändert werden.', - 'Change category for the task "%s"' => 'Kategorie der Aufgabe "%s" ändern', 'Change category' => 'Kategorie ändern', '%s updated the task %s' => '%s hat die Aufgabe %s aktualisiert', '%s opened the task %s' => '%s hat die Aufgabe %s geöffnet', @@ -391,14 +380,12 @@ return array( '%s updated the task #%d' => '%s hat die Aufgabe #%d aktualisiert', '%s created the task #%d' => '%s hat die Aufgabe #%d angelegt', '%s closed the task #%d' => '%s hat die Aufgabe #%d geschlossen', - '%s open the task #%d' => '%s hat die Aufgabe #%d geöffnet', - '%s moved the task #%d to the column "%s"' => '%s hat die Aufgabe #%d in die Spalte "%s" verschoben', - '%s moved the task #%d to the position %d in the column "%s"' => '%s hat die Aufgabe #%d an die Position %d in der Spalte "%s" verschoben', + '%s opened the task #%d' => '%s hat die Aufgabe #%d geöffnet', 'Activity' => 'Aktivität', 'Default values are "%s"' => 'Die Standardwerte sind "%s"', 'Default columns for new projects (Comma-separated)' => 'Standardspalten für neue Projekte (komma-getrennt)', 'Task assignee change' => 'Zuständigkeit geändert', - '%s change the assignee of the task #%d to %s' => '%s hat die Zusständigkeit der Aufgabe #%d geändert um %s', + '%s changed the assignee of the task #%d to %s' => '%s hat die Zusständigkeit der Aufgabe #%d geändert um %s', '%s changed the assignee of the task %s to %s' => '%s hat die Zuständigkeit der Aufgabe %s geändert um %s', 'New password for the user "%s"' => 'Neues Passwort des Benutzers "%s"', 'Choose an event' => 'Aktion wählen', @@ -447,13 +434,10 @@ return array( 'Percentage' => 'Prozentsatz', 'Number of tasks' => 'Anzahl an Aufgaben', 'Task distribution' => 'Aufgabenverteilung', - 'Reportings' => 'Berichte', - 'Task repartition for "%s"' => 'Aufgabenzuweisung für "%s"', 'Analytics' => 'Analyse', 'Subtask' => 'Teilaufgabe', 'My subtasks' => 'Meine Teilaufgaben', 'User repartition' => 'Benutzerverteilung', - 'User repartition for "%s"' => 'Benutzerverteilung für "%s"', 'Clone this project' => 'Projekt kopieren', 'Column removed successfully.' => 'Spalte erfolgreich entfernt.', 'Not enough data to show the graph.' => 'Nicht genügend Daten, um die Grafik zu zeigen.', @@ -470,10 +454,8 @@ return array( 'This value must be numeric' => 'Dieser Wert muss nummerisch sein', 'Unable to create this task.' => 'Diese Aufgabe kann nicht erstellt werden', 'Cumulative flow diagram' => 'Kumulatives Flussdiagramm', - 'Cumulative flow diagram for "%s"' => 'Kumulatives Flussdiagramm für "%s"', 'Daily project summary' => 'Tägliche Projektzusammenfassung', 'Daily project summary export' => 'Export der täglichen Projektzusammenfassung', - 'Daily project summary export for "%s"' => 'Export der täglichen Projektzusammenfassung für "%s"', 'Exports' => 'Exporte', 'This export contains the number of tasks per column grouped per day.' => 'Dieser Export enthält die Anzahl der Aufgaben pro Spalte nach Tagen gruppiert.', 'Active swimlanes' => 'Aktive Swimlane', @@ -485,7 +467,6 @@ return array( 'Remove a swimlane' => 'Swimlane entfernen', 'Show default swimlane' => 'Standard-Swimlane anzeigen', 'Swimlane modification for the project "%s"' => 'Swimlane-Änderung für das Projekt "%s"', - 'Swimlane not found.' => 'Swimlane nicht gefunden', 'Swimlane removed successfully.' => 'Swimlane erfolgreich entfernt.', 'Swimlanes' => 'Swimlanes', 'Swimlane updated successfully.' => 'Swimlane erfolgreich geändert.', @@ -500,7 +481,6 @@ return array( 'Subtask Id' => 'Teilaufgaben-ID', 'Subtasks' => 'Teilaufgaben', 'Subtasks Export' => 'Export von Teilaufgaben', - 'Subtasks exportation for "%s"' => 'Export von Teilaufgaben für "%s"', 'Task Title' => 'Aufgaben-Titel', 'Untitled' => 'unbetitelt', 'Application default' => 'Anwendungsstandard', @@ -607,7 +587,7 @@ return array( 'The currency rate have been added successfully.' => 'Der Währungskurs wurde erfolgreich hinzugefügt.', 'Unable to add this currency rate.' => 'Währungskurs konnte nicht hinzugefügt werden', 'Webhook URL' => 'Webhook-URL', - '%s remove the assignee of the task %s' => '%s Zuordnung für die Aufgabe %s entfernen', + '%s removed the assignee of the task %s' => '%s Zuordnung für die Aufgabe %s entfernen', 'Enable Gravatar images' => 'Aktiviere Gravatar-Bilder', 'Information' => 'Information', 'Check two factor authentication code' => 'Prüfe Zwei-Faktor-Authentifizierungscode', @@ -621,7 +601,6 @@ return array( 'Test your device' => 'Teste dein Gerät', 'Assign a color when the task is moved to a specific column' => 'Weise eine Farbe zu, wenn die Aufgabe zu einer bestimmten Spalte bewegt wird', '%s via Kanboard' => '%s via Kanboard', - 'Burndown chart for "%s"' => 'Burndown-Diagramm für "%s"', 'Burndown chart' => 'Burndown-Diagramm', 'This chart show the task complexity over the time (Work Remaining).' => 'Dieses Diagramm zeigt die Aufgabenkomplexität über den Faktor Zeit (Verbleibende Arbeit).', 'Screenshot taken %s' => 'Screenshot aufgenommen %s ', @@ -686,14 +665,8 @@ return array( 'Move the task to another column when the category is changed' => 'Aufgabe in andere Spalte verschieben, wenn Kategorie geändert wird', 'Send a task by email to someone' => 'Aufgabe per E-Mail versenden', 'Reopen a task' => 'Aufgabe wieder öffnen', - 'Column change' => 'Spalte geändert', - 'Position change' => 'Position geändert', - 'Swimlane change' => 'Swimlane geändert', - 'Assignee change' => 'Zuordnung geändert', - '[%s] Overdue tasks' => '[%s] überfallige Aufgaben', 'Notification' => 'Benachrichtigungen', '%s moved the task #%d to the first swimlane' => '%s hat die Aufgabe #%d in die erste Swimlane verschoben', - '%s moved the task #%d to the swimlane "%s"' => '%s hat die Aufgabe #%d in die Swimlane "%s" verschoben', 'Swimlane' => 'Swimlane', 'Gravatar' => 'Gravatar', '%s moved the task %s to the first swimlane' => '%s hat die Aufgabe %s in die erste Swimlane verschoben', @@ -764,8 +737,6 @@ return array( 'Search by category: ' => 'Suche nach Kategorie: ', 'Search by description: ' => 'Suche nach Beschreibung: ', 'Search by due date: ' => 'Suche nach Fälligkeitsdatum: ', - 'Lead and Cycle time for "%s"' => 'Durchlauf und Zykluszeit für "%s"', - 'Average time spent into each column for "%s"' => 'Durchschnittliche Zeit in jeder Spalte für "%s"', 'Average time spent into each column' => 'Durchschnittszeit in jeder Spalte', 'Average time spent' => 'Durchschnittlicher Zeitverbrauch', 'This chart show the average time spent into each column for the last %d tasks.' => 'Dieses Diagramm zeigt die durchschnittliche Zeit in jeder Spalte der letzten %d Aufgaben.', @@ -806,7 +777,6 @@ return array( 'License:' => 'Lizenz:', 'License' => 'Lizenz', 'Enter the text below' => 'Text unten eingeben', - 'Gantt chart for %s' => 'Gantt Diagramm für %s', 'Sort by position' => 'Nach Position sortieren', 'Sort by date' => 'Nach Datum sortieren', 'Add task' => 'Aufgabe hinzufügen', @@ -847,8 +817,6 @@ return array( 'Version' => 'Version', 'Plugins' => 'Plugins', 'There is no plugin loaded.' => 'Es ist kein Plugin geladen.', - 'Set maximum column height' => 'Setze maximale Spaltenhöhe', - 'Remove maximum column height' => 'Entferne maximale Spaltenhöhe', 'My notifications' => 'Meine Benachrichtigungen', 'Custom filters' => 'Benutzerdefinierte Filter', 'Your custom filter have been created successfully.' => 'Benutzerdefinierten Filter erfolgreich erstellt.', @@ -953,7 +921,6 @@ return array( 'Invalid captcha' => 'Ungültiges Captcha', 'The name must be unique' => 'Der Name muss eindeutig sein', 'View all groups' => 'Alle Gruppen anzeigen', - 'View group members' => 'Gruppenmitglieder anzeigen', 'There is no user available.' => 'Es ist kein Benutzer verfügbar.', 'Do you really want to remove the user "%s" from the group "%s"?' => 'Wollen Sie den Benutzer "%s" wirklich aus der Gruppe "%s" löschen?', 'There is no group.' => 'Es gibt keine Gruppe.', @@ -974,13 +941,10 @@ return array( 'Enter group name...' => 'Geben Sie den Gruppennamen ein...', 'Role:' => 'Rolle:', 'Project members' => 'Projektmitglieder', - 'Compare hours for "%s"' => 'Vergleich der Stunden für %s', '%s mentioned you in the task #%d' => '%s erwähnte Sie in Aufgabe #%d', '%s mentioned you in a comment on the task #%d' => '%s erwähnte Sie in einem Kommentar zur Aufgabe #%d', 'You were mentioned in the task #%d' => 'Sie wurden in der Aufgabe #%d erwähnt', 'You were mentioned in a comment on the task #%d' => 'Sie wurden in einem Kommentar zur Aufgabe #%d erwähnt', - 'Mentioned' => 'Erwähnt', - 'Compare Estimated Time vs Actual Time' => 'Vergleich zwischen erwartetem und tatsächlichem Zeitaufwand', 'Estimated hours: ' => 'Erwarteter Zeitaufwand (Stunden): ', 'Actual hours: ' => 'Tatsächlich aufgewändete Stunden: ', 'Hours Spent' => 'Stunden aufgewändet', @@ -1202,4 +1166,52 @@ return array( 'Email transport' => 'E-Mail Verkehr', 'Webhook token' => 'Webhook Token', 'Imports' => 'Importe', + 'Project tags management' => 'Projektbezogenes Schlagwort-Management', + 'Tag created successfully.' => 'Schlagwort erfolgreich erstellt.', + 'Unable to create this tag.' => 'Das Schlagwort kann nicht erstellt werden.', + 'Tag updated successfully.' => 'Schlagwort erfolgreich aktualisiert.', + 'Unable to update this tag.' => 'Das Schlagwort kann nicht aktualisiert werden.', + 'Tag removed successfully.' => 'Schlagwort erfolgreich entfernt.', + 'Unable to remove this tag.' => 'Das Schlagwort kann nicht entfernt werden.', + 'Global tags management' => 'Globales Schlagwort-Management', + 'Tags' => 'Schlagworte', + 'Tags management' => 'Schlagwort-Management', + 'Add new tag' => 'Neues Schlagwort hinzufügen', + 'Edit a tag' => 'Schlagwort bearbeiten', + 'Project tags' => 'Projektbezogene Schlagwörter', + 'There is no specific tag for this project at the moment.' => 'Es gibt zur Zeit kein spezifisches Schlagwort.', + 'Tag' => 'Schlagwort', + 'Remove a tag' => 'Schlagwort entfernen', + 'Do you really want to remove this tag: "%s"?' => 'Soll dieses Schlagwort wirklich entfernt werden: "%s"?', + 'Global tags' => 'Globale Schlagwörter', + 'There is no global tag at the moment.' => 'Es gibt zur Zeit kein globales Schlagwort', + 'This field cannot be empty' => 'Dieses Feld kann nicht leer sein', + // 'Close a task when there is no activity in an specific column' => '', + // '%s removed a subtask for the task #%d' => '', + // '%s removed a comment on the task #%d' => '', + // 'Comment removed on task #%d' => '', + // 'Subtask removed on task #%d' => '', + 'Hide tasks in this column in the dashboard' => 'Aufgaben in dieser Spalte im Dashboard ausblenden', + // '%s removed a comment on the task %s' => '', + // '%s removed a subtask for the task %s' => '', + // 'Comment removed' => '', + // 'Subtask removed' => '', + // '%s set a new internal link for the task #%d' => '', + // '%s removed an internal link for the task #%d' => '', + // 'A new internal link for the task #%d have been defined' => '', + // 'Internal link removed for the task #%d' => '', + // '%s set a new internal link for the task %s' => '', + // '%s removed an internal link for the task %s' => '', + // 'Automatically set the due date on task creation' => '', + // 'Move the task to another column when closed' => '', + // 'Move the task to another column when not moved during a given period' => '', + // 'Dashboard for %s' => '', + // 'Tasks overview for %s' => '', + // 'Subtasks overview for %s' => '', + // 'Projects overview for %s' => '', + // 'Activity stream for %s' => '', + // 'Calendar for %s' => '', + // 'Notifications for %s' => '', + // 'Subtasks export' => '', + // 'Tasks exportation' => '', ); diff --git a/app/Locale/el_GR/translations.php b/app/Locale/el_GR/translations.php index 59d75a96..cf3bb588 100644 --- a/app/Locale/el_GR/translations.php +++ b/app/Locale/el_GR/translations.php @@ -154,8 +154,6 @@ return array( '%d closed tasks' => '%d κλειστές εργασίες', 'No task for this project' => 'Αριθμός εργασιών για το έργο', 'Public link' => 'Δημόσιος σύνδεσμος', - 'Change assignee' => 'Αλλαγή ανάθεσης', - 'Change assignee for the task "%s"' => 'Αλλαγή ανάθεσης για την εργασία « %s »', 'Timezone' => 'Timezone', 'Sorry, I didn\'t find this information in my database!' => 'Δυστυχώς δεν βρέθηκε αυτή η πληροφορία στη βάση δεδομένων', 'Page not found' => 'Η σελίδα δεν βρέθηκε', @@ -248,7 +246,6 @@ return array( 'Category' => 'Κατηγορία', 'Category:' => 'Κατηγορία:', 'Categories' => 'Κατηγορίες', - 'Category not found.' => 'Η κατηγορία δεν βρέθηκε', 'Your category have been created successfully.' => 'Η κατηγορία δημιουργήθηκε.', 'Unable to create your category.' => 'Δεν είναι δυνατή η δημιουργία της κατηγορίας.', 'Your category have been updated successfully.' => 'Η ενημέρωση της κατηγορίας καταχωρήθηκε με επιτυχία.', @@ -270,7 +267,6 @@ return array( 'Do you really want to remove this file: "%s"?' => 'Αφαίρεση του αρχείου: « %s » ?', 'Attachments' => 'Συνημμένα', 'Edit the task' => 'Διόρθωση εργασίας', - 'Edit the description' => 'Διόρθωση περιγραφής', 'Add a comment' => 'Προσθήκη σχολίου', 'Edit a comment' => 'Διόρθωση σχολίου', 'Summary' => 'Περίληψη', @@ -303,7 +299,6 @@ return array( 'Display another project' => 'Εμφάνιση άλλου έργου', 'Created by %s' => 'Δημιουργήθηκε από %s', 'Tasks Export' => 'Εξαγωγή εργασιών', - 'Tasks exportation for "%s"' => 'Εξαγωγή εργασιών για το έργο « %s »', 'Start Date' => 'Ημερομηνία έναρξης', 'End Date' => 'ημερομηνία λήξης', 'Execute' => 'Εκτέλεση', @@ -326,14 +321,9 @@ return array( 'New sub-task' => 'Νέα υπο-εργασία', 'New attachment added "%s"' => 'Νέα επικόλληση προστέθηκε « %s »', 'New comment posted by %s' => 'Νέο σχόλιο από τον χρήστη « %s »', - 'New attachment' => 'New attachment', 'New comment' => 'Νέο σχόλιο', 'Comment updated' => 'Το σχόλιο ενημερώθηκε', 'New subtask' => 'Νέα υπο-εργασία', - 'Subtask updated' => 'Η Υπο-Εργασία ενημερώθηκε', - 'Task updated' => 'Η εργασία ενημερώθηκε', - 'Task closed' => 'Η εργασία έκλεισε', - 'Task opened' => 'Η εργασία άνοιξε', 'I want to receive notifications only for those projects:' => 'Θέλω να ενημερώνομαι αποκλειστικά για:', 'view the task on Kanboard' => 'Προβολή της εργασίας στο Kanboard', 'Public access' => 'Ανοιχτή πρόσβαση', @@ -368,7 +358,6 @@ return array( 'No external authentication enabled.' => 'Καμία εξωτερική πιστοποιήση ενεργοποιημένη.', 'Password modified successfully.' => 'Το password τροποποιήθηκε με επιτυχία.', 'Unable to change the password.' => 'Αδύνατο να αλλάξει το password.', - 'Change category for the task "%s"' => 'Αλλαγή κατηγορίας της εργασίας « %s »', 'Change category' => 'Αλλαγή κατηγορίας', '%s updated the task %s' => '%s ενημέρωσε την εργασία %s', '%s opened the task %s' => '%s άνοιξε την εργασία %s', @@ -391,14 +380,12 @@ return array( '%s updated the task #%d' => '%s ενημέρωσε την εργασία n°%d', '%s created the task #%d' => '%s δημιούργησε την εργασία n°%d', '%s closed the task #%d' => '%s έκλεισε την εργασία n°%d', - '%s open the task #%d' => '%s άνοιξε την εργασία n°%d', - '%s moved the task #%d to the column "%s"' => '%s μετακίνησε την εργασία n°%d στη στήλη « %s »', - '%s moved the task #%d to the position %d in the column "%s"' => '%s μετακίνησε την εργασία n°%d στη θέση n°%d της στήλης « %s »', + '%s opened the task #%d' => '%s άνοιξε την εργασία n°%d', 'Activity' => 'Δραστηριότητα', 'Default values are "%s"' => 'Οι προεπιλεγμένες τιμές είναι « %s »', 'Default columns for new projects (Comma-separated)' => 'Προεπιλεγμένες στήλες για νέα έργα (Comma-separated)', 'Task assignee change' => 'Αλλαγή εκδοχέα εργασίας', - '%s change the assignee of the task #%d to %s' => '%s άλλαξε τον εκδοχέα της εργασίας n˚%d σε %s', + '%s changed the assignee of the task #%d to %s' => '%s άλλαξε τον εκδοχέα της εργασίας n˚%d σε %s', '%s changed the assignee of the task %s to %s' => '%s ενημέρωσε τον εκδοχέα της εργασίας %s σε %s', 'New password for the user "%s"' => 'Νέο password του χρήστη « %s »', 'Choose an event' => 'Επιλογή event', @@ -447,13 +434,10 @@ return array( 'Percentage' => 'Ποσοστό', 'Number of tasks' => 'Αριθμός εργασιών', 'Task distribution' => 'Κατανομή εργασιών', - 'Reportings' => 'Αναφορές', - 'Task repartition for "%s"' => 'Επανάληψη εργασιών για « %s »', 'Analytics' => 'Αναλύσεις', 'Subtask' => 'Υπο-Εργασία', 'My subtasks' => 'Οι υπο-εργασίες μου', 'User repartition' => 'Επαναλήψεις χρηστών', - 'User repartition for "%s"' => 'Επαναλήψεις χρηστών για « %s »', 'Clone this project' => 'Κλωνοποίηση έργου', 'Column removed successfully.' => 'Η στήλη αφαιρέθηκε με επιτυχία.', 'Not enough data to show the graph.' => 'Ελλειπή δεδομένα για να εμφανιστεί το γράφημα.', @@ -470,10 +454,8 @@ return array( 'This value must be numeric' => 'Η τιμή πρέπει να είναι αριθμός', 'Unable to create this task.' => 'Αδύνατο να δημιουργηθεί αυτή η εργασία.', 'Cumulative flow diagram' => 'Συγκεντρωτικό διάγραμμα ροής', - 'Cumulative flow diagram for "%s"' => 'Συγκεντρωτικό διάγραμμα ροής για « %s »', 'Daily project summary' => 'Καθημερινή περίληψη του έργου', 'Daily project summary export' => 'Εξαγωγή της καθημερινής περίληψης του έργου', - 'Daily project summary export for "%s"' => 'Εξαγωγή της καθημερινής περίληψης του έργου « %s »', 'Exports' => 'Εξαγωγές', 'This export contains the number of tasks per column grouped per day.' => 'Αυτή η κατάσταση περιέχει τον αριθμό των εργασιών ανά στήλη ομαδοποιημένα ανά ημέρα.', 'Active swimlanes' => 'Ενεργές λωρίδες', @@ -485,7 +467,6 @@ return array( 'Remove a swimlane' => 'Αφαίρεση λωρίδας', 'Show default swimlane' => 'Εμφάνιση προεπιλεγμένων λωρίδων', 'Swimlane modification for the project "%s"' => 'Τροποποίηση λωρίδας για το έργο « %s »', - 'Swimlane not found.' => 'Η λωρίδα δεν βρέθηκε.', 'Swimlane removed successfully.' => 'Η λωρίδα αφαιρέθηκε με επιτυχία.', 'Swimlanes' => 'Λωρίδες', 'Swimlane updated successfully.' => 'Η λωρίδα ενημερώθηκε με επιτυχία.', @@ -500,7 +481,6 @@ return array( 'Subtask Id' => 'Id υπο-εργασίας', 'Subtasks' => 'Υπο-Εργασίες', 'Subtasks Export' => 'Εξαγωγή υπο-εργασίων', - 'Subtasks exportation for "%s"' => 'Εξαγωγή υπο-εργασίων για το έργο « %s »', 'Task Title' => 'Τίτλος εργασίας', 'Untitled' => 'Χωρίς τίτλο', 'Application default' => 'Προεπιλογή από την εφαρμογή', @@ -607,7 +587,7 @@ return array( 'The currency rate have been added successfully.' => 'Η ισοτιμία προστέθηκε με επιτυχία.', 'Unable to add this currency rate.' => 'Αδύνατο να προστεθεί αυτή η ισοτιμία.', 'Webhook URL' => 'Webhook URL', - '%s remove the assignee of the task %s' => '%s αφαίρεσε τον εκδοχέα της εργασίας %s', + '%s removed the assignee of the task %s' => '%s αφαίρεσε τον εκδοχέα της εργασίας %s', 'Enable Gravatar images' => 'Ενεργοποίηση εικόνων Gravatar', 'Information' => 'Πληροφορίες', 'Check two factor authentication code' => 'Ελέγξτε δύο παράγοντες ελέγχου ταυτότητας κωδικού', @@ -621,7 +601,6 @@ return array( 'Test your device' => 'Ελέγξτε τη συσκευή σας', 'Assign a color when the task is moved to a specific column' => 'Αντιστοίχιση χρώματος όταν η εργασία κινείται σε μια συγκεκριμένη στήλη', '%s via Kanboard' => '%s via Kanboard', - 'Burndown chart for "%s"' => 'Δημιουργία διαγράμματος για « %s »', 'Burndown chart' => 'Δημιουργία διαγράμματος', 'This chart show the task complexity over the time (Work Remaining).' => 'Αυτό το γράφημα δείχνει την πολυπλοκότητα του έργου κατά την πάροδο του χρόνου (Εργασία που παραμένει).', 'Screenshot taken %s' => 'Το screenshot αποθηκεύτηκε από %s', @@ -686,14 +665,8 @@ return array( 'Move the task to another column when the category is changed' => 'Μετακινήστε την εργασία σε άλλη στήλη, όταν η κατηγορία έχει αλλάξει', 'Send a task by email to someone' => 'Στείλτε μια εργασία μέσω ηλεκτρονικού ταχυδρομείου σε κάποιον', 'Reopen a task' => 'Ξανα-ανοίξτε μια εργασία', - 'Column change' => 'Αλλαγή στήλης', - 'Position change' => 'Αλλαγή θέσης', - 'Swimlane change' => 'Αλλαγή λωρίδας', - 'Assignee change' => 'Αλλαγή εκδοχέα', - '[%s] Overdue tasks' => '[%s] Εκπρόθεσμες εργασίες', 'Notification' => 'Κοινοποίηση', '%s moved the task #%d to the first swimlane' => '%s μετέφερε την εργασία n°%d στην 1η λωρίδα', - '%s moved the task #%d to the swimlane "%s"' => '%s μετέφερε την εργασία n°%d στη λωρίδα « %s »', 'Swimlane' => 'Λωρίδα', 'Gravatar' => 'Gravatar', '%s moved the task %s to the first swimlane' => '%s μετέφερε την εργασία %s στην 1η λωρίδα', @@ -764,8 +737,6 @@ return array( 'Search by category: ' => 'Αναζήτηση βάση κατηγορίας: ', 'Search by description: ' => 'Αναζήτηση βάση περιγραφής: ', 'Search by due date: ' => 'Αναζήτηση βάση ημέρας λήξης: ', - 'Lead and Cycle time for "%s"' => 'Lead & cycle time για « %s »', - 'Average time spent into each column for "%s"' => 'Μέσος χρόνος παραμονής σε κάθε στήλη για « %s »', 'Average time spent into each column' => 'Μέσος χρόνος παραμονής σε κάθε στήλη', 'Average time spent' => 'Μέσος χρόνος που δαπανήθηκε', 'This chart show the average time spent into each column for the last %d tasks.' => 'Αυτό το γράφημα δείχνει ότι ο μέσος χρόνος που δαπανάται σε κάθε στήλη για τις τελευταίες %d εργασίες', @@ -806,7 +777,6 @@ return array( 'License:' => 'Άδεια:', 'License' => 'Άδεια', 'Enter the text below' => 'Πληκτρολογήστε το παρακάτω κείμενο', - 'Gantt chart for %s' => 'Gantt διάγραμμα για %s', 'Sort by position' => 'Ταξινόμηση κατά Θέση', 'Sort by date' => 'Ταξινόμηση κατά ημέρα', 'Add task' => 'Προσθήκη εργασίας', @@ -847,8 +817,6 @@ return array( 'Version' => 'Έκδοση', 'Plugins' => 'Πρόσθετα', 'There is no plugin loaded.' => 'Δεν έχει φορτωθεί plugin', - 'Set maximum column height' => 'Ορισμός μέγιστου ύψους στήλης', - 'Remove maximum column height' => 'Αφαίρεση μέγιστου ύψους στήλης', 'My notifications' => 'Οι ειδοποιήσεις μου', 'Custom filters' => 'Φίλτρα ορισμένα από τον χρήστη', 'Your custom filter have been created successfully.' => 'Το παρεμετροποιημένο από τον χρήστη φίλτρο δημιουργήθηκε με επιτυχία', @@ -953,7 +921,6 @@ return array( 'Invalid captcha' => 'Μη αποδεκτό Captcha', 'The name must be unique' => 'Το όνομα πρέπει να είναι μοναδικό', 'View all groups' => 'Προβολή όλων των ομάδων', - 'View group members' => 'Προβολή των μελών της ομάδας', 'There is no user available.' => 'Δεν υπάρχει διαθέσιμος χρήστης', 'Do you really want to remove the user "%s" from the group "%s"?' => 'Αφαίρεση του χρήστη « %s » από την ομάδα « %s » ?', 'There is no group.' => 'Δεν υπάρχει ομάδα.', @@ -974,13 +941,10 @@ return array( 'Enter group name...' => 'Εισαγωγή ονομασίας ομάδας...', 'Role:' => 'Ρόλος:', 'Project members' => 'Μέλη έργου', - 'Compare hours for "%s"' => 'Σύγκριση ωρών για « %s »', '%s mentioned you in the task #%d' => '%s αναφέρονται σε εσάς, στη εργασία n°%d', '%s mentioned you in a comment on the task #%d' => '%s αναφέρονται σε εσάς σε σχόλιο, στη εργασίας n°%d', 'You were mentioned in the task #%d' => 'Αναφέρεστε στην εργασία n°%d', 'You were mentioned in a comment on the task #%d' => 'Αναφέρεστε σε σχόλιο, στην εργασία n°%d', - 'Mentioned' => 'Αναφέρεται', - 'Compare Estimated Time vs Actual Time' => 'Σύγκριση προβλεπόμενου χρόνου vs πραγματικού χρόνου', 'Estimated hours: ' => 'Προβλεπόμενες ώρες: ', 'Actual hours: ' => 'Πραγματικές ώρες: ', 'Hours Spent' => 'Δαπανόμενες ώρες', @@ -1202,4 +1166,52 @@ return array( // 'Email transport' => '', // 'Webhook token' => '', // 'Imports' => '', + // 'Project tags management' => '', + // 'Tag created successfully.' => '', + // 'Unable to create this tag.' => '', + // 'Tag updated successfully.' => '', + // 'Unable to update this tag.' => '', + // 'Tag removed successfully.' => '', + // 'Unable to remove this tag.' => '', + // 'Global tags management' => '', + // 'Tags' => '', + // 'Tags management' => '', + // 'Add new tag' => '', + // 'Edit a tag' => '', + // 'Project tags' => '', + // 'There is no specific tag for this project at the moment.' => '', + // 'Tag' => '', + // 'Remove a tag' => '', + // 'Do you really want to remove this tag: "%s"?' => '', + // 'Global tags' => '', + // 'There is no global tag at the moment.' => '', + // 'This field cannot be empty' => '', + // 'Close a task when there is no activity in an specific column' => '', + // '%s removed a subtask for the task #%d' => '', + // '%s removed a comment on the task #%d' => '', + // 'Comment removed on task #%d' => '', + // 'Subtask removed on task #%d' => '', + // 'Hide tasks in this column in the dashboard' => '', + // '%s removed a comment on the task %s' => '', + // '%s removed a subtask for the task %s' => '', + // 'Comment removed' => '', + // 'Subtask removed' => '', + // '%s set a new internal link for the task #%d' => '', + // '%s removed an internal link for the task #%d' => '', + // 'A new internal link for the task #%d have been defined' => '', + // 'Internal link removed for the task #%d' => '', + // '%s set a new internal link for the task %s' => '', + // '%s removed an internal link for the task %s' => '', + // 'Automatically set the due date on task creation' => '', + // 'Move the task to another column when closed' => '', + // 'Move the task to another column when not moved during a given period' => '', + // 'Dashboard for %s' => '', + // 'Tasks overview for %s' => '', + // 'Subtasks overview for %s' => '', + // 'Projects overview for %s' => '', + // 'Activity stream for %s' => '', + // 'Calendar for %s' => '', + // 'Notifications for %s' => '', + // 'Subtasks export' => '', + // 'Tasks exportation' => '', ); diff --git a/app/Locale/es_ES/translations.php b/app/Locale/es_ES/translations.php index a646e4ef..5a2c16c0 100644 --- a/app/Locale/es_ES/translations.php +++ b/app/Locale/es_ES/translations.php @@ -20,16 +20,16 @@ return array( 'Orange' => 'Naranja', 'Grey' => 'Gris', 'Brown' => 'Marrón', - 'Deep Orange' => 'Naranja Oscuro', - 'Dark Grey' => 'Gris Oscuro', + 'Deep Orange' => 'Naranja oscuro', + 'Dark Grey' => 'Gris oscuro', 'Pink' => 'Rosa', - 'Teal' => 'Verde Azulado', - 'Cyan' => 'Cián', + 'Teal' => 'Verde azulado', + 'Cyan' => 'Cian', 'Lime' => 'Lima', - 'Light Green' => 'Verde Claro', + 'Light Green' => 'Verde claro', 'Amber' => 'Ámbar', 'Save' => 'Guardar', - 'Login' => 'Iniciar sesión (Ingresar)', + 'Login' => 'Iniciar sesión (ingresar)', 'Official website:' => 'Página web oficial:', 'Unassigned' => 'No asignado', 'View this task' => 'Ver esta tarea', @@ -80,7 +80,7 @@ return array( 'Settings' => 'Preferencias', 'Application settings' => 'Preferencias de la aplicación', 'Language' => 'Idioma', - 'Webhook token:' => 'Token de los disparadores Web (webhooks):', + 'Webhook token:' => 'Token de los disparadores web (webhooks):', 'API token:' => 'Token de la API:', 'Database size:' => 'Tamaño de la base de datos:', 'Download the database' => 'Descargar la base de datos', @@ -154,8 +154,6 @@ return array( '%d closed tasks' => '%d tareas completadas', 'No task for this project' => 'Ninguna tarea para este proyecto', 'Public link' => 'Enlace público', - 'Change assignee' => 'Cambiar responsable', - 'Change assignee for the task "%s"' => 'Cambiar el responsable para la tarea «%s»', 'Timezone' => 'Zona horaria', 'Sorry, I didn\'t find this information in my database!' => '¡Lo siento, no he encontrado esta información en mi base de datos!', 'Page not found' => 'Página no encontrada', @@ -175,7 +173,7 @@ return array( 'Your automatic action have been created successfully.' => 'La acción automatizada ha sido creada correctamente.', 'Unable to create your automatic action.' => 'No se puede crear esta acción automatizada.', 'Remove an action' => 'Eliminar una acción', - 'Unable to remove this action.' => 'No se puede eliminar esta accción.', + 'Unable to remove this action.' => 'No se puede eliminar esta acción.', 'Action removed successfully.' => 'La acción ha sido eliminada correctamente.', 'Automatic actions for the project "%s"' => 'Acciones automatizadas para el proyecto «%s»', 'Add an action' => 'Añadir una acción', @@ -248,7 +246,6 @@ return array( 'Category' => 'Categoría', 'Category:' => 'Categoría:', 'Categories' => 'Categorías', - 'Category not found.' => 'Categoría no hallada.', 'Your category have been created successfully.' => 'Se ha creado su categoría correctamente.', 'Unable to create your category.' => 'No se puede crear su categoría.', 'Your category have been updated successfully.' => 'Se ha actualizado su categoría correctamente.', @@ -270,7 +267,6 @@ return array( 'Do you really want to remove this file: "%s"?' => '¿Realmente desea eliminar este fichero: «%s»?', 'Attachments' => 'Adjuntos', 'Edit the task' => 'Modificar la tarea', - 'Edit the description' => 'Modificar la descripción', 'Add a comment' => 'Añadir un comentario', 'Edit a comment' => 'Modificar un comentario', 'Summary' => 'Resumen', @@ -303,7 +299,6 @@ return array( 'Display another project' => 'Mostrar otro proyecto', 'Created by %s' => 'Creado por %s', 'Tasks Export' => 'Exportar tareas', - 'Tasks exportation for "%s"' => 'Exportación de tareas para «%s»', 'Start Date' => 'Fecha de inicio', 'End Date' => 'Fecha final', 'Execute' => 'Ejecutar', @@ -315,9 +310,9 @@ return array( 'Project cloned successfully.' => 'Proyecto clonado correctamente.', 'Unable to clone this project.' => 'No se puede clonar este proyecto.', 'Enable email notifications' => 'Habilitar notificaciones por correo electrónico', - 'Task position:' => 'Posición de la tarea', - 'The task #%d have been opened.' => 'La tarea #%d ha sido abierta', - 'The task #%d have been closed.' => 'La tarea #%d ha sido cerrada', + 'Task position:' => 'Posición de la tarea:', + 'The task #%d have been opened.' => 'La tarea #%d ha sido abierta.', + 'The task #%d have been closed.' => 'La tarea #%d ha sido cerrada.', 'Sub-task updated' => 'Subtarea actualizada', 'Title:' => 'Título:', 'Status:' => 'Estado:', @@ -326,14 +321,9 @@ return array( 'New sub-task' => 'Nueva subtarea', 'New attachment added "%s"' => 'Nuevo adjunto añadido «%s»', 'New comment posted by %s' => 'Nuevo comentario añadido por %s', - 'New attachment' => 'Nuevo adjunto', 'New comment' => 'Nuevo comentario', 'Comment updated' => 'Comentario actualizado', 'New subtask' => 'Nueva subtarea', - 'Subtask updated' => 'Subtarea actualizada', - 'Task updated' => 'Tarea actualizada', - 'Task closed' => 'Tarea cerrada', - 'Task opened' => 'Tarea abierta', 'I want to receive notifications only for those projects:' => 'Quiero recibir notificaciones sólo de estos proyectos:', 'view the task on Kanboard' => 'ver la tarea en Kanboard', 'Public access' => 'Acceso público', @@ -362,13 +352,12 @@ return array( 'Account type:' => 'Tipo de cuenta:', 'Edit profile' => 'Modificar perfil', 'Change password' => 'Cambiar contraseña', - 'Password modification' => 'Modificacion de contraseña', + 'Password modification' => 'Modificación de contraseña', 'External authentications' => 'Autenticación externa', 'Never connected.' => 'Nunca se ha conectado.', 'No external authentication enabled.' => 'Sin autenticación externa activa.', 'Password modified successfully.' => 'Contraseña cambiada correctamente.', 'Unable to change the password.' => 'No se puede cambiar la contraseña.', - 'Change category for the task "%s"' => 'Cambiar la categoría de la tarea «%s»', 'Change category' => 'Cambiar categoría', '%s updated the task %s' => '%s actualizó la tarea %s', '%s opened the task %s' => '%s abrió la tarea %s', @@ -391,14 +380,12 @@ return array( '%s updated the task #%d' => '%s actualizó la tarea #%d', '%s created the task #%d' => '%s creó la tarea #%d', '%s closed the task #%d' => '%s cerró la tarea #%d', - '%s open the task #%d' => '%s abrió la tarea #%d', - '%s moved the task #%d to the column "%s"' => '%s movió la tarea #%d a la columna «%s»', - '%s moved the task #%d to the position %d in the column "%s"' => '%s movió la tarea #%d a la posición %d de la columna «%s»', + '%s opened the task #%d' => '%s abrió la tarea #%d', 'Activity' => 'Actividad', 'Default values are "%s"' => 'Los valores por defecto son «%s»', 'Default columns for new projects (Comma-separated)' => 'Columnas por defecto para los nuevos proyectos (separadas mediante comas)', 'Task assignee change' => 'Cambiar responsable de la tarea', - '%s change the assignee of the task #%d to %s' => '%s cambió el responsable de la tarea #%d por %s', + '%s changed the assignee of the task #%d to %s' => '%s cambió el responsable de la tarea #%d por %s', '%s changed the assignee of the task %s to %s' => '%s cambió el responsable de la tarea %s por %s', 'New password for the user "%s"' => 'Nueva contraseña para el usuario «%s»', 'Choose an event' => 'Seleccione un evento', @@ -411,13 +398,13 @@ return array( 'About' => 'Acerca de', 'Database driver:' => 'Controlador de la base de datos (driver):', 'Board settings' => 'Preferencias del tablero', - 'Webhook settings' => 'Preferencias del disparador web (Webhook)', + 'Webhook settings' => 'Preferencias del disparador web (webhook)', 'Reset token' => 'Limpiar token', 'API endpoint:' => 'Endpoint del API:', 'Refresh interval for private board' => 'Intervalo de refresco del tablero privado', 'Refresh interval for public board' => 'Intervalo de refresco del tablero público', 'Task highlight period' => 'Periodo de realce de la tarea', - 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Periodo (en segundos) para considerar que una tarea fué modificada recientemente (0 para deshabilitar, 2 días por defecto)', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Periodo (en segundos) para considerar que una tarea fue modificada recientemente (0 para deshabilitar, 2 días por defecto)', 'Frequency in second (60 seconds by default)' => 'Frecuencia en segundos (60 segundos por defecto)', 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Frecuencia en segundos (0 para deshabilitar esta característica, 10 segundos por defecto)', 'Application URL' => 'URL de la aplicación', @@ -435,7 +422,7 @@ return array( 'Dashboard' => 'Tablero', 'Confirmation' => 'Confirmación', 'Allow everybody to access to this project' => 'Permitir a cualquiera acceder a este proyecto', - 'Everybody have access to this project.' => 'Cualquiera tiene acceso a este proyecto', + 'Everybody have access to this project.' => 'Cualquiera tiene acceso a este proyecto.', 'Webhooks' => 'Disparadores web (webhooks)', 'API' => 'API', 'Create a comment from an external provider' => 'Crear un comentario a partir de un proveedor externo', @@ -447,13 +434,10 @@ return array( 'Percentage' => 'Porcentaje', 'Number of tasks' => 'Número de tareas', 'Task distribution' => 'Distribución de tareas', - 'Reportings' => 'Informes', - 'Task repartition for "%s"' => 'Repartición de tareas para «%s»', 'Analytics' => 'Analítica', 'Subtask' => 'Subtarea', 'My subtasks' => 'Mis subtareas', 'User repartition' => 'Repartición de usuarios', - 'User repartition for "%s"' => 'Repartición de usuarios para «%s»', 'Clone this project' => 'Clonar este proyecto', 'Column removed successfully.' => 'Columna eliminada correctamente.', 'Not enough data to show the graph.' => 'No hay suficiente información para mostrar el gráfico.', @@ -470,10 +454,8 @@ return array( 'This value must be numeric' => 'Este valor debe ser numérico', 'Unable to create this task.' => 'No se puede crear esta tarea.', 'Cumulative flow diagram' => 'Diagrama de flujo acumulativo', - 'Cumulative flow diagram for "%s"' => 'Diagrama de flujo acumulativo para «%s»', 'Daily project summary' => 'Resumen diario del proyecto', 'Daily project summary export' => 'Exportar resumen diario del proyecto', - 'Daily project summary export for "%s"' => 'Exportar resumen diario del proyecto para «%s»', 'Exports' => 'Exportaciones', 'This export contains the number of tasks per column grouped per day.' => 'Esta exportación contiene el número de tareas por columna agrupadas por día.', 'Active swimlanes' => 'Calles activas', @@ -485,7 +467,6 @@ return array( 'Remove a swimlane' => 'Eliminar una calle', 'Show default swimlane' => 'Mostrar calle por defecto', 'Swimlane modification for the project "%s"' => 'Modificación de la calle para el proyecto «%s»', - 'Swimlane not found.' => 'Calle no encontrada.', 'Swimlane removed successfully.' => 'Calle eliminada correctamente.', 'Swimlanes' => 'Calles', 'Swimlane updated successfully.' => 'Calle actualizada correctamente.', @@ -500,12 +481,11 @@ return array( 'Subtask Id' => 'Identificador de subtarea', 'Subtasks' => 'Subtareas', 'Subtasks Export' => 'Exportación de subtareas', - 'Subtasks exportation for "%s"' => 'Exportación de subtareas para «%s»', 'Task Title' => 'Título de la tarea', 'Untitled' => 'Sin título', 'Application default' => 'Predefinido por la aplicación', - 'Language:' => 'Idioma', - 'Timezone:' => 'Zona horaria', + 'Language:' => 'Idioma:', + 'Timezone:' => 'Zona horaria:', 'All columns' => 'Todas las columnas', 'Calendar' => 'Calendario', 'Next' => 'Siguiente', @@ -564,7 +544,7 @@ return array( '%dh' => '%dh', 'Expand tasks' => 'Expandir tareas', 'Collapse tasks' => 'Colapsar tareas', - 'Expand/collapse tasks' => 'Expande/colapasa tareas', + 'Expand/collapse tasks' => 'Expande/colapsa tareas', 'Close dialog box' => 'Cerrar caja de diálogo', 'Submit a form' => 'Enviar formulario', 'Board view' => 'Vista de tablero', @@ -597,17 +577,17 @@ return array( 'Executer' => 'Ejecutor', 'Time spent in the column' => 'Tiempo transcurrido en la columna', 'Task transitions' => 'Transiciones de tarea', - 'Task transitions export' => 'Eportar transiciones de tarea', - 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => 'Este informe contiene todos los movimientos de columna para cada tarea con la fecha, el usuario y el tiempo transcurrido en cada trasición.', + 'Task transitions export' => 'Exportar transiciones de tarea', + 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => 'Este informe contiene todos los movimientos de columna para cada tarea con la fecha, el usuario y el tiempo transcurrido en cada transición.', 'Currency rates' => 'Cambio de monedas', 'Rate' => 'Cambio', 'Change reference currency' => 'Cambiar moneda de referencia', 'Add a new currency rate' => 'Añadir nuevo cambio de moneda', 'Reference currency' => 'Moneda de referencia', - 'The currency rate have been added successfully.' => 'Se ha añadido el cambio de moneda correctamente.', + 'The currency rate have been added successfully.' => 'El cambio de moneda se ha añadido correctamente.', 'Unable to add this currency rate.' => 'No se puede añadir este cambio de moneda.', 'Webhook URL' => 'URL del disparador web (webhook)', - '%s remove the assignee of the task %s' => '%s quita el responsable de la tarea %s', + '%s removed the assignee of the task %s' => '%s quita el responsable de la tarea %s', 'Enable Gravatar images' => 'Activar imágenes Gravatar', 'Information' => 'Información', 'Check two factor authentication code' => 'Revisar código de autenticación en dos pasos', @@ -621,89 +601,82 @@ return array( 'Test your device' => 'Probar su dispositivo', 'Assign a color when the task is moved to a specific column' => 'Asignar un color al mover la tarea a una columna específica', '%s via Kanboard' => '%s vía Kanboard', - 'Burndown chart for "%s"' => 'Trabajo pendiente para «%s»', 'Burndown chart' => 'Trabajo pendiente', - 'This chart show the task complexity over the time (Work Remaining).' => 'Este diagrama mestra la complejidad de la tarea a lo largo del tiempo (trabajo restante).', + 'This chart show the task complexity over the time (Work Remaining).' => 'Este diagrama muestra la complejidad de la tarea a lo largo del tiempo (trabajo restante).', 'Screenshot taken %s' => 'Pantallazo tomado el %s', 'Add a screenshot' => 'Añadir un pantallazo', 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Capture un patallazo y pulse CTRL+V o ⌘+V para pegar aquí.', 'Screenshot uploaded successfully.' => 'Pantallazo cargado correctamente.', 'SEK - Swedish Krona' => 'SEK - Corona sueca', 'Identifier' => 'Identificador', - 'Disable two factor authentication' => 'Desactivar la autenticación de dos factores', - 'Do you really want to disable the two factor authentication for this user: "%s"?' => '¿Realmentes quiere desactuvar la autenticación de dos factores para este usuario: "%s?"', + 'Disable two factor authentication' => 'Desactivar la autenticación en dos pasos', + 'Do you really want to disable the two factor authentication for this user: "%s"?' => '¿Realmente desea desactivar la autenticación en dos pasos para este usuario: «%s»?', 'Edit link' => 'Modificar enlace', 'Start to type task title...' => 'Empiece a escribir el título de la tarea...', - 'A task cannot be linked to itself' => 'Una tarea no puede se enlazada con sigo misma', + 'A task cannot be linked to itself' => 'Una tarea no se puede enlazar con sigo misma', 'The exact same link already exists' => 'El mismo enlace ya existe', 'Recurrent task is scheduled to be generated' => 'Tarea recurrente programada para ser generada', 'Score' => 'Puntuación', 'The identifier must be unique' => 'El identificador debe ser único', - 'This linked task id doesn\'t exists' => 'El id de tarea no existe', + 'This linked task id doesn\'t exists' => 'El identificador de tarea no existe', 'This value must be alphanumeric' => 'Este valor debe ser alfanumérico', 'Edit recurrence' => 'Modificar repetición', - 'Generate recurrent task' => 'Generar tarea recurrente', - 'Trigger to generate recurrent task' => 'Disparador para generar tarea recurrente', + 'Generate recurrent task' => 'Generar una tarea recurrente', + 'Trigger to generate recurrent task' => 'Disparador para generar una tarea recurrente', 'Factor to calculate new due date' => 'Factor para calcular la nueva fecha de entrega', 'Timeframe to calculate new due date' => 'Calendario para calcular la nueva fecha de entrega', 'Base date to calculate new due date' => 'Fecha base para calcular la nueva fecha de entrega', 'Action date' => 'Fecha de la acción', - 'Base date to calculate new due date: ' => 'Fecha base para calcular la nueva fecha de entrega:', - 'This task has created this child task: ' => 'Esta tarea ha cerado esta tarea hija:', + 'Base date to calculate new due date: ' => 'Fecha base para calcular la nueva fecha de entrega: ', + 'This task has created this child task: ' => 'Esta tarea ha creado esta tarea hija: ', 'Day(s)' => 'Día(s)', 'Existing due date' => 'Fecha de entrega existente', - 'Factor to calculate new due date: ' => 'Factor para calcular la nueva fecha de entrega:', + 'Factor to calculate new due date: ' => 'Factor para calcular la nueva fecha de entrega: ', 'Month(s)' => 'Mes(es)', 'Recurrence' => 'Repetición', 'This task has been created by: ' => 'Esta tarea ha sido creada por: ', 'Recurrent task has been generated:' => 'Tarea recurrente generada:', - 'Timeframe to calculate new due date: ' => 'Calendario para calcular la nueva fecha de entrega:', - 'Trigger to generate recurrent task: ' => 'Disparador para generar tarea recurrente', + 'Timeframe to calculate new due date: ' => 'Calendario para calcular la nueva fecha de entrega: ', + 'Trigger to generate recurrent task: ' => 'Disparador para generar una tarea recurrente: ', 'When task is closed' => 'Cuando la tarea es cerrada', 'When task is moved from first column' => 'Cuando la tarea es movida desde la primera columna', 'When task is moved to last column' => 'Cuando la tarea es movida a la última columna', 'Year(s)' => 'Año(s)', - 'Calendar settings' => 'Parámetros del Calendario', - 'Project calendar view' => 'Vista de Calendario para el Proyecto', - 'Project settings' => 'Parámetros del Proyecto', - 'Show subtasks based on the time tracking' => 'Mostrar subtareas en base al seguimiento de tiempo', + 'Calendar settings' => 'Preferencias del calendario', + 'Project calendar view' => 'Vista de calendario del proyecto', + 'Project settings' => 'Preferencias del proyecto', + 'Show subtasks based on the time tracking' => 'Mostrar subtareas en base al seguimiento temporal', 'Show tasks based on the creation date' => 'Mostrar tareas en base a la fecha de creación', - 'Show tasks based on the start date' => 'Mostrar tareas en base a la fecha de comienzo', + 'Show tasks based on the start date' => 'Mostrar tareas en base a la fecha de inicio', 'Subtasks time tracking' => 'Seguimiento de tiempo en subtareas', - 'User calendar view' => 'Vista de Calendario para el Usuario', - 'Automatically update the start date' => 'Actualizar automáticamente la fecha de comienzo', + 'User calendar view' => 'Vista de calendario del usuario', + 'Automatically update the start date' => 'Actualizar automáticamente la fecha de inicio', 'iCal feed' => 'Fuente iCal', 'Preferences' => 'Preferencias', 'Security' => 'Seguridad', - 'Two factor authentication disabled' => 'Autenticación de dos factores deshabilitada', - 'Two factor authentication enabled' => 'Autenticación de dos factores habilitada', + 'Two factor authentication disabled' => 'Autenticación en dos pasos deshabilitada', + 'Two factor authentication enabled' => 'Autenticación en dos pasos habilitada', 'Unable to update this user.' => 'No se puede actualizar este usuario.', 'There is no user management for private projects.' => 'No hay gestión de usuarios para proyectos privados.', 'User that will receive the email' => 'Usuario que recibirá el correo', 'Email subject' => 'Asunto del correo', 'Date' => 'Fecha', 'Add a comment log when moving the task between columns' => 'Añadir un comentario al mover la tarea entre columnas', - 'Move the task to another column when the category is changed' => 'Mover la tarea a otra columna cuando cambia la categoría', + 'Move the task to another column when the category is changed' => 'Mover la tarea a otra columna cuando cambie la categoría', 'Send a task by email to someone' => 'Enviar una tarea a alguien por correo', 'Reopen a task' => 'Reabrir tarea', - 'Column change' => 'Cambio de columna', - 'Position change' => 'Cambio de posición', - 'Swimlane change' => 'Cambio de calle', - 'Assignee change' => 'Cambio de responsable', - '[%s] Overdue tasks' => '[%s] Tareas vencidas', 'Notification' => 'Notificación', '%s moved the task #%d to the first swimlane' => '%s movió la tarea #%d a la primera calle', - '%s moved the task #%d to the swimlane "%s"' => '%s movió la tarea #%d a la calle «%s»', 'Swimlane' => 'Calle', 'Gravatar' => 'Gravatar', '%s moved the task %s to the first swimlane' => '%s movió la tarea %s a la primera calle', '%s moved the task %s to the swimlane "%s"' => '%s movió la tarea %s a la calle «%s»', - 'This report contains all subtasks information for the given date range.' => 'Este informe contiene todas la información de las subtareas para el rango proporcionado de fechas.', - 'This report contains all tasks information for the given date range.' => 'Este informe contiene todas la información de las tareas para el rango proporcionado de fechas.', + 'This report contains all subtasks information for the given date range.' => 'Este informe contiene toda la información de las subtareas para el rango de fechas proporcionado.', + 'This report contains all tasks information for the given date range.' => 'Este informe contiene toda la información de las tareas para el rango de fechas proporcionado.', 'Project activities for %s' => 'Actividades del proyecto para %s', 'view the board on Kanboard' => 'ver el tablero en Kanboard', 'The task have been moved to the first swimlane' => 'Se ha movido la tarea a la primera calle', - 'The task have been moved to another swimlane:' => 'Se ha movido la tarea a otra calle', + 'The task have been moved to another swimlane:' => 'Se ha movido la tarea a otra calle:', 'New title: %s' => 'Nuevo título: %s', 'The task is not assigned anymore' => 'La tarea ya no está asignada', 'New assignee: %s' => 'Nuevo responsable: %s', @@ -717,46 +690,46 @@ return array( 'Time spent changed: %sh' => 'Se ha cambiado el tiempo empleado: %sh', 'Time estimated changed: %sh' => 'Se ha cambiado el tiempo estimado: %sh', 'The field "%s" have been updated' => 'Se ha actualizado el campo «%s»', - 'The description has been modified:' => 'Se ha modificado la descripción', + 'The description has been modified:' => 'Se ha modificado la descripción:', 'Do you really want to close the task "%s" as well as all subtasks?' => '¿Realmente desea cerrar la tarea «%s» así como todas las subtareas?', 'I want to receive notifications for:' => 'Deseo recibir notificaciones para:', 'All tasks' => 'Todas las tareas', 'Only for tasks assigned to me' => 'Sólo para las tareas que me han sido asignadas', - 'Only for tasks created by me' => 'Sólo para las taread creadas por mí', - 'Only for tasks created by me and assigned to me' => 'Sólo para las tareas credas por mí y que me han sido asignadas', + 'Only for tasks created by me' => 'Sólo para las tareas creadas por mí', + 'Only for tasks created by me and assigned to me' => 'Sólo para las tareas creadas por mí y que me han sido asignadas', '%%Y-%%m-%%d' => '%%d/%%M/%%Y', 'Total for all columns' => 'Total para todas las columnas', 'You need at least 2 days of data to show the chart.' => 'Necesitas al menos 2 días de datos para mostrar el gráfico.', '<15m' => '<15m', '<30m' => '<30m', 'Stop timer' => 'Parar temporizador', - 'Start timer' => 'Arrancar temporizador', + 'Start timer' => 'Iniciar temporizador', 'Add project member' => 'Añadir miembro al proyecto', 'My activity stream' => 'Mi flujo de actividad', 'My calendar' => 'Mi calendario', 'Search tasks' => 'Buscar tareas', 'Reset filters' => 'Limpiar filtros', 'My tasks due tomorrow' => 'Mis tareas a entregar mañana', - 'Tasks due today' => 'Tareas a antregar hoy', - 'Tasks due tomorrow' => 'Taraes a entregar mañana', + 'Tasks due today' => 'Tareas a entregar hoy', + 'Tasks due tomorrow' => 'Tareas a entregar mañana', 'Tasks due yesterday' => 'Tareas a entregar ayer', 'Closed tasks' => 'Tareas cerradas', 'Open tasks' => 'Tareas abiertas', 'Not assigned' => 'No asignada', - 'View advanced search syntax' => 'Ver sintáxis avanzada de búsqueda', + 'View advanced search syntax' => 'Ver sintaxis de búsqueda avanzada', 'Overview' => 'Resumen', - 'Board/Calendar/List view' => 'Vista de Tablero/Calendario/Lista', + 'Board/Calendar/List view' => 'Vista de tablero/calendario/lista', 'Switch to the board view' => 'Cambiar a vista de tablero', 'Switch to the calendar view' => 'Cambiar a vista de calendario', 'Switch to the list view' => 'Cambiar a vista de lista', 'Go to the search/filter box' => 'Ir a caja de buscar/filtrar', 'There is no activity yet.' => 'Aún no hay actividades.', - 'No tasks found.' => 'No se ha hallado tarea alguna.', + 'No tasks found.' => 'No se ha encontrado ninguna tarea.', 'Keyboard shortcut: "%s"' => 'Atajo de teclado: %s', 'List' => 'Lista', 'Filter' => 'Filtro', 'Advanced search' => 'Búsqueda avanzada', - 'Example of query: ' => 'Ejemplo de query: ', + 'Example of query: ' => 'Ejemplo de consulta: ', 'Search by project: ' => 'Buscar por proyecto: ', 'Search by column: ' => 'Buscar por columna: ', 'Search by assignee: ' => 'Buscar por responsable: ', @@ -764,36 +737,34 @@ return array( 'Search by category: ' => 'Buscar por categoría: ', 'Search by description: ' => 'Buscar por descripción: ', 'Search by due date: ' => 'Buscar por fecha de entrega: ', - 'Lead and Cycle time for "%s"' => 'Plazo de Entrega y Ciclo para «%s»', - 'Average time spent into each column for "%s"' => 'Tiempo medio empleado en cada columna para «%s»', 'Average time spent into each column' => 'Tiempo medio empleado en cada columna', 'Average time spent' => 'Tiempo medio empleado', 'This chart show the average time spent into each column for the last %d tasks.' => 'Esta gráfica muestra el tiempo medio empleado en cada columna para las últimas %d tareas.', - 'Average Lead and Cycle time' => 'Plazo Medio de Entrega y de Ciclo', - 'Average lead time: ' => 'Plazo Medio de entrega: ', - 'Average cycle time: ' => 'Tiempo Medio de Ciclo: ', - 'Cycle Time' => 'Tiempo de Ciclo', - 'Lead Time' => 'Plazo de Entrega', - 'This chart show the average lead and cycle time for the last %d tasks over the time.' => 'Esta gráfica muestra el plazo medio de entrega y de ciclo para las %d últimas tareas transcurridas.', + 'Average Lead and Cycle time' => 'Plazo medio de entrega y de ciclo', + 'Average lead time: ' => 'Plazo medio de entrega: ', + 'Average cycle time: ' => 'Tiempo medio de ciclo: ', + 'Cycle Time' => 'Tiempo de ciclo', + 'Lead Time' => 'Plazo de entrega', + 'This chart show the average lead and cycle time for the last %d tasks over the time.' => 'Esta gráfica muestra el plazo medio de entrega y de ciclo para las %d últimas tareas.', 'Average time into each column' => 'Tiempo medio en cada columna', 'Lead and cycle time' => 'Plazo de entrega y de ciclo', 'Lead time: ' => 'Plazo de entrega: ', - 'Cycle time: ' => 'Tiempo de Ciclo: ', + 'Cycle time: ' => 'Tiempo de ciclo: ', 'Time spent into each column' => 'Tiempo empleado en cada columna', - 'The lead time is the duration between the task creation and the completion.' => 'El plazo de entrega es la duración entre la creación de la tarea su terminación.', - 'The cycle time is the duration between the start date and the completion.' => 'El tiempo de ciclo es la duración entre la fecha de inicio y su terminación.', - 'If the task is not closed the current time is used instead of the completion date.' => 'Si la tarea no se cierra, se usa la fecha actual en lugar de la de terminación.', + 'The lead time is the duration between the task creation and the completion.' => 'El plazo de entrega es la duración entre la creación de la tarea su finalización.', + 'The cycle time is the duration between the start date and the completion.' => 'El tiempo de ciclo es la duración entre la fecha de inicio y su finalización.', + 'If the task is not closed the current time is used instead of the completion date.' => 'Si la tarea no se cierra, se usa la fecha actual en lugar de la de finalización.', 'Set automatically the start date' => 'Poner la fecha de inicio de forma automática', 'Edit Authentication' => 'Modificar autenticación', 'Remote user' => 'Usuario remoto', - 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'Los usuarios remotos no almacenan sus contraseñas en la base de datos Kanboard, por ejemplo: cuentas de LDAP, Google y Github', - 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Si marcas la caja de edición "Desactivar formulario de ingreso", se ignoran las credenciales entradas en el formulario de ingreso.', + 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'Los usuarios remotos no almacenan sus contraseñas en la base de datos Kanboard, por ejemplo: cuentas de LDAP, Google y Github.', + 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Si marcas la opción "Desactivar formulario de ingreso", se ignoran las credenciales introducidas en el formulario de ingreso.', 'New remote user' => 'Nuevo usuario remoto', 'New local user' => 'Nuevo usuario local', - 'Default task color' => 'Color por defecto de tarea', - 'This feature does not work with all browsers.' => 'Esta característica no funciona con todos los navegadores', - 'There is no destination project available.' => 'No está disponible proyecto destino', - 'Trigger automatically subtask time tracking' => 'Disparar de forma automática seguimiento temporal de subtarea', + 'Default task color' => 'Color de la tarea por defecto', + 'This feature does not work with all browsers.' => 'Esta característica no funciona en todos los navegadores.', + 'There is no destination project available.' => 'No está disponible proyecto de destino.', + 'Trigger automatically subtask time tracking' => 'Disparar de forma automática el seguimiento temporal de subtarea', 'Include closed tasks in the cumulative flow diagram' => 'Incluir tareas cerradas en el diagrama de flujo acumulado', 'Current swimlane: %s' => 'Calle en curso: %s', 'Current column: %s' => 'Columna en curso: %s', @@ -805,20 +776,19 @@ return array( 'contributors' => 'contribuyentes', 'License:' => 'Licencia:', 'License' => 'Licencia', - 'Enter the text below' => 'Digita el texto de abajo', - 'Gantt chart for %s' => 'Diagrama de Gantt para %s', - 'Sort by position' => 'Clasificado mediante posición', - 'Sort by date' => 'Clasificado mediante fecha', + 'Enter the text below' => 'Introduzca el texto a continuación', + 'Sort by position' => 'Ordenar por posición', + 'Sort by date' => 'Ordenar por fecha', 'Add task' => 'Añadir tarea', 'Start date:' => 'Fecha de inicio:', 'Due date:' => 'Fecha de entrega:', 'There is no start date or due date for this task.' => 'No hay fecha de inicio o de entrega para esta tarea.', - 'Moving or resizing a task will change the start and due date of the task.' => 'El mover o redimensionar una tarea cambiará la fecha inicio y de entrega de la misma.', + 'Moving or resizing a task will change the start and due date of the task.' => 'Mover o redimensionar una tarea cambiará la fecha inicio y de entrega de la misma.', 'There is no task in your project.' => 'No hay tareas en su proyecto.', - 'Gantt chart' => 'Digrama de Gantt', - 'People who are project managers' => 'Usuarios que son administradores de proyecto', - 'People who are project members' => 'Usuarios que son miembros de proyecto', - 'NOK - Norwegian Krone' => 'NOK - Coronoa Noruega', + 'Gantt chart' => 'Diagrama de Gantt', + 'People who are project managers' => 'Usuarios que son administradores del proyecto', + 'People who are project members' => 'Usuarios que son miembros del proyecto', + 'NOK - Norwegian Krone' => 'NOK - Corona Noruega', 'Show this column' => 'Mostrar esta columna', 'Hide this column' => 'Ocultar esta columna', 'open file' => 'abrir fichero', @@ -826,80 +796,78 @@ return array( 'Users overview' => 'Resumen de usuarios', 'Members' => 'Miembros', 'Shared project' => 'Proyecto compartido', - 'Project managers' => 'Administradores de proyecto', + 'Project managers' => 'Administradores del proyecto', 'Gantt chart for all projects' => 'Diagrama de Gantt para todos los proyectos', 'Projects list' => 'Lista de proyectos', 'Gantt chart for this project' => 'Diagrama de Gantt para este proyecto', 'Project board' => 'Tablero del proyecto', - 'End date:' => 'Fecha final', + 'End date:' => 'Fecha de fin:', 'There is no start date or end date for this project.' => 'No existe fecha de inicio o de fin para este proyecto.', - 'Projects Gantt chart' => 'Diagramas de Gantt de los proyectos', - 'Change task color when using a specific task link' => 'Cambiar colo de la tarea al usar un enlace específico a tarea', - 'Task link creation or modification' => 'Creación o modificación de enlace a tarea', + 'Projects Gantt chart' => 'Diagrama de Gantt de los proyectos', + 'Change task color when using a specific task link' => 'Cambiar el color de la tarea al usar un enlace específico a tarea', + 'Task link creation or modification' => 'Creación o modificación del enlace a tarea', 'Milestone' => 'Hito', 'Documentation: %s' => 'Documentación: %s', - 'Switch to the Gantt chart view' => 'Conmutar a vista de diagrama de Gantt', - 'Reset the search/filter box' => 'Limpiar la caja del filtro de búsqueda', + 'Switch to the Gantt chart view' => 'Cambiar a vista de diagrama de Gantt', + 'Reset the search/filter box' => 'Limpiar el filtro de búsqueda', 'Documentation' => 'Documentación', 'Table of contents' => 'Tabla de contenido', 'Gantt' => 'Gantt', 'Author' => 'Autor', 'Version' => 'Versión', 'Plugins' => 'Plugins', - 'There is no plugin loaded.' => 'No hay ningún plugin cargado', - 'Set maximum column height' => 'Establecer altura máxima de la columna', - 'Remove maximum column height' => 'Eliminar altura máxima de la columna', + 'There is no plugin loaded.' => 'No hay ningún plugin cargado.', 'My notifications' => 'Mis notificaciones', 'Custom filters' => 'Filtros personalizados', 'Your custom filter have been created successfully.' => 'Tus filtros personalizados han sido creados correctamente.', 'Unable to create your custom filter.' => 'No se ha podido crear tu filtro personalizado.', - 'Custom filter removed successfully.' => 'Filtro personalizado ha sido eliminado correctamente.', - 'Unable to remove this custom filter.' => 'No se ha podido eliminar tu filtro personalizado', + 'Custom filter removed successfully.' => 'El filtro personalizado ha sido eliminado correctamente.', + 'Unable to remove this custom filter.' => 'No se ha podido eliminar tu filtro personalizado.', 'Edit custom filter' => 'Modificar filtro personalizado', 'Your custom filter have been updated successfully.' => 'Tu filtro personalizado ha sido actualizado correctamente.', - 'Unable to update custom filter.' => 'No se ha podido actualizar tu filtro personalizado', + 'Unable to update custom filter.' => 'No se ha podido actualizar tu filtro personalizado.', 'Web' => 'Web', 'New attachment on task #%d: %s' => 'Nuevo adjunto en la tarea #%d: %s', 'New comment on task #%d' => 'Nuevo comentario en la tarea #%d', 'Comment updated on task #%d' => 'Comentario actualizado en la tarea #%d', 'New subtask on task #%d' => 'Nueva subtarea en la tarea #%d', - 'Subtask updated on task #%d' => 'La subtarea en la tarea #%d ha sido actualizada', + 'Subtask updated on task #%d' => 'Subtarea actualizada en la tarea #%d', 'New task #%d: %s' => 'Nueva tarea #%d: %s', 'Task updated #%d' => 'Tarea actualizada #%d', - 'Task #%d closed' => 'Tarea #%d ha sido cerrada', - 'Task #%d opened' => 'Tarea #%d ha sido abierta', - 'Column changed for task #%d' => 'Columna para tarea #%d ha sido cambiada', + 'Task #%d closed' => 'Tarea #%d cerrada', + 'Task #%d opened' => 'Tarea #%d abierta', + 'Column changed for task #%d' => 'Columna cambiada para la tarea #%d', 'New position for task #%d' => 'Nueva posición para tarea #%d', - 'Swimlane changed for task #%d' => 'Se cambió el swimlane de la tarea #%d', - 'Assignee changed on task #%d' => 'Se cambió el asignado de la tarea #%d', + 'Swimlane changed for task #%d' => 'Se cambió la calle de la tarea #%d', + 'Assignee changed on task #%d' => 'Se cambió el responsable de la tarea #%d', '%d overdue tasks' => '%d tareas atrasadas', 'Task #%d is overdue' => 'La tarea #%d está atrasada', - 'No new notifications.' => 'No hay nuevas notificaciones', + 'No new notifications.' => 'No hay nuevas notificaciones.', 'Mark all as read' => 'Marcar todo como leído', 'Mark as read' => 'Marcar como leído', - 'Total number of tasks in this column across all swimlanes' => 'Número total de tareas en esta columna por todas las swimlanes', - 'Collapse swimlane' => 'Contraer swimlane', - 'Expand swimlane' => 'Ampliar swimlane', + 'Total number of tasks in this column across all swimlanes' => 'Número total de tareas en esta columna a través de todas las calles', + 'Collapse swimlane' => 'Contraer calle', + 'Expand swimlane' => 'Ampliar calle', 'Add a new filter' => 'Añadir nuevo filtro', 'Share with all project members' => 'Compartir con todos los miembros del proyecto', 'Shared' => 'Compartido', - 'Owner' => 'Dueño', + 'Owner' => 'Propietario', 'Unread notifications' => 'Notificaciones sin leer', - 'Notification methods:' => 'Métodos de notificación', + 'Notification methods:' => 'Métodos de notificación:', 'Import tasks from CSV file' => 'Importar tareas desde archivo CSV', 'Unable to read your file' => 'No es posible leer el archivo', - '%d task(s) have been imported successfully.' => '%d tarea(s) han sido importadas correctamente', - 'Nothing have been imported!' => 'No se ha importado nada!', + '%d task(s) have been imported successfully.' => '%d tarea(s) han sido importadas correctamente.', + 'Nothing have been imported!' => '¡No se ha importado nada!', 'Import users from CSV file' => 'Importar usuarios desde archivo CSV', - '%d user(s) have been imported successfully.' => '%d usuario(s) se han importado correctamente', + '%d user(s) have been imported successfully.' => '%d usuario(s) se han importado correctamente.', 'Comma' => 'Coma', 'Semi-colon' => 'Punto y coma', 'Tab' => 'Tabulación', - 'Vertical bar' => 'Pleca', + 'Vertical bar' => 'Barra vertical', 'Double Quote' => 'Comilla doble', - 'Single Quote' => 'Comilla sencilla', - '%s attached a file to the task #%d' => '%s adjuntó un archivo a la tarea #%d', - 'There is no column or swimlane activated in your project!' => 'No hay ninguna columna o swimlane activada en su proyecto!', + 'Single Quote' => 'Comilla simple', + '%s attached a file to the task #%d' => '%s adjuntó un archivo en la tarea #%d', + 'There is no column or swimlane activated in your project!' => '¡No hay ninguna columna o calle activada en su proyecto!', 'Append filter (instead of replacement)' => 'Añadir filtro (en vez de reemplazar)', 'Append/Replace' => 'Añadir/Reemplazar', 'Append' => 'Añadir', @@ -912,92 +880,88 @@ return array( 'CSV File' => 'Archivo CSV', 'Instructions' => 'Indicaciones', 'Your file must use the predefined CSV format' => 'Su archivo debe utilizar el formato CSV predeterminado', - 'Your file must be encoded in UTF-8' => 'Su archivo debe ser codificado en UTF-8', + 'Your file must be encoded in UTF-8' => 'Su archivo debe estar codificado en UTF-8', 'The first row must be the header' => 'La primera fila debe ser el encabezado', 'Duplicates are not verified for you' => 'Los duplicados no serán verificados', 'The due date must use the ISO format: YYYY-MM-DD' => 'La fecha de entrega debe utilizar el formato ISO: AAAA-MM-DD', 'Download CSV template' => 'Descargar plantilla CSV', - 'No external integration registered.' => 'No se ha registrado integración externa', + 'No external integration registered.' => 'No se ha registrado integración externa.', 'Duplicates are not imported' => 'Los duplicados no son importados', - 'Usernames must be lowercase and unique' => 'Los nombres de usuario deben ser únicos y contener sólo minúsculas', + 'Usernames must be lowercase and unique' => 'Los nombres de usuario deben ser únicos y en minúsculas', 'Passwords will be encrypted if present' => 'Las contraseñas serán cifradas si es que existen', - '%s attached a new file to the task %s' => '%s adjuntó un nuevo archivo a la tarea %s', + '%s attached a new file to the task %s' => '%s adjuntó un nuevo archivo en la tarea %s', 'Link type' => 'Tipo de enlace', 'Assign automatically a category based on a link' => 'Asignar una categoría automáticamente basado en un enlace', - 'BAM - Konvertible Mark' => 'BAM - marco convertible', + 'BAM - Konvertible Mark' => 'BAM - Marco convertible', 'Assignee Username' => 'Nombre de usuario del responsable', 'Assignee Name' => 'Nombre del responsable', 'Groups' => 'Grupos', 'Members of %s' => 'Miembros de %s', 'New group' => 'Nuevo grupo', - 'Group created successfully.' => 'Grupo creado correctamente', - 'Unable to create your group.' => 'No es posible crear el grupo', + 'Group created successfully.' => 'Grupo creado correctamente.', + 'Unable to create your group.' => 'No es posible crear el grupo.', 'Edit group' => 'Modificar grupo', - 'Group updated successfully.' => 'Grupo actualizado correctamente', - 'Unable to update your group.' => 'No es posible actualizar el grupo', + 'Group updated successfully.' => 'Grupo actualizado correctamente.', + 'Unable to update your group.' => 'No es posible actualizar el grupo.', 'Add group member to "%s"' => 'Añadir un miembro del grupo a «%s»', - 'Group member added successfully.' => 'Miembro del grupo añadido correctamente', - 'Unable to add group member.' => 'No es posible añadir miembro del grupo', + 'Group member added successfully.' => 'Miembro del grupo añadido correctamente.', + 'Unable to add group member.' => 'No es posible añadir el miembro del grupo.', 'Remove user from group "%s"' => 'Eliminar usuario del grupo «%s»', - 'User removed successfully from this group.' => 'Usuario eliminado correctamente del grupo', - 'Unable to remove this user from the group.' => 'No es posible eliminar este usuario del grupo', + 'User removed successfully from this group.' => 'Usuario eliminado correctamente del grupo.', + 'Unable to remove this user from the group.' => 'No es posible eliminar este usuario del grupo.', 'Remove group' => 'Eliminar grupo', - 'Group removed successfully.' => 'Grupo eliminado correctamente', - 'Unable to remove this group.' => 'No es posible eliminar este grupo', + 'Group removed successfully.' => 'Grupo eliminado correctamente.', + 'Unable to remove this group.' => 'No es posible eliminar este grupo.', 'Project Permissions' => 'Permisos del proyecto', 'Manager' => 'Administrador', - 'Project Manager' => 'Administrador de proyecto', + 'Project Manager' => 'Administrador del proyecto', 'Project Member' => 'Miembro del proyecto', - 'Project Viewer' => 'Visor de proyectos', - 'Your account is locked for %d minutes' => 'Tu cuenta ha sido bloqueada por %d minuto(s)', + 'Project Viewer' => 'Observador del proyecto', + 'Your account is locked for %d minutes' => 'Tu cuenta ha sido bloqueada durante %d minutos', 'Invalid captcha' => 'CAPTCHA inválido', 'The name must be unique' => 'El nombre debe ser único', 'View all groups' => 'Ver todos los grupos', - 'View group members' => 'Ver miembros del grupo', - 'There is no user available.' => 'No hay usuario disponible', - 'Do you really want to remove the user "%s" from the group "%s"?' => '¿Realmente desea eliminar el usuario "%s" del grupo «%s»?', - 'There is no group.' => 'No hay grupo', - 'External Id' => 'ID externo', - 'Add group member' => 'Añadir un miembro de grupo', + 'There is no user available.' => 'No hay usuario disponible.', + 'Do you really want to remove the user "%s" from the group "%s"?' => '¿Realmente desea eliminar el usuario «%s» del grupo «%s»?', + 'There is no group.' => 'No hay grupo.', + 'External Id' => 'Identificador externo', + 'Add group member' => 'Añadir un miembro al grupo', 'Do you really want to remove this group: "%s"?' => '¿Realmente desea eliminar este grupo: «%s»?', - 'There is no user in this group.' => 'No hay usuario en este grupo', + 'There is no user in this group.' => 'No hay usuario en este grupo.', 'Remove this user' => 'Eliminar este usuario', 'Permissions' => 'Permisos', 'Allowed Users' => 'Usuarios permitidos', - 'No user have been allowed specifically.' => 'Ningun usuario ha sido explícitamente permitido', + 'No user have been allowed specifically.' => 'Ningún usuario ha sido explícitamente permitido.', 'Role' => 'Rol', 'Enter user name...' => 'Ingresa nombre de usuario...', 'Allowed Groups' => 'Grupos permitidos', - 'No group have been allowed specifically.' => 'Ningun grupo ha sido explícitamente permitido', + 'No group have been allowed specifically.' => 'Ningún grupo ha sido explícitamente permitido.', 'Group' => 'Grupo', 'Group Name' => 'Nombre del grupo', 'Enter group name...' => 'Ingresa el nombre del grupo...', 'Role:' => 'Rol:', 'Project members' => 'Miembros del proyecto', - 'Compare hours for "%s"' => 'Compara horas con «%s»', '%s mentioned you in the task #%d' => '%s te mencionó en la tarea #%d', '%s mentioned you in a comment on the task #%d' => '%s te mencionó en un comentario en la tarea #%d', - 'You were mentioned in the task #%d' => 'Te mencionaron en la tarea #%d', - 'You were mentioned in a comment on the task #%d' => 'Te mencionaron en un comentario en la tarea #%d', - 'Mentioned' => 'Mencionado', - 'Compare Estimated Time vs Actual Time' => 'Comparar Tiempo Estimado vs Tiempo Actual', + 'You were mentioned in the task #%d' => 'Fuiste mencionado en la tarea #%d', + 'You were mentioned in a comment on the task #%d' => 'Fuiste mencionado en un comentario de la tarea #%d', 'Estimated hours: ' => 'Horas estimadas: ', 'Actual hours: ' => 'Horas actuales: ', 'Hours Spent' => 'Horas gastadas', - 'Hours Estimated' => 'Hora Estimada', - 'Estimated Time' => 'Tiempo Estimado', - 'Actual Time' => 'Tiempo Actual', + 'Hours Estimated' => 'Horas estimadas', + 'Estimated Time' => 'Tiempo estimado', + 'Actual Time' => 'Tiempo actual', 'Estimated vs actual time' => 'Tiempo estimado vs real', - 'RUB - Russian Ruble' => 'RUB - rublo ruso', - 'Assign the task to the person who does the action when the column is changed' => 'Asignar la tarea a la persona que haga la acción al cambiar de columna', + 'RUB - Russian Ruble' => 'RUB - Rublo ruso', + 'Assign the task to the person who does the action when the column is changed' => 'Asignar la tarea a la persona que hace la acción al cambiar de columna', 'Close a task in a specific column' => 'Cerrar tarea en una columna especifica', 'Time-based One-time Password Algorithm' => 'Algoritmo basado en tiempo de un solo uso', - 'Two-Factor Provider: ' => 'Proveedor de autenticación de dos factores', - 'Disable two-factor authentication' => 'Deshabilitar autenticación de dos factores', - 'Enable two-factor authentication' => 'Habilitar autenticación de dos factorse', - 'There is no integration registered at the moment.' => 'No hay ninguna integración registrada por el momento', + 'Two-Factor Provider: ' => 'Proveedor de autenticación en dos pasos: ', + 'Disable two-factor authentication' => 'Deshabilitar autenticación en dos pasos', + 'Enable two-factor authentication' => 'Habilitar autenticación en dos pasos', + 'There is no integration registered at the moment.' => 'No hay ninguna integración registrada por el momento.', 'Password Reset for Kanboard' => 'Restablecimiento de contraseña para Kanboard', - 'Forgot password?' => '¿Olvidó contraseña?', + 'Forgot password?' => '¿Olvidó la contraseña?', 'Enable "Forget Password"' => 'Habilitar "olvidar contraseña"', 'Password Reset' => 'Restablecer contraseña', 'New password' => 'Nueva contraseña', @@ -1008,19 +972,19 @@ return array( 'Creation' => 'Creación', 'Expiration' => 'Vencimiento', 'Password reset history' => 'Historial de restablecimiento de contraseña', - 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => 'Todas las tareas de la columna "%s" y el swimlane «%s» se han cerrado correctamente', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => 'Todas las tareas de la columna "%s" y la calle «%s» se han cerrado correctamente.', 'Do you really want to close all tasks of this column?' => '¿Realmente desea cerrar todas las tareas de esta columna?', - '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '%d tarea(s) en la columna "%s" y el swimlane «%s» será(n) cerrada(s)', + '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '%d tarea(s) en la columna "%s" y en la calle «%s» será(n) cerrada(s).', 'Close all tasks of this column' => 'Cerrar todas las tareas de esta columna', - 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'Ningún plugin ha registrado un método de notificación para el proyecto. Aún puedes configurar notificaciones individuales en tu perfil de usuario', + 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'Ningún plugin ha registrado un método de notificación para el proyecto. Aún puedes configurar notificaciones individuales en tu perfil de usuario.', 'My dashboard' => 'Mi tablero', 'My profile' => 'Mi perfil', - 'Project owner: ' => 'Dueño del proyecto', - 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'El identificador de proyecto es opcional y debe ser alfanumérico. Ejemplo: MIPROYECTO', - 'Project owner' => 'Dueño del proyecto', - 'Those dates are useful for the project Gantt chart.' => 'Esas fechas son útiles para el diagrama de Gantt', - 'Private projects do not have users and groups management.' => 'Proyectos privados no cuentan con gestión de usuarios y grupos', - 'There is no project member.' => 'No existe miembro del proyecto', + 'Project owner: ' => 'Propietario del proyecto: ', + 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'El identificador del proyecto es opcional y debe ser alfanumérico. Ejemplo: MIPROYECTO', + 'Project owner' => 'Propietario del proyecto', + 'Those dates are useful for the project Gantt chart.' => 'Esas fechas son útiles para el diagrama de Gantt.', + 'Private projects do not have users and groups management.' => 'Los proyectos privados no cuentan con gestión de usuarios y grupos.', + 'There is no project member.' => 'No existe miembro del proyecto.', 'Priority' => 'Prioridad', 'Task priority' => 'Prioridad de la tarea', 'General' => 'General', @@ -1028,16 +992,16 @@ return array( 'Default priority' => 'Prioridad predeterminada', 'Lowest priority' => 'Prioridad más baja', 'Highest priority' => 'Prioridad más alta', - 'If you put zero to the low and high priority, this feature will be disabled.' => 'Si estableces la prioridad más baja y alta como cero esta función será deshabilitada', + 'If you put zero to the low and high priority, this feature will be disabled.' => 'Si estableces la prioridad más baja y más alta a cero esta función será deshabilitada.', 'Close a task when there is no activity' => 'Cerrar tarea cuando no haya actividad', 'Duration in days' => 'Duración en días', 'Send email when there is no activity on a task' => 'Enviar correo cuando no haya actividad en una tarea', - 'Unable to fetch link information.' => 'No es posible obtener información sobre el enlace', - 'Daily background job for tasks' => 'Tarea de fondo diaria para las tareas', + 'Unable to fetch link information.' => 'No es posible obtener información del enlace.', + 'Daily background job for tasks' => 'Trabajo en segundo plano diario para las tareas', 'Auto' => 'Automático', 'Related' => 'Relacionado', 'Attachment' => 'Adjunto', - 'Title not found' => 'Título no ha sido encontrado', + 'Title not found' => 'No se ha encontrado el título', 'Web Link' => 'Enlace web', 'External links' => 'Enlaces externos', 'Add external link' => 'Añadir enlace externo', @@ -1050,50 +1014,50 @@ return array( 'Copy and paste your link here...' => 'Copia y pega tu enlace aquí...', 'URL' => 'URL', 'Internal links' => 'Enlaces internos', - 'Assign to me' => 'Asignar a mí', + 'Assign to me' => 'Asignarme a mí', 'Me' => 'Yo', 'Do not duplicate anything' => 'No duplicar nada', 'Projects management' => 'Administración de proyectos', 'Users management' => 'Administración de usuarios', 'Groups management' => 'Administración de grupos', - 'Create from another project' => 'Crear de otro proyecto', + 'Create from another project' => 'Crear a partir de otro proyecto', 'open' => 'abierto', 'closed' => 'cerrado', - 'Priority:' => 'Prioridad', - 'Reference:' => 'Referencia', + 'Priority:' => 'Prioridad:', + 'Reference:' => 'Referencia:', 'Complexity:' => 'Complejidad:', - 'Swimlane:' => 'Swimlane:', + 'Swimlane:' => 'Calle:', 'Column:' => 'Columna:', 'Position:' => 'Posición:', 'Creator:' => 'Creador:', 'Time estimated:' => 'Tiempo estimado:', '%s hours' => '%s horas', 'Time spent:' => 'Tiempo gastado:', - 'Created:' => 'Creado', - 'Modified:' => 'Modificado', - 'Completed:' => 'Terminado', - 'Started:' => 'Iniciado', - 'Moved:' => 'Movido', + 'Created:' => 'Creado:', + 'Modified:' => 'Modificado:', + 'Completed:' => 'Finalizado:', + 'Started:' => 'Iniciado:', + 'Moved:' => 'Movido:', 'Task #%d' => 'Tarea #%d', - 'Date and time format' => 'Formato de hora y fecha', + 'Date and time format' => 'Formato de fecha y hora', 'Time format' => 'Formato de hora', - 'Start date: ' => 'Fecha de inicio', - 'End date: ' => 'Fecha de terminación', - 'New due date: ' => 'Nueva fecha de entrega', - 'Start date changed: ' => 'Fecha de inicio cambiada', + 'Start date: ' => 'Fecha de inicio: ', + 'End date: ' => 'Fecha de finalización: ', + 'New due date: ' => 'Nueva fecha de entrega: ', + 'Start date changed: ' => 'Fecha de inicio cambiada: ', 'Disable private projects' => 'Deshabilitar proyectos privados', 'Do you really want to remove this custom filter: "%s"?' => '¿Realmente desea eliminar este filtro personalizado: «%s»?', - 'Remove a custom filter' => 'Eliminar filtro personalizado', - 'User activated successfully.' => 'Usuario activado correctamente', - 'Unable to enable this user.' => 'No es posible habilitar este usuario', - 'User disabled successfully.' => 'Usuario deshabilitado correctamente', - 'Unable to disable this user.' => 'No es posible deshabilitar este usuario', - 'All files have been uploaded successfully.' => 'Todos los archivos han sido subidos correctamente', - 'View uploaded files' => 'Ver archivos subidos', - 'The maximum allowed file size is %sB.' => 'El límite de tamaño de archivo permitido para subir es %sB.', - 'Choose files again' => 'Eligir archivos de nuevo', + 'Remove a custom filter' => 'Eliminar el filtro personalizado', + 'User activated successfully.' => 'Usuario activado correctamente.', + 'Unable to enable this user.' => 'No es posible habilitar este usuario.', + 'User disabled successfully.' => 'Usuario deshabilitado correctamente.', + 'Unable to disable this user.' => 'No es posible deshabilitar este usuario.', + 'All files have been uploaded successfully.' => 'Todos los archivos han sido cargados correctamente.', + 'View uploaded files' => 'Ver archivos cargados', + 'The maximum allowed file size is %sB.' => 'El tamaño máximo de archivo es %sB.', + 'Choose files again' => 'Elegir archivos de nuevo', 'Drag and drop your files here' => 'Arrastra y suelta tus archivos aquí', - 'choose files' => 'Elegir archivos', + 'choose files' => 'elegir archivos', 'View profile' => 'Ver perfil', 'Two Factor' => 'Dos factores', 'Disable user' => 'Deshabilitar usuario', @@ -1113,16 +1077,16 @@ return array( 'Change column position' => 'Cambiar posición de la columna', 'Switch to the project overview' => 'Cambiar a vista general del proyecto', 'User filters' => 'Usar filtros', - 'Category filters' => 'Categoría y filtros', - 'Upload a file' => 'Subir archivo', + 'Category filters' => 'Filtros de categoría', + 'Upload a file' => 'Cargar archivo', 'View file' => 'Ver archivo', 'Last activity' => 'Última actividad', 'Change subtask position' => 'Cambiar posición de la subtarea', 'This value must be greater than %d' => 'Este valor debe ser mayor que %d', - 'Another swimlane with the same name exists in the project' => 'Ya existe otro swimlane con el mismo nombre en el proyecto', - 'Example: http://example.kanboard.net/ (used to generate absolute URLs)' => 'Ejemplo: http://ejemplo.kanboard.net/ (Usado para generar URLs absolutas)', - 'Actions duplicated successfully.' => 'Acción duplicada con exito.', - 'Unable to duplicate actions.' => 'No se ha podido duplicar la acción.', + 'Another swimlane with the same name exists in the project' => 'Ya existe otra calle con el mismo nombre en el proyecto', + 'Example: http://example.kanboard.net/ (used to generate absolute URLs)' => 'Ejemplo: http://ejemplo.kanboard.net/ (usado para generar URLs absolutas)', + 'Actions duplicated successfully.' => 'Acciones duplicadas con éxito.', + 'Unable to duplicate actions.' => 'No se han podido duplicar las acciones.', 'Add a new action' => 'Añadir una nueva acción', 'Import from another project' => 'Importar de otro proyecto', 'There is no action at the moment.' => 'No hay ninguna acción en este momento.', @@ -1202,4 +1166,52 @@ return array( 'Email transport' => 'Transporte de correo electrónico', 'Webhook token' => 'Token del disparador web (webhook)', 'Imports' => 'Importaciones', + // 'Project tags management' => '', + // 'Tag created successfully.' => '', + // 'Unable to create this tag.' => '', + // 'Tag updated successfully.' => '', + // 'Unable to update this tag.' => '', + // 'Tag removed successfully.' => '', + // 'Unable to remove this tag.' => '', + // 'Global tags management' => '', + // 'Tags' => '', + // 'Tags management' => '', + // 'Add new tag' => '', + // 'Edit a tag' => '', + // 'Project tags' => '', + // 'There is no specific tag for this project at the moment.' => '', + // 'Tag' => '', + // 'Remove a tag' => '', + // 'Do you really want to remove this tag: "%s"?' => '', + // 'Global tags' => '', + // 'There is no global tag at the moment.' => '', + // 'This field cannot be empty' => '', + // 'Close a task when there is no activity in an specific column' => '', + // '%s removed a subtask for the task #%d' => '', + // '%s removed a comment on the task #%d' => '', + // 'Comment removed on task #%d' => '', + // 'Subtask removed on task #%d' => '', + // 'Hide tasks in this column in the dashboard' => '', + // '%s removed a comment on the task %s' => '', + // '%s removed a subtask for the task %s' => '', + // 'Comment removed' => '', + // 'Subtask removed' => '', + // '%s set a new internal link for the task #%d' => '', + // '%s removed an internal link for the task #%d' => '', + // 'A new internal link for the task #%d have been defined' => '', + // 'Internal link removed for the task #%d' => '', + // '%s set a new internal link for the task %s' => '', + // '%s removed an internal link for the task %s' => '', + // 'Automatically set the due date on task creation' => '', + // 'Move the task to another column when closed' => '', + // 'Move the task to another column when not moved during a given period' => '', + // 'Dashboard for %s' => '', + // 'Tasks overview for %s' => '', + // 'Subtasks overview for %s' => '', + // 'Projects overview for %s' => '', + // 'Activity stream for %s' => '', + // 'Calendar for %s' => '', + // 'Notifications for %s' => '', + // 'Subtasks export' => '', + // 'Tasks exportation' => '', ); diff --git a/app/Locale/fi_FI/translations.php b/app/Locale/fi_FI/translations.php index 8fbb0ba9..ed38fb56 100644 --- a/app/Locale/fi_FI/translations.php +++ b/app/Locale/fi_FI/translations.php @@ -154,8 +154,6 @@ return array( '%d closed tasks' => '%d suljettua tehtävää', 'No task for this project' => 'Ei tehtävää tälle projektille', 'Public link' => 'Julkinen linkki', - 'Change assignee' => 'Vaihda suorittajaa', - 'Change assignee for the task "%s"' => 'Vaihda suorittajaa tehtävälle %s', 'Timezone' => 'Aikavyöhyke', 'Sorry, I didn\'t find this information in my database!' => 'Anteeksi, en löytänyt tätä tietoa tietokannastani', 'Page not found' => 'Sivua ei löydy', @@ -248,7 +246,6 @@ return array( 'Category' => 'Kategoria', 'Category:' => 'Kategoria:', 'Categories' => 'Kategoriat', - 'Category not found.' => 'Kategoriaa ei löytynyt.', 'Your category have been created successfully.' => 'Kategoria luotiin onnistuneesti.', 'Unable to create your category.' => 'Kategorian luonti epäonnistui.', 'Your category have been updated successfully.' => 'Kategoriaa muokattiin onnistuneesti.', @@ -270,7 +267,6 @@ return array( 'Do you really want to remove this file: "%s"?' => 'Haluatko varmasti poistaa tiedoston: "%s"?', 'Attachments' => 'Liitteet', 'Edit the task' => 'Muokkaa tehtävää', - 'Edit the description' => 'Muokkaa kuvausta', 'Add a comment' => 'Lisää kommentti', 'Edit a comment' => 'Muokkaa kommenttia', 'Summary' => 'Yhteenveto', @@ -303,7 +299,6 @@ return array( 'Display another project' => 'Näytä toinen projekti', 'Created by %s' => 'Luonut: %s', 'Tasks Export' => 'Tehtävien vienti', - 'Tasks exportation for "%s"' => 'Tehtävien vienti projektilta "%s"', 'Start Date' => 'Aloituspäivä', 'End Date' => 'Lopetuspäivä', 'Execute' => 'Suorita', @@ -326,14 +321,9 @@ return array( 'New sub-task' => 'Uusi alitehtävä', 'New attachment added "%s"' => 'Uusi liite lisätty "%s"', 'New comment posted by %s' => '%s lisäsi uuden kommentin', - // 'New attachment' => '', // 'New comment' => '', 'Comment updated' => 'Kommentti päivitetty', // 'New subtask' => '', - // 'Subtask updated' => '', - // 'Task updated' => '', - // 'Task closed' => '', - // 'Task opened' => '', 'I want to receive notifications only for those projects:' => 'Haluan vastaanottaa ilmoituksia ainoastaan näistä projekteista:', 'view the task on Kanboard' => 'katso tehtävää Kanboardissa', 'Public access' => 'Julkinen käyttöoikeus', @@ -368,7 +358,6 @@ return array( 'No external authentication enabled.' => 'Muita tunnistautumistapoja ei ole otettu käyttöön.', 'Password modified successfully.' => 'Salasana vaihdettu onnistuneesti.', 'Unable to change the password.' => 'Salasanan vaihto epäonnistui.', - 'Change category for the task "%s"' => 'Vaihda tehtävän "%s" kategoria', 'Change category' => 'Vaihda kategoria', '%s updated the task %s' => '%s päivitti tehtävän %s', '%s opened the task %s' => '%s avasi tehtävän %s', @@ -391,14 +380,12 @@ return array( '%s updated the task #%d' => '%s päivitti tehtävää #%d', '%s created the task #%d' => '%s loi tehtävän #%d', '%s closed the task #%d' => '%s sulki tehtävän #%d', - '%s open the task #%d' => '%s avasi tehtävän #%d', - '%s moved the task #%d to the column "%s"' => '%s siirsi tehtävän #%d sarakkeeseen "%s"', - '%s moved the task #%d to the position %d in the column "%s"' => '%s siirsi tehtävän #%d %d. sarakkeessa %s', + '%s opened the task #%d' => '%s avasi tehtävän #%d', 'Activity' => 'Toiminta', 'Default values are "%s"' => 'Oletusarvot ovat "%s"', 'Default columns for new projects (Comma-separated)' => 'Oletussarakkeet uusille projekteille', 'Task assignee change' => 'Tehtävän saajan vaihto', - '%s change the assignee of the task #%d to %s' => '%s vaihtoi tehtävän #%d saajaksi %s', + '%s changed the assignee of the task #%d to %s' => '%s vaihtoi tehtävän #%d saajaksi %s', '%s changed the assignee of the task %s to %s' => '%s vaihtoi tehtävän %s saajaksi %s', 'New password for the user "%s"' => 'Uusi salasana käyttäjälle "%s"', 'Choose an event' => 'Valitse toiminta', @@ -447,13 +434,10 @@ return array( 'Percentage' => 'Prosentti', 'Number of tasks' => 'Tehtävien määrä', 'Task distribution' => 'Tehtävien jakauma', - 'Reportings' => 'Raportoinnit', - // 'Task repartition for "%s"' => '', 'Analytics' => 'Analytiikka', 'Subtask' => 'Alitehtävä', 'My subtasks' => 'Minun alitehtäväni', // 'User repartition' => '', - // 'User repartition for "%s"' => '', 'Clone this project' => 'Kahdenna projekti', 'Column removed successfully.' => 'Sarake poistettu onnstuneesti.', 'Not enough data to show the graph.' => 'Ei riittävästi dataa graafin näyttämiseksi.', @@ -470,10 +454,8 @@ return array( 'This value must be numeric' => 'Tämän arvon tulee olla numeerinen', 'Unable to create this task.' => 'Tehtävän luonti epäonnistui', 'Cumulative flow diagram' => 'Kumulatiivinen vuokaavio', - 'Cumulative flow diagram for "%s"' => 'Kumulatiivinen vuokaavio kohteelle "%s"', 'Daily project summary' => 'Päivittäinen yhteenveto', 'Daily project summary export' => 'Päivittäisen yhteenvedon vienti', - 'Daily project summary export for "%s"' => 'Päivittäisen yhteenvedon vienti kohteeseen "%s"', 'Exports' => 'Viennit', 'This export contains the number of tasks per column grouped per day.' => 'Tämä tiedosto sisältää tehtäviä sarakkeisiin päiväkohtaisesti ryhmilteltyinä', 'Active swimlanes' => 'Aktiiviset kaistat', @@ -485,7 +467,6 @@ return array( 'Remove a swimlane' => 'Poista kaista', 'Show default swimlane' => 'Näytä oletuskaista', 'Swimlane modification for the project "%s"' => 'Kaistamuutos projektille "%s"', - 'Swimlane not found.' => 'Kaistaa ei löydy', 'Swimlane removed successfully.' => 'Kaista poistettu onnistuneesti.', 'Swimlanes' => 'Kaistat', 'Swimlane updated successfully.' => 'Kaista päivitetty onnistuneesti.', @@ -500,7 +481,6 @@ return array( // 'Subtask Id' => '', // 'Subtasks' => '', // 'Subtasks Export' => '', - // 'Subtasks exportation for "%s"' => '', // 'Task Title' => '', // 'Untitled' => '', // 'Application default' => '', @@ -607,7 +587,7 @@ return array( // 'The currency rate have been added successfully.' => '', // 'Unable to add this currency rate.' => '', // 'Webhook URL' => '', - // '%s remove the assignee of the task %s' => '', + // '%s removed the assignee of the task %s' => '', // 'Enable Gravatar images' => '', // 'Information' => '', // 'Check two factor authentication code' => '', @@ -621,7 +601,6 @@ return array( // 'Test your device' => '', // 'Assign a color when the task is moved to a specific column' => '', // '%s via Kanboard' => '', - // 'Burndown chart for "%s"' => '', // 'Burndown chart' => '', // 'This chart show the task complexity over the time (Work Remaining).' => '', // 'Screenshot taken %s' => '', @@ -686,14 +665,8 @@ return array( // 'Move the task to another column when the category is changed' => '', // 'Send a task by email to someone' => '', // 'Reopen a task' => '', - // 'Column change' => '', - // 'Position change' => '', - // 'Swimlane change' => '', - // 'Assignee change' => '', - // '[%s] Overdue tasks' => '', // 'Notification' => '', // '%s moved the task #%d to the first swimlane' => '', - // '%s moved the task #%d to the swimlane "%s"' => '', // 'Swimlane' => '', // 'Gravatar' => '', // '%s moved the task %s to the first swimlane' => '', @@ -764,8 +737,6 @@ return array( // 'Search by category: ' => '', // 'Search by description: ' => '', // 'Search by due date: ' => '', - // 'Lead and Cycle time for "%s"' => '', - // 'Average time spent into each column for "%s"' => '', // 'Average time spent into each column' => '', // 'Average time spent' => '', // 'This chart show the average time spent into each column for the last %d tasks.' => '', @@ -806,7 +777,6 @@ return array( // 'License:' => '', // 'License' => '', // 'Enter the text below' => '', - // 'Gantt chart for %s' => '', // 'Sort by position' => '', // 'Sort by date' => '', // 'Add task' => '', @@ -847,8 +817,6 @@ return array( // 'Version' => '', // 'Plugins' => '', // 'There is no plugin loaded.' => '', - // 'Set maximum column height' => '', - // 'Remove maximum column height' => '', // 'My notifications' => '', // 'Custom filters' => '', // 'Your custom filter have been created successfully.' => '', @@ -953,7 +921,6 @@ return array( // 'Invalid captcha' => '', // 'The name must be unique' => '', // 'View all groups' => '', - // 'View group members' => '', // 'There is no user available.' => '', // 'Do you really want to remove the user "%s" from the group "%s"?' => '', // 'There is no group.' => '', @@ -974,13 +941,10 @@ return array( // 'Enter group name...' => '', // 'Role:' => '', // 'Project members' => '', - // 'Compare hours for "%s"' => '', // '%s mentioned you in the task #%d' => '', // '%s mentioned you in a comment on the task #%d' => '', // 'You were mentioned in the task #%d' => '', // 'You were mentioned in a comment on the task #%d' => '', - // 'Mentioned' => '', - // 'Compare Estimated Time vs Actual Time' => '', // 'Estimated hours: ' => '', // 'Actual hours: ' => '', // 'Hours Spent' => '', @@ -1202,4 +1166,52 @@ return array( // 'Email transport' => '', // 'Webhook token' => '', // 'Imports' => '', + // 'Project tags management' => '', + // 'Tag created successfully.' => '', + // 'Unable to create this tag.' => '', + // 'Tag updated successfully.' => '', + // 'Unable to update this tag.' => '', + // 'Tag removed successfully.' => '', + // 'Unable to remove this tag.' => '', + // 'Global tags management' => '', + // 'Tags' => '', + // 'Tags management' => '', + // 'Add new tag' => '', + // 'Edit a tag' => '', + // 'Project tags' => '', + // 'There is no specific tag for this project at the moment.' => '', + // 'Tag' => '', + // 'Remove a tag' => '', + // 'Do you really want to remove this tag: "%s"?' => '', + // 'Global tags' => '', + // 'There is no global tag at the moment.' => '', + // 'This field cannot be empty' => '', + // 'Close a task when there is no activity in an specific column' => '', + // '%s removed a subtask for the task #%d' => '', + // '%s removed a comment on the task #%d' => '', + // 'Comment removed on task #%d' => '', + // 'Subtask removed on task #%d' => '', + // 'Hide tasks in this column in the dashboard' => '', + // '%s removed a comment on the task %s' => '', + // '%s removed a subtask for the task %s' => '', + // 'Comment removed' => '', + // 'Subtask removed' => '', + // '%s set a new internal link for the task #%d' => '', + // '%s removed an internal link for the task #%d' => '', + // 'A new internal link for the task #%d have been defined' => '', + // 'Internal link removed for the task #%d' => '', + // '%s set a new internal link for the task %s' => '', + // '%s removed an internal link for the task %s' => '', + // 'Automatically set the due date on task creation' => '', + // 'Move the task to another column when closed' => '', + // 'Move the task to another column when not moved during a given period' => '', + // 'Dashboard for %s' => '', + // 'Tasks overview for %s' => '', + // 'Subtasks overview for %s' => '', + // 'Projects overview for %s' => '', + // 'Activity stream for %s' => '', + // 'Calendar for %s' => '', + // 'Notifications for %s' => '', + // 'Subtasks export' => '', + // 'Tasks exportation' => '', ); diff --git a/app/Locale/fr_FR/translations.php b/app/Locale/fr_FR/translations.php index 19ac2b48..e7184949 100644 --- a/app/Locale/fr_FR/translations.php +++ b/app/Locale/fr_FR/translations.php @@ -154,8 +154,6 @@ return array( '%d closed tasks' => '%d tâches terminées', 'No task for this project' => 'Aucune tâche pour ce projet', 'Public link' => 'Lien public', - 'Change assignee' => 'Changer la personne assignée', - 'Change assignee for the task "%s"' => 'Changer la personne assignée pour la tâche « %s »', 'Timezone' => 'Fuseau horaire', 'Sorry, I didn\'t find this information in my database!' => 'Désolé, je n\'ai pas trouvé cette information dans ma base de données !', 'Page not found' => 'Page introuvable', @@ -248,7 +246,6 @@ return array( 'Category' => 'Catégorie', 'Category:' => 'Catégorie :', 'Categories' => 'Catégories', - 'Category not found.' => 'Catégorie introuvable', 'Your category have been created successfully.' => 'Votre catégorie a été créée avec succès.', 'Unable to create your category.' => 'Impossible de créer votre catégorie.', 'Your category have been updated successfully.' => 'Votre catégorie a été mise à jour avec succès.', @@ -270,7 +267,6 @@ return array( 'Do you really want to remove this file: "%s"?' => 'Voulez-vous vraiment supprimer ce fichier « %s » ?', 'Attachments' => 'Pièces-jointes', 'Edit the task' => 'Modifier la tâche', - 'Edit the description' => 'Modifier la description', 'Add a comment' => 'Ajouter un commentaire', 'Edit a comment' => 'Modifier un commentaire', 'Summary' => 'Résumé', @@ -303,7 +299,6 @@ return array( 'Display another project' => 'Afficher un autre projet', 'Created by %s' => 'Créé par %s', 'Tasks Export' => 'Exportation des tâches', - 'Tasks exportation for "%s"' => 'Exportation des tâches pour « %s »', 'Start Date' => 'Date de début', 'End Date' => 'Date de fin', 'Execute' => 'Exécuter', @@ -326,14 +321,9 @@ return array( 'New sub-task' => 'Nouvelle sous-tâche', 'New attachment added "%s"' => 'Nouvelle pièce-jointe ajoutée « %s »', 'New comment posted by %s' => 'Nouveau commentaire ajouté par « %s »', - 'New attachment' => 'Nouveau document', 'New comment' => 'Nouveau commentaire', 'Comment updated' => 'Commentaire mis à jour', 'New subtask' => 'Nouvelle sous-tâche', - 'Subtask updated' => 'Sous-tâche mise à jour', - 'Task updated' => 'Tâche mise à jour', - 'Task closed' => 'Tâche fermée', - 'Task opened' => 'Tâche ouverte', 'I want to receive notifications only for those projects:' => 'Je souhaite reçevoir les notifications uniquement pour les projets sélectionnés :', 'view the task on Kanboard' => 'voir la tâche sur Kanboard', 'Public access' => 'Accès public', @@ -368,7 +358,6 @@ return array( 'No external authentication enabled.' => 'Aucune authentification externe activée.', 'Password modified successfully.' => 'Mot de passe changé avec succès.', 'Unable to change the password.' => 'Impossible de changer le mot de passe.', - 'Change category for the task "%s"' => 'Changer la catégorie pour la tâche « %s »', 'Change category' => 'Changer de catégorie', '%s updated the task %s' => '%s a mis à jour la tâche %s', '%s opened the task %s' => '%s a ouvert la tâche %s', @@ -391,14 +380,12 @@ return array( '%s updated the task #%d' => '%s a mis à jour la tâche n°%d', '%s created the task #%d' => '%s a créé la tâche n°%d', '%s closed the task #%d' => '%s a fermé la tâche n°%d', - '%s open the task #%d' => '%s a ouvert la tâche n°%d', - '%s moved the task #%d to the column "%s"' => '%s a déplacé la tâche n°%d dans la colonne « %s »', - '%s moved the task #%d to the position %d in the column "%s"' => '%s a déplacé la tâche n°%d à la position n°%d dans la colonne « %s »', + '%s opened the task #%d' => '%s a ouvert la tâche n°%d', 'Activity' => 'Activité', 'Default values are "%s"' => 'Les valeurs par défaut sont « %s »', 'Default columns for new projects (Comma-separated)' => 'Colonnes par défaut pour les nouveaux projets (séparation par des virgules)', 'Task assignee change' => 'Modification de la personne assignée à une tâche', - '%s change the assignee of the task #%d to %s' => '%s a changé la personne assignée à la tâche n˚%d pour %s', + '%s changed the assignee of the task #%d to %s' => '%s a changé la personne assignée à la tâche n˚%d pour %s', '%s changed the assignee of the task %s to %s' => '%s a changé la personne assignée à la tâche %s pour %s', 'New password for the user "%s"' => 'Nouveau mot de passe pour l\'utilisateur « %s »', 'Choose an event' => 'Choisir un événement', @@ -447,13 +434,10 @@ return array( 'Percentage' => 'Pourcentage', 'Number of tasks' => 'Nombre de tâches', 'Task distribution' => 'Répartition des tâches', - 'Reportings' => 'Rapports', - 'Task repartition for "%s"' => 'Répartition des tâches pour « %s »', 'Analytics' => 'Analytique', 'Subtask' => 'Sous-tâche', 'My subtasks' => 'Mes sous-tâches', 'User repartition' => 'Répartition des utilisateurs', - 'User repartition for "%s"' => 'Répartition des utilisateurs pour « %s »', 'Clone this project' => 'Cloner ce projet', 'Column removed successfully.' => 'Colonne supprimée avec succès.', 'Not enough data to show the graph.' => 'Pas assez de données pour afficher le graphique.', @@ -470,10 +454,8 @@ return array( 'This value must be numeric' => 'Cette valeur doit être numérique', 'Unable to create this task.' => 'Impossible de créer cette tâche', 'Cumulative flow diagram' => 'Diagramme de flux cumulé', - 'Cumulative flow diagram for "%s"' => 'Diagramme de flux cumulé pour « %s »', 'Daily project summary' => 'Résumé journalier du projet', 'Daily project summary export' => 'Export du résumé journalier du projet', - 'Daily project summary export for "%s"' => 'Export du résumé quotidien du projet pour « %s »', 'Exports' => 'Exports', 'This export contains the number of tasks per column grouped per day.' => 'Cet export contient le nombre de tâches par colonne groupé par jour.', 'Active swimlanes' => 'Swimlanes actives', @@ -485,7 +467,6 @@ return array( 'Remove a swimlane' => 'Supprimer une swimlane', 'Show default swimlane' => 'Afficher la swimlane par défaut', 'Swimlane modification for the project "%s"' => 'Modification d\'une swimlane pour le projet « %s »', - 'Swimlane not found.' => 'Cette swimlane est introuvable.', 'Swimlane removed successfully.' => 'Swimlane supprimée avec succès.', 'Swimlanes' => 'Swimlanes', 'Swimlane updated successfully.' => 'Swimlane mise à jour avec succès.', @@ -500,7 +481,6 @@ return array( 'Subtask Id' => 'Identifiant de la sous-tâche', 'Subtasks' => 'Sous-tâches', 'Subtasks Export' => 'Exportation des sous-tâches', - 'Subtasks exportation for "%s"' => 'Exportation des sous-tâches pour le projet « %s »', 'Task Title' => 'Titre de la tâche', 'Untitled' => 'Sans nom', 'Application default' => 'Valeur par défaut de l\'application', @@ -607,7 +587,7 @@ return array( 'The currency rate have been added successfully.' => 'Le taux de change a été ajouté avec succès.', 'Unable to add this currency rate.' => 'Impossible d\'ajouter ce taux de change', 'Webhook URL' => 'URL du webhook', - '%s remove the assignee of the task %s' => '%s a enlevé la personne assignée à la tâche %s', + '%s removed the assignee of the task %s' => '%s a enlevé la personne assignée à la tâche %s', 'Enable Gravatar images' => 'Activer les images Gravatar', 'Information' => 'Informations', 'Check two factor authentication code' => 'Vérification du code pour l\'authentification à deux-facteurs', @@ -621,7 +601,6 @@ return array( 'Test your device' => 'Testez votre appareil', 'Assign a color when the task is moved to a specific column' => 'Assigner une couleur lorsque la tâche est déplacée dans une colonne spécifique', '%s via Kanboard' => '%s via Kanboard', - 'Burndown chart for "%s"' => 'Graphique d\'avancement pour « %s »', 'Burndown chart' => 'Graphique d\'avancement', 'This chart show the task complexity over the time (Work Remaining).' => 'Ce graphique représente la complexité des tâches en fonction du temps (travail restant).', 'Screenshot taken %s' => 'Capture d\'écran prise le %s', @@ -686,14 +665,8 @@ return array( 'Move the task to another column when the category is changed' => 'Déplacer une tâche vers une autre colonne lorsque la catégorie a changé', 'Send a task by email to someone' => 'Envoyer une tâche par email à quelqu\'un', 'Reopen a task' => 'Rouvrir une tâche', - 'Column change' => 'Changement de colonne', - 'Position change' => 'Changement de position', - 'Swimlane change' => 'Changement de swimlane', - 'Assignee change' => 'Changement d\'assigné', - '[%s] Overdue tasks' => '[%s] Tâches en retard', 'Notification' => 'Notification', '%s moved the task #%d to the first swimlane' => '%s a déplacé la tâche n°%d dans la première swimlane', - '%s moved the task #%d to the swimlane "%s"' => '%s a déplacé la tâche n°%d dans la swimlane « %s »', 'Swimlane' => 'Swimlane', 'Gravatar' => 'Gravatar', '%s moved the task %s to the first swimlane' => '%s a déplacé la tâche %s dans la première swimlane', @@ -765,8 +738,6 @@ return array( 'Search by category: ' => 'Rechercher par catégorie : ', 'Search by description: ' => 'Rechercher par description : ', 'Search by due date: ' => 'Rechercher par date d\'échéance : ', - 'Lead and Cycle time for "%s"' => 'Lead et cycle time pour « %s »', - 'Average time spent into each column for "%s"' => 'Temps passé moyen dans chaque colonne pour « %s »', 'Average time spent into each column' => 'Temps moyen passé dans chaque colonne', 'Average time spent' => 'Temps moyen passé', 'This chart show the average time spent into each column for the last %d tasks.' => 'Ce graphique montre le temps passé moyen dans chaque colonne pour les %d dernières tâches.', @@ -807,7 +778,6 @@ return array( 'License:' => 'Licence :', 'License' => 'Licence', 'Enter the text below' => 'Entrez le texte ci-dessous', - 'Gantt chart for %s' => 'Diagramme de Gantt pour %s', 'Sort by position' => 'Trier par position', 'Sort by date' => 'Trier par date', 'Add task' => 'Ajouter une tâche', @@ -848,8 +818,6 @@ return array( 'Version' => 'Version', 'Plugins' => 'Extensions', 'There is no plugin loaded.' => 'Il n\'y a aucune extension chargée.', - 'Set maximum column height' => 'Définir la hauteur max. des colonnes', - 'Remove maximum column height' => 'Enlever la hauteur max. des colonnes', 'My notifications' => 'Mes notifications', 'Custom filters' => 'Filtres personalisés', 'Your custom filter have been created successfully.' => 'Votre filter personalisé a été créé avec succès.', @@ -950,11 +918,10 @@ return array( 'Project Manager' => 'Chef de projet', 'Project Member' => 'Membre du projet', 'Project Viewer' => 'Visualiseur de projet', - 'Your account is locked for %d minutes' => 'Votre compte est vérouillé pour %d minutes', + 'Your account is locked for %d minutes' => 'Votre compte est verrouillé pour %d minutes', 'Invalid captcha' => 'Captcha invalid', 'The name must be unique' => 'Le nom doit être unique', 'View all groups' => 'Voir tous les groupes', - 'View group members' => 'Voir les membres du groupe', 'There is no user available.' => 'Il n\'y a aucun utilisateur disponible', 'Do you really want to remove the user "%s" from the group "%s"?' => 'Voulez-vous vraiment supprimer l\'utilisateur « %s » du groupe « %s » ?', 'There is no group.' => 'Il n\'y a aucun groupe.', @@ -975,13 +942,10 @@ return array( 'Enter group name...' => 'Entrez le nom du groupe...', 'Role:' => 'Rôle :', 'Project members' => 'Membres du projet', - 'Compare hours for "%s"' => 'Comparer les heures pour « %s »', '%s mentioned you in the task #%d' => '%s vous a mentionné dans la tâche n°%d', '%s mentioned you in a comment on the task #%d' => '%s vous a mentionné dans un commentaire de la tâche n°%d', 'You were mentioned in the task #%d' => 'Vous avez été mentionné dans la tâche n°%d', 'You were mentioned in a comment on the task #%d' => 'Vous avez été mentionné dans un commentaire de la tâche n°%d', - 'Mentioned' => 'Mentionné', - 'Compare Estimated Time vs Actual Time' => 'Comparer le temps estimé et le temps actuel', 'Estimated hours: ' => 'Heures estimées : ', 'Actual hours: ' => 'Heures actuelles : ', 'Hours Spent' => 'Heures passées', @@ -1203,4 +1167,52 @@ return array( 'Email transport' => 'Transport des emails', 'Webhook token' => 'Jeton de sécurité des webhooks', 'Imports' => 'Importations', + 'Project tags management' => 'Gestion des libellés pour le projet', + 'Tag created successfully.' => 'Libellé créé avec succès.', + 'Unable to create this tag.' => 'Imposssible de créer ce libellé.', + 'Tag updated successfully.' => 'Libellé mis à jour avec succès.', + 'Unable to update this tag.' => 'Impossible de mettre à jour ce libellé.', + 'Tag removed successfully.' => 'Libellé supprimé avec succès.', + 'Unable to remove this tag.' => 'Impossible de supprimer ce libellé.', + 'Global tags management' => 'Gestion des libellés globaux', + 'Tags' => 'Libellés', + 'Tags management' => 'Gestion des libellés', + 'Add new tag' => 'Ajouter un nouveau libellé', + 'Edit a tag' => 'Mettre à jour un libellé', + 'Project tags' => 'Libellés du projet', + 'There is no specific tag for this project at the moment.' => 'Il n\'y a aucun libellé spécifique pour ce projet pour le moment.', + 'Tag' => 'Libellé', + 'Remove a tag' => 'Supprimer un libellé', + 'Do you really want to remove this tag: "%s"?' => 'Voulez-vous vraiment supprimer ce libellé : « %s » ?', + 'Global tags' => 'Libellés globaux', + 'There is no global tag at the moment.' => 'Il n\'y a aucun libellé global pour le moment.', + 'This field cannot be empty' => 'Ce champ ne peut être vide', + 'Close a task when there is no activity in an specific column' => 'Fermer une tâche lorsqu\'il n\'y a aucune activité dans une colonne spécifique', + '%s removed a subtask for the task #%d' => '%s a supprimé une sous-tâche de la tâche n°%d', + '%s removed a comment on the task #%d' => '%s a supprimé un commentaire de la tâche n°%d', + 'Comment removed on task #%d' => 'Commentaire supprimé sur la tâche n°%d', + 'Subtask removed on task #%d' => 'Sous-tâche supprimée sur la tâche n°%d', + 'Hide tasks in this column in the dashboard' => 'Cacher les tâches de cette colonne dans le tableau de bord', + '%s removed a comment on the task %s' => '%s a supprimé un commentaire de la tâche %s', + '%s removed a subtask for the task %s' => '%s a supprimé une sous-tâche de la tâche %s', + 'Comment removed' => 'Commentaire supprimé', + 'Subtask removed' => 'Sous-tâche supprimée', + '%s set a new internal link for the task #%d' => '%s a défini un nouveau lien interne pour la tâche n°%d', + '%s removed an internal link for the task #%d' => '%s a supprimé un lien interne pour la tâche n°%d', + 'A new internal link for the task #%d have been defined' => 'Un nouveau lien interne pour la tâche n°%d a été défini', + 'Internal link removed for the task #%d' => 'Lien interne supprimé pour la tâche n°%d', + '%s set a new internal link for the task %s' => '%s a défini un nouveau lien interne pour la tâche %s', + '%s removed an internal link for the task %s' => '%s a supprimé un lien interne pour la tâche %s', + 'Automatically set the due date on task creation' => 'Définir automatiquement la date d\'échéance lors de la création de la tâche', + 'Move the task to another column when closed' => 'Déplacer la tâche vers une autre colonne lorsque celle-ci est fermée', + 'Move the task to another column when not moved during a given period' => 'Déplacer la tâche vers une autre colonne lorsque celle-ci n\'a pas été bougée pendant une certaine période', + 'Dashboard for %s' => 'Tableau de bord pour %s', + 'Tasks overview for %s' => 'Aperçu des tâches pour %s', + 'Subtasks overview for %s' => 'Aperçu des sous-tâches pour %s', + 'Projects overview for %s' => 'Aperçu des projets pour %s', + 'Activity stream for %s' => 'Flux d\'activité pour %s', + 'Calendar for %s' => 'Calendrier pour %s', + 'Notifications for %s' => 'Notifications pour %s', + 'Subtasks export' => 'Export des sous-tâches', + 'Tasks exportation' => 'Export des tâches', ); diff --git a/app/Locale/hu_HU/translations.php b/app/Locale/hu_HU/translations.php index 3b5d6036..32e34857 100644 --- a/app/Locale/hu_HU/translations.php +++ b/app/Locale/hu_HU/translations.php @@ -154,8 +154,6 @@ return array( '%d closed tasks' => '%d lezárt feladat', 'No task for this project' => 'Nincs feladat ebben a projektben', 'Public link' => 'Nyilvános hivatkozás', - 'Change assignee' => 'Felelős módosítása', - 'Change assignee for the task "%s"' => 'Feladat felelősének módosítása: "%s"', 'Timezone' => 'Időzóna', 'Sorry, I didn\'t find this information in my database!' => 'Ez az információ nem található az adatbázisban!', 'Page not found' => 'Az oldal nem található', @@ -248,7 +246,6 @@ return array( 'Category' => 'Kategória', 'Category:' => 'Kategória:', 'Categories' => 'Kategóriák', - 'Category not found.' => 'Kategória nem található.', 'Your category have been created successfully.' => 'Kategória sikeresen létrehozva.', 'Unable to create your category.' => 'A kategória létrehozása nem lehetséges.', 'Your category have been updated successfully.' => 'Kategória sikeresen frissítve.', @@ -270,7 +267,6 @@ return array( 'Do you really want to remove this file: "%s"?' => 'Valóban törölni akarja ezt a fájlt: "%s"?', 'Attachments' => 'Mellékletek', 'Edit the task' => 'Feladat módosítása', - 'Edit the description' => 'Leírás szerkesztése', 'Add a comment' => 'Új megjegyzés', 'Edit a comment' => 'Megjegyzés szerkesztése', 'Summary' => 'Összegzés', @@ -303,7 +299,6 @@ return array( 'Display another project' => 'Másik projekt megjelenítése', 'Created by %s' => 'Készítette: %s', 'Tasks Export' => 'Feladatok exportálása', - 'Tasks exportation for "%s"' => 'Feladatok exportálása: "%s"', 'Start Date' => 'Kezdés dátuma', 'End Date' => 'Befejezés dátuma', 'Execute' => 'Végrehajt', @@ -326,14 +321,9 @@ return array( 'New sub-task' => 'Új részfeladat', 'New attachment added "%s"' => 'Új melléklet "%s" hozzáadva.', 'New comment posted by %s' => 'Új megjegyzés %s', - 'New attachment' => 'Új melléklet', 'New comment' => 'Új megjegyzés', 'Comment updated' => 'Megjegyzés frissítve', 'New subtask' => 'Új részfeladat', - 'Subtask updated' => 'Részfeladat frissítve', - 'Task updated' => 'Feladat frissítve', - 'Task closed' => 'Feladat lezárva', - 'Task opened' => 'Feladat megnyitva', 'I want to receive notifications only for those projects:' => 'Csak ezekről a projektekről kérek értesítést:', 'view the task on Kanboard' => 'feladat megtekintése a Kanboardon', 'Public access' => 'Nyilvános hozzáférés', @@ -368,7 +358,6 @@ return array( 'No external authentication enabled.' => 'A külső azonosítás nincs engedélyezve.', 'Password modified successfully.' => 'A jelszó sikeresen módosítva.', 'Unable to change the password.' => 'A jelszó módosítása sikertelen.', - 'Change category for the task "%s"' => 'Feladat kategória módosítása "%s"', 'Change category' => 'Kategória módosítása', '%s updated the task %s' => '%s frissítette a feladatot %s', '%s opened the task %s' => '%s megnyitott a feladatot %s', @@ -391,14 +380,12 @@ return array( '%s updated the task #%d' => '%s frissítette a feladatot #%d', '%s created the task #%d' => '%s létrehozta a feladatot #%d', '%s closed the task #%d' => '%s lezárta a feladatot #%d', - '%s open the task #%d' => '%s megnyitotta a feladatot #%d', - '%s moved the task #%d to the column "%s"' => '%s átmozgatta a feladatot #%d a "%s" oszlopba', - '%s moved the task #%d to the position %d in the column "%s"' => '%s átmozgatta a feladatot #%d a %d pozícióba a "%s" oszlopban', + '%s opened the task #%d' => '%s megnyitotta a feladatot #%d', 'Activity' => 'Tevékenységek', 'Default values are "%s"' => 'Az alapértelmezett értékek: %s', 'Default columns for new projects (Comma-separated)' => 'Alapértelmezett oszlopok az új projektekben (vesszővel elválasztva)', 'Task assignee change' => 'Felelős módosítása', - '%s change the assignee of the task #%d to %s' => '%s a felelőst módosította #%d %s', + '%s changed the assignee of the task #%d to %s' => '%s a felelőst módosította #%d %s', '%s changed the assignee of the task %s to %s' => '%s a felelőst %s módosította: %s', 'New password for the user "%s"' => 'Felhasználó új jelszava: %s', 'Choose an event' => 'Válasszon eseményt', @@ -447,13 +434,10 @@ return array( 'Percentage' => 'Százalék', 'Number of tasks' => 'A feladatok száma', 'Task distribution' => 'Feladatelosztás', - 'Reportings' => 'Jelentések', - 'Task repartition for "%s"' => 'Feladat újraosztása: %s', 'Analytics' => 'Analitika', 'Subtask' => 'Részfeladat', 'My subtasks' => 'Részfeladataim', 'User repartition' => 'Felhasználó újrafelosztás', - 'User repartition for "%s"' => 'Felhasználó újrafelosztás: %s', 'Clone this project' => 'Projekt másolása', 'Column removed successfully.' => 'Oszlop sikeresen törölve.', 'Not enough data to show the graph.' => 'Nincs elég adat a grafikonhoz.', @@ -470,10 +454,8 @@ return array( 'This value must be numeric' => 'Ez a mező csak szám lehet', 'Unable to create this task.' => 'A feladat nem hozható létre,', 'Cumulative flow diagram' => 'Kumulatív folyamatábra', - 'Cumulative flow diagram for "%s"' => 'Kumulatív folyamatábra: %s', 'Daily project summary' => 'Napi projektösszefoglaló', 'Daily project summary export' => 'Napi projektösszefoglaló exportálása', - 'Daily project summary export for "%s"' => 'Napi projektösszefoglaló exportálása: %s', 'Exports' => 'Exportálások', 'This export contains the number of tasks per column grouped per day.' => 'Ez az export tartalmazza a feladatok számát oszloponként összesítve, napokra lebontva.', 'Active swimlanes' => 'Aktív sávok', @@ -485,7 +467,6 @@ return array( 'Remove a swimlane' => 'Sáv törlés', 'Show default swimlane' => 'Alapértelmezett sáv megjelenítése', 'Swimlane modification for the project "%s"' => '%s projekt sávjainak módosítása', - 'Swimlane not found.' => 'Sáv nem található', 'Swimlane removed successfully.' => 'Sáv sikeresen törölve.', 'Swimlanes' => 'Sávok', 'Swimlane updated successfully.' => 'Sáv sikeresen frissítve', @@ -500,7 +481,6 @@ return array( 'Subtask Id' => 'Részfeladat id', 'Subtasks' => 'Részfeladatok', 'Subtasks Export' => 'Részfeladat exportálás', - 'Subtasks exportation for "%s"' => 'Részfeladatok exportálása: %s', 'Task Title' => 'Feladat címe', 'Untitled' => 'Névtelen', 'Application default' => 'Alkalmazás alapértelmezett', @@ -607,7 +587,7 @@ return array( 'The currency rate have been added successfully.' => 'Az átváltási árfolyammal történő bővítés sikerült', 'Unable to add this currency rate.' => 'Nem sikerült az átváltási árfolyam felvétele', 'Webhook URL' => 'Webhook URL', - '%s remove the assignee of the task %s' => '%s eltávolította a %s feladathoz rendelt személyt', + '%s removed the assignee of the task %s' => '%s eltávolította a %s feladathoz rendelt személyt', 'Enable Gravatar images' => 'Gravatár képek engedélyezése', 'Information' => 'Információ', 'Check two factor authentication code' => 'Két fázisú beléptető kód ellenőrzése', @@ -621,7 +601,6 @@ return array( 'Test your device' => 'Az eszköz ellenőrzése', 'Assign a color when the task is moved to a specific column' => 'Szín hozzárendelése, ha a feladatot egy adott oszlopba mozgatták', '%s via Kanboard' => '%s a Kanboard-on keresztül', - // 'Burndown chart for "%s"' => '', // 'Burndown chart' => '', 'This chart show the task complexity over the time (Work Remaining).' => 'Ez a diagram a feladat időbeli bonyolultságát ábrázolja (mennyi munka van hátra)', 'Screenshot taken %s' => 'A képernyőmentés megtörtént, %s', @@ -686,14 +665,8 @@ return array( 'Move the task to another column when the category is changed' => 'A feladat átmozgatása egy másik oszlopba, ha megváltozik a kategória', 'Send a task by email to someone' => 'Email-en egy feladat küldése valakinek', 'Reopen a task' => 'Egy feladat újbóli megnyitása', - 'Column change' => 'Oszlop módosítás', - 'Position change' => 'Helyzet módosítás', - 'Swimlane change' => 'Sáv módosítás', - 'Assignee change' => 'Felelős módosítása', - '[%s] Overdue tasks' => '[%s] késésben lévő feladat', 'Notification' => 'Értesítés', '%s moved the task #%d to the first swimlane' => '%s a #%d feladatot az első sávba mozgatta', - '%s moved the task #%d to the swimlane "%s"' => '%s a #%d feladatot a "%s" sávba mozgatta', 'Swimlane' => 'Sáv', 'Gravatar' => 'Gravatár', '%s moved the task %s to the first swimlane' => '%s a %s feladatot az első sávba mozgatta', @@ -764,8 +737,6 @@ return array( 'Search by category: ' => 'Keresés kategória alapján: ', 'Search by description: ' => 'Keresés leírás alapján: ', 'Search by due date: ' => 'Keresés határidő alapján: ', - 'Lead and Cycle time for "%s"' => 'A "%s" átfutási ideje és ciklusideje', - 'Average time spent into each column for "%s"' => 'A "%s" során az egyes oszlopokban töltött átlagos idő', 'Average time spent into each column' => 'Az egyes oszlopokban töltött átlagos idő', 'Average time spent' => 'Az eltöltött átlagos idő', 'This chart show the average time spent into each column for the last %d tasks.' => 'Ez az ábra az utolsó %d feladatra vonatkozóan mutatja az egyes oszlopkban eltöltött átlagos időt.', @@ -806,7 +777,6 @@ return array( 'License:' => 'Engedély:', 'License' => 'Engedély', 'Enter the text below' => 'Adja be a lenti szöveget', - 'Gantt chart for %s' => 'Gantt diagram a %s számára', 'Sort by position' => 'Rendezés hely szerint', 'Sort by date' => 'Rendezés idő szerint', 'Add task' => 'Feladat hozzáadása', @@ -847,8 +817,6 @@ return array( 'Version' => 'Verzió', 'Plugins' => 'Plugin-ek', 'There is no plugin loaded.' => 'Nincs betöltött plugin.', - 'Set maximum column height' => 'Max. oszlopmagasság beállítása', - 'Remove maximum column height' => 'Max. oszlopmagasság törlése', 'My notifications' => 'Emlékeztetőim', 'Custom filters' => 'Egyedi szűrők', 'Your custom filter have been created successfully.' => 'Az ön egyedi szűrője sikeresen létrejött.', @@ -953,7 +921,6 @@ return array( 'Invalid captcha' => 'Érvénytelen captcha', 'The name must be unique' => 'A névnek egyedinek kell lennie', 'View all groups' => 'Az összes csoport megtekintése', - 'View group members' => 'A csoporttagok megtekintése', 'There is no user available.' => 'Nincs ilyen felhasználó.', 'Do you really want to remove the user "%s" from the group "%s"?' => 'Valóban el kívánja távolítani a "%s" felhasználót a "%s" csoportból?', 'There is no group.' => 'Nincs ilyen csoport.', @@ -974,13 +941,10 @@ return array( 'Enter group name...' => 'Adja meg a csoport nevét...', 'Role:' => 'Szerepkör:', 'Project members' => 'Projekt tagok', - 'Compare hours for "%s"' => 'Az órák összehasonlítása "%s" számára', '%s mentioned you in the task #%d' => '%s megemlítette önt a #%d feladatban', '%s mentioned you in a comment on the task #%d' => '%s megemlítette önt a #%d feladathoz fűzött megjegyzésben', 'You were mentioned in the task #%d' => 'Ön meg lett említve a #%d feladatban', 'You were mentioned in a comment on the task #%d' => 'Ön meg lett említve a #%d feladathoz fűzött megjegyzésben', - 'Mentioned' => 'Meg lett említve', - 'Compare Estimated Time vs Actual Time' => 'A becsült és a tényleges idő összehasonlítása', 'Estimated hours: ' => 'Becsült órák: ', 'Actual hours: ' => 'Tényleges órák: ', 'Hours Spent' => 'Ráfordítás órákban', @@ -1202,4 +1166,52 @@ return array( // 'Email transport' => '', // 'Webhook token' => '', // 'Imports' => '', + // 'Project tags management' => '', + // 'Tag created successfully.' => '', + // 'Unable to create this tag.' => '', + // 'Tag updated successfully.' => '', + // 'Unable to update this tag.' => '', + // 'Tag removed successfully.' => '', + // 'Unable to remove this tag.' => '', + // 'Global tags management' => '', + // 'Tags' => '', + // 'Tags management' => '', + // 'Add new tag' => '', + // 'Edit a tag' => '', + // 'Project tags' => '', + // 'There is no specific tag for this project at the moment.' => '', + // 'Tag' => '', + // 'Remove a tag' => '', + // 'Do you really want to remove this tag: "%s"?' => '', + // 'Global tags' => '', + // 'There is no global tag at the moment.' => '', + // 'This field cannot be empty' => '', + // 'Close a task when there is no activity in an specific column' => '', + // '%s removed a subtask for the task #%d' => '', + // '%s removed a comment on the task #%d' => '', + // 'Comment removed on task #%d' => '', + // 'Subtask removed on task #%d' => '', + // 'Hide tasks in this column in the dashboard' => '', + // '%s removed a comment on the task %s' => '', + // '%s removed a subtask for the task %s' => '', + // 'Comment removed' => '', + // 'Subtask removed' => '', + // '%s set a new internal link for the task #%d' => '', + // '%s removed an internal link for the task #%d' => '', + // 'A new internal link for the task #%d have been defined' => '', + // 'Internal link removed for the task #%d' => '', + // '%s set a new internal link for the task %s' => '', + // '%s removed an internal link for the task %s' => '', + // 'Automatically set the due date on task creation' => '', + // 'Move the task to another column when closed' => '', + // 'Move the task to another column when not moved during a given period' => '', + // 'Dashboard for %s' => '', + // 'Tasks overview for %s' => '', + // 'Subtasks overview for %s' => '', + // 'Projects overview for %s' => '', + // 'Activity stream for %s' => '', + // 'Calendar for %s' => '', + // 'Notifications for %s' => '', + // 'Subtasks export' => '', + // 'Tasks exportation' => '', ); diff --git a/app/Locale/id_ID/translations.php b/app/Locale/id_ID/translations.php index 45e32c87..9ecb0121 100644 --- a/app/Locale/id_ID/translations.php +++ b/app/Locale/id_ID/translations.php @@ -154,8 +154,6 @@ return array( '%d closed tasks' => '%d tugas yang ditutup', 'No task for this project' => 'Tidak ada tugas dalam proyek ini', 'Public link' => 'Tautan publik', - 'Change assignee' => 'Mengubah orang yand ditugaskan', - 'Change assignee for the task "%s"' => 'Mengubah orang yang ditugaskan untuk tugas « %s »', 'Timezone' => 'Zona waktu', 'Sorry, I didn\'t find this information in my database!' => 'Maaf, saya tidak menemukan informasi ini dalam basis data saya !', 'Page not found' => 'Halaman tidak ditemukan', @@ -248,7 +246,6 @@ return array( 'Category' => 'Kategori', 'Category:' => 'Kategori :', 'Categories' => 'Kategori', - 'Category not found.' => 'Kategori tidak ditemukan', 'Your category have been created successfully.' => 'Kategori anda berhasil dibuat.', 'Unable to create your category.' => 'Tidak dapat membuat kategori anda.', 'Your category have been updated successfully.' => 'Kategori anda berhasil diperbaharui.', @@ -270,7 +267,6 @@ return array( 'Do you really want to remove this file: "%s"?' => 'Apakah anda yakin akan menghapus berkas ini « %s » ?', 'Attachments' => 'Lampiran', 'Edit the task' => 'Modifikasi tugas', - 'Edit the description' => 'Modifikasi deskripsi', 'Add a comment' => 'Tambahkan komentar', 'Edit a comment' => 'Modifikasi komentar', 'Summary' => 'Ringkasan', @@ -303,7 +299,6 @@ return array( 'Display another project' => 'Lihat proyek lain', 'Created by %s' => 'Dibuat oleh %s', 'Tasks Export' => 'Ekspor Tugas', - 'Tasks exportation for "%s"' => 'Tugas di ekspor untuk « %s »', 'Start Date' => 'Tanggal Mulai', 'End Date' => 'Tanggal Berakhir', 'Execute' => 'Eksekusi', @@ -326,14 +321,9 @@ return array( 'New sub-task' => 'Sub-tugas baru', 'New attachment added "%s"' => 'Lampiran baru ditambahkan « %s »', 'New comment posted by %s' => 'Komentar baru ditambahkan oleh « %s »', - 'New attachment' => 'Lampirkan baru', 'New comment' => 'Komentar baru', 'Comment updated' => 'Komentar diperbaharui', 'New subtask' => 'Sub-tugas baru', - 'Subtask updated' => 'Sub-tugas diperbaharui', - 'Task updated' => 'Tugas diperbaharui', - 'Task closed' => 'Tugas ditutup', - 'Task opened' => 'Tugas dibuka', 'I want to receive notifications only for those projects:' => 'Saya ingin menerima pemberitahuan hanya untuk proyek-proyek yang dipilih :', 'view the task on Kanboard' => 'lihat tugas di Kanboard', 'Public access' => 'Akses publik', @@ -368,7 +358,6 @@ return array( 'No external authentication enabled.' => 'Tidak ada otentifikasi eksternal yang aktif.', 'Password modified successfully.' => 'Kata sandi berhasil dimodifikasi.', 'Unable to change the password.' => 'Tidak dapat merubah kata sandir.', - 'Change category for the task "%s"' => 'Rubah kategori untuk tugas « %s »', 'Change category' => 'Rubah kategori', '%s updated the task %s' => '%s memperbaharui tugas %s', '%s opened the task %s' => '%s membuka tugas %s', @@ -391,14 +380,12 @@ return array( '%s updated the task #%d' => '%s memperbaharui tugas n°%d', '%s created the task #%d' => '%s membuat tugas n°%d', '%s closed the task #%d' => '%s menutup tugas n°%d', - '%s open the task #%d' => '%s membuka tugas n°%d', - '%s moved the task #%d to the column "%s"' => '%s memindahkan tugas n°%d ke kolom « %s »', - '%s moved the task #%d to the position %d in the column "%s"' => '%s memindahkan tugas n°%d ke posisi n°%d dalam kolom « %s »', + '%s opened the task #%d' => '%s membuka tugas n°%d', 'Activity' => 'Aktifitas', 'Default values are "%s"' => 'Standar nilai adalah« %s »', 'Default columns for new projects (Comma-separated)' => 'Kolom default untuk proyek baru (dipisahkan dengan koma)', 'Task assignee change' => 'Mengubah orang ditugaskan untuk tugas', - '%s change the assignee of the task #%d to %s' => '%s rubah orang yang ditugaskan dari tugas n%d ke %s', + '%s changed the assignee of the task #%d to %s' => '%s rubah orang yang ditugaskan dari tugas n%d ke %s', '%s changed the assignee of the task %s to %s' => '%s mengubah orang yang ditugaskan dari tugas %s ke %s', 'New password for the user "%s"' => 'Kata sandi baru untuk pengguna « %s »', 'Choose an event' => 'Pilih acara', @@ -447,13 +434,10 @@ return array( 'Percentage' => 'Persentasi', 'Number of tasks' => 'Jumlah dari tugas', 'Task distribution' => 'Pembagian tugas', - 'Reportings' => 'Pelaporan', - 'Task repartition for "%s"' => 'Pembagian tugas untuk « %s »', 'Analytics' => 'Analitis', 'Subtask' => 'Subtugas', 'My subtasks' => 'Subtugas saya', 'User repartition' => 'Partisi ulang pengguna', - 'User repartition for "%s"' => 'Partisi ulang pengguna untuk « %s »', 'Clone this project' => 'Gandakan proyek ini', 'Column removed successfully.' => 'Kolom berhasil dihapus.', 'Not enough data to show the graph.' => 'Tidak cukup data untuk menampilkan grafik.', @@ -470,10 +454,8 @@ return array( 'This value must be numeric' => 'Nilai ini harus angka', 'Unable to create this task.' => 'Tidak dapat membuat tugas ini', 'Cumulative flow diagram' => 'Diagram alir kumulatif', - 'Cumulative flow diagram for "%s"' => 'Diagram alir kumulatif untuk « %s »', 'Daily project summary' => 'Ringkasan proyek harian', 'Daily project summary export' => 'Ekspor ringkasan proyek harian', - 'Daily project summary export for "%s"' => 'Ekspor ringkasan proyek harian untuk « %s »', 'Exports' => 'Ekspor', 'This export contains the number of tasks per column grouped per day.' => 'Ekspor ini berisi jumlah dari tugas per kolom dikelompokan perhari.', 'Active swimlanes' => 'Swimlanes aktif', @@ -485,7 +467,6 @@ return array( 'Remove a swimlane' => 'Supprimer une swimlane', 'Show default swimlane' => 'Perlihatkan standar swimlane', 'Swimlane modification for the project "%s"' => 'Modifikasi swimlane untuk proyek « %s »', - 'Swimlane not found.' => 'Swimlane tidak ditemukan.', 'Swimlane removed successfully.' => 'Swimlane berhasil dihapus.', 'Swimlanes' => 'Swimlanes', 'Swimlane updated successfully.' => 'Swimlane berhasil diperbaharui.', @@ -500,7 +481,6 @@ return array( 'Subtask Id' => 'Id Subtugas', 'Subtasks' => 'Subtugas', 'Subtasks Export' => 'Ekspor Subtugas', - 'Subtasks exportation for "%s"' => 'Ekspor subtugas untuk « %s »', 'Task Title' => 'Judul Tugas', 'Untitled' => 'Tanpa nama', 'Application default' => 'Aplikasi standar', @@ -607,7 +587,7 @@ return array( 'The currency rate have been added successfully.' => 'Nilai tukar mata uang berhasil ditambahkan.', 'Unable to add this currency rate.' => 'Tidak dapat menambahkan nilai tukar mata uang', 'Webhook URL' => 'URL webhook', - '%s remove the assignee of the task %s' => '%s menghapus penugasan dari tugas %s', + '%s removed the assignee of the task %s' => '%s menghapus penugasan dari tugas %s', 'Enable Gravatar images' => 'Mengaktifkan gambar Gravatar', 'Information' => 'Informasi', 'Check two factor authentication code' => 'Cek dua faktor kode otentifikasi', @@ -621,7 +601,6 @@ return array( 'Test your device' => 'Menguji perangkat anda', 'Assign a color when the task is moved to a specific column' => 'Menetapkan warna ketika tugas tersebut dipindahkan ke kolom tertentu', '%s via Kanboard' => '%s via Kanboard', - 'Burndown chart for "%s"' => 'Grafik Burndown untku « %s »', 'Burndown chart' => 'Grafik Burndown', 'This chart show the task complexity over the time (Work Remaining).' => 'Grafik ini menunjukkan kompleksitas tugas dari waktu ke waktu (Sisa Pekerjaan).', 'Screenshot taken %s' => 'Screenshot diambil %s', @@ -686,14 +665,8 @@ return array( 'Move the task to another column when the category is changed' => 'Pindahkan tugas ke kolom lain ketika kategori berubah', 'Send a task by email to someone' => 'Kirim tugas melalui email ke seseorang', 'Reopen a task' => 'Membuka kembali tugas', - 'Column change' => 'Kolom berubah', - 'Position change' => 'Posisi berubah', - 'Swimlane change' => 'Swimlane berubah', - 'Assignee change' => 'Penerima berubah', - '[%s] Overdue tasks' => '[%s] Tugas terlambat', 'Notification' => 'Pemberitahuan', '%s moved the task #%d to the first swimlane' => '%s memindahkan tugas n°%d ke swimlane pertama', - '%s moved the task #%d to the swimlane "%s"' => '%s memindahkan tugas n°%d ke swimlane « %s »', 'Swimlane' => 'Swimlane', 'Gravatar' => 'Gravatar', '%s moved the task %s to the first swimlane' => '%s memindahkan tugas %s ke swimlane pertama', @@ -764,8 +737,6 @@ return array( 'Search by category: ' => 'Pencarian berdasarkan kategori : ', 'Search by description: ' => 'Pencarian berdasarkan deskripsi : ', 'Search by due date: ' => 'Pencarian berdasarkan tanggal jatuh tempo : ', - 'Lead and Cycle time for "%s"' => 'Memimpin dan Siklus waktu untuk « %s »', - 'Average time spent into each column for "%s"' => 'Rata-rata waktu yang dihabiskan dalam setiap kolom untuk « %s »', 'Average time spent into each column' => 'Rata-rata waktu yang dihabiskan dalam setiap kolom', 'Average time spent' => 'Rata-rata waktu yang dihabiskan', 'This chart show the average time spent into each column for the last %d tasks.' => 'Grafik ini menunjukkan rata-rata waktu yang dihabiskan dalam setiap kolom untuk %d tugas.', @@ -806,7 +777,6 @@ return array( 'License:' => 'Lisensi :', 'License' => 'Lisensi', 'Enter the text below' => 'Masukkan teks di bawah', - 'Gantt chart for %s' => 'Grafik Gantt untuk %s', 'Sort by position' => 'Urutkan berdasarkan posisi', 'Sort by date' => 'Urutkan berdasarkan tanggal', 'Add task' => 'Tambah tugas', @@ -843,172 +813,166 @@ return array( 'Documentation' => 'Dokumentasi', 'Table of contents' => 'Daftar isi', 'Gantt' => 'Gantt', - // 'Author' => '', - // 'Version' => '', - // 'Plugins' => '', - // 'There is no plugin loaded.' => '', - // 'Set maximum column height' => '', - // 'Remove maximum column height' => '', - // 'My notifications' => '', - // 'Custom filters' => '', - // 'Your custom filter have been created successfully.' => '', - // 'Unable to create your custom filter.' => '', - // 'Custom filter removed successfully.' => '', - // 'Unable to remove this custom filter.' => '', - // 'Edit custom filter' => '', - // 'Your custom filter have been updated successfully.' => '', - // 'Unable to update custom filter.' => '', - // 'Web' => '', - // 'New attachment on task #%d: %s' => '', - // 'New comment on task #%d' => '', - // 'Comment updated on task #%d' => '', - // 'New subtask on task #%d' => '', - // 'Subtask updated on task #%d' => '', - // 'New task #%d: %s' => '', - // 'Task updated #%d' => '', - // 'Task #%d closed' => '', - // 'Task #%d opened' => '', - // 'Column changed for task #%d' => '', - // 'New position for task #%d' => '', - // 'Swimlane changed for task #%d' => '', - // 'Assignee changed on task #%d' => '', - // '%d overdue tasks' => '', - // 'Task #%d is overdue' => '', - // 'No new notifications.' => '', - // 'Mark all as read' => '', - // 'Mark as read' => '', - // 'Total number of tasks in this column across all swimlanes' => '', - // 'Collapse swimlane' => '', - // 'Expand swimlane' => '', - // 'Add a new filter' => '', - // 'Share with all project members' => '', - // 'Shared' => '', - // 'Owner' => '', - // 'Unread notifications' => '', - // 'Notification methods:' => '', - // 'Import tasks from CSV file' => '', - // 'Unable to read your file' => '', - // '%d task(s) have been imported successfully.' => '', - // 'Nothing have been imported!' => '', - // 'Import users from CSV file' => '', - // '%d user(s) have been imported successfully.' => '', - // 'Comma' => '', - // 'Semi-colon' => '', - // 'Tab' => '', - // 'Vertical bar' => '', - // 'Double Quote' => '', - // 'Single Quote' => '', - // '%s attached a file to the task #%d' => '', - // 'There is no column or swimlane activated in your project!' => '', - // 'Append filter (instead of replacement)' => '', - // 'Append/Replace' => '', - // 'Append' => '', - // 'Replace' => '', - // 'Import' => '', - // 'change sorting' => '', - // 'Tasks Importation' => '', - // 'Delimiter' => '', - // 'Enclosure' => '', - // 'CSV File' => '', - // 'Instructions' => '', - // 'Your file must use the predefined CSV format' => '', - // 'Your file must be encoded in UTF-8' => '', - // 'The first row must be the header' => '', - // 'Duplicates are not verified for you' => '', - // 'The due date must use the ISO format: YYYY-MM-DD' => '', - // 'Download CSV template' => '', - // 'No external integration registered.' => '', - // 'Duplicates are not imported' => '', - // 'Usernames must be lowercase and unique' => '', - // 'Passwords will be encrypted if present' => '', - // '%s attached a new file to the task %s' => '', + 'Author' => 'Penulis', + 'Version' => 'Versi', + 'Plugins' => 'Plugin', + 'There is no plugin loaded.' => 'Tidak ada plugin yang dimuat', + 'My notifications' => 'Notifikasi saya', + 'Custom filters' => 'Filter kustom', + 'Your custom filter have been created successfully.' => 'Filter kustom anda telah berhasil dibuat', + 'Unable to create your custom filter.' => 'Tidak dapat membuat filter kustom', + 'Custom filter removed successfully.' => 'Filter kustom berhail dihapus', + 'Unable to remove this custom filter.' => 'Tidak dapat menghapus filter kustom', + 'Edit custom filter' => 'Modifikasi filter kustom', + 'Your custom filter have been updated successfully.' => 'Filter kustom anda telah berhasil diperbaharui', + 'Unable to update custom filter.' => 'Tidak dapat memperbaharui filter kustom', + 'Web' => 'Web', + 'New attachment on task #%d: %s' => 'Lampiran baru pada tugas #%d: %s', + 'New comment on task #%d' => 'Komentar baru pada tugas #%d', + 'Comment updated on task #%d' => 'Komentar diperbaharui pada tugas #%d', + 'New subtask on task #%d' => 'Subtask baru pada tugas #%d', + 'Subtask updated on task #%d' => 'Subtask diperbaharui pada tugas #%d', + 'New task #%d: %s' => 'Tugas baru #%d: %s', + 'Task updated #%d' => 'Tugas diperbaharui #%d', + 'Task #%d closed' => 'Tugas #%d ditutup', + 'Task #%d opened' => 'Tugas #%d dibuka', + 'Column changed for task #%d' => 'Kolom berubah untuk tugas #%d', + 'New position for task #%d' => 'Posisi baru untuk tugas #%d', + 'Swimlane changed for task #%d' => 'Swimlane berubah untuk tugas #%d', + 'Assignee changed on task #%d' => 'Orang yang ditugaskan berubah pada tugas #%d', + '%d overdue tasks' => '%d tugas terlambat', + 'Task #%d is overdue' => 'Tugas #%d terlambat', + 'No new notifications.' => 'Tidak ada notifikasi baru', + 'Mark all as read' => 'Tandai semua sebagai sudah dibaca', + 'Mark as read' => 'Tandai sebagai sudah dibaca', + 'Total number of tasks in this column across all swimlanes' => 'Total jumlah tugas di kolom ini di semua swimlanes', + 'Collapse swimlane' => 'Lipat swimlane', + 'Expand swimlane' => 'Perluas swimlane', + 'Add a new filter' => 'Tambah filter baru', + 'Share with all project members' => 'Bagikan dengan semua member proyek', + 'Shared' => 'Dibagikan', + 'Owner' => 'Pemilik', + 'Unread notifications' => 'Notifikasi belum terbaca', + 'Notification methods:' => 'Metode pemberitahuan', + 'Import tasks from CSV file' => 'Impor tugas dari berkas CSV', + 'Unable to read your file' => 'Tidak dapat membaca berkas anda', + '%d task(s) have been imported successfully.' => '%d tugas telah berhasil di impor', + 'Nothing have been imported!' => 'Tidak ada yang dapat di impor', + 'Import users from CSV file' => 'Impor pengguna dari berkas CSV', + '%d user(s) have been imported successfully.' => '%d pengguna telah berhasil di impor', + 'Comma' => 'Koma', + 'Semi-colon' => 'Titik Koma', + 'Tab' => 'Tab', + 'Vertical bar' => 'Bar vertikal', + 'Double Quote' => 'Kutip Ganda', + 'Single Quote' => 'Kutip Satu', + '%s attached a file to the task #%d' => '%s berkas dilampirkan untuk tugas #%d', + 'There is no column or swimlane activated in your project!' => 'Tidak ada kolom atau swimlane aktif untuk proyek anda', + 'Append filter (instead of replacement)' => 'Tambahkan filter (bukan pengganti)', + 'Append/Replace' => 'Tambah/Ganti', + 'Append' => 'Tambahkan', + 'Replace' => 'Ganti', + 'Import' => 'Impor', + 'change sorting' => 'rubah sortir', + 'Tasks Importation' => 'Tugas Impor', + 'Delimiter' => 'Pembatas', + 'Enclosure' => 'Lampiran', + 'CSV File' => 'Berkas CSV', + 'Instructions' => 'Intruksi', + 'Your file must use the predefined CSV format' => 'Berkas Anda harus menggunakan format CSV yang telah ditetapkan', + 'Your file must be encoded in UTF-8' => 'Berkas anda harus di kodekan dalam bentuk UTF-8', + 'The first row must be the header' => 'Baris pertama harus header', + 'Duplicates are not verified for you' => 'Duplikasi tidak diverifikasi untuk anda', + 'The due date must use the ISO format: YYYY-MM-DD' => 'Tanggal jatuh tempo harus menggunakan format ISO: YYYY-MM-DD', + 'Download CSV template' => 'Unduh template CSV', + 'No external integration registered.' => 'Tidak ada integrasi eksternal terdaftar', + 'Duplicates are not imported' => 'Duplikasi tidak diimpor', + 'Usernames must be lowercase and unique' => 'Username harus huruf kecil dan unik', + 'Passwords will be encrypted if present' => 'Kata sandi akan di enkripsi jika ada', + '%s attached a new file to the task %s' => '%s berkas baru dilampirkan untuk tugas %s', 'Link type' => 'Tipe tautan', - // 'Assign automatically a category based on a link' => '', - // 'BAM - Konvertible Mark' => '', - // 'Assignee Username' => '', - // 'Assignee Name' => '', - // 'Groups' => '', - // 'Members of %s' => '', - // 'New group' => '', - // 'Group created successfully.' => '', - // 'Unable to create your group.' => '', - // 'Edit group' => '', - // 'Group updated successfully.' => '', - // 'Unable to update your group.' => '', - // 'Add group member to "%s"' => '', - // 'Group member added successfully.' => '', - // 'Unable to add group member.' => '', - // 'Remove user from group "%s"' => '', - // 'User removed successfully from this group.' => '', - // 'Unable to remove this user from the group.' => '', - // 'Remove group' => '', - // 'Group removed successfully.' => '', - // 'Unable to remove this group.' => '', - // 'Project Permissions' => '', - // 'Manager' => '', - // 'Project Manager' => '', - // 'Project Member' => '', - // 'Project Viewer' => '', - // 'Your account is locked for %d minutes' => '', - // 'Invalid captcha' => '', - // 'The name must be unique' => '', - // 'View all groups' => '', - // 'View group members' => '', - // 'There is no user available.' => '', - // 'Do you really want to remove the user "%s" from the group "%s"?' => '', - // 'There is no group.' => '', - // 'External Id' => '', - // 'Add group member' => '', - // 'Do you really want to remove this group: "%s"?' => '', - // 'There is no user in this group.' => '', - // 'Remove this user' => '', - // 'Permissions' => '', - // 'Allowed Users' => '', - // 'No user have been allowed specifically.' => '', - // 'Role' => '', - // 'Enter user name...' => '', - // 'Allowed Groups' => '', - // 'No group have been allowed specifically.' => '', - // 'Group' => '', - // 'Group Name' => '', - // 'Enter group name...' => '', - // 'Role:' => '', + 'Assign automatically a category based on a link' => 'Menetapkan otomatis kategori berdasarkan tautan', + 'BAM - Konvertible Mark' => 'BAM - Konvertible Mark', + 'Assignee Username' => 'Username yang ditugaskan', + 'Assignee Name' => 'Nama yang ditugaskan', + 'Groups' => 'Grup', + 'Members of %s' => 'Anggota dari %s', + 'New group' => 'Grup baru', + 'Group created successfully.' => 'Grup berhasil dibuat', + 'Unable to create your group.' => 'Tidak dapat membuat grup anda', + 'Edit group' => 'Rubah grup', + 'Group updated successfully.' => 'Grup berhasil diperbaharui', + 'Unable to update your group.' => 'Tidak dapat memperbaharui grup anda', + 'Add group member to "%s"' => 'Tambahkan anggota grup ke "%s"', + 'Group member added successfully.' => 'Anggota grup berhasil ditambahkan', + 'Unable to add group member.' => 'Tidak dapat menambahkan anggota grup', + 'Remove user from group "%s"' => 'hapus pengguna dari grup "%s"', + 'User removed successfully from this group.' => 'Pengguna berhasil dihapus dari grup ini', + 'Unable to remove this user from the group.' => 'Tidak dapat menghapus pengguna dari grup', + 'Remove group' => 'Hapus grup', + 'Group removed successfully.' => 'Grup berhasil dihapus', + 'Unable to remove this group.' => 'Tidak dapat menghapus grup ini', + 'Project Permissions' => 'Izin Proyek', + 'Manager' => 'Manajer', + 'Project Manager' => 'Manajer Proyek', + 'Project Member' => 'Anggota Proyek', + 'Project Viewer' => 'Penonton Proyek', + 'Your account is locked for %d minutes' => 'Akun anda dikunci untuk %d menit', + 'Invalid captcha' => 'Captcha tidak valid', + 'The name must be unique' => 'Nama harus unik', + 'View all groups' => 'Lihat semua grup', + 'There is no user available.' => 'Tidak ada pengguna yang tersedia', + 'Do you really want to remove the user "%s" from the group "%s"?' => 'Anda yakin akan menghapus pengguna "%s" dari grup "%s"?', + 'There is no group.' => 'Tidak ada grup', + 'External Id' => 'Id Eksternal', + 'Add group member' => 'Tambah anggota grup', + 'Do you really want to remove this group: "%s"?' => 'Anda yakin akan menghapus grup ini: "%s"?', + 'There is no user in this group.' => 'Tidak ada pengguna dalam grup ini', + 'Remove this user' => 'Hapus pengguna ini', + 'Permissions' => 'Izin', + 'Allowed Users' => 'Pengguna Yang Diperbolehkan', + 'No user have been allowed specifically.' => 'Tidak ada user yang diperbolehkan secara khusus', + 'Role' => 'Peran', + 'Enter user name...' => 'Masukkan nama pengguna...', + 'Allowed Groups' => 'Grup Yang Diperbolehkan', + 'No group have been allowed specifically.' => 'Tidak ada grup yang diperbolehkan secara khusus', + 'Group' => 'Grup', + 'Group Name' => 'Nama Grup', + 'Enter group name...' => 'Masukkan nama grup...', + 'Role:' => 'Peran:', 'Project members' => 'Anggota proyek', - // 'Compare hours for "%s"' => '', - // '%s mentioned you in the task #%d' => '', - // '%s mentioned you in a comment on the task #%d' => '', - // 'You were mentioned in the task #%d' => '', - // 'You were mentioned in a comment on the task #%d' => '', - // 'Mentioned' => '', - // 'Compare Estimated Time vs Actual Time' => '', - // 'Estimated hours: ' => '', - // 'Actual hours: ' => '', - // 'Hours Spent' => '', - // 'Hours Estimated' => '', - // 'Estimated Time' => '', - // 'Actual Time' => '', - // 'Estimated vs actual time' => '', - // 'RUB - Russian Ruble' => '', - // 'Assign the task to the person who does the action when the column is changed' => '', - // 'Close a task in a specific column' => '', - // 'Time-based One-time Password Algorithm' => '', - // 'Two-Factor Provider: ' => '', - // 'Disable two-factor authentication' => '', - // 'Enable two-factor authentication' => '', - // 'There is no integration registered at the moment.' => '', - // 'Password Reset for Kanboard' => '', - // 'Forgot password?' => '', - // 'Enable "Forget Password"' => '', - // 'Password Reset' => '', - // 'New password' => '', - // 'Change Password' => '', - // 'To reset your password click on this link:' => '', - // 'Last Password Reset' => '', - // 'The password has never been reinitialized.' => '', - // 'Creation' => '', - // 'Expiration' => '', - // 'Password reset history' => '', - // 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => '', + '%s mentioned you in the task #%d' => '%s menyebut anda dalam tugas #%d', + '%s mentioned you in a comment on the task #%d' => '%s menyebut anda dalam komentar pada tugas #%d', + 'You were mentioned in the task #%d' => 'Anda disebutkan dalam tugas #%d', + 'You were mentioned in a comment on the task #%d' => 'Anda disebutkan dalam komentar pada tugas #%d', + 'Estimated hours: ' => 'Estimasi jam:', + 'Actual hours: ' => 'Aktual jam', + 'Hours Spent' => 'Jam dihabiskan', + 'Hours Estimated' => 'Jam diperkirakan', + 'Estimated Time' => 'Waktu Estimasi', + 'Actual Time' => 'Waktu Aktual', + 'Estimated vs actual time' => 'Estimasi vs waktu aktual', + 'RUB - Russian Ruble' => 'RUB - Rusia rubel', + 'Assign the task to the person who does the action when the column is changed' => 'Menetapkan tugas kepada orang yang melakukan tindakan ketika kolom berubah', + 'Close a task in a specific column' => 'Tutup tugas di kolom tertentu', + 'Time-based One-time Password Algorithm' => 'Waktu berbasis Satu-waktu Algoritma Kata sandi', + 'Two-Factor Provider: ' => 'Provider Dua-Faktor', + 'Disable two-factor authentication' => 'Nonaktifkan otentikasi dua-faktor', + 'Enable two-factor authentication' => 'Aktifkan otentikasi dua-faktor', + 'There is no integration registered at the moment.' => 'Tidak ada integrasi yang diregristasi untuk saat ini', + 'Password Reset for Kanboard' => 'Setel ulang Kata sandi untuk Kanboard', + 'Forgot password?' => 'Lupa kata sandi', + 'Enable "Forget Password"' => 'Aktifkan "Lupa Kata Sandi"', + 'Password Reset' => 'Setel ulang Kata sandi', + 'New password' => 'Kata sandi baru', + 'Change Password' => 'Rubah kata sandi', + 'To reset your password click on this link:' => 'Untuk menyetel ulang kata sandi anda klik tautan ini:', + 'Last Password Reset' => 'Terakhir Setel Ulang Kata Sandi', + 'The password has never been reinitialized.' => 'Kata sandi tidak pernah di reinisialisasi', + 'Creation' => 'Kreasi', + 'Expiration' => 'Waktu berakhir', + 'Password reset history' => 'Sejarah setel ulang kata sandi', + 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => 'Semua tugas dalam kolom "%s" dan swimlane "%s" telah berhasil ditutup', // 'Do you really want to close all tasks of this column?' => '', // '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '', // 'Close all tasks of this column' => '', @@ -1202,4 +1166,52 @@ return array( // 'Email transport' => '', // 'Webhook token' => '', // 'Imports' => '', + // 'Project tags management' => '', + // 'Tag created successfully.' => '', + // 'Unable to create this tag.' => '', + // 'Tag updated successfully.' => '', + // 'Unable to update this tag.' => '', + // 'Tag removed successfully.' => '', + // 'Unable to remove this tag.' => '', + // 'Global tags management' => '', + // 'Tags' => '', + // 'Tags management' => '', + // 'Add new tag' => '', + // 'Edit a tag' => '', + // 'Project tags' => '', + // 'There is no specific tag for this project at the moment.' => '', + // 'Tag' => '', + // 'Remove a tag' => '', + // 'Do you really want to remove this tag: "%s"?' => '', + // 'Global tags' => '', + // 'There is no global tag at the moment.' => '', + // 'This field cannot be empty' => '', + // 'Close a task when there is no activity in an specific column' => '', + // '%s removed a subtask for the task #%d' => '', + // '%s removed a comment on the task #%d' => '', + // 'Comment removed on task #%d' => '', + // 'Subtask removed on task #%d' => '', + // 'Hide tasks in this column in the dashboard' => '', + // '%s removed a comment on the task %s' => '', + // '%s removed a subtask for the task %s' => '', + // 'Comment removed' => '', + // 'Subtask removed' => '', + // '%s set a new internal link for the task #%d' => '', + // '%s removed an internal link for the task #%d' => '', + // 'A new internal link for the task #%d have been defined' => '', + // 'Internal link removed for the task #%d' => '', + // '%s set a new internal link for the task %s' => '', + // '%s removed an internal link for the task %s' => '', + // 'Automatically set the due date on task creation' => '', + // 'Move the task to another column when closed' => '', + // 'Move the task to another column when not moved during a given period' => '', + // 'Dashboard for %s' => '', + // 'Tasks overview for %s' => '', + // 'Subtasks overview for %s' => '', + // 'Projects overview for %s' => '', + // 'Activity stream for %s' => '', + // 'Calendar for %s' => '', + // 'Notifications for %s' => '', + // 'Subtasks export' => '', + // 'Tasks exportation' => '', ); diff --git a/app/Locale/it_IT/translations.php b/app/Locale/it_IT/translations.php index 261cd82c..b72ca181 100644 --- a/app/Locale/it_IT/translations.php +++ b/app/Locale/it_IT/translations.php @@ -154,8 +154,6 @@ return array( '%d closed tasks' => '%d task chiusi', 'No task for this project' => 'Nessun task per questo progetto', 'Public link' => 'Link pubblico', - 'Change assignee' => 'Cambia l\'assegnatario', - 'Change assignee for the task "%s"' => 'Cambia l\'assegnatario per il task "%s"', 'Timezone' => 'Fuso orario', 'Sorry, I didn\'t find this information in my database!' => 'Spiacente, non ho trovato questa informazione sul database!', 'Page not found' => 'Pagina non trovata', @@ -248,7 +246,6 @@ return array( 'Category' => 'Categoria', 'Category:' => 'Categoria:', 'Categories' => 'Categorie', - 'Category not found.' => 'Categoria non trovata.', 'Your category have been created successfully.' => 'La tua categoria è stata creata con successo.', 'Unable to create your category.' => 'Impossibile creare la tua categoria.', 'Your category have been updated successfully.' => 'La tua categoria è stata aggiornata con successo.', @@ -270,7 +267,6 @@ return array( 'Do you really want to remove this file: "%s"?' => 'Vuoi davvero cancellare questo file: "%s"?', 'Attachments' => 'Allegati', 'Edit the task' => 'Modifica il task', - 'Edit the description' => 'Modifica la descrizione', 'Add a comment' => 'Aggiungi un commento', 'Edit a comment' => 'Modifica un commento', 'Summary' => 'Sommario', @@ -303,7 +299,6 @@ return array( 'Display another project' => 'Mostra un altro progetto', 'Created by %s' => 'Creato da %s', 'Tasks Export' => 'Export dei task', - 'Tasks exportation for "%s"' => 'Export dei task per "%s"', 'Start Date' => 'Data d\'inizio', 'End Date' => 'Data di fine', 'Execute' => 'Esegui', @@ -326,14 +321,9 @@ return array( 'New sub-task' => 'Nuovo sotto-task', 'New attachment added "%s"' => 'Nuovo allegato aggiunto "%s"', 'New comment posted by %s' => 'Nuovo commento aggiunto da "%s"', - 'New attachment' => 'Nuovo allegato', 'New comment' => 'Nuovo commento', 'Comment updated' => 'Commento aggiornato', 'New subtask' => 'Nuovo sotto-task', - 'Subtask updated' => 'Sotto-task aggiornato', - 'Task updated' => 'Task aggiornato', - 'Task closed' => 'Task chiuso', - 'Task opened' => 'Task aperto', 'I want to receive notifications only for those projects:' => 'Vorrei ricevere le notifiche solo da questi progetti:', 'view the task on Kanboard' => 'visualizza il task su Kanboard', 'Public access' => 'Accesso pubblico', @@ -368,7 +358,6 @@ return array( 'No external authentication enabled.' => 'Nessuna autenticazione esterna abilitata.', 'Password modified successfully.' => 'Password modificata con successo.', 'Unable to change the password.' => 'Impossibile cambiare la password.', - 'Change category for the task "%s"' => 'Cambia categoria per il task "%s"', 'Change category' => 'Cambia categoria', '%s updated the task %s' => '%s ha aggiornato il task %s', '%s opened the task %s' => '%s ha aperto il task %s', @@ -391,14 +380,12 @@ return array( '%s updated the task #%d' => '%s ha aggiornato il task #%d', '%s created the task #%d' => '%s ha creato il task #%d', '%s closed the task #%d' => '%s ha chiuso il task #%d', - '%s open the task #%d' => '%s ha aperto il task #%d', - '%s moved the task #%d to the column "%s"' => '%s ha spostato il task #%d nella colonna "%s"', - '%s moved the task #%d to the position %d in the column "%s"' => '%s ha spostato il task #%d nella posizione %d della colonna "%s"', + '%s opened the task #%d' => '%s ha aperto il task #%d', 'Activity' => 'Attività', 'Default values are "%s"' => 'Valori di default "%s"', 'Default columns for new projects (Comma-separated)' => 'Colonne di default per i nuovi progetti (Separati da virgola)', 'Task assignee change' => 'Cambia l\'assegnatario del task', - '%s change the assignee of the task #%d to %s' => '%s dai l\'assegnazione del task #%d a %s', + '%s changed the assignee of the task #%d to %s' => '%s dai l\'assegnazione del task #%d a %s', '%s changed the assignee of the task %s to %s' => '%s ha cambiato l\'assegnatario del task %s a %s', 'New password for the user "%s"' => 'Nuova password per l\'utente "%s"', 'Choose an event' => 'Scegli un evento', @@ -447,13 +434,10 @@ return array( 'Percentage' => 'Percentuale', 'Number of tasks' => 'Numero di task', 'Task distribution' => 'Distribuzione dei task', - 'Reportings' => 'Rapporti', - 'Task repartition for "%s"' => 'Ripartizione task per "%s"', // 'Analytics' => '', 'Subtask' => 'Sotto-task', 'My subtasks' => 'I miei sotto-task', 'User repartition' => 'Ripartizione per utente', - 'User repartition for "%s"' => 'Ripartizione utente per "%s"', 'Clone this project' => 'Clona questo progetto', 'Column removed successfully.' => 'Colonna rimossa con successo', 'Not enough data to show the graph.' => 'Non ci sono abbastanza dati per visualizzare il grafico.', @@ -470,10 +454,8 @@ return array( 'This value must be numeric' => 'Questo valore deve essere numerico', 'Unable to create this task.' => 'Impossibile creare questo task', 'Cumulative flow diagram' => 'Diagramma di flusso cumulativo', - 'Cumulative flow diagram for "%s"' => 'Diagramma di flusso comulativo per "%s"', 'Daily project summary' => 'Sommario giornaliero del progetto', 'Daily project summary export' => 'Export del sommario giornaliero del progetto', - 'Daily project summary export for "%s"' => 'Export del sommario giornaliero del progetto per "%s"', 'Exports' => 'Esporta', 'This export contains the number of tasks per column grouped per day.' => 'Questo export contiene il numero di task per colonna raggruppati per giorno', 'Active swimlanes' => 'Corsie attive', @@ -485,7 +467,6 @@ return array( 'Remove a swimlane' => 'Rimuovi una corsia', 'Show default swimlane' => 'Mostra la corsia predefinita', 'Swimlane modification for the project "%s"' => 'Modifica corsia per il progetto "%s"', - 'Swimlane not found.' => 'Corsia non trovata.', 'Swimlane removed successfully.' => 'Corsia rimossa con successo.', 'Swimlanes' => 'Corsie', 'Swimlane updated successfully.' => 'Corsia aggiornata con successo.', @@ -500,7 +481,6 @@ return array( 'Subtask Id' => 'Id del sotto-task', 'Subtasks' => 'Sotto-task', 'Subtasks Export' => 'Esporta i sotto-task', - 'Subtasks exportation for "%s"' => 'Export dei sotto-task per "%s"', 'Task Title' => 'Titolo del task', 'Untitled' => 'Senza titolo', 'Application default' => 'Default dell\'applicazione', @@ -607,7 +587,7 @@ return array( 'The currency rate have been added successfully.' => 'Il tasso di cambio è stato aggiunto con successo.', 'Unable to add this currency rate.' => 'Impossibile aggiungere questo tasso di cambio.', 'Webhook URL' => 'URL Webhook', - '%s remove the assignee of the task %s' => '%s rimuove l\'assegnatario del task %s', + '%s removed the assignee of the task %s' => '%s rimuove l\'assegnatario del task %s', 'Enable Gravatar images' => 'Abilita immagini Gravatar', 'Information' => 'Informazioni', 'Check two factor authentication code' => 'Controlla il codice di autenticazione "two-factor"', @@ -621,7 +601,6 @@ return array( 'Test your device' => 'Testa il tuo dispositivo', 'Assign a color when the task is moved to a specific column' => 'Assegna un colore quando il task viene spostato in una colonna specifica', '%s via Kanboard' => '%s tramite Kanboard', - 'Burndown chart for "%s"' => 'Grafico Burndown per "%s"', 'Burndown chart' => 'Grafico Burndown', 'This chart show the task complexity over the time (Work Remaining).' => 'Questo grafico mostra la complessità dei task nel tempo (Lavoro residuo).', 'Screenshot taken %s' => 'Schermata catturata %s', @@ -686,14 +665,8 @@ return array( 'Move the task to another column when the category is changed' => 'Sposta il task in un\'altra colonna quando la categoria viene modificata', 'Send a task by email to someone' => 'Invia un task via email a qualcuno', 'Reopen a task' => 'Riapri un task', - 'Column change' => 'Cambio di colonna', - 'Position change' => 'Cambio di posizione', - 'Swimlane change' => 'Cambio di corsia', - 'Assignee change' => 'Cambio assegnatario', - '[%s] Overdue tasks' => '[%s] Task scaduti', 'Notification' => 'Notifica', '%s moved the task #%d to the first swimlane' => '%s ha spostato il task #%d nella prima corsia', - '%s moved the task #%d to the swimlane "%s"' => '%s ha spostato il task #%d nella corsia "%s"', 'Swimlane' => 'Corsia', // 'Gravatar' => '', '%s moved the task %s to the first swimlane' => '%s ha spostato il task %s nella prima corsia', @@ -764,8 +737,6 @@ return array( 'Search by category: ' => 'Ricerca per categoria: ', 'Search by description: ' => 'Ricerca per descrizione: ', 'Search by due date: ' => 'Ricerca per data di scadenza: ', - 'Lead and Cycle time for "%s"' => 'Tempo di consegna (Lead Time) e lavorazione (Cycle Time) per "%s"', - 'Average time spent into each column for "%s"' => 'Tempo medio trascorso in ogni colonna per "%s"', 'Average time spent into each column' => 'Tempo medio trascorso in ogni colonna', 'Average time spent' => 'Tempo medio trascorso', 'This chart show the average time spent into each column for the last %d tasks.' => 'Questo grafico mostra il tempo medio trascorso in ogni colonna per gli ultimi %d task.', @@ -806,7 +777,6 @@ return array( 'License:' => 'Licenza:', 'License' => 'Licenza', 'Enter the text below' => 'Inserisci il testo qui sotto', - 'Gantt chart for %s' => 'Grafico Gantt per %s', 'Sort by position' => 'Ordina per posizione', 'Sort by date' => 'Ordina per data', 'Add task' => 'Aggiungi task', @@ -847,8 +817,6 @@ return array( 'Version' => 'Versione', 'Plugins' => 'Plugin', 'There is no plugin loaded.' => 'Nessun plugin è stato caricato.', - 'Set maximum column height' => 'Imposta l\'altezza massima della colonna', - 'Remove maximum column height' => 'Rimuovi l\'altezza massima della colonna', 'My notifications' => 'Le mie notifiche', 'Custom filters' => 'Filtri personalizzati', 'Your custom filter have been created successfully.' => 'Il filtro personalizzato è stato creato con successo.', @@ -953,7 +921,6 @@ return array( 'Invalid captcha' => 'Captcha non valido', 'The name must be unique' => 'Il nome deve essere univoco', 'View all groups' => 'Visualiza tutti i gruppi', - 'View group members' => 'Visualizza i membri del gruppo', 'There is no user available.' => 'Nessun utente disponibile.', 'Do you really want to remove the user "%s" from the group "%s"?' => 'Vuoi davvero rimuovere l\'utente "%s" dal gruppo "%s"?', 'There is no group.' => 'Nessun gruppo presente', @@ -974,13 +941,10 @@ return array( 'Enter group name...' => 'Inserisci il nome del gruppo...', 'Role:' => 'Ruolo:', 'Project members' => 'Membri di progetto', - 'Compare hours for "%s"' => 'Confronta le ore per "%s"', '%s mentioned you in the task #%d' => '%s ti ha menzionato nel task #%d', '%s mentioned you in a comment on the task #%d' => '%s ti ha menzionato in un commento del task #%d', 'You were mentioned in the task #%d' => 'Sei stato menzionato nel task #%d', 'You were mentioned in a comment on the task #%d' => 'Sei stato menzionato in un commento del task #%d', - 'Mentioned' => 'Menzionato', - 'Compare Estimated Time vs Actual Time' => 'Confronta il Tempo Stimato vs Tempo Effettivo', 'Estimated hours: ' => 'Ore stimate: ', 'Actual hours: ' => 'Ore effettive: ', 'Hours Spent' => 'Ore impiegate', @@ -1202,4 +1166,52 @@ return array( 'Email transport' => 'Trasporto Email', // 'Webhook token' => '', 'Imports' => 'Importa', + 'Project tags management' => 'Gestione tag di progetto', + 'Tag created successfully.' => 'Tag creato con successo.', + 'Unable to create this tag.' => 'Impossibile creare questo tag.', + 'Tag updated successfully.' => 'Tag aggiornato con successo.', + 'Unable to update this tag.' => 'Impossibile aggiornare questo tag.', + 'Tag removed successfully.' => 'Tag rimosso con successo.', + 'Unable to remove this tag.' => 'Impossibile rimuovere questo tag.', + 'Global tags management' => 'Gestione dei tag globali', + 'Tags' => 'Tag', + 'Tags management' => 'Gestione dei tag', + 'Add new tag' => 'Aggiungi un nuovo tag', + 'Edit a tag' => 'Modifica un tag', + 'Project tags' => 'Tag di progetto', + 'There is no specific tag for this project at the moment.' => 'Non è definito nessun tag specifico per questo progetto al momento.', + // 'Tag' => '', + 'Remove a tag' => 'Rimuovi un tag', + 'Do you really want to remove this tag: "%s"?' => 'Vuoi davvero rimuovere questo tag: "%s"?', + 'Global tags' => 'Tag globali', + 'There is no global tag at the moment.' => 'Non sono definiti tag globali al momento.', + 'This field cannot be empty' => 'Questo campo non può essere vuoto', + // 'Close a task when there is no activity in an specific column' => '', + // '%s removed a subtask for the task #%d' => '', + // '%s removed a comment on the task #%d' => '', + // 'Comment removed on task #%d' => '', + // 'Subtask removed on task #%d' => '', + // 'Hide tasks in this column in the dashboard' => '', + // '%s removed a comment on the task %s' => '', + // '%s removed a subtask for the task %s' => '', + // 'Comment removed' => '', + // 'Subtask removed' => '', + // '%s set a new internal link for the task #%d' => '', + // '%s removed an internal link for the task #%d' => '', + // 'A new internal link for the task #%d have been defined' => '', + // 'Internal link removed for the task #%d' => '', + // '%s set a new internal link for the task %s' => '', + // '%s removed an internal link for the task %s' => '', + // 'Automatically set the due date on task creation' => '', + // 'Move the task to another column when closed' => '', + // 'Move the task to another column when not moved during a given period' => '', + // 'Dashboard for %s' => '', + // 'Tasks overview for %s' => '', + // 'Subtasks overview for %s' => '', + // 'Projects overview for %s' => '', + // 'Activity stream for %s' => '', + // 'Calendar for %s' => '', + // 'Notifications for %s' => '', + // 'Subtasks export' => '', + // 'Tasks exportation' => '', ); diff --git a/app/Locale/ja_JP/translations.php b/app/Locale/ja_JP/translations.php index 061a2069..d3a11fc8 100644 --- a/app/Locale/ja_JP/translations.php +++ b/app/Locale/ja_JP/translations.php @@ -154,8 +154,6 @@ return array( '%d closed tasks' => '%d 個のクローズしたタスク', 'No task for this project' => 'このプロジェクトにタスクがありません', 'Public link' => '公開アクセス用リンク', - 'Change assignee' => '担当を変更する', - 'Change assignee for the task "%s"' => 'タスク「%s」の担当を変更する', 'Timezone' => 'タイムゾーン', 'Sorry, I didn\'t find this information in my database!' => 'データベース上で情報が見つかりませんでした!', 'Page not found' => 'ページが見つかりません', @@ -248,7 +246,6 @@ return array( 'Category' => 'カテゴリ', 'Category:' => 'カテゴリ:', 'Categories' => 'カテゴリ', - 'Category not found.' => 'カテゴリが見つかりません', 'Your category have been created successfully.' => 'カテゴリを作成しました。', 'Unable to create your category.' => 'カテゴリの作成に失敗しました。', 'Your category have been updated successfully.' => 'カテゴリを更新しました。', @@ -270,7 +267,6 @@ return array( 'Do you really want to remove this file: "%s"?' => 'ファイル「%s」を削除しますか?', 'Attachments' => '添付', 'Edit the task' => 'タスクを変更する', - 'Edit the description' => '説明を変更する', 'Add a comment' => 'コメントの追加', 'Edit a comment' => 'コメントを変更する', 'Summary' => '概要', @@ -303,7 +299,6 @@ return array( 'Display another project' => '別のプロジェクトを表示', 'Created by %s' => '%s が作成', 'Tasks Export' => 'タスクの出力', - 'Tasks exportation for "%s"' => '「%s」のタスク出力', 'Start Date' => '開始日', 'End Date' => '終了日', 'Execute' => '実行', @@ -326,14 +321,9 @@ return array( 'New sub-task' => '新しいサブタスク', 'New attachment added "%s"' => '添付ファイル「%s」が追加されました', 'New comment posted by %s' => '「%s」の新しいコメントが追加されました', - 'New attachment' => '新しい添付ファイル', 'New comment' => '新しいコメント', 'Comment updated' => 'コメントが更新されました', 'New subtask' => '新しいサブタスク', - 'Subtask updated' => 'サブタスクの更新', - 'Task updated' => 'タスクの更新', - 'Task closed' => 'タスクのクローズ', - 'Task opened' => 'タスクのオープン', 'I want to receive notifications only for those projects:' => '以下のプロジェクトにのみ通知を受け取る:', 'view the task on Kanboard' => 'Kanboard でタスクを見る', 'Public access' => '公開アクセス設定', @@ -368,7 +358,6 @@ return array( 'No external authentication enabled.' => '外部認証が設定されていません。', 'Password modified successfully.' => 'パスワードを変更しました。', 'Unable to change the password.' => 'パスワードが変更できませんでした。', - 'Change category for the task "%s"' => 'タスク「%s」のカテゴリの変更', 'Change category' => 'カテゴリの変更', '%s updated the task %s' => '%s がタスク %s をアップデートしました', '%s opened the task %s' => '%s がタスク %s をオープンしました', @@ -391,14 +380,12 @@ return array( '%s updated the task #%d' => '%s がタスク #%d を更新しました', '%s created the task #%d' => '%s がタスク #%d を追加しました', '%s closed the task #%d' => '%s がタスク #%d をクローズしました', - '%s open the task #%d' => '%s がタスク #%d をオープンしました', - '%s moved the task #%d to the column "%s"' => '%s がタスク #%d をカラム「%s」に移動しました', - '%s moved the task #%d to the position %d in the column "%s"' => '%s がタスク #%d を位置 %d カラム「%s」移動しました', + '%s opened the task #%d' => '%s がタスク #%d をオープンしました', 'Activity' => 'アクティビティ', 'Default values are "%s"' => 'デフォルト値は「%s」', 'Default columns for new projects (Comma-separated)' => '新規プロジェクトのデフォルトカラム (コンマで区切って入力)', 'Task assignee change' => '担当者の変更', - '%s change the assignee of the task #%d to %s' => '%s がタスク #%d の担当を %s に変更しました', + '%s changed the assignee of the task #%d to %s' => '%s がタスク #%d の担当を %s に変更しました', '%s changed the assignee of the task %s to %s' => '%s がタスク %s の担当を %s に変更しました', 'New password for the user "%s"' => 'ユーザ「%s」の新しいパスワード', 'Choose an event' => 'イベントの選択', @@ -447,13 +434,10 @@ return array( 'Percentage' => '割合', 'Number of tasks' => 'タスク数', 'Task distribution' => 'タスク分布', - 'Reportings' => 'レポート', - 'Task repartition for "%s"' => '「%s」のタスク分布', 'Analytics' => '分析', 'Subtask' => 'サブタスク', 'My subtasks' => '自分のサブタスク', 'User repartition' => '担当者分布', - 'User repartition for "%s"' => '「%s」の担当者分布', 'Clone this project' => 'このプロジェクトを複製する', 'Column removed successfully.' => 'カラムを削除しました', 'Not enough data to show the graph.' => 'グラフを描画するには出たが足りません', @@ -470,10 +454,8 @@ return array( 'This value must be numeric' => 'この値は数字でなければなりません', 'Unable to create this task.' => 'このタスクを作成できませんでした', 'Cumulative flow diagram' => '蓄積フロー図', - 'Cumulative flow diagram for "%s"' => '「%s」の蓄積フロー図', 'Daily project summary' => '日時プロジェクトサマリー', 'Daily project summary export' => '日時プロジェクトサマリーの出力', - 'Daily project summary export for "%s"' => '「%s」の日時プロジェクトサマリーの出力', 'Exports' => '出力', 'This export contains the number of tasks per column grouped per day.' => 'この出力は日時のカラムごとのタスク数を集計したものです', 'Active swimlanes' => 'アクティブなスイムレーン', @@ -485,7 +467,6 @@ return array( 'Remove a swimlane' => 'スイムレーンの削除', 'Show default swimlane' => 'デフォルトスイムレーンの表示', 'Swimlane modification for the project "%s"' => '「%s」に対するスイムレーン変更', - 'Swimlane not found.' => 'スイムレーンが見つかりません。', 'Swimlane removed successfully.' => 'スイムレーンを削除しました。', 'Swimlanes' => 'スイムレーン', 'Swimlane updated successfully.' => 'スイムレーンを更新しました。', @@ -500,7 +481,6 @@ return array( 'Subtask Id' => 'サブタスク Id', 'Subtasks' => 'サブタスク', 'Subtasks Export' => 'サブタスクの出力', - 'Subtasks exportation for "%s"' => '「%s」のサブタスク出力', 'Task Title' => 'タスクタイトル', 'Untitled' => 'タイトル無し', 'Application default' => 'アプリケーションデフォルト', @@ -607,7 +587,7 @@ return array( // 'The currency rate have been added successfully.' => '', 'Unable to add this currency rate.' => 'この通貨レートを追加できません。', 'Webhook URL' => 'Webhook URL', - '%s remove the assignee of the task %s' => '%s がタスク「%s」の担当を解除しました。', + '%s removed the assignee of the task %s' => '%s がタスク「%s」の担当を解除しました。', 'Enable Gravatar images' => 'Gravatar イメージを有効化', 'Information' => '情報 ', 'Check two factor authentication code' => '2 段認証をチェックする', @@ -621,7 +601,6 @@ return array( 'Test your device' => 'デバイスをテストする', // 'Assign a color when the task is moved to a specific column' => '', // '%s via Kanboard' => '', - // 'Burndown chart for "%s"' => '', // 'Burndown chart' => '', // 'This chart show the task complexity over the time (Work Remaining).' => '', // 'Screenshot taken %s' => '', @@ -686,14 +665,8 @@ return array( // 'Move the task to another column when the category is changed' => '', // 'Send a task by email to someone' => '', // 'Reopen a task' => '', - // 'Column change' => '', - // 'Position change' => '', - // 'Swimlane change' => '', - // 'Assignee change' => '', - // '[%s] Overdue tasks' => '', // 'Notification' => '', // '%s moved the task #%d to the first swimlane' => '', - // '%s moved the task #%d to the swimlane "%s"' => '', // 'Swimlane' => '', // 'Gravatar' => '', // '%s moved the task %s to the first swimlane' => '', @@ -764,8 +737,6 @@ return array( // 'Search by category: ' => '', // 'Search by description: ' => '', // 'Search by due date: ' => '', - // 'Lead and Cycle time for "%s"' => '', - // 'Average time spent into each column for "%s"' => '', // 'Average time spent into each column' => '', // 'Average time spent' => '', // 'This chart show the average time spent into each column for the last %d tasks.' => '', @@ -806,7 +777,6 @@ return array( // 'License:' => '', // 'License' => '', // 'Enter the text below' => '', - // 'Gantt chart for %s' => '', // 'Sort by position' => '', // 'Sort by date' => '', // 'Add task' => '', @@ -847,8 +817,6 @@ return array( // 'Version' => '', // 'Plugins' => '', // 'There is no plugin loaded.' => '', - // 'Set maximum column height' => '', - // 'Remove maximum column height' => '', // 'My notifications' => '', // 'Custom filters' => '', // 'Your custom filter have been created successfully.' => '', @@ -953,7 +921,6 @@ return array( // 'Invalid captcha' => '', // 'The name must be unique' => '', // 'View all groups' => '', - // 'View group members' => '', // 'There is no user available.' => '', // 'Do you really want to remove the user "%s" from the group "%s"?' => '', // 'There is no group.' => '', @@ -974,13 +941,10 @@ return array( // 'Enter group name...' => '', // 'Role:' => '', // 'Project members' => '', - // 'Compare hours for "%s"' => '', // '%s mentioned you in the task #%d' => '', // '%s mentioned you in a comment on the task #%d' => '', // 'You were mentioned in the task #%d' => '', // 'You were mentioned in a comment on the task #%d' => '', - // 'Mentioned' => '', - // 'Compare Estimated Time vs Actual Time' => '', // 'Estimated hours: ' => '', // 'Actual hours: ' => '', // 'Hours Spent' => '', @@ -1202,4 +1166,52 @@ return array( // 'Email transport' => '', // 'Webhook token' => '', // 'Imports' => '', + // 'Project tags management' => '', + // 'Tag created successfully.' => '', + // 'Unable to create this tag.' => '', + // 'Tag updated successfully.' => '', + // 'Unable to update this tag.' => '', + // 'Tag removed successfully.' => '', + // 'Unable to remove this tag.' => '', + // 'Global tags management' => '', + // 'Tags' => '', + // 'Tags management' => '', + // 'Add new tag' => '', + // 'Edit a tag' => '', + // 'Project tags' => '', + // 'There is no specific tag for this project at the moment.' => '', + // 'Tag' => '', + // 'Remove a tag' => '', + // 'Do you really want to remove this tag: "%s"?' => '', + // 'Global tags' => '', + // 'There is no global tag at the moment.' => '', + // 'This field cannot be empty' => '', + // 'Close a task when there is no activity in an specific column' => '', + // '%s removed a subtask for the task #%d' => '', + // '%s removed a comment on the task #%d' => '', + // 'Comment removed on task #%d' => '', + // 'Subtask removed on task #%d' => '', + // 'Hide tasks in this column in the dashboard' => '', + // '%s removed a comment on the task %s' => '', + // '%s removed a subtask for the task %s' => '', + // 'Comment removed' => '', + // 'Subtask removed' => '', + // '%s set a new internal link for the task #%d' => '', + // '%s removed an internal link for the task #%d' => '', + // 'A new internal link for the task #%d have been defined' => '', + // 'Internal link removed for the task #%d' => '', + // '%s set a new internal link for the task %s' => '', + // '%s removed an internal link for the task %s' => '', + // 'Automatically set the due date on task creation' => '', + // 'Move the task to another column when closed' => '', + // 'Move the task to another column when not moved during a given period' => '', + // 'Dashboard for %s' => '', + // 'Tasks overview for %s' => '', + // 'Subtasks overview for %s' => '', + // 'Projects overview for %s' => '', + // 'Activity stream for %s' => '', + // 'Calendar for %s' => '', + // 'Notifications for %s' => '', + // 'Subtasks export' => '', + // 'Tasks exportation' => '', ); diff --git a/app/Locale/ko_KR/translations.php b/app/Locale/ko_KR/translations.php index ea13419b..b8ec0c81 100644 --- a/app/Locale/ko_KR/translations.php +++ b/app/Locale/ko_KR/translations.php @@ -100,9 +100,9 @@ return array( 'There is nobody assigned' => '담당자가 없습니다', 'Column on the board:' => '칼럼:', 'Close this task' => '할일 마치기', - 'Open this task' => '할일 열기', - 'There is no description.' => '설명이 없습니다', - 'Add a new task' => '할일 추가 ', + 'Open this task' => '할일을 열다', + 'There is no description.' => '설명이 없다', + 'Add a new task' => '할일을 추가하는 ', 'The username is required' => '사용자 이름이 필요합니다', 'The maximum length is %d characters' => '최대 길이는 "%d" 글자 입니다', 'The minimum length is %d characters' => '최소 길이는 "%d" 글자 입니다', @@ -145,7 +145,7 @@ return array( 'User removed successfully.' => '사용자를 삭제했습니다.', 'Unable to remove this user.' => '사용자 삭제에 실패했습니다.', 'Board updated successfully.' => '보드를 갱신했습니다.', - 'Ready' => '준비완료', + 'Ready' => '준비중', 'Backlog' => '요구사항', 'Work in progress' => '진행중', 'Done' => '완료', @@ -154,8 +154,6 @@ return array( '%d closed tasks' => '%d개의 마친 할일', 'No task for this project' => '이 프로젝트에 할일이 없습니다', 'Public link' => '공개 접속 링크', - 'Change assignee' => '담당자 변경', - 'Change assignee for the task "%s"' => '할일 "%s"의 담당자를 변경', 'Timezone' => '시간대', 'Sorry, I didn\'t find this information in my database!' => '데이터베이스에서 정보가 발견되지 않았습니다!', 'Page not found' => '페이지가 발견되지 않는다', @@ -191,15 +189,15 @@ return array( 'Remove an automatic action' => '자동 액션의 삭제', 'Assign the task to a specific user' => '할일 담당자를 할당', 'Assign the task to the person who does the action' => '액션을 일으킨 사용자를 담당자이자', - 'Duplicate the task to another project' => '할일을 다른 프로젝트로 복사', - 'Move a task to another column' => '할일을 다른 칼럼으로 이동', + 'Duplicate the task to another project' => ' 다른 프로젝트에 할일을 복제하는 ', + 'Move a task to another column' => '할일을 다른 칼럼에 이동하는 ', 'Task modification' => '할일 변경', - 'Task creation' => '할일 만들기', - 'Closing a task' => '할일 종료', - 'Assign a color to a specific user' => '사용자 색 할당', + 'Task creation' => '할일을 만들', + 'Closing a task' => '할일을 닫혔다', + 'Assign a color to a specific user' => '색을 사용자에 할당', 'Column title' => '칼럼의 제목', 'Position' => '위치', - 'Duplicate to another project' => '다른 프로젝트로 복사', + 'Duplicate to another project' => '다른 프로젝트에 복사', 'Duplicate' => '복사', 'link' => '링크', 'Comment updated successfully.' => '댓글을 갱신했습니다.', @@ -248,7 +246,6 @@ return array( 'Category' => '카테고리', 'Category:' => '카테고리:', 'Categories' => '카테고리', - 'Category not found.' => '카테고리가 발견되지 않습니다', 'Your category have been created successfully.' => '카테고리를 작성했습니다.', 'Unable to create your category.' => '카테고리의 작성에 실패했습니다.', 'Your category have been updated successfully.' => '카테고리를 갱신했습니다.', @@ -270,7 +267,6 @@ return array( 'Do you really want to remove this file: "%s"?' => '파일 "%s" 을 삭제할까요?', 'Attachments' => '첨부', 'Edit the task' => '할일 수정', - 'Edit the description' => '설명 수정', 'Add a comment' => '댓글 추가', 'Edit a comment' => '댓글 수정', 'Summary' => '개요', @@ -303,7 +299,6 @@ return array( 'Display another project' => '프로젝트 보기', 'Created by %s' => '작성자 %s', 'Tasks Export' => '할일 내보내기', - 'Tasks exportation for "%s"' => '"%s" 할일 내보내기', 'Start Date' => '시작일', 'End Date' => '종료일', 'Execute' => '실행', @@ -326,14 +321,9 @@ return array( 'New sub-task' => '새로운 서브 할일', 'New attachment added "%s"' => '"%s"의 새로운 첨부 파일', 'New comment posted by %s' => '"%s"님이 댓글을 추가하였습니다', - 'New attachment' => ' 새로운 첨부 파일', 'New comment' => ' 새로운 댓글', 'Comment updated' => '댓글가 갱신되었습니다', 'New subtask' => ' 새로운 서브 할일', - 'Subtask updated' => '서브 할일 갱신', - 'Task updated' => '할일 업데이트', - 'Task closed' => '할일 마침', - 'Task opened' => '할일 시작', 'I want to receive notifications only for those projects:' => '다음 프로젝트의 알림만 받겠습니다:', 'view the task on Kanboard' => 'Kanboard에서 할일을 본다', 'Public access' => '공개 접속 설정', @@ -368,9 +358,8 @@ return array( 'No external authentication enabled.' => '외부 인증이 설정되어 있지 않습니다.', 'Password modified successfully.' => '패스워드를 변경했습니다.', 'Unable to change the password.' => '비밀 번호가 변경할 수 없었습니다.', - 'Change category for the task "%s"' => '할일 "%s"의 카테고리의 변경', 'Change category' => '카테고리 수정', - '%s updated the task %s' => '%s이 할일 %s을 업데이트했습니다', + '%s updated the task %s' => '%s이 할일 %s을 갱신 하였습니다', '%s opened the task %s' => '%s이 할일 %s을 시작시켰습니다', '%s moved the task %s to the position #%d in the column "%s"' => '%s이 할일%s을 위치#%d컬럼%s로 옮겼습니다', '%s moved the task %s to the column "%s"' => '%s이 할일 %s을 칼럼 "%s" 로 옮겼습니다', @@ -391,14 +380,12 @@ return array( '%s updated the task #%d' => '%s이 할일#%d을 갱신했습니다', '%s created the task #%d' => '%s이 할일#%d을 추가했습니다', '%s closed the task #%d' => '%s이 할일#%d을 닫혔습니다', - '%s open the task #%d' => '%s이 할일#%d를 오픈했습니다', - '%s moved the task #%d to the column "%s"' => '%s이 할일#%d을 칼럼"%s"로 옮겼습니다', - '%s moved the task #%d to the position %d in the column "%s"' => '%s이 할일#%d을 칼럼 "%s"의 %d 위치로 이동시켰습니다', + '%s opened the task #%d' => '%s이 할일#%d를 오픈했습니다', 'Activity' => '활동', 'Default values are "%s"' => '기본 값은 "%s" 입니다', 'Default columns for new projects (Comma-separated)' => '새로운 프로젝트의 기본 칼럼 (콤마(,)로 분리됨)', 'Task assignee change' => '담당자의 변경', - '%s change the assignee of the task #%d to %s' => '%s이 할일 #%d의 담당을 %s로 변경합니다', + '%s changed the assignee of the task #%d to %s' => '%s이 할일 #%d의 담당을 %s로 변경합니다', '%s changed the assignee of the task %s to %s' => '%s이 할일 %s의 담당을 %s로 변경했습니다', 'New password for the user "%s"' => '사용자 "%s"의 새로운 패스워드', 'Choose an event' => '행사의 선택', @@ -447,13 +434,10 @@ return array( 'Percentage' => '비중', 'Number of tasks' => '할일 수', 'Task distribution' => '할일 분포', - 'Reportings' => '리포트', - 'Task repartition for "%s"' => '"%s"의 할일 분포', 'Analytics' => '분석', 'Subtask' => '서브 할일', 'My subtasks' => '내 서브 할일', 'User repartition' => '담당자 분포', - 'User repartition for "%s"' => '"%s"의 담당자 분포', 'Clone this project' => '이 프로젝트를 복제하는 ', 'Column removed successfully.' => '(※)컬럼을 삭제했습니다', 'Not enough data to show the graph.' => '그래프를 선묘화하려면 나왔지만 부족합니다', @@ -470,10 +454,8 @@ return array( 'This value must be numeric' => '이 값은 숫자가 아니면 안 됩니다', 'Unable to create this task.' => '이 할일을 작성할 수 없었습니다', 'Cumulative flow diagram' => '축적 흐름', - 'Cumulative flow diagram for "%s"' => '"%s"의 축적 흐름', 'Daily project summary' => '하루 프로젝트 개요', 'Daily project summary export' => '하루 프로젝트 개요의 출력', - 'Daily project summary export for "%s"' => '"%s" 하루 프로젝트 개요의 출력', 'Exports' => '출력', 'This export contains the number of tasks per column grouped per day.' => '이 출력은 날짜의 칼람별 할일 수를 집계한 것입니다', 'Active swimlanes' => '액티브한 스윔레인', @@ -485,7 +467,6 @@ return array( 'Remove a swimlane' => '스윔레인의 삭제', 'Show default swimlane' => '기본 스윔레인의 표시', 'Swimlane modification for the project "%s"' => '"%s" 프로젝트의 스웜레인 수정', - 'Swimlane not found.' => '스윔레인이 발견되지 않습니다.', 'Swimlane removed successfully.' => '스윔레인을 삭제했습니다.', 'Swimlanes' => '스윔레인', 'Swimlane updated successfully.' => '스윔레인을 갱신했습니다.', @@ -500,7 +481,6 @@ return array( 'Subtask Id' => '서브 할일 Id', 'Subtasks' => '서브 할일', 'Subtasks Export' => '서브 할일 출력', - 'Subtasks exportation for "%s"' => '"%s"의 서브 할일 출력', 'Task Title' => '할일 제목', 'Untitled' => '제목 없음', 'Application default' => '애플리케이션 기본', @@ -607,7 +587,7 @@ return array( 'The currency rate have been added successfully.' => '통화가 성공적으로 추가되었습니다', 'Unable to add this currency rate.' => '이 통화 환율을 추가할 수 없습니다.', 'Webhook URL' => 'Webhook URL', - '%s remove the assignee of the task %s' => '%s이 할일 %s의 담당을 삭제했습니다', + '%s removed the assignee of the task %s' => '%s이 할일 %s의 담당을 삭제했습니다', 'Enable Gravatar images' => 'Gravatar이미지를 활성화', 'Information' => '정보', 'Check two factor authentication code' => '2단 인증을 체크한다', @@ -621,7 +601,6 @@ return array( 'Test your device' => '디바이스 테스트', 'Assign a color when the task is moved to a specific column' => '상세 칼럼으로 이동할 할일의 색깔을 지정하세요', '%s via Kanboard' => '%s via E-board', - 'Burndown chart for "%s"' => '"%s" 번다운 차트', 'Burndown chart' => '번다운 차트', // 'This chart show the task complexity over the time (Work Remaining).' => '', 'Screenshot taken %s' => '스크린샷_%s', @@ -671,13 +650,13 @@ return array( 'Show tasks based on the start date' => '시작 날짜로 할일 보기', 'Subtasks time tracking' => '서브 할일 시간 트래킹', 'User calendar view' => '담당자 달력 보기', - 'Automatically update the start date' => '시작일에 자동 업데이트', + 'Automatically update the start date' => '시작일에 자동 갱신', // 'iCal feed' => '', 'Preferences' => '우선권', 'Security' => '보안', 'Two factor authentication disabled' => '이중 인증이 비활성화 되었습니다', 'Two factor authentication enabled' => '이중 인증이 활성화 되었습니다', - 'Unable to update this user.' => '담당자의 업데이트가 가능합니다', + 'Unable to update this user.' => '담당자 갱신이 가능합니다', 'There is no user management for private projects.' => '비밀 프로젝트의 관리 담당자가 없습니다', 'User that will receive the email' => '그 담당자가 이메일을 수신할 것입니다', 'Email subject' => '이메일 제목', @@ -686,14 +665,8 @@ return array( 'Move the task to another column when the category is changed' => '카테고리 변경시 할일을 다른 칼럼으로 이동', 'Send a task by email to someone' => '할일을 이메일로 보내기', 'Reopen a task' => '할일 다시 시작', - 'Column change' => '칼럼 이동', - 'Position change' => '위치 이동', - 'Swimlane change' => '스윔레인 변경', - 'Assignee change' => '담당자 변경', - '[%s] Overdue tasks' => '[%s] 마감시간 지남', 'Notification' => '알림', '%s moved the task #%d to the first swimlane' => '%s가 할일 #%d를 첫번째 스웜레인으로 이동시켰습니다', - '%s moved the task #%d to the swimlane "%s"' => '%s가 할일 #%d를 "%s" 스웜레인으로 이동시켰습니다', 'Swimlane' => '스윔레인', // 'Gravatar' => '', '%s moved the task %s to the first swimlane' => '%s가 할일 %s를 첫번째 스웜레인으로 이동시켰습니다', @@ -716,7 +689,7 @@ return array( 'Recurrence settings have been modified' => '반복할일 설정 수정', 'Time spent changed: %sh' => '경과시간 변경: %s시간', 'Time estimated changed: %sh' => '%s시간으로 예상시간 변경', - 'The field "%s" have been updated' => '%s 필드가 업데이트 되어있습니다', + 'The field "%s" have been updated' => '%s 필드가 갱신되어 있습니다', 'The description has been modified:' => '설명이 수정되어 있습니다: ', 'Do you really want to close the task "%s" as well as all subtasks?' => '할일 "%s"과 서브 할일을 모두 마치시겠습니까?', 'I want to receive notifications for:' => '다음의 알림을 받기를 원합니다:', @@ -764,8 +737,6 @@ return array( 'Search by category: ' => '카테고리로 찾기 ', 'Search by description: ' => '설명으로 찾기 ', 'Search by due date: ' => '마감날짜로 찾기 ', - 'Lead and Cycle time for "%s"' => '"%s"의 리드와 사이클 시간', - 'Average time spent into each column for "%s"' => '"%s"의 각 칼럼 평균 소요시간', 'Average time spent into each column' => '각 칼럼의 평균 소요시간', 'Average time spent' => '평균 소요시간', 'This chart show the average time spent into each column for the last %d tasks.' => '마지막 %d 할일의 칼럼 평균 소요시간을 차트에 표시합니다', @@ -806,7 +777,6 @@ return array( 'License:' => '라이센스:', 'License' => '라이센스', 'Enter the text below' => '아랫글로 들어가기', - 'Gantt chart for %s' => '%s의 간트 차트', 'Sort by position' => '위치별 정렬', 'Sort by date' => '날짜별 정렬', 'Add task' => '할일 추가', @@ -847,8 +817,6 @@ return array( 'Version' => '버전', 'Plugins' => '플러그인', 'There is no plugin loaded.' => '플러그인이 로드되지 않았습니다', - 'Set maximum column height' => '최대 칼럼 높이 제한하기', - 'Remove maximum column height' => '최대 칼럼 높이 없애기', 'My notifications' => '내 알림', 'Custom filters' => '사용자 정의 필터', 'Your custom filter have been created successfully.' => '사용자 정의 필터가 성공적으로 생성되었습니다', @@ -861,11 +829,11 @@ return array( 'Web' => '웹', // 'New attachment on task #%d: %s' => '', 'New comment on task #%d' => '할일 #%d에 새로운 댓글이 달렸습니다', - 'Comment updated on task #%d' => '할일 #%d에 댓글이 업데이트되었습니다', - 'New subtask on task #%d' => '서브 할일 #%d이 업데이트되었습니다', - 'Subtask updated on task #%d' => '서브 할일 #%d가 업데이트되었습니다', + 'Comment updated on task #%d' => '할일 #%d에 댓글이 갱신 되었습니다', + 'New subtask on task #%d' => '서브 할일 #%d이 갱신 되었습니다', + 'Subtask updated on task #%d' => '서브 할일 #%d가 갱신 되었습니다', 'New task #%d: %s' => '할일 #%d: %s이 추가되었습니다', - 'Task updated #%d' => '할일 #%d이 업데이트되었습니다', + 'Task updated #%d' => '할일 #%d이 갱신 되었습니다', 'Task #%d closed' => '할일 #%d를 마쳤습니다', 'Task #%d opened' => '할일 #%d가 시작되었습니다', 'Column changed for task #%d' => '할일 #%d의 칼럼이 변경되었습니다', @@ -953,7 +921,6 @@ return array( // 'Invalid captcha' => '', 'The name must be unique' => '이름은 유일해야 합니다', 'View all groups' => '모든그룹보기', - 'View group members' => '그룹맴버 보기', 'There is no user available.' => '가능한 사용자가 없습니다', 'Do you really want to remove the user "%s" from the group "%s"?' => '"%s" 사용자를 "%s" 에서 삭제하시겠습니까?', 'There is no group.' => '그룹이 없습니다', @@ -974,13 +941,10 @@ return array( 'Enter group name...' => '그룹명을 입력합니다...', 'Role:' => '역할: ', 'Project members' => '프로젝트 멤버', - 'Compare hours for "%s"' => '"%s" 시간동안 비교', '%s mentioned you in the task #%d' => '#%d 할일에서 %s가 당신을 언급하였습니다', '%s mentioned you in a comment on the task #%d' => '#%d 할일에서 %s가 당신의 댓글을 언급하였습니다', 'You were mentioned in the task #%d' => '#%d 할일에서 당신이 언급되었습니다', 'You were mentioned in a comment on the task #%d' => '할일 #%d의 댓글에서 언급되었습니다', - 'Mentioned' => '언급된', - 'Compare Estimated Time vs Actual Time' => '예상 시간과 실제 시간 비교', 'Estimated hours: ' => '예상 시간: ', 'Actual hours: ' => '실제 시간: ', 'Hours Spent' => '소요 시간', @@ -1165,41 +1129,89 @@ return array( 'Upload files' => '파일 올리기', 'Installed Plugins' => '설치된 플러그인', 'Plugin Directory' => '플러그인 폴더', - 'Plugin installed successfully.' => '플러그인이 성공적으로 설치 되었습니다', - 'Plugin updated successfully.' => '플러그인이 성공적으로 업데이트 되었습니다', - 'Plugin removed successfully.' => '플러그인이 성공적으로 삭제 되었습니다', - 'Subtask converted to task successfully.' => '서브 할일이 성공적으로 할일로 변경 되었습니다', - 'Unable to convert the subtask.' => '서브할일 변경 비활성화', - 'Unable to extract plugin archive.' => '플러그인 보관소 비활성화', - 'Plugin not found.' => '플러그인을 찾을 수 없습니다', - 'You don\'t have the permission to remove this plugin.' => '플러그인 삭제 권한이 없습니다', - 'Unable to download plugin archive.' => '플러그인 보관소 다운로드 비활성화', - 'Unable to write temporary file for plugin.' => '플러그인의 임시 파일 기록 비활성화', - 'Unable to open plugin archive.' => '플러그인 보관소 열기 비활성화', - 'There is no file in the plugin archive.' => '플러그인 보관소에 파일이 없습니다', - 'Create tasks in bulk' => '벌크에 할일 생성', + 'Plugin installed successfully.' => '플러그인이 성공적으로 설치 되었습니다.', + 'Plugin updated successfully.' => '플러그인이 성공적으로 갱신 되었습니다.', + 'Plugin removed successfully.' => '플러그인이 성공적으로 삭제 되었습니다.', + 'Subtask converted to task successfully.' => '서브 할일이 할일로 성공적으로 변경 되었습니다.', + 'Unable to convert the subtask.' => '서브 할일로 변경할 수 없습니다.', + 'Unable to extract plugin archive.' => '플러그인 아카이브를 추출할 수 없습니다.', + 'Plugin not found.' => '플러그인을 찾을 수 없습니다.', + 'You don\'t have the permission to remove this plugin.' => '이 플러그인의 삭제 권한이 없습니다.', + 'Unable to download plugin archive.' => '플러그인 아카이브를 다운로드할 수 없습니다.', + 'Unable to write temporary file for plugin.' => '플러그인의 임시 파일을 기록할 수 없습니다.', + 'Unable to open plugin archive.' => '플러그인 아카이브를 열수 없습니다.', + 'There is no file in the plugin archive.' => '플러그인 아카이브에 파일이 존재하지 않습니다.', + 'Create tasks in bulk' => '대량의 할일 만들기', // 'Your Kanboard instance is not configured to install plugins from the user interface.' => '', - 'There is no plugin available.' => '사용할 수 있는 플러그인이 없습니다', + 'There is no plugin available.' => '사용 가능한 플러그인이 없습니다.', 'Install' => '설치', - 'Update' => '업데이트', - 'Up to date' => '최신의', + 'Update' => '갱신', + 'Up to date' => '날짜 갱신', 'Not available' => '사용 불가능', 'Remove plugin' => '플러그인 삭제', - 'Do you really want to remove this plugin: "%s"?' => '플러그인을 삭제 하시겠습니까: "%s"?', - 'Uninstall' => '제거', + 'Do you really want to remove this plugin: "%s"?' => '정말로 플러그인을 삭제하시겠습니까: "%s"?', + 'Uninstall' => '삭제', 'Listing' => '목록', - 'Metadata' => '메타 데이터', + 'Metadata' => '메타데이터', 'Manage projects' => '프로젝트 관리', - 'Convert to task' => '할일로 변경', - 'Convert sub-task to task' => '서브 할일을 할일로 변경', - 'Do you really want to convert this sub-task to a task?' => '서브 할일을 일로 변경하시겠습니까?', - 'My task title' => '할일 제목', - // 'Enter one task by line.' => '', - 'Number of failed login:' => '로그인 실패 횟수', - 'Account locked until:' => '계정이 잠겼습니다:', + 'Convert to task' => '할일로 변경하기', + 'Convert sub-task to task' => '서브 할일을 할일로 변경하기', + 'Do you really want to convert this sub-task to a task?' => '정말로 서브 할일을 할일로 변경하시겠습니까?', + 'My task title' => '나의 할일 제목', + 'Enter one task by line.' => '정확히 하나의 할일로 진입', + 'Number of failed login:' => '로그인 실패 횟수:', + 'Account locked until:' => '다음동안 계정이 잠겼습니다:', 'Email settings' => '이메일 설정', - 'Email sender address' => '이메일 송신자 주소', + 'Email sender address' => '이메일 보낸이 주소', 'Email transport' => '이메일 전송', // 'Webhook token' => '', 'Imports' => '가져오기', + // 'Project tags management' => '', + // 'Tag created successfully.' => '', + // 'Unable to create this tag.' => '', + // 'Tag updated successfully.' => '', + // 'Unable to update this tag.' => '', + // 'Tag removed successfully.' => '', + // 'Unable to remove this tag.' => '', + // 'Global tags management' => '', + // 'Tags' => '', + // 'Tags management' => '', + // 'Add new tag' => '', + // 'Edit a tag' => '', + // 'Project tags' => '', + // 'There is no specific tag for this project at the moment.' => '', + // 'Tag' => '', + // 'Remove a tag' => '', + // 'Do you really want to remove this tag: "%s"?' => '', + // 'Global tags' => '', + // 'There is no global tag at the moment.' => '', + // 'This field cannot be empty' => '', + // 'Close a task when there is no activity in an specific column' => '', + // '%s removed a subtask for the task #%d' => '', + // '%s removed a comment on the task #%d' => '', + // 'Comment removed on task #%d' => '', + // 'Subtask removed on task #%d' => '', + // 'Hide tasks in this column in the dashboard' => '', + // '%s removed a comment on the task %s' => '', + // '%s removed a subtask for the task %s' => '', + // 'Comment removed' => '', + // 'Subtask removed' => '', + // '%s set a new internal link for the task #%d' => '', + // '%s removed an internal link for the task #%d' => '', + // 'A new internal link for the task #%d have been defined' => '', + // 'Internal link removed for the task #%d' => '', + // '%s set a new internal link for the task %s' => '', + // '%s removed an internal link for the task %s' => '', + // 'Automatically set the due date on task creation' => '', + // 'Move the task to another column when closed' => '', + // 'Move the task to another column when not moved during a given period' => '', + // 'Dashboard for %s' => '', + // 'Tasks overview for %s' => '', + // 'Subtasks overview for %s' => '', + // 'Projects overview for %s' => '', + // 'Activity stream for %s' => '', + // 'Calendar for %s' => '', + // 'Notifications for %s' => '', + // 'Subtasks export' => '', + // 'Tasks exportation' => '', ); diff --git a/app/Locale/my_MY/translations.php b/app/Locale/my_MY/translations.php index 913b690a..79baadf7 100644 --- a/app/Locale/my_MY/translations.php +++ b/app/Locale/my_MY/translations.php @@ -154,8 +154,6 @@ return array( '%d closed tasks' => '%d tugas yang ditutup', 'No task for this project' => 'Tidak ada tugas dalam projek ini', 'Public link' => 'Pautan publik', - 'Change assignee' => 'Mengubah orang yand ditugaskan', - 'Change assignee for the task "%s"' => 'Mengubah orang yang ditugaskan untuk tugas « %s »', 'Timezone' => 'Zona waktu', 'Sorry, I didn\'t find this information in my database!' => 'Maaf, saya tidak menemukan informasi ini dalam basis data saya !', 'Page not found' => 'Halaman tidak ditemukan', @@ -248,7 +246,6 @@ return array( 'Category' => 'Kategori', 'Category:' => 'Kategori :', 'Categories' => 'Kategori', - 'Category not found.' => 'Kategori tidak ditemukan', 'Your category have been created successfully.' => 'Kategori anda berhasil dibuat.', 'Unable to create your category.' => 'Tidak dapat membuat kategori anda.', 'Your category have been updated successfully.' => 'Kategori anda berhasil diperbaharui.', @@ -270,7 +267,6 @@ return array( 'Do you really want to remove this file: "%s"?' => 'Apakah anda yakin akan menghapus berkas ini « %s » ?', 'Attachments' => 'Lampiran', 'Edit the task' => 'Modifikasi tugas', - 'Edit the description' => 'Modifikasi deskripsi', 'Add a comment' => 'Tambahkan komentar', 'Edit a comment' => 'Modifikasi komentar', 'Summary' => 'Ringkasan', @@ -303,7 +299,6 @@ return array( 'Display another project' => 'Lihat projek lain', 'Created by %s' => 'Dibuat oleh %s', 'Tasks Export' => 'Ekspor Tugas', - 'Tasks exportation for "%s"' => 'Tugas di ekspor untuk « %s »', 'Start Date' => 'Tanggal Mulai', 'End Date' => 'Tanggal Berakhir', 'Execute' => 'Eksekusi', @@ -326,14 +321,9 @@ return array( 'New sub-task' => 'Sub-tugas baru', 'New attachment added "%s"' => 'Lampiran baru ditambahkan « %s »', 'New comment posted by %s' => 'Komentar baru ditambahkan oleh « %s »', - 'New attachment' => 'Lampirkan baru', 'New comment' => 'Komentar baru', 'Comment updated' => 'Komentar diperbaharui', 'New subtask' => 'Sub-tugas baru', - 'Subtask updated' => 'Sub-tugas diperbaharui', - 'Task updated' => 'Tugas diperbaharui', - 'Task closed' => 'Tugas ditutup', - 'Task opened' => 'Tugas dibuka', 'I want to receive notifications only for those projects:' => 'Saya ingin menerima pemberitahuan hanya untuk projek-projek yang dipilih :', 'view the task on Kanboard' => 'lihat tugas di Kanboard', 'Public access' => 'Akses awam', @@ -368,7 +358,6 @@ return array( 'No external authentication enabled.' => 'Tidak ada otentifikasi eksternal yang aktif.', 'Password modified successfully.' => 'Kata laluan telah berjaya ditukar.', 'Unable to change the password.' => 'Tidak dapat merubah kata laluanr.', - 'Change category for the task "%s"' => 'Rubah kategori untuk tugas « %s »', 'Change category' => 'Tukar kategori', '%s updated the task %s' => '%s memperbaharui tugas %s', '%s opened the task %s' => '%s membuka tugas %s', @@ -391,14 +380,12 @@ return array( '%s updated the task #%d' => '%s memperbaharui tugas n°%d', '%s created the task #%d' => '%s membuat tugas n°%d', '%s closed the task #%d' => '%s menutup tugas n°%d', - '%s open the task #%d' => '%s membuka tugas n°%d', - '%s moved the task #%d to the column "%s"' => '%s memindahkan tugas n°%d ke kolom « %s »', - '%s moved the task #%d to the position %d in the column "%s"' => '%s memindahkan tugas n°%d ke posisi n°%d dalam kolom « %s »', + '%s opened the task #%d' => '%s membuka tugas n°%d', 'Activity' => 'Aktifitas', 'Default values are "%s"' => 'Standar nilai adalah« %s »', 'Default columns for new projects (Comma-separated)' => 'Kolom default untuk projek baru (dipisahkan dengan koma)', 'Task assignee change' => 'Mengubah orang ditugaskan untuk tugas', - '%s change the assignee of the task #%d to %s' => '%s rubah orang yang ditugaskan dari tugas n%d ke %s', + '%s changed the assignee of the task #%d to %s' => '%s rubah orang yang ditugaskan dari tugas n%d ke %s', '%s changed the assignee of the task %s to %s' => '%s mengubah orang yang ditugaskan dari tugas %s ke %s', 'New password for the user "%s"' => 'Kata laluan baru untuk pengguna « %s »', 'Choose an event' => 'Pilih sebuah acara', @@ -447,13 +434,10 @@ return array( 'Percentage' => 'Persentasi', 'Number of tasks' => 'Jumlah dari tugas', 'Task distribution' => 'Pembagian tugas', - 'Reportings' => 'Pelaporan', - 'Task repartition for "%s"' => 'Pembagian tugas untuk « %s »', 'Analytics' => 'Analitis', 'Subtask' => 'Subtugas', 'My subtasks' => 'Subtugas saya', 'User repartition' => 'Partisi ulang pengguna', - 'User repartition for "%s"' => 'Partisi ulang pengguna untuk « %s »', 'Clone this project' => 'Gandakan projek ini', 'Column removed successfully.' => 'Kolom berhasil dihapus.', 'Not enough data to show the graph.' => 'Tidak cukup data untuk menampilkan grafik.', @@ -470,10 +454,8 @@ return array( 'This value must be numeric' => 'Nilai ini harus angka', 'Unable to create this task.' => 'Tidak dapat membuat tugas ini', 'Cumulative flow diagram' => 'Diagram alir kumulatif', - 'Cumulative flow diagram for "%s"' => 'Diagram alir kumulatif untuk « %s »', 'Daily project summary' => 'Ringkasan projek harian', 'Daily project summary export' => 'Ekspot ringkasan projek harian', - 'Daily project summary export for "%s"' => 'Ekspor ringkasan projek harian untuk « %s »', 'Exports' => 'Ekspor', 'This export contains the number of tasks per column grouped per day.' => 'Ekspor ini berisi jumlah dari tugas per kolom dikelompokan perhari.', 'Active swimlanes' => 'Swimlanes aktif', @@ -485,7 +467,6 @@ return array( 'Remove a swimlane' => 'Padam swimlane', 'Show default swimlane' => 'Tampilkan piawai swimlane', 'Swimlane modification for the project "%s"' => 'Modifikasi swimlane untuk projek « %s »', - 'Swimlane not found.' => 'Swimlane tidak ditemui.', 'Swimlane removed successfully.' => 'Swimlane telah dipadamkan.', 'Swimlanes' => 'Swimlanes', 'Swimlane updated successfully.' => 'Swimlane telah dikemaskini.', @@ -500,7 +481,6 @@ return array( 'Subtask Id' => 'Id Subtugas', 'Subtasks' => 'Subtugas', 'Subtasks Export' => 'Ekspot Subtugas', - 'Subtasks exportation for "%s"' => 'Ekspor subtugas untuk « %s »', 'Task Title' => 'Judul Tugas', 'Untitled' => 'Tanpa nama', 'Application default' => 'Aplikasi Piawaian', @@ -607,7 +587,7 @@ return array( 'The currency rate have been added successfully.' => 'Nilai tukar mata uang berhasil ditambahkan.', 'Unable to add this currency rate.' => 'Tidak dapat menambahkan nilai tukar mata uang', 'Webhook URL' => 'URL webhook', - '%s remove the assignee of the task %s' => '%s menghapus penugasan dari tugas %s', + '%s removed the assignee of the task %s' => '%s menghapus penugasan dari tugas %s', 'Enable Gravatar images' => 'Mengaktifkan gambar Gravatar', 'Information' => 'Informasi', 'Check two factor authentication code' => 'Cek dua faktor kode otentifikasi', @@ -621,7 +601,6 @@ return array( 'Test your device' => 'Menguji perangkat anda', 'Assign a color when the task is moved to a specific column' => 'Menetapkan warna ketika tugas tersebut dipindahkan ke kolom tertentu', '%s via Kanboard' => '%s via Kanboard', - 'Burndown chart for "%s"' => 'Grafik Burndown untku « %s »', 'Burndown chart' => 'Grafik Burndown', 'This chart show the task complexity over the time (Work Remaining).' => 'Grafik ini menunjukkan kompleksitas tugas dari waktu ke waktu (Sisa Pekerjaan).', 'Screenshot taken %s' => 'Screenshot diambil %s', @@ -686,14 +665,8 @@ return array( 'Move the task to another column when the category is changed' => 'Pindahkan tugas ke kolom lain ketika kategori berubah', 'Send a task by email to someone' => 'Kirim tugas melalui email ke seseorang', 'Reopen a task' => 'Membuka kembali tugas', - 'Column change' => 'Kolom berubah', - 'Position change' => 'Posisi berubah', - 'Swimlane change' => 'Swimlane berubah', - 'Assignee change' => 'Penerima berubah', - '[%s] Overdue tasks' => '[%s] Tugas terlambat', 'Notification' => 'Pemberitahuan', '%s moved the task #%d to the first swimlane' => '%s memindahkan tugas n°%d ke swimlane pertama', - '%s moved the task #%d to the swimlane "%s"' => '%s memindahkan tugas n°%d ke swimlane « %s »', 'Swimlane' => 'Swimlane', 'Gravatar' => 'Gravatar', '%s moved the task %s to the first swimlane' => '%s memindahkan tugas %s ke swimlane pertama', @@ -764,8 +737,6 @@ return array( 'Search by category: ' => 'Pencarian berdasarkan kategori : ', 'Search by description: ' => 'Pencarian berdasarkan deskripsi : ', 'Search by due date: ' => 'Pencarian berdasarkan tanggal jatuh tempo : ', - 'Lead and Cycle time for "%s"' => 'Memimpin dan Siklus waktu untuk « %s »', - 'Average time spent into each column for "%s"' => 'Rata-rata waktu yang dihabiskan dalam setiap kolom untuk « %s »', 'Average time spent into each column' => 'Rata-rata waktu yang dihabiskan dalam setiap kolom', 'Average time spent' => 'Rata-rata waktu yang dihabiskan', 'This chart show the average time spent into each column for the last %d tasks.' => 'Grafik ini menunjukkan rata-rata waktu yang dihabiskan dalam setiap kolom untuk %d tugas.', @@ -806,7 +777,6 @@ return array( 'License:' => 'Lesen:', 'License' => 'Lesen', 'Enter the text below' => 'Masukkan teks di bawah', - 'Gantt chart for %s' => 'Carta Gantt untuk %s', 'Sort by position' => 'Urutkan berdasarkan posisi', 'Sort by date' => 'Urutkan berdasarkan tanggal', 'Add task' => 'Tambah tugas', @@ -847,8 +817,6 @@ return array( // 'Version' => '', // 'Plugins' => '', // 'There is no plugin loaded.' => '', - // 'Set maximum column height' => '', - // 'Remove maximum column height' => '', // 'My notifications' => '', // 'Custom filters' => '', // 'Your custom filter have been created successfully.' => '', @@ -953,7 +921,6 @@ return array( // 'Invalid captcha' => '', // 'The name must be unique' => '', // 'View all groups' => '', - // 'View group members' => '', // 'There is no user available.' => '', // 'Do you really want to remove the user "%s" from the group "%s"?' => '', // 'There is no group.' => '', @@ -974,13 +941,10 @@ return array( // 'Enter group name...' => '', 'Role:' => 'Peranan', 'Project members' => 'Anggota projek', - // 'Compare hours for "%s"' => '', // '%s mentioned you in the task #%d' => '', // '%s mentioned you in a comment on the task #%d' => '', // 'You were mentioned in the task #%d' => '', // 'You were mentioned in a comment on the task #%d' => '', - // 'Mentioned' => '', - // 'Compare Estimated Time vs Actual Time' => '', // 'Estimated hours: ' => '', // 'Actual hours: ' => '', // 'Hours Spent' => '', @@ -1202,4 +1166,52 @@ return array( // 'Email transport' => '', // 'Webhook token' => '', // 'Imports' => '', + // 'Project tags management' => '', + // 'Tag created successfully.' => '', + // 'Unable to create this tag.' => '', + // 'Tag updated successfully.' => '', + // 'Unable to update this tag.' => '', + // 'Tag removed successfully.' => '', + // 'Unable to remove this tag.' => '', + // 'Global tags management' => '', + // 'Tags' => '', + // 'Tags management' => '', + // 'Add new tag' => '', + // 'Edit a tag' => '', + // 'Project tags' => '', + // 'There is no specific tag for this project at the moment.' => '', + // 'Tag' => '', + // 'Remove a tag' => '', + // 'Do you really want to remove this tag: "%s"?' => '', + // 'Global tags' => '', + // 'There is no global tag at the moment.' => '', + // 'This field cannot be empty' => '', + // 'Close a task when there is no activity in an specific column' => '', + // '%s removed a subtask for the task #%d' => '', + // '%s removed a comment on the task #%d' => '', + // 'Comment removed on task #%d' => '', + // 'Subtask removed on task #%d' => '', + // 'Hide tasks in this column in the dashboard' => '', + // '%s removed a comment on the task %s' => '', + // '%s removed a subtask for the task %s' => '', + // 'Comment removed' => '', + // 'Subtask removed' => '', + // '%s set a new internal link for the task #%d' => '', + // '%s removed an internal link for the task #%d' => '', + // 'A new internal link for the task #%d have been defined' => '', + // 'Internal link removed for the task #%d' => '', + // '%s set a new internal link for the task %s' => '', + // '%s removed an internal link for the task %s' => '', + // 'Automatically set the due date on task creation' => '', + // 'Move the task to another column when closed' => '', + // 'Move the task to another column when not moved during a given period' => '', + // 'Dashboard for %s' => '', + // 'Tasks overview for %s' => '', + // 'Subtasks overview for %s' => '', + // 'Projects overview for %s' => '', + // 'Activity stream for %s' => '', + // 'Calendar for %s' => '', + // 'Notifications for %s' => '', + // 'Subtasks export' => '', + // 'Tasks exportation' => '', ); diff --git a/app/Locale/nb_NO/translations.php b/app/Locale/nb_NO/translations.php index c2e5b62f..3a0c3353 100644 --- a/app/Locale/nb_NO/translations.php +++ b/app/Locale/nb_NO/translations.php @@ -154,8 +154,6 @@ return array( '%d closed tasks' => '%d lukkede oppgaver', 'No task for this project' => 'Ingen oppgaver i dette prosjektet', 'Public link' => 'Offentligt lenke', - 'Change assignee' => 'Tildel oppgaven til andre', - 'Change assignee for the task "%s"' => 'Endre tildeling av oppgaven: "%s"', 'Timezone' => 'Tidssone', 'Sorry, I didn\'t find this information in my database!' => 'Denne informasjonen kunne ikke finnes i databasen!', 'Page not found' => 'Siden er ikke funnet', @@ -248,7 +246,6 @@ return array( 'Category' => 'Kategori', 'Category:' => 'Kategori:', 'Categories' => 'Kategorier', - 'Category not found.' => 'Kategori ikke funnet.', 'Your category have been created successfully.' => 'Kategorien er opprettet.', 'Unable to create your category.' => 'Kategorien kunne ikke opprettes.', 'Your category have been updated successfully.' => 'Kategorien er oppdatert.', @@ -270,7 +267,6 @@ return array( 'Do you really want to remove this file: "%s"?' => 'Vil du fjerne filen: "%s"?', 'Attachments' => 'Vedlegg', 'Edit the task' => 'Rediger oppgaven', - 'Edit the description' => 'Rediger beskrivelsen', 'Add a comment' => 'Legg til en kommentar', 'Edit a comment' => 'Rediger en kommentar', 'Summary' => 'Sammendrag', @@ -303,7 +299,6 @@ return array( 'Display another project' => 'Vis annet prosjekt...', 'Created by %s' => 'Opprettet av %s', 'Tasks Export' => 'Oppgave eksport', - 'Tasks exportation for "%s"' => 'Oppgaveeksportering for "%s"', 'Start Date' => 'Start-dato', 'End Date' => 'Slutt-dato', 'Execute' => 'KKjør', @@ -326,14 +321,9 @@ return array( 'New sub-task' => 'Ny deloppgave', 'New attachment added "%s"' => 'Nytt vedlegg er lagt tilet "%s"', 'New comment posted by %s' => 'Ny kommentar fra %s', - 'New attachment' => 'Nytt vedlegg', 'New comment' => 'Ny kommentar', 'Comment updated' => 'Kommentar oppdatert', 'New subtask' => 'Ny deloppgave', - 'Subtask updated' => 'Deloppgave oppdatert', - 'Task updated' => 'Oppgave oppdatert', - 'Task closed' => 'Oppgave lukket', - 'Task opened' => 'Oppgave åpnet', 'I want to receive notifications only for those projects:' => 'Jeg vil kun ha varslinger for disse prosjekter:', 'view the task on Kanboard' => 'se oppgaven påhovedsiden', 'Public access' => 'Offentlig tilgang', @@ -368,7 +358,6 @@ return array( 'No external authentication enabled.' => 'Ingen eksterne godkjenninger aktiveret.', 'Password modified successfully.' => 'Passord er endret.', 'Unable to change the password.' => 'Passordet kuenne ikke endres.', - 'Change category for the task "%s"' => 'Endre kategori for oppgaven "%s"', 'Change category' => 'Endre kategori', '%s updated the task %s' => '%s oppdaterte oppgaven %s', '%s opened the task %s' => '%s åpnet oppgaven %s', @@ -391,14 +380,12 @@ return array( '%s updated the task #%d' => '%s oppdaterte oppgaven #%d', '%s created the task #%d' => '%s opprettet oppgaven #%d', '%s closed the task #%d' => '%s lukket oppgaven #%d', - '%s open the task #%d' => '%s åpnet oppgaven #%d', - '%s moved the task #%d to the column "%s"' => '%s flyttet oppgaven #%d til kolonnen "%s"', - '%s moved the task #%d to the position %d in the column "%s"' => '%s flyttet oppgaven #%d til posisjonen %d i kolonnen "%s"', + '%s opened the task #%d' => '%s åpnet oppgaven #%d', 'Activity' => 'Aktivitetslogg', 'Default values are "%s"' => 'Standardverdier er "%s"', 'Default columns for new projects (Comma-separated)' => 'Standard kolonne for nye prosjekter (komma-separert)', 'Task assignee change' => 'Endring av oppgaveansvarlig', - '%s change the assignee of the task #%d to %s' => '%s endre ansvarlig for oppgaven #%d til %s', + '%s changed the assignee of the task #%d to %s' => '%s endre ansvarlig for oppgaven #%d til %s', '%s changed the assignee of the task %s to %s' => '%s endret ansvarlig for oppgaven %s til %s', 'New password for the user "%s"' => 'Nytt passord for brukeren "%s"', 'Choose an event' => 'Velg en hendelse', @@ -447,13 +434,10 @@ return array( 'Percentage' => 'Prosent', 'Number of tasks' => 'Antall oppgaver', 'Task distribution' => 'Kolonnefordeling', - 'Reportings' => 'Rapportering', - // 'Task repartition for "%s"' => '', 'Analytics' => 'Analyser', 'Subtask' => 'Deloppgave', 'My subtasks' => 'Mine deloppgaver', 'User repartition' => 'Brukerfordeling', - // 'User repartition for "%s"' => '', 'Clone this project' => 'Kopier dette prosjektet', 'Column removed successfully.' => 'Kolonne flyttet', // 'Not enough data to show the graph.' => '', @@ -470,10 +454,8 @@ return array( // 'This value must be numeric' => '', // 'Unable to create this task.' => '', 'Cumulative flow diagram' => 'Kumulativt flytdiagram', - // 'Cumulative flow diagram for "%s"' => '', 'Daily project summary' => 'Daglig prosjektsammendrag', // 'Daily project summary export' => '', - // 'Daily project summary export for "%s"' => '', 'Exports' => 'Eksporter', // 'This export contains the number of tasks per column grouped per day.' => '', 'Active swimlanes' => 'Aktive svømmebaner', @@ -485,7 +467,6 @@ return array( 'Remove a swimlane' => 'Fjern en svømmebane', 'Show default swimlane' => 'Vis standard svømmebane', // 'Swimlane modification for the project "%s"' => '', - 'Swimlane not found.' => 'Svømmebane ikke funnet', 'Swimlane removed successfully.' => 'Svømmebane fjernet', 'Swimlanes' => 'Svømmebaner', 'Swimlane updated successfully.' => 'Svømmebane oppdatert', @@ -500,7 +481,6 @@ return array( 'Subtask Id' => 'Deloppgave ID', 'Subtasks' => 'Deloppgaver', 'Subtasks Export' => 'Eksporter deloppgaver', - // 'Subtasks exportation for "%s"' => '', 'Task Title' => 'Oppgavetittel', // 'Untitled' => '', 'Application default' => 'Standardinstilling', @@ -607,7 +587,7 @@ return array( // 'The currency rate have been added successfully.' => '', // 'Unable to add this currency rate.' => '', // 'Webhook URL' => '', - // '%s remove the assignee of the task %s' => '', + // '%s removed the assignee of the task %s' => '', // 'Enable Gravatar images' => '', // 'Information' => '', // 'Check two factor authentication code' => '', @@ -621,7 +601,6 @@ return array( // 'Test your device' => '', 'Assign a color when the task is moved to a specific column' => 'Endre til en valgt farge hvis en oppgave flyttes til en spesifikk kolonne', // '%s via Kanboard' => '', - // 'Burndown chart for "%s"' => '', // 'Burndown chart' => '', // 'This chart show the task complexity over the time (Work Remaining).' => '', // 'Screenshot taken %s' => '', @@ -686,14 +665,8 @@ return array( 'Move the task to another column when the category is changed' => 'Flytt oppgaven til en annen kolonne når kategorien endres', 'Send a task by email to someone' => 'Send en oppgave på epost til noen', // 'Reopen a task' => '', - 'Column change' => 'Endret kolonne', - 'Position change' => 'Posisjonsendring', - 'Swimlane change' => 'Endret svømmebane', - 'Assignee change' => 'Endret eier', - // '[%s] Overdue tasks' => '', 'Notification' => 'Varsel', // '%s moved the task #%d to the first swimlane' => '', - // '%s moved the task #%d to the swimlane "%s"' => '', 'Swimlane' => 'Svømmebane', // 'Gravatar' => '', // '%s moved the task %s to the first swimlane' => '', @@ -764,8 +737,6 @@ return array( 'Search by category: ' => 'Søk etter kategori', 'Search by description: ' => 'Søk etter beskrivelse', 'Search by due date: ' => 'Søk etter frist', - // 'Lead and Cycle time for "%s"' => '', - // 'Average time spent into each column for "%s"' => '', // 'Average time spent into each column' => '', // 'Average time spent' => '', // 'This chart show the average time spent into each column for the last %d tasks.' => '', @@ -806,7 +777,6 @@ return array( 'License:' => 'Lisens:', 'License' => 'Lisens', 'Enter the text below' => 'Legg inn teksten nedenfor', - 'Gantt chart for %s' => 'Gantt skjema for %s', // 'Sort by position' => '', 'Sort by date' => 'Sorter etter dato', 'Add task' => 'Legg til oppgave', @@ -847,8 +817,6 @@ return array( // 'Version' => '', // 'Plugins' => '', // 'There is no plugin loaded.' => '', - // 'Set maximum column height' => '', - // 'Remove maximum column height' => '', // 'My notifications' => '', // 'Custom filters' => '', // 'Your custom filter have been created successfully.' => '', @@ -953,7 +921,6 @@ return array( // 'Invalid captcha' => '', // 'The name must be unique' => '', // 'View all groups' => '', - // 'View group members' => '', // 'There is no user available.' => '', // 'Do you really want to remove the user "%s" from the group "%s"?' => '', // 'There is no group.' => '', @@ -974,13 +941,10 @@ return array( // 'Enter group name...' => '', // 'Role:' => '', 'Project members' => 'Prosjektmedlemmer', - // 'Compare hours for "%s"' => '', // '%s mentioned you in the task #%d' => '', // '%s mentioned you in a comment on the task #%d' => '', // 'You were mentioned in the task #%d' => '', // 'You were mentioned in a comment on the task #%d' => '', - // 'Mentioned' => '', - // 'Compare Estimated Time vs Actual Time' => '', // 'Estimated hours: ' => '', // 'Actual hours: ' => '', // 'Hours Spent' => '', @@ -1202,4 +1166,52 @@ return array( // 'Email transport' => '', // 'Webhook token' => '', // 'Imports' => '', + // 'Project tags management' => '', + // 'Tag created successfully.' => '', + // 'Unable to create this tag.' => '', + // 'Tag updated successfully.' => '', + // 'Unable to update this tag.' => '', + // 'Tag removed successfully.' => '', + // 'Unable to remove this tag.' => '', + // 'Global tags management' => '', + // 'Tags' => '', + // 'Tags management' => '', + // 'Add new tag' => '', + // 'Edit a tag' => '', + // 'Project tags' => '', + // 'There is no specific tag for this project at the moment.' => '', + // 'Tag' => '', + // 'Remove a tag' => '', + // 'Do you really want to remove this tag: "%s"?' => '', + // 'Global tags' => '', + // 'There is no global tag at the moment.' => '', + // 'This field cannot be empty' => '', + // 'Close a task when there is no activity in an specific column' => '', + // '%s removed a subtask for the task #%d' => '', + // '%s removed a comment on the task #%d' => '', + // 'Comment removed on task #%d' => '', + // 'Subtask removed on task #%d' => '', + // 'Hide tasks in this column in the dashboard' => '', + // '%s removed a comment on the task %s' => '', + // '%s removed a subtask for the task %s' => '', + // 'Comment removed' => '', + // 'Subtask removed' => '', + // '%s set a new internal link for the task #%d' => '', + // '%s removed an internal link for the task #%d' => '', + // 'A new internal link for the task #%d have been defined' => '', + // 'Internal link removed for the task #%d' => '', + // '%s set a new internal link for the task %s' => '', + // '%s removed an internal link for the task %s' => '', + // 'Automatically set the due date on task creation' => '', + // 'Move the task to another column when closed' => '', + // 'Move the task to another column when not moved during a given period' => '', + // 'Dashboard for %s' => '', + // 'Tasks overview for %s' => '', + // 'Subtasks overview for %s' => '', + // 'Projects overview for %s' => '', + // 'Activity stream for %s' => '', + // 'Calendar for %s' => '', + // 'Notifications for %s' => '', + // 'Subtasks export' => '', + // 'Tasks exportation' => '', ); diff --git a/app/Locale/nl_NL/translations.php b/app/Locale/nl_NL/translations.php index 9d295129..5a026092 100644 --- a/app/Locale/nl_NL/translations.php +++ b/app/Locale/nl_NL/translations.php @@ -154,8 +154,6 @@ return array( '%d closed tasks' => '%d gesloten taken', 'No task for this project' => 'Geen taken voor dit project', 'Public link' => 'Publieke link', - 'Change assignee' => 'Toegewezene aanpassen', - 'Change assignee for the task "%s"' => 'Toegewezene aanpassen voor taak « %s »', 'Timezone' => 'Tijdzone', 'Sorry, I didn\'t find this information in my database!' => 'Sorry deze informatie kon niet worden gevonden in de database !', 'Page not found' => 'Pagina niet gevonden', @@ -248,7 +246,6 @@ return array( 'Category' => 'Categorie', 'Category:' => 'Categorie :', 'Categories' => 'Categorieën', - 'Category not found.' => 'Categorie niet gevonden', 'Your category have been created successfully.' => 'Categorie succesvol aangemaakt.', 'Unable to create your category.' => 'Categorie aanmaken niet gelukt.', 'Your category have been updated successfully.' => 'Categorie succesvol aangepast.', @@ -270,7 +267,6 @@ return array( 'Do you really want to remove this file: "%s"?' => 'Weet u zeker dat u dit bestand wil verwijderen: « %s » ?', 'Attachments' => 'Bijlages', 'Edit the task' => 'Taak aanpassen', - 'Edit the description' => 'Omschrijving aanpassen', 'Add a comment' => 'Commentaar toevoegen', 'Edit a comment' => 'Commentaar aanpassen', 'Summary' => 'Samenvatting', @@ -303,7 +299,6 @@ return array( 'Display another project' => 'Een ander project weergeven', 'Created by %s' => 'Aangemaakt door %s', 'Tasks Export' => 'Taken exporteren', - 'Tasks exportation for "%s"' => 'Taken exporteren voor « %s »', 'Start Date' => 'Startdatum', 'End Date' => 'Einddatum', 'Execute' => 'Uitvoeren', @@ -326,14 +321,9 @@ return array( 'New sub-task' => 'Nieuwe subtaak', 'New attachment added "%s"' => 'Nieuwe bijlage toegevoegd « %s »', 'New comment posted by %s' => 'Nieuw commentaar geplaatst door « %s »', - 'New attachment' => 'Nieuwe bijlage', 'New comment' => 'Nieuw commentaar', 'Comment updated' => 'Commentaar aangepast', 'New subtask' => 'Nieuwe subtaak', - 'Subtask updated' => 'Subtaak aangepast', - 'Task updated' => 'Taak aangepast', - 'Task closed' => 'Taak gesloten', - 'Task opened' => 'Taak geopend', 'I want to receive notifications only for those projects:' => 'Ik wil notificaties ontvangen van de volgende projecten :', 'view the task on Kanboard' => 'taak bekijken op Kanboard', 'Public access' => 'Publieke toegang', @@ -368,7 +358,6 @@ return array( 'No external authentication enabled.' => 'Geen externe authenticatie aangezet.', 'Password modified successfully.' => 'Wachtwoord succesvol aangepast.', 'Unable to change the password.' => 'Aanpassen van wachtwoord niet gelukt.', - 'Change category for the task "%s"' => 'Pas categorie aan voor taak « %s »', 'Change category' => 'Categorie aanpassen', '%s updated the task %s' => '%s heeft taak %s aangepast', '%s opened the task %s' => '%s heeft taak %s geopend', @@ -391,14 +380,12 @@ return array( '%s updated the task #%d' => '%s heeft taak %d aangepast', '%s created the task #%d' => '%s heeft taak %d aangemaakt', '%s closed the task #%d' => '%s heeft taak %d gesloten', - '%s open the task #%d' => '%s a heeft taak %d geopend', - '%s moved the task #%d to the column "%s"' => '%s heeft taak %d verplaatst naar kolom « %s »', - '%s moved the task #%d to the position %d in the column "%s"' => '%s heeft taak %d verplaatst naar positie %d in kolom « %s »', + '%s opened the task #%d' => '%s a heeft taak %d geopend', 'Activity' => 'Activiteit', 'Default values are "%s"' => 'Standaardwaarden zijn « %s »', 'Default columns for new projects (Comma-separated)' => 'Standaard kolommen voor nieuw projecten (komma gescheiden)', 'Task assignee change' => 'Taak toegewezene verandering', - '%s change the assignee of the task #%d to %s' => '%s heeft de toegewezene voor taak %d veranderd in %s', + '%s changed the assignee of the task #%d to %s' => '%s heeft de toegewezene voor taak %d veranderd in %s', '%s changed the assignee of the task %s to %s' => '%s heeft de toegewezene voor taak %s veranderd in %s', 'New password for the user "%s"' => 'Nieuw wachtwoord voor gebruiker « %s »', 'Choose an event' => 'Kies een gebeurtenis', @@ -447,13 +434,10 @@ return array( 'Percentage' => 'Percentage', 'Number of tasks' => 'Aantal taken', 'Task distribution' => 'Distributie van taken', - 'Reportings' => 'Rapporten', - 'Task repartition for "%s"' => 'Taakverdeling voor « %s »', 'Analytics' => 'Analytics', 'Subtask' => 'Subtaak', 'My subtasks' => 'Mijn subtaken', 'User repartition' => 'Gebruikerverdeling', - 'User repartition for "%s"' => 'Gebruikerverdeling voor « %s »', 'Clone this project' => 'Kloon dit project', 'Column removed successfully.' => 'Kolom succesvol verwijderd.', 'Not enough data to show the graph.' => 'Niet genoeg data om de grafiek te laten zien.', @@ -470,10 +454,8 @@ return array( 'This value must be numeric' => 'Deze waarde moet numeriek zijn', 'Unable to create this task.' => 'Aanmaken van de taak mislukt', 'Cumulative flow diagram' => 'Cummulatief stroomdiagram', - 'Cumulative flow diagram for "%s"' => 'Cummulatief stroomdiagram voor « %s »', 'Daily project summary' => 'Dagelijkse project samenvatting', 'Daily project summary export' => 'Dagelijkse project samenvatting export', - 'Daily project summary export for "%s"' => 'Dagelijkse project samenvatting voor « %s »', 'Exports' => 'Exports', 'This export contains the number of tasks per column grouped per day.' => 'Dit rapport bevat het aantal taken per kolom gegroupeerd per dag.', 'Active swimlanes' => 'Actieve swinlanes', @@ -485,7 +467,6 @@ return array( 'Remove a swimlane' => 'Verwijder swinlane', 'Show default swimlane' => 'Standaard swimlane tonen', 'Swimlane modification for the project "%s"' => 'Swinlane aanpassing voor project « %s »', - 'Swimlane not found.' => 'Swimlane niet gevonden.', 'Swimlane removed successfully.' => 'Swimlane succesvol verwijderd.', 'Swimlanes' => 'Swimlanes', 'Swimlane updated successfully.' => 'Swimlane succesvol aangepast.', @@ -500,7 +481,6 @@ return array( 'Subtask Id' => 'Subtaak id', 'Subtasks' => 'Subtaken', 'Subtasks Export' => 'Subtaken exporteren', - 'Subtasks exportation for "%s"' => 'Subtaken exporteren voor project « %s »', 'Task Title' => 'Taak title', 'Untitled' => 'Geen titel', 'Application default' => 'Standaard taal voor applicatie', @@ -607,7 +587,7 @@ return array( // 'The currency rate have been added successfully.' => '', // 'Unable to add this currency rate.' => '', 'Webhook URL' => 'Webhook URL', - // '%s remove the assignee of the task %s' => '', + // '%s removed the assignee of the task %s' => '', // 'Enable Gravatar images' => '', // 'Information' => '', // 'Check two factor authentication code' => '', @@ -621,7 +601,6 @@ return array( // 'Test your device' => '', // 'Assign a color when the task is moved to a specific column' => '', // '%s via Kanboard' => '', - // 'Burndown chart for "%s"' => '', // 'Burndown chart' => '', // 'This chart show the task complexity over the time (Work Remaining).' => '', // 'Screenshot taken %s' => '', @@ -686,14 +665,8 @@ return array( // 'Move the task to another column when the category is changed' => '', // 'Send a task by email to someone' => '', 'Reopen a task' => 'Heropen een taak', - // 'Column change' => '', - // 'Position change' => '', - // 'Swimlane change' => '', - // 'Assignee change' => '', - // '[%s] Overdue tasks' => '', // 'Notification' => '', // '%s moved the task #%d to the first swimlane' => '', - // '%s moved the task #%d to the swimlane "%s"' => '', 'Swimlane' => 'Swimlane', 'Gravatar' => 'Gravatar', '%s moved the task %s to the first swimlane' => '%s heeft de taak %s naar de eerste swimlane verplaatst', @@ -764,8 +737,6 @@ return array( // 'Search by category: ' => '', // 'Search by description: ' => '', // 'Search by due date: ' => '', - // 'Lead and Cycle time for "%s"' => '', - // 'Average time spent into each column for "%s"' => '', // 'Average time spent into each column' => '', // 'Average time spent' => '', // 'This chart show the average time spent into each column for the last %d tasks.' => '', @@ -806,7 +777,6 @@ return array( // 'License:' => '', // 'License' => '', // 'Enter the text below' => '', - // 'Gantt chart for %s' => '', // 'Sort by position' => '', // 'Sort by date' => '', // 'Add task' => '', @@ -847,8 +817,6 @@ return array( // 'Version' => '', // 'Plugins' => '', // 'There is no plugin loaded.' => '', - // 'Set maximum column height' => '', - // 'Remove maximum column height' => '', // 'My notifications' => '', // 'Custom filters' => '', // 'Your custom filter have been created successfully.' => '', @@ -953,7 +921,6 @@ return array( // 'Invalid captcha' => '', // 'The name must be unique' => '', // 'View all groups' => '', - // 'View group members' => '', // 'There is no user available.' => '', // 'Do you really want to remove the user "%s" from the group "%s"?' => '', // 'There is no group.' => '', @@ -974,13 +941,10 @@ return array( // 'Enter group name...' => '', // 'Role:' => '', // 'Project members' => '', - // 'Compare hours for "%s"' => '', // '%s mentioned you in the task #%d' => '', // '%s mentioned you in a comment on the task #%d' => '', // 'You were mentioned in the task #%d' => '', // 'You were mentioned in a comment on the task #%d' => '', - // 'Mentioned' => '', - // 'Compare Estimated Time vs Actual Time' => '', // 'Estimated hours: ' => '', // 'Actual hours: ' => '', // 'Hours Spent' => '', @@ -1202,4 +1166,52 @@ return array( // 'Email transport' => '', // 'Webhook token' => '', // 'Imports' => '', + // 'Project tags management' => '', + // 'Tag created successfully.' => '', + // 'Unable to create this tag.' => '', + // 'Tag updated successfully.' => '', + // 'Unable to update this tag.' => '', + // 'Tag removed successfully.' => '', + // 'Unable to remove this tag.' => '', + // 'Global tags management' => '', + // 'Tags' => '', + // 'Tags management' => '', + // 'Add new tag' => '', + // 'Edit a tag' => '', + // 'Project tags' => '', + // 'There is no specific tag for this project at the moment.' => '', + // 'Tag' => '', + // 'Remove a tag' => '', + // 'Do you really want to remove this tag: "%s"?' => '', + // 'Global tags' => '', + // 'There is no global tag at the moment.' => '', + // 'This field cannot be empty' => '', + // 'Close a task when there is no activity in an specific column' => '', + // '%s removed a subtask for the task #%d' => '', + // '%s removed a comment on the task #%d' => '', + // 'Comment removed on task #%d' => '', + // 'Subtask removed on task #%d' => '', + // 'Hide tasks in this column in the dashboard' => '', + // '%s removed a comment on the task %s' => '', + // '%s removed a subtask for the task %s' => '', + // 'Comment removed' => '', + // 'Subtask removed' => '', + // '%s set a new internal link for the task #%d' => '', + // '%s removed an internal link for the task #%d' => '', + // 'A new internal link for the task #%d have been defined' => '', + // 'Internal link removed for the task #%d' => '', + // '%s set a new internal link for the task %s' => '', + // '%s removed an internal link for the task %s' => '', + // 'Automatically set the due date on task creation' => '', + // 'Move the task to another column when closed' => '', + // 'Move the task to another column when not moved during a given period' => '', + // 'Dashboard for %s' => '', + // 'Tasks overview for %s' => '', + // 'Subtasks overview for %s' => '', + // 'Projects overview for %s' => '', + // 'Activity stream for %s' => '', + // 'Calendar for %s' => '', + // 'Notifications for %s' => '', + // 'Subtasks export' => '', + // 'Tasks exportation' => '', ); diff --git a/app/Locale/pl_PL/translations.php b/app/Locale/pl_PL/translations.php index 34429305..af3bcd4e 100644 --- a/app/Locale/pl_PL/translations.php +++ b/app/Locale/pl_PL/translations.php @@ -154,8 +154,6 @@ return array( '%d closed tasks' => '%d zamkniętych zadań', 'No task for this project' => 'Brak zadań dla tego projektu', 'Public link' => 'Link publiczny', - 'Change assignee' => 'Zmień odpowiedzialną osobę', - 'Change assignee for the task "%s"' => 'Zmień odpowiedzialną osobę dla zadania "%s"', 'Timezone' => 'Strefa czasowa', 'Sorry, I didn\'t find this information in my database!' => 'Niestety nie znaleziono tej informacji w bazie danych', 'Page not found' => 'Strona nie istnieje', @@ -248,7 +246,6 @@ return array( 'Category' => 'Kategoria', 'Category:' => 'Kategoria:', 'Categories' => 'Kategorie', - 'Category not found.' => 'Kategoria nie istnieje', 'Your category have been created successfully.' => 'Pomyślnie utworzono kategorię.', 'Unable to create your category.' => 'Nie można tworzyć kategorii.', 'Your category have been updated successfully.' => 'Pomyślnie zaktualizowano kategorię', @@ -270,7 +267,6 @@ return array( 'Do you really want to remove this file: "%s"?' => 'Czy na pewno chcesz usunąć plik: "%s"?', 'Attachments' => 'Załączniki', 'Edit the task' => 'Edytuj zadanie', - 'Edit the description' => 'Edytuj opis', 'Add a comment' => 'Dodaj komentarz', 'Edit a comment' => 'Edytuj komentarz', 'Summary' => 'Podsumowanie', @@ -303,7 +299,6 @@ return array( 'Display another project' => 'Wyświetl inny projekt', 'Created by %s' => 'Utworzone przez %s', 'Tasks Export' => 'Eksport zadań', - 'Tasks exportation for "%s"' => 'Eksport zadań dla "%s"', 'Start Date' => 'Data początkowa', 'End Date' => 'Data Końcowa', 'Execute' => 'Wykonaj', @@ -326,14 +321,9 @@ return array( 'New sub-task' => 'Nowe Pod-zadanie', 'New attachment added "%s"' => 'Nowy załącznik dodany "%s"', 'New comment posted by %s' => 'Nowy komentarz dodany przez %s', - 'New attachment' => 'Nowy załącznik', 'New comment' => 'Nowy Komentarz', 'Comment updated' => 'Komentarz zaktualizowany', 'New subtask' => 'Nowe pod-zadanie', - 'Subtask updated' => 'Zaktualizowane pod-zadanie', - 'Task updated' => 'Zaktualizowane zadanie', - 'Task closed' => 'Zadanie zamknięte', - 'Task opened' => 'Zadanie otwarte', 'I want to receive notifications only for those projects:' => 'Chcę otrzymywać powiadomienia tylko dla poniższych projektów:', 'view the task on Kanboard' => 'Zobacz zadanie', 'Public access' => 'Dostęp publiczny', @@ -368,7 +358,6 @@ return array( 'No external authentication enabled.' => 'Brak włączonych uwierzytelnień zewnętrznych.', 'Password modified successfully.' => 'Hasło zmienione pomyślne.', 'Unable to change the password.' => 'Nie można zmienić hasła.', - 'Change category for the task "%s"' => 'Zmień kategorię dla zadania "%s"', 'Change category' => 'Zmień kategorię', '%s updated the task %s' => '%s zaktualizował zadanie %s', '%s opened the task %s' => '%s otworzył zadanie %s', @@ -391,14 +380,12 @@ return array( '%s updated the task #%d' => '%s zaktualizował zadanie #%d', '%s created the task #%d' => '%s utworzył zadanie #%d', '%s closed the task #%d' => '%s zamknął zadanie #%d', - '%s open the task #%d' => '%s otworzył zadanie #%d', - '%s moved the task #%d to the column "%s"' => '%s przeniósł zadanie #%d do kolumny "%s"', - '%s moved the task #%d to the position %d in the column "%s"' => '%s przeniósł zadanie #%d na pozycję %d w kolmnie "%s"', + '%s opened the task #%d' => '%s otworzył zadanie #%d', 'Activity' => 'Aktywność', 'Default values are "%s"' => 'Domyślne wartości: "%s"', 'Default columns for new projects (Comma-separated)' => 'Domyślne kolumny dla nowych projektów (oddzielone przecinkiem)', 'Task assignee change' => 'Zmień osobę odpowiedzialną', - '%s change the assignee of the task #%d to %s' => '%s zmienił osobę odpowiedzialną za zadanie #%d na %s', + '%s changed the assignee of the task #%d to %s' => '%s zmienił osobę odpowiedzialną za zadanie #%d na %s', '%s changed the assignee of the task %s to %s' => '%s zmienił osobę odpowiedzialną za zadanie %s na %s', 'New password for the user "%s"' => 'Nowe hasło użytkownika "%s"', 'Choose an event' => 'Wybierz zdarzenie', @@ -447,13 +434,10 @@ return array( 'Percentage' => 'Procent', 'Number of tasks' => 'Liczba zadań', 'Task distribution' => 'Rozmieszczenie zadań', - 'Reportings' => 'Raporty', - 'Task repartition for "%s"' => 'Przydział zadań dla "%s"', 'Analytics' => 'Analizy', 'Subtask' => 'Pod-zadanie', 'My subtasks' => 'Moje pod-zadania', 'User repartition' => 'Przydział użytkownika', - 'User repartition for "%s"' => 'Przydział użytkownika dla "%s"', 'Clone this project' => 'Sklonuj ten projekt', 'Column removed successfully.' => 'Kolumna usunięta pomyślnie.', 'Not enough data to show the graph.' => 'Za mało danych do utworzenia wykresu.', @@ -470,10 +454,8 @@ return array( 'This value must be numeric' => 'Wartość musi być liczbą', 'Unable to create this task.' => 'Nie można tworzyć zadania.', 'Cumulative flow diagram' => 'Zbiorowy diagram przepływu', - 'Cumulative flow diagram for "%s"' => 'Zbiorowy diagram przepływu dla "%s"', 'Daily project summary' => 'Dzienne raport z projektu', 'Daily project summary export' => 'Eksport dziennego podsumowania projektu', - 'Daily project summary export for "%s"' => 'Wygeneruj dzienny raport dla projektu: "%s"', 'Exports' => 'Eksporty', 'This export contains the number of tasks per column grouped per day.' => 'Ten eksport zawiera ilość zadań zgrupowanych w kolumnach na dzień', 'Active swimlanes' => 'Aktywne tory', @@ -485,7 +467,6 @@ return array( 'Remove a swimlane' => 'Usuń tor', 'Show default swimlane' => 'Pokaż domyślny tor', 'Swimlane modification for the project "%s"' => 'Edycja torów dla projektu "%s"', - 'Swimlane not found.' => 'Nie znaleziono toru.', 'Swimlane removed successfully.' => 'Tor usunięty pomyślnie.', 'Swimlanes' => 'Tory', 'Swimlane updated successfully.' => 'Zaktualizowano tor.', @@ -500,7 +481,6 @@ return array( 'Subtask Id' => 'ID pod-zadania', 'Subtasks' => 'Pod-zadania', 'Subtasks Export' => 'Eksport pod-zadań', - 'Subtasks exportation for "%s"' => 'Wygeneruj raport pod-zadań dla projektu "%s"', 'Task Title' => 'Nazwa zadania', 'Untitled' => 'Bez nazwy', 'Application default' => 'Domyślne dla aplikacji', @@ -607,7 +587,7 @@ return array( 'The currency rate have been added successfully.' => 'Dodano kurs waluty', 'Unable to add this currency rate.' => 'Nie można dodać kursu waluty', 'Webhook URL' => 'Adres webhooka', - '%s remove the assignee of the task %s' => '%s usunął osobę przypisaną do zadania %s', + '%s removed the assignee of the task %s' => '%s usunął osobę przypisaną do zadania %s', 'Enable Gravatar images' => 'Włącz Gravatar', 'Information' => 'Informacje', 'Check two factor authentication code' => 'Sprawdź kod weryfikujący', @@ -621,7 +601,6 @@ return array( 'Test your device' => 'Przetestuj urządzenie', 'Assign a color when the task is moved to a specific column' => 'Przypisz kolor gdy zadanie jest przeniesione do danej kolumny', '%s via Kanboard' => '%s poprzez Kanboard', - 'Burndown chart for "%s"' => 'Wykres Burndown dla "%s"', 'Burndown chart' => 'Wykres Burndown', 'This chart show the task complexity over the time (Work Remaining).' => 'Ten wykres pokazuje złożoność zadania na przestrzeni czasu (pozostała praca).', 'Screenshot taken %s' => 'Zrzut ekranu zapisany %s', @@ -686,14 +665,8 @@ return array( 'Move the task to another column when the category is changed' => 'Przenieś zadanie do innej kolumny gdy kategoria ulegnie zmianie', 'Send a task by email to someone' => 'Wyślij zadanie emailem do kogoś', 'Reopen a task' => 'Otwórz ponownie zadanie', - 'Column change' => 'Zmiana kolumny', - 'Position change' => 'Zmiana pozycji', - 'Swimlane change' => 'Zmiana toru', - 'Assignee change' => 'Zmiana przypisanego użytkownika', - '[%s] Overdue tasks' => '[%s] zaległych zadań', 'Notification' => 'Powiadomienie', '%s moved the task #%d to the first swimlane' => '%s przeniosł zadanie #%d na pierwszy tor', - '%s moved the task #%d to the swimlane "%s"' => '%s przeniosł zadanie #%d na tor "%s"', 'Swimlane' => 'Tor', // 'Gravatar' => '', '%s moved the task %s to the first swimlane' => '%s przeniosł zadanie %s na pierwszy tor', @@ -764,8 +737,6 @@ return array( 'Search by category: ' => 'Szukaj wg kategorii:', 'Search by description: ' => 'Szukaj wg opisu:', 'Search by due date: ' => 'Szukaj wg terminu:', - 'Lead and Cycle time for "%s"' => 'Czas cyklu i realizacji dla "%s"', - 'Average time spent into each column for "%s"' => 'Średni czas spędzony w każdej z kolumn dla "%s"', 'Average time spent into each column' => 'Średni czas spędzony w każdej z kolumn', 'Average time spent' => 'Średni spędzony czas', 'This chart show the average time spent into each column for the last %d tasks.' => 'Niniejszy wykres pokazuje średni czas spędzony w każdej z kolumn dla ostatnich %d zadań.', @@ -806,7 +777,6 @@ return array( 'License:' => 'Licencja:', 'License' => 'Licencja', 'Enter the text below' => 'Wpisz tekst poniżej', - 'Gantt chart for %s' => 'Wykres Gantt dla %s', 'Sort by position' => 'Sortuj wg pozycji', 'Sort by date' => 'Sortuj wg daty', 'Add task' => 'Dodaj zadanie', @@ -847,8 +817,6 @@ return array( 'Version' => 'Wersja', 'Plugins' => 'Wtyczki', 'There is no plugin loaded.' => 'Nie wykryto żadnych wtyczek.', - 'Set maximum column height' => 'Rozwiń kolumny', - 'Remove maximum column height' => 'Zwiń kolumny', 'My notifications' => 'Powiadomienia', 'Custom filters' => 'Dostosuj filtry', 'Your custom filter have been created successfully.' => 'Niestandardowy filtr został utworzony.', @@ -953,7 +921,6 @@ return array( 'Invalid captcha' => 'Błędny kod z obrazka (captcha)', 'The name must be unique' => 'Nazwa musi być unikatowa', 'View all groups' => 'Wyświetl wszystkie grupy', - 'View group members' => 'Wyświetl wszystkich członków grupy', 'There is no user available.' => 'Żaden użytkownik nie jest dostępny.', 'Do you really want to remove the user "%s" from the group "%s"?' => 'Czy na pewno chcesz usunąć użytkownika "%s" z grupy "%s"?', 'There is no group.' => 'Nie utworzono jeszcze żadnej grupy.', @@ -974,13 +941,10 @@ return array( 'Enter group name...' => 'Wprowadź nazwę grupy...', 'Role:' => 'Rola:', 'Project members' => 'Uczestnicy projektu', - 'Compare hours for "%s"' => 'Porównaj godziny dla "%s"', '%s mentioned you in the task #%d' => '%s wspomiał o Tobie w zadaniu #%d', '%s mentioned you in a comment on the task #%d' => '%s wspomiał o Tobie w komentarzu do zadania #%d', 'You were mentioned in the task #%d' => 'Wspomiano o Tobie w zadaniu #%d', 'You were mentioned in a comment on the task #%d' => 'Wspomiano o Tobie w komentarzu do zadania #%d', - 'Mentioned' => 'Wspomiano', - 'Compare Estimated Time vs Actual Time' => 'Porównaj szacowany czas z rzeczywistym', 'Estimated hours: ' => 'Szacowane godziny: ', 'Actual hours: ' => 'Rzeczywiste godziny: ', 'Hours Spent' => 'Spędzone godziny', @@ -1202,4 +1166,52 @@ return array( // 'Email transport' => '', // 'Webhook token' => '', // 'Imports' => '', + // 'Project tags management' => '', + // 'Tag created successfully.' => '', + // 'Unable to create this tag.' => '', + // 'Tag updated successfully.' => '', + // 'Unable to update this tag.' => '', + // 'Tag removed successfully.' => '', + // 'Unable to remove this tag.' => '', + // 'Global tags management' => '', + // 'Tags' => '', + // 'Tags management' => '', + // 'Add new tag' => '', + // 'Edit a tag' => '', + // 'Project tags' => '', + // 'There is no specific tag for this project at the moment.' => '', + // 'Tag' => '', + // 'Remove a tag' => '', + // 'Do you really want to remove this tag: "%s"?' => '', + // 'Global tags' => '', + // 'There is no global tag at the moment.' => '', + // 'This field cannot be empty' => '', + // 'Close a task when there is no activity in an specific column' => '', + // '%s removed a subtask for the task #%d' => '', + // '%s removed a comment on the task #%d' => '', + // 'Comment removed on task #%d' => '', + // 'Subtask removed on task #%d' => '', + // 'Hide tasks in this column in the dashboard' => '', + // '%s removed a comment on the task %s' => '', + // '%s removed a subtask for the task %s' => '', + // 'Comment removed' => '', + // 'Subtask removed' => '', + // '%s set a new internal link for the task #%d' => '', + // '%s removed an internal link for the task #%d' => '', + // 'A new internal link for the task #%d have been defined' => '', + // 'Internal link removed for the task #%d' => '', + // '%s set a new internal link for the task %s' => '', + // '%s removed an internal link for the task %s' => '', + // 'Automatically set the due date on task creation' => '', + // 'Move the task to another column when closed' => '', + // 'Move the task to another column when not moved during a given period' => '', + // 'Dashboard for %s' => '', + // 'Tasks overview for %s' => '', + // 'Subtasks overview for %s' => '', + // 'Projects overview for %s' => '', + // 'Activity stream for %s' => '', + // 'Calendar for %s' => '', + // 'Notifications for %s' => '', + // 'Subtasks export' => '', + // 'Tasks exportation' => '', ); diff --git a/app/Locale/pt_BR/translations.php b/app/Locale/pt_BR/translations.php index 04b9feaf..2772d8bb 100644 --- a/app/Locale/pt_BR/translations.php +++ b/app/Locale/pt_BR/translations.php @@ -154,8 +154,6 @@ return array( '%d closed tasks' => '%d tarefas finalizadas', 'No task for this project' => 'Não há tarefa para este projeto', 'Public link' => 'Link público', - 'Change assignee' => 'Alterar designação', - 'Change assignee for the task "%s"' => 'Alterar designação para a tarefa "%s"', 'Timezone' => 'Fuso horário', 'Sorry, I didn\'t find this information in my database!' => 'Desculpe, não encontrei esta informação no meu banco de dados!', 'Page not found' => 'Página não encontrada', @@ -248,7 +246,6 @@ return array( 'Category' => 'Categoria', 'Category:' => 'Categoria:', 'Categories' => 'Categorias', - 'Category not found.' => 'Categoria não encontrada.', 'Your category have been created successfully.' => 'Sua categoria foi criada com sucesso.', 'Unable to create your category.' => 'Não foi possível criar a sua categoria.', 'Your category have been updated successfully.' => 'A sua categoria foi atualizada com sucesso.', @@ -270,7 +267,6 @@ return array( 'Do you really want to remove this file: "%s"?' => 'Você realmente deseja remover este arquivo: "%s"?', 'Attachments' => 'Anexos', 'Edit the task' => 'Editar a tarefa', - 'Edit the description' => 'Editar a descrição', 'Add a comment' => 'Adicionar um comentário', 'Edit a comment' => 'Editar um comentário', 'Summary' => 'Resumo', @@ -303,7 +299,6 @@ return array( 'Display another project' => 'Exibir outro projeto', 'Created by %s' => 'Criado por %s', 'Tasks Export' => 'Exportar Tarefas', - 'Tasks exportation for "%s"' => 'As tarefas foram exportadas para "%s"', 'Start Date' => 'Data inicial', 'End Date' => 'Data final', 'Execute' => 'Executar', @@ -326,14 +321,9 @@ return array( 'New sub-task' => 'Nova subtarefa', 'New attachment added "%s"' => 'Novo anexo adicionado "%s"', 'New comment posted by %s' => 'Novo comentário postado por %s', - 'New attachment' => 'Novo anexo', 'New comment' => 'Novo comentário', 'Comment updated' => 'Comentário atualizado', 'New subtask' => 'Nova subtarefa', - 'Subtask updated' => 'Subtarefa alterada', - 'Task updated' => 'Tarefa alterada', - 'Task closed' => 'Tarefa finalizada', - 'Task opened' => 'Tarefa aberta', 'I want to receive notifications only for those projects:' => 'Quero receber notificações apenas destes projetos:', 'view the task on Kanboard' => 'ver a tarefa no Kanboard', 'Public access' => 'Acesso público', @@ -368,7 +358,6 @@ return array( 'No external authentication enabled.' => 'Nenhuma autenticação externa habilitada.', 'Password modified successfully.' => 'Senha alterada com sucesso.', 'Unable to change the password.' => 'Não foi possível alterar a senha.', - 'Change category for the task "%s"' => 'Mudar categoria da tarefa "%s"', 'Change category' => 'Mudar categoria', '%s updated the task %s' => '%s atualizou a tarefa %s', '%s opened the task %s' => '%s abriu a tarefa %s', @@ -391,14 +380,12 @@ return array( '%s updated the task #%d' => '%s atualizou a tarefa #%d', '%s created the task #%d' => '%s criou a tarefa #%d', '%s closed the task #%d' => '%s finalizou a tarefa #%d', - '%s open the task #%d' => '%s abriu a tarefa #%d', - '%s moved the task #%d to the column "%s"' => '%s moveu a tarefa #%d para a coluna "%s"', - '%s moved the task #%d to the position %d in the column "%s"' => '%s moveu a tarefa #%d para a posição %d na coluna "%s"', + '%s opened the task #%d' => '%s abriu a tarefa #%d', 'Activity' => 'Atividade', 'Default values are "%s"' => 'Os valores padrão são "%s"', 'Default columns for new projects (Comma-separated)' => 'Colunas padrão para novos projetos (Separado por vírgula)', 'Task assignee change' => 'Mudar designação da tarefa', - '%s change the assignee of the task #%d to %s' => '%s mudou a designação da tarefa #%d para %s', + '%s changed the assignee of the task #%d to %s' => '%s mudou a designação da tarefa #%d para %s', '%s changed the assignee of the task %s to %s' => '%s mudou a designação da tarefa %s para %s', 'New password for the user "%s"' => 'Nova senha para o usuário "%s"', 'Choose an event' => 'Escolher um evento', @@ -447,13 +434,10 @@ return array( 'Percentage' => 'Porcentagem', 'Number of tasks' => 'Número de tarefas', 'Task distribution' => 'Distribuição de tarefas', - 'Reportings' => 'Relatórios', - 'Task repartition for "%s"' => 'Redistribuição da tarefa para "%s"', 'Analytics' => 'Estatísticas', 'Subtask' => 'Subtarefa', 'My subtasks' => 'Minhas subtarefas', 'User repartition' => 'Redistribuição de usuário', - 'User repartition for "%s"' => 'Redistribuição de usuário para "%s"', 'Clone this project' => 'Clonar este projeto', 'Column removed successfully.' => 'Coluna removida com sucesso.', 'Not enough data to show the graph.' => 'Não há dados suficientes para mostrar o gráfico.', @@ -470,10 +454,8 @@ return array( 'This value must be numeric' => 'Este valor deve ser numérico', 'Unable to create this task.' => 'Não foi possível criar esta tarefa.', 'Cumulative flow diagram' => 'Fluxograma cumulativo', - 'Cumulative flow diagram for "%s"' => 'Fluxograma cumulativo para "%s"', 'Daily project summary' => 'Resumo diário do projeto', 'Daily project summary export' => 'Exportação diária do resumo do projeto', - 'Daily project summary export for "%s"' => 'Exportação diária do resumo do projeto para "%s"', 'Exports' => 'Exportar', 'This export contains the number of tasks per column grouped per day.' => 'Esta exportação contém o número de tarefas por coluna agrupada por dia.', 'Active swimlanes' => 'Ativar swimlanes', @@ -485,7 +467,6 @@ return array( 'Remove a swimlane' => 'Remover uma swimlane', 'Show default swimlane' => 'Exibir swimlane padrão', 'Swimlane modification for the project "%s"' => 'Modificação de swimlane para o projeto "%s"', - 'Swimlane not found.' => 'Swimlane não encontrada.', 'Swimlane removed successfully.' => 'Swimlane removida com sucesso.', 'Swimlanes' => 'Swimlanes', 'Swimlane updated successfully.' => 'Swimlane atualizada com sucesso.', @@ -500,7 +481,6 @@ return array( 'Subtask Id' => 'ID da subtarefa', 'Subtasks' => 'Subtarefas', 'Subtasks Export' => 'Exportar subtarefas', - 'Subtasks exportation for "%s"' => 'Subtarefas exportadas para "%s"', 'Task Title' => 'Título da Tarefa', 'Untitled' => 'Sem título', 'Application default' => 'Padrão da aplicação', @@ -607,7 +587,7 @@ return array( 'The currency rate have been added successfully.' => 'A taxa de câmbio foi adicionada com sucesso.', 'Unable to add this currency rate.' => 'Impossível de adicionar essa taxa de câmbio.', 'Webhook URL' => 'URL do webhook', - '%s remove the assignee of the task %s' => '%s removeu a pessoa designada para a tarefa %s', + '%s removed the assignee of the task %s' => '%s removeu a pessoa designada para a tarefa %s', 'Enable Gravatar images' => 'Ativar imagens do Gravatar', 'Information' => 'Informações', 'Check two factor authentication code' => 'Verifique o código de autenticação em duas etapas', @@ -621,7 +601,6 @@ return array( 'Test your device' => 'Teste o seu dispositivo', 'Assign a color when the task is moved to a specific column' => 'Atribuir uma cor quando a tarefa é movida em uma coluna específica', '%s via Kanboard' => '%s via Kanboard', - 'Burndown chart for "%s"' => 'Gráfico de Burndown para "%s"', 'Burndown chart' => 'Gráfico de Burndown', 'This chart show the task complexity over the time (Work Remaining).' => 'Este gráfico mostra a complexidade da tarefa ao longo do tempo (Trabalho Restante).', 'Screenshot taken %s' => 'Captura de tela tirada em %s', @@ -686,14 +665,8 @@ return array( 'Move the task to another column when the category is changed' => 'Mover uma tarefa para outra coluna quando a categoria mudou', 'Send a task by email to someone' => 'Enviar uma tarefa por e-mail a alguém', 'Reopen a task' => 'Reabrir uma tarefa', - 'Column change' => 'Mudança de coluna', - 'Position change' => 'Mudança de posição', - 'Swimlane change' => 'Mudança de swimlane', - 'Assignee change' => 'Mudança de designação', - '[%s] Overdue tasks' => '[%s] Tarefas atrasadas', 'Notification' => 'Notificação', '%s moved the task #%d to the first swimlane' => '%s moveu a tarefa #%d para a primeira swimlane', - '%s moved the task #%d to the swimlane "%s"' => '%s moveu a tarefa #%d para a swimlane "%s"', 'Swimlane' => 'Swimlane', 'Gravatar' => 'Gravatar', '%s moved the task %s to the first swimlane' => '%s moveu a tarefa %s para a primeira swimlane', @@ -764,8 +737,6 @@ return array( 'Search by category: ' => 'Pesquisar por categoria: ', 'Search by description: ' => 'Pesquisar por descrição: ', 'Search by due date: ' => 'Pesquisar por data de expiração: ', - 'Lead and Cycle time for "%s"' => 'Lead and Cycle time para "%s"', - 'Average time spent into each column for "%s"' => 'Tempo médio gasto em cada coluna para "%s"', 'Average time spent into each column' => 'Tempo médio gasto em cada coluna', 'Average time spent' => 'Tempo médio gasto', 'This chart show the average time spent into each column for the last %d tasks.' => 'Este gráfico mostra o tempo médio gasto em cada coluna para as %d últimas tarefas.', @@ -806,7 +777,6 @@ return array( 'License:' => 'Licença:', 'License' => 'Licença', 'Enter the text below' => 'Entre o texto abaixo', - 'Gantt chart for %s' => 'Gráfico de Gantt para %s', 'Sort by position' => 'Ordenar por posição', 'Sort by date' => 'Ordenar por data', 'Add task' => 'Adicionar uma tarefa', @@ -847,8 +817,6 @@ return array( 'Version' => 'Versão', 'Plugins' => 'Extensões', 'There is no plugin loaded.' => 'Não há extensões carregadas.', - 'Set maximum column height' => 'Definir a altura máxima das colunas', - 'Remove maximum column height' => 'Retirar a altura máxima das colunas', 'My notifications' => 'Minhas notificações', 'Custom filters' => 'Filtros personalizados', 'Your custom filter have been created successfully.' => 'Seu filtro personalizado foi criado com sucesso.', @@ -953,7 +921,6 @@ return array( 'Invalid captcha' => 'Captcha inválido', 'The name must be unique' => 'O nome deve ser único', 'View all groups' => 'Ver todos os grupos', - 'View group members' => 'Ver os membros do grupo', 'There is no user available.' => 'Não há nenhum usuário disponível', 'Do you really want to remove the user "%s" from the group "%s"?' => 'Você realmente deseja remover o usuário "%s" do grupo "%s"?', 'There is no group.' => 'Não há nenhum grupo.', @@ -974,13 +941,10 @@ return array( 'Enter group name...' => 'Digite o nome do grupo', 'Role:' => 'Função:', 'Project members' => 'Membros de projeto', - 'Compare hours for "%s"' => 'Comparar as horas para "%s"', '%s mentioned you in the task #%d' => '%s mencionou você na tarefa #%d', '%s mentioned you in a comment on the task #%d' => '%s mencionou você num comentário da tarefa #%d', 'You were mentioned in the task #%d' => 'Você foi mencionado na tarefa #%d', 'You were mentioned in a comment on the task #%d' => 'Você foi mencionado num comentário da tarefa #%d', - 'Mentioned' => 'Mencionado', - 'Compare Estimated Time vs Actual Time' => 'Comparar o tempo estimado e o tempo atual', 'Estimated hours: ' => 'Horas estimadas: ', 'Actual hours: ' => 'Horas reais: ', 'Hours Spent' => 'Horas gastas', @@ -1202,4 +1166,52 @@ return array( // 'Email transport' => '', // 'Webhook token' => '', // 'Imports' => '', + // 'Project tags management' => '', + // 'Tag created successfully.' => '', + // 'Unable to create this tag.' => '', + // 'Tag updated successfully.' => '', + // 'Unable to update this tag.' => '', + // 'Tag removed successfully.' => '', + // 'Unable to remove this tag.' => '', + // 'Global tags management' => '', + // 'Tags' => '', + // 'Tags management' => '', + // 'Add new tag' => '', + // 'Edit a tag' => '', + // 'Project tags' => '', + // 'There is no specific tag for this project at the moment.' => '', + // 'Tag' => '', + // 'Remove a tag' => '', + // 'Do you really want to remove this tag: "%s"?' => '', + // 'Global tags' => '', + // 'There is no global tag at the moment.' => '', + // 'This field cannot be empty' => '', + // 'Close a task when there is no activity in an specific column' => '', + // '%s removed a subtask for the task #%d' => '', + // '%s removed a comment on the task #%d' => '', + // 'Comment removed on task #%d' => '', + // 'Subtask removed on task #%d' => '', + // 'Hide tasks in this column in the dashboard' => '', + // '%s removed a comment on the task %s' => '', + // '%s removed a subtask for the task %s' => '', + // 'Comment removed' => '', + // 'Subtask removed' => '', + // '%s set a new internal link for the task #%d' => '', + // '%s removed an internal link for the task #%d' => '', + // 'A new internal link for the task #%d have been defined' => '', + // 'Internal link removed for the task #%d' => '', + // '%s set a new internal link for the task %s' => '', + // '%s removed an internal link for the task %s' => '', + // 'Automatically set the due date on task creation' => '', + // 'Move the task to another column when closed' => '', + // 'Move the task to another column when not moved during a given period' => '', + // 'Dashboard for %s' => '', + // 'Tasks overview for %s' => '', + // 'Subtasks overview for %s' => '', + // 'Projects overview for %s' => '', + // 'Activity stream for %s' => '', + // 'Calendar for %s' => '', + // 'Notifications for %s' => '', + // 'Subtasks export' => '', + // 'Tasks exportation' => '', ); diff --git a/app/Locale/pt_PT/translations.php b/app/Locale/pt_PT/translations.php index d2b1e62d..ebc26cd7 100644 --- a/app/Locale/pt_PT/translations.php +++ b/app/Locale/pt_PT/translations.php @@ -154,8 +154,6 @@ return array( '%d closed tasks' => '%d tarefas finalizadas', 'No task for this project' => 'Não há tarefa para este projecto', 'Public link' => 'Link público', - 'Change assignee' => 'Mudar a assignação', - 'Change assignee for the task "%s"' => 'Modificar assignação para a tarefa "%s"', 'Timezone' => 'Fuso horário', 'Sorry, I didn\'t find this information in my database!' => 'Desculpe, não encontrei esta informação na minha base de dados!', 'Page not found' => 'Página não encontrada', @@ -248,7 +246,6 @@ return array( 'Category' => 'Categoria', 'Category:' => 'Categoria:', 'Categories' => 'Categorias', - 'Category not found.' => 'Categoria não encontrada.', 'Your category have been created successfully.' => 'A sua categoria foi criada com sucesso.', 'Unable to create your category.' => 'Não foi possível criar a sua categoria.', 'Your category have been updated successfully.' => 'A sua categoria foi actualizada com sucesso.', @@ -270,7 +267,6 @@ return array( 'Do you really want to remove this file: "%s"?' => 'Tem a certeza que quer remover este arquivo: "%s"', 'Attachments' => 'Anexos', 'Edit the task' => 'Editar a tarefa', - 'Edit the description' => 'Editar a descrição', 'Add a comment' => 'Adicionar um comentário', 'Edit a comment' => 'Editar um comentário', 'Summary' => 'Resumo', @@ -303,7 +299,6 @@ return array( 'Display another project' => 'Mostrar outro projecto', 'Created by %s' => 'Criado por %s', 'Tasks Export' => 'Exportar Tarefas', - 'Tasks exportation for "%s"' => 'As tarefas foram exportadas para "%s"', 'Start Date' => 'Data inicial', 'End Date' => 'Data final', 'Execute' => 'Executar', @@ -326,14 +321,9 @@ return array( 'New sub-task' => 'Nova subtarefa', 'New attachment added "%s"' => 'Novo anexo adicionado "%s"', 'New comment posted by %s' => 'Novo comentário por %s', - 'New attachment' => 'Novo anexo', 'New comment' => 'Novo comentário', 'Comment updated' => 'Comentário actualizado', 'New subtask' => 'Nova subtarefa', - 'Subtask updated' => 'Subtarefa alterada', - 'Task updated' => 'Tarefa alterada', - 'Task closed' => 'Tarefa finalizada', - 'Task opened' => 'Tarefa aberta', 'I want to receive notifications only for those projects:' => 'Quero receber notificações apenas destes projectos:', 'view the task on Kanboard' => 'ver a tarefa no Kanboard', 'Public access' => 'Acesso público', @@ -368,7 +358,6 @@ return array( 'No external authentication enabled.' => 'Nenhuma autenticação externa activa.', 'Password modified successfully.' => 'Senha alterada com sucesso.', 'Unable to change the password.' => 'Não foi possível alterar a senha.', - 'Change category for the task "%s"' => 'Mudar categoria da tarefa "%s"', 'Change category' => 'Mudar categoria', '%s updated the task %s' => '%s actualizou a tarefa %s', '%s opened the task %s' => '%s abriu a tarefa %s', @@ -382,7 +371,7 @@ return array( 'Not assigned, estimate of %sh' => 'Não assignado, estimado em %sh', '%s updated a comment on the task %s' => '%s actualizou o comentário na tarefa %s', '%s commented the task %s' => '%s comentou a tarefa %s', - '%s\'s activity' => 'Atividades de%s', + '%s\'s activity' => 'Atividades %s', 'RSS feed' => 'Feed RSS', '%s updated a comment on the task #%d' => '%s actualizou um comentário sobre a tarefa #%d', '%s commented on the task #%d' => '%s comentou sobre a tarefa #%d', @@ -391,14 +380,12 @@ return array( '%s updated the task #%d' => '%s actualizou a tarefa #%d', '%s created the task #%d' => '%s criou a tarefa #%d', '%s closed the task #%d' => '%s finalizou a tarefa #%d', - '%s open the task #%d' => '%s abriu a tarefa #%d', - '%s moved the task #%d to the column "%s"' => '%s moveu a tarefa #%d para a coluna "%s"', - '%s moved the task #%d to the position %d in the column "%s"' => '%s moveu a tarefa #%d para a posição %d na coluna "%s"', + '%s opened the task #%d' => '%s abriu a tarefa #%d', 'Activity' => 'Actividade', 'Default values are "%s"' => 'Os valores padrão são "%s"', 'Default columns for new projects (Comma-separated)' => 'Colunas padrão para novos projectos (Separado por vírgula)', 'Task assignee change' => 'Mudar assignação da tarefa', - '%s change the assignee of the task #%d to %s' => '%s mudou a assignação da tarefa #%d para %s', + '%s changed the assignee of the task #%d to %s' => '%s mudou a assignação da tarefa #%d para %s', '%s changed the assignee of the task %s to %s' => '%s mudou a assignação da tarefa %s para %s', 'New password for the user "%s"' => 'Nova senha para o utilizador "%s"', 'Choose an event' => 'Escolher um evento', @@ -447,13 +434,10 @@ return array( 'Percentage' => 'Percentagem', 'Number of tasks' => 'Número de tarefas', 'Task distribution' => 'Distribuição de tarefas', - 'Reportings' => 'Relatórios', - 'Task repartition for "%s"' => 'Redistribuição da tarefa para "%s"', 'Analytics' => 'Estatísticas', 'Subtask' => 'Subtarefa', 'My subtasks' => 'As minhas subtarefas', 'User repartition' => 'Redistribuição de utilizador', - 'User repartition for "%s"' => 'Redistribuição de utilizador para "%s"', 'Clone this project' => 'Clonar este projecto', 'Column removed successfully.' => 'Coluna removida com sucesso.', 'Not enough data to show the graph.' => 'Não há dados suficientes para mostrar o gráfico.', @@ -470,10 +454,8 @@ return array( 'This value must be numeric' => 'Este valor deve ser numérico', 'Unable to create this task.' => 'Não foi possível criar esta tarefa.', 'Cumulative flow diagram' => 'Fluxograma cumulativo', - 'Cumulative flow diagram for "%s"' => 'Fluxograma cumulativo para "%s"', 'Daily project summary' => 'Resumo diário do projecto', 'Daily project summary export' => 'Exportação diária do resumo do projecto', - 'Daily project summary export for "%s"' => 'Exportação diária do resumo do projecto para "%s"', 'Exports' => 'Exportar', 'This export contains the number of tasks per column grouped per day.' => 'Esta exportação contém o número de tarefas por coluna agrupada por dia.', 'Active swimlanes' => 'Activar swimlanes', @@ -485,7 +467,6 @@ return array( 'Remove a swimlane' => 'Remover um swimlane', 'Show default swimlane' => 'Mostrar swimlane padrão', 'Swimlane modification for the project "%s"' => 'Modificação de swimlane para o projecto "%s"', - 'Swimlane not found.' => 'Swimlane não encontrado.', 'Swimlane removed successfully.' => 'Swimlane removido com sucesso.', 'Swimlanes' => 'Swimlanes', 'Swimlane updated successfully.' => 'Swimlane atualizado com sucesso.', @@ -500,7 +481,6 @@ return array( 'Subtask Id' => 'ID da subtarefa', 'Subtasks' => 'Subtarefas', 'Subtasks Export' => 'Exportar subtarefas', - 'Subtasks exportation for "%s"' => 'Subtarefas exportadas para "%s"', 'Task Title' => 'Título da Tarefa', 'Untitled' => 'Sem título', 'Application default' => 'Aplicação padrão', @@ -548,17 +528,17 @@ return array( 'Unable to create your link.' => 'Impossível de adicionar sua associação.', 'Unable to update your link.' => 'Impossível de atualizar sua associação.', 'Unable to remove this link.' => 'Impossível de remover sua associação.', - 'relates to' => 'é associado com', - 'blocks' => 'blocos', - 'is blocked by' => 'está bloqueado por', + 'relates to' => 'é associada a', + 'blocks' => 'bloqueia', + 'is blocked by' => 'está bloqueada por', 'duplicates' => 'duplica', - 'is duplicated by' => 'é duplicado por', - 'is a child of' => 'é um filho de', + 'is duplicated by' => 'é duplicada por', + 'is a child of' => 'é um filha de', 'is a parent of' => 'é um parente do', 'targets milestone' => 'visa um objectivo', 'is a milestone of' => 'é um objectivo de', 'fixes' => 'corrige', - 'is fixed by' => 'foi corrigido por', + 'is fixed by' => 'foi corrigida por', 'This task' => 'Esta tarefa', '<1h' => '<1h', '%dh' => '%dh', @@ -607,7 +587,7 @@ return array( 'The currency rate have been added successfully.' => 'A taxa de câmbio foi adicionada com sucesso.', 'Unable to add this currency rate.' => 'Impossível adicionar essa taxa de câmbio.', 'Webhook URL' => 'URL do webhook', - '%s remove the assignee of the task %s' => '%s removeu a pessoa assignada à tarefa %s', + '%s removed the assignee of the task %s' => '%s removeu a pessoa assignada à tarefa %s', 'Enable Gravatar images' => 'Activar imagem Gravatar', 'Information' => 'Informações', 'Check two factor authentication code' => 'Verificação do código de autenticação com factor duplo', @@ -621,7 +601,6 @@ return array( 'Test your device' => 'Teste o seu dispositivo', 'Assign a color when the task is moved to a specific column' => 'Atribuir uma cor quando a tarefa é movida em uma coluna específica', '%s via Kanboard' => '%s via Kanboard', - 'Burndown chart for "%s"' => 'Gráfico de Burndown para "%s"', 'Burndown chart' => 'Gráfico de Burndown', 'This chart show the task complexity over the time (Work Remaining).' => 'Este gráfico mostra a complexidade da tarefa ao longo do tempo (Trabalho Restante).', 'Screenshot taken %s' => 'Screenshot tirada a %s', @@ -686,14 +665,8 @@ return array( 'Move the task to another column when the category is changed' => 'Mover uma tarefa para outra coluna quando a categoria mudar', 'Send a task by email to someone' => 'Enviar uma tarefa por e-mail a alguém', 'Reopen a task' => 'Reabrir uma tarefa', - 'Column change' => 'Mudança de coluna', - 'Position change' => 'Mudança de posição', - 'Swimlane change' => 'Mudança de swimlane', - 'Assignee change' => 'Mudança de assignação', - '[%s] Overdue tasks' => '[%s] Tarefas atrasadas', 'Notification' => 'Notificação', '%s moved the task #%d to the first swimlane' => '%s moveu a tarefa n° %d no primeiro swimlane', - '%s moved the task #%d to the swimlane "%s"' => '%s moveu a tarefa n° %d no swimlane "%s"', 'Swimlane' => 'Swimlane', 'Gravatar' => 'Gravatar', '%s moved the task %s to the first swimlane' => '%s moveu a tarefa %s no primeiro swimlane', @@ -764,9 +737,7 @@ return array( 'Search by category: ' => 'Pesquisar por categoria: ', 'Search by description: ' => 'Pesquisar por descrição: ', 'Search by due date: ' => 'Pesquisar por data de vencimento: ', - 'Lead and Cycle time for "%s"' => 'Tempo de Espera e Ciclo para "%s"', - 'Average time spent into each column for "%s"' => 'Tempo médio gasto em cada coluna para "%s"', - 'Average time spent into each column' => 'Tempo médio gasto em cada coluna', + 'Average time spent into each column' => 'Tempo médio gasto por coluna', 'Average time spent' => 'Tempo médio gasto', 'This chart show the average time spent into each column for the last %d tasks.' => 'Este gráfico mostra o tempo médio gasto em cada coluna nas últimas %d tarefas.', 'Average Lead and Cycle time' => 'Tempo de Espera e Ciclo médio', @@ -806,7 +777,6 @@ return array( 'License:' => 'Licença:', 'License' => 'Licença', 'Enter the text below' => 'Escreva o texto em baixo', - 'Gantt chart for %s' => 'Gráfico de Gantt para %s', 'Sort by position' => 'Ordenar por posição', 'Sort by date' => 'Ordenar por data', 'Add task' => 'Adicionar tarefa', @@ -847,8 +817,6 @@ return array( 'Version' => 'Versão', 'Plugins' => 'Plugins', 'There is no plugin loaded.' => 'Não existem extras carregados', - 'Set maximum column height' => 'Definir altura máxima da coluna', - 'Remove maximum column height' => 'Remover altura máxima da coluna', 'My notifications' => 'As minhas notificações', 'Custom filters' => 'Filtros personalizados', 'Your custom filter have been created successfully.' => 'O seu filtro personalizado foi criado com sucesso.', @@ -953,7 +921,6 @@ return array( 'Invalid captcha' => 'Captcha inválido', 'The name must be unique' => 'O nome deve ser único', 'View all groups' => 'Ver todos os grupos', - 'View group members' => 'Ver membros do grupo', 'There is no user available.' => 'Não existe utilizador disponivel.', 'Do you really want to remove the user "%s" from the group "%s"?' => 'Tem a certeza que quer remover o utilizador "%s" do grupo "%s"?', 'There is no group.' => 'Não existe grupo.', @@ -974,13 +941,10 @@ return array( 'Enter group name...' => 'Escreva o nome do Grupo', 'Role:' => 'Função:', 'Project members' => 'Membros do projecto', - 'Compare hours for "%s"' => 'Comparar horas para "%s"', '%s mentioned you in the task #%d' => '%s mencionou-te na tarefa #%d', '%s mentioned you in a comment on the task #%d' => '%s mencionou-te num comentário na tarefa #%d', 'You were mentioned in the task #%d' => 'Foi mencionado na tarefa #%d', 'You were mentioned in a comment on the task #%d' => 'Foi mencionado num comentário na tarefa #%d', - 'Mentioned' => 'Mencionado', - 'Compare Estimated Time vs Actual Time' => 'Comparar Tempo Estimado vs Tempo Real', 'Estimated hours: ' => 'Horas estimadas: ', 'Actual hours: ' => 'Horas reais: ', 'Hours Spent' => 'Horas Gastas', @@ -1202,4 +1166,52 @@ return array( 'Email transport' => 'Transportador de Email', 'Webhook token' => 'Token do Webhook', 'Imports' => 'Importados', + 'Project tags management' => 'Gestão de etiquetas do Projecto', + 'Tag created successfully.' => 'Etiqueta criada com sucesso.', + 'Unable to create this tag.' => 'Não foi possivel criar esta etiqueta.', + 'Tag updated successfully.' => 'Etiqueta actualizada com sucesso.', + 'Unable to update this tag.' => 'Não foi possivel actualizar esta etiqueta.', + 'Tag removed successfully.' => 'Etiqueta removida com sucesso.', + 'Unable to remove this tag.' => 'Não foi possivel remover esta etiqueta.', + 'Global tags management' => 'Gestão de etiquetas globais', + 'Tags' => 'Etiquetas', + 'Tags management' => 'Gestão de Etiquetas', + 'Add new tag' => 'Adicionar etiqueta nova', + 'Edit a tag' => 'Editar a etiqueta', + 'Project tags' => 'Etiquetas do Projecto', + 'There is no specific tag for this project at the moment.' => 'De momento não existe nenhuma etiqueta para este projecto.', + 'Tag' => 'Etiqueta', + 'Remove a tag' => 'Remover etiqueta', + 'Do you really want to remove this tag: "%s"?' => 'Tem a certeza que pretende remover esta etiqueta: "%s"?', + 'Global tags' => 'Etiquetas globais', + 'There is no global tag at the moment.' => 'De momento não existe nenhuma etiqueta global.', + 'This field cannot be empty' => 'Este campo não pode ficar vazio', + 'Close a task when there is no activity in an specific column' => 'Fechar tarefa quando não houver actividade numa coluna especifica', + '%s removed a subtask for the task #%d' => '%s removeu uma sub-tarefa da tarefa #%d', + '%s removed a comment on the task #%d' => '%s removeu um comentário da tarefa #%d ', + 'Comment removed on task #%d' => 'Comentário removido da tarefa #%d', + 'Subtask removed on task #%d' => 'Sub-tarefa removida da tarefa #%d', + 'Hide tasks in this column in the dashboard' => 'Esconder do meu painel tarefas nesta coluna', + '%s removed a comment on the task %s' => '%s removeu um comentário da tarefa %s', + '%s removed a subtask for the task %s' => '%s removeu uma sub-tarefa da tarefa %s', + 'Comment removed' => 'Comentário removido', + 'Subtask removed' => 'Sub-tarefa removida', + '%s set a new internal link for the task #%d' => '%s definiu uma nova ligação interna para a tarefa #%d', + '%s removed an internal link for the task #%d' => '%s removeu uma ligação interna da tarefa #%d', + 'A new internal link for the task #%d have been defined' => 'Uma nova ligação para a tarea #%d foi definida', + 'Internal link removed for the task #%d' => 'Ligação interna removida da tarefa #%d', + '%s set a new internal link for the task %s' => '%s definiu uma nova ligação interna para a tarefa %s', + '%s removed an internal link for the task %s' => '%s removeu uma ligação interna da tarefa %s', + 'Automatically set the due date on task creation' => 'Definir data de vencimento automáticamente ao criar uma tarefa', + 'Move the task to another column when closed' => 'Mover a tarefa para outra coluna quando fechada', + 'Move the task to another column when not moved during a given period' => 'Mover a tarefa para outra coluna quando não movida dentro de determinado periodo', + 'Dashboard for %s' => 'Painel de %s', + 'Tasks overview for %s' => 'Vista geral das tarefas de %s', + 'Subtasks overview for %s' => 'Vista geral das sub-tarefas de %s', + 'Projects overview for %s' => 'Vista geral dos projectos de %s', + 'Activity stream for %s' => 'Fluxo de actividade de %s', + 'Calendar for %s' => 'Calendário de %s', + 'Notifications for %s' => 'Notificações de %s', + 'Subtasks export' => 'Exportar sub-tarefas', + 'Tasks exportation' => 'Exportação de tarefas', ); diff --git a/app/Locale/ru_RU/translations.php b/app/Locale/ru_RU/translations.php index f500db20..f3ec5af7 100644 --- a/app/Locale/ru_RU/translations.php +++ b/app/Locale/ru_RU/translations.php @@ -154,8 +154,6 @@ return array( '%d closed tasks' => '%d завершенных задач', 'No task for this project' => 'Нет задач для этого проекта', 'Public link' => 'Ссылка для просмотра', - 'Change assignee' => 'Сменить назначенного', - 'Change assignee for the task "%s"' => 'Сменить назначенного для задачи « %s »', 'Timezone' => 'Часовой пояс', 'Sorry, I didn\'t find this information in my database!' => 'К сожалению, информация в базе данных не найдена !', 'Page not found' => 'Страница не найдена', @@ -248,7 +246,6 @@ return array( 'Category' => 'Категория', 'Category:' => 'Категория:', 'Categories' => 'Категории', - 'Category not found.' => 'Категория не найдена', 'Your category have been created successfully.' => 'Категория создана.', 'Unable to create your category.' => 'Не удалось создать категорию.', 'Your category have been updated successfully.' => 'Категория обновлена.', @@ -270,7 +267,6 @@ return array( 'Do you really want to remove this file: "%s"?' => 'Вы точно хотите удалить этот файл « %s » ?', 'Attachments' => 'Вложения', 'Edit the task' => 'Изменить задачу', - 'Edit the description' => 'Изменить описание', 'Add a comment' => 'Добавить комментарий', 'Edit a comment' => 'Изменить комментарий', 'Summary' => 'Сводка', @@ -303,7 +299,6 @@ return array( 'Display another project' => 'Показать другой проект', 'Created by %s' => 'Создано %s', 'Tasks Export' => 'Экспорт задач', - 'Tasks exportation for "%s"' => 'Задача экспортирована для « %s »', 'Start Date' => 'Дата начала', 'End Date' => 'Дата завершения', 'Execute' => 'Выполнить', @@ -326,14 +321,9 @@ return array( 'New sub-task' => 'Новая подзадача', 'New attachment added "%s"' => 'Добавлено вложение « %s »', 'New comment posted by %s' => 'Новый комментарий написан « %s »', - 'New attachment' => 'Новое вложение', 'New comment' => 'Новый комментарий', 'Comment updated' => 'Комментарий обновлен', 'New subtask' => 'Новая подзадача', - 'Subtask updated' => 'Подзадача обновлена', - 'Task updated' => 'Задача обновлена', - 'Task closed' => 'Задача закрыта', - 'Task opened' => 'Задача открыта', 'I want to receive notifications only for those projects:' => 'Я хочу получать уведомления только по этим проектам:', 'view the task on Kanboard' => 'посмотреть задачу на Kanboard', 'Public access' => 'Общий доступ', @@ -368,7 +358,6 @@ return array( 'No external authentication enabled.' => 'Нет активной внешней аутентификации.', 'Password modified successfully.' => 'Пароль изменен.', 'Unable to change the password.' => 'Не удалось сменить пароль.', - 'Change category for the task "%s"' => 'Сменить категорию для задачи "%s"', 'Change category' => 'Смена категории', '%s updated the task %s' => '%s обновил задачу %s', '%s opened the task %s' => '%s открыл задачу %s', @@ -391,14 +380,12 @@ return array( '%s updated the task #%d' => '%s обновил задачу #%d', '%s created the task #%d' => '%s создал задачу #%d', '%s closed the task #%d' => '%s закрыл задачу #%d', - '%s open the task #%d' => '%s открыл задачу #%d', - '%s moved the task #%d to the column "%s"' => '%s переместил задачу #%d в колонку "%s"', - '%s moved the task #%d to the position %d in the column "%s"' => '%s переместил задачу #%d на позицию %d в колонке "%s"', + '%s opened the task #%d' => '%s открыл задачу #%d', 'Activity' => 'Активность', 'Default values are "%s"' => 'Колонки по умолчанию: "%s"', 'Default columns for new projects (Comma-separated)' => 'Колонки по умолчанию для новых проектов (разделять запятой)', 'Task assignee change' => 'Изменен назначенный', - '%s change the assignee of the task #%d to %s' => '%s сменил назначенного для задачи #%d на %s', + '%s changed the assignee of the task #%d to %s' => '%s сменил назначенного для задачи #%d на %s', '%s changed the assignee of the task %s to %s' => '%s сменил назначенного для задачи %s на %s', 'New password for the user "%s"' => 'Новый пароль для пользователя "%s"', 'Choose an event' => 'Выберите событие', @@ -447,13 +434,10 @@ return array( 'Percentage' => 'Процент', 'Number of tasks' => 'Количество задач', 'Task distribution' => 'Распределение задач', - 'Reportings' => 'Отчетность', - 'Task repartition for "%s"' => 'Распределение задач для "%s"', 'Analytics' => 'Аналитика', 'Subtask' => 'Подзадача', 'My subtasks' => 'Мои подзадачи', 'User repartition' => 'Перераспределение пользователей', - 'User repartition for "%s"' => 'Перераспределение пользователей для "%s"', 'Clone this project' => 'Клонировать проект', 'Column removed successfully.' => 'Колонка успешно удалена.', 'Not enough data to show the graph.' => 'Недостаточно данных, чтобы показать график.', @@ -470,10 +454,8 @@ return array( 'This value must be numeric' => 'Это значение должно быть цифровым', 'Unable to create this task.' => 'Невозможно создать задачу.', 'Cumulative flow diagram' => 'Накопительная диаграма', - 'Cumulative flow diagram for "%s"' => 'Накопительная диаграма для "%s"', 'Daily project summary' => 'Ежедневное состояние проекта', 'Daily project summary export' => 'Экспорт ежедневного резюме проекта', - 'Daily project summary export for "%s"' => 'Экспорт ежедневного резюме проекта "%s"', 'Exports' => 'Экспорт', 'This export contains the number of tasks per column grouped per day.' => 'Этот экспорт содержит ряд задач в колонках, сгруппированные по дням.', 'Active swimlanes' => 'Активные дорожки', @@ -485,7 +467,6 @@ return array( 'Remove a swimlane' => 'Удалить дорожку', 'Show default swimlane' => 'Показать стандартную дорожку', 'Swimlane modification for the project "%s"' => 'Редактирование дорожки для проекта "%s"', - 'Swimlane not found.' => 'Дорожка не найдена.', 'Swimlane removed successfully.' => 'Дорожка успешно удалена', 'Swimlanes' => 'Дорожки', 'Swimlane updated successfully.' => 'Дорожка успешно обновлена.', @@ -500,7 +481,6 @@ return array( 'Subtask Id' => 'Id подзадачи', 'Subtasks' => 'Подзадачи', 'Subtasks Export' => 'Экспортировать подзадачи', - 'Subtasks exportation for "%s"' => 'Экспорт подзадач для "%s"', 'Task Title' => 'Загловок задачи', 'Untitled' => 'Заголовок отсутствует', 'Application default' => 'Приложение по умолчанию', @@ -607,7 +587,7 @@ return array( 'The currency rate have been added successfully.' => 'Курс валюты был успешно добавлен.', 'Unable to add this currency rate.' => 'Невозможно добавить этот курс валюты.', 'Webhook URL' => 'Webhook URL', - '%s remove the assignee of the task %s' => '%s удалить назначенную задачу %s', + '%s removed the assignee of the task %s' => '%s удалить назначенную задачу %s', 'Enable Gravatar images' => 'Включить Gravatar изображения', 'Information' => 'Информация', 'Check two factor authentication code' => 'Проверка кода двухфакторной авторизации', @@ -621,7 +601,6 @@ return array( 'Test your device' => 'Проверьте свое устройство', 'Assign a color when the task is moved to a specific column' => 'Назначить цвет, когда задача перемещается в определенную колонку', '%s via Kanboard' => '%s через Канборд', - 'Burndown chart for "%s"' => 'Диаграмма сгорания для « %s »', 'Burndown chart' => 'Диаграмма сгорания', 'This chart show the task complexity over the time (Work Remaining).' => 'Эта диаграмма показывают сложность задачи по времени (оставшейся работы).', 'Screenshot taken %s' => 'Принято скриншотов %s', @@ -686,14 +665,8 @@ return array( 'Move the task to another column when the category is changed' => 'Переносить задачи в другую колонку при изменении категории', 'Send a task by email to someone' => 'Отправить задачу по email', 'Reopen a task' => 'Переоткрыть задачу', - 'Column change' => 'Изменение колонки', - 'Position change' => 'Позиция изменена', - 'Swimlane change' => 'Дорожка изменена', - 'Assignee change' => 'Назначенный пользователь изменен', - '[%s] Overdue tasks' => '[%s] просроченные задачи', 'Notification' => 'Уведомления', '%s moved the task #%d to the first swimlane' => '%s задач перемещено #%d в первой дорожке', - '%s moved the task #%d to the swimlane "%s"' => '%s задач перемещено #%d в дорожке "%s"', 'Swimlane' => 'Дорожки', 'Gravatar' => 'Граватар', '%s moved the task %s to the first swimlane' => '%s переместил задачу %s на первую дорожку', @@ -721,9 +694,9 @@ return array( 'Do you really want to close the task "%s" as well as all subtasks?' => 'Вы действительно хотите закрыть задачу "%s", а также все подзадачи?', 'I want to receive notifications for:' => 'Я хочу получать уведомления для:', 'All tasks' => 'Все задачи', - 'Only for tasks assigned to me' => 'Только для задач, назначенных на меня', + 'Only for tasks assigned to me' => 'Только для задач, назначенных мне', 'Only for tasks created by me' => 'Только для задач, созданных мной', - 'Only for tasks created by me and assigned to me' => 'Только для задач, созданных мной и назначенных мной', + 'Only for tasks created by me and assigned to me' => 'Только для задач, созданных мной и назначенных мне', '%%Y-%%m-%%d' => '%%Y-%%m-%%d', 'Total for all columns' => 'Суммарно для всех колонок', 'You need at least 2 days of data to show the chart.' => 'Для отображения диаграммы нужно по крайней мере 2 дня.', @@ -764,8 +737,6 @@ return array( 'Search by category: ' => 'Поиск по категориям: ', 'Search by description: ' => 'Поиск по описанию: ', 'Search by due date: ' => 'Поиск по дате завершения: ', - 'Lead and Cycle time for "%s"' => 'Затраченное время и время цикла для "%s"', - 'Average time spent into each column for "%s"' => 'Затрачено времени в среднем в каждой колонке для "%s"', 'Average time spent into each column' => 'Затрачено времени в среднем в каждой колонке', 'Average time spent' => 'Затрачено времени в среднем', 'This chart show the average time spent into each column for the last %d tasks.' => 'Эта диаграмма показывает среднее время, проведенное задачами в каждой колонке за последний %d.', @@ -806,7 +777,6 @@ return array( 'License:' => 'Лицензия:', 'License' => 'Лицензия', 'Enter the text below' => 'Введите текст ниже', - 'Gantt chart for %s' => 'Диаграмма Ганта для %s', 'Sort by position' => 'Сортировать по позиции', 'Sort by date' => 'Сортировать по дате', 'Add task' => 'Добавить задачу', @@ -847,8 +817,6 @@ return array( 'Version' => 'Версия', 'Plugins' => 'Плагины', 'There is no plugin loaded.' => 'Нет установленных плагинов.', - 'Set maximum column height' => 'Установить максимальную высоту колонки', - 'Remove maximum column height' => 'Сбросить максимальную высоту колонки', 'My notifications' => 'Мои уведомления', 'Custom filters' => 'Пользовательские фильтры', 'Your custom filter have been created successfully.' => 'Фильтр был успешно создан.', @@ -953,7 +921,6 @@ return array( 'Invalid captcha' => 'Неверный код подтверждения', 'The name must be unique' => 'Имя должно быть уникальным', 'View all groups' => 'Просмотр всех групп', - 'View group members' => 'Просмотр участников группы', 'There is no user available.' => 'Нет доступных пользователей.', 'Do you really want to remove the user "%s" from the group "%s"?' => 'Вы действительно хотите удалить пользователя "%s" из группы "%s"?', 'There is no group.' => 'Нет созданных групп.', @@ -974,13 +941,10 @@ return array( 'Enter group name...' => 'Введите имя группы...', 'Role:' => 'Роль:', 'Project members' => 'Участники проекта', - 'Compare hours for "%s"' => 'Сравнить часы для "%s"', '%s mentioned you in the task #%d' => '%s упомянул вас в задаче #%d', '%s mentioned you in a comment on the task #%d' => '%s упомянул вас в комментарии к задаче #%d', 'You were mentioned in the task #%d' => 'Вы упомянуты в задаче #%d', 'You were mentioned in a comment on the task #%d' => 'Вы упомянуты в комментарии к задаче #%d', - 'Mentioned' => 'Упоминания', - 'Compare Estimated Time vs Actual Time' => 'Сравнить запланированное время и реальное', 'Estimated hours: ' => 'Запланировано часов: ', 'Actual hours: ' => 'Реально затрачено часов: ', 'Hours Spent' => 'Затрачено часов', @@ -1160,46 +1124,94 @@ return array( 'Projects where "%s" is member' => 'Проекты, где членом является "%s"', 'Open tasks assigned to "%s"' => 'Открытые задачи, назначенные на "%s"', 'Closed tasks assigned to "%s"' => 'Закрытые задачи, назначенные на "%s"', - // 'Assign automatically a color based on a priority' => '', - // 'Overdue tasks for the project(s) "%s"' => '', - // 'Upload files' => '', - // 'Installed Plugins' => '', - // 'Plugin Directory' => '', - // 'Plugin installed successfully.' => '', - // 'Plugin updated successfully.' => '', - // 'Plugin removed successfully.' => '', - // 'Subtask converted to task successfully.' => '', - // 'Unable to convert the subtask.' => '', - // 'Unable to extract plugin archive.' => '', - // 'Plugin not found.' => '', - // 'You don\'t have the permission to remove this plugin.' => '', - // 'Unable to download plugin archive.' => '', - // 'Unable to write temporary file for plugin.' => '', - // 'Unable to open plugin archive.' => '', - // 'There is no file in the plugin archive.' => '', - // 'Create tasks in bulk' => '', - // 'Your Kanboard instance is not configured to install plugins from the user interface.' => '', - // 'There is no plugin available.' => '', - // 'Install' => '', - // 'Update' => '', - // 'Up to date' => '', - // 'Not available' => '', - // 'Remove plugin' => '', - // 'Do you really want to remove this plugin: "%s"?' => '', - // 'Uninstall' => '', - // 'Listing' => '', - // 'Metadata' => '', - // 'Manage projects' => '', - // 'Convert to task' => '', - // 'Convert sub-task to task' => '', - // 'Do you really want to convert this sub-task to a task?' => '', - // 'My task title' => '', - // 'Enter one task by line.' => '', - // 'Number of failed login:' => '', - // 'Account locked until:' => '', - // 'Email settings' => '', - // 'Email sender address' => '', - // 'Email transport' => '', - // 'Webhook token' => '', - // 'Imports' => '', + 'Assign automatically a color based on a priority' => 'Автоматически назначить цвет в зависимости от категории', + 'Overdue tasks for the project(s) "%s"' => 'Просроченные задачи для проекта(ов) "%s"', + 'Upload files' => 'Загрузить файлы', + 'Installed Plugins' => 'Установленные плагины', + 'Plugin Directory' => 'Доступные плагины', + 'Plugin installed successfully.' => 'Плагин успешно установлен.', + 'Plugin updated successfully.' => 'Плагин успешно обновлен.', + 'Plugin removed successfully.' => 'Плагин успешно удален.', + 'Subtask converted to task successfully.' => 'Подзадача успешно преобразована в задачу.', + 'Unable to convert the subtask.' => 'Невозможно преобразовать подзадачу.', + 'Unable to extract plugin archive.' => 'Невозможно распаковать архив с плагином.', + 'Plugin not found.' => 'Плагин не найден.', + 'You don\'t have the permission to remove this plugin.' => 'У Вас нет прав на удаление этого плагина.', + 'Unable to download plugin archive.' => 'Невозможно загрузить архив с плагином.', + 'Unable to write temporary file for plugin.' => 'Невозможно записать временный файл для плагина.', + 'Unable to open plugin archive.' => 'Невозможно открыть архив плагина.', + 'There is no file in the plugin archive.' => 'В арзиве плагина нет файлов.', + 'Create tasks in bulk' => 'Массовое создание задач', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => 'Ваш Kanboard не сконфигурирова для установки плагинов через пользовательский интерфейс.', + 'There is no plugin available.' => 'Нет доступных плагинов.', + 'Install' => 'Установить', + 'Update' => 'Обновить', + 'Up to date' => 'Самый новый', + 'Not available' => 'Недоступен', + 'Remove plugin' => 'Удалить плагин', + 'Do you really want to remove this plugin: "%s"?' => 'Вы действительно хотите удалить плагин: "%s"?', + 'Uninstall' => 'Деинсталлировать', + 'Listing' => 'Список', + 'Metadata' => 'Метаданные', + 'Manage projects' => 'Управление проектами', + 'Convert to task' => 'Преобразовать в задачу', + 'Convert sub-task to task' => 'Преобразовать подзадачу в задачу', + 'Do you really want to convert this sub-task to a task?' => 'Вы действительно хотите преобразовать эту подзадачу в задачу?', + 'My task title' => 'Заголовок задачи', + 'Enter one task by line.' => 'Указывайте одну задачу на строке', + 'Number of failed login:' => 'Число неудачных попыток входа:', + 'Account locked until:' => 'Аккаунт заблокирован до:', + 'Email settings' => 'Настройки почты', + 'Email sender address' => 'Адрес отправителя', + 'Email transport' => 'Почтовый транспорт', + 'Webhook token' => 'Webhook токены', + 'Imports' => 'Импорт', + 'Project tags management' => 'Управление метками проекта', + 'Tag created successfully.' => 'Метка успешно создана.', + 'Unable to create this tag.' => 'Невозможно создать эту метку.', + 'Tag updated successfully.' => 'Метак успешно обновлена.', + 'Unable to update this tag.' => 'Невозможно обновить эту метку.', + 'Tag removed successfully.' => 'Метка успешно удалена.', + 'Unable to remove this tag.' => 'Невозможно удалить эту метку.', + 'Global tags management' => 'Управление глоабльными метками', + 'Tags' => 'Метки', + 'Tags management' => 'Управление метками', + 'Add new tag' => 'Добавить новую метку', + 'Edit a tag' => 'Редактировать метку', + 'Project tags' => 'Метки проекта', + 'There is no specific tag for this project at the moment.' => 'Нет меток для этого проекта.', + 'Tag' => 'Метка', + 'Remove a tag' => 'Удалить метку', + 'Do you really want to remove this tag: "%s"?' => 'Вы действительно хотите удалить метку: "%s"?', + 'Global tags' => 'Глобальные метка', + 'There is no global tag at the moment.' => 'Нет глобальных меток.', + 'This field cannot be empty' => 'Это поле не может быть пустым', + 'Close a task when there is no activity in an specific column' => 'Закрыть задачу при отсутствии активности в определенной колонке', + '%s removed a subtask for the task #%d' => '%s удалил подзадачу для #%d', + '%s removed a comment on the task #%d' => '%s удалил комментарий к задаче #%d', + 'Comment removed on task #%d' => 'Комментарий удален в задаче #%d', + 'Subtask removed on task #%d' => 'Подзадача удалена в задаче #%d', + 'Hide tasks in this column in the dashboard' => 'Не показывать задачи из этой колонки в кабинете', + '%s removed a comment on the task %s' => '%s удалил комментарии к задаче %s', + '%s removed a subtask for the task %s' => '%s удалил подзадачу для %s', + 'Comment removed' => 'Комментарий удален', + 'Subtask removed' => 'Подзадача удалена', + '%s set a new internal link for the task #%d' => '%s добавил внутреннюю ссылку для задачи #%d', + '%s removed an internal link for the task #%d' => '%s удалил внутреннюю ссылку для задачи #%d', + 'A new internal link for the task #%d have been defined' => 'Внешняя ссылка для задачи #%d была установлена', + 'Internal link removed for the task #%d' => 'Внутренняя ссылка удалена для задачи #%d', + '%s set a new internal link for the task %s' => '%s добавил внутреннюю ссылку для задачи %s', + '%s removed an internal link for the task %s' => '%s удалил внутреннюю ссылку для задачи %s', + 'Automatically set the due date on task creation' => 'Автоматическая установка срока задачи при создании', + 'Move the task to another column when closed' => 'Переместить задачу в другую колонку при закрытии', + 'Move the task to another column when not moved during a given period' => 'Переместить задачу в другую колонку если она не был перемещен в указанный период', + 'Dashboard for %s' => 'Панель управления для %s', + 'Tasks overview for %s' => 'Обзор задач для %s', + 'Subtasks overview for %s' => 'Обзор подзадач для %s', + 'Projects overview for %s' => 'Обзор проектов для %s', + 'Activity stream for %s' => 'Лента активности для %s', + 'Calendar for %s' => 'Календарь для %s', + 'Notifications for %s' => 'Уведомления для %s', + 'Subtasks export' => 'Экспорт подзадач', + 'Tasks exportation' => 'Экспортирование задач', ); diff --git a/app/Locale/sr_Latn_RS/translations.php b/app/Locale/sr_Latn_RS/translations.php index 98f72c80..7e28e9a9 100644 --- a/app/Locale/sr_Latn_RS/translations.php +++ b/app/Locale/sr_Latn_RS/translations.php @@ -154,8 +154,6 @@ return array( '%d closed tasks' => '%d zatvorenih zadataka', 'No task for this project' => 'Nema dodeljenih zadataka ovom projektu', 'Public link' => 'Javni link', - 'Change assignee' => 'Izmeni dodelu', - 'Change assignee for the task "%s"' => 'Izmeni dodelu za ovaj zadatak "%s"', 'Timezone' => 'Vremenska zona', 'Sorry, I didn\'t find this information in my database!' => 'Na žalost, nije pronađena informacija u bazi', 'Page not found' => 'Strana nije pronađena', @@ -248,7 +246,6 @@ return array( 'Category' => 'Kategorija', 'Category:' => 'Kategorija:', 'Categories' => 'Kategorije', - 'Category not found.' => 'Kategorija nije pronađena', 'Your category have been created successfully.' => 'Uspešno kreirana kategorija.', 'Unable to create your category.' => 'Nije moguće kreirati kategoriju.', 'Your category have been updated successfully.' => 'Kategorija je uspešno izmenjena', @@ -270,7 +267,6 @@ return array( 'Do you really want to remove this file: "%s"?' => 'Da li da uklonim fajl: "%s"?', 'Attachments' => 'Prilozi', 'Edit the task' => 'Izmena Zadatka', - 'Edit the description' => 'Izmena opisa', 'Add a comment' => 'Dodaj komentar', 'Edit a comment' => 'Izmeni komentar', 'Summary' => 'Pregled', @@ -303,7 +299,6 @@ return array( 'Display another project' => 'Prikaži drugi projekat', 'Created by %s' => 'Kreirao %s', 'Tasks Export' => 'Izvoz zadataka', - 'Tasks exportation for "%s"' => 'Izvoz zadataka za "%s"', 'Start Date' => 'Početni datum', 'End Date' => 'Krajni datum', 'Execute' => 'Izvrši', @@ -326,14 +321,9 @@ return array( 'New sub-task' => 'Novi Pod-zadatak', 'New attachment added "%s"' => 'Novi prilog ubačen "%s"', 'New comment posted by %s' => 'Novi komentar ostavio %s', - // 'New attachment' => '', // 'New comment' => '', 'Comment updated' => 'Komentar izmenjen', // 'New subtask' => '', - // 'Subtask updated' => '', - // 'Task updated' => '', - 'Task closed' => 'Zadatak je zatvoren', - 'Task opened' => 'Zadatak je otvoren', 'I want to receive notifications only for those projects:' => 'Želim obaveštenja samo za ovaj projekat:', 'view the task on Kanboard' => 'Pregledaj zadatke', 'Public access' => 'Javni pristup', @@ -368,7 +358,6 @@ return array( 'No external authentication enabled.' => 'Bez omogućenih spoljnih autentikacija.', 'Password modified successfully.' => 'Uspešna izmena lozinke.', 'Unable to change the password.' => 'Nije moguće izmeniti lozinku.', - 'Change category for the task "%s"' => 'Izmeni kategoriju zadatka "%s"', 'Change category' => 'Izmeni kategoriju', '%s updated the task %s' => '%s izmeni zadatak %s', '%s opened the task %s' => '%s aktivni zadaci %s', @@ -391,14 +380,12 @@ return array( '%s updated the task #%d' => '%s izmenjen zadatak #%d', '%s created the task #%d' => '%s kreirao zadatak #%d', '%s closed the task #%d' => '%s zatvorio zadatak #%d', - '%s open the task #%d' => '%s otvorio zadatak #%d', - '%s moved the task #%d to the column "%s"' => '%s premestio zadatak #%d u kolonu "%s"', - '%s moved the task #%d to the position %d in the column "%s"' => '%s premestio zadatak #%d na pozycję %d w kolmnie "%s"', + '%s opened the task #%d' => '%s otvorio zadatak #%d', 'Activity' => 'Aktivnosti', 'Default values are "%s"' => 'Osnovne vrednosti su: "%s"', 'Default columns for new projects (Comma-separated)' => 'Osnovne kolone za novi projekat (Odvojeni zarezom)', 'Task assignee change' => 'Zmień osobę odpowiedzialną', - '%s change the assignee of the task #%d to %s' => '%s zamena dodele za zadatak #%d na %s', + '%s changed the assignee of the task #%d to %s' => '%s zamena dodele za zadatak #%d na %s', '%s changed the assignee of the task %s to %s' => '%s zamena dodele za zadatak %s na %s', 'New password for the user "%s"' => 'Nova lozinka za korisnika "%s"', 'Choose an event' => 'Izaberi događaj', @@ -447,13 +434,10 @@ return array( 'Percentage' => 'Procenat', 'Number of tasks' => 'Broj zadataka', 'Task distribution' => 'Podela zadataka', - 'Reportings' => 'Izveštaji', - 'Task repartition for "%s"' => 'Zaduženja zadataka za "%s"', 'Analytics' => 'Analiza', 'Subtask' => 'Pod-zadatak', 'My subtasks' => 'Moji pod-zadaci', 'User repartition' => 'Zaduženja korisnika', - 'User repartition for "%s"' => 'Zaduženja korisnika za "%s"', 'Clone this project' => 'Kopiraj projekat', 'Column removed successfully.' => 'Kolumna usunięta pomyslnie.', 'Not enough data to show the graph.' => 'Nedovoljno podataka za grafikon.', @@ -470,10 +454,8 @@ return array( 'This value must be numeric' => 'Vrednost mora biti broj', 'Unable to create this task.' => 'Nije moguće kreirati zadatak.', 'Cumulative flow diagram' => 'Zbirni dijagram toka', - 'Cumulative flow diagram for "%s"' => 'Zbirni dijagram toka za "%s"', 'Daily project summary' => 'Zbirni pregled po danima', 'Daily project summary export' => 'Izvoz zbirnog pregleda po danima', - 'Daily project summary export for "%s"' => 'Izvoz zbirnig pregleda po danima za "%s"', 'Exports' => 'Izvoz', // 'This export contains the number of tasks per column grouped per day.' => '', 'Active swimlanes' => 'Aktivni razdelnik', @@ -485,7 +467,6 @@ return array( 'Remove a swimlane' => 'Ukloni razdelnik', 'Show default swimlane' => 'Prikaži osnovni razdelnik', 'Swimlane modification for the project "%s"' => 'Izmena razdelnika za projekat "%s"', - 'Swimlane not found.' => 'Razdelnik nije pronađen.', 'Swimlane removed successfully.' => 'Razdelnik uspešno uklonjen.', 'Swimlanes' => 'Razdelnici', 'Swimlane updated successfully.' => 'Razdelnik zaktualizowany pomyślnie.', @@ -500,7 +481,6 @@ return array( 'Subtask Id' => 'ID pod-zadania', 'Subtasks' => 'Pod-zadataka', 'Subtasks Export' => 'Eksport pod-zadań', - 'Subtasks exportation for "%s"' => 'Izvoz pod-zadań dla "%s"', 'Task Title' => 'Naslov zadatka', 'Untitled' => 'Bez naslova', 'Application default' => 'Postavke aplikacje', @@ -607,7 +587,7 @@ return array( // 'The currency rate have been added successfully.' => '', // 'Unable to add this currency rate.' => '', // 'Webhook URL' => '', - // '%s remove the assignee of the task %s' => '', + // '%s removed the assignee of the task %s' => '', // 'Enable Gravatar images' => '', // 'Information' => '', // 'Check two factor authentication code' => '', @@ -621,7 +601,6 @@ return array( // 'Test your device' => '', // 'Assign a color when the task is moved to a specific column' => '', // '%s via Kanboard' => '', - // 'Burndown chart for "%s"' => '', // 'Burndown chart' => '', // 'This chart show the task complexity over the time (Work Remaining).' => '', // 'Screenshot taken %s' => '', @@ -686,14 +665,8 @@ return array( // 'Move the task to another column when the category is changed' => '', // 'Send a task by email to someone' => '', // 'Reopen a task' => '', - // 'Column change' => '', - // 'Position change' => '', - // 'Swimlane change' => '', - // 'Assignee change' => '', - // '[%s] Overdue tasks' => '', // 'Notification' => '', // '%s moved the task #%d to the first swimlane' => '', - // '%s moved the task #%d to the swimlane "%s"' => '', // 'Swimlane' => '', // 'Gravatar' => '', // '%s moved the task %s to the first swimlane' => '', @@ -764,8 +737,6 @@ return array( // 'Search by category: ' => '', // 'Search by description: ' => '', // 'Search by due date: ' => '', - // 'Lead and Cycle time for "%s"' => '', - // 'Average time spent into each column for "%s"' => '', // 'Average time spent into each column' => '', // 'Average time spent' => '', // 'This chart show the average time spent into each column for the last %d tasks.' => '', @@ -806,7 +777,6 @@ return array( // 'License:' => '', // 'License' => '', // 'Enter the text below' => '', - // 'Gantt chart for %s' => '', // 'Sort by position' => '', // 'Sort by date' => '', // 'Add task' => '', @@ -847,8 +817,6 @@ return array( // 'Version' => '', // 'Plugins' => '', // 'There is no plugin loaded.' => '', - // 'Set maximum column height' => '', - // 'Remove maximum column height' => '', // 'My notifications' => '', // 'Custom filters' => '', // 'Your custom filter have been created successfully.' => '', @@ -953,7 +921,6 @@ return array( // 'Invalid captcha' => '', // 'The name must be unique' => '', // 'View all groups' => '', - // 'View group members' => '', // 'There is no user available.' => '', // 'Do you really want to remove the user "%s" from the group "%s"?' => '', // 'There is no group.' => '', @@ -974,13 +941,10 @@ return array( // 'Enter group name...' => '', // 'Role:' => '', // 'Project members' => '', - // 'Compare hours for "%s"' => '', // '%s mentioned you in the task #%d' => '', // '%s mentioned you in a comment on the task #%d' => '', // 'You were mentioned in the task #%d' => '', // 'You were mentioned in a comment on the task #%d' => '', - // 'Mentioned' => '', - // 'Compare Estimated Time vs Actual Time' => '', // 'Estimated hours: ' => '', // 'Actual hours: ' => '', // 'Hours Spent' => '', @@ -1202,4 +1166,52 @@ return array( // 'Email transport' => '', // 'Webhook token' => '', // 'Imports' => '', + // 'Project tags management' => '', + // 'Tag created successfully.' => '', + // 'Unable to create this tag.' => '', + // 'Tag updated successfully.' => '', + // 'Unable to update this tag.' => '', + // 'Tag removed successfully.' => '', + // 'Unable to remove this tag.' => '', + // 'Global tags management' => '', + // 'Tags' => '', + // 'Tags management' => '', + // 'Add new tag' => '', + // 'Edit a tag' => '', + // 'Project tags' => '', + // 'There is no specific tag for this project at the moment.' => '', + // 'Tag' => '', + // 'Remove a tag' => '', + // 'Do you really want to remove this tag: "%s"?' => '', + // 'Global tags' => '', + // 'There is no global tag at the moment.' => '', + // 'This field cannot be empty' => '', + // 'Close a task when there is no activity in an specific column' => '', + // '%s removed a subtask for the task #%d' => '', + // '%s removed a comment on the task #%d' => '', + // 'Comment removed on task #%d' => '', + // 'Subtask removed on task #%d' => '', + // 'Hide tasks in this column in the dashboard' => '', + // '%s removed a comment on the task %s' => '', + // '%s removed a subtask for the task %s' => '', + // 'Comment removed' => '', + // 'Subtask removed' => '', + // '%s set a new internal link for the task #%d' => '', + // '%s removed an internal link for the task #%d' => '', + // 'A new internal link for the task #%d have been defined' => '', + // 'Internal link removed for the task #%d' => '', + // '%s set a new internal link for the task %s' => '', + // '%s removed an internal link for the task %s' => '', + // 'Automatically set the due date on task creation' => '', + // 'Move the task to another column when closed' => '', + // 'Move the task to another column when not moved during a given period' => '', + // 'Dashboard for %s' => '', + // 'Tasks overview for %s' => '', + // 'Subtasks overview for %s' => '', + // 'Projects overview for %s' => '', + // 'Activity stream for %s' => '', + // 'Calendar for %s' => '', + // 'Notifications for %s' => '', + // 'Subtasks export' => '', + // 'Tasks exportation' => '', ); diff --git a/app/Locale/sv_SE/translations.php b/app/Locale/sv_SE/translations.php index dcc4dab0..5ad2938c 100644 --- a/app/Locale/sv_SE/translations.php +++ b/app/Locale/sv_SE/translations.php @@ -154,8 +154,6 @@ return array( '%d closed tasks' => '%d stängda uppgifter', 'No task for this project' => 'Inga uppgifter i detta projekt', 'Public link' => 'Publik länk', - 'Change assignee' => 'Ändra uppdragsinnehavare', - 'Change assignee for the task "%s"' => 'Ändra uppdragsinnehavare för uppgiften "%s"', 'Timezone' => 'Tidszon', 'Sorry, I didn\'t find this information in my database!' => 'Informationen kunde inte hittas i databasen.', 'Page not found' => 'Sidan hittas inte', @@ -248,7 +246,6 @@ return array( 'Category' => 'Kategori', 'Category:' => 'Kategori:', 'Categories' => 'Ange kategorier', - 'Category not found.' => 'Kategorin hittades inte', 'Your category have been created successfully.' => 'Din kategori har skapats', 'Unable to create your category.' => 'Kunde inte skapa din kategori', 'Your category have been updated successfully.' => 'Din kategori har uppdaterats', @@ -270,7 +267,6 @@ return array( 'Do you really want to remove this file: "%s"?' => 'Vill du verkligen ta bort denna fil:"%s"?', 'Attachments' => 'Bifogade filer', 'Edit the task' => 'Ändra uppgiften', - 'Edit the description' => 'Ändra beskrivningen', 'Add a comment' => 'Lägg till kommentar', 'Edit a comment' => 'Ändra en kommentar', 'Summary' => 'Sammanfattning', @@ -303,7 +299,6 @@ return array( 'Display another project' => 'Visa ett annat projekt', 'Created by %s' => 'Skapad av %s', 'Tasks Export' => 'Exportera uppgifter', - 'Tasks exportation for "%s"' => 'Exportera uppgifter för "%s"', 'Start Date' => 'Startdatum', 'End Date' => 'Slutdatum', 'Execute' => 'Utför', @@ -326,14 +321,9 @@ return array( 'New sub-task' => 'Ny deluppgift', 'New attachment added "%s"' => 'Ny bifogning tillagd "%s"', 'New comment posted by %s' => 'Ny kommentar postad av %s', - 'New attachment' => 'Ny bifogning', 'New comment' => 'Ny kommentar', 'Comment updated' => 'Kommentaren har uppdaterats', 'New subtask' => 'Ny deluppgift', - 'Subtask updated' => 'Deluppgiften har uppdaterats', - 'Task updated' => 'Uppgiften har uppdaterats', - 'Task closed' => 'Uppgiften har stängts', - 'Task opened' => 'Uppgiften har öppnats', 'I want to receive notifications only for those projects:' => 'Jag vill endast få notiser för dessa projekt:', 'view the task on Kanboard' => 'Visa uppgiften på Kanboard', 'Public access' => 'Publik åtkomst', @@ -368,7 +358,6 @@ return array( 'No external authentication enabled.' => 'Ingen extern autentisering aktiverad.', 'Password modified successfully.' => 'Lösenordet har ändrats.', 'Unable to change the password.' => 'Kunde inte byta lösenord.', - 'Change category for the task "%s"' => 'Byt kategori för uppgiften "%s"', 'Change category' => 'Byt kategori', '%s updated the task %s' => '%s uppdaterade uppgiften %s', '%s opened the task %s' => '%s öppna uppgiften %s', @@ -391,14 +380,12 @@ return array( '%s updated the task #%d' => '%s uppdaterade uppgiften #%d', '%s created the task #%d' => '%s skapade uppgiften #%d', '%s closed the task #%d' => '%s stängde uppgiften #%d', - '%s open the task #%d' => '%s öppnade uppgiften #%d', - '%s moved the task #%d to the column "%s"' => '%s flyttade uppgiften #%d till kolumnen "%s"', - '%s moved the task #%d to the position %d in the column "%s"' => '%s flyttade uppgiften #%d till positionen %d i kolumnen "%s"', + '%s opened the task #%d' => '%s öppnade uppgiften #%d', 'Activity' => 'Aktivitet', 'Default values are "%s"' => 'Standardvärden är "%s"', 'Default columns for new projects (Comma-separated)' => 'Standardkolumner för nya projekt (kommaseparerade)', 'Task assignee change' => 'Ändra tilldelning av uppgiften', - '%s change the assignee of the task #%d to %s' => '%s byt tilldelning av uppgiften #%d till %s', + '%s changed the assignee of the task #%d to %s' => '%s byt tilldelning av uppgiften #%d till %s', '%s changed the assignee of the task %s to %s' => '%s byt tilldelning av uppgiften %s till %s', 'New password for the user "%s"' => 'Nytt lösenord för användaren "%s"', 'Choose an event' => 'Välj en händelse', @@ -447,13 +434,10 @@ return array( 'Percentage' => 'Procent', 'Number of tasks' => 'Antal uppgifter', 'Task distribution' => 'Uppgiftsfördelning', - 'Reportings' => 'Rapportering', - 'Task repartition for "%s"' => 'Uppgiftsdeltagande för "%s"', 'Analytics' => 'Analyser', 'Subtask' => 'Deluppgift', 'My subtasks' => 'Mina deluppgifter', 'User repartition' => 'Användardeltagande', - 'User repartition for "%s"' => 'Användardeltagande för "%s"', 'Clone this project' => 'Klona projektet', 'Column removed successfully.' => 'Kolumnen togs bort', 'Not enough data to show the graph.' => 'Inte tillräckligt med data för att visa graf', @@ -470,10 +454,8 @@ return array( 'This value must be numeric' => 'Värdet måste vara numeriskt', 'Unable to create this task.' => 'Kunde inte skapa uppgiften.', 'Cumulative flow diagram' => 'Diagram med kumulativt flöde', - 'Cumulative flow diagram for "%s"' => 'Diagram med kumulativt flöde för "%s"', 'Daily project summary' => 'Daglig projektsummering', 'Daily project summary export' => 'Export av daglig projektsummering', - 'Daily project summary export for "%s"' => 'Export av daglig projektsummering för "%s"', 'Exports' => 'Exporter', 'This export contains the number of tasks per column grouped per day.' => 'Denna export innehåller antalet uppgifter per kolumn grupperade per dag.', 'Active swimlanes' => 'Aktiva swimlanes', @@ -485,7 +467,6 @@ return array( 'Remove a swimlane' => 'Ta bort en swimlane', 'Show default swimlane' => 'Visa standard swimlane', 'Swimlane modification for the project "%s"' => 'Ändra swimlane för projektet "%s"', - 'Swimlane not found.' => 'Swimlane kunde inte hittas', 'Swimlane removed successfully.' => 'Swimlane togs bort', 'Swimlanes' => 'Swimlanes', 'Swimlane updated successfully.' => 'Swimlane uppdaterad', @@ -500,7 +481,6 @@ return array( 'Subtask Id' => 'Deluppgifts-ID', 'Subtasks' => 'Deluppgift', 'Subtasks Export' => 'Export av deluppgifter', - 'Subtasks exportation for "%s"' => 'Export av deluppgifter för "%s"', 'Task Title' => 'Uppgiftstitel', 'Untitled' => 'Titel saknas', 'Application default' => 'Applikationsstandard', @@ -607,7 +587,7 @@ return array( 'The currency rate have been added successfully.' => 'Valutakursen har lagts till.', 'Unable to add this currency rate.' => 'Kunde inte lägga till valutakursen.', 'Webhook URL' => 'Webhook URL', - '%s remove the assignee of the task %s' => '%s ta bort tilldelningen av uppgiften %s', + '%s removed the assignee of the task %s' => '%s ta bort tilldelningen av uppgiften %s', 'Enable Gravatar images' => 'Aktivera Gravatar bilder', 'Information' => 'Information', 'Check two factor authentication code' => 'Kolla tvåfaktorsverifieringskod', @@ -621,7 +601,6 @@ return array( 'Test your device' => 'Testa din enhet', 'Assign a color when the task is moved to a specific column' => 'Tilldela en färg när uppgiften flyttas till en specifik kolumn', '%s via Kanboard' => '%s via Kanboard', - 'Burndown chart for "%s"' => 'Burndown diagram för "%s"', 'Burndown chart' => 'Burndown diagram', 'This chart show the task complexity over the time (Work Remaining).' => 'Diagrammet visar uppgiftens svårighet över tid (återstående arbete).', 'Screenshot taken %s' => 'Skärmdump tagen %s', @@ -686,14 +665,8 @@ return array( 'Move the task to another column when the category is changed' => 'Flyttas uppgiften till en annan kolumn när kategorin ändras', 'Send a task by email to someone' => 'Skicka en uppgift med e-post till någon', 'Reopen a task' => 'Återöppna en uppgift', - 'Column change' => 'Kolumnändring', - 'Position change' => 'Positionsändring', - 'Swimlane change' => 'Swimlaneändring', - 'Assignee change' => 'Tilldelningsändring', - '[%s] Overdue tasks' => '[%s] Försenade uppgifter', 'Notification' => 'Notis', '%s moved the task #%d to the first swimlane' => '%s flyttade uppgiften #%d till första swimlane', - '%s moved the task #%d to the swimlane "%s"' => '%s flyttade uppgiften #%d till swimlane "%s"', 'Swimlane' => 'Swimlane', 'Gravatar' => 'Gravatar', '%s moved the task %s to the first swimlane' => '%s flyttade uppgiften %s till första swimlane', @@ -764,8 +737,6 @@ return array( 'Search by category: ' => 'Sök efter kategori:', 'Search by description: ' => 'Sök efter beskrivning', 'Search by due date: ' => 'Sök efter förfallodatum', - 'Lead and Cycle time for "%s"' => 'Led- och cykeltid för "%s"', - 'Average time spent into each column for "%s"' => 'Medeltidsåtgång i varje kolumn för "%s"', 'Average time spent into each column' => 'Medeltidsåtgång i varje kolumn', 'Average time spent' => 'Medeltidsåtgång', 'This chart show the average time spent into each column for the last %d tasks.' => 'Diagramet visar medeltidsåtgång i varje kolumn för de senaste %d uppgifterna.', @@ -806,7 +777,6 @@ return array( 'License:' => 'Licens:', 'License' => 'Licens', 'Enter the text below' => 'Fyll i texten nedan', - 'Gantt chart for %s' => 'Gantt-schema för %s', 'Sort by position' => 'Sortera efter position', 'Sort by date' => 'Sortera efter datum', 'Add task' => 'Lägg till uppgift', @@ -847,8 +817,6 @@ return array( // 'Version' => '', // 'Plugins' => '', // 'There is no plugin loaded.' => '', - // 'Set maximum column height' => '', - // 'Remove maximum column height' => '', // 'My notifications' => '', // 'Custom filters' => '', // 'Your custom filter have been created successfully.' => '', @@ -953,7 +921,6 @@ return array( // 'Invalid captcha' => '', // 'The name must be unique' => '', // 'View all groups' => '', - // 'View group members' => '', // 'There is no user available.' => '', // 'Do you really want to remove the user "%s" from the group "%s"?' => '', // 'There is no group.' => '', @@ -974,13 +941,10 @@ return array( // 'Enter group name...' => '', // 'Role:' => '', // 'Project members' => '', - // 'Compare hours for "%s"' => '', // '%s mentioned you in the task #%d' => '', // '%s mentioned you in a comment on the task #%d' => '', // 'You were mentioned in the task #%d' => '', // 'You were mentioned in a comment on the task #%d' => '', - // 'Mentioned' => '', - // 'Compare Estimated Time vs Actual Time' => '', // 'Estimated hours: ' => '', // 'Actual hours: ' => '', // 'Hours Spent' => '', @@ -1202,4 +1166,52 @@ return array( // 'Email transport' => '', // 'Webhook token' => '', // 'Imports' => '', + // 'Project tags management' => '', + // 'Tag created successfully.' => '', + // 'Unable to create this tag.' => '', + // 'Tag updated successfully.' => '', + // 'Unable to update this tag.' => '', + // 'Tag removed successfully.' => '', + // 'Unable to remove this tag.' => '', + // 'Global tags management' => '', + // 'Tags' => '', + // 'Tags management' => '', + // 'Add new tag' => '', + // 'Edit a tag' => '', + // 'Project tags' => '', + // 'There is no specific tag for this project at the moment.' => '', + // 'Tag' => '', + // 'Remove a tag' => '', + // 'Do you really want to remove this tag: "%s"?' => '', + // 'Global tags' => '', + // 'There is no global tag at the moment.' => '', + // 'This field cannot be empty' => '', + // 'Close a task when there is no activity in an specific column' => '', + // '%s removed a subtask for the task #%d' => '', + // '%s removed a comment on the task #%d' => '', + // 'Comment removed on task #%d' => '', + // 'Subtask removed on task #%d' => '', + // 'Hide tasks in this column in the dashboard' => '', + // '%s removed a comment on the task %s' => '', + // '%s removed a subtask for the task %s' => '', + // 'Comment removed' => '', + // 'Subtask removed' => '', + // '%s set a new internal link for the task #%d' => '', + // '%s removed an internal link for the task #%d' => '', + // 'A new internal link for the task #%d have been defined' => '', + // 'Internal link removed for the task #%d' => '', + // '%s set a new internal link for the task %s' => '', + // '%s removed an internal link for the task %s' => '', + // 'Automatically set the due date on task creation' => '', + // 'Move the task to another column when closed' => '', + // 'Move the task to another column when not moved during a given period' => '', + // 'Dashboard for %s' => '', + // 'Tasks overview for %s' => '', + // 'Subtasks overview for %s' => '', + // 'Projects overview for %s' => '', + // 'Activity stream for %s' => '', + // 'Calendar for %s' => '', + // 'Notifications for %s' => '', + // 'Subtasks export' => '', + // 'Tasks exportation' => '', ); diff --git a/app/Locale/th_TH/translations.php b/app/Locale/th_TH/translations.php index 607f62ca..2aee696b 100644 --- a/app/Locale/th_TH/translations.php +++ b/app/Locale/th_TH/translations.php @@ -154,8 +154,6 @@ return array( '%d closed tasks' => '%d งานที่ปิด', 'No task for this project' => 'ไม่มีงานสำหรับโปรเจคนี้', 'Public link' => 'ลิงค์สาธารณะ', - 'Change assignee' => 'เปลี่ยนการกำหนด', - 'Change assignee for the task "%s"' => 'เปลี่ยนการกำหนดสำหรับงาน « %s »', 'Timezone' => 'เขตเวลา', 'Sorry, I didn\'t find this information in my database!' => 'เสียใจด้วย ไม่สามารถหาข้อมูลในฐานข้อมูลได้', 'Page not found' => 'ไม่พบหน้า', @@ -248,7 +246,6 @@ return array( 'Category' => 'หมวด', 'Category:' => 'หมวด:', 'Categories' => 'หมวด', - 'Category not found.' => 'ไม่พบหมวด', 'Your category have been created successfully.' => 'สร้างหมวดเรียบร้อยแล้ว', 'Unable to create your category.' => 'ไม่สามารถสร้างหมวดได้', 'Your category have been updated successfully.' => 'ปรับปรุงหมวดเรียบร้อยแล้ว', @@ -270,7 +267,6 @@ return array( 'Do you really want to remove this file: "%s"?' => 'คุณต้องการลบไฟล์ "%s" ใช่หรือไม่?', 'Attachments' => 'แนบ', 'Edit the task' => 'แก้ไขงาน', - 'Edit the description' => 'แก้ไขคำอธิบาย', 'Add a comment' => 'เพิ่มความคิดเห็น', 'Edit a comment' => 'แก้ไขความคิดเห็น', 'Summary' => 'สรุป', @@ -303,7 +299,6 @@ return array( 'Display another project' => 'แสดงโปรเจคอื่น', 'Created by %s' => 'สร้างโดย %s', 'Tasks Export' => 'ส่งออกงาน', - 'Tasks exportation for "%s"' => 'ส่งออกงานสำหรับ "%s"', 'Start Date' => 'เริ่มวันที่', 'End Date' => 'สิ้นสุดวันที่', 'Execute' => 'ประมวลผล', @@ -326,14 +321,9 @@ return array( 'New sub-task' => 'งานย่อยใหม่', 'New attachment added "%s"' => 'เพิ่มการแนบใหม่ "%s"', 'New comment posted by %s' => 'ความคิดเห็นใหม่จาก %s', - 'New attachment' => 'การแนบใหม่', 'New comment' => 'ความคิดเห็นใหม่', 'Comment updated' => 'ปรับปรุงความคิดเห็น', 'New subtask' => 'งานย่อยใหม่', - 'Subtask updated' => 'ปรับปรุงงานย่อยแล้ว', - 'Task updated' => 'ปรับปรุงงานแล้ว', - 'Task closed' => 'ปิดงาน', - 'Task opened' => 'เปิดงาน', 'I want to receive notifications only for those projects:' => 'ฉันต้องการรับการแจ้งเตือนสำหรับโปรเจค:', 'view the task on Kanboard' => 'แสดงงานบน Kanboard', 'Public access' => 'การเข้าถึงสาธารณะ', @@ -368,7 +358,6 @@ return array( 'No external authentication enabled.' => 'ไม่เปิดการใช้งานการยืนยันภายนอก', 'Password modified successfully.' => 'แก้ไขรหัสผ่านเรียบร้อยแล้ว', 'Unable to change the password.' => 'ไม่สามารถเปลี่ยนรหัสผ่านได้', - 'Change category for the task "%s"' => 'เปลี่ยนหมวดสำหรับงาน "%s"', 'Change category' => 'เปลี่ยนหมวด', '%s updated the task %s' => '%s ปรับปรุงงานแล้ว %s', '%s opened the task %s' => '%s เปิดงานแล้ว %s', @@ -391,14 +380,12 @@ return array( '%s updated the task #%d' => '%s ปรับปรุงงานแล้ว #%d', '%s created the task #%d' => '%s สร้างงานแล้ว #%d', '%s closed the task #%d' => '%s ปิดงานแล้ว #%d', - '%s open the task #%d' => '%s เปิดงานแล้ว #%d', - '%s moved the task #%d to the column "%s"' => '%s ย้ายงานแล้ว #%d ไปที่คอลัมน์ "%s"', - '%s moved the task #%d to the position %d in the column "%s"' => '%s ย้ายงานแล้ว #%d ไปตำแหน่ง %d ในคอลัมน์ที่ "%s"', + '%s opened the task #%d' => '%s เปิดงานแล้ว #%d', 'Activity' => 'กิจกรรม', 'Default values are "%s"' => 'ค่าเริ่มต้น "%s"', 'Default columns for new projects (Comma-separated)' => 'คอลัมน์เริ่มต้นสำหรับโปรเจคใหม่ (Comma-separated)', 'Task assignee change' => 'เปลี่ยนการกำหนดบุคคลของงาน', - '%s change the assignee of the task #%d to %s' => '%s เปลี่ยนผู้รับผิดชอบของงาน #%d เป็น %s', + '%s changed the assignee of the task #%d to %s' => '%s เปลี่ยนผู้รับผิดชอบของงาน #%d เป็น %s', '%s changed the assignee of the task %s to %s' => '%s เปลี่ยนผู้รับผิดชอบของงาน %s เป็น %s', 'New password for the user "%s"' => 'รหัสผ่านใหม่สำหรับผู้ใช้ "%s"', 'Choose an event' => 'เลือกเหตุการณ์', @@ -447,13 +434,10 @@ return array( 'Percentage' => 'เปอร์เซ็นต์', 'Number of tasks' => 'จำนวนงาน', 'Task distribution' => 'การกระจายงาน', - 'Reportings' => 'รายงาน', - // 'Task repartition for "%s"' => '', 'Analytics' => 'การวิเคราะห์', 'Subtask' => 'งานย่อย', 'My subtasks' => 'งานย่อยของฉัน', 'User repartition' => 'การแบ่งงานของผู้ใช้', - 'User repartition for "%s"' => 'การแบ่งงานของผู้ใช้ "%s"', 'Clone this project' => 'เลียนแบบโปรเจคนี้', 'Column removed successfully.' => 'ลบคอลัมน์สำเร็จ', 'Not enough data to show the graph.' => 'ไม่มีข้อมูลแสดงเป็นกราฟ', @@ -470,10 +454,8 @@ return array( 'This value must be numeric' => 'ค่านี้ต้องเป็นตัวเลข', 'Unable to create this task.' => 'ไม่สามารถสร้างงานนี้', 'Cumulative flow diagram' => 'แผนภาพงานสะสม', - 'Cumulative flow diagram for "%s"' => 'แผนภาพงานสะสม "%s"', 'Daily project summary' => 'สรุปโปรเจครายวัน', 'Daily project summary export' => 'ส่งออกสรุปโปรเจครายวัน', - 'Daily project summary export for "%s"' => 'ส่งออกสรุปโปรเจครายวันสำหรับ "%s"', 'Exports' => 'ส่งออก', 'This export contains the number of tasks per column grouped per day.' => 'การส่งออกนี้เป็นการนับจำนวนงานในแต่ละคอลัมน์ในแต่ละวัน', 'Active swimlanes' => 'สวิมเลนพร้อมใช้งาน', @@ -485,7 +467,6 @@ return array( 'Remove a swimlane' => 'ลบสวิมเลน', 'Show default swimlane' => 'แสดงสวิมเลนเริ่มต้น', 'Swimlane modification for the project "%s"' => 'แก้ไขสวิมเลนสำหรับโปรเจค "%s"', - 'Swimlane not found.' => 'หาสวิมเลนไม่พบ', 'Swimlane removed successfully.' => 'ลบสวิมเลนเรียบร้อยแล้ว', 'Swimlanes' => 'สวิมเลน', 'Swimlane updated successfully.' => 'ปรับปรุงสวิมเลนเรียบร้อยแล้ว', @@ -500,7 +481,6 @@ return array( 'Subtask Id' => 'รหัสงานย่อย', 'Subtasks' => 'งานย่อย', 'Subtasks Export' => 'ส่งออก งานย่อย', - 'Subtasks exportation for "%s"' => 'ส่งออกงานย่อยสำหรับ "%s"', 'Task Title' => 'ชื่องาน', 'Untitled' => 'ไม่มีชื่อ', 'Application default' => 'แอพพลิเคชันเริ่มต้น', @@ -607,7 +587,7 @@ return array( 'The currency rate have been added successfully.' => 'เพิ่มอัตราค่าเงินเรียบร้อย', 'Unable to add this currency rate.' => 'ไม่สามารถเพิ่มค่าเงินนี้', // 'Webhook URL' => '', - '%s remove the assignee of the task %s' => '%s เอาผู้รับผิดชอบออกจากงาน %s', + '%s removed the assignee of the task %s' => '%s เอาผู้รับผิดชอบออกจากงาน %s', 'Enable Gravatar images' => 'สามารถใช้งานภาพ Gravatar', 'Information' => 'ข้อมูลสารสนเทศ', // 'Check two factor authentication code' => '', @@ -621,7 +601,6 @@ return array( 'Test your device' => 'ทดสอบอุปกรณ์ของคุณ', 'Assign a color when the task is moved to a specific column' => 'กำหนดสีเมื่องานถูกย้ายไปคอลัมน์ที่กำหนดไว้', // '%s via Kanboard' => '', - 'Burndown chart for "%s"' => 'แผนภูมิงานกับเวลา "%s"', 'Burndown chart' => 'แผนภูมิงานกับเวลา', 'This chart show the task complexity over the time (Work Remaining).' => 'แผนภูมิแสดงความซับซ้อนของงานตามเวลา (งานที่เหลือ)', 'Screenshot taken %s' => 'จับภาพหน้าจอ %s', @@ -686,14 +665,8 @@ return array( 'Move the task to another column when the category is changed' => 'ย้ายงานไปคอลัมน์อื่นเมื่อหมวดถูกเปลี่ยน', 'Send a task by email to someone' => 'ส่งงานโดยถึงบางคน', 'Reopen a task' => 'เปิดงานอีกครั้ง', - 'Column change' => 'เปลี่ยนคอลัมน์', - 'Position change' => 'เปลี่ยนตำแหน่ง', - 'Swimlane change' => 'เปลี่ยนสวิมเลน', - 'Assignee change' => 'เปลี่ยนการผู้รับผิดชอบ', - '[%s] Overdue tasks' => '[%s] งานที่เกินกำหนด', 'Notification' => 'แจ้งเตือน', '%s moved the task #%d to the first swimlane' => '%s ย้ายงาน #%d ไปสวินเลนแรก', - '%s moved the task #%d to the swimlane "%s"' => '%s ย้ายงาน #%d ไปสวินเลน "%s"', 'Swimlane' => 'สวิมเลน', 'Gravatar' => 'รูปแทนตัว', '%s moved the task %s to the first swimlane' => '%s ย้ายงาน %s ไปสวินเลนแรก', @@ -764,8 +737,6 @@ return array( 'Search by category: ' => 'ค้นหาตามหมวด: ', 'Search by description: ' => 'ค้นหาตามคำอธิบาย: ', 'Search by due date: ' => 'ค้นหาตามวันครบกำหนด: ', - 'Lead and Cycle time for "%s"' => 'เวลานำและรอบเวลาสำหรับ "%s"', - 'Average time spent into each column for "%s"' => 'ค่าเฉลี่ยเวลาที่ใช้แต่ละคอลัมน์สำหรับ "%s"', 'Average time spent into each column' => 'ค่าเฉลี่ยเวลาที่ใช้แต่ละคอลัมน์', 'Average time spent' => 'ค่าเฉลี่ยเวลาที่ใช้', 'This chart show the average time spent into each column for the last %d tasks.' => 'แผนภูมิแสดงค่าเฉลี่ยเวลาที่ใช้แต่ละคอลัมน์สำหรับ %d งานล่าสุด', @@ -806,7 +777,6 @@ return array( 'License:' => 'สัญญาอนุญาต:', 'License' => 'สัญญาอนุญาต', 'Enter the text below' => 'พิมพ์ข้อความด้านล่าง', - 'Gantt chart for %s' => 'แผนภูมิแกรนท์สำหรับ %s', 'Sort by position' => 'เรียงตามตำแหน่ง', 'Sort by date' => 'เรียงตามวัน', 'Add task' => 'เพิ่มงาน', @@ -847,8 +817,6 @@ return array( 'Version' => 'เวอร์ชัน', 'Plugins' => 'ปลั๊กอิน', 'There is no plugin loaded.' => 'ไม่มีปลั๊กอินถูกโหลดไว้', - 'Set maximum column height' => 'กำหนดความสูงสูงสุดของคอลัมน์', - 'Remove maximum column height' => 'เอาความสูงสูงสุดของคอลัมน์ออก', 'My notifications' => 'การแจ้งเตือนของฉัน', 'Custom filters' => 'ตัวกรองกำหนดเอง', 'Your custom filter have been created successfully.' => 'ตัวกรองกำหนดเองของคุณสร้างเรียบร้อย', @@ -953,7 +921,6 @@ return array( 'Invalid captcha' => 'captcha ไม่ถูกต้อง', 'The name must be unique' => 'ชื่อต้องไม่ซ้ำ', 'View all groups' => 'แสดงกลุ่มทั้งหมด', - 'View group members' => 'แสดงสมาชิกกลุ่ม', // 'There is no user available.' => '', 'Do you really want to remove the user "%s" from the group "%s"?' => 'คุณต้องการลบผู้ใช้ "%s" ออกจากกลุ่ม "%s"?', 'There is no group.' => 'ไม่มีกลุ่ม', @@ -974,13 +941,10 @@ return array( 'Enter group name...' => 'พิมพ์ชื่อกลุ่ม...', 'Role:' => 'บทบาท:', 'Project members' => 'สมาชิกโปรเจค', - 'Compare hours for "%s"' => 'เปรียบเทียบรายชั่วโมงสำหรับ %s', '%s mentioned you in the task #%d' => '%s กล่าวถึงคุณในงาน #%d', '%s mentioned you in a comment on the task #%d' => '%s กล่าวถึงคุณในความคิดเห็นของงาน #%d', 'You were mentioned in the task #%d' => 'คุณได้รับการกล่าวถึงในงาน #%d', 'You were mentioned in a comment on the task #%d' => 'คุณได้รับการกล่าวถึงในความคิดเห็นของงาน #%d', - 'Mentioned' => 'กล่าวถึง', - 'Compare Estimated Time vs Actual Time' => 'เปรียบเทียบเวลาโดยประมาณกับเวลาที่เกิดขึ้นจริง', 'Estimated hours: ' => 'เวลาโดยประมาณ:', 'Actual hours: ' => 'เวลาที่เกิดขึ้นจริง:', 'Hours Spent' => 'เวลาที่ใช้', @@ -1202,4 +1166,52 @@ return array( // 'Email transport' => '', // 'Webhook token' => '', // 'Imports' => '', + // 'Project tags management' => '', + // 'Tag created successfully.' => '', + // 'Unable to create this tag.' => '', + // 'Tag updated successfully.' => '', + // 'Unable to update this tag.' => '', + // 'Tag removed successfully.' => '', + // 'Unable to remove this tag.' => '', + // 'Global tags management' => '', + // 'Tags' => '', + // 'Tags management' => '', + // 'Add new tag' => '', + // 'Edit a tag' => '', + // 'Project tags' => '', + // 'There is no specific tag for this project at the moment.' => '', + // 'Tag' => '', + // 'Remove a tag' => '', + // 'Do you really want to remove this tag: "%s"?' => '', + // 'Global tags' => '', + // 'There is no global tag at the moment.' => '', + // 'This field cannot be empty' => '', + // 'Close a task when there is no activity in an specific column' => '', + // '%s removed a subtask for the task #%d' => '', + // '%s removed a comment on the task #%d' => '', + // 'Comment removed on task #%d' => '', + // 'Subtask removed on task #%d' => '', + // 'Hide tasks in this column in the dashboard' => '', + // '%s removed a comment on the task %s' => '', + // '%s removed a subtask for the task %s' => '', + // 'Comment removed' => '', + // 'Subtask removed' => '', + // '%s set a new internal link for the task #%d' => '', + // '%s removed an internal link for the task #%d' => '', + // 'A new internal link for the task #%d have been defined' => '', + // 'Internal link removed for the task #%d' => '', + // '%s set a new internal link for the task %s' => '', + // '%s removed an internal link for the task %s' => '', + // 'Automatically set the due date on task creation' => '', + // 'Move the task to another column when closed' => '', + // 'Move the task to another column when not moved during a given period' => '', + // 'Dashboard for %s' => '', + // 'Tasks overview for %s' => '', + // 'Subtasks overview for %s' => '', + // 'Projects overview for %s' => '', + // 'Activity stream for %s' => '', + // 'Calendar for %s' => '', + // 'Notifications for %s' => '', + // 'Subtasks export' => '', + // 'Tasks exportation' => '', ); diff --git a/app/Locale/tr_TR/translations.php b/app/Locale/tr_TR/translations.php index 229d766c..69642b58 100644 --- a/app/Locale/tr_TR/translations.php +++ b/app/Locale/tr_TR/translations.php @@ -154,8 +154,6 @@ return array( '%d closed tasks' => '%d kapatılmış görevler', 'No task for this project' => 'Bu proje için görev yok', 'Public link' => 'Dışa açık link', - 'Change assignee' => 'Atanmış Kullanıcıyı değiştir', - 'Change assignee for the task "%s"' => '"%s" görevi için atanmış kullanıcıyı değiştir', 'Timezone' => 'Saat dilimi', 'Sorry, I didn\'t find this information in my database!' => 'Üzgünüm, bu bilgiyi veri tabanımda bulamadım.', 'Page not found' => 'Sayfa bulunamadı', @@ -248,7 +246,6 @@ return array( 'Category' => 'Kategori', 'Category:' => 'Kategori:', 'Categories' => 'Kategoriler', - 'Category not found.' => 'Kategori bulunamadı.', 'Your category have been created successfully.' => 'Kategori başarıyla oluşturuldu.', 'Unable to create your category.' => 'Kategori oluşturulamadı.', 'Your category have been updated successfully.' => 'Kategori başarıyla güncellendi.', @@ -270,7 +267,6 @@ return array( 'Do you really want to remove this file: "%s"?' => 'Bu dosyayı silmek istediğinize emin misiniz: "%s"?', 'Attachments' => 'Ekler', 'Edit the task' => 'Görevi değiştir', - 'Edit the description' => 'Açıklamayı değiştir', 'Add a comment' => 'Yorum ekle', 'Edit a comment' => 'Yorum değiştir', 'Summary' => 'Özet', @@ -303,7 +299,6 @@ return array( 'Display another project' => 'Başka bir proje göster', 'Created by %s' => '%s tarafından oluşturuldu', 'Tasks Export' => 'Görevleri dışa aktar', - 'Tasks exportation for "%s"' => '"%s" için görevleri dışa aktar', 'Start Date' => 'Başlangıç tarihi', 'End Date' => 'Bitiş tarihi', 'Execute' => 'Gerçekleştir', @@ -326,14 +321,9 @@ return array( 'New sub-task' => 'Yeni alt görev', 'New attachment added "%s"' => 'Yeni dosya "%s" eklendi.', 'New comment posted by %s' => '%s tarafından yeni yorum eklendi', - 'New attachment' => 'Yeni dosya eki', 'New comment' => 'Yeni yorum', 'Comment updated' => 'Yorum güncellendi', 'New subtask' => 'Yeni alt görev', - 'Subtask updated' => 'Alt görev güncellendi', - 'Task updated' => 'Görev güncellendi', - 'Task closed' => 'Görev kapatıldı', - 'Task opened' => 'Görev açıldı', 'I want to receive notifications only for those projects:' => 'Yalnızca bu projelerle ilgili bildirim almak istiyorum:', 'view the task on Kanboard' => 'bu görevi Kanboard üzerinde göster', 'Public access' => 'Dışa açık erişim', @@ -368,7 +358,6 @@ return array( 'No external authentication enabled.' => 'Dış kimlik doğrulama kapalı.', 'Password modified successfully.' => 'Şifre başarıyla değiştirildi.', 'Unable to change the password.' => 'Şifre değiştirilemiyor.', - 'Change category for the task "%s"' => '"%s" görevi için kategori değiştirme', 'Change category' => 'Kategori değiştirme', '%s updated the task %s' => '%s kullanıcısı %s görevini güncelledi', '%s opened the task %s' => '%s kullanıcısı %s görevini açtı', @@ -391,14 +380,12 @@ return array( '%s updated the task #%d' => '%s kullanıcısı #%d nolu görevi güncelledi', '%s created the task #%d' => '%s kullanıcısı #%d nolu görevi oluşturdu', '%s closed the task #%d' => '%s kullanıcısı #%d nolu görevi kapattı', - '%s open the task #%d' => '%s kullanıcısı #%d nolu görevi açtı', - '%s moved the task #%d to the column "%s"' => '%s kullanıcısı #%d nolu görevi "%s" sütununa taşıdı', - '%s moved the task #%d to the position %d in the column "%s"' => '%s kullanıcısı #%d nolu görevi %d pozisyonu "%s" sütununa taşıdı', + '%s opened the task #%d' => '%s kullanıcısı #%d nolu görevi açtı', 'Activity' => 'Aktivite', 'Default values are "%s"' => 'Varsayılan değerler "%s"', 'Default columns for new projects (Comma-separated)' => 'Yeni projeler için varsayılan sütunlar (virgül ile ayrılmış)', 'Task assignee change' => 'Göreve atanan kullanıcı değişikliği', - '%s change the assignee of the task #%d to %s' => '%s kullanıcısı #%d nolu görevin sorumlusunu %s olarak değiştirdi', + '%s changed the assignee of the task #%d to %s' => '%s kullanıcısı #%d nolu görevin sorumlusunu %s olarak değiştirdi', '%s changed the assignee of the task %s to %s' => '%s kullanıcısı %s görevinin sorumlusunu %s olarak değiştirdi', 'New password for the user "%s"' => '"%s" kullanıcısı için yeni şifre', 'Choose an event' => 'Bir durum seçin', @@ -447,13 +434,10 @@ return array( 'Percentage' => 'Yüzde', 'Number of tasks' => 'Görev sayısı', 'Task distribution' => 'Görev dağılımı', - 'Reportings' => 'Raporlar', - 'Task repartition for "%s"' => '"%s" için görev dağılımı', 'Analytics' => 'Analiz', 'Subtask' => 'Alt görev', 'My subtasks' => 'Alt görevlerim', 'User repartition' => 'Kullanıcı dağılımı', - 'User repartition for "%s"' => '"%s" için kullanıcı dağılımı', 'Clone this project' => 'Projenin kopyasını oluştur', 'Column removed successfully.' => 'Sütun başarıyla kaldırıldı.', 'Not enough data to show the graph.' => 'Grafik gösterimi için yeterli veri yok.', @@ -470,10 +454,8 @@ return array( 'This value must be numeric' => 'Bu değer sayı olmalı', 'Unable to create this task.' => 'Bu görev oluşturulamıyor.', 'Cumulative flow diagram' => 'Kümülatif akış diyagramı', - 'Cumulative flow diagram for "%s"' => '"%s" için kümülatif akış diyagramı', 'Daily project summary' => 'Günlük proje özeti', 'Daily project summary export' => 'Günlük proje özetini dışa aktar', - 'Daily project summary export for "%s"' => '"%s" için günlük proje özetinin dışa', 'Exports' => 'Dışa aktarımlar', 'This export contains the number of tasks per column grouped per day.' => 'Bu dışa aktarım günlük gruplanmış olarak her sütundaki görev sayısını içerir.', 'Active swimlanes' => 'Aktif Kulvar', @@ -485,7 +467,6 @@ return array( 'Remove a swimlane' => 'Kulvarı sil', 'Show default swimlane' => 'Varsayılan Kulvarı göster', 'Swimlane modification for the project "%s"' => '"%s" Projesi için Kulvar değişikliği', - 'Swimlane not found.' => 'Kulvar bulunamadı', 'Swimlane removed successfully.' => 'Kulvar başarıyla kaldırıldı.', 'Swimlanes' => 'Kulvarlar', 'Swimlane updated successfully.' => 'Kulvar başarıyla güncellendi.', @@ -500,7 +481,6 @@ return array( 'Subtask Id' => 'Alt görev No:', 'Subtasks' => 'Alt görevler', 'Subtasks Export' => 'Alt görevleri dışa aktar', - 'Subtasks exportation for "%s"' => '"%s" için alt görevleri dışa aktarımı', 'Task Title' => 'Görev Başlığı', 'Untitled' => 'Başlıksız', 'Application default' => 'Uygulama varsayılanları', @@ -607,7 +587,7 @@ return array( 'The currency rate have been added successfully.' => 'Kur başarıyla eklendi', 'Unable to add this currency rate.' => 'Bu kur eklenemedi', // 'Webhook URL' => '', - '%s remove the assignee of the task %s' => '%s, %s görevinin atanan bilgisini kaldırdı', + '%s removed the assignee of the task %s' => '%s, %s görevinin atanan bilgisini kaldırdı', 'Enable Gravatar images' => 'Gravatar resimlerini kullanıma aç', 'Information' => 'Bilgi', 'Check two factor authentication code' => 'İki kademeli doğrulama kodunu kontrol et', @@ -621,7 +601,6 @@ return array( 'Test your device' => 'Cihazınızı test edin', 'Assign a color when the task is moved to a specific column' => 'Görev belirli bir sütuna taşındığında rengini değiştir', '%s via Kanboard' => '%s Kanboard ile', - 'Burndown chart for "%s"' => '%s icin kalan iş grafiği', 'Burndown chart' => 'Kalan iş grafiği', 'This chart show the task complexity over the time (Work Remaining).' => 'Bu grafik zorluk seviyesini zamana göre gösterir (kalan iş)', 'Screenshot taken %s' => 'Ekran görüntüsü alındı %s', @@ -686,14 +665,8 @@ return array( 'Move the task to another column when the category is changed' => 'Kategori değiştirildiğinde görevi başka sütuna taşı', 'Send a task by email to someone' => 'Bir görevi email ile birine gönder', 'Reopen a task' => 'Bir görevi tekrar aç', - 'Column change' => 'Sütun değişikliği', - 'Position change' => 'Konum değişikliği', - 'Swimlane change' => 'Kulvar değişikliği', - 'Assignee change' => 'Atanan değişikliği', - '[%s] Overdue tasks' => '[%s] Gecikmiş görevler', 'Notification' => 'Uyarılar', '%s moved the task #%d to the first swimlane' => '%s, #%d görevini birinci kulvara taşıdı', - '%s moved the task #%d to the swimlane "%s"' => '%s, #%d görevini "%s" kulvarına taşıdı', 'Swimlane' => 'Kulvar', 'Gravatar' => 'Gravatar', '%s moved the task %s to the first swimlane' => '%s, %s görevini ilk kulvara taşıdı', @@ -764,8 +737,6 @@ return array( 'Search by category: ' => 'Kategoriye göre ara', 'Search by description: ' => 'Açıklamaya göre ara', 'Search by due date: ' => 'Tamamlanma tarihine göre ara', - 'Lead and Cycle time for "%s"' => '"%s" için teslim ve çevrim süresi', - 'Average time spent into each column for "%s"' => '"%s" için her bir sütunda geçirilen ortalama zaman', 'Average time spent into each column' => 'Her bir sütunda geçirilen ortalama zaman', 'Average time spent' => 'Harcanan ortalama zaman', 'This chart show the average time spent into each column for the last %d tasks.' => 'Bu grafik son %d görev için her bir sütunda geçirilen ortalama zamanı gösterir.', @@ -806,7 +777,6 @@ return array( 'License:' => 'Lisans:', 'License' => 'Lisans', 'Enter the text below' => 'Aşağıdaki metni girin', - 'Gantt chart for %s' => '%s için Gantt diyagramı', 'Sort by position' => 'Pozisyona göre sırala', 'Sort by date' => 'Tarihe göre sırala', 'Add task' => 'Görev ekle', @@ -847,8 +817,6 @@ return array( 'Version' => 'Versiyon', 'Plugins' => 'Eklentiler', 'There is no plugin loaded.' => 'Yüklenmiş bir eklendi yok', - 'Set maximum column height' => 'Maksimum sütun yüksekliğini belirle', - 'Remove maximum column height' => 'Maksimum sütun yüksekliğini iptal et', 'My notifications' => 'Bildirimlerim', 'Custom filters' => 'Özel filtreler', 'Your custom filter have been created successfully.' => 'Özel filtreleriniz başarıyla oluşturuldu.', @@ -953,7 +921,6 @@ return array( 'Invalid captcha' => 'Geçersiz captcha', 'The name must be unique' => 'İsim tekil olmalı', 'View all groups' => 'Tüm grupları görüntüle', - 'View group members' => 'Grup üyelerini görüntüle', 'There is no user available.' => 'Uygun üye yok', 'Do you really want to remove the user "%s" from the group "%s"?' => '"%s" kullanıcısını "%s" grubundan çıkarmak istediğinize emin misiniz?', 'There is no group.' => 'Hiç grup yok.', @@ -974,13 +941,10 @@ return array( 'Enter group name...' => 'Grup adını girin...', 'Role:' => 'Rol:', 'Project members' => 'Proje üyeleri', - 'Compare hours for "%s"' => '"%s" için saatleri karşılaştır', '%s mentioned you in the task #%d' => '%s sizden #%d görevinde bahsetti', '%s mentioned you in a comment on the task #%d' => '%s sizden #%d görevindeki bir yorumda bahsetti', 'You were mentioned in the task #%d' => '#%d görevinde sizden bahsedildi', 'You were mentioned in a comment on the task #%d' => '#%d görevindeki bir yorumda sizden bahsedildi', - 'Mentioned' => 'Bahsedilmiş', - 'Compare Estimated Time vs Actual Time' => 'Tahmini süre ile gerçekleşen süreyi karşılaştır', 'Estimated hours: ' => 'Tahmini saat:', 'Actual hours: ' => 'Gerçekleşen saat:', 'Hours Spent' => 'Harcanan saat', @@ -1202,4 +1166,52 @@ return array( // 'Email transport' => '', // 'Webhook token' => '', // 'Imports' => '', + // 'Project tags management' => '', + // 'Tag created successfully.' => '', + // 'Unable to create this tag.' => '', + // 'Tag updated successfully.' => '', + // 'Unable to update this tag.' => '', + // 'Tag removed successfully.' => '', + // 'Unable to remove this tag.' => '', + // 'Global tags management' => '', + // 'Tags' => '', + // 'Tags management' => '', + // 'Add new tag' => '', + // 'Edit a tag' => '', + // 'Project tags' => '', + // 'There is no specific tag for this project at the moment.' => '', + // 'Tag' => '', + // 'Remove a tag' => '', + // 'Do you really want to remove this tag: "%s"?' => '', + // 'Global tags' => '', + // 'There is no global tag at the moment.' => '', + // 'This field cannot be empty' => '', + // 'Close a task when there is no activity in an specific column' => '', + // '%s removed a subtask for the task #%d' => '', + // '%s removed a comment on the task #%d' => '', + // 'Comment removed on task #%d' => '', + // 'Subtask removed on task #%d' => '', + // 'Hide tasks in this column in the dashboard' => '', + // '%s removed a comment on the task %s' => '', + // '%s removed a subtask for the task %s' => '', + // 'Comment removed' => '', + // 'Subtask removed' => '', + // '%s set a new internal link for the task #%d' => '', + // '%s removed an internal link for the task #%d' => '', + // 'A new internal link for the task #%d have been defined' => '', + // 'Internal link removed for the task #%d' => '', + // '%s set a new internal link for the task %s' => '', + // '%s removed an internal link for the task %s' => '', + // 'Automatically set the due date on task creation' => '', + // 'Move the task to another column when closed' => '', + // 'Move the task to another column when not moved during a given period' => '', + // 'Dashboard for %s' => '', + // 'Tasks overview for %s' => '', + // 'Subtasks overview for %s' => '', + // 'Projects overview for %s' => '', + // 'Activity stream for %s' => '', + // 'Calendar for %s' => '', + // 'Notifications for %s' => '', + // 'Subtasks export' => '', + // 'Tasks exportation' => '', ); diff --git a/app/Locale/zh_CN/translations.php b/app/Locale/zh_CN/translations.php index 2f3a9f74..b4e9c063 100644 --- a/app/Locale/zh_CN/translations.php +++ b/app/Locale/zh_CN/translations.php @@ -105,7 +105,7 @@ return array( 'Add a new task' => '添加新任务', 'The username is required' => '需要用户名', 'The maximum length is %d characters' => '最长%d个英文字符', - 'The minimum length is %d characters' => '最短%d个英文自负', + 'The minimum length is %d characters' => '最短%d个英文字符', 'The password is required' => '需要密码', 'This value must be an integer' => '该值必须为整数', 'The username must be unique' => '用户名必须唯一', @@ -154,8 +154,6 @@ return array( '%d closed tasks' => '%d个已关闭任务', 'No task for this project' => '该项目尚无任务', 'Public link' => '公开链接', - 'Change assignee' => '变更负责人', - 'Change assignee for the task "%s"' => '更改任务"%s"的负责人', 'Timezone' => '时区', 'Sorry, I didn\'t find this information in my database!' => '抱歉,无法在数据库中找到该信息!', 'Page not found' => '页面未找到', @@ -248,7 +246,6 @@ return array( 'Category' => '分类', 'Category:' => '分类:', 'Categories' => '分类', - 'Category not found.' => '未找到分类。', 'Your category have been created successfully.' => '成功为您创建分类。', 'Unable to create your category.' => '无法为您创建分类。', 'Your category have been updated successfully.' => '成功为您更新分类。', @@ -270,7 +267,6 @@ return array( 'Do you really want to remove this file: "%s"?' => '确定要移除文件"%s"吗?', 'Attachments' => '附件', 'Edit the task' => '修改任务', - 'Edit the description' => '修改描述', 'Add a comment' => '添加评论', 'Edit a comment' => '编辑评论', 'Summary' => '概要', @@ -303,7 +299,6 @@ return array( 'Display another project' => '显示其它项目', 'Created by %s' => '创建者:%s', 'Tasks Export' => '任务导出', - 'Tasks exportation for "%s"' => '导出"%s"的任务', 'Start Date' => '开始时间', 'End Date' => '结束时间', 'Execute' => '执行', @@ -326,14 +321,9 @@ return array( 'New sub-task' => '新建子任务', 'New attachment added "%s"' => '新附件已添加"%s"', 'New comment posted by %s' => '%s 的新评论', - 'New attachment' => '新建附件', 'New comment' => '新建评论', 'Comment updated' => '更新了评论', 'New subtask' => '新建子任务', - 'Subtask updated' => '子任务更新', - 'Task updated' => '任务更新', - 'Task closed' => '任务关闭', - 'Task opened' => '任务开启', 'I want to receive notifications only for those projects:' => '我仅需要收到下面项目的通知:', 'view the task on Kanboard' => '在看板中查看此任务', 'Public access' => '公开访问', @@ -368,7 +358,6 @@ return array( 'No external authentication enabled.' => '未启用外部认证。', 'Password modified successfully.' => '已经成功修改密码。', 'Unable to change the password.' => '无法修改密码。', - 'Change category for the task "%s"' => '变更任务 "%s" 的分类', 'Change category' => '变更分类', '%s updated the task %s' => '%s 更新了任务 %s', '%s opened the task %s' => '%s 开启了任务 %s', @@ -391,14 +380,12 @@ return array( '%s updated the task #%d' => '%s 更新了任务 #%d', '%s created the task #%d' => '%s 创建了任务 #%d', '%s closed the task #%d' => '%s 关闭了任务 #%d', - '%s open the task #%d' => '%s 开启了任务 #%d', - '%s moved the task #%d to the column "%s"' => '%s 将任务 #%d 移动到栏目 "%s"', - '%s moved the task #%d to the position %d in the column "%s"' => '%s将任务#%d移动到"%s"的第 %d 列', + '%s opened the task #%d' => '%s 开启了任务 #%d', 'Activity' => '动态', 'Default values are "%s"' => '默认值为 "%s"', 'Default columns for new projects (Comma-separated)' => '新建项目的默认栏目(用逗号分开)', 'Task assignee change' => '任务分配变更', - '%s change the assignee of the task #%d to %s' => '%s 将任务 #%d 分配给了 %s', + '%s changed the assignee of the task #%d to %s' => '%s 将任务 #%d 分配给了 %s', '%s changed the assignee of the task %s to %s' => '%s 将任务 %s 分配给 %s', 'New password for the user "%s"' => '用户"%s"的新密码', 'Choose an event' => '选择一个事件', @@ -447,13 +434,10 @@ return array( 'Percentage' => '百分比', 'Number of tasks' => '任务数', 'Task distribution' => '任务分布', - 'Reportings' => '报告', - 'Task repartition for "%s"' => '"%s"的任务分析', 'Analytics' => '分析', 'Subtask' => '子任务', 'My subtasks' => '我的子任务', 'User repartition' => '用户分析', - 'User repartition for "%s"' => '"%s"的用户分析', 'Clone this project' => '复制此项目', 'Column removed successfully.' => '成功删除了栏目。', 'Not enough data to show the graph.' => '数据不足,无法绘图。', @@ -470,10 +454,8 @@ return array( 'This value must be numeric' => '这个值必须为数字', 'Unable to create this task.' => '无法创建此任务。', 'Cumulative flow diagram' => '累积流图表', - 'Cumulative flow diagram for "%s"' => '"%s"的累积流图表', 'Daily project summary' => '每日项目汇总', 'Daily project summary export' => '导出每日项目汇总', - 'Daily project summary export for "%s"' => '导出项目"%s"的每日汇总', 'Exports' => '导出', 'This export contains the number of tasks per column grouped per day.' => '此导出包含每列的任务数,按天分组', 'Active swimlanes' => '活动里程碑', @@ -485,7 +467,6 @@ return array( 'Remove a swimlane' => '删除里程碑', 'Show default swimlane' => '显示默认里程碑', 'Swimlane modification for the project "%s"' => '项目"%s"的里程碑变更', - 'Swimlane not found.' => '未找到里程碑。', 'Swimlane removed successfully.' => '成功删除里程碑', 'Swimlanes' => '里程碑', 'Swimlane updated successfully.' => '成功更新了里程碑。', @@ -500,7 +481,6 @@ return array( 'Subtask Id' => '子任务 Id', 'Subtasks' => '子任务', 'Subtasks Export' => '子任务导出', - 'Subtasks exportation for "%s"' => '导出"%s"的子任务', 'Task Title' => '任务标题', 'Untitled' => '无标题', 'Application default' => '程序默认', @@ -607,7 +587,7 @@ return array( 'The currency rate have been added successfully.' => '成功添加汇率。', 'Unable to add this currency rate.' => '无法添加此汇率', 'Webhook URL' => '网络钩子 URL', - '%s remove the assignee of the task %s' => '%s删除了任务%s的负责人', + '%s removed the assignee of the task %s' => '%s删除了任务%s的负责人', 'Enable Gravatar images' => '启用 Gravatar 图像', 'Information' => '信息', 'Check two factor authentication code' => '检查双重认证码', @@ -621,7 +601,6 @@ return array( 'Test your device' => '测试设备', 'Assign a color when the task is moved to a specific column' => '任务移动到指定栏目时设置颜色', '%s via Kanboard' => '%s 通过KanBoard', - 'Burndown chart for "%s"' => '燃尽图:"%s"', 'Burndown chart' => '燃尽图', 'This chart show the task complexity over the time (Work Remaining).' => '图表显示任务随时间(剩余时间)的复杂度', 'Screenshot taken %s' => '已截图 %s', @@ -686,14 +665,8 @@ return array( 'Move the task to another column when the category is changed' => '当任务分类改变时移动到任务栏', 'Send a task by email to someone' => '发送任务邮件到用户', 'Reopen a task' => '重新开始一个任务', - 'Column change' => '任务栏改变', - 'Position change' => '位置改变', - 'Swimlane change' => '里程碑改变', - 'Assignee change' => '指派人改变', - '[%s] Overdue tasks' => '[%s] 超期任务', 'Notification' => '通知', '%s moved the task #%d to the first swimlane' => '%s将任务#%d移动到了首个里程碑', - '%s moved the task #%d to the swimlane "%s"' => '%s将任务#%d移动到了里程碑"%s"下', 'Swimlane' => '里程碑', 'Gravatar' => 'Gravatar头像', '%s moved the task %s to the first swimlane' => '%s将任务%s移动到了首个里程碑', @@ -764,8 +737,6 @@ return array( 'Search by category: ' => '按分类:', 'Search by description: ' => '按描述:', 'Search by due date: ' => '按超期时间:', - // 'Lead and Cycle time for "%s"' => '', - 'Average time spent into each column for "%s"' => '"%s"在每个任务栏下平均花费时间', 'Average time spent into each column' => '每个任务栏平均花费时间', 'Average time spent' => '平均花费时间', 'This chart show the average time spent into each column for the last %d tasks.' => '当前柱状图表示最新%d条任务在每个任务栏下的平均花费时间', @@ -806,7 +777,6 @@ return array( 'License:' => '授权许可:', 'License' => '授权许可', 'Enter the text below' => '输入下方的文本', - 'Gantt chart for %s' => '%s的甘特图', 'Sort by position' => '按位置排序', 'Sort by date' => '按日期排序', 'Add task' => '添加任务', @@ -847,8 +817,6 @@ return array( 'Version' => '版本', 'Plugins' => '插件', 'There is no plugin loaded.' => '当前没有插件载入', - 'Set maximum column height' => '设置任务栏最大高度', - 'Remove maximum column height' => '移除任务栏最大高度', 'My notifications' => '我的通知', 'Custom filters' => '自定义过滤器', 'Your custom filter have been created successfully.' => '成功创建过滤器', @@ -953,7 +921,6 @@ return array( 'Invalid captcha' => '验证码无效', 'The name must be unique' => '请确保用户名唯一', 'View all groups' => '查看所有用户组', - 'View group members' => '查看该组所有用户', 'There is no user available.' => '当前没有有效用户', 'Do you really want to remove the user "%s" from the group "%s"?' => '你确定把用户"%s"从"%s"中移除?', 'There is no group.' => '当前没有用户组', @@ -974,13 +941,10 @@ return array( 'Enter group name...' => '输入用户组名称...', 'Role:' => '角色:', 'Project members' => '项目成员', - 'Compare hours for "%s"' => '比较"%s"的时间', '%s mentioned you in the task #%d' => '%s在任务#%d里提及你', '%s mentioned you in a comment on the task #%d' => '%s在任务#%d的评论里提及你', 'You were mentioned in the task #%d' => '你在任务#%d里被提及', 'You were mentioned in a comment on the task #%d' => '你在任务#%d的评论里被提及', - 'Mentioned' => '被提及', - 'Compare Estimated Time vs Actual Time' => '对比预估时间VS实际时间', 'Estimated hours: ' => '预估小时数', 'Actual hours: ' => '实际小时数', 'Hours Spent' => '花费小时数', @@ -1202,4 +1166,52 @@ return array( // 'Email transport' => '', // 'Webhook token' => '', // 'Imports' => '', + // 'Project tags management' => '', + // 'Tag created successfully.' => '', + // 'Unable to create this tag.' => '', + // 'Tag updated successfully.' => '', + // 'Unable to update this tag.' => '', + // 'Tag removed successfully.' => '', + // 'Unable to remove this tag.' => '', + // 'Global tags management' => '', + // 'Tags' => '', + // 'Tags management' => '', + // 'Add new tag' => '', + // 'Edit a tag' => '', + // 'Project tags' => '', + // 'There is no specific tag for this project at the moment.' => '', + // 'Tag' => '', + // 'Remove a tag' => '', + // 'Do you really want to remove this tag: "%s"?' => '', + // 'Global tags' => '', + // 'There is no global tag at the moment.' => '', + // 'This field cannot be empty' => '', + // 'Close a task when there is no activity in an specific column' => '', + // '%s removed a subtask for the task #%d' => '', + // '%s removed a comment on the task #%d' => '', + // 'Comment removed on task #%d' => '', + // 'Subtask removed on task #%d' => '', + // 'Hide tasks in this column in the dashboard' => '', + // '%s removed a comment on the task %s' => '', + // '%s removed a subtask for the task %s' => '', + // 'Comment removed' => '', + // 'Subtask removed' => '', + // '%s set a new internal link for the task #%d' => '', + // '%s removed an internal link for the task #%d' => '', + // 'A new internal link for the task #%d have been defined' => '', + // 'Internal link removed for the task #%d' => '', + // '%s set a new internal link for the task %s' => '', + // '%s removed an internal link for the task %s' => '', + // 'Automatically set the due date on task creation' => '', + // 'Move the task to another column when closed' => '', + // 'Move the task to another column when not moved during a given period' => '', + // 'Dashboard for %s' => '', + // 'Tasks overview for %s' => '', + // 'Subtasks overview for %s' => '', + // 'Projects overview for %s' => '', + // 'Activity stream for %s' => '', + // 'Calendar for %s' => '', + // 'Notifications for %s' => '', + // 'Subtasks export' => '', + // 'Tasks exportation' => '', ); diff --git a/app/Model/ActionModel.php b/app/Model/ActionModel.php index 53393ed5..b5d2bd06 100644 --- a/app/Model/ActionModel.php +++ b/app/Model/ActionModel.php @@ -86,6 +86,18 @@ class ActionModel extends Base } /** + * Get the projectId by the actionId + * + * @access public + * @param integer $action_id + * @return integer + */ + public function getProjectId($action_id) + { + return $this->db->table(self::TABLE)->eq('id', $action_id)->findOneColumn('project_id') ?: 0; + } + + /** * Attach parameters to actions * * @access private diff --git a/app/Model/BoardModel.php b/app/Model/BoardModel.php index d2718b47..4d559936 100644 --- a/app/Model/BoardModel.php +++ b/app/Model/BoardModel.php @@ -94,66 +94,6 @@ class BoardModel extends Base } /** - * Get all tasks sorted by columns and swimlanes - * - * @access public - * @param integer $project_id - * @param callable $callback - * @return array - */ - public function getBoard($project_id, $callback = null) - { - $swimlanes = $this->swimlaneModel->getSwimlanes($project_id); - $columns = $this->columnModel->getAll($project_id); - $nb_columns = count($columns); - - for ($i = 0, $ilen = count($swimlanes); $i < $ilen; $i++) { - $swimlanes[$i]['columns'] = $columns; - $swimlanes[$i]['nb_columns'] = $nb_columns; - $swimlanes[$i]['nb_tasks'] = 0; - $swimlanes[$i]['nb_swimlanes'] = $ilen; - - for ($j = 0; $j < $nb_columns; $j++) { - $column_id = $columns[$j]['id']; - $swimlane_id = $swimlanes[$i]['id']; - - if (! isset($swimlanes[0]['columns'][$j]['nb_column_tasks'])) { - $swimlanes[0]['columns'][$j]['nb_column_tasks'] = 0; - $swimlanes[0]['columns'][$j]['total_score'] = 0; - } - - $swimlanes[$i]['columns'][$j]['tasks'] = $callback === null ? $this->taskFinderModel->getTasksByColumnAndSwimlane($project_id, $column_id, $swimlane_id) : $callback($project_id, $column_id, $swimlane_id); - $swimlanes[$i]['columns'][$j]['nb_tasks'] = count($swimlanes[$i]['columns'][$j]['tasks']); - $swimlanes[$i]['columns'][$j]['score'] = $this->getColumnSum($swimlanes[$i]['columns'][$j]['tasks'], 'score'); - $swimlanes[$i]['nb_tasks'] += $swimlanes[$i]['columns'][$j]['nb_tasks']; - $swimlanes[0]['columns'][$j]['nb_column_tasks'] += $swimlanes[$i]['columns'][$j]['nb_tasks']; - $swimlanes[0]['columns'][$j]['total_score'] += $swimlanes[$i]['columns'][$j]['score']; - } - } - - return $swimlanes; - } - - /** - * Calculate the sum of the defined field for a list of tasks - * - * @access public - * @param array $tasks - * @param string $field - * @return integer - */ - public function getColumnSum(array &$tasks, $field) - { - $sum = 0; - - foreach ($tasks as $task) { - $sum += $task[$field]; - } - - return $sum; - } - - /** * Get the total of tasks per column * * @access public diff --git a/app/Model/CategoryModel.php b/app/Model/CategoryModel.php index 62fb5611..024d0026 100644 --- a/app/Model/CategoryModel.php +++ b/app/Model/CategoryModel.php @@ -56,6 +56,18 @@ class CategoryModel extends Base } /** + * Get the projectId by the category id + * + * @access public + * @param integer $category_id Category id + * @return integer + */ + public function getProjectId($category_id) + { + return $this->db->table(self::TABLE)->eq('id', $category_id)->findOneColumn('project_id') ?: 0; + } + + /** * Get a category id by the category name and project id * * @access public diff --git a/app/Model/ColumnModel.php b/app/Model/ColumnModel.php index 1adac0f2..5498ef54 100644 --- a/app/Model/ColumnModel.php +++ b/app/Model/ColumnModel.php @@ -32,6 +32,18 @@ class ColumnModel extends Base } /** + * Get projectId by the columnId + * + * @access public + * @param integer $column_id Column id + * @return integer + */ + public function getProjectId($column_id) + { + return $this->db->table(self::TABLE)->eq('id', $column_id)->findOneColumn('project_id'); + } + + /** * Get the first column id for a given project * * @access public @@ -126,19 +138,21 @@ class ColumnModel extends Base * Add a new column to the board * * @access public - * @param integer $project_id Project id - * @param string $title Column title - * @param integer $task_limit Task limit - * @param string $description Column description - * @return boolean|integer + * @param integer $project_id Project id + * @param string $title Column title + * @param integer $task_limit Task limit + * @param string $description Column description + * @param integer $hide_in_dashboard + * @return bool|int */ - public function create($project_id, $title, $task_limit = 0, $description = '') + public function create($project_id, $title, $task_limit = 0, $description = '', $hide_in_dashboard = 0) { $values = array( 'project_id' => $project_id, 'title' => $title, 'task_limit' => intval($task_limit), 'position' => $this->getLastColumnPosition($project_id) + 1, + 'hide_in_dashboard' => $hide_in_dashboard, 'description' => $description, ); @@ -153,13 +167,15 @@ class ColumnModel extends Base * @param string $title Column title * @param integer $task_limit Task limit * @param string $description Optional description + * @param integer $hide_in_dashboard * @return boolean */ - public function update($column_id, $title, $task_limit = 0, $description = '') + public function update($column_id, $title, $task_limit = 0, $description = '', $hide_in_dashboard = 0) { return $this->db->table(self::TABLE)->eq('id', $column_id)->update(array( 'title' => $title, 'task_limit' => intval($task_limit), + 'hide_in_dashboard' => $hide_in_dashboard, 'description' => $description, )); } diff --git a/app/Model/CommentModel.php b/app/Model/CommentModel.php index 36e1fc48..a9e48bd3 100644 --- a/app/Model/CommentModel.php +++ b/app/Model/CommentModel.php @@ -2,7 +2,6 @@ namespace Kanboard\Model; -use Kanboard\Event\CommentEvent; use Kanboard\Core\Base; /** @@ -27,9 +26,26 @@ class CommentModel extends Base */ const EVENT_UPDATE = 'comment.update'; const EVENT_CREATE = 'comment.create'; + const EVENT_DELETE = 'comment.delete'; const EVENT_USER_MENTION = 'comment.user.mention'; /** + * Get projectId from commentId + * + * @access public + * @param integer $comment_id + * @return integer + */ + public function getProjectId($comment_id) + { + return $this->db + ->table(self::TABLE) + ->eq(self::TABLE.'.id', $comment_id) + ->join(TaskModel::TABLE, 'id', 'task_id') + ->findOneColumn(TaskModel::TABLE . '.project_id') ?: 0; + } + + /** * Get all comments for a given task * * @access public @@ -114,9 +130,7 @@ class CommentModel extends Base $comment_id = $this->db->table(self::TABLE)->persist($values); if ($comment_id !== false) { - $event = new CommentEvent(array('id' => $comment_id) + $values); - $this->dispatcher->dispatch(self::EVENT_CREATE, $event); - $this->userMentionModel->fireEvents($values['comment'], self::EVENT_USER_MENTION, $event); + $this->queueManager->push($this->commentEventJob->withParams($comment_id, self::EVENT_CREATE)); } return $comment_id; @@ -137,7 +151,7 @@ class CommentModel extends Base ->update(array('comment' => $values['comment'])); if ($result) { - $this->container['dispatcher']->dispatch(self::EVENT_UPDATE, new CommentEvent($values)); + $this->queueManager->push($this->commentEventJob->withParams($values['id'], self::EVENT_UPDATE)); } return $result; @@ -152,6 +166,7 @@ class CommentModel extends Base */ public function remove($comment_id) { + $this->commentEventJob->execute($comment_id, self::EVENT_DELETE); return $this->db->table(self::TABLE)->eq('id', $comment_id)->remove(); } } diff --git a/app/Model/FileModel.php b/app/Model/FileModel.php index 8cdea9a0..98032f9d 100644 --- a/app/Model/FileModel.php +++ b/app/Model/FileModel.php @@ -5,7 +5,6 @@ namespace Kanboard\Model; use Exception; use Kanboard\Core\Base; use Kanboard\Core\Thumbnail; -use Kanboard\Event\FileEvent; use Kanboard\Core\ObjectStorage\ObjectStorageException; /** @@ -44,13 +43,13 @@ abstract class FileModel extends Base abstract protected function getPathPrefix(); /** - * Get event name + * Fire file creation event * * @abstract * @access protected - * @return string + * @param integer $file_id */ - abstract protected function getEventName(); + abstract protected function fireCreationEvent($file_id); /** * Get PicoDb query to get all files @@ -130,16 +129,16 @@ abstract class FileModel extends Base * Create a file entry in the database * * @access public - * @param integer $id Foreign key - * @param string $name Filename - * @param string $path Path on the disk - * @param integer $size File size + * @param integer $foreign_key_id Foreign key + * @param string $name Filename + * @param string $path Path on the disk + * @param integer $size File size * @return bool|integer */ - public function create($id, $name, $path, $size) + public function create($foreign_key_id, $name, $path, $size) { $values = array( - $this->getForeignKey() => $id, + $this->getForeignKey() => $foreign_key_id, 'name' => substr($name, 0, 255), 'path' => $path, 'is_image' => $this->isImage($name) ? 1 : 0, @@ -152,8 +151,7 @@ abstract class FileModel extends Base if ($result) { $file_id = (int) $this->db->getLastId(); - $event = new FileEvent($values + array('file_id' => $file_id)); - $this->dispatcher->dispatch($this->getEventName(), $event); + $this->fireCreationEvent($file_id); return $file_id; } diff --git a/app/Model/GroupModel.php b/app/Model/GroupModel.php index 0a975570..b43423b3 100644 --- a/app/Model/GroupModel.php +++ b/app/Model/GroupModel.php @@ -116,4 +116,23 @@ class GroupModel extends Base { return $this->db->table(self::TABLE)->eq('id', $values['id'])->update($values); } + + /** + * Get groupId from externalGroupId and create the group if not found + * + * @access public + * @param string $name + * @param string $external_id + * @return bool|integer + */ + public function getOrCreateExternalGroupId($name, $external_id) + { + $group_id = $this->db->table(self::TABLE)->eq('external_id', $external_id)->findOneColumn('id'); + + if (empty($group_id)) { + $group_id = $this->create($name, $external_id); + } + + return $group_id; + } } diff --git a/app/Model/NotificationModel.php b/app/Model/NotificationModel.php index 8937b77e..803d4f18 100644 --- a/app/Model/NotificationModel.php +++ b/app/Model/NotificationModel.php @@ -3,9 +3,15 @@ namespace Kanboard\Model; use Kanboard\Core\Base; +use Kanboard\EventBuilder\CommentEventBuilder; +use Kanboard\EventBuilder\EventIteratorBuilder; +use Kanboard\EventBuilder\SubtaskEventBuilder; +use Kanboard\EventBuilder\TaskEventBuilder; +use Kanboard\EventBuilder\TaskFileEventBuilder; +use Kanboard\EventBuilder\TaskLinkEventBuilder; /** - * Notification + * Notification Model * * @package Kanboard\Model * @author Frederic Guillot @@ -16,121 +22,79 @@ class NotificationModel extends Base * Get the event title with author * * @access public - * @param string $event_author - * @param string $event_name - * @param array $event_data + * @param string $eventAuthor + * @param string $eventName + * @param array $eventData * @return string */ - public function getTitleWithAuthor($event_author, $event_name, array $event_data) + public function getTitleWithAuthor($eventAuthor, $eventName, array $eventData) { - switch ($event_name) { - case TaskModel::EVENT_ASSIGNEE_CHANGE: - $assignee = $event_data['task']['assignee_name'] ?: $event_data['task']['assignee_username']; + foreach ($this->getIteratorBuilder() as $builder) { + $title = $builder->buildTitleWithAuthor($eventAuthor, $eventName, $eventData); - if (! empty($assignee)) { - return e('%s change the assignee of the task #%d to %s', $event_author, $event_data['task']['id'], $assignee); - } - - return e('%s remove the assignee of the task %s', $event_author, e('#%d', $event_data['task']['id'])); - case TaskModel::EVENT_UPDATE: - return e('%s updated the task #%d', $event_author, $event_data['task']['id']); - case TaskModel::EVENT_CREATE: - return e('%s created the task #%d', $event_author, $event_data['task']['id']); - case TaskModel::EVENT_CLOSE: - return e('%s closed the task #%d', $event_author, $event_data['task']['id']); - case TaskModel::EVENT_OPEN: - return e('%s open the task #%d', $event_author, $event_data['task']['id']); - case TaskModel::EVENT_MOVE_COLUMN: - return e( - '%s moved the task #%d to the column "%s"', - $event_author, - $event_data['task']['id'], - $event_data['task']['column_title'] - ); - case TaskModel::EVENT_MOVE_POSITION: - return e( - '%s moved the task #%d to the position %d in the column "%s"', - $event_author, - $event_data['task']['id'], - $event_data['task']['position'], - $event_data['task']['column_title'] - ); - case TaskModel::EVENT_MOVE_SWIMLANE: - if ($event_data['task']['swimlane_id'] == 0) { - return e('%s moved the task #%d to the first swimlane', $event_author, $event_data['task']['id']); - } - - return e( - '%s moved the task #%d to the swimlane "%s"', - $event_author, - $event_data['task']['id'], - $event_data['task']['swimlane_name'] - ); - case SubtaskModel::EVENT_UPDATE: - return e('%s updated a subtask for the task #%d', $event_author, $event_data['task']['id']); - case SubtaskModel::EVENT_CREATE: - return e('%s created a subtask for the task #%d', $event_author, $event_data['task']['id']); - case CommentModel::EVENT_UPDATE: - return e('%s updated a comment on the task #%d', $event_author, $event_data['task']['id']); - case CommentModel::EVENT_CREATE: - return e('%s commented on the task #%d', $event_author, $event_data['task']['id']); - case TaskFileModel::EVENT_CREATE: - return e('%s attached a file to the task #%d', $event_author, $event_data['task']['id']); - case TaskModel::EVENT_USER_MENTION: - return e('%s mentioned you in the task #%d', $event_author, $event_data['task']['id']); - case CommentModel::EVENT_USER_MENTION: - return e('%s mentioned you in a comment on the task #%d', $event_author, $event_data['task']['id']); - default: - return e('Notification'); + if ($title !== '') { + return $title; + } } + + return e('Notification'); } /** * Get the event title without author * * @access public - * @param string $event_name - * @param array $event_data + * @param string $eventName + * @param array $eventData * @return string */ - public function getTitleWithoutAuthor($event_name, array $event_data) + public function getTitleWithoutAuthor($eventName, array $eventData) { - switch ($event_name) { - case TaskFileModel::EVENT_CREATE: - return e('New attachment on task #%d: %s', $event_data['file']['task_id'], $event_data['file']['name']); - case CommentModel::EVENT_CREATE: - return e('New comment on task #%d', $event_data['comment']['task_id']); - case CommentModel::EVENT_UPDATE: - return e('Comment updated on task #%d', $event_data['comment']['task_id']); - case SubtaskModel::EVENT_CREATE: - return e('New subtask on task #%d', $event_data['subtask']['task_id']); - case SubtaskModel::EVENT_UPDATE: - return e('Subtask updated on task #%d', $event_data['subtask']['task_id']); - case TaskModel::EVENT_CREATE: - return e('New task #%d: %s', $event_data['task']['id'], $event_data['task']['title']); - case TaskModel::EVENT_UPDATE: - return e('Task updated #%d', $event_data['task']['id']); - case TaskModel::EVENT_CLOSE: - return e('Task #%d closed', $event_data['task']['id']); - case TaskModel::EVENT_OPEN: - return e('Task #%d opened', $event_data['task']['id']); - case TaskModel::EVENT_MOVE_COLUMN: - return e('Column changed for task #%d', $event_data['task']['id']); - case TaskModel::EVENT_MOVE_POSITION: - return e('New position for task #%d', $event_data['task']['id']); - case TaskModel::EVENT_MOVE_SWIMLANE: - return e('Swimlane changed for task #%d', $event_data['task']['id']); - case TaskModel::EVENT_ASSIGNEE_CHANGE: - return e('Assignee changed on task #%d', $event_data['task']['id']); - case TaskModel::EVENT_OVERDUE: - $nb = count($event_data['tasks']); - return $nb > 1 ? e('%d overdue tasks', $nb) : e('Task #%d is overdue', $event_data['tasks'][0]['id']); - case TaskModel::EVENT_USER_MENTION: - return e('You were mentioned in the task #%d', $event_data['task']['id']); - case CommentModel::EVENT_USER_MENTION: - return e('You were mentioned in a comment on the task #%d', $event_data['task']['id']); - default: - return e('Notification'); + foreach ($this->getIteratorBuilder() as $builder) { + $title = $builder->buildTitleWithoutAuthor($eventName, $eventData); + + if ($title !== '') { + return $title; + } } + + return e('Notification'); + } + + /** + * Get task id from event + * + * @access public + * @param string $eventName + * @param array $eventData + * @return integer + */ + public function getTaskIdFromEvent($eventName, array $eventData) + { + if ($eventName === TaskModel::EVENT_OVERDUE) { + return $eventData['tasks'][0]['id']; + } + + return isset($eventData['task']['id']) ? $eventData['task']['id'] : 0; + } + + /** + * Get iterator builder + * + * @access protected + * @return EventIteratorBuilder + */ + protected function getIteratorBuilder() + { + $iterator = new EventIteratorBuilder(); + $iterator + ->withBuilder(TaskEventBuilder::getInstance($this->container)) + ->withBuilder(CommentEventBuilder::getInstance($this->container)) + ->withBuilder(SubtaskEventBuilder::getInstance($this->container)) + ->withBuilder(TaskFileEventBuilder::getInstance($this->container)) + ->withBuilder(TaskLinkEventBuilder::getInstance($this->container)) + ; + + return $iterator; } } diff --git a/app/Model/ProjectDuplicationModel.php b/app/Model/ProjectDuplicationModel.php index b67f8302..d32fa367 100644 --- a/app/Model/ProjectDuplicationModel.php +++ b/app/Model/ProjectDuplicationModel.php @@ -22,7 +22,15 @@ class ProjectDuplicationModel extends Base */ public function getOptionalSelection() { - return array('categoryModel', 'projectPermissionModel', 'actionModel', 'swimlaneModel', 'taskModel', 'projectMetadataModel'); + return array( + 'categoryModel', + 'projectPermissionModel', + 'actionModel', + 'swimlaneModel', + 'tagDuplicationModel', + 'projectMetadataModel', + 'projectTaskDuplicationModel', + ); } /** @@ -33,7 +41,16 @@ class ProjectDuplicationModel extends Base */ public function getPossibleSelection() { - return array('boardModel', 'categoryModel', 'projectPermissionModel', 'actionModel', 'swimlaneModel', 'taskModel', 'projectMetadataModel'); + return array( + 'boardModel', + 'categoryModel', + 'projectPermissionModel', + 'actionModel', + 'swimlaneModel', + 'tagDuplicationModel', + 'projectMetadataModel', + 'projectTaskDuplicationModel', + ); } /** @@ -129,6 +146,9 @@ class ProjectDuplicationModel extends Base 'is_public' => 0, 'is_private' => $private ? 1 : $is_private, 'owner_id' => $owner_id, + 'priority_default' => $project['priority_default'], + 'priority_start' => $project['priority_start'], + 'priority_end' => $project['priority_end'], ); if (! $this->db->table(ProjectModel::TABLE)->save($values)) { @@ -139,7 +159,7 @@ class ProjectDuplicationModel extends Base } /** - * Make sure that the creator of the duplicated project is alsp owner + * Make sure that the creator of the duplicated project is also owner * * @access private * @param integer $dst_project_id diff --git a/app/Model/ProjectFileModel.php b/app/Model/ProjectFileModel.php index b464bb2a..4de4d66d 100644 --- a/app/Model/ProjectFileModel.php +++ b/app/Model/ProjectFileModel.php @@ -61,14 +61,13 @@ class ProjectFileModel extends FileModel } /** - * Get event name + * Fire file creation event * - * @abstract * @access protected - * @return string + * @param integer $file_id */ - protected function getEventName() + protected function fireCreationEvent($file_id) { - return self::EVENT_CREATE; + $this->queueManager->push($this->projectFileEventJob->withParams($file_id, self::EVENT_CREATE)); } } diff --git a/app/Model/ProjectModel.php b/app/Model/ProjectModel.php index 7382537e..d2019b72 100644 --- a/app/Model/ProjectModel.php +++ b/app/Model/ProjectModel.php @@ -246,19 +246,6 @@ class ProjectModel extends Base } /** - * Get Priority range from a project - * - * @access public - * @param array $project - * @return array - */ - public function getPriorities(array $project) - { - $range = range($project['priority_start'], $project['priority_end']); - return array_combine($range, $range); - } - - /** * Gather some task metrics for a given project * * @access public @@ -331,7 +318,7 @@ class ProjectModel extends Base public function getQueryColumnStats(array $project_ids) { if (empty($project_ids)) { - return $this->db->table(ProjectModel::TABLE)->limit(0); + return $this->db->table(ProjectModel::TABLE)->eq(ProjectModel::TABLE.'.id', 0); } return $this->db diff --git a/app/Model/ProjectPermissionModel.php b/app/Model/ProjectPermissionModel.php index a7c1857c..4882343d 100644 --- a/app/Model/ProjectPermissionModel.php +++ b/app/Model/ProjectPermissionModel.php @@ -152,6 +152,18 @@ class ProjectPermissionModel extends Base } /** + * Get all project ids by user + * + * @access public + * @param integer $user_id + * @return array + */ + public function getProjectIds($user_id) + { + return array_keys($this->projectUserRoleModel->getProjectsByUser($user_id)); + } + + /** * Copy permissions to another project * * @param integer $project_src_id Project Template diff --git a/app/Model/ProjectTaskDuplicationModel.php b/app/Model/ProjectTaskDuplicationModel.php new file mode 100644 index 00000000..5d2e1322 --- /dev/null +++ b/app/Model/ProjectTaskDuplicationModel.php @@ -0,0 +1,35 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Project Task Duplication Model + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class ProjectTaskDuplicationModel extends Base +{ + /** + * Duplicate all tasks to another project + * + * @access public + * @param integer $src_project_id + * @param integer $dst_project_id + * @return boolean + */ + public function duplicate($src_project_id, $dst_project_id) + { + $task_ids = $this->taskFinderModel->getAllIds($src_project_id, array(TaskModel::STATUS_OPEN, TaskModel::STATUS_CLOSED)); + + foreach ($task_ids as $task_id) { + if (! $this->taskProjectDuplicationModel->duplicateToProject($task_id, $dst_project_id)) { + return false; + } + } + + return true; + } +} diff --git a/app/Model/ProjectTaskPriorityModel.php b/app/Model/ProjectTaskPriorityModel.php new file mode 100644 index 00000000..c1a0257a --- /dev/null +++ b/app/Model/ProjectTaskPriorityModel.php @@ -0,0 +1,74 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Project Task Priority Model + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class ProjectTaskPriorityModel extends Base +{ + /** + * Get Priority range from a project + * + * @access public + * @param array $project + * @return array + */ + public function getPriorities(array $project) + { + $range = range($project['priority_start'], $project['priority_end']); + return array_combine($range, $range); + } + + /** + * Get task priority settings + * + * @access public + * @param int $project_id + * @return array|null + */ + public function getPrioritySettings($project_id) + { + return $this->db + ->table(ProjectModel::TABLE) + ->columns('priority_default', 'priority_start', 'priority_end') + ->eq('id', $project_id) + ->findOne(); + } + + /** + * Get default task priority + * + * @access public + * @param int $project_id + * @return int + */ + public function getDefaultPriority($project_id) + { + return $this->db->table(ProjectModel::TABLE)->eq('id', $project_id)->findOneColumn('priority_default') ?: 0; + } + + /** + * Get priority for a destination project + * + * @access public + * @param integer $dst_project_id + * @param integer $priority + * @return integer + */ + public function getPriorityForProject($dst_project_id, $priority) + { + $settings = $this->getPrioritySettings($dst_project_id); + + if ($priority >= $settings['priority_start'] && $priority <= $settings['priority_end']) { + return $priority; + } + + return $settings['priority_default']; + } +} diff --git a/app/Model/SubtaskModel.php b/app/Model/SubtaskModel.php index 019064ad..608ffce7 100644 --- a/app/Model/SubtaskModel.php +++ b/app/Model/SubtaskModel.php @@ -4,7 +4,6 @@ namespace Kanboard\Model; use PicoDb\Database; use Kanboard\Core\Base; -use Kanboard\Event\SubtaskEvent; /** * Subtask Model @@ -22,25 +21,13 @@ class SubtaskModel extends Base const TABLE = 'subtasks'; /** - * Task "done" status - * - * @var integer - */ - const STATUS_DONE = 2; - - /** - * Task "in progress" status - * - * @var integer - */ - const STATUS_INPROGRESS = 1; - - /** - * Task "todo" status + * Subtask status * * @var integer */ const STATUS_TODO = 0; + const STATUS_INPROGRESS = 1; + const STATUS_DONE = 2; /** * Events @@ -52,6 +39,22 @@ class SubtaskModel extends Base const EVENT_DELETE = 'subtask.delete'; /** + * Get projectId from subtaskId + * + * @access public + * @param integer $subtask_id + * @return integer + */ + public function getProjectId($subtask_id) + { + return $this->db + ->table(self::TABLE) + ->eq(self::TABLE.'.id', $subtask_id) + ->join(TaskModel::TABLE, 'id', 'task_id') + ->findOneColumn(TaskModel::TABLE . '.project_id') ?: 0; + } + + /** * Get available status * * @access public @@ -67,26 +70,6 @@ class SubtaskModel extends Base } /** - * Add subtask status status to the resultset - * - * @access public - * @param array $subtasks Subtasks - * @return array - */ - public function addStatusName(array $subtasks) - { - $status = $this->getStatusList(); - - foreach ($subtasks as &$subtask) { - $subtask['status_name'] = $status[$subtask['status']]; - $subtask['timer_start_date'] = isset($subtask['timer_start_date']) ? $subtask['timer_start_date'] : 0; - $subtask['is_timer_started'] = ! empty($subtask['timer_start_date']); - } - - return $subtasks; - } - - /** * Get the query to fetch subtasks assigned to a user * * @access public @@ -162,35 +145,6 @@ class SubtaskModel extends Base } /** - * Prepare data before insert/update - * - * @access public - * @param array $values Form values - */ - public function prepare(array &$values) - { - $this->helper->model->removeFields($values, array('another_subtask')); - $this->helper->model->resetFields($values, array('time_estimated', 'time_spent')); - } - - /** - * Prepare data before insert - * - * @access public - * @param array $values Form values - */ - public function prepareCreation(array &$values) - { - $this->prepare($values); - - $values['position'] = $this->getLastPosition($values['task_id']) + 1; - $values['status'] = isset($values['status']) ? $values['status'] : self::STATUS_TODO; - $values['time_estimated'] = isset($values['time_estimated']) ? $values['time_estimated'] : 0; - $values['time_spent'] = isset($values['time_spent']) ? $values['time_spent'] : 0; - $values['user_id'] = isset($values['user_id']) ? $values['user_id'] : 0; - } - - /** * Get the position of the last column for a given project * * @access public @@ -219,10 +173,8 @@ class SubtaskModel extends Base $subtask_id = $this->db->table(self::TABLE)->persist($values); if ($subtask_id !== false) { - $this->container['dispatcher']->dispatch( - self::EVENT_CREATE, - new SubtaskEvent(array('id' => $subtask_id) + $values) - ); + $this->subtaskTimeTrackingModel->updateTaskTimeTracking($values['task_id']); + $this->queueManager->push($this->subtaskEventJob->withParams($subtask_id, self::EVENT_CREATE)); } return $subtask_id; @@ -232,124 +184,24 @@ class SubtaskModel extends Base * Update * * @access public - * @param array $values Form values - * @param bool $fire_events If true, will be called an event + * @param array $values + * @param bool $fire_event * @return bool */ - public function update(array $values, $fire_events = true) + public function update(array $values, $fire_event = true) { $this->prepare($values); - $subtask = $this->getById($values['id']); $result = $this->db->table(self::TABLE)->eq('id', $values['id'])->save($values); - if ($result && $fire_events) { - $event = $subtask; - $event['changes'] = array_diff_assoc($values, $subtask); - $this->container['dispatcher']->dispatch(self::EVENT_UPDATE, new SubtaskEvent($event)); - } - - return $result; - } - - /** - * Close all subtasks of a task - * - * @access public - * @param integer $task_id - * @return boolean - */ - public function closeAll($task_id) - { - return $this->db->table(self::TABLE)->eq('task_id', $task_id)->update(array('status' => self::STATUS_DONE)); - } - - /** - * Save subtask position - * - * @access public - * @param integer $task_id - * @param integer $subtask_id - * @param integer $position - * @return boolean - */ - public function changePosition($task_id, $subtask_id, $position) - { - if ($position < 1 || $position > $this->db->table(self::TABLE)->eq('task_id', $task_id)->count()) { - return false; - } - - $subtask_ids = $this->db->table(self::TABLE)->eq('task_id', $task_id)->neq('id', $subtask_id)->asc('position')->findAllByColumn('id'); - $offset = 1; - $results = array(); + if ($result) { + $this->subtaskTimeTrackingModel->updateTaskTimeTracking($values['task_id']); - foreach ($subtask_ids as $current_subtask_id) { - if ($offset == $position) { - $offset++; + if ($fire_event) { + $this->queueManager->push($this->subtaskEventJob->withParams($values['id'], self::EVENT_UPDATE, $values)); } - - $results[] = $this->db->table(self::TABLE)->eq('id', $current_subtask_id)->update(array('position' => $offset)); - $offset++; - } - - $results[] = $this->db->table(self::TABLE)->eq('id', $subtask_id)->update(array('position' => $position)); - - return !in_array(false, $results, true); - } - - /** - * Change the status of subtask - * - * @access public - * @param integer $subtask_id - * @return boolean|integer - */ - public function toggleStatus($subtask_id) - { - $subtask = $this->getById($subtask_id); - $status = ($subtask['status'] + 1) % 3; - - $values = array( - 'id' => $subtask['id'], - 'status' => $status, - 'task_id' => $subtask['task_id'], - ); - - if (empty($subtask['user_id']) && $this->userSession->isLogged()) { - $values['user_id'] = $this->userSession->getId(); } - return $this->update($values) ? $status : false; - } - - /** - * Get the subtask in progress for this user - * - * @access public - * @param integer $user_id - * @return array - */ - public function getSubtaskInProgress($user_id) - { - return $this->db->table(self::TABLE) - ->eq('status', self::STATUS_INPROGRESS) - ->eq('user_id', $user_id) - ->findOne(); - } - - /** - * Return true if the user have a subtask in progress - * - * @access public - * @param integer $user_id - * @return boolean - */ - public function hasSubtaskInProgress($user_id) - { - return $this->configModel->get('subtask_restriction') == 1 && - $this->db->table(self::TABLE) - ->eq('status', self::STATUS_INPROGRESS) - ->eq('user_id', $user_id) - ->exists(); + return $result; } /** @@ -361,14 +213,8 @@ class SubtaskModel extends Base */ public function remove($subtask_id) { - $subtask = $this->getById($subtask_id); - $result = $this->db->table(self::TABLE)->eq('id', $subtask_id)->remove(); - - if ($result) { - $this->container['dispatcher']->dispatch(self::EVENT_DELETE, new SubtaskEvent($subtask)); - } - - return $result; + $this->subtaskEventJob->execute($subtask_id, self::EVENT_DELETE); + return $this->db->table(self::TABLE)->eq('id', $subtask_id)->remove(); } /** @@ -400,29 +246,51 @@ class SubtaskModel extends Base } /** - * Convert a subtask to a task + * Prepare data before insert/update * - * @access public - * @param integer $project_id - * @param integer $subtask_id - * @return integer + * @access protected + * @param array $values Form values */ - public function convertToTask($project_id, $subtask_id) + protected function prepare(array &$values) { - $subtask = $this->getById($subtask_id); + $this->helper->model->removeFields($values, array('another_subtask')); + $this->helper->model->resetFields($values, array('time_estimated', 'time_spent')); + } - $task_id = $this->taskCreationModel->create(array( - 'project_id' => $project_id, - 'title' => $subtask['title'], - 'time_estimated' => $subtask['time_estimated'], - 'time_spent' => $subtask['time_spent'], - 'owner_id' => $subtask['user_id'], - )); + /** + * Prepare data before insert + * + * @access protected + * @param array $values Form values + */ + protected function prepareCreation(array &$values) + { + $this->prepare($values); - if ($task_id !== false) { - $this->remove($subtask_id); + $values['position'] = $this->getLastPosition($values['task_id']) + 1; + $values['status'] = isset($values['status']) ? $values['status'] : self::STATUS_TODO; + $values['time_estimated'] = isset($values['time_estimated']) ? $values['time_estimated'] : 0; + $values['time_spent'] = isset($values['time_spent']) ? $values['time_spent'] : 0; + $values['user_id'] = isset($values['user_id']) ? $values['user_id'] : 0; + } + + /** + * Add subtask status status to the resultset + * + * @access public + * @param array $subtasks Subtasks + * @return array + */ + public function addStatusName(array $subtasks) + { + $status = $this->getStatusList(); + + foreach ($subtasks as &$subtask) { + $subtask['status_name'] = $status[$subtask['status']]; + $subtask['timer_start_date'] = isset($subtask['timer_start_date']) ? $subtask['timer_start_date'] : 0; + $subtask['is_timer_started'] = ! empty($subtask['timer_start_date']); } - return $task_id; + return $subtasks; } } diff --git a/app/Model/SubtaskPositionModel.php b/app/Model/SubtaskPositionModel.php new file mode 100644 index 00000000..3c26465d --- /dev/null +++ b/app/Model/SubtaskPositionModel.php @@ -0,0 +1,47 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Class SubtaskPositionModel + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class SubtaskPositionModel extends Base +{ + /** + * Change subtask position + * + * @access public + * @param integer $task_id + * @param integer $subtask_id + * @param integer $position + * @return boolean + */ + public function changePosition($task_id, $subtask_id, $position) + { + if ($position < 1 || $position > $this->db->table(SubtaskModel::TABLE)->eq('task_id', $task_id)->count()) { + return false; + } + + $subtask_ids = $this->db->table(SubtaskModel::TABLE)->eq('task_id', $task_id)->neq('id', $subtask_id)->asc('position')->findAllByColumn('id'); + $offset = 1; + $results = array(); + + foreach ($subtask_ids as $current_subtask_id) { + if ($offset == $position) { + $offset++; + } + + $results[] = $this->db->table(SubtaskModel::TABLE)->eq('id', $current_subtask_id)->update(array('position' => $offset)); + $offset++; + } + + $results[] = $this->db->table(SubtaskModel::TABLE)->eq('id', $subtask_id)->update(array('position' => $position)); + + return !in_array(false, $results, true); + } +} diff --git a/app/Model/SubtaskStatusModel.php b/app/Model/SubtaskStatusModel.php new file mode 100644 index 00000000..c99d6055 --- /dev/null +++ b/app/Model/SubtaskStatusModel.php @@ -0,0 +1,88 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Class SubtaskStatusModel + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class SubtaskStatusModel extends Base +{ + /** + * Get the subtask in progress for this user + * + * @access public + * @param integer $user_id + * @return array + */ + public function getSubtaskInProgress($user_id) + { + return $this->db->table(SubtaskModel::TABLE) + ->eq('status', SubtaskModel::STATUS_INPROGRESS) + ->eq('user_id', $user_id) + ->findOne(); + } + + /** + * Return true if the user have a subtask in progress + * + * @access public + * @param integer $user_id + * @return boolean + */ + public function hasSubtaskInProgress($user_id) + { + return $this->configModel->get('subtask_restriction') == 1 && + $this->db->table(SubtaskModel::TABLE) + ->eq('status', SubtaskModel::STATUS_INPROGRESS) + ->eq('user_id', $user_id) + ->exists(); + } + + /** + * Change the status of subtask + * + * @access public + * @param integer $subtask_id + * @return boolean|integer + */ + public function toggleStatus($subtask_id) + { + $subtask = $this->subtaskModel->getById($subtask_id); + $status = ($subtask['status'] + 1) % 3; + + $values = array( + 'id' => $subtask['id'], + 'status' => $status, + 'task_id' => $subtask['task_id'], + ); + + if (empty($subtask['user_id']) && $this->userSession->isLogged()) { + $values['user_id'] = $this->userSession->getId(); + $subtask['user_id'] = $values['user_id']; + } + + $this->subtaskTimeTrackingModel->toggleTimer($subtask_id, $subtask['user_id'], $status); + + return $this->subtaskModel->update($values) ? $status : false; + } + + /** + * Close all subtasks of a task + * + * @access public + * @param integer $task_id + * @return boolean + */ + public function closeAll($task_id) + { + return $this->db + ->table(SubtaskModel::TABLE) + ->eq('task_id', $task_id) + ->update(array('status' => SubtaskModel::STATUS_DONE)); + } +} diff --git a/app/Model/SubtaskTaskConversionModel.php b/app/Model/SubtaskTaskConversionModel.php new file mode 100644 index 00000000..8bf83d76 --- /dev/null +++ b/app/Model/SubtaskTaskConversionModel.php @@ -0,0 +1,41 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Class SubtaskTaskConversionModel + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class SubtaskTaskConversionModel extends Base +{ + /** + * Convert a subtask to a task + * + * @access public + * @param integer $project_id + * @param integer $subtask_id + * @return integer + */ + public function convertToTask($project_id, $subtask_id) + { + $subtask = $this->subtaskModel->getById($subtask_id); + + $task_id = $this->taskCreationModel->create(array( + 'project_id' => $project_id, + 'title' => $subtask['title'], + 'time_estimated' => $subtask['time_estimated'], + 'time_spent' => $subtask['time_spent'], + 'owner_id' => $subtask['user_id'], + )); + + if ($task_id !== false) { + $this->subtaskModel->remove($subtask_id); + } + + return $task_id; + } +} diff --git a/app/Model/SubtaskTimeTrackingModel.php b/app/Model/SubtaskTimeTrackingModel.php index 062e594a..3b1b97e4 100644 --- a/app/Model/SubtaskTimeTrackingModel.php +++ b/app/Model/SubtaskTimeTrackingModel.php @@ -160,6 +160,28 @@ class SubtaskTimeTrackingModel extends Base } /** + * Start or stop timer according to subtask status + * + * @access public + * @param integer $subtask_id + * @param integer $user_id + * @param integer $status + * @return boolean + */ + public function toggleTimer($subtask_id, $user_id, $status) + { + if ($this->configModel->get('subtask_time_tracking') == 1) { + if ($status == SubtaskModel::STATUS_INPROGRESS) { + return $this->subtaskTimeTrackingModel->logStartTime($subtask_id, $user_id); + } elseif ($status == SubtaskModel::STATUS_DONE) { + return $this->subtaskTimeTrackingModel->logEndTime($subtask_id, $user_id); + } + } + + return false; + } + + /** * Log start time * * @access public @@ -252,7 +274,6 @@ class SubtaskTimeTrackingModel extends Base { $subtask = $this->subtaskModel->getById($subtask_id); - // Fire the event subtask.update return $this->subtaskModel->update(array( 'id' => $subtask['id'], 'time_spent' => $subtask['time_spent'] + $time_spent, diff --git a/app/Model/SwimlaneModel.php b/app/Model/SwimlaneModel.php index 35e39879..f20bfa2f 100644 --- a/app/Model/SwimlaneModel.php +++ b/app/Model/SwimlaneModel.php @@ -94,15 +94,17 @@ class SwimlaneModel extends Base * * @access public * @param integer $project_id - * @return array + * @return array|null */ public function getFirstActiveSwimlane($project_id) { - return $this->db->table(self::TABLE) - ->eq('is_active', self::ACTIVE) - ->eq('project_id', $project_id) - ->orderBy('position', 'asc') - ->findOne(); + $swimlanes = $this->getSwimlanes($project_id); + + if (empty($swimlanes)) { + return null; + } + + return $swimlanes[0]; } /** @@ -184,18 +186,18 @@ class SwimlaneModel extends Base ->orderBy('position', 'asc') ->findAll(); - $default_swimlane = $this->db + $defaultSwimlane = $this->db ->table(ProjectModel::TABLE) ->eq('id', $project_id) ->eq('show_default_swimlane', 1) ->findOneColumn('default_swimlane'); - if ($default_swimlane) { - if ($default_swimlane === 'Default swimlane') { - $default_swimlane = t($default_swimlane); + if ($defaultSwimlane) { + if ($defaultSwimlane === 'Default swimlane') { + $defaultSwimlane = t($defaultSwimlane); } - array_unshift($swimlanes, array('id' => 0, 'name' => $default_swimlane)); + array_unshift($swimlanes, array('id' => 0, 'name' => $defaultSwimlane)); } return $swimlanes; diff --git a/app/Model/TagDuplicationModel.php b/app/Model/TagDuplicationModel.php new file mode 100644 index 00000000..fb0d8170 --- /dev/null +++ b/app/Model/TagDuplicationModel.php @@ -0,0 +1,87 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Tag Duplication + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class TagDuplicationModel extends Base +{ + /** + * Duplicate project tags to another project + * + * @access public + * @param integer $src_project_id + * @param integer $dst_project_id + * @return bool + */ + public function duplicate($src_project_id, $dst_project_id) + { + $tags = $this->tagModel->getAllByProject($src_project_id); + $results = array(); + + foreach ($tags as $tag) { + $results[] = $this->tagModel->create($dst_project_id, $tag['name']); + } + + return ! in_array(false, $results, true); + } + + /** + * Link tags to the new tasks + * + * @access public + * @param integer $src_task_id + * @param integer $dst_task_id + * @param integer $dst_project_id + */ + public function duplicateTaskTagsToAnotherProject($src_task_id, $dst_task_id, $dst_project_id) + { + $tags = $this->taskTagModel->getTagsByTask($src_task_id); + + foreach ($tags as $tag) { + $tag_id = $this->tagModel->getIdByName($dst_project_id, $tag['name']); + + if ($tag_id) { + $this->taskTagModel->associateTag($dst_task_id, $tag_id); + } + } + } + + /** + * Duplicate tags to the new task + * + * @access public + * @param integer $src_task_id + * @param integer $dst_task_id + */ + public function duplicateTaskTags($src_task_id, $dst_task_id) + { + $tags = $this->taskTagModel->getTagsByTask($src_task_id); + + foreach ($tags as $tag) { + $this->taskTagModel->associateTag($dst_task_id, $tag['id']); + } + } + + /** + * Remove tags that are not available in destination project + * + * @access public + * @param integer $task_id + * @param integer $dst_project_id + */ + public function syncTaskTagsToAnotherProject($task_id, $dst_project_id) + { + $tag_ids = $this->taskTagModel->getTagIdsByTaskNotAvailableInProject($task_id, $dst_project_id); + + foreach ($tag_ids as $tag_id) { + $this->taskTagModel->dissociateTag($task_id, $tag_id); + } + } +} diff --git a/app/Model/TagModel.php b/app/Model/TagModel.php new file mode 100644 index 00000000..e85c5a87 --- /dev/null +++ b/app/Model/TagModel.php @@ -0,0 +1,180 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Class TagModel + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class TagModel extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'tags'; + + /** + * Get all tags + * + * @access public + * @return array + */ + public function getAll() + { + return $this->db->table(self::TABLE)->asc('name')->findAll(); + } + + /** + * Get all tags by project + * + * @access public + * @param integer $project_id + * @return array + */ + public function getAllByProject($project_id) + { + return $this->db->table(self::TABLE)->eq('project_id', $project_id)->asc('name')->findAll(); + } + + /** + * Get assignable tags for a project + * + * @access public + * @param integer $project_id + * @return array + */ + public function getAssignableList($project_id) + { + return $this->db->hashtable(self::TABLE) + ->beginOr() + ->eq('project_id', $project_id) + ->eq('project_id', 0) + ->closeOr() + ->asc('name') + ->getAll('id', 'name'); + } + + /** + * Get one tag + * + * @access public + * @param integer $tag_id + * @return array|null + */ + public function getById($tag_id) + { + return $this->db->table(self::TABLE)->eq('id', $tag_id)->findOne(); + } + + /** + * Get tag id from tag name + * + * @access public + * @param int $project_id + * @param string $tag + * @return integer + */ + public function getIdByName($project_id, $tag) + { + return $this->db + ->table(self::TABLE) + ->beginOr() + ->eq('project_id', 0) + ->eq('project_id', $project_id) + ->closeOr() + ->ilike('name', $tag) + ->asc('project_id') + ->findOneColumn('id'); + } + + /** + * Return true if the tag exists + * + * @access public + * @param integer $project_id + * @param string $tag + * @param integer $tag_id + * @return boolean + */ + public function exists($project_id, $tag, $tag_id = 0) + { + return $this->db + ->table(self::TABLE) + ->neq('id', $tag_id) + ->beginOr() + ->eq('project_id', 0) + ->eq('project_id', $project_id) + ->closeOr() + ->ilike('name', $tag) + ->asc('project_id') + ->exists(); + } + + /** + * Return tag id and create a new tag if necessary + * + * @access public + * @param int $project_id + * @param string $tag + * @return bool|int + */ + public function findOrCreateTag($project_id, $tag) + { + $tag_id = $this->getIdByName($project_id, $tag); + + if (empty($tag_id)) { + $tag_id = $this->create($project_id, $tag); + } + + return $tag_id; + } + + /** + * Add a new tag + * + * @access public + * @param int $project_id + * @param string $tag + * @return bool|int + */ + public function create($project_id, $tag) + { + return $this->db->table(self::TABLE)->persist(array( + 'project_id' => $project_id, + 'name' => $tag, + )); + } + + /** + * Update a tag + * + * @access public + * @param integer $tag_id + * @param string $tag + * @return bool + */ + public function update($tag_id, $tag) + { + return $this->db->table(self::TABLE)->eq('id', $tag_id)->update(array( + 'name' => $tag, + )); + } + + /** + * Remove a tag + * + * @access public + * @param integer $tag_id + * @return bool + */ + public function remove($tag_id) + { + return $this->db->table(self::TABLE)->eq('id', $tag_id)->remove(); + } +} diff --git a/app/Model/TaskCreationModel.php b/app/Model/TaskCreationModel.php index 3800f831..b9b07d5e 100644 --- a/app/Model/TaskCreationModel.php +++ b/app/Model/TaskCreationModel.php @@ -3,7 +3,6 @@ namespace Kanboard\Model; use Kanboard\Core\Base; -use Kanboard\Event\TaskEvent; /** * Task Creation @@ -22,11 +21,13 @@ class TaskCreationModel extends Base */ public function create(array $values) { - if (! $this->projectModel->exists($values['project_id'])) { - return 0; - } - $position = empty($values['position']) ? 0 : $values['position']; + $tags = array(); + + if (isset($values['tags'])) { + $tags = $values['tags']; + unset($values['tags']); + } $this->prepare($values); $task_id = $this->db->table(TaskModel::TABLE)->persist($values); @@ -36,7 +37,14 @@ class TaskCreationModel extends Base $this->taskPositionModel->movePosition($values['project_id'], $task_id, $values['column_id'], $position, $values['swimlane_id'], false); } - $this->fireEvents($task_id, $values); + if (! empty($tags)) { + $this->taskTagModel->save($values['project_id'], $task_id, $tags); + } + + $this->queueManager->push($this->taskEventJob->withParams( + $task_id, + array(TaskModel::EVENT_CREATE_UPDATE, TaskModel::EVENT_CREATE) + )); } return (int) $task_id; @@ -45,16 +53,16 @@ class TaskCreationModel extends Base /** * Prepare data * - * @access public + * @access protected * @param array $values Form values */ - public function prepare(array &$values) + protected function prepare(array &$values) { $values = $this->dateParser->convert($values, array('date_due')); $values = $this->dateParser->convert($values, array('date_started'), true); $this->helper->model->removeFields($values, array('another_task')); - $this->helper->model->resetFields($values, array('date_started', 'creator_id', 'owner_id', 'swimlane_id', 'date_due', 'score', 'category_id', 'time_estimated')); + $this->helper->model->resetFields($values, array('creator_id', 'owner_id', 'swimlane_id', 'date_due', 'date_started', 'score', 'category_id', 'time_estimated', 'time_spent')); if (empty($values['column_id'])) { $values['column_id'] = $this->columnModel->getFirstColumnId($values['project_id']); @@ -77,27 +85,7 @@ class TaskCreationModel extends Base $values['date_modification'] = $values['date_creation']; $values['date_moved'] = $values['date_creation']; $values['position'] = $this->taskFinderModel->countByColumnAndSwimlaneId($values['project_id'], $values['column_id'], $values['swimlane_id']) + 1; - } - /** - * Fire events - * - * @access private - * @param integer $task_id Task id - * @param array $values Form values - */ - private function fireEvents($task_id, array $values) - { - $event = new TaskEvent(array('task_id' => $task_id) + $values); - - $this->logger->debug('Event fired: '.TaskModel::EVENT_CREATE_UPDATE); - $this->logger->debug('Event fired: '.TaskModel::EVENT_CREATE); - - $this->dispatcher->dispatch(TaskModel::EVENT_CREATE_UPDATE, $event); - $this->dispatcher->dispatch(TaskModel::EVENT_CREATE, $event); - - if (! empty($values['description'])) { - $this->userMentionModel->fireEvents($values['description'], TaskModel::EVENT_USER_MENTION, $event); - } + $this->hook->reference('model:task:creation:prepare', $values); } } diff --git a/app/Model/TaskDuplicationModel.php b/app/Model/TaskDuplicationModel.php index 9a4613e2..c9079653 100644 --- a/app/Model/TaskDuplicationModel.php +++ b/app/Model/TaskDuplicationModel.php @@ -2,10 +2,7 @@ namespace Kanboard\Model; -use DateTime; -use DateInterval; use Kanboard\Core\Base; -use Kanboard\Event\TaskEvent; /** * Task Duplication @@ -18,10 +15,10 @@ class TaskDuplicationModel extends Base /** * Fields to copy when duplicating a task * - * @access private - * @var array + * @access protected + * @var string[] */ - private $fields_to_duplicate = array( + protected $fieldsToDuplicate = array( 'title', 'description', 'date_due', @@ -30,6 +27,7 @@ class TaskDuplicationModel extends Base 'column_id', 'owner_id', 'score', + 'priority', 'category_id', 'time_estimated', 'swimlane_id', @@ -49,106 +47,13 @@ class TaskDuplicationModel extends Base */ public function duplicate($task_id) { - return $this->save($task_id, $this->copyFields($task_id)); - } + $new_task_id = $this->save($task_id, $this->copyFields($task_id)); - /** - * Duplicate recurring task - * - * @access public - * @param integer $task_id Task id - * @return boolean|integer Recurrence task id - */ - public function duplicateRecurringTask($task_id) - { - $values = $this->copyFields($task_id); - - if ($values['recurrence_status'] == TaskModel::RECURRING_STATUS_PENDING) { - $values['recurrence_parent'] = $task_id; - $values['column_id'] = $this->columnModel->getFirstColumnId($values['project_id']); - $this->calculateRecurringTaskDueDate($values); - - $recurring_task_id = $this->save($task_id, $values); - - if ($recurring_task_id > 0) { - $parent_update = $this->db - ->table(TaskModel::TABLE) - ->eq('id', $task_id) - ->update(array( - 'recurrence_status' => TaskModel::RECURRING_STATUS_PROCESSED, - 'recurrence_child' => $recurring_task_id, - )); - - if ($parent_update) { - return $recurring_task_id; - } - } + if ($new_task_id !== false) { + $this->tagDuplicationModel->duplicateTaskTags($task_id, $new_task_id); } - return false; - } - - /** - * Duplicate a task to another project - * - * @access public - * @param integer $task_id - * @param integer $project_id - * @param integer $swimlane_id - * @param integer $column_id - * @param integer $category_id - * @param integer $owner_id - * @return boolean|integer - */ - public function duplicateToProject($task_id, $project_id, $swimlane_id = null, $column_id = null, $category_id = null, $owner_id = null) - { - $values = $this->copyFields($task_id); - $values['project_id'] = $project_id; - $values['column_id'] = $column_id !== null ? $column_id : $values['column_id']; - $values['swimlane_id'] = $swimlane_id !== null ? $swimlane_id : $values['swimlane_id']; - $values['category_id'] = $category_id !== null ? $category_id : $values['category_id']; - $values['owner_id'] = $owner_id !== null ? $owner_id : $values['owner_id']; - - $this->checkDestinationProjectValues($values); - - return $this->save($task_id, $values); - } - - /** - * Move a task to another project - * - * @access public - * @param integer $task_id - * @param integer $project_id - * @param integer $swimlane_id - * @param integer $column_id - * @param integer $category_id - * @param integer $owner_id - * @return boolean - */ - public function moveToProject($task_id, $project_id, $swimlane_id = null, $column_id = null, $category_id = null, $owner_id = null) - { - $task = $this->taskFinderModel->getById($task_id); - - $values = array(); - $values['is_active'] = 1; - $values['project_id'] = $project_id; - $values['column_id'] = $column_id !== null ? $column_id : $task['column_id']; - $values['position'] = $this->taskFinderModel->countByColumnId($project_id, $values['column_id']) + 1; - $values['swimlane_id'] = $swimlane_id !== null ? $swimlane_id : $task['swimlane_id']; - $values['category_id'] = $category_id !== null ? $category_id : $task['category_id']; - $values['owner_id'] = $owner_id !== null ? $owner_id : $task['owner_id']; - - $this->checkDestinationProjectValues($values); - - if ($this->db->table(TaskModel::TABLE)->eq('id', $task['id'])->update($values)) { - $this->container['dispatcher']->dispatch( - TaskModel::EVENT_MOVE_PROJECT, - new TaskEvent(array_merge($task, $values, array('task_id' => $task['id']))) - ); - } - - return true; + return $new_task_id; } /** @@ -191,58 +96,28 @@ class TaskDuplicationModel extends Base $values['column_id'] = $values['column_id'] ?: $this->columnModel->getFirstColumnId($values['project_id']); } - return $values; - } + // Check if priority exists for destination project + $values['priority'] = $this->projectTaskPriorityModel->getPriorityForProject( + $values['project_id'], + empty($values['priority']) ? 0 : $values['priority'] + ); - /** - * Calculate new due date for new recurrence task - * - * @access public - * @param array $values Task fields - */ - public function calculateRecurringTaskDueDate(array &$values) - { - if (! empty($values['date_due']) && $values['recurrence_factor'] != 0) { - if ($values['recurrence_basedate'] == TaskModel::RECURRING_BASEDATE_TRIGGERDATE) { - $values['date_due'] = time(); - } - - $factor = abs($values['recurrence_factor']); - $subtract = $values['recurrence_factor'] < 0; - - switch ($values['recurrence_timeframe']) { - case TaskModel::RECURRING_TIMEFRAME_MONTHS: - $interval = 'P' . $factor . 'M'; - break; - case TaskModel::RECURRING_TIMEFRAME_YEARS: - $interval = 'P' . $factor . 'Y'; - break; - default: - $interval = 'P' . $factor . 'D'; - } - - $date_due = new DateTime(); - $date_due->setTimestamp($values['date_due']); - - $subtract ? $date_due->sub(new DateInterval($interval)) : $date_due->add(new DateInterval($interval)); - - $values['date_due'] = $date_due->getTimestamp(); - } + return $values; } /** * Duplicate fields for the new task * - * @access private + * @access protected * @param integer $task_id Task id * @return array */ - private function copyFields($task_id) + protected function copyFields($task_id) { $task = $this->taskFinderModel->getById($task_id); $values = array(); - foreach ($this->fields_to_duplicate as $field) { + foreach ($this->fieldsToDuplicate as $field) { $values[$field] = $task[$field]; } @@ -252,16 +127,16 @@ class TaskDuplicationModel extends Base /** * Create the new task and duplicate subtasks * - * @access private + * @access protected * @param integer $task_id Task id * @param array $values Form values * @return boolean|integer */ - private function save($task_id, array $values) + protected function save($task_id, array $values) { $new_task_id = $this->taskCreationModel->create($values); - if ($new_task_id) { + if ($new_task_id !== false) { $this->subtaskModel->duplicate($task_id, $new_task_id); } diff --git a/app/Model/TaskFileModel.php b/app/Model/TaskFileModel.php index 24c1ad4b..0163da28 100644 --- a/app/Model/TaskFileModel.php +++ b/app/Model/TaskFileModel.php @@ -61,15 +61,19 @@ class TaskFileModel extends FileModel } /** - * Get event name + * Get projectId from fileId * - * @abstract - * @access protected - * @return string + * @access public + * @param integer $file_id + * @return integer */ - protected function getEventName() + public function getProjectId($file_id) { - return self::EVENT_CREATE; + return $this->db + ->table(self::TABLE) + ->eq(self::TABLE.'.id', $file_id) + ->join(TaskModel::TABLE, 'id', 'task_id') + ->findOneColumn(TaskModel::TABLE . '.project_id') ?: 0; } /** @@ -85,4 +89,15 @@ class TaskFileModel extends FileModel $original_filename = e('Screenshot taken %s', $this->helper->dt->datetime(time())).'.png'; return $this->uploadContent($task_id, $original_filename, $blob); } + + /** + * Fire file creation event + * + * @access protected + * @param integer $file_id + */ + protected function fireCreationEvent($file_id) + { + $this->queueManager->push($this->taskFileEventJob->withParams($file_id, self::EVENT_CREATE)); + } } diff --git a/app/Model/TaskFinderModel.php b/app/Model/TaskFinderModel.php index 8b636e28..3c32e140 100644 --- a/app/Model/TaskFinderModel.php +++ b/app/Model/TaskFinderModel.php @@ -2,7 +2,6 @@ namespace Kanboard\Model; -use PDO; use Kanboard\Core\Base; /** @@ -63,25 +62,26 @@ class TaskFinderModel extends Base return $this->db ->table(TaskModel::TABLE) ->columns( - 'tasks.id', - 'tasks.title', - 'tasks.date_due', - 'tasks.date_creation', - 'tasks.project_id', - 'tasks.color_id', - 'tasks.priority', - 'tasks.time_spent', - 'tasks.time_estimated', - 'tasks.is_active', - 'tasks.creator_id', - 'projects.name AS project_name', - 'columns.title AS column_title' + TaskModel::TABLE.'.id', + TaskModel::TABLE.'.title', + TaskModel::TABLE.'.date_due', + TaskModel::TABLE.'.date_creation', + TaskModel::TABLE.'.project_id', + TaskModel::TABLE.'.color_id', + TaskModel::TABLE.'.priority', + TaskModel::TABLE.'.time_spent', + TaskModel::TABLE.'.time_estimated', + TaskModel::TABLE.'.is_active', + TaskModel::TABLE.'.creator_id', + ProjectModel::TABLE.'.name AS project_name', + ColumnModel::TABLE.'.title AS column_title' ) ->join(ProjectModel::TABLE, 'id', 'project_id') ->join(ColumnModel::TABLE, 'id', 'column_id') ->eq(TaskModel::TABLE.'.owner_id', $user_id) ->eq(TaskModel::TABLE.'.is_active', TaskModel::STATUS_OPEN) - ->eq(ProjectModel::TABLE.'.is_active', ProjectModel::ACTIVE); + ->eq(ProjectModel::TABLE.'.is_active', ProjectModel::ACTIVE) + ->eq(ColumnModel::TABLE.'.hide_in_dashboard', 0); } /** @@ -102,36 +102,36 @@ class TaskFinderModel extends Base '(SELECT COUNT(*) FROM '.TaskLinkModel::TABLE.' WHERE '.TaskLinkModel::TABLE.'.task_id = tasks.id) AS nb_links', '(SELECT COUNT(*) FROM '.TaskExternalLinkModel::TABLE.' WHERE '.TaskExternalLinkModel::TABLE.'.task_id = tasks.id) AS nb_external_links', '(SELECT DISTINCT 1 FROM '.TaskLinkModel::TABLE.' WHERE '.TaskLinkModel::TABLE.'.task_id = tasks.id AND '.TaskLinkModel::TABLE.'.link_id = 9) AS is_milestone', - 'tasks.id', - 'tasks.reference', - 'tasks.title', - 'tasks.description', - 'tasks.date_creation', - 'tasks.date_modification', - 'tasks.date_completed', - 'tasks.date_started', - 'tasks.date_due', - 'tasks.color_id', - 'tasks.project_id', - 'tasks.column_id', - 'tasks.swimlane_id', - 'tasks.owner_id', - 'tasks.creator_id', - 'tasks.position', - 'tasks.is_active', - 'tasks.score', - 'tasks.category_id', - 'tasks.priority', - 'tasks.date_moved', - 'tasks.recurrence_status', - 'tasks.recurrence_trigger', - 'tasks.recurrence_factor', - 'tasks.recurrence_timeframe', - 'tasks.recurrence_basedate', - 'tasks.recurrence_parent', - 'tasks.recurrence_child', - 'tasks.time_estimated', - 'tasks.time_spent', + TaskModel::TABLE.'.id', + TaskModel::TABLE.'.reference', + TaskModel::TABLE.'.title', + TaskModel::TABLE.'.description', + TaskModel::TABLE.'.date_creation', + TaskModel::TABLE.'.date_modification', + TaskModel::TABLE.'.date_completed', + TaskModel::TABLE.'.date_started', + TaskModel::TABLE.'.date_due', + TaskModel::TABLE.'.color_id', + TaskModel::TABLE.'.project_id', + TaskModel::TABLE.'.column_id', + TaskModel::TABLE.'.swimlane_id', + TaskModel::TABLE.'.owner_id', + TaskModel::TABLE.'.creator_id', + TaskModel::TABLE.'.position', + TaskModel::TABLE.'.is_active', + TaskModel::TABLE.'.score', + TaskModel::TABLE.'.category_id', + TaskModel::TABLE.'.priority', + TaskModel::TABLE.'.date_moved', + TaskModel::TABLE.'.recurrence_status', + TaskModel::TABLE.'.recurrence_trigger', + TaskModel::TABLE.'.recurrence_factor', + TaskModel::TABLE.'.recurrence_timeframe', + TaskModel::TABLE.'.recurrence_basedate', + TaskModel::TABLE.'.recurrence_parent', + TaskModel::TABLE.'.recurrence_child', + TaskModel::TABLE.'.time_estimated', + TaskModel::TABLE.'.time_spent', UserModel::TABLE.'.username AS assignee_username', UserModel::TABLE.'.name AS assignee_name', UserModel::TABLE.'.email AS assignee_email', @@ -153,26 +153,6 @@ class TaskFinderModel extends Base } /** - * Get all tasks shown on the board (sorted by position) - * - * @access public - * @param integer $project_id Project id - * @param integer $column_id Column id - * @param integer $swimlane_id Swimlane id - * @return array - */ - public function getTasksByColumnAndSwimlane($project_id, $column_id, $swimlane_id = 0) - { - return $this->getExtendedQuery() - ->eq(TaskModel::TABLE.'.project_id', $project_id) - ->eq(TaskModel::TABLE.'.column_id', $column_id) - ->eq(TaskModel::TABLE.'.swimlane_id', $swimlane_id) - ->eq(TaskModel::TABLE.'.is_active', TaskModel::STATUS_OPEN) - ->asc(TaskModel::TABLE.'.position') - ->findAll(); - } - - /** * Get all tasks for a given project and status * * @access public @@ -186,6 +166,7 @@ class TaskFinderModel extends Base ->table(TaskModel::TABLE) ->eq(TaskModel::TABLE.'.project_id', $project_id) ->eq(TaskModel::TABLE.'.is_active', $status_id) + ->asc(TaskModel::TABLE.'.id') ->findAll(); } @@ -203,7 +184,8 @@ class TaskFinderModel extends Base ->table(TaskModel::TABLE) ->eq(TaskModel::TABLE.'.project_id', $project_id) ->in(TaskModel::TABLE.'.is_active', $status) - ->findAllByColumn('id'); + ->asc(TaskModel::TABLE.'.id') + ->findAllByColumn(TaskModel::TABLE.'.id'); } /** @@ -315,59 +297,30 @@ class TaskFinderModel extends Base */ public function getDetails($task_id) { - $sql = ' - SELECT - tasks.id, - tasks.reference, - tasks.title, - tasks.description, - tasks.date_creation, - tasks.date_completed, - tasks.date_modification, - tasks.date_due, - tasks.date_started, - tasks.time_estimated, - tasks.time_spent, - tasks.color_id, - tasks.project_id, - tasks.column_id, - tasks.owner_id, - tasks.creator_id, - tasks.position, - tasks.is_active, - tasks.score, - tasks.category_id, - tasks.priority, - tasks.swimlane_id, - tasks.date_moved, - tasks.recurrence_status, - tasks.recurrence_trigger, - tasks.recurrence_factor, - tasks.recurrence_timeframe, - tasks.recurrence_basedate, - tasks.recurrence_parent, - tasks.recurrence_child, - project_has_categories.name AS category_name, - swimlanes.name AS swimlane_name, - projects.name AS project_name, - projects.default_swimlane, - columns.title AS column_title, - users.username AS assignee_username, - users.name AS assignee_name, - creators.username AS creator_username, - creators.name AS creator_name - FROM tasks - LEFT JOIN users ON users.id = tasks.owner_id - LEFT JOIN users AS creators ON creators.id = tasks.creator_id - LEFT JOIN project_has_categories ON project_has_categories.id = tasks.category_id - LEFT JOIN projects ON projects.id = tasks.project_id - LEFT JOIN columns ON columns.id = tasks.column_id - LEFT JOIN swimlanes ON swimlanes.id = tasks.swimlane_id - WHERE tasks.id = ? - '; - - $rq = $this->db->execute($sql, array($task_id)); - return $rq->fetch(PDO::FETCH_ASSOC); + return $this->db->table(TaskModel::TABLE) + ->columns( + TaskModel::TABLE.'.*', + CategoryModel::TABLE.'.name AS category_name', + SwimlaneModel::TABLE.'.name AS swimlane_name', + ProjectModel::TABLE.'.name AS project_name', + ProjectModel::TABLE.'.default_swimlane', + ColumnModel::TABLE.'.title AS column_title', + UserModel::TABLE.'.username AS assignee_username', + UserModel::TABLE.'.name AS assignee_name', + 'uc.username AS creator_username', + 'uc.name AS creator_name', + CategoryModel::TABLE.'.description AS category_description', + ColumnModel::TABLE.'.position AS column_position', + ProjectModel::TABLE.'.default_swimlane' + ) + ->join(UserModel::TABLE, 'id', 'owner_id', TaskModel::TABLE) + ->left(UserModel::TABLE, 'uc', 'id', TaskModel::TABLE, 'creator_id') + ->join(CategoryModel::TABLE, 'id', 'category_id', TaskModel::TABLE) + ->join(ColumnModel::TABLE, 'id', 'column_id', TaskModel::TABLE) + ->join(SwimlaneModel::TABLE, 'id', 'swimlane_id', TaskModel::TABLE) + ->join(ProjectModel::TABLE, 'id', 'project_id', TaskModel::TABLE) + ->eq(TaskModel::TABLE.'.id', $task_id) + ->findOne(); } /** @@ -387,6 +340,7 @@ class TaskFinderModel extends Base 'ua.name AS assignee_name', 'ua.username AS assignee_username', 'uc.email AS creator_email', + 'uc.name AS creator_name', 'uc.username AS creator_username' ); } diff --git a/app/Model/TaskLinkModel.php b/app/Model/TaskLinkModel.php index 45225e35..e8d3c5df 100644 --- a/app/Model/TaskLinkModel.php +++ b/app/Model/TaskLinkModel.php @@ -3,7 +3,6 @@ namespace Kanboard\Model; use Kanboard\Core\Base; -use Kanboard\Event\TaskLinkEvent; /** * TaskLink model @@ -26,7 +25,24 @@ class TaskLinkModel extends Base * * @var string */ - const EVENT_CREATE_UPDATE = 'tasklink.create_update'; + const EVENT_CREATE_UPDATE = 'task_internal_link.create_update'; + const EVENT_DELETE = 'task_internal_link.delete'; + + /** + * Get projectId from $task_link_id + * + * @access public + * @param integer $task_link_id + * @return integer + */ + public function getProjectId($task_link_id) + { + return $this->db + ->table(self::TABLE) + ->eq(self::TABLE.'.id', $task_link_id) + ->join(TaskModel::TABLE, 'id', 'task_id') + ->findOneColumn(TaskModel::TABLE . '.project_id') ?: 0; + } /** * Get a task link @@ -37,7 +53,19 @@ class TaskLinkModel extends Base */ public function getById($task_link_id) { - return $this->db->table(self::TABLE)->eq('id', $task_link_id)->findOne(); + return $this->db + ->table(self::TABLE) + ->columns( + self::TABLE.'.id', + self::TABLE.'.opposite_task_id', + self::TABLE.'.task_id', + self::TABLE.'.link_id', + LinkModel::TABLE.'.label', + LinkModel::TABLE.'.opposite_id AS opposite_link_id' + ) + ->eq(self::TABLE.'.id', $task_link_id) + ->join(LinkModel::TABLE, 'id', 'link_id') + ->findOne(); } /** @@ -124,62 +152,31 @@ class TaskLinkModel extends Base } /** - * Publish events - * - * @access private - * @param array $events - */ - private function fireEvents(array $events) - { - foreach ($events as $event) { - $event['project_id'] = $this->taskFinderModel->getProjectId($event['task_id']); - $this->container['dispatcher']->dispatch(self::EVENT_CREATE_UPDATE, new TaskLinkEvent($event)); - } - } - - /** * Create a new link * * @access public * @param integer $task_id Task id * @param integer $opposite_task_id Opposite task id * @param integer $link_id Link id - * @return integer Task link id + * @return integer|boolean */ public function create($task_id, $opposite_task_id, $link_id) { - $events = array(); $this->db->startTransaction(); - // Get opposite link $opposite_link_id = $this->linkModel->getOppositeLinkId($link_id); + $task_link_id1 = $this->createTaskLink($task_id, $opposite_task_id, $link_id); + $task_link_id2 = $this->createTaskLink($opposite_task_id, $task_id, $opposite_link_id); - $values = array( - 'task_id' => $task_id, - 'opposite_task_id' => $opposite_task_id, - 'link_id' => $link_id, - ); - - // Create the original task link - $this->db->table(self::TABLE)->insert($values); - $task_link_id = $this->db->getLastId(); - $events[] = $values; - - // Create the opposite task link - $values = array( - 'task_id' => $opposite_task_id, - 'opposite_task_id' => $task_id, - 'link_id' => $opposite_link_id, - ); - - $this->db->table(self::TABLE)->insert($values); - $events[] = $values; + if ($task_link_id1 === false || $task_link_id2 === false) { + $this->db->cancelTransaction(); + return false; + } $this->db->closeTransaction(); + $this->fireEvents(array($task_link_id1, $task_link_id2), self::EVENT_CREATE_UPDATE); - $this->fireEvents($events); - - return (int) $task_link_id; + return $task_link_id1; } /** @@ -194,46 +191,24 @@ class TaskLinkModel extends Base */ public function update($task_link_id, $task_id, $opposite_task_id, $link_id) { - $events = array(); $this->db->startTransaction(); - // Get original task link $task_link = $this->getById($task_link_id); - - // Find opposite task link $opposite_task_link = $this->getOppositeTaskLink($task_link); - - // Get opposite link $opposite_link_id = $this->linkModel->getOppositeLinkId($link_id); - // Update the original task link - $values = array( - 'task_id' => $task_id, - 'opposite_task_id' => $opposite_task_id, - 'link_id' => $link_id, - ); - - $rs1 = $this->db->table(self::TABLE)->eq('id', $task_link_id)->update($values); - $events[] = $values; - - // Update the opposite link - $values = array( - 'task_id' => $opposite_task_id, - 'opposite_task_id' => $task_id, - 'link_id' => $opposite_link_id, - ); + $result1 = $this->updateTaskLink($task_link_id, $task_id, $opposite_task_id, $link_id); + $result2 = $this->updateTaskLink($opposite_task_link['id'], $opposite_task_id, $task_id, $opposite_link_id); - $rs2 = $this->db->table(self::TABLE)->eq('id', $opposite_task_link['id'])->update($values); - $events[] = $values; + if ($result1 === false || $result2 === false) { + $this->db->cancelTransaction(); + return false; + } $this->db->closeTransaction(); + $this->fireEvents(array($task_link_id, $opposite_task_link['id']), self::EVENT_CREATE_UPDATE); - if ($rs1 && $rs2) { - $this->fireEvents($events); - return true; - } - - return false; + return true; } /** @@ -245,21 +220,83 @@ class TaskLinkModel extends Base */ public function remove($task_link_id) { + $this->taskLinkEventJob->execute($task_link_id, self::EVENT_DELETE); + $this->db->startTransaction(); $link = $this->getById($task_link_id); $link_id = $this->linkModel->getOppositeLinkId($link['link_id']); - $this->db->table(self::TABLE)->eq('id', $task_link_id)->remove(); + $result1 = $this->db + ->table(self::TABLE) + ->eq('id', $task_link_id) + ->remove(); - $this->db + $result2 = $this->db ->table(self::TABLE) ->eq('opposite_task_id', $link['task_id']) ->eq('task_id', $link['opposite_task_id']) - ->eq('link_id', $link_id)->remove(); + ->eq('link_id', $link_id) + ->remove(); + + if ($result1 === false || $result2 === false) { + $this->db->cancelTransaction(); + return false; + } $this->db->closeTransaction(); return true; } + + /** + * Publish events + * + * @access protected + * @param integer[] $task_link_ids + * @param string $eventName + */ + protected function fireEvents(array $task_link_ids, $eventName) + { + foreach ($task_link_ids as $task_link_id) { + $this->queueManager->push($this->taskLinkEventJob->withParams($task_link_id, $eventName)); + } + } + + /** + * Create task link + * + * @access protected + * @param integer $task_id + * @param integer $opposite_task_id + * @param integer $link_id + * @return integer|boolean + */ + protected function createTaskLink($task_id, $opposite_task_id, $link_id) + { + return $this->db->table(self::TABLE)->persist(array( + 'task_id' => $task_id, + 'opposite_task_id' => $opposite_task_id, + 'link_id' => $link_id, + )); + } + + /** + * Update task link + * + * @access protected + * @param integer $task_link_id + * @param integer $task_id + * @param integer $opposite_task_id + * @param integer $link_id + * @return boolean + */ + protected function updateTaskLink($task_link_id, $task_id, $opposite_task_id, $link_id) + { + return $this->db->table(self::TABLE)->eq('id', $task_link_id)->update(array( + 'task_id' => $task_id, + 'opposite_task_id' => $opposite_task_id, + 'link_id' => $link_id, + )); + } } diff --git a/app/Model/TaskModel.php b/app/Model/TaskModel.php index b0e7772a..5cddb509 100644 --- a/app/Model/TaskModel.php +++ b/app/Model/TaskModel.php @@ -5,7 +5,7 @@ namespace Kanboard\Model; use Kanboard\Core\Base; /** - * Task model + * Task Model * * @package Kanboard\Model * @author Frederic Guillot @@ -17,80 +17,80 @@ class TaskModel extends Base * * @var string */ - const TABLE = 'tasks'; + const TABLE = 'tasks'; /** * Task status * * @var integer */ - const STATUS_OPEN = 1; - const STATUS_CLOSED = 0; + const STATUS_OPEN = 1; + const STATUS_CLOSED = 0; /** * Events * * @var string */ - const EVENT_MOVE_PROJECT = 'task.move.project'; - const EVENT_MOVE_COLUMN = 'task.move.column'; - const EVENT_MOVE_POSITION = 'task.move.position'; - const EVENT_MOVE_SWIMLANE = 'task.move.swimlane'; - const EVENT_UPDATE = 'task.update'; - const EVENT_CREATE = 'task.create'; - const EVENT_CLOSE = 'task.close'; - const EVENT_OPEN = 'task.open'; - const EVENT_CREATE_UPDATE = 'task.create_update'; + const EVENT_MOVE_PROJECT = 'task.move.project'; + const EVENT_MOVE_COLUMN = 'task.move.column'; + const EVENT_MOVE_POSITION = 'task.move.position'; + const EVENT_MOVE_SWIMLANE = 'task.move.swimlane'; + const EVENT_UPDATE = 'task.update'; + const EVENT_CREATE = 'task.create'; + const EVENT_CLOSE = 'task.close'; + const EVENT_OPEN = 'task.open'; + const EVENT_CREATE_UPDATE = 'task.create_update'; const EVENT_ASSIGNEE_CHANGE = 'task.assignee_change'; - const EVENT_OVERDUE = 'task.overdue'; - const EVENT_USER_MENTION = 'task.user.mention'; - const EVENT_DAILY_CRONJOB = 'task.cronjob.daily'; + const EVENT_OVERDUE = 'task.overdue'; + const EVENT_USER_MENTION = 'task.user.mention'; + const EVENT_DAILY_CRONJOB = 'task.cronjob.daily'; /** * Recurrence: status * * @var integer */ - const RECURRING_STATUS_NONE = 0; - const RECURRING_STATUS_PENDING = 1; - const RECURRING_STATUS_PROCESSED = 2; + const RECURRING_STATUS_NONE = 0; + const RECURRING_STATUS_PENDING = 1; + const RECURRING_STATUS_PROCESSED = 2; /** * Recurrence: trigger * * @var integer */ - const RECURRING_TRIGGER_FIRST_COLUMN = 0; - const RECURRING_TRIGGER_LAST_COLUMN = 1; - const RECURRING_TRIGGER_CLOSE = 2; + const RECURRING_TRIGGER_FIRST_COLUMN = 0; + const RECURRING_TRIGGER_LAST_COLUMN = 1; + const RECURRING_TRIGGER_CLOSE = 2; /** * Recurrence: timeframe * * @var integer */ - const RECURRING_TIMEFRAME_DAYS = 0; - const RECURRING_TIMEFRAME_MONTHS = 1; - const RECURRING_TIMEFRAME_YEARS = 2; + const RECURRING_TIMEFRAME_DAYS = 0; + const RECURRING_TIMEFRAME_MONTHS = 1; + const RECURRING_TIMEFRAME_YEARS = 2; /** * Recurrence: base date used to calculate new due date * * @var integer */ - const RECURRING_BASEDATE_DUEDATE = 0; - const RECURRING_BASEDATE_TRIGGERDATE = 1; + const RECURRING_BASEDATE_DUEDATE = 0; + const RECURRING_BASEDATE_TRIGGERDATE = 1; /** * Remove a task * * @access public - * @param integer $task_id Task id + * @param integer $task_id Task id * @return boolean */ public function remove($task_id) { - if (! $this->taskFinderModel->exists($task_id)) { + if (!$this->taskFinderModel->exists($task_id)) { return false; } @@ -105,7 +105,7 @@ class TaskModel extends Base * Example: "Fix bug #1234" will return 1234 * * @access public - * @param string $message Text + * @param string $message Text * @return integer */ public function getTaskIdFromText($message) @@ -118,69 +118,11 @@ class TaskModel extends Base } /** - * Return the list user selectable recurrence status - * - * @access public - * @return array - */ - public function getRecurrenceStatusList() - { - return array( - TaskModel::RECURRING_STATUS_NONE => t('No'), - TaskModel::RECURRING_STATUS_PENDING => t('Yes'), - ); - } - - /** - * Return the list recurrence triggers - * - * @access public - * @return array - */ - public function getRecurrenceTriggerList() - { - return array( - TaskModel::RECURRING_TRIGGER_FIRST_COLUMN => t('When task is moved from first column'), - TaskModel::RECURRING_TRIGGER_LAST_COLUMN => t('When task is moved to last column'), - TaskModel::RECURRING_TRIGGER_CLOSE => t('When task is closed'), - ); - } - - /** - * Return the list options to calculate recurrence due date - * - * @access public - * @return array - */ - public function getRecurrenceBasedateList() - { - return array( - TaskModel::RECURRING_BASEDATE_DUEDATE => t('Existing due date'), - TaskModel::RECURRING_BASEDATE_TRIGGERDATE => t('Action date'), - ); - } - - /** - * Return the list recurrence timeframes - * - * @access public - * @return array - */ - public function getRecurrenceTimeframeList() - { - return array( - TaskModel::RECURRING_TIMEFRAME_DAYS => t('Day(s)'), - TaskModel::RECURRING_TIMEFRAME_MONTHS => t('Month(s)'), - TaskModel::RECURRING_TIMEFRAME_YEARS => t('Year(s)'), - ); - } - - /** * Get task progress based on the column position * * @access public - * @param array $task - * @param array $columns + * @param array $task + * @param array $columns * @return integer */ public function getProgress(array $task, array $columns) @@ -201,25 +143,4 @@ class TaskModel extends Base return round(($position * 100) / count($columns), 1); } - - /** - * Helper method to duplicate all tasks to another project - * - * @access public - * @param integer $src_project_id - * @param integer $dst_project_id - * @return boolean - */ - public function duplicate($src_project_id, $dst_project_id) - { - $task_ids = $this->taskFinderModel->getAllIds($src_project_id, array(TaskModel::STATUS_OPEN, TaskModel::STATUS_CLOSED)); - - foreach ($task_ids as $task_id) { - if (! $this->taskDuplicationModel->duplicateToProject($task_id, $dst_project_id)) { - return false; - } - } - - return true; - } } diff --git a/app/Model/TaskModificationModel.php b/app/Model/TaskModificationModel.php index 762af2c5..6e16fbec 100644 --- a/app/Model/TaskModificationModel.php +++ b/app/Model/TaskModificationModel.php @@ -3,7 +3,6 @@ namespace Kanboard\Model; use Kanboard\Core\Base; -use Kanboard\Event\TaskEvent; /** * Task Modification @@ -23,13 +22,14 @@ class TaskModificationModel extends Base */ public function update(array $values, $fire_events = true) { - $original_task = $this->taskFinderModel->getById($values['id']); + $task = $this->taskFinderModel->getById($values['id']); + $this->updateTags($values, $task); $this->prepare($values); - $result = $this->db->table(TaskModel::TABLE)->eq('id', $original_task['id'])->update($values); + $result = $this->db->table(TaskModel::TABLE)->eq('id', $task['id'])->update($values); if ($fire_events && $result) { - $this->fireEvents($original_task, $values); + $this->fireEvents($task, $values); } return $result; @@ -38,52 +38,65 @@ class TaskModificationModel extends Base /** * Fire events * - * @access public - * @param array $task - * @param array $new_values + * @access protected + * @param array $task + * @param array $changes */ - public function fireEvents(array $task, array $new_values) + protected function fireEvents(array $task, array $changes) { $events = array(); - $event_data = array_merge($task, $new_values, array('task_id' => $task['id'])); - - // Values changed - $event_data['changes'] = array_diff_assoc($new_values, $task); - unset($event_data['changes']['date_modification']); - if ($this->isFieldModified('owner_id', $event_data['changes'])) { + if ($this->isAssigneeChanged($task, $changes)) { $events[] = TaskModel::EVENT_ASSIGNEE_CHANGE; - } elseif (! empty($event_data['changes'])) { + } elseif ($this->isModified($task, $changes)) { $events[] = TaskModel::EVENT_CREATE_UPDATE; $events[] = TaskModel::EVENT_UPDATE; } - foreach ($events as $event) { - $this->logger->debug('Event fired: '.$event); - $this->dispatcher->dispatch($event, new TaskEvent($event_data)); + if (! empty($events)) { + $this->queueManager->push($this->taskEventJob + ->withParams($task['id'], $events, $changes, array(), $task) + ); } } /** + * Return true if the task have been modified + * + * @access protected + * @param array $task + * @param array $changes + * @return bool + */ + protected function isModified(array $task, array $changes) + { + $diff = array_diff_assoc($changes, $task); + unset($diff['date_modification']); + return count($diff) > 0; + } + + /** * Return true if the field is the only modified value * - * @access public - * @param string $field - * @param array $changes - * @return boolean + * @access protected + * @param array $task + * @param array $changes + * @return bool */ - public function isFieldModified($field, array $changes) + protected function isAssigneeChanged(array $task, array $changes) { - return isset($changes[$field]) && count($changes) === 1; + $diff = array_diff_assoc($changes, $task); + unset($diff['date_modification']); + return isset($changes['owner_id']) && $task['owner_id'] != $changes['owner_id'] && count($diff) === 1; } /** * Prepare data before task modification * - * @access public - * @param array $values Form values + * @access protected + * @param array $values */ - public function prepare(array &$values) + protected function prepare(array &$values) { $values = $this->dateParser->convert($values, array('date_due')); $values = $this->dateParser->convert($values, array('date_started'), true); @@ -93,5 +106,22 @@ class TaskModificationModel extends Base $this->helper->model->convertIntegerFields($values, array('priority', 'is_active', 'recurrence_status', 'recurrence_trigger', 'recurrence_factor', 'recurrence_timeframe', 'recurrence_basedate')); $values['date_modification'] = time(); + + $this->hook->reference('model:task:modification:prepare', $values); + } + + /** + * Update tags + * + * @access protected + * @param array $values + * @param array $original_task + */ + protected function updateTags(array &$values, array $original_task) + { + if (isset($values['tags'])) { + $this->taskTagModel->save($original_task['project_id'], $values['id'], $values['tags']); + unset($values['tags']); + } } } diff --git a/app/Model/TaskPositionModel.php b/app/Model/TaskPositionModel.php index 9fdb8f7d..3d95a763 100644 --- a/app/Model/TaskPositionModel.php +++ b/app/Model/TaskPositionModel.php @@ -3,7 +3,6 @@ namespace Kanboard\Model; use Kanboard\Core\Base; -use Kanboard\Event\TaskEvent; /** * Task Position @@ -17,15 +16,16 @@ class TaskPositionModel extends Base * Move a task to another column or to another position * * @access public - * @param integer $project_id Project id - * @param integer $task_id Task id - * @param integer $column_id Column id - * @param integer $position Position (must be >= 1) - * @param integer $swimlane_id Swimlane id - * @param boolean $fire_events Fire events - * @return boolean + * @param integer $project_id Project id + * @param integer $task_id Task id + * @param integer $column_id Column id + * @param integer $position Position (must be >= 1) + * @param integer $swimlane_id Swimlane id + * @param boolean $fire_events Fire events + * @param bool $onlyOpen Do not move closed tasks + * @return bool */ - public function movePosition($project_id, $task_id, $column_id, $position, $swimlane_id = 0, $fire_events = true) + public function movePosition($project_id, $task_id, $column_id, $position, $swimlane_id = 0, $fire_events = true, $onlyOpen = true) { if ($position < 1) { return false; @@ -33,7 +33,7 @@ class TaskPositionModel extends Base $task = $this->taskFinderModel->getById($task_id); - if ($task['is_active'] == TaskModel::STATUS_CLOSED) { + if ($onlyOpen && $task['is_active'] == TaskModel::STATUS_CLOSED) { return true; } @@ -212,8 +212,7 @@ class TaskPositionModel extends Base */ private function fireEvents(array $task, $new_column_id, $new_position, $new_swimlane_id) { - $event_data = array( - 'task_id' => $task['id'], + $changes = array( 'project_id' => $task['project_id'], 'position' => $new_position, 'column_id' => $new_column_id, @@ -226,14 +225,26 @@ class TaskPositionModel extends Base ); if ($task['swimlane_id'] != $new_swimlane_id) { - $this->logger->debug('Event fired: '.TaskModel::EVENT_MOVE_SWIMLANE); - $this->dispatcher->dispatch(TaskModel::EVENT_MOVE_SWIMLANE, new TaskEvent($event_data)); + $this->queueManager->push($this->taskEventJob->withParams( + $task['id'], + array(TaskModel::EVENT_MOVE_SWIMLANE), + $changes, + $changes + )); } elseif ($task['column_id'] != $new_column_id) { - $this->logger->debug('Event fired: '.TaskModel::EVENT_MOVE_COLUMN); - $this->dispatcher->dispatch(TaskModel::EVENT_MOVE_COLUMN, new TaskEvent($event_data)); + $this->queueManager->push($this->taskEventJob->withParams( + $task['id'], + array(TaskModel::EVENT_MOVE_COLUMN), + $changes, + $changes + )); } elseif ($task['position'] != $new_position) { - $this->logger->debug('Event fired: '.TaskModel::EVENT_MOVE_POSITION); - $this->dispatcher->dispatch(TaskModel::EVENT_MOVE_POSITION, new TaskEvent($event_data)); + $this->queueManager->push($this->taskEventJob->withParams( + $task['id'], + array(TaskModel::EVENT_MOVE_POSITION), + $changes, + $changes + )); } } } diff --git a/app/Model/TaskProjectDuplicationModel.php b/app/Model/TaskProjectDuplicationModel.php new file mode 100644 index 00000000..8ebed255 --- /dev/null +++ b/app/Model/TaskProjectDuplicationModel.php @@ -0,0 +1,60 @@ +<?php + +namespace Kanboard\Model; + +/** + * Task Project Duplication + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class TaskProjectDuplicationModel extends TaskDuplicationModel +{ + /** + * Duplicate a task to another project + * + * @access public + * @param integer $task_id + * @param integer $project_id + * @param integer $swimlane_id + * @param integer $column_id + * @param integer $category_id + * @param integer $owner_id + * @return boolean|integer + */ + public function duplicateToProject($task_id, $project_id, $swimlane_id = null, $column_id = null, $category_id = null, $owner_id = null) + { + $values = $this->prepare($task_id, $project_id, $swimlane_id, $column_id, $category_id, $owner_id); + $this->checkDestinationProjectValues($values); + $new_task_id = $this->save($task_id, $values); + + if ($new_task_id !== false) { + $this->tagDuplicationModel->duplicateTaskTagsToAnotherProject($task_id, $new_task_id, $project_id); + } + + return $new_task_id; + } + + /** + * Prepare values before duplication + * + * @access protected + * @param integer $task_id + * @param integer $project_id + * @param integer $swimlane_id + * @param integer $column_id + * @param integer $category_id + * @param integer $owner_id + * @return array + */ + protected function prepare($task_id, $project_id, $swimlane_id, $column_id, $category_id, $owner_id) + { + $values = $this->copyFields($task_id); + $values['project_id'] = $project_id; + $values['column_id'] = $column_id !== null ? $column_id : $values['column_id']; + $values['swimlane_id'] = $swimlane_id !== null ? $swimlane_id : $values['swimlane_id']; + $values['category_id'] = $category_id !== null ? $category_id : $values['category_id']; + $values['owner_id'] = $owner_id !== null ? $owner_id : $values['owner_id']; + return $values; + } +} diff --git a/app/Model/TaskProjectMoveModel.php b/app/Model/TaskProjectMoveModel.php new file mode 100644 index 00000000..ae3ae084 --- /dev/null +++ b/app/Model/TaskProjectMoveModel.php @@ -0,0 +1,65 @@ +<?php + +namespace Kanboard\Model; + +/** + * Task Project Move + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class TaskProjectMoveModel extends TaskDuplicationModel +{ + /** + * Move a task to another project + * + * @access public + * @param integer $task_id + * @param integer $project_id + * @param integer $swimlane_id + * @param integer $column_id + * @param integer $category_id + * @param integer $owner_id + * @return boolean + */ + public function moveToProject($task_id, $project_id, $swimlane_id = null, $column_id = null, $category_id = null, $owner_id = null) + { + $task = $this->taskFinderModel->getById($task_id); + $values = $this->prepare($project_id, $swimlane_id, $column_id, $category_id, $owner_id, $task); + + $this->checkDestinationProjectValues($values); + $this->tagDuplicationModel->syncTaskTagsToAnotherProject($task_id, $project_id); + + if ($this->db->table(TaskModel::TABLE)->eq('id', $task_id)->update($values)) { + $this->queueManager->push($this->taskEventJob->withParams($task_id, array(TaskModel::EVENT_MOVE_PROJECT), $values)); + } + + return true; + } + + /** + * Prepare new task values + * + * @access protected + * @param integer $project_id + * @param integer $swimlane_id + * @param integer $column_id + * @param integer $category_id + * @param integer $owner_id + * @param array $task + * @return array + */ + protected function prepare($project_id, $swimlane_id, $column_id, $category_id, $owner_id, array $task) + { + $values = array(); + $values['is_active'] = 1; + $values['project_id'] = $project_id; + $values['column_id'] = $column_id !== null ? $column_id : $task['column_id']; + $values['position'] = $this->taskFinderModel->countByColumnId($project_id, $values['column_id']) + 1; + $values['swimlane_id'] = $swimlane_id !== null ? $swimlane_id : $task['swimlane_id']; + $values['category_id'] = $category_id !== null ? $category_id : $task['category_id']; + $values['owner_id'] = $owner_id !== null ? $owner_id : $task['owner_id']; + $values['priority'] = $task['priority']; + return $values; + } +} diff --git a/app/Model/TaskRecurrenceModel.php b/app/Model/TaskRecurrenceModel.php new file mode 100644 index 00000000..ffe43f8c --- /dev/null +++ b/app/Model/TaskRecurrenceModel.php @@ -0,0 +1,147 @@ +<?php + +namespace Kanboard\Model; + +use DateInterval; +use DateTime; + +/** + * Task Recurrence + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class TaskRecurrenceModel extends TaskDuplicationModel +{ + /** + * Return the list user selectable recurrence status + * + * @access public + * @return array + */ + public function getRecurrenceStatusList() + { + return array( + TaskModel::RECURRING_STATUS_NONE => t('No'), + TaskModel::RECURRING_STATUS_PENDING => t('Yes'), + ); + } + + /** + * Return the list recurrence triggers + * + * @access public + * @return array + */ + public function getRecurrenceTriggerList() + { + return array( + TaskModel::RECURRING_TRIGGER_FIRST_COLUMN => t('When task is moved from first column'), + TaskModel::RECURRING_TRIGGER_LAST_COLUMN => t('When task is moved to last column'), + TaskModel::RECURRING_TRIGGER_CLOSE => t('When task is closed'), + ); + } + + /** + * Return the list options to calculate recurrence due date + * + * @access public + * @return array + */ + public function getRecurrenceBasedateList() + { + return array( + TaskModel::RECURRING_BASEDATE_DUEDATE => t('Existing due date'), + TaskModel::RECURRING_BASEDATE_TRIGGERDATE => t('Action date'), + ); + } + + /** + * Return the list recurrence timeframes + * + * @access public + * @return array + */ + public function getRecurrenceTimeframeList() + { + return array( + TaskModel::RECURRING_TIMEFRAME_DAYS => t('Day(s)'), + TaskModel::RECURRING_TIMEFRAME_MONTHS => t('Month(s)'), + TaskModel::RECURRING_TIMEFRAME_YEARS => t('Year(s)'), + ); + } + + /** + * Duplicate recurring task + * + * @access public + * @param integer $task_id Task id + * @return boolean|integer Recurrence task id + */ + public function duplicateRecurringTask($task_id) + { + $values = $this->copyFields($task_id); + + if ($values['recurrence_status'] == TaskModel::RECURRING_STATUS_PENDING) { + $values['recurrence_parent'] = $task_id; + $values['column_id'] = $this->columnModel->getFirstColumnId($values['project_id']); + $this->calculateRecurringTaskDueDate($values); + + $recurring_task_id = $this->save($task_id, $values); + + if ($recurring_task_id !== false) { + $this->tagDuplicationModel->duplicateTaskTags($task_id, $recurring_task_id); + + $parent_update = $this->db + ->table(TaskModel::TABLE) + ->eq('id', $task_id) + ->update(array( + 'recurrence_status' => TaskModel::RECURRING_STATUS_PROCESSED, + 'recurrence_child' => $recurring_task_id, + )); + + if ($parent_update) { + return $recurring_task_id; + } + } + } + + return false; + } + + /** + * Calculate new due date for new recurrence task + * + * @access public + * @param array $values Task fields + */ + public function calculateRecurringTaskDueDate(array &$values) + { + if (! empty($values['date_due']) && $values['recurrence_factor'] != 0) { + if ($values['recurrence_basedate'] == TaskModel::RECURRING_BASEDATE_TRIGGERDATE) { + $values['date_due'] = time(); + } + + $factor = abs($values['recurrence_factor']); + $subtract = $values['recurrence_factor'] < 0; + + switch ($values['recurrence_timeframe']) { + case TaskModel::RECURRING_TIMEFRAME_MONTHS: + $interval = 'P' . $factor . 'M'; + break; + case TaskModel::RECURRING_TIMEFRAME_YEARS: + $interval = 'P' . $factor . 'Y'; + break; + default: + $interval = 'P' . $factor . 'D'; + } + + $date_due = new DateTime(); + $date_due->setTimestamp($values['date_due']); + + $subtract ? $date_due->sub(new DateInterval($interval)) : $date_due->add(new DateInterval($interval)); + + $values['date_due'] = $date_due->getTimestamp(); + } + } +} diff --git a/app/Model/TaskStatusModel.php b/app/Model/TaskStatusModel.php index 4d573f0e..dc114698 100644 --- a/app/Model/TaskStatusModel.php +++ b/app/Model/TaskStatusModel.php @@ -3,7 +3,6 @@ namespace Kanboard\Model; use Kanboard\Core\Base; -use Kanboard\Event\TaskEvent; /** * Task Status @@ -46,7 +45,7 @@ class TaskStatusModel extends Base */ public function close($task_id) { - $this->subtaskModel->closeAll($task_id); + $this->subtaskStatusModel->closeAll($task_id); return $this->changeStatus($task_id, TaskModel::STATUS_CLOSED, time(), TaskModel::EVENT_CLOSE); } @@ -101,10 +100,10 @@ class TaskStatusModel extends Base * @param integer $task_id Task id * @param integer $status Task status * @param integer $date_completed Timestamp - * @param string $event Event name + * @param string $event_name Event name * @return boolean */ - private function changeStatus($task_id, $status, $date_completed, $event) + private function changeStatus($task_id, $status, $date_completed, $event_name) { if (! $this->taskFinderModel->exists($task_id)) { return false; @@ -120,8 +119,7 @@ class TaskStatusModel extends Base )); if ($result) { - $this->logger->debug('Event fired: '.$event); - $this->dispatcher->dispatch($event, new TaskEvent(array('task_id' => $task_id) + $this->taskFinderModel->getById($task_id))); + $this->queueManager->push($this->taskEventJob->withParams($task_id, array($event_name))); } return $result; diff --git a/app/Model/TaskTagModel.php b/app/Model/TaskTagModel.php new file mode 100644 index 00000000..0553cc6c --- /dev/null +++ b/app/Model/TaskTagModel.php @@ -0,0 +1,184 @@ +<?php + +namespace Kanboard\Model; + +use Kanboard\Core\Base; + +/** + * Class TaskTagModel + * + * @package Kanboard\Model + * @author Frederic Guillot + */ +class TaskTagModel extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'task_has_tags'; + + /** + * Get all tags not available in a project + * + * @access public + * @param integer $task_id + * @param integer $project_id + * @return array + */ + public function getTagIdsByTaskNotAvailableInProject($task_id, $project_id) + { + return $this->db->table(TagModel::TABLE) + ->eq(self::TABLE.'.task_id', $task_id) + ->notIn(TagModel::TABLE.'.project_id', array(0, $project_id)) + ->join(self::TABLE, 'tag_id', 'id') + ->findAllByColumn(TagModel::TABLE.'.id'); + } + + /** + * Get all tags associated to a task + * + * @access public + * @param integer $task_id + * @return array + */ + public function getTagsByTask($task_id) + { + return $this->db->table(TagModel::TABLE) + ->columns(TagModel::TABLE.'.id', TagModel::TABLE.'.name') + ->eq(self::TABLE.'.task_id', $task_id) + ->join(self::TABLE, 'tag_id', 'id') + ->findAll(); + } + + /** + * Get all tags associated to a list of tasks + * + * @access public + * @param integer[] $task_ids + * @return array + */ + public function getTagsByTasks($task_ids) + { + if (empty($task_ids)) { + return array(); + } + + $tags = $this->db->table(TagModel::TABLE) + ->columns(TagModel::TABLE.'.id', TagModel::TABLE.'.name', self::TABLE.'.task_id') + ->in(self::TABLE.'.task_id', $task_ids) + ->join(self::TABLE, 'tag_id', 'id') + ->findAll(); + + return array_column_index($tags, 'task_id'); + } + + /** + * Get dictionary of tags + * + * @access public + * @param integer $task_id + * @return array + */ + public function getList($task_id) + { + $tags = $this->getTagsByTask($task_id); + return array_column($tags, 'name', 'id'); + } + + /** + * Add or update a list of tags to a task + * + * @access public + * @param integer $project_id + * @param integer $task_id + * @param string[] $tags + * @return boolean + */ + public function save($project_id, $task_id, array $tags) + { + $task_tags = $this->getList($task_id); + $tags = array_filter($tags); + + return $this->associateTags($project_id, $task_id, $task_tags, $tags) && + $this->dissociateTags($task_id, $task_tags, $tags); + } + + /** + * Associate a tag to a task + * + * @access public + * @param integer $task_id + * @param integer $tag_id + * @return boolean + */ + public function associateTag($task_id, $tag_id) + { + return $this->db->table(self::TABLE)->insert(array( + 'task_id' => $task_id, + 'tag_id' => $tag_id, + )); + } + + /** + * Dissociate a tag from a task + * + * @access public + * @param integer $task_id + * @param integer $tag_id + * @return boolean + */ + public function dissociateTag($task_id, $tag_id) + { + return $this->db->table(self::TABLE) + ->eq('task_id', $task_id) + ->eq('tag_id', $tag_id) + ->remove(); + } + + /** + * Associate missing tags + * + * @access protected + * @param integer $project_id + * @param integer $task_id + * @param array $task_tags + * @param string[] $tags + * @return bool + */ + protected function associateTags($project_id, $task_id, $task_tags, $tags) + { + foreach ($tags as $tag) { + $tag_id = $this->tagModel->findOrCreateTag($project_id, $tag); + + if (! isset($task_tags[$tag_id]) && ! $this->associateTag($task_id, $tag_id)) { + return false; + } + } + + return true; + } + + /** + * Dissociate removed tags + * + * @access protected + * @param integer $task_id + * @param array $task_tags + * @param string[] $tags + * @return bool + */ + protected function dissociateTags($task_id, $task_tags, $tags) + { + foreach ($task_tags as $tag_id => $tag) { + if (! in_array($tag, $tags)) { + if (! $this->dissociateTag($task_id, $tag_id)) { + return false; + } + } + } + + return true; + } +} diff --git a/app/Model/UserModel.php b/app/Model/UserModel.php index f7a051c5..56b1a960 100644 --- a/app/Model/UserModel.php +++ b/app/Model/UserModel.php @@ -65,17 +65,6 @@ class UserModel extends Base } /** - * Return the full name - * - * @param array $user User properties - * @return string - */ - public function getFullname(array $user) - { - return $user['name'] ?: $user['username']; - } - - /** * Return true is the given user id is administrator * * @access public @@ -230,7 +219,7 @@ class UserModel extends Base $result = array(); foreach ($users as $user) { - $result[$user['id']] = $this->getFullname($user); + $result[$user['id']] = $this->helper->user->getFullname($user); } asort($result); diff --git a/app/Notification/MailNotification.php b/app/Notification/MailNotification.php index 2d27179c..9e042820 100644 --- a/app/Notification/MailNotification.php +++ b/app/Notification/MailNotification.php @@ -4,10 +4,6 @@ namespace Kanboard\Notification; use Kanboard\Core\Base; use Kanboard\Core\Notification\NotificationInterface; -use Kanboard\Model\TaskModel; -use Kanboard\Model\TaskFileModel; -use Kanboard\Model\CommentModel; -use Kanboard\Model\SubtaskModel; /** * Email Notification @@ -76,76 +72,16 @@ class MailNotification extends Base implements NotificationInterface * Get the mail subject for a given template name * * @access public - * @param string $event_name Event name - * @param array $event_data Event data - * @return string - */ - public function getMailSubject($event_name, array $event_data) - { - switch ($event_name) { - case TaskFileModel::EVENT_CREATE: - $subject = $this->getStandardMailSubject(e('New attachment'), $event_data); - break; - case CommentModel::EVENT_CREATE: - $subject = $this->getStandardMailSubject(e('New comment'), $event_data); - break; - case CommentModel::EVENT_UPDATE: - $subject = $this->getStandardMailSubject(e('Comment updated'), $event_data); - break; - case SubtaskModel::EVENT_CREATE: - $subject = $this->getStandardMailSubject(e('New subtask'), $event_data); - break; - case SubtaskModel::EVENT_UPDATE: - $subject = $this->getStandardMailSubject(e('Subtask updated'), $event_data); - break; - case TaskModel::EVENT_CREATE: - $subject = $this->getStandardMailSubject(e('New task'), $event_data); - break; - case TaskModel::EVENT_UPDATE: - $subject = $this->getStandardMailSubject(e('Task updated'), $event_data); - break; - case TaskModel::EVENT_CLOSE: - $subject = $this->getStandardMailSubject(e('Task closed'), $event_data); - break; - case TaskModel::EVENT_OPEN: - $subject = $this->getStandardMailSubject(e('Task opened'), $event_data); - break; - case TaskModel::EVENT_MOVE_COLUMN: - $subject = $this->getStandardMailSubject(e('Column change'), $event_data); - break; - case TaskModel::EVENT_MOVE_POSITION: - $subject = $this->getStandardMailSubject(e('Position change'), $event_data); - break; - case TaskModel::EVENT_MOVE_SWIMLANE: - $subject = $this->getStandardMailSubject(e('Swimlane change'), $event_data); - break; - case TaskModel::EVENT_ASSIGNEE_CHANGE: - $subject = $this->getStandardMailSubject(e('Assignee change'), $event_data); - break; - case TaskModel::EVENT_USER_MENTION: - case CommentModel::EVENT_USER_MENTION: - $subject = $this->getStandardMailSubject(e('Mentioned'), $event_data); - break; - case TaskModel::EVENT_OVERDUE: - $subject = e('[%s] Overdue tasks', $event_data['project_name']); - break; - default: - $subject = e('Notification'); - } - - return $subject; - } - - /** - * Get the mail subject for a given label - * - * @access private - * @param string $label Label - * @param array $data Template data + * @param string $eventName Event name + * @param array $eventData Event data * @return string */ - private function getStandardMailSubject($label, array $data) + public function getMailSubject($eventName, array $eventData) { - return sprintf('[%s][%s] %s (#%d)', $data['task']['project_name'], $label, $data['task']['title'], $data['task']['id']); + return sprintf( + '[%s] %s', + isset($eventData['project_name']) ? $eventData['project_name'] : $eventData['task']['project_name'], + $this->notificationModel->getTitleWithoutAuthor($eventName, $eventData) + ); } } diff --git a/app/Pagination/ProjectPagination.php b/app/Pagination/ProjectPagination.php new file mode 100644 index 00000000..8f1fa87c --- /dev/null +++ b/app/Pagination/ProjectPagination.php @@ -0,0 +1,35 @@ +<?php + +namespace Kanboard\Pagination; + +use Kanboard\Core\Base; +use Kanboard\Core\Paginator; +use Kanboard\Model\ProjectModel; + +/** + * Class ProjectPagination + * + * @package Kanboard\Pagination + * @author Frederic Guillot + */ +class ProjectPagination extends Base +{ + /** + * Get dashboard pagination + * + * @access public + * @param integer $user_id + * @param string $method + * @param integer $max + * @return Paginator + */ + public function getDashboardPaginator($user_id, $method, $max) + { + return $this->paginator + ->setUrl('DashboardController', $method, array('pagination' => 'projects', 'user_id' => $user_id)) + ->setMax($max) + ->setOrder(ProjectModel::TABLE.'.name') + ->setQuery($this->projectModel->getQueryColumnStats($this->projectPermissionModel->getActiveProjectIds($user_id))) + ->calculateOnlyIf($this->request->getStringParam('pagination') === 'projects'); + } +} diff --git a/app/Pagination/SubtaskPagination.php b/app/Pagination/SubtaskPagination.php new file mode 100644 index 00000000..c55d0fb4 --- /dev/null +++ b/app/Pagination/SubtaskPagination.php @@ -0,0 +1,39 @@ +<?php + +namespace Kanboard\Pagination; + +use Kanboard\Core\Base; +use Kanboard\Core\Paginator; +use Kanboard\Model\SubtaskModel; +use Kanboard\Model\TaskModel; + +/** + * Class SubtaskPagination + * + * @package Kanboard\Pagination + * @author Frederic Guillot + */ +class SubtaskPagination extends Base +{ + /** + * Get dashboard pagination + * + * @access public + * @param integer $user_id + * @param string $method + * @param integer $max + * @return Paginator + */ + public function getDashboardPaginator($user_id, $method, $max) + { + $query = $this->subtaskModel->getUserQuery($user_id, array(SubtaskModel::STATUS_TODO, SubtaskModel::STATUS_INPROGRESS)); + $this->hook->reference('pagination:dashboard:subtask:query', $query); + + return $this->paginator + ->setUrl('DashboardController', $method, array('pagination' => 'subtasks', 'user_id' => $user_id)) + ->setMax($max) + ->setOrder(TaskModel::TABLE.'.id') + ->setQuery($query) + ->calculateOnlyIf($this->request->getStringParam('pagination') === 'subtasks'); + } +} diff --git a/app/Pagination/TaskPagination.php b/app/Pagination/TaskPagination.php new file mode 100644 index 00000000..5fe986e7 --- /dev/null +++ b/app/Pagination/TaskPagination.php @@ -0,0 +1,38 @@ +<?php + +namespace Kanboard\Pagination; + +use Kanboard\Core\Base; +use Kanboard\Core\Paginator; +use Kanboard\Model\TaskModel; + +/** + * Class TaskPagination + * + * @package Kanboard\Pagination + * @author Frederic Guillot + */ +class TaskPagination extends Base +{ + /** + * Get dashboard pagination + * + * @access public + * @param integer $user_id + * @param string $method + * @param integer $max + * @return Paginator + */ + public function getDashboardPaginator($user_id, $method, $max) + { + $query = $this->taskFinderModel->getUserQuery($user_id); + $this->hook->reference('pagination:dashboard:task:query', $query); + + return $this->paginator + ->setUrl('DashboardController', $method, array('pagination' => 'tasks', 'user_id' => $user_id)) + ->setMax($max) + ->setOrder(TaskModel::TABLE.'.id') + ->setQuery($query) + ->calculateOnlyIf($this->request->getStringParam('pagination') === 'tasks'); + } +} diff --git a/app/Pagination/UserPagination.php b/app/Pagination/UserPagination.php new file mode 100644 index 00000000..430b7d2f --- /dev/null +++ b/app/Pagination/UserPagination.php @@ -0,0 +1,32 @@ +<?php + +namespace Kanboard\Pagination; + +use Kanboard\Core\Base; +use Kanboard\Core\Paginator; +use Kanboard\Model\UserModel; + +/** + * Class UserPagination + * + * @package Kanboard\Pagination + * @author Frederic Guillot + */ +class UserPagination extends Base +{ + /** + * Get user listing paginator + * + * @access public + * @return Paginator + */ + public function getListingPaginator() + { + return $this->paginator + ->setUrl('UserListController', 'show') + ->setMax(30) + ->setOrder(UserModel::TABLE.'.username') + ->setQuery($this->userModel->getQuery()) + ->calculate(); + } +} diff --git a/app/Schema/Mysql.php b/app/Schema/Mysql.php index 934b063f..99fed66f 100644 --- a/app/Schema/Mysql.php +++ b/app/Schema/Mysql.php @@ -6,7 +6,35 @@ use PDO; use Kanboard\Core\Security\Token; use Kanboard\Core\Security\Role; -const VERSION = 110; +const VERSION = 112; + +function version_112(PDO $pdo) +{ + $pdo->exec('ALTER TABLE columns ADD COLUMN hide_in_dashboard INT DEFAULT 0 NOT NULL'); +} + +function version_111(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE tags ( + id INT NOT NULL AUTO_INCREMENT, + name VARCHAR(255) NOT NULL, + project_id INT NOT NULL, + UNIQUE(project_id, name), + PRIMARY KEY(id) + ) ENGINE=InnoDB CHARSET=utf8 + "); + + $pdo->exec(" + CREATE TABLE task_has_tags ( + task_id INT NOT NULL, + tag_id INT NOT NULL, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE, + FOREIGN KEY(tag_id) REFERENCES tags(id) ON DELETE CASCADE, + UNIQUE(tag_id, task_id) + ) ENGINE=InnoDB CHARSET=utf8 + "); +} function version_110(PDO $pdo) { diff --git a/app/Schema/Postgres.php b/app/Schema/Postgres.php index 3ef49498..b982bcae 100644 --- a/app/Schema/Postgres.php +++ b/app/Schema/Postgres.php @@ -6,7 +6,34 @@ use PDO; use Kanboard\Core\Security\Token; use Kanboard\Core\Security\Role; -const VERSION = 89; +const VERSION = 91; + +function version_91(PDO $pdo) +{ + $pdo->exec("ALTER TABLE columns ADD COLUMN hide_in_dashboard BOOLEAN DEFAULT '0'"); +} + +function version_90(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE tags ( + id SERIAL PRIMARY KEY, + name VARCHAR(255) NOT NULL, + project_id INTEGER NOT NULL, + UNIQUE(project_id, name) + ) + "); + + $pdo->exec(" + CREATE TABLE task_has_tags ( + task_id INTEGER NOT NULL, + tag_id INTEGER NOT NULL, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE, + FOREIGN KEY(tag_id) REFERENCES tags(id) ON DELETE CASCADE, + UNIQUE(tag_id, task_id) + ) + "); +} function version_89(PDO $pdo) { diff --git a/app/Schema/Sql/mysql.sql b/app/Schema/Sql/mysql.sql index 92ca3686..8d494dcf 100644 --- a/app/Schema/Sql/mysql.sql +++ b/app/Schema/Sql/mysql.sql @@ -45,6 +45,7 @@ CREATE TABLE `columns` ( `project_id` int(11) NOT NULL, `task_limit` int(11) DEFAULT '0', `description` text, + `hide_in_dashboard` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `idx_title_project` (`title`,`project_id`), KEY `columns_project_idx` (`project_id`), @@ -414,6 +415,17 @@ CREATE TABLE `swimlanes` ( CONSTRAINT `swimlanes_ibfk_1` FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `tags`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `tags` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL, + `project_id` int(11) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `project_id` (`project_id`,`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +/*!40101 SET character_set_client = @saved_cs_client */; DROP TABLE IF EXISTS `task_has_external_links`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; @@ -479,6 +491,18 @@ CREATE TABLE `task_has_metadata` ( CONSTRAINT `task_has_metadata_ibfk_1` FOREIGN KEY (`task_id`) REFERENCES `tasks` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `task_has_tags`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `task_has_tags` ( + `task_id` int(11) NOT NULL, + `tag_id` int(11) NOT NULL, + UNIQUE KEY `tag_id` (`tag_id`,`task_id`), + KEY `task_id` (`task_id`), + CONSTRAINT `task_has_tags_ibfk_1` FOREIGN KEY (`task_id`) REFERENCES `tasks` (`id`) ON DELETE CASCADE, + CONSTRAINT `task_has_tags_ibfk_2` FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +/*!40101 SET character_set_client = @saved_cs_client */; DROP TABLE IF EXISTS `tasks`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; @@ -647,7 +671,7 @@ CREATE TABLE `users` ( LOCK TABLES `settings` WRITE; /*!40000 ALTER TABLE `settings` DISABLE KEYS */; -INSERT INTO `settings` VALUES ('api_token','e8a7a983f25efa80e203d44a832c9570a5083d3fefa91366989c00e931d0',0,0),('application_currency','USD',0,0),('application_date_format','m/d/Y',0,0),('application_language','en_US',0,0),('application_stylesheet','',0,0),('application_timezone','UTC',0,0),('application_url','',0,0),('board_columns','',0,0),('board_highlight_period','172800',0,0),('board_private_refresh_interval','10',0,0),('board_public_refresh_interval','60',0,0),('calendar_project_tasks','date_started',0,0),('calendar_user_subtasks_time_tracking','0',0,0),('calendar_user_tasks','date_started',0,0),('cfd_include_closed_tasks','1',0,0),('default_color','yellow',0,0),('integration_gravatar','0',0,0),('password_reset','1',0,0),('project_categories','',0,0),('subtask_restriction','0',0,0),('subtask_time_tracking','1',0,0),('webhook_token','296892f9c821909a92df539b028fdb384e47c9f7a34a8f9cad598e0edbba',0,0),('webhook_url','',0,0); +INSERT INTO `settings` VALUES ('api_token','4064ef3d26efa9a0ff78fa7067d8bb9d99323455128edd89e9dc7c53ed76',0,0),('application_currency','USD',0,0),('application_date_format','m/d/Y',0,0),('application_language','en_US',0,0),('application_stylesheet','',0,0),('application_timezone','UTC',0,0),('application_url','',0,0),('board_columns','',0,0),('board_highlight_period','172800',0,0),('board_private_refresh_interval','10',0,0),('board_public_refresh_interval','60',0,0),('calendar_project_tasks','date_started',0,0),('calendar_user_subtasks_time_tracking','0',0,0),('calendar_user_tasks','date_started',0,0),('cfd_include_closed_tasks','1',0,0),('default_color','yellow',0,0),('integration_gravatar','0',0,0),('password_reset','1',0,0),('project_categories','',0,0),('subtask_restriction','0',0,0),('subtask_time_tracking','1',0,0),('webhook_token','c8f53c0bcd8aead902ad04f180ffafd7889b9c0062c2d510e2297ef543b8',0,0),('webhook_url','',0,0); /*!40000 ALTER TABLE `settings` ENABLE KEYS */; UNLOCK TABLES; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; @@ -676,4 +700,4 @@ UNLOCK TABLES; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; -INSERT INTO users (username, password, role) VALUES ('admin', '$2y$10$kliMGeKgDYtx9Igek9jGDu0eZM.KXivgzvqtnMuWMkjvZiIc.8p8S', 'app-admin');INSERT INTO schema_version VALUES ('110'); +INSERT INTO users (username, password, role) VALUES ('admin', '$2y$10$yUJ9QnhG.f47yO.YvWKo3eMAHULukpluDNTOF9.Z7QQg0vOfFRB6u', 'app-admin');INSERT INTO schema_version VALUES ('112'); diff --git a/app/Schema/Sql/postgres.sql b/app/Schema/Sql/postgres.sql index 6c17c1b1..0add9c91 100644 --- a/app/Schema/Sql/postgres.sql +++ b/app/Schema/Sql/postgres.sql @@ -98,7 +98,8 @@ CREATE TABLE "columns" ( "position" integer, "project_id" integer NOT NULL, "task_limit" integer DEFAULT 0, - "description" "text" + "description" "text", + "hide_in_dashboard" boolean DEFAULT false ); @@ -740,6 +741,36 @@ ALTER SEQUENCE "swimlanes_id_seq" OWNED BY "swimlanes"."id"; -- +-- Name: tags; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "tags" ( + "id" integer NOT NULL, + "name" character varying(255) NOT NULL, + "project_id" integer NOT NULL +); + + +-- +-- Name: tags_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "tags_id_seq" + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: tags_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "tags_id_seq" OWNED BY "tags"."id"; + + +-- -- Name: task_has_external_links; Type: TABLE; Schema: public; Owner: - -- @@ -874,6 +905,16 @@ ALTER SEQUENCE "task_has_subtasks_id_seq" OWNED BY "subtasks"."id"; -- +-- Name: task_has_tags; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "task_has_tags" ( + "task_id" integer NOT NULL, + "tag_id" integer NOT NULL +); + + +-- -- Name: tasks; Type: TABLE; Schema: public; Owner: - -- @@ -1236,6 +1277,13 @@ ALTER TABLE ONLY "swimlanes" ALTER COLUMN "id" SET DEFAULT "nextval"('"swimlanes -- Name: id; Type: DEFAULT; Schema: public; Owner: - -- +ALTER TABLE ONLY "tags" ALTER COLUMN "id" SET DEFAULT "nextval"('"tags_id_seq"'::"regclass"); + + +-- +-- Name: id; Type: DEFAULT; Schema: public; Owner: - +-- + ALTER TABLE ONLY "task_has_external_links" ALTER COLUMN "id" SET DEFAULT "nextval"('"task_has_external_links_id_seq"'::"regclass"); @@ -1545,6 +1593,22 @@ ALTER TABLE ONLY "swimlanes" -- +-- Name: tags_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "tags" + ADD CONSTRAINT "tags_pkey" PRIMARY KEY ("id"); + + +-- +-- Name: tags_project_id_name_key; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "tags" + ADD CONSTRAINT "tags_project_id_name_key" UNIQUE ("project_id", "name"); + + +-- -- Name: task_has_external_links_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -1585,6 +1649,14 @@ ALTER TABLE ONLY "subtasks" -- +-- Name: task_has_tags_tag_id_task_id_key; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "task_has_tags" + ADD CONSTRAINT "task_has_tags_tag_id_task_id_key" UNIQUE ("tag_id", "task_id"); + + +-- -- Name: tasks_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -2031,6 +2103,22 @@ ALTER TABLE ONLY "subtasks" -- +-- Name: task_has_tags_tag_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "task_has_tags" + ADD CONSTRAINT "task_has_tags_tag_id_fkey" FOREIGN KEY ("tag_id") REFERENCES "tags"("id") ON DELETE CASCADE; + + +-- +-- Name: task_has_tags_task_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "task_has_tags" + ADD CONSTRAINT "task_has_tags_task_id_fkey" FOREIGN KEY ("task_id") REFERENCES "tasks"("id") ON DELETE CASCADE; + + +-- -- Name: tasks_column_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- @@ -2155,8 +2243,8 @@ INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('board_high INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('board_public_refresh_interval', '60', 0, 0); INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('board_private_refresh_interval', '10', 0, 0); INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('board_columns', '', 0, 0); -INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('webhook_token', '85b9f242e49f4c50176591a2f9b812c626384b89ff985a02068455a5be07', 0, 0); -INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('api_token', '207d1aaeb9d6d5c01f9ef1e6d61baca86c4c66fdd0b95e76b5c5953681e4', 0, 0); +INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('webhook_token', 'c9a7c2a4523f1724b2ca047c5685f8e2b26bba47eb69baf4f22d5d50d837', 0, 0); +INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('api_token', 'c57a6cb1789269547b616454e4e2f06d3de0514f83baf8fa5b5a8af44a08', 0, 0); INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('application_language', 'en_US', 0, 0); INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('application_timezone', 'UTC', 0, 0); INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('application_url', '', 0, 0); @@ -2225,4 +2313,4 @@ SELECT pg_catalog.setval('links_id_seq', 11, true); -- PostgreSQL database dump complete -- -INSERT INTO users (username, password, role) VALUES ('admin', '$2y$10$kliMGeKgDYtx9Igek9jGDu0eZM.KXivgzvqtnMuWMkjvZiIc.8p8S', 'app-admin');INSERT INTO schema_version VALUES ('89'); +INSERT INTO users (username, password, role) VALUES ('admin', '$2y$10$yUJ9QnhG.f47yO.YvWKo3eMAHULukpluDNTOF9.Z7QQg0vOfFRB6u', 'app-admin');INSERT INTO schema_version VALUES ('91'); diff --git a/app/Schema/Sqlite.php b/app/Schema/Sqlite.php index 9ded7ed9..2a7735ee 100644 --- a/app/Schema/Sqlite.php +++ b/app/Schema/Sqlite.php @@ -6,7 +6,34 @@ use Kanboard\Core\Security\Token; use Kanboard\Core\Security\Role; use PDO; -const VERSION = 101; +const VERSION = 103; + +function version_103(PDO $pdo) +{ + $pdo->exec("ALTER TABLE columns ADD COLUMN hide_in_dashboard INTEGER DEFAULT 0 NOT NULL"); +} + +function version_102(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE tags ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + project_id INTEGER NOT NULL, + UNIQUE(project_id, name) + ) + "); + + $pdo->exec(" + CREATE TABLE task_has_tags ( + task_id INTEGER NOT NULL, + tag_id INTEGER NOT NULL, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE, + FOREIGN KEY(tag_id) REFERENCES tags(id) ON DELETE CASCADE, + UNIQUE(tag_id, task_id) + ) + "); +} function version_101(PDO $pdo) { diff --git a/app/ServiceProvider/ActionProvider.php b/app/ServiceProvider/ActionProvider.php index 34202052..946fbf41 100644 --- a/app/ServiceProvider/ActionProvider.php +++ b/app/ServiceProvider/ActionProvider.php @@ -3,6 +3,9 @@ namespace Kanboard\ServiceProvider; use Kanboard\Action\TaskAssignColorPriority; +use Kanboard\Action\TaskAssignDueDateOnCreation; +use Kanboard\Action\TaskMoveColumnClosed; +use Kanboard\Action\TaskMoveColumnNotMovedPeriod; use Pimple\Container; use Pimple\ServiceProviderInterface; use Kanboard\Core\Action\ActionManager; @@ -32,6 +35,7 @@ use Kanboard\Action\TaskMoveColumnUnAssigned; use Kanboard\Action\TaskOpen; use Kanboard\Action\TaskUpdateStartDate; use Kanboard\Action\TaskCloseNoActivity; +use Kanboard\Action\TaskCloseNoActivityColumn; /** * Action Provider @@ -68,6 +72,7 @@ class ActionProvider implements ServiceProviderInterface $container['actionManager']->register(new TaskClose($container)); $container['actionManager']->register(new TaskCloseColumn($container)); $container['actionManager']->register(new TaskCloseNoActivity($container)); + $container['actionManager']->register(new TaskCloseNoActivityColumn($container)); $container['actionManager']->register(new TaskCreation($container)); $container['actionManager']->register(new TaskDuplicateAnotherProject($container)); $container['actionManager']->register(new TaskEmail($container)); @@ -75,9 +80,12 @@ class ActionProvider implements ServiceProviderInterface $container['actionManager']->register(new TaskMoveAnotherProject($container)); $container['actionManager']->register(new TaskMoveColumnAssigned($container)); $container['actionManager']->register(new TaskMoveColumnCategoryChange($container)); + $container['actionManager']->register(new TaskMoveColumnClosed($container)); + $container['actionManager']->register(new TaskMoveColumnNotMovedPeriod($container)); $container['actionManager']->register(new TaskMoveColumnUnAssigned($container)); $container['actionManager']->register(new TaskOpen($container)); $container['actionManager']->register(new TaskUpdateStartDate($container)); + $container['actionManager']->register(new TaskAssignDueDateOnCreation($container)); return $container; } diff --git a/app/ServiceProvider/ApiProvider.php b/app/ServiceProvider/ApiProvider.php index 93b3c7f5..d5d1f260 100644 --- a/app/ServiceProvider/ApiProvider.php +++ b/app/ServiceProvider/ApiProvider.php @@ -3,26 +3,29 @@ namespace Kanboard\ServiceProvider; use JsonRPC\Server; -use Kanboard\Api\ActionApi; -use Kanboard\Api\AppApi; -use Kanboard\Api\BoardApi; -use Kanboard\Api\CategoryApi; -use Kanboard\Api\ColumnApi; -use Kanboard\Api\CommentApi; -use Kanboard\Api\FileApi; -use Kanboard\Api\GroupApi; -use Kanboard\Api\GroupMemberApi; -use Kanboard\Api\LinkApi; -use Kanboard\Api\MeApi; -use Kanboard\Api\Middleware\AuthenticationApiMiddleware; -use Kanboard\Api\ProjectApi; -use Kanboard\Api\ProjectPermissionApi; -use Kanboard\Api\SubtaskApi; -use Kanboard\Api\SubtaskTimeTrackingApi; -use Kanboard\Api\SwimlaneApi; -use Kanboard\Api\TaskApi; -use Kanboard\Api\TaskLinkApi; -use Kanboard\Api\UserApi; +use Kanboard\Api\Procedure\ActionProcedure; +use Kanboard\Api\Procedure\AppProcedure; +use Kanboard\Api\Procedure\BoardProcedure; +use Kanboard\Api\Procedure\CategoryProcedure; +use Kanboard\Api\Procedure\ColumnProcedure; +use Kanboard\Api\Procedure\CommentProcedure; +use Kanboard\Api\Procedure\ProjectFileProcedure; +use Kanboard\Api\Procedure\TaskExternalLinkProcedure; +use Kanboard\Api\Procedure\TaskFileProcedure; +use Kanboard\Api\Procedure\GroupProcedure; +use Kanboard\Api\Procedure\GroupMemberProcedure; +use Kanboard\Api\Procedure\LinkProcedure; +use Kanboard\Api\Procedure\MeProcedure; +use Kanboard\Api\Middleware\AuthenticationMiddleware; +use Kanboard\Api\Procedure\ProjectProcedure; +use Kanboard\Api\Procedure\ProjectPermissionProcedure; +use Kanboard\Api\Procedure\SubtaskProcedure; +use Kanboard\Api\Procedure\SubtaskTimeTrackingProcedure; +use Kanboard\Api\Procedure\SwimlaneProcedure; +use Kanboard\Api\Procedure\TaskMetadataProcedure; +use Kanboard\Api\Procedure\TaskProcedure; +use Kanboard\Api\Procedure\TaskLinkProcedure; +use Kanboard\Api\Procedure\UserProcedure; use Pimple\Container; use Pimple\ServiceProviderInterface; @@ -45,31 +48,35 @@ class ApiProvider implements ServiceProviderInterface $server = new Server(); $server->setAuthenticationHeader(API_AUTHENTICATION_HEADER); $server->getMiddlewareHandler() - ->withMiddleware(new AuthenticationApiMiddleware($container)) + ->withMiddleware(new AuthenticationMiddleware($container)) ; $server->getProcedureHandler() - ->withObject(new MeApi($container)) - ->withObject(new ActionApi($container)) - ->withObject(new AppApi($container)) - ->withObject(new BoardApi($container)) - ->withObject(new ColumnApi($container)) - ->withObject(new CategoryApi($container)) - ->withObject(new CommentApi($container)) - ->withObject(new FileApi($container)) - ->withObject(new LinkApi($container)) - ->withObject(new ProjectApi($container)) - ->withObject(new ProjectPermissionApi($container)) - ->withObject(new SubtaskApi($container)) - ->withObject(new SubtaskTimeTrackingApi($container)) - ->withObject(new SwimlaneApi($container)) - ->withObject(new TaskApi($container)) - ->withObject(new TaskLinkApi($container)) - ->withObject(new UserApi($container)) - ->withObject(new GroupApi($container)) - ->withObject(new GroupMemberApi($container)) + ->withObject(new MeProcedure($container)) + ->withObject(new ActionProcedure($container)) + ->withObject(new AppProcedure($container)) + ->withObject(new BoardProcedure($container)) + ->withObject(new ColumnProcedure($container)) + ->withObject(new CategoryProcedure($container)) + ->withObject(new CommentProcedure($container)) + ->withObject(new TaskFileProcedure($container)) + ->withObject(new ProjectFileProcedure($container)) + ->withObject(new LinkProcedure($container)) + ->withObject(new ProjectProcedure($container)) + ->withObject(new ProjectPermissionProcedure($container)) + ->withObject(new SubtaskProcedure($container)) + ->withObject(new SubtaskTimeTrackingProcedure($container)) + ->withObject(new SwimlaneProcedure($container)) + ->withObject(new TaskProcedure($container)) + ->withObject(new TaskLinkProcedure($container)) + ->withObject(new TaskExternalLinkProcedure($container)) + ->withObject(new TaskMetadataProcedure($container)) + ->withObject(new UserProcedure($container)) + ->withObject(new GroupProcedure($container)) + ->withObject(new GroupMemberProcedure($container)) + ->withBeforeMethod('beforeProcedure') ; - + $container['api'] = $server; return $container; } diff --git a/app/ServiceProvider/AuthenticationProvider.php b/app/ServiceProvider/AuthenticationProvider.php index 2fad8a3a..978bc05b 100644 --- a/app/ServiceProvider/AuthenticationProvider.php +++ b/app/ServiceProvider/AuthenticationProvider.php @@ -46,9 +46,13 @@ class AuthenticationProvider implements ServiceProviderInterface $container['projectAccessMap'] = $this->getProjectAccessMap(); $container['applicationAccessMap'] = $this->getApplicationAccessMap(); + $container['apiAccessMap'] = $this->getApiAccessMap(); + $container['apiProjectAccessMap'] = $this->getApiProjectAccessMap(); $container['projectAuthorization'] = new Authorization($container['projectAccessMap']); $container['applicationAuthorization'] = new Authorization($container['applicationAccessMap']); + $container['apiAuthorization'] = new Authorization($container['apiAccessMap']); + $container['apiProjectAuthorization'] = new Authorization($container['apiProjectAccessMap']); return $container; } @@ -88,6 +92,7 @@ class AuthenticationProvider implements ServiceProviderInterface $acl->add('ProjectFileController', '*', Role::PROJECT_MEMBER); $acl->add('ProjectUserOverviewController', '*', Role::PROJECT_MANAGER); $acl->add('ProjectStatusController', '*', Role::PROJECT_MANAGER); + $acl->add('ProjectTagController', '*', Role::PROJECT_MANAGER); $acl->add('SubtaskController', '*', Role::PROJECT_MEMBER); $acl->add('SubtaskRestrictionController', '*', Role::PROJECT_MEMBER); $acl->add('SubtaskStatusController', '*', Role::PROJECT_MEMBER); @@ -131,6 +136,7 @@ class AuthenticationProvider implements ServiceProviderInterface $acl->add('AvatarFileController', 'show', Role::APP_PUBLIC); $acl->add('ConfigController', '*', Role::APP_ADMIN); + $acl->add('TagController', '*', Role::APP_ADMIN); $acl->add('PluginController', '*', Role::APP_ADMIN); $acl->add('CurrencyController', '*', Role::APP_ADMIN); $acl->add('ProjectGanttController', '*', Role::APP_MANAGER); @@ -149,4 +155,59 @@ class AuthenticationProvider implements ServiceProviderInterface return $acl; } + + /** + * Get ACL for the API + * + * @access public + * @return AccessMap + */ + public function getApiAccessMap() + { + $acl = new AccessMap; + $acl->setDefaultRole(Role::APP_USER); + $acl->setRoleHierarchy(Role::APP_ADMIN, array(Role::APP_MANAGER, Role::APP_USER, Role::APP_PUBLIC)); + $acl->setRoleHierarchy(Role::APP_MANAGER, array(Role::APP_USER, Role::APP_PUBLIC)); + + $acl->add('UserProcedure', '*', Role::APP_ADMIN); + $acl->add('GroupMemberProcedure', '*', Role::APP_ADMIN); + $acl->add('GroupProcedure', '*', Role::APP_ADMIN); + $acl->add('LinkProcedure', '*', Role::APP_ADMIN); + $acl->add('TaskProcedure', array('getOverdueTasks'), Role::APP_ADMIN); + $acl->add('ProjectProcedure', array('getAllProjects'), Role::APP_ADMIN); + $acl->add('ProjectProcedure', array('createProject'), Role::APP_MANAGER); + + return $acl; + } + + /** + * Get ACL for the API + * + * @access public + * @return AccessMap + */ + public function getApiProjectAccessMap() + { + $acl = new AccessMap; + $acl->setDefaultRole(Role::PROJECT_VIEWER); + $acl->setRoleHierarchy(Role::PROJECT_MANAGER, array(Role::PROJECT_MEMBER, Role::PROJECT_VIEWER)); + $acl->setRoleHierarchy(Role::PROJECT_MEMBER, array(Role::PROJECT_VIEWER)); + + $acl->add('ActionProcedure', array('removeAction', 'getActions', 'createAction'), Role::PROJECT_MANAGER); + $acl->add('CategoryProcedure', '*', Role::PROJECT_MANAGER); + $acl->add('ColumnProcedure', '*', Role::PROJECT_MANAGER); + $acl->add('CommentProcedure', array('removeComment', 'createComment', 'updateComment'), Role::PROJECT_MEMBER); + $acl->add('ProjectPermissionProcedure', '*', Role::PROJECT_MANAGER); + $acl->add('ProjectProcedure', array('updateProject', 'removeProject', 'enableProject', 'disableProject', 'enableProjectPublicAccess', 'disableProjectPublicAccess'), Role::PROJECT_MANAGER); + $acl->add('SubtaskProcedure', '*', Role::PROJECT_MEMBER); + $acl->add('SubtaskTimeTrackingProcedure', '*', Role::PROJECT_MEMBER); + $acl->add('SwimlaneProcedure', '*', Role::PROJECT_MANAGER); + $acl->add('ProjectFileProcedure', '*', Role::PROJECT_MEMBER); + $acl->add('TaskFileProcedure', '*', Role::PROJECT_MEMBER); + $acl->add('TaskLinkProcedure', '*', Role::PROJECT_MEMBER); + $acl->add('TaskExternalLinkProcedure', array('createExternalTaskLink', 'updateExternalTaskLink', 'removeExternalTaskLink'), Role::PROJECT_MEMBER); + $acl->add('TaskProcedure', '*', Role::PROJECT_MEMBER); + + return $acl; + } } diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php index 3e6efb02..aab41c74 100644 --- a/app/ServiceProvider/ClassProvider.php +++ b/app/ServiceProvider/ClassProvider.php @@ -55,15 +55,25 @@ class ClassProvider implements ServiceProviderInterface 'ProjectNotificationModel', 'ProjectMetadataModel', 'ProjectGroupRoleModel', + 'ProjectTaskDuplicationModel', + 'ProjectTaskPriorityModel', 'ProjectUserRoleModel', 'RememberMeSessionModel', 'SubtaskModel', + 'SubtaskPositionModel', + 'SubtaskStatusModel', + 'SubtaskTaskConversionModel', 'SubtaskTimeTrackingModel', 'SwimlaneModel', + 'TagDuplicationModel', + 'TagModel', 'TaskModel', 'TaskAnalyticModel', 'TaskCreationModel', 'TaskDuplicationModel', + 'TaskProjectDuplicationModel', + 'TaskProjectMoveModel', + 'TaskRecurrenceModel', 'TaskExternalLinkModel', 'TaskFinderModel', 'TaskFileModel', @@ -71,6 +81,7 @@ class ClassProvider implements ServiceProviderInterface 'TaskModificationModel', 'TaskPositionModel', 'TaskStatusModel', + 'TaskTagModel', 'TaskMetadataModel', 'TimezoneModel', 'TransitionModel', @@ -97,8 +108,9 @@ class ClassProvider implements ServiceProviderInterface 'ProjectValidator', 'SubtaskValidator', 'SwimlaneValidator', - 'TaskValidator', + 'TagValidator', 'TaskLinkValidator', + 'TaskValidator', 'UserValidator', ), 'Import' => array( @@ -110,6 +122,12 @@ class ClassProvider implements ServiceProviderInterface 'TaskExport', 'TransitionExport', ), + 'Pagination' => array( + 'TaskPagination', + 'SubtaskPagination', + 'ProjectPagination', + 'UserPagination', + ), 'Core' => array( 'DateParser', 'Lexer', diff --git a/app/ServiceProvider/EventDispatcherProvider.php b/app/ServiceProvider/EventDispatcherProvider.php index 57543fe4..ebf42cbf 100644 --- a/app/ServiceProvider/EventDispatcherProvider.php +++ b/app/ServiceProvider/EventDispatcherProvider.php @@ -11,7 +11,6 @@ use Kanboard\Subscriber\BootstrapSubscriber; use Kanboard\Subscriber\NotificationSubscriber; use Kanboard\Subscriber\ProjectDailySummarySubscriber; use Kanboard\Subscriber\ProjectModificationDateSubscriber; -use Kanboard\Subscriber\SubtaskTimeTrackingSubscriber; use Kanboard\Subscriber\TransitionSubscriber; use Kanboard\Subscriber\RecurringTaskSubscriber; @@ -31,7 +30,6 @@ class EventDispatcherProvider implements ServiceProviderInterface $container['dispatcher']->addSubscriber(new ProjectDailySummarySubscriber($container)); $container['dispatcher']->addSubscriber(new ProjectModificationDateSubscriber($container)); $container['dispatcher']->addSubscriber(new NotificationSubscriber($container)); - $container['dispatcher']->addSubscriber(new SubtaskTimeTrackingSubscriber($container)); $container['dispatcher']->addSubscriber(new TransitionSubscriber($container)); $container['dispatcher']->addSubscriber(new RecurringTaskSubscriber($container)); diff --git a/app/ServiceProvider/FilterProvider.php b/app/ServiceProvider/FilterProvider.php index cdef9ed8..443d3588 100644 --- a/app/ServiceProvider/FilterProvider.php +++ b/app/ServiceProvider/FilterProvider.php @@ -21,11 +21,14 @@ use Kanboard\Filter\TaskDueDateFilter; use Kanboard\Filter\TaskIdFilter; use Kanboard\Filter\TaskLinkFilter; use Kanboard\Filter\TaskModificationDateFilter; +use Kanboard\Filter\TaskMovedDateFilter; +use Kanboard\Filter\TaskPriorityFilter; use Kanboard\Filter\TaskProjectFilter; use Kanboard\Filter\TaskReferenceFilter; use Kanboard\Filter\TaskStatusFilter; use Kanboard\Filter\TaskSubtaskAssigneeFilter; use Kanboard\Filter\TaskSwimlaneFilter; +use Kanboard\Filter\TaskTagFilter; use Kanboard\Filter\TaskTitleFilter; use Kanboard\Model\ProjectModel; use Kanboard\Model\ProjectGroupRoleModel; @@ -136,6 +139,7 @@ class FilterProvider implements ServiceProviderInterface ->withFilter(TaskColorFilter::getInstance() ->setColorModel($c['colorModel']) ) + ->withFilter(new TaskPriorityFilter()) ->withFilter(new TaskColumnFilter()) ->withFilter(new TaskCommentFilter()) ->withFilter(TaskCreationDateFilter::getInstance() @@ -155,6 +159,9 @@ class FilterProvider implements ServiceProviderInterface ->withFilter(TaskModificationDateFilter::getInstance() ->setDateParser($c['dateParser']) ) + ->withFilter(TaskMovedDateFilter::getInstance() + ->setDateParser($c['dateParser']) + ) ->withFilter(new TaskProjectFilter()) ->withFilter(new TaskReferenceFilter()) ->withFilter(new TaskStatusFilter()) @@ -163,6 +170,9 @@ class FilterProvider implements ServiceProviderInterface ->setDatabase($c['db']) ) ->withFilter(new TaskSwimlaneFilter()) + ->withFilter(TaskTagFilter::getInstance() + ->setDatabase($c['db']) + ) ->withFilter(new TaskTitleFilter(), true) ; diff --git a/app/ServiceProvider/JobProvider.php b/app/ServiceProvider/JobProvider.php new file mode 100644 index 00000000..2194b11c --- /dev/null +++ b/app/ServiceProvider/JobProvider.php @@ -0,0 +1,67 @@ +<?php + +namespace Kanboard\ServiceProvider; + +use Kanboard\Job\CommentEventJob; +use Kanboard\Job\NotificationJob; +use Kanboard\Job\ProjectFileEventJob; +use Kanboard\Job\ProjectMetricJob; +use Kanboard\Job\SubtaskEventJob; +use Kanboard\Job\TaskEventJob; +use Kanboard\Job\TaskFileEventJob; +use Kanboard\Job\TaskLinkEventJob; +use Pimple\Container; +use Pimple\ServiceProviderInterface; + +/** + * Class JobProvider + * + * @package Kanboard\ServiceProvider + * @author Frederic Guillot + */ +class JobProvider implements ServiceProviderInterface +{ + /** + * Register providers + * + * @access public + * @param \Pimple\Container $container + * @return \Pimple\Container + */ + public function register(Container $container) + { + $container['commentEventJob'] = $container->factory(function ($c) { + return new CommentEventJob($c); + }); + + $container['subtaskEventJob'] = $container->factory(function ($c) { + return new SubtaskEventJob($c); + }); + + $container['taskEventJob'] = $container->factory(function ($c) { + return new TaskEventJob($c); + }); + + $container['taskFileEventJob'] = $container->factory(function ($c) { + return new TaskFileEventJob($c); + }); + + $container['taskLinkEventJob'] = $container->factory(function ($c) { + return new TaskLinkEventJob($c); + }); + + $container['projectFileEventJob'] = $container->factory(function ($c) { + return new ProjectFileEventJob($c); + }); + + $container['notificationJob'] = $container->factory(function ($c) { + return new NotificationJob($c); + }); + + $container['projectMetricJob'] = $container->factory(function ($c) { + return new ProjectMetricJob($c); + }); + + return $container; + } +} diff --git a/app/ServiceProvider/QueueProvider.php b/app/ServiceProvider/QueueProvider.php index 946b436a..570f2e77 100644 --- a/app/ServiceProvider/QueueProvider.php +++ b/app/ServiceProvider/QueueProvider.php @@ -15,9 +15,11 @@ use Pimple\ServiceProviderInterface; class QueueProvider implements ServiceProviderInterface { /** - * Registers services on the given container. + * Register providers * - * @param Container $container + * @access public + * @param \Pimple\Container $container + * @return \Pimple\Container */ public function register(Container $container) { diff --git a/app/ServiceProvider/RouteProvider.php b/app/ServiceProvider/RouteProvider.php index 3d1391df..8801e3d0 100644 --- a/app/ServiceProvider/RouteProvider.php +++ b/app/ServiceProvider/RouteProvider.php @@ -59,6 +59,7 @@ class RouteProvider implements ServiceProviderInterface $container['route']->addRoute('project/:project_id/duplicate', 'ProjectViewController', 'duplicate'); $container['route']->addRoute('project/:project_id/permissions', 'ProjectPermissionController', 'index'); $container['route']->addRoute('project/:project_id/activity', 'ActivityController', 'project'); + $container['route']->addRoute('project/:project_id/tags', 'ProjectTagController', 'index'); // Project Overview $container['route']->addRoute('project/:project_id/overview', 'ProjectOverviewController', 'show'); @@ -174,6 +175,7 @@ class RouteProvider implements ServiceProviderInterface $container['route']->addRoute('settings/api', 'ConfigController', 'api'); $container['route']->addRoute('settings/links', 'LinkController', 'index'); $container['route']->addRoute('settings/currencies', 'CurrencyController', 'index'); + $container['route']->addRoute('settings/tags', 'TagController', 'index'); // Plugins $container['route']->addRoute('extensions', 'PluginController', 'show'); diff --git a/app/Subscriber/BaseSubscriber.php b/app/Subscriber/BaseSubscriber.php index fdea29f6..92441962 100644 --- a/app/Subscriber/BaseSubscriber.php +++ b/app/Subscriber/BaseSubscriber.php @@ -12,28 +12,4 @@ use Kanboard\Core\Base; */ class BaseSubscriber extends Base { - /** - * Method called - * - * @access private - * @var array - */ - private $called = array(); - - /** - * Check if a listener has been executed - * - * @access public - * @param string $key - * @return boolean - */ - public function isExecuted($key = '') - { - if (isset($this->called[$key])) { - return true; - } - - $this->called[$key] = true; - return false; - } } diff --git a/app/Subscriber/BootstrapSubscriber.php b/app/Subscriber/BootstrapSubscriber.php index 7d12e9ae..3618f30f 100644 --- a/app/Subscriber/BootstrapSubscriber.php +++ b/app/Subscriber/BootstrapSubscriber.php @@ -21,7 +21,7 @@ class BootstrapSubscriber extends BaseSubscriber implements EventSubscriberInter $this->actionManager->attachEvents(); if ($this->userSession->isLogged()) { - $this->sessionStorage->hasSubtaskInProgress = $this->subtaskModel->hasSubtaskInProgress($this->userSession->getId()); + $this->sessionStorage->hasSubtaskInProgress = $this->subtaskStatusModel->hasSubtaskInProgress($this->userSession->getId()); } } diff --git a/app/Subscriber/NotificationSubscriber.php b/app/Subscriber/NotificationSubscriber.php index db11e585..7cc68b26 100644 --- a/app/Subscriber/NotificationSubscriber.php +++ b/app/Subscriber/NotificationSubscriber.php @@ -3,7 +3,7 @@ namespace Kanboard\Subscriber; use Kanboard\Event\GenericEvent; -use Kanboard\Job\NotificationJob; +use Kanboard\Model\TaskLinkModel; use Kanboard\Model\TaskModel; use Kanboard\Model\CommentModel; use Kanboard\Model\SubtaskModel; @@ -26,21 +26,20 @@ class NotificationSubscriber extends BaseSubscriber implements EventSubscriberIn TaskModel::EVENT_ASSIGNEE_CHANGE => 'handleEvent', SubtaskModel::EVENT_CREATE => 'handleEvent', SubtaskModel::EVENT_UPDATE => 'handleEvent', + SubtaskModel::EVENT_DELETE => 'handleEvent', CommentModel::EVENT_CREATE => 'handleEvent', CommentModel::EVENT_UPDATE => 'handleEvent', + CommentModel::EVENT_DELETE => 'handleEvent', CommentModel::EVENT_USER_MENTION => 'handleEvent', TaskFileModel::EVENT_CREATE => 'handleEvent', + TaskLinkModel::EVENT_CREATE_UPDATE => 'handleEvent', + TaskLinkModel::EVENT_DELETE => 'handleEvent', ); } public function handleEvent(GenericEvent $event, $eventName) { - if (!$this->isExecuted($eventName)) { - $this->logger->debug('Subscriber executed: ' . __METHOD__); - - $this->queueManager->push(NotificationJob::getInstance($this->container) - ->withParams($event, $eventName, get_class($event)) - ); - } + $this->logger->debug('Subscriber executed: ' . __METHOD__); + $this->queueManager->push($this->notificationJob->withParams($event, $eventName)); } } diff --git a/app/Subscriber/ProjectDailySummarySubscriber.php b/app/Subscriber/ProjectDailySummarySubscriber.php index 6971a121..eaa9d468 100644 --- a/app/Subscriber/ProjectDailySummarySubscriber.php +++ b/app/Subscriber/ProjectDailySummarySubscriber.php @@ -3,7 +3,6 @@ namespace Kanboard\Subscriber; use Kanboard\Event\TaskEvent; -use Kanboard\Job\ProjectMetricJob; use Kanboard\Model\TaskModel; use Symfony\Component\EventDispatcher\EventSubscriberInterface; @@ -22,9 +21,7 @@ class ProjectDailySummarySubscriber extends BaseSubscriber implements EventSubsc public function execute(TaskEvent $event) { - if (isset($event['project_id']) && !$this->isExecuted()) { - $this->logger->debug('Subscriber executed: '.__METHOD__); - $this->queueManager->push(ProjectMetricJob::getInstance($this->container)->withParams($event['project_id'])); - } + $this->logger->debug('Subscriber executed: '.__METHOD__); + $this->queueManager->push($this->projectMetricJob->withParams($event['task']['project_id'])); } } diff --git a/app/Subscriber/ProjectModificationDateSubscriber.php b/app/Subscriber/ProjectModificationDateSubscriber.php index fee04eaa..1ffe0248 100644 --- a/app/Subscriber/ProjectModificationDateSubscriber.php +++ b/app/Subscriber/ProjectModificationDateSubscriber.php @@ -24,9 +24,7 @@ class ProjectModificationDateSubscriber extends BaseSubscriber implements EventS public function execute(GenericEvent $event) { - if (isset($event['project_id']) && !$this->isExecuted()) { - $this->logger->debug('Subscriber executed: '.__METHOD__); - $this->projectModel->updateModificationDate($event['project_id']); - } + $this->logger->debug('Subscriber executed: '.__METHOD__); + $this->projectModel->updateModificationDate($event['task']['project_id']); } } diff --git a/app/Subscriber/RecurringTaskSubscriber.php b/app/Subscriber/RecurringTaskSubscriber.php index 75b7ff76..3e2848f8 100644 --- a/app/Subscriber/RecurringTaskSubscriber.php +++ b/app/Subscriber/RecurringTaskSubscriber.php @@ -19,12 +19,13 @@ class RecurringTaskSubscriber extends BaseSubscriber implements EventSubscriberI public function onMove(TaskEvent $event) { $this->logger->debug('Subscriber executed: '.__METHOD__); + $task = $event['task']; - if ($event['recurrence_status'] == TaskModel::RECURRING_STATUS_PENDING) { - if ($event['recurrence_trigger'] == TaskModel::RECURRING_TRIGGER_FIRST_COLUMN && $this->columnModel->getFirstColumnId($event['project_id']) == $event['src_column_id']) { - $this->taskDuplicationModel->duplicateRecurringTask($event['task_id']); - } elseif ($event['recurrence_trigger'] == TaskModel::RECURRING_TRIGGER_LAST_COLUMN && $this->columnModel->getLastColumnId($event['project_id']) == $event['dst_column_id']) { - $this->taskDuplicationModel->duplicateRecurringTask($event['task_id']); + if ($task['recurrence_status'] == TaskModel::RECURRING_STATUS_PENDING) { + if ($task['recurrence_trigger'] == TaskModel::RECURRING_TRIGGER_FIRST_COLUMN && $this->columnModel->getFirstColumnId($task['project_id']) == $event['src_column_id']) { + $this->taskRecurrenceModel->duplicateRecurringTask($task['id']); + } elseif ($task['recurrence_trigger'] == TaskModel::RECURRING_TRIGGER_LAST_COLUMN && $this->columnModel->getLastColumnId($task['project_id']) == $event['dst_column_id']) { + $this->taskRecurrenceModel->duplicateRecurringTask($task['id']); } } } @@ -32,9 +33,10 @@ class RecurringTaskSubscriber extends BaseSubscriber implements EventSubscriberI public function onClose(TaskEvent $event) { $this->logger->debug('Subscriber executed: '.__METHOD__); + $task = $event['task']; - if ($event['recurrence_status'] == TaskModel::RECURRING_STATUS_PENDING && $event['recurrence_trigger'] == TaskModel::RECURRING_TRIGGER_CLOSE) { - $this->taskDuplicationModel->duplicateRecurringTask($event['task_id']); + if ($task['recurrence_status'] == TaskModel::RECURRING_STATUS_PENDING && $task['recurrence_trigger'] == TaskModel::RECURRING_TRIGGER_CLOSE) { + $this->taskRecurrenceModel->duplicateRecurringTask($event['task_id']); } } } diff --git a/app/Subscriber/SubtaskTimeTrackingSubscriber.php b/app/Subscriber/SubtaskTimeTrackingSubscriber.php deleted file mode 100644 index 7e39c126..00000000 --- a/app/Subscriber/SubtaskTimeTrackingSubscriber.php +++ /dev/null @@ -1,48 +0,0 @@ -<?php - -namespace Kanboard\Subscriber; - -use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Kanboard\Model\SubtaskModel; -use Kanboard\Event\SubtaskEvent; - -class SubtaskTimeTrackingSubscriber extends BaseSubscriber implements EventSubscriberInterface -{ - public static function getSubscribedEvents() - { - return array( - SubtaskModel::EVENT_CREATE => 'updateTaskTime', - SubtaskModel::EVENT_DELETE => 'updateTaskTime', - SubtaskModel::EVENT_UPDATE => array( - array('logStartEnd', 10), - array('updateTaskTime', 0), - ) - ); - } - - public function updateTaskTime(SubtaskEvent $event) - { - if (isset($event['task_id'])) { - $this->logger->debug('Subscriber executed: '.__METHOD__); - $this->subtaskTimeTrackingModel->updateTaskTimeTracking($event['task_id']); - } - } - - public function logStartEnd(SubtaskEvent $event) - { - if (isset($event['status']) && $this->configModel->get('subtask_time_tracking') == 1) { - $this->logger->debug('Subscriber executed: '.__METHOD__); - $subtask = $this->subtaskModel->getById($event['id']); - - if (empty($subtask['user_id'])) { - return false; - } - - if ($subtask['status'] == SubtaskModel::STATUS_INPROGRESS) { - return $this->subtaskTimeTrackingModel->logStartTime($subtask['id'], $subtask['user_id']); - } else { - return $this->subtaskTimeTrackingModel->logEndTime($subtask['id'], $subtask['user_id']); - } - } - } -} diff --git a/app/Template/action/index.php b/app/Template/action/index.php index 0a94e4f0..085ea3ad 100644 --- a/app/Template/action/index.php +++ b/app/Template/action/index.php @@ -15,7 +15,7 @@ <?php if (empty($actions)): ?> <p class="alert"><?= t('There is no action at the moment.') ?></p> <?php else: ?> - <table> + <table class="table-scrolling"> <tr> <th><?= t('Automatic actions') ?></th> <th><?= t('Action parameters') ?></th> diff --git a/app/Template/activity/task.php b/app/Template/activity/task.php index 04c64c63..39953d1a 100644 --- a/app/Template/activity/task.php +++ b/app/Template/activity/task.php @@ -1,9 +1,12 @@ -<div class="task-show-title color-<?= $task['color_id'] ?>"> - <h2><?= $this->text->e($task['title']) ?></h2> -</div> +<?= $this->render('task/details', array( + 'task' => $task, + 'tags' => $tags, + 'project' => $project, + 'editable' => false, +)) ?> <div class="page-header"> <h2><?= t('Activity stream') ?></h2> </div> -<?= $this->render('event/events', array('events' => $events)) ?>
\ No newline at end of file +<?= $this->render('event/events', array('events' => $events)) ?> diff --git a/app/Template/analytic/avg_time_columns.php b/app/Template/analytic/avg_time_columns.php index 5f6c6b35..91c269fc 100644 --- a/app/Template/analytic/avg_time_columns.php +++ b/app/Template/analytic/avg_time_columns.php @@ -9,7 +9,7 @@ <div id="chart" data-metrics='<?= json_encode($metrics, JSON_HEX_APOS) ?>' data-label="<?= t('Average time spent') ?>"></div> - <table class="table-stripped"> + <table class="table-striped"> <tr> <th><?= t('Column') ?></th> <th><?= t('Average time spent') ?></th> diff --git a/app/Template/analytic/compare_hours.php b/app/Template/analytic/compare_hours.php index 70d8d02b..c0b9cfc3 100644 --- a/app/Template/analytic/compare_hours.php +++ b/app/Template/analytic/compare_hours.php @@ -1,5 +1,5 @@ <div class="page-header"> - <h2><?= t('Compare Estimated Time vs Actual Time') ?></h2> + <h2><?= t('Estimated vs actual time') ?></h2> </div> <div class="listing"> @@ -23,7 +23,7 @@ <?php if ($paginator->isEmpty()): ?> <p class="alert"><?= t('No tasks found.') ?></p> <?php elseif (! $paginator->isEmpty()): ?> - <table class="table-fixed table-small"> + <table class="table-fixed table-small table-scrolling"> <tr> <th class="column-5"><?= $paginator->order(t('Id'), 'tasks.id') ?></th> <th><?= $paginator->order(t('Title'), 'tasks.title') ?></th> diff --git a/app/Template/analytic/sidebar.php b/app/Template/analytic/sidebar.php index de3dccf8..0f5ee101 100644 --- a/app/Template/analytic/sidebar.php +++ b/app/Template/analytic/sidebar.php @@ -1,5 +1,4 @@ <div class="sidebar"> - <h2><?= t('Reportings') ?></h2> <ul> <li <?= $this->app->checkMenuSelection('AnalyticController', 'tasks') ?>> <?= $this->url->link(t('Task distribution'), 'AnalyticController', 'tasks', array('project_id' => $project['id'])) ?> diff --git a/app/Template/analytic/tasks.php b/app/Template/analytic/tasks.php index 9e7b1fd7..4bc19784 100644 --- a/app/Template/analytic/tasks.php +++ b/app/Template/analytic/tasks.php @@ -9,7 +9,7 @@ <div id="chart" data-metrics='<?= json_encode($metrics, JSON_HEX_APOS) ?>'></div> - <table> + <table class="table-striped"> <tr> <th><?= t('Column') ?></th> <th><?= t('Number of tasks') ?></th> diff --git a/app/Template/analytic/users.php b/app/Template/analytic/users.php index 9d1d3a1e..91bec11b 100644 --- a/app/Template/analytic/users.php +++ b/app/Template/analytic/users.php @@ -9,7 +9,7 @@ <div id="chart" data-metrics='<?= json_encode($metrics, JSON_HEX_APOS) ?>'></div> - <table> + <table class="table-striped"> <tr> <th><?= t('User') ?></th> <th><?= t('Number of tasks') ?></th> diff --git a/app/Template/board/table_column.php b/app/Template/board/table_column.php index f7a9f6ad..c0b71eab 100644 --- a/app/Template/board/table_column.php +++ b/app/Template/board/table_column.php @@ -5,9 +5,9 @@ <!-- column in collapsed mode --> <div class="board-column-collapsed"> - <span class="board-column-header-task-count" title="<?= t('Show this column') ?>"> + <small class="board-column-header-task-count" title="<?= t('Show this column') ?>"> <span id="task-number-column-<?= $column['id'] ?>"><?= $column['nb_tasks'] ?></span> - </span> + </small> </div> <!-- column in expanded mode --> @@ -18,9 +18,9 @@ </div> <?php endif ?> - <?php if ($swimlane['nb_swimlanes'] > 1 && ! empty($column['nb_column_tasks'])): ?> + <?php if ($swimlane['nb_swimlanes'] > 1 && ! empty($column['column_nb_tasks'])): ?> <span title="<?= t('Total number of tasks in this column across all swimlanes') ?>" class="board-column-header-task-count"> - (<span><?= $column['nb_column_tasks'] ?></span>) + (<span><?= $column['column_nb_tasks'] ?></span>) </span> <?php endif ?> @@ -47,6 +47,7 @@ </li> <?php endif ?> <?php endif ?> + <?= $this->hook->render('template:board:column:dropdown', array('swimlane' => $swimlane, 'column' => $column)) ?> </ul> </span> <?php endif ?> diff --git a/app/Template/board/table_tasks.php b/app/Template/board/table_tasks.php index fd9ce5e7..1651f5d6 100644 --- a/app/Template/board/table_tasks.php +++ b/app/Template/board/table_tasks.php @@ -4,7 +4,8 @@ <td class=" board-column-<?= $column['id'] ?> <?= $column['task_limit'] > 0 && $column['nb_tasks'] > $column['task_limit'] ? 'board-task-list-limit' : '' ?> - "> + " + > <!-- tasks list --> <div class="board-task-list board-column-expanded" data-column-id="<?= $column['id'] ?>" data-swimlane-id="<?= $swimlane['id'] ?>" data-task-limit="<?= $column['task_limit'] ?>"> diff --git a/app/Template/board/task_avatar.php b/app/Template/board/task_avatar.php index 14b55476..28e0813e 100644 --- a/app/Template/board/task_avatar.php +++ b/app/Template/board/task_avatar.php @@ -3,7 +3,7 @@ <span <?php if ($this->user->hasProjectAccess('TaskModificationController', 'edit', $task['project_id'])): ?> class="task-board-assignee task-board-change-assignee" - data-url="<?= $this->url->href('TaskPopoverController', 'changeAssignee', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"> + data-url="<?= $this->url->href('TaskModificationController', 'edit', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"> <?php else: ?> class="task-board-assignee"> <?php endif ?> diff --git a/app/Template/board/task_footer.php b/app/Template/board/task_footer.php index f6cbff70..bc34363c 100644 --- a/app/Template/board/task_footer.php +++ b/app/Template/board/task_footer.php @@ -6,8 +6,8 @@ <?php else: ?> <?= $this->url->link( $this->text->e($task['category_name']), - 'TaskPopoverController', - 'changeCategory', + 'TaskModificationController', + 'edit', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover' . (! empty($task['category_description']) ? ' tooltip' : ''), @@ -18,6 +18,16 @@ </div> <?php endif ?> +<?php if (! empty($task['tags'])): ?> + <div class="task-tags"> + <ul> + <?php foreach ($task['tags'] as $tag): ?> + <li><?= $this->text->e($tag['name']) ?></li> + <?php endforeach ?> + </ul> + </div> +<?php endif ?> + <div class="task-board-icons"> <?php if ($task['score']): ?> <span class="task-score" title="<?= t('Complexity') ?>"> @@ -27,7 +37,7 @@ <?php endif ?> <?php if (! empty($task['date_due'])): ?> - <?php if (date('d') == date('d', $task['date_due'])): ?> + <?php if (date('Y-m-d') == date('Y-m-d', $task['date_due'])): ?> <span class="task-board-date task-board-date-today"> <?php elseif (time() > $task['date_due']): ?> <span class="task-board-date task-board-date-overdue"> diff --git a/app/Template/board/tooltip_external_links.php b/app/Template/board/tooltip_external_links.php index 65331864..a9f1fc7f 100644 --- a/app/Template/board/tooltip_external_links.php +++ b/app/Template/board/tooltip_external_links.php @@ -1,5 +1,5 @@ <div class="tooltip-large"> - <table> + <table class="table-small"> <tr> <th class="column-20"><?= t('Type') ?></th> <th class="column-70"><?= t('Title') ?></th> diff --git a/app/Template/board/tooltip_files.php b/app/Template/board/tooltip_files.php index 6f9e2640..4e704dac 100644 --- a/app/Template/board/tooltip_files.php +++ b/app/Template/board/tooltip_files.php @@ -1,5 +1,5 @@ <div class="tooltip-large"> - <table> + <table class="table-small"> <?php foreach ($files as $file): ?> <tr> <th> diff --git a/app/Template/board/tooltip_subtasks.php b/app/Template/board/tooltip_subtasks.php index 0322d373..8d5bc059 100644 --- a/app/Template/board/tooltip_subtasks.php +++ b/app/Template/board/tooltip_subtasks.php @@ -1,5 +1,5 @@ <div class="tooltip-large"> - <table> + <table class="table-small"> <tr> <th class="column-80"><?= t('Subtask') ?></th> <th><?= t('Assignee') ?></th> diff --git a/app/Template/board/tooltip_tasklinks.php b/app/Template/board/tooltip_tasklinks.php index d1156cbe..08432e71 100644 --- a/app/Template/board/tooltip_tasklinks.php +++ b/app/Template/board/tooltip_tasklinks.php @@ -1,5 +1,5 @@ <div class="tooltip-large"> - <table> + <table class="table-small"> <?php foreach ($links as $label => $grouped_links): ?> <tr> <th colspan="4"><?= t($label) ?></th> diff --git a/app/Template/category/index.php b/app/Template/category/index.php index a103d89f..ac60d9a8 100644 --- a/app/Template/category/index.php +++ b/app/Template/category/index.php @@ -2,7 +2,7 @@ <div class="page-header"> <h2><?= t('Categories') ?></h2> </div> -<table> +<table class="table-striped"> <tr> <th><?= t('Category Name') ?></th> <th class="column-8"><?= t('Actions') ?></th> diff --git a/app/Template/column/create.php b/app/Template/column/create.php index 023de525..812e9139 100644 --- a/app/Template/column/create.php +++ b/app/Template/column/create.php @@ -13,6 +13,8 @@ <?= $this->form->label(t('Task limit'), 'task_limit') ?> <?= $this->form->number('task_limit', $values, $errors) ?> + <?= $this->form->checkbox('hide_in_dashboard', t('Hide tasks in this column in the dashboard'), 1) ?> + <?= $this->form->label(t('Description'), 'description') ?> <?= $this->form->textarea('description', $values, $errors, array(), 'markdown-editor') ?> diff --git a/app/Template/column/edit.php b/app/Template/column/edit.php index a742e4b9..89487298 100644 --- a/app/Template/column/edit.php +++ b/app/Template/column/edit.php @@ -15,6 +15,8 @@ <?= $this->form->label(t('Task limit'), 'task_limit') ?> <?= $this->form->number('task_limit', $values, $errors) ?> + <?= $this->form->checkbox('hide_in_dashboard', t('Hide tasks in this column in the dashboard'), 1, $values['hide_in_dashboard'] == 1) ?> + <?= $this->form->label(t('Description'), 'description') ?> <?= $this->form->textarea('description', $values, $errors, array(), 'markdown-editor') ?> diff --git a/app/Template/column/index.php b/app/Template/column/index.php index 04760a16..66890ba5 100644 --- a/app/Template/column/index.php +++ b/app/Template/column/index.php @@ -12,7 +12,7 @@ <p class="alert alert-error"><?= t('Your board doesn\'t have any columns!') ?></p> <?php else: ?> <table - class="columns-table table-stripped" + class="columns-table table-striped" data-save-position-url="<?= $this->url->href('ColumnController', 'move', array('project_id' => $project['id'])) ?>"> <thead> <tr> diff --git a/app/Template/comment/show.php b/app/Template/comment/show.php index 8419a14e..16a807bc 100644 --- a/app/Template/comment/show.php +++ b/app/Template/comment/show.php @@ -4,10 +4,10 @@ <div class="comment-title"> <?php if (! empty($comment['username'])): ?> - <span class="comment-username"><?= $this->text->e($comment['name'] ?: $comment['username']) ?></span> + <strong class="comment-username"><?= $this->text->e($comment['name'] ?: $comment['username']) ?></strong> <?php endif ?> - <span class="comment-date"><?= $this->dt->datetime($comment['date_creation']) ?></span> + <small class="comment-date"><?= $this->dt->datetime($comment['date_creation']) ?></small> </div> <div class="comment-content"> diff --git a/app/Template/comments/show.php b/app/Template/comments/show.php index 43f6b2c2..5c6d8e20 100644 --- a/app/Template/comments/show.php +++ b/app/Template/comments/show.php @@ -5,8 +5,10 @@ <div class="accordion-content" id="comments"> <?php if (!isset($is_public) || !$is_public): ?> <div class="comment-sorting"> - <i class="fa fa-sort"></i> - <?= $this->url->link(t('change sorting'), 'CommentController', 'toggleSorting', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> + <small> + <i class="fa fa-sort"></i> + <?= $this->url->link(t('change sorting'), 'CommentController', 'toggleSorting', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> + </small> </div> <?php endif ?> <?php foreach ($comments as $comment): ?> diff --git a/app/Template/config/about.php b/app/Template/config/about.php index 8e2d1325..8d5a575d 100644 --- a/app/Template/config/about.php +++ b/app/Template/config/about.php @@ -9,7 +9,7 @@ </li> <li> <?= t('Author:') ?> - <strong>Frédéric Guillot</strong> (<a href="https://github.com/fguillot/kanboard/blob/master/CONTRIBUTORS.md" target="_blank"><?= t('contributors') ?></a>) + <strong>Frédéric Guillot</strong> (<a href="https://github.com/kanboard/kanboard/blob/master/CONTRIBUTORS.md" target="_blank"><?= t('contributors') ?></a>) </li> <li> <?= t('License:') ?> diff --git a/app/Template/config/keyboard_shortcuts.php b/app/Template/config/keyboard_shortcuts.php index da532661..1b1a9477 100644 --- a/app/Template/config/keyboard_shortcuts.php +++ b/app/Template/config/keyboard_shortcuts.php @@ -19,7 +19,6 @@ <h3><?= t('Task view') ?></h3> <ul> <li><?= t('Edit task') ?> = <strong>e</strong></li> - <li><?= t('Edit description') ?> = <strong>d</strong></li> <li><?= t('New subtask') ?> = <strong>s</strong></li> <li><?= t('New comment') ?> = <strong>c</strong></li> <li><?= t('New internal link') ?> = <strong>l</strong></li> @@ -33,4 +32,4 @@ <li><?= t('Close dialog box') ?> = <strong>ESC</strong></li> <li><?= t('Submit a form') ?> = <strong>CTRL+ENTER</strong> <?= t('or') ?> <strong>⌘+ENTER</strong></li> </ul> -</div>
\ No newline at end of file +</div> diff --git a/app/Template/config/sidebar.php b/app/Template/config/sidebar.php index 29caa0ef..239edc19 100644 --- a/app/Template/config/sidebar.php +++ b/app/Template/config/sidebar.php @@ -1,5 +1,4 @@ <div class="sidebar"> - <h2><?= t('Actions') ?></h2> <ul> <li <?= $this->app->checkMenuSelection('ConfigController', 'index') ?>> <?= $this->url->link(t('About'), 'ConfigController', 'index') ?> @@ -19,6 +18,9 @@ <li <?= $this->app->checkMenuSelection('ConfigController', 'calendar') ?>> <?= $this->url->link(t('Calendar settings'), 'ConfigController', 'calendar') ?> </li> + <li <?= $this->app->checkMenuSelection('TagController', 'index') ?>> + <?= $this->url->link(t('Tags management'), 'TagController', 'index') ?> + </li> <li <?= $this->app->checkMenuSelection('LinkController') ?>> <?= $this->url->link(t('Link settings'), 'LinkController', 'index') ?> </li> diff --git a/app/Template/currency/index.php b/app/Template/currency/index.php index 9881cee5..db9b21af 100644 --- a/app/Template/currency/index.php +++ b/app/Template/currency/index.php @@ -4,7 +4,7 @@ <?php if (! empty($rates)): ?> -<table class="table-stripped"> +<table class="table-striped"> <tr> <th class="column-35"><?= t('Currency') ?></th> <th><?= t('Rate') ?></th> diff --git a/app/Template/custom_filter/index.php b/app/Template/custom_filter/index.php index 08c8040c..dcab891b 100644 --- a/app/Template/custom_filter/index.php +++ b/app/Template/custom_filter/index.php @@ -3,7 +3,7 @@ <h2><?= t('Custom filters') ?></h2> </div> <div> - <table> + <table class="table-striped table-scrolling"> <tr> <th class="column-15"><?= t('Name') ?></th> <th class="column-30"><?= t('Filter') ?></th> diff --git a/app/Template/dashboard/notifications.php b/app/Template/dashboard/notifications.php index e0e9b878..a189d74f 100644 --- a/app/Template/dashboard/notifications.php +++ b/app/Template/dashboard/notifications.php @@ -13,7 +13,7 @@ </ul> </div> - <table class="table-fixed table-small"> + <table class="table-striped table-scrolling table-small"> <tr> <th><?= t('Notification') ?></th> <th class="column-20"><?= t('Date') ?></th> @@ -36,10 +36,8 @@ <i class="fa fa-file-o fa-fw"></i> <?php endif ?> - <?php if ($this->text->contains($notification['event_name'], 'task.overdue')): ?> - <?php if (count($notification['event_data']['tasks']) > 1): ?> - <?= $notification['title'] ?> - <?php endif ?> + <?php if ($this->text->contains($notification['event_name'], 'task.overdue') && count($notification['event_data']['tasks']) > 1): ?> + <?= $notification['title'] ?> <?php else: ?> <?= $this->url->link($notification['title'], 'WebNotificationController', 'redirect', array('notification_id' => $notification['id'], 'user_id' => $user['id'])) ?> <?php endif ?> diff --git a/app/Template/dashboard/projects.php b/app/Template/dashboard/projects.php index 962e4d83..f8806c01 100644 --- a/app/Template/dashboard/projects.php +++ b/app/Template/dashboard/projects.php @@ -4,10 +4,10 @@ <?php if ($paginator->isEmpty()): ?> <p class="alert"><?= t('Your are not member of any project.') ?></p> <?php else: ?> - <table class="table-fixed table-small"> + <table class="table-striped table-small table-scrolling"> <tr> - <th class="column-5"><?= $paginator->order('Id', 'id') ?></th> - <th class="column-3"><?= $paginator->order('<i class="fa fa-lock fa-fw" title="'.t('Private project').'"></i>', 'is_private') ?></th> + <th class="column-5"><?= $paginator->order('Id', \Kanboard\Model\ProjectModel::TABLE.'.id') ?></th> + <th class="column-3"><?= $paginator->order('<i class="fa fa-lock fa-fw" title="'.t('Private project').'"></i>', \Kanboard\Model\ProjectModel::TABLE.'.is_private') ?></th> <th class="column-25"><?= $paginator->order(t('Project'), \Kanboard\Model\ProjectModel::TABLE.'.name') ?></th> <th class="column-10"><?= t('Tasks') ?></th> <th><?= t('Columns') ?></th> @@ -43,7 +43,7 @@ <td class="dashboard-project-stats"> <?php foreach ($project['columns'] as $column): ?> <strong title="<?= t('Task count') ?>"><?= $column['nb_tasks'] ?></strong> - <span><?= $this->text->e($column['title']) ?></span> + <small><?= $this->text->e($column['title']) ?></small> <?php endforeach ?> </td> diff --git a/app/Template/dashboard/show.php b/app/Template/dashboard/show.php index 637b60f8..aec6f591 100644 --- a/app/Template/dashboard/show.php +++ b/app/Template/dashboard/show.php @@ -2,11 +2,18 @@ <form method="get" action="<?= $this->url->dir() ?>" class="search"> <?= $this->form->hidden('controller', array('controller' => 'SearchController')) ?> <?= $this->form->hidden('action', array('action' => 'index')) ?> - <?= $this->form->text('search', array(), array(), array('placeholder="'.t('Search').'"'), 'form-input-large') ?> - <?= $this->render('app/filters_helper') ?> + + <div class="input-addon"> + <?= $this->form->text('search', array(), array(), array('placeholder="'.t('Search').'"'), 'input-addon-field') ?> + <div class="input-addon-item"> + <?= $this->render('app/filters_helper') ?> + </div> + </div> </form> </div> <?= $this->render('dashboard/projects', array('paginator' => $project_paginator, 'user' => $user)) ?> <?= $this->render('dashboard/tasks', array('paginator' => $task_paginator, 'user' => $user)) ?> <?= $this->render('dashboard/subtasks', array('paginator' => $subtask_paginator, 'user' => $user)) ?> + +<?= $this->hook->render('template:dashboard:show', array('user' => $user)) ?> diff --git a/app/Template/dashboard/sidebar.php b/app/Template/dashboard/sidebar.php index 86cc20f8..108c028a 100644 --- a/app/Template/dashboard/sidebar.php +++ b/app/Template/dashboard/sidebar.php @@ -1,5 +1,4 @@ <div class="sidebar"> - <h2><?= $this->text->e($user['name'] ?: $user['username']) ?></h2> <ul> <li <?= $this->app->checkMenuSelection('DashboardController', 'show') ?>> <?= $this->url->link(t('Overview'), 'DashboardController', 'show', array('user_id' => $user['id'])) ?> @@ -22,6 +21,6 @@ <li <?= $this->app->checkMenuSelection('DashboardController', 'notifications') ?>> <?= $this->url->link(t('My notifications'), 'DashboardController', 'notifications', array('user_id' => $user['id'])) ?> </li> - <?= $this->hook->render('template:dashboard:sidebar') ?> + <?= $this->hook->render('template:dashboard:sidebar', array('user' => $user)) ?> </ul> </div> diff --git a/app/Template/dashboard/subtasks.php b/app/Template/dashboard/subtasks.php index 8e0aa3ce..b71deeb9 100644 --- a/app/Template/dashboard/subtasks.php +++ b/app/Template/dashboard/subtasks.php @@ -4,12 +4,12 @@ <?php if ($paginator->isEmpty()): ?> <p class="alert"><?= t('There is nothing assigned to you.') ?></p> <?php else: ?> - <table class="table-fixed table-small"> + <table class="table-striped table-small table-scrolling"> <tr> - <th class="column-5"><?= $paginator->order('Id', 'tasks.id') ?></th> + <th class="column-5"><?= $paginator->order('Id', \Kanboard\Model\TaskModel::TABLE.'.id') ?></th> <th class="column-20"><?= $paginator->order(t('Project'), 'project_name') ?></th> <th><?= $paginator->order(t('Task'), 'task_name') ?></th> - <th><?= $paginator->order(t('Subtask'), 'title') ?></th> + <th><?= $paginator->order(t('Subtask'), \Kanboard\Model\SubtaskModel::TABLE.'.title') ?></th> <th class="column-20"><?= t('Time tracking') ?></th> </tr> <?php foreach ($paginator->getCollection() as $subtask): ?> diff --git a/app/Template/dashboard/tasks.php b/app/Template/dashboard/tasks.php index 4b83a96a..427b903d 100644 --- a/app/Template/dashboard/tasks.php +++ b/app/Template/dashboard/tasks.php @@ -4,14 +4,14 @@ <?php if ($paginator->isEmpty()): ?> <p class="alert"><?= t('There is nothing assigned to you.') ?></p> <?php else: ?> - <table class="table-fixed table-small"> + <table class="table-striped table-small table-scrolling"> <tr> - <th class="column-5"><?= $paginator->order('Id', 'tasks.id') ?></th> + <th class="column-5"><?= $paginator->order('Id', \Kanboard\Model\TaskModel::TABLE.'.id') ?></th> <th class="column-20"><?= $paginator->order(t('Project'), 'project_name') ?></th> - <th><?= $paginator->order(t('Task'), 'title') ?></th> - <th class="column-5"><?= $paginator->order('Priority', 'tasks.priority') ?></th> + <th><?= $paginator->order(t('Task'), \Kanboard\Model\TaskModel::TABLE.'.title') ?></th> + <th class="column-8"><?= $paginator->order(t('Priority'), \Kanboard\Model\TaskModel::TABLE.'.priority') ?></th> <th class="column-20"><?= t('Time tracking') ?></th> - <th class="column-10"><?= $paginator->order(t('Due date'), 'date_due') ?></th> + <th class="column-10"><?= $paginator->order(t('Due date'), \Kanboard\Model\TaskModel::TABLE.'.date_due') ?></th> <th class="column-10"><?= $paginator->order(t('Column'), 'column_title') ?></th> </tr> <?php foreach ($paginator->getCollection() as $task): ?> diff --git a/app/Template/event/comment_create.php b/app/Template/event/comment_create.php index 45132e6d..780bba93 100644 --- a/app/Template/event/comment_create.php +++ b/app/Template/event/comment_create.php @@ -3,7 +3,7 @@ $this->text->e($author), $this->url->link(t('#%d', $task['id']), 'TaskViewController', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ) ?> - <span class="activity-date"><?= $this->dt->datetime($date_creation) ?></span> + <small class="activity-date"><?= $this->dt->datetime($date_creation) ?></small> </p> <div class="activity-description"> <p class="activity-task-title"><?= $this->text->e($task['title']) ?></p> diff --git a/app/Template/event/comment_delete.php b/app/Template/event/comment_delete.php new file mode 100644 index 00000000..e3a2f9fa --- /dev/null +++ b/app/Template/event/comment_delete.php @@ -0,0 +1,11 @@ +<p class="activity-title"> + <?= e('%s removed a comment on the task %s', + $this->text->e($author), + $this->url->link(t('#%d', $task['id']), 'TaskViewController', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) + ) ?> + <small class="activity-date"><?= $this->dt->datetime($date_creation) ?></small> +</p> +<div class="activity-description"> + <p class="activity-task-title"><?= $this->text->e($task['title']) ?></p> + <div class="markdown"><?= $this->text->markdown($comment['comment']) ?></div> +</div> diff --git a/app/Template/event/comment_update.php b/app/Template/event/comment_update.php index 5a0821bd..9e25ec2d 100644 --- a/app/Template/event/comment_update.php +++ b/app/Template/event/comment_update.php @@ -3,8 +3,11 @@ $this->text->e($author), $this->url->link(t('#%d', $task['id']), 'TaskViewController', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ) ?> - <span class="activity-date"><?= $this->dt->datetime($date_creation) ?></span> + <small class="activity-date"><?= $this->dt->datetime($date_creation) ?></small> </p> <div class="activity-description"> <p class="activity-task-title"><?= $this->text->e($task['title']) ?></p> + <?php if (! empty($comment['comment'])): ?> + <div class="markdown"><?= $this->text->markdown($comment['comment']) ?></div> + <?php endif ?> </div> diff --git a/app/Template/event/subtask_create.php b/app/Template/event/subtask_create.php index 1bf36c05..9a115c73 100644 --- a/app/Template/event/subtask_create.php +++ b/app/Template/event/subtask_create.php @@ -3,7 +3,7 @@ $this->text->e($author), $this->url->link(t('#%d', $task['id']), 'TaskViewController', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ) ?> - <span class="activity-date"><?= $this->dt->datetime($date_creation) ?></span> + <small class="activity-date"><?= $this->dt->datetime($date_creation) ?></small> </p> <div class="activity-description"> <p class="activity-task-title"><?= $this->text->e($task['title']) ?></p> diff --git a/app/Template/event/subtask_delete.php b/app/Template/event/subtask_delete.php new file mode 100644 index 00000000..7f0d6d58 --- /dev/null +++ b/app/Template/event/subtask_delete.php @@ -0,0 +1,15 @@ +<p class="activity-title"> + <?= e('%s removed a subtask for the task %s', + $this->text->e($author), + $this->url->link(t('#%d', $task['id']), 'TaskViewController', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) + ) ?> + <small class="activity-date"><?= $this->dt->datetime($date_creation) ?></small> +</p> +<div class="activity-description"> + <p class="activity-task-title"><?= $this->text->e($task['title']) ?></p> + <ul> + <li> + <?= $this->text->e($subtask['title']) ?> (<strong><?= $this->text->e($subtask['status_name']) ?></strong>) + </li> + </ul> +</div> diff --git a/app/Template/event/subtask_update.php b/app/Template/event/subtask_update.php index 201402f6..e566022e 100644 --- a/app/Template/event/subtask_update.php +++ b/app/Template/event/subtask_update.php @@ -3,7 +3,7 @@ $this->text->e($author), $this->url->link(t('#%d', $task['id']), 'TaskViewController', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ) ?> - <span class="activity-date"><?= $this->dt->datetime($date_creation) ?></span> + <small class="activity-date"><?= $this->dt->datetime($date_creation) ?></small> </p> <div class="activity-description"> <p class="activity-task-title"><?= $this->text->e($task['title']) ?></p> diff --git a/app/Template/event/task_assignee_change.php b/app/Template/event/task_assignee_change.php index 7c962223..405f8ac1 100644 --- a/app/Template/event/task_assignee_change.php +++ b/app/Template/event/task_assignee_change.php @@ -8,9 +8,9 @@ $this->text->e($assignee) ) ?> <?php else: ?> - <?= e('%s remove the assignee of the task %s', $this->text->e($author), $this->url->link(t('#%d', $task['id']), 'TaskViewController', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']))) ?> + <?= e('%s removed the assignee of the task %s', $this->text->e($author), $this->url->link(t('#%d', $task['id']), 'TaskViewController', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']))) ?> <?php endif ?> - <span class="activity-date"><?= $this->dt->datetime($date_creation) ?></span> + <small class="activity-date"><?= $this->dt->datetime($date_creation) ?></small> </p> <div class="activity-description"> <p class="activity-task-title"><?= $this->text->e($task['title']) ?></p> diff --git a/app/Template/event/task_close.php b/app/Template/event/task_close.php index 90ff9207..1ac81ea9 100644 --- a/app/Template/event/task_close.php +++ b/app/Template/event/task_close.php @@ -3,7 +3,7 @@ $this->text->e($author), $this->url->link(t('#%d', $task['id']), 'TaskViewController', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ) ?> - <span class="activity-date"><?= $this->dt->datetime($date_creation) ?></span> + <small class="activity-date"><?= $this->dt->datetime($date_creation) ?></small> </p> <div class="activity-description"> <p class="activity-task-title"><?= $this->text->e($task['title']) ?></p> diff --git a/app/Template/event/task_create.php b/app/Template/event/task_create.php index 017a5ada..9d0ff358 100644 --- a/app/Template/event/task_create.php +++ b/app/Template/event/task_create.php @@ -3,7 +3,7 @@ $this->text->e($author), $this->url->link(t('#%d', $task['id']), 'TaskViewController', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ) ?> - <span class="activity-date"><?= $this->dt->datetime($date_creation) ?></span> + <small class="activity-date"><?= $this->dt->datetime($date_creation) ?></small> </p> <div class="activity-description"> <p class="activity-task-title"><?= $this->text->e($task['title']) ?></p> diff --git a/app/Template/event/task_file_create.php b/app/Template/event/task_file_create.php index d329529a..7e58fdc1 100644 --- a/app/Template/event/task_file_create.php +++ b/app/Template/event/task_file_create.php @@ -3,7 +3,7 @@ $this->text->e($author), $this->url->link(t('#%d', $task['id']), 'TaskViewController', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ) ?> - <span class="activity-date"><?= $this->dt->datetime($date_creation) ?></span> + <small class="activity-date"><?= $this->dt->datetime($date_creation) ?></small> </p> <div class="activity-description"> <p class="activity-task-title"><?= $this->text->e($file['name']) ?></p> diff --git a/app/Template/event/task_internal_link_create_update.php b/app/Template/event/task_internal_link_create_update.php new file mode 100644 index 00000000..4bc6ae9a --- /dev/null +++ b/app/Template/event/task_internal_link_create_update.php @@ -0,0 +1,16 @@ +<p class="activity-title"> + <?= e('%s set a new internal link for the task %s', + $this->text->e($author), + $this->url->link(t('#%d', $task['id']), 'TaskViewController', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) + ) ?> + <small class="activity-date"><?= $this->dt->datetime($date_creation) ?></small> +</p> +<div class="activity-description"> + <p class="activity-task-title"> + <?= e( + 'This task is now linked to the task %s with the relation "%s"', + $this->url->link(t('#%d', $task_link['opposite_task_id']), 'TaskViewController', 'show', array('task_id' => $task_link['opposite_task_id'])), + $this->text->e($task_link['label']) + ) ?> + </p> +</div> diff --git a/app/Template/event/task_internal_link_delete.php b/app/Template/event/task_internal_link_delete.php new file mode 100644 index 00000000..3465fa57 --- /dev/null +++ b/app/Template/event/task_internal_link_delete.php @@ -0,0 +1,16 @@ +<p class="activity-title"> + <?= e('%s removed an internal link for the task %s', + $this->text->e($author), + $this->url->link(t('#%d', $task['id']), 'TaskViewController', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) + ) ?> + <small class="activity-date"><?= $this->dt->datetime($date_creation) ?></small> +</p> +<div class="activity-description"> + <p class="activity-task-title"> + <?= e( + 'The link with the relation "%s" to the task %s have been removed', + $this->text->e($task_link['label']), + $this->url->link(t('#%d', $task_link['opposite_task_id']), 'TaskViewController', 'show', array('task_id' => $task_link['opposite_task_id'])) + ) ?> + </p> +</div> diff --git a/app/Template/event/task_move_column.php b/app/Template/event/task_move_column.php index f3155e47..e7e5ec28 100644 --- a/app/Template/event/task_move_column.php +++ b/app/Template/event/task_move_column.php @@ -4,7 +4,7 @@ $this->url->link(t('#%d', $task['id']), 'TaskViewController', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])), $this->text->e($task['column_title']) ) ?> - <span class="activity-date"><?= $this->dt->datetime($date_creation) ?></span> + <small class="activity-date"><?= $this->dt->datetime($date_creation) ?></small> </p> <div class="activity-description"> <p class="activity-task-title"><?= $this->text->e($task['title']) ?></p> diff --git a/app/Template/event/task_move_position.php b/app/Template/event/task_move_position.php index ecdd02b6..48fbbb1e 100644 --- a/app/Template/event/task_move_position.php +++ b/app/Template/event/task_move_position.php @@ -5,7 +5,7 @@ $task['position'], $this->text->e($task['column_title']) ) ?> - <span class="activity-date"><?= $this->dt->datetime($date_creation) ?></span> + <small class="activity-date"><?= $this->dt->datetime($date_creation) ?></small> </p> <div class="activity-description"> <p class="activity-task-title"><?= $this->text->e($task['title']) ?></p> diff --git a/app/Template/event/task_move_swimlane.php b/app/Template/event/task_move_swimlane.php index fe9bfb55..a467875b 100644 --- a/app/Template/event/task_move_swimlane.php +++ b/app/Template/event/task_move_swimlane.php @@ -11,7 +11,7 @@ $this->text->e($task['swimlane_name']) ) ?> <?php endif ?> - <span class="activity-date"><?= $this->dt->datetime($date_creation) ?></span> + <small class="activity-date"><?= $this->dt->datetime($date_creation) ?></small> </p> <div class="activity-description"> <p class="activity-task-title"><?= $this->text->e($task['title']) ?></p> diff --git a/app/Template/event/task_open.php b/app/Template/event/task_open.php index 548aa98f..6d5252a1 100644 --- a/app/Template/event/task_open.php +++ b/app/Template/event/task_open.php @@ -3,7 +3,7 @@ $this->text->e($author), $this->url->link(t('#%d', $task['id']), 'TaskViewController', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ) ?> - <span class="activity-date"><?= $this->dt->datetime($date_creation) ?></span> + <small class="activity-date"><?= $this->dt->datetime($date_creation) ?></small> </p> <div class="activity-description"> <p class="activity-task-title"><?= $this->text->e($task['title']) ?></p> diff --git a/app/Template/event/task_update.php b/app/Template/event/task_update.php index 7c7507c0..2608f623 100644 --- a/app/Template/event/task_update.php +++ b/app/Template/event/task_update.php @@ -3,7 +3,7 @@ $this->text->e($author), $this->url->link(t('#%d', $task['id']), 'TaskViewController', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ) ?> - <span class="activity-date"><?= $this->dt->datetime($date_creation) ?></span> + <small class="activity-date"><?= $this->dt->datetime($date_creation) ?></small> </p> <div class="activity-description"> <p class="activity-task-title"><?= $this->text->e($task['title']) ?></p> diff --git a/app/Template/export/sidebar.php b/app/Template/export/sidebar.php index 55fbaeef..463c0cee 100644 --- a/app/Template/export/sidebar.php +++ b/app/Template/export/sidebar.php @@ -1,5 +1,4 @@ <div class="sidebar"> - <h2><?= t('Exports') ?></h2> <ul> <li <?= $this->app->checkMenuSelection('ExportController', 'tasks') ?>> <?= $this->url->link(t('Tasks'), 'ExportController', 'tasks', array('project_id' => $project['id'])) ?> diff --git a/app/Template/export/subtasks.php b/app/Template/export/subtasks.php index a82cb3d1..878a7132 100644 --- a/app/Template/export/subtasks.php +++ b/app/Template/export/subtasks.php @@ -1,5 +1,5 @@ <div class="page-header"> - <h2><?= t('Subtasks exportation for "%s"', $project['name']) ?></h2> + <h2><?= t('Subtasks export') ?></h2> </div> <p class="alert alert-info"><?= t('This report contains all subtasks information for the given date range.') ?></p> @@ -21,4 +21,4 @@ <div class="form-actions"> <button type="submit" class="btn btn-blue"><?= t('Execute') ?></button> </div> -</form>
\ No newline at end of file +</form> diff --git a/app/Template/export/summary.php b/app/Template/export/summary.php index 60aa306f..d9362a9b 100644 --- a/app/Template/export/summary.php +++ b/app/Template/export/summary.php @@ -1,5 +1,5 @@ <div class="page-header"> - <h2><?= t('Daily project summary export for "%s"', $project['name']) ?></h2> + <h2><?= t('Daily project summary export') ?></h2> </div> <p class="alert alert-info"><?= t('This export contains the number of tasks per column grouped per day.') ?></p> @@ -21,4 +21,4 @@ <div class="form-actions"> <button type="submit" class="btn btn-blue"><?= t('Execute') ?></button> </div> -</form>
\ No newline at end of file +</form> diff --git a/app/Template/export/tasks.php b/app/Template/export/tasks.php index bed8ab90..ae411326 100644 --- a/app/Template/export/tasks.php +++ b/app/Template/export/tasks.php @@ -1,5 +1,5 @@ <div class="page-header"> - <h2><?= t('Tasks exportation for "%s"', $project['name']) ?></h2> + <h2><?= t('Tasks exportation') ?></h2> </div> <p class="alert alert-info"><?= t('This report contains all tasks information for the given date range.') ?></p> @@ -21,4 +21,4 @@ <div class="form-actions"> <button type="submit" class="btn btn-blue"><?= t('Execute') ?></button> </div> -</form>
\ No newline at end of file +</form> diff --git a/app/Template/feed/project.php b/app/Template/feed/project.php deleted file mode 100644 index 213a04d4..00000000 --- a/app/Template/feed/project.php +++ /dev/null @@ -1,27 +0,0 @@ -<?= '<?xml version="1.0" encoding="utf-8"?>' ?> -<feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom"> - <title><?= t('%s\'s activity', $project['name']) ?></title> - <link rel="alternate" type="text/html" href="<?= $this->url->base() ?>"/> - <link rel="self" type="application/atom+xml" href="<?= $this->url->href('FeedController', 'project', array('token' => $project['token']), false, '', true) ?>"/> - <updated><?= date(DATE_ATOM) ?></updated> - <id><?= $this->url->href('FeedController', 'project', array('token' => $project['token']), false, '', true) ?></id> - <icon><?= $this->url->base() ?>assets/img/favicon.png</icon> - - <?php foreach ($events as $e): ?> - <entry> - <title type="text"><?= $e['event_title'] ?></title> - <link rel="alternate" href="<?= $this->url->href('TaskViewController', 'show', array('task_id' => $e['task_id']), false, '', true) ?>"/> - <id><?= $e['id'].'-'.$e['event_name'].'-'.$e['task_id'].'-'.$e['date_creation'] ?></id> - <published><?= date(DATE_ATOM, $e['date_creation']) ?></published> - <updated><?= date(DATE_ATOM, $e['date_creation']) ?></updated> - <author> - <name><?= $this->text->e($e['author']) ?></name> - </author> - <content type="html"> - <![CDATA[ - <?= $e['event_content'] ?> - ]]> - </content> - </entry> - <?php endforeach ?> -</feed> diff --git a/app/Template/feed/user.php b/app/Template/feed/user.php deleted file mode 100644 index 0c45f03c..00000000 --- a/app/Template/feed/user.php +++ /dev/null @@ -1,27 +0,0 @@ -<?= '<?xml version="1.0" encoding="utf-8"?>' ?> -<feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom"> - <title><?= t('Project activities for %s', $user['name'] ?: $user['username']) ?></title> - <link rel="alternate" type="text/html" href="<?= $this->url->base() ?>"/> - <link rel="self" type="application/atom+xml" href="<?= $this->url->href('FeedController', 'user', array('token' => $user['token']), false, '', true) ?>"/> - <updated><?= date(DATE_ATOM) ?></updated> - <id><?= $this->url->href('FeedController', 'user', array('token' => $user['token']), false, '', true) ?></id> - <icon><?= $this->url->base() ?>assets/img/favicon.png</icon> - - <?php foreach ($events as $e): ?> - <entry> - <title type="text"><?= $e['event_title'] ?></title> - <link rel="alternate" href="<?= $this->url->href('TaskViewController', 'show', array('task_id' => $e['task_id']), false, '', true) ?>"/> - <id><?= $e['id'].'-'.$e['event_name'].'-'.$e['task_id'].'-'.$e['date_creation'] ?></id> - <published><?= date(DATE_ATOM, $e['date_creation']) ?></published> - <updated><?= date(DATE_ATOM, $e['date_creation']) ?></updated> - <author> - <name><?= $this->text->e($e['author']) ?></name> - </author> - <content type="html"> - <![CDATA[ - <?= $e['event_content'] ?> - ]]> - </content> - </entry> - <?php endforeach ?> -</feed> diff --git a/app/Template/group/index.php b/app/Template/group/index.php index 1062e18c..fe8a07e7 100644 --- a/app/Template/group/index.php +++ b/app/Template/group/index.php @@ -8,7 +8,7 @@ <?php if ($paginator->isEmpty()): ?> <p class="alert"><?= t('There is no group.') ?></p> <?php else: ?> - <table class="table-small table-fixed"> + <table class="table-small table-fixed table-scrolling"> <tr> <th class="column-5"><?= $paginator->order(t('Id'), 'id') ?></th> <th class="column-20"><?= $paginator->order(t('External Id'), 'external_id') ?></th> diff --git a/app/Template/group/users.php b/app/Template/group/users.php index a4895ab7..73597b39 100644 --- a/app/Template/group/users.php +++ b/app/Template/group/users.php @@ -8,7 +8,7 @@ <?php if ($paginator->isEmpty()): ?> <p class="alert"><?= t('There is no user in this group.') ?></p> <?php else: ?> - <table> + <table class="table-striped table-scrolling"> <tr> <th><?= $paginator->order(t('Id'), 'id') ?></th> <th><?= $paginator->order(t('Username'), 'username') ?></th> diff --git a/app/Template/header.php b/app/Template/header.php index 13521ae7..2fe68ebe 100644 --- a/app/Template/header.php +++ b/app/Template/header.php @@ -1,118 +1,26 @@ -<header> - <nav> - <h1> - <span class="logo"> - <?= $this->url->link('K<span>B</span>', 'DashboardController', 'show', array(), false, '', t('Dashboard')) ?> - </span> - <span class="title"> - <?php if (isset($project) && ! empty($project)): ?> - <?= $this->url->link($this->text->e($project['name']), 'BoardViewController', 'show', array('project_id' => $project['id'])) ?> - <?php else: ?> - <?= $this->text->e($title) ?> - <?php endif ?> - </span> - <?php if (! empty($description)): ?> - <span class="tooltip" title="<?= $this->text->markdownAttribute($description) ?>"> - <i class="fa fa-info-circle"></i> - </span> - <?php endif ?> - </h1> - <ul> - <?php if (isset($board_selector) && ! empty($board_selector)): ?> - <li> - <select id="board-selector" - class="chosen-select select-auto-redirect" - tabindex="-1" - data-search-threshold="0" - data-notfound="<?= t('No results match:') ?>" - data-placeholder="<?= t('Display another project') ?>" - data-redirect-regex="PROJECT_ID" - data-redirect-url="<?= $this->url->href('BoardViewController', 'show', array('project_id' => 'PROJECT_ID')) ?>"> - <option value=""></option> - <?php foreach ($board_selector as $board_id => $board_name): ?> - <option value="<?= $board_id ?>"><?= $this->text->e($board_name) ?></option> - <?php endforeach ?> - </select> - </li> - <?php endif ?> - <li class="user-links"> - <?php if ($this->user->hasNotifications()): ?> - <span class="notification"> - <?= $this->url->link('<i class="fa fa-bell web-notification-icon"></i>', 'DashboardController', 'notifications', array('user_id' => $this->user->getId()), false, '', t('Unread notifications')) ?> - </span> - <?php endif ?> - - <?php $has_project_creation_access = $this->user->hasAccess('ProjectCreationController', 'create'); ?> - <?php $is_private_project_enabled = $this->app->config('disable_private_project', 0) == 0; ?> - - <?php if ($has_project_creation_access || (!$has_project_creation_access && $is_private_project_enabled)): ?> - <div class="dropdown"> - <a href="#" class="dropdown-menu dropdown-menu-link-icon"><i class="fa fa-plus fa-fw"></i><i class="fa fa-caret-down"></i></a> - <ul> - <?php if ($has_project_creation_access): ?> - <li><i class="fa fa-plus fa-fw"></i> - <?= $this->url->link(t('New project'), 'ProjectCreationController', 'create', array(), false, 'popover') ?></li> - <?php endif ?> - <?php if ($is_private_project_enabled): ?> - <li> - <i class="fa fa-lock fa-fw"></i> - <?= $this->url->link(t('New private project'), 'ProjectCreationController', 'createPrivate', array(), false, 'popover') ?> - </li> - <?php endif ?> - </ul> - </div> - <?php endif ?> +<?php $_title = $this->render('header/title', array( + 'project' => isset($project) ? $project : null, + 'task' => isset($task) ? $task : null, + 'description' => isset($description) ? $description : null, + 'title' => $title, +)) ?> - <div class="dropdown"> - <a href="#" class="dropdown-menu dropdown-menu-link-icon"><?= $this->avatar->currentUserSmall('avatar-inline') ?><i class="fa fa-caret-down"></i></a> - <ul> - <li class="no-hover"><strong><?= $this->text->e($this->user->getFullname()) ?></strong></li> - <li> - <i class="fa fa-tachometer fa-fw"></i> - <?= $this->url->link(t('My dashboard'), 'DashboardController', 'show', array('user_id' => $this->user->getId())) ?> - </li> - <li> - <i class="fa fa-home fa-fw"></i> - <?= $this->url->link(t('My profile'), 'UserViewController', 'show', array('user_id' => $this->user->getId())) ?> - </li> - <li> - <i class="fa fa-folder fa-fw"></i> - <?= $this->url->link(t('Projects management'), 'ProjectListController', 'show') ?> - </li> - <?php if ($this->user->hasAccess('UserListController', 'show')): ?> - <li> - <i class="fa fa-user fa-fw"></i> - <?= $this->url->link(t('Users management'), 'UserListController', 'show') ?> - </li> - <li> - <i class="fa fa-group fa-fw"></i> - <?= $this->url->link(t('Groups management'), 'GroupListController', 'index') ?> - </li> - <li> - <i class="fa fa-cubes" aria-hidden="true"></i> - <?= $this->url->link(t('Plugins'), 'PluginController', 'show') ?> - </li> - <li> - <i class="fa fa-cog fa-fw"></i> - <?= $this->url->link(t('Settings'), 'ConfigController', 'index') ?> - </li> - <?php endif ?> +<?php $_top_right_corner = implode(' ', array( + $this->render('header/user_notifications'), + $this->render('header/creation_dropdown'), + $this->render('header/user_dropdown') + )) ?> - <?= $this->hook->render('template:header:dropdown') ?> - - <li> - <i class="fa fa-life-ring fa-fw"></i> - <?= $this->url->link(t('Documentation'), 'DocumentationController', 'show') ?> - </li> - <?php if (! DISABLE_LOGOUT): ?> - <li> - <i class="fa fa-sign-out fa-fw"></i> - <?= $this->url->link(t('Logout'), 'AuthController', 'logout') ?> - </li> - <?php endif ?> - </ul> - </div> - </li> - </ul> - </nav> +<header> + <div class="title-container"> + <?= $_title ?> + </div> + <?php if (! empty($board_selector)): ?> + <div class="board-selector-container"> + <?= $this->render('header/board_selector', array('board_selector' => $board_selector)) ?> + </div> + <?php endif ?> + <div class="menus-container pull-right"> + <?= $_top_right_corner ?> + </div> </header> diff --git a/app/Template/header/board_selector.php b/app/Template/header/board_selector.php new file mode 100644 index 00000000..b42d47f9 --- /dev/null +++ b/app/Template/header/board_selector.php @@ -0,0 +1,13 @@ +<select id="board-selector" + class="chosen-select select-auto-redirect" + tabindex="-1" + data-search-threshold="0" + data-notfound="<?= t('No results match:') ?>" + data-placeholder="<?= t('Display another project') ?>" + data-redirect-regex="PROJECT_ID" + data-redirect-url="<?= $this->url->href('BoardViewController', 'show', array('project_id' => 'PROJECT_ID')) ?>"> + <option value=""></option> + <?php foreach ($board_selector as $board_id => $board_name): ?> + <option value="<?= $board_id ?>"><?= $this->text->e($board_name) ?></option> + <?php endforeach ?> +</select> diff --git a/app/Template/header/creation_dropdown.php b/app/Template/header/creation_dropdown.php new file mode 100644 index 00000000..d3b9e7cb --- /dev/null +++ b/app/Template/header/creation_dropdown.php @@ -0,0 +1,22 @@ +<?php $has_project_creation_access = $this->user->hasAccess('ProjectCreationController', 'create'); ?> +<?php $is_private_project_enabled = $this->app->config('disable_private_project', 0) == 0; ?> + +<?php if ($has_project_creation_access || (!$has_project_creation_access && $is_private_project_enabled)): ?> + <div class="dropdown"> + <a href="#" class="dropdown-menu dropdown-menu-link-icon"><i class="fa fa-plus fa-fw"></i><i class="fa fa-caret-down"></i></a> + <ul> + <?php if ($has_project_creation_access): ?> + <li><i class="fa fa-plus fa-fw"></i> + <?= $this->url->link(t('New project'), 'ProjectCreationController', 'create', array(), false, 'popover') ?> + </li> + <?php endif ?> + <?php if ($is_private_project_enabled): ?> + <li> + <i class="fa fa-lock fa-fw"></i> + <?= $this->url->link(t('New private project'), 'ProjectCreationController', 'createPrivate', array(), false, 'popover') ?> + </li> + <?php endif ?> + <?= $this->hook->render('template:header:creation-dropdown') ?> + </ul> + </div> +<?php endif ?> diff --git a/app/Template/header/title.php b/app/Template/header/title.php new file mode 100644 index 00000000..61c6ee9a --- /dev/null +++ b/app/Template/header/title.php @@ -0,0 +1,17 @@ +<h1> + <span class="logo"> + <?= $this->url->link('K<span>B</span>', 'DashboardController', 'show', array(), false, '', t('Dashboard')) ?> + </span> + <span class="title"> + <?php if (! empty($project) && ! empty($task)): ?> + <?= $this->url->link($this->text->e($project['name']), 'BoardViewController', 'show', array('project_id' => $project['id'])) ?> + <?php else: ?> + <?= $this->text->e($title) ?> + <?php endif ?> + </span> + <?php if (! empty($description)): ?> + <small class="tooltip" title="<?= $this->text->markdownAttribute($description) ?>"> + <i class="fa fa-info-circle"></i> + </small> + <?php endif ?> +</h1> diff --git a/app/Template/header/user_dropdown.php b/app/Template/header/user_dropdown.php new file mode 100644 index 00000000..49d08213 --- /dev/null +++ b/app/Template/header/user_dropdown.php @@ -0,0 +1,49 @@ +<div class="dropdown"> + <a href="#" class="dropdown-menu dropdown-menu-link-icon"><?= $this->avatar->currentUserSmall('avatar-inline') ?><i class="fa fa-caret-down"></i></a> + <ul> + <li class="no-hover"><strong><?= $this->text->e($this->user->getFullname()) ?></strong></li> + <li> + <i class="fa fa-tachometer fa-fw"></i> + <?= $this->url->link(t('My dashboard'), 'DashboardController', 'show', array('user_id' => $this->user->getId())) ?> + </li> + <li> + <i class="fa fa-home fa-fw"></i> + <?= $this->url->link(t('My profile'), 'UserViewController', 'show', array('user_id' => $this->user->getId())) ?> + </li> + <li> + <i class="fa fa-folder fa-fw"></i> + <?= $this->url->link(t('Projects management'), 'ProjectListController', 'show') ?> + </li> + <?php if ($this->user->hasAccess('UserListController', 'show')): ?> + <li> + <i class="fa fa-user fa-fw"></i> + <?= $this->url->link(t('Users management'), 'UserListController', 'show') ?> + </li> + <li> + <i class="fa fa-group fa-fw"></i> + <?= $this->url->link(t('Groups management'), 'GroupListController', 'index') ?> + </li> + <li> + <i class="fa fa-cubes" aria-hidden="true"></i> + <?= $this->url->link(t('Plugins'), 'PluginController', 'show') ?> + </li> + <li> + <i class="fa fa-cog fa-fw"></i> + <?= $this->url->link(t('Settings'), 'ConfigController', 'index') ?> + </li> + <?php endif ?> + + <?= $this->hook->render('template:header:dropdown') ?> + + <li> + <i class="fa fa-life-ring fa-fw"></i> + <?= $this->url->link(t('Documentation'), 'DocumentationController', 'show') ?> + </li> + <?php if (! DISABLE_LOGOUT): ?> + <li> + <i class="fa fa-sign-out fa-fw"></i> + <?= $this->url->link(t('Logout'), 'AuthController', 'logout') ?> + </li> + <?php endif ?> + </ul> +</div> diff --git a/app/Template/header/user_notifications.php b/app/Template/header/user_notifications.php new file mode 100644 index 00000000..83c545d2 --- /dev/null +++ b/app/Template/header/user_notifications.php @@ -0,0 +1,5 @@ +<?php if ($this->user->hasNotifications()): ?> + <span class="notification"> + <?= $this->url->link('<i class="fa fa-bell web-notification-icon"></i>', 'DashboardController', 'notifications', array('user_id' => $this->user->getId()), false, '', t('Unread notifications')) ?> + </span> +<?php endif ?> diff --git a/app/Template/layout.php b/app/Template/layout.php index 411237cb..8c85ffc6 100644 --- a/app/Template/layout.php +++ b/app/Template/layout.php @@ -15,7 +15,6 @@ <?= $this->asset->colorCss() ?> <?= $this->asset->css('assets/css/vendor.min.css') ?> <?= $this->asset->css('assets/css/app.min.css') ?> - <?= $this->asset->css('assets/css/print.min.css', true, 'print') ?> <?= $this->asset->customCss() ?> <?php if (! isset($not_editable)): ?> diff --git a/app/Template/link/index.php b/app/Template/link/index.php index 7e32069a..70ead4a6 100644 --- a/app/Template/link/index.php +++ b/app/Template/link/index.php @@ -2,7 +2,7 @@ <h2><?= t('Link labels') ?></h2> </div> <?php if (! empty($links)): ?> -<table> +<table class="table-striped table-scrolling"> <tr> <th class="column-70"><?= t('Link labels') ?></th> <th><?= t('Actions') ?></th> diff --git a/app/Template/notification/comment_delete.php b/app/Template/notification/comment_delete.php new file mode 100644 index 00000000..928623ec --- /dev/null +++ b/app/Template/notification/comment_delete.php @@ -0,0 +1,7 @@ +<h2><?= $this->text->e($task['title']) ?> (#<?= $task['id'] ?>)</h2> + +<h3><?= t('Comment removed') ?></h3> + +<?= $this->text->markdown($comment['comment']) ?> + +<?= $this->render('notification/footer', array('task' => $task, 'application_url' => $application_url)) ?> diff --git a/app/Template/notification/subtask_delete.php b/app/Template/notification/subtask_delete.php new file mode 100644 index 00000000..8c5f262c --- /dev/null +++ b/app/Template/notification/subtask_delete.php @@ -0,0 +1,11 @@ +<h2><?= $this->text->e($task['title']) ?> (#<?= $task['id'] ?>)</h2> + +<h3><?= t('Subtask removed') ?></h3> + +<ul> + <li><?= t('Title:') ?> <?= $this->text->e($subtask['title']) ?></li> + <li><?= t('Status:') ?> <?= $this->text->e($subtask['status_name']) ?></li> + <li><?= t('Assignee:') ?> <?= $this->text->e($subtask['name'] ?: $subtask['username'] ?: '?') ?></li> +</ul> + +<?= $this->render('notification/footer', array('task' => $task, 'application_url' => $application_url)) ?> diff --git a/app/Template/notification/task_file_create.php b/app/Template/notification/task_file_create.php index feab8dd2..c19f7279 100644 --- a/app/Template/notification/task_file_create.php +++ b/app/Template/notification/task_file_create.php @@ -2,4 +2,4 @@ <p><?= t('New attachment added "%s"', $file['name']) ?></p> -<?= $this->render('notification/footer', array('task' => $task, 'application_url' => $application_url)) ?>
\ No newline at end of file +<?= $this->render('notification/footer', array('task' => $task, 'application_url' => $application_url)) ?> diff --git a/app/Template/notification/task_internal_link_create_update.php b/app/Template/notification/task_internal_link_create_update.php new file mode 100644 index 00000000..73cad84d --- /dev/null +++ b/app/Template/notification/task_internal_link_create_update.php @@ -0,0 +1,11 @@ +<h2><?= $this->text->e($task['title']) ?> (#<?= $task['id'] ?>)</h2> + +<p> + <?= e( + 'This task is now linked to the task %s with the relation "%s"', + $this->url->link(t('#%d', $task_link['opposite_task_id']), 'TaskViewController', 'show', array('task_id' => $task_link['opposite_task_id'])), + $this->text->e($task_link['label']) + ) ?> +</p> + +<?= $this->render('notification/footer', array('task' => $task, 'application_url' => $application_url)) ?> diff --git a/app/Template/notification/task_internal_link_delete.php b/app/Template/notification/task_internal_link_delete.php new file mode 100644 index 00000000..bb54e0a7 --- /dev/null +++ b/app/Template/notification/task_internal_link_delete.php @@ -0,0 +1,11 @@ +<h2><?= $this->text->e($task['title']) ?> (#<?= $task['id'] ?>)</h2> + +<p> + <?= e( + 'The link with the relation "%s" to the task %s have been removed', + $this->text->e($task_link['label']), + $this->url->link(t('#%d', $task_link['opposite_task_id']), 'TaskViewController', 'show', array('task_id' => $task_link['opposite_task_id'])) + ) ?> +</p> + +<?= $this->render('notification/footer', array('task' => $task, 'application_url' => $application_url)) ?> diff --git a/app/Template/plugin/sidebar.php b/app/Template/plugin/sidebar.php index e1b47632..dd1a2a6b 100644 --- a/app/Template/plugin/sidebar.php +++ b/app/Template/plugin/sidebar.php @@ -1,5 +1,4 @@ <div class="sidebar"> - <h2><?= t('Actions') ?></h2> <ul> <li <?= $this->app->checkMenuSelection('PluginController', 'show') ?>> <?= $this->url->link(t('Installed Plugins'), 'PluginController', 'show') ?> diff --git a/app/Template/project/sidebar.php b/app/Template/project/sidebar.php index 9bc0c9c4..3be0da48 100644 --- a/app/Template/project/sidebar.php +++ b/app/Template/project/sidebar.php @@ -1,5 +1,4 @@ <div class="sidebar"> - <h2><?= t('Actions') ?></h2> <ul> <li <?= $this->app->checkMenuSelection('ProjectViewController', 'show') ?>> <?= $this->url->link(t('Summary'), 'ProjectViewController', 'show', array('project_id' => $project['id'])) ?> @@ -32,6 +31,9 @@ <li <?= $this->app->checkMenuSelection('CategoryController') ?>> <?= $this->url->link(t('Categories'), 'CategoryController', 'index', array('project_id' => $project['id'])) ?> </li> + <li <?= $this->app->checkMenuSelection('ProjectTagController') ?>> + <?= $this->url->link(t('Tags'), 'ProjectTagController', 'index', array('project_id' => $project['id'])) ?> + </li> <?php if ($project['is_private'] == 0): ?> <li <?= $this->app->checkMenuSelection('ProjectPermissionController') ?>> <?= $this->url->link(t('Permissions'), 'ProjectPermissionController', 'index', array('project_id' => $project['id'])) ?> diff --git a/app/Template/project_creation/create.php b/app/Template/project_creation/create.php index 01d06bab..b90b15c4 100644 --- a/app/Template/project_creation/create.php +++ b/app/Template/project_creation/create.php @@ -19,13 +19,14 @@ <p class="alert"><?= t('Which parts of the project do you want to duplicate?') ?></p> <?php if (! $is_private): ?> - <?= $this->form->checkbox('projectPermission', t('Permissions'), 1, true) ?> + <?= $this->form->checkbox('projectPermissionModel', t('Permissions'), 1, true) ?> <?php endif ?> <?= $this->form->checkbox('categoryModel', t('Categories'), 1, true) ?> + <?= $this->form->checkbox('tagDuplicationModel', t('Tags'), 1, true) ?> <?= $this->form->checkbox('actionModel', t('Actions'), 1, true) ?> <?= $this->form->checkbox('swimlaneModel', t('Swimlanes'), 1, true) ?> - <?= $this->form->checkbox('taskModel', t('Tasks'), 1, false) ?> + <?= $this->form->checkbox('projectTaskDuplicationModel', t('Tasks'), 1, false) ?> </div> <div class="form-actions"> diff --git a/app/Template/project_header/dropdown.php b/app/Template/project_header/dropdown.php index 79a1b389..f8901289 100644 --- a/app/Template/project_header/dropdown.php +++ b/app/Template/project_header/dropdown.php @@ -20,14 +20,6 @@ <i class="fa fa-arrows-h fa-fw"></i> <a href="#" class="filter-toggle-scrolling" title="<?= t('Keyboard shortcut: "%s"', 'c') ?>"><?= t('Horizontal scrolling') ?></a> </span> </li> - <li> - <span class="filter-max-height" style="display: none"> - <i class="fa fa-arrows-v fa-fw"></i> <a href="#" class="filter-toggle-height"><?= t('Set maximum column height') ?></a> - </span> - <span class="filter-min-height"> - <i class="fa fa-arrows-v fa-fw"></i> <a href="#" class="filter-toggle-height"><?= t('Remove maximum column height') ?></a> - </span> - </li> <?php endif ?> <?php if ($this->user->hasProjectAccess('TaskCreationController', 'show', $project['id'])): ?> diff --git a/app/Template/project_header/header.php b/app/Template/project_header/header.php index aaa8137b..6c91e38b 100644 --- a/app/Template/project_header/header.php +++ b/app/Template/project_header/header.php @@ -1,15 +1,21 @@ <div class="project-header"> <?= $this->hook->render('template:project:header:before', array('project' => $project)) ?> - <?= $this->render('project_header/dropdown', array('project' => $project, 'board_view' => $board_view)) ?> - <?= $this->render('project_header/views', array('project' => $project, 'filters' => $filters)) ?> - <?= $this->render('project_header/search', array( - 'project' => $project, - 'filters' => $filters, - 'custom_filters_list' => isset($custom_filters_list) ? $custom_filters_list : array(), - 'users_list' => isset($users_list) ? $users_list : array(), - 'categories_list' => isset($categories_list) ? $categories_list : array(), - )) ?> + <div class="dropdown-component"> + <?= $this->render('project_header/dropdown', array('project' => $project, 'board_view' => $board_view)) ?> + </div> + <div class="views-switcher-component"> + <?= $this->render('project_header/views', array('project' => $project, 'filters' => $filters)) ?> + </div> + <div class="filter-box-component"> + <?= $this->render('project_header/search', array( + 'project' => $project, + 'filters' => $filters, + 'custom_filters_list' => isset($custom_filters_list) ? $custom_filters_list : array(), + 'users_list' => isset($users_list) ? $users_list : array(), + 'categories_list' => isset($categories_list) ? $categories_list : array(), + )) ?> + </div> <?= $this->hook->render('template:project:header:after', array('project' => $project)) ?> -</div>
\ No newline at end of file +</div> diff --git a/app/Template/project_header/search.php b/app/Template/project_header/search.php index 8885d9c9..512e88d7 100644 --- a/app/Template/project_header/search.php +++ b/app/Template/project_header/search.php @@ -3,43 +3,54 @@ <?= $this->form->hidden('controller', $filters) ?> <?= $this->form->hidden('action', $filters) ?> <?= $this->form->hidden('project_id', $filters) ?> - <?= $this->form->text('search', $filters, array(), array('placeholder="'.t('Filter').'"')) ?> - <?= $this->render('app/filters_helper', array('reset' => 'status:open', 'project' => $project)) ?> + <div class="input-addon"> + <?= $this->form->text('search', $filters, array(), array('placeholder="'.t('Filter').'"'), 'input-addon-field') ?> + <div class="input-addon-item"> + <?= $this->render('app/filters_helper', array('reset' => 'status:open', 'project' => $project)) ?> + </div> - <?php if (isset($custom_filters_list) && ! empty($custom_filters_list)): ?> - <div class="dropdown"> - <a href="#" class="dropdown-menu dropdown-menu-link-icon" title="<?= t('Custom filters') ?>"><i class="fa fa-bookmark fa-fw"></i><i class="fa fa-caret-down"></i></a> - <ul> - <?php foreach ($custom_filters_list as $filter): ?> - <li><a href="#" class="filter-helper" data-<?php if ($filter['append']): ?><?= 'append-' ?><?php endif ?>filter='<?= $this->text->e($filter['filter']) ?>'><?= $this->text->e($filter['name']) ?></a></li> - <?php endforeach ?> - </ul> - </div> - <?php endif ?> + <?php if (isset($custom_filters_list) && ! empty($custom_filters_list)): ?> + <div class="input-addon-item"> + <div class="dropdown"> + <a href="#" class="dropdown-menu dropdown-menu-link-icon" title="<?= t('Custom filters') ?>"><i class="fa fa-bookmark fa-fw"></i><i class="fa fa-caret-down"></i></a> + <ul> + <?php foreach ($custom_filters_list as $filter): ?> + <li><a href="#" class="filter-helper" data-<?php if ($filter['append']): ?><?= 'append-' ?><?php endif ?>filter='<?= $this->text->e($filter['filter']) ?>'><?= $this->text->e($filter['name']) ?></a></li> + <?php endforeach ?> + </ul> + </div> + </div> + <?php endif ?> - <?php if (isset($users_list)): ?> - <div class="dropdown"> - <a href="#" class="dropdown-menu dropdown-menu-link-icon" title="<?= t('User filters') ?>"><i class="fa fa-users fa-fw"></i> <i class="fa fa-caret-down"></i></a> - <ul> - <li><a href="#" class="filter-helper" data-unique-filter="assignee:nobody"><?= t('Not assigned') ?></a></li> - <?php foreach ($users_list as $user): ?> - <li><a href="#" class="filter-helper" data-unique-filter='assignee:"<?= $this->text->e($user) ?>"'><?= $this->text->e($user) ?></a></li> - <?php endforeach ?> - </ul> - </div> - <?php endif ?> + <?php if (isset($users_list)): ?> + <div class="input-addon-item"> + <div class="dropdown"> + <a href="#" class="dropdown-menu dropdown-menu-link-icon" title="<?= t('User filters') ?>"><i class="fa fa-users fa-fw"></i> <i class="fa fa-caret-down"></i></a> + <ul> + <li><a href="#" class="filter-helper" data-unique-filter="assignee:nobody"><?= t('Not assigned') ?></a></li> + <?php foreach ($users_list as $user): ?> + <li><a href="#" class="filter-helper" data-unique-filter='assignee:"<?= $this->text->e($user) ?>"'><?= $this->text->e($user) ?></a></li> + <?php endforeach ?> + </ul> + </div> + </div> + <?php endif ?> - <?php if (isset($categories_list) && ! empty($categories_list)): ?> - <div class="dropdown"> - <a href="#" class="dropdown-menu dropdown-menu-link-icon" title="<?= t('Category filters') ?>"><i class="fa fa-tags fa-fw"></i><i class="fa fa-caret-down"></i></a> - <ul> - <li><a href="#" class="filter-helper" data-unique-filter="category:none"><?= t('No category') ?></a></li> - <?php foreach ($categories_list as $category): ?> - <li><a href="#" class="filter-helper" data-unique-filter='category:"<?= $this->text->e($category) ?>"'><?= $this->text->e($category) ?></a></li> - <?php endforeach ?> - </ul> + <?php if (isset($categories_list) && ! empty($categories_list)): ?> + <div class="input-addon-item"> + <div class="dropdown"> + <a href="#" class="dropdown-menu dropdown-menu-link-icon" title="<?= t('Category filters') ?>"><i class="fa fa-tags fa-fw"></i><i class="fa fa-caret-down"></i></a> + <ul> + <li><a href="#" class="filter-helper" data-unique-filter="category:none"><?= t('No category') ?></a></li> + <?php foreach ($categories_list as $category): ?> + <li><a href="#" class="filter-helper" data-unique-filter='category:"<?= $this->text->e($category) ?>"'><?= $this->text->e($category) ?></a></li> + <?php endforeach ?> + </ul> + </div> + </div> + <?php endif ?> </div> - <?php endif ?> + </form> </div> diff --git a/app/Template/project_list/show.php b/app/Template/project_list/show.php index 8b9f1396..8e4c3e6a 100644 --- a/app/Template/project_list/show.php +++ b/app/Template/project_list/show.php @@ -12,13 +12,13 @@ <?php if ($paginator->isEmpty()): ?> <p class="alert"><?= t('No project') ?></p> <?php else: ?> - <table class="table-stripped table-small"> + <table class="table-striped table-scrolling"> <tr> - <th class="column-3"><?= $paginator->order(t('Id'), 'id') ?></th> - <th class="column-5"><?= $paginator->order(t('Status'), 'is_active') ?></th> + <th class="column-5"><?= $paginator->order(t('Id'), 'id') ?></th> + <th class="column-8"><?= $paginator->order(t('Status'), 'is_active') ?></th> <th class="column-15"><?= $paginator->order(t('Project'), 'name') ?></th> - <th class="column-8"><?= $paginator->order(t('Start date'), 'start_date') ?></th> - <th class="column-8"><?= $paginator->order(t('End date'), 'end_date') ?></th> + <th class="column-10"><?= $paginator->order(t('Start date'), 'start_date') ?></th> + <th class="column-10"><?= $paginator->order(t('End date'), 'end_date') ?></th> <th class="column-15"><?= $paginator->order(t('Owner'), 'owner_id') ?></th> <?php if ($this->user->hasAccess('ProjectUserOverviewController', 'managers')): ?> <th class="column-10"><?= t('Users') ?></th> @@ -73,7 +73,7 @@ <td class="dashboard-project-stats"> <?php foreach ($project['columns'] as $column): ?> <strong title="<?= t('Task count') ?>"><?= $column['nb_tasks'] ?></strong> - <span><?= $this->text->e($column['title']) ?></span> + <small><?= $this->text->e($column['title']) ?></small> <?php endforeach ?> </td> </tr> diff --git a/app/Template/project_overview/columns.php b/app/Template/project_overview/columns.php index cc5782bd..daae9ca7 100644 --- a/app/Template/project_overview/columns.php +++ b/app/Template/project_overview/columns.php @@ -1,8 +1,8 @@ <div class="project-overview-columns"> <?php foreach ($project['columns'] as $column): ?> <div class="project-overview-column"> - <strong title="<?= t('Task count') ?>"><?= $column['nb_tasks'] ?></strong><br> - <span><?= $this->text->e($column['title']) ?></span> + <strong title="<?= t('Task count') ?>"><?= $column['nb_tasks'] ?></strong> + <small><?= $this->text->e($column['title']) ?></small> </div> <?php endforeach ?> </div> diff --git a/app/Template/project_overview/files.php b/app/Template/project_overview/files.php index fa870938..826e6325 100644 --- a/app/Template/project_overview/files.php +++ b/app/Template/project_overview/files.php @@ -1,5 +1,5 @@ <?php if (! empty($files)): ?> - <table class="table-stripped"> + <table class="table-striped table-scrolling"> <tr> <th><?= t('Filename') ?></th> <th><?= t('Creator') ?></th> diff --git a/app/Template/project_permission/index.php b/app/Template/project_permission/index.php index d850ec50..c7b17782 100644 --- a/app/Template/project_permission/index.php +++ b/app/Template/project_permission/index.php @@ -9,7 +9,7 @@ <?php if (empty($users)): ?> <div class="alert"><?= t('No user have been allowed specifically.') ?></div> <?php else: ?> - <table> + <table class="table-scrolling"> <tr> <th class="column-50"><?= t('User') ?></th> <th><?= t('Role') ?></th> @@ -69,7 +69,7 @@ <?php if (empty($groups)): ?> <div class="alert"><?= t('No group have been allowed specifically.') ?></div> <?php else: ?> - <table> + <table class="table-scrolling"> <tr> <th class="column-50"><?= t('Group') ?></th> <th><?= t('Role') ?></th> diff --git a/app/Template/project_tag/create.php b/app/Template/project_tag/create.php new file mode 100644 index 00000000..bfd1084a --- /dev/null +++ b/app/Template/project_tag/create.php @@ -0,0 +1,16 @@ +<div class="page-header"> + <h2><?= t('Add new tag') ?></h2> +</div> +<form method="post" class="popover-form" action="<?= $this->url->href('ProjectTagController', 'save', array('project_id' => $project['id'])) ?>" autocomplete="off"> + <?= $this->form->csrf() ?> + <?= $this->form->hidden('project_id', $values) ?> + + <?= $this->form->label(t('Name'), 'name') ?> + <?= $this->form->text('name', $values, $errors, array('autofocus', 'required', 'maxlength="255"')) ?> + + <div class="form-actions"> + <button type="submit" class="btn btn-blue"><?= t('Save') ?></button> + <?= t('or') ?> + <?= $this->url->link(t('cancel'), 'ProjectTagController', 'index', array('project_id' => $project['id']), false, 'close-popover') ?> + </div> +</form> diff --git a/app/Template/project_tag/edit.php b/app/Template/project_tag/edit.php new file mode 100644 index 00000000..9bf261bd --- /dev/null +++ b/app/Template/project_tag/edit.php @@ -0,0 +1,17 @@ +<div class="page-header"> + <h2><?= t('Edit a tag') ?></h2> +</div> +<form method="post" class="popover-form" action="<?= $this->url->href('ProjectTagController', 'update', array('tag_id' => $tag['id'], 'project_id' => $project['id'])) ?>" autocomplete="off"> + <?= $this->form->csrf() ?> + <?= $this->form->hidden('id', $values) ?> + <?= $this->form->hidden('project_id', $values) ?> + + <?= $this->form->label(t('Name'), 'name') ?> + <?= $this->form->text('name', $values, $errors, array('autofocus', 'required', 'maxlength="255"')) ?> + + <div class="form-actions"> + <button type="submit" class="btn btn-blue"><?= t('Save') ?></button> + <?= t('or') ?> + <?= $this->url->link(t('cancel'), 'ProjectTagController', 'index', array(), false, 'close-popover') ?> + </div> +</form> diff --git a/app/Template/project_tag/index.php b/app/Template/project_tag/index.php new file mode 100644 index 00000000..f77e21ee --- /dev/null +++ b/app/Template/project_tag/index.php @@ -0,0 +1,31 @@ +<div class="page-header"> + <h2><?= t('Project tags') ?></h2> + <ul> + <li> + <i class="fa fa-plus" aria-hidden="true"></i> + <?= $this->url->link(t('Add new tag'), 'ProjectTagController', 'create', array('project_id' => $project['id']), false, 'popover') ?> + </li> + </ul> +</div> + +<?php if (empty($tags)): ?> + <p class="alert"><?= t('There is no specific tag for this project at the moment.') ?></p> +<?php else: ?> + <table class="table-striped table-scrolling"> + <tr> + <th class="column-80"><?= t('Tag') ?></th> + <th><?= t('Action') ?></th> + </tr> + <?php foreach ($tags as $tag): ?> + <tr> + <td><?= $this->text->e($tag['name']) ?></td> + <td> + <i class="fa fa-times" aria-hidden="true"></i> + <?= $this->url->link(t('Remove'), 'ProjectTagController', 'confirm', array('tag_id' => $tag['id'], 'project_id' => $project['id']), false, 'popover') ?> + <i class="fa fa-pencil-square-o" aria-hidden="true"></i> + <?= $this->url->link(t('Edit'), 'ProjectTagController', 'edit', array('tag_id' => $tag['id'], 'project_id' => $project['id']), false, 'popover') ?> + </td> + </tr> + <?php endforeach ?> + </table> +<?php endif ?> diff --git a/app/Template/project_tag/remove.php b/app/Template/project_tag/remove.php new file mode 100644 index 00000000..f4aadab1 --- /dev/null +++ b/app/Template/project_tag/remove.php @@ -0,0 +1,15 @@ +<div class="page-header"> + <h2><?= t('Remove a tag') ?></h2> +</div> + +<div class="confirm"> + <p class="alert alert-info"> + <?= t('Do you really want to remove this tag: "%s"?', $tag['name']) ?> + </p> + + <div class="form-actions"> + <?= $this->url->link(t('Yes'), 'ProjectTagController', 'remove', array('tag_id' => $tag['id'], 'project_id' => $project['id']), true, 'btn btn-red popover-link') ?> + <?= t('or') ?> + <?= $this->url->link(t('cancel'), 'ProjectTagController', 'index', array('project_id' => $project['id']), false, 'close-popover') ?> + </div> +</div> diff --git a/app/Template/project_user_overview/roles.php b/app/Template/project_user_overview/roles.php index 87c8df85..011714d4 100644 --- a/app/Template/project_user_overview/roles.php +++ b/app/Template/project_user_overview/roles.php @@ -1,7 +1,7 @@ <?php if ($paginator->isEmpty()): ?> <p class="alert"><?= t('No project') ?></p> <?php else: ?> - <table class="table-fixed"> + <table class="table-fixed table-scrolling"> <tr> <th class="column-20"><?= $paginator->order(t('User'), 'users.username') ?></th> <th class="column-25"><?= $paginator->order(t('Project'), 'projects.name') ?></th> diff --git a/app/Template/project_user_overview/sidebar.php b/app/Template/project_user_overview/sidebar.php index 9a87d4eb..ccbf9cab 100644 --- a/app/Template/project_user_overview/sidebar.php +++ b/app/Template/project_user_overview/sidebar.php @@ -1,6 +1,4 @@ <div class="sidebar"> - <h2><?= t('Actions') ?></h2> - <?= $this->form->select( 'user_id', $users, diff --git a/app/Template/project_user_overview/tasks.php b/app/Template/project_user_overview/tasks.php index af0a3d97..8d682170 100644 --- a/app/Template/project_user_overview/tasks.php +++ b/app/Template/project_user_overview/tasks.php @@ -1,7 +1,7 @@ <?php if ($paginator->isEmpty()): ?> <p class="alert"><?= t('No tasks found.') ?></p> <?php elseif (! $paginator->isEmpty()): ?> - <table class="table-small"> + <table class="table-small table-striped table-scrolling"> <tr> <th class="column-5"><?= $paginator->order(t('Id'), 'tasks.id') ?></th> <th class="column-10"><?= $paginator->order(t('Project'), 'projects.name') ?></th> diff --git a/app/Template/project_user_overview/tooltip_users.php b/app/Template/project_user_overview/tooltip_users.php index 7117a87f..99b00030 100644 --- a/app/Template/project_user_overview/tooltip_users.php +++ b/app/Template/project_user_overview/tooltip_users.php @@ -1,7 +1,7 @@ <?php if (empty($users)): ?> <p><?= t('There is no project member.') ?></p> <?php else: ?> - <table> + <table class="table-small"> <?php foreach ($roles as $role => $role_name): ?> <?php if (isset($users[$role])): ?> <tr><th><?= $role_name ?></th></tr> diff --git a/app/Template/project_view/duplicate.php b/app/Template/project_view/duplicate.php index d2cd127a..561378d1 100644 --- a/app/Template/project_view/duplicate.php +++ b/app/Template/project_view/duplicate.php @@ -11,14 +11,15 @@ <?= $this->form->csrf() ?> <?php if ($project['is_private'] == 0): ?> - <?= $this->form->checkbox('projectPermission', t('Permissions'), 1, true) ?> + <?= $this->form->checkbox('projectPermissionModel', t('Permissions'), 1, true) ?> <?php endif ?> <?= $this->form->checkbox('categoryModel', t('Categories'), 1, true) ?> + <?= $this->form->checkbox('tagDuplicationModel', t('Tags'), 1, true) ?> <?= $this->form->checkbox('actionModel', t('Actions'), 1, true) ?> <?= $this->form->checkbox('swimlaneModel', t('Swimlanes'), 1, false) ?> - <?= $this->form->checkbox('taskModel', t('Tasks'), 1, false) ?> <?= $this->form->checkbox('projectMetadataModel', t('Metadata'), 1, false) ?> + <?= $this->form->checkbox('projectTaskDuplicationModel', t('Tasks'), 1, false) ?> <div class="form-actions"> <button type="submit" class="btn btn-red"><?= t('Duplicate') ?></button> diff --git a/app/Template/project_view/show.php b/app/Template/project_view/show.php index 5efe8ce6..afe60384 100644 --- a/app/Template/project_view/show.php +++ b/app/Template/project_view/show.php @@ -52,11 +52,12 @@ <div class="page-header"> <h2><?= t('Board') ?></h2> </div> -<table class="table-stripped"> +<table class="table-striped table-scrolling"> <tr> - <th class="column-60"><?= t('Column') ?></th> + <th class="column-40"><?= t('Column') ?></th> <th class="column-20"><?= t('Task limit') ?></th> <th class="column-20"><?= t('Active tasks') ?></th> + <th class="column-20"><?= t('Hide tasks in this column in the dashboard') ?></th> </tr> <?php foreach ($stats['columns'] as $column): ?> <tr> @@ -70,6 +71,13 @@ </td> <td><?= $column['task_limit'] ?: '∞' ?></td> <td><?= $column['nb_active_tasks'] ?></td> + <td> + <?php if ($column['hide_in_dashboard'] == 1): ?> + <?= t('Yes') ?> + <?php else: ?> + <?= t('No') ?> + <?php endif ?> + </td> </tr> <?php endforeach ?> </table> diff --git a/app/Template/search/activity.php b/app/Template/search/activity.php index 9abc7d7e..1dfd9234 100644 --- a/app/Template/search/activity.php +++ b/app/Template/search/activity.php @@ -12,8 +12,13 @@ <form method="get" action="<?= $this->url->dir() ?>" class="search"> <?= $this->form->hidden('controller', $values) ?> <?= $this->form->hidden('action', $values) ?> - <?= $this->form->text('search', $values, array(), array(empty($values['search']) ? 'autofocus' : '', 'placeholder="'.t('Search').'"'), 'form-input-large') ?> - <?= $this->render('activity/filter_dropdown') ?> + + <div class="input-addon"> + <?= $this->form->text('search', $values, array(), array(empty($values['search']) ? 'autofocus' : '', 'placeholder="'.t('Search').'"'), 'input-addon-field') ?> + <div class="input-addon-item"> + <?= $this->render('app/filters_helper') ?> + </div> + </div> </form> </div> diff --git a/app/Template/search/index.php b/app/Template/search/index.php index bc528af7..c59a5c99 100644 --- a/app/Template/search/index.php +++ b/app/Template/search/index.php @@ -12,8 +12,13 @@ <form method="get" action="<?= $this->url->dir() ?>" class="search"> <?= $this->form->hidden('controller', $values) ?> <?= $this->form->hidden('action', $values) ?> - <?= $this->form->text('search', $values, array(), array(empty($values['search']) ? 'autofocus' : '', 'placeholder="'.t('Search').'"'), 'form-input-large') ?> - <?= $this->render('app/filters_helper') ?> + + <div class="input-addon"> + <?= $this->form->text('search', $values, array(), array(empty($values['search']) ? 'autofocus' : '', 'placeholder="'.t('Search').'"'), 'input-addon-field') ?> + <div class="input-addon-item"> + <?= $this->render('app/filters_helper') ?> + </div> + </div> </form> </div> diff --git a/app/Template/search/results.php b/app/Template/search/results.php index 8376b9e8..8c439a8a 100644 --- a/app/Template/search/results.php +++ b/app/Template/search/results.php @@ -1,4 +1,4 @@ -<table class="table-fixed table-small"> +<table class="table-small table-scrolling"> <tr> <th class="column-8"><?= $paginator->order(t('Project'), 'tasks.project_id') ?></th> <th class="column-5"><?= $paginator->order(t('Id'), 'tasks.id') ?></th> diff --git a/app/Template/subtask/table.php b/app/Template/subtask/table.php index 4c6484ef..5c60df44 100644 --- a/app/Template/subtask/table.php +++ b/app/Template/subtask/table.php @@ -1,6 +1,6 @@ <?php if (! empty($subtasks)): ?> <table - class="subtasks-table table-stripped" + class="subtasks-table table-striped table-scrolling" data-save-position-url="<?= $this->url->href('SubtaskController', 'movePosition', array('project_id' => $task['project_id'], 'task_id' => $task['id'])) ?>" > <thead> diff --git a/app/Template/swimlane/table.php b/app/Template/swimlane/table.php index be123b08..cefef9de 100644 --- a/app/Template/swimlane/table.php +++ b/app/Template/swimlane/table.php @@ -1,5 +1,5 @@ <table - class="swimlanes-table table-stripped" + class="swimlanes-table table-striped table-scrolling" data-save-position-url="<?= $this->url->href('SwimlaneController', 'move', array('project_id' => $project['id'])) ?>"> <thead> <tr> diff --git a/app/Template/tag/create.php b/app/Template/tag/create.php new file mode 100644 index 00000000..9b32bc46 --- /dev/null +++ b/app/Template/tag/create.php @@ -0,0 +1,16 @@ +<div class="page-header"> + <h2><?= t('Add new tag') ?></h2> +</div> +<form method="post" class="popover-form" action="<?= $this->url->href('TagController', 'save') ?>" autocomplete="off"> + <?= $this->form->csrf() ?> + <?= $this->form->hidden('project_id', $values) ?> + + <?= $this->form->label(t('Name'), 'name') ?> + <?= $this->form->text('name', $values, $errors, array('autofocus', 'required', 'maxlength="255"')) ?> + + <div class="form-actions"> + <button type="submit" class="btn btn-blue"><?= t('Save') ?></button> + <?= t('or') ?> + <?= $this->url->link(t('cancel'), 'TagController', 'index', array(), false, 'close-popover') ?> + </div> +</form> diff --git a/app/Template/tag/edit.php b/app/Template/tag/edit.php new file mode 100644 index 00000000..f751ff49 --- /dev/null +++ b/app/Template/tag/edit.php @@ -0,0 +1,17 @@ +<div class="page-header"> + <h2><?= t('Edit a tag') ?></h2> +</div> +<form method="post" class="popover-form" action="<?= $this->url->href('TagController', 'update', array('tag_id' => $tag['id'])) ?>" autocomplete="off"> + <?= $this->form->csrf() ?> + <?= $this->form->hidden('id', $values) ?> + <?= $this->form->hidden('project_id', $values) ?> + + <?= $this->form->label(t('Name'), 'name') ?> + <?= $this->form->text('name', $values, $errors, array('autofocus', 'required', 'maxlength="255"')) ?> + + <div class="form-actions"> + <button type="submit" class="btn btn-blue"><?= t('Save') ?></button> + <?= t('or') ?> + <?= $this->url->link(t('cancel'), 'TagController', 'index', array(), false, 'close-popover') ?> + </div> +</form> diff --git a/app/Template/tag/index.php b/app/Template/tag/index.php new file mode 100644 index 00000000..8e0c9a06 --- /dev/null +++ b/app/Template/tag/index.php @@ -0,0 +1,31 @@ +<div class="page-header"> + <h2><?= t('Global tags') ?></h2> + <ul> + <li> + <i class="fa fa-plus" aria-hidden="true"></i> + <?= $this->url->link(t('Add new tag'), 'TagController', 'create', array(), false, 'popover') ?> + </li> + </ul> +</div> + +<?php if (empty($tags)): ?> + <p class="alert"><?= t('There is no global tag at the moment.') ?></p> +<?php else: ?> + <table class="table-striped table-scrolling"> + <tr> + <th class="column-80"><?= t('Tag') ?></th> + <th><?= t('Action') ?></th> + </tr> + <?php foreach ($tags as $tag): ?> + <tr> + <td><?= $this->text->e($tag['name']) ?></td> + <td> + <i class="fa fa-times" aria-hidden="true"></i> + <?= $this->url->link(t('Remove'), 'TagController', 'confirm', array('tag_id' => $tag['id']), false, 'popover') ?> + <i class="fa fa-pencil-square-o" aria-hidden="true"></i> + <?= $this->url->link(t('Edit'), 'TagController', 'edit', array('tag_id' => $tag['id']), false, 'popover') ?> + </td> + </tr> + <?php endforeach ?> + </table> +<?php endif ?> diff --git a/app/Template/tag/remove.php b/app/Template/tag/remove.php new file mode 100644 index 00000000..46ea3f99 --- /dev/null +++ b/app/Template/tag/remove.php @@ -0,0 +1,15 @@ +<div class="page-header"> + <h2><?= t('Remove a tag') ?></h2> +</div> + +<div class="confirm"> + <p class="alert alert-info"> + <?= t('Do you really want to remove this tag: "%s"?', $tag['name']) ?> + </p> + + <div class="form-actions"> + <?= $this->url->link(t('Yes'), 'TagController', 'remove', array('tag_id' => $tag['id']), true, 'btn btn-red popover-link') ?> + <?= t('or') ?> + <?= $this->url->link(t('cancel'), 'TagController', 'index', array(), false, 'close-popover') ?> + </div> +</div> diff --git a/app/Template/task/analytics.php b/app/Template/task/analytics.php index db2d0cef..071f24a7 100644 --- a/app/Template/task/analytics.php +++ b/app/Template/task/analytics.php @@ -1,6 +1,10 @@ -<div class="task-show-title color-<?= $task['color_id'] ?>"> - <h2><?= $this->text->e($task['title']) ?></h2> -</div> +<?= $this->render('task/details', array( + 'task' => $task, + 'tags' => $tags, + 'project' => $project, + 'editable' => false, +)) ?> + <div class="page-header"> <h2><?= t('Analytics') ?></h2> </div> @@ -14,7 +18,7 @@ <h3 id="analytic-task-time-column"><?= t('Time spent into each column') ?></h3> <div id="chart" data-metrics='<?= json_encode($time_spent_columns, JSON_HEX_APOS) ?>' data-label="<?= t('Time spent') ?>"></div> -<table class="table-stripped"> +<table class="table-striped"> <tr> <th><?= t('Column') ?></th> <th><?= t('Time spent') ?></th> diff --git a/app/Template/task/color_picker.php b/app/Template/task/color_picker.php deleted file mode 100644 index 0c62fa70..00000000 --- a/app/Template/task/color_picker.php +++ /dev/null @@ -1,11 +0,0 @@ -<div class="color-picker"> -<?php foreach ($colors_list as $color_id => $color_name): ?> - <div - data-color-id="<?= $color_id ?>" - class="color-square color-<?= $color_id ?> <?= isset($values['color_id']) && $values['color_id'] === $color_id ? 'color-square-selected' : '' ?>" - title="<?= $this->text->e($color_name) ?>"> - </div> -<?php endforeach ?> -</div> - -<?= $this->form->hidden('color_id', $values) ?>
\ No newline at end of file diff --git a/app/Template/task/details.php b/app/Template/task/details.php index fe2bba67..a39c1bab 100644 --- a/app/Template/task/details.php +++ b/app/Template/task/details.php @@ -4,150 +4,164 @@ <?= $this->hook->render('template:task:details:top', array('task' => $task)) ?> <div class="task-summary-container color-<?= $task['color_id'] ?>"> - <div class="task-summary-column"> - <ul class="no-bullet"> - <li> - <strong><?= t('Status:') ?></strong> - <span> - <?php if ($task['is_active'] == 1): ?> - <?= t('open') ?> - <?php else: ?> - <?= t('closed') ?> + <div class="task-summary-columns"> + <div class="task-summary-column"> + <ul class="no-bullet"> + <li> + <strong><?= t('Status:') ?></strong> + <span> + <?php if ($task['is_active'] == 1): ?> + <?= t('open') ?> + <?php else: ?> + <?= t('closed') ?> + <?php endif ?> + </span> + </li> + <li> + <strong><?= t('Priority:') ?></strong> <span><?= $task['priority'] ?></span> + </li> + <?php if (! empty($task['reference'])): ?> + <li> + <strong><?= t('Reference:') ?></strong> <span><?= $this->text->e($task['reference']) ?></span> + </li> + <?php endif ?> + <?php if (! empty($task['score'])): ?> + <li> + <strong><?= t('Complexity:') ?></strong> <span><?= $this->text->e($task['score']) ?></span> + </li> <?php endif ?> - </span> - </li> - <li> - <strong><?= t('Priority:') ?></strong> <span><?= $task['priority'] ?></span> - </li> - <?php if (! empty($task['reference'])): ?> + <?php if ($project['is_public']): ?> <li> - <strong><?= t('Reference:') ?></strong> <span><?= $this->text->e($task['reference']) ?></span> + <small> + <i class="fa fa-external-link fa-fw"></i> + <?= $this->url->link(t('Public link'), 'TaskViewController', 'readonly', array('task_id' => $task['id'], 'token' => $project['token']), false, '', '', true) ?> + </small> </li> - <?php endif ?> - <?php if (! empty($task['score'])): ?> + <?php endif ?> + <?php if ($project['is_public'] && !$editable): ?> <li> - <strong><?= t('Complexity:') ?></strong> <span><?= $this->text->e($task['score']) ?></span> + <small> + <i class="fa fa-th fa-fw"></i> + <?= $this->url->link(t('Back to the board'), 'BoardViewController', 'readonly', array('token' => $project['token'])) ?> + </small> </li> - <?php endif ?> - <?php if ($project['is_public']): ?> - <li class="smaller"> - <i class="fa fa-external-link fa-fw"></i> - <?= $this->url->link(t('Public link'), 'TaskViewController', 'readonly', array('task_id' => $task['id'], 'token' => $project['token']), false, '', '', true) ?> - </li> - <?php endif ?> - <?php if ($project['is_public'] && !$editable): ?> - <li class="smaller"> - <i class="fa fa-th fa-fw"></i> - <?= $this->url->link(t('Back to the board'), 'BoardViewController', 'readonly', array('token' => $project['token'])) ?> - </li> - <?php endif ?> - <li class="smaller"> + <?php endif ?> - <?= $this->hook->render('template:task:details:first-column', array('task' => $task)) ?> - </ul> - </div> - <div class="task-summary-column"> - <ul class="no-bullet"> - <?php if (! empty($task['category_name'])): ?> + <?= $this->hook->render('template:task:details:first-column', array('task' => $task)) ?> + </ul> + </div> + <div class="task-summary-column"> + <ul class="no-bullet"> + <?php if (! empty($task['category_name'])): ?> + <li> + <strong><?= t('Category:') ?></strong> + <span><?= $this->text->e($task['category_name']) ?></span> + </li> + <?php endif ?> + <?php if (! empty($task['swimlane_name'])): ?> + <li> + <strong><?= t('Swimlane:') ?></strong> + <span><?= $this->text->e($task['swimlane_name']) ?></span> + </li> + <?php endif ?> <li> - <strong><?= t('Category:') ?></strong> - <span><?= $this->text->e($task['category_name']) ?></span> + <strong><?= t('Column:') ?></strong> + <span><?= $this->text->e($task['column_title']) ?></span> </li> - <?php endif ?> - <?php if (! empty($task['swimlane_name'])): ?> <li> - <strong><?= t('Swimlane:') ?></strong> - <span><?= $this->text->e($task['swimlane_name']) ?></span> + <strong><?= t('Position:') ?></strong> + <span><?= $task['position'] ?></span> </li> - <?php endif ?> - <li> - <strong><?= t('Column:') ?></strong> - <span><?= $this->text->e($task['column_title']) ?></span> - </li> - <li> - <strong><?= t('Position:') ?></strong> - <span><?= $task['position'] ?></span> - </li> - <?= $this->hook->render('template:task:details:second-column', array('task' => $task)) ?> - </ul> - </div> - <div class="task-summary-column"> - <ul class="no-bullet"> - <li> - <strong><?= t('Assignee:') ?></strong> - <span> - <?php if ($task['assignee_username']): ?> - <?= $this->text->e($task['assignee_name'] ?: $task['assignee_username']) ?> - <?php else: ?> - <?= t('not assigned') ?> + <?= $this->hook->render('template:task:details:second-column', array('task' => $task)) ?> + </ul> + </div> + <div class="task-summary-column"> + <ul class="no-bullet"> + <li> + <strong><?= t('Assignee:') ?></strong> + <span> + <?php if ($task['assignee_username']): ?> + <?= $this->text->e($task['assignee_name'] ?: $task['assignee_username']) ?> + <?php else: ?> + <?= t('not assigned') ?> + <?php endif ?> + </span> + </li> + <?php if ($task['creator_username']): ?> + <li> + <strong><?= t('Creator:') ?></strong> + <span><?= $this->text->e($task['creator_name'] ?: $task['creator_username']) ?></span> + </li> + <?php endif ?> + <?php if ($task['date_due']): ?> + <li> + <strong><?= t('Due date:') ?></strong> + <span><?= $this->dt->date($task['date_due']) ?></span> + </li> + <?php endif ?> + <?php if ($task['time_estimated']): ?> + <li> + <strong><?= t('Time estimated:') ?></strong> + <span><?= t('%s hours', $task['time_estimated']) ?></span> + </li> <?php endif ?> - </span> - </li> - <?php if ($task['creator_username']): ?> + <?php if ($task['time_spent']): ?> <li> - <strong><?= t('Creator:') ?></strong> - <span><?= $this->text->e($task['creator_name'] ?: $task['creator_username']) ?></span> + <strong><?= t('Time spent:') ?></strong> + <span><?= t('%s hours', $task['time_spent']) ?></span> </li> - <?php endif ?> - <?php if ($task['date_due']): ?> - <li> - <strong><?= t('Due date:') ?></strong> - <span><?= $this->dt->date($task['date_due']) ?></span> - </li> - <?php endif ?> - <?php if ($task['time_estimated']): ?> - <li> - <strong><?= t('Time estimated:') ?></strong> - <span><?= t('%s hours', $task['time_estimated']) ?></span> - </li> - <?php endif ?> - <?php if ($task['time_spent']): ?> - <li> - <strong><?= t('Time spent:') ?></strong> - <span><?= t('%s hours', $task['time_spent']) ?></span> - </li> - <?php endif ?> + <?php endif ?> - <?= $this->hook->render('template:task:details:third-column', array('task' => $task)) ?> - </ul> - </div> - <div class="task-summary-column"> - <ul class="no-bullet"> - <li> - <strong><?= t('Created:') ?></strong> - <span><?= $this->dt->datetime($task['date_creation']) ?></span> - </li> - <li> - <strong><?= t('Modified:') ?></strong> - <span><?= $this->dt->datetime($task['date_modification']) ?></span> - </li> - <?php if ($task['date_completed']): ?> - <li> - <strong><?= t('Completed:') ?></strong> - <span><?= $this->dt->datetime($task['date_completed']) ?></span> - </li> - <?php endif ?> - <?php if ($task['date_started']): ?> - <li> - <strong><?= t('Started:') ?></strong> - <span><?= $this->dt->datetime($task['date_started']) ?></span> - </li> - <?php endif ?> - <?php if ($task['date_moved']): ?> - <li> - <strong><?= t('Moved:') ?></strong> - <span><?= $this->dt->datetime($task['date_moved']) ?></span> - </li> - <?php endif ?> + <?= $this->hook->render('template:task:details:third-column', array('task' => $task)) ?> + </ul> + </div> + <div class="task-summary-column"> + <ul class="no-bullet"> + <li> + <strong><?= t('Created:') ?></strong> + <span><?= $this->dt->datetime($task['date_creation']) ?></span> + </li> + <li> + <strong><?= t('Modified:') ?></strong> + <span><?= $this->dt->datetime($task['date_modification']) ?></span> + </li> + <?php if ($task['date_completed']): ?> + <li> + <strong><?= t('Completed:') ?></strong> + <span><?= $this->dt->datetime($task['date_completed']) ?></span> + </li> + <?php endif ?> + <?php if ($task['date_started']): ?> + <li> + <strong><?= t('Started:') ?></strong> + <span><?= $this->dt->datetime($task['date_started']) ?></span> + </li> + <?php endif ?> + <?php if ($task['date_moved']): ?> + <li> + <strong><?= t('Moved:') ?></strong> + <span><?= $this->dt->datetime($task['date_moved']) ?></span> + </li> + <?php endif ?> - <?= $this->hook->render('template:task:details:fourth-column', array('task' => $task)) ?> - </ul> + <?= $this->hook->render('template:task:details:fourth-column', array('task' => $task)) ?> + </ul> + </div> </div> + <?php if (! empty($tags)): ?> + <div class="task-tags"> + <ul> + <?php foreach ($tags as $tag): ?> + <li><?= $this->text->e($tag) ?></li> + <?php endforeach ?> + </ul> + </div> + <?php endif ?> </div> <?php if ($editable && empty($task['date_started'])): ?> - <div class="task-summary-buttons"> + <div class="buttons-header"> <?= $this->url->button('fa-play', t('Set start date'), 'TaskModificationController', 'start', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> </div> <?php endif ?> diff --git a/app/Template/task/dropdown.php b/app/Template/task/dropdown.php index b6b9c789..95c7a88c 100644 --- a/app/Template/task/dropdown.php +++ b/app/Template/task/dropdown.php @@ -8,22 +8,10 @@ </li> <?php endif ?> <li> - <i class="fa fa-user fa-fw"></i> - <?= $this->url->link(t('Change assignee'), 'TaskPopoverController', 'changeAssignee', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?> - </li> - <li> - <i class="fa fa-tag fa-fw"></i> - <?= $this->url->link(t('Change category'), 'TaskPopoverController', 'changeCategory', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?> - </li> - <li> <i class="fa fa-pencil-square-o fa-fw"></i> <?= $this->url->link(t('Edit the task'), 'TaskModificationController', 'edit', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?> </li> <li> - <i class="fa fa-align-left fa-fw"></i> - <?= $this->url->link(t('Edit the description'), 'TaskModificationController', 'description', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?> - </li> - <li> <i class="fa fa-plus fa-fw"></i> <?= $this->url->link(t('Add a sub-task'), 'SubtaskController', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?> </li> diff --git a/app/Template/task/layout.php b/app/Template/task/layout.php index 00e0e9ae..7f6c2913 100644 --- a/app/Template/task/layout.php +++ b/app/Template/task/layout.php @@ -4,7 +4,6 @@ <section class="sidebar-container" id="task-view" data-edit-url="<?= $this->url->href('TaskModificationController', 'edit', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>" - data-description-url="<?= $this->url->href('TaskModificationController', 'description', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>" data-subtask-url="<?= $this->url->href('SubtaskController', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>" data-internal-link-url="<?= $this->url->href('TaskInternalLinkController', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>" data-comment-url="<?= $this->url->href('CommentController', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"> diff --git a/app/Template/task/public.php b/app/Template/task/public.php index 94782163..b8405ff7 100644 --- a/app/Template/task/public.php +++ b/app/Template/task/public.php @@ -1,5 +1,10 @@ <section id="main" class="public-task"> - <?= $this->render('task/details', array('task' => $task, 'project' => $project, 'editable' => false)) ?> + <?= $this->render('task/details', array( + 'task' => $task, + 'tags' => $tags, + 'project' => $project, + 'editable' => false, + )) ?> <?= $this->render('task/description', array( 'task' => $task, diff --git a/app/Template/task/show.php b/app/Template/task/show.php index 2b54eea8..a5c2d5a7 100644 --- a/app/Template/task/show.php +++ b/app/Template/task/show.php @@ -2,51 +2,64 @@ <?= $this->render('task/details', array( 'task' => $task, + 'tags' => $tags, 'project' => $project, 'editable' => $this->user->hasProjectAccess('TaskModificationController', 'edit', $project['id']), )) ?> -<?= $this->hook->render('template:task:show:before-description', array('task' => $task, 'project' => $project)) ?> -<?= $this->render('task/description', array('task' => $task)) ?> +<?php if (!empty($task['description'])): ?> + <?= $this->hook->render('template:task:show:before-description', array('task' => $task, 'project' => $project)) ?> + <?= $this->render('task/description', array('task' => $task)) ?> +<?php endif ?> -<?= $this->hook->render('template:task:show:before-subtasks', array('task' => $task, 'project' => $project)) ?> -<?= $this->render('subtask/show', array( - 'task' => $task, - 'subtasks' => $subtasks, - 'project' => $project, - 'editable' => true, -)) ?> +<?php if(!empty($subtasks)): ?> + <?= $this->hook->render('template:task:show:before-subtasks', array('task' => $task, 'project' => $project)) ?> + <?= $this->render('subtask/show', array( + 'task' => $task, + 'subtasks' => $subtasks, + 'project' => $project, + 'editable' => true, + )) ?> +<?php endif ?> -<?= $this->hook->render('template:task:show:before-internal-links', array('task' => $task, 'project' => $project)) ?> -<?= $this->render('task_internal_link/show', array( - 'task' => $task, - 'links' => $internal_links, - 'project' => $project, - 'link_label_list' => $link_label_list, - 'editable' => true, - 'is_public' => false, -)) ?> +<?php if (!empty($internal_links)): ?> + <?= $this->hook->render('template:task:show:before-internal-links', array('task' => $task, 'project' => $project)) ?> + <?= $this->render('task_internal_link/show', array( + 'task' => $task, + 'links' => $internal_links, + 'project' => $project, + 'link_label_list' => $link_label_list, + 'editable' => true, + 'is_public' => false, + )) ?> +<?php endif ?> -<?= $this->hook->render('template:task:show:before-external-links', array('task' => $task, 'project' => $project)) ?> -<?= $this->render('task_external_link/show', array( - 'task' => $task, - 'links' => $external_links, - 'project' => $project, -)) ?> +<?php if (!empty($external_links)): ?> + <?= $this->hook->render('template:task:show:before-external-links', array('task' => $task, 'project' => $project)) ?> + <?= $this->render('task_external_link/show', array( + 'task' => $task, + 'links' => $external_links, + 'project' => $project, + )) ?> +<?php endif ?> -<?= $this->hook->render('template:task:show:before-attachments', array('task' => $task, 'project' => $project)) ?> -<?= $this->render('task_file/show', array( - 'task' => $task, - 'files' => $files, - 'images' => $images -)) ?> +<?php if (!empty($files) || !empty($images)): ?> + <?= $this->hook->render('template:task:show:before-attachments', array('task' => $task, 'project' => $project)) ?> + <?= $this->render('task_file/show', array( + 'task' => $task, + 'files' => $files, + 'images' => $images + )) ?> +<?php endif ?> -<?= $this->hook->render('template:task:show:before-comments', array('task' => $task, 'project' => $project)) ?> -<?= $this->render('comments/show', array( - 'task' => $task, - 'comments' => $comments, - 'project' => $project, - 'editable' => $this->user->hasProjectAccess('CommentController', 'edit', $project['id']), -)) ?> +<?php if (!empty($comments)): ?> + <?= $this->hook->render('template:task:show:before-comments', array('task' => $task, 'project' => $project)) ?> + <?= $this->render('comments/show', array( + 'task' => $task, + 'comments' => $comments, + 'project' => $project, + 'editable' => $this->user->hasProjectAccess('CommentController', 'edit', $project['id']), + )) ?> +<?php endif ?> <?= $this->hook->render('template:task:show:bottom', array('task' => $task, 'project' => $project)) ?> diff --git a/app/Template/task/sidebar.php b/app/Template/task/sidebar.php index e77ec18a..728741ba 100644 --- a/app/Template/task/sidebar.php +++ b/app/Template/task/sidebar.php @@ -1,5 +1,7 @@ <div class="sidebar sidebar-icons"> - <h2><?= t('Task #%d', $task['id']) ?></h2> + <div class="sidebar-title"> + <h2><?= t('Task #%d', $task['id']) ?></h2> + </div> <ul> <li <?= $this->app->checkMenuSelection('TaskViewController', 'show') ?>> <i class="fa fa-newspaper-o fa-fw"></i> @@ -28,17 +30,15 @@ </ul> <?php if ($this->user->hasProjectAccess('TaskModificationController', 'edit', $task['project_id'])): ?> - <h2><?= t('Actions') ?></h2> + <div class="sidebar-title"> + <h2><?= t('Actions') ?></h2> + </div> <ul> <li> <i class="fa fa-pencil-square-o fa-fw"></i> <?= $this->url->link(t('Edit the task'), 'TaskModificationController', 'edit', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?> </li> <li> - <i class="fa fa-align-left fa-fw"></i> - <?= $this->url->link(t('Edit the description'), 'TaskModificationController', 'description', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?> - </li> - <li> <i class="fa fa-refresh fa-rotate-90 fa-fw"></i> <?= $this->url->link(t('Edit recurrence'), 'TaskRecurrenceController', 'edit', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?> </li> diff --git a/app/Template/task/time_tracking_details.php b/app/Template/task/time_tracking_details.php index 1a179522..7cb419e0 100644 --- a/app/Template/task/time_tracking_details.php +++ b/app/Template/task/time_tracking_details.php @@ -1,6 +1,9 @@ -<div class="task-show-title color-<?= $task['color_id'] ?>"> - <h2><?= $this->text->e($task['title']) ?></h2> -</div> +<?= $this->render('task/details', array( + 'task' => $task, + 'tags' => $tags, + 'project' => $project, + 'editable' => false, +)) ?> <?= $this->render('task/time_tracking_summary', array('task' => $task)) ?> @@ -8,7 +11,7 @@ <?php if ($subtask_paginator->isEmpty()): ?> <p class="alert"><?= t('There is nothing to show.') ?></p> <?php else: ?> - <table class="table-fixed"> + <table class="table-fixed table-scrolling"> <tr> <th class="column-15"><?= $subtask_paginator->order(t('User'), 'username') ?></th> <th><?= $subtask_paginator->order(t('Subtask'), 'subtask_title') ?></th> diff --git a/app/Template/task/transitions.php b/app/Template/task/transitions.php index 9e04c4e1..4a9f22ce 100644 --- a/app/Template/task/transitions.php +++ b/app/Template/task/transitions.php @@ -1,6 +1,9 @@ -<div class="task-show-title color-<?= $task['color_id'] ?>"> - <h2><?= $this->text->e($task['title']) ?></h2> -</div> +<?= $this->render('task/details', array( + 'task' => $task, + 'tags' => $tags, + 'project' => $project, + 'editable' => false, +)) ?> <div class="page-header"> <h2><?= t('Transitions') ?></h2> @@ -9,7 +12,7 @@ <?php if (empty($transitions)): ?> <p class="alert"><?= t('There is nothing to show.') ?></p> <?php else: ?> - <table class="table-stripped"> + <table class="table-striped table-scrolling"> <tr> <th><?= t('Date') ?></th> <th><?= t('Source column') ?></th> diff --git a/app/Template/task_bulk/show.php b/app/Template/task_bulk/show.php index 1391c2c1..11ddea31 100644 --- a/app/Template/task_bulk/show.php +++ b/app/Template/task_bulk/show.php @@ -1,5 +1,5 @@ <div class="page-header"> - <h2><?= t('Create tasks in bulk') ?></h2> + <h2><?= $this->text->e($project['name']) ?> > <?= t('Create tasks in bulk') ?></h2> </div> <form class="popover-form" method="post" action="<?= $this->url->href('TaskBulkController', 'save', array('project_id' => $project['id'])) ?>" autocomplete="off"> @@ -8,6 +8,7 @@ <?= $this->form->hidden('swimlane_id', $values) ?> <?= $this->form->hidden('project_id', $values) ?> + <?= $this->task->selectColor($values) ?> <?= $this->task->selectAssignee($users_list, $values, $errors) ?> <?= $this->task->selectCategory($categories_list, $values, $errors) ?> @@ -15,8 +16,6 @@ <?= $this->form->textarea('tasks', $values, $errors, array('placeholder="'.t('My task title').'"')) ?> <p class="form-help"><?= t('Enter one task by line.') ?></p> - <?= $this->render('task/color_picker', array('colors_list' => $colors_list, 'values' => $values)) ?> - <div class="form-actions"> <button type="submit" class="btn btn-blue"><?= t('Save') ?></button> <?= t('or') ?> <?= $this->url->link(t('cancel'), 'BoardViewController', 'show', array('project_id' => $project['id']), false, 'close-popover') ?> diff --git a/app/Template/task_creation/show.php b/app/Template/task_creation/show.php index 7bebbfe9..1a8a17d5 100644 --- a/app/Template/task_creation/show.php +++ b/app/Template/task_creation/show.php @@ -1,49 +1,45 @@ <div class="page-header"> - <h2><?= t('New task') ?></h2> + <h2><?= $this->text->e($project['name']) ?> > <?= t('New task') ?></h2> </div> <form class="popover-form" method="post" action="<?= $this->url->href('TaskCreationController', 'save', array('project_id' => $values['project_id'])) ?>" autocomplete="off"> - <?= $this->form->csrf() ?> - <div class="form-column"> - <?= $this->form->label(t('Title'), 'title') ?> - <?= $this->form->text('title', $values, $errors, array('autofocus', 'required', 'maxlength="200"', 'tabindex="1"'), 'form-input-large') ?> - - <?= $this->form->label(t('Description'), 'description') ?> - <?= $this->form->textarea( - 'description', - $values, - $errors, - array( - 'placeholder="'.t('Leave a description').'"', - 'tabindex="2"', - 'data-mention-search-url="'.$this->url->href('UserAjaxController', 'mention', array('project_id' => $values['project_id'])).'"' - ), - 'markdown-editor' - ) ?> - - <?= $this->render('task/color_picker', array('colors_list' => $colors_list, 'values' => $values)) ?> - - <?php if (! isset($duplicate)): ?> - <?= $this->form->checkbox('another_task', t('Create another task'), 1, isset($values['another_task']) && $values['another_task'] == 1) ?> - <?php endif ?> - - <?= $this->hook->render('template:task:form:left-column', array('values' => $values, 'errors' => $errors)) ?> - </div> - - <div class="form-column"> - <?= $this->form->hidden('project_id', $values) ?> - <?= $this->task->selectAssignee($users_list, $values, $errors) ?> - <?= $this->task->selectCategory($categories_list, $values, $errors) ?> - <?= $this->task->selectSwimlane($swimlanes_list, $values, $errors) ?> - <?= $this->task->selectColumn($columns_list, $values, $errors) ?> - <?= $this->task->selectPriority($project, $values) ?> - <?= $this->task->selectScore($values, $errors) ?> - <?= $this->task->selectTimeEstimated($values, $errors) ?> - <?= $this->task->selectDueDate($values, $errors) ?> - - <?= $this->hook->render('template:task:form:right-column', array('values' => $values, 'errors' => $errors)) ?> + <div class="form-columns"> + <div class="form-column"> + <?= $this->task->selectTitle($values, $errors) ?> + <?= $this->task->selectDescription($values, $errors) ?> + <?= $this->task->selectTags($project) ?> + + <?php if (! isset($duplicate)): ?> + <?= $this->form->checkbox('another_task', t('Create another task'), 1, isset($values['another_task']) && $values['another_task'] == 1) ?> + <?php endif ?> + + <?= $this->hook->render('template:task:form:first-column', array('values' => $values, 'errors' => $errors)) ?> + </div> + + <div class="form-column"> + <?= $this->form->hidden('project_id', $values) ?> + <?= $this->task->selectColor($values) ?> + <?= $this->task->selectAssignee($users_list, $values, $errors) ?> + <?= $this->task->selectCategory($categories_list, $values, $errors) ?> + <?= $this->task->selectSwimlane($swimlanes_list, $values, $errors) ?> + <?= $this->task->selectColumn($columns_list, $values, $errors) ?> + <?= $this->task->selectPriority($project, $values) ?> + <?= $this->task->selectScore($values, $errors) ?> + <?= $this->task->selectReference($values, $errors) ?> + + <?= $this->hook->render('template:task:form:second-column', array('values' => $values, 'errors' => $errors)) ?> + </div> + + <div class="form-column"> + <?= $this->task->selectTimeEstimated($values, $errors) ?> + <?= $this->task->selectTimeSpent($values, $errors) ?> + <?= $this->task->selectStartDate($values, $errors) ?> + <?= $this->task->selectDueDate($values, $errors) ?> + + <?= $this->hook->render('template:task:form:third-column', array('values' => $values, 'errors' => $errors)) ?> + </div> </div> <div class="form-actions"> diff --git a/app/Template/task_external_link/table.php b/app/Template/task_external_link/table.php index 56ef0363..cdfe0028 100644 --- a/app/Template/task_external_link/table.php +++ b/app/Template/task_external_link/table.php @@ -1,5 +1,5 @@ <?php if (! empty($links)): ?> -<table class="table-stripped table-small"> +<table class="table-striped table-scrolling"> <tr> <th class="column-10"><?= t('Type') ?></th> <th><?= t('Title') ?></th> diff --git a/app/Template/task_file/files.php b/app/Template/task_file/files.php index 7ca59b1c..94c26f73 100644 --- a/app/Template/task_file/files.php +++ b/app/Template/task_file/files.php @@ -1,5 +1,5 @@ <?php if (! empty($files)): ?> - <table class="table-stripped table-small"> + <table class="table-striped table-scrolling"> <tr> <th><?= t('Filename') ?></th> <th><?= t('Creator') ?></th> diff --git a/app/Template/task_gantt_creation/show.php b/app/Template/task_gantt_creation/show.php index 683bc8c8..7906c39a 100644 --- a/app/Template/task_gantt_creation/show.php +++ b/app/Template/task_gantt_creation/show.php @@ -1,30 +1,41 @@ <div class="page-header"> - <h2><?= t('New task') ?></h2> + <h2><?= $this->text->e($project['name']) ?> > <?= t('New task') ?></h2> </div> -<form method="post" action="<?= $this->url->href('TaskGanttCreationController', 'save', array('project_id' => $values['project_id'])) ?>" autocomplete="off"> +<form class="popover-form" method="post" action="<?= $this->url->href('TaskGanttCreationController', 'save', array('project_id' => $values['project_id'])) ?>" autocomplete="off"> <?= $this->form->csrf() ?> <?= $this->form->hidden('project_id', $values) ?> <?= $this->form->hidden('column_id', $values) ?> <?= $this->form->hidden('position', $values) ?> - <div class="form-column"> - <?= $this->form->label(t('Title'), 'title') ?> - <?= $this->form->text('title', $values, $errors, array('autofocus', 'required', 'maxlength="200"', 'tabindex="1"'), 'form-input-large') ?> + <div class="form-columns"> + <div class="form-column"> + <?= $this->task->selectTitle($values, $errors) ?> + <?= $this->task->selectDescription($values, $errors) ?> + <?= $this->task->selectTags($project) ?> - <?= $this->form->label(t('Description'), 'description') ?> - <?= $this->form->textarea('description', $values, $errors, array('placeholder="'.t('Leave a description').'"', 'tabindex="2"'), 'markdown-editor') ?> + <?= $this->hook->render('template:task:form:first-column', array('values' => $values, 'errors' => $errors)) ?> + </div> - <?= $this->render('task/color_picker', array('colors_list' => $colors_list, 'values' => $values)) ?> - </div> + <div class="form-column"> + <?= $this->task->selectColor($values) ?> + <?= $this->task->selectAssignee($users_list, $values, $errors) ?> + <?= $this->task->selectCategory($categories_list, $values, $errors) ?> + <?= $this->task->selectSwimlane($swimlanes_list, $values, $errors) ?> + <?= $this->task->selectPriority($project, $values) ?> + <?= $this->task->selectScore($values, $errors) ?> + <?= $this->task->selectReference($values, $errors) ?> + + <?= $this->hook->render('template:task:form:second-column', array('values' => $values, 'errors' => $errors)) ?> + </div> + + <div class="form-column"> + <?= $this->task->selectTimeEstimated($values, $errors) ?> + <?= $this->task->selectTimeSpent($values, $errors) ?> + <?= $this->task->selectStartDate($values, $errors) ?> + <?= $this->task->selectDueDate($values, $errors) ?> - <div class="form-column"> - <?= $this->task->selectAssignee($users_list, $values, $errors) ?> - <?= $this->task->selectCategory($categories_list, $values, $errors) ?> - <?= $this->task->selectSwimlane($swimlanes_list, $values, $errors) ?> - <?= $this->task->selectPriority($project, $values) ?> - <?= $this->task->selectScore($values, $errors) ?> - <?= $this->task->selectStartDate($values, $errors) ?> - <?= $this->task->selectDueDate($values, $errors) ?> + <?= $this->hook->render('template:task:form:third-column', array('values' => $values, 'errors' => $errors)) ?> + </div> </div> <div class="form-actions"> diff --git a/app/Template/task_import/sidebar.php b/app/Template/task_import/sidebar.php index 4cd92af8..04896948 100644 --- a/app/Template/task_import/sidebar.php +++ b/app/Template/task_import/sidebar.php @@ -1,5 +1,4 @@ <div class="sidebar"> - <h2><?= t('Imports') ?></h2> <ul> <li <?= $this->app->checkMenuSelection('TaskImportController', 'show') ?>> <?= $this->url->link(t('Tasks').' (CSV)', 'TaskImportController', 'show', array('project_id' => $project['id'])) ?> diff --git a/app/Template/task_internal_link/table.php b/app/Template/task_internal_link/table.php index 424d4791..6584a33a 100644 --- a/app/Template/task_internal_link/table.php +++ b/app/Template/task_internal_link/table.php @@ -1,5 +1,5 @@ <?php if (! empty($links)): ?> -<table class="task-links-table table-stripped"> +<table class="task-links-table table-striped table-scrolling"> <?php foreach ($links as $label => $grouped_links): ?> <?php $hide_td = false ?> <?php foreach ($grouped_links as $link): ?> diff --git a/app/Template/task_list/show.php b/app/Template/task_list/show.php index bb95b6a3..0518e4c3 100644 --- a/app/Template/task_list/show.php +++ b/app/Template/task_list/show.php @@ -4,7 +4,7 @@ <?php if ($paginator->isEmpty()): ?> <p class="alert"><?= t('No tasks found.') ?></p> <?php elseif (! $paginator->isEmpty()): ?> - <table class="table-fixed table-small"> + <table class="table-striped table-scrolling table-small"> <tr> <th class="column-5"><?= $paginator->order(t('Id'), 'tasks.id') ?></th> <th class="column-10"><?= $paginator->order(t('Swimlane'), 'tasks.swimlane_id') ?></th> diff --git a/app/Template/task_modification/edit_description.php b/app/Template/task_modification/edit_description.php deleted file mode 100644 index 339ed036..00000000 --- a/app/Template/task_modification/edit_description.php +++ /dev/null @@ -1,27 +0,0 @@ -<div class="page-header"> - <h2><?= t('Edit the description') ?></h2> -</div> - -<form class="popover-form" method="post" action="<?= $this->url->href('TaskModificationController', 'updateDescription', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>" autocomplete="off"> - - <?= $this->form->csrf() ?> - <?= $this->form->hidden('id', $values) ?> - - <?= $this->form->textarea( - 'description', - $values, - $errors, - array( - 'autofocus', - 'placeholder="'.t('Leave a description').'"', - 'data-mention-search-url="'.$this->url->href('UserAjaxController', 'mention', array('project_id' => $task['project_id'])).'"' - ), - 'markdown-editor' - ) ?> - - <div class="form-actions"> - <button type="submit" class="btn btn-blue"><?= t('Save') ?></button> - <?= t('or') ?> - <?= $this->url->link(t('cancel'), 'TaskViewController', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'close-popover') ?> - </div> -</form> diff --git a/app/Template/task_modification/edit_task.php b/app/Template/task_modification/edit_task.php deleted file mode 100644 index 0707fd9a..00000000 --- a/app/Template/task_modification/edit_task.php +++ /dev/null @@ -1,38 +0,0 @@ -<div class="page-header"> - <h2><?= t('Edit a task') ?></h2> -</div> -<form class="popover-form" method="post" action="<?= $this->url->href('TaskModificationController', 'update', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>" autocomplete="off"> - <?= $this->form->csrf() ?> - <?= $this->form->hidden('id', $values) ?> - <?= $this->form->hidden('project_id', $values) ?> - - <div class="form-column"> - <?= $this->form->label(t('Title'), 'title') ?> - <?= $this->form->text('title', $values, $errors, array('autofocus', 'required', 'maxlength="200"', 'tabindex="1"')) ?> - <?= $this->task->selectAssignee($users_list, $values, $errors) ?> - <?= $this->task->selectCategory($categories_list, $values, $errors) ?> - <?= $this->task->selectPriority($project, $values) ?> - <?= $this->task->selectScore($values, $errors) ?> - - <?= $this->hook->render('template:task:form:left-column', array('values' => $values, 'errors' => $errors)) ?> - </div> - - <div class="form-column"> - <?= $this->task->selectTimeEstimated($values, $errors) ?> - <?= $this->task->selectTimeSpent($values, $errors) ?> - <?= $this->task->selectStartDate($values, $errors) ?> - <?= $this->task->selectDueDate($values, $errors) ?> - - <?= $this->hook->render('template:task:form:right-column', array('values' => $values, 'errors' => $errors)) ?> - </div> - - <div class="form-clear"> - <?= $this->render('task/color_picker', array('colors_list' => $colors_list, 'values' => $values)) ?> - </div> - - <div class="form-actions"> - <button type="submit" class="btn btn-blue" tabindex="15"><?= t('Save') ?></button> - <?= t('or') ?> - <?= $this->url->link(t('cancel'), 'TaskViewController', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'close-popover') ?> - </div> -</form> diff --git a/app/Template/task_modification/show.php b/app/Template/task_modification/show.php new file mode 100644 index 00000000..734b247a --- /dev/null +++ b/app/Template/task_modification/show.php @@ -0,0 +1,44 @@ +<div class="page-header"> + <h2><?= $this->text->e($project['name']) ?> > <?= $this->text->e($task['title']) ?></h2> +</div> +<form class="popover-form" method="post" action="<?= $this->url->href('TaskModificationController', 'update', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>" autocomplete="off"> + <?= $this->form->csrf() ?> + <?= $this->form->hidden('id', $values) ?> + <?= $this->form->hidden('project_id', $values) ?> + + <div class="form-columns"> + <div class="form-column"> + <?= $this->task->selectTitle($values, $errors) ?> + <?= $this->task->selectDescription($values, $errors) ?> + <?= $this->task->selectTags($project, $tags) ?> + + <?= $this->hook->render('template:task:form:first-column', array('values' => $values, 'errors' => $errors)) ?> + </div> + + <div class="form-column"> + <?= $this->task->selectColor($values) ?> + <?= $this->task->selectAssignee($users_list, $values, $errors) ?> + <?= $this->task->selectCategory($categories_list, $values, $errors) ?> + <?= $this->task->selectPriority($project, $values) ?> + <?= $this->task->selectScore($values, $errors) ?> + <?= $this->task->selectReference($values, $errors) ?> + + <?= $this->hook->render('template:task:form:second-column', array('values' => $values, 'errors' => $errors)) ?> + </div> + + <div class="form-column"> + <?= $this->task->selectTimeEstimated($values, $errors) ?> + <?= $this->task->selectTimeSpent($values, $errors) ?> + <?= $this->task->selectStartDate($values, $errors) ?> + <?= $this->task->selectDueDate($values, $errors) ?> + + <?= $this->hook->render('template:task:form:third-column', array('values' => $values, 'errors' => $errors)) ?> + </div> + </div> + + <div class="form-actions"> + <button type="submit" class="btn btn-blue" tabindex="15"><?= t('Save') ?></button> + <?= t('or') ?> + <?= $this->url->link(t('cancel'), 'TaskViewController', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'close-popover') ?> + </div> +</form> diff --git a/app/Template/task_popover/change_assignee.php b/app/Template/task_popover/change_assignee.php deleted file mode 100644 index 02f3e198..00000000 --- a/app/Template/task_popover/change_assignee.php +++ /dev/null @@ -1,20 +0,0 @@ -<section id="main"> - <div class="page-header"> - <h2><?= t('Change assignee for the task "%s"', $values['title']) ?></h2> - </div> - <form class="popover-form" method="post" action="<?= $this->url->href('TaskPopoverController', 'updateAssignee', array('task_id' => $values['id'], 'project_id' => $project['id'])) ?>"> - - <?= $this->form->csrf() ?> - - <?= $this->form->hidden('id', $values) ?> - <?= $this->form->hidden('project_id', $values) ?> - - <?= $this->task->selectAssignee($users_list, $values, array(), array('autofocus')) ?> - - <div class="form-actions"> - <button type="submit" class="btn btn-blue"><?= t('Save') ?></button> - <?= t('or') ?> - <?= $this->url->link(t('cancel'), 'BoardViewController', 'show', array('project_id' => $project['id']), false, 'close-popover') ?> - </div> - </form> -</section> diff --git a/app/Template/task_popover/change_category.php b/app/Template/task_popover/change_category.php deleted file mode 100644 index eb6a373d..00000000 --- a/app/Template/task_popover/change_category.php +++ /dev/null @@ -1,20 +0,0 @@ -<section id="main"> - <div class="page-header"> - <h2><?= t('Change category for the task "%s"', $values['title']) ?></h2> - </div> - <form class="popover-form" method="post" action="<?= $this->url->href('TaskPopoverController', 'updateCategory', array('task_id' => $values['id'], 'project_id' => $project['id'])) ?>"> - - <?= $this->form->csrf() ?> - - <?= $this->form->hidden('id', $values) ?> - <?= $this->form->hidden('project_id', $values) ?> - - <?= $this->task->selectCategory($categories_list, $values, array(), array('autofocus'), true) ?> - - <div class="form-actions"> - <button type="submit" class="btn btn-blue"><?= t('Save') ?></button> - <?= t('or') ?> - <?= $this->url->link(t('cancel'), 'BoardViewController', 'show', array('project_id' => $project['id']), false, 'close-popover') ?> - </div> - </form> -</section> diff --git a/app/Template/user_creation/local.php b/app/Template/user_creation/local.php index 4c224cec..059a0114 100644 --- a/app/Template/user_creation/local.php +++ b/app/Template/user_creation/local.php @@ -4,37 +4,39 @@ <form class="popover-form" method="post" action="<?= $this->url->href('UserCreationController', 'save') ?>" autocomplete="off"> <?= $this->form->csrf() ?> - <div class="form-column"> - <?= $this->form->label(t('Username'), 'username') ?> - <?= $this->form->text('username', $values, $errors, array('autofocus', 'required', 'maxlength="50"')) ?> + <div class="form-columns"> + <div class="form-column"> + <?= $this->form->label(t('Username'), 'username') ?> + <?= $this->form->text('username', $values, $errors, array('autofocus', 'required', 'maxlength="50"')) ?> - <?= $this->form->label(t('Name'), 'name') ?> - <?= $this->form->text('name', $values, $errors) ?> + <?= $this->form->label(t('Name'), 'name') ?> + <?= $this->form->text('name', $values, $errors) ?> - <?= $this->form->label(t('Email'), 'email') ?> - <?= $this->form->email('email', $values, $errors) ?> + <?= $this->form->label(t('Email'), 'email') ?> + <?= $this->form->email('email', $values, $errors) ?> - <?= $this->form->label(t('Password'), 'password') ?> - <?= $this->form->password('password', $values, $errors, array('required')) ?> + <?= $this->form->label(t('Password'), 'password') ?> + <?= $this->form->password('password', $values, $errors, array('required')) ?> - <?= $this->form->label(t('Confirmation'), 'confirmation') ?> - <?= $this->form->password('confirmation', $values, $errors, array('required')) ?> - </div> + <?= $this->form->label(t('Confirmation'), 'confirmation') ?> + <?= $this->form->password('confirmation', $values, $errors, array('required')) ?> + </div> - <div class="form-column"> - <?= $this->form->label(t('Add project member'), 'project_id') ?> - <?= $this->form->select('project_id', $projects, $values, $errors) ?> + <div class="form-column"> + <?= $this->form->label(t('Add project member'), 'project_id') ?> + <?= $this->form->select('project_id', $projects, $values, $errors) ?> - <?= $this->form->label(t('Timezone'), 'timezone') ?> - <?= $this->form->select('timezone', $timezones, $values, $errors) ?> + <?= $this->form->label(t('Timezone'), 'timezone') ?> + <?= $this->form->select('timezone', $timezones, $values, $errors) ?> - <?= $this->form->label(t('Language'), 'language') ?> - <?= $this->form->select('language', $languages, $values, $errors) ?> + <?= $this->form->label(t('Language'), 'language') ?> + <?= $this->form->select('language', $languages, $values, $errors) ?> - <?= $this->form->label(t('Role'), 'role') ?> - <?= $this->form->select('role', $roles, $values, $errors) ?> + <?= $this->form->label(t('Role'), 'role') ?> + <?= $this->form->select('role', $roles, $values, $errors) ?> - <?= $this->form->checkbox('notifications_enabled', t('Enable email notifications'), 1, isset($values['notifications_enabled']) && $values['notifications_enabled'] == 1 ? true : false) ?> + <?= $this->form->checkbox('notifications_enabled', t('Enable email notifications'), 1, isset($values['notifications_enabled']) && $values['notifications_enabled'] == 1 ? true : false) ?> + </div> </div> <div class="form-actions"> diff --git a/app/Template/user_creation/remote.php b/app/Template/user_creation/remote.php index dc4981a4..41d0d3c7 100644 --- a/app/Template/user_creation/remote.php +++ b/app/Template/user_creation/remote.php @@ -2,38 +2,39 @@ <h2><?= t('New remote user') ?></h2> </div> <form class="popover-form" method="post" action="<?= $this->url->href('UserCreationController', 'save') ?>" autocomplete="off"> - <?= $this->form->csrf() ?> <?= $this->form->hidden('is_ldap_user', array('is_ldap_user' => 1)) ?> - <div class="form-column"> - <?= $this->form->label(t('Username'), 'username') ?> - <?= $this->form->text('username', $values, $errors, array('autofocus', 'required', 'maxlength="50"')) ?> - - <?= $this->form->label(t('Name'), 'name') ?> - <?= $this->form->text('name', $values, $errors) ?> + <div class="form-columns"> + <div class="form-column"> + <?= $this->form->label(t('Username'), 'username') ?> + <?= $this->form->text('username', $values, $errors, array('autofocus', 'required', 'maxlength="50"')) ?> + + <?= $this->form->label(t('Name'), 'name') ?> + <?= $this->form->text('name', $values, $errors) ?> - <?= $this->form->label(t('Email'), 'email') ?> - <?= $this->form->email('email', $values, $errors) ?> + <?= $this->form->label(t('Email'), 'email') ?> + <?= $this->form->email('email', $values, $errors) ?> - <?= $this->hook->render('template:user:create-remote:form', array('values' => $values, 'errors' => $errors)) ?> - </div> + <?= $this->hook->render('template:user:create-remote:form', array('values' => $values, 'errors' => $errors)) ?> + </div> - <div class="form-column"> - <?= $this->form->label(t('Add project member'), 'project_id') ?> - <?= $this->form->select('project_id', $projects, $values, $errors) ?> + <div class="form-column"> + <?= $this->form->label(t('Add project member'), 'project_id') ?> + <?= $this->form->select('project_id', $projects, $values, $errors) ?> - <?= $this->form->label(t('Timezone'), 'timezone') ?> - <?= $this->form->select('timezone', $timezones, $values, $errors) ?> + <?= $this->form->label(t('Timezone'), 'timezone') ?> + <?= $this->form->select('timezone', $timezones, $values, $errors) ?> - <?= $this->form->label(t('Language'), 'language') ?> - <?= $this->form->select('language', $languages, $values, $errors) ?> + <?= $this->form->label(t('Language'), 'language') ?> + <?= $this->form->select('language', $languages, $values, $errors) ?> - <?= $this->form->label(t('Role'), 'role') ?> - <?= $this->form->select('role', $roles, $values, $errors) ?> + <?= $this->form->label(t('Role'), 'role') ?> + <?= $this->form->select('role', $roles, $values, $errors) ?> - <?= $this->form->checkbox('notifications_enabled', t('Enable email notifications'), 1, isset($values['notifications_enabled']) && $values['notifications_enabled'] == 1 ? true : false) ?> - <?= $this->form->checkbox('disable_login_form', t('Disallow login form'), 1, isset($values['disable_login_form']) && $values['disable_login_form'] == 1) ?> + <?= $this->form->checkbox('notifications_enabled', t('Enable email notifications'), 1, isset($values['notifications_enabled']) && $values['notifications_enabled'] == 1 ? true : false) ?> + <?= $this->form->checkbox('disable_login_form', t('Disallow login form'), 1, isset($values['disable_login_form']) && $values['disable_login_form'] == 1) ?> + </div> </div> <div class="form-actions"> diff --git a/app/Template/user_list/show.php b/app/Template/user_list/show.php index b2bd9377..5e285c89 100644 --- a/app/Template/user_list/show.php +++ b/app/Template/user_list/show.php @@ -12,7 +12,7 @@ <?php if ($paginator->isEmpty()): ?> <p class="alert"><?= t('No user') ?></p> <?php else: ?> - <table class="table-stripped"> + <table class="table-scrolling table-striped"> <tr> <th class="column-5"><?= $paginator->order(t('Id'), 'id') ?></th> <th class="column-18"><?= $paginator->order(t('Username'), 'username') ?></th> diff --git a/app/Template/user_modification/show.php b/app/Template/user_modification/show.php index 396d550d..506c9161 100644 --- a/app/Template/user_modification/show.php +++ b/app/Template/user_modification/show.php @@ -11,16 +11,16 @@ <?= $this->form->text('username', $values, $errors, array('required', isset($values['is_ldap_user']) && $values['is_ldap_user'] == 1 ? 'readonly' : '', 'maxlength="50"')) ?> <?= $this->form->label(t('Name'), 'name') ?> - <?= $this->form->text('name', $values, $errors) ?> + <?= $this->form->text('name', $values, $errors, array($this->user->hasAccess('UserModificationController', 'show/edit_name') ? '' : 'readonly')) ?> <?= $this->form->label(t('Email'), 'email') ?> - <?= $this->form->email('email', $values, $errors) ?> + <?= $this->form->email('email', $values, $errors, array($this->user->hasAccess('UserModificationController', 'show/edit_email') ? '' : 'readonly')) ?> <?= $this->form->label(t('Timezone'), 'timezone') ?> - <?= $this->form->select('timezone', $timezones, $values, $errors) ?> + <?= $this->form->select('timezone', $timezones, $values, $errors, array($this->user->hasAccess('UserModificationController', 'show/edit_timezone') ? '' : 'disabled')) ?> <?= $this->form->label(t('Language'), 'language') ?> - <?= $this->form->select('language', $languages, $values, $errors) ?> + <?= $this->form->select('language', $languages, $values, $errors, array($this->user->hasAccess('UserModificationController', 'show/edit_language') ? '' : 'disabled')) ?> <?php if ($this->user->isAdmin()): ?> <?= $this->form->label(t('Role'), 'role') ?> diff --git a/app/Template/user_view/last.php b/app/Template/user_view/last.php index 3de4d5e2..72f59bf6 100644 --- a/app/Template/user_view/last.php +++ b/app/Template/user_view/last.php @@ -5,7 +5,7 @@ <?php if (empty($last_logins)): ?> <p class="alert"><?= t('Never connected.') ?></p> <?php else: ?> - <table class="table-small table-fixed"> + <table class="table-small table-fixed table-scrolling table-striped"> <tr> <th class="column-20"><?= t('Login date') ?></th> <th class="column-15"><?= t('Authentication method') ?></th> @@ -21,4 +21,4 @@ </tr> <?php endforeach ?> </table> -<?php endif ?>
\ No newline at end of file +<?php endif ?> diff --git a/app/Template/user_view/password_reset.php b/app/Template/user_view/password_reset.php index 1371ce11..de7047e0 100644 --- a/app/Template/user_view/password_reset.php +++ b/app/Template/user_view/password_reset.php @@ -5,7 +5,7 @@ <?php if (empty($tokens)): ?> <p class="alert"><?= t('The password has never been reinitialized.') ?></p> <?php else: ?> - <table class="table-small table-fixed"> + <table class="table-small table-fixed table-scrolling table-striped"> <tr> <th class="column-20"><?= t('Creation') ?></th> <th class="column-20"><?= t('Expiration') ?></th> @@ -23,4 +23,4 @@ </tr> <?php endforeach ?> </table> -<?php endif ?>
\ No newline at end of file +<?php endif ?> diff --git a/app/Template/user_view/sessions.php b/app/Template/user_view/sessions.php index eda3ef7f..10497e4f 100644 --- a/app/Template/user_view/sessions.php +++ b/app/Template/user_view/sessions.php @@ -5,7 +5,7 @@ <?php if (empty($sessions)): ?> <p class="alert"><?= t('No session.') ?></p> <?php else: ?> - <table class="table-small table-fixed"> + <table class="table-small table-fixed table-scrolling table-striped"> <tr> <th class="column-20"><?= t('Creation date') ?></th> <th class="column-20"><?= t('Expiration date') ?></th> diff --git a/app/Template/user_view/sidebar.php b/app/Template/user_view/sidebar.php index d200a7f5..a80daefa 100644 --- a/app/Template/user_view/sidebar.php +++ b/app/Template/user_view/sidebar.php @@ -1,5 +1,7 @@ <div class="sidebar"> - <h2><?= t('Information') ?></h2> + <div class="sidebar-title"> + <h2><?= t('Information') ?></h2> + </div> <ul> <?php if ($this->user->hasAccess('UserViewController', 'show')): ?> <li <?= $this->app->checkMenuSelection('UserViewController', 'show') ?>> @@ -12,24 +14,34 @@ </li> <?php endif ?> <?php if ($this->user->isAdmin() || $this->user->isCurrentUser($user['id'])): ?> - <li <?= $this->app->checkMenuSelection('UserViewController', 'timesheet') ?>> - <?= $this->url->link(t('Time tracking'), 'UserViewController', 'timesheet', array('user_id' => $user['id'])) ?> - </li> - <li <?= $this->app->checkMenuSelection('UserViewController', 'lastLogin') ?>> - <?= $this->url->link(t('Last logins'), 'UserViewController', 'lastLogin', array('user_id' => $user['id'])) ?> - </li> - <li <?= $this->app->checkMenuSelection('UserViewController', 'sessions') ?>> - <?= $this->url->link(t('Persistent connections'), 'UserViewController', 'sessions', array('user_id' => $user['id'])) ?> - </li> - <li <?= $this->app->checkMenuSelection('UserViewController', 'passwordReset') ?>> - <?= $this->url->link(t('Password reset history'), 'UserViewController', 'passwordReset', array('user_id' => $user['id'])) ?> - </li> + <?php if ($this->user->hasAccess('UserViewController', 'timesheet')): ?> + <li <?= $this->app->checkMenuSelection('UserViewController', 'timesheet') ?>> + <?= $this->url->link(t('Time tracking'), 'UserViewController', 'timesheet', array('user_id' => $user['id'])) ?> + </li> + <?php endif ?> + <?php if ($this->user->hasAccess('UserViewController', 'lastLogin')): ?> + <li <?= $this->app->checkMenuSelection('UserViewController', 'lastLogin') ?>> + <?= $this->url->link(t('Last logins'), 'UserViewController', 'lastLogin', array('user_id' => $user['id'])) ?> + </li> + <?php endif ?> + <?php if ($this->user->hasAccess('UserViewController', 'sessions')): ?> + <li <?= $this->app->checkMenuSelection('UserViewController', 'sessions') ?>> + <?= $this->url->link(t('Persistent connections'), 'UserViewController', 'sessions', array('user_id' => $user['id'])) ?> + </li> + <?php endif ?> + <?php if ($this->user->hasAccess('UserViewController', 'passwordReset')): ?> + <li <?= $this->app->checkMenuSelection('UserViewController', 'passwordReset') ?>> + <?= $this->url->link(t('Password reset history'), 'UserViewController', 'passwordReset', array('user_id' => $user['id'])) ?> + </li> + <?php endif ?> <?php endif ?> <?= $this->hook->render('template:user:sidebar:information', array('user' => $user)) ?> </ul> - <h2><?= t('Actions') ?></h2> + <div class="sidebar-title"> + <h2><?= t('Actions') ?></h2> + </div> <ul> <?php if ($this->user->isAdmin() || $this->user->isCurrentUser($user['id'])): ?> @@ -42,13 +54,13 @@ </li> <?php endif ?> - <?php if ($user['is_ldap_user'] == 0): ?> + <?php if ($user['is_ldap_user'] == 0 && $this->user->hasAccess('UserCredentialController', 'changePassword')): ?> <li <?= $this->app->checkMenuSelection('UserCredentialController', 'changePassword') ?>> <?= $this->url->link(t('Change password'), 'UserCredentialController', 'changePassword', array('user_id' => $user['id'])) ?> </li> <?php endif ?> - <?php if ($this->user->isCurrentUser($user['id'])): ?> + <?php if ($this->user->isCurrentUser($user['id']) && $this->user->hasAccess('TwoFactorController', 'index')): ?> <li <?= $this->app->checkMenuSelection('TwoFactorController', 'index') ?>> <?= $this->url->link(t('Two factor authentication'), 'TwoFactorController', 'index', array('user_id' => $user['id'])) ?> </li> @@ -58,18 +70,26 @@ </li> <?php endif ?> - <li <?= $this->app->checkMenuSelection('UserViewController', 'share') ?>> - <?= $this->url->link(t('Public access'), 'UserViewController', 'share', array('user_id' => $user['id'])) ?> - </li> - <li <?= $this->app->checkMenuSelection('UserViewController', 'notifications') ?>> - <?= $this->url->link(t('Notifications'), 'UserViewController', 'notifications', array('user_id' => $user['id'])) ?> - </li> - <li <?= $this->app->checkMenuSelection('UserViewController', 'external') ?>> - <?= $this->url->link(t('External accounts'), 'UserViewController', 'external', array('user_id' => $user['id'])) ?> - </li> - <li <?= $this->app->checkMenuSelection('UserViewController', 'integrations') ?>> - <?= $this->url->link(t('Integrations'), 'UserViewController', 'integrations', array('user_id' => $user['id'])) ?> - </li> + <?php if ($this->user->hasAccess('UserViewController', 'share')): ?> + <li <?= $this->app->checkMenuSelection('UserViewController', 'share') ?>> + <?= $this->url->link(t('Public access'), 'UserViewController', 'share', array('user_id' => $user['id'])) ?> + </li> + <?php endif ?> + <?php if ($this->user->hasAccess('UserViewController', 'notifications')): ?> + <li <?= $this->app->checkMenuSelection('UserViewController', 'notifications') ?>> + <?= $this->url->link(t('Notifications'), 'UserViewController', 'notifications', array('user_id' => $user['id'])) ?> + </li> + <?php endif ?> + <?php if ($this->user->hasAccess('UserViewController', 'external')): ?> + <li <?= $this->app->checkMenuSelection('UserViewController', 'external') ?>> + <?= $this->url->link(t('External accounts'), 'UserViewController', 'external', array('user_id' => $user['id'])) ?> + </li> + <?php endif ?> + <?php if ($this->user->hasAccess('UserViewController', 'integrations')): ?> + <li <?= $this->app->checkMenuSelection('UserViewController', 'integrations') ?>> + <?= $this->url->link(t('Integrations'), 'UserViewController', 'integrations', array('user_id' => $user['id'])) ?> + </li> + <?php endif ?> <?php endif ?> <?php if ($this->user->hasAccess('UserCredentialController', 'changeAuthentication')): ?> diff --git a/app/Template/user_view/timesheet.php b/app/Template/user_view/timesheet.php index 3df57492..aeffd2f4 100644 --- a/app/Template/user_view/timesheet.php +++ b/app/Template/user_view/timesheet.php @@ -6,7 +6,7 @@ <?php if ($subtask_paginator->isEmpty()): ?> <p class="alert"><?= t('There is nothing to show.') ?></p> <?php else: ?> - <table class="table-fixed"> + <table class="table-fixed table-scrolling table-striped"> <tr> <th class="column-25"><?= $subtask_paginator->order(t('Task'), 'task_title') ?></th> <th class="column-25"><?= $subtask_paginator->order(t('Subtask'), 'subtask_title') ?></th> diff --git a/app/User/Avatar/LetterAvatarProvider.php b/app/User/Avatar/LetterAvatarProvider.php index b7a95f41..727f9109 100644 --- a/app/User/Avatar/LetterAvatarProvider.php +++ b/app/User/Avatar/LetterAvatarProvider.php @@ -142,7 +142,7 @@ class LetterAvatarProvider extends Base implements AvatarProviderInterface // Make hash more sensitive for short string like 'a', 'b', 'c' $str .= 'x'; - $max = intval(9007199254740991 / $seed2); + $max = intval(PHP_INT_MAX / $seed2); for ($i = 0, $ilen = mb_strlen($str, 'UTF-8'); $i < $ilen; $i++) { if ($hash > $max) { diff --git a/app/User/OAuthUserProvider.php b/app/User/OAuthUserProvider.php index dec26250..e5fedcca 100644 --- a/app/User/OAuthUserProvider.php +++ b/app/User/OAuthUserProvider.php @@ -13,14 +13,6 @@ use Kanboard\Core\User\UserProviderInterface; abstract class OAuthUserProvider implements UserProviderInterface { /** - * Get external id column name - * - * @access public - * @return string - */ - abstract public function getExternalIdColumn(); - - /** * User properties * * @access protected @@ -69,7 +61,7 @@ abstract class OAuthUserProvider implements UserProviderInterface */ public function getExternalId() { - return $this->user['id']; + return isset($this->user['id']) ? $this->user['id'] : ''; } /** @@ -102,7 +94,7 @@ abstract class OAuthUserProvider implements UserProviderInterface */ public function getName() { - return $this->user['name']; + return isset($this->user['name']) ? $this->user['name'] : ''; } /** @@ -113,7 +105,7 @@ abstract class OAuthUserProvider implements UserProviderInterface */ public function getEmail() { - return $this->user['email']; + return isset($this->user['email']) ? $this->user['email'] : ''; } /** diff --git a/app/User/ReverseProxyUserProvider.php b/app/User/ReverseProxyUserProvider.php index 723b8155..34d2187d 100644 --- a/app/User/ReverseProxyUserProvider.php +++ b/app/User/ReverseProxyUserProvider.php @@ -22,14 +22,23 @@ class ReverseProxyUserProvider implements UserProviderInterface protected $username = ''; /** + * User profile if the user already exists + * + * @access protected + * @var array + */ + private $userProfile = array(); + + /** * Constructor * * @access public * @param string $username */ - public function __construct($username) + public function __construct($username, array $userProfile = array()) { $this->username = $username; + $this->userProfile = $userProfile; } /** @@ -84,7 +93,15 @@ class ReverseProxyUserProvider implements UserProviderInterface */ public function getRole() { - return REVERSE_PROXY_DEFAULT_ADMIN === $this->username ? Role::APP_ADMIN : Role::APP_USER; + if (REVERSE_PROXY_DEFAULT_ADMIN === $this->username) { + return Role::APP_ADMIN; + } + + if (isset($this->userProfile['role'])) { + return $this->userProfile['role']; + } + + return Role::APP_USER; } /** diff --git a/app/Validator/ProjectValidator.php b/app/Validator/ProjectValidator.php index 9ef59111..8c6117a4 100644 --- a/app/Validator/ProjectValidator.php +++ b/app/Validator/ProjectValidator.php @@ -28,7 +28,7 @@ class ProjectValidator extends BaseValidator new Validators\Integer('priority_start', t('This value must be an integer')), new Validators\Integer('priority_end', t('This value must be an integer')), new Validators\Integer('is_active', t('This value must be an integer')), - new Validators\Required('name', t('The project name is required')), + new Validators\NotEmpty('name', t('This field cannot be empty')), new Validators\MaxLength('name', t('The maximum length is %d characters', 50), 50), new Validators\MaxLength('identifier', t('The maximum length is %d characters', 50), 50), new Validators\MaxLength('start_date', t('The maximum length is %d characters', 10), 10), @@ -47,11 +47,15 @@ class ProjectValidator extends BaseValidator */ public function validateCreation(array $values) { + $rules = array( + new Validators\Required('name', t('The project name is required')), + ); + if (! empty($values['identifier'])) { $values['identifier'] = strtoupper($values['identifier']); } - $v = new Validator($values, $this->commonValidationRules()); + $v = new Validator($values, array_merge($this->commonValidationRules(), $rules)); return array( $v->execute(), diff --git a/app/Validator/TagValidator.php b/app/Validator/TagValidator.php new file mode 100644 index 00000000..0567cf3e --- /dev/null +++ b/app/Validator/TagValidator.php @@ -0,0 +1,76 @@ +<?php + +namespace Kanboard\Validator; + +use SimpleValidator\Validator; +use SimpleValidator\Validators; + +/** + * Tag Validator + * + * @package Kanboard\Validator + * @author Frederic Guillot + */ +class TagValidator extends BaseValidator +{ + /** + * Validate creation + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateCreation(array $values) + { + $v = new Validator($values, $this->commonValidationRules()); + $result = $v->execute(); + $errors = $v->getErrors(); + + if ($result && $this->tagModel->exists($values['project_id'], $values['name'])) { + $result = false; + $errors = array('name' => array(t('The name must be unique'))); + } + + return array($result, $errors); + } + + /** + * Validate modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateModification(array $values) + { + $rules = array( + new Validators\Required('id', t('Field required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + $result = $v->execute(); + $errors = $v->getErrors(); + + if ($result && $this->tagModel->exists($values['project_id'], $values['name'], $values['id'])) { + $result = false; + $errors = array('name' => array(t('The name must be unique'))); + } + + return array($result, $errors); + } + + /** + * Common validation rules + * + * @access protected + * @return array + */ + protected function commonValidationRules() + { + return array( + new Validators\Required('project_id', t('Field required')), + new Validators\Required('name', t('Field required')), + new Validators\MaxLength('name', t('The maximum length is %d characters', 255), 255), + ); + } +} diff --git a/app/Validator/TaskValidator.php b/app/Validator/TaskValidator.php index 90bda6f3..8aa5c440 100644 --- a/app/Validator/TaskValidator.php +++ b/app/Validator/TaskValidator.php @@ -182,53 +182,6 @@ class TaskValidator extends BaseValidator } /** - * Validate assignee change - * - * @access public - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateAssigneeModification(array $values) - { - $rules = array( - new Validators\Required('id', t('The id is required')), - new Validators\Required('project_id', t('The project is required')), - new Validators\Required('owner_id', t('This value is required')), - ); - - $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); - - return array( - $v->execute(), - $v->getErrors() - ); - } - - /** - * Validate category change - * - * @access public - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateCategoryModification(array $values) - { - $rules = array( - new Validators\Required('id', t('The id is required')), - new Validators\Required('project_id', t('The project is required')), - new Validators\Required('category_id', t('This value is required')), - - ); - - $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); - - return array( - $v->execute(), - $v->getErrors() - ); - } - - /** * Validate project modification * * @access public diff --git a/app/common.php b/app/common.php index 72be3603..15fd7a75 100644 --- a/app/common.php +++ b/app/common.php @@ -46,6 +46,7 @@ $container->register(new Kanboard\ServiceProvider\ActionProvider()); $container->register(new Kanboard\ServiceProvider\ExternalLinkProvider()); $container->register(new Kanboard\ServiceProvider\AvatarProvider()); $container->register(new Kanboard\ServiceProvider\FilterProvider()); +$container->register(new Kanboard\ServiceProvider\JobProvider()); $container->register(new Kanboard\ServiceProvider\QueueProvider()); $container->register(new Kanboard\ServiceProvider\ApiProvider()); $container->register(new Kanboard\ServiceProvider\CommandProvider()); diff --git a/app/constants.php b/app/constants.php index 604f6acd..40b88fe9 100644 --- a/app/constants.php +++ b/app/constants.php @@ -21,7 +21,7 @@ defined('PLUGIN_INSTALLER') or define('PLUGIN_INSTALLER', true); defined('DEBUG') or define('DEBUG', strtolower(getenv('DEBUG')) === 'true'); // Logging drivers: syslog, stdout, stderr or file -defined('LOG_DRIVER') or define('LOG_DRIVER', getenv('LOG_DRIVER')); +defined('LOG_DRIVER') or define('LOG_DRIVER', ''); // Logging file defined('LOG_FILE') or define('LOG_FILE', DATA_DIR.DIRECTORY_SEPARATOR.'debug.log'); @@ -134,3 +134,5 @@ defined('HTTP_PROXY_PORT') or define('HTTP_PROXY_PORT', '3128'); defined('HTTP_PROXY_USERNAME') or define('HTTP_PROXY_USERNAME', ''); defined('HTTP_PROXY_PASSWORD') or define('HTTP_PROXY_PASSWORD', ''); defined('HTTP_VERIFY_SSL_CERTIFICATE') or define('HTTP_VERIFY_SSL_CERTIFICATE', true); + +defined('TOTP_ISSUER') or define('TOTP_ISSUER', 'Kanboard'); diff --git a/app/functions.php b/app/functions.php index b759763f..eaf33a52 100644 --- a/app/functions.php +++ b/app/functions.php @@ -3,6 +3,82 @@ use Kanboard\Core\Translator; /** + * Associate another dict to a dict based on a common key + * + * @param array $input + * @param array $relations + * @param string $relation + * @param string $column + */ +function array_merge_relation(array &$input, array &$relations, $relation, $column) +{ + foreach ($input as &$row) { + if (isset($row[$column]) && isset($relations[$row[$column]])) { + $row[$relation] = $relations[$row[$column]]; + } else { + $row[$relation] = array(); + } + } +} + +/** + * Create indexed array from a list of dict + * + * $input = [ + * ['k1' => 1, 'k2' => 2], ['k1' => 3, 'k2' => 4], ['k1' => 2, 'k2' => 5] + * ] + * + * array_column_index($input, 'k1') will returns: + * + * [ + * 1 => [['k1' => 1, 'k2' => 2], ['k1' => 2, 'k2' => 5]], + * 3 => [['k1' => 3, 'k2' => 4]], + * ] + * + * @param array $input + * @param string $column + * @return array + */ +function array_column_index(array &$input, $column) +{ + $result = array(); + + foreach ($input as &$row) { + if (isset($row[$column])) { + $result[$row[$column]][] = $row; + } + } + + return $result; +} + +/** + * Sum all values from a single column in the input array + * + * $input = [ + * ['column' => 2], ['column' => 3] + * ] + * + * array_column_sum($input, 'column') returns 5 + * + * @param array $input + * @param string $column + * @return double + */ +function array_column_sum(array &$input, $column) +{ + $sum = 0.0; + + foreach ($input as &$row) { + if (isset($row[$column])) { + $sum += (float) $row[$column]; + } + } + + return $sum; +} + +/** * Build version number from git-archive output * * @param string $ref |