summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/Action/TaskDuplicateAnotherProject.php2
-rw-r--r--app/Action/TaskMoveAnotherProject.php2
-rw-r--r--app/Api/Procedure/ProjectFileProcedure.php68
-rw-r--r--app/Api/Procedure/SubtaskTimeTrackingProcedure.php12
-rw-r--r--app/Api/Procedure/TaskExternalLinkProcedure.php106
-rw-r--r--app/Api/Procedure/TaskFileProcedure.php4
-rw-r--r--app/Api/Procedure/TaskProcedure.php4
-rw-r--r--app/Auth/ReverseProxyAuth.php3
-rw-r--r--app/Console/TaskOverdueNotificationCommand.php2
-rw-r--r--app/Controller/ActionCreationController.php2
-rw-r--r--app/Controller/BoardTooltipController.php6
-rw-r--r--app/Controller/ColumnController.php20
-rw-r--r--app/Controller/TaskDuplicationController.php4
-rw-r--r--app/Controller/TaskRecurrenceController.php8
-rw-r--r--app/Controller/WebNotificationController.php4
-rw-r--r--app/Core/Base.php6
-rw-r--r--app/Core/ExternalLink/ExternalLinkManager.php24
-rw-r--r--app/Core/Filter/Lexer.php2
-rw-r--r--app/Core/Ldap/User.php8
-rw-r--r--app/Core/Queue/JobHandler.php1
-rw-r--r--app/Formatter/TaskICalFormatter.php17
-rw-r--r--app/Helper/TaskHelper.php25
-rw-r--r--app/Helper/UserHelper.php8
-rw-r--r--app/Locale/bs_BA/translations.php2
-rw-r--r--app/Locale/cs_CZ/translations.php136
-rw-r--r--app/Locale/da_DK/translations.php2
-rw-r--r--app/Locale/de_DE/translations.php40
-rw-r--r--app/Locale/el_GR/translations.php2
-rw-r--r--app/Locale/es_ES/translations.php2
-rw-r--r--app/Locale/fi_FI/translations.php2
-rw-r--r--app/Locale/fr_FR/translations.php4
-rw-r--r--app/Locale/hu_HU/translations.php2
-rw-r--r--app/Locale/id_ID/translations.php2
-rw-r--r--app/Locale/it_IT/translations.php38
-rw-r--r--app/Locale/ja_JP/translations.php2
-rw-r--r--app/Locale/ko_KR/translations.php2
-rw-r--r--app/Locale/my_MY/translations.php2
-rw-r--r--app/Locale/nb_NO/translations.php2
-rw-r--r--app/Locale/nl_NL/translations.php2
-rw-r--r--app/Locale/pl_PL/translations.php2
-rw-r--r--app/Locale/pt_BR/translations.php2
-rw-r--r--app/Locale/pt_PT/translations.php40
-rw-r--r--app/Locale/ru_RU/translations.php124
-rw-r--r--app/Locale/sr_Latn_RS/translations.php2
-rw-r--r--app/Locale/sv_SE/translations.php2
-rw-r--r--app/Locale/th_TH/translations.php2
-rw-r--r--app/Locale/tr_TR/translations.php2
-rw-r--r--app/Locale/zh_CN/translations.php2
-rw-r--r--app/Model/ColumnModel.php18
-rw-r--r--app/Model/NotificationModel.php37
-rw-r--r--app/Model/ProjectDuplicationModel.php24
-rw-r--r--app/Model/ProjectModel.php13
-rw-r--r--app/Model/ProjectTaskDuplicationModel.php35
-rw-r--r--app/Model/ProjectTaskPriorityModel.php74
-rw-r--r--app/Model/SwimlaneModel.php24
-rw-r--r--app/Model/TagDuplicationModel.php87
-rw-r--r--app/Model/TaskCreationModel.php2
-rw-r--r--app/Model/TaskDuplicationModel.php165
-rw-r--r--app/Model/TaskFinderModel.php8
-rw-r--r--app/Model/TaskModel.php143
-rw-r--r--app/Model/TaskModificationModel.php2
-rw-r--r--app/Model/TaskProjectDuplicationModel.php60
-rw-r--r--app/Model/TaskProjectMoveModel.php68
-rw-r--r--app/Model/TaskRecurrenceModel.php147
-rw-r--r--app/Model/TaskTagModel.php18
-rw-r--r--app/Schema/Mysql.php7
-rw-r--r--app/Schema/Postgres.php7
-rw-r--r--app/Schema/Sql/mysql.sql28
-rw-r--r--app/Schema/Sql/postgres.sql96
-rw-r--r--app/Schema/Sqlite.php7
-rw-r--r--app/ServiceProvider/ApiProvider.php4
-rw-r--r--app/ServiceProvider/AuthenticationProvider.php2
-rw-r--r--app/ServiceProvider/ClassProvider.php6
-rw-r--r--app/Subscriber/RecurringTaskSubscriber.php6
-rw-r--r--app/Template/board/task_footer.php2
-rw-r--r--app/Template/column/create.php2
-rw-r--r--app/Template/column/edit.php2
-rw-r--r--app/Template/dashboard/notifications.php6
-rw-r--r--app/Template/project_creation/create.php3
-rw-r--r--app/Template/project_view/duplicate.php3
-rw-r--r--app/Template/project_view/show.php10
-rw-r--r--app/Template/task_creation/show.php1
-rw-r--r--app/Template/task_gantt_creation/show.php1
-rw-r--r--app/Template/task_modification/show.php1
-rw-r--r--app/User/Avatar/LetterAvatarProvider.php2
-rw-r--r--app/User/ReverseProxyUserProvider.php21
-rw-r--r--app/constants.php2
87 files changed, 1355 insertions, 547 deletions
diff --git a/app/Action/TaskDuplicateAnotherProject.php b/app/Action/TaskDuplicateAnotherProject.php
index 1d4a2f13..d70d2ee8 100644
--- a/app/Action/TaskDuplicateAnotherProject.php
+++ b/app/Action/TaskDuplicateAnotherProject.php
@@ -76,7 +76,7 @@ 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);
}
/**
diff --git a/app/Action/TaskMoveAnotherProject.php b/app/Action/TaskMoveAnotherProject.php
index 73ad4b69..66635a63 100644
--- a/app/Action/TaskMoveAnotherProject.php
+++ b/app/Action/TaskMoveAnotherProject.php
@@ -75,7 +75,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'));
}
/**
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/Procedure/SubtaskTimeTrackingProcedure.php b/app/Api/Procedure/SubtaskTimeTrackingProcedure.php
index 5d1988d6..5ceaa08d 100644
--- a/app/Api/Procedure/SubtaskTimeTrackingProcedure.php
+++ b/app/Api/Procedure/SubtaskTimeTrackingProcedure.php
@@ -5,7 +5,7 @@ namespace Kanboard\Api\Procedure;
use Kanboard\Api\Authorization\SubtaskAuthorization;
/**
- * Subtask Time Tracking API controller
+ * Subtask Time Tracking API controller
*
* @package Kanboard\Api\Procedure
* @author Frederic Guillot
@@ -19,19 +19,19 @@ class SubtaskTimeTrackingProcedure extends BaseProcedure
return $this->subtaskTimeTrackingModel->hasTimer($subtask_id, $user_id);
}
- public function logSubtaskStartTime($subtask_id, $user_id)
+ public function setSubtaskStartTime($subtask_id, $user_id)
{
- SubtaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'logSubtaskStartTime', $subtask_id);
+ SubtaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'setSubtaskStartTime', $subtask_id);
return $this->subtaskTimeTrackingModel->logStartTime($subtask_id, $user_id);
}
- public function logSubtaskEndTime($subtask_id,$user_id)
+ public function setSubtaskEndTime($subtask_id, $user_id)
{
- SubtaskAuthorization::getInstance($this->container)->check($this->getClassName(), 'logSubtaskEndTime', $subtask_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)
+ 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/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/Procedure/TaskFileProcedure.php b/app/Api/Procedure/TaskFileProcedure.php
index 5aa7ea0b..bd006578 100644
--- a/app/Api/Procedure/TaskFileProcedure.php
+++ b/app/Api/Procedure/TaskFileProcedure.php
@@ -30,7 +30,7 @@ class TaskFileProcedure extends BaseProcedure
public function downloadTaskFile($file_id)
{
TaskFileAuthorization::getInstance($this->container)->check($this->getClassName(), 'downloadTaskFile', $file_id);
-
+
try {
$file = $this->taskFileModel->getById($file_id);
@@ -51,7 +51,7 @@ class TaskFileProcedure extends BaseProcedure
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;
}
}
diff --git a/app/Api/Procedure/TaskProcedure.php b/app/Api/Procedure/TaskProcedure.php
index 2d29a4ef..8661deef 100644
--- a/app/Api/Procedure/TaskProcedure.php
+++ b/app/Api/Procedure/TaskProcedure.php
@@ -77,13 +77,13 @@ class TaskProcedure extends BaseProcedure
public function moveTaskToProject($task_id, $project_id, $swimlane_id = null, $column_id = null, $category_id = null, $owner_id = null)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'moveTaskToProject', $project_id);
- return $this->taskDuplicationModel->moveToProject($task_id, $project_id, $swimlane_id, $column_id, $category_id, $owner_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)
{
ProjectAuthorization::getInstance($this->container)->check($this->getClassName(), 'duplicateTaskToProject', $project_id);
- return $this->taskDuplicationModel->duplicateToProject($task_id, $project_id, $swimlane_id, $column_id, $category_id, $owner_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,
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/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/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/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/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/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/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/Base.php b/app/Core/Base.php
index eacca65d..8103ec14 100644
--- a/app/Core/Base.php
+++ b/app/Core/Base.php
@@ -86,15 +86,21 @@ 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\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
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/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/Queue/JobHandler.php b/app/Core/Queue/JobHandler.php
index f8736cce..7ca36328 100644
--- a/app/Core/Queue/JobHandler.php
+++ b/app/Core/Queue/JobHandler.php
@@ -40,6 +40,7 @@ class JobHandler extends Base
{
$payload = $job->getBody();
$className = $payload['class'];
+ $this->memoryCache->flush();
$this->prepareJobSession($payload['user_id']);
if (DEBUG) {
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/TaskHelper.php b/app/Helper/TaskHelper.php
index 58e9ce4f..e1d65cca 100644
--- a/app/Helper/TaskHelper.php
+++ b/app/Helper/TaskHelper.php
@@ -27,17 +27,17 @@ 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)
@@ -70,6 +70,7 @@ class TaskHelper extends Base
$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) {
@@ -167,10 +168,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');
@@ -180,7 +191,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);
@@ -192,7 +203,7 @@ 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);
+ $attributes = array_merge(array('tabindex="12"', 'placeholder="'.$placeholder.'"'), $attributes);
$html = $this->helper->form->label(t('Start Date'), 'date_started');
$html .= $this->helper->form->text('date_started', $values, $errors, $attributes, 'form-datetime');
@@ -203,7 +214,7 @@ class TaskHelper extends Base
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);
+ $attributes = array_merge(array('tabindex="13"', 'placeholder="'.$placeholder.'"'), $attributes);
$html = $this->helper->form->label(t('Due Date'), 'date_due');
$html .= $this->helper->form->text('date_due', $values, $errors, $attributes, 'form-date');
diff --git a/app/Helper/UserHelper.php b/app/Helper/UserHelper.php
index ae3efe1d..ab259a62 100644
--- a/app/Helper/UserHelper.php
+++ b/app/Helper/UserHelper.php
@@ -107,6 +107,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 +132,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/Locale/bs_BA/translations.php b/app/Locale/bs_BA/translations.php
index 5a60829f..5f513347 100644
--- a/app/Locale/bs_BA/translations.php
+++ b/app/Locale/bs_BA/translations.php
@@ -1215,4 +1215,6 @@ return array(
// 'Do you really want to remove this tag: "%s"?' => '',
// 'Global tags' => '',
// 'There is no global tag at the moment.' => '',
+ // 'This field cannot be empty' => '',
+ // 'Hide tasks in this column in the dashboard' => '',
);
diff --git a/app/Locale/cs_CZ/translations.php b/app/Locale/cs_CZ/translations.php
index f0fce806..1c28f4f9 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',
@@ -393,8 +393,8 @@ return array(
'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 change 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',
@@ -457,13 +457,13 @@ 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',
@@ -471,26 +471,26 @@ return array(
'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 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ů',
@@ -510,7 +510,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',
@@ -523,9 +523,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.',
@@ -549,8 +549,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',
@@ -592,7 +592,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',
@@ -604,28 +604,28 @@ return array(
'%s remove 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 for "%s"' => 'Burndown-Chart pro "%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' => '',
@@ -680,27 +680,27 @@ 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',
+ 'Column change' => 'Změna sloupce',
+ 'Position change' => 'Změna pozice',
+ 'Swimlane change' => 'Změna dráhy',
+ 'Assignee change' => 'Změna přidělení',
+ '[%s] Overdue tasks' => '[%s] přetažených úkolů',
+ 'Notification' => 'Upozornění',
+ '%s moved the task #%d to the first swimlane' => '%s přesunul úkol #%d do první dráhy',
+ '%s moved the task #%d to the swimlane "%s"' => '%s přesunul úkol #%d do dráhy "%s"',
// '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',
@@ -708,11 +708,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',
@@ -1215,4 +1215,6 @@ return array(
// 'Do you really want to remove this tag: "%s"?' => '',
// 'Global tags' => '',
// 'There is no global tag at the moment.' => '',
+ // 'This field cannot be empty' => '',
+ // 'Hide tasks in this column in the dashboard' => '',
);
diff --git a/app/Locale/da_DK/translations.php b/app/Locale/da_DK/translations.php
index afe14d7f..abebd394 100644
--- a/app/Locale/da_DK/translations.php
+++ b/app/Locale/da_DK/translations.php
@@ -1215,4 +1215,6 @@ return array(
// 'Do you really want to remove this tag: "%s"?' => '',
// 'Global tags' => '',
// 'There is no global tag at the moment.' => '',
+ // 'This field cannot be empty' => '',
+ // 'Hide tasks in this column in the dashboard' => '',
);
diff --git a/app/Locale/de_DE/translations.php b/app/Locale/de_DE/translations.php
index 232b9f56..f569206b 100644
--- a/app/Locale/de_DE/translations.php
+++ b/app/Locale/de_DE/translations.php
@@ -1196,23 +1196,25 @@ return array(
'Email transport' => 'E-Mail Verkehr',
'Webhook token' => 'Webhook Token',
'Imports' => 'Importe',
- // '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.' => '',
+ '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',
+ 'Hide tasks in this column in the dashboard' => 'Aufgaben in dieser Spalte im Dashboard ausblenden',
);
diff --git a/app/Locale/el_GR/translations.php b/app/Locale/el_GR/translations.php
index 6454af12..c1d7c579 100644
--- a/app/Locale/el_GR/translations.php
+++ b/app/Locale/el_GR/translations.php
@@ -1215,4 +1215,6 @@ return array(
// 'Do you really want to remove this tag: "%s"?' => '',
// 'Global tags' => '',
// 'There is no global tag at the moment.' => '',
+ // 'This field cannot be empty' => '',
+ // 'Hide tasks in this column in the dashboard' => '',
);
diff --git a/app/Locale/es_ES/translations.php b/app/Locale/es_ES/translations.php
index 6ba86f8f..5699ce6f 100644
--- a/app/Locale/es_ES/translations.php
+++ b/app/Locale/es_ES/translations.php
@@ -1215,4 +1215,6 @@ return array(
// 'Do you really want to remove this tag: "%s"?' => '',
// 'Global tags' => '',
// 'There is no global tag at the moment.' => '',
+ // 'This field cannot be empty' => '',
+ // 'Hide tasks in this column in the dashboard' => '',
);
diff --git a/app/Locale/fi_FI/translations.php b/app/Locale/fi_FI/translations.php
index cb2a8d9a..6fe4852c 100644
--- a/app/Locale/fi_FI/translations.php
+++ b/app/Locale/fi_FI/translations.php
@@ -1215,4 +1215,6 @@ return array(
// 'Do you really want to remove this tag: "%s"?' => '',
// 'Global tags' => '',
// 'There is no global tag at the moment.' => '',
+ // 'This field cannot be empty' => '',
+ // 'Hide tasks in this column in the dashboard' => '',
);
diff --git a/app/Locale/fr_FR/translations.php b/app/Locale/fr_FR/translations.php
index cac1c73a..7663da0f 100644
--- a/app/Locale/fr_FR/translations.php
+++ b/app/Locale/fr_FR/translations.php
@@ -944,7 +944,7 @@ 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',
@@ -1216,4 +1216,6 @@ return array(
'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',
+ // 'Hide tasks in this column in the dashboard' => '',
);
diff --git a/app/Locale/hu_HU/translations.php b/app/Locale/hu_HU/translations.php
index e032be66..96db72ef 100644
--- a/app/Locale/hu_HU/translations.php
+++ b/app/Locale/hu_HU/translations.php
@@ -1215,4 +1215,6 @@ return array(
// 'Do you really want to remove this tag: "%s"?' => '',
// 'Global tags' => '',
// 'There is no global tag at the moment.' => '',
+ // 'This field cannot be empty' => '',
+ // 'Hide tasks in this column in the dashboard' => '',
);
diff --git a/app/Locale/id_ID/translations.php b/app/Locale/id_ID/translations.php
index 29d11b8d..2d6e5aa3 100644
--- a/app/Locale/id_ID/translations.php
+++ b/app/Locale/id_ID/translations.php
@@ -1215,4 +1215,6 @@ return array(
// 'Do you really want to remove this tag: "%s"?' => '',
// 'Global tags' => '',
// 'There is no global tag at the moment.' => '',
+ // 'This field cannot be empty' => '',
+ // 'Hide tasks in this column in the dashboard' => '',
);
diff --git a/app/Locale/it_IT/translations.php b/app/Locale/it_IT/translations.php
index c5710a4e..e10b61da 100644
--- a/app/Locale/it_IT/translations.php
+++ b/app/Locale/it_IT/translations.php
@@ -1196,23 +1196,25 @@ return array(
'Email transport' => 'Trasporto Email',
// 'Webhook token' => '',
'Imports' => 'Importa',
- // '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.' => '',
+ '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' => '',
- // 'Do you really want to remove this tag: "%s"?' => '',
- // 'Global tags' => '',
- // 'There is no global tag at the moment.' => '',
+ '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',
+ // 'Hide tasks in this column in the dashboard' => '',
);
diff --git a/app/Locale/ja_JP/translations.php b/app/Locale/ja_JP/translations.php
index 589917f0..2fe13ac9 100644
--- a/app/Locale/ja_JP/translations.php
+++ b/app/Locale/ja_JP/translations.php
@@ -1215,4 +1215,6 @@ return array(
// 'Do you really want to remove this tag: "%s"?' => '',
// 'Global tags' => '',
// 'There is no global tag at the moment.' => '',
+ // 'This field cannot be empty' => '',
+ // 'Hide tasks in this column in the dashboard' => '',
);
diff --git a/app/Locale/ko_KR/translations.php b/app/Locale/ko_KR/translations.php
index 2f142b48..bd25d11f 100644
--- a/app/Locale/ko_KR/translations.php
+++ b/app/Locale/ko_KR/translations.php
@@ -1215,4 +1215,6 @@ return array(
// 'Do you really want to remove this tag: "%s"?' => '',
// 'Global tags' => '',
// 'There is no global tag at the moment.' => '',
+ // 'This field cannot be empty' => '',
+ // 'Hide tasks in this column in the dashboard' => '',
);
diff --git a/app/Locale/my_MY/translations.php b/app/Locale/my_MY/translations.php
index 02fa16f8..ff8960aa 100644
--- a/app/Locale/my_MY/translations.php
+++ b/app/Locale/my_MY/translations.php
@@ -1215,4 +1215,6 @@ return array(
// 'Do you really want to remove this tag: "%s"?' => '',
// 'Global tags' => '',
// 'There is no global tag at the moment.' => '',
+ // 'This field cannot be empty' => '',
+ // 'Hide tasks in this column in the dashboard' => '',
);
diff --git a/app/Locale/nb_NO/translations.php b/app/Locale/nb_NO/translations.php
index 6bb30642..8752a159 100644
--- a/app/Locale/nb_NO/translations.php
+++ b/app/Locale/nb_NO/translations.php
@@ -1215,4 +1215,6 @@ return array(
// 'Do you really want to remove this tag: "%s"?' => '',
// 'Global tags' => '',
// 'There is no global tag at the moment.' => '',
+ // 'This field cannot be empty' => '',
+ // 'Hide tasks in this column in the dashboard' => '',
);
diff --git a/app/Locale/nl_NL/translations.php b/app/Locale/nl_NL/translations.php
index 8dddbe7d..e07ea32c 100644
--- a/app/Locale/nl_NL/translations.php
+++ b/app/Locale/nl_NL/translations.php
@@ -1215,4 +1215,6 @@ return array(
// 'Do you really want to remove this tag: "%s"?' => '',
// 'Global tags' => '',
// 'There is no global tag at the moment.' => '',
+ // 'This field cannot be empty' => '',
+ //' Hide tasks in this column in the dashboard' => '',
);
diff --git a/app/Locale/pl_PL/translations.php b/app/Locale/pl_PL/translations.php
index 46c3b034..896d2ed4 100644
--- a/app/Locale/pl_PL/translations.php
+++ b/app/Locale/pl_PL/translations.php
@@ -1215,4 +1215,6 @@ return array(
// 'Do you really want to remove this tag: "%s"?' => '',
// 'Global tags' => '',
// 'There is no global tag at the moment.' => '',
+ // 'This field cannot be empty' => '',
+ // 'Hide tasks in this column in the dashboard' => '',
);
diff --git a/app/Locale/pt_BR/translations.php b/app/Locale/pt_BR/translations.php
index 6a0ebd65..40f3bb4d 100644
--- a/app/Locale/pt_BR/translations.php
+++ b/app/Locale/pt_BR/translations.php
@@ -1215,4 +1215,6 @@ return array(
// 'Do you really want to remove this tag: "%s"?' => '',
// 'Global tags' => '',
// 'There is no global tag at the moment.' => '',
+ // 'This field cannot be empty' => '',
+ // 'Hide tasks in this column in the dashboard' => '',
);
diff --git a/app/Locale/pt_PT/translations.php b/app/Locale/pt_PT/translations.php
index 42826240..08375ad0 100644
--- a/app/Locale/pt_PT/translations.php
+++ b/app/Locale/pt_PT/translations.php
@@ -1196,23 +1196,25 @@ return array(
'Email transport' => 'Transportador de Email',
'Webhook token' => 'Token do Webhook',
'Imports' => 'Importados',
- // '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.' => '',
+ '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' => '',
+ //'Hide tasks in this column in the dashboard' => '',
);
diff --git a/app/Locale/ru_RU/translations.php b/app/Locale/ru_RU/translations.php
index e7b73ef0..c6285f6a 100644
--- a/app/Locale/ru_RU/translations.php
+++ b/app/Locale/ru_RU/translations.php
@@ -715,9 +715,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 дня.',
@@ -1154,65 +1154,67 @@ 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' => '',
+ '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' => '',
// '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.' => '',
+ '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' => 'Это поле не может быть пустым',
+ 'Hide tasks in this column in the dashboard' => 'Не показывать задачи из этой колонки в кабинете',
);
diff --git a/app/Locale/sr_Latn_RS/translations.php b/app/Locale/sr_Latn_RS/translations.php
index 96a463d1..92ed3424 100644
--- a/app/Locale/sr_Latn_RS/translations.php
+++ b/app/Locale/sr_Latn_RS/translations.php
@@ -1215,4 +1215,6 @@ return array(
// 'Do you really want to remove this tag: "%s"?' => '',
// 'Global tags' => '',
// 'There is no global tag at the moment.' => '',
+ // 'This field cannot be empty' => '',
+ // 'Hide tasks in this column in the dashboard' => '',
);
diff --git a/app/Locale/sv_SE/translations.php b/app/Locale/sv_SE/translations.php
index cdfb36f6..eedcf0fc 100644
--- a/app/Locale/sv_SE/translations.php
+++ b/app/Locale/sv_SE/translations.php
@@ -1215,4 +1215,6 @@ return array(
// 'Do you really want to remove this tag: "%s"?' => '',
// 'Global tags' => '',
// 'There is no global tag at the moment.' => '',
+ // 'This field cannot be empty' => '',
+ // 'Hide tasks in this column in the dashboard' => '',
);
diff --git a/app/Locale/th_TH/translations.php b/app/Locale/th_TH/translations.php
index fd59c003..a6de8bce 100644
--- a/app/Locale/th_TH/translations.php
+++ b/app/Locale/th_TH/translations.php
@@ -1215,4 +1215,6 @@ return array(
// 'Do you really want to remove this tag: "%s"?' => '',
// 'Global tags' => '',
// 'There is no global tag at the moment.' => '',
+ // 'This field cannot be empty' => '',
+ // 'Hide tasks in this column in the dashboard' => '',
);
diff --git a/app/Locale/tr_TR/translations.php b/app/Locale/tr_TR/translations.php
index ab888266..35e29649 100644
--- a/app/Locale/tr_TR/translations.php
+++ b/app/Locale/tr_TR/translations.php
@@ -1215,4 +1215,6 @@ return array(
// 'Do you really want to remove this tag: "%s"?' => '',
// 'Global tags' => '',
// 'There is no global tag at the moment.' => '',
+ // 'This field cannot be empty' => '',
+ // 'Hide tasks in this column in the dashboard' => '',
);
diff --git a/app/Locale/zh_CN/translations.php b/app/Locale/zh_CN/translations.php
index cafb8c55..0ef01ef7 100644
--- a/app/Locale/zh_CN/translations.php
+++ b/app/Locale/zh_CN/translations.php
@@ -1215,4 +1215,6 @@ return array(
// 'Do you really want to remove this tag: "%s"?' => '',
// 'Global tags' => '',
// 'There is no global tag at the moment.' => '',
+ // 'This field cannot be empty' => '',
+ // 'Hide tasks in this column in the dashboard' => '',
);
diff --git a/app/Model/ColumnModel.php b/app/Model/ColumnModel.php
index 795fe692..5498ef54 100644
--- a/app/Model/ColumnModel.php
+++ b/app/Model/ColumnModel.php
@@ -138,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,
);
@@ -165,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/NotificationModel.php b/app/Model/NotificationModel.php
index 8937b77e..4d697b5e 100644
--- a/app/Model/NotificationModel.php
+++ b/app/Model/NotificationModel.php
@@ -133,4 +133,41 @@ class NotificationModel extends Base
return e('Notification');
}
}
+
+ /**
+ * Get task id from event
+ *
+ * @access public
+ * @param string $event_name
+ * @param array $event_data
+ * @return integer
+ */
+ public function getTaskIdFromEvent($event_name, array $event_data)
+ {
+ switch ($event_name) {
+ case TaskFileModel::EVENT_CREATE:
+ return $event_data['file']['task_id'];
+ case CommentModel::EVENT_CREATE:
+ case CommentModel::EVENT_UPDATE:
+ return $event_data['comment']['task_id'];
+ case SubtaskModel::EVENT_CREATE:
+ case SubtaskModel::EVENT_UPDATE:
+ return $event_data['subtask']['task_id'];
+ case TaskModel::EVENT_CREATE:
+ case TaskModel::EVENT_UPDATE:
+ case TaskModel::EVENT_CLOSE:
+ case TaskModel::EVENT_OPEN:
+ case TaskModel::EVENT_MOVE_COLUMN:
+ case TaskModel::EVENT_MOVE_POSITION:
+ case TaskModel::EVENT_MOVE_SWIMLANE:
+ case TaskModel::EVENT_ASSIGNEE_CHANGE:
+ case CommentModel::EVENT_USER_MENTION:
+ case TaskModel::EVENT_USER_MENTION:
+ return $event_data['task']['id'];
+ case TaskModel::EVENT_OVERDUE:
+ return $event_data['tasks'][0]['id'];
+ default:
+ return 0;
+ }
+ }
}
diff --git a/app/Model/ProjectDuplicationModel.php b/app/Model/ProjectDuplicationModel.php
index b67f8302..94b83c80 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)) {
diff --git a/app/Model/ProjectModel.php b/app/Model/ProjectModel.php
index 7382537e..850531c9 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
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/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/TaskCreationModel.php b/app/Model/TaskCreationModel.php
index fa2d32c6..cd70a028 100644
--- a/app/Model/TaskCreationModel.php
+++ b/app/Model/TaskCreationModel.php
@@ -60,7 +60,7 @@ class TaskCreationModel extends Base
$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']);
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/TaskFinderModel.php b/app/Model/TaskFinderModel.php
index 0e99c407..7268052c 100644
--- a/app/Model/TaskFinderModel.php
+++ b/app/Model/TaskFinderModel.php
@@ -81,7 +81,8 @@ class TaskFinderModel extends Base
->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);
}
/**
@@ -166,6 +167,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();
}
@@ -183,7 +185,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');
}
/**
@@ -367,6 +370,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/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 1b176a41..be5f53c8 100644
--- a/app/Model/TaskModificationModel.php
+++ b/app/Model/TaskModificationModel.php
@@ -108,8 +108,6 @@ class TaskModificationModel extends Base
if (isset($values['tags'])) {
$this->taskTagModel->save($original_task['project_id'], $values['id'], $values['tags']);
unset($values['tags']);
- } else {
- $this->taskTagModel->save($original_task['project_id'], $values['id'], array());
}
}
}
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..eda23c0b
--- /dev/null
+++ b/app/Model/TaskProjectMoveModel.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace Kanboard\Model;
+
+use Kanboard\Event\TaskEvent;
+
+/**
+ * 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)) {
+ $event = new TaskEvent(array_merge($task, $values, array('task_id' => $task['id'])));
+ $this->dispatcher->dispatch(TaskModel::EVENT_MOVE_PROJECT, $event);
+ }
+
+ 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/TaskTagModel.php b/app/Model/TaskTagModel.php
index 91dfd224..0553cc6c 100644
--- a/app/Model/TaskTagModel.php
+++ b/app/Model/TaskTagModel.php
@@ -20,6 +20,23 @@ class TaskTagModel extends Base
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
@@ -82,6 +99,7 @@ class TaskTagModel extends Base
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);
diff --git a/app/Schema/Mysql.php b/app/Schema/Mysql.php
index 82ccb8c8..99fed66f 100644
--- a/app/Schema/Mysql.php
+++ b/app/Schema/Mysql.php
@@ -6,7 +6,12 @@ use PDO;
use Kanboard\Core\Security\Token;
use Kanboard\Core\Security\Role;
-const VERSION = 111;
+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)
{
diff --git a/app/Schema/Postgres.php b/app/Schema/Postgres.php
index 229cbd25..b982bcae 100644
--- a/app/Schema/Postgres.php
+++ b/app/Schema/Postgres.php
@@ -6,7 +6,12 @@ use PDO;
use Kanboard\Core\Security\Token;
use Kanboard\Core\Security\Role;
-const VERSION = 90;
+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)
{
diff --git a/app/Schema/Sql/mysql.sql b/app/Schema/Sql/mysql.sql
index 92ca3686..67dd170a 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','19ffd9709d03ce50675c3a43d1c49c1ac207f4bc45f06c5b2701fbdf8929',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','1d62395a742260738a406789366a84138ced50a1be62e8862c5cf8d0a561',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$g28mYPBdsf3/gX/ayd7A8.HSPBRQ/zM/PXlfijelJhXwhnukCRIDi', 'app-admin');INSERT INTO schema_version VALUES ('112');
diff --git a/app/Schema/Sql/postgres.sql b/app/Schema/Sql/postgres.sql
index 6c17c1b1..5b4142b7 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$g28mYPBdsf3/gX/ayd7A8.HSPBRQ/zM/PXlfijelJhXwhnukCRIDi', 'app-admin');INSERT INTO schema_version VALUES ('91');
diff --git a/app/Schema/Sqlite.php b/app/Schema/Sqlite.php
index dac348d4..2a7735ee 100644
--- a/app/Schema/Sqlite.php
+++ b/app/Schema/Sqlite.php
@@ -6,7 +6,12 @@ use Kanboard\Core\Security\Token;
use Kanboard\Core\Security\Role;
use PDO;
-const VERSION = 102;
+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)
{
diff --git a/app/ServiceProvider/ApiProvider.php b/app/ServiceProvider/ApiProvider.php
index f88d9b4f..5cf6231c 100644
--- a/app/ServiceProvider/ApiProvider.php
+++ b/app/ServiceProvider/ApiProvider.php
@@ -9,6 +9,8 @@ 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;
@@ -57,6 +59,7 @@ class ApiProvider implements ServiceProviderInterface
->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))
@@ -65,6 +68,7 @@ class ApiProvider implements ServiceProviderInterface
->withObject(new SwimlaneProcedure($container))
->withObject(new TaskProcedure($container))
->withObject(new TaskLinkProcedure($container))
+ ->withObject(new TaskExternalLinkProcedure($container))
->withObject(new UserProcedure($container))
->withObject(new GroupProcedure($container))
->withObject(new GroupMemberProcedure($container))
diff --git a/app/ServiceProvider/AuthenticationProvider.php b/app/ServiceProvider/AuthenticationProvider.php
index 751fe514..978bc05b 100644
--- a/app/ServiceProvider/AuthenticationProvider.php
+++ b/app/ServiceProvider/AuthenticationProvider.php
@@ -202,8 +202,10 @@ class AuthenticationProvider implements ServiceProviderInterface
$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 c0fb93bd..e32c0d43 100644
--- a/app/ServiceProvider/ClassProvider.php
+++ b/app/ServiceProvider/ClassProvider.php
@@ -55,16 +55,22 @@ class ClassProvider implements ServiceProviderInterface
'ProjectNotificationModel',
'ProjectMetadataModel',
'ProjectGroupRoleModel',
+ 'ProjectTaskDuplicationModel',
+ 'ProjectTaskPriorityModel',
'ProjectUserRoleModel',
'RememberMeSessionModel',
'SubtaskModel',
'SubtaskTimeTrackingModel',
'SwimlaneModel',
+ 'TagDuplicationModel',
'TagModel',
'TaskModel',
'TaskAnalyticModel',
'TaskCreationModel',
'TaskDuplicationModel',
+ 'TaskProjectDuplicationModel',
+ 'TaskProjectMoveModel',
+ 'TaskRecurrenceModel',
'TaskExternalLinkModel',
'TaskFinderModel',
'TaskFileModel',
diff --git a/app/Subscriber/RecurringTaskSubscriber.php b/app/Subscriber/RecurringTaskSubscriber.php
index 75b7ff76..21cd3996 100644
--- a/app/Subscriber/RecurringTaskSubscriber.php
+++ b/app/Subscriber/RecurringTaskSubscriber.php
@@ -22,9 +22,9 @@ class RecurringTaskSubscriber extends BaseSubscriber implements EventSubscriberI
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']);
+ $this->taskRecurrenceModel->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']);
+ $this->taskRecurrenceModel->duplicateRecurringTask($event['task_id']);
}
}
}
@@ -34,7 +34,7 @@ class RecurringTaskSubscriber extends BaseSubscriber implements EventSubscriberI
$this->logger->debug('Subscriber executed: '.__METHOD__);
if ($event['recurrence_status'] == TaskModel::RECURRING_STATUS_PENDING && $event['recurrence_trigger'] == TaskModel::RECURRING_TRIGGER_CLOSE) {
- $this->taskDuplicationModel->duplicateRecurringTask($event['task_id']);
+ $this->taskRecurrenceModel->duplicateRecurringTask($event['task_id']);
}
}
}
diff --git a/app/Template/board/task_footer.php b/app/Template/board/task_footer.php
index dbe775d5..bc34363c 100644
--- a/app/Template/board/task_footer.php
+++ b/app/Template/board/task_footer.php
@@ -37,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/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/dashboard/notifications.php b/app/Template/dashboard/notifications.php
index e0e9b878..3b70b49f 100644
--- a/app/Template/dashboard/notifications.php
+++ b/app/Template/dashboard/notifications.php
@@ -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/project_creation/create.php b/app/Template/project_creation/create.php
index 01d06bab..d00883ba 100644
--- a/app/Template/project_creation/create.php
+++ b/app/Template/project_creation/create.php
@@ -23,9 +23,10 @@
<?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_view/duplicate.php b/app/Template/project_view/duplicate.php
index d2cd127a..d66ff591 100644
--- a/app/Template/project_view/duplicate.php
+++ b/app/Template/project_view/duplicate.php
@@ -15,10 +15,11 @@
<?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..667a576c 100644
--- a/app/Template/project_view/show.php
+++ b/app/Template/project_view/show.php
@@ -54,9 +54,10 @@
</div>
<table class="table-stripped">
<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/task_creation/show.php b/app/Template/task_creation/show.php
index f799919a..57e77f37 100644
--- a/app/Template/task_creation/show.php
+++ b/app/Template/task_creation/show.php
@@ -27,6 +27,7 @@
<?= $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>
diff --git a/app/Template/task_gantt_creation/show.php b/app/Template/task_gantt_creation/show.php
index 5e4286bd..7521d805 100644
--- a/app/Template/task_gantt_creation/show.php
+++ b/app/Template/task_gantt_creation/show.php
@@ -23,6 +23,7 @@
<?= $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>
diff --git a/app/Template/task_modification/show.php b/app/Template/task_modification/show.php
index d747407e..cc38582c 100644
--- a/app/Template/task_modification/show.php
+++ b/app/Template/task_modification/show.php
@@ -21,6 +21,7 @@
<?= $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>
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/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/constants.php b/app/constants.php
index 604f6acd..fc120692 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');