summaryrefslogtreecommitdiff
path: root/app/Core
diff options
context:
space:
mode:
Diffstat (limited to 'app/Core')
-rw-r--r--app/Core/Action/ActionManager.php16
-rw-r--r--app/Core/Base.php342
-rw-r--r--app/Core/Cache/BaseCache.php (renamed from app/Core/Cache/Base.php)6
-rw-r--r--app/Core/Cache/CacheInterface.php16
-rw-r--r--app/Core/Cache/FileCache.php98
-rw-r--r--app/Core/Cache/MemoryCache.php10
-rw-r--r--app/Core/Controller/Runner.php2
-rw-r--r--app/Core/DateParser.php21
-rw-r--r--app/Core/Event/EventManager.php1
-rw-r--r--app/Core/ExternalTask/AccessForbiddenException.php13
-rw-r--r--app/Core/ExternalTask/ExternalTaskException.php15
-rw-r--r--app/Core/ExternalTask/ExternalTaskInterface.php26
-rw-r--r--app/Core/ExternalTask/ExternalTaskManager.php58
-rw-r--r--app/Core/ExternalTask/ExternalTaskProviderInterface.php77
-rw-r--r--app/Core/ExternalTask/NotFoundException.php13
-rw-r--r--app/Core/ExternalTask/ProviderNotFoundException.php13
-rw-r--r--app/Core/Filter/FormatterInterface.php2
-rw-r--r--app/Core/Filter/LexerBuilder.php4
-rw-r--r--app/Core/Helper.php4
-rw-r--r--app/Core/Http/Request.php16
-rw-r--r--app/Core/Http/Response.php12
-rw-r--r--app/Core/Mail/Transport/Mail.php4
-rw-r--r--app/Core/Mail/Transport/Smtp.php10
-rw-r--r--app/Core/Markdown.php26
-rw-r--r--app/Core/Paginator.php45
-rw-r--r--app/Core/Plugin/Base.php13
-rw-r--r--app/Core/Plugin/Directory.php6
-rw-r--r--app/Core/Plugin/Hook.php17
-rw-r--r--app/Core/Plugin/Installer.php27
-rw-r--r--app/Core/Plugin/Loader.php48
-rw-r--r--app/Core/Plugin/PluginException.php15
-rw-r--r--app/Core/Plugin/PluginInstallerException.php4
-rw-r--r--app/Core/Plugin/Version.php38
-rw-r--r--app/Core/Queue/JobHandler.php36
-rw-r--r--app/Core/Queue/QueueManager.php6
-rw-r--r--app/Core/Security/Role.php12
-rw-r--r--app/Core/Session/SessionStorage.php1
-rw-r--r--app/Core/Tool.php53
-rw-r--r--app/Core/Translator.php26
-rw-r--r--app/Core/User/UserSession.php46
40 files changed, 888 insertions, 310 deletions
diff --git a/app/Core/Action/ActionManager.php b/app/Core/Action/ActionManager.php
index 1dfd820c..aec9ef02 100644
--- a/app/Core/Action/ActionManager.php
+++ b/app/Core/Action/ActionManager.php
@@ -139,4 +139,20 @@ class ActionManager extends Base
return $this;
}
+
+ /**
+ * Remove all listeners for automated actions
+ *
+ * @access public
+ */
+ public function removeEvents()
+ {
+ foreach ($this->dispatcher->getListeners() as $eventName => $listeners) {
+ foreach ($listeners as $listener) {
+ if (is_array($listener) && $listener[0] instanceof ActionBase) {
+ $this->dispatcher->removeListener($eventName, $listener);
+ }
+ }
+ }
+ }
}
diff --git a/app/Core/Base.php b/app/Core/Base.php
index 8103ec14..17ed5b33 100644
--- a/app/Core/Base.php
+++ b/app/Core/Base.php
@@ -10,151 +10,196 @@ 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\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\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\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\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 \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\ExternalTask\ExternalTaskManager $externalTaskManager
+ * @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\UserCacheDecorator $userCacheDecorator
+ * @property \Kanboard\Decorator\ColumnRestrictionCacheDecorator $columnRestrictionCacheDecorator
+ * @property \Kanboard\Decorator\ColumnMoveRestrictionCacheDecorator $columnMoveRestrictionCacheDecorator
+ * @property \Kanboard\Decorator\ProjectRoleRestrictionCacheDecorator $projectRoleRestrictionCacheDecorator
+ * @property \Kanboard\Formatter\BoardColumnFormatter $boardColumnFormatter
+ * @property \Kanboard\Formatter\BoardFormatter $boardFormatter
+ * @property \Kanboard\Formatter\BoardSwimlaneFormatter $boardSwimlaneFormatter
+ * @property \Kanboard\Formatter\BoardTaskFormatter $boardTaskFormatter
+ * @property \Kanboard\Formatter\GroupAutoCompleteFormatter $groupAutoCompleteFormatter
+ * @property \Kanboard\Formatter\ProjectActivityEventFormatter $projectActivityEventFormatter
+ * @property \Kanboard\Formatter\ProjectGanttFormatter $projectGanttFormatter
+ * @property \Kanboard\Formatter\SubtaskTimeTrackingCalendarFormatter $subtaskTimeTrackingCalendarFormatter
+ * @property \Kanboard\Formatter\TaskAutoCompleteFormatter $taskAutoCompleteFormatter
+ * @property \Kanboard\Formatter\TaskCalendarFormatter $taskCalendarFormatter
+ * @property \Kanboard\Formatter\TaskGanttFormatter $taskGanttFormatter
+ * @property \Kanboard\Formatter\TaskICalFormatter $taskICalFormatter
+ * @property \Kanboard\Formatter\TaskSuggestMenuFormatter $taskSuggestMenuFormatter
+ * @property \Kanboard\Formatter\UserAutoCompleteFormatter $userAutoCompleteFormatter
+ * @property \Kanboard\Formatter\UserMentionFormatter $userMentionFormatter
+ * @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\ColumnRestrictionModel $columnRestrictionModel
+ * @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\InviteModel $inviteModel
+ * @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\ProjectRoleRestrictionModel $projectRoleRestrictionModel
+ * @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\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\ColumnRestrictionValidator $columnRestrictionValidator
+ * @property \Kanboard\Validator\ColumnMoveRestrictionValidator $columnMoveRestrictionValidator
+ * @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\ProjectRoleValidator $projectRoleValidator
+ * @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 \Kanboard\Job\UserMentionJob $userMentionJob
+ * @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
{
@@ -178,10 +223,10 @@ abstract class Base
}
/**
- * Load automatically models
+ * Load automatically dependencies
*
* @access public
- * @param string $name Model name
+ * @param string $name Class name
* @return mixed
*/
public function __get($name)
@@ -199,7 +244,6 @@ abstract class Base
*/
public static function getInstance(Container $container)
{
- $self = new static($container);
- return $self;
+ return new static($container);
}
}
diff --git a/app/Core/Cache/Base.php b/app/Core/Cache/BaseCache.php
index d62b8507..b51c4c0c 100644
--- a/app/Core/Cache/Base.php
+++ b/app/Core/Cache/BaseCache.php
@@ -3,12 +3,12 @@
namespace Kanboard\Core\Cache;
/**
- * Base class for cache drivers
+ * Base Class for Cache Drivers
*
- * @package cache
+ * @package Kanboard\Core\Cache
* @author Frederic Guillot
*/
-abstract class Base
+abstract class BaseCache implements CacheInterface
{
/**
* Proxy cache
diff --git a/app/Core/Cache/CacheInterface.php b/app/Core/Cache/CacheInterface.php
index d9e9747a..033732cf 100644
--- a/app/Core/Cache/CacheInterface.php
+++ b/app/Core/Cache/CacheInterface.php
@@ -3,24 +3,24 @@
namespace Kanboard\Core\Cache;
/**
- * Cache Interface
+ * Interface CacheInterface
*
- * @package cache
- * @author Frederic Guillot
+ * @package Kanboard\Core\Cache
+ * @author Frederic Guillot
*/
interface CacheInterface
{
/**
- * Save a new value in the cache
+ * Store an item in the cache
*
* @access public
* @param string $key
- * @param string $value
+ * @param mixed $value
*/
public function set($key, $value);
/**
- * Fetch value from cache
+ * Retrieve an item from the cache by key
*
* @access public
* @param string $key
@@ -29,14 +29,14 @@ interface CacheInterface
public function get($key);
/**
- * Clear all cache
+ * Remove all items from the cache
*
* @access public
*/
public function flush();
/**
- * Remove cached value
+ * Remove an item from the cache
*
* @access public
* @param string $key
diff --git a/app/Core/Cache/FileCache.php b/app/Core/Cache/FileCache.php
new file mode 100644
index 00000000..1e0a60ef
--- /dev/null
+++ b/app/Core/Cache/FileCache.php
@@ -0,0 +1,98 @@
+<?php
+
+namespace Kanboard\Core\Cache;
+
+use Kanboard\Core\Tool;
+use LogicException;
+
+/**
+ * Class FileCache
+ *
+ * @package Kanboard\Core\Cache
+ */
+class FileCache extends BaseCache
+{
+ /**
+ * Store an item in the cache
+ *
+ * @access public
+ * @param string $key
+ * @param mixed $value
+ */
+ public function set($key, $value)
+ {
+ $this->createCacheFolder();
+ file_put_contents($this->getFilenameFromKey($key), serialize($value));
+ }
+
+ /**
+ * Retrieve an item from the cache by key
+ *
+ * @access public
+ * @param string $key
+ * @return mixed Null when not found, cached value otherwise
+ */
+ public function get($key)
+ {
+ $filename = $this->getFilenameFromKey($key);
+
+ if (file_exists($filename)) {
+ return unserialize(file_get_contents($filename));
+ }
+
+ return null;
+ }
+
+ /**
+ * Remove all items from the cache
+ *
+ * @access public
+ */
+ public function flush()
+ {
+ $this->createCacheFolder();
+ Tool::removeAllFiles(CACHE_DIR, false);
+ }
+
+ /**
+ * Remove an item from the cache
+ *
+ * @access public
+ * @param string $key
+ */
+ public function remove($key)
+ {
+ $filename = $this->getFilenameFromKey($key);
+
+ if (file_exists($filename)) {
+ unlink($filename);
+ }
+ }
+
+ /**
+ * Get absolute filename from the key
+ *
+ * @access protected
+ * @param string $key
+ * @return string
+ */
+ protected function getFilenameFromKey($key)
+ {
+ return CACHE_DIR.DIRECTORY_SEPARATOR.$key;
+ }
+
+ /**
+ * Create cache folder if missing
+ *
+ * @access protected
+ * @throws LogicException
+ */
+ protected function createCacheFolder()
+ {
+ if (! is_dir(CACHE_DIR)) {
+ if (! mkdir(CACHE_DIR, 0755)) {
+ throw new LogicException('Unable to create cache directory: '.CACHE_DIR);
+ }
+ }
+ }
+}
diff --git a/app/Core/Cache/MemoryCache.php b/app/Core/Cache/MemoryCache.php
index 39e3947b..4fb94728 100644
--- a/app/Core/Cache/MemoryCache.php
+++ b/app/Core/Cache/MemoryCache.php
@@ -3,12 +3,12 @@
namespace Kanboard\Core\Cache;
/**
- * Memory Cache
+ * Memory Cache Driver
*
- * @package cache
+ * @package Kanboard\Core\Cache
* @author Frederic Guillot
*/
-class MemoryCache extends Base implements CacheInterface
+class MemoryCache extends BaseCache
{
/**
* Container
@@ -19,7 +19,7 @@ class MemoryCache extends Base implements CacheInterface
private $storage = array();
/**
- * Save a new value in the cache
+ * Store an item in the cache
*
* @access public
* @param string $key
@@ -31,7 +31,7 @@ class MemoryCache extends Base implements CacheInterface
}
/**
- * Fetch value from cache
+ * Retrieve an item from the cache by key
*
* @access public
* @param string $key
diff --git a/app/Core/Controller/Runner.php b/app/Core/Controller/Runner.php
index 8353cf69..48346390 100644
--- a/app/Core/Controller/Runner.php
+++ b/app/Core/Controller/Runner.php
@@ -35,7 +35,7 @@ class Runner extends Base
$controllerObject->notFound($e->hasLayout());
} catch (AccessForbiddenException $e) {
$controllerObject = new AppController($this->container);
- $controllerObject->accessForbidden($e->hasLayout());
+ $controllerObject->accessForbidden($e->hasLayout(), $e->getMessage());
}
}
diff --git a/app/Core/DateParser.php b/app/Core/DateParser.php
index a7b10a7a..9d012d12 100644
--- a/app/Core/DateParser.php
+++ b/app/Core/DateParser.php
@@ -13,7 +13,6 @@ use DateTime;
class DateParser extends Base
{
const DATE_FORMAT = 'm/d/Y';
- const DATE_TIME_FORMAT = 'm/d/Y H:i';
const TIME_FORMAT = 'H:i';
/**
@@ -35,7 +34,7 @@ class DateParser extends Base
*/
public function getUserDateTimeFormat()
{
- return $this->configModel->get('application_datetime_format', DateParser::DATE_TIME_FORMAT);
+ return $this->getUserDateFormat().' '.$this->getUserTimeFormat();
}
/**
@@ -239,23 +238,11 @@ class DateParser extends Base
*/
public function getHours(DateTime $d1, DateTime $d2)
{
- $seconds = $this->getRoundedSeconds(abs($d1->getTimestamp() - $d2->getTimestamp()));
+ $seconds = abs($d1->getTimestamp() - $d2->getTimestamp());
return round($seconds / 3600, 2);
}
/**
- * Round the timestamp to the nearest quarter
- *
- * @access public
- * @param integer $seconds Timestamp
- * @return integer
- */
- public function getRoundedSeconds($seconds)
- {
- return (int) round($seconds / (15 * 60)) * (15 * 60);
- }
-
- /**
* Get ISO-8601 date from user input
*
* @access public
@@ -304,7 +291,9 @@ class DateParser extends Base
{
foreach ($fields as $field) {
if (! empty($values[$field])) {
- $values[$field] = date($format, $values[$field]);
+ if (ctype_digit($values[$field])) {
+ $values[$field] = date($format, $values[$field]);
+ }
} else {
$values[$field] = '';
}
diff --git a/app/Core/Event/EventManager.php b/app/Core/Event/EventManager.php
index 9ae43170..48a9d299 100644
--- a/app/Core/Event/EventManager.php
+++ b/app/Core/Event/EventManager.php
@@ -53,6 +53,7 @@ class EventManager
TaskModel::EVENT_CREATE_UPDATE => t('Task creation or modification'),
TaskModel::EVENT_ASSIGNEE_CHANGE => t('Task assignee change'),
TaskModel::EVENT_DAILY_CRONJOB => t('Daily background job for tasks'),
+ TaskModel::EVENT_MOVE_SWIMLANE => t('Move a task to another swimlane'),
);
$events = array_merge($events, $this->events);
diff --git a/app/Core/ExternalTask/AccessForbiddenException.php b/app/Core/ExternalTask/AccessForbiddenException.php
new file mode 100644
index 00000000..2b5ebd33
--- /dev/null
+++ b/app/Core/ExternalTask/AccessForbiddenException.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace Kanboard\Core\ExternalTask;
+
+/**
+ * Class AccessForbiddenException
+ *
+ * @package Kanboard\Core\ExternalTask
+ * @author Frederic Guillot
+ */
+class AccessForbiddenException extends ExternalTaskException
+{
+}
diff --git a/app/Core/ExternalTask/ExternalTaskException.php b/app/Core/ExternalTask/ExternalTaskException.php
new file mode 100644
index 00000000..07e5665d
--- /dev/null
+++ b/app/Core/ExternalTask/ExternalTaskException.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Kanboard\Core\ExternalTask;
+
+use Exception;
+
+/**
+ * Class NotFoundException
+ *
+ * @package Kanboard\Core\ExternalTask
+ * @author Frederic Guillot
+ */
+class ExternalTaskException extends Exception
+{
+}
diff --git a/app/Core/ExternalTask/ExternalTaskInterface.php b/app/Core/ExternalTask/ExternalTaskInterface.php
new file mode 100644
index 00000000..084af509
--- /dev/null
+++ b/app/Core/ExternalTask/ExternalTaskInterface.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Kanboard\Core\ExternalTask;
+
+/**
+ * Interface ExternalTaskInterface
+ *
+ * @package Kanboard\Core\ExternalTask
+ * @author Frederic Guillot
+ */
+interface ExternalTaskInterface
+{
+ /**
+ * Return Uniform Resource Identifier for the task
+ *
+ * @return string
+ */
+ public function getUri();
+
+ /**
+ * Return a dict to populate the task form
+ *
+ * @return array
+ */
+ public function getFormValues();
+}
diff --git a/app/Core/ExternalTask/ExternalTaskManager.php b/app/Core/ExternalTask/ExternalTaskManager.php
new file mode 100644
index 00000000..102ec459
--- /dev/null
+++ b/app/Core/ExternalTask/ExternalTaskManager.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace Kanboard\Core\ExternalTask;
+
+/**
+ * Class ExternalTaskManager
+ *
+ * @package Kanboard\Core\ExternalTask
+ * @author Frederic Guillot
+ */
+class ExternalTaskManager
+{
+ protected $providers = array();
+
+ /**
+ * Register a new task provider
+ *
+ * @param ExternalTaskProviderInterface $externalTaskProvider
+ * @return $this
+ */
+ public function register(ExternalTaskProviderInterface $externalTaskProvider)
+ {
+ $this->providers[$externalTaskProvider->getName()] = $externalTaskProvider;
+ return $this;
+ }
+
+ /**
+ * Get task provider
+ *
+ * @param string $name
+ * @return ExternalTaskProviderInterface|null
+ * @throws ProviderNotFoundException
+ */
+ public function getProvider($name)
+ {
+ if (isset($this->providers[$name])) {
+ return $this->providers[$name];
+ }
+
+ throw new ProviderNotFoundException('Unable to load this provider: '.$name);
+ }
+
+ /**
+ * Get list of task providers
+ *
+ * @return array
+ */
+ public function getProvidersList()
+ {
+ $providers = array_keys($this->providers);
+
+ if (count($providers)) {
+ return array_combine($providers, $providers);
+ }
+
+ return array();
+ }
+}
diff --git a/app/Core/ExternalTask/ExternalTaskProviderInterface.php b/app/Core/ExternalTask/ExternalTaskProviderInterface.php
new file mode 100644
index 00000000..f67f7552
--- /dev/null
+++ b/app/Core/ExternalTask/ExternalTaskProviderInterface.php
@@ -0,0 +1,77 @@
+<?php
+
+namespace Kanboard\Core\ExternalTask;
+
+/**
+ * Interface ExternalTaskProviderInterface
+ *
+ * @package Kanboard\Core\ExternalTask
+ * @author Frederic Guillot
+ */
+interface ExternalTaskProviderInterface
+{
+ /**
+ * Get provider name (visible in the user interface)
+ *
+ * @access public
+ * @return string
+ */
+ public function getName();
+
+ /**
+ * Retrieve task from external system or cache
+ *
+ * @access public
+ * @throws \Kanboard\Core\ExternalTask\ExternalTaskException
+ * @param string $uri
+ * @return ExternalTaskInterface
+ */
+ public function fetch($uri);
+
+ /**
+ * Save external task to another system
+ *
+ * @throws \Kanboard\Core\ExternalTask\ExternalTaskException
+ * @param string $uri
+ * @param array $formValues
+ * @param array $formErrors
+ * @return bool
+ */
+ public function save($uri, array $formValues, array &$formErrors);
+
+ /**
+ * Get task import template name
+ *
+ * @return string
+ */
+ public function getImportFormTemplate();
+
+ /**
+ * Get creation form template
+ *
+ * @return string
+ */
+ public function getCreationFormTemplate();
+
+ /**
+ * Get modification form template
+ *
+ * @return string
+ */
+ public function getModificationFormTemplate();
+
+ /**
+ * Get task view template name
+ *
+ * @return string
+ */
+ public function getViewTemplate();
+
+ /**
+ * Build external task URI based on import form values
+ *
+ * @param array $formValues
+ * @return string
+ */
+ public function buildTaskUri(array $formValues);
+}
diff --git a/app/Core/ExternalTask/NotFoundException.php b/app/Core/ExternalTask/NotFoundException.php
new file mode 100644
index 00000000..34eff8ea
--- /dev/null
+++ b/app/Core/ExternalTask/NotFoundException.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace Kanboard\Core\ExternalTask;
+
+/**
+ * Class NotFoundException
+ *
+ * @package Kanboard\Core\ExternalTask
+ * @author Frederic Guillot
+ */
+class NotFoundException extends ExternalTaskException
+{
+}
diff --git a/app/Core/ExternalTask/ProviderNotFoundException.php b/app/Core/ExternalTask/ProviderNotFoundException.php
new file mode 100644
index 00000000..6ad1fae1
--- /dev/null
+++ b/app/Core/ExternalTask/ProviderNotFoundException.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace Kanboard\Core\ExternalTask;
+
+/**
+ * Class ProviderNotFoundException
+ *
+ * @package Kanboard\Core\ExternalTask
+ * @author Frederic Guillot
+ */
+class ProviderNotFoundException extends ExternalTaskException
+{
+}
diff --git a/app/Core/Filter/FormatterInterface.php b/app/Core/Filter/FormatterInterface.php
index b7c04c51..0ff84976 100644
--- a/app/Core/Filter/FormatterInterface.php
+++ b/app/Core/Filter/FormatterInterface.php
@@ -17,7 +17,7 @@ interface FormatterInterface
*
* @access public
* @param Table $query
- * @return FormatterInterface
+ * @return $this
*/
public function withQuery(Table $query);
diff --git a/app/Core/Filter/LexerBuilder.php b/app/Core/Filter/LexerBuilder.php
index 7a9a714f..e3ab725b 100644
--- a/app/Core/Filter/LexerBuilder.php
+++ b/app/Core/Filter/LexerBuilder.php
@@ -51,7 +51,7 @@ class LexerBuilder
*/
public function __construct()
{
- $this->lexer = new Lexer;
+ $this->lexer = new Lexer();
$this->queryBuilder = new QueryBuilder();
}
@@ -69,7 +69,7 @@ class LexerBuilder
foreach ($attributes as $attribute) {
$this->filters[$attribute] = $filter;
- $this->lexer->addToken(sprintf("/^(%s:)/", $attribute), $attribute);
+ $this->lexer->addToken(sprintf("/^(%s:)/i", $attribute), $attribute);
if ($default) {
$this->lexer->setDefaultToken($attribute);
diff --git a/app/Core/Helper.php b/app/Core/Helper.php
index 43151be8..ab7c3b7b 100644
--- a/app/Core/Helper.php
+++ b/app/Core/Helper.php
@@ -12,12 +12,15 @@ use Pimple\Container;
*
* @property \Kanboard\Helper\AppHelper $app
* @property \Kanboard\Helper\AssetHelper $asset
+ * @property \Kanboard\Helper\AvatarHelper $avatar
+ * @property \Kanboard\Helper\BoardHelper $board
* @property \Kanboard\Helper\CalendarHelper $calendar
* @property \Kanboard\Helper\DateHelper $dt
* @property \Kanboard\Helper\FileHelper $file
* @property \Kanboard\Helper\FormHelper $form
* @property \Kanboard\Helper\HookHelper $hook
* @property \Kanboard\Helper\ICalHelper $ical
+ * @property \Kanboard\Helper\ModalHelper $modal
* @property \Kanboard\Helper\ModelHelper $model
* @property \Kanboard\Helper\SubtaskHelper $subtask
* @property \Kanboard\Helper\TaskHelper $task
@@ -25,6 +28,7 @@ use Pimple\Container;
* @property \Kanboard\Helper\UrlHelper $url
* @property \Kanboard\Helper\UserHelper $user
* @property \Kanboard\Helper\LayoutHelper $layout
+ * @property \Kanboard\Helper\ProjectRoleHelper $projectRole
* @property \Kanboard\Helper\ProjectHeaderHelper $projectHeader
* @property \Kanboard\Helper\ProjectActivityHelper $projectActivity
* @property \Kanboard\Helper\MailHelper $mail
diff --git a/app/Core/Http/Request.php b/app/Core/Http/Request.php
index e0df2d3c..44bfdbe6 100644
--- a/app/Core/Http/Request.php
+++ b/app/Core/Http/Request.php
@@ -105,7 +105,7 @@ class Request extends Base
{
if (! empty($this->post) && ! empty($this->post['csrf_token']) && $this->token->validateCSRFToken($this->post['csrf_token'])) {
unset($this->post['csrf_token']);
- return $this->post;
+ return $this->filterValues($this->post);
}
return array();
@@ -301,6 +301,7 @@ class Request extends Base
public function getIpAddress()
{
$keys = array(
+ 'HTTP_X_REAL_IP',
'HTTP_CLIENT_IP',
'HTTP_X_FORWARDED_FOR',
'HTTP_X_FORWARDED',
@@ -343,4 +344,17 @@ class Request extends Base
{
return isset($this->server[$variable]) ? $this->server[$variable] : '';
}
+
+ protected function filterValues(array $values)
+ {
+ foreach ($values as $key => $value) {
+
+ // IE11 Workaround when submitting multipart/form-data
+ if (strpos($key, '-----------------------------') === 0) {
+ unset($values[$key]);
+ }
+ }
+
+ return $values;
+ }
}
diff --git a/app/Core/Http/Response.php b/app/Core/Http/Response.php
index 0f16fb65..0af763a6 100644
--- a/app/Core/Http/Response.php
+++ b/app/Core/Http/Response.php
@@ -129,6 +129,18 @@ class Response extends Base
}
/**
+ * Add P3P headers for Internet Explorer
+ *
+ * @access public
+ * @return $this
+ */
+ public function withP3P()
+ {
+ $this->withHeader('P3P', 'CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"');
+ return $this;
+ }
+
+ /**
* Set HTTP response body
*
* @access public
diff --git a/app/Core/Mail/Transport/Mail.php b/app/Core/Mail/Transport/Mail.php
index d27925f0..c99cc8ba 100644
--- a/app/Core/Mail/Transport/Mail.php
+++ b/app/Core/Mail/Transport/Mail.php
@@ -33,8 +33,8 @@ class Mail extends Base implements ClientInterface
$message = Swift_Message::newInstance()
->setSubject($subject)
->setFrom(array($this->helper->mail->getMailSenderAddress() => $author))
- ->setBody($html, 'text/html')
- ->setTo(array($email => $name));
+ ->setTo(array($email => $name))
+ ->setBody($html, 'text/html');
Swift_Mailer::newInstance($this->getTransport())->send($message);
} catch (Swift_TransportException $e) {
diff --git a/app/Core/Mail/Transport/Smtp.php b/app/Core/Mail/Transport/Smtp.php
index 66f0a3aa..815dde5d 100644
--- a/app/Core/Mail/Transport/Smtp.php
+++ b/app/Core/Mail/Transport/Smtp.php
@@ -25,6 +25,16 @@ class Smtp extends Mail
$transport->setPassword(MAIL_SMTP_PASSWORD);
$transport->setEncryption(MAIL_SMTP_ENCRYPTION);
+ if (HTTP_VERIFY_SSL_CERTIFICATE === false) {
+ $transport->setStreamOptions(array(
+ 'ssl' => array(
+ 'allow_self_signed' => true,
+ 'verify_peer' => false,
+ 'verify_peer_name' => false,
+ )
+ ));
+ }
+
return $transport;
}
}
diff --git a/app/Core/Markdown.php b/app/Core/Markdown.php
index b5abe5ed..4487bf2a 100644
--- a/app/Core/Markdown.php
+++ b/app/Core/Markdown.php
@@ -86,18 +86,23 @@ class Markdown extends Parsedown
*/
protected function inlineUserLink(array $Excerpt)
{
- if (! $this->isPublicLink && preg_match('/^@([^\s]+)/', $Excerpt['text'], $matches)) {
- $user_id = $this->container['userModel']->getIdByUsername($matches[1]);
+ if (! $this->isPublicLink && preg_match('/^@([^\s,!:?]+)/', $Excerpt['text'], $matches)) {
+ $username = rtrim($matches[1], '.');
+ $user = $this->container['userCacheDecorator']->getByUsername($username);
- if (! empty($user_id)) {
- $url = $this->container['helper']->url->href('UserViewController', 'profile', array('user_id' => $user_id));
+ if (! empty($user)) {
+ $url = $this->container['helper']->url->href('UserViewController', 'profile', array('user_id' => $user['id']));
return array(
- 'extent' => strlen($matches[0]),
+ 'extent' => strlen($username) + 1,
'element' => array(
- 'name' => 'a',
- 'text' => $matches[0],
- 'attributes' => array('href' => $url, 'class' => 'user-mention-link'),
+ 'name' => 'a',
+ 'text' => '@' . $username,
+ 'attributes' => array(
+ 'href' => $url,
+ 'class' => 'user-mention-link',
+ 'title' => $user['name'] ?: $user['username'],
+ ),
),
);
}
@@ -125,7 +130,10 @@ class Markdown extends Parsedown
array(
'token' => $token,
'task_id' => $task_id,
- )
+ ),
+ false,
+ '',
+ true
);
}
diff --git a/app/Core/Paginator.php b/app/Core/Paginator.php
index cfe89938..9075a713 100644
--- a/app/Core/Paginator.php
+++ b/app/Core/Paginator.php
@@ -232,6 +232,17 @@ class Paginator
}
/**
+ * Get the number of current page
+ *
+ * @access public
+ * @return integer
+ */
+ public function getPage()
+ {
+ return $this->page;
+ }
+
+ /**
* Set the default column order
*
* @access public
@@ -271,6 +282,16 @@ class Paginator
}
/**
+ * Get the maximum number of items per page.
+ *
+ * @return int
+ */
+ public function getMax()
+ {
+ return $this->limit;
+ }
+
+ /**
* Return true if the collection is empty
*
* @access public
@@ -353,7 +374,9 @@ class Paginator
'&larr; '.t('Previous'),
$this->controller,
$this->action,
- $this->getUrlParams($this->page - 1, $this->order, $this->direction)
+ $this->getUrlParams($this->page - 1, $this->order, $this->direction),
+ false,
+ 'js-modal-replace'
);
} else {
$html .= '&larr; '.t('Previous');
@@ -379,7 +402,9 @@ class Paginator
t('Next').' &rarr;',
$this->controller,
$this->action,
- $this->getUrlParams($this->page + 1, $this->order, $this->direction)
+ $this->getUrlParams($this->page + 1, $this->order, $this->direction),
+ false,
+ 'js-modal-replace'
);
} else {
$html .= t('Next').' &rarr;';
@@ -391,6 +416,17 @@ class Paginator
}
/**
+ * Generate the page showing.
+ *
+ * @access public
+ * @return string
+ */
+ public function generatPageShowing()
+ {
+ return '<span class="pagination-showing">'.t('Showing %d-%d of %d', (($this->getPage() - 1) * $this->getMax() + 1), min($this->getTotal(), $this->getPage() * $this->getMax()), $this->getTotal()).'</span>';
+ }
+
+ /**
* Return true if there is no pagination to show
*
* @access public
@@ -413,6 +449,7 @@ class Paginator
if (! $this->hasNothingtoShow()) {
$html .= '<div class="pagination">';
+ $html .= $this->generatPageShowing();
$html .= $this->generatePreviousLink();
$html .= $this->generateNextLink();
$html .= '</div>';
@@ -453,7 +490,9 @@ class Paginator
$label,
$this->controller,
$this->action,
- $this->getUrlParams($this->page, $column, $direction)
+ $this->getUrlParams($this->page, $column, $direction),
+ false,
+ 'js-modal-replace'
);
}
}
diff --git a/app/Core/Plugin/Base.php b/app/Core/Plugin/Base.php
index 9d8167a9..e0b5954a 100644
--- a/app/Core/Plugin/Base.php
+++ b/app/Core/Plugin/Base.php
@@ -131,4 +131,17 @@ abstract class Base extends \Kanboard\Core\Base
{
return '';
}
+
+ /**
+ * Get application compatibility version
+ *
+ * Examples: >=1.0.36, 1.0.37, APP_VERSION
+ *
+ * @access public
+ * @return string
+ */
+ public function getCompatibleVersion()
+ {
+ return APP_VERSION;
+ }
}
diff --git a/app/Core/Plugin/Directory.php b/app/Core/Plugin/Directory.php
index 21f11ca9..dc32e655 100644
--- a/app/Core/Plugin/Directory.php
+++ b/app/Core/Plugin/Directory.php
@@ -36,11 +36,7 @@ class Directory extends BaseCore
*/
public function isCompatible(array $plugin, $appVersion = APP_VERSION)
{
- if (strpos($appVersion, 'master') !== false) {
- return true;
- }
-
- return $plugin['compatible_version'] === $appVersion;
+ return Version::isCompatible($plugin['compatible_version'], $appVersion);
}
/**
diff --git a/app/Core/Plugin/Hook.php b/app/Core/Plugin/Hook.php
index ade69150..ca197937 100644
--- a/app/Core/Plugin/Hook.php
+++ b/app/Core/Plugin/Hook.php
@@ -96,4 +96,21 @@ class Hook
return null;
}
+
+ /**
+ * Hook with reference
+ *
+ * @access public
+ * @param string $hook
+ * @param mixed $param
+ * @return mixed
+ */
+ public function reference($hook, &$param)
+ {
+ foreach ($this->getListeners($hook) as $listener) {
+ $listener($param);
+ }
+
+ return $param;
+ }
}
diff --git a/app/Core/Plugin/Installer.php b/app/Core/Plugin/Installer.php
index 48c4d978..b3618aeb 100644
--- a/app/Core/Plugin/Installer.php
+++ b/app/Core/Plugin/Installer.php
@@ -2,9 +2,8 @@
namespace Kanboard\Core\Plugin;
-use RecursiveDirectoryIterator;
-use RecursiveIteratorIterator;
use ZipArchive;
+use Kanboard\Core\Tool;
/**
* Class Installer
@@ -64,7 +63,7 @@ class Installer extends \Kanboard\Core\Base
throw new PluginInstallerException(e('You don\'t have the permission to remove this plugin.'));
}
- $this->removeAllDirectories($pluginFolder);
+ Tool::removeAllFiles($pluginFolder);
}
/**
@@ -137,26 +136,4 @@ class Installer extends \Kanboard\Core\Base
unlink($zip->filename);
$zip->close();
}
-
- /**
- * Remove recursively a directory
- *
- * @access protected
- * @param string $directory
- */
- protected function removeAllDirectories($directory)
- {
- $it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS);
- $files = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);
-
- foreach ($files as $file) {
- if ($file->isDir()) {
- rmdir($file->getRealPath());
- } else {
- unlink($file->getRealPath());
- }
- }
-
- rmdir($directory);
- }
}
diff --git a/app/Core/Plugin/Loader.php b/app/Core/Plugin/Loader.php
index f2f6add7..38f41d39 100644
--- a/app/Core/Plugin/Loader.php
+++ b/app/Core/Plugin/Loader.php
@@ -4,6 +4,7 @@ namespace Kanboard\Core\Plugin;
use Composer\Autoload\ClassLoader;
use DirectoryIterator;
+use Exception;
use LogicException;
use Kanboard\Core\Tool;
@@ -22,6 +23,7 @@ class Loader extends \Kanboard\Core\Base
* @var array
*/
protected $plugins = array();
+ protected $incompatiblePlugins = array();
/**
* Get list of loaded plugins
@@ -35,6 +37,17 @@ class Loader extends \Kanboard\Core\Base
}
/**
+ * Get list of not compatible plugins
+ *
+ * @access public
+ * @return Base[]
+ */
+ public function getIncompatiblePlugins()
+ {
+ return $this->incompatiblePlugins;
+ }
+
+ /**
* Scan plugin folder and load plugins
*
* @access public
@@ -51,8 +64,7 @@ class Loader extends \Kanboard\Core\Base
foreach ($dir as $fileInfo) {
if ($fileInfo->isDir() && substr($fileInfo->getFilename(), 0, 1) !== '.') {
$pluginName = $fileInfo->getFilename();
- $this->loadSchema($pluginName);
- $this->initializePlugin($pluginName, $this->loadPlugin($pluginName));
+ $this->initializePlugin($pluginName);
}
}
}
@@ -85,7 +97,7 @@ class Loader extends \Kanboard\Core\Base
$className = '\Kanboard\Plugin\\'.$pluginName.'\\Plugin';
if (! class_exists($className)) {
- throw new LogicException('Unable to load this plugin class '.$className);
+ throw new LogicException('Unable to load this plugin class: '.$className);
}
return new $className($this->container);
@@ -96,18 +108,30 @@ class Loader extends \Kanboard\Core\Base
*
* @access public
* @param string $pluginName
- * @param Base $plugin
*/
- public function initializePlugin($pluginName, Base $plugin)
+ public function initializePlugin($pluginName)
{
- if (method_exists($plugin, 'onStartup')) {
- $this->dispatcher->addListener('app.bootstrap', array($plugin, 'onStartup'));
- }
+ try {
+ $plugin = $this->loadPlugin($pluginName);
- Tool::buildDIC($this->container, $plugin->getClasses());
- Tool::buildDICHelpers($this->container, $plugin->getHelpers());
+ if (Version::isCompatible($plugin->getCompatibleVersion(), APP_VERSION)) {
+ $this->loadSchema($pluginName);
+
+ if (method_exists($plugin, 'onStartup')) {
+ $this->dispatcher->addListener('app.bootstrap', array($plugin, 'onStartup'));
+ }
- $plugin->initialize();
- $this->plugins[$pluginName] = $plugin;
+ Tool::buildDIC($this->container, $plugin->getClasses());
+ Tool::buildDICHelpers($this->container, $plugin->getHelpers());
+
+ $plugin->initialize();
+ $this->plugins[$pluginName] = $plugin;
+ } else {
+ $this->incompatiblePlugins[$pluginName] = $plugin;
+ $this->logger->error($pluginName.' is not compatible with this version');
+ }
+ } catch (Exception $e) {
+ $this->logger->critical($pluginName.': '.$e->getMessage());
+ }
}
}
diff --git a/app/Core/Plugin/PluginException.php b/app/Core/Plugin/PluginException.php
new file mode 100644
index 00000000..fae7de35
--- /dev/null
+++ b/app/Core/Plugin/PluginException.php
@@ -0,0 +1,15 @@
+<?php
+
+namespace Kanboard\Core\Plugin;
+
+use Exception;
+
+/**
+ * Class PluginException
+ *
+ * @package Kanboard\Core\Plugin
+ * @author Frederic Guillot
+ */
+class PluginException extends Exception
+{
+}
diff --git a/app/Core/Plugin/PluginInstallerException.php b/app/Core/Plugin/PluginInstallerException.php
index 7d356c9b..31745f22 100644
--- a/app/Core/Plugin/PluginInstallerException.php
+++ b/app/Core/Plugin/PluginInstallerException.php
@@ -2,14 +2,12 @@
namespace Kanboard\Core\Plugin;
-use Exception;
-
/**
* Class PluginInstallerException
*
* @package Kanboard\Core\Plugin
* @author Frederic Guillot
*/
-class PluginInstallerException extends Exception
+class PluginInstallerException extends PluginException
{
}
diff --git a/app/Core/Plugin/Version.php b/app/Core/Plugin/Version.php
new file mode 100644
index 00000000..ba5e0443
--- /dev/null
+++ b/app/Core/Plugin/Version.php
@@ -0,0 +1,38 @@
+<?php
+
+namespace Kanboard\Core\Plugin;
+
+/**
+ * Class Version
+ *
+ * @package Kanboard\Core\Plugin
+ * @author Frederic Guillot
+ */
+class Version
+{
+ /**
+ * Check plugin version compatibility with application version
+ *
+ * @param string $pluginCompatibleVersion
+ * @param string $appVersion
+ * @return bool
+ */
+ public static function isCompatible($pluginCompatibleVersion, $appVersion = APP_VERSION)
+ {
+ if (strpos($appVersion, 'master') !== false) {
+ return true;
+ }
+
+ $appVersion = str_replace('v', '', $appVersion);
+ $pluginCompatibleVersion = str_replace('v', '', $pluginCompatibleVersion);
+
+ foreach (array('>=', '>', '<=', '<') as $operator) {
+ if (strpos($pluginCompatibleVersion, $operator) === 0) {
+ $pluginVersion = substr($pluginCompatibleVersion, strlen($operator));
+ return version_compare($appVersion, $pluginVersion, $operator);
+ }
+ }
+
+ return $pluginCompatibleVersion === $appVersion;
+ }
+}
diff --git a/app/Core/Queue/JobHandler.php b/app/Core/Queue/JobHandler.php
index 7ca36328..11c1fb69 100644
--- a/app/Core/Queue/JobHandler.php
+++ b/app/Core/Queue/JobHandler.php
@@ -2,6 +2,7 @@
namespace Kanboard\Core\Queue;
+use Exception;
use Kanboard\Core\Base;
use Kanboard\Job\BaseJob;
use SimpleQueue\Job;
@@ -39,16 +40,23 @@ class JobHandler extends Base
public function executeJob(Job $job)
{
$payload = $job->getBody();
- $className = $payload['class'];
- $this->memoryCache->flush();
- $this->prepareJobSession($payload['user_id']);
- if (DEBUG) {
- $this->logger->debug(__METHOD__.' Received job => '.$className.' ('.getmypid().')');
- }
+ try {
+ $className = $payload['class'];
+ $this->prepareJobSession($payload['user_id']);
+ $this->prepareJobEnvironment();
+
+ if (DEBUG) {
+ $this->logger->debug(__METHOD__.' Received job => '.$className.' ('.getmypid().')');
+ $this->logger->debug(__METHOD__.' => '.json_encode($payload));
+ }
- $worker = new $className($this->container);
- call_user_func_array(array($worker, 'execute'), $payload['params']);
+ $worker = new $className($this->container);
+ call_user_func_array(array($worker, 'execute'), $payload['params']);
+ } catch (Exception $e) {
+ $this->logger->error(__METHOD__.': Error during job execution: '.$e->getMessage());
+ $this->logger->error(__METHOD__ .' => '.json_encode($payload));
+ }
}
/**
@@ -67,4 +75,16 @@ class JobHandler extends Base
$this->userSession->initialize($user);
}
}
+
+ /**
+ * Flush in-memory caching and specific events
+ *
+ * @access protected
+ */
+ protected function prepareJobEnvironment()
+ {
+ $this->memoryCache->flush();
+ $this->actionManager->removeEvents();
+ $this->dispatcher->dispatch('app.bootstrap');
+ }
}
diff --git a/app/Core/Queue/QueueManager.php b/app/Core/Queue/QueueManager.php
index f34cb220..1d7c2d1e 100644
--- a/app/Core/Queue/QueueManager.php
+++ b/app/Core/Queue/QueueManager.php
@@ -42,9 +42,13 @@ class QueueManager extends Base
*/
public function push(BaseJob $job)
{
+ $jobClassName = get_class($job);
+
if ($this->queue !== null) {
+ $this->logger->debug(__METHOD__.': Job pushed in queue: '.$jobClassName);
$this->queue->push(JobHandler::getInstance($this->container)->serializeJob($job));
} else {
+ $this->logger->debug(__METHOD__.': Job executed synchronously: '.$jobClassName);
call_user_func_array(array($job, 'execute'), $job->getJobParams());
}
@@ -60,7 +64,7 @@ class QueueManager extends Base
public function listen()
{
if ($this->queue === null) {
- throw new LogicException('No Queue Driver defined!');
+ throw new LogicException('No queue driver defined or unable to connect to broker!');
}
while ($job = $this->queue->pull()) {
diff --git a/app/Core/Security/Role.php b/app/Core/Security/Role.php
index cb45a8af..c16d4094 100644
--- a/app/Core/Security/Role.php
+++ b/app/Core/Security/Role.php
@@ -50,6 +50,18 @@ class Role
}
/**
+ * Check if the given role is custom or not
+ *
+ * @access public
+ * @param string $role
+ * @return bool
+ */
+ public function isCustomProjectRole($role)
+ {
+ return ! empty($role) && $role !== self::PROJECT_MANAGER && $role !== self::PROJECT_MEMBER && $role !== self::PROJECT_VIEWER;
+ }
+
+ /**
* Get role name
*
* @access public
diff --git a/app/Core/Session/SessionStorage.php b/app/Core/Session/SessionStorage.php
index 9e93602c..e6478d8d 100644
--- a/app/Core/Session/SessionStorage.php
+++ b/app/Core/Session/SessionStorage.php
@@ -19,6 +19,7 @@ namespace Kanboard\Core\Session;
* @property bool $hasSubtaskInProgress
* @property bool $hasRememberMe
* @property bool $boardCollapsed
+ * @property string $scope
* @property bool $twoFactorBeforeCodeCalled
* @property string $twoFactorSecret
* @property string $oauthState
diff --git a/app/Core/Tool.php b/app/Core/Tool.php
index bfa6c955..6e457641 100644
--- a/app/Core/Tool.php
+++ b/app/Core/Tool.php
@@ -3,6 +3,8 @@
namespace Kanboard\Core;
use Pimple\Container;
+use RecursiveDirectoryIterator;
+use RecursiveIteratorIterator;
/**
* Tool class
@@ -13,7 +15,33 @@ use Pimple\Container;
class Tool
{
/**
- * Build dependency injection container from an array
+ * Remove recursively a directory
+ *
+ * @static
+ * @access public
+ * @param string $directory
+ * @param bool $removeDirectory
+ */
+ public static function removeAllFiles($directory, $removeDirectory = true)
+ {
+ $it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS);
+ $files = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);
+
+ foreach ($files as $file) {
+ if ($file->isDir()) {
+ rmdir($file->getRealPath());
+ } else {
+ unlink($file->getRealPath());
+ }
+ }
+
+ if ($removeDirectory) {
+ rmdir($directory);
+ }
+ }
+
+ /**
+ * Build dependency injection containers from an array
*
* @static
* @access public
@@ -36,6 +64,29 @@ class Tool
}
/**
+ * Build dependency injection container from an array
+ *
+ * @static
+ * @access public
+ * @param Container $container
+ * @param array $namespaces
+ * @return Container
+ */
+ public static function buildFactories(Container $container, array $namespaces)
+ {
+ foreach ($namespaces as $namespace => $classes) {
+ foreach ($classes as $name) {
+ $class = '\\Kanboard\\'.$namespace.'\\'.$name;
+ $container[lcfirst($name)] = $container->factory(function ($c) use ($class) {
+ return new $class($c);
+ });
+ }
+ }
+
+ return $container;
+ }
+
+ /**
* Build dependency injection container for custom helpers from an array
*
* @static
diff --git a/app/Core/Translator.php b/app/Core/Translator.php
index 113c0dc6..ac2e2aae 100644
--- a/app/Core/Translator.php
+++ b/app/Core/Translator.php
@@ -11,13 +11,6 @@ namespace Kanboard\Core;
class Translator
{
/**
- * Locale path
- *
- * @var string
- */
- const PATH = 'app/Locale';
-
- /**
* Locale
*
* @static
@@ -171,9 +164,13 @@ class Translator
* @param string $language Locale code: fr_FR
* @param string $path Locale folder
*/
- public static function load($language, $path = self::PATH)
+ public static function load($language, $path = '')
{
- $filename = $path.DIRECTORY_SEPARATOR.$language.DIRECTORY_SEPARATOR.'translations.php';
+ if ($path === '') {
+ $path = self::getDefaultFolder();
+ }
+
+ $filename = implode(DIRECTORY_SEPARATOR, array($path, $language, 'translations.php'));
if (file_exists($filename)) {
self::$locales = array_merge(self::$locales, require($filename));
@@ -190,4 +187,15 @@ class Translator
{
self::$locales = array();
}
+
+ /**
+ * Get default locales folder
+ *
+ * @access public
+ * @return string
+ */
+ public static function getDefaultFolder()
+ {
+ return implode(DIRECTORY_SEPARATOR, array(__DIR__, '..', 'Locale'));
+ }
}
diff --git a/app/Core/User/UserSession.php b/app/Core/User/UserSession.php
index 9c63f07a..7917b223 100644
--- a/app/Core/User/UserSession.php
+++ b/app/Core/User/UserSession.php
@@ -179,50 +179,4 @@ class UserSession extends Base
{
$this->sessionStorage->filters[$project_id] = $filters;
}
-
- /**
- * Is board collapsed or expanded
- *
- * @access public
- * @param integer $project_id
- * @return boolean
- */
- public function isBoardCollapsed($project_id)
- {
- return ! empty($this->sessionStorage->boardCollapsed[$project_id]) ? $this->sessionStorage->boardCollapsed[$project_id] : false;
- }
-
- /**
- * Set board display mode
- *
- * @access public
- * @param integer $project_id
- * @param boolean $is_collapsed
- */
- public function setBoardDisplayMode($project_id, $is_collapsed)
- {
- $this->sessionStorage->boardCollapsed[$project_id] = $is_collapsed;
- }
-
- /**
- * Set comments sorting
- *
- * @access public
- * @param string $order
- */
- public function setCommentSorting($order)
- {
- $this->sessionStorage->commentSorting = $order;
- }
-
- /**
- * Get comments sorting direction
- *
- * @access public
- * @return string
- */
- public function getCommentSorting()
- {
- return empty($this->sessionStorage->commentSorting) ? 'ASC' : $this->sessionStorage->commentSorting;
- }
}