summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFrederic Guillot <fred@kanboard.net>2016-09-08 22:33:16 -0400
committerFrederic Guillot <fred@kanboard.net>2016-09-08 22:33:16 -0400
commit75470c72428c8d8f278d160369558ab31b137fb1 (patch)
tree7fcad6cbc661e2762f1dfa5f643a5beac5217a17
parentfedf4ea2de21fcf95fc5aa942cedc7924865f160 (diff)
Apply column restrictions to the board
-rw-r--r--app/Controller/BoardAjaxController.php12
-rw-r--r--app/Core/Base.php327
-rw-r--r--app/Core/Helper.php1
-rw-r--r--app/Decorator/ColumnMoveRestrictionCacheDecorator.php57
-rw-r--r--app/Formatter/BoardTaskFormatter.php5
-rw-r--r--app/Helper/BoardHelper.php16
-rw-r--r--app/Model/ColumnMoveRestrictionModel.php17
-rw-r--r--app/ServiceProvider/CacheProvider.php8
-rw-r--r--app/ServiceProvider/ClassProvider.php2
-rw-r--r--app/Template/board/task_private.php3
-rw-r--r--assets/js/app.min.js2
-rw-r--r--assets/js/src/BoardDragAndDrop.js7
-rw-r--r--tests/units/Formatter/BoardFormatterTest.php1
-rw-r--r--tests/units/Helper/BoardHelperTest.php94
14 files changed, 383 insertions, 169 deletions
diff --git a/app/Controller/BoardAjaxController.php b/app/Controller/BoardAjaxController.php
index ccd47667..5e771fd6 100644
--- a/app/Controller/BoardAjaxController.php
+++ b/app/Controller/BoardAjaxController.php
@@ -28,11 +28,21 @@ class BoardAjaxController extends BaseController
}
$values = $this->request->getJson();
+ $canMoveTask = $this->columnMoveRestrictionModel->isAllowed(
+ $project_id,
+ $this->helper->user->getProjectUserRole($project_id),
+ $values['src_column_id'],
+ $values['dst_column_id']
+ );
+
+ if (! $canMoveTask) {
+ throw new AccessForbiddenException("You don't have the permission to move this task");
+ }
$result =$this->taskPositionModel->movePosition(
$project_id,
$values['task_id'],
- $values['column_id'],
+ $values['dst_column_id'],
$values['position'],
$values['swimlane_id']
);
diff --git a/app/Core/Base.php b/app/Core/Base.php
index 3b7c5e66..747b1917 100644
--- a/app/Core/Base.php
+++ b/app/Core/Base.php
@@ -10,168 +10,171 @@ use Pimple\Container;
* @package core
* @author Frederic Guillot
*
- * @property \Kanboard\Analytic\TaskDistributionAnalytic $taskDistributionAnalytic
- * @property \Kanboard\Analytic\UserDistributionAnalytic $userDistributionAnalytic
- * @property \Kanboard\Analytic\EstimatedTimeComparisonAnalytic $estimatedTimeComparisonAnalytic
- * @property \Kanboard\Analytic\AverageLeadCycleTimeAnalytic $averageLeadCycleTimeAnalytic
- * @property \Kanboard\Analytic\AverageTimeSpentColumnAnalytic $averageTimeSpentColumnAnalytic
- * @property \Kanboard\Core\Action\ActionManager $actionManager
- * @property \Kanboard\Core\ExternalLink\ExternalLinkManager $externalLinkManager
- * @property \Kanboard\Core\Cache\MemoryCache $memoryCache
- * @property \Kanboard\Core\Cache\BaseCache $cacheDriver
- * @property \Kanboard\Core\Event\EventManager $eventManager
- * @property \Kanboard\Core\Group\GroupManager $groupManager
- * @property \Kanboard\Core\Http\Client $httpClient
- * @property \Kanboard\Core\Http\OAuth2 $oauth
- * @property \Kanboard\Core\Http\RememberMeCookie $rememberMeCookie
- * @property \Kanboard\Core\Http\Request $request
- * @property \Kanboard\Core\Http\Response $response
- * @property \Kanboard\Core\Http\Router $router
- * @property \Kanboard\Core\Http\Route $route
- * @property \Kanboard\Core\Queue\QueueManager $queueManager
- * @property \Kanboard\Core\Mail\Client $emailClient
- * @property \Kanboard\Core\ObjectStorage\ObjectStorageInterface $objectStorage
- * @property \Kanboard\Core\Plugin\Hook $hook
- * @property \Kanboard\Core\Plugin\Loader $pluginLoader
- * @property \Kanboard\Core\Security\AuthenticationManager $authenticationManager
- * @property \Kanboard\Core\Security\AccessMap $applicationAccessMap
- * @property \Kanboard\Core\Security\AccessMap $projectAccessMap
- * @property \Kanboard\Core\Security\AccessMap $apiAccessMap
- * @property \Kanboard\Core\Security\AccessMap $apiProjectAccessMap
- * @property \Kanboard\Core\Security\Authorization $applicationAuthorization
- * @property \Kanboard\Core\Security\Authorization $projectAuthorization
- * @property \Kanboard\Core\Security\Authorization $apiAuthorization
- * @property \Kanboard\Core\Security\Authorization $apiProjectAuthorization
- * @property \Kanboard\Core\Security\Role $role
- * @property \Kanboard\Core\Security\Token $token
- * @property \Kanboard\Core\Session\FlashMessage $flash
- * @property \Kanboard\Core\Session\SessionManager $sessionManager
- * @property \Kanboard\Core\Session\SessionStorage $sessionStorage
- * @property \Kanboard\Core\User\Avatar\AvatarManager $avatarManager
- * @property \Kanboard\Core\User\GroupSync $groupSync
- * @property \Kanboard\Core\User\UserProfile $userProfile
- * @property \Kanboard\Core\User\UserSync $userSync
- * @property \Kanboard\Core\User\UserSession $userSession
- * @property \Kanboard\Core\DateParser $dateParser
- * @property \Kanboard\Core\Helper $helper
- * @property \Kanboard\Core\Paginator $paginator
- * @property \Kanboard\Core\Template $template
- * @property \Kanboard\Decorator\MetadataCacheDecorator $userMetadataCacheDecorator
- * @property \Kanboard\Model\ActionModel $actionModel
- * @property \Kanboard\Model\ActionParameterModel $actionParameterModel
- * @property \Kanboard\Model\AvatarFileModel $avatarFileModel
- * @property \Kanboard\Model\BoardModel $boardModel
- * @property \Kanboard\Model\CategoryModel $categoryModel
- * @property \Kanboard\Model\ColorModel $colorModel
- * @property \Kanboard\Model\ColumnModel $columnModel
- * @property \Kanboard\Model\CommentModel $commentModel
- * @property \Kanboard\Model\ConfigModel $configModel
- * @property \Kanboard\Model\CurrencyModel $currencyModel
- * @property \Kanboard\Model\CustomFilterModel $customFilterModel
- * @property \Kanboard\Model\TaskFileModel $taskFileModel
- * @property \Kanboard\Model\ProjectFileModel $projectFileModel
- * @property \Kanboard\Model\GroupModel $groupModel
- * @property \Kanboard\Model\GroupMemberModel $groupMemberModel
- * @property \Kanboard\Model\LanguageModel $languageModel
- * @property \Kanboard\Model\LastLoginModel $lastLoginModel
- * @property \Kanboard\Model\LinkModel $linkModel
- * @property \Kanboard\Model\NotificationModel $notificationModel
- * @property \Kanboard\Model\PasswordResetModel $passwordResetModel
- * @property \Kanboard\Model\ProjectModel $projectModel
- * @property \Kanboard\Model\ProjectActivityModel $projectActivityModel
- * @property \Kanboard\Model\ProjectDuplicationModel $projectDuplicationModel
- * @property \Kanboard\Model\ProjectDailyColumnStatsModel $projectDailyColumnStatsModel
- * @property \Kanboard\Model\ProjectDailyStatsModel $projectDailyStatsModel
- * @property \Kanboard\Model\ProjectMetadataModel $projectMetadataModel
- * @property \Kanboard\Model\ProjectPermissionModel $projectPermissionModel
- * @property \Kanboard\Model\ProjectUserRoleModel $projectUserRoleModel
- * @property \Kanboard\Model\ProjectGroupRoleModel $projectGroupRoleModel
- * @property \Kanboard\Model\ProjectNotificationModel $projectNotificationModel
- * @property \Kanboard\Model\ProjectNotificationTypeModel $projectNotificationTypeModel
- * @property \Kanboard\Model\ProjectTaskDuplicationModel $projectTaskDuplicationModel
- * @property \Kanboard\Model\ProjectTaskPriorityModel $projectTaskPriorityModel
- * @property \Kanboard\Model\RememberMeSessionModel $rememberMeSessionModel
- * @property \Kanboard\Model\SubtaskModel $subtaskModel
- * @property \Kanboard\Model\SubtaskPositionModel $subtaskPositionModel
- * @property \Kanboard\Model\SubtaskStatusModel $subtaskStatusModel
- * @property \Kanboard\Model\SubtaskTaskConversionModel $subtaskTaskConversionModel
- * @property \Kanboard\Model\SubtaskTimeTrackingModel $subtaskTimeTrackingModel
- * @property \Kanboard\Model\SwimlaneModel $swimlaneModel
- * @property \Kanboard\Model\TagDuplicationModel $tagDuplicationModel
- * @property \Kanboard\Model\TagModel $tagModel
- * @property \Kanboard\Model\TaskModel $taskModel
- * @property \Kanboard\Model\TaskAnalyticModel $taskAnalyticModel
- * @property \Kanboard\Model\TaskCreationModel $taskCreationModel
- * @property \Kanboard\Model\TaskDuplicationModel $taskDuplicationModel
- * @property \Kanboard\Model\TaskProjectDuplicationModel $taskProjectDuplicationModel
- * @property \Kanboard\Model\TaskProjectMoveModel $taskProjectMoveModel
- * @property \Kanboard\Model\TaskRecurrenceModel $taskRecurrenceModel
- * @property \Kanboard\Model\TaskExternalLinkModel $taskExternalLinkModel
- * @property \Kanboard\Model\TaskFinderModel $taskFinderModel
- * @property \Kanboard\Model\TaskLinkModel $taskLinkModel
- * @property \Kanboard\Model\TaskModificationModel $taskModificationModel
- * @property \Kanboard\Model\TaskPositionModel $taskPositionModel
- * @property \Kanboard\Model\TaskStatusModel $taskStatusModel
- * @property \Kanboard\Model\TaskTagModel $taskTagModel
- * @property \Kanboard\Model\TaskMetadataModel $taskMetadataModel
- * @property \Kanboard\Model\TimezoneModel $timezoneModel
- * @property \Kanboard\Model\TransitionModel $transitionModel
- * @property \Kanboard\Model\UserModel $userModel
- * @property \Kanboard\Model\UserLockingModel $userLockingModel
- * @property \Kanboard\Model\UserMentionModel $userMentionModel
- * @property \Kanboard\Model\UserNotificationModel $userNotificationModel
- * @property \Kanboard\Model\UserNotificationTypeModel $userNotificationTypeModel
- * @property \Kanboard\Model\UserNotificationFilterModel $userNotificationFilterModel
- * @property \Kanboard\Model\UserUnreadNotificationModel $userUnreadNotificationModel
- * @property \Kanboard\Model\UserMetadataModel $userMetadataModel
- * @property \Kanboard\Pagination\TaskPagination $taskPagination
- * @property \Kanboard\Pagination\SubtaskPagination $subtaskPagination
- * @property \Kanboard\Pagination\ProjectPagination $projectPagination
- * @property \Kanboard\Pagination\UserPagination $userPagination
- * @property \Kanboard\Validator\ActionValidator $actionValidator
- * @property \Kanboard\Validator\AuthValidator $authValidator
- * @property \Kanboard\Validator\ColumnValidator $columnValidator
- * @property \Kanboard\Validator\CategoryValidator $categoryValidator
- * @property \Kanboard\Validator\CommentValidator $commentValidator
- * @property \Kanboard\Validator\CurrencyValidator $currencyValidator
- * @property \Kanboard\Validator\CustomFilterValidator $customFilterValidator
- * @property \Kanboard\Validator\ExternalLinkValidator $externalLinkValidator
- * @property \Kanboard\Validator\GroupValidator $groupValidator
- * @property \Kanboard\Validator\LinkValidator $linkValidator
- * @property \Kanboard\Validator\PasswordResetValidator $passwordResetValidator
- * @property \Kanboard\Validator\ProjectValidator $projectValidator
- * @property \Kanboard\Validator\SubtaskValidator $subtaskValidator
- * @property \Kanboard\Validator\SwimlaneValidator $swimlaneValidator
- * @property \Kanboard\Validator\TagValidator $tagValidator
- * @property \Kanboard\Validator\TaskLinkValidator $taskLinkValidator
- * @property \Kanboard\Validator\TaskValidator $taskValidator
- * @property \Kanboard\Validator\UserValidator $userValidator
- * @property \Kanboard\Import\TaskImport $taskImport
- * @property \Kanboard\Import\UserImport $userImport
- * @property \Kanboard\Export\SubtaskExport $subtaskExport
- * @property \Kanboard\Export\TaskExport $taskExport
- * @property \Kanboard\Export\TransitionExport $transitionExport
- * @property \Kanboard\Core\Filter\QueryBuilder $projectGroupRoleQuery
- * @property \Kanboard\Core\Filter\QueryBuilder $projectUserRoleQuery
- * @property \Kanboard\Core\Filter\QueryBuilder $projectActivityQuery
- * @property \Kanboard\Core\Filter\QueryBuilder $userQuery
- * @property \Kanboard\Core\Filter\QueryBuilder $projectQuery
- * @property \Kanboard\Core\Filter\QueryBuilder $taskQuery
- * @property \Kanboard\Core\Filter\LexerBuilder $taskLexer
- * @property \Kanboard\Core\Filter\LexerBuilder $projectActivityLexer
- * @property \Kanboard\Job\CommentEventJob $commentEventJob
- * @property \Kanboard\Job\SubtaskEventJob $subtaskEventJob
- * @property \Kanboard\Job\TaskEventJob $taskEventJob
- * @property \Kanboard\Job\TaskFileEventJob $taskFileEventJob
- * @property \Kanboard\Job\TaskLinkEventJob $taskLinkEventJob
- * @property \Kanboard\Job\ProjectFileEventJob $projectFileEventJob
- * @property \Kanboard\Job\NotificationJob $notificationJob
- * @property \Kanboard\Job\ProjectMetricJob $projectMetricJob
- * @property \Psr\Log\LoggerInterface $logger
- * @property \PicoDb\Database $db
- * @property \Symfony\Component\EventDispatcher\EventDispatcher $dispatcher
- * @property \Symfony\Component\Console\Application $cli
- * @property \JsonRPC\Server $api
+ * @property \Kanboard\Analytic\TaskDistributionAnalytic $taskDistributionAnalytic
+ * @property \Kanboard\Analytic\UserDistributionAnalytic $userDistributionAnalytic
+ * @property \Kanboard\Analytic\EstimatedTimeComparisonAnalytic $estimatedTimeComparisonAnalytic
+ * @property \Kanboard\Analytic\AverageLeadCycleTimeAnalytic $averageLeadCycleTimeAnalytic
+ * @property \Kanboard\Analytic\AverageTimeSpentColumnAnalytic $averageTimeSpentColumnAnalytic
+ * @property \Kanboard\Core\Action\ActionManager $actionManager
+ * @property \Kanboard\Core\ExternalLink\ExternalLinkManager $externalLinkManager
+ * @property \Kanboard\Core\Cache\MemoryCache $memoryCache
+ * @property \Kanboard\Core\Cache\BaseCache $cacheDriver
+ * @property \Kanboard\Core\Event\EventManager $eventManager
+ * @property \Kanboard\Core\Group\GroupManager $groupManager
+ * @property \Kanboard\Core\Http\Client $httpClient
+ * @property \Kanboard\Core\Http\OAuth2 $oauth
+ * @property \Kanboard\Core\Http\RememberMeCookie $rememberMeCookie
+ * @property \Kanboard\Core\Http\Request $request
+ * @property \Kanboard\Core\Http\Response $response
+ * @property \Kanboard\Core\Http\Router $router
+ * @property \Kanboard\Core\Http\Route $route
+ * @property \Kanboard\Core\Queue\QueueManager $queueManager
+ * @property \Kanboard\Core\Mail\Client $emailClient
+ * @property \Kanboard\Core\ObjectStorage\ObjectStorageInterface $objectStorage
+ * @property \Kanboard\Core\Plugin\Hook $hook
+ * @property \Kanboard\Core\Plugin\Loader $pluginLoader
+ * @property \Kanboard\Core\Security\AuthenticationManager $authenticationManager
+ * @property \Kanboard\Core\Security\AccessMap $applicationAccessMap
+ * @property \Kanboard\Core\Security\AccessMap $projectAccessMap
+ * @property \Kanboard\Core\Security\AccessMap $apiAccessMap
+ * @property \Kanboard\Core\Security\AccessMap $apiProjectAccessMap
+ * @property \Kanboard\Core\Security\Authorization $applicationAuthorization
+ * @property \Kanboard\Core\Security\Authorization $projectAuthorization
+ * @property \Kanboard\Core\Security\Authorization $apiAuthorization
+ * @property \Kanboard\Core\Security\Authorization $apiProjectAuthorization
+ * @property \Kanboard\Core\Security\Role $role
+ * @property \Kanboard\Core\Security\Token $token
+ * @property \Kanboard\Core\Session\FlashMessage $flash
+ * @property \Kanboard\Core\Session\SessionManager $sessionManager
+ * @property \Kanboard\Core\Session\SessionStorage $sessionStorage
+ * @property \Kanboard\Core\User\Avatar\AvatarManager $avatarManager
+ * @property \Kanboard\Core\User\GroupSync $groupSync
+ * @property \Kanboard\Core\User\UserProfile $userProfile
+ * @property \Kanboard\Core\User\UserSync $userSync
+ * @property \Kanboard\Core\User\UserSession $userSession
+ * @property \Kanboard\Core\DateParser $dateParser
+ * @property \Kanboard\Core\Helper $helper
+ * @property \Kanboard\Core\Paginator $paginator
+ * @property \Kanboard\Core\Template $template
+ * @property \Kanboard\Decorator\MetadataCacheDecorator $userMetadataCacheDecorator
+ * @property \Kanboard\Decorator\ColumnMoveRestrictionCacheDecorator $columnMoveRestrictionCacheDecorator
+ * @property \Kanboard\Model\ActionModel $actionModel
+ * @property \Kanboard\Model\ActionParameterModel $actionParameterModel
+ * @property \Kanboard\Model\AvatarFileModel $avatarFileModel
+ * @property \Kanboard\Model\BoardModel $boardModel
+ * @property \Kanboard\Model\CategoryModel $categoryModel
+ * @property \Kanboard\Model\ColorModel $colorModel
+ * @property \Kanboard\Model\ColumnModel $columnModel
+ * @property \Kanboard\Model\ColumnMoveRestrictionModel $columnMoveRestrictionModel
+ * @property \Kanboard\Model\CommentModel $commentModel
+ * @property \Kanboard\Model\ConfigModel $configModel
+ * @property \Kanboard\Model\CurrencyModel $currencyModel
+ * @property \Kanboard\Model\CustomFilterModel $customFilterModel
+ * @property \Kanboard\Model\TaskFileModel $taskFileModel
+ * @property \Kanboard\Model\ProjectFileModel $projectFileModel
+ * @property \Kanboard\Model\GroupModel $groupModel
+ * @property \Kanboard\Model\GroupMemberModel $groupMemberModel
+ * @property \Kanboard\Model\LanguageModel $languageModel
+ * @property \Kanboard\Model\LastLoginModel $lastLoginModel
+ * @property \Kanboard\Model\LinkModel $linkModel
+ * @property \Kanboard\Model\NotificationModel $notificationModel
+ * @property \Kanboard\Model\PasswordResetModel $passwordResetModel
+ * @property \Kanboard\Model\ProjectModel $projectModel
+ * @property \Kanboard\Model\ProjectActivityModel $projectActivityModel
+ * @property \Kanboard\Model\ProjectDuplicationModel $projectDuplicationModel
+ * @property \Kanboard\Model\ProjectDailyColumnStatsModel $projectDailyColumnStatsModel
+ * @property \Kanboard\Model\ProjectDailyStatsModel $projectDailyStatsModel
+ * @property \Kanboard\Model\ProjectMetadataModel $projectMetadataModel
+ * @property \Kanboard\Model\ProjectPermissionModel $projectPermissionModel
+ * @property \Kanboard\Model\ProjectUserRoleModel $projectUserRoleModel
+ * @property \Kanboard\Model\ProjectGroupRoleModel $projectGroupRoleModel
+ * @property \Kanboard\Model\ProjectNotificationModel $projectNotificationModel
+ * @property \Kanboard\Model\ProjectNotificationTypeModel $projectNotificationTypeModel
+ * @property \Kanboard\Model\ProjectRoleModel $projectRoleModel
+ * @property \Kanboard\Model\ProjectTaskDuplicationModel $projectTaskDuplicationModel
+ * @property \Kanboard\Model\ProjectTaskPriorityModel $projectTaskPriorityModel
+ * @property \Kanboard\Model\RememberMeSessionModel $rememberMeSessionModel
+ * @property \Kanboard\Model\SubtaskModel $subtaskModel
+ * @property \Kanboard\Model\SubtaskPositionModel $subtaskPositionModel
+ * @property \Kanboard\Model\SubtaskStatusModel $subtaskStatusModel
+ * @property \Kanboard\Model\SubtaskTaskConversionModel $subtaskTaskConversionModel
+ * @property \Kanboard\Model\SubtaskTimeTrackingModel $subtaskTimeTrackingModel
+ * @property \Kanboard\Model\SwimlaneModel $swimlaneModel
+ * @property \Kanboard\Model\TagDuplicationModel $tagDuplicationModel
+ * @property \Kanboard\Model\TagModel $tagModel
+ * @property \Kanboard\Model\TaskModel $taskModel
+ * @property \Kanboard\Model\TaskAnalyticModel $taskAnalyticModel
+ * @property \Kanboard\Model\TaskCreationModel $taskCreationModel
+ * @property \Kanboard\Model\TaskDuplicationModel $taskDuplicationModel
+ * @property \Kanboard\Model\TaskProjectDuplicationModel $taskProjectDuplicationModel
+ * @property \Kanboard\Model\TaskProjectMoveModel $taskProjectMoveModel
+ * @property \Kanboard\Model\TaskRecurrenceModel $taskRecurrenceModel
+ * @property \Kanboard\Model\TaskExternalLinkModel $taskExternalLinkModel
+ * @property \Kanboard\Model\TaskFinderModel $taskFinderModel
+ * @property \Kanboard\Model\TaskLinkModel $taskLinkModel
+ * @property \Kanboard\Model\TaskModificationModel $taskModificationModel
+ * @property \Kanboard\Model\TaskPositionModel $taskPositionModel
+ * @property \Kanboard\Model\TaskStatusModel $taskStatusModel
+ * @property \Kanboard\Model\TaskTagModel $taskTagModel
+ * @property \Kanboard\Model\TaskMetadataModel $taskMetadataModel
+ * @property \Kanboard\Model\TimezoneModel $timezoneModel
+ * @property \Kanboard\Model\TransitionModel $transitionModel
+ * @property \Kanboard\Model\UserModel $userModel
+ * @property \Kanboard\Model\UserLockingModel $userLockingModel
+ * @property \Kanboard\Model\UserMentionModel $userMentionModel
+ * @property \Kanboard\Model\UserNotificationModel $userNotificationModel
+ * @property \Kanboard\Model\UserNotificationTypeModel $userNotificationTypeModel
+ * @property \Kanboard\Model\UserNotificationFilterModel $userNotificationFilterModel
+ * @property \Kanboard\Model\UserUnreadNotificationModel $userUnreadNotificationModel
+ * @property \Kanboard\Model\UserMetadataModel $userMetadataModel
+ * @property \Kanboard\Pagination\TaskPagination $taskPagination
+ * @property \Kanboard\Pagination\SubtaskPagination $subtaskPagination
+ * @property \Kanboard\Pagination\ProjectPagination $projectPagination
+ * @property \Kanboard\Pagination\UserPagination $userPagination
+ * @property \Kanboard\Validator\ActionValidator $actionValidator
+ * @property \Kanboard\Validator\AuthValidator $authValidator
+ * @property \Kanboard\Validator\ColumnValidator $columnValidator
+ * @property \Kanboard\Validator\CategoryValidator $categoryValidator
+ * @property \Kanboard\Validator\CommentValidator $commentValidator
+ * @property \Kanboard\Validator\CurrencyValidator $currencyValidator
+ * @property \Kanboard\Validator\CustomFilterValidator $customFilterValidator
+ * @property \Kanboard\Validator\ExternalLinkValidator $externalLinkValidator
+ * @property \Kanboard\Validator\GroupValidator $groupValidator
+ * @property \Kanboard\Validator\LinkValidator $linkValidator
+ * @property \Kanboard\Validator\PasswordResetValidator $passwordResetValidator
+ * @property \Kanboard\Validator\ProjectValidator $projectValidator
+ * @property \Kanboard\Validator\SubtaskValidator $subtaskValidator
+ * @property \Kanboard\Validator\SwimlaneValidator $swimlaneValidator
+ * @property \Kanboard\Validator\TagValidator $tagValidator
+ * @property \Kanboard\Validator\TaskLinkValidator $taskLinkValidator
+ * @property \Kanboard\Validator\TaskValidator $taskValidator
+ * @property \Kanboard\Validator\UserValidator $userValidator
+ * @property \Kanboard\Import\TaskImport $taskImport
+ * @property \Kanboard\Import\UserImport $userImport
+ * @property \Kanboard\Export\SubtaskExport $subtaskExport
+ * @property \Kanboard\Export\TaskExport $taskExport
+ * @property \Kanboard\Export\TransitionExport $transitionExport
+ * @property \Kanboard\Core\Filter\QueryBuilder $projectGroupRoleQuery
+ * @property \Kanboard\Core\Filter\QueryBuilder $projectUserRoleQuery
+ * @property \Kanboard\Core\Filter\QueryBuilder $projectActivityQuery
+ * @property \Kanboard\Core\Filter\QueryBuilder $userQuery
+ * @property \Kanboard\Core\Filter\QueryBuilder $projectQuery
+ * @property \Kanboard\Core\Filter\QueryBuilder $taskQuery
+ * @property \Kanboard\Core\Filter\LexerBuilder $taskLexer
+ * @property \Kanboard\Core\Filter\LexerBuilder $projectActivityLexer
+ * @property \Kanboard\Job\CommentEventJob $commentEventJob
+ * @property \Kanboard\Job\SubtaskEventJob $subtaskEventJob
+ * @property \Kanboard\Job\TaskEventJob $taskEventJob
+ * @property \Kanboard\Job\TaskFileEventJob $taskFileEventJob
+ * @property \Kanboard\Job\TaskLinkEventJob $taskLinkEventJob
+ * @property \Kanboard\Job\ProjectFileEventJob $projectFileEventJob
+ * @property \Kanboard\Job\NotificationJob $notificationJob
+ * @property \Kanboard\Job\ProjectMetricJob $projectMetricJob
+ * @property \Psr\Log\LoggerInterface $logger
+ * @property \PicoDb\Database $db
+ * @property \Symfony\Component\EventDispatcher\EventDispatcher $dispatcher
+ * @property \Symfony\Component\Console\Application $cli
+ * @property \JsonRPC\Server $api
*/
abstract class Base
{
diff --git a/app/Core/Helper.php b/app/Core/Helper.php
index 43151be8..c98b3c5e 100644
--- a/app/Core/Helper.php
+++ b/app/Core/Helper.php
@@ -12,6 +12,7 @@ use Pimple\Container;
*
* @property \Kanboard\Helper\AppHelper $app
* @property \Kanboard\Helper\AssetHelper $asset
+ * @property \Kanboard\Helper\BoardHelper $board
* @property \Kanboard\Helper\CalendarHelper $calendar
* @property \Kanboard\Helper\DateHelper $dt
* @property \Kanboard\Helper\FileHelper $file
diff --git a/app/Decorator/ColumnMoveRestrictionCacheDecorator.php b/app/Decorator/ColumnMoveRestrictionCacheDecorator.php
new file mode 100644
index 00000000..331bdebb
--- /dev/null
+++ b/app/Decorator/ColumnMoveRestrictionCacheDecorator.php
@@ -0,0 +1,57 @@
+<?php
+
+namespace Kanboard\Decorator;
+
+use Kanboard\Core\Cache\CacheInterface;
+use Kanboard\Model\ColumnMoveRestrictionModel;
+
+/**
+ * Class ColumnMoveRestrictionCacheDecorator
+ *
+ * @package Kanboard\Decorator
+ * @author Frederic Guillot
+ */
+class ColumnMoveRestrictionCacheDecorator
+{
+ protected $cachePrefix = 'column_move_restriction:';
+
+ /**
+ * @var CacheInterface
+ */
+ protected $cache;
+
+ /**
+ * @var ColumnMoveRestrictionModel
+ */
+ protected $columnMoveRestrictionModel;
+
+ /**
+ * ColumnMoveRestrictionDecorator constructor.
+ *
+ * @param CacheInterface $cache
+ * @param ColumnMoveRestrictionModel $columnMoveRestrictionModel
+ */
+ public function __construct(CacheInterface $cache, ColumnMoveRestrictionModel $columnMoveRestrictionModel)
+ {
+ $this->cache = $cache;
+ $this->columnMoveRestrictionModel = $columnMoveRestrictionModel;
+ }
+
+ /**
+ * Proxy method to get column Ids
+ * @param int $project_id
+ * @return array|mixed
+ */
+ public function getAllSrcColumns($project_id)
+ {
+ $key = $this->cachePrefix.$project_id;
+ $columnIds = $this->cache->get($key);
+
+ if ($columnIds === null) {
+ $columnIds = $this->columnMoveRestrictionModel->getAllSrcColumns($project_id);
+ $this->cache->set($key, $columnIds);
+ }
+
+ return $columnIds;
+ }
+}
diff --git a/app/Formatter/BoardTaskFormatter.php b/app/Formatter/BoardTaskFormatter.php
index 3bf171b1..5956ae35 100644
--- a/app/Formatter/BoardTaskFormatter.php
+++ b/app/Formatter/BoardTaskFormatter.php
@@ -79,6 +79,11 @@ class BoardTaskFormatter extends BaseFormatter implements FormatterInterface
{
$tasks = array_values(array_filter($this->tasks, array($this, 'filterTasks')));
array_merge_relation($tasks, $this->tags, 'tags', 'id');
+
+ foreach ($tasks as &$task) {
+ $task['is_draggable'] = $this->helper->board->isDraggable($task);
+ }
+
return $tasks;
}
diff --git a/app/Helper/BoardHelper.php b/app/Helper/BoardHelper.php
index f5df3db2..c3d28dc4 100644
--- a/app/Helper/BoardHelper.php
+++ b/app/Helper/BoardHelper.php
@@ -24,4 +24,20 @@ class BoardHelper extends Base
{
return $this->userMetadataCacheDecorator->get(UserMetadataModel::KEY_BOARD_COLLAPSED.$project_id, 0) == 1;
}
+
+ /**
+ * Return true if the task can be moved by the connected user
+ *
+ * @param array $task
+ * @return bool
+ */
+ public function isDraggable(array $task)
+ {
+ if ($task['is_active'] == 1 && $this->helper->user->hasProjectAccess('BoardViewController', 'save', $task['project_id'])) {
+ $srcColumnIds = $this->columnMoveRestrictionCacheDecorator->getAllSrcColumns($task['project_id']);
+ return ! isset($srcColumnIds[$task['column_id']]);
+ }
+
+ return false;
+ }
}
diff --git a/app/Model/ColumnMoveRestrictionModel.php b/app/Model/ColumnMoveRestrictionModel.php
index fa44edd1..63e739bf 100644
--- a/app/Model/ColumnMoveRestrictionModel.php
+++ b/app/Model/ColumnMoveRestrictionModel.php
@@ -42,7 +42,8 @@ class ColumnMoveRestrictionModel extends Base
*/
public function getAll($project_id)
{
- return $this->db->table(self::TABLE)
+ return $this->db
+ ->table(self::TABLE)
->columns(
'restriction_id',
'src_column_id',
@@ -59,6 +60,20 @@ class ColumnMoveRestrictionModel extends Base
}
/**
+ * Get all source column Ids
+ *
+ * @param int $project_id
+ * @return array
+ */
+ public function getAllSrcColumns($project_id)
+ {
+ return $this->db
+ ->hashtable(self::TABLE)
+ ->eq(self::TABLE.'.project_id', $project_id)
+ ->getAll('src_column_id', 'src_column_id');
+ }
+
+ /**
* Create a new column restriction
*
* @param int $project_id
diff --git a/app/ServiceProvider/CacheProvider.php b/app/ServiceProvider/CacheProvider.php
index fac44d53..90d63f81 100644
--- a/app/ServiceProvider/CacheProvider.php
+++ b/app/ServiceProvider/CacheProvider.php
@@ -4,6 +4,7 @@ namespace Kanboard\ServiceProvider;
use Kanboard\Core\Cache\FileCache;
use Kanboard\Core\Cache\MemoryCache;
+use Kanboard\Decorator\ColumnMoveRestrictionCacheDecorator;
use Kanboard\Decorator\MetadataCacheDecorator;
use Pimple\Container;
use Pimple\ServiceProviderInterface;
@@ -46,6 +47,13 @@ class CacheProvider implements ServiceProviderInterface
);
};
+ $container['columnMoveRestrictionCacheDecorator'] = function($c) {
+ return new ColumnMoveRestrictionCacheDecorator(
+ $c['memoryCache'],
+ $c['columnMoveRestrictionModel']
+ );
+ };
+
return $container;
}
}
diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php
index d837500a..e79ffcee 100644
--- a/app/ServiceProvider/ClassProvider.php
+++ b/app/ServiceProvider/ClassProvider.php
@@ -34,6 +34,7 @@ class ClassProvider implements ServiceProviderInterface
'CategoryModel',
'ColorModel',
'ColumnModel',
+ 'ColumnMoveRestrictionModel',
'CommentModel',
'ConfigModel',
'CurrencyModel',
@@ -55,6 +56,7 @@ class ClassProvider implements ServiceProviderInterface
'ProjectNotificationModel',
'ProjectMetadataModel',
'ProjectGroupRoleModel',
+ 'ProjectRoleModel',
'ProjectTaskDuplicationModel',
'ProjectTaskPriorityModel',
'ProjectUserRoleModel',
diff --git a/app/Template/board/task_private.php b/app/Template/board/task_private.php
index 94b396a6..01da46db 100644
--- a/app/Template/board/task_private.php
+++ b/app/Template/board/task_private.php
@@ -1,6 +1,7 @@
<div class="
task-board
- <?= $task['is_active'] == 1 ? ($this->user->hasProjectAccess('BoardViewController', 'save', $task['project_id']) ? 'draggable-item ' : '').'task-board-status-open '.($task['date_modification'] > (time() - $board_highlight_period) ? 'task-board-recent' : '') : 'task-board-status-closed' ?>
+ <?= $task['is_draggable'] ? 'draggable-item ' : '' ?>
+ <?= $task['is_active'] == 1 ? 'task-board-status-open '.($task['date_modification'] > (time() - $board_highlight_period) ? 'task-board-recent' : '') : 'task-board-status-closed' ?>
color-<?= $task['color_id'] ?>"
data-task-id="<?= $task['id'] ?>"
data-column-id="<?= $task['column_id'] ?>"
diff --git a/assets/js/app.min.js b/assets/js/app.min.js
index d6f3ca2d..31ec96b7 100644
--- a/assets/js/app.min.js
+++ b/assets/js/app.min.js
@@ -1,2 +1,2 @@
Vue.component("chart-project-task-distribution",{props:["metrics"],template:'<div id="chart"></div>',ready:function(){for(var t=[],e=0;e<this.metrics.length;e++)t.push([this.metrics[e].column_title,this.metrics[e].nb_tasks]);c3.generate({data:{columns:t,type:"donut"}})}}),Vue.component("chart-project-time-comparison",{props:["metrics","labelSpent","labelEstimated","labelClosed","labelOpen"],template:'<div id="chart"></div>',ready:function(){var t=[this.labelSpent],e=[this.labelEstimated],o=[];for(var a in this.metrics)t.push(this.metrics[a].time_spent),e.push(this.metrics[a].time_estimated),o.push("open"===a?this.labelOpen:this.labelClosed);c3.generate({data:{columns:[t,e],type:"bar"},bar:{width:{ratio:.2}},axis:{x:{type:"category",categories:o}},legend:{show:!0}})}}),Vue.component("chart-project-user-distribution",{props:["metrics"],template:'<div id="chart"></div>',ready:function(){for(var t=[],e=0;e<this.metrics.length;e++)t.push([this.metrics[e].user,this.metrics[e].nb_tasks]);c3.generate({data:{columns:t,type:"donut"}})}}),Vue.component("submit-cancel",{props:["labelButton","labelOr","labelCancel","callback"],template:'<div class="form-actions"><button type="button" class="btn btn-blue" @click="onSubmit" :disabled="isLoading"><span v-show="isLoading"><i class="fa fa-spinner fa-pulse"></i> </span>{{ labelButton }}</button> {{ labelOr }} <a href="#" v-on:click.prevent="onCancel">{{ labelCancel }}</a></div>',data:function(){return{loading:!1}},computed:{isLoading:function(){return this.loading}},methods:{onSubmit:function(){this.loading=!0,this.callback()},onCancel:function(){_KB.get("Popover").close()}}}),Vue.component("task-move-position",{props:["board","saveUrl"],template:"#template-task-move-position",data:function(){return{swimlaneId:0,columnId:0,position:1,columns:[],tasks:[],positionChoice:"before"}},ready:function(){this.columns=this.board[0].columns,this.columnId=this.columns[0].id,this.tasks=this.columns[0].tasks},methods:{onChangeSwimlane:function(){var t=this;this.columnId=0,this.position=1,this.columns=[],this.tasks=[],this.positionChoice="before",this.board.forEach(function(e){e.id===t.swimlaneId&&(t.columns=e.columns,t.tasks=t.columns[0].tasks,t.columnId=t.columns[0].id)})},onChangeColumn:function(){var t=this;this.position=1,this.tasks=[],this.positionChoice="before",this.columns.forEach(function(e){e.id==t.columnId&&(t.tasks=e.tasks)})},onSubmit:function(){"after"==this.positionChoice&&this.position++,$.ajax({cache:!1,url:this.saveUrl,contentType:"application/json",type:"POST",processData:!1,data:JSON.stringify({column_id:this.columnId,swimlane_id:this.swimlaneId,position:this.position}),complete:function(){window.location.reload(!0)}})}}});var Kanboard={};Kanboard.Accordion=function(t){this.app=t},Kanboard.Accordion.prototype.listen=function(){$(document).on("click",".accordion-toggle",function(t){var e=$(this).parents(".accordion-section");t.preventDefault(),e.hasClass("accordion-collapsed")?(e.find(".accordion-content").show(),e.removeClass("accordion-collapsed")):(e.find(".accordion-content").hide(),e.addClass("accordion-collapsed"))})},Kanboard.App=function(){this.controllers={}},Kanboard.App.prototype.get=function(t){return this.controllers[t]},Kanboard.App.prototype.execute=function(){for(var t in Kanboard)if("App"!==t){var e=new Kanboard[t](this);this.controllers[t]=e,"function"==typeof e.execute&&e.execute(),"function"==typeof e.listen&&e.listen(),"function"==typeof e.focus&&e.focus(),"function"==typeof e.keyboardShortcuts&&e.keyboardShortcuts()}this.focus(),this.chosen(),this.keyboardShortcuts(),this.datePicker(),this.autoComplete(),this.tagAutoComplete(),new Vue({el:"body"})},Kanboard.App.prototype.keyboardShortcuts=function(){var t=this;Mousetrap.bindGlobal("mod+enter",function(){var e=$("form");1==e.length?e.submit():e.length>1&&("INPUT"===document.activeElement.tagName||"TEXTAREA"===document.activeElement.tagName?$(document.activeElement).parents("form").submit():t.get("Popover").isOpen()&&$("#popover-container form").submit())}),Mousetrap.bind("b",function(t){t.preventDefault(),$("#board-selector").trigger("chosen:open")}),Mousetrap.bindGlobal("esc",function(){t.get("Popover").close(),t.get("Dropdown").close()}),Mousetrap.bind("?",function(){t.get("Popover").open($("body").data("keyboard-shortcut-url"))})},Kanboard.App.prototype.focus=function(){$(document).on("focus",".auto-select",function(){$(this).select()}),$(document).on("mouseup",".auto-select",function(t){t.preventDefault()})},Kanboard.App.prototype.chosen=function(){$(".chosen-select").each(function(){var t=$(this).data("search-threshold");void 0===t&&(t=10),$(this).chosen({width:"180px",no_results_text:$(this).data("notfound"),disable_search_threshold:t})}),$(".select-auto-redirect").change(function(){var t=new RegExp($(this).data("redirect-regex"),"g");window.location=$(this).data("redirect-url").replace(t,$(this).val())})},Kanboard.App.prototype.datePicker=function(){var t=$("body"),e=t.data("js-date-format"),o=t.data("js-time-format"),a=t.data("js-lang");$.datepicker.setDefaults($.datepicker.regional[a]),$.timepicker.setDefaults($.timepicker.regional[a]),$(".form-date").datepicker({showOtherMonths:!0,selectOtherMonths:!0,dateFormat:e,constrainInput:!1}),$(".form-datetime").datetimepicker({dateFormat:e,timeFormat:o,constrainInput:!1})},Kanboard.App.prototype.tagAutoComplete=function(){$(".tag-autocomplete").select2({tags:!0})},Kanboard.App.prototype.autoComplete=function(){$(".autocomplete").each(function(){var t=$(this),e=t.data("dst-field"),o=t.data("dst-extra-field");""==$("#form-"+e).val()&&t.parent().find("button[type=submit]").attr("disabled","disabled"),t.autocomplete({source:t.data("search-url"),minLength:1,select:function(a,n){$("input[name="+e+"]").val(n.item.id),o&&$("input[name="+o+"]").val(n.item[o]),t.parent().find("button[type=submit]").removeAttr("disabled")}})})},Kanboard.App.prototype.hasId=function(t){return!!document.getElementById(t)},Kanboard.App.prototype.showLoadingIcon=function(){$("body").append('<span id="app-loading-icon">&nbsp;<i class="fa fa-spinner fa-spin"></i></span>')},Kanboard.App.prototype.hideLoadingIcon=function(){$("#app-loading-icon").remove()},Kanboard.App.prototype.formatDuration=function(t){return t>=86400?Math.round(t/86400)+"d":t>=3600?Math.round(t/3600)+"h":t>=60?Math.round(t/60)+"m":t+"s"},Kanboard.App.prototype.isVisible=function(){var t="";return"undefined"!=typeof document.hidden?t="visibilityState":"undefined"!=typeof document.mozHidden?t="mozVisibilityState":"undefined"!=typeof document.msHidden?t="msVisibilityState":"undefined"!=typeof document.webkitHidden&&(t="webkitVisibilityState"),""==t||"visible"==document[t]},Kanboard.AvgTimeColumnChart=function(t){this.app=t},Kanboard.AvgTimeColumnChart.prototype.execute=function(){this.app.hasId("analytic-avg-time-column")&&this.show()},Kanboard.AvgTimeColumnChart.prototype.show=function(){var t=$("#chart"),e=t.data("metrics"),o=[t.data("label")],a=[];for(var n in e)o.push(e[n].average),a.push(e[n].title);c3.generate({data:{columns:[o],type:"bar"},bar:{width:{ratio:.5}},axis:{x:{type:"category",categories:a},y:{tick:{format:this.app.formatDuration}}},legend:{show:!1}})},Kanboard.BoardCollapsedMode=function(t){this.app=t},Kanboard.BoardCollapsedMode.prototype.keyboardShortcuts=function(){var t=this;t.app.hasId("board")&&Mousetrap.bind("s",function(){t.toggle()})},Kanboard.BoardCollapsedMode.prototype.toggle=function(){var t=this;this.app.showLoadingIcon(),$.ajax({cache:!1,url:$('.filter-display-mode:not([style="display: none;"]) a').attr("href"),success:function(e){$(".filter-display-mode").toggle(),t.app.get("BoardDragAndDrop").refresh(e)}})},Kanboard.BoardColumnView=function(t){this.app=t},Kanboard.BoardColumnView.prototype.execute=function(){this.app.hasId("board")&&this.render()},Kanboard.BoardColumnView.prototype.listen=function(){var t=this;$(document).on("click",".board-toggle-column-view",function(){t.toggle($(this).data("column-id"))})},Kanboard.BoardColumnView.prototype.onBoardRendered=function(){this.render()},Kanboard.BoardColumnView.prototype.render=function(){var t=this;$(".board-column-header").each(function(){var e=$(this).data("column-id");localStorage.getItem("hidden_column_"+e)&&t.hideColumn(e)})},Kanboard.BoardColumnView.prototype.toggle=function(t){localStorage.getItem("hidden_column_"+t)?this.showColumn(t):this.hideColumn(t)},Kanboard.BoardColumnView.prototype.hideColumn=function(t){$(".board-column-"+t+" .board-column-expanded").hide(),$(".board-column-"+t+" .board-column-collapsed").show(),$(".board-column-header-"+t+" .board-column-expanded").hide(),$(".board-column-header-"+t+" .board-column-collapsed").show(),$(".board-column-header-"+t).each(function(){$(this).removeClass("board-column-compact"),$(this).addClass("board-column-header-collapsed")}),$(".board-column-"+t).each(function(){$(this).addClass("board-column-task-collapsed")}),$(".board-column-"+t+" .board-rotation").each(function(){$(this).css("width",$(".board-column-"+t).height())}),localStorage.setItem("hidden_column_"+t,1)},Kanboard.BoardColumnView.prototype.showColumn=function(t){$(".board-column-"+t+" .board-column-expanded").show(),$(".board-column-"+t+" .board-column-collapsed").hide(),$(".board-column-header-"+t+" .board-column-expanded").show(),$(".board-column-header-"+t+" .board-column-collapsed").hide(),$(".board-column-header-"+t).removeClass("board-column-header-collapsed"),$(".board-column-"+t).removeClass("board-column-task-collapsed"),0==localStorage.getItem("horizontal_scroll")&&$(".board-column-header-"+t).addClass("board-column-compact"),localStorage.removeItem("hidden_column_"+t)},Kanboard.BoardHorizontalScrolling=function(t){this.app=t},Kanboard.BoardHorizontalScrolling.prototype.execute=function(){this.app.hasId("board")&&this.render()},Kanboard.BoardHorizontalScrolling.prototype.listen=function(){var t=this;$(document).on("click",".filter-toggle-scrolling",function(e){e.preventDefault(),t.toggle()})},Kanboard.BoardHorizontalScrolling.prototype.keyboardShortcuts=function(){var t=this;t.app.hasId("board")&&Mousetrap.bind("c",function(){t.toggle()})},Kanboard.BoardHorizontalScrolling.prototype.onBoardRendered=function(){this.render()},Kanboard.BoardHorizontalScrolling.prototype.toggle=function(){var t=localStorage.getItem("horizontal_scroll")||1;localStorage.setItem("horizontal_scroll",0==t?1:0),this.render()},Kanboard.BoardHorizontalScrolling.prototype.render=function(){0==localStorage.getItem("horizontal_scroll")?($(".filter-wide").show(),$(".filter-compact").hide(),$("#board-container").addClass("board-container-compact"),$("#board th:not(.board-column-header-collapsed)").addClass("board-column-compact")):($(".filter-wide").hide(),$(".filter-compact").show(),$("#board-container").removeClass("board-container-compact"),$("#board th").removeClass("board-column-compact"))},Kanboard.BoardPolling=function(t){this.app=t},Kanboard.BoardPolling.prototype.execute=function(){if(this.app.hasId("board")){var t=parseInt($("#board").attr("data-check-interval"));t>0&&window.setInterval(this.check.bind(this),1e3*t)}},Kanboard.BoardPolling.prototype.check=function(){if(this.app.isVisible()&&!this.app.get("BoardDragAndDrop").savingInProgress){var t=this;this.app.showLoadingIcon(),$.ajax({cache:!1,url:$("#board").data("check-url"),statusCode:{200:function(e){t.app.get("BoardDragAndDrop").refresh(e)},304:function(){t.app.hideLoadingIcon()}}})}},Kanboard.BoardTask=function(t){this.app=t},Kanboard.BoardTask.prototype.listen=function(){var t=this;$(document).on("click",".task-board-change-assignee",function(e){e.preventDefault(),e.stopPropagation(),t.app.get("Popover").open($(this).data("url"))}),$(document).on("click",".task-board",function(t){"A"!=t.target.tagName&&"IMG"!=t.target.tagName&&(window.location=$(this).data("task-url"))})},Kanboard.BoardTask.prototype.keyboardShortcuts=function(){var t=this;t.app.hasId("board")&&Mousetrap.bind("n",function(){t.app.get("Popover").open($("#board").data("task-creation-url"))})},Kanboard.BurndownChart=function(t){this.app=t},Kanboard.BurndownChart.prototype.execute=function(){this.app.hasId("analytic-burndown")&&this.show()},Kanboard.BurndownChart.prototype.show=function(){for(var t=$("#chart"),e=t.data("metrics"),o=[[t.data("label-total")]],a=[],n=d3.time.format("%Y-%m-%d"),i=d3.time.format(t.data("date-format")),r=0;r<e.length;r++)for(var s=0;s<e[r].length;s++)0==r?o.push([e[r][s]]):(o[s+1].push(e[r][s]),s>0&&(void 0==o[0][r]&&o[0].push(0),o[0][r]+=e[r][s]),0==s&&a.push(i(n.parse(e[r][s]))));c3.generate({data:{columns:o},axis:{x:{type:"category",categories:a}}})},Kanboard.Calendar=function(t){this.app=t},Kanboard.Calendar.prototype.execute=function(){var t=$("#calendar");1==t.length&&this.show(t)},Kanboard.Calendar.prototype.show=function(t){t.fullCalendar({lang:$("body").data("js-lang"),editable:!0,eventLimit:!0,defaultView:"month",header:{left:"prev,next today",center:"title",right:"month,agendaWeek,agendaDay"},eventDrop:function(e){$.ajax({cache:!1,url:t.data("save-url"),contentType:"application/json",type:"POST",processData:!1,data:JSON.stringify({task_id:e.id,date_due:e.start.format()})})},viewRender:function(){var e=t.data("check-url"),o={start:t.fullCalendar("getView").start.format(),end:t.fullCalendar("getView").end.format()};for(var a in o)e+="&"+a+"="+o[a];$.getJSON(e,function(e){t.fullCalendar("removeEvents"),t.fullCalendar("addEventSource",e),t.fullCalendar("rerenderEvents")})}})},Kanboard.Column=function(t){this.app=t},Kanboard.Column.prototype.listen=function(){this.dragAndDrop()},Kanboard.Column.prototype.dragAndDrop=function(){var t=this;$(".draggable-row-handle").mouseenter(function(){$(this).parent().parent().addClass("draggable-item-hover")}).mouseleave(function(){$(this).parent().parent().removeClass("draggable-item-hover")}),$(".columns-table tbody").sortable({forcePlaceholderSize:!0,handle:"td:first i",helper:function(t,e){return e.children().each(function(){$(this).width($(this).width())}),e},stop:function(e,o){var a=o.item;a.removeClass("draggable-item-selected"),t.savePosition(a.data("column-id"),a.index()+1)},start:function(t,e){e.item.addClass("draggable-item-selected")}}).disableSelection()},Kanboard.Column.prototype.savePosition=function(t,e){var o=$(".columns-table").data("save-position-url"),a=this;this.app.showLoadingIcon(),$.ajax({cache:!1,url:o,contentType:"application/json",type:"POST",processData:!1,data:JSON.stringify({column_id:t,position:e}),complete:function(){a.app.hideLoadingIcon()}})},Kanboard.CumulativeFlowDiagram=function(t){this.app=t},Kanboard.CumulativeFlowDiagram.prototype.execute=function(){this.app.hasId("analytic-cfd")&&this.show()},Kanboard.CumulativeFlowDiagram.prototype.show=function(){for(var t=$("#chart"),e=t.data("metrics"),o=[],a=[],n=[],i=d3.time.format("%Y-%m-%d"),r=d3.time.format(t.data("date-format")),s=0;s<e.length;s++)for(var d=0;d<e[s].length;d++)0==s?(o.push([e[s][d]]),d>0&&a.push(e[s][d])):(o[d].push(e[s][d]),0==d&&n.push(r(i.parse(e[s][d]))));c3.generate({data:{columns:o,type:"area-spline",groups:[a]},axis:{x:{type:"category",categories:n}}})},Kanboard.Dropdown=function(t){this.app=t},Kanboard.Dropdown.prototype.listen=function(){var t=this;$(document).on("click",function(){t.close()}),$(document).on("click",".dropdown-menu",function(e){e.preventDefault(),e.stopImmediatePropagation(),t.close();var o=$(this).next("ul"),a=$(this).offset();$("body").append(jQuery("<div>",{id:"dropdown"})),o.clone().appendTo("#dropdown");var n=$("#dropdown ul");n.addClass("dropdown-submenu-open");var i=n.outerHeight(),r=n.outerWidth();a.top+i-$(window).scrollTop()<$(window).height()||$(window).scrollTop()+a.top<i?n.css("top",a.top+$(this).height()):n.css("top",a.top-i-5),a.left+r>$(window).width()?n.css("left",a.left-r+$(this).outerWidth()):n.css("left",a.left)}),$(document).on("click",".dropdown-submenu-open li",function(t){$(t.target).is("li")&&$(this).find("a:visible")[0].click()})},Kanboard.Dropdown.prototype.close=function(){$("#dropdown").remove()},Kanboard.Dropdown.prototype.onPopoverOpened=function(){this.close()},Kanboard.FileUpload=function(t){this.app=t,this.files=[],this.currentFile=0},Kanboard.FileUpload.prototype.onPopoverOpened=function(){var t=document.getElementById("file-dropzone"),e=this;t&&(t.ondragover=t.ondragenter=function(t){t.stopPropagation(),t.preventDefault()},t.ondrop=function(t){t.stopPropagation(),t.preventDefault(),e.files=t.dataTransfer.files,e.show(),$("#file-error-max-size").hide()},$(document).on("click","#file-browser",function(t){t.preventDefault(),$("#file-form-element").get(0).click()}),$(document).on("click","#file-upload-button",function(t){t.preventDefault(),e.currentFile=0,e.checkFiles()}),$("#file-form-element").change(function(){e.files=document.getElementById("file-form-element").files,e.show(),$("#file-error-max-size").hide()}))},Kanboard.FileUpload.prototype.show=function(){if($("#file-list").remove(),this.files.length>0){$("#file-upload-button").prop("disabled",!1),$("#file-dropzone-inner").hide();for(var t=jQuery("<ul>",{id:"file-list"}),e=0;e<this.files.length;e++){var o=jQuery("<span>",{id:"file-percentage-"+e}).append("(0%)"),a=jQuery("<progress>",{id:"file-progress-"+e,value:0}),n=jQuery("<li>",{id:"file-label-"+e}).append(a).append("&nbsp;").append(this.files[e].name).append("&nbsp;").append(o);t.append(n)}$("#file-dropzone").append(t)}else $("#file-dropzone-inner").show()},Kanboard.FileUpload.prototype.checkFiles=function(){for(var t=parseInt($("#file-dropzone").data("max-size")),e=0;e<this.files.length;e++)if(this.files[e].size>t)return $("#file-error-max-size").show(),$("#file-label-"+e).addClass("file-error"),void $("#file-upload-button").prop("disabled",!0);this.uploadFiles()},Kanboard.FileUpload.prototype.uploadFiles=function(){this.files.length>0&&this.uploadFile(this.files[this.currentFile])},Kanboard.FileUpload.prototype.uploadFile=function(t){var e=document.getElementById("file-dropzone"),o=e.dataset.url,a=new XMLHttpRequest,n=new FormData;a.upload.addEventListener("progress",this.updateProgress.bind(this)),a.upload.addEventListener("load",this.transferComplete.bind(this)),a.open("POST",o,!0),n.append("files[]",t),a.send(n)},Kanboard.FileUpload.prototype.updateProgress=function(t){t.lengthComputable&&($("#file-progress-"+this.currentFile).val(t.loaded/t.total),$("#file-percentage-"+this.currentFile).text("("+Math.floor(t.loaded/t.total*100)+"%)"))},Kanboard.FileUpload.prototype.transferComplete=function(){if(this.currentFile++,this.currentFile<this.files.length)this.uploadFile(this.files[this.currentFile]);else{var t=$("#file-upload-button");t.prop("disabled",!0),t.parent().hide(),$("#file-done").show()}},Kanboard.Gantt=function(t){this.app=t,this.data=[],this.options={container:"#gantt-chart",showWeekends:!0,allowMoves:!0,allowResizes:!0,cellWidth:21,cellHeight:31,slideWidth:1e3,vHeaderWidth:200}},Kanboard.Gantt.prototype.execute=function(){this.app.hasId("gantt-chart")&&this.show()},Kanboard.Gantt.prototype.saveRecord=function(t){this.app.showLoadingIcon(),$.ajax({cache:!1,url:$(this.options.container).data("save-url"),contentType:"application/json",type:"POST",processData:!1,data:JSON.stringify(t),complete:this.app.hideLoadingIcon.bind(this)})},Kanboard.Gantt.prototype.show=function(){this.data=this.prepareData($(this.options.container).data("records"));var t=Math.floor(this.options.slideWidth/this.options.cellWidth+5),e=this.getDateRange(t),o=e[0],a=e[1],n=$(this.options.container),i=jQuery("<div>",{"class":"ganttview"});i.append(this.renderVerticalHeader()),i.append(this.renderSlider(o,a)),n.append(i),jQuery("div.ganttview-grid-row div.ganttview-grid-row-cell:last-child",n).addClass("last"),jQuery("div.ganttview-hzheader-days div.ganttview-hzheader-day:last-child",n).addClass("last"),jQuery("div.ganttview-hzheader-months div.ganttview-hzheader-month:last-child",n).addClass("last"),$(this.options.container).data("readonly")?(this.options.allowResizes=!1,this.options.allowMoves=!1):(this.listenForBlockResize(o),this.listenForBlockMove(o))},Kanboard.Gantt.prototype.renderVerticalHeader=function(){for(var t=jQuery("<div>",{"class":"ganttview-vtheader"}),e=jQuery("<div>",{"class":"ganttview-vtheader-item"}),o=jQuery("<div>",{"class":"ganttview-vtheader-series"}),a=0;a<this.data.length;a++){var n=jQuery("<span>").append(jQuery("<i>",{"class":"fa fa-info-circle tooltip",title:this.getVerticalHeaderTooltip(this.data[a])})).append("&nbsp;");"task"==this.data[a].type?n.append(jQuery("<a>",{href:this.data[a].link,title:this.data[a].title}).append(this.data[a].title)):n.append(jQuery("<a>",{href:this.data[a].board_link,title:$(this.options.container).data("label-board-link")}).append('<i class="fa fa-th"></i>')).append("&nbsp;").append(jQuery("<a>",{href:this.data[a].gantt_link,title:$(this.options.container).data("label-gantt-link")}).append('<i class="fa fa-sliders"></i>')).append("&nbsp;").append(jQuery("<a>",{href:this.data[a].link}).append(this.data[a].title)),o.append(jQuery("<div>",{"class":"ganttview-vtheader-series-name"}).append(n))}return e.append(o),t.append(e),t},Kanboard.Gantt.prototype.renderSlider=function(t,e){var o=jQuery("<div>",{"class":"ganttview-slide-container"}),a=this.getDates(t,e);return o.append(this.renderHorizontalHeader(a)),o.append(this.renderGrid(a)),o.append(this.addBlockContainers()),this.addBlocks(o,t),o},Kanboard.Gantt.prototype.renderHorizontalHeader=function(t){var e=jQuery("<div>",{"class":"ganttview-hzheader"}),o=jQuery("<div>",{"class":"ganttview-hzheader-months"}),a=jQuery("<div>",{"class":"ganttview-hzheader-days"}),n=0;for(var i in t)for(var r in t[i]){var s=t[i][r].length*this.options.cellWidth;n+=s,o.append(jQuery("<div>",{"class":"ganttview-hzheader-month",css:{width:s-1+"px"}}).append($.datepicker.regional[$("body").data("js-lang")].monthNames[r]+" "+i));for(var d in t[i][r])a.append(jQuery("<div>",{"class":"ganttview-hzheader-day"}).append(t[i][r][d].getDate()))}return o.css("width",n+"px"),a.css("width",n+"px"),e.append(o).append(a),e},Kanboard.Gantt.prototype.renderGrid=function(t){var e=jQuery("<div>",{"class":"ganttview-grid"}),o=jQuery("<div>",{"class":"ganttview-grid-row"});for(var a in t)for(var n in t[a])for(var i in t[a][n]){var r=jQuery("<div>",{"class":"ganttview-grid-row-cell"});this.options.showWeekends&&this.isWeekend(t[a][n][i])&&r.addClass("ganttview-weekend"),o.append(r)}var s=jQuery("div.ganttview-grid-row-cell",o).length*this.options.cellWidth;o.css("width",s+"px"),e.css("width",s+"px");for(var d=0;d<this.data.length;d++)e.append(o.clone());return e},Kanboard.Gantt.prototype.addBlockContainers=function(){for(var t=jQuery("<div>",{"class":"ganttview-blocks"}),e=0;e<this.data.length;e++)t.append(jQuery("<div>",{"class":"ganttview-block-container"}));return t},Kanboard.Gantt.prototype.addBlocks=function(t,e){for(var o=jQuery("div.ganttview-blocks div.ganttview-block-container",t),a=0,n=0;n<this.data.length;n++){var i=this.data[n],r=this.daysBetween(i.start,i.end)+1,s=this.daysBetween(e,i.start),d=jQuery("<div>",{"class":"ganttview-block-text"}),l=jQuery("<div>",{"class":"ganttview-block tooltip"+(this.options.allowMoves?" ganttview-block-movable":""),title:this.getBarTooltip(i),css:{width:r*this.options.cellWidth-9+"px","margin-left":s*this.options.cellWidth+"px"}}).append(d);r>=2&&d.append(i.progress),l.data("record",i),this.setBarColor(l,i),jQuery(o[a]).append(l),a+=1}},Kanboard.Gantt.prototype.getVerticalHeaderTooltip=function(t){var e="";if("task"==t.type)e="<strong>"+t.column_title+"</strong> ("+t.progress+")<br/>"+t.title;else{var o=["project-manager","project-member"];for(var a in o){var n=o[a];if(!jQuery.isEmptyObject(t.users[n])){var i=jQuery("<ul>");for(var r in t.users[n])r&&i.append(jQuery("<li>").append(t.users[n][r]));e+="<p><strong>"+$(this.options.container).data("label-"+n)+"</strong></p>"+i[0].outerHTML}}}return e},Kanboard.Gantt.prototype.getBarTooltip=function(t){var e="";return t.not_defined?e=$(this.options.container).data("label-not-defined"):("task"==t.type&&(e="<strong>"+t.progress+"</strong><br/>"+$(this.options.container).data("label-assignee")+" "+(t.assignee?t.assignee:"")+"<br/>"),e+=$(this.options.container).data("label-start-date")+" "+$.datepicker.formatDate("yy-mm-dd",t.start)+"<br/>",e+=$(this.options.container).data("label-end-date")+" "+$.datepicker.formatDate("yy-mm-dd",t.end)),e},Kanboard.Gantt.prototype.setBarColor=function(t,e){e.not_defined?t.addClass("ganttview-block-not-defined"):(t.css("background-color",e.color.background),t.css("border-color",e.color.border),"0%"!=e.progress&&t.append(jQuery("<div>",{css:{"z-index":0,position:"absolute",top:0,bottom:0,"background-color":e.color.border,width:e.progress,opacity:.4}})))},Kanboard.Gantt.prototype.listenForBlockResize=function(t){var e=this;jQuery("div.ganttview-block",this.options.container).resizable({grid:this.options.cellWidth,handles:"e,w",delay:300,stop:function(){var o=jQuery(this);e.updateDataAndPosition(o,t),e.saveRecord(o.data("record"))}})},Kanboard.Gantt.prototype.listenForBlockMove=function(t){var e=this;jQuery("div.ganttview-block",this.options.container).draggable({axis:"x",delay:300,grid:[this.options.cellWidth,this.options.cellWidth],stop:function(){var o=jQuery(this);e.updateDataAndPosition(o,t),e.saveRecord(o.data("record"))}})},Kanboard.Gantt.prototype.updateDataAndPosition=function(t,e){var o=jQuery("div.ganttview-slide-container",this.options.container),a=o.scrollLeft(),n=t.offset().left-o.offset().left-1+a,i=t.data("record");i.not_defined=!1,this.setBarColor(t,i);var r=Math.round(n/this.options.cellWidth),s=this.addDays(this.cloneDate(e),r);i.start=s;var d=t.outerWidth(),l=Math.round(d/this.options.cellWidth)-1;i.end=this.addDays(this.cloneDate(s),l),"task"===i.type&&l>0&&jQuery("div.ganttview-block-text",t).text(i.progress),t.attr("title",this.getBarTooltip(i)),t.data("record",i),t.css("top","").css("left","").css("position","relative").css("margin-left",n+"px")},Kanboard.Gantt.prototype.getDates=function(t,e){var o=[];o[t.getFullYear()]=[],o[t.getFullYear()][t.getMonth()]=[t];for(var a=t;this.compareDate(a,e)==-1;){var n=this.addDays(this.cloneDate(a),1);o[n.getFullYear()]||(o[n.getFullYear()]=[]),o[n.getFullYear()][n.getMonth()]||(o[n.getFullYear()][n.getMonth()]=[]),o[n.getFullYear()][n.getMonth()].push(n),a=n}return o},Kanboard.Gantt.prototype.prepareData=function(t){for(var e=0;e<t.length;e++){var o=new Date(t[e].start[0],t[e].start[1]-1,t[e].start[2],0,0,0,0);t[e].start=o;var a=new Date(t[e].end[0],t[e].end[1]-1,t[e].end[2],0,0,0,0);t[e].end=a}return t},Kanboard.Gantt.prototype.getDateRange=function(t){for(var e=new Date,o=new Date,a=0;a<this.data.length;a++){var n=new Date;n.setTime(Date.parse(this.data[a].start));var i=new Date;i.setTime(Date.parse(this.data[a].end)),0==a&&(e=n,o=i),1==this.compareDate(e,n)&&(e=n),this.compareDate(o,i)==-1&&(o=i)}return this.daysBetween(e,o)<t&&(o=this.addDays(this.cloneDate(e),t)),e.setDate(e.getDate()-1),[e,o]},Kanboard.Gantt.prototype.daysBetween=function(t,e){if(!t||!e)return 0;for(var o=0,a=this.cloneDate(t);this.compareDate(a,e)==-1;)o+=1,this.addDays(a,1);return o},Kanboard.Gantt.prototype.isWeekend=function(t){return t.getDay()%6==0},Kanboard.Gantt.prototype.cloneDate=function(t){return new Date(t.getTime())},Kanboard.Gantt.prototype.addDays=function(t,e){return t.setDate(t.getDate()+1*e),t},Kanboard.Gantt.prototype.compareDate=function(t,e){if(isNaN(t)||isNaN(e))throw new Error(t+" - "+e);if(t instanceof Date&&e instanceof Date)return t<e?-1:t>e?1:0;throw new TypeError(t+" - "+e)},Kanboard.LeadCycleTimeChart=function(t){this.app=t},Kanboard.LeadCycleTimeChart.prototype.execute=function(){this.app.hasId("analytic-lead-cycle-time")&&this.show()},Kanboard.LeadCycleTimeChart.prototype.show=function(){var t=$("#chart"),e=t.data("metrics"),o=[t.data("label-cycle")],a=[t.data("label-lead")],n=[],i={};i[t.data("label-cycle")]="area",i[t.data("label-lead")]="area-spline";var r={};r[t.data("label-lead")]="#afb42b",r[t.data("label-cycle")]="#4e342e";for(var s=0;s<e.length;s++)o.push(parseInt(e[s].avg_cycle_time)),a.push(parseInt(e[s].avg_lead_time)),n.push(e[s].day);c3.generate({data:{columns:[a,o],types:i,colors:r},axis:{x:{type:"category",categories:n},y:{tick:{format:this.app.formatDuration}}}})},Kanboard.Markdown=function(t){this.app=t,this.editor=null},Kanboard.Markdown.prototype.onPopoverOpened=function(){this.listen()},Kanboard.Markdown.prototype.onPopoverClosed=function(){this.listen()},Kanboard.Markdown.prototype.listen=function(){var t=$(".markdown-editor");this.editor&&this.destroy(),t.length>0&&this.show(t[0])},Kanboard.Markdown.prototype.destroy=function(){var t=this.editor.codemirror,e=t.getWrapperElement();for(var o in["toolbar","statusbar","sideBySide"])this.editor.gui[o]&&e.parentNode.removeChild(this.editor.gui[o]);t.toTextArea(),this.editor=null},Kanboard.Markdown.prototype.show=function(t){var e=["bold","italic","strikethrough","heading","|","unordered-list","ordered-list","link","|","code","table"];this.editor=new SimpleMDE({element:t,status:!1,toolbarTips:!1,autoDownloadFontAwesome:!1,spellChecker:!1,autosave:{enabled:!1},forceSync:!0,blockStyles:{italic:"_"},toolbar:!t.hasAttribute("data-markdown-editor-disable-toolbar")&&e,placeholder:t.getAttribute("placeholder")})},Kanboard.Notification=function(t){this.app=t},Kanboard.Notification.prototype.execute=function(){$(".alert-fade-out").delay(4e3).fadeOut(800,function(){$(this).remove()})},Kanboard.Popover=function(t){this.app=t},Kanboard.Popover.prototype.listen=function(){var t=this;$(document).on("click",".popover",function(e){t.onClick(e)}),$(document).on("click",".close-popover",function(e){t.close(e)}),$(document).on("click","#popover-container",function(e){t.close(e)}),$(document).on("click","#popover-content",function(t){t.stopPropagation()})},Kanboard.Popover.prototype.onClick=function(t){t.preventDefault(),t.stopPropagation();var e=t.currentTarget||t.target,o=e.getAttribute("href");o||(o=e.getAttribute("data-href")),o&&this.open(o)},Kanboard.Popover.prototype.isOpen=function(){return $("#popover-container").size()>0},Kanboard.Popover.prototype.open=function(t){var e=this;e.isOpen()||$.get(t,function(t){$("body").prepend('<div id="popover-container"><div id="popover-content">'+t+"</div></div>"),e.executeOnOpenedListeners()})},Kanboard.Popover.prototype.close=function(t){this.isOpen()&&(t&&t.preventDefault(),$("#popover-container").remove(),this.executeOnClosedListeners())},Kanboard.Popover.prototype.ajaxReload=function(t,e,o){var a=e.getResponseHeader("X-Ajax-Redirect");"self"===a?window.location.reload():a&&a.indexOf("#")>-1?window.location=a.split("#")[0]:a?window.location=a:($("#popover-content").html(t),$("#popover-content input[autofocus]").focus(),o.executeOnOpenedListeners())},Kanboard.Popover.prototype.executeOnOpenedListeners=function(){for(var t in this.app.controllers){var e=this.app.get(t);"function"==typeof e.onPopoverOpened&&e.onPopoverOpened()}this.afterOpen()},Kanboard.Popover.prototype.executeOnClosedListeners=function(){for(var t in this.app.controllers){var e=this.app.get(t);"function"==typeof e.onPopoverClosed&&e.onPopoverClosed()}},Kanboard.Popover.prototype.afterOpen=function(){var t=this,e=$("#popover-content .popover-form");e&&e.on("submit",function(o){o.preventDefault(),$.ajax({type:"POST",url:e.attr("action"),data:e.serialize(),success:function(e,o,a){t.ajaxReload(e,a,t)},beforeSend:function(){var t=$('.popover-form button[type="submit"]');t.html('<i class="fa fa-spinner fa-pulse"></i> '+t.html()),t.attr("disabled",!0)}})}),$(document).on("click",".popover-link",function(e){e.preventDefault(),$.ajax({type:"GET",url:$(this).attr("href"),success:function(e,o,a){t.ajaxReload(e,a,t)}})}),$("[autofocus]").each(function(){$(this).focus()}),this.app.datePicker(),this.app.autoComplete(),this.app.tagAutoComplete(),new Vue({el:"#popover-container"})},Kanboard.ProjectCreation=function(t){this.app=t},Kanboard.ProjectCreation.prototype.onPopoverOpened=function(){
-$("#project-creation-form #form-src_project_id").on("change",function(){var t=$(this).val();0==t?$(".project-creation-options").hide():$(".project-creation-options").show()})},Kanboard.ProjectPermission=function(t){this.app=t},Kanboard.ProjectPermission.prototype.listen=function(){$(".project-change-role").on("change",function(){$.ajax({cache:!1,url:$(this).data("url"),contentType:"application/json",type:"POST",processData:!1,data:JSON.stringify({id:$(this).data("id"),role:$(this).val()})})})},Kanboard.Screenshot=function(t){this.app=t,this.pasteCatcher=null},Kanboard.Screenshot.prototype.onPopoverOpened=function(){this.app.hasId("screenshot-zone")&&this.initialize()},Kanboard.Screenshot.prototype.initialize=function(){this.destroy(),window.Clipboard||(this.pasteCatcher=document.createElement("div"),this.pasteCatcher.id="screenshot-pastezone",this.pasteCatcher.contentEditable="true",this.pasteCatcher.style.opacity=0,this.pasteCatcher.style.position="fixed",this.pasteCatcher.style.top=0,this.pasteCatcher.style.right=0,this.pasteCatcher.style.width=0,document.body.insertBefore(this.pasteCatcher,document.body.firstChild),this.pasteCatcher.focus(),document.addEventListener("click",this.setFocus.bind(this)),document.getElementById("screenshot-zone").addEventListener("click",this.setFocus.bind(this))),window.addEventListener("paste",this.pasteHandler.bind(this))},Kanboard.Screenshot.prototype.destroy=function(){null!=this.pasteCatcher?document.body.removeChild(this.pasteCatcher):document.getElementById("screenshot-pastezone")&&document.body.removeChild(document.getElementById("screenshot-pastezone")),document.removeEventListener("click",this.setFocus.bind(this)),this.pasteCatcher=null},Kanboard.Screenshot.prototype.setFocus=function(){null!==this.pasteCatcher&&this.pasteCatcher.focus()},Kanboard.Screenshot.prototype.pasteHandler=function(t){if(t.clipboardData&&t.clipboardData.items){var e=t.clipboardData.items;if(e)for(var o=0;o<e.length;o++)if(e[o].type.indexOf("image")!==-1){var a=e[o].getAsFile(),n=new FileReader,i=this;n.onload=function(t){i.createImage(t.target.result)},n.readAsDataURL(a)}}else setTimeout(this.checkInput.bind(this),100)},Kanboard.Screenshot.prototype.checkInput=function(){var t=this.pasteCatcher.childNodes[0];t&&"IMG"===t.tagName&&this.createImage(t.src),this.pasteCatcher.innerHTML=""},Kanboard.Screenshot.prototype.createImage=function(t){var e=new Image;e.src=t,e.onload=function(){var e=t.split("base64,"),o=e[1];$("input[name=screenshot]").val(o)};var o=document.getElementById("screenshot-zone");o.innerHTML="",o.className="screenshot-pasted",o.appendChild(e),this.destroy(),this.initialize()},Kanboard.Search=function(t){this.app=t},Kanboard.Search.prototype.focus=function(){$(document).on("focus","#form-search",function(){var t=$("#form-search");if(t[0].setSelectionRange){var e=2*t.val().length;t[0].setSelectionRange(e,e)}})},Kanboard.Search.prototype.listen=function(){$(document).on("click",".filter-helper",function(t){t.preventDefault();var e=$(this).data("filter"),o=$(this).data("append-filter"),a=$(this).data("unique-filter"),n=$("#form-search");if(a){var i=a.substr(0,a.indexOf(":"));e=n.val().replace(new RegExp("("+i+":[#a-z0-9]+)","g"),""),e=e.replace(new RegExp("("+i+':"(.+)")',"g"),""),e=e.trim(),e+=" "+a}else o&&(e=n.val()+" "+o);n.val(e),$("form.search").submit()})},Kanboard.Search.prototype.goToView=function(t){var e=$(t);e.length&&(window.location=e.attr("href"))},Kanboard.Search.prototype.keyboardShortcuts=function(){var t=this;Mousetrap.bind("v o",function(){t.goToView(".view-overview")}),Mousetrap.bind("v b",function(){t.goToView(".view-board")}),Mousetrap.bind("v c",function(){t.goToView(".view-calendar")}),Mousetrap.bind("v l",function(){t.goToView(".view-listing")}),Mousetrap.bind("v g",function(){t.goToView(".view-gantt")}),Mousetrap.bind("f",function(t){t.preventDefault();var e=document.getElementById("form-search");e&&e.focus()}),Mousetrap.bind("r",function(t){t.preventDefault();var e=$(".filter-reset").data("filter"),o=$("#form-search");o.val(e),$("form.search").submit()})},Kanboard.Session=function(t){this.app=t},Kanboard.Session.prototype.execute=function(){window.setInterval(this.checkSession,6e4)},Kanboard.Session.prototype.checkSession=function(){$(".form-login").length||$.ajax({cache:!1,url:$("body").data("status-url"),statusCode:{401:function(){window.location=$("body").data("login-url")}}})},Kanboard.Subtask=function(t){this.app=t},Kanboard.Subtask.prototype.listen=function(){var t=this;this.dragAndDrop(),$(document).on("click",".subtask-toggle-status",function(e){var o=$(this);e.preventDefault(),$.ajax({cache:!1,url:o.attr("href"),success:function(e){o.hasClass("subtask-refresh-table")?$(".subtasks-table").replaceWith(e):o.replaceWith(e),t.dragAndDrop()}})}),$(document).on("click",".subtask-toggle-timer",function(e){var o=$(this);e.preventDefault(),$.ajax({cache:!1,url:o.attr("href"),success:function(e){$(".subtasks-table").replaceWith(e),t.dragAndDrop()}})})},Kanboard.Subtask.prototype.dragAndDrop=function(){var t=this;$(".draggable-row-handle").mouseenter(function(){$(this).parent().parent().addClass("draggable-item-hover")}).mouseleave(function(){$(this).parent().parent().removeClass("draggable-item-hover")}),$(".subtasks-table tbody").sortable({forcePlaceholderSize:!0,handle:"td:first i",helper:function(t,e){return e.children().each(function(){$(this).width($(this).width())}),e},stop:function(e,o){var a=o.item;a.removeClass("draggable-item-selected"),t.savePosition(a.data("subtask-id"),a.index()+1)},start:function(t,e){e.item.addClass("draggable-item-selected")}}).disableSelection()},Kanboard.Subtask.prototype.savePosition=function(t,e){var o=$(".subtasks-table").data("save-position-url"),a=this;this.app.showLoadingIcon(),$.ajax({cache:!1,url:o,contentType:"application/json",type:"POST",processData:!1,data:JSON.stringify({subtask_id:t,position:e}),complete:function(){a.app.hideLoadingIcon()}})},Kanboard.Swimlane=function(t){this.app=t},Kanboard.Swimlane.prototype.execute=function(){$(".swimlanes-table").length&&this.dragAndDrop()},Kanboard.Swimlane.prototype.listen=function(){var t=this;$(document).on("click",".board-swimlane-toggle",function(e){e.preventDefault();var o=$(this).data("swimlane-id");t.isCollapsed(o)?t.expand(o):t.collapse(o)})},Kanboard.Swimlane.prototype.onBoardRendered=function(){for(var t=this.getAllCollapsed(),e=0;e<t.length;e++)this.collapse(t[e])},Kanboard.Swimlane.prototype.getStorageKey=function(){return"hidden_swimlanes_"+$("#board").data("project-id")},Kanboard.Swimlane.prototype.expand=function(t){var e=this.getAllCollapsed(),o=e.indexOf(t);o>-1&&e.splice(o,1),localStorage.setItem(this.getStorageKey(),JSON.stringify(e)),$(".board-swimlane-columns-"+t).css("display","table-row"),$(".board-swimlane-tasks-"+t).css("display","table-row"),$(".hide-icon-swimlane-"+t).css("display","inline"),$(".show-icon-swimlane-"+t).css("display","none")},Kanboard.Swimlane.prototype.collapse=function(t){var e=this.getAllCollapsed();e.indexOf(t)<0&&(e.push(t),localStorage.setItem(this.getStorageKey(),JSON.stringify(e))),$(".board-swimlane-columns-"+t+":not(:first-child)").css("display","none"),$(".board-swimlane-tasks-"+t).css("display","none"),$(".hide-icon-swimlane-"+t).css("display","none"),$(".show-icon-swimlane-"+t).css("display","inline")},Kanboard.Swimlane.prototype.isCollapsed=function(t){return this.getAllCollapsed().indexOf(t)>-1},Kanboard.Swimlane.prototype.getAllCollapsed=function(){return JSON.parse(localStorage.getItem(this.getStorageKey()))||[]},Kanboard.Swimlane.prototype.dragAndDrop=function(){var t=this;$(".draggable-row-handle").mouseenter(function(){$(this).parent().parent().addClass("draggable-item-hover")}).mouseleave(function(){$(this).parent().parent().removeClass("draggable-item-hover")}),$(".swimlanes-table tbody").sortable({forcePlaceholderSize:!0,handle:"td:first i",helper:function(t,e){return e.children().each(function(){$(this).width($(this).width())}),e},stop:function(e,o){var a=o.item;a.removeClass("draggable-item-selected"),t.savePosition(a.data("swimlane-id"),a.index()+1)},start:function(t,e){e.item.addClass("draggable-item-selected")}}).disableSelection()},Kanboard.Swimlane.prototype.savePosition=function(t,e){var o=$(".swimlanes-table").data("save-position-url"),a=this;this.app.showLoadingIcon(),$.ajax({cache:!1,url:o,contentType:"application/json",type:"POST",processData:!1,data:JSON.stringify({swimlane_id:t,position:e}),complete:function(){a.app.hideLoadingIcon()}})},Kanboard.Task=function(t){this.app=t},Kanboard.Task.prototype.keyboardShortcuts=function(){var t=$("#task-view"),e=this;this.app.hasId("task-view")&&(Mousetrap.bind("e",function(){e.app.get("Popover").open(t.data("edit-url"))}),Mousetrap.bind("c",function(){e.app.get("Popover").open(t.data("comment-url"))}),Mousetrap.bind("s",function(){e.app.get("Popover").open(t.data("subtask-url"))}),Mousetrap.bind("l",function(){e.app.get("Popover").open(t.data("internal-link-url"))}))},Kanboard.Task.prototype.onPopoverOpened=function(){var t=this,e=0;t.renderColorPicker(),$(document).on("click",".assign-me",function(t){var e=$(this).data("current-id"),o="#"+$(this).data("target-id");t.preventDefault(),$(o+" option[value="+e+"]").length&&$(o).val(e)}),$(document).on("change","select.task-reload-project-destination",function(){if(e>0)$(this).val(e);else{e=$(this).val();var o=$(this).data("redirect").replace(/PROJECT_ID/g,e);$(".loading-icon").show(),$.ajax({type:"GET",url:o,success:function(o,a,n){e=0,$(".loading-icon").hide(),t.app.get("Popover").ajaxReload(o,n,t.app.get("Popover"))}})}})},Kanboard.Task.prototype.renderColorPicker=function(){function t(t){return $('<div class="color-picker-option"><div class="color-picker-square color-'+t.id+'"></div><div class="color-picker-label">'+t.text+"</div></div>")}$(".color-picker").select2({minimumResultsForSearch:1/0,templateResult:t,templateSelection:t})},Kanboard.TaskTimeColumnChart=function(t){this.app=t},Kanboard.TaskTimeColumnChart.prototype.execute=function(){this.app.hasId("analytic-task-time-column")&&this.show()},Kanboard.TaskTimeColumnChart.prototype.show=function(){for(var t=$("#chart"),e=t.data("metrics"),o=[t.data("label")],a=[],n=0;n<e.length;n++)o.push(e[n].time_spent),a.push(e[n].title);c3.generate({data:{columns:[o],type:"bar"},bar:{width:{ratio:.5}},axis:{x:{type:"category",categories:a},y:{tick:{format:this.app.formatDuration}}},legend:{show:!1}})},Kanboard.Tooltip=function(t){this.app=t},Kanboard.Tooltip.prototype.onBoardRendered=function(){this.execute()},Kanboard.Tooltip.prototype.execute=function(){$(".tooltip").tooltip({track:!1,show:!1,hide:!1,position:{my:"left-20 top",at:"center bottom+9",using:function(t,e){$(this).css(t);var o=e.target.left+e.target.width/2-e.element.left-20;$("<div>").addClass("tooltip-arrow").addClass(e.vertical).addClass(o<1?"align-left":"align-right").appendTo(this)}},content:function(){var t=this,e=$(this).attr("data-href");return e?($.get(e,function(e){var o=$(".ui-tooltip:visible");$(".ui-tooltip-content:visible").html(e),o.css({top:"",left:""}),o.children(".tooltip-arrow").remove();var a=$(t).tooltip("option","position");a.of=$(t),o.position(a)}),'<i class="fa fa-spinner fa-spin"></i>'):'<div class="markdown">'+$(this).attr("title")+"</div>"}}).on("mouseenter",function(){var t=this;$(this).tooltip("open"),$(".ui-tooltip").on("mouseleave",function(){$(t).tooltip("close")})}).on("mouseleave focusout",function(t){t.stopImmediatePropagation();var e=this;setTimeout(function(){$(".ui-tooltip:hover").length||$(e).tooltip("close")},100)})},Kanboard.BoardDragAndDrop=function(t){this.app=t,this.savingInProgress=!1},Kanboard.BoardDragAndDrop.prototype.execute=function(){this.app.hasId("board")&&(this.dragAndDrop(),this.executeListeners())},Kanboard.BoardDragAndDrop.prototype.dragAndDrop=function(){var t=this,e=$(".board-task-list"),o={forcePlaceholderSize:!0,tolerance:"pointer",connectWith:".board-task-list",placeholder:"draggable-placeholder",items:".draggable-item",stop:function(e,o){var a=o.item,n=a.attr("data-task-id"),i=a.attr("data-position"),r=a.attr("data-column-id"),s=a.attr("data-swimlane-id"),d=a.parent().attr("data-column-id"),l=a.parent().attr("data-swimlane-id"),p=a.index()+1;a.removeClass("draggable-item-selected"),d==r&&l==s&&p==i||(t.changeTaskState(n),t.save(n,d,p,l))},start:function(t,e){e.item.addClass("draggable-item-selected"),e.placeholder.height(e.item.height())}};isMobile.any&&($(".task-board-sort-handle").css("display","inline"),o.handle=".task-board-sort-handle"),e.each(function(){$(this).css("min-height",$(this).parent().height())}),e.sortable(o)},Kanboard.BoardDragAndDrop.prototype.changeTaskState=function(t){var e=$("div[data-task-id="+t+"]");e.addClass("task-board-saving-state"),e.find(".task-board-saving-icon").show()},Kanboard.BoardDragAndDrop.prototype.save=function(t,e,o,a){var n=this;n.app.showLoadingIcon(),n.savingInProgress=!0,$.ajax({cache:!1,url:$("#board").data("save-url"),contentType:"application/json",type:"POST",processData:!1,data:JSON.stringify({task_id:t,column_id:e,swimlane_id:a,position:o}),success:function(t){n.refresh(t),n.savingInProgress=!1},error:function(){n.app.hideLoadingIcon(),n.savingInProgress=!1}})},Kanboard.BoardDragAndDrop.prototype.refresh=function(t){$("#board-container").replaceWith(t),this.app.hideLoadingIcon(),this.dragAndDrop(),this.executeListeners()},Kanboard.BoardDragAndDrop.prototype.executeListeners=function(){for(var t in this.app.controllers){var e=this.app.get(t);"function"==typeof e.onBoardRendered&&e.onBoardRendered()}};var _KB=null;jQuery(document).ready(function(){_KB=new Kanboard.App,_KB.execute()}); \ No newline at end of file
+$("#project-creation-form #form-src_project_id").on("change",function(){var t=$(this).val();0==t?$(".project-creation-options").hide():$(".project-creation-options").show()})},Kanboard.ProjectPermission=function(t){this.app=t},Kanboard.ProjectPermission.prototype.listen=function(){$(".project-change-role").on("change",function(){$.ajax({cache:!1,url:$(this).data("url"),contentType:"application/json",type:"POST",processData:!1,data:JSON.stringify({id:$(this).data("id"),role:$(this).val()})})})},Kanboard.Screenshot=function(t){this.app=t,this.pasteCatcher=null},Kanboard.Screenshot.prototype.onPopoverOpened=function(){this.app.hasId("screenshot-zone")&&this.initialize()},Kanboard.Screenshot.prototype.initialize=function(){this.destroy(),window.Clipboard||(this.pasteCatcher=document.createElement("div"),this.pasteCatcher.id="screenshot-pastezone",this.pasteCatcher.contentEditable="true",this.pasteCatcher.style.opacity=0,this.pasteCatcher.style.position="fixed",this.pasteCatcher.style.top=0,this.pasteCatcher.style.right=0,this.pasteCatcher.style.width=0,document.body.insertBefore(this.pasteCatcher,document.body.firstChild),this.pasteCatcher.focus(),document.addEventListener("click",this.setFocus.bind(this)),document.getElementById("screenshot-zone").addEventListener("click",this.setFocus.bind(this))),window.addEventListener("paste",this.pasteHandler.bind(this))},Kanboard.Screenshot.prototype.destroy=function(){null!=this.pasteCatcher?document.body.removeChild(this.pasteCatcher):document.getElementById("screenshot-pastezone")&&document.body.removeChild(document.getElementById("screenshot-pastezone")),document.removeEventListener("click",this.setFocus.bind(this)),this.pasteCatcher=null},Kanboard.Screenshot.prototype.setFocus=function(){null!==this.pasteCatcher&&this.pasteCatcher.focus()},Kanboard.Screenshot.prototype.pasteHandler=function(t){if(t.clipboardData&&t.clipboardData.items){var e=t.clipboardData.items;if(e)for(var o=0;o<e.length;o++)if(e[o].type.indexOf("image")!==-1){var a=e[o].getAsFile(),n=new FileReader,i=this;n.onload=function(t){i.createImage(t.target.result)},n.readAsDataURL(a)}}else setTimeout(this.checkInput.bind(this),100)},Kanboard.Screenshot.prototype.checkInput=function(){var t=this.pasteCatcher.childNodes[0];t&&"IMG"===t.tagName&&this.createImage(t.src),this.pasteCatcher.innerHTML=""},Kanboard.Screenshot.prototype.createImage=function(t){var e=new Image;e.src=t,e.onload=function(){var e=t.split("base64,"),o=e[1];$("input[name=screenshot]").val(o)};var o=document.getElementById("screenshot-zone");o.innerHTML="",o.className="screenshot-pasted",o.appendChild(e),this.destroy(),this.initialize()},Kanboard.Search=function(t){this.app=t},Kanboard.Search.prototype.focus=function(){$(document).on("focus","#form-search",function(){var t=$("#form-search");if(t[0].setSelectionRange){var e=2*t.val().length;t[0].setSelectionRange(e,e)}})},Kanboard.Search.prototype.listen=function(){$(document).on("click",".filter-helper",function(t){t.preventDefault();var e=$(this).data("filter"),o=$(this).data("append-filter"),a=$(this).data("unique-filter"),n=$("#form-search");if(a){var i=a.substr(0,a.indexOf(":"));e=n.val().replace(new RegExp("("+i+":[#a-z0-9]+)","g"),""),e=e.replace(new RegExp("("+i+':"(.+)")',"g"),""),e=e.trim(),e+=" "+a}else o&&(e=n.val()+" "+o);n.val(e),$("form.search").submit()})},Kanboard.Search.prototype.goToView=function(t){var e=$(t);e.length&&(window.location=e.attr("href"))},Kanboard.Search.prototype.keyboardShortcuts=function(){var t=this;Mousetrap.bind("v o",function(){t.goToView(".view-overview")}),Mousetrap.bind("v b",function(){t.goToView(".view-board")}),Mousetrap.bind("v c",function(){t.goToView(".view-calendar")}),Mousetrap.bind("v l",function(){t.goToView(".view-listing")}),Mousetrap.bind("v g",function(){t.goToView(".view-gantt")}),Mousetrap.bind("f",function(t){t.preventDefault();var e=document.getElementById("form-search");e&&e.focus()}),Mousetrap.bind("r",function(t){t.preventDefault();var e=$(".filter-reset").data("filter"),o=$("#form-search");o.val(e),$("form.search").submit()})},Kanboard.Session=function(t){this.app=t},Kanboard.Session.prototype.execute=function(){window.setInterval(this.checkSession,6e4)},Kanboard.Session.prototype.checkSession=function(){$(".form-login").length||$.ajax({cache:!1,url:$("body").data("status-url"),statusCode:{401:function(){window.location=$("body").data("login-url")}}})},Kanboard.Subtask=function(t){this.app=t},Kanboard.Subtask.prototype.listen=function(){var t=this;this.dragAndDrop(),$(document).on("click",".subtask-toggle-status",function(e){var o=$(this);e.preventDefault(),$.ajax({cache:!1,url:o.attr("href"),success:function(e){o.hasClass("subtask-refresh-table")?$(".subtasks-table").replaceWith(e):o.replaceWith(e),t.dragAndDrop()}})}),$(document).on("click",".subtask-toggle-timer",function(e){var o=$(this);e.preventDefault(),$.ajax({cache:!1,url:o.attr("href"),success:function(e){$(".subtasks-table").replaceWith(e),t.dragAndDrop()}})})},Kanboard.Subtask.prototype.dragAndDrop=function(){var t=this;$(".draggable-row-handle").mouseenter(function(){$(this).parent().parent().addClass("draggable-item-hover")}).mouseleave(function(){$(this).parent().parent().removeClass("draggable-item-hover")}),$(".subtasks-table tbody").sortable({forcePlaceholderSize:!0,handle:"td:first i",helper:function(t,e){return e.children().each(function(){$(this).width($(this).width())}),e},stop:function(e,o){var a=o.item;a.removeClass("draggable-item-selected"),t.savePosition(a.data("subtask-id"),a.index()+1)},start:function(t,e){e.item.addClass("draggable-item-selected")}}).disableSelection()},Kanboard.Subtask.prototype.savePosition=function(t,e){var o=$(".subtasks-table").data("save-position-url"),a=this;this.app.showLoadingIcon(),$.ajax({cache:!1,url:o,contentType:"application/json",type:"POST",processData:!1,data:JSON.stringify({subtask_id:t,position:e}),complete:function(){a.app.hideLoadingIcon()}})},Kanboard.Swimlane=function(t){this.app=t},Kanboard.Swimlane.prototype.execute=function(){$(".swimlanes-table").length&&this.dragAndDrop()},Kanboard.Swimlane.prototype.listen=function(){var t=this;$(document).on("click",".board-swimlane-toggle",function(e){e.preventDefault();var o=$(this).data("swimlane-id");t.isCollapsed(o)?t.expand(o):t.collapse(o)})},Kanboard.Swimlane.prototype.onBoardRendered=function(){for(var t=this.getAllCollapsed(),e=0;e<t.length;e++)this.collapse(t[e])},Kanboard.Swimlane.prototype.getStorageKey=function(){return"hidden_swimlanes_"+$("#board").data("project-id")},Kanboard.Swimlane.prototype.expand=function(t){var e=this.getAllCollapsed(),o=e.indexOf(t);o>-1&&e.splice(o,1),localStorage.setItem(this.getStorageKey(),JSON.stringify(e)),$(".board-swimlane-columns-"+t).css("display","table-row"),$(".board-swimlane-tasks-"+t).css("display","table-row"),$(".hide-icon-swimlane-"+t).css("display","inline"),$(".show-icon-swimlane-"+t).css("display","none")},Kanboard.Swimlane.prototype.collapse=function(t){var e=this.getAllCollapsed();e.indexOf(t)<0&&(e.push(t),localStorage.setItem(this.getStorageKey(),JSON.stringify(e))),$(".board-swimlane-columns-"+t+":not(:first-child)").css("display","none"),$(".board-swimlane-tasks-"+t).css("display","none"),$(".hide-icon-swimlane-"+t).css("display","none"),$(".show-icon-swimlane-"+t).css("display","inline")},Kanboard.Swimlane.prototype.isCollapsed=function(t){return this.getAllCollapsed().indexOf(t)>-1},Kanboard.Swimlane.prototype.getAllCollapsed=function(){return JSON.parse(localStorage.getItem(this.getStorageKey()))||[]},Kanboard.Swimlane.prototype.dragAndDrop=function(){var t=this;$(".draggable-row-handle").mouseenter(function(){$(this).parent().parent().addClass("draggable-item-hover")}).mouseleave(function(){$(this).parent().parent().removeClass("draggable-item-hover")}),$(".swimlanes-table tbody").sortable({forcePlaceholderSize:!0,handle:"td:first i",helper:function(t,e){return e.children().each(function(){$(this).width($(this).width())}),e},stop:function(e,o){var a=o.item;a.removeClass("draggable-item-selected"),t.savePosition(a.data("swimlane-id"),a.index()+1)},start:function(t,e){e.item.addClass("draggable-item-selected")}}).disableSelection()},Kanboard.Swimlane.prototype.savePosition=function(t,e){var o=$(".swimlanes-table").data("save-position-url"),a=this;this.app.showLoadingIcon(),$.ajax({cache:!1,url:o,contentType:"application/json",type:"POST",processData:!1,data:JSON.stringify({swimlane_id:t,position:e}),complete:function(){a.app.hideLoadingIcon()}})},Kanboard.Task=function(t){this.app=t},Kanboard.Task.prototype.keyboardShortcuts=function(){var t=$("#task-view"),e=this;this.app.hasId("task-view")&&(Mousetrap.bind("e",function(){e.app.get("Popover").open(t.data("edit-url"))}),Mousetrap.bind("c",function(){e.app.get("Popover").open(t.data("comment-url"))}),Mousetrap.bind("s",function(){e.app.get("Popover").open(t.data("subtask-url"))}),Mousetrap.bind("l",function(){e.app.get("Popover").open(t.data("internal-link-url"))}))},Kanboard.Task.prototype.onPopoverOpened=function(){var t=this,e=0;t.renderColorPicker(),$(document).on("click",".assign-me",function(t){var e=$(this).data("current-id"),o="#"+$(this).data("target-id");t.preventDefault(),$(o+" option[value="+e+"]").length&&$(o).val(e)}),$(document).on("change","select.task-reload-project-destination",function(){if(e>0)$(this).val(e);else{e=$(this).val();var o=$(this).data("redirect").replace(/PROJECT_ID/g,e);$(".loading-icon").show(),$.ajax({type:"GET",url:o,success:function(o,a,n){e=0,$(".loading-icon").hide(),t.app.get("Popover").ajaxReload(o,n,t.app.get("Popover"))}})}})},Kanboard.Task.prototype.renderColorPicker=function(){function t(t){return $('<div class="color-picker-option"><div class="color-picker-square color-'+t.id+'"></div><div class="color-picker-label">'+t.text+"</div></div>")}$(".color-picker").select2({minimumResultsForSearch:1/0,templateResult:t,templateSelection:t})},Kanboard.TaskTimeColumnChart=function(t){this.app=t},Kanboard.TaskTimeColumnChart.prototype.execute=function(){this.app.hasId("analytic-task-time-column")&&this.show()},Kanboard.TaskTimeColumnChart.prototype.show=function(){for(var t=$("#chart"),e=t.data("metrics"),o=[t.data("label")],a=[],n=0;n<e.length;n++)o.push(e[n].time_spent),a.push(e[n].title);c3.generate({data:{columns:[o],type:"bar"},bar:{width:{ratio:.5}},axis:{x:{type:"category",categories:a},y:{tick:{format:this.app.formatDuration}}},legend:{show:!1}})},Kanboard.Tooltip=function(t){this.app=t},Kanboard.Tooltip.prototype.onBoardRendered=function(){this.execute()},Kanboard.Tooltip.prototype.execute=function(){$(".tooltip").tooltip({track:!1,show:!1,hide:!1,position:{my:"left-20 top",at:"center bottom+9",using:function(t,e){$(this).css(t);var o=e.target.left+e.target.width/2-e.element.left-20;$("<div>").addClass("tooltip-arrow").addClass(e.vertical).addClass(o<1?"align-left":"align-right").appendTo(this)}},content:function(){var t=this,e=$(this).attr("data-href");return e?($.get(e,function(e){var o=$(".ui-tooltip:visible");$(".ui-tooltip-content:visible").html(e),o.css({top:"",left:""}),o.children(".tooltip-arrow").remove();var a=$(t).tooltip("option","position");a.of=$(t),o.position(a)}),'<i class="fa fa-spinner fa-spin"></i>'):'<div class="markdown">'+$(this).attr("title")+"</div>"}}).on("mouseenter",function(){var t=this;$(this).tooltip("open"),$(".ui-tooltip").on("mouseleave",function(){$(t).tooltip("close")})}).on("mouseleave focusout",function(t){t.stopImmediatePropagation();var e=this;setTimeout(function(){$(".ui-tooltip:hover").length||$(e).tooltip("close")},100)})},Kanboard.BoardDragAndDrop=function(t){this.app=t,this.savingInProgress=!1},Kanboard.BoardDragAndDrop.prototype.execute=function(){this.app.hasId("board")&&(this.dragAndDrop(),this.executeListeners())},Kanboard.BoardDragAndDrop.prototype.dragAndDrop=function(){var t=this,e=$(".board-task-list"),o={forcePlaceholderSize:!0,tolerance:"pointer",connectWith:".board-task-list",placeholder:"draggable-placeholder",items:".draggable-item",stop:function(e,o){var a=o.item,n=a.attr("data-task-id"),i=a.attr("data-position"),r=a.attr("data-column-id"),s=a.attr("data-swimlane-id"),d=a.parent().attr("data-column-id"),l=a.parent().attr("data-swimlane-id"),p=a.index()+1;a.removeClass("draggable-item-selected"),d==r&&l==s&&p==i||(t.changeTaskState(n),t.save(n,r,d,p,l))},start:function(t,e){e.item.addClass("draggable-item-selected"),e.placeholder.height(e.item.height())}};isMobile.any&&($(".task-board-sort-handle").css("display","inline"),o.handle=".task-board-sort-handle"),e.each(function(){$(this).css("min-height",$(this).parent().height())}),e.sortable(o)},Kanboard.BoardDragAndDrop.prototype.changeTaskState=function(t){var e=$("div[data-task-id="+t+"]");e.addClass("task-board-saving-state"),e.find(".task-board-saving-icon").show()},Kanboard.BoardDragAndDrop.prototype.save=function(t,e,o,a,n){var i=this;i.app.showLoadingIcon(),i.savingInProgress=!0,$.ajax({cache:!1,url:$("#board").data("save-url"),contentType:"application/json",type:"POST",processData:!1,data:JSON.stringify({task_id:t,src_column_id:e,dst_column_id:o,swimlane_id:n,position:a}),success:function(t){i.refresh(t),i.savingInProgress=!1},error:function(){i.app.hideLoadingIcon(),i.savingInProgress=!1}})},Kanboard.BoardDragAndDrop.prototype.refresh=function(t){$("#board-container").replaceWith(t),this.app.hideLoadingIcon(),this.dragAndDrop(),this.executeListeners()},Kanboard.BoardDragAndDrop.prototype.executeListeners=function(){for(var t in this.app.controllers){var e=this.app.get(t);"function"==typeof e.onBoardRendered&&e.onBoardRendered()}};var _KB=null;jQuery(document).ready(function(){_KB=new Kanboard.App,_KB.execute()}); \ No newline at end of file
diff --git a/assets/js/src/BoardDragAndDrop.js b/assets/js/src/BoardDragAndDrop.js
index c8e24444..b7850e3f 100644
--- a/assets/js/src/BoardDragAndDrop.js
+++ b/assets/js/src/BoardDragAndDrop.js
@@ -34,7 +34,7 @@ Kanboard.BoardDragAndDrop.prototype.dragAndDrop = function() {
if (newColumnId != taskColumnId || newSwimlaneId != taskSwimlaneId || newPosition != taskPosition) {
self.changeTaskState(taskId);
- self.save(taskId, newColumnId, newPosition, newSwimlaneId);
+ self.save(taskId, taskColumnId, newColumnId, newPosition, newSwimlaneId);
}
},
start: function(event, ui) {
@@ -62,7 +62,7 @@ Kanboard.BoardDragAndDrop.prototype.changeTaskState = function(taskId) {
task.find('.task-board-saving-icon').show();
};
-Kanboard.BoardDragAndDrop.prototype.save = function(taskId, columnId, position, swimlaneId) {
+Kanboard.BoardDragAndDrop.prototype.save = function(taskId, srcColumnId, dstColumnId, position, swimlaneId) {
var self = this;
self.app.showLoadingIcon();
self.savingInProgress = true;
@@ -75,7 +75,8 @@ Kanboard.BoardDragAndDrop.prototype.save = function(taskId, columnId, position,
processData: false,
data: JSON.stringify({
"task_id": taskId,
- "column_id": columnId,
+ "src_column_id": srcColumnId,
+ "dst_column_id": dstColumnId,
"swimlane_id": swimlaneId,
"position": position
}),
diff --git a/tests/units/Formatter/BoardFormatterTest.php b/tests/units/Formatter/BoardFormatterTest.php
index 0be1be94..4d469eac 100644
--- a/tests/units/Formatter/BoardFormatterTest.php
+++ b/tests/units/Formatter/BoardFormatterTest.php
@@ -123,6 +123,7 @@ class BoardFormatterTest extends Base
$this->assertSame(0, $board[2]['columns'][3]['nb_tasks']);
$this->assertEquals('Task 8', $board[2]['columns'][2]['tasks'][0]['title']);
+ $this->assertArrayHasKey('is_draggable', $board[2]['columns'][2]['tasks'][0]);
}
public function testFormatWithoutDefaultSwimlane()
diff --git a/tests/units/Helper/BoardHelperTest.php b/tests/units/Helper/BoardHelperTest.php
new file mode 100644
index 00000000..641ca9cb
--- /dev/null
+++ b/tests/units/Helper/BoardHelperTest.php
@@ -0,0 +1,94 @@
+<?php
+
+use Kanboard\Core\Security\Role;
+use Kanboard\Helper\BoardHelper;
+use Kanboard\Model\ColumnMoveRestrictionModel;
+use Kanboard\Model\ProjectModel;
+use Kanboard\Model\ProjectRoleModel;
+use Kanboard\Model\ProjectUserRoleModel;
+use Kanboard\Model\TaskCreationModel;
+use Kanboard\Model\TaskFinderModel;
+use Kanboard\Model\TaskStatusModel;
+use Kanboard\Model\UserModel;
+
+require_once __DIR__.'/../Base.php';
+
+class BoardHelperTest extends Base
+{
+ public function testIsDraggableWithProjectMember()
+ {
+ $boardHelper = new BoardHelper($this->container);
+ $projectModel = new ProjectModel($this->container);
+ $taskCreationModel = new TaskCreationModel($this->container);
+ $taskFinderModel = new TaskFinderModel($this->container);
+ $projectUserRole = new ProjectUserRoleModel($this->container);
+ $userModel = new UserModel($this->container);
+
+ $this->container['sessionStorage']->user = array(
+ 'id' => 2,
+ 'role' => Role::APP_USER,
+ );
+
+ $this->assertEquals(2, $userModel->create(array('username' => 'user')));
+ $this->assertEquals(1, $projectModel->create(array('name' => 'Test')));
+ $this->assertTrue($projectUserRole->addUser(1, 2, Role::PROJECT_MEMBER));
+ $this->assertEquals(1, $taskCreationModel->create(array('project_id' => 1, 'title' => 'test')));
+
+ $task = $taskFinderModel->getById(1);
+ $this->assertTrue($boardHelper->isDraggable($task));
+ }
+
+ public function testIsDraggableWithClosedTask()
+ {
+ $boardHelper = new BoardHelper($this->container);
+ $projectModel = new ProjectModel($this->container);
+ $taskCreationModel = new TaskCreationModel($this->container);
+ $taskFinderModel = new TaskFinderModel($this->container);
+ $taskStatusModel = new TaskStatusModel($this->container);
+ $projectUserRole = new ProjectUserRoleModel($this->container);
+ $userModel = new UserModel($this->container);
+
+ $this->container['sessionStorage']->user = array(
+ 'id' => 2,
+ 'role' => Role::APP_USER,
+ );
+
+ $this->assertEquals(2, $userModel->create(array('username' => 'user')));
+ $this->assertEquals(1, $projectModel->create(array('name' => 'Test')));
+ $this->assertTrue($projectUserRole->addUser(1, 2, Role::PROJECT_MEMBER));
+ $this->assertEquals(1, $taskCreationModel->create(array('project_id' => 1, 'title' => 'test')));
+ $this->assertTrue($taskStatusModel->close(1));
+
+ $task = $taskFinderModel->getById(1);
+ $this->assertFalse($boardHelper->isDraggable($task));
+ }
+
+ public function testIsDraggableWithColumnRestrictions()
+ {
+ $boardHelper = new BoardHelper($this->container);
+ $projectModel = new ProjectModel($this->container);
+ $taskCreationModel = new TaskCreationModel($this->container);
+ $taskFinderModel = new TaskFinderModel($this->container);
+ $projectUserRole = new ProjectUserRoleModel($this->container);
+ $userModel = new UserModel($this->container);
+ $projectRoleModel = new ProjectRoleModel($this->container);
+ $columnMoveRestrictionModel = new ColumnMoveRestrictionModel($this->container);
+
+ $this->container['sessionStorage']->user = array(
+ 'id' => 2,
+ 'role' => Role::APP_USER,
+ );
+
+ $this->assertEquals(2, $userModel->create(array('username' => 'user')));
+ $this->assertEquals(1, $projectModel->create(array('name' => 'Test')));
+
+ $this->assertEquals(1, $projectRoleModel->create(1, 'Custom Role'));
+ $this->assertEquals(1, $columnMoveRestrictionModel->create(1, 1, 2, 3));
+
+ $this->assertTrue($projectUserRole->addUser(1, 2, 'Custom Role'));
+ $this->assertEquals(1, $taskCreationModel->create(array('project_id' => 1, 'title' => 'test', 'column_id' => 2)));
+
+ $task = $taskFinderModel->getById(1);
+ $this->assertFalse($boardHelper->isDraggable($task));
+ }
+}