diff options
Diffstat (limited to 'app')
90 files changed, 1686 insertions, 470 deletions
diff --git a/app/Api/Middleware/AuthenticationMiddleware.php b/app/Api/Middleware/AuthenticationMiddleware.php index 8e309593..c4fa874a 100644 --- a/app/Api/Middleware/AuthenticationMiddleware.php +++ b/app/Api/Middleware/AuthenticationMiddleware.php @@ -28,6 +28,7 @@ class AuthenticationMiddleware extends Base implements MiddlewareInterface public function execute($username, $password, $procedureName) { $this->dispatcher->dispatch('app.bootstrap'); + $this->sessionStorage->scope = 'API'; if ($this->isUserAuthenticated($username, $password)) { $this->userSession->initialize($this->userModel->getByUsername($username)); diff --git a/app/Auth/ApiAccessTokenAuth.php b/app/Auth/ApiAccessTokenAuth.php new file mode 100644 index 00000000..12ab21a7 --- /dev/null +++ b/app/Auth/ApiAccessTokenAuth.php @@ -0,0 +1,119 @@ +<?php + +namespace Kanboard\Auth; + +use Kanboard\Core\Base; +use Kanboard\Core\Security\PasswordAuthenticationProviderInterface; +use Kanboard\Model\UserModel; +use Kanboard\User\DatabaseUserProvider; + +/** + * API Access Token Authentication Provider + * + * @package Kanboard\Auth + * @author Frederic Guillot + */ +class ApiAccessTokenAuth extends Base implements PasswordAuthenticationProviderInterface +{ + /** + * User properties + * + * @access protected + * @var array + */ + protected $userInfo = array(); + + /** + * Username + * + * @access protected + * @var string + */ + protected $username = ''; + + /** + * Password + * + * @access protected + * @var string + */ + protected $password = ''; + + /** + * Get authentication provider name + * + * @access public + * @return string + */ + public function getName() + { + return 'API Access Token'; + } + + /** + * Authenticate the user + * + * @access public + * @return boolean + */ + public function authenticate() + { + if (! isset($this->sessionStorage->scope) || $this->sessionStorage->scope !== 'API') { + $this->logger->debug(__METHOD__.': Authentication provider skipped because invalid scope'); + return false; + } + + $user = $this->db + ->table(UserModel::TABLE) + ->columns('id', 'password') + ->eq('username', $this->username) + ->eq('api_access_token', $this->password) + ->notNull('api_access_token') + ->eq('is_active', 1) + ->findOne(); + + if (! empty($user)) { + $this->userInfo = $user; + return true; + } + + return false; + } + + /** + * Get user object + * + * @access public + * @return \Kanboard\User\DatabaseUserProvider + */ + public function getUser() + { + if (empty($this->userInfo)) { + return null; + } + + return new DatabaseUserProvider($this->userInfo); + } + + /** + * Set username + * + * @access public + * @param string $username + */ + public function setUsername($username) + { + $this->username = $username; + } + + /** + * Set password + * + * @access public + * @param string $password + */ + public function setPassword($password) + { + $this->password = $password; + } +} diff --git a/app/Auth/DatabaseAuth.php b/app/Auth/DatabaseAuth.php index ecb42c17..84a1e019 100644 --- a/app/Auth/DatabaseAuth.php +++ b/app/Auth/DatabaseAuth.php @@ -11,7 +11,7 @@ use Kanboard\User\DatabaseUserProvider; /** * Database Authentication Provider * - * @package auth + * @package Kanboard\Auth * @author Frederic Guillot */ class DatabaseAuth extends Base implements PasswordAuthenticationProviderInterface, SessionCheckProviderInterface diff --git a/app/Auth/LdapAuth.php b/app/Auth/LdapAuth.php index a8dcfcb6..05ffbebf 100644 --- a/app/Auth/LdapAuth.php +++ b/app/Auth/LdapAuth.php @@ -12,7 +12,7 @@ use Kanboard\Core\Security\PasswordAuthenticationProviderInterface; /** * LDAP Authentication Provider * - * @package auth + * @package Kanboard\Auth * @author Frederic Guillot */ class LdapAuth extends Base implements PasswordAuthenticationProviderInterface diff --git a/app/Auth/RememberMeAuth.php b/app/Auth/RememberMeAuth.php index 5d0a8b2e..e0f4ceb6 100644 --- a/app/Auth/RememberMeAuth.php +++ b/app/Auth/RememberMeAuth.php @@ -7,9 +7,9 @@ use Kanboard\Core\Security\PreAuthenticationProviderInterface; use Kanboard\User\DatabaseUserProvider; /** - * Rember Me Cookie Authentication Provider + * RememberMe Cookie Authentication Provider * - * @package auth + * @package Kanboard\Auth * @author Frederic Guillot */ class RememberMeAuth extends Base implements PreAuthenticationProviderInterface diff --git a/app/Auth/ReverseProxyAuth.php b/app/Auth/ReverseProxyAuth.php index fdf936b1..02afc302 100644 --- a/app/Auth/ReverseProxyAuth.php +++ b/app/Auth/ReverseProxyAuth.php @@ -10,7 +10,7 @@ use Kanboard\User\ReverseProxyUserProvider; /** * Reverse-Proxy Authentication Provider * - * @package auth + * @package Kanboard\Auth * @author Frederic Guillot */ class ReverseProxyAuth extends Base implements PreAuthenticationProviderInterface, SessionCheckProviderInterface diff --git a/app/Auth/TotpAuth.php b/app/Auth/TotpAuth.php index 8e1ebe35..abfb2168 100644 --- a/app/Auth/TotpAuth.php +++ b/app/Auth/TotpAuth.php @@ -11,7 +11,7 @@ use Kanboard\Core\Security\PostAuthenticationProviderInterface; /** * TOTP Authentication Provider * - * @package auth + * @package Kanboard\Auth * @author Frederic Guillot */ class TotpAuth extends Base implements PostAuthenticationProviderInterface diff --git a/app/Console/DatabaseMigrationCommand.php b/app/Console/DatabaseMigrationCommand.php new file mode 100644 index 00000000..252d4369 --- /dev/null +++ b/app/Console/DatabaseMigrationCommand.php @@ -0,0 +1,23 @@ +<?php + +namespace Kanboard\Console; + +use Kanboard\ServiceProvider\DatabaseProvider; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class DatabaseMigrationCommand extends DatabaseVersionCommand +{ + protected function configure() + { + $this + ->setName('db:migrate') + ->setDescription('Execute SQL migrations'); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + parent::execute($input, $output); + DatabaseProvider::runMigrations($this->container['db']); + } +} diff --git a/app/Console/DatabaseVersionCommand.php b/app/Console/DatabaseVersionCommand.php new file mode 100644 index 00000000..5b1f1ed1 --- /dev/null +++ b/app/Console/DatabaseVersionCommand.php @@ -0,0 +1,23 @@ +<?php + +namespace Kanboard\Console; + +use Kanboard\ServiceProvider\DatabaseProvider; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class DatabaseVersionCommand extends BaseCommand +{ + protected function configure() + { + $this + ->setName('db:version') + ->setDescription('Show database schema version'); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $output->writeln('<info>Current version: '.DatabaseProvider::getSchemaVersion($this->container['db']).'</info>'); + $output->writeln('<info>Last version: '.\Schema\VERSION.'</info>'); + } +} diff --git a/app/Controller/CommentController.php b/app/Controller/CommentController.php index c61a0602..526bd2bf 100644 --- a/app/Controller/CommentController.php +++ b/app/Controller/CommentController.php @@ -48,6 +48,7 @@ class CommentController extends BaseController */ public function create(array $values = array(), array $errors = array()) { + $project = $this->getProject(); $task = $this->getTask(); if (empty($values)) { @@ -57,10 +58,13 @@ class CommentController extends BaseController ); } - $this->response->html($this->template->render('comment/create', array( + $values['project_id'] = $task['project_id']; + + $this->response->html($this->helper->layout->task('comment/create', array( 'values' => $values, 'errors' => $errors, 'task' => $task, + 'project' => $project, ))); } @@ -103,8 +107,14 @@ class CommentController extends BaseController $task = $this->getTask(); $comment = $this->getComment(); + if (empty($values)) { + $values = $comment; + } + + $values['project_id'] = $task['project_id']; + $this->response->html($this->template->render('comment/edit', array( - 'values' => empty($values) ? $comment : $values, + 'values' => $values, 'errors' => $errors, 'comment' => $comment, 'task' => $task, diff --git a/app/Controller/TaskAjaxController.php b/app/Controller/TaskAjaxController.php index f9feff15..609dd23c 100644 --- a/app/Controller/TaskAjaxController.php +++ b/app/Controller/TaskAjaxController.php @@ -5,8 +5,12 @@ namespace Kanboard\Controller; use Kanboard\Filter\TaskIdExclusionFilter; use Kanboard\Filter\TaskIdFilter; use Kanboard\Filter\TaskProjectsFilter; +use Kanboard\Filter\TaskStartsWithIdFilter; +use Kanboard\Filter\TaskStatusFilter; use Kanboard\Filter\TaskTitleFilter; use Kanboard\Formatter\TaskAutoCompleteFormatter; +use Kanboard\Formatter\TaskSuggestMenuFormatter; +use Kanboard\Model\TaskModel; /** * Task Ajax Controller @@ -19,7 +23,6 @@ class TaskAjaxController extends BaseController /** * Task auto-completion (Ajax) * - * @access public */ public function autocomplete() { @@ -46,4 +49,24 @@ class TaskAjaxController extends BaseController $this->response->json($filter->format(new TaskAutoCompleteFormatter($this->container))); } } + + /** + * Task ID suggest menu + */ + public function suggest() + { + $taskId = $this->request->getIntegerParam('search'); + $projectIds = $this->projectPermissionModel->getActiveProjectIds($this->userSession->getId()); + + if (empty($projectIds)) { + $this->response->json(array()); + } else { + $filter = $this->taskQuery + ->withFilter(new TaskProjectsFilter($projectIds)) + ->withFilter(new TaskStatusFilter(TaskModel::STATUS_OPEN)) + ->withFilter(new TaskStartsWithIdFilter($taskId)); + + $this->response->json($filter->format(new TaskSuggestMenuFormatter($this->container))); + } + } } diff --git a/app/Controller/UserAjaxController.php b/app/Controller/UserAjaxController.php index ed180471..d93bfe9a 100644 --- a/app/Controller/UserAjaxController.php +++ b/app/Controller/UserAjaxController.php @@ -4,6 +4,7 @@ namespace Kanboard\Controller; use Kanboard\Filter\UserNameFilter; use Kanboard\Formatter\UserAutoCompleteFormatter; +use Kanboard\Formatter\UserMentionFormatter; use Kanboard\Model\UserModel; /** @@ -35,9 +36,14 @@ class UserAjaxController extends BaseController public function mention() { $project_id = $this->request->getStringParam('project_id'); - $query = $this->request->getStringParam('q'); + $query = $this->request->getStringParam('search'); $users = $this->projectPermissionModel->findUsernames($project_id, $query); - $this->response->json($users); + + $this->response->json( + UserMentionFormatter::getInstance($this->container) + ->withUsers($users) + ->format() + ); } /** diff --git a/app/Controller/UserApiAccessController.php b/app/Controller/UserApiAccessController.php new file mode 100644 index 00000000..e03514d5 --- /dev/null +++ b/app/Controller/UserApiAccessController.php @@ -0,0 +1,50 @@ +<?php + +namespace Kanboard\Controller; + +use Kanboard\Core\Security\Token; + +/** + * Class UserApiAccessController + * + * @package Kanboard\Controller + * @author Frederic Guillot + */ +class UserApiAccessController extends BaseController +{ + public function show() + { + $user = $this->getUser(); + + return $this->response->html($this->helper->layout->user('user_api_access/show', array( + 'user' => $user, + 'title' => t('API User Access'), + ))); + } + + public function generate() + { + $user = $this->getUser(); + $this->checkCSRFParam(); + + $this->userModel->update(array( + 'id' => $user['id'], + 'api_access_token' => Token::getToken(), + )); + + $this->response->redirect($this->helper->url->to('UserApiAccessController', 'show', array('user_id' => $user['id']))); + } + + public function remove() + { + $user = $this->getUser(); + $this->checkCSRFParam(); + + $this->userModel->update(array( + 'id' => $user['id'], + 'api_access_token' => null, + )); + + $this->response->redirect($this->helper->url->to('UserApiAccessController', 'show', array('user_id' => $user['id']))); + } +}
\ No newline at end of file diff --git a/app/Core/Base.php b/app/Core/Base.php index 3dbf47f9..e7ccafaa 100644 --- a/app/Core/Base.php +++ b/app/Core/Base.php @@ -126,7 +126,6 @@ use Pimple\Container; * @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 @@ -178,6 +177,7 @@ use Pimple\Container; * @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 diff --git a/app/Core/Helper.php b/app/Core/Helper.php index b5c560af..9660c348 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\AvatarHelper $avatar * @property \Kanboard\Helper\BoardHelper $board * @property \Kanboard\Helper\CalendarHelper $calendar * @property \Kanboard\Helper\DateHelper $dt diff --git a/app/Core/Markdown.php b/app/Core/Markdown.php index b5abe5ed..799aefb4 100644 --- a/app/Core/Markdown.php +++ b/app/Core/Markdown.php @@ -86,7 +86,7 @@ class Markdown extends Parsedown */ protected function inlineUserLink(array $Excerpt) { - if (! $this->isPublicLink && preg_match('/^@([^\s]+)/', $Excerpt['text'], $matches)) { + if (! $this->isPublicLink && preg_match('/^@([^\s,!.:?]+)/', $Excerpt['text'], $matches)) { $user_id = $this->container['userModel']->getIdByUsername($matches[1]); if (! empty($user_id)) { @@ -125,7 +125,10 @@ class Markdown extends Parsedown array( 'token' => $token, 'task_id' => $task_id, - ) + ), + false, + '', + true ); } 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/Event/GenericEvent.php b/app/Event/GenericEvent.php index 94a51479..e87d9481 100644 --- a/app/Event/GenericEvent.php +++ b/app/Event/GenericEvent.php @@ -14,6 +14,28 @@ class GenericEvent extends BaseEvent implements ArrayAccess $this->container = $values; } + public function getTaskId() + { + if (isset($this->container['task']['id'])) { + return $this->container['task']['id']; + } + + if (isset($this->container['task_id'])) { + return $this->container['task_id']; + } + + return null; + } + + public function getProjectId() + { + if (isset($this->container['task']['project_id'])) { + return $this->container['task']['project_id']; + } + + return null; + } + public function getAll() { return $this->container; diff --git a/app/Event/ProjectFileEvent.php b/app/Event/ProjectFileEvent.php index 5d57e463..e1d29c48 100644 --- a/app/Event/ProjectFileEvent.php +++ b/app/Event/ProjectFileEvent.php @@ -4,4 +4,12 @@ namespace Kanboard\Event; class ProjectFileEvent extends GenericEvent { + public function getProjectId() + { + if (isset($this->container['file']['project_id'])) { + return $this->container['file']['project_id']; + } + + return null; + } } diff --git a/app/Filter/TaskStartsWithIdFilter.php b/app/Filter/TaskStartsWithIdFilter.php new file mode 100644 index 00000000..8b7cc678 --- /dev/null +++ b/app/Filter/TaskStartsWithIdFilter.php @@ -0,0 +1,38 @@ +<?php + +namespace Kanboard\Filter; + +use Kanboard\Core\Filter\FilterInterface; +use Kanboard\Model\TaskModel; + +/** + * Class TaskIdSearchFilter + * + * @package Kanboard\Filter + * @author Frederic Guillot + */ +class TaskStartsWithIdFilter extends BaseFilter implements FilterInterface +{ + /** + * Get search attribute + * + * @access public + * @return string[] + */ + public function getAttributes() + { + return array('starts_with_id'); + } + + /** + * Apply filter + * + * @access public + * @return FilterInterface + */ + public function apply() + { + $this->query->ilike('CAST('.TaskModel::TABLE.'.id AS CHAR(8))', $this->value.'%'); + return $this; + } +} diff --git a/app/Formatter/BaseFormatter.php b/app/Formatter/BaseFormatter.php index 89c48437..0d62628e 100644 --- a/app/Formatter/BaseFormatter.php +++ b/app/Formatter/BaseFormatter.php @@ -4,7 +4,6 @@ namespace Kanboard\Formatter; use Kanboard\Core\Base; use PicoDb\Table; -use Pimple\Container; /** * Class BaseFormatter @@ -23,19 +22,6 @@ abstract class BaseFormatter extends Base protected $query; /** - * Get object instance - * - * @static - * @access public - * @param Container $container - * @return static - */ - public static function getInstance(Container $container) - { - return new static($container); - } - - /** * Set query * * @access public diff --git a/app/Formatter/BaseTaskCalendarFormatter.php b/app/Formatter/BaseTaskCalendarFormatter.php index 8fab3e9a..3d9ead4d 100644 --- a/app/Formatter/BaseTaskCalendarFormatter.php +++ b/app/Formatter/BaseTaskCalendarFormatter.php @@ -2,8 +2,6 @@ namespace Kanboard\Formatter; -use Kanboard\Core\Filter\FormatterInterface; - /** * Common class to handle calendar events * @@ -34,7 +32,7 @@ abstract class BaseTaskCalendarFormatter extends BaseFormatter * @access public * @param string $start_column Column name for the start date * @param string $end_column Column name for the end date - * @return FormatterInterface + * @return $this */ public function setColumns($start_column, $end_column = '') { diff --git a/app/Formatter/TaskAutoCompleteFormatter.php b/app/Formatter/TaskAutoCompleteFormatter.php index 2d9f7341..3a4f1e1a 100644 --- a/app/Formatter/TaskAutoCompleteFormatter.php +++ b/app/Formatter/TaskAutoCompleteFormatter.php @@ -14,6 +14,20 @@ use Kanboard\Model\TaskModel; */ class TaskAutoCompleteFormatter extends BaseFormatter implements FormatterInterface { + protected $limit = 25; + + /** + * Limit number of results + * + * @param $limit + * @return $this + */ + public function withLimit($limit) + { + $this->limit = $limit; + return $this; + } + /** * Apply formatter * @@ -22,11 +36,15 @@ class TaskAutoCompleteFormatter extends BaseFormatter implements FormatterInterf */ public function format() { - $tasks = $this->query->columns( - TaskModel::TABLE.'.id', - TaskModel::TABLE.'.title', - ProjectModel::TABLE.'.name AS project_name' - )->asc(TaskModel::TABLE.'.id')->findAll(); + $tasks = $this->query + ->columns( + TaskModel::TABLE.'.id', + TaskModel::TABLE.'.title', + ProjectModel::TABLE.'.name AS project_name' + ) + ->asc(TaskModel::TABLE.'.id') + ->limit($this->limit) + ->findAll(); foreach ($tasks as &$task) { $task['value'] = $task['title']; diff --git a/app/Formatter/TaskSuggestMenuFormatter.php b/app/Formatter/TaskSuggestMenuFormatter.php new file mode 100644 index 00000000..518f99e6 --- /dev/null +++ b/app/Formatter/TaskSuggestMenuFormatter.php @@ -0,0 +1,63 @@ +<?php + +namespace Kanboard\Formatter; + +use Kanboard\Core\Filter\FormatterInterface; +use Kanboard\Model\ProjectModel; +use Kanboard\Model\TaskModel; + +/** + * Class TaskSuggestMenuFormatter + * + * @package Kanboard\Formatter + * @author Frederic Guillot + */ +class TaskSuggestMenuFormatter extends BaseFormatter implements FormatterInterface +{ + protected $limit = 25; + + /** + * Limit number of results + * + * @param $limit + * @return $this + */ + public function withLimit($limit) + { + $this->limit = $limit; + return $this; + } + + /** + * Apply formatter + * + * @access public + * @return mixed + */ + public function format() + { + $result = array(); + $tasks = $this->query + ->columns( + TaskModel::TABLE.'.id', + TaskModel::TABLE.'.title', + ProjectModel::TABLE.'.name AS project_name' + ) + ->asc(TaskModel::TABLE.'.id') + ->limit($this->limit) + ->findAll(); + + foreach ($tasks as $task) { + $html = '#'.$task['id'].' '; + $html .= $this->helper->text->e($task['title']).' '; + $html .= '<small>'.$this->helper->text->e($task['project_name']).'</small>'; + + $result[] = array( + 'value' => (string) $task['id'], + 'html' => $html, + ); + } + + return $result; + } +} diff --git a/app/Formatter/UserMentionFormatter.php b/app/Formatter/UserMentionFormatter.php new file mode 100644 index 00000000..395fc463 --- /dev/null +++ b/app/Formatter/UserMentionFormatter.php @@ -0,0 +1,60 @@ +<?php + +namespace Kanboard\Formatter; + +/** + * Class UserMentionFormatter + * + * @package Kanboard\Formatter + * @author Frederic Guillot + */ +class UserMentionFormatter extends BaseFormatter +{ + protected $users = array(); + + /** + * Set users + * + * @param array $users + * @return $this + */ + public function withUsers(array $users) { + $this->users = $users; + return $this; + } + + /** + * Apply formatter + * + * @access public + * @return array + */ + public function format() + { + $result = array(); + + foreach ($this->users as $user) { + $html = $this->helper->avatar->small( + $user['id'], + $user['username'], + $user['name'], + $user['email'], + $user['avatar_path'], + 'avatar-inline' + ); + + $html .= ' '.$this->helper->text->e($user['username']); + + if (! empty($user['name'])) { + $html .= ' <small>'.$this->helper->text->e($user['name']).'</small>'; + } + + $result[] = array( + 'value' => $user['username'], + 'html' => $html, + ); + } + + return $result; + } +}
\ No newline at end of file diff --git a/app/Helper/FormHelper.php b/app/Helper/FormHelper.php index 629de9ff..9eabd724 100644 --- a/app/Helper/FormHelper.php +++ b/app/Helper/FormHelper.php @@ -131,16 +131,34 @@ class FormHelper extends Base * Display a checkbox field * * @access public - * @param string $name Field name - * @param string $label Form label - * @param string $value Form value - * @param boolean $checked Field selected or not - * @param string $class CSS class + * @param string $name Field name + * @param string $label Form label + * @param string $value Form value + * @param boolean $checked Field selected or not + * @param string $class CSS class + * @param array $attributes * @return string */ - public function checkbox($name, $label, $value, $checked = false, $class = '') + public function checkbox($name, $label, $value, $checked = false, $class = '', array $attributes = array()) { - return '<label><input type="checkbox" name="'.$name.'" class="'.$class.'" value="'.$this->helper->text->e($value).'" '.($checked ? 'checked="checked"' : '').'> '.$this->helper->text->e($label).'</label>'; + $htmlAttributes = ''; + + if ($checked) { + $attributes['checked'] = 'checked'; + } + + foreach ($attributes as $attribute => $attributeValue) { + $htmlAttributes .= sprintf('%s="%s"', $attribute, $this->helper->text->e($attributeValue)); + } + + return sprintf( + '<label><input type="checkbox" name="%s" class="%s" value="%s" %s> %s</label>', + $name, + $class, + $this->helper->text->e($value), + $htmlAttributes, + $this->helper->text->e($label) + ); } /** @@ -195,15 +213,25 @@ class FormHelper extends Base { $params = array( 'name' => $name, - 'text' => isset($values[$name]) ? $this->helper->text->e($values[$name]) : '', + 'text' => isset($values[$name]) ? $values[$name] : '', 'css' => $this->errorClass($errors, $name), 'required' => isset($attributes['required']) && $attributes['required'], 'tabindex' => isset($attributes['tabindex']) ? $attributes['tabindex'] : '-1', 'labelPreview' => t('Preview'), 'labelWrite' => t('Write'), 'placeholder' => t('Write your text in Markdown'), + 'autofocus' => isset($attributes['autofocus']) && $attributes['autofocus'], + 'suggestOptions' => array( + 'triggers' => array( + '#' => $this->helper->url->to('TaskAjaxController', 'suggest', array('search' => 'SEARCH_TERM')), + ) + ), ); + if (isset($values['project_id'])) { + $params['suggestOptions']['triggers']['@'] = $this->helper->url->to('UserAjaxController', 'mention', array('project_id' => $values['project_id'], 'search' => 'SEARCH_TERM')); + } + $html = '<div class="js-text-editor" data-params=\''.json_encode($params, JSON_HEX_APOS).'\'></div>'; $html .= $this->errorList($errors, $name); diff --git a/app/Job/CommentEventJob.php b/app/Job/CommentEventJob.php index 47cf8020..62fae40a 100644 --- a/app/Job/CommentEventJob.php +++ b/app/Job/CommentEventJob.php @@ -31,7 +31,6 @@ class CommentEventJob extends BaseJob * * @param int $commentId * @param string $eventName - * @return $this */ public function execute($commentId, $eventName) { @@ -43,7 +42,8 @@ class CommentEventJob extends BaseJob $this->dispatcher->dispatch($eventName, $event); if ($eventName === CommentModel::EVENT_CREATE) { - $this->userMentionModel->fireEvents($event['comment']['comment'], CommentModel::EVENT_USER_MENTION, $event); + $userMentionJob = $this->userMentionJob->withParams($event['comment']['comment'], CommentModel::EVENT_USER_MENTION, $event); + $this->queueManager->push($userMentionJob); } } } diff --git a/app/Job/TaskEventJob.php b/app/Job/TaskEventJob.php index 7d026a68..acc7fca3 100644 --- a/app/Job/TaskEventJob.php +++ b/app/Job/TaskEventJob.php @@ -69,7 +69,8 @@ class TaskEventJob extends BaseJob $this->dispatcher->dispatch($eventName, $event); if ($eventName === TaskModel::EVENT_CREATE) { - $this->userMentionModel->fireEvents($event['task']['description'], TaskModel::EVENT_USER_MENTION, $event); + $userMentionJob = $this->userMentionJob->withParams($event['task']['description'], TaskModel::EVENT_USER_MENTION, $event); + $this->queueManager->push($userMentionJob); } } } diff --git a/app/Job/UserMentionJob.php b/app/Job/UserMentionJob.php new file mode 100644 index 00000000..bbb27131 --- /dev/null +++ b/app/Job/UserMentionJob.php @@ -0,0 +1,72 @@ +<?php + +namespace Kanboard\Job; + +use Kanboard\Event\GenericEvent; +use Kanboard\Model\UserModel; + +/** + * Class UserMentionJob + * + * @package Kanboard\Job + * @author Frederic Guillot + */ +class UserMentionJob extends BaseJob +{ + /** + * Set job parameters + * + * @param string $text + * @param string $eventName + * @param GenericEvent $event + * @return $this + */ + public function withParams($text, $eventName, GenericEvent $event) + { + $this->jobParams = array($text, $eventName, $event->getAll()); + return $this; + } + + /** + * Execute job + * + * @param string $text + * @param string $eventName + * @param array $eventData + */ + public function execute($text, $eventName, array $eventData) + { + $event = new GenericEvent($eventData); + $users = $this->getMentionedUsers($text); + + foreach ($users as $user) { + if ($this->projectPermissionModel->isMember($event->getProjectId(), $user['id'])) { + $event['mention'] = $user; + $this->dispatcher->dispatch($eventName, $event); + } + } + } + + /** + * Get list of mentioned users + * + * @access public + * @param string $text + * @return array + */ + public function getMentionedUsers($text) + { + $users = array(); + + if (preg_match_all('/@([^\s,!.:?]+)/', $text, $matches)) { + $users = $this->db->table(UserModel::TABLE) + ->columns('id', 'username', 'name', 'email', 'language') + ->eq('notifications_enabled', 1) + ->neq('id', $this->userSession->getId()) + ->in('username', array_unique($matches[1])) + ->findAll(); + } + + return $users; + } +} diff --git a/app/Locale/bs_BA/translations.php b/app/Locale/bs_BA/translations.php index cb9ad9d5..e27a4501 100644 --- a/app/Locale/bs_BA/translations.php +++ b/app/Locale/bs_BA/translations.php @@ -1278,4 +1278,13 @@ return array( // 'Moving a task is not permitted' => '', // 'This value must be in the range %d to %d' => '', // 'You are not allowed to move this task.' => '', + // 'API User Access' => '', + // 'Preview' => '', + // 'Write' => '', + // 'Write your text in Markdown' => '', + // 'New External Task: %s' => '', + // 'No personal API access token registered.' => '', + // 'Your personal API access token is "%s"' => '', + // 'Remove your token' => '', + // 'Generate a new token' => '', ); diff --git a/app/Locale/cs_CZ/translations.php b/app/Locale/cs_CZ/translations.php index 8541f303..5091de00 100644 --- a/app/Locale/cs_CZ/translations.php +++ b/app/Locale/cs_CZ/translations.php @@ -1278,4 +1278,13 @@ return array( // 'Moving a task is not permitted' => '', // 'This value must be in the range %d to %d' => '', // 'You are not allowed to move this task.' => '', + // 'API User Access' => '', + // 'Preview' => '', + // 'Write' => '', + // 'Write your text in Markdown' => '', + // 'New External Task: %s' => '', + // 'No personal API access token registered.' => '', + // 'Your personal API access token is "%s"' => '', + // 'Remove your token' => '', + // 'Generate a new token' => '', ); diff --git a/app/Locale/da_DK/translations.php b/app/Locale/da_DK/translations.php index 78a6d35e..2ca864e5 100644 --- a/app/Locale/da_DK/translations.php +++ b/app/Locale/da_DK/translations.php @@ -1278,4 +1278,13 @@ return array( // 'Moving a task is not permitted' => '', // 'This value must be in the range %d to %d' => '', // 'You are not allowed to move this task.' => '', + // 'API User Access' => '', + // 'Preview' => '', + // 'Write' => '', + // 'Write your text in Markdown' => '', + // 'New External Task: %s' => '', + // 'No personal API access token registered.' => '', + // 'Your personal API access token is "%s"' => '', + // 'Remove your token' => '', + // 'Generate a new token' => '', ); diff --git a/app/Locale/de_DE/translations.php b/app/Locale/de_DE/translations.php index 433b7a2f..950a3b77 100644 --- a/app/Locale/de_DE/translations.php +++ b/app/Locale/de_DE/translations.php @@ -1278,4 +1278,13 @@ return array( 'Moving a task is not permitted' => 'Verschieben einer Aufgabe ist nicht erlaubt', 'This value must be in the range %d to %d' => 'Dieser Wert muss im Bereich %d bis %d sein', 'You are not allowed to move this task.' => 'Sie haben nicht die Berechtigung, diese Aufgabe zu verschieben.', + // 'API User Access' => '', + // 'Preview' => '', + // 'Write' => '', + // 'Write your text in Markdown' => '', + // 'New External Task: %s' => '', + // 'No personal API access token registered.' => '', + // 'Your personal API access token is "%s"' => '', + // 'Remove your token' => '', + // 'Generate a new token' => '', ); diff --git a/app/Locale/el_GR/translations.php b/app/Locale/el_GR/translations.php index ad17b4ed..7b7d855e 100644 --- a/app/Locale/el_GR/translations.php +++ b/app/Locale/el_GR/translations.php @@ -1278,4 +1278,13 @@ return array( // 'Moving a task is not permitted' => '', // 'This value must be in the range %d to %d' => '', // 'You are not allowed to move this task.' => '', + // 'API User Access' => '', + // 'Preview' => '', + // 'Write' => '', + // 'Write your text in Markdown' => '', + // 'New External Task: %s' => '', + // 'No personal API access token registered.' => '', + // 'Your personal API access token is "%s"' => '', + // 'Remove your token' => '', + // 'Generate a new token' => '', ); diff --git a/app/Locale/es_ES/translations.php b/app/Locale/es_ES/translations.php index b3a68751..984d42db 100644 --- a/app/Locale/es_ES/translations.php +++ b/app/Locale/es_ES/translations.php @@ -1278,4 +1278,13 @@ return array( // 'Moving a task is not permitted' => '', // 'This value must be in the range %d to %d' => '', // 'You are not allowed to move this task.' => '', + // 'API User Access' => '', + // 'Preview' => '', + // 'Write' => '', + // 'Write your text in Markdown' => '', + // 'New External Task: %s' => '', + // 'No personal API access token registered.' => '', + // 'Your personal API access token is "%s"' => '', + // 'Remove your token' => '', + // 'Generate a new token' => '', ); diff --git a/app/Locale/fi_FI/translations.php b/app/Locale/fi_FI/translations.php index 733625b6..41e51fa1 100644 --- a/app/Locale/fi_FI/translations.php +++ b/app/Locale/fi_FI/translations.php @@ -1278,4 +1278,13 @@ return array( // 'Moving a task is not permitted' => '', // 'This value must be in the range %d to %d' => '', // 'You are not allowed to move this task.' => '', + // 'API User Access' => '', + // 'Preview' => '', + // 'Write' => '', + // 'Write your text in Markdown' => '', + // 'New External Task: %s' => '', + // 'No personal API access token registered.' => '', + // 'Your personal API access token is "%s"' => '', + // 'Remove your token' => '', + // 'Generate a new token' => '', ); diff --git a/app/Locale/fr_FR/translations.php b/app/Locale/fr_FR/translations.php index 59f4609e..daac98ed 100644 --- a/app/Locale/fr_FR/translations.php +++ b/app/Locale/fr_FR/translations.php @@ -1279,4 +1279,13 @@ return array( 'Moving a task is not permitted' => 'Déplaçer une tâche n\'est pas autorisé', 'This value must be in the range %d to %d' => 'Cette valeur doit être définie entre %d et %d', 'You are not allowed to move this task.' => 'Vous n\'êtes pas autorisé à déplacer cette tâche.', + 'API User Access' => 'Accès utilisateur de l\'API', + 'Preview' => 'Aperçu', + 'Write' => 'Écrire', + 'Write your text in Markdown' => 'Écrivez votre texte en Markdown', + 'New External Task: %s' => 'Nouvelle tâche externe : %s', + 'No personal API access token registered.' => 'Aucun jeton d\'accès personnel à l\'API enregistré.', + 'Your personal API access token is "%s"' => 'Votre jeton d\'accès personnel à l\'API est « %s »', + 'Remove your token' => 'Supprimer votre jeton', + 'Generate a new token' => 'Générer un nouveau jeton', ); diff --git a/app/Locale/hu_HU/translations.php b/app/Locale/hu_HU/translations.php index b5a23428..784c27ba 100644 --- a/app/Locale/hu_HU/translations.php +++ b/app/Locale/hu_HU/translations.php @@ -1278,4 +1278,13 @@ return array( // 'Moving a task is not permitted' => '', // 'This value must be in the range %d to %d' => '', // 'You are not allowed to move this task.' => '', + // 'API User Access' => '', + // 'Preview' => '', + // 'Write' => '', + // 'Write your text in Markdown' => '', + // 'New External Task: %s' => '', + // 'No personal API access token registered.' => '', + // 'Your personal API access token is "%s"' => '', + // 'Remove your token' => '', + // 'Generate a new token' => '', ); diff --git a/app/Locale/id_ID/translations.php b/app/Locale/id_ID/translations.php index e711a512..9398d51d 100644 --- a/app/Locale/id_ID/translations.php +++ b/app/Locale/id_ID/translations.php @@ -1278,4 +1278,13 @@ return array( // 'Moving a task is not permitted' => '', // 'This value must be in the range %d to %d' => '', // 'You are not allowed to move this task.' => '', + // 'API User Access' => '', + // 'Preview' => '', + // 'Write' => '', + // 'Write your text in Markdown' => '', + // 'New External Task: %s' => '', + // 'No personal API access token registered.' => '', + // 'Your personal API access token is "%s"' => '', + // 'Remove your token' => '', + // 'Generate a new token' => '', ); diff --git a/app/Locale/it_IT/translations.php b/app/Locale/it_IT/translations.php index fa5f5105..65defe86 100644 --- a/app/Locale/it_IT/translations.php +++ b/app/Locale/it_IT/translations.php @@ -297,7 +297,7 @@ return array( 'Display another project' => 'Mostra un altro progetto', 'Created by %s' => 'Creato da %s', 'Tasks Export' => 'Export dei task', - 'Start Date' => 'Data d\'inizio', + 'Start Date' => 'Data di inizio', 'End Date' => 'Data di fine', 'Execute' => 'Esegui', 'Task Id' => 'Id del task', @@ -780,8 +780,8 @@ return array( 'Add task' => 'Aggiungi task', 'Start date:' => 'Data di inizio:', 'Due date:' => 'Data di completamento:', - 'There is no start date or due date for this task.' => 'Nessuna data di inzio o di scadenza per questo task.', - 'Moving or resizing a task will change the start and due date of the task.' => 'Spostando o ridimensionado un task ne modifca la data di inzio e di scadenza.', + 'There is no start date or due date for this task.' => 'Nessuna data di inizio o di scadenza per questo task.', + 'Moving or resizing a task will change the start and due date of the task.' => 'Spostando o ridimensionado un task ne modifca la data di inizio e di scadenza.', 'There is no task in your project.' => 'Non ci sono task nel tuo progetto.', 'Gantt chart' => 'Grafici Gantt', 'People who are project managers' => 'Persone che sono manager di progetto', @@ -1184,7 +1184,7 @@ return array( 'Global tags' => 'Tag globali', 'There is no global tag at the moment.' => 'Non sono definiti tag globali al momento.', 'This field cannot be empty' => 'Questo campo non può essere vuoto', - 'Close a task when there is no activity in an specific column' => 'Chiudi un task quando non vi è alcuna attività in un specifica colonna', + 'Close a task when there is no activity in an specific column' => 'Chiudi un task quando non vi è alcuna attività in una specifica colonna', '%s removed a subtask for the task #%d' => '%s rimosso un subtask per il task #%d', '%s removed a comment on the task #%d' => '%s rimosso un commento nel task #%d', 'Comment removed on task #%d' => 'Commento rimosso nel task #%d', @@ -1234,17 +1234,17 @@ return array( 'Remove this role' => 'Cancella questo ruolo', 'There is no restriction for this role.' => 'Non ci sono restrizioni per questo ruolo.', 'Only moving task between those columns is permitted' => 'È permesso solo muovere i tast tra queste colonne', - 'Close a task in a specific column when not moved during a given period' => 'Chiudi un task in una colonna specifica quando non è mosso per un determinato periodo', + 'Close a task in a specific column when not moved during a given period' => 'Chiudi un task in una colonna specifica quando non viene mosso per un determinato periodo', 'Edit columns' => 'Modifica colonne', 'The column restriction has been created successfully.' => 'La restrizione per le colonne è stata creata correttamente.', 'Unable to create this column restriction.' => 'Impossibile creare questa restrizione per colonne.', 'Column restriction removed successfully.' => 'Restrizione per colonne rimossa correttamente.', 'Unable to remove this restriction.' => 'Impossibile rimuovere questa restrizione.', - 'Your custom project role has been created successfully.' => 'La regola per il progetto personalizzato è stata creata correttamente.', - 'Unable to create custom project role.' => 'Impossibile creare la regola per il progetto personalizzato.', - 'Your custom project role has been updated successfully.' => 'La regola per il progetto personalizzato è stata modificata correttamente.', - 'Unable to update custom project role.' => 'Impossibile modificare correttamente la regola per il progetto personalizzato.', - 'Custom project role removed successfully.' => 'Regola per il progetto personalizzato rimossa correttamente.', + 'Your custom project role has been created successfully.' => 'La regola personalizzata per il progetto è stata creata correttamente.', + 'Unable to create custom project role.' => 'Impossibile creare la regola personalizzata per il progetto.', + 'Your custom project role has been updated successfully.' => 'La regola personalizzata per il progetto è stata modificata correttamente.', + 'Unable to update custom project role.' => 'Impossibile modificare correttamente la regola personalizzata per il progetto.', + 'Custom project role removed successfully.' => 'Regola personalizzata per il progetto rimossa correttamente.', 'Unable to remove this project role.' => 'Impossibile rimuovere questa regola per il progetto.', 'The project restriction has been created successfully.' => 'La restrizione di progetto è stata creata con successo.', 'Unable to create this project restriction.' => 'Impossibile creare la restrizione di progetto.', @@ -1257,10 +1257,10 @@ return array( 'Task creation is not permitted' => 'Creazione task non permessa', 'Closing or opening a task is not permitted' => 'Chiudere o aprire task non è permesso', 'New drag and drop restriction for the role "%s"' => 'Nuova restrizione "drag and drop" per la regola "%s"', - 'People belonging to this role will be able to move tasks only between the source and the destination column.' => 'Gli utenti che appartengono a questa regola saranno in grado di muovere i task solo tra la colonna di origine e quella di destinazione', - 'Remove a column restriction' => 'Rimuovere restrizione colonna', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => 'Gli utenti che appartengono a questa regola saranno in grado di spostare i task solo tra la colonna di origine e quella di destinazione', + 'Remove a column restriction' => 'Rimuovere una restrizione di colonna', 'Do you really want to remove this column restriction: "%s" to "%s"?' => 'Vuoi davvero rimuovere questa restrizione di colonna: "%s" a "%s"?', - 'New column restriction for the role "%s"' => 'Nuova restrizione colonna per la regola "%s"', + 'New column restriction for the role "%s"' => 'Nuova restrizione di colonna per la regola "%s"', 'Rule' => 'Regola', 'Do you really want to remove this column restriction?' => 'Vuoi davvero rimuovere questa restrizione di colonna?', 'Custom roles' => 'Regole personalizzate', @@ -1273,9 +1273,18 @@ return array( 'Restriction' => 'Restrizione', 'Remove a project restriction' => 'Rimuovi restrizione di progetto', 'Do you really want to remove this project restriction: "%s"?' => 'Vuoi davvero rimuovere questa restrizione di progetto: "%s"?', - 'Duplicate to multiple projects' => 'Dupplica i più progetti', - 'This field is required' => 'Questo campo è richiesto', - 'Moving a task is not permitted' => 'Muovere task non è permesso', + 'Duplicate to multiple projects' => 'Duplica su più progetti', + 'This field is required' => 'Questo campo è obbligatorio', + 'Moving a task is not permitted' => 'Spostare task non è permesso', 'This value must be in the range %d to %d' => 'Questo valore deve essere compreso tra %d e %d', - 'You are not allowed to move this task.' => 'Non ti è permesso muovere questo task.', + 'You are not allowed to move this task.' => 'Non ti è permesso spostare questo task.', + // 'API User Access' => '', + // 'Preview' => '', + // 'Write' => '', + // 'Write your text in Markdown' => '', + // 'New External Task: %s' => '', + // 'No personal API access token registered.' => '', + // 'Your personal API access token is "%s"' => '', + // 'Remove your token' => '', + // 'Generate a new token' => '', ); diff --git a/app/Locale/ja_JP/translations.php b/app/Locale/ja_JP/translations.php index b9feed07..d0487fb7 100644 --- a/app/Locale/ja_JP/translations.php +++ b/app/Locale/ja_JP/translations.php @@ -1278,4 +1278,13 @@ return array( // 'Moving a task is not permitted' => '', // 'This value must be in the range %d to %d' => '', // 'You are not allowed to move this task.' => '', + // 'API User Access' => '', + // 'Preview' => '', + // 'Write' => '', + // 'Write your text in Markdown' => '', + // 'New External Task: %s' => '', + // 'No personal API access token registered.' => '', + // 'Your personal API access token is "%s"' => '', + // 'Remove your token' => '', + // 'Generate a new token' => '', ); diff --git a/app/Locale/ko_KR/translations.php b/app/Locale/ko_KR/translations.php index 5339111b..a613b467 100644 --- a/app/Locale/ko_KR/translations.php +++ b/app/Locale/ko_KR/translations.php @@ -402,9 +402,9 @@ return array( 'Refresh interval for private board' => '비공개 보드의 갱신 빈도', 'Refresh interval for public board' => '공개 보드의 갱신 빈도', 'Task highlight period' => '할일의 하이라이트 기간', - // 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => '', - // 'Frequency in second (60 seconds by default)' => '', - // 'Frequency in second (0 to disable this feature, 10 seconds by default)' => '', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => '최근 수정된 작업 고려 기간(초), (비활성화:0, 기본값:2일)', + 'Frequency in second (60 seconds by default)' => '초당 횟수(기본값:60초)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => '초당 횟수(비활성화:0, 기본값:10초)', 'Application URL' => '애플리케이션의 URL', 'Token regenerated.' => '토큰이 다시 생성되었습니다.', 'Date format' => '데이터 포멧', @@ -555,18 +555,18 @@ return array( 'No results match:' => '결과가 일치하지 않았습니다', 'Currency' => '통화', 'Private project' => '개인 프로젝트', - // 'AUD - Australian Dollar' => '', - // 'CAD - Canadian Dollar' => '', - // 'CHF - Swiss Francs' => '', + 'AUD - Australian Dollar' => 'AUD - 호주 달러', + 'CAD - Canadian Dollar' => 'CAD -캐나다 달러', + 'CHF - Swiss Francs' => 'CHF - 스위스 프랑', 'Custom Stylesheet' => '커스텀 스타일 시트', 'download' => '다운로드', - // 'EUR - Euro' => '', - // 'GBP - British Pound' => '', - // 'INR - Indian Rupee' => '', - // 'JPY - Japanese Yen' => '', - // 'NZD - New Zealand Dollar' => '', - // 'RSD - Serbian dinar' => '', - // 'USD - US Dollar' => '', + 'EUR - Euro' => 'EUR - 유로', + 'GBP - British Pound' => 'GBP - 영국 파운드', + 'INR - Indian Rupee' => 'INR - 인도 루피', + 'JPY - Japanese Yen' => 'JPY - 일본 엔', + 'NZD - New Zealand Dollar' => 'NZD - 뉴질랜드 달러', + 'RSD - Serbian dinar' => 'RSD - 세르비아 디나르', + 'USD - US Dollar' => 'USD - 미국 달러', 'Destination column' => '이동 후 컬럼', 'Move the task to another column when assigned to a user' => '사용자의 할당을 하면 할일을 다른 컬럼에 이동', 'Move the task to another column when assignee is cleared' => '사용자의 할당이 없어지면 할일을 다른 컬럼에 이동', @@ -600,12 +600,12 @@ return array( 'Assign a color when the task is moved to a specific column' => '상세 컬럼으로 이동할 할일의 색깔을 지정하세요', '%s via Kanboard' => '%s via E-board', 'Burndown chart' => '번다운 차트', - // 'This chart show the task complexity over the time (Work Remaining).' => '', + 'This chart show the task complexity over the time (Work Remaining).' => '이 차트는 시간에 따른 할일의 복잡성을 보여줍니다. (남아있는 일)', 'Screenshot taken %s' => '스크린샷_%s', 'Add a screenshot' => '스크린샷 추가', 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => '스크린샷을 CTRL+V 혹은 ⌘+V를 눌러 붙여넣기', 'Screenshot uploaded successfully.' => '스크린샷을 업로드하였습니다', - // 'SEK - Swedish Krona' => '', + 'SEK - Swedish Krona' => 'SEK - 스위스 크로나', 'Identifier' => '식별자', 'Disable two factor authentication' => '이중 인증 비활성화', 'Do you really want to disable the two factor authentication for this user: "%s"?' => '"%s" 담당자의 이중 인증을 비활성화 하시겠습니까?', @@ -613,7 +613,7 @@ return array( 'Start to type task title...' => '할일 제목을 입력하세요', 'A task cannot be linked to itself' => '할일을 자기자신에게 연결할 수 없습니다', 'The exact same link already exists' => '동일한 링크가 이미 존재합니다', - // 'Recurrent task is scheduled to be generated' => '', + 'Recurrent task is scheduled to be generated' => '반복 할일이 생성된 예정입니다.', 'Score' => '점수', 'The identifier must be unique' => '식별자는 유일해야 합니다', 'This linked task id doesn\'t exists' => '연결된 할일 ID가 존재하지 않습니다', @@ -626,7 +626,7 @@ return array( 'Base date to calculate new due date' => '새로운 기본 종료날짜 계산', 'Action date' => '시작날짜', 'Base date to calculate new due date: ' => '새로운 기본 종료날짜 계산: ', - // 'This task has created this child task: ' => '', + 'This task has created this child task: ' => '이 할일은 하위 할일을 만들었습니다.', 'Day(s)' => '일', 'Existing due date' => '기존 종료날짜', 'Factor to calculate new due date: ' => '새로운 종료날짜 계산: ', @@ -635,7 +635,7 @@ return array( 'This task has been created by: ' => '할일을 만들었습니다: ', 'Recurrent task has been generated:' => '반복 할일이 생성되었습니다', 'Timeframe to calculate new due date: ' => '종료날짜 계산 단위', - // 'Trigger to generate recurrent task: ' => '', + 'Trigger to generate recurrent task: ' => '반복 할일 생성 트리거', 'When task is closed' => '할일을 마쳤을때', 'When task is moved from first column' => '할일이 첫번째 컬럼으로 옮겨졌을때', 'When task is moved to last column' => '할일이 마지막 컬럼으로 옮겨졌을때', @@ -649,7 +649,7 @@ return array( 'Subtasks time tracking' => '서브 할일 시간 트래킹', 'User calendar view' => '담당자 달력 보기', 'Automatically update the start date' => '시작일에 자동 갱신', - // 'iCal feed' => '', + 'iCal feed' => 'iCal 피드', 'Preferences' => '우선권', 'Security' => '보안', 'Two factor authentication disabled' => '이중 인증이 비활성화 되었습니다', @@ -666,7 +666,7 @@ return array( 'Notification' => '알림', '%s moved the task #%d to the first swimlane' => '%s가 할일 #%d를 첫번째 스웜레인으로 이동시켰습니다', 'Swimlane' => '스윔레인', - // 'Gravatar' => '', + 'Gravatar' => 'Gravatar', '%s moved the task %s to the first swimlane' => '%s가 할일 %s를 첫번째 스웜레인으로 이동시켰습니다', '%s moved the task %s to the swimlane "%s"' => '%s가 할일 %s를 %s 스웜레인으로 이동시켰습니다', 'This report contains all subtasks information for the given date range.' => '해당 기간의 모든 서브 할일 정보가 보고서에 포함됩니다', @@ -695,7 +695,7 @@ return array( 'Only for tasks assigned to me' => '내가 담당자인 일', 'Only for tasks created by me' => '내가 만든 일', 'Only for tasks created by me and assigned to me' => '내가 만들었거나 내가 담당자인 일', - // '%%Y-%%m-%%d' => '', + '%%Y-%%m-%%d' => '%%Y-%%m-%%d', 'Total for all columns' => '모든 컬럼', 'You need at least 2 days of data to show the chart.' => '차트를 보기 위하여 최소 2일의 데이터가 필요합니다', '<15m' => '<15분', @@ -781,12 +781,12 @@ return array( 'Start date:' => '시작일:', 'Due date:' => '만기일:', 'There is no start date or due date for this task.' => '할일의 시작일 또는 만기일이 없습니다', - // 'Moving or resizing a task will change the start and due date of the task.' => '', - // 'There is no task in your project.' => '', + 'Moving or resizing a task will change the start and due date of the task.' => '할일의 이동 혹은 리사이징으로 시작시간과 마감시간이 변경됩니다.', + 'There is no task in your project.' => '프로젝트에 할일이 없습니다.', 'Gantt chart' => '간트 차트', 'People who are project managers' => '프로젝트 매니저', 'People who are project members' => '프로젝트 멤버', - // 'NOK - Norwegian Krone' => '', + 'NOK - Norwegian Krone' => 'NOK - 노르웨이 크로네', 'Show this column' => '컬럼 보이기', 'Hide this column' => '컬럼 숨기기', 'open file' => '열기', @@ -802,14 +802,14 @@ return array( 'End date:' => '날짜 수정', 'There is no start date or end date for this project.' => '이 프로젝트에는 시작날짜와 종료날짜가 없습니다', 'Projects Gantt chart' => '프로젝트 간트차트', - // 'Change task color when using a specific task link' => '', - // 'Task link creation or modification' => '', + 'Change task color when using a specific task link' => '특정 할일 링크를 사용할때 할일의 색깔 변경', + 'Task link creation or modification' => '할일 링크 생성 혹은 수정', 'Milestone' => '마일스톤', 'Documentation: %s' => '문서: %s', 'Switch to the Gantt chart view' => '간트 차트 보기로 변경', - // 'Reset the search/filter box' => '', + 'Reset the search/filter box' => '찾기/필터 박스 초기화', 'Documentation' => '문서', - // 'Table of contents' => '', + 'Table of contents' => '목차', 'Gantt' => '간트', 'Author' => '글쓴이', 'Version' => '버전', @@ -825,7 +825,7 @@ return array( 'Your custom filter have been updated successfully.' => '사용자 정의 필터가 성공적으로 수정되었습니다', 'Unable to update custom filter.' => '정의 필터 수정 비활성화', 'Web' => '웹', - // 'New attachment on task #%d: %s' => '', + 'New attachment on task #%d: %s' => '할일 #%d의 새로운 첨부파일: %s', 'New comment on task #%d' => '할일 #%d에 새로운 댓글이 달렸습니다', 'Comment updated on task #%d' => '할일 #%d에 댓글이 갱신 되었습니다', 'New subtask on task #%d' => '서브 할일 #%d이 갱신 되었습니다', @@ -843,7 +843,7 @@ return array( 'No new notifications.' => '알림이 없습니다', 'Mark all as read' => '모두 읽음', 'Mark as read' => '읽음', - // 'Total number of tasks in this column across all swimlanes' => '', + 'Total number of tasks in this column across all swimlanes' => '모든 스웜라인 칼럼의 할일 수', 'Collapse swimlane' => '스웜라인 붕괴', 'Expand swimlane' => '스웜라인 확장', 'Add a new filter' => '새로운 필터 추가', @@ -866,7 +866,7 @@ return array( 'Single Quote' => '따옴표', '%s attached a file to the task #%d' => '%s가 할일 #%d에 파일을 추가하였습니다', 'There is no column or swimlane activated in your project!' => '프로젝트에 활성화된 컬럼이나 스웜라인이 없습니다', - // 'Append filter (instead of replacement)' => '', + 'Append filter (instead of replacement)' => '필터 (변경 대신)추가', 'Append/Replace' => '추가/변경', 'Append' => '추가', 'Replace' => '변경', @@ -890,7 +890,7 @@ return array( '%s attached a new file to the task %s' => '%s이 새로운 파일을 할일 %s에 추가했습니다', 'Link type' => '링크 형식', 'Assign automatically a category based on a link' => '링크 기반 자동 카테고리 할당', - // 'BAM - Konvertible Mark' => '', + 'BAM - Konvertible Mark' => 'BAM - 보스니아 마르카', 'Assignee Username' => '담당자의 사용자이름', 'Assignee Name' => '당장자 이름', 'Groups' => '그룹', @@ -916,7 +916,7 @@ return array( 'Project Member' => '프로젝트 멤버', 'Project Viewer' => '프로젝트 뷰어', 'Your account is locked for %d minutes' => '%d분 동안 계정이 잠깁니다', - // 'Invalid captcha' => '', + 'Invalid captcha' => '잘못된 보안 문자', 'The name must be unique' => '이름은 유일해야 합니다', 'View all groups' => '모든그룹보기', 'There is no user available.' => '가능한 사용자가 없습니다', @@ -950,14 +950,14 @@ return array( 'Estimated Time' => '예상 시간', 'Actual Time' => '실제 시간', 'Estimated vs actual time' => '예상 vs 실제 시간', - // 'RUB - Russian Ruble' => '', + 'RUB - Russian Ruble' => 'RUB - 러시아 루블', 'Assign the task to the person who does the action when the column is changed' => '컬럼이 변경되면 액션하지 않는 사람에게 할일을 할당합니다', 'Close a task in a specific column' => '상세 컬럼의 할일을 종료합니다', 'Time-based One-time Password Algorithm' => '시간에 기반한 1회용 패스워드 알고리즘', 'Two-Factor Provider: ' => '이중 인증: ', 'Disable two-factor authentication' => '이중 인증 비활성화', 'Enable two-factor authentication' => '이중 인증 활성화', - // 'There is no integration registered at the moment.' => '', + 'There is no integration registered at the moment.' => '현재 등록된 통합이 없습니다.', 'Password Reset for Kanboard' => 'Kanboard의 비밀번호 초기화', 'Forgot password?' => '비밀번호 찾기', 'Enable "Forget Password"' => '"비밀번호 분실" 활성화', @@ -977,9 +977,9 @@ return array( 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => '프로젝트 알림 방법으로 플러그인이 등록되지 않았습니다. 각각의 알림을 프로파일에서 설정하실 수 있습니다', 'My dashboard' => '대시보드', 'My profile' => '프로필', - 'Project owner: ' => '프로젝트 소유자', + 'Project owner: ' => '프로젝트 소유자:', 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => '프로젝트 구분자는 선택사항이며, 숫자와 문자만 가능합니다. 예: MYPROJECT.', - // 'Project owner' => '', + 'Project owner' => '프로젝트 소유자', 'Those dates are useful for the project Gantt chart.' => '이 날짜는 프로젝트 간트 차트에 사용됩니다', 'Private projects do not have users and groups management.' => '비밀 프로젝트는 사용자나 관리 그룹이 소유하지 않습니다', 'There is no project member.' => '프로젝트 맴버가 없습니다', @@ -995,7 +995,7 @@ return array( 'Duration in days' => '기간', 'Send email when there is no activity on a task' => '활동이 없는 할일을 이메일로 보냅니다', 'Unable to fetch link information.' => '링크 정보 가져오기 비활성화', - // 'Daily background job for tasks' => '', + 'Daily background job for tasks' => '할일의 일일 배경 작업 ', 'Auto' => '자동', 'Related' => '연관된', 'Attachment' => '첨부', @@ -1010,7 +1010,7 @@ return array( 'Edit external link' => '외부 링크 수정', 'External link' => '외부 링크', 'Copy and paste your link here...' => '여기에 링크를 복사/붙여넣기', - // 'URL' => '', + 'URL' => '인터넷 주소', 'Internal links' => '내부 링크', 'Assign to me' => '나에게 할당', 'Me' => '나', @@ -1047,9 +1047,9 @@ return array( 'Do you really want to remove this custom filter: "%s"?' => '정의 필터를 삭제하시겠습니까: "%s"?', 'Remove a custom filter' => '정의 필터 삭제', 'User activated successfully.' => '사용자가 성공적으로 활성화되었습니다', - // 'Unable to enable this user.' => '', + 'Unable to enable this user.' => '이 사용자를 활성화할 수 없습니다.', 'User disabled successfully.' => '사용자가 성공적으로 비활성화되었습니다', - // 'Unable to disable this user.' => '', + 'Unable to disable this user.' => '이 사용자를 비활성화할 수 없습니다.', 'All files have been uploaded successfully.' => '모든 파일이 성공적으로 업로드되었습니다', 'View uploaded files' => '업로드 파일 보기', 'The maximum allowed file size is %sB.' => '업로드 파일의 최대 크기는 %sB 입니다', @@ -1093,7 +1093,7 @@ return array( 'Local File' => '로컬 파일', 'Configuration' => '구성', 'PHP version:' => 'PHP 버전:', - // 'PHP SAPI:' => '', + 'PHP SAPI:' => 'PHP SAPI:', 'OS version:' => '운영체제 버전:', 'Database version:' => '데이터베이스 버전:', 'Browser:' => '브라우저:', @@ -1140,7 +1140,7 @@ return array( 'Unable to open plugin archive.' => '플러그인 아카이브를 열수 없습니다.', 'There is no file in the plugin archive.' => '플러그인 아카이브에 파일이 존재하지 않습니다.', 'Create tasks in bulk' => '대량의 할일 만들기', - // 'Your Kanboard instance is not configured to install plugins from the user interface.' => '', + 'Your Kanboard instance is not configured to install plugins from the user interface.' => '칸보드 인스턴스가 사용자 인터페이스에서 플러그인을 설치하도록 구성되지 않았습니다', 'There is no plugin available.' => '사용 가능한 플러그인이 없습니다.', 'Install' => '설치', 'Update' => '갱신', @@ -1212,70 +1212,79 @@ return array( 'Notifications for %s' => '%s의 알림', 'Subtasks export' => '서브할일 내보내기', 'Tasks exportation' => '할일 내보내기', - // 'Assign a color when the task is moved to a specific swimlane' => '', - // 'Assign a priority when the task is moved to a specific swimlane' => '', - // 'User unlocked successfully.' => '', - // 'Unable to unlock the user.' => '', - // 'Move a task to another swimlane' => '', - // 'Creator Name' => '', - // 'Time spent and estimated' => '', - // 'Move position' => '', - // 'Move task to another position on the board' => '', - // 'Insert before this task' => '', - // 'Insert after this task' => '', - // 'Unlock this user' => '', - // 'Custom Project Roles' => '', - // 'Add a new custom role' => '', - // 'Restrictions for the role "%s"' => '', - // 'Add a new project restriction' => '', - // 'Add a new drag and drop restriction' => '', - // 'Add a new column restriction' => '', - // 'Edit this role' => '', - // 'Remove this role' => '', - // 'There is no restriction for this role.' => '', - // 'Only moving task between those columns is permitted' => '', - // 'Close a task in a specific column when not moved during a given period' => '', - // 'Edit columns' => '', - // 'The column restriction has been created successfully.' => '', - // 'Unable to create this column restriction.' => '', - // 'Column restriction removed successfully.' => '', - // 'Unable to remove this restriction.' => '', - // 'Your custom project role has been created successfully.' => '', - // 'Unable to create custom project role.' => '', - // 'Your custom project role has been updated successfully.' => '', - // 'Unable to update custom project role.' => '', - // 'Custom project role removed successfully.' => '', - // 'Unable to remove this project role.' => '', - // 'The project restriction has been created successfully.' => '', - // 'Unable to create this project restriction.' => '', - // 'Project restriction removed successfully.' => '', - // 'You cannot create tasks in this column.' => '', - // 'Task creation is permitted for this column' => '', - // 'Closing or opening a task is permitted for this column' => '', - // 'Task creation is blocked for this column' => '', - // 'Closing or opening a task is blocked for this column' => '', - // 'Task creation is not permitted' => '', - // 'Closing or opening a task is not permitted' => '', - // 'New drag and drop restriction for the role "%s"' => '', - // 'People belonging to this role will be able to move tasks only between the source and the destination column.' => '', - // 'Remove a column restriction' => '', - // 'Do you really want to remove this column restriction: "%s" to "%s"?' => '', - // 'New column restriction for the role "%s"' => '', - // 'Rule' => '', - // 'Do you really want to remove this column restriction?' => '', - // 'Custom roles' => '', - // 'New custom project role' => '', - // 'Edit custom project role' => '', - // 'Remove a custom role' => '', - // 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => '', - // 'There is no custom role for this project.' => '', - // 'New project restriction for the role "%s"' => '', - // 'Restriction' => '', - // 'Remove a project restriction' => '', - // 'Do you really want to remove this project restriction: "%s"?' => '', - // 'Duplicate to multiple projects' => '', - // 'This field is required' => '', - // 'Moving a task is not permitted' => '', - // 'This value must be in the range %d to %d' => '', - // 'You are not allowed to move this task.' => '', + 'Assign a color when the task is moved to a specific swimlane' => '할일이 특정 스웜라인으로 옮겨질 때 색상 지정', + 'Assign a priority when the task is moved to a specific swimlane' => '할일이 특정 스웜라인으로 옮겨질 때 우선순위 지정', + 'User unlocked successfully.' => '사용자 잠금 성공', + 'Unable to unlock the user.' => '사용자 해제 성공', + 'Move a task to another swimlane' => '다른 스웜라인으로 할일 이동', + 'Creator Name' => '생성자 이름', + 'Time spent and estimated' => '소요시간과 예상시간', + 'Move position' => '이동 위치', + 'Move task to another position on the board' => '할일을 보드의 다른 위치로 이동', + 'Insert before this task' => '이 할일 이전에 삽입', + 'Insert after this task' => '이 할일 이후에 삽입', + 'Unlock this user' => '사용자 잠금', + 'Custom Project Roles' => '정의 프로젝트 규칙', + 'Add a new custom role' => '새로운 정의 규칙 추가', + 'Restrictions for the role "%s"' => '"%s" 역할의 제약', + 'Add a new project restriction' => '새로운 프로젝트 제약 추가', + 'Add a new drag and drop restriction' => '새로운 드래그 앤 드롭 제약 추가', + 'Add a new column restriction' => '새로운 칼럼 제약 추가', + 'Edit this role' => '역할 수정', + 'Remove this role' => '역할 삭제', + 'There is no restriction for this role.' => '역할에 대한 제약이 없습니다.', + 'Only moving task between those columns is permitted' => '칼럼간 이동만 허용됩니다.', + 'Close a task in a specific column when not moved during a given period' => '특정 기간동안 이동하지 않은 특정 칼럼의 할일 마치기', + 'Edit columns' => '칼럼 수정', + 'The column restriction has been created successfully.' => '칼럼 제약이 생성되었습니다.', + 'Unable to create this column restriction.' => '칼럼 제약을 생성할 수 없습니다.', + 'Column restriction removed successfully.' => '칼럼 제약이 삭제되었습니다.', + 'Unable to remove this restriction.' => '칼럼 제약을 삭제할 수 없습니다.', + 'Your custom project role has been created successfully.' => '정의 프로젝트 역할이 생성되었습니다.', + 'Unable to create custom project role.' => '정의 프로젝트 역할을 생성할 수 없습니다.', + 'Your custom project role has been updated successfully.' => '정의 프로젝트 역할이 수정되었습니다.', + 'Unable to update custom project role.' => '정의 프로젝트 역할을 수정할 수 없습니다.', + 'Custom project role removed successfully.' => '정의 프로젝트 역할이 삭제되었습니다.', + 'Unable to remove this project role.' => '정의 프로젝트 역할을 삭제할 수 없습니다.', + 'The project restriction has been created successfully.' => '프로젝트 제약이 생성되었습니다.', + 'Unable to create this project restriction.' => '프로젝트 제약을 생성할 수 없습니다.', + 'Project restriction removed successfully.' => '프로젝트 제약을 삭제하였습니다.', + 'You cannot create tasks in this column.' => '이 칼럼의 할일을 생성할 수 없습니다.', + 'Task creation is permitted for this column' => '이 칼럼의 할일 생성이 허가되었습니다.', + 'Closing or opening a task is permitted for this column' => '이 칼럼의 할일 열기 혹은 닫기가 허가되었습니다.', + 'Task creation is blocked for this column' => '이 칼럼의 할일 생성이 거부되었습니다.', + 'Closing or opening a task is blocked for this column' => '이 칼럼의 할일 열기 혹은 닫기가 거부되었습니다.', + 'Task creation is not permitted' => '할일 생성이 거부되었습니다.', + 'Closing or opening a task is not permitted' => '할일 열기 혹은 닫기가 거부되었습니다.', + 'New drag and drop restriction for the role "%s"' => '"%s" 역할의 새로운 드래그 앤 드롭 제약', + 'People belonging to this role will be able to move tasks only between the source and the destination column.' => '이 역할에 속한 사용자는 원본 및 대상 칼럼 사이에서만 할일을 이동할 수 있습니다', + 'Remove a column restriction' => '칼럼 제약 삭제', + 'Do you really want to remove this column restriction: "%s" to "%s"?' => '칼럼 제약을 "%s" 에서 "%s"로 이동하시겠습니까?', + 'New column restriction for the role "%s"' => '"%s" 역할의 새로운 칼럼 제약', + 'Rule' => '역할', + 'Do you really want to remove this column restriction?' => '칼럼 제약을 삭제하시겠습니까?', + 'Custom roles' => '정의 역할', + 'New custom project role' => '새로운 정의 프로젝트 역할', + 'Edit custom project role' => '정의 프로젝트 역할 수정', + 'Remove a custom role' => '정의 역할 삭제', + 'Do you really want to remove this custom role: "%s"? All people assigned to this role will become project member.' => '"%s" 정의 역할을 삭제하시겠습니까? 이 역할에 할당 된 모든 사람들이 프로젝트 멤버가됩니다.', + 'There is no custom role for this project.' => '이 프로젝트에는 정의 역할이 없습니다.', + 'New project restriction for the role "%s"' => '"%s" 역할의 새로운 프로젝트 제약', + 'Restriction' => '제약', + 'Remove a project restriction' => '프로젝트 제약 삭제', + 'Do you really want to remove this project restriction: "%s"?' => '"%s" 프로젝트 제약을 삭제하시겠습니까?', + 'Duplicate to multiple projects' => '다수의 프로젝트 복제', + 'This field is required' => '이 필드는 필수 항목입니다.', + 'Moving a task is not permitted' => '할일 이동이 거부되었습니다.', + 'This value must be in the range %d to %d' => '값의 범위는 %d 부터 %d 까지 입니다.', + 'You are not allowed to move this task.' => '당신은 할일 이동이 거부되었습니다.', + // 'API User Access' => '', + // 'Preview' => '', + // 'Write' => '', + // 'Write your text in Markdown' => '', + // 'New External Task: %s' => '', + // 'No personal API access token registered.' => '', + // 'Your personal API access token is "%s"' => '', + // 'Remove your token' => '', + // 'Generate a new token' => '', ); diff --git a/app/Locale/my_MY/translations.php b/app/Locale/my_MY/translations.php index 048d8b4b..be335dec 100644 --- a/app/Locale/my_MY/translations.php +++ b/app/Locale/my_MY/translations.php @@ -1278,4 +1278,13 @@ return array( // 'Moving a task is not permitted' => '', // 'This value must be in the range %d to %d' => '', // 'You are not allowed to move this task.' => '', + // 'API User Access' => '', + // 'Preview' => '', + // 'Write' => '', + // 'Write your text in Markdown' => '', + // 'New External Task: %s' => '', + // 'No personal API access token registered.' => '', + // 'Your personal API access token is "%s"' => '', + // 'Remove your token' => '', + // 'Generate a new token' => '', ); diff --git a/app/Locale/nb_NO/translations.php b/app/Locale/nb_NO/translations.php index 0d2a340d..6b49545c 100644 --- a/app/Locale/nb_NO/translations.php +++ b/app/Locale/nb_NO/translations.php @@ -1278,4 +1278,13 @@ return array( // 'Moving a task is not permitted' => '', // 'This value must be in the range %d to %d' => '', // 'You are not allowed to move this task.' => '', + // 'API User Access' => '', + // 'Preview' => '', + // 'Write' => '', + // 'Write your text in Markdown' => '', + // 'New External Task: %s' => '', + // 'No personal API access token registered.' => '', + // 'Your personal API access token is "%s"' => '', + // 'Remove your token' => '', + // 'Generate a new token' => '', ); diff --git a/app/Locale/nl_NL/translations.php b/app/Locale/nl_NL/translations.php index e1745c14..44434a4e 100644 --- a/app/Locale/nl_NL/translations.php +++ b/app/Locale/nl_NL/translations.php @@ -1278,4 +1278,13 @@ return array( // 'Moving a task is not permitted' => '', // 'This value must be in the range %d to %d' => '', // 'You are not allowed to move this task.' => '', + // 'API User Access' => '', + // 'Preview' => '', + // 'Write' => '', + // 'Write your text in Markdown' => '', + // 'New External Task: %s' => '', + // 'No personal API access token registered.' => '', + // 'Your personal API access token is "%s"' => '', + // 'Remove your token' => '', + // 'Generate a new token' => '', ); diff --git a/app/Locale/pl_PL/translations.php b/app/Locale/pl_PL/translations.php index 164abe0c..51c0fef8 100644 --- a/app/Locale/pl_PL/translations.php +++ b/app/Locale/pl_PL/translations.php @@ -1278,4 +1278,13 @@ return array( // 'Moving a task is not permitted' => '', // 'This value must be in the range %d to %d' => '', // 'You are not allowed to move this task.' => '', + // 'API User Access' => '', + // 'Preview' => '', + // 'Write' => '', + // 'Write your text in Markdown' => '', + // 'New External Task: %s' => '', + // 'No personal API access token registered.' => '', + // 'Your personal API access token is "%s"' => '', + // 'Remove your token' => '', + // 'Generate a new token' => '', ); diff --git a/app/Locale/pt_BR/translations.php b/app/Locale/pt_BR/translations.php index 637de30e..c0fa2387 100644 --- a/app/Locale/pt_BR/translations.php +++ b/app/Locale/pt_BR/translations.php @@ -1278,4 +1278,13 @@ return array( 'Moving a task is not permitted' => 'Mover uma tarefa não é permitido', 'This value must be in the range %d to %d' => 'Este valor precisa estar no intervalo %d até %d', // 'You are not allowed to move this task.' => '', + // 'API User Access' => '', + // 'Preview' => '', + // 'Write' => '', + // 'Write your text in Markdown' => '', + // 'New External Task: %s' => '', + // 'No personal API access token registered.' => '', + // 'Your personal API access token is "%s"' => '', + // 'Remove your token' => '', + // 'Generate a new token' => '', ); diff --git a/app/Locale/pt_PT/translations.php b/app/Locale/pt_PT/translations.php index 3b8975d9..47763d30 100644 --- a/app/Locale/pt_PT/translations.php +++ b/app/Locale/pt_PT/translations.php @@ -1278,4 +1278,13 @@ return array( 'Moving a task is not permitted' => 'Não é permitido mover uma tarefa', 'This value must be in the range %d to %d' => 'Este valor deve estar entre %d e %d', 'You are not allowed to move this task.' => 'Não lhe é permitido mover esta tarefa.', + // 'API User Access' => '', + // 'Preview' => '', + // 'Write' => '', + // 'Write your text in Markdown' => '', + // 'New External Task: %s' => '', + // 'No personal API access token registered.' => '', + // 'Your personal API access token is "%s"' => '', + // 'Remove your token' => '', + // 'Generate a new token' => '', ); diff --git a/app/Locale/ru_RU/translations.php b/app/Locale/ru_RU/translations.php index 1e5c02c2..4d821f6a 100644 --- a/app/Locale/ru_RU/translations.php +++ b/app/Locale/ru_RU/translations.php @@ -1278,4 +1278,13 @@ return array( 'Moving a task is not permitted' => 'Перемещение задачи не разрешено', 'This value must be in the range %d to %d' => 'Значение должно находиться в диапазоне от %d до %d', // 'You are not allowed to move this task.' => '', + // 'API User Access' => '', + // 'Preview' => '', + // 'Write' => '', + // 'Write your text in Markdown' => '', + // 'New External Task: %s' => '', + // 'No personal API access token registered.' => '', + // 'Your personal API access token is "%s"' => '', + // 'Remove your token' => '', + // 'Generate a new token' => '', ); diff --git a/app/Locale/sr_Latn_RS/translations.php b/app/Locale/sr_Latn_RS/translations.php index 2f061260..470e3390 100644 --- a/app/Locale/sr_Latn_RS/translations.php +++ b/app/Locale/sr_Latn_RS/translations.php @@ -1278,4 +1278,13 @@ return array( // 'Moving a task is not permitted' => '', // 'This value must be in the range %d to %d' => '', // 'You are not allowed to move this task.' => '', + // 'API User Access' => '', + // 'Preview' => '', + // 'Write' => '', + // 'Write your text in Markdown' => '', + // 'New External Task: %s' => '', + // 'No personal API access token registered.' => '', + // 'Your personal API access token is "%s"' => '', + // 'Remove your token' => '', + // 'Generate a new token' => '', ); diff --git a/app/Locale/sv_SE/translations.php b/app/Locale/sv_SE/translations.php index f32728e7..2ec4fa82 100644 --- a/app/Locale/sv_SE/translations.php +++ b/app/Locale/sv_SE/translations.php @@ -1278,4 +1278,13 @@ return array( // 'Moving a task is not permitted' => '', // 'This value must be in the range %d to %d' => '', // 'You are not allowed to move this task.' => '', + // 'API User Access' => '', + // 'Preview' => '', + // 'Write' => '', + // 'Write your text in Markdown' => '', + // 'New External Task: %s' => '', + // 'No personal API access token registered.' => '', + // 'Your personal API access token is "%s"' => '', + // 'Remove your token' => '', + // 'Generate a new token' => '', ); diff --git a/app/Locale/th_TH/translations.php b/app/Locale/th_TH/translations.php index 4025714a..5e0912fc 100644 --- a/app/Locale/th_TH/translations.php +++ b/app/Locale/th_TH/translations.php @@ -1278,4 +1278,13 @@ return array( // 'Moving a task is not permitted' => '', // 'This value must be in the range %d to %d' => '', // 'You are not allowed to move this task.' => '', + // 'API User Access' => '', + // 'Preview' => '', + // 'Write' => '', + // 'Write your text in Markdown' => '', + // 'New External Task: %s' => '', + // 'No personal API access token registered.' => '', + // 'Your personal API access token is "%s"' => '', + // 'Remove your token' => '', + // 'Generate a new token' => '', ); diff --git a/app/Locale/tr_TR/translations.php b/app/Locale/tr_TR/translations.php index 24399891..1a648bbf 100644 --- a/app/Locale/tr_TR/translations.php +++ b/app/Locale/tr_TR/translations.php @@ -1278,4 +1278,13 @@ return array( 'Moving a task is not permitted' => 'Görev taşımaya izin verilmemiş', 'This value must be in the range %d to %d' => 'Bu değer şu aralıkta olmalı: "%d" "%d"', 'You are not allowed to move this task.' => 'Bu görevi taşımaya izniniz yok.', + // 'API User Access' => '', + // 'Preview' => '', + // 'Write' => '', + // 'Write your text in Markdown' => '', + // 'New External Task: %s' => '', + // 'No personal API access token registered.' => '', + // 'Your personal API access token is "%s"' => '', + // 'Remove your token' => '', + // 'Generate a new token' => '', ); diff --git a/app/Locale/zh_CN/translations.php b/app/Locale/zh_CN/translations.php index e27a8a56..c87adbec 100644 --- a/app/Locale/zh_CN/translations.php +++ b/app/Locale/zh_CN/translations.php @@ -1278,4 +1278,13 @@ return array( 'Moving a task is not permitted' => '禁止移动任务', 'This value must be in the range %d to %d' => '输入值必须在%d到%d之间', 'You are not allowed to move this task.' => '你不能移动此任务', + // 'API User Access' => '', + // 'Preview' => '', + // 'Write' => '', + // 'Write your text in Markdown' => '', + // 'New External Task: %s' => '', + // 'No personal API access token registered.' => '', + // 'Your personal API access token is "%s"' => '', + // 'Remove your token' => '', + // 'Generate a new token' => '', ); diff --git a/app/Model/ProjectPermissionModel.php b/app/Model/ProjectPermissionModel.php index 25b6a382..dabd406c 100644 --- a/app/Model/ProjectPermissionModel.php +++ b/app/Model/ProjectPermissionModel.php @@ -62,17 +62,33 @@ class ProjectPermissionModel extends Base ->withFilter(new ProjectUserRoleProjectFilter($project_id)) ->withFilter(new ProjectUserRoleUsernameFilter($input)) ->getQuery() - ->findAllByColumn('username'); + ->columns( + UserModel::TABLE.'.id', + UserModel::TABLE.'.username', + UserModel::TABLE.'.name', + UserModel::TABLE.'.email', + UserModel::TABLE.'.avatar_path' + ) + ->findAll(); $groupMembers = $this->projectGroupRoleQuery ->withFilter(new ProjectGroupRoleProjectFilter($project_id)) ->withFilter(new ProjectGroupRoleUsernameFilter($input)) ->getQuery() - ->findAllByColumn('username'); + ->columns( + UserModel::TABLE.'.id', + UserModel::TABLE.'.username', + UserModel::TABLE.'.name', + UserModel::TABLE.'.email', + UserModel::TABLE.'.avatar_path' + ) + ->findAll(); - $members = array_unique(array_merge($userMembers, $groupMembers)); + $userMembers = array_column_index_unique($userMembers, 'username'); + $groupMembers = array_column_index_unique($groupMembers, 'username'); + $members = array_merge($userMembers, $groupMembers); - sort($members); + ksort($members); return $members; } diff --git a/app/Model/UserMentionModel.php b/app/Model/UserMentionModel.php deleted file mode 100644 index cdb9949e..00000000 --- a/app/Model/UserMentionModel.php +++ /dev/null @@ -1,62 +0,0 @@ -<?php - -namespace Kanboard\Model; - -use Kanboard\Core\Base; -use Kanboard\Event\GenericEvent; - -/** - * User Mention - * - * @package Kanboard\Model - * @author Frederic Guillot - */ -class UserMentionModel extends Base -{ - /** - * Get list of mentioned users - * - * @access public - * @param string $content - * @return array - */ - public function getMentionedUsers($content) - { - $users = array(); - - if (preg_match_all('/@([^\s]+)/', $content, $matches)) { - $users = $this->db->table(UserModel::TABLE) - ->columns('id', 'username', 'name', 'email', 'language') - ->eq('notifications_enabled', 1) - ->neq('id', $this->userSession->getId()) - ->in('username', array_unique($matches[1])) - ->findAll(); - } - - return $users; - } - - /** - * Fire events for user mentions - * - * @access public - * @param string $content - * @param string $eventName - * @param GenericEvent $event - */ - public function fireEvents($content, $eventName, GenericEvent $event) - { - if (empty($event['project_id'])) { - $event['project_id'] = $this->taskFinderModel->getProjectId($event['task_id']); - } - - $users = $this->getMentionedUsers($content); - - foreach ($users as $user) { - if ($this->projectPermissionModel->isMember($event['project_id'], $user['id'])) { - $event['mention'] = $user; - $this->dispatcher->dispatch($eventName, $event); - } - } - } -} diff --git a/app/Schema/Mysql.php b/app/Schema/Mysql.php index b50164ca..fac8688a 100644 --- a/app/Schema/Mysql.php +++ b/app/Schema/Mysql.php @@ -6,7 +6,17 @@ use PDO; use Kanboard\Core\Security\Token; use Kanboard\Core\Security\Role; -const VERSION = 116; +const VERSION = 118; + +function version_118(PDO $pdo) +{ + $pdo->exec('ALTER TABLE `users` ADD COLUMN `api_access_token` VARCHAR(255) DEFAULT NULL'); +} + +function version_117(PDO $pdo) +{ + $pdo->exec("ALTER TABLE `settings` MODIFY `value` TEXT"); +} function version_116(PDO $pdo) { diff --git a/app/Schema/Postgres.php b/app/Schema/Postgres.php index 83926f19..32a7a744 100644 --- a/app/Schema/Postgres.php +++ b/app/Schema/Postgres.php @@ -6,7 +6,17 @@ use PDO; use Kanboard\Core\Security\Token; use Kanboard\Core\Security\Role; -const VERSION = 95; +const VERSION = 97; + +function version_97(PDO $pdo) +{ + $pdo->exec('ALTER TABLE "users" ADD COLUMN api_access_token VARCHAR(255) DEFAULT NULL'); +} + +function version_96(PDO $pdo) +{ + $pdo->exec('ALTER TABLE "settings" ALTER COLUMN "value" TYPE TEXT'); +} function version_95(PDO $pdo) { diff --git a/app/Schema/Sql/mysql.sql b/app/Schema/Sql/mysql.sql index 8d494dcf..0ee88d88 100644 --- a/app/Schema/Sql/mysql.sql +++ b/app/Schema/Sql/mysql.sql @@ -35,6 +35,44 @@ CREATE TABLE `actions` ( CONSTRAINT `actions_ibfk_1` FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `column_has_move_restrictions`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `column_has_move_restrictions` ( + `restriction_id` int(11) NOT NULL AUTO_INCREMENT, + `project_id` int(11) NOT NULL, + `role_id` int(11) NOT NULL, + `src_column_id` int(11) NOT NULL, + `dst_column_id` int(11) NOT NULL, + PRIMARY KEY (`restriction_id`), + UNIQUE KEY `role_id` (`role_id`,`src_column_id`,`dst_column_id`), + KEY `project_id` (`project_id`), + KEY `src_column_id` (`src_column_id`), + KEY `dst_column_id` (`dst_column_id`), + CONSTRAINT `column_has_move_restrictions_ibfk_1` FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON DELETE CASCADE, + CONSTRAINT `column_has_move_restrictions_ibfk_2` FOREIGN KEY (`role_id`) REFERENCES `project_has_roles` (`role_id`) ON DELETE CASCADE, + CONSTRAINT `column_has_move_restrictions_ibfk_3` FOREIGN KEY (`src_column_id`) REFERENCES `columns` (`id`) ON DELETE CASCADE, + CONSTRAINT `column_has_move_restrictions_ibfk_4` FOREIGN KEY (`dst_column_id`) REFERENCES `columns` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +/*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `column_has_restrictions`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `column_has_restrictions` ( + `restriction_id` int(11) NOT NULL AUTO_INCREMENT, + `project_id` int(11) NOT NULL, + `role_id` int(11) NOT NULL, + `column_id` int(11) NOT NULL, + `rule` varchar(255) NOT NULL, + PRIMARY KEY (`restriction_id`), + UNIQUE KEY `role_id` (`role_id`,`column_id`,`rule`), + KEY `project_id` (`project_id`), + KEY `column_id` (`column_id`), + CONSTRAINT `column_has_restrictions_ibfk_1` FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON DELETE CASCADE, + CONSTRAINT `column_has_restrictions_ibfk_2` FOREIGN KEY (`role_id`) REFERENCES `project_has_roles` (`role_id`) ON DELETE CASCADE, + CONSTRAINT `column_has_restrictions_ibfk_3` FOREIGN KEY (`column_id`) REFERENCES `columns` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +/*!40101 SET character_set_client = @saved_cs_client */; DROP TABLE IF EXISTS `columns`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; @@ -260,7 +298,7 @@ DROP TABLE IF EXISTS `project_has_groups`; CREATE TABLE `project_has_groups` ( `group_id` int(11) NOT NULL, `project_id` int(11) NOT NULL, - `role` varchar(25) NOT NULL, + `role` varchar(255) NOT NULL, UNIQUE KEY `group_id` (`group_id`,`project_id`), KEY `project_id` (`project_id`), CONSTRAINT `project_has_groups_ibfk_1` FOREIGN KEY (`group_id`) REFERENCES `groups` (`id`) ON DELETE CASCADE, @@ -292,19 +330,46 @@ CREATE TABLE `project_has_notification_types` ( CONSTRAINT `project_has_notification_types_ibfk_1` FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `project_has_roles`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `project_has_roles` ( + `role_id` int(11) NOT NULL AUTO_INCREMENT, + `role` varchar(255) NOT NULL, + `project_id` int(11) NOT NULL, + PRIMARY KEY (`role_id`), + UNIQUE KEY `project_id` (`project_id`,`role`), + CONSTRAINT `project_has_roles_ibfk_1` FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +/*!40101 SET character_set_client = @saved_cs_client */; DROP TABLE IF EXISTS `project_has_users`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `project_has_users` ( `project_id` int(11) NOT NULL, `user_id` int(11) NOT NULL, - `role` varchar(25) NOT NULL DEFAULT 'project-viewer', + `role` varchar(255) NOT NULL, UNIQUE KEY `idx_project_user` (`project_id`,`user_id`), KEY `user_id` (`user_id`), CONSTRAINT `project_has_users_ibfk_1` FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON DELETE CASCADE, CONSTRAINT `project_has_users_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; +DROP TABLE IF EXISTS `project_role_has_restrictions`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `project_role_has_restrictions` ( + `restriction_id` int(11) NOT NULL AUTO_INCREMENT, + `project_id` int(11) NOT NULL, + `role_id` int(11) NOT NULL, + `rule` varchar(255) NOT NULL, + PRIMARY KEY (`restriction_id`), + UNIQUE KEY `role_id` (`role_id`,`rule`), + KEY `project_id` (`project_id`), + CONSTRAINT `project_role_has_restrictions_ibfk_1` FOREIGN KEY (`project_id`) REFERENCES `projects` (`id`) ON DELETE CASCADE, + CONSTRAINT `project_role_has_restrictions_ibfk_2` FOREIGN KEY (`role_id`) REFERENCES `project_has_roles` (`role_id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +/*!40101 SET character_set_client = @saved_cs_client */; DROP TABLE IF EXISTS `projects`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; @@ -359,7 +424,7 @@ DROP TABLE IF EXISTS `settings`; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `settings` ( `option` varchar(100) NOT NULL, - `value` varchar(255) DEFAULT '', + `value` text, `changed_by` int(11) NOT NULL DEFAULT '0', `changed_on` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`option`) @@ -537,6 +602,8 @@ CREATE TABLE `tasks` ( `recurrence_parent` int(11) DEFAULT NULL, `recurrence_child` int(11) DEFAULT NULL, `priority` int(11) DEFAULT '0', + `external_provider` varchar(255) DEFAULT NULL, + `external_uri` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_task_active` (`is_active`), KEY `column_id` (`column_id`), @@ -648,6 +715,7 @@ CREATE TABLE `users` ( `role` varchar(25) NOT NULL DEFAULT 'app-user', `is_active` tinyint(1) DEFAULT '1', `avatar_path` varchar(255) DEFAULT NULL, + `api_access_token` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `users_username_idx` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; @@ -671,7 +739,7 @@ CREATE TABLE `users` ( LOCK TABLES `settings` WRITE; /*!40000 ALTER TABLE `settings` DISABLE KEYS */; -INSERT INTO `settings` VALUES ('api_token','4064ef3d26efa9a0ff78fa7067d8bb9d99323455128edd89e9dc7c53ed76',0,0),('application_currency','USD',0,0),('application_date_format','m/d/Y',0,0),('application_language','en_US',0,0),('application_stylesheet','',0,0),('application_timezone','UTC',0,0),('application_url','',0,0),('board_columns','',0,0),('board_highlight_period','172800',0,0),('board_private_refresh_interval','10',0,0),('board_public_refresh_interval','60',0,0),('calendar_project_tasks','date_started',0,0),('calendar_user_subtasks_time_tracking','0',0,0),('calendar_user_tasks','date_started',0,0),('cfd_include_closed_tasks','1',0,0),('default_color','yellow',0,0),('integration_gravatar','0',0,0),('password_reset','1',0,0),('project_categories','',0,0),('subtask_restriction','0',0,0),('subtask_time_tracking','1',0,0),('webhook_token','c8f53c0bcd8aead902ad04f180ffafd7889b9c0062c2d510e2297ef543b8',0,0),('webhook_url','',0,0); +INSERT INTO `settings` VALUES ('api_token','f149956cb60c88d01123a28964fc035b1ce4513be454f2a85fe6b4ca3758',0,0),('application_currency','USD',0,0),('application_date_format','m/d/Y',0,0),('application_language','en_US',0,0),('application_stylesheet','',0,0),('application_timezone','UTC',0,0),('application_url','',0,0),('board_columns','',0,0),('board_highlight_period','172800',0,0),('board_private_refresh_interval','10',0,0),('board_public_refresh_interval','60',0,0),('calendar_project_tasks','date_started',0,0),('calendar_user_subtasks_time_tracking','0',0,0),('calendar_user_tasks','date_started',0,0),('cfd_include_closed_tasks','1',0,0),('default_color','yellow',0,0),('integration_gravatar','0',0,0),('password_reset','1',0,0),('project_categories','',0,0),('subtask_restriction','0',0,0),('subtask_time_tracking','1',0,0),('webhook_token','47d1d896b6612234c7543eb3f3a09a0a669f77a079d13ad3d810ccb79896',0,0),('webhook_url','',0,0); /*!40000 ALTER TABLE `settings` ENABLE KEYS */; UNLOCK TABLES; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; @@ -700,4 +768,4 @@ UNLOCK TABLES; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; -INSERT INTO users (username, password, role) VALUES ('admin', '$2y$10$yUJ9QnhG.f47yO.YvWKo3eMAHULukpluDNTOF9.Z7QQg0vOfFRB6u', 'app-admin');INSERT INTO schema_version VALUES ('112'); +INSERT INTO users (username, password, role) VALUES ('admin', '$2y$10$R1zYk04d96KcHRpd9.r5I.5I6mgKIgUdsaISZYmaDLPIJCUO0FFJG', 'app-admin');INSERT INTO schema_version VALUES ('118'); diff --git a/app/Schema/Sql/postgres.sql b/app/Schema/Sql/postgres.sql index 0add9c91..578b0c75 100644 --- a/app/Schema/Sql/postgres.sql +++ b/app/Schema/Sql/postgres.sql @@ -2,8 +2,8 @@ -- PostgreSQL database dump -- --- Dumped from database version 9.5.2 --- Dumped by pg_dump version 9.5.2 +-- Dumped from database version 9.6.1 +-- Dumped by pg_dump version 9.6.1 SET statement_timeout = 0; SET lock_timeout = 0; @@ -89,6 +89,70 @@ ALTER SEQUENCE "actions_id_seq" OWNED BY "actions"."id"; -- +-- Name: column_has_move_restrictions; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "column_has_move_restrictions" ( + "restriction_id" integer NOT NULL, + "project_id" integer NOT NULL, + "role_id" integer NOT NULL, + "src_column_id" integer NOT NULL, + "dst_column_id" integer NOT NULL +); + + +-- +-- Name: column_has_move_restrictions_restriction_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "column_has_move_restrictions_restriction_id_seq" + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: column_has_move_restrictions_restriction_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "column_has_move_restrictions_restriction_id_seq" OWNED BY "column_has_move_restrictions"."restriction_id"; + + +-- +-- Name: column_has_restrictions; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "column_has_restrictions" ( + "restriction_id" integer NOT NULL, + "project_id" integer NOT NULL, + "role_id" integer NOT NULL, + "column_id" integer NOT NULL, + "rule" character varying(255) NOT NULL +); + + +-- +-- Name: column_has_restrictions_restriction_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "column_has_restrictions_restriction_id_seq" + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: column_has_restrictions_restriction_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "column_has_restrictions_restriction_id_seq" OWNED BY "column_has_restrictions"."restriction_id"; + + +-- -- Name: columns; Type: TABLE; Schema: public; Owner: - -- @@ -499,7 +563,7 @@ ALTER SEQUENCE "project_has_files_id_seq" OWNED BY "project_has_files"."id"; CREATE TABLE "project_has_groups" ( "group_id" integer NOT NULL, "project_id" integer NOT NULL, - "role" character varying(25) NOT NULL + "role" character varying(255) NOT NULL ); @@ -547,17 +611,78 @@ ALTER SEQUENCE "project_has_notification_types_id_seq" OWNED BY "project_has_not -- +-- Name: project_has_roles; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "project_has_roles" ( + "role_id" integer NOT NULL, + "role" character varying(255) NOT NULL, + "project_id" integer NOT NULL +); + + +-- +-- Name: project_has_roles_role_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "project_has_roles_role_id_seq" + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: project_has_roles_role_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "project_has_roles_role_id_seq" OWNED BY "project_has_roles"."role_id"; + + +-- -- Name: project_has_users; Type: TABLE; Schema: public; Owner: - -- CREATE TABLE "project_has_users" ( "project_id" integer NOT NULL, "user_id" integer NOT NULL, - "role" character varying(25) DEFAULT 'project-viewer'::character varying NOT NULL + "role" character varying(255) DEFAULT 'project-viewer'::character varying NOT NULL +); + + +-- +-- Name: project_role_has_restrictions; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE "project_role_has_restrictions" ( + "restriction_id" integer NOT NULL, + "project_id" integer NOT NULL, + "role_id" integer NOT NULL, + "rule" character varying(255) NOT NULL ); -- +-- Name: project_role_has_restrictions_restriction_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE "project_role_has_restrictions_restriction_id_seq" + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: project_role_has_restrictions_restriction_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE "project_role_has_restrictions_restriction_id_seq" OWNED BY "project_role_has_restrictions"."restriction_id"; + + +-- -- Name: projects; Type: TABLE; Schema: public; Owner: - -- @@ -652,7 +777,7 @@ CREATE TABLE "schema_version" ( CREATE TABLE "settings" ( "option" character varying(100) NOT NULL, - "value" character varying(255) DEFAULT ''::character varying, + "value" "text" DEFAULT ''::character varying, "changed_by" integer DEFAULT 0 NOT NULL, "changed_on" integer DEFAULT 0 NOT NULL ); @@ -948,7 +1073,9 @@ CREATE TABLE "tasks" ( "recurrence_basedate" integer DEFAULT 0 NOT NULL, "recurrence_parent" integer, "recurrence_child" integer, - "priority" integer DEFAULT 0 + "priority" integer DEFAULT 0, + "external_provider" character varying(255), + "external_uri" character varying(255) ); @@ -1117,7 +1244,8 @@ CREATE TABLE "users" ( "gitlab_id" integer, "role" character varying(25) DEFAULT 'app-user'::character varying NOT NULL, "is_active" boolean DEFAULT true, - "avatar_path" character varying(255) + "avatar_path" character varying(255), + "api_access_token" character varying(255) DEFAULT NULL::character varying ); @@ -1141,203 +1269,231 @@ ALTER SEQUENCE "users_id_seq" OWNED BY "users"."id"; -- --- Name: id; Type: DEFAULT; Schema: public; Owner: - +-- Name: action_has_params id; Type: DEFAULT; Schema: public; Owner: - -- ALTER TABLE ONLY "action_has_params" ALTER COLUMN "id" SET DEFAULT "nextval"('"action_has_params_id_seq"'::"regclass"); -- --- Name: id; Type: DEFAULT; Schema: public; Owner: - +-- Name: actions id; Type: DEFAULT; Schema: public; Owner: - -- ALTER TABLE ONLY "actions" ALTER COLUMN "id" SET DEFAULT "nextval"('"actions_id_seq"'::"regclass"); -- --- Name: id; Type: DEFAULT; Schema: public; Owner: - +-- Name: column_has_move_restrictions restriction_id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "column_has_move_restrictions" ALTER COLUMN "restriction_id" SET DEFAULT "nextval"('"column_has_move_restrictions_restriction_id_seq"'::"regclass"); + + +-- +-- Name: column_has_restrictions restriction_id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "column_has_restrictions" ALTER COLUMN "restriction_id" SET DEFAULT "nextval"('"column_has_restrictions_restriction_id_seq"'::"regclass"); + + +-- +-- Name: columns id; Type: DEFAULT; Schema: public; Owner: - -- ALTER TABLE ONLY "columns" ALTER COLUMN "id" SET DEFAULT "nextval"('"columns_id_seq"'::"regclass"); -- --- Name: id; Type: DEFAULT; Schema: public; Owner: - +-- Name: comments id; Type: DEFAULT; Schema: public; Owner: - -- ALTER TABLE ONLY "comments" ALTER COLUMN "id" SET DEFAULT "nextval"('"comments_id_seq"'::"regclass"); -- --- Name: id; Type: DEFAULT; Schema: public; Owner: - +-- Name: custom_filters id; Type: DEFAULT; Schema: public; Owner: - -- ALTER TABLE ONLY "custom_filters" ALTER COLUMN "id" SET DEFAULT "nextval"('"custom_filters_id_seq"'::"regclass"); -- --- Name: id; Type: DEFAULT; Schema: public; Owner: - +-- Name: groups id; Type: DEFAULT; Schema: public; Owner: - -- ALTER TABLE ONLY "groups" ALTER COLUMN "id" SET DEFAULT "nextval"('"groups_id_seq"'::"regclass"); -- --- Name: id; Type: DEFAULT; Schema: public; Owner: - +-- Name: last_logins id; Type: DEFAULT; Schema: public; Owner: - -- ALTER TABLE ONLY "last_logins" ALTER COLUMN "id" SET DEFAULT "nextval"('"last_logins_id_seq"'::"regclass"); -- --- Name: id; Type: DEFAULT; Schema: public; Owner: - +-- Name: links id; Type: DEFAULT; Schema: public; Owner: - -- ALTER TABLE ONLY "links" ALTER COLUMN "id" SET DEFAULT "nextval"('"links_id_seq"'::"regclass"); -- --- Name: id; Type: DEFAULT; Schema: public; Owner: - +-- Name: project_activities id; Type: DEFAULT; Schema: public; Owner: - -- ALTER TABLE ONLY "project_activities" ALTER COLUMN "id" SET DEFAULT "nextval"('"project_activities_id_seq"'::"regclass"); -- --- Name: id; Type: DEFAULT; Schema: public; Owner: - +-- Name: project_daily_column_stats id; Type: DEFAULT; Schema: public; Owner: - -- ALTER TABLE ONLY "project_daily_column_stats" ALTER COLUMN "id" SET DEFAULT "nextval"('"project_daily_summaries_id_seq"'::"regclass"); -- --- Name: id; Type: DEFAULT; Schema: public; Owner: - +-- Name: project_daily_stats id; Type: DEFAULT; Schema: public; Owner: - -- ALTER TABLE ONLY "project_daily_stats" ALTER COLUMN "id" SET DEFAULT "nextval"('"project_daily_stats_id_seq"'::"regclass"); -- --- Name: id; Type: DEFAULT; Schema: public; Owner: - +-- Name: project_has_categories id; Type: DEFAULT; Schema: public; Owner: - -- ALTER TABLE ONLY "project_has_categories" ALTER COLUMN "id" SET DEFAULT "nextval"('"project_has_categories_id_seq"'::"regclass"); -- --- Name: id; Type: DEFAULT; Schema: public; Owner: - +-- Name: project_has_files id; Type: DEFAULT; Schema: public; Owner: - -- ALTER TABLE ONLY "project_has_files" ALTER COLUMN "id" SET DEFAULT "nextval"('"project_has_files_id_seq"'::"regclass"); -- --- Name: id; Type: DEFAULT; Schema: public; Owner: - +-- Name: project_has_notification_types id; Type: DEFAULT; Schema: public; Owner: - -- ALTER TABLE ONLY "project_has_notification_types" ALTER COLUMN "id" SET DEFAULT "nextval"('"project_has_notification_types_id_seq"'::"regclass"); -- --- Name: id; Type: DEFAULT; Schema: public; Owner: - +-- Name: project_has_roles role_id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "project_has_roles" ALTER COLUMN "role_id" SET DEFAULT "nextval"('"project_has_roles_role_id_seq"'::"regclass"); + + +-- +-- Name: project_role_has_restrictions restriction_id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "project_role_has_restrictions" ALTER COLUMN "restriction_id" SET DEFAULT "nextval"('"project_role_has_restrictions_restriction_id_seq"'::"regclass"); + + +-- +-- Name: projects id; Type: DEFAULT; Schema: public; Owner: - -- ALTER TABLE ONLY "projects" ALTER COLUMN "id" SET DEFAULT "nextval"('"projects_id_seq"'::"regclass"); -- --- Name: id; Type: DEFAULT; Schema: public; Owner: - +-- Name: remember_me id; Type: DEFAULT; Schema: public; Owner: - -- ALTER TABLE ONLY "remember_me" ALTER COLUMN "id" SET DEFAULT "nextval"('"remember_me_id_seq"'::"regclass"); -- --- Name: id; Type: DEFAULT; Schema: public; Owner: - +-- Name: subtask_time_tracking id; Type: DEFAULT; Schema: public; Owner: - -- ALTER TABLE ONLY "subtask_time_tracking" ALTER COLUMN "id" SET DEFAULT "nextval"('"subtask_time_tracking_id_seq"'::"regclass"); -- --- Name: id; Type: DEFAULT; Schema: public; Owner: - +-- Name: subtasks id; Type: DEFAULT; Schema: public; Owner: - -- ALTER TABLE ONLY "subtasks" ALTER COLUMN "id" SET DEFAULT "nextval"('"task_has_subtasks_id_seq"'::"regclass"); -- --- Name: id; Type: DEFAULT; Schema: public; Owner: - +-- Name: swimlanes id; Type: DEFAULT; Schema: public; Owner: - -- ALTER TABLE ONLY "swimlanes" ALTER COLUMN "id" SET DEFAULT "nextval"('"swimlanes_id_seq"'::"regclass"); -- --- Name: id; Type: DEFAULT; Schema: public; Owner: - +-- Name: tags id; Type: DEFAULT; Schema: public; Owner: - -- ALTER TABLE ONLY "tags" ALTER COLUMN "id" SET DEFAULT "nextval"('"tags_id_seq"'::"regclass"); -- --- Name: id; Type: DEFAULT; Schema: public; Owner: - +-- Name: task_has_external_links id; Type: DEFAULT; Schema: public; Owner: - -- ALTER TABLE ONLY "task_has_external_links" ALTER COLUMN "id" SET DEFAULT "nextval"('"task_has_external_links_id_seq"'::"regclass"); -- --- Name: id; Type: DEFAULT; Schema: public; Owner: - +-- Name: task_has_files id; Type: DEFAULT; Schema: public; Owner: - -- ALTER TABLE ONLY "task_has_files" ALTER COLUMN "id" SET DEFAULT "nextval"('"task_has_files_id_seq"'::"regclass"); -- --- Name: id; Type: DEFAULT; Schema: public; Owner: - +-- Name: task_has_links id; Type: DEFAULT; Schema: public; Owner: - -- ALTER TABLE ONLY "task_has_links" ALTER COLUMN "id" SET DEFAULT "nextval"('"task_has_links_id_seq"'::"regclass"); -- --- Name: id; Type: DEFAULT; Schema: public; Owner: - +-- Name: tasks id; Type: DEFAULT; Schema: public; Owner: - -- ALTER TABLE ONLY "tasks" ALTER COLUMN "id" SET DEFAULT "nextval"('"tasks_id_seq"'::"regclass"); -- --- Name: id; Type: DEFAULT; Schema: public; Owner: - +-- Name: transitions id; Type: DEFAULT; Schema: public; Owner: - -- ALTER TABLE ONLY "transitions" ALTER COLUMN "id" SET DEFAULT "nextval"('"transitions_id_seq"'::"regclass"); -- --- Name: id; Type: DEFAULT; Schema: public; Owner: - +-- Name: user_has_notification_types id; Type: DEFAULT; Schema: public; Owner: - -- ALTER TABLE ONLY "user_has_notification_types" ALTER COLUMN "id" SET DEFAULT "nextval"('"user_has_notification_types_id_seq"'::"regclass"); -- --- Name: id; Type: DEFAULT; Schema: public; Owner: - +-- Name: user_has_unread_notifications id; Type: DEFAULT; Schema: public; Owner: - -- ALTER TABLE ONLY "user_has_unread_notifications" ALTER COLUMN "id" SET DEFAULT "nextval"('"user_has_unread_notifications_id_seq"'::"regclass"); -- --- Name: id; Type: DEFAULT; Schema: public; Owner: - +-- Name: users id; Type: DEFAULT; Schema: public; Owner: - -- ALTER TABLE ONLY "users" ALTER COLUMN "id" SET DEFAULT "nextval"('"users_id_seq"'::"regclass"); -- --- Name: action_has_params_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: action_has_params action_has_params_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "action_has_params" @@ -1345,7 +1501,7 @@ ALTER TABLE ONLY "action_has_params" -- --- Name: actions_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: actions actions_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "actions" @@ -1353,7 +1509,39 @@ ALTER TABLE ONLY "actions" -- --- Name: columns_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: column_has_move_restrictions column_has_move_restrictions_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "column_has_move_restrictions" + ADD CONSTRAINT "column_has_move_restrictions_pkey" PRIMARY KEY ("restriction_id"); + + +-- +-- Name: column_has_move_restrictions column_has_move_restrictions_role_id_src_column_id_dst_colu_key; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "column_has_move_restrictions" + ADD CONSTRAINT "column_has_move_restrictions_role_id_src_column_id_dst_colu_key" UNIQUE ("role_id", "src_column_id", "dst_column_id"); + + +-- +-- Name: column_has_restrictions column_has_restrictions_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "column_has_restrictions" + ADD CONSTRAINT "column_has_restrictions_pkey" PRIMARY KEY ("restriction_id"); + + +-- +-- Name: column_has_restrictions column_has_restrictions_role_id_column_id_rule_key; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "column_has_restrictions" + ADD CONSTRAINT "column_has_restrictions_role_id_column_id_rule_key" UNIQUE ("role_id", "column_id", "rule"); + + +-- +-- Name: columns columns_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "columns" @@ -1361,7 +1549,7 @@ ALTER TABLE ONLY "columns" -- --- Name: columns_title_project_id_key; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: columns columns_title_project_id_key; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "columns" @@ -1369,7 +1557,7 @@ ALTER TABLE ONLY "columns" -- --- Name: comments_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: comments comments_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "comments" @@ -1377,7 +1565,7 @@ ALTER TABLE ONLY "comments" -- --- Name: currencies_currency_key; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: currencies currencies_currency_key; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "currencies" @@ -1385,7 +1573,7 @@ ALTER TABLE ONLY "currencies" -- --- Name: custom_filters_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: custom_filters custom_filters_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "custom_filters" @@ -1393,7 +1581,7 @@ ALTER TABLE ONLY "custom_filters" -- --- Name: group_has_users_group_id_user_id_key; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: group_has_users group_has_users_group_id_user_id_key; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "group_has_users" @@ -1401,7 +1589,7 @@ ALTER TABLE ONLY "group_has_users" -- --- Name: groups_name_key; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: groups groups_name_key; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "groups" @@ -1409,7 +1597,7 @@ ALTER TABLE ONLY "groups" -- --- Name: groups_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: groups groups_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "groups" @@ -1417,7 +1605,7 @@ ALTER TABLE ONLY "groups" -- --- Name: last_logins_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: last_logins last_logins_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "last_logins" @@ -1425,7 +1613,7 @@ ALTER TABLE ONLY "last_logins" -- --- Name: links_label_key; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: links links_label_key; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "links" @@ -1433,7 +1621,7 @@ ALTER TABLE ONLY "links" -- --- Name: links_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: links links_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "links" @@ -1441,7 +1629,7 @@ ALTER TABLE ONLY "links" -- --- Name: password_reset_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: password_reset password_reset_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "password_reset" @@ -1449,7 +1637,7 @@ ALTER TABLE ONLY "password_reset" -- --- Name: plugin_schema_versions_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: plugin_schema_versions plugin_schema_versions_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "plugin_schema_versions" @@ -1457,7 +1645,7 @@ ALTER TABLE ONLY "plugin_schema_versions" -- --- Name: project_activities_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: project_activities project_activities_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "project_activities" @@ -1465,7 +1653,7 @@ ALTER TABLE ONLY "project_activities" -- --- Name: project_daily_stats_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: project_daily_stats project_daily_stats_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "project_daily_stats" @@ -1473,7 +1661,7 @@ ALTER TABLE ONLY "project_daily_stats" -- --- Name: project_daily_summaries_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: project_daily_column_stats project_daily_summaries_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "project_daily_column_stats" @@ -1481,7 +1669,7 @@ ALTER TABLE ONLY "project_daily_column_stats" -- --- Name: project_has_categories_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: project_has_categories project_has_categories_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "project_has_categories" @@ -1489,7 +1677,7 @@ ALTER TABLE ONLY "project_has_categories" -- --- Name: project_has_categories_project_id_name_key; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: project_has_categories project_has_categories_project_id_name_key; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "project_has_categories" @@ -1497,7 +1685,7 @@ ALTER TABLE ONLY "project_has_categories" -- --- Name: project_has_files_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: project_has_files project_has_files_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "project_has_files" @@ -1505,7 +1693,7 @@ ALTER TABLE ONLY "project_has_files" -- --- Name: project_has_groups_group_id_project_id_key; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: project_has_groups project_has_groups_group_id_project_id_key; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "project_has_groups" @@ -1513,7 +1701,7 @@ ALTER TABLE ONLY "project_has_groups" -- --- Name: project_has_metadata_project_id_name_key; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: project_has_metadata project_has_metadata_project_id_name_key; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "project_has_metadata" @@ -1521,7 +1709,7 @@ ALTER TABLE ONLY "project_has_metadata" -- --- Name: project_has_notification_types_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: project_has_notification_types project_has_notification_types_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "project_has_notification_types" @@ -1529,7 +1717,7 @@ ALTER TABLE ONLY "project_has_notification_types" -- --- Name: project_has_notification_types_project_id_notification_type_key; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: project_has_notification_types project_has_notification_types_project_id_notification_type_key; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "project_has_notification_types" @@ -1537,7 +1725,23 @@ ALTER TABLE ONLY "project_has_notification_types" -- --- Name: project_has_users_project_id_user_id_key; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: project_has_roles project_has_roles_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "project_has_roles" + ADD CONSTRAINT "project_has_roles_pkey" PRIMARY KEY ("role_id"); + + +-- +-- Name: project_has_roles project_has_roles_project_id_role_key; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "project_has_roles" + ADD CONSTRAINT "project_has_roles_project_id_role_key" UNIQUE ("project_id", "role"); + + +-- +-- Name: project_has_users project_has_users_project_id_user_id_key; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "project_has_users" @@ -1545,7 +1749,23 @@ ALTER TABLE ONLY "project_has_users" -- --- Name: projects_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: project_role_has_restrictions project_role_has_restrictions_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "project_role_has_restrictions" + ADD CONSTRAINT "project_role_has_restrictions_pkey" PRIMARY KEY ("restriction_id"); + + +-- +-- Name: project_role_has_restrictions project_role_has_restrictions_role_id_rule_key; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "project_role_has_restrictions" + ADD CONSTRAINT "project_role_has_restrictions_role_id_rule_key" UNIQUE ("role_id", "rule"); + + +-- +-- Name: projects projects_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "projects" @@ -1553,7 +1773,7 @@ ALTER TABLE ONLY "projects" -- --- Name: remember_me_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: remember_me remember_me_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "remember_me" @@ -1561,7 +1781,7 @@ ALTER TABLE ONLY "remember_me" -- --- Name: settings_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: settings settings_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "settings" @@ -1569,7 +1789,7 @@ ALTER TABLE ONLY "settings" -- --- Name: subtask_time_tracking_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: subtask_time_tracking subtask_time_tracking_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "subtask_time_tracking" @@ -1577,7 +1797,7 @@ ALTER TABLE ONLY "subtask_time_tracking" -- --- Name: swimlanes_name_project_id_key; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: swimlanes swimlanes_name_project_id_key; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "swimlanes" @@ -1585,7 +1805,7 @@ ALTER TABLE ONLY "swimlanes" -- --- Name: swimlanes_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: swimlanes swimlanes_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "swimlanes" @@ -1593,7 +1813,7 @@ ALTER TABLE ONLY "swimlanes" -- --- Name: tags_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: tags tags_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "tags" @@ -1601,7 +1821,7 @@ ALTER TABLE ONLY "tags" -- --- Name: tags_project_id_name_key; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: tags tags_project_id_name_key; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "tags" @@ -1609,7 +1829,7 @@ ALTER TABLE ONLY "tags" -- --- Name: task_has_external_links_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: task_has_external_links task_has_external_links_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "task_has_external_links" @@ -1617,7 +1837,7 @@ ALTER TABLE ONLY "task_has_external_links" -- --- Name: task_has_files_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: task_has_files task_has_files_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "task_has_files" @@ -1625,7 +1845,7 @@ ALTER TABLE ONLY "task_has_files" -- --- Name: task_has_links_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: task_has_links task_has_links_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "task_has_links" @@ -1633,7 +1853,7 @@ ALTER TABLE ONLY "task_has_links" -- --- Name: task_has_metadata_task_id_name_key; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: task_has_metadata task_has_metadata_task_id_name_key; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "task_has_metadata" @@ -1641,7 +1861,7 @@ ALTER TABLE ONLY "task_has_metadata" -- --- Name: task_has_subtasks_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: subtasks task_has_subtasks_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "subtasks" @@ -1649,7 +1869,7 @@ ALTER TABLE ONLY "subtasks" -- --- Name: task_has_tags_tag_id_task_id_key; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: task_has_tags task_has_tags_tag_id_task_id_key; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "task_has_tags" @@ -1657,7 +1877,7 @@ ALTER TABLE ONLY "task_has_tags" -- --- Name: tasks_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: tasks tasks_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "tasks" @@ -1665,7 +1885,7 @@ ALTER TABLE ONLY "tasks" -- --- Name: transitions_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: transitions transitions_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "transitions" @@ -1673,7 +1893,7 @@ ALTER TABLE ONLY "transitions" -- --- Name: user_has_metadata_user_id_name_key; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: user_has_metadata user_has_metadata_user_id_name_key; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "user_has_metadata" @@ -1681,7 +1901,7 @@ ALTER TABLE ONLY "user_has_metadata" -- --- Name: user_has_notification_types_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: user_has_notification_types user_has_notification_types_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "user_has_notification_types" @@ -1689,7 +1909,7 @@ ALTER TABLE ONLY "user_has_notification_types" -- --- Name: user_has_notifications_project_id_user_id_key; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: user_has_notifications user_has_notifications_project_id_user_id_key; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "user_has_notifications" @@ -1697,7 +1917,7 @@ ALTER TABLE ONLY "user_has_notifications" -- --- Name: user_has_unread_notifications_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: user_has_unread_notifications user_has_unread_notifications_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "user_has_unread_notifications" @@ -1705,7 +1925,7 @@ ALTER TABLE ONLY "user_has_unread_notifications" -- --- Name: users_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- Name: users users_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "users" @@ -1839,7 +2059,7 @@ CREATE UNIQUE INDEX "users_username_idx" ON "users" USING "btree" ("username"); -- --- Name: action_has_params_action_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: action_has_params action_has_params_action_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "action_has_params" @@ -1847,7 +2067,7 @@ ALTER TABLE ONLY "action_has_params" -- --- Name: actions_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: actions actions_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "actions" @@ -1855,7 +2075,63 @@ ALTER TABLE ONLY "actions" -- --- Name: columns_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: column_has_move_restrictions column_has_move_restrictions_dst_column_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "column_has_move_restrictions" + ADD CONSTRAINT "column_has_move_restrictions_dst_column_id_fkey" FOREIGN KEY ("dst_column_id") REFERENCES "columns"("id") ON DELETE CASCADE; + + +-- +-- Name: column_has_move_restrictions column_has_move_restrictions_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "column_has_move_restrictions" + ADD CONSTRAINT "column_has_move_restrictions_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE; + + +-- +-- Name: column_has_move_restrictions column_has_move_restrictions_role_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "column_has_move_restrictions" + ADD CONSTRAINT "column_has_move_restrictions_role_id_fkey" FOREIGN KEY ("role_id") REFERENCES "project_has_roles"("role_id") ON DELETE CASCADE; + + +-- +-- Name: column_has_move_restrictions column_has_move_restrictions_src_column_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "column_has_move_restrictions" + ADD CONSTRAINT "column_has_move_restrictions_src_column_id_fkey" FOREIGN KEY ("src_column_id") REFERENCES "columns"("id") ON DELETE CASCADE; + + +-- +-- Name: column_has_restrictions column_has_restrictions_column_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "column_has_restrictions" + ADD CONSTRAINT "column_has_restrictions_column_id_fkey" FOREIGN KEY ("column_id") REFERENCES "columns"("id") ON DELETE CASCADE; + + +-- +-- Name: column_has_restrictions column_has_restrictions_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "column_has_restrictions" + ADD CONSTRAINT "column_has_restrictions_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE; + + +-- +-- Name: column_has_restrictions column_has_restrictions_role_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "column_has_restrictions" + ADD CONSTRAINT "column_has_restrictions_role_id_fkey" FOREIGN KEY ("role_id") REFERENCES "project_has_roles"("role_id") ON DELETE CASCADE; + + +-- +-- Name: columns columns_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "columns" @@ -1863,7 +2139,7 @@ ALTER TABLE ONLY "columns" -- --- Name: comments_task_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: comments comments_task_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "comments" @@ -1871,7 +2147,7 @@ ALTER TABLE ONLY "comments" -- --- Name: group_has_users_group_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: group_has_users group_has_users_group_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "group_has_users" @@ -1879,7 +2155,7 @@ ALTER TABLE ONLY "group_has_users" -- --- Name: group_has_users_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: group_has_users group_has_users_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "group_has_users" @@ -1887,7 +2163,7 @@ ALTER TABLE ONLY "group_has_users" -- --- Name: last_logins_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: last_logins last_logins_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "last_logins" @@ -1895,7 +2171,7 @@ ALTER TABLE ONLY "last_logins" -- --- Name: password_reset_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: password_reset password_reset_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "password_reset" @@ -1903,7 +2179,7 @@ ALTER TABLE ONLY "password_reset" -- --- Name: project_activities_creator_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: project_activities project_activities_creator_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "project_activities" @@ -1911,7 +2187,7 @@ ALTER TABLE ONLY "project_activities" -- --- Name: project_activities_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: project_activities project_activities_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "project_activities" @@ -1919,7 +2195,7 @@ ALTER TABLE ONLY "project_activities" -- --- Name: project_activities_task_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: project_activities project_activities_task_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "project_activities" @@ -1927,7 +2203,7 @@ ALTER TABLE ONLY "project_activities" -- --- Name: project_daily_stats_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: project_daily_stats project_daily_stats_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "project_daily_stats" @@ -1935,7 +2211,7 @@ ALTER TABLE ONLY "project_daily_stats" -- --- Name: project_daily_summaries_column_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: project_daily_column_stats project_daily_summaries_column_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "project_daily_column_stats" @@ -1943,7 +2219,7 @@ ALTER TABLE ONLY "project_daily_column_stats" -- --- Name: project_daily_summaries_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: project_daily_column_stats project_daily_summaries_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "project_daily_column_stats" @@ -1951,7 +2227,7 @@ ALTER TABLE ONLY "project_daily_column_stats" -- --- Name: project_has_categories_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: project_has_categories project_has_categories_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "project_has_categories" @@ -1959,7 +2235,7 @@ ALTER TABLE ONLY "project_has_categories" -- --- Name: project_has_files_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: project_has_files project_has_files_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "project_has_files" @@ -1967,7 +2243,7 @@ ALTER TABLE ONLY "project_has_files" -- --- Name: project_has_groups_group_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: project_has_groups project_has_groups_group_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "project_has_groups" @@ -1975,7 +2251,7 @@ ALTER TABLE ONLY "project_has_groups" -- --- Name: project_has_groups_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: project_has_groups project_has_groups_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "project_has_groups" @@ -1983,7 +2259,7 @@ ALTER TABLE ONLY "project_has_groups" -- --- Name: project_has_metadata_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: project_has_metadata project_has_metadata_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "project_has_metadata" @@ -1991,7 +2267,7 @@ ALTER TABLE ONLY "project_has_metadata" -- --- Name: project_has_notification_types_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: project_has_notification_types project_has_notification_types_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "project_has_notification_types" @@ -1999,7 +2275,15 @@ ALTER TABLE ONLY "project_has_notification_types" -- --- Name: project_has_users_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: project_has_roles project_has_roles_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "project_has_roles" + ADD CONSTRAINT "project_has_roles_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE; + + +-- +-- Name: project_has_users project_has_users_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "project_has_users" @@ -2007,7 +2291,7 @@ ALTER TABLE ONLY "project_has_users" -- --- Name: project_has_users_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: project_has_users project_has_users_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "project_has_users" @@ -2015,7 +2299,23 @@ ALTER TABLE ONLY "project_has_users" -- --- Name: remember_me_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: project_role_has_restrictions project_role_has_restrictions_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "project_role_has_restrictions" + ADD CONSTRAINT "project_role_has_restrictions_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE; + + +-- +-- Name: project_role_has_restrictions project_role_has_restrictions_role_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY "project_role_has_restrictions" + ADD CONSTRAINT "project_role_has_restrictions_role_id_fkey" FOREIGN KEY ("role_id") REFERENCES "project_has_roles"("role_id") ON DELETE CASCADE; + + +-- +-- Name: remember_me remember_me_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "remember_me" @@ -2023,7 +2323,7 @@ ALTER TABLE ONLY "remember_me" -- --- Name: subtask_time_tracking_subtask_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: subtask_time_tracking subtask_time_tracking_subtask_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "subtask_time_tracking" @@ -2031,7 +2331,7 @@ ALTER TABLE ONLY "subtask_time_tracking" -- --- Name: subtask_time_tracking_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: subtask_time_tracking subtask_time_tracking_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "subtask_time_tracking" @@ -2039,7 +2339,7 @@ ALTER TABLE ONLY "subtask_time_tracking" -- --- Name: swimlanes_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: swimlanes swimlanes_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "swimlanes" @@ -2047,7 +2347,7 @@ ALTER TABLE ONLY "swimlanes" -- --- Name: task_has_external_links_task_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: task_has_external_links task_has_external_links_task_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "task_has_external_links" @@ -2055,7 +2355,7 @@ ALTER TABLE ONLY "task_has_external_links" -- --- Name: task_has_files_task_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: task_has_files task_has_files_task_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "task_has_files" @@ -2063,7 +2363,7 @@ ALTER TABLE ONLY "task_has_files" -- --- Name: task_has_links_link_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: task_has_links task_has_links_link_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "task_has_links" @@ -2071,7 +2371,7 @@ ALTER TABLE ONLY "task_has_links" -- --- Name: task_has_links_opposite_task_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: task_has_links task_has_links_opposite_task_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "task_has_links" @@ -2079,7 +2379,7 @@ ALTER TABLE ONLY "task_has_links" -- --- Name: task_has_links_task_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: task_has_links task_has_links_task_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "task_has_links" @@ -2087,7 +2387,7 @@ ALTER TABLE ONLY "task_has_links" -- --- Name: task_has_metadata_task_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: task_has_metadata task_has_metadata_task_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "task_has_metadata" @@ -2095,7 +2395,7 @@ ALTER TABLE ONLY "task_has_metadata" -- --- Name: task_has_subtasks_task_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: subtasks task_has_subtasks_task_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "subtasks" @@ -2103,7 +2403,7 @@ ALTER TABLE ONLY "subtasks" -- --- Name: task_has_tags_tag_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: task_has_tags task_has_tags_tag_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "task_has_tags" @@ -2111,7 +2411,7 @@ ALTER TABLE ONLY "task_has_tags" -- --- Name: task_has_tags_task_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: task_has_tags task_has_tags_task_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "task_has_tags" @@ -2119,7 +2419,7 @@ ALTER TABLE ONLY "task_has_tags" -- --- Name: tasks_column_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: tasks tasks_column_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "tasks" @@ -2127,7 +2427,7 @@ ALTER TABLE ONLY "tasks" -- --- Name: tasks_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: tasks tasks_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "tasks" @@ -2135,7 +2435,7 @@ ALTER TABLE ONLY "tasks" -- --- Name: transitions_dst_column_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: transitions transitions_dst_column_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "transitions" @@ -2143,7 +2443,7 @@ ALTER TABLE ONLY "transitions" -- --- Name: transitions_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: transitions transitions_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "transitions" @@ -2151,7 +2451,7 @@ ALTER TABLE ONLY "transitions" -- --- Name: transitions_src_column_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: transitions transitions_src_column_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "transitions" @@ -2159,7 +2459,7 @@ ALTER TABLE ONLY "transitions" -- --- Name: transitions_task_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: transitions transitions_task_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "transitions" @@ -2167,7 +2467,7 @@ ALTER TABLE ONLY "transitions" -- --- Name: transitions_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: transitions transitions_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "transitions" @@ -2175,7 +2475,7 @@ ALTER TABLE ONLY "transitions" -- --- Name: user_has_metadata_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: user_has_metadata user_has_metadata_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "user_has_metadata" @@ -2183,7 +2483,7 @@ ALTER TABLE ONLY "user_has_metadata" -- --- Name: user_has_notification_types_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: user_has_notification_types user_has_notification_types_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "user_has_notification_types" @@ -2191,7 +2491,7 @@ ALTER TABLE ONLY "user_has_notification_types" -- --- Name: user_has_notifications_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: user_has_notifications user_has_notifications_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "user_has_notifications" @@ -2199,7 +2499,7 @@ ALTER TABLE ONLY "user_has_notifications" -- --- Name: user_has_notifications_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: user_has_notifications user_has_notifications_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "user_has_notifications" @@ -2207,7 +2507,7 @@ ALTER TABLE ONLY "user_has_notifications" -- --- Name: user_has_unread_notifications_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- Name: user_has_unread_notifications user_has_unread_notifications_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- ALTER TABLE ONLY "user_has_unread_notifications" @@ -2222,8 +2522,8 @@ ALTER TABLE ONLY "user_has_unread_notifications" -- PostgreSQL database dump -- --- Dumped from database version 9.5.2 --- Dumped by pg_dump version 9.5.2 +-- Dumped from database version 9.6.1 +-- Dumped by pg_dump version 9.6.1 SET statement_timeout = 0; SET lock_timeout = 0; @@ -2243,8 +2543,8 @@ INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('board_high INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('board_public_refresh_interval', '60', 0, 0); INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('board_private_refresh_interval', '10', 0, 0); INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('board_columns', '', 0, 0); -INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('webhook_token', 'c9a7c2a4523f1724b2ca047c5685f8e2b26bba47eb69baf4f22d5d50d837', 0, 0); -INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('api_token', 'c57a6cb1789269547b616454e4e2f06d3de0514f83baf8fa5b5a8af44a08', 0, 0); +INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('webhook_token', 'b64911d9b61ea71adada348105281e16470e268fce7cb9bf1895958d4bbc', 0, 0); +INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('api_token', 'f63bd25c2e952d78b70f178fd96b4207ef29315ca73d308af37c02d8d51f', 0, 0); INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('application_language', 'en_US', 0, 0); INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('application_timezone', 'UTC', 0, 0); INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('application_url', '', 0, 0); @@ -2272,8 +2572,8 @@ INSERT INTO settings (option, value, changed_by, changed_on) VALUES ('password_r -- PostgreSQL database dump -- --- Dumped from database version 9.5.2 --- Dumped by pg_dump version 9.5.2 +-- Dumped from database version 9.6.1 +-- Dumped by pg_dump version 9.6.1 SET statement_timeout = 0; SET lock_timeout = 0; @@ -2313,4 +2613,4 @@ SELECT pg_catalog.setval('links_id_seq', 11, true); -- PostgreSQL database dump complete -- -INSERT INTO users (username, password, role) VALUES ('admin', '$2y$10$yUJ9QnhG.f47yO.YvWKo3eMAHULukpluDNTOF9.Z7QQg0vOfFRB6u', 'app-admin');INSERT INTO schema_version VALUES ('91'); +INSERT INTO users (username, password, role) VALUES ('admin', '$2y$10$0.8BJyhOEBHGqfwD3nIJHuxObqTlZGJ/KRNDVHfSu7RGd42rEbSa.', 'app-admin');INSERT INTO schema_version VALUES ('97'); diff --git a/app/Schema/Sqlite.php b/app/Schema/Sqlite.php index edf6ce63..11dcd537 100644 --- a/app/Schema/Sqlite.php +++ b/app/Schema/Sqlite.php @@ -6,7 +6,12 @@ use Kanboard\Core\Security\Token; use Kanboard\Core\Security\Role; use PDO; -const VERSION = 107; +const VERSION = 108; + +function version_108(PDO $pdo) +{ + $pdo->exec('ALTER TABLE users ADD COLUMN api_access_token VARCHAR(255) DEFAULT NULL'); +} function version_107(PDO $pdo) { diff --git a/app/ServiceProvider/AuthenticationProvider.php b/app/ServiceProvider/AuthenticationProvider.php index adff1e63..c2dad0e6 100644 --- a/app/ServiceProvider/AuthenticationProvider.php +++ b/app/ServiceProvider/AuthenticationProvider.php @@ -2,6 +2,7 @@ namespace Kanboard\ServiceProvider; +use Kanboard\Auth\ApiAccessTokenAuth; use Pimple\Container; use Pimple\ServiceProviderInterface; use Kanboard\Core\Security\AuthenticationManager; @@ -44,6 +45,8 @@ class AuthenticationProvider implements ServiceProviderInterface $container['authenticationManager']->register(new LdapAuth($container)); } + $container['authenticationManager']->register(new ApiAccessTokenAuth($container)); + $container['projectAccessMap'] = $this->getProjectAccessMap(); $container['applicationAccessMap'] = $this->getApplicationAccessMap(); $container['apiAccessMap'] = $this->getApiAccessMap(); diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php index c5bf0678..8d471b79 100644 --- a/app/ServiceProvider/ClassProvider.php +++ b/app/ServiceProvider/ClassProvider.php @@ -91,7 +91,6 @@ class ClassProvider implements ServiceProviderInterface 'TransitionModel', 'UserModel', 'UserLockingModel', - 'UserMentionModel', 'UserNotificationModel', 'UserNotificationFilterModel', 'UserUnreadNotificationModel', diff --git a/app/ServiceProvider/CommandProvider.php b/app/ServiceProvider/CommandProvider.php index 55c2712b..c9abb294 100644 --- a/app/ServiceProvider/CommandProvider.php +++ b/app/ServiceProvider/CommandProvider.php @@ -3,6 +3,8 @@ namespace Kanboard\ServiceProvider; use Kanboard\Console\CronjobCommand; +use Kanboard\Console\DatabaseMigrationCommand; +use Kanboard\Console\DatabaseVersionCommand; use Kanboard\Console\LocaleComparatorCommand; use Kanboard\Console\LocaleSyncCommand; use Kanboard\Console\PluginInstallCommand; @@ -55,6 +57,8 @@ class CommandProvider implements ServiceProviderInterface $application->add(new PluginUpgradeCommand($container)); $application->add(new PluginInstallCommand($container)); $application->add(new PluginUninstallCommand($container)); + $application->add(new DatabaseMigrationCommand($container)); + $application->add(new DatabaseVersionCommand($container)); $container['cli'] = $application; return $container; diff --git a/app/ServiceProvider/DatabaseProvider.php b/app/ServiceProvider/DatabaseProvider.php index a3f57457..9998ac43 100644 --- a/app/ServiceProvider/DatabaseProvider.php +++ b/app/ServiceProvider/DatabaseProvider.php @@ -27,6 +27,10 @@ class DatabaseProvider implements ServiceProviderInterface { $container['db'] = $this->getInstance(); + if (DB_RUN_MIGRATIONS) { + self::runMigrations($container['db']); + } + if (DEBUG) { $container['db']->getStatementHandler() ->withLogging() @@ -38,7 +42,7 @@ class DatabaseProvider implements ServiceProviderInterface } /** - * Setup the database driver and execute schema migration + * Setup the database driver * * @access public * @return \PicoDb\Database @@ -59,12 +63,39 @@ class DatabaseProvider implements ServiceProviderInterface throw new LogicException('Database driver not supported'); } - if ($db->schema()->check(\Schema\VERSION)) { - return $db; - } else { + return $db; + } + + /** + * Get current database version + * + * @static + * @access public + * @param Database $db + * @return int + */ + public static function getSchemaVersion(Database $db) + { + return $db->getDriver()->getSchemaVersion(); + } + + /** + * Execute database migrations + * + * @static + * @access public + * @throws RuntimeException + * @param Database $db + * @return bool + */ + public static function runMigrations(Database $db) + { + if (! $db->schema()->check(\Schema\VERSION)) { $messages = $db->getLogMessages(); throw new RuntimeException('Unable to run SQL migrations: '.implode(', ', $messages).' (You may have to fix it manually)'); } + + return true; } /** @@ -79,7 +110,7 @@ class DatabaseProvider implements ServiceProviderInterface return new Database(array( 'driver' => 'sqlite', - 'filename' => DB_FILENAME + 'filename' => DB_FILENAME, )); } diff --git a/app/ServiceProvider/JobProvider.php b/app/ServiceProvider/JobProvider.php index 2194b11c..4e5e0f1a 100644 --- a/app/ServiceProvider/JobProvider.php +++ b/app/ServiceProvider/JobProvider.php @@ -10,6 +10,7 @@ use Kanboard\Job\SubtaskEventJob; use Kanboard\Job\TaskEventJob; use Kanboard\Job\TaskFileEventJob; use Kanboard\Job\TaskLinkEventJob; +use Kanboard\Job\UserMentionJob; use Pimple\Container; use Pimple\ServiceProviderInterface; @@ -62,6 +63,10 @@ class JobProvider implements ServiceProviderInterface return new ProjectMetricJob($c); }); + $container['userMentionJob'] = $container->factory(function ($c) { + return new UserMentionJob($c); + }); + return $container; } } diff --git a/app/ServiceProvider/RouteProvider.php b/app/ServiceProvider/RouteProvider.php index 0d1a7931..52687647 100644 --- a/app/ServiceProvider/RouteProvider.php +++ b/app/ServiceProvider/RouteProvider.php @@ -158,6 +158,7 @@ class RouteProvider implements ServiceProviderInterface $container['route']->addRoute('user/:user_id/authentication', 'UserCredentialController', 'changeAuthentication'); $container['route']->addRoute('user/:user_id/2fa', 'TwoFactorController', 'index'); $container['route']->addRoute('user/:user_id/avatar', 'AvatarFileController', 'show'); + $container['route']->addRoute('user/:user_id/api', 'UserApiAccessController', 'show'); // Groups $container['route']->addRoute('groups', 'GroupListController', 'index'); diff --git a/app/Subscriber/NotificationSubscriber.php b/app/Subscriber/NotificationSubscriber.php index 7cc68b26..ad16685b 100644 --- a/app/Subscriber/NotificationSubscriber.php +++ b/app/Subscriber/NotificationSubscriber.php @@ -15,25 +15,25 @@ class NotificationSubscriber extends BaseSubscriber implements EventSubscriberIn public static function getSubscribedEvents() { return array( - TaskModel::EVENT_USER_MENTION => 'handleEvent', - TaskModel::EVENT_CREATE => 'handleEvent', - TaskModel::EVENT_UPDATE => 'handleEvent', - TaskModel::EVENT_CLOSE => 'handleEvent', - TaskModel::EVENT_OPEN => 'handleEvent', - TaskModel::EVENT_MOVE_COLUMN => 'handleEvent', - TaskModel::EVENT_MOVE_POSITION => 'handleEvent', - TaskModel::EVENT_MOVE_SWIMLANE => 'handleEvent', - TaskModel::EVENT_ASSIGNEE_CHANGE => 'handleEvent', - SubtaskModel::EVENT_CREATE => 'handleEvent', - SubtaskModel::EVENT_UPDATE => 'handleEvent', - SubtaskModel::EVENT_DELETE => 'handleEvent', - CommentModel::EVENT_CREATE => 'handleEvent', - CommentModel::EVENT_UPDATE => 'handleEvent', - CommentModel::EVENT_DELETE => 'handleEvent', - CommentModel::EVENT_USER_MENTION => 'handleEvent', - TaskFileModel::EVENT_CREATE => 'handleEvent', - TaskLinkModel::EVENT_CREATE_UPDATE => 'handleEvent', - TaskLinkModel::EVENT_DELETE => 'handleEvent', + TaskModel::EVENT_USER_MENTION => 'handleEvent', + TaskModel::EVENT_CREATE => 'handleEvent', + TaskModel::EVENT_UPDATE => 'handleEvent', + TaskModel::EVENT_CLOSE => 'handleEvent', + TaskModel::EVENT_OPEN => 'handleEvent', + TaskModel::EVENT_MOVE_COLUMN => 'handleEvent', + TaskModel::EVENT_MOVE_POSITION => 'handleEvent', + TaskModel::EVENT_MOVE_SWIMLANE => 'handleEvent', + TaskModel::EVENT_ASSIGNEE_CHANGE => 'handleEvent', + SubtaskModel::EVENT_CREATE => 'handleEvent', + SubtaskModel::EVENT_UPDATE => 'handleEvent', + SubtaskModel::EVENT_DELETE => 'handleEvent', + CommentModel::EVENT_CREATE => 'handleEvent', + CommentModel::EVENT_UPDATE => 'handleEvent', + CommentModel::EVENT_DELETE => 'handleEvent', + CommentModel::EVENT_USER_MENTION => 'handleEvent', + TaskFileModel::EVENT_CREATE => 'handleEvent', + TaskLinkModel::EVENT_CREATE_UPDATE => 'handleEvent', + TaskLinkModel::EVENT_DELETE => 'handleEvent', ); } diff --git a/app/Template/category/edit.php b/app/Template/category/edit.php index 72fd40de..d8ca313d 100644 --- a/app/Template/category/edit.php +++ b/app/Template/category/edit.php @@ -10,10 +10,10 @@ <?= $this->form->hidden('project_id', $values) ?> <?= $this->form->label(t('Category Name'), 'name') ?> - <?= $this->form->text('name', $values, $errors, array('autofocus', 'required', 'maxlength="50"')) ?> + <?= $this->form->text('name', $values, $errors, array('autofocus', 'required', 'maxlength="50"', 'tabindex="1"')) ?> <?= $this->form->label(t('Description'), 'description') ?> - <?= $this->form->textEditor('description', $values, $errors) ?> + <?= $this->form->textEditor('description', $values, $errors, array('tabindex' => 2)) ?> <div class="form-actions"> <button type="submit" class="btn btn-blue"><?= t('Save') ?></button> diff --git a/app/Template/category/index.php b/app/Template/category/index.php index ac60d9a8..336b79a2 100644 --- a/app/Template/category/index.php +++ b/app/Template/category/index.php @@ -15,9 +15,11 @@ <a href="#" class="dropdown-menu dropdown-menu-link-icon"><i class="fa fa-cog fa-fw"></i><i class="fa fa-caret-down"></i></a> <ul> <li> + <i class="fa fa-pencil-square-o fa-fw" aria-hidden="true"></i> <?= $this->url->link(t('Edit'), 'CategoryController', 'edit', array('project_id' => $project['id'], 'category_id' => $category_id), false, 'popover') ?> </li> <li> + <i class="fa fa-trash-o fa-fw" aria-hidden="true"></i> <?= $this->url->link(t('Remove'), 'CategoryController', 'confirm', array('project_id' => $project['id'], 'category_id' => $category_id), false, 'popover') ?> </li> </ul> diff --git a/app/Template/column/create.php b/app/Template/column/create.php index 71c94062..f4cded52 100644 --- a/app/Template/column/create.php +++ b/app/Template/column/create.php @@ -8,18 +8,18 @@ <?= $this->form->hidden('project_id', $values) ?> <?= $this->form->label(t('Title'), 'title') ?> - <?= $this->form->text('title', $values, $errors, array('autofocus', 'required', 'maxlength="50"')) ?> + <?= $this->form->text('title', $values, $errors, array('autofocus', 'required', 'maxlength="50"', 'tabindex="1"')) ?> <?= $this->form->label(t('Task limit'), 'task_limit') ?> - <?= $this->form->number('task_limit', $values, $errors) ?> + <?= $this->form->number('task_limit', $values, $errors, array('tabindex="2"')) ?> - <?= $this->form->checkbox('hide_in_dashboard', t('Hide tasks in this column in the dashboard'), 1) ?> + <?= $this->form->checkbox('hide_in_dashboard', t('Hide tasks in this column in the dashboard'), 1, false, '', array('tabindex' => 3)) ?> <?= $this->form->label(t('Description'), 'description') ?> - <?= $this->form->textEditor('description', $values, $errors) ?> + <?= $this->form->textEditor('description', $values, $errors, array('tabindex' => 4)) ?> <div class="form-actions"> - <button type="submit" class="btn btn-blue"><?= t('Save') ?></button> + <button type="submit" class="btn btn-blue" tabindex="5"><?= t('Save') ?></button> <?= t('or') ?> <?= $this->url->link(t('cancel'), 'column', 'index', array('project_id' => $project['id']), false, 'close-popover') ?> </div> diff --git a/app/Template/comments/show.php b/app/Template/comments/show.php index 5c6d8e20..dfc13821 100644 --- a/app/Template/comments/show.php +++ b/app/Template/comments/show.php @@ -26,6 +26,7 @@ 'values' => array( 'user_id' => $this->user->getId(), 'task_id' => $task['id'], + 'project_id' => $task['project_id'], ), 'errors' => array(), 'task' => $task, diff --git a/app/Template/notification/comment_create.php b/app/Template/notification/comment_create.php index fefc8ba1..41262a7e 100644 --- a/app/Template/notification/comment_create.php +++ b/app/Template/notification/comment_create.php @@ -6,6 +6,6 @@ <h3><?= t('New comment') ?></h3> <?php endif ?> -<?= $this->text->markdown($comment['comment']) ?> +<?= $this->text->markdown($comment['comment'], true) ?> <?= $this->render('notification/footer', array('task' => $task, 'application_url' => $application_url)) ?>
\ No newline at end of file diff --git a/app/Template/notification/comment_delete.php b/app/Template/notification/comment_delete.php index 928623ec..14babbd9 100644 --- a/app/Template/notification/comment_delete.php +++ b/app/Template/notification/comment_delete.php @@ -2,6 +2,6 @@ <h3><?= t('Comment removed') ?></h3> -<?= $this->text->markdown($comment['comment']) ?> +<?= $this->text->markdown($comment['comment'], true) ?> <?= $this->render('notification/footer', array('task' => $task, 'application_url' => $application_url)) ?> diff --git a/app/Template/notification/comment_update.php b/app/Template/notification/comment_update.php index 2477d8b3..f1cffae6 100644 --- a/app/Template/notification/comment_update.php +++ b/app/Template/notification/comment_update.php @@ -2,6 +2,6 @@ <h3><?= t('Comment updated') ?></h3> -<?= $this->text->markdown($comment['comment']) ?> +<?= $this->text->markdown($comment['comment'], true) ?> <?= $this->render('notification/footer', array('task' => $task, 'application_url' => $application_url)) ?>
\ No newline at end of file diff --git a/app/Template/notification/comment_user_mention.php b/app/Template/notification/comment_user_mention.php index 372183df..0990e7ab 100644 --- a/app/Template/notification/comment_user_mention.php +++ b/app/Template/notification/comment_user_mention.php @@ -2,6 +2,6 @@ <p><?= $this->text->e($task['title']) ?></p> -<?= $this->text->markdown($comment['comment']) ?> +<?= $this->text->markdown($comment['comment'], true) ?> <?= $this->render('notification/footer', array('task' => $task, 'application_url' => $application_url)) ?>
\ No newline at end of file diff --git a/app/Template/notification/task_assignee_change.php b/app/Template/notification/task_assignee_change.php index 53f7c5c1..f075fdbf 100644 --- a/app/Template/notification/task_assignee_change.php +++ b/app/Template/notification/task_assignee_change.php @@ -14,7 +14,7 @@ <?php if (! empty($task['description'])): ?> <h2><?= t('Description') ?></h2> - <?= $this->text->markdown($task['description']) ?: t('There is no description.') ?> + <?= $this->text->markdown($task['description'], true) ?: t('There is no description.') ?> <?php endif ?> <?= $this->render('notification/footer', array('task' => $task, 'application_url' => $application_url)) ?>
\ No newline at end of file diff --git a/app/Template/notification/task_create.php b/app/Template/notification/task_create.php index 3cd68ac0..3439e357 100644 --- a/app/Template/notification/task_create.php +++ b/app/Template/notification/task_create.php @@ -37,7 +37,7 @@ <?php if (! empty($task['description'])): ?> <h2><?= t('Description') ?></h2> - <?= $this->text->markdown($task['description']) ?> + <?= $this->text->markdown($task['description'], true) ?> <?php endif ?> <?= $this->render('notification/footer', array('task' => $task, 'application_url' => $application_url)) ?>
\ No newline at end of file diff --git a/app/Template/notification/task_update.php b/app/Template/notification/task_update.php index 8adb2553..9abe8e0a 100644 --- a/app/Template/notification/task_update.php +++ b/app/Template/notification/task_update.php @@ -1,4 +1,4 @@ <h2><?= $this->text->e($task['title']) ?> (#<?= $task['id'] ?>)</h2> -<?= $this->render('task/changes', array('changes' => $changes, 'task' => $task)) ?> +<?= $this->render('task/changes', array('changes' => $changes, 'task' => $task, 'public' => true)) ?> <?= $this->render('notification/footer', array('task' => $task, 'application_url' => $application_url)) ?>
\ No newline at end of file diff --git a/app/Template/notification/task_user_mention.php b/app/Template/notification/task_user_mention.php index 3d8c8e95..71ad348b 100644 --- a/app/Template/notification/task_user_mention.php +++ b/app/Template/notification/task_user_mention.php @@ -2,6 +2,6 @@ <p><?= $this->text->e($task['title']) ?></p> <h2><?= t('Description') ?></h2> -<?= $this->text->markdown($task['description']) ?> +<?= $this->text->markdown($task['description'], true) ?> <?= $this->render('notification/footer', array('task' => $task, 'application_url' => $application_url)) ?>
\ No newline at end of file diff --git a/app/Template/swimlane/create.php b/app/Template/swimlane/create.php index 0eb25411..207b526c 100644 --- a/app/Template/swimlane/create.php +++ b/app/Template/swimlane/create.php @@ -7,13 +7,13 @@ <?= $this->form->hidden('project_id', $values) ?> <?= $this->form->label(t('Name'), 'name') ?> - <?= $this->form->text('name', $values, $errors, array('autofocus', 'required', 'maxlength="50"')) ?> + <?= $this->form->text('name', $values, $errors, array('autofocus', 'required', 'maxlength="50"', 'tabindex="1"')) ?> <?= $this->form->label(t('Description'), 'description') ?> - <?= $this->form->textEditor('description', $values, $errors) ?> + <?= $this->form->textEditor('description', $values, $errors, array('tabindex' => 2)) ?> <div class="form-actions"> - <button type="submit" class="btn btn-blue"><?= t('Save') ?></button> + <button type="submit" class="btn btn-blue" tabindex="3"><?= t('Save') ?></button> <?= t('or') ?> <?= $this->url->link(t('cancel'), 'SwimlaneController', 'index', array('project_id' => $project['id']), false, 'close-popover') ?> </div> diff --git a/app/Template/swimlane/edit.php b/app/Template/swimlane/edit.php index 2cbabb60..d225b345 100644 --- a/app/Template/swimlane/edit.php +++ b/app/Template/swimlane/edit.php @@ -10,13 +10,13 @@ <?= $this->form->hidden('project_id', $values) ?> <?= $this->form->label(t('Name'), 'name') ?> - <?= $this->form->text('name', $values, $errors, array('autofocus', 'required', 'maxlength="50"')) ?> + <?= $this->form->text('name', $values, $errors, array('autofocus', 'required', 'maxlength="50"', 'tabindex="1"')) ?> <?= $this->form->label(t('Description'), 'description') ?> - <?= $this->form->textEditor('description', $values, $errors) ?> + <?= $this->form->textEditor('description', $values, $errors, array('tabindex' => 2)) ?> <div class="form-actions"> - <button type="submit" class="btn btn-blue"><?= t('Save') ?></button> + <button type="submit" class="btn btn-blue" tabindex="3"><?= t('Save') ?></button> <?= t('or') ?> <?= $this->url->link(t('cancel'), 'SwimlaneController', 'index', array('project_id' => $project['id']), false, 'close-popover') ?> </div> diff --git a/app/Template/swimlane/edit_default.php b/app/Template/swimlane/edit_default.php index f271c513..8a0c0a15 100644 --- a/app/Template/swimlane/edit_default.php +++ b/app/Template/swimlane/edit_default.php @@ -6,7 +6,7 @@ <?= $this->form->hidden('id', $values) ?> <?= $this->form->label(t('Name'), 'default_swimlane') ?> - <?= $this->form->text('default_swimlane', $values, $errors, array('required', 'maxlength="50"')) ?> + <?= $this->form->text('default_swimlane', $values, $errors, array('autofocus', 'required', 'maxlength="50"')) ?> <?= $this->form->checkbox('show_default_swimlane', t('Show default swimlane'), 1, $values['show_default_swimlane'] == 1) ?> diff --git a/app/Template/swimlane/table.php b/app/Template/swimlane/table.php index cefef9de..81daed01 100644 --- a/app/Template/swimlane/table.php +++ b/app/Template/swimlane/table.php @@ -20,12 +20,15 @@ <a href="#" class="dropdown-menu dropdown-menu-link-icon"><i class="fa fa-cog fa-fw"></i><i class="fa fa-caret-down"></i></a> <ul> <li> + <i class="fa fa-pencil-square-o fa-fw" aria-hidden="true"></i> <?= $this->url->link(t('Edit'), 'SwimlaneController', 'editDefault', array('project_id' => $project['id']), false, 'popover') ?> </li> <li> <?php if ($default_swimlane['show_default_swimlane'] == 1): ?> + <i class="fa fa-toggle-off fa-fw" aria-hidden="true"></i> <?= $this->url->link(t('Disable'), 'SwimlaneController', 'disableDefault', array('project_id' => $project['id']), true) ?> <?php else: ?> + <i class="fa fa-toggle-on fa-fw" aria-hidden="true"></i> <?= $this->url->link(t('Enable'), 'SwimlaneController', 'enableDefault', array('project_id' => $project['id']), true) ?> <?php endif ?> </li> @@ -55,16 +58,20 @@ <a href="#" class="dropdown-menu dropdown-menu-link-icon"><i class="fa fa-cog fa-fw"></i><i class="fa fa-caret-down"></i></a> <ul> <li> + <i class="fa fa-pencil-square-o fa-fw" aria-hidden="true"></i> <?= $this->url->link(t('Edit'), 'SwimlaneController', 'edit', array('project_id' => $project['id'], 'swimlane_id' => $swimlane['id']), false, 'popover') ?> </li> <li> <?php if ($swimlane['is_active']): ?> + <i class="fa fa-toggle-off fa-fw" aria-hidden="true"></i> <?= $this->url->link(t('Disable'), 'SwimlaneController', 'disable', array('project_id' => $project['id'], 'swimlane_id' => $swimlane['id']), true) ?> <?php else: ?> + <i class="fa fa-toggle-on fa-fw" aria-hidden="true"></i> <?= $this->url->link(t('Enable'), 'SwimlaneController', 'enable', array('project_id' => $project['id'], 'swimlane_id' => $swimlane['id']), true) ?> <?php endif ?> </li> <li> + <i class="fa fa-trash-o fa-fw" aria-hidden="true"></i> <?= $this->url->link(t('Remove'), 'SwimlaneController', 'confirm', array('project_id' => $project['id'], 'swimlane_id' => $swimlane['id']), false, 'popover') ?> </li> </ul> diff --git a/app/Template/task/changes.php b/app/Template/task/changes.php index 9d36f09f..2c2bf267 100644 --- a/app/Template/task/changes.php +++ b/app/Template/task/changes.php @@ -69,6 +69,10 @@ <?php if (! empty($changes['description'])): ?> <p><strong><?= t('The description has been modified:') ?></strong></p> - <div class="markdown"><?= $this->text->markdown($task['description']) ?></div> + <?php if (isset($public)): ?> + <div class="markdown"><?= $this->text->markdown($task['description'], true) ?></div> + <?php else: ?> + <div class="markdown"><?= $this->text->markdown($task['description']) ?></div> + <?php endif ?> <?php endif ?> <?php endif ?>
\ No newline at end of file diff --git a/app/Template/task_move_position/show.php b/app/Template/task_move_position/show.php index c1a02484..91241016 100644 --- a/app/Template/task_move_position/show.php +++ b/app/Template/task_move_position/show.php @@ -2,45 +2,22 @@ <h2><?= t('Move task to another position on the board') ?></h2> </div> -<script type="x/template" id="template-task-move-position"> - <?= $this->form->label(t('Swimlane'), 'swimlane') ?> - <select v-model="swimlaneId" @change="onChangeSwimlane()" id="form-swimlane"> - <option v-for="swimlane in board" v-bind:value="swimlane.id"> - {{ swimlane.name }} - </option> - </select> +<form> - <div v-if="columns.length > 0"> - <?= $this->form->label(t('Column'), 'column') ?> - <select v-model="columnId" @change="onChangeColumn()" id="form-column"> - <option v-for="column in columns" v-bind:value="column.id"> - {{ column.title }} - </option> - </select> - </div> +<?= $this->app->component('task-move-position', array( + 'saveUrl' => $this->url->href('TaskMovePositionController', 'save', array('task_id' => $task['id'], 'project_id' => $task['project_id'])), + 'board' => $board, + 'swimlaneLabel' => t('Swimlane'), + 'columnLabel' => t('Column'), + 'positionLabel' => t('Position'), + 'beforeLabel' => t('Insert before this task'), + 'afterLabel' => t('Insert after this task'), +)) ?> - <div v-if="tasks.length > 0"> - <?= $this->form->label(t('Position'), 'position') ?> - <select v-model="position" id="form-position"> - <option v-for="task in tasks" v-bind:value="task.position">#{{ task.id }} - {{ task.title }}</option> - </select> - <label><input type="radio" value="before" v-model="positionChoice"><?= t('Insert before this task') ?></label> - <label><input type="radio" value="after" v-model="positionChoice"><?= t('Insert after this task') ?></label> - </div> +<?= $this->app->component('submit-cancel', array( + 'submitLabel' => t('Save'), + 'orLabel' => t('or'), + 'cancelLabel' => t('cancel'), +)) ?> - <div v-if="errorMessage"> - <div class="alert alert-error">{{ errorMessage }}</div> - </div> - - <submit-cancel - label-button="<?= t('Save') ?>" - label-or="<?= t('or') ?>" - label-cancel="<?= t('cancel') ?>" - :callback="onSubmit"> - </submit-cancel> -</script> - -<task-move-position - save-url="<?= $this->url->href('TaskMovePositionController', 'save', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>" - :board='<?= json_encode($board, JSON_HEX_APOS) ?>' -></task-move-position> +</form> diff --git a/app/Template/user_api_access/show.php b/app/Template/user_api_access/show.php new file mode 100644 index 00000000..3d58e0d5 --- /dev/null +++ b/app/Template/user_api_access/show.php @@ -0,0 +1,17 @@ +<div class="page-header"> + <h2><?= t('API User Access') ?></h2> +</div> + +<p class="alert"> + <?php if (empty($user['api_access_token'])): ?> + <?= t('No personal API access token registered.') ?> + <?php else: ?> + <?= t('Your personal API access token is "%s"', $user['api_access_token']) ?> + <?php endif ?> +</p> + +<?php if (! empty($user['api_access_token'])): ?> + <?= $this->url->link(t('Remove your token'), 'UserApiAccessController', 'remove', array('user_id' => $user['id']), true, 'btn btn-red') ?> +<?php endif ?> + +<?= $this->url->link(t('Generate a new token'), 'UserApiAccessController', 'generate', array('user_id' => $user['id']), true, 'btn btn-blue') ?> diff --git a/app/Template/user_view/sidebar.php b/app/Template/user_view/sidebar.php index a80daefa..ef494e42 100644 --- a/app/Template/user_view/sidebar.php +++ b/app/Template/user_view/sidebar.php @@ -90,6 +90,11 @@ <?= $this->url->link(t('Integrations'), 'UserViewController', 'integrations', array('user_id' => $user['id'])) ?> </li> <?php endif ?> + <?php if ($this->user->hasAccess('UserApiAccessController', 'show')): ?> + <li <?= $this->app->checkMenuSelection('UserApiAccessController', 'show') ?>> + <?= $this->url->link(t('API'), 'UserApiAccessController', 'show', array('user_id' => $user['id'])) ?> + </li> + <?php endif ?> <?php endif ?> <?php if ($this->user->hasAccess('UserCredentialController', 'changeAuthentication')): ?> diff --git a/app/constants.php b/app/constants.php index 3adb0835..ba14cde6 100644 --- a/app/constants.php +++ b/app/constants.php @@ -35,6 +35,9 @@ defined('LOG_FILE') or define('LOG_FILE', DATA_DIR.DIRECTORY_SEPARATOR.'debug.lo // Application version defined('APP_VERSION') or define('APP_VERSION', build_app_version('$Format:%d$', '$Format:%H$')); +// Run automatically database migrations +defined('DB_RUN_MIGRATIONS') or define('DB_RUN_MIGRATIONS', true); + // Database driver: sqlite, mysql or postgres defined('DB_DRIVER') or define('DB_DRIVER', 'sqlite'); diff --git a/app/functions.php b/app/functions.php index 8f0d482c..e732f308 100644 --- a/app/functions.php +++ b/app/functions.php @@ -53,6 +53,37 @@ function array_column_index(array &$input, $column) } /** + * Create indexed array from a list of dict with unique values + * + * $input = [ + * ['k1' => 1, 'k2' => 2], ['k1' => 3, 'k2' => 4], ['k1' => 1, 'k2' => 5] + * ] + * + * array_column_index_unique($input, 'k1') will returns: + * + * [ + * 1 => ['k1' => 1, 'k2' => 2], + * 3 => ['k1' => 3, 'k2' => 4], + * ] + * + * @param array $input + * @param string $column + * @return array + */ +function array_column_index_unique(array &$input, $column) +{ + $result = array(); + + foreach ($input as &$row) { + if (isset($row[$column]) && ! isset($result[$row[$column]])) { + $result[$row[$column]] = $row; + } + } + + return $result; +} + +/** * Sum all values from a single column in the input array * * $input = [ |