From 26e3996014936268f4acbfa214fa881af9320ddd Mon Sep 17 00:00:00 2001 From: Frederic Guillot Date: Sat, 9 Jan 2016 17:28:31 -0500 Subject: Add forgot password feature --- app/Validator/Base.php | 36 ++++++++++++ app/Validator/PasswordResetValidator.php | 98 ++++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 app/Validator/Base.php create mode 100644 app/Validator/PasswordResetValidator.php (limited to 'app/Validator') diff --git a/app/Validator/Base.php b/app/Validator/Base.php new file mode 100644 index 00000000..6c56e2fd --- /dev/null +++ b/app/Validator/Base.php @@ -0,0 +1,36 @@ +$method($values); + + if (! $result) { + break; + } + } + + return array($result, $errors); + } +} diff --git a/app/Validator/PasswordResetValidator.php b/app/Validator/PasswordResetValidator.php new file mode 100644 index 00000000..6f21cbca --- /dev/null +++ b/app/Validator/PasswordResetValidator.php @@ -0,0 +1,98 @@ +executeValidators(array('validateFields', 'validateCaptcha'), $values); + } + + /** + * Validate modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateModification(array $values) + { + $v = new Validator($values, array( + new Validators\Required('password', t('The password is required')), + new Validators\MinLength('password', t('The minimum length is %d characters', 6), 6), + new Validators\Required('confirmation', t('The confirmation is required')), + new Validators\Equals('password', 'confirmation', t('Passwords don\'t match')), + )); + + return array( + $v->execute(), + $v->getErrors(), + ); + } + + /** + * Validate fields + * + * @access protected + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + protected function validateFields(array $values) + { + $v = new Validator($values, array( + new Validators\Required('captcha', t('This value is required')), + new Validators\Required('username', t('The username is required')), + new Validators\MaxLength('username', t('The maximum length is %d characters', 50), 50), + )); + + return array( + $v->execute(), + $v->getErrors(), + ); + } + + /** + * Validate captcha + * + * @access protected + * @param array $values Form values + * @return boolean + */ + protected function validateCaptcha(array $values) + { + $result = true; + $errors = array(); + + if (! isset($this->sessionStorage->captcha)) { + $result = false; + } else { + $builder = new CaptchaBuilder; + $builder->setPhrase($this->sessionStorage->captcha); + $result = $builder->testPhrase(isset($values['captcha']) ? $values['captcha'] : ''); + + if (! $result) { + $errors['captcha'] = array(t('Invalid captcha')); + } + } + + return array($result, $errors);; + } +} -- cgit v1.2.3 From f7e8bb8fa83fb7ef2be12cad3f4627f91f09cfb2 Mon Sep 17 00:00:00 2001 From: Frederic Guillot Date: Mon, 11 Jan 2016 21:08:37 -0500 Subject: Move user validator methods --- app/Api/User.php | 4 +- app/Controller/User.php | 8 +- app/Model/User.php | 128 ---------------------------- app/ServiceProvider/ClassProvider.php | 1 + app/Validator/Base.php | 18 ++++ app/Validator/PasswordResetValidator.php | 7 +- app/Validator/UserValidator.php | 128 ++++++++++++++++++++++++++++ tests/units/Model/UserTest.php | 32 ------- tests/units/Validator/UserValidatorTest.php | 41 +++++++++ 9 files changed, 195 insertions(+), 172 deletions(-) create mode 100644 app/Validator/UserValidator.php create mode 100644 tests/units/Validator/UserValidatorTest.php (limited to 'app/Validator') diff --git a/app/Api/User.php b/app/Api/User.php index 06e305f2..63c222fe 100644 --- a/app/Api/User.php +++ b/app/Api/User.php @@ -42,7 +42,7 @@ class User extends \Kanboard\Core\Base 'role' => $role, ); - list($valid, ) = $this->user->validateCreation($values); + list($valid, ) = $this->userValidator->validateCreation($values); return $valid ? $this->user->create($values) : false; } @@ -94,7 +94,7 @@ class User extends \Kanboard\Core\Base } } - list($valid, ) = $this->user->validateApiModification($values); + list($valid, ) = $this->userValidator->validateApiModification($values); return $valid && $this->user->update($values); } } diff --git a/app/Controller/User.php b/app/Controller/User.php index 2a811219..97e01553 100644 --- a/app/Controller/User.php +++ b/app/Controller/User.php @@ -108,7 +108,7 @@ class User extends Base public function save() { $values = $this->request->getValues(); - list($valid, $errors) = $this->user->validateCreation($values); + list($valid, $errors) = $this->userValidator->validateCreation($values); if ($valid) { $project_id = empty($values['project_id']) ? 0 : $values['project_id']; @@ -329,7 +329,7 @@ class User extends Base if ($this->request->isPost()) { $values = $this->request->getValues(); - list($valid, $errors) = $this->user->validatePasswordModification($values); + list($valid, $errors) = $this->userValidator->validatePasswordModification($values); if ($valid) { if ($this->user->update($values)) { @@ -371,7 +371,7 @@ class User extends Base } } - list($valid, $errors) = $this->user->validateModification($values); + list($valid, $errors) = $this->userValidator->validateModification($values); if ($valid) { if ($this->user->update($values)) { @@ -409,7 +409,7 @@ class User extends Base if ($this->request->isPost()) { $values = $this->request->getValues() + array('disable_login_form' => 0, 'is_ldap_user' => 0); - list($valid, $errors) = $this->user->validateModification($values); + list($valid, $errors) = $this->userValidator->validateModification($values); if ($valid) { if ($this->user->update($values)) { diff --git a/app/Model/User.php b/app/Model/User.php index 50e9b310..84785ce5 100644 --- a/app/Model/User.php +++ b/app/Model/User.php @@ -369,132 +369,4 @@ class User extends Base ->eq('id', $user_id) ->save(array('token' => '')); } - - /** - * Common validation rules - * - * @access private - * @return array - */ - private function commonValidationRules() - { - return array( - new Validators\MaxLength('role', t('The maximum length is %d characters', 25), 25), - new Validators\MaxLength('username', t('The maximum length is %d characters', 50), 50), - new Validators\Unique('username', t('The username must be unique'), $this->db->getConnection(), self::TABLE, 'id'), - new Validators\Email('email', t('Email address invalid')), - new Validators\Integer('is_ldap_user', t('This value must be an integer')), - ); - } - - /** - * Common password validation rules - * - * @access private - * @return array - */ - private function commonPasswordValidationRules() - { - return array( - new Validators\Required('password', t('The password is required')), - new Validators\MinLength('password', t('The minimum length is %d characters', 6), 6), - new Validators\Required('confirmation', t('The confirmation is required')), - new Validators\Equals('password', 'confirmation', t('Passwords don\'t match')), - ); - } - - /** - * Validate user creation - * - * @access public - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateCreation(array $values) - { - $rules = array( - new Validators\Required('username', t('The username is required')), - ); - - if (isset($values['is_ldap_user']) && $values['is_ldap_user'] == 1) { - $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); - } else { - $v = new Validator($values, array_merge($rules, $this->commonValidationRules(), $this->commonPasswordValidationRules())); - } - - return array( - $v->execute(), - $v->getErrors() - ); - } - - /** - * Validate user modification - * - * @access public - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateModification(array $values) - { - $rules = array( - new Validators\Required('id', t('The user id is required')), - new Validators\Required('username', t('The username is required')), - ); - - $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); - - return array( - $v->execute(), - $v->getErrors() - ); - } - - /** - * Validate user API modification - * - * @access public - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateApiModification(array $values) - { - $rules = array( - new Validators\Required('id', t('The user id is required')), - ); - - $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); - - return array( - $v->execute(), - $v->getErrors() - ); - } - - /** - * Validate password modification - * - * @access public - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validatePasswordModification(array $values) - { - $rules = array( - new Validators\Required('id', t('The user id is required')), - new Validators\Required('current_password', t('The current password is required')), - ); - - $v = new Validator($values, array_merge($rules, $this->commonPasswordValidationRules())); - - if ($v->execute()) { - if ($this->authenticationManager->passwordAuthentication($this->userSession->getUsername(), $values['current_password'], false)) { - return array(true, array()); - } else { - return array(false, array('current_password' => array(t('Wrong password')))); - } - } - - return array(false, $v->getErrors()); - } } diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php index e206ef68..ca6dcf5d 100644 --- a/app/ServiceProvider/ClassProvider.php +++ b/app/ServiceProvider/ClassProvider.php @@ -87,6 +87,7 @@ class ClassProvider implements ServiceProviderInterface ), 'Validator' => array( 'PasswordResetValidator', + 'UserValidator', ), 'Core' => array( 'DateParser', diff --git a/app/Validator/Base.php b/app/Validator/Base.php index 6c56e2fd..ba32a503 100644 --- a/app/Validator/Base.php +++ b/app/Validator/Base.php @@ -2,6 +2,8 @@ namespace Kanboard\Validator; +use SimpleValidator\Validators; + /** * Base Validator * @@ -33,4 +35,20 @@ class Base extends \Kanboard\Core\Base return array($result, $errors); } + + /** + * Common password validation rules + * + * @access protected + * @return array + */ + protected function commonPasswordValidationRules() + { + return array( + new Validators\Required('password', t('The password is required')), + new Validators\MinLength('password', t('The minimum length is %d characters', 6), 6), + new Validators\Required('confirmation', t('The confirmation is required')), + new Validators\Equals('password', 'confirmation', t('Passwords don\'t match')), + ); + } } diff --git a/app/Validator/PasswordResetValidator.php b/app/Validator/PasswordResetValidator.php index 6f21cbca..9ef45045 100644 --- a/app/Validator/PasswordResetValidator.php +++ b/app/Validator/PasswordResetValidator.php @@ -35,12 +35,7 @@ class PasswordResetValidator extends Base */ public function validateModification(array $values) { - $v = new Validator($values, array( - new Validators\Required('password', t('The password is required')), - new Validators\MinLength('password', t('The minimum length is %d characters', 6), 6), - new Validators\Required('confirmation', t('The confirmation is required')), - new Validators\Equals('password', 'confirmation', t('Passwords don\'t match')), - )); + $v = new Validator($values, $this->commonPasswordValidationRules()); return array( $v->execute(), diff --git a/app/Validator/UserValidator.php b/app/Validator/UserValidator.php new file mode 100644 index 00000000..d85d335f --- /dev/null +++ b/app/Validator/UserValidator.php @@ -0,0 +1,128 @@ +db->getConnection(), User::TABLE, 'id'), + new Validators\Email('email', t('Email address invalid')), + new Validators\Integer('is_ldap_user', t('This value must be an integer')), + ); + } + + /** + * Validate user creation + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateCreation(array $values) + { + $rules = array( + new Validators\Required('username', t('The username is required')), + ); + + if (isset($values['is_ldap_user']) && $values['is_ldap_user'] == 1) { + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + } else { + $v = new Validator($values, array_merge($rules, $this->commonValidationRules(), $this->commonPasswordValidationRules())); + } + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate user modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateModification(array $values) + { + $rules = array( + new Validators\Required('id', t('The user id is required')), + new Validators\Required('username', t('The username is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate user API modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateApiModification(array $values) + { + $rules = array( + new Validators\Required('id', t('The user id is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate password modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validatePasswordModification(array $values) + { + $rules = array( + new Validators\Required('id', t('The user id is required')), + new Validators\Required('current_password', t('The current password is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonPasswordValidationRules())); + + if ($v->execute()) { + if ($this->authenticationManager->passwordAuthentication($this->userSession->getUsername(), $values['current_password'], false)) { + return array(true, array()); + } else { + return array(false, array('current_password' => array(t('Wrong password')))); + } + } + + return array(false, $v->getErrors()); + } +} diff --git a/tests/units/Model/UserTest.php b/tests/units/Model/UserTest.php index 1d381006..0987fa56 100644 --- a/tests/units/Model/UserTest.php +++ b/tests/units/Model/UserTest.php @@ -391,36 +391,4 @@ class UserTest extends Base $this->assertEquals('toto', $user['username']); $this->assertEmpty($user['token']); } - - public function testValidatePasswordModification() - { - $u = new User($this->container); - - $this->container['sessionStorage']->user = array( - 'id' => 1, - 'role' => Role::APP_ADMIN, - 'username' => 'admin', - ); - - $result = $u->validatePasswordModification(array()); - $this->assertFalse($result[0]); - - $result = $u->validatePasswordModification(array('id' => 1)); - $this->assertFalse($result[0]); - - $result = $u->validatePasswordModification(array('id' => 1, 'password' => '123456')); - $this->assertFalse($result[0]); - - $result = $u->validatePasswordModification(array('id' => 1, 'password' => '123456', 'confirmation' => 'wrong')); - $this->assertFalse($result[0]); - - $result = $u->validatePasswordModification(array('id' => 1, 'password' => '123456', 'confirmation' => '123456')); - $this->assertFalse($result[0]); - - $result = $u->validatePasswordModification(array('id' => 1, 'password' => '123456', 'confirmation' => '123456', 'current_password' => 'wrong')); - $this->assertFalse($result[0]); - - $result = $u->validatePasswordModification(array('id' => 1, 'password' => '123456', 'confirmation' => '123456', 'current_password' => 'admin')); - $this->assertTrue($result[0]); - } } diff --git a/tests/units/Validator/UserValidatorTest.php b/tests/units/Validator/UserValidatorTest.php new file mode 100644 index 00000000..6d904ade --- /dev/null +++ b/tests/units/Validator/UserValidatorTest.php @@ -0,0 +1,41 @@ +container); + + $this->container['sessionStorage']->user = array( + 'id' => 1, + 'role' => Role::APP_ADMIN, + 'username' => 'admin', + ); + + $result = $validator->validatePasswordModification(array()); + $this->assertFalse($result[0]); + + $result = $validator->validatePasswordModification(array('id' => 1)); + $this->assertFalse($result[0]); + + $result = $validator->validatePasswordModification(array('id' => 1, 'password' => '123456')); + $this->assertFalse($result[0]); + + $result = $validator->validatePasswordModification(array('id' => 1, 'password' => '123456', 'confirmation' => 'wrong')); + $this->assertFalse($result[0]); + + $result = $validator->validatePasswordModification(array('id' => 1, 'password' => '123456', 'confirmation' => '123456')); + $this->assertFalse($result[0]); + + $result = $validator->validatePasswordModification(array('id' => 1, 'password' => '123456', 'confirmation' => '123456', 'current_password' => 'wrong')); + $this->assertFalse($result[0]); + + $result = $validator->validatePasswordModification(array('id' => 1, 'password' => '123456', 'confirmation' => '123456', 'current_password' => 'admin')); + $this->assertTrue($result[0]); + } +} -- cgit v1.2.3 From 4f38bf4c689ac3b3cb8b37c86e865602502caaf6 Mon Sep 17 00:00:00 2001 From: Frederic Guillot Date: Mon, 11 Jan 2016 21:13:54 -0500 Subject: Move TaskValidator class --- app/Model/TaskValidator.php | 246 ---------------------------------- app/ServiceProvider/ClassProvider.php | 2 +- app/Validator/TaskValidator.php | 246 ++++++++++++++++++++++++++++++++++ 3 files changed, 247 insertions(+), 247 deletions(-) delete mode 100644 app/Model/TaskValidator.php create mode 100644 app/Validator/TaskValidator.php (limited to 'app/Validator') diff --git a/app/Model/TaskValidator.php b/app/Model/TaskValidator.php deleted file mode 100644 index 683cb0b1..00000000 --- a/app/Model/TaskValidator.php +++ /dev/null @@ -1,246 +0,0 @@ -dateParser->getDateFormats()), - new Validators\Date('date_started', t('Invalid date'), $this->dateParser->getAllFormats()), - new Validators\Numeric('time_spent', t('This value must be numeric')), - new Validators\Numeric('time_estimated', t('This value must be numeric')), - ); - } - - /** - * Validate task creation - * - * @access public - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateCreation(array $values) - { - $rules = array( - new Validators\Required('project_id', t('The project is required')), - new Validators\Required('title', t('The title is required')), - ); - - $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); - - return array( - $v->execute(), - $v->getErrors() - ); - } - - /** - * Validate description creation - * - * @access public - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateDescriptionCreation(array $values) - { - $rules = array( - new Validators\Required('id', t('The id is required')), - ); - - $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); - - return array( - $v->execute(), - $v->getErrors() - ); - } - - /** - * Validate edit recurrence - * - * @access public - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateEditRecurrence(array $values) - { - $rules = array( - new Validators\Required('id', t('The id is required')), - ); - - $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); - - return array( - $v->execute(), - $v->getErrors() - ); - } - - - /** - * Validate task modification (form) - * - * @access public - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateModification(array $values) - { - $rules = array( - new Validators\Required('id', t('The id is required')), - new Validators\Required('title', t('The title is required')), - ); - - $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); - - return array( - $v->execute(), - $v->getErrors() - ); - } - - /** - * Validate task modification (Api) - * - * @access public - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateApiModification(array $values) - { - $rules = array( - new Validators\Required('id', t('The id is required')), - ); - - $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); - - return array( - $v->execute(), - $v->getErrors() - ); - } - - /** - * Validate assignee change - * - * @access public - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateAssigneeModification(array $values) - { - $rules = array( - new Validators\Required('id', t('The id is required')), - new Validators\Required('project_id', t('The project is required')), - new Validators\Required('owner_id', t('This value is required')), - ); - - $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); - - return array( - $v->execute(), - $v->getErrors() - ); - } - - /** - * Validate category change - * - * @access public - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateCategoryModification(array $values) - { - $rules = array( - new Validators\Required('id', t('The id is required')), - new Validators\Required('project_id', t('The project is required')), - new Validators\Required('category_id', t('This value is required')), - - ); - - $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); - - return array( - $v->execute(), - $v->getErrors() - ); - } - - /** - * Validate project modification - * - * @access public - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateProjectModification(array $values) - { - $rules = array( - new Validators\Required('id', t('The id is required')), - new Validators\Required('project_id', t('The project is required')), - ); - - $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); - - return array( - $v->execute(), - $v->getErrors() - ); - } - - /** - * Validate time tracking modification (form) - * - * @access public - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateTimeModification(array $values) - { - $rules = array( - new Validators\Required('id', t('The id is required')), - ); - - $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); - - return array( - $v->execute(), - $v->getErrors() - ); - } -} diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php index ca6dcf5d..44dba45b 100644 --- a/app/ServiceProvider/ClassProvider.php +++ b/app/ServiceProvider/ClassProvider.php @@ -63,7 +63,6 @@ class ClassProvider implements ServiceProviderInterface 'TaskPermission', 'TaskPosition', 'TaskStatus', - 'TaskValidator', 'TaskImport', 'TaskMetadata', 'Transition', @@ -87,6 +86,7 @@ class ClassProvider implements ServiceProviderInterface ), 'Validator' => array( 'PasswordResetValidator', + 'TaskValidator', 'UserValidator', ), 'Core' => array( diff --git a/app/Validator/TaskValidator.php b/app/Validator/TaskValidator.php new file mode 100644 index 00000000..c18a9761 --- /dev/null +++ b/app/Validator/TaskValidator.php @@ -0,0 +1,246 @@ +dateParser->getDateFormats()), + new Validators\Date('date_started', t('Invalid date'), $this->dateParser->getAllFormats()), + new Validators\Numeric('time_spent', t('This value must be numeric')), + new Validators\Numeric('time_estimated', t('This value must be numeric')), + ); + } + + /** + * Validate task creation + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateCreation(array $values) + { + $rules = array( + new Validators\Required('project_id', t('The project is required')), + new Validators\Required('title', t('The title is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate description creation + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateDescriptionCreation(array $values) + { + $rules = array( + new Validators\Required('id', t('The id is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate edit recurrence + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateEditRecurrence(array $values) + { + $rules = array( + new Validators\Required('id', t('The id is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + + /** + * Validate task modification (form) + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateModification(array $values) + { + $rules = array( + new Validators\Required('id', t('The id is required')), + new Validators\Required('title', t('The title is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate task modification (Api) + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateApiModification(array $values) + { + $rules = array( + new Validators\Required('id', t('The id is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate assignee change + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateAssigneeModification(array $values) + { + $rules = array( + new Validators\Required('id', t('The id is required')), + new Validators\Required('project_id', t('The project is required')), + new Validators\Required('owner_id', t('This value is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate category change + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateCategoryModification(array $values) + { + $rules = array( + new Validators\Required('id', t('The id is required')), + new Validators\Required('project_id', t('The project is required')), + new Validators\Required('category_id', t('This value is required')), + + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate project modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateProjectModification(array $values) + { + $rules = array( + new Validators\Required('id', t('The id is required')), + new Validators\Required('project_id', t('The project is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate time tracking modification (form) + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateTimeModification(array $values) + { + $rules = array( + new Validators\Required('id', t('The id is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } +} -- cgit v1.2.3 From b50f53ab3aac1311c80e2c1db98bf263f0c81984 Mon Sep 17 00:00:00 2001 From: Frederic Guillot Date: Mon, 11 Jan 2016 21:27:49 -0500 Subject: Move task link validator methods --- app/Controller/Tasklink.php | 4 +- app/Core/Base.php | 4 +- app/Model/TaskLink.php | 57 -------------------- app/Model/User.php | 3 -- app/ServiceProvider/ClassProvider.php | 1 + app/Validator/TaskLinkValidator.php | 71 ++++++++++++++++++++++++ tests/units/Model/TaskLinkTest.php | 49 ----------------- tests/units/Validator/TaskLinkValidatorTest.php | 72 +++++++++++++++++++++++++ 8 files changed, 149 insertions(+), 112 deletions(-) create mode 100644 app/Validator/TaskLinkValidator.php create mode 100644 tests/units/Validator/TaskLinkValidatorTest.php (limited to 'app/Validator') diff --git a/app/Controller/Tasklink.php b/app/Controller/Tasklink.php index 068bf16d..a81d3ee5 100644 --- a/app/Controller/Tasklink.php +++ b/app/Controller/Tasklink.php @@ -69,7 +69,7 @@ class Tasklink extends Base $values = $this->request->getValues(); $ajax = $this->request->isAjax() || $this->request->getIntegerParam('ajax'); - list($valid, $errors) = $this->taskLink->validateCreation($values); + list($valid, $errors) = $this->taskLinkValidator->validateCreation($values); if ($valid) { if ($this->taskLink->create($values['task_id'], $values['opposite_task_id'], $values['link_id'])) { @@ -125,7 +125,7 @@ class Tasklink extends Base $task = $this->getTask(); $values = $this->request->getValues(); - list($valid, $errors) = $this->taskLink->validateModification($values); + list($valid, $errors) = $this->taskLinkValidator->validateModification($values); if ($valid) { if ($this->taskLink->update($values['id'], $values['task_id'], $values['opposite_task_id'], $values['link_id'])) { diff --git a/app/Core/Base.php b/app/Core/Base.php index f32c1442..5a726ffa 100644 --- a/app/Core/Base.php +++ b/app/Core/Base.php @@ -100,7 +100,6 @@ use Pimple\Container; * @property \Kanboard\Model\TaskPermission $taskPermission * @property \Kanboard\Model\TaskPosition $taskPosition * @property \Kanboard\Model\TaskStatus $taskStatus - * @property \Kanboard\Model\TaskValidator $taskValidator * @property \Kanboard\Model\TaskMetadata $taskMetadata * @property \Kanboard\Model\Transition $transition * @property \Kanboard\Model\User $user @@ -114,6 +113,9 @@ use Pimple\Container; * @property \Kanboard\Model\UserMetadata $userMetadata * @property \Kanboard\Model\Webhook $webhook * @property \Kanboard\Validator\PasswordResetValidator $passwordResetValidator + * @property \Kanboard\Validator\TaskLinkValidator $taskLinkValidator + * @property \Kanboard\Validator\TaskValidator $taskValidator + * @property \Kanboard\Validator\UserValidator $userValidator * @property \Psr\Log\LoggerInterface $logger * @property \PicoDb\Database $db * @property \Symfony\Component\EventDispatcher\EventDispatcher $dispatcher diff --git a/app/Model/TaskLink.php b/app/Model/TaskLink.php index 1ac59203..87aae55e 100644 --- a/app/Model/TaskLink.php +++ b/app/Model/TaskLink.php @@ -2,8 +2,6 @@ namespace Kanboard\Model; -use SimpleValidator\Validator; -use SimpleValidator\Validators; use Kanboard\Event\TaskLinkEvent; /** @@ -261,59 +259,4 @@ class TaskLink extends Base return true; } - - /** - * Common validation rules - * - * @access private - * @return array - */ - private function commonValidationRules() - { - return array( - new Validators\Required('task_id', t('Field required')), - new Validators\Required('opposite_task_id', t('Field required')), - new Validators\Required('link_id', t('Field required')), - new Validators\NotEquals('opposite_task_id', 'task_id', t('A task cannot be linked to itself')), - new Validators\Exists('opposite_task_id', t('This linked task id doesn\'t exists'), $this->db->getConnection(), Task::TABLE, 'id') - ); - } - - /** - * Validate creation - * - * @access public - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateCreation(array $values) - { - $v = new Validator($values, $this->commonValidationRules()); - - return array( - $v->execute(), - $v->getErrors() - ); - } - - /** - * Validate modification - * - * @access public - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateModification(array $values) - { - $rules = array( - new Validators\Required('id', t('Field required')), - ); - - $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); - - return array( - $v->execute(), - $v->getErrors() - ); - } } diff --git a/app/Model/User.php b/app/Model/User.php index 84785ce5..ac0e7491 100644 --- a/app/Model/User.php +++ b/app/Model/User.php @@ -3,9 +3,6 @@ namespace Kanboard\Model; use PicoDb\Database; -use SimpleValidator\Validator; -use SimpleValidator\Validators; -use Kanboard\Core\Session\SessionManager; use Kanboard\Core\Security\Token; use Kanboard\Core\Security\Role; diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php index 44dba45b..80017bb0 100644 --- a/app/ServiceProvider/ClassProvider.php +++ b/app/ServiceProvider/ClassProvider.php @@ -87,6 +87,7 @@ class ClassProvider implements ServiceProviderInterface 'Validator' => array( 'PasswordResetValidator', 'TaskValidator', + 'TaskLinkValidator', 'UserValidator', ), 'Core' => array( diff --git a/app/Validator/TaskLinkValidator.php b/app/Validator/TaskLinkValidator.php new file mode 100644 index 00000000..021eb0b6 --- /dev/null +++ b/app/Validator/TaskLinkValidator.php @@ -0,0 +1,71 @@ +db->getConnection(), Task::TABLE, 'id') + ); + } + + /** + * Validate creation + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateCreation(array $values) + { + $v = new Validator($values, $this->commonValidationRules()); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateModification(array $values) + { + $rules = array( + new Validators\Required('id', t('Field required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } +} diff --git a/tests/units/Model/TaskLinkTest.php b/tests/units/Model/TaskLinkTest.php index 4db42d99..192a4298 100644 --- a/tests/units/Model/TaskLinkTest.php +++ b/tests/units/Model/TaskLinkTest.php @@ -194,53 +194,4 @@ class TaskLinkTest extends Base $links = $tl->getAll(2); $this->assertEmpty($links); } - - public function testValidation() - { - $tl = new TaskLink($this->container); - $p = new Project($this->container); - $tc = new TaskCreation($this->container); - - $this->assertEquals(1, $p->create(array('name' => 'test'))); - $this->assertEquals(1, $tc->create(array('project_id' => 1, 'title' => 'A'))); - $this->assertEquals(2, $tc->create(array('project_id' => 1, 'title' => 'B'))); - - $links = $tl->getAll(1); - $this->assertEmpty($links); - - $links = $tl->getAll(2); - $this->assertEmpty($links); - - // Check creation - $r = $tl->validateCreation(array('task_id' => 1, 'link_id' => 1, 'opposite_task_id' => 2)); - $this->assertTrue($r[0]); - - $r = $tl->validateCreation(array('task_id' => 1, 'link_id' => 1)); - $this->assertFalse($r[0]); - - $r = $tl->validateCreation(array('task_id' => 1, 'opposite_task_id' => 2)); - $this->assertFalse($r[0]); - - $r = $tl->validateCreation(array('task_id' => 1, 'opposite_task_id' => 2)); - $this->assertFalse($r[0]); - - $r = $tl->validateCreation(array('task_id' => 1, 'link_id' => 1, 'opposite_task_id' => 1)); - $this->assertFalse($r[0]); - - // Check modification - $r = $tl->validateModification(array('id' => 1, 'task_id' => 1, 'link_id' => 1, 'opposite_task_id' => 2)); - $this->assertTrue($r[0]); - - $r = $tl->validateModification(array('id' => 1, 'task_id' => 1, 'link_id' => 1)); - $this->assertFalse($r[0]); - - $r = $tl->validateModification(array('id' => 1, 'task_id' => 1, 'opposite_task_id' => 2)); - $this->assertFalse($r[0]); - - $r = $tl->validateModification(array('id' => 1, 'task_id' => 1, 'opposite_task_id' => 2)); - $this->assertFalse($r[0]); - - $r = $tl->validateModification(array('id' => 1, 'task_id' => 1, 'link_id' => 1, 'opposite_task_id' => 1)); - $this->assertFalse($r[0]); - } } diff --git a/tests/units/Validator/TaskLinkValidatorTest.php b/tests/units/Validator/TaskLinkValidatorTest.php new file mode 100644 index 00000000..5ac4588e --- /dev/null +++ b/tests/units/Validator/TaskLinkValidatorTest.php @@ -0,0 +1,72 @@ +container); + $tl = new TaskLink($this->container); + $p = new Project($this->container); + $tc = new TaskCreation($this->container); + + $this->assertEquals(1, $p->create(array('name' => 'test'))); + $this->assertEquals(1, $tc->create(array('project_id' => 1, 'title' => 'A'))); + $this->assertEquals(2, $tc->create(array('project_id' => 1, 'title' => 'B'))); + + $links = $tl->getAll(1); + $this->assertEmpty($links); + + $links = $tl->getAll(2); + $this->assertEmpty($links); + + // Check creation + $r = $validator->validateCreation(array('task_id' => 1, 'link_id' => 1, 'opposite_task_id' => 2)); + $this->assertTrue($r[0]); + + $r = $validator->validateCreation(array('task_id' => 1, 'link_id' => 1)); + $this->assertFalse($r[0]); + + $r = $validator->validateCreation(array('task_id' => 1, 'opposite_task_id' => 2)); + $this->assertFalse($r[0]); + + $r = $validator->validateCreation(array('task_id' => 1, 'opposite_task_id' => 2)); + $this->assertFalse($r[0]); + + $r = $validator->validateCreation(array('task_id' => 1, 'link_id' => 1, 'opposite_task_id' => 1)); + $this->assertFalse($r[0]); + } + + public function testValidateModification() + { + $validator = new TaskLinkValidator($this->container); + $p = new Project($this->container); + $tc = new TaskCreation($this->container); + + $this->assertEquals(1, $p->create(array('name' => 'test'))); + $this->assertEquals(1, $tc->create(array('project_id' => 1, 'title' => 'A'))); + $this->assertEquals(2, $tc->create(array('project_id' => 1, 'title' => 'B'))); + + // Check modification + $r = $validator->validateModification(array('id' => 1, 'task_id' => 1, 'link_id' => 1, 'opposite_task_id' => 2)); + $this->assertTrue($r[0]); + + $r = $validator->validateModification(array('id' => 1, 'task_id' => 1, 'link_id' => 1)); + $this->assertFalse($r[0]); + + $r = $validator->validateModification(array('id' => 1, 'task_id' => 1, 'opposite_task_id' => 2)); + $this->assertFalse($r[0]); + + $r = $validator->validateModification(array('id' => 1, 'task_id' => 1, 'opposite_task_id' => 2)); + $this->assertFalse($r[0]); + + $r = $validator->validateModification(array('id' => 1, 'task_id' => 1, 'link_id' => 1, 'opposite_task_id' => 1)); + $this->assertFalse($r[0]); + } +} -- cgit v1.2.3 From 20b9c3d0302625a97fa53eaf299bab2dca3f4970 Mon Sep 17 00:00:00 2001 From: Frederic Guillot Date: Mon, 11 Jan 2016 21:36:00 -0500 Subject: Move swimlane validator methods --- app/Controller/Swimlane.php | 6 +-- app/Core/Base.php | 1 + app/Model/Swimlane.php | 84 ------------------------------ app/ServiceProvider/ClassProvider.php | 1 + app/Validator/SwimlaneValidator.php | 96 +++++++++++++++++++++++++++++++++++ app/Validator/TaskLinkValidator.php | 2 +- 6 files changed, 102 insertions(+), 88 deletions(-) create mode 100644 app/Validator/SwimlaneValidator.php (limited to 'app/Validator') diff --git a/app/Controller/Swimlane.php b/app/Controller/Swimlane.php index 5229621c..66410888 100644 --- a/app/Controller/Swimlane.php +++ b/app/Controller/Swimlane.php @@ -60,7 +60,7 @@ class Swimlane extends Base { $project = $this->getProject(); $values = $this->request->getValues(); - list($valid, $errors) = $this->swimlane->validateCreation($values); + list($valid, $errors) = $this->swimlaneValidator->validateCreation($values); if ($valid) { if ($this->swimlane->create($values)) { @@ -84,7 +84,7 @@ class Swimlane extends Base $project = $this->getProject(); $values = $this->request->getValues() + array('show_default_swimlane' => 0); - list($valid, ) = $this->swimlane->validateDefaultModification($values); + list($valid, ) = $this->swimlaneValidator->validateDefaultModification($values); if ($valid) { if ($this->swimlane->updateDefault($values)) { @@ -126,7 +126,7 @@ class Swimlane extends Base $project = $this->getProject(); $values = $this->request->getValues(); - list($valid, $errors) = $this->swimlane->validateModification($values); + list($valid, $errors) = $this->swimlaneValidator->validateModification($values); if ($valid) { if ($this->swimlane->update($values)) { diff --git a/app/Core/Base.php b/app/Core/Base.php index 5a726ffa..522e5e05 100644 --- a/app/Core/Base.php +++ b/app/Core/Base.php @@ -113,6 +113,7 @@ use Pimple\Container; * @property \Kanboard\Model\UserMetadata $userMetadata * @property \Kanboard\Model\Webhook $webhook * @property \Kanboard\Validator\PasswordResetValidator $passwordResetValidator + * @property \Kanboard\Validator\SwimlaneValidator $swimlaneValidator * @property \Kanboard\Validator\TaskLinkValidator $taskLinkValidator * @property \Kanboard\Validator\TaskValidator $taskValidator * @property \Kanboard\Validator\UserValidator $userValidator diff --git a/app/Model/Swimlane.php b/app/Model/Swimlane.php index df44985a..e5124e8e 100644 --- a/app/Model/Swimlane.php +++ b/app/Model/Swimlane.php @@ -2,9 +2,6 @@ namespace Kanboard\Model; -use SimpleValidator\Validator; -use SimpleValidator\Validators; - /** * Swimlanes * @@ -470,85 +467,4 @@ class Swimlane extends Base return true; } - - /** - * Validate creation - * - * @access public - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateCreation(array $values) - { - $rules = array( - new Validators\Required('project_id', t('The project id is required')), - new Validators\Required('name', t('The name is required')), - ); - - $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); - - return array( - $v->execute(), - $v->getErrors() - ); - } - - /** - * Validate modification - * - * @access public - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateModification(array $values) - { - $rules = array( - new Validators\Required('id', t('The id is required')), - new Validators\Required('name', t('The name is required')), - ); - - $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); - - return array( - $v->execute(), - $v->getErrors() - ); - } - - /** - * Validate default swimlane modification - * - * @access public - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateDefaultModification(array $values) - { - $rules = array( - new Validators\Required('id', t('The id is required')), - new Validators\Required('default_swimlane', t('The name is required')), - ); - - $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); - - return array( - $v->execute(), - $v->getErrors() - ); - } - - /** - * Common validation rules - * - * @access private - * @return array - */ - private function commonValidationRules() - { - return array( - new Validators\Integer('id', t('The id must be an integer')), - new Validators\Integer('project_id', t('The project id must be an integer')), - new Validators\MaxLength('name', t('The maximum length is %d characters', 50), 50) - ); - } } diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php index 80017bb0..89430509 100644 --- a/app/ServiceProvider/ClassProvider.php +++ b/app/ServiceProvider/ClassProvider.php @@ -86,6 +86,7 @@ class ClassProvider implements ServiceProviderInterface ), 'Validator' => array( 'PasswordResetValidator', + 'SwimlaneValidator', 'TaskValidator', 'TaskLinkValidator', 'UserValidator', diff --git a/app/Validator/SwimlaneValidator.php b/app/Validator/SwimlaneValidator.php new file mode 100644 index 00000000..4cc780f9 --- /dev/null +++ b/app/Validator/SwimlaneValidator.php @@ -0,0 +1,96 @@ +commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateModification(array $values) + { + $rules = array( + new Validators\Required('id', t('The id is required')), + new Validators\Required('name', t('The name is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate default swimlane modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateDefaultModification(array $values) + { + $rules = array( + new Validators\Required('id', t('The id is required')), + new Validators\Required('default_swimlane', t('The name is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Common validation rules + * + * @access private + * @return array + */ + private function commonValidationRules() + { + return array( + new Validators\Integer('id', t('The id must be an integer')), + new Validators\Integer('project_id', t('The project id must be an integer')), + new Validators\MaxLength('name', t('The maximum length is %d characters', 50), 50) + ); + } +} diff --git a/app/Validator/TaskLinkValidator.php b/app/Validator/TaskLinkValidator.php index 021eb0b6..c88c2b16 100644 --- a/app/Validator/TaskLinkValidator.php +++ b/app/Validator/TaskLinkValidator.php @@ -7,7 +7,7 @@ use SimpleValidator\Validators; use Kanboard\Model\Task; /** - * TaskLink Validator + * Task Link Validator * * @package validator * @author Frederic Guillot -- cgit v1.2.3 From 9a661a5e9ede5fc699f573179683b7db973a2fe0 Mon Sep 17 00:00:00 2001 From: Frederic Guillot Date: Mon, 11 Jan 2016 21:47:51 -0500 Subject: Move subtask validator methods --- app/Api/Subtask.php | 4 +- app/Controller/Subtask.php | 4 +- app/Core/Base.php | 1 + app/Model/Subtask.php | 90 +----------------------------- app/ServiceProvider/ClassProvider.php | 1 + app/Validator/SubtaskValidator.php | 101 ++++++++++++++++++++++++++++++++++ 6 files changed, 108 insertions(+), 93 deletions(-) create mode 100644 app/Validator/SubtaskValidator.php (limited to 'app/Validator') diff --git a/app/Api/Subtask.php b/app/Api/Subtask.php index 7baee3d3..782fdb02 100644 --- a/app/Api/Subtask.php +++ b/app/Api/Subtask.php @@ -36,7 +36,7 @@ class Subtask extends \Kanboard\Core\Base 'status' => $status, ); - list($valid, ) = $this->subtask->validateCreation($values); + list($valid, ) = $this->subtaskValidator->validateCreation($values); return $valid ? $this->subtask->create($values) : false; } @@ -58,7 +58,7 @@ class Subtask extends \Kanboard\Core\Base } } - list($valid, ) = $this->subtask->validateApiModification($values); + list($valid, ) = $this->subtaskValidator->validateApiModification($values); return $valid && $this->subtask->update($values); } } diff --git a/app/Controller/Subtask.php b/app/Controller/Subtask.php index c93b637d..caaaa85e 100644 --- a/app/Controller/Subtask.php +++ b/app/Controller/Subtask.php @@ -63,7 +63,7 @@ class Subtask extends Base $task = $this->getTask(); $values = $this->request->getValues(); - list($valid, $errors) = $this->subtask->validateCreation($values); + list($valid, $errors) = $this->subtaskValidator->validateCreation($values); if ($valid) { if ($this->subtask->create($values)) { @@ -113,7 +113,7 @@ class Subtask extends Base $this->getSubtask(); $values = $this->request->getValues(); - list($valid, $errors) = $this->subtask->validateModification($values); + list($valid, $errors) = $this->subtaskValidator->validateModification($values); if ($valid) { if ($this->subtask->update($values)) { diff --git a/app/Core/Base.php b/app/Core/Base.php index 522e5e05..54a3a7d4 100644 --- a/app/Core/Base.php +++ b/app/Core/Base.php @@ -113,6 +113,7 @@ use Pimple\Container; * @property \Kanboard\Model\UserMetadata $userMetadata * @property \Kanboard\Model\Webhook $webhook * @property \Kanboard\Validator\PasswordResetValidator $passwordResetValidator + * @property \Kanboard\Validator\SubtaskValidator $subtaskValidator * @property \Kanboard\Validator\SwimlaneValidator $swimlaneValidator * @property \Kanboard\Validator\TaskLinkValidator $taskLinkValidator * @property \Kanboard\Validator\TaskValidator $taskValidator diff --git a/app/Model/Subtask.php b/app/Model/Subtask.php index 664e41e1..0e039bb3 100644 --- a/app/Model/Subtask.php +++ b/app/Model/Subtask.php @@ -4,11 +4,9 @@ namespace Kanboard\Model; use PicoDb\Database; use Kanboard\Event\SubtaskEvent; -use SimpleValidator\Validator; -use SimpleValidator\Validators; /** - * Subtask model + * Subtask Model * * @package model * @author Frederic Guillot @@ -451,90 +449,4 @@ class Subtask extends Base } }); } - - /** - * Validate creation - * - * @access public - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateCreation(array $values) - { - $rules = array( - new Validators\Required('task_id', t('The task id is required')), - new Validators\Required('title', t('The title is required')), - ); - - $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); - - return array( - $v->execute(), - $v->getErrors() - ); - } - - /** - * Validate modification - * - * @access public - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateModification(array $values) - { - $rules = array( - new Validators\Required('id', t('The subtask id is required')), - new Validators\Required('task_id', t('The task id is required')), - new Validators\Required('title', t('The title is required')), - ); - - $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); - - return array( - $v->execute(), - $v->getErrors() - ); - } - - /** - * Validate API modification - * - * @access public - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateApiModification(array $values) - { - $rules = array( - new Validators\Required('id', t('The subtask id is required')), - new Validators\Required('task_id', t('The task id is required')), - ); - - $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); - - return array( - $v->execute(), - $v->getErrors() - ); - } - - /** - * Common validation rules - * - * @access private - * @return array - */ - private function commonValidationRules() - { - return array( - new Validators\Integer('id', t('The subtask id must be an integer')), - new Validators\Integer('task_id', t('The task id must be an integer')), - new Validators\MaxLength('title', t('The maximum length is %d characters', 255), 255), - new Validators\Integer('user_id', t('The user id must be an integer')), - new Validators\Integer('status', t('The status must be an integer')), - new Validators\Numeric('time_estimated', t('The time must be a numeric value')), - new Validators\Numeric('time_spent', t('The time must be a numeric value')), - ); - } } diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php index 89430509..58ef48fb 100644 --- a/app/ServiceProvider/ClassProvider.php +++ b/app/ServiceProvider/ClassProvider.php @@ -86,6 +86,7 @@ class ClassProvider implements ServiceProviderInterface ), 'Validator' => array( 'PasswordResetValidator', + 'SubtaskValidator', 'SwimlaneValidator', 'TaskValidator', 'TaskLinkValidator', diff --git a/app/Validator/SubtaskValidator.php b/app/Validator/SubtaskValidator.php new file mode 100644 index 00000000..1989b7f4 --- /dev/null +++ b/app/Validator/SubtaskValidator.php @@ -0,0 +1,101 @@ +commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateModification(array $values) + { + $rules = array( + new Validators\Required('id', t('The subtask id is required')), + new Validators\Required('task_id', t('The task id is required')), + new Validators\Required('title', t('The title is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate API modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateApiModification(array $values) + { + $rules = array( + new Validators\Required('id', t('The subtask id is required')), + new Validators\Required('task_id', t('The task id is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Common validation rules + * + * @access private + * @return array + */ + private function commonValidationRules() + { + return array( + new Validators\Integer('id', t('The subtask id must be an integer')), + new Validators\Integer('task_id', t('The task id must be an integer')), + new Validators\MaxLength('title', t('The maximum length is %d characters', 255), 255), + new Validators\Integer('user_id', t('The user id must be an integer')), + new Validators\Integer('status', t('The status must be an integer')), + new Validators\Numeric('time_estimated', t('The time must be a numeric value')), + new Validators\Numeric('time_spent', t('The time must be a numeric value')), + ); + } +} -- cgit v1.2.3 From e31185a2bd85d7d8e16a614cb21dfdbff10ce070 Mon Sep 17 00:00:00 2001 From: Frederic Guillot Date: Mon, 11 Jan 2016 21:54:52 -0500 Subject: Minor fixes --- app/Controller/PasswordReset.php | 2 +- app/Core/Base.php | 1 + app/Core/Session/SessionStorage.php | 2 ++ app/Validator/PasswordResetValidator.php | 1 - 4 files changed, 4 insertions(+), 2 deletions(-) (limited to 'app/Validator') diff --git a/app/Controller/PasswordReset.php b/app/Controller/PasswordReset.php index ebc1f77a..23567c9c 100644 --- a/app/Controller/PasswordReset.php +++ b/app/Controller/PasswordReset.php @@ -67,7 +67,7 @@ class PasswordReset extends Base /** * Set the new password */ - public function update(array $values = array(), array $errors = array()) + public function update() { $this->checkActivation(); diff --git a/app/Core/Base.php b/app/Core/Base.php index 54a3a7d4..21e80628 100644 --- a/app/Core/Base.php +++ b/app/Core/Base.php @@ -79,6 +79,7 @@ use Pimple\Container; * @property \Kanboard\Model\ProjectMetadata $projectMetadata * @property \Kanboard\Model\ProjectPermission $projectPermission * @property \Kanboard\Model\ProjectUserRole $projectUserRole + * @property \Kanboard\Model\projectUserRoleFilter $projectUserRoleFilter * @property \Kanboard\Model\ProjectGroupRole $projectGroupRole * @property \Kanboard\Model\ProjectNotification $projectNotification * @property \Kanboard\Model\ProjectNotificationType $projectNotificationType diff --git a/app/Core/Session/SessionStorage.php b/app/Core/Session/SessionStorage.php index f55a7ee3..5ee1d811 100644 --- a/app/Core/Session/SessionStorage.php +++ b/app/Core/Session/SessionStorage.php @@ -20,6 +20,8 @@ namespace Kanboard\Core\Session; * @property bool $hasSubtaskInProgress * @property bool $hasRememberMe * @property bool $boardCollapsed + * @property bool $twoFactorBeforeCodeCalled + * @property string $twoFactorSecret */ class SessionStorage { diff --git a/app/Validator/PasswordResetValidator.php b/app/Validator/PasswordResetValidator.php index 9ef45045..baf2d8d7 100644 --- a/app/Validator/PasswordResetValidator.php +++ b/app/Validator/PasswordResetValidator.php @@ -73,7 +73,6 @@ class PasswordResetValidator extends Base */ protected function validateCaptcha(array $values) { - $result = true; $errors = array(); if (! isset($this->sessionStorage->captcha)) { -- cgit v1.2.3 From 3699073371acc66ccacb56a89e87147a9c90d8c4 Mon Sep 17 00:00:00 2001 From: Frederic Guillot Date: Mon, 11 Jan 2016 22:07:39 -0500 Subject: Move project validator methods --- app/Api/Me.php | 2 +- app/Api/Project.php | 4 +- app/Controller/Project.php | 4 +- app/Core/Base.php | 1 + app/Model/Project.php | 69 --------------------- app/ServiceProvider/ClassProvider.php | 1 + app/Validator/ProjectValidator.php | 83 ++++++++++++++++++++++++++ tests/units/Model/ProjectTest.php | 25 -------- tests/units/Validator/ProjectValidatorTest.php | 67 +++++++++++++++++++++ 9 files changed, 157 insertions(+), 99 deletions(-) create mode 100644 app/Validator/ProjectValidator.php create mode 100644 tests/units/Validator/ProjectValidatorTest.php (limited to 'app/Validator') diff --git a/app/Api/Me.php b/app/Api/Me.php index 37851731..df8ec070 100644 --- a/app/Api/Me.php +++ b/app/Api/Me.php @@ -44,7 +44,7 @@ class Me extends Base 'is_private' => 1, ); - list($valid, ) = $this->project->validateCreation($values); + list($valid, ) = $this->projectValidator->validateCreation($values); return $valid ? $this->project->create($values, $this->userSession->getId(), true) : false; } diff --git a/app/Api/Project.php b/app/Api/Project.php index f934432d..8e311f7f 100644 --- a/app/Api/Project.php +++ b/app/Api/Project.php @@ -69,7 +69,7 @@ class Project extends Base 'description' => $description ); - list($valid, ) = $this->project->validateCreation($values); + list($valid, ) = $this->projectValidator->validateCreation($values); return $valid ? $this->project->create($values) : false; } @@ -81,7 +81,7 @@ class Project extends Base 'description' => $description ); - list($valid, ) = $this->project->validateModification($values); + list($valid, ) = $this->projectValidator->validateModification($values); return $valid && $this->project->update($values); } } diff --git a/app/Controller/Project.php b/app/Controller/Project.php index 5e75db4e..27c827d1 100644 --- a/app/Controller/Project.php +++ b/app/Controller/Project.php @@ -169,7 +169,7 @@ class Project extends Base } } - list($valid, $errors) = $this->project->validateModification($values); + list($valid, $errors) = $this->projectValidator->validateModification($values); if ($valid) { if ($this->project->update($values)) { @@ -329,7 +329,7 @@ class Project extends Base public function save() { $values = $this->request->getValues(); - list($valid, $errors) = $this->project->validateCreation($values); + list($valid, $errors) = $this->projectValidator->validateCreation($values); if ($valid) { $project_id = $this->project->create($values, $this->userSession->getId(), true); diff --git a/app/Core/Base.php b/app/Core/Base.php index 21e80628..723831e3 100644 --- a/app/Core/Base.php +++ b/app/Core/Base.php @@ -114,6 +114,7 @@ use Pimple\Container; * @property \Kanboard\Model\UserMetadata $userMetadata * @property \Kanboard\Model\Webhook $webhook * @property \Kanboard\Validator\PasswordResetValidator $passwordResetValidator + * @property \Kanboard\Validator\ProjectValidator $projectValidator * @property \Kanboard\Validator\SubtaskValidator $subtaskValidator * @property \Kanboard\Validator\SwimlaneValidator $swimlaneValidator * @property \Kanboard\Validator\TaskLinkValidator $taskLinkValidator diff --git a/app/Model/Project.php b/app/Model/Project.php index 8a949ba6..63077348 100644 --- a/app/Model/Project.php +++ b/app/Model/Project.php @@ -2,8 +2,6 @@ namespace Kanboard\Model; -use SimpleValidator\Validator; -use SimpleValidator\Validators; use Kanboard\Core\Security\Token; use Kanboard\Core\Security\Role; @@ -510,71 +508,4 @@ class Project extends Base ->eq('id', $project_id) ->save(array('is_public' => 0, 'token' => '')); } - - /** - * Common validation rules - * - * @access private - * @return array - */ - private function commonValidationRules() - { - return array( - new Validators\Integer('id', t('This value must be an integer')), - new Validators\Integer('is_active', t('This value must be an integer')), - new Validators\Required('name', t('The project name is required')), - new Validators\MaxLength('name', t('The maximum length is %d characters', 50), 50), - new Validators\MaxLength('identifier', t('The maximum length is %d characters', 50), 50), - new Validators\MaxLength('start_date', t('The maximum length is %d characters', 10), 10), - new Validators\MaxLength('end_date', t('The maximum length is %d characters', 10), 10), - new Validators\AlphaNumeric('identifier', t('This value must be alphanumeric')) , - new Validators\Unique('identifier', t('The identifier must be unique'), $this->db->getConnection(), self::TABLE), - ); - } - - /** - * Validate project creation - * - * @access public - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateCreation(array $values) - { - if (! empty($values['identifier'])) { - $values['identifier'] = strtoupper($values['identifier']); - } - - $v = new Validator($values, $this->commonValidationRules()); - - return array( - $v->execute(), - $v->getErrors() - ); - } - - /** - * Validate project modification - * - * @access public - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateModification(array $values) - { - if (! empty($values['identifier'])) { - $values['identifier'] = strtoupper($values['identifier']); - } - - $rules = array( - new Validators\Required('id', t('This value is required')), - ); - - $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); - - return array( - $v->execute(), - $v->getErrors() - ); - } } diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php index 58ef48fb..bb9e757e 100644 --- a/app/ServiceProvider/ClassProvider.php +++ b/app/ServiceProvider/ClassProvider.php @@ -86,6 +86,7 @@ class ClassProvider implements ServiceProviderInterface ), 'Validator' => array( 'PasswordResetValidator', + 'ProjectValidator', 'SubtaskValidator', 'SwimlaneValidator', 'TaskValidator', diff --git a/app/Validator/ProjectValidator.php b/app/Validator/ProjectValidator.php new file mode 100644 index 00000000..53cb7a37 --- /dev/null +++ b/app/Validator/ProjectValidator.php @@ -0,0 +1,83 @@ +db->getConnection(), Project::TABLE), + ); + } + + /** + * Validate project creation + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateCreation(array $values) + { + if (! empty($values['identifier'])) { + $values['identifier'] = strtoupper($values['identifier']); + } + + $v = new Validator($values, $this->commonValidationRules()); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate project modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateModification(array $values) + { + if (! empty($values['identifier'])) { + $values['identifier'] = strtoupper($values['identifier']); + } + + $rules = array( + new Validators\Required('id', t('This value is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } +} diff --git a/tests/units/Model/ProjectTest.php b/tests/units/Model/ProjectTest.php index 4d8e6fc7..990cee5a 100644 --- a/tests/units/Model/ProjectTest.php +++ b/tests/units/Model/ProjectTest.php @@ -277,30 +277,5 @@ class ProjectTest extends Base $project = $p->getByIdentifier(''); $this->assertFalse($project); - - // Validation rules - $r = $p->validateCreation(array('name' => 'test', 'identifier' => 'TEST1')); - $this->assertFalse($r[0]); - - $r = $p->validateCreation(array('name' => 'test', 'identifier' => 'test1')); - $this->assertFalse($r[0]); - - $r = $p->validateModification(array('id' => 1, 'name' => 'test', 'identifier' => 'TEST1')); - $this->assertTrue($r[0]); - - $r = $p->validateModification(array('id' => 1, 'name' => 'test', 'identifier' => 'test3')); - $this->assertTrue($r[0]); - - $r = $p->validateModification(array('id' => 1, 'name' => 'test', 'identifier' => '')); - $this->assertTrue($r[0]); - - $r = $p->validateModification(array('id' => 1, 'name' => 'test', 'identifier' => 'TEST2')); - $this->assertFalse($r[0]); - - $r = $p->validateCreation(array('name' => 'test', 'identifier' => 'a-b-c')); - $this->assertFalse($r[0]); - - $r = $p->validateCreation(array('name' => 'test', 'identifier' => 'test 123')); - $this->assertFalse($r[0]); } } diff --git a/tests/units/Validator/ProjectValidatorTest.php b/tests/units/Validator/ProjectValidatorTest.php new file mode 100644 index 00000000..a73e8247 --- /dev/null +++ b/tests/units/Validator/ProjectValidatorTest.php @@ -0,0 +1,67 @@ +container); + $p = new Project($this->container); + + $this->assertEquals(1, $p->create(array('name' => 'UnitTest1', 'identifier' => 'test1'))); + $this->assertEquals(2, $p->create(array('name' => 'UnitTest2'))); + + $project = $p->getById(1); + $this->assertNotEmpty($project); + $this->assertEquals('TEST1', $project['identifier']); + + $project = $p->getById(2); + $this->assertNotEmpty($project); + $this->assertEquals('', $project['identifier']); + + $r = $validator->validateCreation(array('name' => 'test', 'identifier' => 'TEST1')); + $this->assertFalse($r[0]); + + $r = $validator->validateCreation(array('name' => 'test', 'identifier' => 'test1')); + $this->assertFalse($r[0]); + + $r = $validator->validateCreation(array('name' => 'test', 'identifier' => 'a-b-c')); + $this->assertFalse($r[0]); + + $r = $validator->validateCreation(array('name' => 'test', 'identifier' => 'test 123')); + $this->assertFalse($r[0]); + } + + public function testValidateModification() + { + $validator = new ProjectValidator($this->container); + $p = new Project($this->container); + + $this->assertEquals(1, $p->create(array('name' => 'UnitTest1', 'identifier' => 'test1'))); + $this->assertEquals(2, $p->create(array('name' => 'UnitTest2', 'identifier' => 'TEST2'))); + + $project = $p->getById(1); + $this->assertNotEmpty($project); + $this->assertEquals('TEST1', $project['identifier']); + + $project = $p->getById(2); + $this->assertNotEmpty($project); + $this->assertEquals('TEST2', $project['identifier']); + + $r = $validator->validateModification(array('id' => 1, 'name' => 'test', 'identifier' => 'TEST1')); + $this->assertTrue($r[0]); + + $r = $validator->validateModification(array('id' => 1, 'name' => 'test', 'identifier' => 'test3')); + $this->assertTrue($r[0]); + + $r = $validator->validateModification(array('id' => 1, 'name' => 'test', 'identifier' => '')); + $this->assertTrue($r[0]); + + $r = $validator->validateModification(array('id' => 1, 'name' => 'test', 'identifier' => 'TEST2')); + $this->assertFalse($r[0]); + } +} -- cgit v1.2.3 From dc35a78374e9b091505cfc56eefcd0c631c56e3a Mon Sep 17 00:00:00 2001 From: Frederic Guillot Date: Wed, 13 Jan 2016 21:45:14 -0500 Subject: Move some validators to separate classes --- app/Api/Action.php | 2 +- app/Controller/Action.php | 2 +- app/Controller/Column.php | 4 +-- app/Core/Base.php | 2 ++ app/Model/Action.php | 26 ---------------- app/Model/ActionParameter.php | 3 -- app/Model/Board.php | 45 --------------------------- app/ServiceProvider/ClassProvider.php | 2 ++ app/Validator/ActionValidator.php | 38 +++++++++++++++++++++++ app/Validator/ColumnValidator.php | 58 +++++++++++++++++++++++++++++++++++ 10 files changed, 104 insertions(+), 78 deletions(-) create mode 100644 app/Validator/ActionValidator.php create mode 100644 app/Validator/ColumnValidator.php (limited to 'app/Validator') diff --git a/app/Api/Action.php b/app/Api/Action.php index bdb5b26e..9e3b86f6 100644 --- a/app/Api/Action.php +++ b/app/Api/Action.php @@ -44,7 +44,7 @@ class Action extends \Kanboard\Core\Base 'params' => $params, ); - list($valid, ) = $this->action->validateCreation($values); + list($valid, ) = $this->actionValidator->validateCreation($values); if (! $valid) { return false; diff --git a/app/Controller/Action.php b/app/Controller/Action.php index 9b803893..645b53b7 100644 --- a/app/Controller/Action.php +++ b/app/Controller/Action.php @@ -116,7 +116,7 @@ class Action extends Base */ private function doCreation(array $project, array $values) { - list($valid, ) = $this->action->validateCreation($values); + list($valid, ) = $this->actionValidator->validateCreation($values); if ($valid) { if ($this->action->create($values) !== false) { diff --git a/app/Controller/Column.php b/app/Controller/Column.php index b484fe12..1ce575d7 100644 --- a/app/Controller/Column.php +++ b/app/Controller/Column.php @@ -51,7 +51,7 @@ class Column extends Base $values['title['.$column_id.']'] = $column_title; } - list($valid, $errors) = $this->board->validateCreation($data); + list($valid, $errors) = $this->columnValidator->validateCreation($data); if ($valid) { if ($this->board->addColumn($project['id'], $data['title'], $data['task_limit'], $data['description'])) { @@ -94,7 +94,7 @@ class Column extends Base $project = $this->getProject(); $values = $this->request->getValues(); - list($valid, $errors) = $this->board->validateModification($values); + list($valid, $errors) = $this->columnValidator->validateModification($values); if ($valid) { if ($this->board->updateColumn($values['id'], $values['title'], $values['task_limit'], $values['description'])) { diff --git a/app/Core/Base.php b/app/Core/Base.php index 723831e3..22f09403 100644 --- a/app/Core/Base.php +++ b/app/Core/Base.php @@ -113,6 +113,8 @@ use Pimple\Container; * @property \Kanboard\Model\UserUnreadNotification $userUnreadNotification * @property \Kanboard\Model\UserMetadata $userMetadata * @property \Kanboard\Model\Webhook $webhook + * @property \Kanboard\Validator\ActionValidator $actionValidator + * @property \Kanboard\Validator\ColumnValidator $columnValidator * @property \Kanboard\Validator\PasswordResetValidator $passwordResetValidator * @property \Kanboard\Validator\ProjectValidator $projectValidator * @property \Kanboard\Validator\SubtaskValidator $subtaskValidator diff --git a/app/Model/Action.php b/app/Model/Action.php index 5fcfbaa7..4da2fb8f 100644 --- a/app/Model/Action.php +++ b/app/Model/Action.php @@ -2,9 +2,6 @@ namespace Kanboard\Model; -use SimpleValidator\Validator; -use SimpleValidator\Validators; - /** * Action Model * @@ -188,27 +185,4 @@ class Action extends Base return true; } - - /** - * Validate action creation - * - * @access public - * @param array $values Required parameters to save an action - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateCreation(array $values) - { - $v = new Validator($values, array( - new Validators\Required('project_id', t('The project id is required')), - new Validators\Integer('project_id', t('This value must be an integer')), - new Validators\Required('event_name', t('This value is required')), - new Validators\Required('action_name', t('This value is required')), - new Validators\Required('params', t('This value is required')), - )); - - return array( - $v->execute(), - $v->getErrors() - ); - } } diff --git a/app/Model/ActionParameter.php b/app/Model/ActionParameter.php index 1e4d7544..62b03142 100644 --- a/app/Model/ActionParameter.php +++ b/app/Model/ActionParameter.php @@ -2,9 +2,6 @@ namespace Kanboard\Model; -use SimpleValidator\Validator; -use SimpleValidator\Validators; - /** * Action Parameter Model * diff --git a/app/Model/Board.php b/app/Model/Board.php index 79a1a92d..0f980f68 100644 --- a/app/Model/Board.php +++ b/app/Model/Board.php @@ -2,8 +2,6 @@ namespace Kanboard\Model; -use SimpleValidator\Validator; -use SimpleValidator\Validators; use PicoDb\Database; /** @@ -436,47 +434,4 @@ class Board extends Base { return $this->db->table(self::TABLE)->eq('id', $column_id)->remove(); } - - /** - * Validate column modification - * - * @access public - * @param array $values Required parameters to update a column - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateModification(array $values) - { - $v = new Validator($values, array( - new Validators\Integer('task_limit', t('This value must be an integer')), - new Validators\Required('title', t('The title is required')), - new Validators\MaxLength('title', t('The maximum length is %d characters', 50), 50), - )); - - return array( - $v->execute(), - $v->getErrors() - ); - } - - /** - * Validate column creation - * - * @access public - * @param array $values Required parameters to save an action - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateCreation(array $values) - { - $v = new Validator($values, array( - new Validators\Required('project_id', t('The project id is required')), - new Validators\Integer('project_id', t('This value must be an integer')), - new Validators\Required('title', t('The title is required')), - new Validators\MaxLength('title', t('The maximum length is %d characters', 50), 50), - )); - - return array( - $v->execute(), - $v->getErrors() - ); - } } diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php index bb9e757e..61c97a07 100644 --- a/app/ServiceProvider/ClassProvider.php +++ b/app/ServiceProvider/ClassProvider.php @@ -85,6 +85,8 @@ class ClassProvider implements ServiceProviderInterface 'GroupAutoCompleteFormatter', ), 'Validator' => array( + 'ActionValidator', + 'ColumnValidator', 'PasswordResetValidator', 'ProjectValidator', 'SubtaskValidator', diff --git a/app/Validator/ActionValidator.php b/app/Validator/ActionValidator.php new file mode 100644 index 00000000..95ee7d21 --- /dev/null +++ b/app/Validator/ActionValidator.php @@ -0,0 +1,38 @@ +execute(), + $v->getErrors() + ); + } +} diff --git a/app/Validator/ColumnValidator.php b/app/Validator/ColumnValidator.php new file mode 100644 index 00000000..4c644e8a --- /dev/null +++ b/app/Validator/ColumnValidator.php @@ -0,0 +1,58 @@ +execute(), + $v->getErrors() + ); + } + + /** + * Validate column creation + * + * @access public + * @param array $values Required parameters to save an action + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateCreation(array $values) + { + $v = new Validator($values, array( + new Validators\Required('project_id', t('The project id is required')), + new Validators\Integer('project_id', t('This value must be an integer')), + new Validators\Required('title', t('The title is required')), + new Validators\MaxLength('title', t('The maximum length is %d characters', 50), 50), + )); + + return array( + $v->execute(), + $v->getErrors() + ); + } +} -- cgit v1.2.3 From 805be7d33155478ef32c4bd3643dcf4025d85a05 Mon Sep 17 00:00:00 2001 From: Frederic Guillot Date: Thu, 14 Jan 2016 20:18:13 -0500 Subject: Move validator methods --- app/Api/Category.php | 4 +- app/Api/Comment.php | 4 +- app/Api/Link.php | 4 +- app/Controller/Auth.php | 2 +- app/Controller/Category.php | 4 +- app/Controller/Comment.php | 4 +- app/Controller/Currency.php | 2 +- app/Controller/Customfilter.php | 4 +- app/Controller/Group.php | 4 +- app/Controller/Link.php | 4 +- app/Core/Base.php | 9 +- app/Model/Authentication.php | 130 --------------------- app/Model/Category.php | 62 ---------- app/Model/Comment.php | 61 ---------- app/Model/Currency.php | 23 ---- app/Model/CustomFilter.php | 62 ---------- app/Model/Group.php | 55 --------- app/Model/Link.php | 45 ------- app/ServiceProvider/ClassProvider.php | 8 +- app/Validator/AuthValidator.php | 130 +++++++++++++++++++++ app/Validator/CategoryValidator.php | 74 ++++++++++++ app/Validator/CommentValidator.php | 74 ++++++++++++ app/Validator/CurrencyValidator.php | 35 ++++++ app/Validator/CustomFilterValidator.php | 74 ++++++++++++ app/Validator/GroupValidator.php | 71 +++++++++++ app/Validator/LinkValidator.php | 59 ++++++++++ tests/units/Model/CommentTest.php | 49 -------- tests/units/Model/CustomFilterTest.php | 28 ----- tests/units/Model/GroupTest.php | 22 ---- tests/units/Model/LinkTest.php | 46 -------- tests/units/Validator/CommentValidatorTest.php | 57 +++++++++ .../units/Validator/CustomFilterValidatorTest.php | 40 +++++++ tests/units/Validator/GroupValidatorTest.php | 30 +++++ tests/units/Validator/LinkValidatorTest.php | 54 +++++++++ 34 files changed, 731 insertions(+), 603 deletions(-) delete mode 100644 app/Model/Authentication.php create mode 100644 app/Validator/AuthValidator.php create mode 100644 app/Validator/CategoryValidator.php create mode 100644 app/Validator/CommentValidator.php create mode 100644 app/Validator/CurrencyValidator.php create mode 100644 app/Validator/CustomFilterValidator.php create mode 100644 app/Validator/GroupValidator.php create mode 100644 app/Validator/LinkValidator.php create mode 100644 tests/units/Validator/CommentValidatorTest.php create mode 100644 tests/units/Validator/CustomFilterValidatorTest.php create mode 100644 tests/units/Validator/GroupValidatorTest.php create mode 100644 tests/units/Validator/LinkValidatorTest.php (limited to 'app/Validator') diff --git a/app/Api/Category.php b/app/Api/Category.php index 458eaef6..fbd61c56 100644 --- a/app/Api/Category.php +++ b/app/Api/Category.php @@ -32,7 +32,7 @@ class Category extends \Kanboard\Core\Base 'name' => $name, ); - list($valid, ) = $this->category->validateCreation($values); + list($valid, ) = $this->categoryValidator->validateCreation($values); return $valid ? $this->category->create($values) : false; } @@ -43,7 +43,7 @@ class Category extends \Kanboard\Core\Base 'name' => $name, ); - list($valid, ) = $this->category->validateModification($values); + list($valid, ) = $this->categoryValidator->validateModification($values); return $valid && $this->category->update($values); } } diff --git a/app/Api/Comment.php b/app/Api/Comment.php index 4c4027c0..1fc1c708 100644 --- a/app/Api/Comment.php +++ b/app/Api/Comment.php @@ -34,7 +34,7 @@ class Comment extends \Kanboard\Core\Base 'reference' => $reference, ); - list($valid, ) = $this->comment->validateCreation($values); + list($valid, ) = $this->commentValidator->validateCreation($values); return $valid ? $this->comment->create($values) : false; } @@ -46,7 +46,7 @@ class Comment extends \Kanboard\Core\Base 'comment' => $content, ); - list($valid, ) = $this->comment->validateModification($values); + list($valid, ) = $this->commentValidator->validateModification($values); return $valid && $this->comment->update($values); } } diff --git a/app/Api/Link.php b/app/Api/Link.php index d4df18fe..23a9916d 100644 --- a/app/Api/Link.php +++ b/app/Api/Link.php @@ -72,7 +72,7 @@ class Link extends \Kanboard\Core\Base 'opposite_label' => $opposite_label, ); - list($valid, ) = $this->link->validateCreation($values); + list($valid, ) = $this->linkValidator->validateCreation($values); return $valid ? $this->link->create($label, $opposite_label) : false; } @@ -93,7 +93,7 @@ class Link extends \Kanboard\Core\Base 'label' => $label, ); - list($valid, ) = $this->link->validateModification($values); + list($valid, ) = $this->linkValidator->validateModification($values); return $valid && $this->link->update($values); } diff --git a/app/Controller/Auth.php b/app/Controller/Auth.php index 07e66070..5284e126 100644 --- a/app/Controller/Auth.php +++ b/app/Controller/Auth.php @@ -39,7 +39,7 @@ class Auth extends Base { $values = $this->request->getValues(); $this->sessionStorage->hasRememberMe = ! empty($values['remember_me']); - list($valid, $errors) = $this->authentication->validateForm($values); + list($valid, $errors) = $this->authValidator->validateForm($values); if ($valid) { $this->redirectAfterLogin(); diff --git a/app/Controller/Category.php b/app/Controller/Category.php index 9864348c..a0af4139 100644 --- a/app/Controller/Category.php +++ b/app/Controller/Category.php @@ -57,7 +57,7 @@ class Category extends Base $project = $this->getProject(); $values = $this->request->getValues(); - list($valid, $errors) = $this->category->validateCreation($values); + list($valid, $errors) = $this->categoryValidator->validateCreation($values); if ($valid) { if ($this->category->create($values)) { @@ -99,7 +99,7 @@ class Category extends Base $project = $this->getProject(); $values = $this->request->getValues(); - list($valid, $errors) = $this->category->validateModification($values); + list($valid, $errors) = $this->categoryValidator->validateModification($values); if ($valid) { if ($this->category->update($values)) { diff --git a/app/Controller/Comment.php b/app/Controller/Comment.php index 54339e48..a608dd1c 100644 --- a/app/Controller/Comment.php +++ b/app/Controller/Comment.php @@ -78,7 +78,7 @@ class Comment extends Base $values = $this->request->getValues(); $ajax = $this->request->isAjax() || $this->request->getIntegerParam('ajax'); - list($valid, $errors) = $this->comment->validateCreation($values); + list($valid, $errors) = $this->commentValidator->validateCreation($values); if ($valid) { if ($this->comment->create($values)) { @@ -127,7 +127,7 @@ class Comment extends Base $comment = $this->getComment(); $values = $this->request->getValues(); - list($valid, $errors) = $this->comment->validateModification($values); + list($valid, $errors) = $this->commentValidator->validateModification($values); if ($valid) { if ($this->comment->update($values)) { diff --git a/app/Controller/Currency.php b/app/Controller/Currency.php index 4c5b8ee8..3d0deeac 100644 --- a/app/Controller/Currency.php +++ b/app/Controller/Currency.php @@ -51,7 +51,7 @@ class Currency extends Base public function create() { $values = $this->request->getValues(); - list($valid, $errors) = $this->currency->validate($values); + list($valid, $errors) = $this->currencyValidator->validateCreation($values); if ($valid) { if ($this->currency->create($values['currency'], $values['rate'])) { diff --git a/app/Controller/Customfilter.php b/app/Controller/Customfilter.php index 12cc8e78..1b43f1d0 100644 --- a/app/Controller/Customfilter.php +++ b/app/Controller/Customfilter.php @@ -42,7 +42,7 @@ class Customfilter extends Base $values = $this->request->getValues(); $values['user_id'] = $this->userSession->getId(); - list($valid, $errors) = $this->customFilter->validateCreation($values); + list($valid, $errors) = $this->customFilterValidator->validateCreation($values); if ($valid) { if ($this->customFilter->create($values)) { @@ -121,7 +121,7 @@ class Customfilter extends Base $values += array('append' => 0); } - list($valid, $errors) = $this->customFilter->validateModification($values); + list($valid, $errors) = $this->customFilterValidator->validateModification($values); if ($valid) { if ($this->customFilter->update($values)) { diff --git a/app/Controller/Group.php b/app/Controller/Group.php index 3c9c4a07..e952c0e5 100644 --- a/app/Controller/Group.php +++ b/app/Controller/Group.php @@ -79,7 +79,7 @@ class Group extends Base public function save() { $values = $this->request->getValues(); - list($valid, $errors) = $this->group->validateCreation($values); + list($valid, $errors) = $this->groupValidator->validateCreation($values); if ($valid) { if ($this->group->create($values['name']) !== false) { @@ -120,7 +120,7 @@ class Group extends Base public function update() { $values = $this->request->getValues(); - list($valid, $errors) = $this->group->validateModification($values); + list($valid, $errors) = $this->groupValidator->validateModification($values); if ($valid) { if ($this->group->update($values) !== false) { diff --git a/app/Controller/Link.php b/app/Controller/Link.php index 2ae57b1a..d52d1f91 100644 --- a/app/Controller/Link.php +++ b/app/Controller/Link.php @@ -67,7 +67,7 @@ class Link extends Base public function save() { $values = $this->request->getValues(); - list($valid, $errors) = $this->link->validateCreation($values); + list($valid, $errors) = $this->linkValidator->validateCreation($values); if ($valid) { if ($this->link->create($values['label'], $values['opposite_label']) !== false) { @@ -108,7 +108,7 @@ class Link extends Base public function update() { $values = $this->request->getValues(); - list($valid, $errors) = $this->link->validateModification($values); + list($valid, $errors) = $this->linkValidator->validateModification($values); if ($valid) { if ($this->link->update($values)) { diff --git a/app/Core/Base.php b/app/Core/Base.php index 22f09403..a3a30e60 100644 --- a/app/Core/Base.php +++ b/app/Core/Base.php @@ -54,7 +54,6 @@ use Pimple\Container; * @property \Kanboard\Formatter\GroupAutoCompleteFormatter $groupAutoCompleteFormatter * @property \Kanboard\Model\Action $action * @property \Kanboard\Model\ActionParameter $actionParameter - * @property \Kanboard\Model\Authentication $authentication * @property \Kanboard\Model\Board $board * @property \Kanboard\Model\Category $category * @property \Kanboard\Model\Color $color @@ -114,7 +113,15 @@ use Pimple\Container; * @property \Kanboard\Model\UserMetadata $userMetadata * @property \Kanboard\Model\Webhook $webhook * @property \Kanboard\Validator\ActionValidator $actionValidator + * @property \Kanboard\Validator\AuthValidator $authValidator * @property \Kanboard\Validator\ColumnValidator $columnValidator + * @property \Kanboard\Validator\CategoryValidator $categoryValidator + * @property \Kanboard\Validator\ColumnValidator $columnValidator + * @property \Kanboard\Validator\CommentValidator $commentValidator + * @property \Kanboard\Validator\CurrencyValidator $currencyValidator + * @property \Kanboard\Validator\CustomFilterValidator $customFilterValidator + * @property \Kanboard\Validator\GroupValidator $groupValidator + * @property \Kanboard\Validator\LinkValidator $linkValidator * @property \Kanboard\Validator\PasswordResetValidator $passwordResetValidator * @property \Kanboard\Validator\ProjectValidator $projectValidator * @property \Kanboard\Validator\SubtaskValidator $subtaskValidator diff --git a/app/Model/Authentication.php b/app/Model/Authentication.php deleted file mode 100644 index d10f2bf8..00000000 --- a/app/Model/Authentication.php +++ /dev/null @@ -1,130 +0,0 @@ -$method($values); - - if (! $result) { - break; - } - } - - return array($result, $errors); - } - - /** - * Validate credentials syntax - * - * @access private - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - private function validateFields(array $values) - { - $v = new Validator($values, array( - new Validators\Required('username', t('The username is required')), - new Validators\MaxLength('username', t('The maximum length is %d characters', 50), 50), - new Validators\Required('password', t('The password is required')), - )); - - return array( - $v->execute(), - $v->getErrors(), - ); - } - - /** - * Validate user locking - * - * @access private - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - private function validateLocking(array $values) - { - $result = true; - $errors = array(); - - if ($this->userLocking->isLocked($values['username'])) { - $result = false; - $errors['login'] = t('Your account is locked for %d minutes', BRUTEFORCE_LOCKDOWN_DURATION); - $this->logger->error('Account locked: '.$values['username']); - } - - return array($result, $errors); - } - - /** - * Validate password syntax - * - * @access private - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - private function validateCredentials(array $values) - { - $result = true; - $errors = array(); - - if (! $this->authenticationManager->passwordAuthentication($values['username'], $values['password'])) { - $result = false; - $errors['login'] = t('Bad username or password'); - } - - return array($result, $errors); - } - - /** - * Validate captcha - * - * @access private - * @param array $values Form values - * @return boolean - */ - private function validateCaptcha(array $values) - { - $result = true; - $errors = array(); - - if ($this->userLocking->hasCaptcha($values['username'])) { - if (! isset($this->sessionStorage->captcha)) { - $result = false; - } else { - $builder = new CaptchaBuilder; - $builder->setPhrase($this->sessionStorage->captcha); - $result = $builder->testPhrase(isset($values['captcha']) ? $values['captcha'] : ''); - - if (! $result) { - $errors['login'] = t('Invalid captcha'); - } - } - } - - return array($result, $errors);; - } -} diff --git a/app/Model/Category.php b/app/Model/Category.php index bf40c60a..58cee738 100644 --- a/app/Model/Category.php +++ b/app/Model/Category.php @@ -2,9 +2,6 @@ namespace Kanboard\Model; -use SimpleValidator\Validator; -use SimpleValidator\Validators; - /** * Category model * @@ -212,63 +209,4 @@ class Category extends Base return true; } - - /** - * Validate category creation - * - * @access public - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateCreation(array $values) - { - $rules = array( - new Validators\Required('project_id', t('The project id is required')), - new Validators\Required('name', t('The name is required')), - ); - - $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); - - return array( - $v->execute(), - $v->getErrors() - ); - } - - /** - * Validate category modification - * - * @access public - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateModification(array $values) - { - $rules = array( - new Validators\Required('id', t('The id is required')), - new Validators\Required('name', t('The name is required')), - ); - - $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); - - return array( - $v->execute(), - $v->getErrors() - ); - } - - /** - * Common validation rules - * - * @access private - * @return array - */ - private function commonValidationRules() - { - return array( - new Validators\Integer('id', t('The id must be an integer')), - new Validators\Integer('project_id', t('The project id must be an integer')), - new Validators\MaxLength('name', t('The maximum length is %d characters', 50), 50) - ); - } } diff --git a/app/Model/Comment.php b/app/Model/Comment.php index eb83f8d6..6eb4a1e5 100644 --- a/app/Model/Comment.php +++ b/app/Model/Comment.php @@ -3,8 +3,6 @@ namespace Kanboard\Model; use Kanboard\Event\CommentEvent; -use SimpleValidator\Validator; -use SimpleValidator\Validators; /** * Comment model @@ -152,63 +150,4 @@ class Comment extends Base { return $this->db->table(self::TABLE)->eq('id', $comment_id)->remove(); } - - /** - * Validate comment creation - * - * @access public - * @param array $values Required parameters to save an action - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateCreation(array $values) - { - $rules = array( - new Validators\Required('task_id', t('This value is required')), - ); - - $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); - - return array( - $v->execute(), - $v->getErrors() - ); - } - - /** - * Validate comment modification - * - * @access public - * @param array $values Required parameters to save an action - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateModification(array $values) - { - $rules = array( - new Validators\Required('id', t('This value is required')), - ); - - $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); - - return array( - $v->execute(), - $v->getErrors() - ); - } - - /** - * Common validation rules - * - * @access private - * @return array - */ - private function commonValidationRules() - { - return array( - new Validators\Integer('id', t('This value must be an integer')), - new Validators\Integer('task_id', t('This value must be an integer')), - new Validators\Integer('user_id', t('This value must be an integer')), - new Validators\MaxLength('reference', t('The maximum length is %d characters', 50), 50), - new Validators\Required('comment', t('Comment is required')) - ); - } } diff --git a/app/Model/Currency.php b/app/Model/Currency.php index c1156610..316a0141 100644 --- a/app/Model/Currency.php +++ b/app/Model/Currency.php @@ -2,9 +2,6 @@ namespace Kanboard\Model; -use SimpleValidator\Validator; -use SimpleValidator\Validators; - /** * Currency * @@ -83,24 +80,4 @@ class Currency extends Base { return $this->db->table(self::TABLE)->eq('currency', $currency)->update(array('rate' => $rate)); } - - /** - * Validate - * - * @access public - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validate(array $values) - { - $v = new Validator($values, array( - new Validators\Required('currency', t('Field required')), - new Validators\Required('rate', t('Field required')), - )); - - return array( - $v->execute(), - $v->getErrors() - ); - } } diff --git a/app/Model/CustomFilter.php b/app/Model/CustomFilter.php index 6550b4a7..3a6a1a3a 100644 --- a/app/Model/CustomFilter.php +++ b/app/Model/CustomFilter.php @@ -2,9 +2,6 @@ namespace Kanboard\Model; -use SimpleValidator\Validator; -use SimpleValidator\Validators; - /** * Custom Filter model * @@ -102,63 +99,4 @@ class CustomFilter extends Base { return $this->db->table(self::TABLE)->eq('id', $filter_id)->remove(); } - - /** - * Common validation rules - * - * @access private - * @return array - */ - private function commonValidationRules() - { - return array( - new Validators\Required('project_id', t('Field required')), - new Validators\Required('user_id', t('Field required')), - new Validators\Required('name', t('Field required')), - new Validators\Required('filter', t('Field required')), - new Validators\Integer('user_id', t('This value must be an integer')), - new Validators\Integer('project_id', t('This value must be an integer')), - new Validators\MaxLength('name', t('The maximum length is %d characters', 100), 100), - new Validators\MaxLength('filter', t('The maximum length is %d characters', 100), 100) - ); - } - - /** - * Validate filter creation - * - * @access public - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateCreation(array $values) - { - $v = new Validator($values, $this->commonValidationRules()); - - return array( - $v->execute(), - $v->getErrors() - ); - } - - /** - * Validate filter modification - * - * @access public - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateModification(array $values) - { - $rules = array( - new Validators\Required('id', t('Field required')), - new Validators\Integer('id', t('This value must be an integer')), - ); - - $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); - - return array( - $v->execute(), - $v->getErrors() - ); - } } diff --git a/app/Model/Group.php b/app/Model/Group.php index a086fe9d..ce8c0284 100644 --- a/app/Model/Group.php +++ b/app/Model/Group.php @@ -117,59 +117,4 @@ class Group extends Base { return $this->db->table(self::TABLE)->eq('id', $values['id'])->update($values); } - - /** - * Validate creation - * - * @access public - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateCreation(array $values) - { - $v = new Validator($values, $this->commonValidationRules()); - - return array( - $v->execute(), - $v->getErrors() - ); - } - - /** - * Validate modification - * - * @access public - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateModification(array $values) - { - $rules = array( - new Validators\Required('id', t('The id is required')), - ); - - $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); - - return array( - $v->execute(), - $v->getErrors() - ); - } - - /** - * Common validation rules - * - * @access private - * @return array - */ - private function commonValidationRules() - { - return array( - new Validators\Required('name', t('The name is required')), - new Validators\MaxLength('name', t('The maximum length is %d characters', 100), 100), - new Validators\Unique('name', t('The name must be unique'), $this->db->getConnection(), self::TABLE, 'id'), - new Validators\MaxLength('external_id', t('The maximum length is %d characters', 255), 255), - new Validators\Integer('id', t('This value must be an integer')), - ); - } } diff --git a/app/Model/Link.php b/app/Model/Link.php index 00b6dfc5..7b81a237 100644 --- a/app/Model/Link.php +++ b/app/Model/Link.php @@ -3,8 +3,6 @@ namespace Kanboard\Model; use PDO; -use SimpleValidator\Validator; -use SimpleValidator\Validators; /** * Link model @@ -176,47 +174,4 @@ class Link extends Base $this->db->table(self::TABLE)->eq('opposite_id', $link_id)->update(array('opposite_id' => 0)); return $this->db->table(self::TABLE)->eq('id', $link_id)->remove(); } - - /** - * Validate creation - * - * @access public - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateCreation(array $values) - { - $v = new Validator($values, array( - new Validators\Required('label', t('Field required')), - new Validators\Unique('label', t('This label must be unique'), $this->db->getConnection(), self::TABLE), - new Validators\NotEquals('label', 'opposite_label', t('The labels must be different')), - )); - - return array( - $v->execute(), - $v->getErrors() - ); - } - - /** - * Validate modification - * - * @access public - * @param array $values Form values - * @return array $valid, $errors [0] = Success or not, [1] = List of errors - */ - public function validateModification(array $values) - { - $v = new Validator($values, array( - new Validators\Required('id', t('Field required')), - new Validators\Required('opposite_id', t('Field required')), - new Validators\Required('label', t('Field required')), - new Validators\Unique('label', t('This label must be unique'), $this->db->getConnection(), self::TABLE), - )); - - return array( - $v->execute(), - $v->getErrors() - ); - } } diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php index 61c97a07..a0f8901a 100644 --- a/app/ServiceProvider/ClassProvider.php +++ b/app/ServiceProvider/ClassProvider.php @@ -17,7 +17,6 @@ class ClassProvider implements ServiceProviderInterface 'Model' => array( 'Action', 'ActionParameter', - 'Authentication', 'Board', 'Category', 'Color', @@ -86,7 +85,14 @@ class ClassProvider implements ServiceProviderInterface ), 'Validator' => array( 'ActionValidator', + 'AuthValidator', + 'CategoryValidator', 'ColumnValidator', + 'CommentValidator', + 'CurrencyValidator', + 'CustomFilterValidator', + 'GroupValidator', + 'LinkValidator', 'PasswordResetValidator', 'ProjectValidator', 'SubtaskValidator', diff --git a/app/Validator/AuthValidator.php b/app/Validator/AuthValidator.php new file mode 100644 index 00000000..e77a88c8 --- /dev/null +++ b/app/Validator/AuthValidator.php @@ -0,0 +1,130 @@ +$method($values); + + if (! $result) { + break; + } + } + + return array($result, $errors); + } + + /** + * Validate credentials syntax + * + * @access private + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + private function validateFields(array $values) + { + $v = new Validator($values, array( + new Validators\Required('username', t('The username is required')), + new Validators\MaxLength('username', t('The maximum length is %d characters', 50), 50), + new Validators\Required('password', t('The password is required')), + )); + + return array( + $v->execute(), + $v->getErrors(), + ); + } + + /** + * Validate user locking + * + * @access private + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + private function validateLocking(array $values) + { + $result = true; + $errors = array(); + + if ($this->userLocking->isLocked($values['username'])) { + $result = false; + $errors['login'] = t('Your account is locked for %d minutes', BRUTEFORCE_LOCKDOWN_DURATION); + $this->logger->error('Account locked: '.$values['username']); + } + + return array($result, $errors); + } + + /** + * Validate password syntax + * + * @access private + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + private function validateCredentials(array $values) + { + $result = true; + $errors = array(); + + if (! $this->authenticationManager->passwordAuthentication($values['username'], $values['password'])) { + $result = false; + $errors['login'] = t('Bad username or password'); + } + + return array($result, $errors); + } + + /** + * Validate captcha + * + * @access private + * @param array $values Form values + * @return boolean + */ + private function validateCaptcha(array $values) + { + $result = true; + $errors = array(); + + if ($this->userLocking->hasCaptcha($values['username'])) { + if (! isset($this->sessionStorage->captcha)) { + $result = false; + } else { + $builder = new CaptchaBuilder; + $builder->setPhrase($this->sessionStorage->captcha); + $result = $builder->testPhrase(isset($values['captcha']) ? $values['captcha'] : ''); + + if (! $result) { + $errors['login'] = t('Invalid captcha'); + } + } + } + + return array($result, $errors);; + } +} diff --git a/app/Validator/CategoryValidator.php b/app/Validator/CategoryValidator.php new file mode 100644 index 00000000..715aed66 --- /dev/null +++ b/app/Validator/CategoryValidator.php @@ -0,0 +1,74 @@ +commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate category modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateModification(array $values) + { + $rules = array( + new Validators\Required('id', t('The id is required')), + new Validators\Required('name', t('The name is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Common validation rules + * + * @access private + * @return array + */ + private function commonValidationRules() + { + return array( + new Validators\Integer('id', t('The id must be an integer')), + new Validators\Integer('project_id', t('The project id must be an integer')), + new Validators\MaxLength('name', t('The maximum length is %d characters', 50), 50) + ); + } +} diff --git a/app/Validator/CommentValidator.php b/app/Validator/CommentValidator.php new file mode 100644 index 00000000..4eb54206 --- /dev/null +++ b/app/Validator/CommentValidator.php @@ -0,0 +1,74 @@ +commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate comment modification + * + * @access public + * @param array $values Required parameters to save an action + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateModification(array $values) + { + $rules = array( + new Validators\Required('id', t('This value is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Common validation rules + * + * @access private + * @return array + */ + private function commonValidationRules() + { + return array( + new Validators\Integer('id', t('This value must be an integer')), + new Validators\Integer('task_id', t('This value must be an integer')), + new Validators\Integer('user_id', t('This value must be an integer')), + new Validators\MaxLength('reference', t('The maximum length is %d characters', 50), 50), + new Validators\Required('comment', t('Comment is required')) + ); + } +} diff --git a/app/Validator/CurrencyValidator.php b/app/Validator/CurrencyValidator.php new file mode 100644 index 00000000..a00af738 --- /dev/null +++ b/app/Validator/CurrencyValidator.php @@ -0,0 +1,35 @@ +execute(), + $v->getErrors() + ); + } +} diff --git a/app/Validator/CustomFilterValidator.php b/app/Validator/CustomFilterValidator.php new file mode 100644 index 00000000..07f2a1eb --- /dev/null +++ b/app/Validator/CustomFilterValidator.php @@ -0,0 +1,74 @@ +commonValidationRules()); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate filter modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateModification(array $values) + { + $rules = array( + new Validators\Required('id', t('Field required')), + new Validators\Integer('id', t('This value must be an integer')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } +} diff --git a/app/Validator/GroupValidator.php b/app/Validator/GroupValidator.php new file mode 100644 index 00000000..2226abd3 --- /dev/null +++ b/app/Validator/GroupValidator.php @@ -0,0 +1,71 @@ +commonValidationRules()); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateModification(array $values) + { + $rules = array( + new Validators\Required('id', t('The id is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Common validation rules + * + * @access private + * @return array + */ + private function commonValidationRules() + { + return array( + new Validators\Required('name', t('The name is required')), + new Validators\MaxLength('name', t('The maximum length is %d characters', 100), 100), + new Validators\Unique('name', t('The name must be unique'), $this->db->getConnection(), Group::TABLE, 'id'), + new Validators\MaxLength('external_id', t('The maximum length is %d characters', 255), 255), + new Validators\Integer('id', t('This value must be an integer')), + ); + } +} diff --git a/app/Validator/LinkValidator.php b/app/Validator/LinkValidator.php new file mode 100644 index 00000000..10a826da --- /dev/null +++ b/app/Validator/LinkValidator.php @@ -0,0 +1,59 @@ +db->getConnection(), Link::TABLE), + new Validators\NotEquals('label', 'opposite_label', t('The labels must be different')), + )); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateModification(array $values) + { + $v = new Validator($values, array( + new Validators\Required('id', t('Field required')), + new Validators\Required('opposite_id', t('Field required')), + new Validators\Required('label', t('Field required')), + new Validators\Unique('label', t('This label must be unique'), $this->db->getConnection(), Link::TABLE), + )); + + return array( + $v->execute(), + $v->getErrors() + ); + } +} diff --git a/tests/units/Model/CommentTest.php b/tests/units/Model/CommentTest.php index 07c39fe3..ec4e7a56 100644 --- a/tests/units/Model/CommentTest.php +++ b/tests/units/Model/CommentTest.php @@ -90,53 +90,4 @@ class CommentTest extends Base $this->assertFalse($c->remove(1)); $this->assertFalse($c->remove(1111)); } - - public function testValidateCreation() - { - $c = new Comment($this->container); - - $result = $c->validateCreation(array('user_id' => 1, 'task_id' => 1, 'comment' => 'bla')); - $this->assertTrue($result[0]); - - $result = $c->validateCreation(array('user_id' => 1, 'task_id' => 1, 'comment' => '')); - $this->assertFalse($result[0]); - - $result = $c->validateCreation(array('user_id' => 1, 'task_id' => 'a', 'comment' => 'bla')); - $this->assertFalse($result[0]); - - $result = $c->validateCreation(array('user_id' => 'b', 'task_id' => 1, 'comment' => 'bla')); - $this->assertFalse($result[0]); - - $result = $c->validateCreation(array('user_id' => 1, 'comment' => 'bla')); - $this->assertFalse($result[0]); - - $result = $c->validateCreation(array('task_id' => 1, 'comment' => 'bla')); - $this->assertTrue($result[0]); - - $result = $c->validateCreation(array('comment' => 'bla')); - $this->assertFalse($result[0]); - - $result = $c->validateCreation(array()); - $this->assertFalse($result[0]); - } - - public function testValidateModification() - { - $c = new Comment($this->container); - - $result = $c->validateModification(array('id' => 1, 'comment' => 'bla')); - $this->assertTrue($result[0]); - - $result = $c->validateModification(array('id' => 1, 'comment' => '')); - $this->assertFalse($result[0]); - - $result = $c->validateModification(array('comment' => 'bla')); - $this->assertFalse($result[0]); - - $result = $c->validateModification(array('id' => 'b', 'comment' => 'bla')); - $this->assertFalse($result[0]); - - $result = $c->validateModification(array()); - $this->assertFalse($result[0]); - } } diff --git a/tests/units/Model/CustomFilterTest.php b/tests/units/Model/CustomFilterTest.php index 190da899..a73bc401 100644 --- a/tests/units/Model/CustomFilterTest.php +++ b/tests/units/Model/CustomFilterTest.php @@ -52,34 +52,6 @@ class CustomFilterTest extends Base $this->assertEquals(1, $filter['is_shared']); } - public function testValidation() - { - $cf = new CustomFilter($this->container); - - // Validate creation - $r = $cf->validateCreation(array('filter' => 'test', 'name' => 'test', 'user_id' => 1, 'project_id' => 1, 'is_shared' => 0)); - $this->assertTrue($r[0]); - - $r = $cf->validateCreation(array('filter' => str_repeat('a', 101), 'name' => 'test', 'user_id' => 1, 'project_id' => 1, 'is_shared' => 0)); - $this->assertFalse($r[0]); - - $r = $cf->validateCreation(array('name' => 'test', 'user_id' => 1, 'project_id' => 1, 'is_shared' => 0)); - $this->assertFalse($r[0]); - - // Validate modification - $r = $cf->validateModification(array('id' => 1, 'filter' => 'test', 'name' => 'test', 'user_id' => 1, 'project_id' => 1, 'is_shared' => 0)); - $this->assertTrue($r[0]); - - $r = $cf->validateModification(array('filter' => 'test', 'name' => 'test', 'user_id' => 1, 'project_id' => 1, 'is_shared' => 0)); - $this->assertFalse($r[0]); - - $r = $cf->validateModification(array('id' => 1, 'filter' => str_repeat('a', 101), 'name' => 'test', 'user_id' => 1, 'project_id' => 1, 'is_shared' => 0)); - $this->assertFalse($r[0]); - - $r = $cf->validateModification(array('id' => 1, 'name' => 'test', 'user_id' => 1, 'project_id' => 1, 'is_shared' => 0)); - $this->assertFalse($r[0]); - } - public function testGetAll() { $u = new User($this->container); diff --git a/tests/units/Model/GroupTest.php b/tests/units/Model/GroupTest.php index 2cbb254a..36b47dc5 100644 --- a/tests/units/Model/GroupTest.php +++ b/tests/units/Model/GroupTest.php @@ -57,26 +57,4 @@ class GroupTest extends Base $this->assertTrue($groupModel->remove(1)); $this->assertEmpty($groupModel->getById(1)); } - - public function testValidateCreation() - { - $groupModel = new Group($this->container); - - $result = $groupModel->validateCreation(array('name' => 'Test')); - $this->assertTrue($result[0]); - - $result = $groupModel->validateCreation(array('name' => '')); - $this->assertFalse($result[0]); - } - - public function testValidateModification() - { - $groupModel = new Group($this->container); - - $result = $groupModel->validateModification(array('name' => 'Test', 'id' => 1)); - $this->assertTrue($result[0]); - - $result = $groupModel->validateModification(array('name' => 'Test')); - $this->assertFalse($result[0]); - } } diff --git a/tests/units/Model/LinkTest.php b/tests/units/Model/LinkTest.php index de9d843a..b102646d 100644 --- a/tests/units/Model/LinkTest.php +++ b/tests/units/Model/LinkTest.php @@ -124,50 +124,4 @@ class LinkTest extends Base $this->assertArrayNotHasKey(0, $links); $this->assertEquals('relates to', $links[1]); } - - public function testValidateCreation() - { - $l = new Link($this->container); - - $r = $l->validateCreation(array('label' => 'a')); - $this->assertTrue($r[0]); - - $r = $l->validateCreation(array('label' => 'a', 'opposite_label' => 'b')); - $this->assertTrue($r[0]); - - $r = $l->validateCreation(array('label' => 'relates to')); - $this->assertFalse($r[0]); - - $r = $l->validateCreation(array('label' => 'a', 'opposite_label' => 'a')); - $this->assertFalse($r[0]); - - $r = $l->validateCreation(array('label' => '')); - $this->assertFalse($r[0]); - } - - public function testValidateModification() - { - $l = new Link($this->container); - - $r = $l->validateModification(array('id' => 20, 'label' => 'a', 'opposite_id' => 0)); - $this->assertTrue($r[0]); - - $r = $l->validateModification(array('id' => 20, 'label' => 'a', 'opposite_id' => '1')); - $this->assertTrue($r[0]); - - $r = $l->validateModification(array('id' => 20, 'label' => 'relates to', 'opposite_id' => '1')); - $this->assertFalse($r[0]); - - $r = $l->validateModification(array('id' => 20, 'label' => '', 'opposite_id' => '1')); - $this->assertFalse($r[0]); - - $r = $l->validateModification(array('label' => '', 'opposite_id' => '1')); - $this->assertFalse($r[0]); - - $r = $l->validateModification(array('id' => 20, 'opposite_id' => '1')); - $this->assertFalse($r[0]); - - $r = $l->validateModification(array('label' => 'test')); - $this->assertFalse($r[0]); - } } diff --git a/tests/units/Validator/CommentValidatorTest.php b/tests/units/Validator/CommentValidatorTest.php new file mode 100644 index 00000000..378fe924 --- /dev/null +++ b/tests/units/Validator/CommentValidatorTest.php @@ -0,0 +1,57 @@ +container); + + $result = $validator->validateCreation(array('user_id' => 1, 'task_id' => 1, 'comment' => 'bla')); + $this->assertTrue($result[0]); + + $result = $validator->validateCreation(array('user_id' => 1, 'task_id' => 1, 'comment' => '')); + $this->assertFalse($result[0]); + + $result = $validator->validateCreation(array('user_id' => 1, 'task_id' => 'a', 'comment' => 'bla')); + $this->assertFalse($result[0]); + + $result = $validator->validateCreation(array('user_id' => 'b', 'task_id' => 1, 'comment' => 'bla')); + $this->assertFalse($result[0]); + + $result = $validator->validateCreation(array('user_id' => 1, 'comment' => 'bla')); + $this->assertFalse($result[0]); + + $result = $validator->validateCreation(array('task_id' => 1, 'comment' => 'bla')); + $this->assertTrue($result[0]); + + $result = $validator->validateCreation(array('comment' => 'bla')); + $this->assertFalse($result[0]); + + $result = $validator->validateCreation(array()); + $this->assertFalse($result[0]); + } + + public function testValidateModification() + { + $validator = new CommentValidator($this->container); + + $result = $validator->validateModification(array('id' => 1, 'comment' => 'bla')); + $this->assertTrue($result[0]); + + $result = $validator->validateModification(array('id' => 1, 'comment' => '')); + $this->assertFalse($result[0]); + + $result = $validator->validateModification(array('comment' => 'bla')); + $this->assertFalse($result[0]); + + $result = $validator->validateModification(array('id' => 'b', 'comment' => 'bla')); + $this->assertFalse($result[0]); + + $result = $validator->validateModification(array()); + $this->assertFalse($result[0]); + } +} diff --git a/tests/units/Validator/CustomFilterValidatorTest.php b/tests/units/Validator/CustomFilterValidatorTest.php new file mode 100644 index 00000000..3b70e42c --- /dev/null +++ b/tests/units/Validator/CustomFilterValidatorTest.php @@ -0,0 +1,40 @@ +container); + + // Validate creation + $r = $validator->validateCreation(array('filter' => 'test', 'name' => 'test', 'user_id' => 1, 'project_id' => 1, 'is_shared' => 0)); + $this->assertTrue($r[0]); + + $r = $validator->validateCreation(array('filter' => str_repeat('a', 101), 'name' => 'test', 'user_id' => 1, 'project_id' => 1, 'is_shared' => 0)); + $this->assertFalse($r[0]); + + $r = $validator->validateCreation(array('name' => 'test', 'user_id' => 1, 'project_id' => 1, 'is_shared' => 0)); + $this->assertFalse($r[0]); + } + + public function testValidateModification() + { + $validator = new CustomFilterValidator($this->container); + + $r = $validator->validateModification(array('id' => 1, 'filter' => 'test', 'name' => 'test', 'user_id' => 1, 'project_id' => 1, 'is_shared' => 0)); + $this->assertTrue($r[0]); + + $r = $validator->validateModification(array('filter' => 'test', 'name' => 'test', 'user_id' => 1, 'project_id' => 1, 'is_shared' => 0)); + $this->assertFalse($r[0]); + + $r = $validator->validateModification(array('id' => 1, 'filter' => str_repeat('a', 101), 'name' => 'test', 'user_id' => 1, 'project_id' => 1, 'is_shared' => 0)); + $this->assertFalse($r[0]); + + $r = $validator->validateModification(array('id' => 1, 'name' => 'test', 'user_id' => 1, 'project_id' => 1, 'is_shared' => 0)); + $this->assertFalse($r[0]); + } +} diff --git a/tests/units/Validator/GroupValidatorTest.php b/tests/units/Validator/GroupValidatorTest.php new file mode 100644 index 00000000..879f99ce --- /dev/null +++ b/tests/units/Validator/GroupValidatorTest.php @@ -0,0 +1,30 @@ +container); + + $result = $validator->validateCreation(array('name' => 'Test')); + $this->assertTrue($result[0]); + + $result = $validator->validateCreation(array('name' => '')); + $this->assertFalse($result[0]); + } + + public function testValidateModification() + { + $validator = new GroupValidator($this->container); + + $result = $validator->validateModification(array('name' => 'Test', 'id' => 1)); + $this->assertTrue($result[0]); + + $result = $validator->validateModification(array('name' => 'Test')); + $this->assertFalse($result[0]); + } +} diff --git a/tests/units/Validator/LinkValidatorTest.php b/tests/units/Validator/LinkValidatorTest.php new file mode 100644 index 00000000..8b7b182c --- /dev/null +++ b/tests/units/Validator/LinkValidatorTest.php @@ -0,0 +1,54 @@ +container); + + $r = $validator->validateCreation(array('label' => 'a')); + $this->assertTrue($r[0]); + + $r = $validator->validateCreation(array('label' => 'a', 'opposite_label' => 'b')); + $this->assertTrue($r[0]); + + $r = $validator->validateCreation(array('label' => 'relates to')); + $this->assertFalse($r[0]); + + $r = $validator->validateCreation(array('label' => 'a', 'opposite_label' => 'a')); + $this->assertFalse($r[0]); + + $r = $validator->validateCreation(array('label' => '')); + $this->assertFalse($r[0]); + } + + public function testValidateModification() + { + $validator = new LinkValidator($this->container); + + $r = $validator->validateModification(array('id' => 20, 'label' => 'a', 'opposite_id' => 0)); + $this->assertTrue($r[0]); + + $r = $validator->validateModification(array('id' => 20, 'label' => 'a', 'opposite_id' => '1')); + $this->assertTrue($r[0]); + + $r = $validator->validateModification(array('id' => 20, 'label' => 'relates to', 'opposite_id' => '1')); + $this->assertFalse($r[0]); + + $r = $validator->validateModification(array('id' => 20, 'label' => '', 'opposite_id' => '1')); + $this->assertFalse($r[0]); + + $r = $validator->validateModification(array('label' => '', 'opposite_id' => '1')); + $this->assertFalse($r[0]); + + $r = $validator->validateModification(array('id' => 20, 'opposite_id' => '1')); + $this->assertFalse($r[0]); + + $r = $validator->validateModification(array('label' => 'test')); + $this->assertFalse($r[0]); + } +} -- cgit v1.2.3 From a8f404421f3e404ccd285da861a8cb25f3642f31 Mon Sep 17 00:00:00 2001 From: Frederic Guillot Date: Thu, 14 Jan 2016 20:40:31 -0500 Subject: Fix bug: unable to set currency rate with Postgres --- ChangeLog | 1 + app/Model/Currency.php | 4 +-- app/Validator/CurrencyValidator.php | 1 + tests/units/Model/CurrencyTest.php | 46 +++++++++++++++++++++++++ tests/units/Validator/CurrencyValidatorTest.php | 27 +++++++++++++++ 5 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 tests/units/Model/CurrencyTest.php create mode 100644 tests/units/Validator/CurrencyValidatorTest.php (limited to 'app/Validator') diff --git a/ChangeLog b/ChangeLog index e068703f..e03819e5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -15,6 +15,7 @@ Bug fixes: * Automatic action listeners were using the same instance * Fix wrong link for category in task footer +* Unable to set currency rate with Postgres database Version 1.0.23 -------------- diff --git a/app/Model/Currency.php b/app/Model/Currency.php index 316a0141..4351d2aa 100644 --- a/app/Model/Currency.php +++ b/app/Model/Currency.php @@ -42,7 +42,7 @@ class Currency extends Base $reference = $this->config->get('application_currency', 'USD'); if ($reference !== $currency) { - $rates = $rates === null ? $this->db->hashtable(self::TABLE)->getAll('currency', 'rate') : array(); + $rates = $rates === null ? $this->db->hashtable(self::TABLE)->getAll('currency', 'rate') : $rates; $rate = isset($rates[$currency]) ? $rates[$currency] : 1; return $rate * $price; @@ -65,7 +65,7 @@ class Currency extends Base return $this->update($currency, $rate); } - return $this->persist(self::TABLE, compact('currency', 'rate')); + return $this->db->table(self::TABLE)->insert(array('currency' => $currency, 'rate' => $rate)); } /** diff --git a/app/Validator/CurrencyValidator.php b/app/Validator/CurrencyValidator.php index a00af738..ee191523 100644 --- a/app/Validator/CurrencyValidator.php +++ b/app/Validator/CurrencyValidator.php @@ -25,6 +25,7 @@ class CurrencyValidator extends Base $v = new Validator($values, array( new Validators\Required('currency', t('Field required')), new Validators\Required('rate', t('Field required')), + new Validators\Numeric('rate', t('This value must be numeric')), )); return array( diff --git a/tests/units/Model/CurrencyTest.php b/tests/units/Model/CurrencyTest.php new file mode 100644 index 00000000..ee11c0fb --- /dev/null +++ b/tests/units/Model/CurrencyTest.php @@ -0,0 +1,46 @@ +container); + $currencies = $currencyModel->getAll(); + $this->assertCount(0, $currencies); + + $this->assertNotFalse($currencyModel->create('USD', 9.9)); + $currencies = $currencyModel->getAll(); + $this->assertCount(1, $currencies); + $this->assertEquals('USD', $currencies[0]['currency']); + $this->assertEquals(9.9, $currencies[0]['rate']); + } + + public function testCreate() + { + $currencyModel = new Currency($this->container); + $this->assertNotFalse($currencyModel->create('EUR', 1.2)); + $this->assertNotFalse($currencyModel->create('EUR', 1.5)); + } + + public function testUpdate() + { + $currencyModel = new Currency($this->container); + $this->assertNotFalse($currencyModel->create('EUR', 1.1)); + $this->assertNotFalse($currencyModel->update('EUR', 2.2)); + } + + public function testGetPrice() + { + $currencyModel = new Currency($this->container); + + $this->assertEquals(123, $currencyModel->getPrice('USD', 123)); + + $this->assertNotFalse($currencyModel->create('EUR', 0.5)); + $this->assertEquals(50.0, $currencyModel->getPrice('EUR', 100)); + $this->assertEquals(50.0, $currencyModel->getPrice('EUR', 100)); // test with cached result + } +} diff --git a/tests/units/Validator/CurrencyValidatorTest.php b/tests/units/Validator/CurrencyValidatorTest.php new file mode 100644 index 00000000..39c06d44 --- /dev/null +++ b/tests/units/Validator/CurrencyValidatorTest.php @@ -0,0 +1,27 @@ +container); + $result = $validator->validateCreation(array()); + $this->assertFalse($result[0]); + + $result = $validator->validateCreation(array('currency' => 'EUR')); + $this->assertFalse($result[0]); + + $result = $validator->validateCreation(array('rate' => 1.9)); + $this->assertFalse($result[0]); + + $result = $validator->validateCreation(array('currency' => 'EUR', 'rate' => 'foobar')); + $this->assertFalse($result[0]); + + $result = $validator->validateCreation(array('currency' => 'EUR', 'rate' => 1.25)); + $this->assertTrue($result[0]); + } +} -- cgit v1.2.3 From 27b9b7a727de7a9608d85bce6ca94e81bbdf7ffb Mon Sep 17 00:00:00 2001 From: Frederic Guillot Date: Sat, 16 Jan 2016 22:29:29 -0500 Subject: Minor code improvements --- app/Action/TaskClose.php | 2 -- app/Core/Http/Route.php | 1 - app/Core/Ldap/User.php | 2 +- app/Model/Config.php | 1 - app/Model/Group.php | 3 --- app/Model/ProjectUserRole.php | 2 +- app/ServiceProvider/EventDispatcherProvider.php | 1 - app/Validator/AuthValidator.php | 29 ++++++++----------------- 8 files changed, 11 insertions(+), 30 deletions(-) (limited to 'app/Validator') diff --git a/app/Action/TaskClose.php b/app/Action/TaskClose.php index b8c5e175..cf91e83e 100644 --- a/app/Action/TaskClose.php +++ b/app/Action/TaskClose.php @@ -2,8 +2,6 @@ namespace Kanboard\Action; -use Kanboard\Model\Task; - /** * Close automatically a task * diff --git a/app/Core/Http/Route.php b/app/Core/Http/Route.php index ed831467..7836146d 100644 --- a/app/Core/Http/Route.php +++ b/app/Core/Http/Route.php @@ -2,7 +2,6 @@ namespace Kanboard\Core\Http; -use RuntimeException; use Kanboard\Core\Base; /** diff --git a/app/Core/Ldap/User.php b/app/Core/Ldap/User.php index 04c4cc7e..d36d6f34 100644 --- a/app/Core/Ldap/User.php +++ b/app/Core/Ldap/User.php @@ -40,7 +40,7 @@ class User * @access public * @param Client $client * @param string $username - * @return array + * @return LdapUserProvider */ public static function getUser(Client $client, $username) { diff --git a/app/Model/Config.php b/app/Model/Config.php index d815246f..55999310 100644 --- a/app/Model/Config.php +++ b/app/Model/Config.php @@ -4,7 +4,6 @@ namespace Kanboard\Model; use Kanboard\Core\Translator; use Kanboard\Core\Security\Token; -use Kanboard\Core\Session\SessionManager; /** * Config model diff --git a/app/Model/Group.php b/app/Model/Group.php index ce8c0284..67899503 100644 --- a/app/Model/Group.php +++ b/app/Model/Group.php @@ -2,9 +2,6 @@ namespace Kanboard\Model; -use SimpleValidator\Validator; -use SimpleValidator\Validators; - /** * Group Model * diff --git a/app/Model/ProjectUserRole.php b/app/Model/ProjectUserRole.php index 6b9c23b0..8149a253 100644 --- a/app/Model/ProjectUserRole.php +++ b/app/Model/ProjectUserRole.php @@ -28,7 +28,7 @@ class ProjectUserRole extends Base */ public function getActiveProjectsByUser($user_id) { - return $this->getProjectsByUser($user_id, $status = array(Project::ACTIVE)); + return $this->getProjectsByUser($user_id, array(Project::ACTIVE)); } /** diff --git a/app/ServiceProvider/EventDispatcherProvider.php b/app/ServiceProvider/EventDispatcherProvider.php index 8280a138..880caa41 100644 --- a/app/ServiceProvider/EventDispatcherProvider.php +++ b/app/ServiceProvider/EventDispatcherProvider.php @@ -11,7 +11,6 @@ use Kanboard\Subscriber\NotificationSubscriber; use Kanboard\Subscriber\ProjectDailySummarySubscriber; use Kanboard\Subscriber\ProjectModificationDateSubscriber; use Kanboard\Subscriber\SubtaskTimeTrackingSubscriber; -use Kanboard\Subscriber\TaskMovedDateSubscriber; use Kanboard\Subscriber\TransitionSubscriber; use Kanboard\Subscriber\RecurringTaskSubscriber; diff --git a/app/Validator/AuthValidator.php b/app/Validator/AuthValidator.php index e77a88c8..36ccdff0 100644 --- a/app/Validator/AuthValidator.php +++ b/app/Validator/AuthValidator.php @@ -23,28 +23,17 @@ class AuthValidator extends Base */ public function validateForm(array $values) { - $result = false; - $errors = array(); - - foreach (array('validateFields', 'validateLocking', 'validateCaptcha', 'validateCredentials') as $method) { - list($result, $errors) = $this->$method($values); - - if (! $result) { - break; - } - } - - return array($result, $errors); + return $this->executeValidators(array('validateFields', 'validateLocking', 'validateCaptcha', 'validateCredentials'), $values); } /** * Validate credentials syntax * - * @access private + * @access protected * @param array $values Form values * @return array $valid, $errors [0] = Success or not, [1] = List of errors */ - private function validateFields(array $values) + protected function validateFields(array $values) { $v = new Validator($values, array( new Validators\Required('username', t('The username is required')), @@ -61,11 +50,11 @@ class AuthValidator extends Base /** * Validate user locking * - * @access private + * @access protected * @param array $values Form values * @return array $valid, $errors [0] = Success or not, [1] = List of errors */ - private function validateLocking(array $values) + protected function validateLocking(array $values) { $result = true; $errors = array(); @@ -82,11 +71,11 @@ class AuthValidator extends Base /** * Validate password syntax * - * @access private + * @access protected * @param array $values Form values * @return array $valid, $errors [0] = Success or not, [1] = List of errors */ - private function validateCredentials(array $values) + protected function validateCredentials(array $values) { $result = true; $errors = array(); @@ -102,11 +91,11 @@ class AuthValidator extends Base /** * Validate captcha * - * @access private + * @access protected * @param array $values Form values * @return boolean */ - private function validateCaptcha(array $values) + protected function validateCaptcha(array $values) { $result = true; $errors = array(); -- cgit v1.2.3 From 051bf1c9dbb5733242c7657d6d507389206b33ee Mon Sep 17 00:00:00 2001 From: Frederic Guillot Date: Sun, 24 Jan 2016 20:38:39 -0500 Subject: Add configurable task priority --- ChangeLog | 1 + app/Controller/ProjectEdit.php | 10 +++++++++ app/Controller/Taskcreation.php | 1 + app/Controller/Taskmodification.php | 2 ++ app/Helper/Task.php | 33 +++++++++++++++++++++++++++- app/Locale/bs_BA/translations.php | 9 ++++++++ app/Locale/cs_CZ/translations.php | 9 ++++++++ app/Locale/da_DK/translations.php | 9 ++++++++ app/Locale/de_DE/translations.php | 9 ++++++++ app/Locale/es_ES/translations.php | 9 ++++++++ app/Locale/fi_FI/translations.php | 9 ++++++++ app/Locale/fr_FR/translations.php | 9 ++++++++ app/Locale/hu_HU/translations.php | 9 ++++++++ app/Locale/id_ID/translations.php | 9 ++++++++ app/Locale/it_IT/translations.php | 9 ++++++++ app/Locale/ja_JP/translations.php | 9 ++++++++ app/Locale/my_MY/translations.php | 9 ++++++++ app/Locale/nb_NO/translations.php | 9 ++++++++ app/Locale/nl_NL/translations.php | 9 ++++++++ app/Locale/pl_PL/translations.php | 9 ++++++++ app/Locale/pt_BR/translations.php | 9 ++++++++ app/Locale/pt_PT/translations.php | 9 ++++++++ app/Locale/ru_RU/translations.php | 9 ++++++++ app/Locale/sr_Latn_RS/translations.php | 9 ++++++++ app/Locale/sv_SE/translations.php | 9 ++++++++ app/Locale/th_TH/translations.php | 9 ++++++++ app/Locale/tr_TR/translations.php | 9 ++++++++ app/Locale/zh_CN/translations.php | 9 ++++++++ app/Model/Project.php | 4 ++++ app/Model/TaskFinder.php | 2 ++ app/Model/TaskModification.php | 2 +- app/Schema/Mysql.php | 10 ++++++++- app/Schema/Postgres.php | 10 ++++++++- app/Schema/Sqlite.php | 10 ++++++++- app/ServiceProvider/RouteProvider.php | 1 + app/Template/board/task_footer.php | 2 ++ app/Template/board/task_private.php | 1 + app/Template/board/task_public.php | 1 + app/Template/project_edit/dates.php | 1 + app/Template/project_edit/description.php | 1 + app/Template/project_edit/general.php | 1 + app/Template/project_edit/task_priority.php | 29 ++++++++++++++++++++++++ app/Template/task/details.php | 3 +++ app/Template/task_creation/form.php | 20 +++++++++-------- app/Template/task_modification/edit_task.php | 12 +++++----- app/Validator/ProjectValidator.php | 3 +++ app/Validator/TaskValidator.php | 1 + tests/units/Helper/TaskHelperTest.php | 32 +++++++++++++++++++++++++++ tests/units/Model/ProjectTest.php | 20 +++++++++++++++++ 49 files changed, 401 insertions(+), 19 deletions(-) create mode 100644 app/Template/project_edit/task_priority.php create mode 100644 tests/units/Helper/TaskHelperTest.php (limited to 'app/Validator') diff --git a/ChangeLog b/ChangeLog index 6c46bb00..ec5f36b4 100644 --- a/ChangeLog +++ b/ChangeLog @@ -4,6 +4,7 @@ Version 1.0.25 (unreleased) New features: * Add project owner (Directly Responsible Individual) +* Add configurable task priority Version 1.0.24 -------------- diff --git a/app/Controller/ProjectEdit.php b/app/Controller/ProjectEdit.php index 3b0a3da3..0dfc7de3 100644 --- a/app/Controller/ProjectEdit.php +++ b/app/Controller/ProjectEdit.php @@ -40,6 +40,16 @@ class ProjectEdit extends Base $this->renderView('project_edit/description', $values, $errors); } + /** + * Change task priority + * + * @access public + */ + public function priority(array $values = array(), array $errors = array()) + { + $this->renderView('project_edit/task_priority', $values, $errors); + } + /** * Validate and update a project * diff --git a/app/Controller/Taskcreation.php b/app/Controller/Taskcreation.php index 4d74fac6..49ccea7f 100644 --- a/app/Controller/Taskcreation.php +++ b/app/Controller/Taskcreation.php @@ -32,6 +32,7 @@ class Taskcreation extends Base } $this->response->html($this->template->$method('task_creation/form', array( + 'project' => $project, 'ajax' => $this->request->isAjax(), 'errors' => $errors, 'values' => $values + array('project_id' => $project['id']), diff --git a/app/Controller/Taskmodification.php b/app/Controller/Taskmodification.php index 81cf430f..2c97970b 100644 --- a/app/Controller/Taskmodification.php +++ b/app/Controller/Taskmodification.php @@ -98,6 +98,7 @@ class Taskmodification extends Base public function edit(array $values = array(), array $errors = array()) { $task = $this->getTask(); + $project = $this->project->getById($task['project_id']); $ajax = $this->request->isAjax(); if (empty($values)) { @@ -107,6 +108,7 @@ class Taskmodification extends Base $this->dateParser->format($values, array('date_due')); $params = array( + 'project' => $project, 'values' => $values, 'errors' => $errors, 'task' => $task, diff --git a/app/Helper/Task.php b/app/Helper/Task.php index 1405a167..500b8a89 100644 --- a/app/Helper/Task.php +++ b/app/Helper/Task.php @@ -2,13 +2,15 @@ namespace Kanboard\Helper; +use Kanboard\Core\Base; + /** * Task helpers * * @package helper * @author Frederic Guillot */ -class Task extends \Kanboard\Core\Base +class Task extends Base { public function getColors() { @@ -34,4 +36,33 @@ class Task extends \Kanboard\Core\Base { return $this->taskPermission->canRemoveTask($task); } + + public function selectPriority(array $project, array $values) + { + $html = ''; + + if ($project['priority_end'] > $project['priority_start']) { + $range = range($project['priority_start'], $project['priority_end']); + $options = array_combine($range, $range); + $values += array('priority' => $project['priority_default']); + + $html .= $this->helper->form->label(t('Priority'), 'priority'); + $html .= $this->helper->form->select('priority', $options, $values, array(), array('tabindex="7"')); + } + + return $html; + } + + public function formatPriority(array $project, array $task) + { + $html = ''; + + if ($project['priority_end'] > $project['priority_start']) { + $html .= ''; + $html .= $task['priority'] >= 0 ? 'P'.$task['priority'] : '-P'.abs($task['priority']); + $html .= ''; + } + + return $html; + } } diff --git a/app/Locale/bs_BA/translations.php b/app/Locale/bs_BA/translations.php index e1af1c08..90ab1296 100644 --- a/app/Locale/bs_BA/translations.php +++ b/app/Locale/bs_BA/translations.php @@ -1109,4 +1109,13 @@ return array( // 'Those dates are useful for the project Gantt chart.' => '', // 'Private projects do not have users and groups management.' => '', // 'There is no project member.' => '', + // 'Priority' => '', + // 'Task priority' => '', + // 'General' => '', + // 'Dates' => '', + // 'Default priority' => '', + // 'Lowest priority' => '', + // 'Highest priority' => '', + // 'If you put zero to the low and high priority, this feature will be disabled.' => '', + // 'Priority: %d' => '', ); diff --git a/app/Locale/cs_CZ/translations.php b/app/Locale/cs_CZ/translations.php index b93b7c23..83d88f35 100644 --- a/app/Locale/cs_CZ/translations.php +++ b/app/Locale/cs_CZ/translations.php @@ -1109,4 +1109,13 @@ return array( // 'Those dates are useful for the project Gantt chart.' => '', // 'Private projects do not have users and groups management.' => '', // 'There is no project member.' => '', + // 'Priority' => '', + // 'Task priority' => '', + // 'General' => '', + // 'Dates' => '', + // 'Default priority' => '', + // 'Lowest priority' => '', + // 'Highest priority' => '', + // 'If you put zero to the low and high priority, this feature will be disabled.' => '', + // 'Priority: %d' => '', ); diff --git a/app/Locale/da_DK/translations.php b/app/Locale/da_DK/translations.php index 63850d70..7a82bc1e 100644 --- a/app/Locale/da_DK/translations.php +++ b/app/Locale/da_DK/translations.php @@ -1109,4 +1109,13 @@ return array( // 'Those dates are useful for the project Gantt chart.' => '', // 'Private projects do not have users and groups management.' => '', // 'There is no project member.' => '', + // 'Priority' => '', + // 'Task priority' => '', + // 'General' => '', + // 'Dates' => '', + // 'Default priority' => '', + // 'Lowest priority' => '', + // 'Highest priority' => '', + // 'If you put zero to the low and high priority, this feature will be disabled.' => '', + // 'Priority: %d' => '', ); diff --git a/app/Locale/de_DE/translations.php b/app/Locale/de_DE/translations.php index ef056972..1158dd67 100644 --- a/app/Locale/de_DE/translations.php +++ b/app/Locale/de_DE/translations.php @@ -1109,4 +1109,13 @@ return array( // 'Those dates are useful for the project Gantt chart.' => '', // 'Private projects do not have users and groups management.' => '', // 'There is no project member.' => '', + // 'Priority' => '', + // 'Task priority' => '', + // 'General' => '', + // 'Dates' => '', + // 'Default priority' => '', + // 'Lowest priority' => '', + // 'Highest priority' => '', + // 'If you put zero to the low and high priority, this feature will be disabled.' => '', + // 'Priority: %d' => '', ); diff --git a/app/Locale/es_ES/translations.php b/app/Locale/es_ES/translations.php index 91709476..16c96ec5 100644 --- a/app/Locale/es_ES/translations.php +++ b/app/Locale/es_ES/translations.php @@ -1109,4 +1109,13 @@ return array( // 'Those dates are useful for the project Gantt chart.' => '', // 'Private projects do not have users and groups management.' => '', // 'There is no project member.' => '', + // 'Priority' => '', + // 'Task priority' => '', + // 'General' => '', + // 'Dates' => '', + // 'Default priority' => '', + // 'Lowest priority' => '', + // 'Highest priority' => '', + // 'If you put zero to the low and high priority, this feature will be disabled.' => '', + // 'Priority: %d' => '', ); diff --git a/app/Locale/fi_FI/translations.php b/app/Locale/fi_FI/translations.php index c887e02d..cde825e2 100644 --- a/app/Locale/fi_FI/translations.php +++ b/app/Locale/fi_FI/translations.php @@ -1109,4 +1109,13 @@ return array( // 'Those dates are useful for the project Gantt chart.' => '', // 'Private projects do not have users and groups management.' => '', // 'There is no project member.' => '', + // 'Priority' => '', + // 'Task priority' => '', + // 'General' => '', + // 'Dates' => '', + // 'Default priority' => '', + // 'Lowest priority' => '', + // 'Highest priority' => '', + // 'If you put zero to the low and high priority, this feature will be disabled.' => '', + // 'Priority: %d' => '', ); diff --git a/app/Locale/fr_FR/translations.php b/app/Locale/fr_FR/translations.php index f36ce104..32110e1c 100644 --- a/app/Locale/fr_FR/translations.php +++ b/app/Locale/fr_FR/translations.php @@ -1112,4 +1112,13 @@ return array( 'Those dates are useful for the project Gantt chart.' => 'Ces dates sont utiles pour le diagramme de Gantt des projets.', 'Private projects do not have users and groups management.' => 'Les projets privés n\'ont pas de gestion d\'utilisateurs et de groupes.', 'There is no project member.' => 'Il y a aucun membre du projet.', + 'Priority' => 'Priorité', + 'Task priority' => 'Priorité des tâches', + 'General' => 'Général', + 'Dates' => 'Dates', + 'Default priority' => 'Priorité par défaut', + 'Lowest priority' => 'Priorité basse', + 'Highest priority' => 'Priorité haute', + 'If you put zero to the low and high priority, this feature will be disabled.' => 'Si vous mettez zéro pour la priorité basse et haute, cette fonctionnalité sera désactivée.', + 'Priority: %d' => 'Priorité : %d', ); diff --git a/app/Locale/hu_HU/translations.php b/app/Locale/hu_HU/translations.php index e12a7aad..25d55bb2 100644 --- a/app/Locale/hu_HU/translations.php +++ b/app/Locale/hu_HU/translations.php @@ -1109,4 +1109,13 @@ return array( // 'Those dates are useful for the project Gantt chart.' => '', // 'Private projects do not have users and groups management.' => '', // 'There is no project member.' => '', + // 'Priority' => '', + // 'Task priority' => '', + // 'General' => '', + // 'Dates' => '', + // 'Default priority' => '', + // 'Lowest priority' => '', + // 'Highest priority' => '', + // 'If you put zero to the low and high priority, this feature will be disabled.' => '', + // 'Priority: %d' => '', ); diff --git a/app/Locale/id_ID/translations.php b/app/Locale/id_ID/translations.php index 49d61d58..e3316405 100644 --- a/app/Locale/id_ID/translations.php +++ b/app/Locale/id_ID/translations.php @@ -1109,4 +1109,13 @@ return array( // 'Those dates are useful for the project Gantt chart.' => '', // 'Private projects do not have users and groups management.' => '', // 'There is no project member.' => '', + // 'Priority' => '', + // 'Task priority' => '', + // 'General' => '', + // 'Dates' => '', + // 'Default priority' => '', + // 'Lowest priority' => '', + // 'Highest priority' => '', + // 'If you put zero to the low and high priority, this feature will be disabled.' => '', + // 'Priority: %d' => '', ); diff --git a/app/Locale/it_IT/translations.php b/app/Locale/it_IT/translations.php index 5e2c32a9..1e32213f 100644 --- a/app/Locale/it_IT/translations.php +++ b/app/Locale/it_IT/translations.php @@ -1109,4 +1109,13 @@ return array( // 'Those dates are useful for the project Gantt chart.' => '', // 'Private projects do not have users and groups management.' => '', // 'There is no project member.' => '', + // 'Priority' => '', + // 'Task priority' => '', + // 'General' => '', + // 'Dates' => '', + // 'Default priority' => '', + // 'Lowest priority' => '', + // 'Highest priority' => '', + // 'If you put zero to the low and high priority, this feature will be disabled.' => '', + // 'Priority: %d' => '', ); diff --git a/app/Locale/ja_JP/translations.php b/app/Locale/ja_JP/translations.php index f1884b93..b9cde718 100644 --- a/app/Locale/ja_JP/translations.php +++ b/app/Locale/ja_JP/translations.php @@ -1109,4 +1109,13 @@ return array( // 'Those dates are useful for the project Gantt chart.' => '', // 'Private projects do not have users and groups management.' => '', // 'There is no project member.' => '', + // 'Priority' => '', + // 'Task priority' => '', + // 'General' => '', + // 'Dates' => '', + // 'Default priority' => '', + // 'Lowest priority' => '', + // 'Highest priority' => '', + // 'If you put zero to the low and high priority, this feature will be disabled.' => '', + // 'Priority: %d' => '', ); diff --git a/app/Locale/my_MY/translations.php b/app/Locale/my_MY/translations.php index 65aaa4e3..43c288c7 100644 --- a/app/Locale/my_MY/translations.php +++ b/app/Locale/my_MY/translations.php @@ -1109,4 +1109,13 @@ return array( // 'Those dates are useful for the project Gantt chart.' => '', // 'Private projects do not have users and groups management.' => '', // 'There is no project member.' => '', + // 'Priority' => '', + // 'Task priority' => '', + // 'General' => '', + // 'Dates' => '', + // 'Default priority' => '', + // 'Lowest priority' => '', + // 'Highest priority' => '', + // 'If you put zero to the low and high priority, this feature will be disabled.' => '', + // 'Priority: %d' => '', ); diff --git a/app/Locale/nb_NO/translations.php b/app/Locale/nb_NO/translations.php index 520149d5..682f44a8 100644 --- a/app/Locale/nb_NO/translations.php +++ b/app/Locale/nb_NO/translations.php @@ -1109,4 +1109,13 @@ return array( // 'Those dates are useful for the project Gantt chart.' => '', // 'Private projects do not have users and groups management.' => '', // 'There is no project member.' => '', + // 'Priority' => '', + // 'Task priority' => '', + // 'General' => '', + // 'Dates' => '', + // 'Default priority' => '', + // 'Lowest priority' => '', + // 'Highest priority' => '', + // 'If you put zero to the low and high priority, this feature will be disabled.' => '', + // 'Priority: %d' => '', ); diff --git a/app/Locale/nl_NL/translations.php b/app/Locale/nl_NL/translations.php index 87f5323e..4f38f256 100644 --- a/app/Locale/nl_NL/translations.php +++ b/app/Locale/nl_NL/translations.php @@ -1109,4 +1109,13 @@ return array( // 'Those dates are useful for the project Gantt chart.' => '', // 'Private projects do not have users and groups management.' => '', // 'There is no project member.' => '', + // 'Priority' => '', + // 'Task priority' => '', + // 'General' => '', + // 'Dates' => '', + // 'Default priority' => '', + // 'Lowest priority' => '', + // 'Highest priority' => '', + // 'If you put zero to the low and high priority, this feature will be disabled.' => '', + // 'Priority: %d' => '', ); diff --git a/app/Locale/pl_PL/translations.php b/app/Locale/pl_PL/translations.php index 37dcfe3d..ee0ceb47 100644 --- a/app/Locale/pl_PL/translations.php +++ b/app/Locale/pl_PL/translations.php @@ -1109,4 +1109,13 @@ return array( // 'Those dates are useful for the project Gantt chart.' => '', // 'Private projects do not have users and groups management.' => '', // 'There is no project member.' => '', + // 'Priority' => '', + // 'Task priority' => '', + // 'General' => '', + // 'Dates' => '', + // 'Default priority' => '', + // 'Lowest priority' => '', + // 'Highest priority' => '', + // 'If you put zero to the low and high priority, this feature will be disabled.' => '', + // 'Priority: %d' => '', ); diff --git a/app/Locale/pt_BR/translations.php b/app/Locale/pt_BR/translations.php index 6e47c514..09e87048 100644 --- a/app/Locale/pt_BR/translations.php +++ b/app/Locale/pt_BR/translations.php @@ -1109,4 +1109,13 @@ return array( // 'Those dates are useful for the project Gantt chart.' => '', // 'Private projects do not have users and groups management.' => '', // 'There is no project member.' => '', + // 'Priority' => '', + // 'Task priority' => '', + // 'General' => '', + // 'Dates' => '', + // 'Default priority' => '', + // 'Lowest priority' => '', + // 'Highest priority' => '', + // 'If you put zero to the low and high priority, this feature will be disabled.' => '', + // 'Priority: %d' => '', ); diff --git a/app/Locale/pt_PT/translations.php b/app/Locale/pt_PT/translations.php index f57657ee..19c2ebf7 100644 --- a/app/Locale/pt_PT/translations.php +++ b/app/Locale/pt_PT/translations.php @@ -1109,4 +1109,13 @@ return array( // 'Those dates are useful for the project Gantt chart.' => '', // 'Private projects do not have users and groups management.' => '', // 'There is no project member.' => '', + // 'Priority' => '', + // 'Task priority' => '', + // 'General' => '', + // 'Dates' => '', + // 'Default priority' => '', + // 'Lowest priority' => '', + // 'Highest priority' => '', + // 'If you put zero to the low and high priority, this feature will be disabled.' => '', + // 'Priority: %d' => '', ); diff --git a/app/Locale/ru_RU/translations.php b/app/Locale/ru_RU/translations.php index 02bf9934..d09258ed 100644 --- a/app/Locale/ru_RU/translations.php +++ b/app/Locale/ru_RU/translations.php @@ -1109,4 +1109,13 @@ return array( // 'Those dates are useful for the project Gantt chart.' => '', // 'Private projects do not have users and groups management.' => '', // 'There is no project member.' => '', + // 'Priority' => '', + // 'Task priority' => '', + // 'General' => '', + // 'Dates' => '', + // 'Default priority' => '', + // 'Lowest priority' => '', + // 'Highest priority' => '', + // 'If you put zero to the low and high priority, this feature will be disabled.' => '', + // 'Priority: %d' => '', ); diff --git a/app/Locale/sr_Latn_RS/translations.php b/app/Locale/sr_Latn_RS/translations.php index c779cd6c..2b3553c2 100644 --- a/app/Locale/sr_Latn_RS/translations.php +++ b/app/Locale/sr_Latn_RS/translations.php @@ -1109,4 +1109,13 @@ return array( // 'Those dates are useful for the project Gantt chart.' => '', // 'Private projects do not have users and groups management.' => '', // 'There is no project member.' => '', + // 'Priority' => '', + // 'Task priority' => '', + // 'General' => '', + // 'Dates' => '', + // 'Default priority' => '', + // 'Lowest priority' => '', + // 'Highest priority' => '', + // 'If you put zero to the low and high priority, this feature will be disabled.' => '', + // 'Priority: %d' => '', ); diff --git a/app/Locale/sv_SE/translations.php b/app/Locale/sv_SE/translations.php index ca412273..1c01e94d 100644 --- a/app/Locale/sv_SE/translations.php +++ b/app/Locale/sv_SE/translations.php @@ -1109,4 +1109,13 @@ return array( // 'Those dates are useful for the project Gantt chart.' => '', // 'Private projects do not have users and groups management.' => '', // 'There is no project member.' => '', + // 'Priority' => '', + // 'Task priority' => '', + // 'General' => '', + // 'Dates' => '', + // 'Default priority' => '', + // 'Lowest priority' => '', + // 'Highest priority' => '', + // 'If you put zero to the low and high priority, this feature will be disabled.' => '', + // 'Priority: %d' => '', ); diff --git a/app/Locale/th_TH/translations.php b/app/Locale/th_TH/translations.php index b2b9a55b..e0de5844 100644 --- a/app/Locale/th_TH/translations.php +++ b/app/Locale/th_TH/translations.php @@ -1109,4 +1109,13 @@ return array( // 'Those dates are useful for the project Gantt chart.' => '', // 'Private projects do not have users and groups management.' => '', // 'There is no project member.' => '', + // 'Priority' => '', + // 'Task priority' => '', + // 'General' => '', + // 'Dates' => '', + // 'Default priority' => '', + // 'Lowest priority' => '', + // 'Highest priority' => '', + // 'If you put zero to the low and high priority, this feature will be disabled.' => '', + // 'Priority: %d' => '', ); diff --git a/app/Locale/tr_TR/translations.php b/app/Locale/tr_TR/translations.php index 17163f45..c4da560d 100644 --- a/app/Locale/tr_TR/translations.php +++ b/app/Locale/tr_TR/translations.php @@ -1109,4 +1109,13 @@ return array( // 'Those dates are useful for the project Gantt chart.' => '', // 'Private projects do not have users and groups management.' => '', // 'There is no project member.' => '', + // 'Priority' => '', + // 'Task priority' => '', + // 'General' => '', + // 'Dates' => '', + // 'Default priority' => '', + // 'Lowest priority' => '', + // 'Highest priority' => '', + // 'If you put zero to the low and high priority, this feature will be disabled.' => '', + // 'Priority: %d' => '', ); diff --git a/app/Locale/zh_CN/translations.php b/app/Locale/zh_CN/translations.php index e4b732ef..e0b90b58 100644 --- a/app/Locale/zh_CN/translations.php +++ b/app/Locale/zh_CN/translations.php @@ -1109,4 +1109,13 @@ return array( // 'Those dates are useful for the project Gantt chart.' => '', // 'Private projects do not have users and groups management.' => '', // 'There is no project member.' => '', + // 'Priority' => '', + // 'Task priority' => '', + // 'General' => '', + // 'Dates' => '', + // 'Default priority' => '', + // 'Lowest priority' => '', + // 'Highest priority' => '', + // 'If you put zero to the low and high priority, this feature will be disabled.' => '', + // 'Priority: %d' => '', ); diff --git a/app/Model/Project.php b/app/Model/Project.php index ba0716b0..d0a8bfc8 100644 --- a/app/Model/Project.php +++ b/app/Model/Project.php @@ -334,6 +334,8 @@ class Project extends Base $values['identifier'] = strtoupper($values['identifier']); } + $this->convertIntegerFields($values, array('priority_default', 'priority_start', 'priority_end')); + if (! $this->db->table(self::TABLE)->save($values)) { $this->db->cancelTransaction(); return false; @@ -400,6 +402,8 @@ class Project extends Base $values['identifier'] = strtoupper($values['identifier']); } + $this->convertIntegerFields($values, array('priority_default', 'priority_start', 'priority_end')); + return $this->exists($values['id']) && $this->db->table(self::TABLE)->eq('id', $values['id'])->save($values); } diff --git a/app/Model/TaskFinder.php b/app/Model/TaskFinder.php index 836fbe46..1c83136b 100644 --- a/app/Model/TaskFinder.php +++ b/app/Model/TaskFinder.php @@ -113,6 +113,7 @@ class TaskFinder extends Base 'tasks.is_active', 'tasks.score', 'tasks.category_id', + 'tasks.priority', 'tasks.date_moved', 'tasks.recurrence_status', 'tasks.recurrence_trigger', @@ -308,6 +309,7 @@ class TaskFinder extends Base tasks.is_active, tasks.score, tasks.category_id, + tasks.priority, tasks.swimlane_id, tasks.date_moved, tasks.recurrence_status, diff --git a/app/Model/TaskModification.php b/app/Model/TaskModification.php index a0ad292c..eee7b2e0 100644 --- a/app/Model/TaskModification.php +++ b/app/Model/TaskModification.php @@ -88,7 +88,7 @@ class TaskModification extends Base $this->dateParser->convert($values, array('date_started'), true); $this->removeFields($values, array('another_task', 'id')); $this->resetFields($values, array('date_due', 'date_started', 'score', 'category_id', 'time_estimated', 'time_spent')); - $this->convertIntegerFields($values, array('is_active', 'recurrence_status', 'recurrence_trigger', 'recurrence_factor', 'recurrence_timeframe', 'recurrence_basedate')); + $this->convertIntegerFields($values, array('priority', 'is_active', 'recurrence_status', 'recurrence_trigger', 'recurrence_factor', 'recurrence_timeframe', 'recurrence_basedate')); $values['date_modification'] = time(); } diff --git a/app/Schema/Mysql.php b/app/Schema/Mysql.php index ad7f7be1..8f1db510 100644 --- a/app/Schema/Mysql.php +++ b/app/Schema/Mysql.php @@ -6,7 +6,15 @@ use PDO; use Kanboard\Core\Security\Token; use Kanboard\Core\Security\Role; -const VERSION = 102; +const VERSION = 103; + +function version_103(PDO $pdo) +{ + $pdo->exec("ALTER TABLE projects ADD COLUMN priority_default INT DEFAULT 0"); + $pdo->exec("ALTER TABLE projects ADD COLUMN priority_start INT DEFAULT 0"); + $pdo->exec("ALTER TABLE projects ADD COLUMN priority_end INT DEFAULT 3"); + $pdo->exec("ALTER TABLE tasks ADD COLUMN priority INT DEFAULT 0"); +} function version_102(PDO $pdo) { diff --git a/app/Schema/Postgres.php b/app/Schema/Postgres.php index d23a7723..a7bf8054 100644 --- a/app/Schema/Postgres.php +++ b/app/Schema/Postgres.php @@ -6,7 +6,15 @@ use PDO; use Kanboard\Core\Security\Token; use Kanboard\Core\Security\Role; -const VERSION = 82; +const VERSION = 83; + +function version_83(PDO $pdo) +{ + $pdo->exec("ALTER TABLE projects ADD COLUMN priority_default INTEGER DEFAULT 0"); + $pdo->exec("ALTER TABLE projects ADD COLUMN priority_start INTEGER DEFAULT 0"); + $pdo->exec("ALTER TABLE projects ADD COLUMN priority_end INTEGER DEFAULT 3"); + $pdo->exec("ALTER TABLE tasks ADD COLUMN priority INTEGER DEFAULT 0"); +} function version_82(PDO $pdo) { diff --git a/app/Schema/Sqlite.php b/app/Schema/Sqlite.php index daa70f54..08689749 100644 --- a/app/Schema/Sqlite.php +++ b/app/Schema/Sqlite.php @@ -6,7 +6,15 @@ use Kanboard\Core\Security\Token; use Kanboard\Core\Security\Role; use PDO; -const VERSION = 94; +const VERSION = 95; + +function version_95(PDO $pdo) +{ + $pdo->exec("ALTER TABLE projects ADD COLUMN priority_default INTEGER DEFAULT 0"); + $pdo->exec("ALTER TABLE projects ADD COLUMN priority_start INTEGER DEFAULT 0"); + $pdo->exec("ALTER TABLE projects ADD COLUMN priority_end INTEGER DEFAULT 3"); + $pdo->exec("ALTER TABLE tasks ADD COLUMN priority INTEGER DEFAULT 0"); +} function version_94(PDO $pdo) { diff --git a/app/ServiceProvider/RouteProvider.php b/app/ServiceProvider/RouteProvider.php index 5a5c8652..057a1b3c 100644 --- a/app/ServiceProvider/RouteProvider.php +++ b/app/ServiceProvider/RouteProvider.php @@ -64,6 +64,7 @@ class RouteProvider implements ServiceProviderInterface $container['route']->addRoute('project/:project_id/edit', 'ProjectEdit', 'edit'); $container['route']->addRoute('project/:project_id/edit/dates', 'ProjectEdit', 'dates'); $container['route']->addRoute('project/:project_id/edit/description', 'ProjectEdit', 'description'); + $container['route']->addRoute('project/:project_id/edit/priority', 'ProjectEdit', 'priority'); // ProjectUser routes $container['route']->addRoute('projects/managers/:user_id', 'projectuser', 'managers'); diff --git a/app/Template/board/task_footer.php b/app/Template/board/task_footer.php index 4a16364c..26f3b1d4 100644 --- a/app/Template/board/task_footer.php +++ b/app/Template/board/task_footer.php @@ -69,4 +69,6 @@ + + task->formatPriority($project, $task) ?> diff --git a/app/Template/board/task_private.php b/app/Template/board/task_private.php index 8d76453c..4880af00 100644 --- a/app/Template/board/task_private.php +++ b/app/Template/board/task_private.php @@ -78,6 +78,7 @@ render('board/task_footer', array( 'task' => $task, 'not_editable' => $not_editable, + 'project' => $project, )) ?> diff --git a/app/Template/board/task_public.php b/app/Template/board/task_public.php index bacdcef4..d02722bb 100644 --- a/app/Template/board/task_public.php +++ b/app/Template/board/task_public.php @@ -25,5 +25,6 @@ render('board/task_footer', array( 'task' => $task, 'not_editable' => $not_editable, + 'project' => $project, )) ?> \ No newline at end of file diff --git a/app/Template/project_edit/dates.php b/app/Template/project_edit/dates.php index d3f4bad8..cb585c6a 100644 --- a/app/Template/project_edit/dates.php +++ b/app/Template/project_edit/dates.php @@ -4,6 +4,7 @@
  • url->link(t('General'), 'ProjectEdit', 'edit', array('project_id' => $project['id'])) ?>
  • url->link(t('Dates'), 'ProjectEdit', 'dates', array('project_id' => $project['id'])) ?>
  • url->link(t('Description'), 'ProjectEdit', 'description', array('project_id' => $project['id'])) ?>
  • +
  • url->link(t('Task priority'), 'ProjectEdit', 'priority', array('project_id' => $project['id'])) ?>
  • diff --git a/app/Template/project_edit/description.php b/app/Template/project_edit/description.php index 3af484d5..dce8ab10 100644 --- a/app/Template/project_edit/description.php +++ b/app/Template/project_edit/description.php @@ -4,6 +4,7 @@
  • url->link(t('General'), 'ProjectEdit', 'edit', array('project_id' => $project['id'])) ?>
  • url->link(t('Dates'), 'ProjectEdit', 'dates', array('project_id' => $project['id'])) ?>
  • url->link(t('Description'), 'ProjectEdit', 'description', array('project_id' => $project['id'])) ?>
  • +
  • url->link(t('Task priority'), 'ProjectEdit', 'priority', array('project_id' => $project['id'])) ?>
  • diff --git a/app/Template/project_edit/general.php b/app/Template/project_edit/general.php index 1da913da..5caefa2d 100644 --- a/app/Template/project_edit/general.php +++ b/app/Template/project_edit/general.php @@ -4,6 +4,7 @@
  • url->link(t('General'), 'ProjectEdit', 'edit', array('project_id' => $project['id'])) ?>
  • url->link(t('Dates'), 'ProjectEdit', 'dates', array('project_id' => $project['id'])) ?>
  • url->link(t('Description'), 'ProjectEdit', 'description', array('project_id' => $project['id'])) ?>
  • +
  • url->link(t('Task priority'), 'ProjectEdit', 'priority', array('project_id' => $project['id'])) ?>
  • diff --git a/app/Template/project_edit/task_priority.php b/app/Template/project_edit/task_priority.php new file mode 100644 index 00000000..e54215b2 --- /dev/null +++ b/app/Template/project_edit/task_priority.php @@ -0,0 +1,29 @@ + + + form->csrf() ?> + form->hidden('id', $values) ?> + form->hidden('name', $values) ?> + + form->label(t('Default priority'), 'priority_default') ?> + form->number('priority_default', $values, $errors) ?> + + form->label(t('Lowest priority'), 'priority_start') ?> + form->number('priority_start', $values, $errors) ?> + + form->label(t('Highest priority'), 'priority_end') ?> + form->number('priority_end', $values, $errors) ?> + +
    + +
    +
    + +

    diff --git a/app/Template/task/details.php b/app/Template/task/details.php index 74799b15..d885ca9c 100644 --- a/app/Template/task/details.php +++ b/app/Template/task/details.php @@ -4,6 +4,9 @@ e($task['score']) ?>
      +
    • + +
    • diff --git a/app/Template/task_creation/form.php b/app/Template/task_creation/form.php index c9f367a0..eaf9024d 100644 --- a/app/Template/task_creation/form.php +++ b/app/Template/task_creation/form.php @@ -16,7 +16,7 @@
      form->label(t('Title'), 'title') ?> - form->text('title', $values, $errors, array('autofocus', 'required', 'maxlength="200"', 'tabindex="1"'), 'form-input-large') ?>
      + form->text('title', $values, $errors, array('autofocus', 'required', 'maxlength="200"', 'tabindex="1"'), 'form-input-large') ?> form->label(t('Description'), 'description') ?> @@ -57,32 +57,34 @@ form->hidden('project_id', $values) ?> form->label(t('Assignee'), 'owner_id') ?> - form->select('owner_id', $users_list, $values, $errors, array('tabindex="3"')) ?>
      + form->select('owner_id', $users_list, $values, $errors, array('tabindex="3"')) ?> form->label(t('Category'), 'category_id') ?> - form->select('category_id', $categories_list, $values, $errors, array('tabindex="4"')) ?>
      + form->select('category_id', $categories_list, $values, $errors, array('tabindex="4"')) ?> form->label(t('Swimlane'), 'swimlane_id') ?> - form->select('swimlane_id', $swimlanes_list, $values, $errors, array('tabindex="5"')) ?>
      + form->select('swimlane_id', $swimlanes_list, $values, $errors, array('tabindex="5"')) ?> form->label(t('Column'), 'column_id') ?> - form->select('column_id', $columns_list, $values, $errors, array('tabindex="6"')) ?>
      + form->select('column_id', $columns_list, $values, $errors, array('tabindex="6"')) ?> + + task->selectPriority($project, $values) ?> form->label(t('Complexity'), 'score') ?> - form->number('score', $values, $errors, array('tabindex="8"')) ?>
      + form->number('score', $values, $errors, array('tabindex="9"')) ?> form->label(t('Original estimate'), 'time_estimated') ?> - form->numeric('time_estimated', $values, $errors, array('tabindex="9"')) ?>
      + form->numeric('time_estimated', $values, $errors, array('tabindex="10"')) ?> form->label(t('Due Date'), 'date_due') ?> - form->text('date_due', $values, $errors, array('placeholder="'.$this->text->in($date_format, $date_formats).'"', 'tabindex="10"'), 'form-date') ?>
      + form->text('date_due', $values, $errors, array('placeholder="'.$this->text->in($date_format, $date_formats).'"', 'tabindex="11"'), 'form-date') ?>
      - + url->link(t('cancel'), 'board', 'show', array('project_id' => $values['project_id']), false, 'close-popover') ?>
      \ No newline at end of file diff --git a/app/Template/task_modification/edit_task.php b/app/Template/task_modification/edit_task.php index 6fdb77b5..2701dd8f 100644 --- a/app/Template/task_modification/edit_task.php +++ b/app/Template/task_modification/edit_task.php @@ -8,7 +8,7 @@
      form->label(t('Title'), 'title') ?> - form->text('title', $values, $errors, array('autofocus', 'required', 'maxlength="200"', 'tabindex="1"')) ?>
      + form->text('title', $values, $errors, array('autofocus', 'required', 'maxlength="200"', 'tabindex="1"')) ?> form->label(t('Description'), 'description') ?>
      @@ -45,16 +45,18 @@ form->hidden('project_id', $values) ?> form->label(t('Assignee'), 'owner_id') ?> - form->select('owner_id', $users_list, $values, $errors, array('tabindex="3"')) ?>
      + form->select('owner_id', $users_list, $values, $errors, array('tabindex="3"')) ?> form->label(t('Category'), 'category_id') ?> - form->select('category_id', $categories_list, $values, $errors, array('tabindex="4"')) ?>
      + form->select('category_id', $categories_list, $values, $errors, array('tabindex="4"')) ?> form->label(t('Complexity'), 'score') ?> - form->number('score', $values, $errors, array('tabindex="6"')) ?>
      + form->number('score', $values, $errors, array('tabindex="6"')) ?> + + task->selectPriority($project, $values) ?> form->label(t('Due Date'), 'date_due') ?> - form->text('date_due', $values, $errors, array('placeholder="'.$this->text->in($date_format, $date_formats).'"', 'tabindex="7"'), 'form-date') ?>
      + form->text('date_due', $values, $errors, array('placeholder="'.$this->text->in($date_format, $date_formats).'"', 'tabindex="8"'), 'form-date') ?>
      diff --git a/app/Validator/ProjectValidator.php b/app/Validator/ProjectValidator.php index 53cb7a37..1c6c90f8 100644 --- a/app/Validator/ProjectValidator.php +++ b/app/Validator/ProjectValidator.php @@ -24,6 +24,9 @@ class ProjectValidator extends Base { return array( new Validators\Integer('id', t('This value must be an integer')), + new Validators\Integer('priority_default', t('This value must be an integer')), + new Validators\Integer('priority_start', t('This value must be an integer')), + new Validators\Integer('priority_end', t('This value must be an integer')), new Validators\Integer('is_active', t('This value must be an integer')), new Validators\Required('name', t('The project name is required')), new Validators\MaxLength('name', t('The maximum length is %d characters', 50), 50), diff --git a/app/Validator/TaskValidator.php b/app/Validator/TaskValidator.php index c18a9761..7b73aeba 100644 --- a/app/Validator/TaskValidator.php +++ b/app/Validator/TaskValidator.php @@ -37,6 +37,7 @@ class TaskValidator extends Base new Validators\Integer('recurrence_basedate', t('This value must be an integer')), new Validators\Integer('recurrence_trigger', t('This value must be an integer')), new Validators\Integer('recurrence_status', t('This value must be an integer')), + new Validators\Integer('priority', t('This value must be an integer')), new Validators\MaxLength('title', t('The maximum length is %d characters', 200), 200), new Validators\MaxLength('reference', t('The maximum length is %d characters', 50), 50), new Validators\Date('date_due', t('Invalid date'), $this->dateParser->getDateFormats()), diff --git a/tests/units/Helper/TaskHelperTest.php b/tests/units/Helper/TaskHelperTest.php new file mode 100644 index 00000000..726188e4 --- /dev/null +++ b/tests/units/Helper/TaskHelperTest.php @@ -0,0 +1,32 @@ +container); + $this->assertNotEmpty($helper->selectPriority(array('priority_end' => '3', 'priority_start' => '1', 'priority_default' => '2'), array())); + $this->assertEmpty($helper->selectPriority(array('priority_end' => '3', 'priority_start' => '3', 'priority_default' => '2'), array())); + } + + public function testFormatPriority() + { + $helper = new Task($this->container); + + $this->assertEquals( + 'P2', + $helper->formatPriority(array('priority_end' => '3', 'priority_start' => '1', 'priority_default' => '2'), array('priority' => 2)) + ); + + $this->assertEquals( + '-P6', + $helper->formatPriority(array('priority_end' => '3', 'priority_start' => '1', 'priority_default' => '2'), array('priority' => -6)) + ); + + $this->assertEmpty($helper->formatPriority(array('priority_end' => '3', 'priority_start' => '3', 'priority_default' => '2'), array())); + } +} diff --git a/tests/units/Model/ProjectTest.php b/tests/units/Model/ProjectTest.php index afd047b0..cadb42a6 100644 --- a/tests/units/Model/ProjectTest.php +++ b/tests/units/Model/ProjectTest.php @@ -305,4 +305,24 @@ class ProjectTest extends Base $this->assertEquals('', $project['owner_username']); $this->assertEquals(0, $project['owner_id']); } + + public function testPriority() + { + $projectModel = new Project($this->container); + $this->assertEquals(1, $projectModel->create(array('name' => 'My project 2'))); + + $project = $projectModel->getById(1); + $this->assertNotEmpty($project); + $this->assertEquals(0, $project['priority_default']); + $this->assertEquals(0, $project['priority_start']); + $this->assertEquals(3, $project['priority_end']); + + $this->assertTrue($projectModel->update(array('id' => 1, 'priority_start' => 2, 'priority_end' => 5, 'priority_default' => 4))); + + $project = $projectModel->getById(1); + $this->assertNotEmpty($project); + $this->assertEquals(4, $project['priority_default']); + $this->assertEquals(2, $project['priority_start']); + $this->assertEquals(5, $project['priority_end']); + } } -- cgit v1.2.3 From 5c92f467867b43034b9e66b46b3b465ba9db655c Mon Sep 17 00:00:00 2001 From: Frederic Guillot Date: Sat, 30 Jan 2016 20:38:20 -0500 Subject: Add external links for tasks with plugin api --- ChangeLog | 1 + app/Controller/Base.php | 11 +- app/Controller/BoardTooltip.php | 14 ++ app/Controller/Comment.php | 10 -- app/Controller/TaskExternalLink.php | 185 +++++++++++++++++++++ app/Controller/Tasklink.php | 37 +++-- app/Controller/Taskmodification.php | 7 +- app/Core/Base.php | 3 + app/Core/ExternalLink/ExternalLinkInterface.php | 36 ++++ app/Core/ExternalLink/ExternalLinkManager.php | 171 +++++++++++++++++++ .../ExternalLink/ExternalLinkProviderInterface.php | 71 ++++++++ .../ExternalLink/ExternalLinkProviderNotFound.php | 15 ++ app/Core/Http/Client.php | 13 ++ app/Core/Http/Response.php | 5 +- app/ExternalLink/AttachmentLink.php | 26 +++ app/ExternalLink/AttachmentLinkProvider.php | 117 +++++++++++++ app/ExternalLink/BaseLink.php | 44 +++++ app/ExternalLink/BaseLinkProvider.php | 33 ++++ app/ExternalLink/WebLink.php | 37 +++++ app/ExternalLink/WebLinkProvider.php | 77 +++++++++ app/Locale/bs_BA/translations.php | 26 +++ app/Locale/cs_CZ/translations.php | 26 +++ app/Locale/da_DK/translations.php | 26 +++ app/Locale/de_DE/translations.php | 26 +++ app/Locale/el_GR/translations.php | 26 +++ app/Locale/es_ES/translations.php | 26 +++ app/Locale/fi_FI/translations.php | 26 +++ app/Locale/fr_FR/translations.php | 26 +++ app/Locale/hu_HU/translations.php | 26 +++ app/Locale/id_ID/translations.php | 26 +++ app/Locale/it_IT/translations.php | 26 +++ app/Locale/ja_JP/translations.php | 26 +++ app/Locale/my_MY/translations.php | 26 +++ app/Locale/nb_NO/translations.php | 26 +++ app/Locale/nl_NL/translations.php | 26 +++ app/Locale/pl_PL/translations.php | 26 +++ app/Locale/pt_BR/translations.php | 26 +++ app/Locale/pt_PT/translations.php | 26 +++ app/Locale/ru_RU/translations.php | 26 +++ app/Locale/sr_Latn_RS/translations.php | 26 +++ app/Locale/sv_SE/translations.php | 26 +++ app/Locale/th_TH/translations.php | 26 +++ app/Locale/tr_TR/translations.php | 26 +++ app/Locale/zh_CN/translations.php | 26 +++ app/Model/TaskExternalLink.php | 99 +++++++++++ app/Model/TaskFinder.php | 11 +- app/Model/User.php | 2 +- app/Schema/Mysql.php | 20 ++- app/Schema/Postgres.php | 20 ++- app/Schema/Sqlite.php | 20 ++- app/ServiceProvider/AuthenticationProvider.php | 3 + app/ServiceProvider/ClassProvider.php | 2 + app/ServiceProvider/ExternalLinkProvider.php | 34 ++++ app/ServiceProvider/RouteProvider.php | 7 + app/Template/board/task_footer.php | 6 +- app/Template/board/task_menu.php | 1 + app/Template/board/tooltip_external_links.php | 20 +++ app/Template/comment/create.php | 4 +- app/Template/task/comments.php | 3 +- app/Template/task/show.php | 17 +- app/Template/task/sidebar.php | 22 ++- app/Template/task_creation/form.php | 2 +- app/Template/task_external_link/create.php | 13 ++ app/Template/task_external_link/edit.php | 13 ++ app/Template/task_external_link/find.php | 32 ++++ app/Template/task_external_link/form.php | 13 ++ app/Template/task_external_link/remove.php | 15 ++ app/Template/task_external_link/show.php | 50 ++++++ app/Template/tasklink/create.php | 4 +- app/Template/tasklink/show.php | 12 +- app/Validator/ExternalLinkValidator.php | 76 +++++++++ app/common.php | 1 + assets/js/app.js | 2 +- assets/js/src/Popover.js | 17 +- .../Core/ExternalLink/ExternalLinkManagerTest.php | 120 +++++++++++++ .../ExternalLink/AttachmentLinkProviderTest.php | 64 +++++++ tests/units/ExternalLink/AttachmentLinkTest.php | 18 ++ tests/units/ExternalLink/WebLinkProviderTest.php | 52 ++++++ tests/units/ExternalLink/WebLinkTest.php | 57 +++++++ tests/units/Model/TaskExternalLinkTest.php | 167 +++++++++++++++++++ .../units/Validator/ExternalLinkValidatorTest.php | 63 +++++++ 81 files changed, 2544 insertions(+), 75 deletions(-) create mode 100644 app/Controller/TaskExternalLink.php create mode 100644 app/Core/ExternalLink/ExternalLinkInterface.php create mode 100644 app/Core/ExternalLink/ExternalLinkManager.php create mode 100644 app/Core/ExternalLink/ExternalLinkProviderInterface.php create mode 100644 app/Core/ExternalLink/ExternalLinkProviderNotFound.php create mode 100644 app/ExternalLink/AttachmentLink.php create mode 100644 app/ExternalLink/AttachmentLinkProvider.php create mode 100644 app/ExternalLink/BaseLink.php create mode 100644 app/ExternalLink/BaseLinkProvider.php create mode 100644 app/ExternalLink/WebLink.php create mode 100644 app/ExternalLink/WebLinkProvider.php create mode 100644 app/Model/TaskExternalLink.php create mode 100644 app/ServiceProvider/ExternalLinkProvider.php create mode 100644 app/Template/board/tooltip_external_links.php create mode 100644 app/Template/task_external_link/create.php create mode 100644 app/Template/task_external_link/edit.php create mode 100644 app/Template/task_external_link/find.php create mode 100644 app/Template/task_external_link/form.php create mode 100644 app/Template/task_external_link/remove.php create mode 100644 app/Template/task_external_link/show.php create mode 100644 app/Validator/ExternalLinkValidator.php create mode 100644 tests/units/Core/ExternalLink/ExternalLinkManagerTest.php create mode 100644 tests/units/ExternalLink/AttachmentLinkProviderTest.php create mode 100644 tests/units/ExternalLink/AttachmentLinkTest.php create mode 100644 tests/units/ExternalLink/WebLinkProviderTest.php create mode 100644 tests/units/ExternalLink/WebLinkTest.php create mode 100644 tests/units/Model/TaskExternalLinkTest.php create mode 100644 tests/units/Validator/ExternalLinkValidatorTest.php (limited to 'app/Validator') diff --git a/ChangeLog b/ChangeLog index bc0aad32..eb6abf28 100644 --- a/ChangeLog +++ b/ChangeLog @@ -10,6 +10,7 @@ Breaking changes: New features: +* Add external links for tasks with plugin api * Add project owner (Directly Responsible Individual) * Add configurable task priority * Add Greek translation diff --git a/app/Controller/Base.php b/app/Controller/Base.php index efeab31e..fb64bcf3 100644 --- a/app/Controller/Base.php +++ b/app/Controller/Base.php @@ -189,9 +189,15 @@ abstract class Base extends \Kanboard\Core\Base */ protected function taskLayout($template, array $params) { + $params['ajax'] = $this->request->isAjax() || $this->request->getIntegerParam('ajax') === 1; $content = $this->template->render($template, $params); - $params['task_content_for_layout'] = $content; + + if ($params['ajax']) { + return $content; + } + $params['title'] = $params['task']['project_name'].' > '.$params['task']['title']; + $params['task_content_for_layout'] = $content; $params['board_selector'] = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId()); return $this->template->layout('task/layout', $params); @@ -319,7 +325,8 @@ abstract class Base extends \Kanboard\Core\Base * @param array &$project * @return string */ - protected function getProjectDescription(array &$project) { + protected function getProjectDescription(array &$project) + { if ($project['owner_id'] > 0) { $description = t('Project owner: ').'**'.$this->template->e($project['owner_name'] ?: $project['owner_username']).'**'.PHP_EOL.PHP_EOL; diff --git a/app/Controller/BoardTooltip.php b/app/Controller/BoardTooltip.php index bcf7de81..06f4d729 100644 --- a/app/Controller/BoardTooltip.php +++ b/app/Controller/BoardTooltip.php @@ -24,6 +24,20 @@ class BoardTooltip extends Base ))); } + /** + * Get links on mouseover + * + * @access public + */ + public function externallinks() + { + $task = $this->getTask(); + $this->response->html($this->template->render('board/tooltip_external_links', array( + 'links' => $this->taskExternalLink->getAll($task['id']), + 'task' => $task, + ))); + } + /** * Get subtasks on mouseover * diff --git a/app/Controller/Comment.php b/app/Controller/Comment.php index a608dd1c..c77a4712 100644 --- a/app/Controller/Comment.php +++ b/app/Controller/Comment.php @@ -41,7 +41,6 @@ class Comment extends Base public function create(array $values = array(), array $errors = array()) { $task = $this->getTask(); - $ajax = $this->request->isAjax() || $this->request->getIntegerParam('ajax'); if (empty($values)) { $values = array( @@ -50,15 +49,6 @@ class Comment extends Base ); } - if ($ajax) { - $this->response->html($this->template->render('comment/create', array( - 'values' => $values, - 'errors' => $errors, - 'task' => $task, - 'ajax' => $ajax, - ))); - } - $this->response->html($this->taskLayout('comment/create', array( 'values' => $values, 'errors' => $errors, diff --git a/app/Controller/TaskExternalLink.php b/app/Controller/TaskExternalLink.php new file mode 100644 index 00000000..3209751b --- /dev/null +++ b/app/Controller/TaskExternalLink.php @@ -0,0 +1,185 @@ +getTask(); + + $this->response->html($this->taskLayout('task_external_link/show', array( + 'links' => $this->taskExternalLink->getAll($task['id']), + 'task' => $task, + 'title' => t('List of external links'), + ))); + } + + /** + * First creation form + * + * @access public + */ + public function find(array $values = array(), array $errors = array()) + { + $task = $this->getTask(); + + $this->response->html($this->taskLayout('task_external_link/find', array( + 'values' => $values, + 'errors' => $errors, + 'task' => $task, + 'types' => $this->externalLinkManager->getTypes(), + ))); + } + + /** + * Second creation form + * + * @access public + */ + public function create() + { + try { + + $task = $this->getTask(); + $values = $this->request->getValues(); + + $provider = $this->externalLinkManager->setUserInput($values)->find(); + $link = $provider->getLink(); + + $this->response->html($this->taskLayout('task_external_link/create', array( + 'values' => array( + 'title' => $link->getTitle(), + 'url' => $link->getUrl(), + 'link_type' => $provider->getType(), + ), + 'dependencies' => $provider->getDependencies(), + 'errors' => array(), + 'task' => $task, + ))); + + } catch (ExternalLinkProviderNotFound $e) { + $errors = array('text' => array(t('Unable to fetch link information.'))); + $this->find($values, $errors); + } + } + + /** + * Save link + * + * @access public + */ + public function save() + { + $task = $this->getTask(); + $values = $this->request->getValues(); + list($valid, $errors) = $this->externalLinkValidator->validateCreation($values); + + if ($valid && $this->taskExternalLink->create($values)) { + $this->flash->success(t('Link added successfully.')); + return $this->response->redirect($this->helper->url->to('TaskExternalLink', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])), true); + } + + $this->edit($values, $errors); + } + + /** + * Edit form + * + * @access public + */ + public function edit(array $values = array(), array $errors = array()) + { + $task = $this->getTask(); + $link_id = $this->request->getIntegerParam('link_id'); + + if ($link_id > 0) { + $values = $this->taskExternalLink->getById($link_id); + } + + if (empty($values)) { + return $this->notfound(); + } + + $provider = $this->externalLinkManager->getProvider($values['link_type']); + + $this->response->html($this->taskLayout('task_external_link/edit', array( + 'values' => $values, + 'errors' => $errors, + 'task' => $task, + 'dependencies' => $provider->getDependencies(), + ))); + } + + /** + * Update link + * + * @access public + */ + public function update() + { + $task = $this->getTask(); + $values = $this->request->getValues(); + list($valid, $errors) = $this->externalLinkValidator->validateModification($values); + + if ($valid && $this->taskExternalLink->update($values)) { + $this->flash->success(t('Link updated successfully.')); + return $this->response->redirect($this->helper->url->to('TaskExternalLink', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']))); + } + + $this->edit($values, $errors); + } + + /** + * Confirmation dialog before removing a link + * + * @access public + */ + public function confirm() + { + $task = $this->getTask(); + $link_id = $this->request->getIntegerParam('link_id'); + $link = $this->taskExternalLink->getById($link_id); + + if (empty($link)) { + return $this->notfound(); + } + + $this->response->html($this->taskLayout('task_external_link/remove', array( + 'link' => $link, + 'task' => $task, + ))); + } + + /** + * Remove a link + * + * @access public + */ + public function remove() + { + $this->checkCSRFParam(); + $task = $this->getTask(); + + if ($this->taskExternalLink->remove($this->request->getIntegerParam('link_id'))) { + $this->flash->success(t('Link removed successfully.')); + } else { + $this->flash->failure(t('Unable to remove this link.')); + } + + $this->response->redirect($this->helper->url->to('TaskExternalLink', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']))); + } +} diff --git a/app/Controller/Tasklink.php b/app/Controller/Tasklink.php index a81d3ee5..4338e7bf 100644 --- a/app/Controller/Tasklink.php +++ b/app/Controller/Tasklink.php @@ -28,6 +28,25 @@ class Tasklink extends Base return $link; } + /** + * Show links + * + * @access public + */ + public function show() + { + $task = $this->getTask(); + $project = $this->project->getById($task['project_id']); + + $this->response->html($this->taskLayout('tasklink/show', array( + 'links' => $this->taskLink->getAllGroupedByLabel($task['id']), + 'task' => $task, + 'project' => $project, + 'editable' => true, + 'is_public' => false, + ))); + } + /** * Creation form * @@ -36,18 +55,6 @@ class Tasklink extends Base public function create(array $values = array(), array $errors = array()) { $task = $this->getTask(); - $ajax = $this->request->isAjax() || $this->request->getIntegerParam('ajax'); - - if ($ajax && empty($errors)) { - $this->response->html($this->template->render('tasklink/create', array( - 'values' => $values, - 'errors' => $errors, - 'task' => $task, - 'labels' => $this->link->getList(0, false), - 'title' => t('Add a new link'), - 'ajax' => $ajax, - ))); - } $this->response->html($this->taskLayout('tasklink/create', array( 'values' => $values, @@ -76,10 +83,10 @@ class Tasklink extends Base $this->flash->success(t('Link added successfully.')); if ($ajax) { - $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $task['project_id']))); + return $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $task['project_id']))); } - $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])).'#links'); + return $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])).'#links'); } $errors = array('title' => array(t('The exact same link already exists'))); @@ -130,7 +137,7 @@ class Tasklink extends Base if ($valid) { if ($this->taskLink->update($values['id'], $values['task_id'], $values['opposite_task_id'], $values['link_id'])) { $this->flash->success(t('Link updated successfully.')); - $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])).'#links'); + return $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])).'#links'); } $this->flash->failure(t('Unable to update your link.')); diff --git a/app/Controller/Taskmodification.php b/app/Controller/Taskmodification.php index 2c97970b..1fcc416b 100644 --- a/app/Controller/Taskmodification.php +++ b/app/Controller/Taskmodification.php @@ -80,14 +80,9 @@ class Taskmodification extends Base 'values' => $values, 'errors' => $errors, 'task' => $task, - 'ajax' => $ajax, ); - if ($ajax) { - $this->response->html($this->template->render('task_modification/edit_description', $params)); - } else { - $this->response->html($this->taskLayout('task_modification/edit_description', $params)); - } + $this->response->html($this->taskLayout('task_modification/edit_description', $params)); } /** diff --git a/app/Core/Base.php b/app/Core/Base.php index 2821e5ae..ab99fcea 100644 --- a/app/Core/Base.php +++ b/app/Core/Base.php @@ -16,6 +16,7 @@ use Pimple\Container; * @property \Kanboard\Analytic\AverageLeadCycleTimeAnalytic $averageLeadCycleTimeAnalytic * @property \Kanboard\Analytic\AverageTimeSpentColumnAnalytic $averageTimeSpentColumnAnalytic * @property \Kanboard\Core\Action\ActionManager $actionManager + * @property \Kanboard\Core\ExternalLink\ExternalLinkManager $externalLinkManager * @property \Kanboard\Core\Cache\MemoryCache $memoryCache * @property \Kanboard\Core\Event\EventManager $eventManager * @property \Kanboard\Core\Group\GroupManager $groupManager @@ -97,6 +98,7 @@ use Pimple\Container; * @property \Kanboard\Model\TaskCreation $taskCreation * @property \Kanboard\Model\TaskDuplication $taskDuplication * @property \Kanboard\Model\TaskExport $taskExport + * @property \Kanboard\Model\TaskExternalLink $taskExternalLink * @property \Kanboard\Model\TaskImport $taskImport * @property \Kanboard\Model\TaskFinder $taskFinder * @property \Kanboard\Model\TaskFilter $taskFilter @@ -132,6 +134,7 @@ use Pimple\Container; * @property \Kanboard\Validator\SubtaskValidator $subtaskValidator * @property \Kanboard\Validator\SwimlaneValidator $swimlaneValidator * @property \Kanboard\Validator\TaskLinkValidator $taskLinkValidator + * @property \Kanboard\Validator\TaskExternalLinkValidator $taskExternalLinkValidator * @property \Kanboard\Validator\TaskValidator $taskValidator * @property \Kanboard\Validator\UserValidator $userValidator * @property \Psr\Log\LoggerInterface $logger diff --git a/app/Core/ExternalLink/ExternalLinkInterface.php b/app/Core/ExternalLink/ExternalLinkInterface.php new file mode 100644 index 00000000..2dbc0a19 --- /dev/null +++ b/app/Core/ExternalLink/ExternalLinkInterface.php @@ -0,0 +1,36 @@ +providers, $provider); + return $this; + } + + /** + * Get provider + * + * @access public + * @param string $type + * @throws ExternalLinkProviderNotFound + * @return ExternalLinkProviderInterface + */ + public function getProvider($type) + { + foreach ($this->providers as $provider) { + if ($provider->getType() === $type) { + return $provider; + } + } + + throw new ExternalLinkProviderNotFound('Unable to find link provider: '.$type); + } + + /** + * Get link types + * + * @access public + * @return array + */ + public function getTypes() + { + $types = array(); + + foreach ($this->providers as $provider) { + $types[$provider->getType()] = $provider->getName(); + } + + asort($types); + + return array(self::TYPE_AUTO => t('Auto')) + $types; + } + + /** + * Get dependency label from a provider + * + * @access public + * @param string $type + * @param string $dependency + * @return string + */ + public function getDependencyLabel($type, $dependency) + { + $provider = $this->getProvider($type); + $dependencies = $provider->getDependencies(); + return isset($dependencies[$dependency]) ? $dependencies[$dependency] : $dependency; + } + + /** + * Find a provider that match + * + * @access public + * @throws ExternalLinkProviderNotFound + * @return ExternalLinkProviderInterface + */ + public function find() + { + $provider = null; + + if ($this->userInputType === self::TYPE_AUTO) { + $provider = $this->findProvider(); + } else { + $provider = $this->getProvider($this->userInputType); + $provider->setUserTextInput($this->userInputText); + } + + if ($provider === null) { + throw new ExternalLinkProviderNotFound('Unable to find link information from provided information'); + } + + return $provider; + } + + /** + * Set form values + * + * @access public + * @param array $values + * @return ExternalLinkManager + */ + public function setUserInput(array $values) + { + $this->userInputType = empty($values['type']) ? self::TYPE_AUTO : $values['type']; + $this->userInputText = empty($values['text']) ? '' : trim($values['text']); + return $this; + } + + /** + * Find a provider that user input + * + * @access private + * @return ExternalLinkProviderInterface + */ + private function findProvider() + { + foreach ($this->providers as $provider) { + $provider->setUserTextInput($this->userInputText); + + if ($provider->match()) { + return $provider; + } + } + + return null; + } +} diff --git a/app/Core/ExternalLink/ExternalLinkProviderInterface.php b/app/Core/ExternalLink/ExternalLinkProviderInterface.php new file mode 100644 index 00000000..c908e1eb --- /dev/null +++ b/app/Core/ExternalLink/ExternalLinkProviderInterface.php @@ -0,0 +1,71 @@ + t('Related'), + * 'child' => t('Child'), + * 'parent' => t('Parent'), + * 'self' => t('Self'), + * ] + * + * The dictionary key is saved in the database. + * + * @access public + * @return array + */ + public function getDependencies(); + + /** + * Set text entered by the user + * + * @access public + * @param string $input + */ + public function setUserTextInput($input); + + /** + * Return true if the provider can parse correctly the user input + * + * @access public + * @return boolean + */ + public function match(); + + /** + * Get the link found with the properties + * + * @access public + * @return ExternalLinkInterface + */ + public function getLink(); +} diff --git a/app/Core/ExternalLink/ExternalLinkProviderNotFound.php b/app/Core/ExternalLink/ExternalLinkProviderNotFound.php new file mode 100644 index 00000000..4fd05202 --- /dev/null +++ b/app/Core/ExternalLink/ExternalLinkProviderNotFound.php @@ -0,0 +1,15 @@ +doRequest('GET', $url, '', $headers); + } + /** * Send a GET HTTP request and parse JSON response * diff --git a/app/Core/Http/Response.php b/app/Core/Http/Response.php index 7fefddeb..a0d8137b 100644 --- a/app/Core/Http/Response.php +++ b/app/Core/Http/Response.php @@ -68,11 +68,12 @@ class Response extends Base * * @access public * @param string $url Redirection URL + * @param boolean $self If Ajax request and true: refresh the current page */ - public function redirect($url) + public function redirect($url, $self = false) { if ($this->request->getServerVariable('HTTP_X_REQUESTED_WITH') === 'XMLHttpRequest') { - header('X-Ajax-Redirect: '.$url); + header('X-Ajax-Redirect: '.($self ? 'self' : $url)); } else { header('Location: '.$url); } diff --git a/app/ExternalLink/AttachmentLink.php b/app/ExternalLink/AttachmentLink.php new file mode 100644 index 00000000..5a0d1344 --- /dev/null +++ b/app/ExternalLink/AttachmentLink.php @@ -0,0 +1,26 @@ +url, PHP_URL_PATH); + return basename($path); + } +} diff --git a/app/ExternalLink/AttachmentLinkProvider.php b/app/ExternalLink/AttachmentLinkProvider.php new file mode 100644 index 00000000..df27284f --- /dev/null +++ b/app/ExternalLink/AttachmentLinkProvider.php @@ -0,0 +1,117 @@ + t('Related'), + ); + } + + /** + * Return true if the provider can parse correctly the user input + * + * @access public + * @return boolean + */ + public function match() + { + if (preg_match('/^https?:\/\/.*\.([^\/]+)$/', $this->userInput, $matches)) { + return $this->isValidExtension($matches[1]); + } + + return false; + } + + /** + * Get the link found with the properties + * + * @access public + * @return ExternalLinkInterface + */ + public function getLink() + { + $link = new AttachmentLink($this->container); + $link->setUrl($this->userInput); + + return $link; + } + + /** + * Check file extension + * + * @access protected + * @param string $extension + * @return boolean + */ + protected function isValidExtension($extension) + { + $extension = strtolower($extension); + + foreach ($this->extensions as $ext) { + if ($extension === $ext) { + return false; + } + } + + return true; + } +} diff --git a/app/ExternalLink/BaseLink.php b/app/ExternalLink/BaseLink.php new file mode 100644 index 00000000..08693ae7 --- /dev/null +++ b/app/ExternalLink/BaseLink.php @@ -0,0 +1,44 @@ +url; + } + + /** + * Set link URL + * + * @access public + * @param string $url + */ + public function setUrl($url) + { + $this->url = $url; + } +} diff --git a/app/ExternalLink/BaseLinkProvider.php b/app/ExternalLink/BaseLinkProvider.php new file mode 100644 index 00000000..749cda94 --- /dev/null +++ b/app/ExternalLink/BaseLinkProvider.php @@ -0,0 +1,33 @@ +userInput = trim($input); + } +} diff --git a/app/ExternalLink/WebLink.php b/app/ExternalLink/WebLink.php new file mode 100644 index 00000000..9338ca42 --- /dev/null +++ b/app/ExternalLink/WebLink.php @@ -0,0 +1,37 @@ +httpClient->get($this->url); + + if (preg_match('/(.*)<\/title>/siU', $html, $matches)) { + return trim($matches[1]); + } + + $components = parse_url($this->url); + + if (! empty($components['host']) && ! empty($components['path'])) { + return $components['host'].$components['path']; + } + + return t('Title not found'); + } +} diff --git a/app/ExternalLink/WebLinkProvider.php b/app/ExternalLink/WebLinkProvider.php new file mode 100644 index 00000000..ea6dc132 --- /dev/null +++ b/app/ExternalLink/WebLinkProvider.php @@ -0,0 +1,77 @@ +<?php + +namespace Kanboard\ExternalLink; + +use Kanboard\Core\ExternalLink\ExternalLinkProviderInterface; + +/** + * Web Link Provider + * + * @package externalLink + * @author Frederic Guillot + */ +class WebLinkProvider extends BaseLinkProvider implements ExternalLinkProviderInterface +{ + /** + * Get provider name + * + * @access public + * @return string + */ + public function getName() + { + return t('Web Link'); + } + + /** + * Get link type + * + * @access public + * @return string + */ + public function getType() + { + return 'weblink'; + } + + /** + * Get a dictionary of supported dependency types by the provider + * + * @access public + * @return array + */ + public function getDependencies() + { + return array( + 'related' => t('Related'), + ); + } + + /** + * Return true if the provider can parse correctly the user input + * + * @access public + * @return boolean + */ + public function match() + { + $startWithHttp = strpos($this->userInput, 'http://') === 0 || strpos($this->userInput, 'https://') === 0; + $validUrl = filter_var($this->userInput, FILTER_VALIDATE_URL); + + return $startWithHttp && $validUrl; + } + + /** + * Get the link found with the properties + * + * @access public + * @return ExternalLinkInterface + */ + public function getLink() + { + $link = new WebLink($this->container); + $link->setUrl($this->userInput); + + return $link; + } +} diff --git a/app/Locale/bs_BA/translations.php b/app/Locale/bs_BA/translations.php index 208f7d77..af090d23 100644 --- a/app/Locale/bs_BA/translations.php +++ b/app/Locale/bs_BA/translations.php @@ -1097,4 +1097,30 @@ return array( // 'Highest priority' => '', // 'If you put zero to the low and high priority, this feature will be disabled.' => '', // 'Priority: %d' => '', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/cs_CZ/translations.php b/app/Locale/cs_CZ/translations.php index 70d2815b..6dacadcb 100644 --- a/app/Locale/cs_CZ/translations.php +++ b/app/Locale/cs_CZ/translations.php @@ -1097,4 +1097,30 @@ return array( // 'Highest priority' => '', // 'If you put zero to the low and high priority, this feature will be disabled.' => '', // 'Priority: %d' => '', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/da_DK/translations.php b/app/Locale/da_DK/translations.php index db32e047..e30eac46 100644 --- a/app/Locale/da_DK/translations.php +++ b/app/Locale/da_DK/translations.php @@ -1097,4 +1097,30 @@ return array( // 'Highest priority' => '', // 'If you put zero to the low and high priority, this feature will be disabled.' => '', // 'Priority: %d' => '', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/de_DE/translations.php b/app/Locale/de_DE/translations.php index 55d144d6..77835aa2 100644 --- a/app/Locale/de_DE/translations.php +++ b/app/Locale/de_DE/translations.php @@ -1097,4 +1097,30 @@ return array( 'Highest priority' => 'Höchste Priorität', 'If you put zero to the low and high priority, this feature will be disabled.' => 'Wenn Sie Null bei höchster und niedrigster Priorität eintragen, wird diese Funktion deaktiviert.', 'Priority: %d' => 'Priorität: %d', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/el_GR/translations.php b/app/Locale/el_GR/translations.php index e87d1daa..d75c25d1 100644 --- a/app/Locale/el_GR/translations.php +++ b/app/Locale/el_GR/translations.php @@ -1097,4 +1097,30 @@ return array( // 'Highest priority' => '', // 'If you put zero to the low and high priority, this feature will be disabled.' => '', // 'Priority: %d' => '', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/es_ES/translations.php b/app/Locale/es_ES/translations.php index c8bc0d7b..02dd6790 100644 --- a/app/Locale/es_ES/translations.php +++ b/app/Locale/es_ES/translations.php @@ -1097,4 +1097,30 @@ return array( // 'Highest priority' => '', // 'If you put zero to the low and high priority, this feature will be disabled.' => '', // 'Priority: %d' => '', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/fi_FI/translations.php b/app/Locale/fi_FI/translations.php index 6ae4a373..df20259a 100644 --- a/app/Locale/fi_FI/translations.php +++ b/app/Locale/fi_FI/translations.php @@ -1097,4 +1097,30 @@ return array( // 'Highest priority' => '', // 'If you put zero to the low and high priority, this feature will be disabled.' => '', // 'Priority: %d' => '', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/fr_FR/translations.php b/app/Locale/fr_FR/translations.php index f04f95ba..df74e5b4 100644 --- a/app/Locale/fr_FR/translations.php +++ b/app/Locale/fr_FR/translations.php @@ -1100,4 +1100,30 @@ return array( 'Highest priority' => 'Priorité haute', 'If you put zero to the low and high priority, this feature will be disabled.' => 'Si vous mettez zéro pour la priorité basse et haute, cette fonctionnalité sera désactivée.', 'Priority: %d' => 'Priorité : %d', + 'Close a task when there is no activity' => 'Fermer une tâche sans activité', + 'Duration in days' => 'Durée en jours', + 'Send email when there is no activity on a task' => 'Envoyer un email lorsqu\'il n\'y a pas d\'activité sur une tâche', + 'List of external links' => 'Liste des liens externes', + 'Unable to fetch link information.' => 'Impossible de récupérer les informations sur le lien.', + 'Daily background job for tasks' => 'Tâche planifée quotidienne pour les tâches', + 'Auto' => 'Auto', + 'Related' => 'Relié', + 'Attachment' => 'Pièce-jointe', + 'Title not found' => 'Titre non trouvé', + 'Web Link' => 'Lien web', + 'External links' => 'Liens externes', + 'Add external link' => 'Ajouter un lien externe', + 'Type' => 'Type', + 'Dependency' => 'Dépendance', + 'View internal links' => 'Voir les liens internes', + 'View external links' => 'Voir les liens externes', + 'Add internal link' => 'Ajouter un lien interne', + 'Add a new external link' => 'Ajouter un nouveau lien externe', + 'Edit external link' => 'Modifier un lien externe', + 'External link' => 'Lien externe', + 'Copy and paste your link here...' => 'Copier-coller vôtre lien ici...', + 'URL' => 'URL', + 'There is no external link for the moment.' => 'Il n\'y a pas de lien externe pour le moment.', + 'Internal links' => 'Liens internes', + 'There is no internal link for the moment.' => 'Il n\'y a pas de lien interne pour le moment.', ); diff --git a/app/Locale/hu_HU/translations.php b/app/Locale/hu_HU/translations.php index 5f34ac56..b8d6d1b6 100644 --- a/app/Locale/hu_HU/translations.php +++ b/app/Locale/hu_HU/translations.php @@ -1097,4 +1097,30 @@ return array( // 'Highest priority' => '', // 'If you put zero to the low and high priority, this feature will be disabled.' => '', // 'Priority: %d' => '', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/id_ID/translations.php b/app/Locale/id_ID/translations.php index 5c9be946..02ee727c 100644 --- a/app/Locale/id_ID/translations.php +++ b/app/Locale/id_ID/translations.php @@ -1097,4 +1097,30 @@ return array( // 'Highest priority' => '', // 'If you put zero to the low and high priority, this feature will be disabled.' => '', // 'Priority: %d' => '', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/it_IT/translations.php b/app/Locale/it_IT/translations.php index 4ff71c7b..b9c45642 100644 --- a/app/Locale/it_IT/translations.php +++ b/app/Locale/it_IT/translations.php @@ -1097,4 +1097,30 @@ return array( 'Highest priority' => 'Priorità massima', 'If you put zero to the low and high priority, this feature will be disabled.' => 'Se imposti a zero la priorità massima e minima, questa funzionalità sarà disabilitata.', 'Priority: %d' => 'Priorità: %d', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/ja_JP/translations.php b/app/Locale/ja_JP/translations.php index 21961f3b..a16c44e8 100644 --- a/app/Locale/ja_JP/translations.php +++ b/app/Locale/ja_JP/translations.php @@ -1097,4 +1097,30 @@ return array( // 'Highest priority' => '', // 'If you put zero to the low and high priority, this feature will be disabled.' => '', // 'Priority: %d' => '', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/my_MY/translations.php b/app/Locale/my_MY/translations.php index 643f5b4a..93185888 100644 --- a/app/Locale/my_MY/translations.php +++ b/app/Locale/my_MY/translations.php @@ -1097,4 +1097,30 @@ return array( // 'Highest priority' => '', // 'If you put zero to the low and high priority, this feature will be disabled.' => '', // 'Priority: %d' => '', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/nb_NO/translations.php b/app/Locale/nb_NO/translations.php index 66ccd125..b35dce3d 100644 --- a/app/Locale/nb_NO/translations.php +++ b/app/Locale/nb_NO/translations.php @@ -1097,4 +1097,30 @@ return array( // 'Highest priority' => '', // 'If you put zero to the low and high priority, this feature will be disabled.' => '', // 'Priority: %d' => '', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/nl_NL/translations.php b/app/Locale/nl_NL/translations.php index d7268df3..0117a978 100644 --- a/app/Locale/nl_NL/translations.php +++ b/app/Locale/nl_NL/translations.php @@ -1097,4 +1097,30 @@ return array( // 'Highest priority' => '', // 'If you put zero to the low and high priority, this feature will be disabled.' => '', // 'Priority: %d' => '', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/pl_PL/translations.php b/app/Locale/pl_PL/translations.php index 222f1fe2..4f4f840f 100644 --- a/app/Locale/pl_PL/translations.php +++ b/app/Locale/pl_PL/translations.php @@ -1097,4 +1097,30 @@ return array( // 'Highest priority' => '', // 'If you put zero to the low and high priority, this feature will be disabled.' => '', // 'Priority: %d' => '', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/pt_BR/translations.php b/app/Locale/pt_BR/translations.php index 155d4e83..1b5b35a6 100644 --- a/app/Locale/pt_BR/translations.php +++ b/app/Locale/pt_BR/translations.php @@ -1097,4 +1097,30 @@ return array( // 'Highest priority' => '', // 'If you put zero to the low and high priority, this feature will be disabled.' => '', // 'Priority: %d' => '', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/pt_PT/translations.php b/app/Locale/pt_PT/translations.php index 99bb096f..baa8eab0 100644 --- a/app/Locale/pt_PT/translations.php +++ b/app/Locale/pt_PT/translations.php @@ -1097,4 +1097,30 @@ return array( 'Highest priority' => 'Prioridade mais alta', 'If you put zero to the low and high priority, this feature will be disabled.' => 'Se colocar zero na prioridade baixa ou alta, essa funcionalidade será desactivada.', 'Priority: %d' => 'Prioridade: %d', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/ru_RU/translations.php b/app/Locale/ru_RU/translations.php index 93419c5b..3aa717b9 100644 --- a/app/Locale/ru_RU/translations.php +++ b/app/Locale/ru_RU/translations.php @@ -1097,4 +1097,30 @@ return array( 'Highest priority' => 'Нивысший приоритет', 'If you put zero to the low and high priority, this feature will be disabled.' => 'Если Вы введете 0 для наименьшего и наивысшего приоритета, этот функционал будет отключен.', 'Priority: %d' => 'Приоритет: %d', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/sr_Latn_RS/translations.php b/app/Locale/sr_Latn_RS/translations.php index 430a299f..4b320fe7 100644 --- a/app/Locale/sr_Latn_RS/translations.php +++ b/app/Locale/sr_Latn_RS/translations.php @@ -1097,4 +1097,30 @@ return array( // 'Highest priority' => '', // 'If you put zero to the low and high priority, this feature will be disabled.' => '', // 'Priority: %d' => '', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/sv_SE/translations.php b/app/Locale/sv_SE/translations.php index a4d61922..c225cc05 100644 --- a/app/Locale/sv_SE/translations.php +++ b/app/Locale/sv_SE/translations.php @@ -1097,4 +1097,30 @@ return array( // 'Highest priority' => '', // 'If you put zero to the low and high priority, this feature will be disabled.' => '', // 'Priority: %d' => '', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/th_TH/translations.php b/app/Locale/th_TH/translations.php index e6411efc..7fa53c6f 100644 --- a/app/Locale/th_TH/translations.php +++ b/app/Locale/th_TH/translations.php @@ -1097,4 +1097,30 @@ return array( // 'Highest priority' => '', // 'If you put zero to the low and high priority, this feature will be disabled.' => '', // 'Priority: %d' => '', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/tr_TR/translations.php b/app/Locale/tr_TR/translations.php index a628d6f8..35613345 100644 --- a/app/Locale/tr_TR/translations.php +++ b/app/Locale/tr_TR/translations.php @@ -1097,4 +1097,30 @@ return array( // 'Highest priority' => '', // 'If you put zero to the low and high priority, this feature will be disabled.' => '', // 'Priority: %d' => '', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Locale/zh_CN/translations.php b/app/Locale/zh_CN/translations.php index 8df2c01b..9de31cdd 100644 --- a/app/Locale/zh_CN/translations.php +++ b/app/Locale/zh_CN/translations.php @@ -1097,4 +1097,30 @@ return array( // 'Highest priority' => '', // 'If you put zero to the low and high priority, this feature will be disabled.' => '', // 'Priority: %d' => '', + // 'Close a task when there is no activity' => '', + // 'Duration in days' => '', + // 'Send email when there is no activity on a task' => '', + // 'List of external links' => '', + // 'Unable to fetch link information.' => '', + // 'Daily background job for tasks' => '', + // 'Auto' => '', + // 'Related' => '', + // 'Attachment' => '', + // 'Title not found' => '', + // 'Web Link' => '', + // 'External links' => '', + // 'Add external link' => '', + // 'Type' => '', + // 'Dependency' => '', + // 'View internal links' => '', + // 'View external links' => '', + // 'Add internal link' => '', + // 'Add a new external link' => '', + // 'Edit external link' => '', + // 'External link' => '', + // 'Copy and paste your link here...' => '', + // 'URL' => '', + // 'There is no external link for the moment.' => '', + // 'Internal links' => '', + // 'There is no internal link for the moment.' => '', ); diff --git a/app/Model/TaskExternalLink.php b/app/Model/TaskExternalLink.php new file mode 100644 index 00000000..f2c756b4 --- /dev/null +++ b/app/Model/TaskExternalLink.php @@ -0,0 +1,99 @@ +<?php + +namespace Kanboard\Model; + +/** + * Task External Link Model + * + * @package model + * @author Frederic Guillot + */ +class TaskExternalLink extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'task_has_external_links'; + + /** + * Get all links + * + * @access public + * @param integer $task_id + * @return array + */ + public function getAll($task_id) + { + $types = $this->externalLinkManager->getTypes(); + + $links = $this->db->table(self::TABLE) + ->columns(self::TABLE.'.*', User::TABLE.'.name AS creator_name', User::TABLE.'.username AS creator_username') + ->eq('task_id', $task_id) + ->asc('title') + ->join(User::TABLE, 'id', 'creator_id') + ->findAll(); + + foreach ($links as &$link) { + $link['dependency_label'] = $this->externalLinkManager->getDependencyLabel($link['link_type'], $link['dependency']); + $link['type'] = isset($types[$link['link_type']]) ? $types[$link['link_type']] : t('Unknown'); + } + + return $links; + } + + /** + * Get link + * + * @access public + * @param integer $link_id + * @return array + */ + public function getById($link_id) + { + return $this->db->table(self::TABLE)->eq('id', $link_id)->findOne(); + } + + /** + * Add a new link in the database + * + * @access public + * @param array $values Form values + * @return boolean|integer + */ + public function create(array $values) + { + unset($values['id']); + $values['creator_id'] = $this->userSession->getId(); + $values['date_creation'] = time(); + $values['date_modification'] = $values['date_creation']; + + return $this->persist(self::TABLE, $values); + } + + /** + * Modify external link + * + * @access public + * @param array $values Form values + * @return boolean + */ + public function update(array $values) + { + $values['date_modification'] = time(); + return $this->db->table(self::TABLE)->eq('id', $values['id'])->update($values); + } + + /** + * Remove a link + * + * @access public + * @param integer $link_id + * @return boolean + */ + public function remove($link_id) + { + return $this->db->table(self::TABLE)->eq('id', $link_id)->remove(); + } +} diff --git a/app/Model/TaskFinder.php b/app/Model/TaskFinder.php index 1c83136b..ab290bce 100644 --- a/app/Model/TaskFinder.php +++ b/app/Model/TaskFinder.php @@ -88,11 +88,12 @@ class TaskFinder extends Base return $this->db ->table(Task::TABLE) ->columns( - '(SELECT count(*) FROM '.Comment::TABLE.' WHERE task_id=tasks.id) AS nb_comments', - '(SELECT count(*) FROM '.File::TABLE.' WHERE task_id=tasks.id) AS nb_files', - '(SELECT count(*) FROM '.Subtask::TABLE.' WHERE '.Subtask::TABLE.'.task_id=tasks.id) AS nb_subtasks', - '(SELECT count(*) FROM '.Subtask::TABLE.' WHERE '.Subtask::TABLE.'.task_id=tasks.id AND status=2) AS nb_completed_subtasks', - '(SELECT count(*) FROM '.TaskLink::TABLE.' WHERE '.TaskLink::TABLE.'.task_id = tasks.id) AS nb_links', + '(SELECT COUNT(*) FROM '.Comment::TABLE.' WHERE task_id=tasks.id) AS nb_comments', + '(SELECT COUNT(*) FROM '.File::TABLE.' WHERE task_id=tasks.id) AS nb_files', + '(SELECT COUNT(*) FROM '.Subtask::TABLE.' WHERE '.Subtask::TABLE.'.task_id=tasks.id) AS nb_subtasks', + '(SELECT COUNT(*) FROM '.Subtask::TABLE.' WHERE '.Subtask::TABLE.'.task_id=tasks.id AND status=2) AS nb_completed_subtasks', + '(SELECT COUNT(*) FROM '.TaskLink::TABLE.' WHERE '.TaskLink::TABLE.'.task_id = tasks.id) AS nb_links', + '(SELECT COUNT(*) FROM '.TaskExternalLink::TABLE.' WHERE '.TaskExternalLink::TABLE.'.task_id = tasks.id) AS nb_external_links', '(SELECT DISTINCT 1 FROM '.TaskLink::TABLE.' WHERE '.TaskLink::TABLE.'.task_id = tasks.id AND '.TaskLink::TABLE.'.link_id = 9) AS is_milestone', 'tasks.id', 'tasks.reference', diff --git a/app/Model/User.php b/app/Model/User.php index 0174a040..dd622207 100644 --- a/app/Model/User.php +++ b/app/Model/User.php @@ -265,7 +265,7 @@ class User extends Base * * @access public * @param array $values Form values - * @return array + * @return boolean */ public function update(array $values) { diff --git a/app/Schema/Mysql.php b/app/Schema/Mysql.php index 8f1db510..5433c751 100644 --- a/app/Schema/Mysql.php +++ b/app/Schema/Mysql.php @@ -6,7 +6,25 @@ use PDO; use Kanboard\Core\Security\Token; use Kanboard\Core\Security\Role; -const VERSION = 103; +const VERSION = 104; + +function version_104(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE task_has_external_links ( + id INT NOT NULL AUTO_INCREMENT, + link_type VARCHAR(100) NOT NULL, + dependency VARCHAR(100) NOT NULL, + title VARCHAR(255) NOT NULL, + url VARCHAR(255) NOT NULL, + date_creation INT NOT NULL, + date_modification INT NOT NULL, + task_id INT NOT NULL, + creator_id INT DEFAULT 0, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE + ) ENGINE=InnoDB CHARSET=utf8 + "); +} function version_103(PDO $pdo) { diff --git a/app/Schema/Postgres.php b/app/Schema/Postgres.php index a7bf8054..363b633b 100644 --- a/app/Schema/Postgres.php +++ b/app/Schema/Postgres.php @@ -6,7 +6,25 @@ use PDO; use Kanboard\Core\Security\Token; use Kanboard\Core\Security\Role; -const VERSION = 83; +const VERSION = 84; + +function version_84(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE task_has_external_links ( + id SERIAL, + link_type VARCHAR(100) NOT NULL, + dependency VARCHAR(100) NOT NULL, + title VARCHAR(255) NOT NULL, + url VARCHAR(255) NOT NULL, + date_creation INT NOT NULL, + date_modification INT NOT NULL, + task_id INT NOT NULL, + creator_id INT DEFAULT 0, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE + ) + "); +} function version_83(PDO $pdo) { diff --git a/app/Schema/Sqlite.php b/app/Schema/Sqlite.php index 08689749..bc701341 100644 --- a/app/Schema/Sqlite.php +++ b/app/Schema/Sqlite.php @@ -6,7 +6,25 @@ use Kanboard\Core\Security\Token; use Kanboard\Core\Security\Role; use PDO; -const VERSION = 95; +const VERSION = 96; + +function version_96(PDO $pdo) +{ + $pdo->exec(" + CREATE TABLE task_has_external_links ( + id INTEGER PRIMARY KEY, + link_type TEXT NOT NULL, + dependency TEXT NOT NULL, + title TEXT NOT NULL, + url TEXT NOT NULL, + date_creation INTEGER NOT NULL, + date_modification INTEGER NOT NULL, + task_id INTEGER NOT NULL, + creator_id INTEGER DEFAULT 0, + FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE + ) + "); +} function version_95(PDO $pdo) { diff --git a/app/ServiceProvider/AuthenticationProvider.php b/app/ServiceProvider/AuthenticationProvider.php index ed0962b6..144abb0e 100644 --- a/app/ServiceProvider/AuthenticationProvider.php +++ b/app/ServiceProvider/AuthenticationProvider.php @@ -89,6 +89,9 @@ class AuthenticationProvider implements ServiceProviderInterface $acl->add('Taskduplication', '*', Role::PROJECT_MEMBER); $acl->add('TaskImport', '*', Role::PROJECT_MANAGER); $acl->add('Tasklink', '*', Role::PROJECT_MEMBER); + $acl->add('Tasklink', array('show'), Role::PROJECT_VIEWER); + $acl->add('TaskExternalLink', '*', Role::PROJECT_MEMBER); + $acl->add('TaskExternalLink', array('show'), Role::PROJECT_VIEWER); $acl->add('Taskmodification', '*', Role::PROJECT_MEMBER); $acl->add('Taskstatus', '*', Role::PROJECT_MEMBER); $acl->add('Timer', '*', Role::PROJECT_MEMBER); diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php index df4e183b..61a4c512 100644 --- a/app/ServiceProvider/ClassProvider.php +++ b/app/ServiceProvider/ClassProvider.php @@ -61,6 +61,7 @@ class ClassProvider implements ServiceProviderInterface 'TaskCreation', 'TaskDuplication', 'TaskExport', + 'TaskExternalLink', 'TaskFinder', 'TaskFilter', 'TaskLink', @@ -97,6 +98,7 @@ class ClassProvider implements ServiceProviderInterface 'CommentValidator', 'CurrencyValidator', 'CustomFilterValidator', + 'ExternalLinkValidator', 'GroupValidator', 'LinkValidator', 'PasswordResetValidator', diff --git a/app/ServiceProvider/ExternalLinkProvider.php b/app/ServiceProvider/ExternalLinkProvider.php new file mode 100644 index 00000000..c4bbc4cf --- /dev/null +++ b/app/ServiceProvider/ExternalLinkProvider.php @@ -0,0 +1,34 @@ +<?php + +namespace Kanboard\ServiceProvider; + +use Pimple\Container; +use Pimple\ServiceProviderInterface; +use Kanboard\Core\ExternalLink\ExternalLinkManager; +use Kanboard\ExternalLink\WebLinkProvider; +use Kanboard\ExternalLink\AttachmentLinkProvider; + +/** + * External Link Provider + * + * @package serviceProvider + * @author Frederic Guillot + */ +class ExternalLinkProvider implements ServiceProviderInterface +{ + /** + * Register providers + * + * @access public + * @param \Pimple\Container $container + * @return \Pimple\Container + */ + public function register(Container $container) + { + $container['externalLinkManager'] = new ExternalLinkManager($container); + $container['externalLinkManager']->register(new WebLinkProvider($container)); + $container['externalLinkManager']->register(new AttachmentLinkProvider($container)); + + return $container; + } +} diff --git a/app/ServiceProvider/RouteProvider.php b/app/ServiceProvider/RouteProvider.php index dd9ee23b..ebe087ae 100644 --- a/app/ServiceProvider/RouteProvider.php +++ b/app/ServiceProvider/RouteProvider.php @@ -106,11 +106,18 @@ class RouteProvider implements ServiceProviderInterface $container['route']->addRoute('project/:project_id/task/:task_id/screenshot', 'file', 'screenshot'); $container['route']->addRoute('project/:project_id/task/:task_id/upload', 'file', 'create'); $container['route']->addRoute('project/:project_id/task/:task_id/comment', 'comment', 'create'); + $container['route']->addRoute('project/:project_id/task/:task_id/links', 'tasklink', 'show'); $container['route']->addRoute('project/:project_id/task/:task_id/link', 'tasklink', 'create'); $container['route']->addRoute('project/:project_id/task/:task_id/transitions', 'task', 'transitions'); $container['route']->addRoute('project/:project_id/task/:task_id/analytics', 'task', 'analytics'); $container['route']->addRoute('project/:project_id/task/:task_id/remove', 'task', 'remove'); + $container['route']->addRoute('project/:project_id/task/:task_id/links/external', 'TaskExternalLink', 'show'); + $container['route']->addRoute('project/:project_id/task/:task_id/link/external/new', 'TaskExternalLink', 'find'); + $container['route']->addRoute('project/:project_id/task/:task_id/link/external/save', 'TaskExternalLink', 'create'); + $container['route']->addRoute('project/:project_id/task/:task_id/link/external/:link_id', 'TaskExternalLink', 'edit'); + $container['route']->addRoute('project/:project_id/task/:task_id/link/external/:link_id/remove', 'TaskExternalLink', 'confirm'); + $container['route']->addRoute('project/:project_id/task/:task_id/edit', 'taskmodification', 'edit'); $container['route']->addRoute('project/:project_id/task/:task_id/description', 'taskmodification', 'description'); $container['route']->addRoute('project/:project_id/task/:task_id/recurrence', 'taskmodification', 'recurrence'); diff --git a/app/Template/board/task_footer.php b/app/Template/board/task_footer.php index 26f3b1d4..1912dd83 100644 --- a/app/Template/board/task_footer.php +++ b/app/Template/board/task_footer.php @@ -35,7 +35,11 @@ <?php endif ?> <?php if (! empty($task['nb_links'])): ?> - <span title="<?= t('Links') ?>" class="tooltip" data-href="<?= $this->url->href('BoardTooltip', 'tasklinks', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><i class="fa fa-code-fork"></i> <?= $task['nb_links'] ?></span> + <span title="<?= t('Links') ?>" class="tooltip" data-href="<?= $this->url->href('BoardTooltip', 'tasklinks', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><i class="fa fa-code-fork fa-fw"></i><?= $task['nb_links'] ?></span> + <?php endif ?> + + <?php if (! empty($task['nb_external_links'])): ?> + <span title="<?= t('External links') ?>" class="tooltip" data-href="<?= $this->url->href('BoardTooltip', 'externallinks', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><i class="fa fa-external-link fa-fw"></i><?= $task['nb_external_links'] ?></span> <?php endif ?> <?php if (! empty($task['nb_subtasks'])): ?> diff --git a/app/Template/board/task_menu.php b/app/Template/board/task_menu.php index b5ed125d..9e26e15b 100644 --- a/app/Template/board/task_menu.php +++ b/app/Template/board/task_menu.php @@ -7,6 +7,7 @@ <li><i class="fa fa-pencil-square-o fa-fw"></i> <?= $this->url->link(t('Edit this task'), 'taskmodification', 'edit', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?></li> <li><i class="fa fa-comment-o fa-fw"></i> <?= $this->url->link(t('Add a comment'), 'comment', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?></li> <li><i class="fa fa-code-fork fa-fw"></i> <?= $this->url->link(t('Add a link'), 'tasklink', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?></li> + <li><i class="fa fa-external-link fa-fw"></i> <?= $this->url->link(t('Add external link'), 'TaskExternalLink', 'find', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?></li> <li><i class="fa fa-camera fa-fw"></i> <?= $this->url->link(t('Add a screenshot'), 'BoardPopover', 'screenshot', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?></li> <?php if ($task['is_active'] == 1): ?> <li><i class="fa fa-close fa-fw"></i> <?= $this->url->link(t('Close this task'), 'taskstatus', 'close', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'redirect' => 'board'), false, 'popover') ?></li> diff --git a/app/Template/board/tooltip_external_links.php b/app/Template/board/tooltip_external_links.php new file mode 100644 index 00000000..7681c06c --- /dev/null +++ b/app/Template/board/tooltip_external_links.php @@ -0,0 +1,20 @@ +<table class="table-striped table-small"> + <tr> + <th class="column-20"><?= t('Type') ?></th> + <th class="column-80"><?= t('Title') ?></th> + <th class="column-10"><?= t('Dependency') ?></th> + </tr> + <?php foreach ($links as $link): ?> + <tr> + <td> + <?= $link['type'] ?> + </td> + <td> + <a href="<?= $link['url'] ?>" target="_blank"><?= $this->e($link['title']) ?></a> + </td> + <td> + <?= $this->e($link['dependency_label']) ?> + </td> + </tr> + <?php endforeach ?> +</table> \ No newline at end of file diff --git a/app/Template/comment/create.php b/app/Template/comment/create.php index e9a6404d..8ce9aac3 100644 --- a/app/Template/comment/create.php +++ b/app/Template/comment/create.php @@ -2,7 +2,7 @@ <h2><?= t('Add a comment') ?></h2> </div> -<form method="post" action="<?= $this->url->href('comment', 'save', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'ajax' => isset($ajax))) ?>" autocomplete="off" class="form-comment"> +<form method="post" action="<?= $this->url->href('comment', 'save', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'ajax' => $ajax)) ?>" autocomplete="off" class="form-comment"> <?= $this->form->csrf() ?> <?= $this->form->hidden('task_id', $values) ?> <?= $this->form->hidden('user_id', $values) ?> @@ -41,7 +41,7 @@ <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> <?php if (! isset($skip_cancel)): ?> <?= t('or') ?> - <?php if (isset($ajax)): ?> + <?php if ($ajax): ?> <?= $this->url->link(t('cancel'), 'board', 'show', array('project_id' => $task['project_id'])) ?> <?php else: ?> <?= $this->url->link(t('cancel'), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> diff --git a/app/Template/task/comments.php b/app/Template/task/comments.php index 57fb305f..ef85287b 100644 --- a/app/Template/task/comments.php +++ b/app/Template/task/comments.php @@ -28,7 +28,8 @@ 'task_id' => $task['id'], ), 'errors' => array(), - 'task' => $task + 'task' => $task, + 'ajax' => $ajax, )) ?> <?php endif ?> </div> diff --git a/app/Template/task/show.php b/app/Template/task/show.php index f6d47e53..b848d49e 100644 --- a/app/Template/task/show.php +++ b/app/Template/task/show.php @@ -13,14 +13,6 @@ <?= $this->render('task/description', array('task' => $task)) ?> -<?= $this->render('tasklink/show', array( - 'task' => $task, - 'links' => $links, - 'link_label_list' => $link_label_list, - 'editable' => $this->user->hasProjectAccess('tasklink', 'edit', $project['id']), - 'is_public' => false, -)) ?> - <?= $this->render('subtask/show', array( 'task' => $task, 'subtasks' => $subtasks, @@ -29,6 +21,14 @@ 'editable' => $this->user->hasProjectAccess('subtask', 'edit', $project['id']), )) ?> +<?= $this->render('tasklink/show', array( + 'task' => $task, + 'links' => $links, + 'link_label_list' => $link_label_list, + 'editable' => $this->user->hasProjectAccess('tasklink', 'edit', $project['id']), + 'is_public' => false, +)) ?> + <?= $this->render('task/time_tracking_summary', array('task' => $task)) ?> <?= $this->render('file/show', array( @@ -42,4 +42,5 @@ 'comments' => $comments, 'project' => $project, 'editable' => $this->user->hasProjectAccess('comment', 'edit', $project['id']), + 'ajax' => $ajax, )) ?> diff --git a/app/Template/task/sidebar.php b/app/Template/task/sidebar.php index f522c1c4..b5a2c4b4 100644 --- a/app/Template/task/sidebar.php +++ b/app/Template/task/sidebar.php @@ -21,6 +21,25 @@ <?= $this->hook->render('template:task:sidebar:information') ?> </ul> + + <h2><?= t('Links') ?></h2> + <ul> + <li <?= $this->app->checkMenuSelection('tasklink', 'show') ?>> + <?= $this->url->link(t('View internal links'), 'tasklink', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> + </li> + <li <?= $this->app->checkMenuSelection('TaskExternalLink', 'show') ?>> + <?= $this->url->link(t('View external links'), 'TaskExternalLink', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> + </li> + <?php if ($this->user->hasProjectAccess('tasklink', 'create', $task['project_id'])): ?> + <li <?= $this->app->checkMenuSelection('tasklink', 'create') ?>> + <?= $this->url->link(t('Add internal link'), 'tasklink', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> + </li> + <li <?= $this->app->checkMenuSelection('TaskExternalLink', 'find') ?>> + <?= $this->url->link(t('Add external link'), 'TaskExternalLink', 'find', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> + </li> + <?php endif ?> + </ul> + <?php if ($this->user->hasProjectAccess('taskmodification', 'edit', $task['project_id'])): ?> <h2><?= t('Actions') ?></h2> <ul> @@ -36,9 +55,6 @@ <li <?= $this->app->checkMenuSelection('subtask', 'create') ?>> <?= $this->url->link(t('Add a sub-task'), 'subtask', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> </li> - <li <?= $this->app->checkMenuSelection('tasklink', 'create') ?>> - <?= $this->url->link(t('Add a link'), 'tasklink', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> - </li> <li <?= $this->app->checkMenuSelection('comment', 'create') ?>> <?= $this->url->link(t('Add a comment'), 'comment', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> </li> diff --git a/app/Template/task_creation/form.php b/app/Template/task_creation/form.php index eaf9024d..01b000f2 100644 --- a/app/Template/task_creation/form.php +++ b/app/Template/task_creation/form.php @@ -10,7 +10,7 @@ </div> <?php endif ?> -<form id="task-form" method="post" action="<?= $this->url->href('taskcreation', 'save', array('project_id' => $values['project_id'])) ?>" autocomplete="off"> +<form id="task-form" class="popover-form" method="post" action="<?= $this->url->href('taskcreation', 'save', array('project_id' => $values['project_id'])) ?>" autocomplete="off"> <?= $this->form->csrf() ?> diff --git a/app/Template/task_external_link/create.php b/app/Template/task_external_link/create.php new file mode 100644 index 00000000..3179d6af --- /dev/null +++ b/app/Template/task_external_link/create.php @@ -0,0 +1,13 @@ +<div class="page-header"> + <h2><?= t('Add a new external link') ?></h2> +</div> + +<form class="popover-form" action="<?= $this->url->href('TaskExternalLink', 'save', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'ajax' => $ajax)) ?>" method="post" autocomplete="off"> + <?= $this->render('task_external_link/form', array('task' => $task, 'dependencies' => $dependencies, 'values' => $values, 'errors' => $errors)) ?> + + <div class="form-actions"> + <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"> + <?= t('or') ?> + <?= $this->url->link(t('cancel'), 'TaskExternalLink', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'close-popover') ?> + </div> +</form> \ No newline at end of file diff --git a/app/Template/task_external_link/edit.php b/app/Template/task_external_link/edit.php new file mode 100644 index 00000000..cf9ddfed --- /dev/null +++ b/app/Template/task_external_link/edit.php @@ -0,0 +1,13 @@ +<div class="page-header"> + <h2><?= t('Edit external link') ?></h2> +</div> + +<form class="popover-form" action="<?= $this->url->href('TaskExternalLink', 'update', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'ajax' => $ajax)) ?>" method="post" autocomplete="off"> + <?= $this->render('task_external_link/form', array('task' => $task, 'dependencies' => $dependencies, 'values' => $values, 'errors' => $errors)) ?> + + <div class="form-actions"> + <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"> + <?= t('or') ?> + <?= $this->url->link(t('cancel'), 'TaskExternalLink', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'close-popover') ?> + </div> +</form> \ No newline at end of file diff --git a/app/Template/task_external_link/find.php b/app/Template/task_external_link/find.php new file mode 100644 index 00000000..a2304014 --- /dev/null +++ b/app/Template/task_external_link/find.php @@ -0,0 +1,32 @@ +<div class="page-header"> + <h2><?= t('Add a new external link') ?></h2> +</div> + +<form class="popover-form" action="<?= $this->url->href('TaskExternalLink', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'ajax' => $ajax)) ?>" method="post" autocomplete="off"> + <?= $this->form->csrf() ?> + <?= $this->form->hidden('task_id', array('task_id' => $task['id'])) ?> + + <?= $this->form->label(t('External link'), 'text') ?> + <?= $this->form->text( + 'text', + $values, + $errors, + array( + 'required', + 'autofocus', + 'placeholder="'.t('Copy and paste your link here...').'"', + )) ?> + + <?= $this->form->label(t('Link type'), 'type') ?> + <?= $this->form->select('type', $types, $values) ?> + + <div class="form-actions"> + <input type="submit" value="<?= t('Next') ?>" class="btn btn-blue"/> + <?= t('or') ?> + <?php if ($ajax): ?> + <?= $this->url->link(t('cancel'), 'board', 'show', array('project_id' => $task['project_id']), false, 'close-popover') ?> + <?php else: ?> + <?= $this->url->link(t('cancel'), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> + <?php endif ?> + </div> +</form> \ No newline at end of file diff --git a/app/Template/task_external_link/form.php b/app/Template/task_external_link/form.php new file mode 100644 index 00000000..932ca521 --- /dev/null +++ b/app/Template/task_external_link/form.php @@ -0,0 +1,13 @@ +<?= $this->form->csrf() ?> +<?= $this->form->hidden('task_id', array('task_id' => $task['id'])) ?> +<?= $this->form->hidden('id', $values) ?> +<?= $this->form->hidden('link_type', $values) ?> + +<?= $this->form->label(t('URL'), 'url') ?> +<?= $this->form->text('url', $values, $errors, array('required')) ?> + +<?= $this->form->label(t('Title'), 'title') ?> +<?= $this->form->text('title', $values, $errors, array('required')) ?> + +<?= $this->form->label(t('Dependency'), 'dependency') ?> +<?= $this->form->select('dependency', $dependencies, $values, $errors) ?> diff --git a/app/Template/task_external_link/remove.php b/app/Template/task_external_link/remove.php new file mode 100644 index 00000000..f55e751c --- /dev/null +++ b/app/Template/task_external_link/remove.php @@ -0,0 +1,15 @@ +<div class="page-header"> + <h2><?= t('Remove a link') ?></h2> +</div> + +<div class="confirm"> + <p class="alert alert-info"> + <?= t('Do you really want to remove this link: "%s"?', $link['title']) ?> + </p> + + <div class="form-actions"> + <?= $this->url->link(t('Yes'), 'TaskExternalLink', 'remove', array('link_id' => $link['id'], 'task_id' => $task['id'], 'project_id' => $task['project_id']), true, 'btn btn-red') ?> + <?= t('or') ?> + <?= $this->url->link(t('cancel'), 'TaskExternalLink', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> + </div> +</div> \ No newline at end of file diff --git a/app/Template/task_external_link/show.php b/app/Template/task_external_link/show.php new file mode 100644 index 00000000..7e8b1948 --- /dev/null +++ b/app/Template/task_external_link/show.php @@ -0,0 +1,50 @@ +<div class="page-header"> + <h2><?= t('External links') ?></h2> +</div> + +<?php if (empty($links)): ?> + <p class="alert"><?= t('There is no external link for the moment.') ?></p> +<?php else: ?> + <table class="table-stripped table-small"> + <tr> + <th class="column-10"><?= t('Type') ?></th> + <th><?= t('Title') ?></th> + <th class="column-10"><?= t('Dependency') ?></th> + <th class="column-15"><?= t('Creator') ?></th> + <th class="column-15"><?= t('Date') ?></th> + <?php if ($this->user->hasProjectAccess('TaskExternalLink', 'edit', $task['project_id'])): ?> + <th class="column-5"><?= t('Action') ?></th> + <?php endif ?> + </tr> + <?php foreach ($links as $link): ?> + <tr> + <td> + <?= $link['type'] ?> + </td> + <td> + <a href="<?= $link['url'] ?>" target="_blank"><?= $this->e($link['title']) ?></a> + </td> + <td> + <?= $this->e($link['dependency_label']) ?> + </td> + <td> + <?= $this->e($link['creator_name'] ?: $link['creator_username']) ?> + </td> + <td> + <?= dt('%B %e, %Y', $link['date_creation']) ?> + </td> + <?php if ($this->user->hasProjectAccess('TaskExternalLink', 'edit', $task['project_id'])): ?> + <td> + <div class="dropdown"> + <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><?= $this->url->link(t('Edit'), 'TaskExternalLink', 'edit', array('link_id' => $link['id'], 'task_id' => $task['id'], 'project_id' => $task['project_id'])) ?></li> + <li><?= $this->url->link(t('Remove'), 'TaskExternalLink', 'confirm', array('link_id' => $link['id'], 'task_id' => $task['id'], 'project_id' => $task['project_id'])) ?></li> + </ul> + </div> + </td> + <?php endif ?> + </tr> + <?php endforeach ?> + </table> +<?php endif ?> diff --git a/app/Template/tasklink/create.php b/app/Template/tasklink/create.php index 2832bdc7..f4c3cdae 100644 --- a/app/Template/tasklink/create.php +++ b/app/Template/tasklink/create.php @@ -2,7 +2,7 @@ <h2><?= t('Add a new link') ?></h2> </div> -<form action="<?= $this->url->href('tasklink', 'save', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'ajax' => isset($ajax))) ?>" method="post" autocomplete="off"> +<form action="<?= $this->url->href('tasklink', 'save', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'ajax' => $ajax)) ?>" method="post" autocomplete="off"> <?= $this->form->csrf() ?> <?= $this->form->hidden('task_id', array('task_id' => $task['id'])) ?> @@ -28,7 +28,7 @@ <div class="form-actions"> <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> <?= t('or') ?> - <?php if (isset($ajax)): ?> + <?php if ($ajax): ?> <?= $this->url->link(t('cancel'), 'board', 'show', array('project_id' => $task['project_id']), false, 'close-popover') ?> <?php else: ?> <?= $this->url->link(t('cancel'), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> diff --git a/app/Template/tasklink/show.php b/app/Template/tasklink/show.php index 5843da17..11b12b73 100644 --- a/app/Template/tasklink/show.php +++ b/app/Template/tasklink/show.php @@ -1,15 +1,17 @@ -<?php if (! empty($links)): ?> <div class="page-header"> - <h2><?= t('Links') ?></h2> + <h2><?= t('Internal links') ?></h2> </div> -<table id="links"> +<?php if (empty($links)): ?> + <p class="alert"><?= t('There is no internal link for the moment.') ?></p> +<?php else: ?> +<table id="links" class="table-small table-stripped"> <tr> <th class="column-20"><?= t('Label') ?></th> <th class="column-30"><?= t('Task') ?></th> <th class="column-20"><?= t('Project') ?></th> <th><?= t('Column') ?></th> <th><?= t('Assignee') ?></th> - <?php if ($editable): ?> + <?php if ($editable && $this->user->hasProjectAccess('Tasklink', 'edit', $task['project_id'])): ?> <th class="column-5"><?= t('Action') ?></th> <?php endif ?> </tr> @@ -64,7 +66,7 @@ <?php endif ?> <?php endif ?> </td> - <?php if ($editable): ?> + <?php if ($editable && $this->user->hasProjectAccess('Tasklink', 'edit', $task['project_id'])): ?> <td> <div class="dropdown"> <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> diff --git a/app/Validator/ExternalLinkValidator.php b/app/Validator/ExternalLinkValidator.php new file mode 100644 index 00000000..fff4133b --- /dev/null +++ b/app/Validator/ExternalLinkValidator.php @@ -0,0 +1,76 @@ +<?php + +namespace Kanboard\Validator; + +use SimpleValidator\Validator; +use SimpleValidator\Validators; + +/** + * External Link Validator + * + * @package validator + * @author Frederic Guillot + */ +class ExternalLinkValidator extends Base +{ + /** + * Validate creation + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateCreation(array $values) + { + $v = new Validator($values, $this->commonValidationRules()); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Validate modification + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateModification(array $values) + { + $rules = array( + new Validators\Required('id', t('The id is required')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); + + return array( + $v->execute(), + $v->getErrors() + ); + } + + /** + * Common validation rules + * + * @access private + * @return array + */ + private function commonValidationRules() + { + return array( + new Validators\Required('url', t('Field required')), + new Validators\MaxLength('url', t('The maximum length is %d characters', 255), 255), + new Validators\Required('title', t('Field required')), + new Validators\MaxLength('title', t('The maximum length is %d characters', 255), 255), + new Validators\Required('link_type', t('Field required')), + new Validators\MaxLength('link_type', t('The maximum length is %d characters', 100), 100), + new Validators\Required('dependency', t('Field required')), + new Validators\MaxLength('dependency', t('The maximum length is %d characters', 100), 100), + new Validators\Integer('id', t('This value must be an integer')), + new Validators\Required('task_id', t('Field required')), + new Validators\Integer('task_id', t('This value must be an integer')), + ); + } +} diff --git a/app/common.php b/app/common.php index fe287811..399fcb86 100644 --- a/app/common.php +++ b/app/common.php @@ -34,4 +34,5 @@ $container->register(new Kanboard\ServiceProvider\EventDispatcherProvider); $container->register(new Kanboard\ServiceProvider\GroupProvider); $container->register(new Kanboard\ServiceProvider\RouteProvider); $container->register(new Kanboard\ServiceProvider\ActionProvider); +$container->register(new Kanboard\ServiceProvider\ExternalLinkProvider); $container->register(new Kanboard\ServiceProvider\PluginProvider); diff --git a/assets/js/app.js b/assets/js/app.js index c1ae01cf..7e9c8b0c 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -1278,4 +1278,4 @@ if (typeof jQuery === 'undefined') { return jQuery; })); -!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){function c(a){return a>1&&5>a&&1!==~~(a/10)}function d(a,b,d,e){var f=a+" ";switch(d){case"s":return b||e?"pár sekund":"pár sekundami";case"m":return b?"minuta":e?"minutu":"minutou";case"mm":return b||e?f+(c(a)?"minuty":"minut"):f+"minutami";case"h":return b?"hodina":e?"hodinu":"hodinou";case"hh":return b||e?f+(c(a)?"hodiny":"hodin"):f+"hodinami";case"d":return b||e?"den":"dnem";case"dd":return b||e?f+(c(a)?"dny":"dní"):f+"dny";case"M":return b||e?"měsíc":"měsícem";case"MM":return b||e?f+(c(a)?"měsíce":"měsíců"):f+"měsíci";case"y":return b||e?"rok":"rokem";case"yy":return b||e?f+(c(a)?"roky":"let"):f+"lety"}}var e="leden_únor_březen_duben_květen_červen_červenec_srpen_září_říjen_listopad_prosinec".split("_"),f="led_úno_bře_dub_kvě_čvn_čvc_srp_zář_říj_lis_pro".split("_");(b.defineLocale||b.lang).call(b,"cs",{months:e,monthsShort:f,monthsParse:function(a,b){var c,d=[];for(c=0;12>c;c++)d[c]=new RegExp("^"+a[c]+"$|^"+b[c]+"$","i");return d}(e,f),weekdays:"neděle_pondělí_úterý_středa_čtvrtek_pátek_sobota".split("_"),weekdaysShort:"ne_po_út_st_čt_pá_so".split("_"),weekdaysMin:"ne_po_út_st_čt_pá_so".split("_"),longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd D. MMMM YYYY LT"},calendar:{sameDay:"[dnes v] LT",nextDay:"[zítra v] LT",nextWeek:function(){switch(this.day()){case 0:return"[v neděli v] LT";case 1:case 2:return"[v] dddd [v] LT";case 3:return"[ve středu v] LT";case 4:return"[ve čtvrtek v] LT";case 5:return"[v pátek v] LT";case 6:return"[v sobotu v] LT"}},lastDay:"[včera v] LT",lastWeek:function(){switch(this.day()){case 0:return"[minulou neděli v] LT";case 1:case 2:return"[minulé] dddd [v] LT";case 3:return"[minulou středu v] LT";case 4:case 5:return"[minulý] dddd [v] LT";case 6:return"[minulou sobotu v] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"před %s",s:d,m:d,mm:d,h:d,hh:d,d:d,dd:d,M:d,MM:d,y:d,yy:d},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("cs","cs",{closeText:"Zavřít",prevText:"<Dříve",nextText:"Později>",currentText:"Nyní",monthNames:["leden","únor","březen","duben","květen","červen","červenec","srpen","září","říjen","listopad","prosinec"],monthNamesShort:["led","úno","bře","dub","kvě","čer","čvc","srp","zář","říj","lis","pro"],dayNames:["neděle","pondělí","úterý","středa","čtvrtek","pátek","sobota"],dayNamesShort:["ne","po","út","st","čt","pá","so"],dayNamesMin:["ne","po","út","st","čt","pá","so"],weekHeader:"Týd",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("cs",{buttonText:{month:"Měsíc",week:"Týden",day:"Den",list:"Agenda"},allDayText:"Celý den",eventLimitText:function(a){return"+další: "+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"da",{months:"januar_februar_marts_april_maj_juni_juli_august_september_oktober_november_december".split("_"),monthsShort:"jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec".split("_"),weekdays:"søndag_mandag_tirsdag_onsdag_torsdag_fredag_lørdag".split("_"),weekdaysShort:"søn_man_tir_ons_tor_fre_lør".split("_"),weekdaysMin:"sø_ma_ti_on_to_fr_lø".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd [d.] D. MMMM YYYY LT"},calendar:{sameDay:"[I dag kl.] LT",nextDay:"[I morgen kl.] LT",nextWeek:"dddd [kl.] LT",lastDay:"[I går kl.] LT",lastWeek:"[sidste] dddd [kl] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"%s siden",s:"få sekunder",m:"et minut",mm:"%d minutter",h:"en time",hh:"%d timer",d:"en dag",dd:"%d dage",M:"en måned",MM:"%d måneder",y:"et år",yy:"%d år"},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("da","da",{closeText:"Luk",prevText:"<Forrige",nextText:"Næste>",currentText:"Idag",monthNames:["Januar","Februar","Marts","April","Maj","Juni","Juli","August","September","Oktober","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],dayNames:["Søndag","Mandag","Tirsdag","Onsdag","Torsdag","Fredag","Lørdag"],dayNamesShort:["Søn","Man","Tir","Ons","Tor","Fre","Lør"],dayNamesMin:["Sø","Ma","Ti","On","To","Fr","Lø"],weekHeader:"Uge",dateFormat:"dd-mm-yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("da",{buttonText:{month:"Måned",week:"Uge",day:"Dag",list:"Agenda"},allDayText:"Hele dagen",eventLimitText:"flere"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){function c(a,b,c,d){var e={m:["eine Minute","einer Minute"],h:["eine Stunde","einer Stunde"],d:["ein Tag","einem Tag"],dd:[a+" Tage",a+" Tagen"],M:["ein Monat","einem Monat"],MM:[a+" Monate",a+" Monaten"],y:["ein Jahr","einem Jahr"],yy:[a+" Jahre",a+" Jahren"]};return b?e[c][0]:e[c][1]}(b.defineLocale||b.lang).call(b,"de",{months:"Januar_Februar_März_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember".split("_"),monthsShort:"Jan._Febr._Mrz._Apr._Mai_Jun._Jul._Aug._Sept._Okt._Nov._Dez.".split("_"),weekdays:"Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag".split("_"),weekdaysShort:"So._Mo._Di._Mi._Do._Fr._Sa.".split("_"),weekdaysMin:"So_Mo_Di_Mi_Do_Fr_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd, D. MMMM YYYY LT"},calendar:{sameDay:"[Heute um] LT [Uhr]",sameElse:"L",nextDay:"[Morgen um] LT [Uhr]",nextWeek:"dddd [um] LT [Uhr]",lastDay:"[Gestern um] LT [Uhr]",lastWeek:"[letzten] dddd [um] LT [Uhr]"},relativeTime:{future:"in %s",past:"vor %s",s:"ein paar Sekunden",m:c,mm:"%d Minuten",h:c,hh:"%d Stunden",d:c,dd:c,M:c,MM:c,y:c,yy:c},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("de","de",{closeText:"Schließen",prevText:"<Zurück",nextText:"Vor>",currentText:"Heute",monthNames:["Januar","Februar","März","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember"],monthNamesShort:["Jan","Feb","Mär","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Dez"],dayNames:["Sonntag","Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag"],dayNamesShort:["So","Mo","Di","Mi","Do","Fr","Sa"],dayNamesMin:["So","Mo","Di","Mi","Do","Fr","Sa"],weekHeader:"KW",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("de",{buttonText:{month:"Monat",week:"Woche",day:"Tag",list:"Terminübersicht"},allDayText:"Ganztägig",eventLimitText:function(a){return"+ weitere "+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){var c="ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.".split("_"),d="ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic".split("_");(b.defineLocale||b.lang).call(b,"es",{months:"enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre".split("_"),monthsShort:function(a,b){return/-MMM-/.test(b)?d[a.month()]:c[a.month()]},weekdays:"domingo_lunes_martes_miércoles_jueves_viernes_sábado".split("_"),weekdaysShort:"dom._lun._mar._mié._jue._vie._sáb.".split("_"),weekdaysMin:"Do_Lu_Ma_Mi_Ju_Vi_Sá".split("_"),longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY LT",LLLL:"dddd, D [de] MMMM [de] YYYY LT"},calendar:{sameDay:function(){return"[hoy a la"+(1!==this.hours()?"s":"")+"] LT"},nextDay:function(){return"[mañana a la"+(1!==this.hours()?"s":"")+"] LT"},nextWeek:function(){return"dddd [a la"+(1!==this.hours()?"s":"")+"] LT"},lastDay:function(){return"[ayer a la"+(1!==this.hours()?"s":"")+"] LT"},lastWeek:function(){return"[el] dddd [pasado a la"+(1!==this.hours()?"s":"")+"] LT"},sameElse:"L"},relativeTime:{future:"en %s",past:"hace %s",s:"unos segundos",m:"un minuto",mm:"%d minutos",h:"una hora",hh:"%d horas",d:"un día",dd:"%d días",M:"un mes",MM:"%d meses",y:"un año",yy:"%d años"},ordinalParse:/\d{1,2}º/,ordinal:"%dº",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("es","es",{closeText:"Cerrar",prevText:"<Ant",nextText:"Sig>",currentText:"Hoy",monthNames:["enero","febrero","marzo","abril","mayo","junio","julio","agosto","septiembre","octubre","noviembre","diciembre"],monthNamesShort:["ene","feb","mar","abr","may","jun","jul","ago","sep","oct","nov","dic"],dayNames:["domingo","lunes","martes","miércoles","jueves","viernes","sábado"],dayNamesShort:["dom","lun","mar","mié","jue","vie","sáb"],dayNamesMin:["D","L","M","X","J","V","S"],weekHeader:"Sm",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("es",{buttonText:{month:"Mes",week:"Semana",day:"Día",list:"Agenda"},allDayHtml:"Todo<br/>el día",eventLimitText:"más"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"el",{monthsNominativeEl:"Ιανουάριος_Φεβρουάριος_Μάρτιος_Απρίλιος_Μάιος_Ιούνιος_Ιούλιος_Αύγουστος_Σεπτέμβριος_Οκτώβριος_Νοέμβριος_Δεκέμβριος".split("_"),monthsGenitiveEl:"Ιανουαρίου_Φεβρουαρίου_Μαρτίου_Απριλίου_Μαΐου_Ιουνίου_Ιουλίου_Αυγούστου_Σεπτεμβρίου_Οκτωβρίου_Νοεμβρίου_Δεκεμβρίου".split("_"),months:function(a,b){return/D/.test(b.substring(0,b.indexOf("MMMM")))?this._monthsGenitiveEl[a.month()]:this._monthsNominativeEl[a.month()]},monthsShort:"Ιαν_Φεβ_Μαρ_Απρ_Μαϊ_Ιουν_Ιουλ_Αυγ_Σεπ_Οκτ_Νοε_Δεκ".split("_"),weekdays:"Κυριακή_Δευτέρα_Τρίτη_Τετάρτη_Πέμπτη_Παρασκευή_Σάββατο".split("_"),weekdaysShort:"Κυρ_Δευ_Τρι_Τετ_Πεμ_Παρ_Σαβ".split("_"),weekdaysMin:"Κυ_Δε_Τρ_Τε_Πε_Πα_Σα".split("_"),meridiem:function(a,b,c){return a>11?c?"μμ":"ΜΜ":c?"πμ":"ΠΜ"},isPM:function(a){return"μ"===(a+"").toLowerCase()[0]},meridiemParse:/[ΠΜ]\.?Μ?\.?/i,longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendarEl:{sameDay:"[Σήμερα {}] LT",nextDay:"[Αύριο {}] LT",nextWeek:"dddd [{}] LT",lastDay:"[Χθες {}] LT",lastWeek:function(){switch(this.day()){case 6:return"[το προηγούμενο] dddd [{}] LT";default:return"[την προηγούμενη] dddd [{}] LT"}},sameElse:"L"},calendar:function(a,b){var c=this._calendarEl[a],d=b&&b.hours();return"function"==typeof c&&(c=c.apply(b)),c.replace("{}",d%12===1?"στη":"στις")},relativeTime:{future:"σε %s",past:"%s πριν",s:"λίγα δευτερόλεπτα",m:"ένα λεπτό",mm:"%d λεπτά",h:"μία ώρα",hh:"%d ώρες",d:"μία μέρα",dd:"%d μέρες",M:"ένας μήνας",MM:"%d μήνες",y:"ένας χρόνος",yy:"%d χρόνια"},ordinalParse:/\d{1,2}η/,ordinal:"%dη",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("el","el",{closeText:"Κλείσιμο",prevText:"Προηγούμενος",nextText:"Επόμενος",currentText:"Σήμερα",monthNames:["Ιανουάριος","Φεβρουάριος","Μάρτιος","Απρίλιος","Μάιος","Ιούνιος","Ιούλιος","Αύγουστος","Σεπτέμβριος","Οκτώβριος","Νοέμβριος","Δεκέμβριος"],monthNamesShort:["Ιαν","Φεβ","Μαρ","Απρ","Μαι","Ιουν","Ιουλ","Αυγ","Σεπ","Οκτ","Νοε","Δεκ"],dayNames:["Κυριακή","Δευτέρα","Τρίτη","Τετάρτη","Πέμπτη","Παρασκευή","Σάββατο"],dayNamesShort:["Κυρ","Δευ","Τρι","Τετ","Πεμ","Παρ","Σαβ"],dayNamesMin:["Κυ","Δε","Τρ","Τε","Πε","Πα","Σα"],weekHeader:"Εβδ",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("el",{buttonText:{month:"Μήνας",week:"Εβδομάδα",day:"Ημέρα",list:"Ατζέντα"},allDayText:"Ολοήμερο",eventLimitText:"περισσότερα"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){function c(a,b,c,e){var f="";switch(c){case"s":return e?"muutaman sekunnin":"muutama sekunti";case"m":return e?"minuutin":"minuutti";case"mm":f=e?"minuutin":"minuuttia";break;case"h":return e?"tunnin":"tunti";case"hh":f=e?"tunnin":"tuntia";break;case"d":return e?"päivän":"päivä";case"dd":f=e?"päivän":"päivää";break;case"M":return e?"kuukauden":"kuukausi";case"MM":f=e?"kuukauden":"kuukautta";break;case"y":return e?"vuoden":"vuosi";case"yy":f=e?"vuoden":"vuotta"}return f=d(a,e)+" "+f}function d(a,b){return 10>a?b?f[a]:e[a]:a}var e="nolla yksi kaksi kolme neljä viisi kuusi seitsemän kahdeksan yhdeksän".split(" "),f=["nolla","yhden","kahden","kolmen","neljän","viiden","kuuden",e[7],e[8],e[9]];(b.defineLocale||b.lang).call(b,"fi",{months:"tammikuu_helmikuu_maaliskuu_huhtikuu_toukokuu_kesäkuu_heinäkuu_elokuu_syyskuu_lokakuu_marraskuu_joulukuu".split("_"),monthsShort:"tammi_helmi_maalis_huhti_touko_kesä_heinä_elo_syys_loka_marras_joulu".split("_"),weekdays:"sunnuntai_maanantai_tiistai_keskiviikko_torstai_perjantai_lauantai".split("_"),weekdaysShort:"su_ma_ti_ke_to_pe_la".split("_"),weekdaysMin:"su_ma_ti_ke_to_pe_la".split("_"),longDateFormat:{LT:"HH.mm",LTS:"HH.mm.ss",L:"DD.MM.YYYY",LL:"Do MMMM[ta] YYYY",LLL:"Do MMMM[ta] YYYY, [klo] LT",LLLL:"dddd, Do MMMM[ta] YYYY, [klo] LT",l:"D.M.YYYY",ll:"Do MMM YYYY",lll:"Do MMM YYYY, [klo] LT",llll:"ddd, Do MMM YYYY, [klo] LT"},calendar:{sameDay:"[tänään] [klo] LT",nextDay:"[huomenna] [klo] LT",nextWeek:"dddd [klo] LT",lastDay:"[eilen] [klo] LT",lastWeek:"[viime] dddd[na] [klo] LT",sameElse:"L"},relativeTime:{future:"%s päästä",past:"%s sitten",s:c,m:c,mm:c,h:c,hh:c,d:c,dd:c,M:c,MM:c,y:c,yy:c},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("fi","fi",{closeText:"Sulje",prevText:"«Edellinen",nextText:"Seuraava»",currentText:"Tänään",monthNames:["Tammikuu","Helmikuu","Maaliskuu","Huhtikuu","Toukokuu","Kesäkuu","Heinäkuu","Elokuu","Syyskuu","Lokakuu","Marraskuu","Joulukuu"],monthNamesShort:["Tammi","Helmi","Maalis","Huhti","Touko","Kesä","Heinä","Elo","Syys","Loka","Marras","Joulu"],dayNamesShort:["Su","Ma","Ti","Ke","To","Pe","La"],dayNames:["Sunnuntai","Maanantai","Tiistai","Keskiviikko","Torstai","Perjantai","Lauantai"],dayNamesMin:["Su","Ma","Ti","Ke","To","Pe","La"],weekHeader:"Vk",dateFormat:"d.m.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("fi",{buttonText:{month:"Kuukausi",week:"Viikko",day:"Päivä",list:"Tapahtumat"},allDayText:"Koko päivä",eventLimitText:"lisää"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"fr",{months:"janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre".split("_"),monthsShort:"janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.".split("_"),weekdays:"dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi".split("_"),weekdaysShort:"dim._lun._mar._mer._jeu._ven._sam.".split("_"),weekdaysMin:"Di_Lu_Ma_Me_Je_Ve_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[Aujourd'hui à] LT",nextDay:"[Demain à] LT",nextWeek:"dddd [à] LT",lastDay:"[Hier à] LT",lastWeek:"dddd [dernier à] LT",sameElse:"L"},relativeTime:{future:"dans %s",past:"il y a %s",s:"quelques secondes",m:"une minute",mm:"%d minutes",h:"une heure",hh:"%d heures",d:"un jour",dd:"%d jours",M:"un mois",MM:"%d mois",y:"un an",yy:"%d ans"},ordinalParse:/\d{1,2}(er|)/,ordinal:function(a){return a+(1===a?"er":"")},week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("fr","fr",{closeText:"Fermer",prevText:"Précédent",nextText:"Suivant",currentText:"Aujourd'hui",monthNames:["janvier","février","mars","avril","mai","juin","juillet","août","septembre","octobre","novembre","décembre"],monthNamesShort:["janv.","févr.","mars","avr.","mai","juin","juil.","août","sept.","oct.","nov.","déc."],dayNames:["dimanche","lundi","mardi","mercredi","jeudi","vendredi","samedi"],dayNamesShort:["dim.","lun.","mar.","mer.","jeu.","ven.","sam."],dayNamesMin:["D","L","M","M","J","V","S"],weekHeader:"Sem.",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("fr",{buttonText:{month:"Mois",week:"Semaine",day:"Jour",list:"Mon planning"},allDayHtml:"Toute la<br/>journée",eventLimitText:"en plus"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){function c(a,b,c,d){var e=a;switch(c){case"s":return d||b?"néhány másodperc":"néhány másodperce";case"m":return"egy"+(d||b?" perc":" perce");case"mm":return e+(d||b?" perc":" perce");case"h":return"egy"+(d||b?" óra":" órája");case"hh":return e+(d||b?" óra":" órája");case"d":return"egy"+(d||b?" nap":" napja");case"dd":return e+(d||b?" nap":" napja");case"M":return"egy"+(d||b?" hónap":" hónapja");case"MM":return e+(d||b?" hónap":" hónapja");case"y":return"egy"+(d||b?" év":" éve");case"yy":return e+(d||b?" év":" éve")}return""}function d(a){return(a?"":"[múlt] ")+"["+e[this.day()]+"] LT[-kor]"}var e="vasárnap hétfőn kedden szerdán csütörtökön pénteken szombaton".split(" ");(b.defineLocale||b.lang).call(b,"hu",{months:"január_február_március_április_május_június_július_augusztus_szeptember_október_november_december".split("_"),monthsShort:"jan_feb_márc_ápr_máj_jún_júl_aug_szept_okt_nov_dec".split("_"),weekdays:"vasárnap_hétfő_kedd_szerda_csütörtök_péntek_szombat".split("_"),weekdaysShort:"vas_hét_kedd_sze_csüt_pén_szo".split("_"),weekdaysMin:"v_h_k_sze_cs_p_szo".split("_"),longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"YYYY.MM.DD.",LL:"YYYY. MMMM D.",LLL:"YYYY. MMMM D., LT",LLLL:"YYYY. MMMM D., dddd LT"},meridiemParse:/de|du/i,isPM:function(a){return"u"===a.charAt(1).toLowerCase()},meridiem:function(a,b,c){return 12>a?c===!0?"de":"DE":c===!0?"du":"DU"},calendar:{sameDay:"[ma] LT[-kor]",nextDay:"[holnap] LT[-kor]",nextWeek:function(){return d.call(this,!0)},lastDay:"[tegnap] LT[-kor]",lastWeek:function(){return d.call(this,!1)},sameElse:"L"},relativeTime:{future:"%s múlva",past:"%s",s:c,m:c,mm:c,h:c,hh:c,d:c,dd:c,M:c,MM:c,y:c,yy:c},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}}),a.fullCalendar.datepickerLang("hu","hu",{closeText:"bezár",prevText:"vissza",nextText:"előre",currentText:"ma",monthNames:["Január","Február","Március","Április","Május","Június","Július","Augusztus","Szeptember","Október","November","December"],monthNamesShort:["Jan","Feb","Már","Ápr","Máj","Jún","Júl","Aug","Szep","Okt","Nov","Dec"],dayNames:["Vasárnap","Hétfő","Kedd","Szerda","Csütörtök","Péntek","Szombat"],dayNamesShort:["Vas","Hét","Ked","Sze","Csü","Pén","Szo"],dayNamesMin:["V","H","K","Sze","Cs","P","Szo"],weekHeader:"Hét",dateFormat:"yy.mm.dd.",firstDay:1,isRTL:!1,showMonthAfterYear:!0,yearSuffix:""}),a.fullCalendar.lang("hu",{buttonText:{month:"Hónap",week:"Hét",day:"Nap",list:"Napló"},allDayText:"Egész nap",eventLimitText:"további"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"id",{months:"Januari_Februari_Maret_April_Mei_Juni_Juli_Agustus_September_Oktober_November_Desember".split("_"),monthsShort:"Jan_Feb_Mar_Apr_Mei_Jun_Jul_Ags_Sep_Okt_Nov_Des".split("_"),weekdays:"Minggu_Senin_Selasa_Rabu_Kamis_Jumat_Sabtu".split("_"),weekdaysShort:"Min_Sen_Sel_Rab_Kam_Jum_Sab".split("_"),weekdaysMin:"Mg_Sn_Sl_Rb_Km_Jm_Sb".split("_"),longDateFormat:{LT:"HH.mm",LTS:"LT.ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY [pukul] LT",LLLL:"dddd, D MMMM YYYY [pukul] LT"},meridiemParse:/pagi|siang|sore|malam/,meridiemHour:function(a,b){return 12===a&&(a=0),"pagi"===b?a:"siang"===b?a>=11?a:a+12:"sore"===b||"malam"===b?a+12:void 0},meridiem:function(a,b,c){return 11>a?"pagi":15>a?"siang":19>a?"sore":"malam"},calendar:{sameDay:"[Hari ini pukul] LT",nextDay:"[Besok pukul] LT",nextWeek:"dddd [pukul] LT",lastDay:"[Kemarin pukul] LT",lastWeek:"dddd [lalu pukul] LT",sameElse:"L"},relativeTime:{future:"dalam %s",past:"%s yang lalu",s:"beberapa detik",m:"semenit",mm:"%d menit",h:"sejam",hh:"%d jam",d:"sehari",dd:"%d hari",M:"sebulan",MM:"%d bulan",y:"setahun",yy:"%d tahun"},week:{dow:1,doy:7}}),a.fullCalendar.datepickerLang("id","id",{closeText:"Tutup",prevText:"<mundur",nextText:"maju>",currentText:"hari ini",monthNames:["Januari","Februari","Maret","April","Mei","Juni","Juli","Agustus","September","Oktober","Nopember","Desember"],monthNamesShort:["Jan","Feb","Mar","Apr","Mei","Jun","Jul","Agus","Sep","Okt","Nop","Des"],dayNames:["Minggu","Senin","Selasa","Rabu","Kamis","Jumat","Sabtu"],dayNamesShort:["Min","Sen","Sel","Rab","kam","Jum","Sab"],dayNamesMin:["Mg","Sn","Sl","Rb","Km","jm","Sb"],weekHeader:"Mg",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("id",{buttonText:{month:"Bulan",week:"Minggu",day:"Hari",list:"Agenda"},allDayHtml:"Sehari<br/>penuh",eventLimitText:"lebih"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"it",{months:"gennaio_febbraio_marzo_aprile_maggio_giugno_luglio_agosto_settembre_ottobre_novembre_dicembre".split("_"),monthsShort:"gen_feb_mar_apr_mag_giu_lug_ago_set_ott_nov_dic".split("_"),weekdays:"Domenica_Lunedì_Martedì_Mercoledì_Giovedì_Venerdì_Sabato".split("_"),weekdaysShort:"Dom_Lun_Mar_Mer_Gio_Ven_Sab".split("_"),weekdaysMin:"D_L_Ma_Me_G_V_S".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[Oggi alle] LT",nextDay:"[Domani alle] LT",nextWeek:"dddd [alle] LT",lastDay:"[Ieri alle] LT",lastWeek:function(){switch(this.day()){case 0:return"[la scorsa] dddd [alle] LT";default:return"[lo scorso] dddd [alle] LT"}},sameElse:"L"},relativeTime:{future:function(a){return(/^[0-9].+$/.test(a)?"tra":"in")+" "+a},past:"%s fa",s:"alcuni secondi",m:"un minuto",mm:"%d minuti",h:"un'ora",hh:"%d ore",d:"un giorno",dd:"%d giorni",M:"un mese",MM:"%d mesi",y:"un anno",yy:"%d anni"},ordinalParse:/\d{1,2}º/,ordinal:"%dº",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("it","it",{closeText:"Chiudi",prevText:"<Prec",nextText:"Succ>",currentText:"Oggi",monthNames:["Gennaio","Febbraio","Marzo","Aprile","Maggio","Giugno","Luglio","Agosto","Settembre","Ottobre","Novembre","Dicembre"],monthNamesShort:["Gen","Feb","Mar","Apr","Mag","Giu","Lug","Ago","Set","Ott","Nov","Dic"],dayNames:["Domenica","Lunedì","Martedì","Mercoledì","Giovedì","Venerdì","Sabato"],dayNamesShort:["Dom","Lun","Mar","Mer","Gio","Ven","Sab"],dayNamesMin:["Do","Lu","Ma","Me","Gi","Ve","Sa"],weekHeader:"Sm",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("it",{buttonText:{month:"Mese",week:"Settimana",day:"Giorno",list:"Agenda"},allDayHtml:"Tutto il<br/>giorno",eventLimitText:function(a){return"+altri "+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"ja",{months:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),monthsShort:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),weekdays:"日曜日_月曜日_火曜日_水曜日_木曜日_金曜日_土曜日".split("_"),weekdaysShort:"日_月_火_水_木_金_土".split("_"),weekdaysMin:"日_月_火_水_木_金_土".split("_"),longDateFormat:{LT:"Ah時m分",LTS:"LTs秒",L:"YYYY/MM/DD",LL:"YYYY年M月D日",LLL:"YYYY年M月D日LT",LLLL:"YYYY年M月D日LT dddd"},meridiemParse:/午前|午後/i,isPM:function(a){return"午後"===a},meridiem:function(a,b,c){return 12>a?"午前":"午後"},calendar:{sameDay:"[今日] LT",nextDay:"[明日] LT",nextWeek:"[来週]dddd LT",lastDay:"[昨日] LT",lastWeek:"[前週]dddd LT",sameElse:"L"},relativeTime:{future:"%s後",past:"%s前",s:"数秒",m:"1分",mm:"%d分",h:"1時間",hh:"%d時間",d:"1日",dd:"%d日",M:"1ヶ月",MM:"%dヶ月",y:"1年",yy:"%d年"}}),a.fullCalendar.datepickerLang("ja","ja",{closeText:"閉じる",prevText:"<前",nextText:"次>",currentText:"今日",monthNames:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],monthNamesShort:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],dayNames:["日曜日","月曜日","火曜日","水曜日","木曜日","金曜日","土曜日"],dayNamesShort:["日","月","火","水","木","金","土"],dayNamesMin:["日","月","火","水","木","金","土"],weekHeader:"週",dateFormat:"yy/mm/dd",firstDay:0,isRTL:!1,showMonthAfterYear:!0,yearSuffix:"年"}),a.fullCalendar.lang("ja",{buttonText:{month:"月",week:"週",day:"日",list:"予定リスト"},allDayText:"終日",eventLimitText:function(a){return"他 "+a+" 件"}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){var c="jan._feb._mrt._apr._mei_jun._jul._aug._sep._okt._nov._dec.".split("_"),d="jan_feb_mrt_apr_mei_jun_jul_aug_sep_okt_nov_dec".split("_");(b.defineLocale||b.lang).call(b,"nl",{months:"januari_februari_maart_april_mei_juni_juli_augustus_september_oktober_november_december".split("_"),monthsShort:function(a,b){return/-MMM-/.test(b)?d[a.month()]:c[a.month()]},weekdays:"zondag_maandag_dinsdag_woensdag_donderdag_vrijdag_zaterdag".split("_"),weekdaysShort:"zo._ma._di._wo._do._vr._za.".split("_"),weekdaysMin:"Zo_Ma_Di_Wo_Do_Vr_Za".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD-MM-YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[vandaag om] LT",nextDay:"[morgen om] LT",nextWeek:"dddd [om] LT",lastDay:"[gisteren om] LT",lastWeek:"[afgelopen] dddd [om] LT",sameElse:"L"},relativeTime:{future:"over %s",past:"%s geleden",s:"een paar seconden",m:"één minuut",mm:"%d minuten",h:"één uur",hh:"%d uur",d:"één dag",dd:"%d dagen",M:"één maand",MM:"%d maanden",y:"één jaar",yy:"%d jaar"},ordinalParse:/\d{1,2}(ste|de)/,ordinal:function(a){return a+(1===a||8===a||a>=20?"ste":"de")},week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("nl","nl",{closeText:"Sluiten",prevText:"←",nextText:"→",currentText:"Vandaag",monthNames:["januari","februari","maart","april","mei","juni","juli","augustus","september","oktober","november","december"],monthNamesShort:["jan","feb","mrt","apr","mei","jun","jul","aug","sep","okt","nov","dec"],dayNames:["zondag","maandag","dinsdag","woensdag","donderdag","vrijdag","zaterdag"],dayNamesShort:["zon","maa","din","woe","don","vri","zat"],dayNamesMin:["zo","ma","di","wo","do","vr","za"],weekHeader:"Wk",dateFormat:"dd-mm-yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("nl",{buttonText:{month:"Maand",week:"Week",day:"Dag",list:"Agenda"},allDayText:"Hele dag",eventLimitText:"extra"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"nb",{months:"januar_februar_mars_april_mai_juni_juli_august_september_oktober_november_desember".split("_"),monthsShort:"jan_feb_mar_apr_mai_jun_jul_aug_sep_okt_nov_des".split("_"),weekdays:"søndag_mandag_tirsdag_onsdag_torsdag_fredag_lørdag".split("_"),weekdaysShort:"søn_man_tirs_ons_tors_fre_lør".split("_"),weekdaysMin:"sø_ma_ti_on_to_fr_lø".split("_"),longDateFormat:{LT:"H.mm",LTS:"LT.ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY [kl.] LT",LLLL:"dddd D. MMMM YYYY [kl.] LT"},calendar:{sameDay:"[i dag kl.] LT",nextDay:"[i morgen kl.] LT",nextWeek:"dddd [kl.] LT",lastDay:"[i går kl.] LT",lastWeek:"[forrige] dddd [kl.] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"for %s siden",s:"noen sekunder",m:"ett minutt",mm:"%d minutter",h:"en time",hh:"%d timer",d:"en dag",dd:"%d dager",M:"en måned",MM:"%d måneder",y:"ett år",yy:"%d år"},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("nb","nb",{closeText:"Lukk",prevText:"«Forrige",nextText:"Neste»",currentText:"I dag",monthNames:["januar","februar","mars","april","mai","juni","juli","august","september","oktober","november","desember"],monthNamesShort:["jan","feb","mar","apr","mai","jun","jul","aug","sep","okt","nov","des"],dayNamesShort:["søn","man","tir","ons","tor","fre","lør"],dayNames:["søndag","mandag","tirsdag","onsdag","torsdag","fredag","lørdag"],dayNamesMin:["sø","ma","ti","on","to","fr","lø"],weekHeader:"Uke",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("nb",{buttonText:{month:"Måned",week:"Uke",day:"Dag",list:"Agenda"},allDayText:"Hele dagen",eventLimitText:"til"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){function c(a){return 5>a%10&&a%10>1&&~~(a/10)%10!==1}function d(a,b,d){var e=a+" ";switch(d){case"m":return b?"minuta":"minutę";case"mm":return e+(c(a)?"minuty":"minut");case"h":return b?"godzina":"godzinę";case"hh":return e+(c(a)?"godziny":"godzin");case"MM":return e+(c(a)?"miesiące":"miesięcy");case"yy":return e+(c(a)?"lata":"lat")}}var e="styczeń_luty_marzec_kwiecień_maj_czerwiec_lipiec_sierpień_wrzesień_październik_listopad_grudzień".split("_"),f="stycznia_lutego_marca_kwietnia_maja_czerwca_lipca_sierpnia_września_października_listopada_grudnia".split("_");(b.defineLocale||b.lang).call(b,"pl",{months:function(a,b){return/D MMMM/.test(b)?f[a.month()]:e[a.month()]},monthsShort:"sty_lut_mar_kwi_maj_cze_lip_sie_wrz_paź_lis_gru".split("_"),weekdays:"niedziela_poniedziałek_wtorek_środa_czwartek_piątek_sobota".split("_"),weekdaysShort:"nie_pon_wt_śr_czw_pt_sb".split("_"),weekdaysMin:"N_Pn_Wt_Śr_Cz_Pt_So".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[Dziś o] LT",nextDay:"[Jutro o] LT",nextWeek:"[W] dddd [o] LT",lastDay:"[Wczoraj o] LT",lastWeek:function(){switch(this.day()){case 0:return"[W zeszłą niedzielę o] LT";case 3:return"[W zeszłą środę o] LT";case 6:return"[W zeszłą sobotę o] LT";default:return"[W zeszły] dddd [o] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"%s temu",s:"kilka sekund",m:d,mm:d,h:d,hh:d,d:"1 dzień",dd:"%d dni",M:"miesiąc",MM:d,y:"rok",yy:d},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("pl","pl",{closeText:"Zamknij",prevText:"<Poprzedni",nextText:"Następny>",currentText:"Dziś",monthNames:["Styczeń","Luty","Marzec","Kwiecień","Maj","Czerwiec","Lipiec","Sierpień","Wrzesień","Październik","Listopad","Grudzień"],monthNamesShort:["Sty","Lu","Mar","Kw","Maj","Cze","Lip","Sie","Wrz","Pa","Lis","Gru"],dayNames:["Niedziela","Poniedziałek","Wtorek","Środa","Czwartek","Piątek","Sobota"],dayNamesShort:["Nie","Pn","Wt","Śr","Czw","Pt","So"],dayNamesMin:["N","Pn","Wt","Śr","Cz","Pt","So"],weekHeader:"Tydz",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("pl",{buttonText:{month:"Miesiąc",week:"Tydzień",day:"Dzień",list:"Plan dnia"},allDayText:"Cały dzień",eventLimitText:"więcej"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"pt",{months:"janeiro_fevereiro_março_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro".split("_"),monthsShort:"jan_fev_mar_abr_mai_jun_jul_ago_set_out_nov_dez".split("_"),weekdays:"domingo_segunda-feira_terça-feira_quarta-feira_quinta-feira_sexta-feira_sábado".split("_"),weekdaysShort:"dom_seg_ter_qua_qui_sex_sáb".split("_"),weekdaysMin:"dom_2ª_3ª_4ª_5ª_6ª_sáb".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY LT",LLLL:"dddd, D [de] MMMM [de] YYYY LT"},calendar:{sameDay:"[Hoje às] LT",nextDay:"[Amanhã às] LT",nextWeek:"dddd [às] LT",lastDay:"[Ontem às] LT",lastWeek:function(){return 0===this.day()||6===this.day()?"[Último] dddd [às] LT":"[Última] dddd [às] LT"},sameElse:"L"},relativeTime:{future:"em %s",past:"há %s",s:"segundos",m:"um minuto",mm:"%d minutos",h:"uma hora",hh:"%d horas",d:"um dia",dd:"%d dias",M:"um mês",MM:"%d meses",y:"um ano",yy:"%d anos"},ordinalParse:/\d{1,2}º/,ordinal:"%dº",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("pt","pt",{closeText:"Fechar",prevText:"Anterior",nextText:"Seguinte",currentText:"Hoje",monthNames:["Janeiro","Fevereiro","Março","Abril","Maio","Junho","Julho","Agosto","Setembro","Outubro","Novembro","Dezembro"],monthNamesShort:["Jan","Fev","Mar","Abr","Mai","Jun","Jul","Ago","Set","Out","Nov","Dez"],dayNames:["Domingo","Segunda-feira","Terça-feira","Quarta-feira","Quinta-feira","Sexta-feira","Sábado"],dayNamesShort:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],dayNamesMin:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],weekHeader:"Sem",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("pt",{buttonText:{month:"Mês",week:"Semana",day:"Dia",list:"Agenda"},allDayText:"Todo o dia",eventLimitText:"mais"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"pt-br",{months:"janeiro_fevereiro_março_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro".split("_"),monthsShort:"jan_fev_mar_abr_mai_jun_jul_ago_set_out_nov_dez".split("_"),weekdays:"domingo_segunda-feira_terça-feira_quarta-feira_quinta-feira_sexta-feira_sábado".split("_"),weekdaysShort:"dom_seg_ter_qua_qui_sex_sáb".split("_"),weekdaysMin:"dom_2ª_3ª_4ª_5ª_6ª_sáb".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY [às] LT",LLLL:"dddd, D [de] MMMM [de] YYYY [às] LT"},calendar:{sameDay:"[Hoje às] LT",nextDay:"[Amanhã às] LT",nextWeek:"dddd [às] LT",lastDay:"[Ontem às] LT",lastWeek:function(){return 0===this.day()||6===this.day()?"[Último] dddd [às] LT":"[Última] dddd [às] LT"},sameElse:"L"},relativeTime:{future:"em %s",past:"%s atrás",s:"segundos",m:"um minuto",mm:"%d minutos",h:"uma hora",hh:"%d horas",d:"um dia",dd:"%d dias",M:"um mês",MM:"%d meses",y:"um ano",yy:"%d anos"},ordinalParse:/\d{1,2}º/,ordinal:"%dº"}),a.fullCalendar.datepickerLang("pt-br","pt-BR",{closeText:"Fechar",prevText:"<Anterior",nextText:"Próximo>",currentText:"Hoje",monthNames:["Janeiro","Fevereiro","Março","Abril","Maio","Junho","Julho","Agosto","Setembro","Outubro","Novembro","Dezembro"],monthNamesShort:["Jan","Fev","Mar","Abr","Mai","Jun","Jul","Ago","Set","Out","Nov","Dez"],dayNames:["Domingo","Segunda-feira","Terça-feira","Quarta-feira","Quinta-feira","Sexta-feira","Sábado"],dayNamesShort:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],dayNamesMin:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],weekHeader:"Sm",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("pt-br",{buttonText:{month:"Mês",week:"Semana",day:"Dia",list:"Compromissos"},allDayText:"dia inteiro",eventLimitText:function(a){return"mais +"+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){function c(a,b){var c=a.split("_");return b%10===1&&b%100!==11?c[0]:b%10>=2&&4>=b%10&&(10>b%100||b%100>=20)?c[1]:c[2]}function d(a,b,d){var e={mm:b?"минута_минуты_минут":"минуту_минуты_минут",hh:"час_часа_часов",dd:"день_дня_дней",MM:"месяц_месяца_месяцев",yy:"год_года_лет"};return"m"===d?b?"минута":"минуту":a+" "+c(e[d],+a)}function e(a,b){var c={nominative:"январь_февраль_март_апрель_май_июнь_июль_август_сентябрь_октябрь_ноябрь_декабрь".split("_"),accusative:"января_февраля_марта_апреля_мая_июня_июля_августа_сентября_октября_ноября_декабря".split("_")},d=/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/.test(b)?"accusative":"nominative";return c[d][a.month()]}function f(a,b){var c={nominative:"янв_фев_март_апр_май_июнь_июль_авг_сен_окт_ноя_дек".split("_"),accusative:"янв_фев_мар_апр_мая_июня_июля_авг_сен_окт_ноя_дек".split("_")},d=/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/.test(b)?"accusative":"nominative";return c[d][a.month()]}function g(a,b){var c={nominative:"воскресенье_понедельник_вторник_среда_четверг_пятница_суббота".split("_"),accusative:"воскресенье_понедельник_вторник_среду_четверг_пятницу_субботу".split("_")},d=/\[ ?[Вв] ?(?:прошлую|следующую|эту)? ?\] ?dddd/.test(b)?"accusative":"nominative";return c[d][a.day()]}(b.defineLocale||b.lang).call(b,"ru",{months:e,monthsShort:f,weekdays:g,weekdaysShort:"вс_пн_вт_ср_чт_пт_сб".split("_"),weekdaysMin:"вс_пн_вт_ср_чт_пт_сб".split("_"),monthsParse:[/^янв/i,/^фев/i,/^мар/i,/^апр/i,/^ма[й|я]/i,/^июн/i,/^июл/i,/^авг/i,/^сен/i,/^окт/i,/^ноя/i,/^дек/i],longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY г.",LLL:"D MMMM YYYY г., LT",LLLL:"dddd, D MMMM YYYY г., LT"},calendar:{sameDay:"[Сегодня в] LT",nextDay:"[Завтра в] LT",lastDay:"[Вчера в] LT",nextWeek:function(){return 2===this.day()?"[Во] dddd [в] LT":"[В] dddd [в] LT"},lastWeek:function(a){if(a.week()===this.week())return 2===this.day()?"[Во] dddd [в] LT":"[В] dddd [в] LT";switch(this.day()){case 0:return"[В прошлое] dddd [в] LT";case 1:case 2:case 4:return"[В прошлый] dddd [в] LT";case 3:case 5:case 6:return"[В прошлую] dddd [в] LT"}},sameElse:"L"},relativeTime:{future:"через %s",past:"%s назад",s:"несколько секунд",m:d,mm:d,h:"час",hh:d,d:"день",dd:d,M:"месяц",MM:d,y:"год",yy:d},meridiemParse:/ночи|утра|дня|вечера/i,isPM:function(a){return/^(дня|вечера)$/.test(a)},meridiem:function(a,b,c){return 4>a?"ночи":12>a?"утра":17>a?"дня":"вечера"},ordinalParse:/\d{1,2}-(й|го|я)/,ordinal:function(a,b){switch(b){case"M":case"d":case"DDD":return a+"-й";case"D":return a+"-го";case"w":case"W":return a+"-я";default:return a}},week:{dow:1,doy:7}}),a.fullCalendar.datepickerLang("ru","ru",{closeText:"Закрыть",prevText:"<Пред",nextText:"След>",currentText:"Сегодня",monthNames:["Январь","Февраль","Март","Апрель","Май","Июнь","Июль","Август","Сентябрь","Октябрь","Ноябрь","Декабрь"],monthNamesShort:["Янв","Фев","Мар","Апр","Май","Июн","Июл","Авг","Сен","Окт","Ноя","Дек"],dayNames:["воскресенье","понедельник","вторник","среда","четверг","пятница","суббота"],dayNamesShort:["вск","пнд","втр","срд","чтв","птн","сбт"],dayNamesMin:["Вс","Пн","Вт","Ср","Чт","Пт","Сб"],weekHeader:"Нед",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("ru",{buttonText:{month:"Месяц",week:"Неделя",day:"День",list:"Повестка дня"},allDayText:"Весь день",eventLimitText:function(a){return"+ ещё "+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"sv",{months:"januari_februari_mars_april_maj_juni_juli_augusti_september_oktober_november_december".split("_"),monthsShort:"jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec".split("_"),weekdays:"söndag_måndag_tisdag_onsdag_torsdag_fredag_lördag".split("_"),weekdaysShort:"sön_mån_tis_ons_tor_fre_lör".split("_"),weekdaysMin:"sö_må_ti_on_to_fr_lö".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"YYYY-MM-DD",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[Idag] LT",nextDay:"[Imorgon] LT",lastDay:"[Igår] LT",nextWeek:"dddd LT",lastWeek:"[Förra] dddd[en] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"för %s sedan",s:"några sekunder",m:"en minut",mm:"%d minuter",h:"en timme",hh:"%d timmar",d:"en dag",dd:"%d dagar",M:"en månad",MM:"%d månader",y:"ett år",yy:"%d år"},ordinalParse:/\d{1,2}(e|a)/,ordinal:function(a){var b=a%10,c=1===~~(a%100/10)?"e":1===b?"a":2===b?"a":"e";return a+c},week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("sv","sv",{closeText:"Stäng",prevText:"«Förra",nextText:"Nästa»",currentText:"Idag",monthNames:["Januari","Februari","Mars","April","Maj","Juni","Juli","Augusti","September","Oktober","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],dayNamesShort:["Sön","Mån","Tis","Ons","Tor","Fre","Lör"],dayNames:["Söndag","Måndag","Tisdag","Onsdag","Torsdag","Fredag","Lördag"],dayNamesMin:["Sö","Må","Ti","On","To","Fr","Lö"],weekHeader:"Ve",dateFormat:"yy-mm-dd",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("sv",{buttonText:{month:"Månad",week:"Vecka",day:"Dag",list:"Program"},allDayText:"Heldag",eventLimitText:"till"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){var c={words:{m:["jedan minut","jedne minute"],mm:["minut","minute","minuta"],h:["jedan sat","jednog sata"],hh:["sat","sata","sati"],dd:["dan","dana","dana"],MM:["mesec","meseca","meseci"],yy:["godina","godine","godina"]},correctGrammaticalCase:function(a,b){return 1===a?b[0]:a>=2&&4>=a?b[1]:b[2]},translate:function(a,b,d){var e=c.words[d];return 1===d.length?b?e[0]:e[1]:a+" "+c.correctGrammaticalCase(a,e)}};(b.defineLocale||b.lang).call(b,"sr",{months:["januar","februar","mart","april","maj","jun","jul","avgust","septembar","oktobar","novembar","decembar"],monthsShort:["jan.","feb.","mar.","apr.","maj","jun","jul","avg.","sep.","okt.","nov.","dec."],weekdays:["nedelja","ponedeljak","utorak","sreda","četvrtak","petak","subota"],weekdaysShort:["ned.","pon.","uto.","sre.","čet.","pet.","sub."],weekdaysMin:["ne","po","ut","sr","če","pe","su"],longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"DD. MM. YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd, D. MMMM YYYY LT"},calendar:{sameDay:"[danas u] LT",nextDay:"[sutra u] LT",nextWeek:function(){switch(this.day()){case 0:return"[u] [nedelju] [u] LT";case 3:return"[u] [sredu] [u] LT";case 6:return"[u] [subotu] [u] LT";case 1:case 2:case 4:case 5:return"[u] dddd [u] LT"}},lastDay:"[juče u] LT",lastWeek:function(){var a=["[prošle] [nedelje] [u] LT","[prošlog] [ponedeljka] [u] LT","[prošlog] [utorka] [u] LT","[prošle] [srede] [u] LT","[prošlog] [četvrtka] [u] LT","[prošlog] [petka] [u] LT","[prošle] [subote] [u] LT"];return a[this.day()]},sameElse:"L"},relativeTime:{future:"za %s",past:"pre %s",s:"nekoliko sekundi",m:c.translate,mm:c.translate,h:c.translate,hh:c.translate,d:"dan",dd:c.translate,M:"mesec",MM:c.translate,y:"godinu",yy:c.translate},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}}),a.fullCalendar.datepickerLang("sr","sr",{closeText:"Затвори",prevText:"<",nextText:">",currentText:"Данас",monthNames:["Јануар","Фебруар","Март","Април","Мај","Јун","Јул","Август","Септембар","Октобар","Новембар","Децембар"],monthNamesShort:["Јан","Феб","Мар","Апр","Мај","Јун","Јул","Авг","Сеп","Окт","Нов","Дец"],dayNames:["Недеља","Понедељак","Уторак","Среда","Четвртак","Петак","Субота"],dayNamesShort:["Нед","Пон","Уто","Сре","Чет","Пет","Суб"],dayNamesMin:["Не","По","Ут","Ср","Че","Пе","Су"],weekHeader:"Сед",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("sr",{buttonText:{month:"Месец",week:"Недеља",day:"Дан",list:"Планер"},allDayText:"Цео дан",eventLimitText:function(a){return"+ још "+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"th",{months:"มกราคม_กุมภาพันธ์_มีนาคม_เมษายน_พฤษภาคม_มิถุนายน_กรกฎาคม_สิงหาคม_กันยายน_ตุลาคม_พฤศจิกายน_ธันวาคม".split("_"),monthsShort:"มกรา_กุมภา_มีนา_เมษา_พฤษภา_มิถุนา_กรกฎา_สิงหา_กันยา_ตุลา_พฤศจิกา_ธันวา".split("_"),weekdays:"อาทิตย์_จันทร์_อังคาร_พุธ_พฤหัสบดี_ศุกร์_เสาร์".split("_"),weekdaysShort:"อาทิตย์_จันทร์_อังคาร_พุธ_พฤหัส_ศุกร์_เสาร์".split("_"),weekdaysMin:"อา._จ._อ._พ._พฤ._ศ._ส.".split("_"),longDateFormat:{LT:"H นาฬิกา m นาที",LTS:"LT s วินาที",L:"YYYY/MM/DD",LL:"D MMMM YYYY",LLL:"D MMMM YYYY เวลา LT",LLLL:"วันddddที่ D MMMM YYYY เวลา LT"},meridiemParse:/ก่อนเที่ยง|หลังเที่ยง/,isPM:function(a){return"หลังเที่ยง"===a},meridiem:function(a,b,c){return 12>a?"ก่อนเที่ยง":"หลังเที่ยง"},calendar:{sameDay:"[วันนี้ เวลา] LT",nextDay:"[พรุ่งนี้ เวลา] LT",nextWeek:"dddd[หน้า เวลา] LT",lastDay:"[เมื่อวานนี้ เวลา] LT",lastWeek:"[วัน]dddd[ที่แล้ว เวลา] LT",sameElse:"L"},relativeTime:{future:"อีก %s",past:"%sที่แล้ว",s:"ไม่กี่วินาที",m:"1 นาที",mm:"%d นาที",h:"1 ชั่วโมง",hh:"%d ชั่วโมง",d:"1 วัน",dd:"%d วัน",M:"1 เดือน",MM:"%d เดือน",y:"1 ปี",yy:"%d ปี"}}),a.fullCalendar.datepickerLang("th","th",{closeText:"ปิด",prevText:"« ย้อน",nextText:"ถัดไป »",currentText:"วันนี้",monthNames:["มกราคม","กุมภาพันธ์","มีนาคม","เมษายน","พฤษภาคม","มิถุนายน","กรกฎาคม","สิงหาคม","กันยายน","ตุลาคม","พฤศจิกายน","ธันวาคม"],monthNamesShort:["ม.ค.","ก.พ.","มี.ค.","เม.ย.","พ.ค.","มิ.ย.","ก.ค.","ส.ค.","ก.ย.","ต.ค.","พ.ย.","ธ.ค."],dayNames:["อาทิตย์","จันทร์","อังคาร","พุธ","พฤหัสบดี","ศุกร์","เสาร์"],dayNamesShort:["อา.","จ.","อ.","พ.","พฤ.","ศ.","ส."],dayNamesMin:["อา.","จ.","อ.","พ.","พฤ.","ศ.","ส."],weekHeader:"Wk",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("th",{buttonText:{month:"เดือน",week:"สัปดาห์",day:"วัน",list:"แผนงาน"},allDayText:"ตลอดวัน",eventLimitText:"เพิ่มเติม"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){var c={1:"'inci",5:"'inci",8:"'inci",70:"'inci",80:"'inci",2:"'nci",7:"'nci",20:"'nci",50:"'nci",3:"'üncü",4:"'üncü",100:"'üncü",6:"'ncı",9:"'uncu",10:"'uncu",30:"'uncu",60:"'ıncı",90:"'ıncı"};(b.defineLocale||b.lang).call(b,"tr",{months:"Ocak_Şubat_Mart_Nisan_Mayıs_Haziran_Temmuz_Ağustos_Eylül_Ekim_Kasım_Aralık".split("_"),monthsShort:"Oca_Şub_Mar_Nis_May_Haz_Tem_Ağu_Eyl_Eki_Kas_Ara".split("_"),weekdays:"Pazar_Pazartesi_Salı_Çarşamba_Perşembe_Cuma_Cumartesi".split("_"),weekdaysShort:"Paz_Pts_Sal_Çar_Per_Cum_Cts".split("_"),weekdaysMin:"Pz_Pt_Sa_Ça_Pe_Cu_Ct".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[bugün saat] LT",nextDay:"[yarın saat] LT",nextWeek:"[haftaya] dddd [saat] LT",lastDay:"[dün] LT",lastWeek:"[geçen hafta] dddd [saat] LT",sameElse:"L"},relativeTime:{future:"%s sonra",past:"%s önce",s:"birkaç saniye",m:"bir dakika",mm:"%d dakika",h:"bir saat",hh:"%d saat",d:"bir gün",dd:"%d gün",M:"bir ay",MM:"%d ay",y:"bir yıl",yy:"%d yıl"},ordinalParse:/\d{1,2}'(inci|nci|üncü|ncı|uncu|ıncı)/,ordinal:function(a){if(0===a)return a+"'ıncı";var b=a%10,d=a%100-b,e=a>=100?100:null;return a+(c[b]||c[d]||c[e])},week:{dow:1,doy:7}}),a.fullCalendar.datepickerLang("tr","tr",{closeText:"kapat",prevText:"<geri",nextText:"ileri>",currentText:"bugün",monthNames:["Ocak","Şubat","Mart","Nisan","Mayıs","Haziran","Temmuz","Ağustos","Eylül","Ekim","Kasım","Aralık"],monthNamesShort:["Oca","Şub","Mar","Nis","May","Haz","Tem","Ağu","Eyl","Eki","Kas","Ara"],dayNames:["Pazar","Pazartesi","Salı","Çarşamba","Perşembe","Cuma","Cumartesi"],dayNamesShort:["Pz","Pt","Sa","Ça","Pe","Cu","Ct"],dayNamesMin:["Pz","Pt","Sa","Ça","Pe","Cu","Ct"],weekHeader:"Hf",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("tr",{buttonText:{next:"ileri",month:"Ay",week:"Hafta",day:"Gün",list:"Ajanda"},allDayText:"Tüm gün",eventLimitText:"daha fazla"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"zh-cn",{months:"一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月".split("_"),monthsShort:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),weekdays:"星期日_星期一_星期二_星期三_星期四_星期五_星期六".split("_"),weekdaysShort:"周日_周一_周二_周三_周四_周五_周六".split("_"),weekdaysMin:"日_一_二_三_四_五_六".split("_"),longDateFormat:{LT:"Ah点mm",LTS:"Ah点m分s秒",L:"YYYY-MM-DD",LL:"YYYY年MMMD日",LLL:"YYYY年MMMD日LT",LLLL:"YYYY年MMMD日ddddLT",l:"YYYY-MM-DD",ll:"YYYY年MMMD日",lll:"YYYY年MMMD日LT",llll:"YYYY年MMMD日ddddLT"},meridiemParse:/凌晨|早上|上午|中午|下午|晚上/,meridiemHour:function(a,b){return 12===a&&(a=0),"凌晨"===b||"早上"===b||"上午"===b?a:"下午"===b||"晚上"===b?a+12:a>=11?a:a+12},meridiem:function(a,b,c){var d=100*a+b;return 600>d?"凌晨":900>d?"早上":1130>d?"上午":1230>d?"中午":1800>d?"下午":"晚上"},calendar:{sameDay:function(){return 0===this.minutes()?"[今天]Ah[点整]":"[今天]LT"},nextDay:function(){return 0===this.minutes()?"[明天]Ah[点整]":"[明天]LT"},lastDay:function(){return 0===this.minutes()?"[昨天]Ah[点整]":"[昨天]LT"},nextWeek:function(){var a,c;return a=b().startOf("week"),c=this.unix()-a.unix()>=604800?"[下]":"[本]",0===this.minutes()?c+"dddAh点整":c+"dddAh点mm"},lastWeek:function(){var a,c;return a=b().startOf("week"),c=this.unix()<a.unix()?"[上]":"[本]",0===this.minutes()?c+"dddAh点整":c+"dddAh点mm"},sameElse:"LL"},ordinalParse:/\d{1,2}(日|月|周)/,ordinal:function(a,b){switch(b){case"d":case"D":case"DDD":return a+"日";case"M":return a+"月";case"w":case"W":return a+"周";default:return a}},relativeTime:{future:"%s内",past:"%s前",s:"几秒",m:"1分钟",mm:"%d分钟",h:"1小时",hh:"%d小时",d:"1天",dd:"%d天",M:"1个月",MM:"%d个月",y:"1年",yy:"%d年"},week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("zh-cn","zh-CN",{closeText:"关闭",prevText:"<上月",nextText:"下月>",currentText:"今天",monthNames:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],monthNamesShort:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],dayNames:["星期日","星期一","星期二","星期三","星期四","星期五","星期六"],dayNamesShort:["周日","周一","周二","周三","周四","周五","周六"],dayNamesMin:["日","一","二","三","四","五","六"],weekHeader:"周",dateFormat:"yy-mm-dd",firstDay:1,isRTL:!1,showMonthAfterYear:!0,yearSuffix:"年"}),a.fullCalendar.lang("zh-cn",{buttonText:{month:"月",week:"周",day:"日",list:"日程"},allDayText:"全天",eventLimitText:function(a){return"另外 "+a+" 个"}})});(function(){function t(x){this.app=x;this.router=new v();this.router.addRoute("screenshot-zone",e)}t.prototype.isOpen=function(){return $("#popover-container").size()>0};t.prototype.open=function(y){var x=this;x.app.dropdown.close();$.get(y,function(z){$("body").append('<div id="popover-container"><div id="popover-content">'+z+"</div></div>");x.app.refresh();x.router.dispatch(this.app);x.afterOpen()})};t.prototype.close=function(x){if(this.isOpen()){if(x){x.preventDefault()}$("#popover-container").remove()}};t.prototype.onClick=function(y){y.preventDefault();y.stopPropagation();var x=y.target.getAttribute("href");if(!x){x=y.target.getAttribute("data-href")}if(x){this.open(x)}};t.prototype.listen=function(){$(document).on("click",".popover",this.onClick.bind(this));$(document).on("click",".close-popover",this.close.bind(this));$(document).on("click","#popover-container",this.close.bind(this));$(document).on("click","#popover-content",function(x){x.stopPropagation()})};t.prototype.afterOpen=function(){var x=this;var y=$("#task-form");if(y){y.on("submit",function(z){z.preventDefault();$.ajax({type:"POST",url:y.attr("action"),data:y.serialize(),success:function(B,C,A){if(A.getResponseHeader("X-Ajax-Redirect")){window.location=A.getResponseHeader("X-Ajax-Redirect")}else{$("#popover-content").html(B);x.afterOpen()}}})})}};function r(){}r.prototype.listen=function(){var x=this;$(document).on("click",function(){x.close()});$(document).on("click",".dropdown-menu",function(B){B.preventDefault();B.stopImmediatePropagation();x.close();var z=$(this).next("ul");var C=$(this).offset();$("body").append(jQuery("<div>",{id:"dropdown"}));z.clone().appendTo("#dropdown");var D=$("#dropdown ul");D.addClass("dropdown-submenu-open");var A=D.outerHeight();var y=D.outerWidth();if(C.top+A-$(window).scrollTop()>$(window).height()){D.css("top",C.top-A-5)}else{D.css("top",C.top+$(this).height())}if(C.left+y>$(window).width()){D.css("left",C.left-y+$(this).outerWidth())}else{D.css("left",C.left)}});$(document).on("click",".dropdown-submenu-open li",function(y){if($(y.target).is("li")){$(this).find("a:visible")[0].click()}});$("textarea[data-mention-search-url]").textcomplete([{match:/(^|\s)@(\w*)$/,search:function(z,A){var y=$("textarea[data-mention-search-url]").data("mention-search-url");$.getJSON(y,{q:z}).done(function(B){A(B)}).fail(function(){A([])})},replace:function(y){return"$1@"+y+" "},cache:true}],{className:"textarea-dropdown"})};r.prototype.close=function(){$("#dropdown").remove()};function q(x){this.app=x}q.prototype.listen=function(){var x=this;$(".tooltip").tooltip({track:false,show:false,hide:false,position:{my:"left-20 top",at:"center bottom+9",using:function(y,z){$(this).css(y);var A=z.target.left+z.target.width/2-z.element.left-20;$("<div>").addClass("tooltip-arrow").addClass(z.vertical).addClass(A<1?"align-left":"align-right").appendTo(this)}},content:function(){var A=this;var y=$(this).attr("data-href");if(!y){return'<div class="markdown">'+$(this).attr("title")+"</div>"}$.get(y,function z(D){var C=$(".ui-tooltip:visible");$(".ui-tooltip-content:visible").html(D);C.css({top:"",left:""});C.children(".tooltip-arrow").remove();var B=$(A).tooltip("option","position");B.of=$(A);C.position(B);$("#tooltip-subtasks a").not(".popover").click(function(E){E.preventDefault();E.stopPropagation();if($(this).hasClass("popover-subtask-restriction")){x.app.popover.open($(this).attr("href"));$(A).tooltip("close")}else{$.get($(this).attr("href"),z)}})});return'<i class="fa fa-spinner fa-spin"></i>'}}).on("mouseenter",function(){var y=this;$(this).tooltip("open");$(".ui-tooltip").on("mouseleave",function(){$(y).tooltip("close")})}).on("mouseleave focusout",function(y){y.stopImmediatePropagation();var z=this;setTimeout(function(){if(!$(".ui-tooltip:hover").length){$(z).tooltip("close")}},100)})};function l(){}l.prototype.showPreview=function(B){B.preventDefault();var y=$(".write-area");var A=$(".preview-area");var x=$("textarea");$("#markdown-write").parent().removeClass("form-tab-selected");$("#markdown-preview").parent().addClass("form-tab-selected");var z=$.ajax({url:$("body").data("markdown-preview-url"),contentType:"application/json",type:"POST",processData:false,dataType:"html",data:JSON.stringify({text:x.val()})});z.done(function(C){A.find(".markdown").html(C);A.css("height",x.css("height"));A.css("width",x.css("width"));y.hide();A.show()})};l.prototype.showWriter=function(x){x.preventDefault();$("#markdown-write").parent().addClass("form-tab-selected");$("#markdown-preview").parent().removeClass("form-tab-selected");$(".write-area").show();$(".preview-area").hide()};l.prototype.listen=function(){$(document).on("click","#markdown-preview",this.showPreview.bind(this));$(document).on("click","#markdown-write",this.showWriter.bind(this))};function b(){}b.prototype.expand=function(x){x.preventDefault();$(".sidebar-container").removeClass("sidebar-collapsed");$(".sidebar-collapse").show();$(".sidebar h2").show();$(".sidebar ul").show();$(".sidebar-expand").hide()};b.prototype.collapse=function(x){x.preventDefault();$(".sidebar-container").addClass("sidebar-collapsed");$(".sidebar-expand").show();$(".sidebar h2").hide();$(".sidebar ul").hide();$(".sidebar-collapse").hide()};b.prototype.listen=function(){$(document).on("click",".sidebar-collapse",this.collapse);$(document).on("click",".sidebar-expand",this.expand)};function f(x){this.app=x;this.keyboardShortcuts()}f.prototype.focus=function(){$(document).on("focus","#form-search",function(){if($("#form-search")[0].setSelectionRange){$("#form-search")[0].setSelectionRange($("#form-search").val().length,$("#form-search").val().length)}})};f.prototype.listen=function(){var x=this;$(document).on("click",".filter-helper",function(A){A.preventDefault();var z=$(this).data("filter");var y=$(this).data("append-filter");if(y){z=$("#form-search").val()+" "+y}$("#form-search").val(z);if($("#board").length){x.app.board.reloadFilters(z)}else{$("form.search").submit()}})};f.prototype.keyboardShortcuts=function(){var x=this;Mousetrap.bind("v b",function(z){var y=$(".view-board");if(y.length){window.location=y.attr("href")}});Mousetrap.bind("v c",function(z){var y=$(".view-calendar");if(y.length){window.location=y.attr("href")}});Mousetrap.bind("v l",function(z){var y=$(".view-listing");if(y.length){window.location=y.attr("href")}});Mousetrap.bind("v g",function(z){var y=$(".view-gantt");if(y.length){window.location=y.attr("href")}});Mousetrap.bind("f",function(z){z.preventDefault();var y=document.getElementById("form-search");if(y){y.focus()}});Mousetrap.bind("r",function(z){z.preventDefault();var y=$(".filter-reset").data("filter");$("#form-search").val(y);if($("#board").length){x.app.board.reloadFilters(y)}else{$("form.search").submit()}})};function m(){this.board=new k(this);this.markdown=new l();this.sidebar=new b();this.search=new f(this);this.swimlane=new g();this.dropdown=new r();this.tooltip=new q(this);this.popover=new t(this);this.task=new a();this.project=new n();this.keyboardShortcuts();this.chosen();this.poll();$(".alert-fade-out").delay(4000).fadeOut(800,function(){$(this).remove()});var x=false;$("select.task-reload-project-destination").change(function(){if(!x){$(".loading-icon").show();x=true;window.location=$(this).data("redirect").replace(/PROJECT_ID/g,$(this).val())}})}m.prototype.listen=function(){this.project.listen();this.popover.listen();this.markdown.listen();this.sidebar.listen();this.tooltip.listen();this.dropdown.listen();this.search.listen();this.task.listen();this.swimlane.listen();this.search.focus();this.autoComplete();this.datePicker();this.focus()};m.prototype.refresh=function(){$(document).off();this.listen()};m.prototype.focus=function(){$("[autofocus]").each(function(x,y){$(this).focus()});$(document).on("focus",".auto-select",function(){$(this).select()});$(document).on("mouseup",".auto-select",function(x){x.preventDefault()})};m.prototype.poll=function(){window.setInterval(this.checkSession,60000)};m.prototype.keyboardShortcuts=function(){var x=this;Mousetrap.bindGlobal("mod+enter",function(){$("form").submit()});Mousetrap.bind("b",function(y){y.preventDefault();$("#board-selector").trigger("chosen:open")});Mousetrap.bindGlobal("esc",function(){x.popover.close();x.dropdown.close()})};m.prototype.checkSession=function(){if(!$(".form-login").length){$.ajax({cache:false,url:$("body").data("status-url"),statusCode:{401:function(){window.location=$("body").data("login-url")}}})}};m.prototype.datePicker=function(){$.datepicker.setDefaults($.datepicker.regional[$("body").data("js-lang")]);$(".form-date").datepicker({showOtherMonths:true,selectOtherMonths:true,dateFormat:"yy-mm-dd",constrainInput:false});$(".form-datetime").datetimepicker({controlType:"select",oneLine:true,dateFormat:"yy-mm-dd",constrainInput:false})};m.prototype.autoComplete=function(){$(".autocomplete").each(function(){var y=$(this);var z=y.data("dst-field");var x=y.data("dst-extra-field");if($("#form-"+z).val()==""){y.parent().find("input[type=submit]").attr("disabled","disabled")}y.autocomplete({source:y.data("search-url"),minLength:1,select:function(A,B){$("input[name="+z+"]").val(B.item.id);if(x){$("input[name="+x+"]").val(B.item[x])}y.parent().find("input[type=submit]").removeAttr("disabled")}})})};m.prototype.chosen=function(){$(".chosen-select").each(function(){var x=$(this).data("search-threshold");if(x===undefined){x=10}$(this).chosen({width:"180px",no_results_text:$(this).data("notfound"),disable_search_threshold:x})});$(".select-auto-redirect").change(function(){var x=new RegExp($(this).data("redirect-regex"),"g");window.location=$(this).data("redirect-url").replace(x,$(this).val())})};m.prototype.showLoadingIcon=function(){$("body").append('<span id="app-loading-icon"> <i class="fa fa-spinner fa-spin"></i></span>')};m.prototype.hideLoadingIcon=function(){$("#app-loading-icon").remove()};m.prototype.isVisible=function(){var x="";if(typeof document.hidden!=="undefined"){x="visibilityState"}else{if(typeof document.mozHidden!=="undefined"){x="mozVisibilityState"}else{if(typeof document.msHidden!=="undefined"){x="msVisibilityState"}else{if(typeof document.webkitHidden!=="undefined"){x="webkitVisibilityState"}}}}if(x!=""){return document[x]=="visible"}return true};m.prototype.formatDuration=function(x){if(x>=86400){return Math.round(x/86400)+"d"}else{if(x>=3600){return Math.round(x/3600)+"h"}else{if(x>=60){return Math.round(x/60)+"m"}}}return x+"s"};function e(){this.pasteCatcher=null}e.prototype.execute=function(){this.initialize()};e.prototype.initialize=function(){this.destroy();if(!window.Clipboard){this.pasteCatcher=document.createElement("div");this.pasteCatcher.id="screenshot-pastezone";this.pasteCatcher.contentEditable="true";this.pasteCatcher.style.opacity=0;this.pasteCatcher.style.position="fixed";this.pasteCatcher.style.top=0;this.pasteCatcher.style.right=0;this.pasteCatcher.style.width=0;document.body.insertBefore(this.pasteCatcher,document.body.firstChild);this.pasteCatcher.focus();document.addEventListener("click",this.setFocus.bind(this));document.getElementById("screenshot-zone").addEventListener("click",this.setFocus.bind(this))}window.addEventListener("paste",this.pasteHandler.bind(this))};e.prototype.destroy=function(){if(this.pasteCatcher!=null){document.body.removeChild(this.pasteCatcher)}else{if(document.getElementById("screenshot-pastezone")){document.body.removeChild(document.getElementById("screenshot-pastezone"))}}document.removeEventListener("click",this.setFocus.bind(this));this.pasteCatcher=null};e.prototype.setFocus=function(){if(this.pasteCatcher!==null){this.pasteCatcher.focus()}};e.prototype.pasteHandler=function(C){if(C.clipboardData&&C.clipboardData.items){var A=C.clipboardData.items;if(A){for(var B=0;B<A.length;B++){if(A[B].type.indexOf("image")!==-1){var z=A[B].getAsFile();var x=new FileReader();var y=this;x.onload=function(D){y.createImage(D.target.result)};x.readAsDataURL(z)}}}}else{setTimeout(this.checkInput.bind(this),100)}};e.prototype.checkInput=function(){var x=this.pasteCatcher.childNodes[0];if(x){if(x.tagName==="IMG"){this.createImage(x.src)}}this.pasteCatcher.innerHTML=""};e.prototype.createImage=function(z){var y=new Image();y.src=z;y.onload=function(){var A=z.split("base64,");var B=A[1];$("input[name=screenshot]").val(B)};var x=document.getElementById("screenshot-zone");x.innerHTML="";x.className="screenshot-pasted";x.appendChild(y);this.destroy();this.initialize()};function j(){}j.prototype.execute=function(){var x=$("#calendar");x.fullCalendar({lang:$("body").data("js-lang"),editable:true,eventLimit:true,defaultView:"month",header:{left:"prev,next today",center:"title",right:"month,agendaWeek,agendaDay"},eventDrop:function(y){$.ajax({cache:false,url:x.data("save-url"),contentType:"application/json",type:"POST",processData:false,data:JSON.stringify({task_id:y.id,date_due:y.start.format()})})},viewRender:function(){var y=x.data("check-url");var A={start:x.fullCalendar("getView").start.format(),end:x.fullCalendar("getView").end.format()};for(var z in A){y+="&"+z+"="+A[z]}$.getJSON(y,function(B){x.fullCalendar("removeEvents");x.fullCalendar("addEventSource",B);x.fullCalendar("rerenderEvents")})}})};function k(x){this.app=x;this.checkInterval=null;this.savingInProgress=false}k.prototype.execute=function(){this.app.swimlane.refresh();this.restoreColumnViewMode();this.compactView();this.poll();this.keyboardShortcuts();this.listen();this.dragAndDrop();$(window).on("load",this.columnScrolling);$(window).resize(this.columnScrolling)};k.prototype.poll=function(){var x=parseInt($("#board").attr("data-check-interval"));if(x>0){this.checkInterval=window.setInterval(this.check.bind(this),x*1000)}};k.prototype.reloadFilters=function(x){this.app.showLoadingIcon();$.ajax({cache:false,url:$("#board").data("reload-url"),contentType:"application/json",type:"POST",processData:false,data:JSON.stringify({search:x}),success:this.refresh.bind(this),error:this.app.hideLoadingIcon.bind(this)})};k.prototype.check=function(){if(this.app.isVisible()&&!this.savingInProgress){var x=this;this.app.showLoadingIcon();$.ajax({cache:false,url:$("#board").data("check-url"),statusCode:{200:function(y){x.refresh(y)},304:function(){x.app.hideLoadingIcon()}}})}};k.prototype.save=function(A,B,x,z){var y=this;this.app.showLoadingIcon();this.savingInProgress=true;$.ajax({cache:false,url:$("#board").data("save-url"),contentType:"application/json",type:"POST",processData:false,data:JSON.stringify({task_id:A,column_id:B,swimlane_id:z,position:x}),success:function(C){y.refresh(C);this.savingInProgress=false},error:function(){y.app.hideLoadingIcon();this.savingInProgress=false}})};k.prototype.refresh=function(x){$("#board-container").replaceWith(x);this.app.refresh();this.app.swimlane.refresh();this.app.hideLoadingIcon();this.listen();this.dragAndDrop();this.compactView();this.restoreColumnViewMode();this.columnScrolling()};k.prototype.dragAndDrop=function(){var x=this;var y={forcePlaceholderSize:true,tolerance:"pointer",connectWith:".board-task-list",placeholder:"draggable-placeholder",items:".draggable-item",stop:function(A,H){var C=H.item;var G=C.attr("data-task-id");var I=C.attr("data-position");var F=C.attr("data-column-id");var E=C.attr("data-swimlane-id");var B=C.parent().attr("data-column-id");var z=C.parent().attr("data-swimlane-id");var D=C.index()+1;C.removeClass("draggable-item-selected");if(B!=F||z!=E||D!=I){x.changeTaskState(G);x.save(G,B,D,z)}},start:function(z,A){A.item.addClass("draggable-item-selected");A.placeholder.height(A.item.height())}};if($.support.touch){$(".task-board-sort-handle").css("display","inline");y.handle=".task-board-sort-handle"}$(".board-task-list").sortable(y)};k.prototype.changeTaskState=function(y){var x=$("div[data-task-id="+y+"]");x.addClass("task-board-saving-state");x.find(".task-board-saving-icon").show()};k.prototype.listen=function(){var x=this;$(document).on("click",".task-board",function(y){if(y.target.tagName!="A"){window.location=$(this).data("task-url")}});$(document).on("click",".filter-toggle-scrolling",function(y){y.preventDefault();x.toggleCompactView()});$(document).on("click",".filter-toggle-height",function(y){y.preventDefault();x.toggleColumnScrolling()});$(document).on("click",".board-toggle-column-view",function(){x.toggleColumnViewMode($(this).data("column-id"))})};k.prototype.toggleColumnScrolling=function(){var x=localStorage.getItem("column_scroll");if(x==undefined){x=1}localStorage.setItem("column_scroll",x==0?1:0);this.columnScrolling()};k.prototype.columnScrolling=function(){if(localStorage.getItem("column_scroll")==0){var x=80;$(".filter-max-height").show();$(".filter-min-height").hide();$(".board-rotation-wrapper").css("min-height","");$(".board-task-list").each(function(){var y=$(this).height();if(y>x){x=y}});$(".board-task-list").css("min-height",x);$(".board-task-list").css("height","")}else{$(".filter-max-height").hide();$(".filter-min-height").show();if($(".board-swimlane").length>1){$(".board-task-list").each(function(){if($(this).height()>500){$(this).css("height",500)}else{$(this).css("min-height",320);$(".board-rotation-wrapper").css("min-height",320)}})}else{var x=$(window).height()-170;$(".board-task-list").css("height",x);$(".board-rotation-wrapper").css("min-height",x)}}};k.prototype.toggleCompactView=function(){var x=localStorage.getItem("horizontal_scroll")||1;localStorage.setItem("horizontal_scroll",x==0?1:0);this.compactView()};k.prototype.compactView=function(){if(localStorage.getItem("horizontal_scroll")==0){$(".filter-wide").show();$(".filter-compact").hide();$("#board-container").addClass("board-container-compact");$("#board th:not(.board-column-header-collapsed)").addClass("board-column-compact")}else{$(".filter-wide").hide();$(".filter-compact").show();$("#board-container").removeClass("board-container-compact");$("#board th").removeClass("board-column-compact")}};k.prototype.toggleCollapsedMode=function(){var x=this;this.app.showLoadingIcon();$.ajax({cache:false,url:$('.filter-display-mode:not([style="display: none;"]) a').attr("href"),success:function(y){$(".filter-display-mode").toggle();x.refresh(y)}})};k.prototype.restoreColumnViewMode=function(){var x=this;$(".board-column-header").each(function(){var y=$(this).data("column-id");if(localStorage.getItem("hidden_column_"+y)){x.hideColumn(y)}})};k.prototype.toggleColumnViewMode=function(x){if(localStorage.getItem("hidden_column_"+x)){this.showColumn(x)}else{this.hideColumn(x)}};k.prototype.hideColumn=function(x){$(".board-column-"+x+" .board-column-expanded").hide();$(".board-column-"+x+" .board-column-collapsed").show();$(".board-column-header-"+x+" .board-column-expanded").hide();$(".board-column-header-"+x+" .board-column-collapsed").show();$(".board-column-header-"+x).each(function(){$(this).removeClass("board-column-compact");$(this).addClass("board-column-header-collapsed")});$(".board-column-"+x).each(function(){$(this).addClass("board-column-task-collapsed")});$(".board-column-"+x+" .board-rotation").each(function(){$(this).css("width",$(".board-column-"+x+"").height())});localStorage.setItem("hidden_column_"+x,1)};k.prototype.showColumn=function(x){$(".board-column-"+x+" .board-column-expanded").show();$(".board-column-"+x+" .board-column-collapsed").hide();$(".board-column-header-"+x+" .board-column-expanded").show();$(".board-column-header-"+x+" .board-column-collapsed").hide();$(".board-column-header-"+x).removeClass("board-column-header-collapsed");$(".board-column-"+x).removeClass("board-column-task-collapsed");if(localStorage.getItem("horizontal_scroll")==0){$(".board-column-header-"+x).addClass("board-column-compact")}localStorage.removeItem("hidden_column_"+x)};k.prototype.keyboardShortcuts=function(){var x=this;Mousetrap.bind("c",function(){x.toggleCompactView()});Mousetrap.bind("s",function(){x.toggleCollapsedMode()});Mousetrap.bind("n",function(){x.app.popover.open($("#board").data("task-creation-url"))})};function g(){}g.prototype.getStorageKey=function(){return"hidden_swimlanes_"+$("#board").data("project-id")};g.prototype.expand=function(y){var z=this.getAllCollapsed();var x=z.indexOf(y);if(x>-1){z.splice(x,1)}localStorage.setItem(this.getStorageKey(),JSON.stringify(z));$(".board-swimlane-columns-"+y).css("display","table-row");$(".board-swimlane-tasks-"+y).css("display","table-row");$(".hide-icon-swimlane-"+y).css("display","inline");$(".show-icon-swimlane-"+y).css("display","none")};g.prototype.collapse=function(x){var y=this.getAllCollapsed();if(y.indexOf(x)<0){y.push(x);localStorage.setItem(this.getStorageKey(),JSON.stringify(y))}$(".board-swimlane-columns-"+x+":not(:first-child)").css("display","none");$(".board-swimlane-tasks-"+x).css("display","none");$(".hide-icon-swimlane-"+x).css("display","none");$(".show-icon-swimlane-"+x).css("display","inline")};g.prototype.isCollapsed=function(x){return this.getAllCollapsed().indexOf(x)>-1};g.prototype.getAllCollapsed=function(){return JSON.parse(localStorage.getItem(this.getStorageKey()))||[]};g.prototype.refresh=function(){var y=this.getAllCollapsed();for(var x=0;x<y.length;x++){this.collapse(y[x])}};g.prototype.listen=function(){var x=this;$(document).on("click",".board-swimlane-toggle",function(z){z.preventDefault();var y=$(this).data("swimlane-id");if(x.isCollapsed(y)){x.expand(y)}else{x.collapse(y)}})};function c(x){this.app=x;this.data=[];this.options={container:"#gantt-chart",showWeekends:true,allowMoves:true,allowResizes:true,cellWidth:21,cellHeight:31,slideWidth:1000,vHeaderWidth:200}}c.prototype.saveRecord=function(x){this.app.showLoadingIcon();$.ajax({cache:false,url:$(this.options.container).data("save-url"),contentType:"application/json",type:"POST",processData:false,data:JSON.stringify(x),complete:this.app.hideLoadingIcon.bind(this)})};c.prototype.execute=function(){this.data=this.prepareData($(this.options.container).data("records"));var A=Math.floor((this.options.slideWidth/this.options.cellWidth)+5);var z=this.getDateRange(A);var x=z[0];var C=z[1];var y=$(this.options.container);var B=jQuery("<div>",{"class":"ganttview"});B.append(this.renderVerticalHeader());B.append(this.renderSlider(x,C));y.append(B);jQuery("div.ganttview-grid-row div.ganttview-grid-row-cell:last-child",y).addClass("last");jQuery("div.ganttview-hzheader-days div.ganttview-hzheader-day:last-child",y).addClass("last");jQuery("div.ganttview-hzheader-months div.ganttview-hzheader-month:last-child",y).addClass("last");if(!$(this.options.container).data("readonly")){this.listenForBlockResize(x);this.listenForBlockMove(x)}else{this.options.allowResizes=false;this.options.allowMoves=false}};c.prototype.renderVerticalHeader=function(){var B=jQuery("<div>",{"class":"ganttview-vtheader"});var y=jQuery("<div>",{"class":"ganttview-vtheader-item"});var A=jQuery("<div>",{"class":"ganttview-vtheader-series"});for(var x=0;x<this.data.length;x++){var z=jQuery("<span>").append(jQuery("<i>",{"class":"fa fa-info-circle tooltip",title:this.getVerticalHeaderTooltip(this.data[x])})).append(" ");if(this.data[x].type=="task"){z.append(jQuery("<a>",{href:this.data[x].link,target:"_blank",title:this.data[x].title}).append(this.data[x].title))}else{z.append(jQuery("<a>",{href:this.data[x].board_link,target:"_blank",title:$(this.options.container).data("label-board-link")}).append('<i class="fa fa-th"></i>')).append(" ").append(jQuery("<a>",{href:this.data[x].gantt_link,target:"_blank",title:$(this.options.container).data("label-gantt-link")}).append('<i class="fa fa-sliders"></i>')).append(" ").append(jQuery("<a>",{href:this.data[x].link,target:"_blank"}).append(this.data[x].title))}A.append(jQuery("<div>",{"class":"ganttview-vtheader-series-name"}).append(z))}y.append(A);B.append(y);return B};c.prototype.renderSlider=function(y,A){var x=jQuery("<div>",{"class":"ganttview-slide-container"});var z=this.getDates(y,A);x.append(this.renderHorizontalHeader(z));x.append(this.renderGrid(z));x.append(this.addBlockContainers());this.addBlocks(x,y);return x};c.prototype.renderHorizontalHeader=function(x){var E=jQuery("<div>",{"class":"ganttview-hzheader"});var C=jQuery("<div>",{"class":"ganttview-hzheader-months"});var B=jQuery("<div>",{"class":"ganttview-hzheader-days"});var A=0;for(var F in x){for(var z in x[F]){var G=x[F][z].length*this.options.cellWidth;A=A+G;C.append(jQuery("<div>",{"class":"ganttview-hzheader-month",css:{width:(G-1)+"px"}}).append($.datepicker.regional[$("body").data("js-lang")].monthNames[z]+" "+F));for(var D in x[F][z]){B.append(jQuery("<div>",{"class":"ganttview-hzheader-day"}).append(x[F][z][D].getDate()))}}}C.css("width",A+"px");B.css("width",A+"px");E.append(C).append(B);return E};c.prototype.renderGrid=function(x){var G=jQuery("<div>",{"class":"ganttview-grid"});var B=jQuery("<div>",{"class":"ganttview-grid-row"});for(var E in x){for(var z in x[E]){for(var D in x[E][z]){var A=jQuery("<div>",{"class":"ganttview-grid-row-cell"});if(this.options.showWeekends&&this.isWeekend(x[E][z][D])){A.addClass("ganttview-weekend")}B.append(A)}}}var F=jQuery("div.ganttview-grid-row-cell",B).length*this.options.cellWidth;B.css("width",F+"px");G.css("width",F+"px");for(var C=0;C<this.data.length;C++){G.append(B.clone())}return G};c.prototype.addBlockContainers=function(){var y=jQuery("<div>",{"class":"ganttview-blocks"});for(var x=0;x<this.data.length;x++){y.append(jQuery("<div>",{"class":"ganttview-block-container"}))}return y};c.prototype.addBlocks=function(y,x){var F=jQuery("div.ganttview-blocks div.ganttview-block-container",y);var z=0;for(var C=0;C<this.data.length;C++){var D=this.data[C];var G=this.daysBetween(D.start,D.end)+1;var B=this.daysBetween(x,D.start);var E=jQuery("<div>",{"class":"ganttview-block-text"});var A=jQuery("<div>",{"class":"ganttview-block tooltip"+(this.options.allowMoves?" ganttview-block-movable":""),title:this.getBarTooltip(D),css:{width:((G*this.options.cellWidth)-9)+"px","margin-left":(B*this.options.cellWidth)+"px"}}).append(E);if(G>=2){E.append(D.progress)}A.data("record",D);this.setBarColor(A,D);if(D.progress!="0%"){A.append(jQuery("<div>",{css:{"z-index":0,position:"absolute",top:0,bottom:0,"background-color":D.color.border,width:D.progress,opacity:0.4}}))}jQuery(F[z]).append(A);z=z+1}};c.prototype.getVerticalHeaderTooltip=function(y){var D="";if(y.type=="task"){D="<strong>"+y.column_title+"</strong> ("+y.progress+")<br/>"+y.title}else{var A=["managers","members"];for(var z in A){var B=A[z];if(!jQuery.isEmptyObject(y.users[B])){var C=jQuery("<ul>");for(var x in y.users[B]){C.append(jQuery("<li>").append(y.users[B][x]))}D+="<p><strong>"+$(this.options.container).data("label-"+B)+"</strong></p>"+C[0].outerHTML}}}return D};c.prototype.getBarTooltip=function(x){var y="";if(x.not_defined){y=$(this.options.container).data("label-not-defined")}else{if(x.type=="task"){y="<strong>"+x.progress+"</strong><br/>"+$(this.options.container).data("label-assignee")+" "+(x.assignee?x.assignee:"")+"<br/>"}y+=$(this.options.container).data("label-start-date")+" "+$.datepicker.formatDate("yy-mm-dd",x.start)+"<br/>";y+=$(this.options.container).data("label-end-date")+" "+$.datepicker.formatDate("yy-mm-dd",x.end)}return y};c.prototype.setBarColor=function(y,x){if(x.not_defined){y.addClass("ganttview-block-not-defined")}else{y.css("background-color",x.color.background);y.css("border-color",x.color.border)}};c.prototype.listenForBlockResize=function(x){var y=this;jQuery("div.ganttview-block",this.options.container).resizable({grid:this.options.cellWidth,handles:"e,w",delay:300,stop:function(){var z=jQuery(this);y.updateDataAndPosition(z,x);y.saveRecord(z.data("record"))}})};c.prototype.listenForBlockMove=function(x){var y=this;jQuery("div.ganttview-block",this.options.container).draggable({axis:"x",delay:300,grid:[this.options.cellWidth,this.options.cellWidth],stop:function(){var z=jQuery(this);y.updateDataAndPosition(z,x);y.saveRecord(z.data("record"))}})};c.prototype.updateDataAndPosition=function(C,A){var x=jQuery("div.ganttview-slide-container",this.options.container);var G=x.scrollLeft();var D=C.offset().left-x.offset().left-1+G;var F=C.data("record");F.not_defined=false;this.setBarColor(C,F);var z=Math.round(D/this.options.cellWidth);var E=this.addDays(this.cloneDate(A),z);F.start=E;var y=C.outerWidth();var B=Math.round(y/this.options.cellWidth)-1;F.end=this.addDays(this.cloneDate(E),B);if(F.type==="task"&&B>0){jQuery("div.ganttview-block-text",C).text(F.progress)}C.attr("title",this.getBarTooltip(F));C.data("record",F);C.css("top","").css("left","").css("position","relative").css("margin-left",D+"px")};c.prototype.getDates=function(B,x){var A=[];A[B.getFullYear()]=[];A[B.getFullYear()][B.getMonth()]=[B];var z=B;while(this.compareDate(z,x)==-1){var y=this.addDays(this.cloneDate(z),1);if(!A[y.getFullYear()]){A[y.getFullYear()]=[]}if(!A[y.getFullYear()][y.getMonth()]){A[y.getFullYear()][y.getMonth()]=[]}A[y.getFullYear()][y.getMonth()].push(y);z=y}return A};c.prototype.prepareData=function(z){for(var y=0;y<z.length;y++){var A=new Date(z[y].start[0],z[y].start[1]-1,z[y].start[2],0,0,0,0);z[y].start=A;var x=new Date(z[y].end[0],z[y].end[1]-1,z[y].end[2],0,0,0,0);z[y].end=x}return z};c.prototype.getDateRange=function(z){var C=new Date();var y=new Date();for(var A=0;A<this.data.length;A++){var B=new Date();B.setTime(Date.parse(this.data[A].start));var x=new Date();x.setTime(Date.parse(this.data[A].end));if(A==0){C=B;y=x}if(this.compareDate(C,B)==1){C=B}if(this.compareDate(y,x)==-1){y=x}}if(this.daysBetween(C,y)<z){y=this.addDays(this.cloneDate(C),z)}C.setDate(C.getDate()-1);return[C,y]};c.prototype.daysBetween=function(A,x){if(!A||!x){return 0}var z=0,y=this.cloneDate(A);while(this.compareDate(y,x)==-1){z=z+1;this.addDays(y,1)}return z};c.prototype.isWeekend=function(x){return x.getDay()%6==0};c.prototype.cloneDate=function(x){return new Date(x.getTime())};c.prototype.addDays=function(x,y){x.setDate(x.getDate()+y*1);return x};c.prototype.compareDate=function(y,x){if(isNaN(y)||isNaN(x)){throw new Error(y+" - "+x)}else{if(y instanceof Date&&x instanceof Date){return(y<x)?-1:(y>x)?1:0}else{throw new TypeError(y+" - "+x)}}};function a(){}a.prototype.listen=function(){$(document).on("click",".color-square",function(){$(".color-square-selected").removeClass("color-square-selected");$(this).addClass("color-square-selected");$("#form-color_id").val($(this).data("color-id"))})};function n(){}n.prototype.listen=function(){$(".project-change-role").on("change",function(){$.ajax({cache:false,url:$(this).data("url"),contentType:"application/json",type:"POST",processData:false,data:JSON.stringify({id:$(this).data("id"),role:$(this).val()})})})};function s(){}s.prototype.execute=function(){var z=$("#chart").data("metrics");var y=[];for(var x=0;x<z.length;x++){y.push([z[x].column_title,z[x].nb_tasks])}c3.generate({data:{columns:y,type:"donut"}})};function p(){}p.prototype.execute=function(){var z=$("#chart").data("metrics");var y=[];for(var x=0;x<z.length;x++){y.push([z[x].user,z[x].nb_tasks])}c3.generate({data:{columns:y,type:"donut"}})};function d(){}d.prototype.execute=function(){var D=$("#chart").data("metrics");var C=[];var x=[];var y=[];var A=d3.time.format("%Y-%m-%d");var E=d3.time.format($("#chart").data("date-format"));for(var B=0;B<D.length;B++){for(var z=0;z<D[B].length;z++){if(B==0){C.push([D[B][z]]);if(z>0){x.push(D[B][z])}}else{C[z].push(D[B][z]);if(z==0){y.push(E(A.parse(D[B][z])))}}}}c3.generate({data:{columns:C,type:"area-spline",groups:[x]},axis:{x:{type:"category",categories:y}}})};function o(){}o.prototype.execute=function(){var C=$("#chart").data("metrics");var B=[[$("#chart").data("label-total")]];var x=[];var z=d3.time.format("%Y-%m-%d");var D=d3.time.format($("#chart").data("date-format"));for(var A=0;A<C.length;A++){for(var y=0;y<C[A].length;y++){if(A==0){B.push([C[A][y]])}else{B[y+1].push(C[A][y]);if(y>0){if(B[0][A]==undefined){B[0].push(0)}B[0][A]+=C[A][y]}if(y==0){x.push(D(z.parse(C[A][y])))}}}}c3.generate({data:{columns:B},axis:{x:{type:"category",categories:x}}})};function h(x){this.app=x}h.prototype.execute=function(){var z=$("#chart").data("metrics");var A=[$("#chart").data("label")];var x=[];for(var y in z){A.push(z[y].average);x.push(z[y].title)}c3.generate({data:{columns:[A],type:"bar"},bar:{width:{ratio:0.5}},axis:{x:{type:"category",categories:x},y:{tick:{format:this.app.formatDuration}}},legend:{show:false}})};function w(x){this.app=x}w.prototype.execute=function(){var z=$("#chart").data("metrics");var A=[$("#chart").data("label")];var x=[];for(var y=0;y<z.length;y++){A.push(z[y].time_spent);x.push(z[y].title)}c3.generate({data:{columns:[A],type:"bar"},bar:{width:{ratio:0.5}},axis:{x:{type:"category",categories:x},y:{tick:{format:this.app.formatDuration}}},legend:{show:false}})};function u(x){this.app=x}u.prototype.execute=function(){var D=$("#chart").data("metrics");var C=[$("#chart").data("label-cycle")];var z=[$("#chart").data("label-lead")];var y=[];var B={};B[$("#chart").data("label-cycle")]="area";B[$("#chart").data("label-lead")]="area-spline";var x={};x[$("#chart").data("label-lead")]="#afb42b";x[$("#chart").data("label-cycle")]="#4e342e";for(var A=0;A<D.length;A++){C.push(parseInt(D[A].avg_cycle_time));z.push(parseInt(D[A].avg_lead_time));y.push(D[A].day)}c3.generate({data:{columns:[z,C],types:B,colors:x},axis:{x:{type:"category",categories:y},y:{tick:{format:this.app.formatDuration}}}})};function i(x){this.app=x}i.prototype.execute=function(){var C=$("#chart").data("metrics");var y=$("#chart").data("label-open");var x=$("#chart").data("label-closed");var D=[$("#chart").data("label-spent")];var B=[$("#chart").data("label-estimated")];var A=[];for(var z in C){D.push(parseFloat(C[z].time_spent));B.push(parseFloat(C[z].time_estimated));A.push(z=="open"?y:x)}c3.generate({data:{columns:[D,B],type:"bar"},bar:{width:{ratio:0.2}},axis:{x:{type:"category",categories:A}},legend:{show:true}})};function v(){this.routes={}}v.prototype.addRoute=function(y,x){this.routes[y]=x};v.prototype.dispatch=function(y){for(var z in this.routes){if(document.getElementById(z)){var x=Object.create(this.routes[z].prototype);this.routes[z].apply(x,[y]);x.execute();break}}};jQuery(document).ready(function(){var y=new m();var x=new v();x.addRoute("board",k);x.addRoute("calendar",j);x.addRoute("screenshot-zone",e);x.addRoute("analytic-task-repartition",s);x.addRoute("analytic-user-repartition",p);x.addRoute("analytic-cfd",d);x.addRoute("analytic-burndown",o);x.addRoute("analytic-avg-time-column",h);x.addRoute("analytic-task-time-column",w);x.addRoute("analytic-lead-cycle-time",u);x.addRoute("analytic-compare-hours",i);x.addRoute("gantt-chart",c);x.dispatch(y);y.listen()})})(); \ No newline at end of file +!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){function c(a){return a>1&&5>a&&1!==~~(a/10)}function d(a,b,d,e){var f=a+" ";switch(d){case"s":return b||e?"pár sekund":"pár sekundami";case"m":return b?"minuta":e?"minutu":"minutou";case"mm":return b||e?f+(c(a)?"minuty":"minut"):f+"minutami";case"h":return b?"hodina":e?"hodinu":"hodinou";case"hh":return b||e?f+(c(a)?"hodiny":"hodin"):f+"hodinami";case"d":return b||e?"den":"dnem";case"dd":return b||e?f+(c(a)?"dny":"dní"):f+"dny";case"M":return b||e?"měsíc":"měsícem";case"MM":return b||e?f+(c(a)?"měsíce":"měsíců"):f+"měsíci";case"y":return b||e?"rok":"rokem";case"yy":return b||e?f+(c(a)?"roky":"let"):f+"lety"}}var e="leden_únor_březen_duben_květen_červen_červenec_srpen_září_říjen_listopad_prosinec".split("_"),f="led_úno_bře_dub_kvě_čvn_čvc_srp_zář_říj_lis_pro".split("_");(b.defineLocale||b.lang).call(b,"cs",{months:e,monthsShort:f,monthsParse:function(a,b){var c,d=[];for(c=0;12>c;c++)d[c]=new RegExp("^"+a[c]+"$|^"+b[c]+"$","i");return d}(e,f),weekdays:"neděle_pondělí_úterý_středa_čtvrtek_pátek_sobota".split("_"),weekdaysShort:"ne_po_út_st_čt_pá_so".split("_"),weekdaysMin:"ne_po_út_st_čt_pá_so".split("_"),longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd D. MMMM YYYY LT"},calendar:{sameDay:"[dnes v] LT",nextDay:"[zítra v] LT",nextWeek:function(){switch(this.day()){case 0:return"[v neděli v] LT";case 1:case 2:return"[v] dddd [v] LT";case 3:return"[ve středu v] LT";case 4:return"[ve čtvrtek v] LT";case 5:return"[v pátek v] LT";case 6:return"[v sobotu v] LT"}},lastDay:"[včera v] LT",lastWeek:function(){switch(this.day()){case 0:return"[minulou neděli v] LT";case 1:case 2:return"[minulé] dddd [v] LT";case 3:return"[minulou středu v] LT";case 4:case 5:return"[minulý] dddd [v] LT";case 6:return"[minulou sobotu v] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"před %s",s:d,m:d,mm:d,h:d,hh:d,d:d,dd:d,M:d,MM:d,y:d,yy:d},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("cs","cs",{closeText:"Zavřít",prevText:"<Dříve",nextText:"Později>",currentText:"Nyní",monthNames:["leden","únor","březen","duben","květen","červen","červenec","srpen","září","říjen","listopad","prosinec"],monthNamesShort:["led","úno","bře","dub","kvě","čer","čvc","srp","zář","říj","lis","pro"],dayNames:["neděle","pondělí","úterý","středa","čtvrtek","pátek","sobota"],dayNamesShort:["ne","po","út","st","čt","pá","so"],dayNamesMin:["ne","po","út","st","čt","pá","so"],weekHeader:"Týd",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("cs",{buttonText:{month:"Měsíc",week:"Týden",day:"Den",list:"Agenda"},allDayText:"Celý den",eventLimitText:function(a){return"+další: "+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"da",{months:"januar_februar_marts_april_maj_juni_juli_august_september_oktober_november_december".split("_"),monthsShort:"jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec".split("_"),weekdays:"søndag_mandag_tirsdag_onsdag_torsdag_fredag_lørdag".split("_"),weekdaysShort:"søn_man_tir_ons_tor_fre_lør".split("_"),weekdaysMin:"sø_ma_ti_on_to_fr_lø".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd [d.] D. MMMM YYYY LT"},calendar:{sameDay:"[I dag kl.] LT",nextDay:"[I morgen kl.] LT",nextWeek:"dddd [kl.] LT",lastDay:"[I går kl.] LT",lastWeek:"[sidste] dddd [kl] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"%s siden",s:"få sekunder",m:"et minut",mm:"%d minutter",h:"en time",hh:"%d timer",d:"en dag",dd:"%d dage",M:"en måned",MM:"%d måneder",y:"et år",yy:"%d år"},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("da","da",{closeText:"Luk",prevText:"<Forrige",nextText:"Næste>",currentText:"Idag",monthNames:["Januar","Februar","Marts","April","Maj","Juni","Juli","August","September","Oktober","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],dayNames:["Søndag","Mandag","Tirsdag","Onsdag","Torsdag","Fredag","Lørdag"],dayNamesShort:["Søn","Man","Tir","Ons","Tor","Fre","Lør"],dayNamesMin:["Sø","Ma","Ti","On","To","Fr","Lø"],weekHeader:"Uge",dateFormat:"dd-mm-yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("da",{buttonText:{month:"Måned",week:"Uge",day:"Dag",list:"Agenda"},allDayText:"Hele dagen",eventLimitText:"flere"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){function c(a,b,c,d){var e={m:["eine Minute","einer Minute"],h:["eine Stunde","einer Stunde"],d:["ein Tag","einem Tag"],dd:[a+" Tage",a+" Tagen"],M:["ein Monat","einem Monat"],MM:[a+" Monate",a+" Monaten"],y:["ein Jahr","einem Jahr"],yy:[a+" Jahre",a+" Jahren"]};return b?e[c][0]:e[c][1]}(b.defineLocale||b.lang).call(b,"de",{months:"Januar_Februar_März_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember".split("_"),monthsShort:"Jan._Febr._Mrz._Apr._Mai_Jun._Jul._Aug._Sept._Okt._Nov._Dez.".split("_"),weekdays:"Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag".split("_"),weekdaysShort:"So._Mo._Di._Mi._Do._Fr._Sa.".split("_"),weekdaysMin:"So_Mo_Di_Mi_Do_Fr_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd, D. MMMM YYYY LT"},calendar:{sameDay:"[Heute um] LT [Uhr]",sameElse:"L",nextDay:"[Morgen um] LT [Uhr]",nextWeek:"dddd [um] LT [Uhr]",lastDay:"[Gestern um] LT [Uhr]",lastWeek:"[letzten] dddd [um] LT [Uhr]"},relativeTime:{future:"in %s",past:"vor %s",s:"ein paar Sekunden",m:c,mm:"%d Minuten",h:c,hh:"%d Stunden",d:c,dd:c,M:c,MM:c,y:c,yy:c},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("de","de",{closeText:"Schließen",prevText:"<Zurück",nextText:"Vor>",currentText:"Heute",monthNames:["Januar","Februar","März","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember"],monthNamesShort:["Jan","Feb","Mär","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Dez"],dayNames:["Sonntag","Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag"],dayNamesShort:["So","Mo","Di","Mi","Do","Fr","Sa"],dayNamesMin:["So","Mo","Di","Mi","Do","Fr","Sa"],weekHeader:"KW",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("de",{buttonText:{month:"Monat",week:"Woche",day:"Tag",list:"Terminübersicht"},allDayText:"Ganztägig",eventLimitText:function(a){return"+ weitere "+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){var c="ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.".split("_"),d="ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic".split("_");(b.defineLocale||b.lang).call(b,"es",{months:"enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre".split("_"),monthsShort:function(a,b){return/-MMM-/.test(b)?d[a.month()]:c[a.month()]},weekdays:"domingo_lunes_martes_miércoles_jueves_viernes_sábado".split("_"),weekdaysShort:"dom._lun._mar._mié._jue._vie._sáb.".split("_"),weekdaysMin:"Do_Lu_Ma_Mi_Ju_Vi_Sá".split("_"),longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY LT",LLLL:"dddd, D [de] MMMM [de] YYYY LT"},calendar:{sameDay:function(){return"[hoy a la"+(1!==this.hours()?"s":"")+"] LT"},nextDay:function(){return"[mañana a la"+(1!==this.hours()?"s":"")+"] LT"},nextWeek:function(){return"dddd [a la"+(1!==this.hours()?"s":"")+"] LT"},lastDay:function(){return"[ayer a la"+(1!==this.hours()?"s":"")+"] LT"},lastWeek:function(){return"[el] dddd [pasado a la"+(1!==this.hours()?"s":"")+"] LT"},sameElse:"L"},relativeTime:{future:"en %s",past:"hace %s",s:"unos segundos",m:"un minuto",mm:"%d minutos",h:"una hora",hh:"%d horas",d:"un día",dd:"%d días",M:"un mes",MM:"%d meses",y:"un año",yy:"%d años"},ordinalParse:/\d{1,2}º/,ordinal:"%dº",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("es","es",{closeText:"Cerrar",prevText:"<Ant",nextText:"Sig>",currentText:"Hoy",monthNames:["enero","febrero","marzo","abril","mayo","junio","julio","agosto","septiembre","octubre","noviembre","diciembre"],monthNamesShort:["ene","feb","mar","abr","may","jun","jul","ago","sep","oct","nov","dic"],dayNames:["domingo","lunes","martes","miércoles","jueves","viernes","sábado"],dayNamesShort:["dom","lun","mar","mié","jue","vie","sáb"],dayNamesMin:["D","L","M","X","J","V","S"],weekHeader:"Sm",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("es",{buttonText:{month:"Mes",week:"Semana",day:"Día",list:"Agenda"},allDayHtml:"Todo<br/>el día",eventLimitText:"más"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"el",{monthsNominativeEl:"Ιανουάριος_Φεβρουάριος_Μάρτιος_Απρίλιος_Μάιος_Ιούνιος_Ιούλιος_Αύγουστος_Σεπτέμβριος_Οκτώβριος_Νοέμβριος_Δεκέμβριος".split("_"),monthsGenitiveEl:"Ιανουαρίου_Φεβρουαρίου_Μαρτίου_Απριλίου_Μαΐου_Ιουνίου_Ιουλίου_Αυγούστου_Σεπτεμβρίου_Οκτωβρίου_Νοεμβρίου_Δεκεμβρίου".split("_"),months:function(a,b){return/D/.test(b.substring(0,b.indexOf("MMMM")))?this._monthsGenitiveEl[a.month()]:this._monthsNominativeEl[a.month()]},monthsShort:"Ιαν_Φεβ_Μαρ_Απρ_Μαϊ_Ιουν_Ιουλ_Αυγ_Σεπ_Οκτ_Νοε_Δεκ".split("_"),weekdays:"Κυριακή_Δευτέρα_Τρίτη_Τετάρτη_Πέμπτη_Παρασκευή_Σάββατο".split("_"),weekdaysShort:"Κυρ_Δευ_Τρι_Τετ_Πεμ_Παρ_Σαβ".split("_"),weekdaysMin:"Κυ_Δε_Τρ_Τε_Πε_Πα_Σα".split("_"),meridiem:function(a,b,c){return a>11?c?"μμ":"ΜΜ":c?"πμ":"ΠΜ"},isPM:function(a){return"μ"===(a+"").toLowerCase()[0]},meridiemParse:/[ΠΜ]\.?Μ?\.?/i,longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendarEl:{sameDay:"[Σήμερα {}] LT",nextDay:"[Αύριο {}] LT",nextWeek:"dddd [{}] LT",lastDay:"[Χθες {}] LT",lastWeek:function(){switch(this.day()){case 6:return"[το προηγούμενο] dddd [{}] LT";default:return"[την προηγούμενη] dddd [{}] LT"}},sameElse:"L"},calendar:function(a,b){var c=this._calendarEl[a],d=b&&b.hours();return"function"==typeof c&&(c=c.apply(b)),c.replace("{}",d%12===1?"στη":"στις")},relativeTime:{future:"σε %s",past:"%s πριν",s:"λίγα δευτερόλεπτα",m:"ένα λεπτό",mm:"%d λεπτά",h:"μία ώρα",hh:"%d ώρες",d:"μία μέρα",dd:"%d μέρες",M:"ένας μήνας",MM:"%d μήνες",y:"ένας χρόνος",yy:"%d χρόνια"},ordinalParse:/\d{1,2}η/,ordinal:"%dη",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("el","el",{closeText:"Κλείσιμο",prevText:"Προηγούμενος",nextText:"Επόμενος",currentText:"Σήμερα",monthNames:["Ιανουάριος","Φεβρουάριος","Μάρτιος","Απρίλιος","Μάιος","Ιούνιος","Ιούλιος","Αύγουστος","Σεπτέμβριος","Οκτώβριος","Νοέμβριος","Δεκέμβριος"],monthNamesShort:["Ιαν","Φεβ","Μαρ","Απρ","Μαι","Ιουν","Ιουλ","Αυγ","Σεπ","Οκτ","Νοε","Δεκ"],dayNames:["Κυριακή","Δευτέρα","Τρίτη","Τετάρτη","Πέμπτη","Παρασκευή","Σάββατο"],dayNamesShort:["Κυρ","Δευ","Τρι","Τετ","Πεμ","Παρ","Σαβ"],dayNamesMin:["Κυ","Δε","Τρ","Τε","Πε","Πα","Σα"],weekHeader:"Εβδ",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("el",{buttonText:{month:"Μήνας",week:"Εβδομάδα",day:"Ημέρα",list:"Ατζέντα"},allDayText:"Ολοήμερο",eventLimitText:"περισσότερα"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){function c(a,b,c,e){var f="";switch(c){case"s":return e?"muutaman sekunnin":"muutama sekunti";case"m":return e?"minuutin":"minuutti";case"mm":f=e?"minuutin":"minuuttia";break;case"h":return e?"tunnin":"tunti";case"hh":f=e?"tunnin":"tuntia";break;case"d":return e?"päivän":"päivä";case"dd":f=e?"päivän":"päivää";break;case"M":return e?"kuukauden":"kuukausi";case"MM":f=e?"kuukauden":"kuukautta";break;case"y":return e?"vuoden":"vuosi";case"yy":f=e?"vuoden":"vuotta"}return f=d(a,e)+" "+f}function d(a,b){return 10>a?b?f[a]:e[a]:a}var e="nolla yksi kaksi kolme neljä viisi kuusi seitsemän kahdeksan yhdeksän".split(" "),f=["nolla","yhden","kahden","kolmen","neljän","viiden","kuuden",e[7],e[8],e[9]];(b.defineLocale||b.lang).call(b,"fi",{months:"tammikuu_helmikuu_maaliskuu_huhtikuu_toukokuu_kesäkuu_heinäkuu_elokuu_syyskuu_lokakuu_marraskuu_joulukuu".split("_"),monthsShort:"tammi_helmi_maalis_huhti_touko_kesä_heinä_elo_syys_loka_marras_joulu".split("_"),weekdays:"sunnuntai_maanantai_tiistai_keskiviikko_torstai_perjantai_lauantai".split("_"),weekdaysShort:"su_ma_ti_ke_to_pe_la".split("_"),weekdaysMin:"su_ma_ti_ke_to_pe_la".split("_"),longDateFormat:{LT:"HH.mm",LTS:"HH.mm.ss",L:"DD.MM.YYYY",LL:"Do MMMM[ta] YYYY",LLL:"Do MMMM[ta] YYYY, [klo] LT",LLLL:"dddd, Do MMMM[ta] YYYY, [klo] LT",l:"D.M.YYYY",ll:"Do MMM YYYY",lll:"Do MMM YYYY, [klo] LT",llll:"ddd, Do MMM YYYY, [klo] LT"},calendar:{sameDay:"[tänään] [klo] LT",nextDay:"[huomenna] [klo] LT",nextWeek:"dddd [klo] LT",lastDay:"[eilen] [klo] LT",lastWeek:"[viime] dddd[na] [klo] LT",sameElse:"L"},relativeTime:{future:"%s päästä",past:"%s sitten",s:c,m:c,mm:c,h:c,hh:c,d:c,dd:c,M:c,MM:c,y:c,yy:c},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("fi","fi",{closeText:"Sulje",prevText:"«Edellinen",nextText:"Seuraava»",currentText:"Tänään",monthNames:["Tammikuu","Helmikuu","Maaliskuu","Huhtikuu","Toukokuu","Kesäkuu","Heinäkuu","Elokuu","Syyskuu","Lokakuu","Marraskuu","Joulukuu"],monthNamesShort:["Tammi","Helmi","Maalis","Huhti","Touko","Kesä","Heinä","Elo","Syys","Loka","Marras","Joulu"],dayNamesShort:["Su","Ma","Ti","Ke","To","Pe","La"],dayNames:["Sunnuntai","Maanantai","Tiistai","Keskiviikko","Torstai","Perjantai","Lauantai"],dayNamesMin:["Su","Ma","Ti","Ke","To","Pe","La"],weekHeader:"Vk",dateFormat:"d.m.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("fi",{buttonText:{month:"Kuukausi",week:"Viikko",day:"Päivä",list:"Tapahtumat"},allDayText:"Koko päivä",eventLimitText:"lisää"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"fr",{months:"janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre".split("_"),monthsShort:"janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.".split("_"),weekdays:"dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi".split("_"),weekdaysShort:"dim._lun._mar._mer._jeu._ven._sam.".split("_"),weekdaysMin:"Di_Lu_Ma_Me_Je_Ve_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[Aujourd'hui à] LT",nextDay:"[Demain à] LT",nextWeek:"dddd [à] LT",lastDay:"[Hier à] LT",lastWeek:"dddd [dernier à] LT",sameElse:"L"},relativeTime:{future:"dans %s",past:"il y a %s",s:"quelques secondes",m:"une minute",mm:"%d minutes",h:"une heure",hh:"%d heures",d:"un jour",dd:"%d jours",M:"un mois",MM:"%d mois",y:"un an",yy:"%d ans"},ordinalParse:/\d{1,2}(er|)/,ordinal:function(a){return a+(1===a?"er":"")},week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("fr","fr",{closeText:"Fermer",prevText:"Précédent",nextText:"Suivant",currentText:"Aujourd'hui",monthNames:["janvier","février","mars","avril","mai","juin","juillet","août","septembre","octobre","novembre","décembre"],monthNamesShort:["janv.","févr.","mars","avr.","mai","juin","juil.","août","sept.","oct.","nov.","déc."],dayNames:["dimanche","lundi","mardi","mercredi","jeudi","vendredi","samedi"],dayNamesShort:["dim.","lun.","mar.","mer.","jeu.","ven.","sam."],dayNamesMin:["D","L","M","M","J","V","S"],weekHeader:"Sem.",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("fr",{buttonText:{month:"Mois",week:"Semaine",day:"Jour",list:"Mon planning"},allDayHtml:"Toute la<br/>journée",eventLimitText:"en plus"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){function c(a,b,c,d){var e=a;switch(c){case"s":return d||b?"néhány másodperc":"néhány másodperce";case"m":return"egy"+(d||b?" perc":" perce");case"mm":return e+(d||b?" perc":" perce");case"h":return"egy"+(d||b?" óra":" órája");case"hh":return e+(d||b?" óra":" órája");case"d":return"egy"+(d||b?" nap":" napja");case"dd":return e+(d||b?" nap":" napja");case"M":return"egy"+(d||b?" hónap":" hónapja");case"MM":return e+(d||b?" hónap":" hónapja");case"y":return"egy"+(d||b?" év":" éve");case"yy":return e+(d||b?" év":" éve")}return""}function d(a){return(a?"":"[múlt] ")+"["+e[this.day()]+"] LT[-kor]"}var e="vasárnap hétfőn kedden szerdán csütörtökön pénteken szombaton".split(" ");(b.defineLocale||b.lang).call(b,"hu",{months:"január_február_március_április_május_június_július_augusztus_szeptember_október_november_december".split("_"),monthsShort:"jan_feb_márc_ápr_máj_jún_júl_aug_szept_okt_nov_dec".split("_"),weekdays:"vasárnap_hétfő_kedd_szerda_csütörtök_péntek_szombat".split("_"),weekdaysShort:"vas_hét_kedd_sze_csüt_pén_szo".split("_"),weekdaysMin:"v_h_k_sze_cs_p_szo".split("_"),longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"YYYY.MM.DD.",LL:"YYYY. MMMM D.",LLL:"YYYY. MMMM D., LT",LLLL:"YYYY. MMMM D., dddd LT"},meridiemParse:/de|du/i,isPM:function(a){return"u"===a.charAt(1).toLowerCase()},meridiem:function(a,b,c){return 12>a?c===!0?"de":"DE":c===!0?"du":"DU"},calendar:{sameDay:"[ma] LT[-kor]",nextDay:"[holnap] LT[-kor]",nextWeek:function(){return d.call(this,!0)},lastDay:"[tegnap] LT[-kor]",lastWeek:function(){return d.call(this,!1)},sameElse:"L"},relativeTime:{future:"%s múlva",past:"%s",s:c,m:c,mm:c,h:c,hh:c,d:c,dd:c,M:c,MM:c,y:c,yy:c},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}}),a.fullCalendar.datepickerLang("hu","hu",{closeText:"bezár",prevText:"vissza",nextText:"előre",currentText:"ma",monthNames:["Január","Február","Március","Április","Május","Június","Július","Augusztus","Szeptember","Október","November","December"],monthNamesShort:["Jan","Feb","Már","Ápr","Máj","Jún","Júl","Aug","Szep","Okt","Nov","Dec"],dayNames:["Vasárnap","Hétfő","Kedd","Szerda","Csütörtök","Péntek","Szombat"],dayNamesShort:["Vas","Hét","Ked","Sze","Csü","Pén","Szo"],dayNamesMin:["V","H","K","Sze","Cs","P","Szo"],weekHeader:"Hét",dateFormat:"yy.mm.dd.",firstDay:1,isRTL:!1,showMonthAfterYear:!0,yearSuffix:""}),a.fullCalendar.lang("hu",{buttonText:{month:"Hónap",week:"Hét",day:"Nap",list:"Napló"},allDayText:"Egész nap",eventLimitText:"további"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"id",{months:"Januari_Februari_Maret_April_Mei_Juni_Juli_Agustus_September_Oktober_November_Desember".split("_"),monthsShort:"Jan_Feb_Mar_Apr_Mei_Jun_Jul_Ags_Sep_Okt_Nov_Des".split("_"),weekdays:"Minggu_Senin_Selasa_Rabu_Kamis_Jumat_Sabtu".split("_"),weekdaysShort:"Min_Sen_Sel_Rab_Kam_Jum_Sab".split("_"),weekdaysMin:"Mg_Sn_Sl_Rb_Km_Jm_Sb".split("_"),longDateFormat:{LT:"HH.mm",LTS:"LT.ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY [pukul] LT",LLLL:"dddd, D MMMM YYYY [pukul] LT"},meridiemParse:/pagi|siang|sore|malam/,meridiemHour:function(a,b){return 12===a&&(a=0),"pagi"===b?a:"siang"===b?a>=11?a:a+12:"sore"===b||"malam"===b?a+12:void 0},meridiem:function(a,b,c){return 11>a?"pagi":15>a?"siang":19>a?"sore":"malam"},calendar:{sameDay:"[Hari ini pukul] LT",nextDay:"[Besok pukul] LT",nextWeek:"dddd [pukul] LT",lastDay:"[Kemarin pukul] LT",lastWeek:"dddd [lalu pukul] LT",sameElse:"L"},relativeTime:{future:"dalam %s",past:"%s yang lalu",s:"beberapa detik",m:"semenit",mm:"%d menit",h:"sejam",hh:"%d jam",d:"sehari",dd:"%d hari",M:"sebulan",MM:"%d bulan",y:"setahun",yy:"%d tahun"},week:{dow:1,doy:7}}),a.fullCalendar.datepickerLang("id","id",{closeText:"Tutup",prevText:"<mundur",nextText:"maju>",currentText:"hari ini",monthNames:["Januari","Februari","Maret","April","Mei","Juni","Juli","Agustus","September","Oktober","Nopember","Desember"],monthNamesShort:["Jan","Feb","Mar","Apr","Mei","Jun","Jul","Agus","Sep","Okt","Nop","Des"],dayNames:["Minggu","Senin","Selasa","Rabu","Kamis","Jumat","Sabtu"],dayNamesShort:["Min","Sen","Sel","Rab","kam","Jum","Sab"],dayNamesMin:["Mg","Sn","Sl","Rb","Km","jm","Sb"],weekHeader:"Mg",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("id",{buttonText:{month:"Bulan",week:"Minggu",day:"Hari",list:"Agenda"},allDayHtml:"Sehari<br/>penuh",eventLimitText:"lebih"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"it",{months:"gennaio_febbraio_marzo_aprile_maggio_giugno_luglio_agosto_settembre_ottobre_novembre_dicembre".split("_"),monthsShort:"gen_feb_mar_apr_mag_giu_lug_ago_set_ott_nov_dic".split("_"),weekdays:"Domenica_Lunedì_Martedì_Mercoledì_Giovedì_Venerdì_Sabato".split("_"),weekdaysShort:"Dom_Lun_Mar_Mer_Gio_Ven_Sab".split("_"),weekdaysMin:"D_L_Ma_Me_G_V_S".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[Oggi alle] LT",nextDay:"[Domani alle] LT",nextWeek:"dddd [alle] LT",lastDay:"[Ieri alle] LT",lastWeek:function(){switch(this.day()){case 0:return"[la scorsa] dddd [alle] LT";default:return"[lo scorso] dddd [alle] LT"}},sameElse:"L"},relativeTime:{future:function(a){return(/^[0-9].+$/.test(a)?"tra":"in")+" "+a},past:"%s fa",s:"alcuni secondi",m:"un minuto",mm:"%d minuti",h:"un'ora",hh:"%d ore",d:"un giorno",dd:"%d giorni",M:"un mese",MM:"%d mesi",y:"un anno",yy:"%d anni"},ordinalParse:/\d{1,2}º/,ordinal:"%dº",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("it","it",{closeText:"Chiudi",prevText:"<Prec",nextText:"Succ>",currentText:"Oggi",monthNames:["Gennaio","Febbraio","Marzo","Aprile","Maggio","Giugno","Luglio","Agosto","Settembre","Ottobre","Novembre","Dicembre"],monthNamesShort:["Gen","Feb","Mar","Apr","Mag","Giu","Lug","Ago","Set","Ott","Nov","Dic"],dayNames:["Domenica","Lunedì","Martedì","Mercoledì","Giovedì","Venerdì","Sabato"],dayNamesShort:["Dom","Lun","Mar","Mer","Gio","Ven","Sab"],dayNamesMin:["Do","Lu","Ma","Me","Gi","Ve","Sa"],weekHeader:"Sm",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("it",{buttonText:{month:"Mese",week:"Settimana",day:"Giorno",list:"Agenda"},allDayHtml:"Tutto il<br/>giorno",eventLimitText:function(a){return"+altri "+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"ja",{months:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),monthsShort:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),weekdays:"日曜日_月曜日_火曜日_水曜日_木曜日_金曜日_土曜日".split("_"),weekdaysShort:"日_月_火_水_木_金_土".split("_"),weekdaysMin:"日_月_火_水_木_金_土".split("_"),longDateFormat:{LT:"Ah時m分",LTS:"LTs秒",L:"YYYY/MM/DD",LL:"YYYY年M月D日",LLL:"YYYY年M月D日LT",LLLL:"YYYY年M月D日LT dddd"},meridiemParse:/午前|午後/i,isPM:function(a){return"午後"===a},meridiem:function(a,b,c){return 12>a?"午前":"午後"},calendar:{sameDay:"[今日] LT",nextDay:"[明日] LT",nextWeek:"[来週]dddd LT",lastDay:"[昨日] LT",lastWeek:"[前週]dddd LT",sameElse:"L"},relativeTime:{future:"%s後",past:"%s前",s:"数秒",m:"1分",mm:"%d分",h:"1時間",hh:"%d時間",d:"1日",dd:"%d日",M:"1ヶ月",MM:"%dヶ月",y:"1年",yy:"%d年"}}),a.fullCalendar.datepickerLang("ja","ja",{closeText:"閉じる",prevText:"<前",nextText:"次>",currentText:"今日",monthNames:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],monthNamesShort:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],dayNames:["日曜日","月曜日","火曜日","水曜日","木曜日","金曜日","土曜日"],dayNamesShort:["日","月","火","水","木","金","土"],dayNamesMin:["日","月","火","水","木","金","土"],weekHeader:"週",dateFormat:"yy/mm/dd",firstDay:0,isRTL:!1,showMonthAfterYear:!0,yearSuffix:"年"}),a.fullCalendar.lang("ja",{buttonText:{month:"月",week:"週",day:"日",list:"予定リスト"},allDayText:"終日",eventLimitText:function(a){return"他 "+a+" 件"}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){var c="jan._feb._mrt._apr._mei_jun._jul._aug._sep._okt._nov._dec.".split("_"),d="jan_feb_mrt_apr_mei_jun_jul_aug_sep_okt_nov_dec".split("_");(b.defineLocale||b.lang).call(b,"nl",{months:"januari_februari_maart_april_mei_juni_juli_augustus_september_oktober_november_december".split("_"),monthsShort:function(a,b){return/-MMM-/.test(b)?d[a.month()]:c[a.month()]},weekdays:"zondag_maandag_dinsdag_woensdag_donderdag_vrijdag_zaterdag".split("_"),weekdaysShort:"zo._ma._di._wo._do._vr._za.".split("_"),weekdaysMin:"Zo_Ma_Di_Wo_Do_Vr_Za".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD-MM-YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[vandaag om] LT",nextDay:"[morgen om] LT",nextWeek:"dddd [om] LT",lastDay:"[gisteren om] LT",lastWeek:"[afgelopen] dddd [om] LT",sameElse:"L"},relativeTime:{future:"over %s",past:"%s geleden",s:"een paar seconden",m:"één minuut",mm:"%d minuten",h:"één uur",hh:"%d uur",d:"één dag",dd:"%d dagen",M:"één maand",MM:"%d maanden",y:"één jaar",yy:"%d jaar"},ordinalParse:/\d{1,2}(ste|de)/,ordinal:function(a){return a+(1===a||8===a||a>=20?"ste":"de")},week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("nl","nl",{closeText:"Sluiten",prevText:"←",nextText:"→",currentText:"Vandaag",monthNames:["januari","februari","maart","april","mei","juni","juli","augustus","september","oktober","november","december"],monthNamesShort:["jan","feb","mrt","apr","mei","jun","jul","aug","sep","okt","nov","dec"],dayNames:["zondag","maandag","dinsdag","woensdag","donderdag","vrijdag","zaterdag"],dayNamesShort:["zon","maa","din","woe","don","vri","zat"],dayNamesMin:["zo","ma","di","wo","do","vr","za"],weekHeader:"Wk",dateFormat:"dd-mm-yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("nl",{buttonText:{month:"Maand",week:"Week",day:"Dag",list:"Agenda"},allDayText:"Hele dag",eventLimitText:"extra"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"nb",{months:"januar_februar_mars_april_mai_juni_juli_august_september_oktober_november_desember".split("_"),monthsShort:"jan_feb_mar_apr_mai_jun_jul_aug_sep_okt_nov_des".split("_"),weekdays:"søndag_mandag_tirsdag_onsdag_torsdag_fredag_lørdag".split("_"),weekdaysShort:"søn_man_tirs_ons_tors_fre_lør".split("_"),weekdaysMin:"sø_ma_ti_on_to_fr_lø".split("_"),longDateFormat:{LT:"H.mm",LTS:"LT.ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY [kl.] LT",LLLL:"dddd D. MMMM YYYY [kl.] LT"},calendar:{sameDay:"[i dag kl.] LT",nextDay:"[i morgen kl.] LT",nextWeek:"dddd [kl.] LT",lastDay:"[i går kl.] LT",lastWeek:"[forrige] dddd [kl.] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"for %s siden",s:"noen sekunder",m:"ett minutt",mm:"%d minutter",h:"en time",hh:"%d timer",d:"en dag",dd:"%d dager",M:"en måned",MM:"%d måneder",y:"ett år",yy:"%d år"},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("nb","nb",{closeText:"Lukk",prevText:"«Forrige",nextText:"Neste»",currentText:"I dag",monthNames:["januar","februar","mars","april","mai","juni","juli","august","september","oktober","november","desember"],monthNamesShort:["jan","feb","mar","apr","mai","jun","jul","aug","sep","okt","nov","des"],dayNamesShort:["søn","man","tir","ons","tor","fre","lør"],dayNames:["søndag","mandag","tirsdag","onsdag","torsdag","fredag","lørdag"],dayNamesMin:["sø","ma","ti","on","to","fr","lø"],weekHeader:"Uke",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("nb",{buttonText:{month:"Måned",week:"Uke",day:"Dag",list:"Agenda"},allDayText:"Hele dagen",eventLimitText:"til"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){function c(a){return 5>a%10&&a%10>1&&~~(a/10)%10!==1}function d(a,b,d){var e=a+" ";switch(d){case"m":return b?"minuta":"minutę";case"mm":return e+(c(a)?"minuty":"minut");case"h":return b?"godzina":"godzinę";case"hh":return e+(c(a)?"godziny":"godzin");case"MM":return e+(c(a)?"miesiące":"miesięcy");case"yy":return e+(c(a)?"lata":"lat")}}var e="styczeń_luty_marzec_kwiecień_maj_czerwiec_lipiec_sierpień_wrzesień_październik_listopad_grudzień".split("_"),f="stycznia_lutego_marca_kwietnia_maja_czerwca_lipca_sierpnia_września_października_listopada_grudnia".split("_");(b.defineLocale||b.lang).call(b,"pl",{months:function(a,b){return/D MMMM/.test(b)?f[a.month()]:e[a.month()]},monthsShort:"sty_lut_mar_kwi_maj_cze_lip_sie_wrz_paź_lis_gru".split("_"),weekdays:"niedziela_poniedziałek_wtorek_środa_czwartek_piątek_sobota".split("_"),weekdaysShort:"nie_pon_wt_śr_czw_pt_sb".split("_"),weekdaysMin:"N_Pn_Wt_Śr_Cz_Pt_So".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[Dziś o] LT",nextDay:"[Jutro o] LT",nextWeek:"[W] dddd [o] LT",lastDay:"[Wczoraj o] LT",lastWeek:function(){switch(this.day()){case 0:return"[W zeszłą niedzielę o] LT";case 3:return"[W zeszłą środę o] LT";case 6:return"[W zeszłą sobotę o] LT";default:return"[W zeszły] dddd [o] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"%s temu",s:"kilka sekund",m:d,mm:d,h:d,hh:d,d:"1 dzień",dd:"%d dni",M:"miesiąc",MM:d,y:"rok",yy:d},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("pl","pl",{closeText:"Zamknij",prevText:"<Poprzedni",nextText:"Następny>",currentText:"Dziś",monthNames:["Styczeń","Luty","Marzec","Kwiecień","Maj","Czerwiec","Lipiec","Sierpień","Wrzesień","Październik","Listopad","Grudzień"],monthNamesShort:["Sty","Lu","Mar","Kw","Maj","Cze","Lip","Sie","Wrz","Pa","Lis","Gru"],dayNames:["Niedziela","Poniedziałek","Wtorek","Środa","Czwartek","Piątek","Sobota"],dayNamesShort:["Nie","Pn","Wt","Śr","Czw","Pt","So"],dayNamesMin:["N","Pn","Wt","Śr","Cz","Pt","So"],weekHeader:"Tydz",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("pl",{buttonText:{month:"Miesiąc",week:"Tydzień",day:"Dzień",list:"Plan dnia"},allDayText:"Cały dzień",eventLimitText:"więcej"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"pt",{months:"janeiro_fevereiro_março_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro".split("_"),monthsShort:"jan_fev_mar_abr_mai_jun_jul_ago_set_out_nov_dez".split("_"),weekdays:"domingo_segunda-feira_terça-feira_quarta-feira_quinta-feira_sexta-feira_sábado".split("_"),weekdaysShort:"dom_seg_ter_qua_qui_sex_sáb".split("_"),weekdaysMin:"dom_2ª_3ª_4ª_5ª_6ª_sáb".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY LT",LLLL:"dddd, D [de] MMMM [de] YYYY LT"},calendar:{sameDay:"[Hoje às] LT",nextDay:"[Amanhã às] LT",nextWeek:"dddd [às] LT",lastDay:"[Ontem às] LT",lastWeek:function(){return 0===this.day()||6===this.day()?"[Último] dddd [às] LT":"[Última] dddd [às] LT"},sameElse:"L"},relativeTime:{future:"em %s",past:"há %s",s:"segundos",m:"um minuto",mm:"%d minutos",h:"uma hora",hh:"%d horas",d:"um dia",dd:"%d dias",M:"um mês",MM:"%d meses",y:"um ano",yy:"%d anos"},ordinalParse:/\d{1,2}º/,ordinal:"%dº",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("pt","pt",{closeText:"Fechar",prevText:"Anterior",nextText:"Seguinte",currentText:"Hoje",monthNames:["Janeiro","Fevereiro","Março","Abril","Maio","Junho","Julho","Agosto","Setembro","Outubro","Novembro","Dezembro"],monthNamesShort:["Jan","Fev","Mar","Abr","Mai","Jun","Jul","Ago","Set","Out","Nov","Dez"],dayNames:["Domingo","Segunda-feira","Terça-feira","Quarta-feira","Quinta-feira","Sexta-feira","Sábado"],dayNamesShort:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],dayNamesMin:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],weekHeader:"Sem",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("pt",{buttonText:{month:"Mês",week:"Semana",day:"Dia",list:"Agenda"},allDayText:"Todo o dia",eventLimitText:"mais"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"pt-br",{months:"janeiro_fevereiro_março_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro".split("_"),monthsShort:"jan_fev_mar_abr_mai_jun_jul_ago_set_out_nov_dez".split("_"),weekdays:"domingo_segunda-feira_terça-feira_quarta-feira_quinta-feira_sexta-feira_sábado".split("_"),weekdaysShort:"dom_seg_ter_qua_qui_sex_sáb".split("_"),weekdaysMin:"dom_2ª_3ª_4ª_5ª_6ª_sáb".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY [às] LT",LLLL:"dddd, D [de] MMMM [de] YYYY [às] LT"},calendar:{sameDay:"[Hoje às] LT",nextDay:"[Amanhã às] LT",nextWeek:"dddd [às] LT",lastDay:"[Ontem às] LT",lastWeek:function(){return 0===this.day()||6===this.day()?"[Último] dddd [às] LT":"[Última] dddd [às] LT"},sameElse:"L"},relativeTime:{future:"em %s",past:"%s atrás",s:"segundos",m:"um minuto",mm:"%d minutos",h:"uma hora",hh:"%d horas",d:"um dia",dd:"%d dias",M:"um mês",MM:"%d meses",y:"um ano",yy:"%d anos"},ordinalParse:/\d{1,2}º/,ordinal:"%dº"}),a.fullCalendar.datepickerLang("pt-br","pt-BR",{closeText:"Fechar",prevText:"<Anterior",nextText:"Próximo>",currentText:"Hoje",monthNames:["Janeiro","Fevereiro","Março","Abril","Maio","Junho","Julho","Agosto","Setembro","Outubro","Novembro","Dezembro"],monthNamesShort:["Jan","Fev","Mar","Abr","Mai","Jun","Jul","Ago","Set","Out","Nov","Dez"],dayNames:["Domingo","Segunda-feira","Terça-feira","Quarta-feira","Quinta-feira","Sexta-feira","Sábado"],dayNamesShort:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],dayNamesMin:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],weekHeader:"Sm",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("pt-br",{buttonText:{month:"Mês",week:"Semana",day:"Dia",list:"Compromissos"},allDayText:"dia inteiro",eventLimitText:function(a){return"mais +"+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){function c(a,b){var c=a.split("_");return b%10===1&&b%100!==11?c[0]:b%10>=2&&4>=b%10&&(10>b%100||b%100>=20)?c[1]:c[2]}function d(a,b,d){var e={mm:b?"минута_минуты_минут":"минуту_минуты_минут",hh:"час_часа_часов",dd:"день_дня_дней",MM:"месяц_месяца_месяцев",yy:"год_года_лет"};return"m"===d?b?"минута":"минуту":a+" "+c(e[d],+a)}function e(a,b){var c={nominative:"январь_февраль_март_апрель_май_июнь_июль_август_сентябрь_октябрь_ноябрь_декабрь".split("_"),accusative:"января_февраля_марта_апреля_мая_июня_июля_августа_сентября_октября_ноября_декабря".split("_")},d=/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/.test(b)?"accusative":"nominative";return c[d][a.month()]}function f(a,b){var c={nominative:"янв_фев_март_апр_май_июнь_июль_авг_сен_окт_ноя_дек".split("_"),accusative:"янв_фев_мар_апр_мая_июня_июля_авг_сен_окт_ноя_дек".split("_")},d=/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/.test(b)?"accusative":"nominative";return c[d][a.month()]}function g(a,b){var c={nominative:"воскресенье_понедельник_вторник_среда_четверг_пятница_суббота".split("_"),accusative:"воскресенье_понедельник_вторник_среду_четверг_пятницу_субботу".split("_")},d=/\[ ?[Вв] ?(?:прошлую|следующую|эту)? ?\] ?dddd/.test(b)?"accusative":"nominative";return c[d][a.day()]}(b.defineLocale||b.lang).call(b,"ru",{months:e,monthsShort:f,weekdays:g,weekdaysShort:"вс_пн_вт_ср_чт_пт_сб".split("_"),weekdaysMin:"вс_пн_вт_ср_чт_пт_сб".split("_"),monthsParse:[/^янв/i,/^фев/i,/^мар/i,/^апр/i,/^ма[й|я]/i,/^июн/i,/^июл/i,/^авг/i,/^сен/i,/^окт/i,/^ноя/i,/^дек/i],longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY г.",LLL:"D MMMM YYYY г., LT",LLLL:"dddd, D MMMM YYYY г., LT"},calendar:{sameDay:"[Сегодня в] LT",nextDay:"[Завтра в] LT",lastDay:"[Вчера в] LT",nextWeek:function(){return 2===this.day()?"[Во] dddd [в] LT":"[В] dddd [в] LT"},lastWeek:function(a){if(a.week()===this.week())return 2===this.day()?"[Во] dddd [в] LT":"[В] dddd [в] LT";switch(this.day()){case 0:return"[В прошлое] dddd [в] LT";case 1:case 2:case 4:return"[В прошлый] dddd [в] LT";case 3:case 5:case 6:return"[В прошлую] dddd [в] LT"}},sameElse:"L"},relativeTime:{future:"через %s",past:"%s назад",s:"несколько секунд",m:d,mm:d,h:"час",hh:d,d:"день",dd:d,M:"месяц",MM:d,y:"год",yy:d},meridiemParse:/ночи|утра|дня|вечера/i,isPM:function(a){return/^(дня|вечера)$/.test(a)},meridiem:function(a,b,c){return 4>a?"ночи":12>a?"утра":17>a?"дня":"вечера"},ordinalParse:/\d{1,2}-(й|го|я)/,ordinal:function(a,b){switch(b){case"M":case"d":case"DDD":return a+"-й";case"D":return a+"-го";case"w":case"W":return a+"-я";default:return a}},week:{dow:1,doy:7}}),a.fullCalendar.datepickerLang("ru","ru",{closeText:"Закрыть",prevText:"<Пред",nextText:"След>",currentText:"Сегодня",monthNames:["Январь","Февраль","Март","Апрель","Май","Июнь","Июль","Август","Сентябрь","Октябрь","Ноябрь","Декабрь"],monthNamesShort:["Янв","Фев","Мар","Апр","Май","Июн","Июл","Авг","Сен","Окт","Ноя","Дек"],dayNames:["воскресенье","понедельник","вторник","среда","четверг","пятница","суббота"],dayNamesShort:["вск","пнд","втр","срд","чтв","птн","сбт"],dayNamesMin:["Вс","Пн","Вт","Ср","Чт","Пт","Сб"],weekHeader:"Нед",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("ru",{buttonText:{month:"Месяц",week:"Неделя",day:"День",list:"Повестка дня"},allDayText:"Весь день",eventLimitText:function(a){return"+ ещё "+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"sv",{months:"januari_februari_mars_april_maj_juni_juli_augusti_september_oktober_november_december".split("_"),monthsShort:"jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec".split("_"),weekdays:"söndag_måndag_tisdag_onsdag_torsdag_fredag_lördag".split("_"),weekdaysShort:"sön_mån_tis_ons_tor_fre_lör".split("_"),weekdaysMin:"sö_må_ti_on_to_fr_lö".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"YYYY-MM-DD",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[Idag] LT",nextDay:"[Imorgon] LT",lastDay:"[Igår] LT",nextWeek:"dddd LT",lastWeek:"[Förra] dddd[en] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"för %s sedan",s:"några sekunder",m:"en minut",mm:"%d minuter",h:"en timme",hh:"%d timmar",d:"en dag",dd:"%d dagar",M:"en månad",MM:"%d månader",y:"ett år",yy:"%d år"},ordinalParse:/\d{1,2}(e|a)/,ordinal:function(a){var b=a%10,c=1===~~(a%100/10)?"e":1===b?"a":2===b?"a":"e";return a+c},week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("sv","sv",{closeText:"Stäng",prevText:"«Förra",nextText:"Nästa»",currentText:"Idag",monthNames:["Januari","Februari","Mars","April","Maj","Juni","Juli","Augusti","September","Oktober","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],dayNamesShort:["Sön","Mån","Tis","Ons","Tor","Fre","Lör"],dayNames:["Söndag","Måndag","Tisdag","Onsdag","Torsdag","Fredag","Lördag"],dayNamesMin:["Sö","Må","Ti","On","To","Fr","Lö"],weekHeader:"Ve",dateFormat:"yy-mm-dd",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("sv",{buttonText:{month:"Månad",week:"Vecka",day:"Dag",list:"Program"},allDayText:"Heldag",eventLimitText:"till"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){var c={words:{m:["jedan minut","jedne minute"],mm:["minut","minute","minuta"],h:["jedan sat","jednog sata"],hh:["sat","sata","sati"],dd:["dan","dana","dana"],MM:["mesec","meseca","meseci"],yy:["godina","godine","godina"]},correctGrammaticalCase:function(a,b){return 1===a?b[0]:a>=2&&4>=a?b[1]:b[2]},translate:function(a,b,d){var e=c.words[d];return 1===d.length?b?e[0]:e[1]:a+" "+c.correctGrammaticalCase(a,e)}};(b.defineLocale||b.lang).call(b,"sr",{months:["januar","februar","mart","april","maj","jun","jul","avgust","septembar","oktobar","novembar","decembar"],monthsShort:["jan.","feb.","mar.","apr.","maj","jun","jul","avg.","sep.","okt.","nov.","dec."],weekdays:["nedelja","ponedeljak","utorak","sreda","četvrtak","petak","subota"],weekdaysShort:["ned.","pon.","uto.","sre.","čet.","pet.","sub."],weekdaysMin:["ne","po","ut","sr","če","pe","su"],longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"DD. MM. YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd, D. MMMM YYYY LT"},calendar:{sameDay:"[danas u] LT",nextDay:"[sutra u] LT",nextWeek:function(){switch(this.day()){case 0:return"[u] [nedelju] [u] LT";case 3:return"[u] [sredu] [u] LT";case 6:return"[u] [subotu] [u] LT";case 1:case 2:case 4:case 5:return"[u] dddd [u] LT"}},lastDay:"[juče u] LT",lastWeek:function(){var a=["[prošle] [nedelje] [u] LT","[prošlog] [ponedeljka] [u] LT","[prošlog] [utorka] [u] LT","[prošle] [srede] [u] LT","[prošlog] [četvrtka] [u] LT","[prošlog] [petka] [u] LT","[prošle] [subote] [u] LT"];return a[this.day()]},sameElse:"L"},relativeTime:{future:"za %s",past:"pre %s",s:"nekoliko sekundi",m:c.translate,mm:c.translate,h:c.translate,hh:c.translate,d:"dan",dd:c.translate,M:"mesec",MM:c.translate,y:"godinu",yy:c.translate},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}}),a.fullCalendar.datepickerLang("sr","sr",{closeText:"Затвори",prevText:"<",nextText:">",currentText:"Данас",monthNames:["Јануар","Фебруар","Март","Април","Мај","Јун","Јул","Август","Септембар","Октобар","Новембар","Децембар"],monthNamesShort:["Јан","Феб","Мар","Апр","Мај","Јун","Јул","Авг","Сеп","Окт","Нов","Дец"],dayNames:["Недеља","Понедељак","Уторак","Среда","Четвртак","Петак","Субота"],dayNamesShort:["Нед","Пон","Уто","Сре","Чет","Пет","Суб"],dayNamesMin:["Не","По","Ут","Ср","Че","Пе","Су"],weekHeader:"Сед",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("sr",{buttonText:{month:"Месец",week:"Недеља",day:"Дан",list:"Планер"},allDayText:"Цео дан",eventLimitText:function(a){return"+ још "+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"th",{months:"มกราคม_กุมภาพันธ์_มีนาคม_เมษายน_พฤษภาคม_มิถุนายน_กรกฎาคม_สิงหาคม_กันยายน_ตุลาคม_พฤศจิกายน_ธันวาคม".split("_"),monthsShort:"มกรา_กุมภา_มีนา_เมษา_พฤษภา_มิถุนา_กรกฎา_สิงหา_กันยา_ตุลา_พฤศจิกา_ธันวา".split("_"),weekdays:"อาทิตย์_จันทร์_อังคาร_พุธ_พฤหัสบดี_ศุกร์_เสาร์".split("_"),weekdaysShort:"อาทิตย์_จันทร์_อังคาร_พุธ_พฤหัส_ศุกร์_เสาร์".split("_"),weekdaysMin:"อา._จ._อ._พ._พฤ._ศ._ส.".split("_"),longDateFormat:{LT:"H นาฬิกา m นาที",LTS:"LT s วินาที",L:"YYYY/MM/DD",LL:"D MMMM YYYY",LLL:"D MMMM YYYY เวลา LT",LLLL:"วันddddที่ D MMMM YYYY เวลา LT"},meridiemParse:/ก่อนเที่ยง|หลังเที่ยง/,isPM:function(a){return"หลังเที่ยง"===a},meridiem:function(a,b,c){return 12>a?"ก่อนเที่ยง":"หลังเที่ยง"},calendar:{sameDay:"[วันนี้ เวลา] LT",nextDay:"[พรุ่งนี้ เวลา] LT",nextWeek:"dddd[หน้า เวลา] LT",lastDay:"[เมื่อวานนี้ เวลา] LT",lastWeek:"[วัน]dddd[ที่แล้ว เวลา] LT",sameElse:"L"},relativeTime:{future:"อีก %s",past:"%sที่แล้ว",s:"ไม่กี่วินาที",m:"1 นาที",mm:"%d นาที",h:"1 ชั่วโมง",hh:"%d ชั่วโมง",d:"1 วัน",dd:"%d วัน",M:"1 เดือน",MM:"%d เดือน",y:"1 ปี",yy:"%d ปี"}}),a.fullCalendar.datepickerLang("th","th",{closeText:"ปิด",prevText:"« ย้อน",nextText:"ถัดไป »",currentText:"วันนี้",monthNames:["มกราคม","กุมภาพันธ์","มีนาคม","เมษายน","พฤษภาคม","มิถุนายน","กรกฎาคม","สิงหาคม","กันยายน","ตุลาคม","พฤศจิกายน","ธันวาคม"],monthNamesShort:["ม.ค.","ก.พ.","มี.ค.","เม.ย.","พ.ค.","มิ.ย.","ก.ค.","ส.ค.","ก.ย.","ต.ค.","พ.ย.","ธ.ค."],dayNames:["อาทิตย์","จันทร์","อังคาร","พุธ","พฤหัสบดี","ศุกร์","เสาร์"],dayNamesShort:["อา.","จ.","อ.","พ.","พฤ.","ศ.","ส."],dayNamesMin:["อา.","จ.","อ.","พ.","พฤ.","ศ.","ส."],weekHeader:"Wk",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("th",{buttonText:{month:"เดือน",week:"สัปดาห์",day:"วัน",list:"แผนงาน"},allDayText:"ตลอดวัน",eventLimitText:"เพิ่มเติม"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){var c={1:"'inci",5:"'inci",8:"'inci",70:"'inci",80:"'inci",2:"'nci",7:"'nci",20:"'nci",50:"'nci",3:"'üncü",4:"'üncü",100:"'üncü",6:"'ncı",9:"'uncu",10:"'uncu",30:"'uncu",60:"'ıncı",90:"'ıncı"};(b.defineLocale||b.lang).call(b,"tr",{months:"Ocak_Şubat_Mart_Nisan_Mayıs_Haziran_Temmuz_Ağustos_Eylül_Ekim_Kasım_Aralık".split("_"),monthsShort:"Oca_Şub_Mar_Nis_May_Haz_Tem_Ağu_Eyl_Eki_Kas_Ara".split("_"),weekdays:"Pazar_Pazartesi_Salı_Çarşamba_Perşembe_Cuma_Cumartesi".split("_"),weekdaysShort:"Paz_Pts_Sal_Çar_Per_Cum_Cts".split("_"),weekdaysMin:"Pz_Pt_Sa_Ça_Pe_Cu_Ct".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[bugün saat] LT",nextDay:"[yarın saat] LT",nextWeek:"[haftaya] dddd [saat] LT",lastDay:"[dün] LT",lastWeek:"[geçen hafta] dddd [saat] LT",sameElse:"L"},relativeTime:{future:"%s sonra",past:"%s önce",s:"birkaç saniye",m:"bir dakika",mm:"%d dakika",h:"bir saat",hh:"%d saat",d:"bir gün",dd:"%d gün",M:"bir ay",MM:"%d ay",y:"bir yıl",yy:"%d yıl"},ordinalParse:/\d{1,2}'(inci|nci|üncü|ncı|uncu|ıncı)/,ordinal:function(a){if(0===a)return a+"'ıncı";var b=a%10,d=a%100-b,e=a>=100?100:null;return a+(c[b]||c[d]||c[e])},week:{dow:1,doy:7}}),a.fullCalendar.datepickerLang("tr","tr",{closeText:"kapat",prevText:"<geri",nextText:"ileri>",currentText:"bugün",monthNames:["Ocak","Şubat","Mart","Nisan","Mayıs","Haziran","Temmuz","Ağustos","Eylül","Ekim","Kasım","Aralık"],monthNamesShort:["Oca","Şub","Mar","Nis","May","Haz","Tem","Ağu","Eyl","Eki","Kas","Ara"],dayNames:["Pazar","Pazartesi","Salı","Çarşamba","Perşembe","Cuma","Cumartesi"],dayNamesShort:["Pz","Pt","Sa","Ça","Pe","Cu","Ct"],dayNamesMin:["Pz","Pt","Sa","Ça","Pe","Cu","Ct"],weekHeader:"Hf",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("tr",{buttonText:{next:"ileri",month:"Ay",week:"Hafta",day:"Gün",list:"Ajanda"},allDayText:"Tüm gün",eventLimitText:"daha fazla"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"zh-cn",{months:"一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月".split("_"),monthsShort:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),weekdays:"星期日_星期一_星期二_星期三_星期四_星期五_星期六".split("_"),weekdaysShort:"周日_周一_周二_周三_周四_周五_周六".split("_"),weekdaysMin:"日_一_二_三_四_五_六".split("_"),longDateFormat:{LT:"Ah点mm",LTS:"Ah点m分s秒",L:"YYYY-MM-DD",LL:"YYYY年MMMD日",LLL:"YYYY年MMMD日LT",LLLL:"YYYY年MMMD日ddddLT",l:"YYYY-MM-DD",ll:"YYYY年MMMD日",lll:"YYYY年MMMD日LT",llll:"YYYY年MMMD日ddddLT"},meridiemParse:/凌晨|早上|上午|中午|下午|晚上/,meridiemHour:function(a,b){return 12===a&&(a=0),"凌晨"===b||"早上"===b||"上午"===b?a:"下午"===b||"晚上"===b?a+12:a>=11?a:a+12},meridiem:function(a,b,c){var d=100*a+b;return 600>d?"凌晨":900>d?"早上":1130>d?"上午":1230>d?"中午":1800>d?"下午":"晚上"},calendar:{sameDay:function(){return 0===this.minutes()?"[今天]Ah[点整]":"[今天]LT"},nextDay:function(){return 0===this.minutes()?"[明天]Ah[点整]":"[明天]LT"},lastDay:function(){return 0===this.minutes()?"[昨天]Ah[点整]":"[昨天]LT"},nextWeek:function(){var a,c;return a=b().startOf("week"),c=this.unix()-a.unix()>=604800?"[下]":"[本]",0===this.minutes()?c+"dddAh点整":c+"dddAh点mm"},lastWeek:function(){var a,c;return a=b().startOf("week"),c=this.unix()<a.unix()?"[上]":"[本]",0===this.minutes()?c+"dddAh点整":c+"dddAh点mm"},sameElse:"LL"},ordinalParse:/\d{1,2}(日|月|周)/,ordinal:function(a,b){switch(b){case"d":case"D":case"DDD":return a+"日";case"M":return a+"月";case"w":case"W":return a+"周";default:return a}},relativeTime:{future:"%s内",past:"%s前",s:"几秒",m:"1分钟",mm:"%d分钟",h:"1小时",hh:"%d小时",d:"1天",dd:"%d天",M:"1个月",MM:"%d个月",y:"1年",yy:"%d年"},week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("zh-cn","zh-CN",{closeText:"关闭",prevText:"<上月",nextText:"下月>",currentText:"今天",monthNames:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],monthNamesShort:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],dayNames:["星期日","星期一","星期二","星期三","星期四","星期五","星期六"],dayNamesShort:["周日","周一","周二","周三","周四","周五","周六"],dayNamesMin:["日","一","二","三","四","五","六"],weekHeader:"周",dateFormat:"yy-mm-dd",firstDay:1,isRTL:!1,showMonthAfterYear:!0,yearSuffix:"年"}),a.fullCalendar.lang("zh-cn",{buttonText:{month:"月",week:"周",day:"日",list:"日程"},allDayText:"全天",eventLimitText:function(a){return"另外 "+a+" 个"}})});(function(){function t(x){this.app=x;this.router=new v();this.router.addRoute("screenshot-zone",e)}t.prototype.isOpen=function(){return $("#popover-container").size()>0};t.prototype.open=function(y){var x=this;x.app.dropdown.close();$.get(y,function(z){$("body").append('<div id="popover-container"><div id="popover-content">'+z+"</div></div>");x.app.refresh();x.router.dispatch(this.app);x.afterOpen()})};t.prototype.close=function(x){if(this.isOpen()){if(x){x.preventDefault()}$("#popover-container").remove()}};t.prototype.onClick=function(y){y.preventDefault();y.stopPropagation();var x=y.target.getAttribute("href");if(!x){x=y.target.getAttribute("data-href")}if(x){this.open(x)}};t.prototype.listen=function(){$(document).on("click",".popover",this.onClick.bind(this));$(document).on("click",".close-popover",this.close.bind(this));$(document).on("click","#popover-container",this.close.bind(this));$(document).on("click","#popover-content",function(x){x.stopPropagation()})};t.prototype.afterOpen=function(){var y=this;var x=$(".popover-form");if(x){x.on("submit",function(z){z.preventDefault();$.ajax({type:"POST",url:x.attr("action"),data:x.serialize(),success:function(B,D,A){var C=A.getResponseHeader("X-Ajax-Redirect");if(C){window.location=C==="self"?window.location.href:C}else{$("#popover-content").html(B);$("input[autofocus]").focus();y.afterOpen()}}})})}};function r(){}r.prototype.listen=function(){var x=this;$(document).on("click",function(){x.close()});$(document).on("click",".dropdown-menu",function(B){B.preventDefault();B.stopImmediatePropagation();x.close();var z=$(this).next("ul");var C=$(this).offset();$("body").append(jQuery("<div>",{id:"dropdown"}));z.clone().appendTo("#dropdown");var D=$("#dropdown ul");D.addClass("dropdown-submenu-open");var A=D.outerHeight();var y=D.outerWidth();if(C.top+A-$(window).scrollTop()>$(window).height()){D.css("top",C.top-A-5)}else{D.css("top",C.top+$(this).height())}if(C.left+y>$(window).width()){D.css("left",C.left-y+$(this).outerWidth())}else{D.css("left",C.left)}});$(document).on("click",".dropdown-submenu-open li",function(y){if($(y.target).is("li")){$(this).find("a:visible")[0].click()}});$("textarea[data-mention-search-url]").textcomplete([{match:/(^|\s)@(\w*)$/,search:function(z,A){var y=$("textarea[data-mention-search-url]").data("mention-search-url");$.getJSON(y,{q:z}).done(function(B){A(B)}).fail(function(){A([])})},replace:function(y){return"$1@"+y+" "},cache:true}],{className:"textarea-dropdown"})};r.prototype.close=function(){$("#dropdown").remove()};function q(x){this.app=x}q.prototype.listen=function(){var x=this;$(".tooltip").tooltip({track:false,show:false,hide:false,position:{my:"left-20 top",at:"center bottom+9",using:function(y,z){$(this).css(y);var A=z.target.left+z.target.width/2-z.element.left-20;$("<div>").addClass("tooltip-arrow").addClass(z.vertical).addClass(A<1?"align-left":"align-right").appendTo(this)}},content:function(){var A=this;var y=$(this).attr("data-href");if(!y){return'<div class="markdown">'+$(this).attr("title")+"</div>"}$.get(y,function z(D){var C=$(".ui-tooltip:visible");$(".ui-tooltip-content:visible").html(D);C.css({top:"",left:""});C.children(".tooltip-arrow").remove();var B=$(A).tooltip("option","position");B.of=$(A);C.position(B);$("#tooltip-subtasks a").not(".popover").click(function(E){E.preventDefault();E.stopPropagation();if($(this).hasClass("popover-subtask-restriction")){x.app.popover.open($(this).attr("href"));$(A).tooltip("close")}else{$.get($(this).attr("href"),z)}})});return'<i class="fa fa-spinner fa-spin"></i>'}}).on("mouseenter",function(){var y=this;$(this).tooltip("open");$(".ui-tooltip").on("mouseleave",function(){$(y).tooltip("close")})}).on("mouseleave focusout",function(y){y.stopImmediatePropagation();var z=this;setTimeout(function(){if(!$(".ui-tooltip:hover").length){$(z).tooltip("close")}},100)})};function l(){}l.prototype.showPreview=function(B){B.preventDefault();var y=$(".write-area");var A=$(".preview-area");var x=$("textarea");$("#markdown-write").parent().removeClass("form-tab-selected");$("#markdown-preview").parent().addClass("form-tab-selected");var z=$.ajax({url:$("body").data("markdown-preview-url"),contentType:"application/json",type:"POST",processData:false,dataType:"html",data:JSON.stringify({text:x.val()})});z.done(function(C){A.find(".markdown").html(C);A.css("height",x.css("height"));A.css("width",x.css("width"));y.hide();A.show()})};l.prototype.showWriter=function(x){x.preventDefault();$("#markdown-write").parent().addClass("form-tab-selected");$("#markdown-preview").parent().removeClass("form-tab-selected");$(".write-area").show();$(".preview-area").hide()};l.prototype.listen=function(){$(document).on("click","#markdown-preview",this.showPreview.bind(this));$(document).on("click","#markdown-write",this.showWriter.bind(this))};function b(){}b.prototype.expand=function(x){x.preventDefault();$(".sidebar-container").removeClass("sidebar-collapsed");$(".sidebar-collapse").show();$(".sidebar h2").show();$(".sidebar ul").show();$(".sidebar-expand").hide()};b.prototype.collapse=function(x){x.preventDefault();$(".sidebar-container").addClass("sidebar-collapsed");$(".sidebar-expand").show();$(".sidebar h2").hide();$(".sidebar ul").hide();$(".sidebar-collapse").hide()};b.prototype.listen=function(){$(document).on("click",".sidebar-collapse",this.collapse);$(document).on("click",".sidebar-expand",this.expand)};function f(x){this.app=x;this.keyboardShortcuts()}f.prototype.focus=function(){$(document).on("focus","#form-search",function(){if($("#form-search")[0].setSelectionRange){$("#form-search")[0].setSelectionRange($("#form-search").val().length,$("#form-search").val().length)}})};f.prototype.listen=function(){var x=this;$(document).on("click",".filter-helper",function(A){A.preventDefault();var z=$(this).data("filter");var y=$(this).data("append-filter");if(y){z=$("#form-search").val()+" "+y}$("#form-search").val(z);if($("#board").length){x.app.board.reloadFilters(z)}else{$("form.search").submit()}})};f.prototype.keyboardShortcuts=function(){var x=this;Mousetrap.bind("v b",function(z){var y=$(".view-board");if(y.length){window.location=y.attr("href")}});Mousetrap.bind("v c",function(z){var y=$(".view-calendar");if(y.length){window.location=y.attr("href")}});Mousetrap.bind("v l",function(z){var y=$(".view-listing");if(y.length){window.location=y.attr("href")}});Mousetrap.bind("v g",function(z){var y=$(".view-gantt");if(y.length){window.location=y.attr("href")}});Mousetrap.bind("f",function(z){z.preventDefault();var y=document.getElementById("form-search");if(y){y.focus()}});Mousetrap.bind("r",function(z){z.preventDefault();var y=$(".filter-reset").data("filter");$("#form-search").val(y);if($("#board").length){x.app.board.reloadFilters(y)}else{$("form.search").submit()}})};function m(){this.board=new k(this);this.markdown=new l();this.sidebar=new b();this.search=new f(this);this.swimlane=new g();this.dropdown=new r();this.tooltip=new q(this);this.popover=new t(this);this.task=new a();this.project=new n();this.keyboardShortcuts();this.chosen();this.poll();$(".alert-fade-out").delay(4000).fadeOut(800,function(){$(this).remove()});var x=false;$("select.task-reload-project-destination").change(function(){if(!x){$(".loading-icon").show();x=true;window.location=$(this).data("redirect").replace(/PROJECT_ID/g,$(this).val())}})}m.prototype.listen=function(){this.project.listen();this.popover.listen();this.markdown.listen();this.sidebar.listen();this.tooltip.listen();this.dropdown.listen();this.search.listen();this.task.listen();this.swimlane.listen();this.search.focus();this.autoComplete();this.datePicker();this.focus()};m.prototype.refresh=function(){$(document).off();this.listen()};m.prototype.focus=function(){$("[autofocus]").each(function(x,y){$(this).focus()});$(document).on("focus",".auto-select",function(){$(this).select()});$(document).on("mouseup",".auto-select",function(x){x.preventDefault()})};m.prototype.poll=function(){window.setInterval(this.checkSession,60000)};m.prototype.keyboardShortcuts=function(){var x=this;Mousetrap.bindGlobal("mod+enter",function(){$("form").submit()});Mousetrap.bind("b",function(y){y.preventDefault();$("#board-selector").trigger("chosen:open")});Mousetrap.bindGlobal("esc",function(){x.popover.close();x.dropdown.close()})};m.prototype.checkSession=function(){if(!$(".form-login").length){$.ajax({cache:false,url:$("body").data("status-url"),statusCode:{401:function(){window.location=$("body").data("login-url")}}})}};m.prototype.datePicker=function(){$.datepicker.setDefaults($.datepicker.regional[$("body").data("js-lang")]);$(".form-date").datepicker({showOtherMonths:true,selectOtherMonths:true,dateFormat:"yy-mm-dd",constrainInput:false});$(".form-datetime").datetimepicker({controlType:"select",oneLine:true,dateFormat:"yy-mm-dd",constrainInput:false})};m.prototype.autoComplete=function(){$(".autocomplete").each(function(){var y=$(this);var z=y.data("dst-field");var x=y.data("dst-extra-field");if($("#form-"+z).val()==""){y.parent().find("input[type=submit]").attr("disabled","disabled")}y.autocomplete({source:y.data("search-url"),minLength:1,select:function(A,B){$("input[name="+z+"]").val(B.item.id);if(x){$("input[name="+x+"]").val(B.item[x])}y.parent().find("input[type=submit]").removeAttr("disabled")}})})};m.prototype.chosen=function(){$(".chosen-select").each(function(){var x=$(this).data("search-threshold");if(x===undefined){x=10}$(this).chosen({width:"180px",no_results_text:$(this).data("notfound"),disable_search_threshold:x})});$(".select-auto-redirect").change(function(){var x=new RegExp($(this).data("redirect-regex"),"g");window.location=$(this).data("redirect-url").replace(x,$(this).val())})};m.prototype.showLoadingIcon=function(){$("body").append('<span id="app-loading-icon"> <i class="fa fa-spinner fa-spin"></i></span>')};m.prototype.hideLoadingIcon=function(){$("#app-loading-icon").remove()};m.prototype.isVisible=function(){var x="";if(typeof document.hidden!=="undefined"){x="visibilityState"}else{if(typeof document.mozHidden!=="undefined"){x="mozVisibilityState"}else{if(typeof document.msHidden!=="undefined"){x="msVisibilityState"}else{if(typeof document.webkitHidden!=="undefined"){x="webkitVisibilityState"}}}}if(x!=""){return document[x]=="visible"}return true};m.prototype.formatDuration=function(x){if(x>=86400){return Math.round(x/86400)+"d"}else{if(x>=3600){return Math.round(x/3600)+"h"}else{if(x>=60){return Math.round(x/60)+"m"}}}return x+"s"};function e(){this.pasteCatcher=null}e.prototype.execute=function(){this.initialize()};e.prototype.initialize=function(){this.destroy();if(!window.Clipboard){this.pasteCatcher=document.createElement("div");this.pasteCatcher.id="screenshot-pastezone";this.pasteCatcher.contentEditable="true";this.pasteCatcher.style.opacity=0;this.pasteCatcher.style.position="fixed";this.pasteCatcher.style.top=0;this.pasteCatcher.style.right=0;this.pasteCatcher.style.width=0;document.body.insertBefore(this.pasteCatcher,document.body.firstChild);this.pasteCatcher.focus();document.addEventListener("click",this.setFocus.bind(this));document.getElementById("screenshot-zone").addEventListener("click",this.setFocus.bind(this))}window.addEventListener("paste",this.pasteHandler.bind(this))};e.prototype.destroy=function(){if(this.pasteCatcher!=null){document.body.removeChild(this.pasteCatcher)}else{if(document.getElementById("screenshot-pastezone")){document.body.removeChild(document.getElementById("screenshot-pastezone"))}}document.removeEventListener("click",this.setFocus.bind(this));this.pasteCatcher=null};e.prototype.setFocus=function(){if(this.pasteCatcher!==null){this.pasteCatcher.focus()}};e.prototype.pasteHandler=function(C){if(C.clipboardData&&C.clipboardData.items){var A=C.clipboardData.items;if(A){for(var B=0;B<A.length;B++){if(A[B].type.indexOf("image")!==-1){var z=A[B].getAsFile();var x=new FileReader();var y=this;x.onload=function(D){y.createImage(D.target.result)};x.readAsDataURL(z)}}}}else{setTimeout(this.checkInput.bind(this),100)}};e.prototype.checkInput=function(){var x=this.pasteCatcher.childNodes[0];if(x){if(x.tagName==="IMG"){this.createImage(x.src)}}this.pasteCatcher.innerHTML=""};e.prototype.createImage=function(z){var y=new Image();y.src=z;y.onload=function(){var A=z.split("base64,");var B=A[1];$("input[name=screenshot]").val(B)};var x=document.getElementById("screenshot-zone");x.innerHTML="";x.className="screenshot-pasted";x.appendChild(y);this.destroy();this.initialize()};function j(){}j.prototype.execute=function(){var x=$("#calendar");x.fullCalendar({lang:$("body").data("js-lang"),editable:true,eventLimit:true,defaultView:"month",header:{left:"prev,next today",center:"title",right:"month,agendaWeek,agendaDay"},eventDrop:function(y){$.ajax({cache:false,url:x.data("save-url"),contentType:"application/json",type:"POST",processData:false,data:JSON.stringify({task_id:y.id,date_due:y.start.format()})})},viewRender:function(){var y=x.data("check-url");var A={start:x.fullCalendar("getView").start.format(),end:x.fullCalendar("getView").end.format()};for(var z in A){y+="&"+z+"="+A[z]}$.getJSON(y,function(B){x.fullCalendar("removeEvents");x.fullCalendar("addEventSource",B);x.fullCalendar("rerenderEvents")})}})};function k(x){this.app=x;this.checkInterval=null;this.savingInProgress=false}k.prototype.execute=function(){this.app.swimlane.refresh();this.restoreColumnViewMode();this.compactView();this.poll();this.keyboardShortcuts();this.listen();this.dragAndDrop();$(window).on("load",this.columnScrolling);$(window).resize(this.columnScrolling)};k.prototype.poll=function(){var x=parseInt($("#board").attr("data-check-interval"));if(x>0){this.checkInterval=window.setInterval(this.check.bind(this),x*1000)}};k.prototype.reloadFilters=function(x){this.app.showLoadingIcon();$.ajax({cache:false,url:$("#board").data("reload-url"),contentType:"application/json",type:"POST",processData:false,data:JSON.stringify({search:x}),success:this.refresh.bind(this),error:this.app.hideLoadingIcon.bind(this)})};k.prototype.check=function(){if(this.app.isVisible()&&!this.savingInProgress){var x=this;this.app.showLoadingIcon();$.ajax({cache:false,url:$("#board").data("check-url"),statusCode:{200:function(y){x.refresh(y)},304:function(){x.app.hideLoadingIcon()}}})}};k.prototype.save=function(A,B,x,z){var y=this;this.app.showLoadingIcon();this.savingInProgress=true;$.ajax({cache:false,url:$("#board").data("save-url"),contentType:"application/json",type:"POST",processData:false,data:JSON.stringify({task_id:A,column_id:B,swimlane_id:z,position:x}),success:function(C){y.refresh(C);this.savingInProgress=false},error:function(){y.app.hideLoadingIcon();this.savingInProgress=false}})};k.prototype.refresh=function(x){$("#board-container").replaceWith(x);this.app.refresh();this.app.swimlane.refresh();this.app.hideLoadingIcon();this.listen();this.dragAndDrop();this.compactView();this.restoreColumnViewMode();this.columnScrolling()};k.prototype.dragAndDrop=function(){var x=this;var y={forcePlaceholderSize:true,tolerance:"pointer",connectWith:".board-task-list",placeholder:"draggable-placeholder",items:".draggable-item",stop:function(A,H){var C=H.item;var G=C.attr("data-task-id");var I=C.attr("data-position");var F=C.attr("data-column-id");var E=C.attr("data-swimlane-id");var B=C.parent().attr("data-column-id");var z=C.parent().attr("data-swimlane-id");var D=C.index()+1;C.removeClass("draggable-item-selected");if(B!=F||z!=E||D!=I){x.changeTaskState(G);x.save(G,B,D,z)}},start:function(z,A){A.item.addClass("draggable-item-selected");A.placeholder.height(A.item.height())}};if($.support.touch){$(".task-board-sort-handle").css("display","inline");y.handle=".task-board-sort-handle"}$(".board-task-list").sortable(y)};k.prototype.changeTaskState=function(y){var x=$("div[data-task-id="+y+"]");x.addClass("task-board-saving-state");x.find(".task-board-saving-icon").show()};k.prototype.listen=function(){var x=this;$(document).on("click",".task-board",function(y){if(y.target.tagName!="A"){window.location=$(this).data("task-url")}});$(document).on("click",".filter-toggle-scrolling",function(y){y.preventDefault();x.toggleCompactView()});$(document).on("click",".filter-toggle-height",function(y){y.preventDefault();x.toggleColumnScrolling()});$(document).on("click",".board-toggle-column-view",function(){x.toggleColumnViewMode($(this).data("column-id"))})};k.prototype.toggleColumnScrolling=function(){var x=localStorage.getItem("column_scroll");if(x==undefined){x=1}localStorage.setItem("column_scroll",x==0?1:0);this.columnScrolling()};k.prototype.columnScrolling=function(){if(localStorage.getItem("column_scroll")==0){var x=80;$(".filter-max-height").show();$(".filter-min-height").hide();$(".board-rotation-wrapper").css("min-height","");$(".board-task-list").each(function(){var y=$(this).height();if(y>x){x=y}});$(".board-task-list").css("min-height",x);$(".board-task-list").css("height","")}else{$(".filter-max-height").hide();$(".filter-min-height").show();if($(".board-swimlane").length>1){$(".board-task-list").each(function(){if($(this).height()>500){$(this).css("height",500)}else{$(this).css("min-height",320);$(".board-rotation-wrapper").css("min-height",320)}})}else{var x=$(window).height()-170;$(".board-task-list").css("height",x);$(".board-rotation-wrapper").css("min-height",x)}}};k.prototype.toggleCompactView=function(){var x=localStorage.getItem("horizontal_scroll")||1;localStorage.setItem("horizontal_scroll",x==0?1:0);this.compactView()};k.prototype.compactView=function(){if(localStorage.getItem("horizontal_scroll")==0){$(".filter-wide").show();$(".filter-compact").hide();$("#board-container").addClass("board-container-compact");$("#board th:not(.board-column-header-collapsed)").addClass("board-column-compact")}else{$(".filter-wide").hide();$(".filter-compact").show();$("#board-container").removeClass("board-container-compact");$("#board th").removeClass("board-column-compact")}};k.prototype.toggleCollapsedMode=function(){var x=this;this.app.showLoadingIcon();$.ajax({cache:false,url:$('.filter-display-mode:not([style="display: none;"]) a').attr("href"),success:function(y){$(".filter-display-mode").toggle();x.refresh(y)}})};k.prototype.restoreColumnViewMode=function(){var x=this;$(".board-column-header").each(function(){var y=$(this).data("column-id");if(localStorage.getItem("hidden_column_"+y)){x.hideColumn(y)}})};k.prototype.toggleColumnViewMode=function(x){if(localStorage.getItem("hidden_column_"+x)){this.showColumn(x)}else{this.hideColumn(x)}};k.prototype.hideColumn=function(x){$(".board-column-"+x+" .board-column-expanded").hide();$(".board-column-"+x+" .board-column-collapsed").show();$(".board-column-header-"+x+" .board-column-expanded").hide();$(".board-column-header-"+x+" .board-column-collapsed").show();$(".board-column-header-"+x).each(function(){$(this).removeClass("board-column-compact");$(this).addClass("board-column-header-collapsed")});$(".board-column-"+x).each(function(){$(this).addClass("board-column-task-collapsed")});$(".board-column-"+x+" .board-rotation").each(function(){$(this).css("width",$(".board-column-"+x+"").height())});localStorage.setItem("hidden_column_"+x,1)};k.prototype.showColumn=function(x){$(".board-column-"+x+" .board-column-expanded").show();$(".board-column-"+x+" .board-column-collapsed").hide();$(".board-column-header-"+x+" .board-column-expanded").show();$(".board-column-header-"+x+" .board-column-collapsed").hide();$(".board-column-header-"+x).removeClass("board-column-header-collapsed");$(".board-column-"+x).removeClass("board-column-task-collapsed");if(localStorage.getItem("horizontal_scroll")==0){$(".board-column-header-"+x).addClass("board-column-compact")}localStorage.removeItem("hidden_column_"+x)};k.prototype.keyboardShortcuts=function(){var x=this;Mousetrap.bind("c",function(){x.toggleCompactView()});Mousetrap.bind("s",function(){x.toggleCollapsedMode()});Mousetrap.bind("n",function(){x.app.popover.open($("#board").data("task-creation-url"))})};function g(){}g.prototype.getStorageKey=function(){return"hidden_swimlanes_"+$("#board").data("project-id")};g.prototype.expand=function(y){var z=this.getAllCollapsed();var x=z.indexOf(y);if(x>-1){z.splice(x,1)}localStorage.setItem(this.getStorageKey(),JSON.stringify(z));$(".board-swimlane-columns-"+y).css("display","table-row");$(".board-swimlane-tasks-"+y).css("display","table-row");$(".hide-icon-swimlane-"+y).css("display","inline");$(".show-icon-swimlane-"+y).css("display","none")};g.prototype.collapse=function(x){var y=this.getAllCollapsed();if(y.indexOf(x)<0){y.push(x);localStorage.setItem(this.getStorageKey(),JSON.stringify(y))}$(".board-swimlane-columns-"+x+":not(:first-child)").css("display","none");$(".board-swimlane-tasks-"+x).css("display","none");$(".hide-icon-swimlane-"+x).css("display","none");$(".show-icon-swimlane-"+x).css("display","inline")};g.prototype.isCollapsed=function(x){return this.getAllCollapsed().indexOf(x)>-1};g.prototype.getAllCollapsed=function(){return JSON.parse(localStorage.getItem(this.getStorageKey()))||[]};g.prototype.refresh=function(){var y=this.getAllCollapsed();for(var x=0;x<y.length;x++){this.collapse(y[x])}};g.prototype.listen=function(){var x=this;$(document).on("click",".board-swimlane-toggle",function(z){z.preventDefault();var y=$(this).data("swimlane-id");if(x.isCollapsed(y)){x.expand(y)}else{x.collapse(y)}})};function c(x){this.app=x;this.data=[];this.options={container:"#gantt-chart",showWeekends:true,allowMoves:true,allowResizes:true,cellWidth:21,cellHeight:31,slideWidth:1000,vHeaderWidth:200}}c.prototype.saveRecord=function(x){this.app.showLoadingIcon();$.ajax({cache:false,url:$(this.options.container).data("save-url"),contentType:"application/json",type:"POST",processData:false,data:JSON.stringify(x),complete:this.app.hideLoadingIcon.bind(this)})};c.prototype.execute=function(){this.data=this.prepareData($(this.options.container).data("records"));var A=Math.floor((this.options.slideWidth/this.options.cellWidth)+5);var z=this.getDateRange(A);var x=z[0];var C=z[1];var y=$(this.options.container);var B=jQuery("<div>",{"class":"ganttview"});B.append(this.renderVerticalHeader());B.append(this.renderSlider(x,C));y.append(B);jQuery("div.ganttview-grid-row div.ganttview-grid-row-cell:last-child",y).addClass("last");jQuery("div.ganttview-hzheader-days div.ganttview-hzheader-day:last-child",y).addClass("last");jQuery("div.ganttview-hzheader-months div.ganttview-hzheader-month:last-child",y).addClass("last");if(!$(this.options.container).data("readonly")){this.listenForBlockResize(x);this.listenForBlockMove(x)}else{this.options.allowResizes=false;this.options.allowMoves=false}};c.prototype.renderVerticalHeader=function(){var B=jQuery("<div>",{"class":"ganttview-vtheader"});var y=jQuery("<div>",{"class":"ganttview-vtheader-item"});var A=jQuery("<div>",{"class":"ganttview-vtheader-series"});for(var x=0;x<this.data.length;x++){var z=jQuery("<span>").append(jQuery("<i>",{"class":"fa fa-info-circle tooltip",title:this.getVerticalHeaderTooltip(this.data[x])})).append(" ");if(this.data[x].type=="task"){z.append(jQuery("<a>",{href:this.data[x].link,target:"_blank",title:this.data[x].title}).append(this.data[x].title))}else{z.append(jQuery("<a>",{href:this.data[x].board_link,target:"_blank",title:$(this.options.container).data("label-board-link")}).append('<i class="fa fa-th"></i>')).append(" ").append(jQuery("<a>",{href:this.data[x].gantt_link,target:"_blank",title:$(this.options.container).data("label-gantt-link")}).append('<i class="fa fa-sliders"></i>')).append(" ").append(jQuery("<a>",{href:this.data[x].link,target:"_blank"}).append(this.data[x].title))}A.append(jQuery("<div>",{"class":"ganttview-vtheader-series-name"}).append(z))}y.append(A);B.append(y);return B};c.prototype.renderSlider=function(y,A){var x=jQuery("<div>",{"class":"ganttview-slide-container"});var z=this.getDates(y,A);x.append(this.renderHorizontalHeader(z));x.append(this.renderGrid(z));x.append(this.addBlockContainers());this.addBlocks(x,y);return x};c.prototype.renderHorizontalHeader=function(x){var E=jQuery("<div>",{"class":"ganttview-hzheader"});var C=jQuery("<div>",{"class":"ganttview-hzheader-months"});var B=jQuery("<div>",{"class":"ganttview-hzheader-days"});var A=0;for(var F in x){for(var z in x[F]){var G=x[F][z].length*this.options.cellWidth;A=A+G;C.append(jQuery("<div>",{"class":"ganttview-hzheader-month",css:{width:(G-1)+"px"}}).append($.datepicker.regional[$("body").data("js-lang")].monthNames[z]+" "+F));for(var D in x[F][z]){B.append(jQuery("<div>",{"class":"ganttview-hzheader-day"}).append(x[F][z][D].getDate()))}}}C.css("width",A+"px");B.css("width",A+"px");E.append(C).append(B);return E};c.prototype.renderGrid=function(x){var G=jQuery("<div>",{"class":"ganttview-grid"});var B=jQuery("<div>",{"class":"ganttview-grid-row"});for(var E in x){for(var z in x[E]){for(var D in x[E][z]){var A=jQuery("<div>",{"class":"ganttview-grid-row-cell"});if(this.options.showWeekends&&this.isWeekend(x[E][z][D])){A.addClass("ganttview-weekend")}B.append(A)}}}var F=jQuery("div.ganttview-grid-row-cell",B).length*this.options.cellWidth;B.css("width",F+"px");G.css("width",F+"px");for(var C=0;C<this.data.length;C++){G.append(B.clone())}return G};c.prototype.addBlockContainers=function(){var y=jQuery("<div>",{"class":"ganttview-blocks"});for(var x=0;x<this.data.length;x++){y.append(jQuery("<div>",{"class":"ganttview-block-container"}))}return y};c.prototype.addBlocks=function(y,x){var F=jQuery("div.ganttview-blocks div.ganttview-block-container",y);var z=0;for(var C=0;C<this.data.length;C++){var D=this.data[C];var G=this.daysBetween(D.start,D.end)+1;var B=this.daysBetween(x,D.start);var E=jQuery("<div>",{"class":"ganttview-block-text"});var A=jQuery("<div>",{"class":"ganttview-block tooltip"+(this.options.allowMoves?" ganttview-block-movable":""),title:this.getBarTooltip(D),css:{width:((G*this.options.cellWidth)-9)+"px","margin-left":(B*this.options.cellWidth)+"px"}}).append(E);if(G>=2){E.append(D.progress)}A.data("record",D);this.setBarColor(A,D);if(D.progress!="0%"){A.append(jQuery("<div>",{css:{"z-index":0,position:"absolute",top:0,bottom:0,"background-color":D.color.border,width:D.progress,opacity:0.4}}))}jQuery(F[z]).append(A);z=z+1}};c.prototype.getVerticalHeaderTooltip=function(y){var D="";if(y.type=="task"){D="<strong>"+y.column_title+"</strong> ("+y.progress+")<br/>"+y.title}else{var A=["managers","members"];for(var z in A){var B=A[z];if(!jQuery.isEmptyObject(y.users[B])){var C=jQuery("<ul>");for(var x in y.users[B]){C.append(jQuery("<li>").append(y.users[B][x]))}D+="<p><strong>"+$(this.options.container).data("label-"+B)+"</strong></p>"+C[0].outerHTML}}}return D};c.prototype.getBarTooltip=function(x){var y="";if(x.not_defined){y=$(this.options.container).data("label-not-defined")}else{if(x.type=="task"){y="<strong>"+x.progress+"</strong><br/>"+$(this.options.container).data("label-assignee")+" "+(x.assignee?x.assignee:"")+"<br/>"}y+=$(this.options.container).data("label-start-date")+" "+$.datepicker.formatDate("yy-mm-dd",x.start)+"<br/>";y+=$(this.options.container).data("label-end-date")+" "+$.datepicker.formatDate("yy-mm-dd",x.end)}return y};c.prototype.setBarColor=function(y,x){if(x.not_defined){y.addClass("ganttview-block-not-defined")}else{y.css("background-color",x.color.background);y.css("border-color",x.color.border)}};c.prototype.listenForBlockResize=function(x){var y=this;jQuery("div.ganttview-block",this.options.container).resizable({grid:this.options.cellWidth,handles:"e,w",delay:300,stop:function(){var z=jQuery(this);y.updateDataAndPosition(z,x);y.saveRecord(z.data("record"))}})};c.prototype.listenForBlockMove=function(x){var y=this;jQuery("div.ganttview-block",this.options.container).draggable({axis:"x",delay:300,grid:[this.options.cellWidth,this.options.cellWidth],stop:function(){var z=jQuery(this);y.updateDataAndPosition(z,x);y.saveRecord(z.data("record"))}})};c.prototype.updateDataAndPosition=function(C,A){var x=jQuery("div.ganttview-slide-container",this.options.container);var G=x.scrollLeft();var D=C.offset().left-x.offset().left-1+G;var F=C.data("record");F.not_defined=false;this.setBarColor(C,F);var z=Math.round(D/this.options.cellWidth);var E=this.addDays(this.cloneDate(A),z);F.start=E;var y=C.outerWidth();var B=Math.round(y/this.options.cellWidth)-1;F.end=this.addDays(this.cloneDate(E),B);if(F.type==="task"&&B>0){jQuery("div.ganttview-block-text",C).text(F.progress)}C.attr("title",this.getBarTooltip(F));C.data("record",F);C.css("top","").css("left","").css("position","relative").css("margin-left",D+"px")};c.prototype.getDates=function(B,x){var A=[];A[B.getFullYear()]=[];A[B.getFullYear()][B.getMonth()]=[B];var z=B;while(this.compareDate(z,x)==-1){var y=this.addDays(this.cloneDate(z),1);if(!A[y.getFullYear()]){A[y.getFullYear()]=[]}if(!A[y.getFullYear()][y.getMonth()]){A[y.getFullYear()][y.getMonth()]=[]}A[y.getFullYear()][y.getMonth()].push(y);z=y}return A};c.prototype.prepareData=function(z){for(var y=0;y<z.length;y++){var A=new Date(z[y].start[0],z[y].start[1]-1,z[y].start[2],0,0,0,0);z[y].start=A;var x=new Date(z[y].end[0],z[y].end[1]-1,z[y].end[2],0,0,0,0);z[y].end=x}return z};c.prototype.getDateRange=function(z){var C=new Date();var y=new Date();for(var A=0;A<this.data.length;A++){var B=new Date();B.setTime(Date.parse(this.data[A].start));var x=new Date();x.setTime(Date.parse(this.data[A].end));if(A==0){C=B;y=x}if(this.compareDate(C,B)==1){C=B}if(this.compareDate(y,x)==-1){y=x}}if(this.daysBetween(C,y)<z){y=this.addDays(this.cloneDate(C),z)}C.setDate(C.getDate()-1);return[C,y]};c.prototype.daysBetween=function(A,x){if(!A||!x){return 0}var z=0,y=this.cloneDate(A);while(this.compareDate(y,x)==-1){z=z+1;this.addDays(y,1)}return z};c.prototype.isWeekend=function(x){return x.getDay()%6==0};c.prototype.cloneDate=function(x){return new Date(x.getTime())};c.prototype.addDays=function(x,y){x.setDate(x.getDate()+y*1);return x};c.prototype.compareDate=function(y,x){if(isNaN(y)||isNaN(x)){throw new Error(y+" - "+x)}else{if(y instanceof Date&&x instanceof Date){return(y<x)?-1:(y>x)?1:0}else{throw new TypeError(y+" - "+x)}}};function a(){}a.prototype.listen=function(){$(document).on("click",".color-square",function(){$(".color-square-selected").removeClass("color-square-selected");$(this).addClass("color-square-selected");$("#form-color_id").val($(this).data("color-id"))})};function n(){}n.prototype.listen=function(){$(".project-change-role").on("change",function(){$.ajax({cache:false,url:$(this).data("url"),contentType:"application/json",type:"POST",processData:false,data:JSON.stringify({id:$(this).data("id"),role:$(this).val()})})})};function s(){}s.prototype.execute=function(){var z=$("#chart").data("metrics");var y=[];for(var x=0;x<z.length;x++){y.push([z[x].column_title,z[x].nb_tasks])}c3.generate({data:{columns:y,type:"donut"}})};function p(){}p.prototype.execute=function(){var z=$("#chart").data("metrics");var y=[];for(var x=0;x<z.length;x++){y.push([z[x].user,z[x].nb_tasks])}c3.generate({data:{columns:y,type:"donut"}})};function d(){}d.prototype.execute=function(){var D=$("#chart").data("metrics");var C=[];var x=[];var y=[];var A=d3.time.format("%Y-%m-%d");var E=d3.time.format($("#chart").data("date-format"));for(var B=0;B<D.length;B++){for(var z=0;z<D[B].length;z++){if(B==0){C.push([D[B][z]]);if(z>0){x.push(D[B][z])}}else{C[z].push(D[B][z]);if(z==0){y.push(E(A.parse(D[B][z])))}}}}c3.generate({data:{columns:C,type:"area-spline",groups:[x]},axis:{x:{type:"category",categories:y}}})};function o(){}o.prototype.execute=function(){var C=$("#chart").data("metrics");var B=[[$("#chart").data("label-total")]];var x=[];var z=d3.time.format("%Y-%m-%d");var D=d3.time.format($("#chart").data("date-format"));for(var A=0;A<C.length;A++){for(var y=0;y<C[A].length;y++){if(A==0){B.push([C[A][y]])}else{B[y+1].push(C[A][y]);if(y>0){if(B[0][A]==undefined){B[0].push(0)}B[0][A]+=C[A][y]}if(y==0){x.push(D(z.parse(C[A][y])))}}}}c3.generate({data:{columns:B},axis:{x:{type:"category",categories:x}}})};function h(x){this.app=x}h.prototype.execute=function(){var z=$("#chart").data("metrics");var A=[$("#chart").data("label")];var x=[];for(var y in z){A.push(z[y].average);x.push(z[y].title)}c3.generate({data:{columns:[A],type:"bar"},bar:{width:{ratio:0.5}},axis:{x:{type:"category",categories:x},y:{tick:{format:this.app.formatDuration}}},legend:{show:false}})};function w(x){this.app=x}w.prototype.execute=function(){var z=$("#chart").data("metrics");var A=[$("#chart").data("label")];var x=[];for(var y=0;y<z.length;y++){A.push(z[y].time_spent);x.push(z[y].title)}c3.generate({data:{columns:[A],type:"bar"},bar:{width:{ratio:0.5}},axis:{x:{type:"category",categories:x},y:{tick:{format:this.app.formatDuration}}},legend:{show:false}})};function u(x){this.app=x}u.prototype.execute=function(){var D=$("#chart").data("metrics");var C=[$("#chart").data("label-cycle")];var z=[$("#chart").data("label-lead")];var y=[];var B={};B[$("#chart").data("label-cycle")]="area";B[$("#chart").data("label-lead")]="area-spline";var x={};x[$("#chart").data("label-lead")]="#afb42b";x[$("#chart").data("label-cycle")]="#4e342e";for(var A=0;A<D.length;A++){C.push(parseInt(D[A].avg_cycle_time));z.push(parseInt(D[A].avg_lead_time));y.push(D[A].day)}c3.generate({data:{columns:[z,C],types:B,colors:x},axis:{x:{type:"category",categories:y},y:{tick:{format:this.app.formatDuration}}}})};function i(x){this.app=x}i.prototype.execute=function(){var C=$("#chart").data("metrics");var y=$("#chart").data("label-open");var x=$("#chart").data("label-closed");var D=[$("#chart").data("label-spent")];var B=[$("#chart").data("label-estimated")];var A=[];for(var z in C){D.push(parseFloat(C[z].time_spent));B.push(parseFloat(C[z].time_estimated));A.push(z=="open"?y:x)}c3.generate({data:{columns:[D,B],type:"bar"},bar:{width:{ratio:0.2}},axis:{x:{type:"category",categories:A}},legend:{show:true}})};function v(){this.routes={}}v.prototype.addRoute=function(y,x){this.routes[y]=x};v.prototype.dispatch=function(y){for(var z in this.routes){if(document.getElementById(z)){var x=Object.create(this.routes[z].prototype);this.routes[z].apply(x,[y]);x.execute();break}}};jQuery(document).ready(function(){var y=new m();var x=new v();x.addRoute("board",k);x.addRoute("calendar",j);x.addRoute("screenshot-zone",e);x.addRoute("analytic-task-repartition",s);x.addRoute("analytic-user-repartition",p);x.addRoute("analytic-cfd",d);x.addRoute("analytic-burndown",o);x.addRoute("analytic-avg-time-column",h);x.addRoute("analytic-task-time-column",w);x.addRoute("analytic-lead-cycle-time",u);x.addRoute("analytic-compare-hours",i);x.addRoute("gantt-chart",c);x.dispatch(y);y.listen()})})(); \ No newline at end of file diff --git a/assets/js/src/Popover.js b/assets/js/src/Popover.js index 8d72dec8..2686d70d 100644 --- a/assets/js/src/Popover.js +++ b/assets/js/src/Popover.js @@ -55,22 +55,25 @@ Popover.prototype.listen = function() { Popover.prototype.afterOpen = function() { var self = this; - var taskForm = $("#task-form"); + var popoverForm = $(".popover-form"); - if (taskForm) { - taskForm.on("submit", function(e) { + if (popoverForm) { + popoverForm.on("submit", function(e) { e.preventDefault(); $.ajax({ type: "POST", - url: taskForm.attr("action"), - data: taskForm.serialize(), + url: popoverForm.attr("action"), + data: popoverForm.serialize(), success: function(data, textStatus, request) { - if (request.getResponseHeader("X-Ajax-Redirect")) { - window.location = request.getResponseHeader("X-Ajax-Redirect"); + var redirect = request.getResponseHeader("X-Ajax-Redirect"); + + if (redirect) { + window.location = redirect === 'self' ? window.location.href : redirect; } else { $("#popover-content").html(data); + $("input[autofocus]").focus(); self.afterOpen(); } } diff --git a/tests/units/Core/ExternalLink/ExternalLinkManagerTest.php b/tests/units/Core/ExternalLink/ExternalLinkManagerTest.php new file mode 100644 index 00000000..d284a80b --- /dev/null +++ b/tests/units/Core/ExternalLink/ExternalLinkManagerTest.php @@ -0,0 +1,120 @@ +<?php + +require_once __DIR__.'/../../Base.php'; + +use Kanboard\Core\ExternalLink\ExternalLinkManager; +use Kanboard\ExternalLink\WebLinkProvider; +use Kanboard\ExternalLink\AttachmentLinkProvider; + +class ExternalLinkManagerTest extends Base +{ + public function testRegister() + { + $externalLinkManager = new ExternalLinkManager($this->container); + $webLinkProvider = new WebLinkProvider($this->container); + $attachmentLinkProvider = new AttachmentLinkProvider($this->container); + + $externalLinkManager->register($webLinkProvider); + $externalLinkManager->register($attachmentLinkProvider); + + $this->assertInstanceOf(get_class($webLinkProvider), $externalLinkManager->getProvider($webLinkProvider->getType())); + $this->assertInstanceOf(get_class($attachmentLinkProvider), $externalLinkManager->getProvider($attachmentLinkProvider->getType())); + } + + public function testGetProviderNotFound() + { + $externalLinkManager = new ExternalLinkManager($this->container); + + $this->setExpectedException('\Kanboard\Core\ExternalLink\ExternalLinkProviderNotFound'); + $externalLinkManager->getProvider('not found'); + } + + public function testGetTypes() + { + $externalLinkManager = new ExternalLinkManager($this->container); + $webLinkProvider = new WebLinkProvider($this->container); + $attachmentLinkProvider = new AttachmentLinkProvider($this->container); + + $this->assertEquals(array(ExternalLinkManager::TYPE_AUTO => 'Auto'), $externalLinkManager->getTypes()); + + $externalLinkManager->register($webLinkProvider); + $externalLinkManager->register($attachmentLinkProvider); + + $this->assertEquals( + array(ExternalLinkManager::TYPE_AUTO => 'Auto', 'attachment' => 'Attachment', 'weblink' => 'Web Link'), + $externalLinkManager->getTypes() + ); + } + + public function testGetDependencyLabel() + { + $externalLinkManager = new ExternalLinkManager($this->container); + $webLinkProvider = new WebLinkProvider($this->container); + $attachmentLinkProvider = new AttachmentLinkProvider($this->container); + + $externalLinkManager->register($webLinkProvider); + $externalLinkManager->register($attachmentLinkProvider); + + $this->assertSame('Related', $externalLinkManager->getDependencyLabel($webLinkProvider->getType(), 'related')); + $this->assertSame('custom', $externalLinkManager->getDependencyLabel($webLinkProvider->getType(), 'custom')); + } + + public function testFindProviderNotFound() + { + $externalLinkManager = new ExternalLinkManager($this->container); + $webLinkProvider = new WebLinkProvider($this->container); + $attachmentLinkProvider = new AttachmentLinkProvider($this->container); + + $externalLinkManager->register($webLinkProvider); + $externalLinkManager->register($attachmentLinkProvider); + + $this->setExpectedException('\Kanboard\Core\ExternalLink\ExternalLinkProviderNotFound'); + $externalLinkManager->find(); + } + + public function testFindProvider() + { + $externalLinkManager = new ExternalLinkManager($this->container); + $webLinkProvider = new WebLinkProvider($this->container); + $attachmentLinkProvider = new AttachmentLinkProvider($this->container); + + $externalLinkManager->register($webLinkProvider); + $externalLinkManager->register($attachmentLinkProvider); + + $externalLinkManager->setUserInput(array('text' => 'https://google.com/', 'type' => ExternalLinkManager::TYPE_AUTO)); + $this->assertSame($webLinkProvider, $externalLinkManager->find()); + + $externalLinkManager->setUserInput(array('text' => 'https://google.com/file.pdf', 'type' => ExternalLinkManager::TYPE_AUTO)); + $this->assertSame($attachmentLinkProvider, $externalLinkManager->find()); + } + + public function testFindProviderWithSelectedType() + { + $externalLinkManager = new ExternalLinkManager($this->container); + $webLinkProvider = new WebLinkProvider($this->container); + $attachmentLinkProvider = new AttachmentLinkProvider($this->container); + + $externalLinkManager->register($webLinkProvider); + $externalLinkManager->register($attachmentLinkProvider); + + $externalLinkManager->setUserInput(array('text' => 'https://google.com/', 'type' => $webLinkProvider->getType())); + $this->assertSame($webLinkProvider, $externalLinkManager->find()); + + $externalLinkManager->setUserInput(array('text' => 'https://google.com/file.pdf', 'type' => $attachmentLinkProvider->getType())); + $this->assertSame($attachmentLinkProvider, $externalLinkManager->find()); + } + + public function testFindProviderWithSelectedTypeNotFound() + { + $externalLinkManager = new ExternalLinkManager($this->container); + $webLinkProvider = new WebLinkProvider($this->container); + $attachmentLinkProvider = new AttachmentLinkProvider($this->container); + + $externalLinkManager->register($webLinkProvider); + $externalLinkManager->register($attachmentLinkProvider); + + $this->setExpectedException('\Kanboard\Core\ExternalLink\ExternalLinkProviderNotFound'); + $externalLinkManager->setUserInput(array('text' => 'https://google.com/', 'type' => 'not found')); + $externalLinkManager->find(); + } +} diff --git a/tests/units/ExternalLink/AttachmentLinkProviderTest.php b/tests/units/ExternalLink/AttachmentLinkProviderTest.php new file mode 100644 index 00000000..fe374664 --- /dev/null +++ b/tests/units/ExternalLink/AttachmentLinkProviderTest.php @@ -0,0 +1,64 @@ +<?php + +require_once __DIR__.'/../Base.php'; + +use Kanboard\ExternalLink\AttachmentLinkProvider; + +class AttachmentLinkProviderTest extends Base +{ + public function testGetName() + { + $attachmentLinkProvider = new AttachmentLinkProvider($this->container); + $this->assertEquals('Attachment', $attachmentLinkProvider->getName()); + } + + public function testGetType() + { + $attachmentLinkProvider = new AttachmentLinkProvider($this->container); + $this->assertEquals('attachment', $attachmentLinkProvider->getType()); + } + + public function testGetDependencies() + { + $attachmentLinkProvider = new AttachmentLinkProvider($this->container); + $this->assertEquals(array('related' => 'Related'), $attachmentLinkProvider->getDependencies()); + } + + public function testMatch() + { + $attachmentLinkProvider = new AttachmentLinkProvider($this->container); + + $attachmentLinkProvider->setUserTextInput('http://kanboard.net/FILE.DOC'); + $this->assertTrue($attachmentLinkProvider->match()); + + $attachmentLinkProvider->setUserTextInput('http://kanboard.net/folder/document.PDF'); + $this->assertTrue($attachmentLinkProvider->match()); + + $attachmentLinkProvider->setUserTextInput('http://kanboard.net/archive.zip'); + $this->assertTrue($attachmentLinkProvider->match()); + + $attachmentLinkProvider->setUserTextInput(' https://kanboard.net/folder/archive.tar '); + $this->assertTrue($attachmentLinkProvider->match()); + + $attachmentLinkProvider->setUserTextInput('http:// invalid url'); + $this->assertFalse($attachmentLinkProvider->match()); + + $attachmentLinkProvider->setUserTextInput(''); + $this->assertFalse($attachmentLinkProvider->match()); + + $attachmentLinkProvider->setUserTextInput('http://kanboard.net/folder/document.html'); + $this->assertFalse($attachmentLinkProvider->match()); + + $attachmentLinkProvider->setUserTextInput('http://kanboard.net/folder/DOC.HTML'); + $this->assertFalse($attachmentLinkProvider->match()); + + $attachmentLinkProvider->setUserTextInput('http://kanboard.net/folder/document.do'); + $this->assertFalse($attachmentLinkProvider->match()); + } + + public function testGetLink() + { + $attachmentLinkProvider = new AttachmentLinkProvider($this->container); + $this->assertInstanceOf('\Kanboard\ExternalLink\AttachmentLink', $attachmentLinkProvider->getLink()); + } +} diff --git a/tests/units/ExternalLink/AttachmentLinkTest.php b/tests/units/ExternalLink/AttachmentLinkTest.php new file mode 100644 index 00000000..0211869c --- /dev/null +++ b/tests/units/ExternalLink/AttachmentLinkTest.php @@ -0,0 +1,18 @@ +<?php + +require_once __DIR__.'/../Base.php'; + +use Kanboard\ExternalLink\AttachmentLink; + +class AttachmentLinkTest extends Base +{ + public function testGetTitleFromUrl() + { + $url = 'https://kanboard.net/folder/document.pdf'; + + $link = new AttachmentLink($this->container); + $link->setUrl($url); + $this->assertEquals($url, $link->getUrl()); + $this->assertEquals('document.pdf', $link->getTitle()); + } +} diff --git a/tests/units/ExternalLink/WebLinkProviderTest.php b/tests/units/ExternalLink/WebLinkProviderTest.php new file mode 100644 index 00000000..95110ed8 --- /dev/null +++ b/tests/units/ExternalLink/WebLinkProviderTest.php @@ -0,0 +1,52 @@ +<?php + +require_once __DIR__.'/../Base.php'; + +use Kanboard\ExternalLink\WebLinkProvider; + +class WebLinkProviderTest extends Base +{ + public function testGetName() + { + $webLinkProvider = new WebLinkProvider($this->container); + $this->assertEquals('Web Link', $webLinkProvider->getName()); + } + + public function testGetType() + { + $webLinkProvider = new WebLinkProvider($this->container); + $this->assertEquals('weblink', $webLinkProvider->getType()); + } + + public function testGetDependencies() + { + $webLinkProvider = new WebLinkProvider($this->container); + $this->assertEquals(array('related' => 'Related'), $webLinkProvider->getDependencies()); + } + + public function testMatch() + { + $webLinkProvider = new WebLinkProvider($this->container); + + $webLinkProvider->setUserTextInput('http://kanboard.net/'); + $this->assertTrue($webLinkProvider->match()); + + $webLinkProvider->setUserTextInput('http://kanboard.net/mypage'); + $this->assertTrue($webLinkProvider->match()); + + $webLinkProvider->setUserTextInput(' https://kanboard.net/ '); + $this->assertTrue($webLinkProvider->match()); + + $webLinkProvider->setUserTextInput('http:// invalid url'); + $this->assertFalse($webLinkProvider->match()); + + $webLinkProvider->setUserTextInput(''); + $this->assertFalse($webLinkProvider->match()); + } + + public function testGetLink() + { + $webLinkProvider = new WebLinkProvider($this->container); + $this->assertInstanceOf('\Kanboard\ExternalLink\WebLink', $webLinkProvider->getLink()); + } +} diff --git a/tests/units/ExternalLink/WebLinkTest.php b/tests/units/ExternalLink/WebLinkTest.php new file mode 100644 index 00000000..0644620f --- /dev/null +++ b/tests/units/ExternalLink/WebLinkTest.php @@ -0,0 +1,57 @@ +<?php + +require_once __DIR__.'/../Base.php'; + +use Kanboard\ExternalLink\WebLink; + +class WebLinkTest extends Base +{ + public function testGetTitleFromHtml() + { + $url = 'http://kanboard.net/something'; + $title = 'My title'; + $html = '<!DOCTYPE html><html><head><title> '.$title.' Test'; + + $this->container['httpClient'] = $this + ->getMockBuilder('\Kanboard\Core\Http\Client') + ->setConstructorArgs(array($this->container)) + ->setMethods(array('get')) + ->getMock(); + + $webLink = new WebLink($this->container); + $webLink->setUrl($url); + $this->assertEquals($url, $webLink->getUrl()); + + $this->container['httpClient'] + ->expects($this->once()) + ->method('get') + ->with($url) + ->will($this->returnValue($html)); + + $this->assertEquals($title, $webLink->getTitle()); + } + + public function testGetTitleFromUrl() + { + $url = 'http://kanboard.net/something'; + $html = 'Test'; + + $this->container['httpClient'] = $this + ->getMockBuilder('\Kanboard\Core\Http\Client') + ->setConstructorArgs(array($this->container)) + ->setMethods(array('get')) + ->getMock(); + + $webLink = new WebLink($this->container); + $webLink->setUrl($url); + $this->assertEquals($url, $webLink->getUrl()); + + $this->container['httpClient'] + ->expects($this->once()) + ->method('get') + ->with($url) + ->will($this->returnValue($html)); + + $this->assertEquals('kanboard.net/something', $webLink->getTitle()); + } +} diff --git a/tests/units/Model/TaskExternalLinkTest.php b/tests/units/Model/TaskExternalLinkTest.php new file mode 100644 index 00000000..b3f01759 --- /dev/null +++ b/tests/units/Model/TaskExternalLinkTest.php @@ -0,0 +1,167 @@ +container); + $taskCreationModel = new TaskCreation($this->container); + $taskExternalLinkModel = new TaskExternalLink($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + $this->assertEquals(1, $taskCreationModel->create(array('title' => 'Test', 'project_id' => 1))); + $this->assertEquals(1, $taskExternalLinkModel->create(array('task_id' => 1, 'id' => '', 'url' => 'http://kanboard.net/', 'title' => 'My website', 'link_type' => 'weblink', 'dependency' => 'related'))); + + $link = $taskExternalLinkModel->getById(1); + $this->assertNotEmpty($link); + $this->assertEquals('My website', $link['title']); + $this->assertEquals('http://kanboard.net/', $link['url']); + $this->assertEquals('related', $link['dependency']); + $this->assertEquals('weblink', $link['link_type']); + $this->assertEquals(0, $link['creator_id']); + $this->assertEquals(time(), $link['date_modification'], '', 2); + $this->assertEquals(time(), $link['date_creation'], '', 2); + } + + public function testCreateWithUserSession() + { + $this->container['sessionStorage']->user = array('id' => 1); + + $projectModel = new Project($this->container); + $taskCreationModel = new TaskCreation($this->container); + $taskExternalLinkModel = new TaskExternalLink($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + $this->assertEquals(1, $taskCreationModel->create(array('title' => 'Test', 'project_id' => 1))); + $this->assertEquals(1, $taskExternalLinkModel->create(array('task_id' => 1, 'id' => '', 'url' => 'http://kanboard.net/', 'title' => 'My website', 'link_type' => 'weblink', 'dependency' => 'related'))); + + $link = $taskExternalLinkModel->getById(1); + $this->assertNotEmpty($link); + $this->assertEquals('My website', $link['title']); + $this->assertEquals('http://kanboard.net/', $link['url']); + $this->assertEquals('related', $link['dependency']); + $this->assertEquals('weblink', $link['link_type']); + $this->assertEquals(1, $link['creator_id']); + $this->assertEquals(time(), $link['date_modification'], '', 2); + $this->assertEquals(time(), $link['date_creation'], '', 2); + } + + public function testCreateWithNoType() + { + $projectModel = new Project($this->container); + $taskCreationModel = new TaskCreation($this->container); + $taskExternalLinkModel = new TaskExternalLink($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + $this->assertEquals(1, $taskCreationModel->create(array('title' => 'Test', 'project_id' => 1))); + $this->assertFalse($taskExternalLinkModel->create(array('task_id' => 1, 'id' => '', 'url' => 'http://kanboard.net/', 'title' => 'My website', 'dependency' => 'related'))); + } + + public function testCreateWithNoDependency() + { + $projectModel = new Project($this->container); + $taskCreationModel = new TaskCreation($this->container); + $taskExternalLinkModel = new TaskExternalLink($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + $this->assertEquals(1, $taskCreationModel->create(array('title' => 'Test', 'project_id' => 1))); + $this->assertFalse($taskExternalLinkModel->create(array('task_id' => 1, 'id' => '', 'url' => 'http://kanboard.net/', 'title' => 'My website', 'link_type' => 'test'))); + } + + public function testCreateWithNoTitle() + { + $projectModel = new Project($this->container); + $taskCreationModel = new TaskCreation($this->container); + $taskExternalLinkModel = new TaskExternalLink($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + $this->assertEquals(1, $taskCreationModel->create(array('title' => 'Test', 'project_id' => 1))); + $this->assertFalse($taskExternalLinkModel->create(array('task_id' => 1, 'id' => '', 'url' => 'http://kanboard.net/', 'link_type' => 'test', 'dependency' => 'test'))); + } + + public function testCreateWithNoUrl() + { + $projectModel = new Project($this->container); + $taskCreationModel = new TaskCreation($this->container); + $taskExternalLinkModel = new TaskExternalLink($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + $this->assertEquals(1, $taskCreationModel->create(array('title' => 'Test', 'project_id' => 1))); + $this->assertFalse($taskExternalLinkModel->create(array('task_id' => 1, 'id' => '', 'title' => 'test', 'link_type' => 'test', 'dependency' => 'test'))); + } + + public function testModification() + { + $projectModel = new Project($this->container); + $taskCreationModel = new TaskCreation($this->container); + $taskExternalLinkModel = new TaskExternalLink($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + $this->assertEquals(1, $taskCreationModel->create(array('title' => 'Test', 'project_id' => 1))); + $this->assertEquals(1, $taskExternalLinkModel->create(array('task_id' => 1, 'id' => '', 'url' => 'http://kanboard.net/', 'title' => 'My website', 'link_type' => 'weblink', 'dependency' => 'related'))); + + sleep(1); + + $this->assertTrue($taskExternalLinkModel->update(array('id' => 1, 'url' => 'https://kanboard.net/'))); + + $link = $taskExternalLinkModel->getById(1); + $this->assertNotEmpty($link); + $this->assertEquals('https://kanboard.net/', $link['url']); + $this->assertEquals(time(), $link['date_modification'], '', 2); + } + + public function testRemove() + { + $projectModel = new Project($this->container); + $taskCreationModel = new TaskCreation($this->container); + $taskExternalLinkModel = new TaskExternalLink($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + $this->assertEquals(1, $taskCreationModel->create(array('title' => 'Test', 'project_id' => 1))); + $this->assertEquals(1, $taskExternalLinkModel->create(array('task_id' => 1, 'id' => '', 'url' => 'http://kanboard.net/', 'title' => 'My website', 'link_type' => 'weblink', 'dependency' => 'related'))); + + $this->assertTrue($taskExternalLinkModel->remove(1)); + $this->assertFalse($taskExternalLinkModel->remove(1)); + + $this->assertEmpty($taskExternalLinkModel->getById(1)); + } + + public function testGetAll() + { + $this->container['sessionStorage']->user = array('id' => 1); + $this->container['externalLinkManager'] = new ExternalLinkManager($this->container); + + $projectModel = new Project($this->container); + $taskCreationModel = new TaskCreation($this->container); + $taskExternalLinkModel = new TaskExternalLink($this->container); + $webLinkProvider = new WebLinkProvider($this->container); + + $this->container['externalLinkManager']->register($webLinkProvider); + + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + $this->assertEquals(1, $taskCreationModel->create(array('title' => 'Test', 'project_id' => 1))); + $this->assertEquals(1, $taskExternalLinkModel->create(array('task_id' => 1, 'url' => 'https://miniflux.net/', 'title' => 'MX', 'link_type' => 'weblink', 'dependency' => 'related'))); + $this->assertEquals(2, $taskExternalLinkModel->create(array('task_id' => 1, 'url' => 'http://kanboard.net/', 'title' => 'KB', 'link_type' => 'weblink', 'dependency' => 'related'))); + + $links = $taskExternalLinkModel->getAll(1); + $this->assertCount(2, $links); + $this->assertEquals('KB', $links[0]['title']); + $this->assertEquals('MX', $links[1]['title']); + $this->assertEquals('Web Link', $links[0]['type']); + $this->assertEquals('Web Link', $links[1]['type']); + $this->assertEquals('Related', $links[0]['dependency_label']); + $this->assertEquals('Related', $links[1]['dependency_label']); + $this->assertEquals('admin', $links[0]['creator_username']); + $this->assertEquals('admin', $links[1]['creator_username']); + $this->assertEquals('', $links[0]['creator_name']); + $this->assertEquals('', $links[1]['creator_name']); + } +} diff --git a/tests/units/Validator/ExternalLinkValidatorTest.php b/tests/units/Validator/ExternalLinkValidatorTest.php new file mode 100644 index 00000000..b41b779a --- /dev/null +++ b/tests/units/Validator/ExternalLinkValidatorTest.php @@ -0,0 +1,63 @@ +container); + + $result = $validator->validateCreation(array('url' => 'http://somewhere', 'task_id' => 1, 'title' => 'Title', 'link_type' => 'weblink', 'dependency' => 'related')); + $this->assertTrue($result[0]); + + $result = $validator->validateCreation(array('url' => 'http://somewhere', 'task_id' => 'abc', 'title' => 'Title', 'link_type' => 'weblink', 'dependency' => 'related')); + $this->assertFalse($result[0]); + + $result = $validator->validateCreation(array('url' => 'http://somewhere', 'task_id' => 1, 'title' => 'Title', 'link_type' => 'weblink')); + $this->assertFalse($result[0]); + + $result = $validator->validateCreation(array('url' => 'http://somewhere', 'task_id' => 1, 'title' => 'Title', 'dependency' => 'related')); + $this->assertFalse($result[0]); + + $result = $validator->validateCreation(array('url' => 'http://somewhere', 'task_id' => 1, 'link_type' => 'weblink', 'dependency' => 'related')); + $this->assertFalse($result[0]); + + $result = $validator->validateCreation(array('url' => 'http://somewhere', 'title' => 'Title', 'link_type' => 'weblink', 'dependency' => 'related')); + $this->assertFalse($result[0]); + + $result = $validator->validateCreation(array('task_id' => 1, 'title' => 'Title', 'link_type' => 'weblink', 'dependency' => 'related')); + $this->assertFalse($result[0]); + } + + public function testValidateModification() + { + $validator = new ExternalLinkValidator($this->container); + + $result = $validator->validateModification(array('id' => 1, 'url' => 'http://somewhere', 'task_id' => 1, 'title' => 'Title', 'link_type' => 'weblink', 'dependency' => 'related')); + $this->assertTrue($result[0]); + + $result = $validator->validateModification(array('id' => 1, 'url' => 'http://somewhere', 'task_id' => 'abc', 'title' => 'Title', 'link_type' => 'weblink', 'dependency' => 'related')); + $this->assertFalse($result[0]); + + $result = $validator->validateModification(array('id' => 1, 'url' => 'http://somewhere', 'task_id' => 1, 'title' => 'Title', 'link_type' => 'weblink')); + $this->assertFalse($result[0]); + + $result = $validator->validateModification(array('id' => 1, 'url' => 'http://somewhere', 'task_id' => 1, 'title' => 'Title', 'dependency' => 'related')); + $this->assertFalse($result[0]); + + $result = $validator->validateModification(array('id' => 1, 'url' => 'http://somewhere', 'task_id' => 1, 'link_type' => 'weblink', 'dependency' => 'related')); + $this->assertFalse($result[0]); + + $result = $validator->validateModification(array('id' => 1, 'url' => 'http://somewhere', 'title' => 'Title', 'link_type' => 'weblink', 'dependency' => 'related')); + $this->assertFalse($result[0]); + + $result = $validator->validateModification(array('id' => 1, 'task_id' => 1, 'title' => 'Title', 'link_type' => 'weblink', 'dependency' => 'related')); + $this->assertFalse($result[0]); + + $result = $validator->validateModification(array('url' => 'http://somewhere', 'task_id' => 1, 'title' => 'Title', 'link_type' => 'weblink', 'dependency' => 'related')); + $this->assertFalse($result[0]); + } +} -- cgit v1.2.3 From 58cef289674717027b6b470044504a661f3bd9ad Mon Sep 17 00:00:00 2001 From: Frederic Guillot Date: Sat, 6 Feb 2016 18:08:41 -0500 Subject: The date time format can be chosen in application settings --- ChangeLog | 1 + app/Controller/Analytic.php | 4 +- app/Controller/Config.php | 4 +- app/Controller/Export.php | 2 +- app/Controller/Task.php | 4 +- app/Controller/Taskmodification.php | 6 +- app/Core/DateParser.php | 215 ++++++++++++++++------------ app/Core/Translator.php | 28 ---- app/Helper/Dt.php | 46 +++++- app/Helper/Task.php | 5 +- app/Locale/bs_BA/translations.php | 19 +-- app/Locale/cs_CZ/translations.php | 19 +-- app/Locale/da_DK/translations.php | 19 +-- app/Locale/de_DE/translations.php | 19 +-- app/Locale/el_GR/translations.php | 19 +-- app/Locale/es_ES/translations.php | 19 +-- app/Locale/fi_FI/translations.php | 19 +-- app/Locale/fr_FR/translations.php | 19 +-- app/Locale/hu_HU/translations.php | 19 +-- app/Locale/id_ID/translations.php | 19 +-- app/Locale/it_IT/translations.php | 19 +-- app/Locale/ja_JP/translations.php | 19 +-- app/Locale/my_MY/translations.php | 19 +-- app/Locale/nb_NO/translations.php | 19 +-- app/Locale/nl_NL/translations.php | 19 +-- app/Locale/pl_PL/translations.php | 19 +-- app/Locale/pt_BR/translations.php | 19 +-- app/Locale/pt_PT/translations.php | 19 +-- app/Locale/ru_RU/translations.php | 19 +-- app/Locale/sr_Latn_RS/translations.php | 19 +-- app/Locale/sv_SE/translations.php | 19 +-- app/Locale/th_TH/translations.php | 19 +-- app/Locale/tr_TR/translations.php | 19 +-- app/Locale/zh_CN/translations.php | 19 +-- app/Model/File.php | 2 +- app/Model/TaskCreation.php | 5 +- app/Model/TaskExport.php | 2 +- app/Model/TaskModification.php | 5 +- app/Template/app/notifications.php | 2 +- app/Template/app/tasks.php | 2 +- app/Template/board/task_footer.php | 2 +- app/Template/board/tooltip_comments.php | 2 +- app/Template/comment/show.php | 2 +- app/Template/config/application.php | 6 + app/Template/event/events.php | 2 +- app/Template/file/show.php | 4 +- app/Template/listing/show.php | 2 +- app/Template/notification/task_create.php | 4 +- app/Template/notification/task_overdue.php | 2 +- app/Template/project/index.php | 4 +- app/Template/project/show.php | 6 +- app/Template/project_user/tasks.php | 4 +- app/Template/search/results.php | 2 +- app/Template/task/changes.php | 4 +- app/Template/task/details.php | 12 +- app/Template/task/time_tracking_details.php | 4 +- app/Template/task/transitions.php | 2 +- app/Template/task_external_link/show.php | 2 +- app/Template/user/last.php | 2 +- app/Template/user/password_reset.php | 4 +- app/Template/user/sessions.php | 4 +- app/Template/user/timesheet.php | 4 +- app/Validator/TaskValidator.php | 4 +- app/functions.php | 10 -- doc/translations.markdown | 13 +- tests/units/Core/DateParserTest.php | 182 +++++++++++++++++------ tests/units/Helper/DatetimeHelperTest.php | 52 +++++-- tests/units/Model/TaskCreationTest.php | 4 +- tests/units/Model/TaskDuplicationTest.php | 6 +- 69 files changed, 557 insertions(+), 583 deletions(-) (limited to 'app/Validator') diff --git a/ChangeLog b/ChangeLog index ef58e0ac..6b9d17fd 100644 --- a/ChangeLog +++ b/ChangeLog @@ -23,6 +23,7 @@ New features: Improvements: +* The date time format can be chosen in application settings * Export only open tasks in iCal feed * Remove time form on task summary page and move that to task edit form * Replace box shadow by a larger border width when a task is recently modified diff --git a/app/Controller/Analytic.php b/app/Controller/Analytic.php index e7ab9ebc..6ce062c4 100644 --- a/app/Controller/Analytic.php +++ b/app/Controller/Analytic.php @@ -31,7 +31,7 @@ class Analytic extends Base 'average' => $this->averageLeadCycleTimeAnalytic->build($project['id']), 'metrics' => $this->projectDailyStats->getRawMetrics($project['id'], $from, $to), 'date_format' => $this->config->get('application_date_format'), - 'date_formats' => $this->dateParser->getAvailableFormats(), + 'date_formats' => $this->dateParser->getAvailableFormats($this->dateParser->getDateFormats()), 'title' => t('Lead and Cycle time for "%s"', $project['name']), ))); } @@ -154,7 +154,7 @@ class Analytic extends Base 'metrics' => $display_graph ? $this->projectDailyColumnStats->getAggregatedMetrics($project['id'], $from, $to, $column) : array(), 'project' => $project, 'date_format' => $this->config->get('application_date_format'), - 'date_formats' => $this->dateParser->getAvailableFormats(), + 'date_formats' => $this->dateParser->getAvailableFormats($this->dateParser->getDateFormats()), 'title' => t($title, $project['name']), ))); } diff --git a/app/Controller/Config.php b/app/Controller/Config.php index 53f7cdb6..80522bbe 100644 --- a/app/Controller/Config.php +++ b/app/Controller/Config.php @@ -85,7 +85,9 @@ class Config extends Base $this->response->html($this->helper->layout->config('config/application', array( 'languages' => $this->config->getLanguages(), 'timezones' => $this->config->getTimezones(), - 'date_formats' => $this->dateParser->getAvailableFormats(), + 'date_formats' => $this->dateParser->getAvailableFormats($this->dateParser->getDateFormats()), + 'datetime_formats' => $this->dateParser->getAvailableFormats($this->dateParser->getDateTimeFormats()), + 'time_formats' => $this->dateParser->getAvailableFormats($this->dateParser->getTimeFormats()), 'title' => t('Settings').' > '.t('Application settings'), ))); } diff --git a/app/Controller/Export.php b/app/Controller/Export.php index 977a4107..726edd42 100644 --- a/app/Controller/Export.php +++ b/app/Controller/Export.php @@ -37,7 +37,7 @@ class Export extends Base ), 'errors' => array(), 'date_format' => $this->config->get('application_date_format'), - 'date_formats' => $this->dateParser->getAvailableFormats(), + 'date_formats' => $this->dateParser->getAvailableFormats($this->dateParser->getDateFormats()), 'project' => $project, 'title' => $page_title, ), 'export/sidebar')); diff --git a/app/Controller/Task.php b/app/Controller/Task.php index 40ff8448..4db8f86e 100644 --- a/app/Controller/Task.php +++ b/app/Controller/Task.php @@ -62,7 +62,7 @@ class Task extends Base 'time_spent' => $task['time_spent'] ?: '', ); - $this->dateParser->format($values, array('date_started'), 'Y-m-d H:i'); + $values = $this->dateParser->format($values, array('date_started'), $this->config->get('application_datetime_format', 'm/d/Y H:i')); $this->response->html($this->helper->layout->task('task/show', array( 'project' => $this->project->getById($task['project_id']), @@ -77,8 +77,6 @@ class Task extends Base 'columns_list' => $this->board->getColumnsList($task['project_id']), 'colors_list' => $this->color->getList(), 'users_list' => $this->projectUserRole->getAssignableUsersList($task['project_id'], true, false, false), - 'date_format' => $this->config->get('application_date_format'), - 'date_formats' => $this->dateParser->getAvailableFormats(), 'title' => $task['project_name'].' > '.$task['title'], 'recurrence_trigger_list' => $this->task->getRecurrenceTriggerList(), 'recurrence_timeframe_list' => $this->task->getRecurrenceTimeframeList(), diff --git a/app/Controller/Taskmodification.php b/app/Controller/Taskmodification.php index 54b4b23a..a321322d 100644 --- a/app/Controller/Taskmodification.php +++ b/app/Controller/Taskmodification.php @@ -82,8 +82,8 @@ class Taskmodification extends Base $values = $this->hook->merge('controller:task:form:default', $values, array('default_values' => $values)); } - $this->dateParser->format($values, array('date_due')); - $this->dateParser->format($values, array('date_started'), 'Y-m-d H:i'); + $values = $this->dateParser->format($values, array('date_due'), $this->config->get('application_date_format', 'm/d/Y')); + $values = $this->dateParser->format($values, array('date_started'), $this->config->get('application_datetime_format', 'm/d/Y H:i')); $this->response->html($this->helper->layout->task('task_modification/edit_task', array( 'project' => $project, @@ -93,8 +93,6 @@ class Taskmodification extends Base 'users_list' => $this->projectUserRole->getAssignableUsersList($task['project_id']), 'colors_list' => $this->color->getList(), 'categories_list' => $this->category->getList($task['project_id']), - 'date_format' => $this->config->get('application_date_format'), - 'date_formats' => $this->dateParser->getAvailableFormats(), ))); } diff --git a/app/Core/DateParser.php b/app/Core/DateParser.php index 6577af0f..20e79ff9 100644 --- a/app/Core/DateParser.php +++ b/app/Core/DateParser.php @@ -13,67 +13,91 @@ use DateTime; class DateParser extends Base { /** - * Return true if the date is within the date range + * List of time formats * * @access public - * @param DateTime $date - * @param DateTime $start - * @param DateTime $end - * @return boolean + * @return string[] */ - public function withinDateRange(DateTime $date, DateTime $start, DateTime $end) + public function getTimeFormats() { - return $date >= $start && $date <= $end; + return array( + 'H:i', + 'g:i a', + ); } /** - * Get the total number of hours between 2 datetime objects - * Minutes are rounded to the nearest quarter + * List of date formats * * @access public - * @param DateTime $d1 - * @param DateTime $d2 - * @return float + * @param boolean $iso + * @return string[] */ - public function getHours(DateTime $d1, DateTime $d2) + public function getDateFormats($iso = false) { - $seconds = $this->getRoundedSeconds(abs($d1->getTimestamp() - $d2->getTimestamp())); - return round($seconds / 3600, 2); + $iso_formats = array( + 'Y-m-d', + 'Y_m_d', + ); + + $user_formats = array( + 'm/d/Y', + 'd/m/Y', + 'Y/m/d', + 'd.m.Y', + ); + + return $iso ? array_merge($iso_formats, $user_formats) : $user_formats; } /** - * Round the timestamp to the nearest quarter + * List of datetime formats * * @access public - * @param integer $seconds Timestamp - * @return integer + * @param boolean $iso + * @return string[] */ - public function getRoundedSeconds($seconds) + public function getDateTimeFormats($iso = false) { - return (int) round($seconds / (15 * 60)) * (15 * 60); + $formats = array(); + + foreach ($this->getDateFormats($iso) as $date) { + foreach ($this->getTimeFormats() as $time) { + $formats[] = $date.' '.$time; + } + } + + return $formats; } /** - * Return a timestamp if the given date format is correct otherwise return 0 + * List of all date formats * * @access public - * @param string $value Date to parse - * @param string $format Date format - * @return integer + * @param boolean $iso + * @return string[] */ - public function getValidDate($value, $format) + public function getAllDateFormats($iso = false) { - $date = DateTime::createFromFormat($format, $value); + return array_merge($this->getDateFormats($iso), $this->getDateTimeFormats($iso)); + } - if ($date !== false) { - $errors = DateTime::getLastErrors(); - if ($errors['error_count'] === 0 && $errors['warning_count'] === 0) { - $timestamp = $date->getTimestamp(); - return $timestamp > 0 ? $timestamp : 0; - } + /** + * Get available formats (visible in settings) + * + * @access public + * @param array $formats + * @return array + */ + public function getAvailableFormats(array $formats) + { + $values = array(); + + foreach ($formats as $format) { + $values[$format] = date($format); } - return 0; + return $values; } /** @@ -85,7 +109,11 @@ class DateParser extends Base */ public function getTimestamp($value) { - foreach ($this->getAllFormats() as $format) { + if (ctype_digit($value)) { + return (int) $value; + } + + foreach ($this->getAllDateFormats(true) as $format) { $timestamp = $this->getValidDate($value, $format); if ($timestamp !== 0) { @@ -97,104 +125,103 @@ class DateParser extends Base } /** - * Get ISO8601 date from user input + * Return a timestamp if the given date format is correct otherwise return 0 * - * @access public - * @param string $value Date to parse - * @return string + * @access private + * @param string $value Date to parse + * @param string $format Date format + * @return integer */ - public function getIsoDate($value) + private function getValidDate($value, $format) { - return date('Y-m-d', ctype_digit($value) ? $value : $this->getTimestamp($value)); + $date = DateTime::createFromFormat($format, $value); + + if ($date !== false) { + $errors = DateTime::getLastErrors(); + if ($errors['error_count'] === 0 && $errors['warning_count'] === 0) { + $timestamp = $date->getTimestamp(); + return $timestamp > 0 ? $timestamp : 0; + } + } + + return 0; } /** - * Get all combinations of date/time formats + * Return true if the date is within the date range * * @access public - * @return string[] + * @param DateTime $date + * @param DateTime $start + * @param DateTime $end + * @return boolean */ - public function getAllFormats() + public function withinDateRange(DateTime $date, DateTime $start, DateTime $end) { - $formats = array(); - - foreach ($this->getDateFormats() as $date) { - foreach ($this->getTimeFormats() as $time) { - $formats[] = $date.' '.$time; - } - } - - return array_merge($formats, $this->getDateFormats()); + return $date >= $start && $date <= $end; } /** - * Return the list of supported date formats (for the parser) + * Get the total number of hours between 2 datetime objects + * Minutes are rounded to the nearest quarter * * @access public - * @return string[] + * @param DateTime $d1 + * @param DateTime $d2 + * @return float */ - public function getDateFormats() + public function getHours(DateTime $d1, DateTime $d2) { - return array( - $this->config->get('application_date_format', 'm/d/Y'), - 'Y-m-d', - 'Y_m_d', - ); + $seconds = $this->getRoundedSeconds(abs($d1->getTimestamp() - $d2->getTimestamp())); + return round($seconds / 3600, 2); } /** - * Return the list of supported time formats (for the parser) + * Round the timestamp to the nearest quarter * * @access public - * @return string[] + * @param integer $seconds Timestamp + * @return integer */ - public function getTimeFormats() + public function getRoundedSeconds($seconds) { - return array( - 'H:i', - 'g:i A', - 'g:iA', - ); + return (int) round($seconds / (15 * 60)) * (15 * 60); } /** - * Return the list of available date formats (for the config page) + * Get ISO-8601 date from user input * * @access public - * @return array + * @param string $value Date to parse + * @return string */ - public function getAvailableFormats() + public function getIsoDate($value) { - return array( - 'm/d/Y' => date('m/d/Y'), - 'd/m/Y' => date('d/m/Y'), - 'Y/m/d' => date('Y/m/d'), - 'd.m.Y' => date('d.m.Y'), - ); + return date('Y-m-d', $this->getTimestamp($value)); } /** - * Remove the time from a timestamp + * Get a timetstamp from an ISO date format * * @access public - * @param integer $timestamp Timestamp + * @param string $value * @return integer */ - public function removeTimeFromTimestamp($timestamp) + public function getTimestampFromIsoFormat($value) { - return mktime(0, 0, 0, date('m', $timestamp), date('d', $timestamp), date('Y', $timestamp)); + return $this->removeTimeFromTimestamp(ctype_digit($value) ? $value : strtotime($value)); } /** - * Get a timetstamp from an ISO date format + * Remove the time from a timestamp * * @access public - * @param string $date + * @param integer $timestamp * @return integer */ - public function getTimestampFromIsoFormat($date) + public function removeTimeFromTimestamp($timestamp) { - return $this->removeTimeFromTimestamp(ctype_digit($date) ? $date : strtotime($date)); + return mktime(0, 0, 0, date('m', $timestamp), date('d', $timestamp), date('Y', $timestamp)); } /** @@ -204,13 +231,10 @@ class DateParser extends Base * @param array $values Database values * @param string[] $fields Date fields * @param string $format Date format + * @return array */ - public function format(array &$values, array $fields, $format = '') + public function format(array $values, array $fields, $format) { - if ($format === '') { - $format = $this->config->get('application_date_format'); - } - foreach ($fields as $field) { if (! empty($values[$field])) { $values[$field] = date($format, $values[$field]); @@ -218,23 +242,28 @@ class DateParser extends Base $values[$field] = ''; } } + + return $values; } /** - * Convert date (form input data) + * Convert date to timestamp * * @access public * @param array $values Database values * @param string[] $fields Date fields * @param boolean $keep_time Keep time or not + * @return array */ - public function convert(array &$values, array $fields, $keep_time = false) + public function convert(array $values, array $fields, $keep_time = false) { foreach ($fields as $field) { - if (! empty($values[$field]) && ! is_numeric($values[$field])) { + if (! empty($values[$field])) { $timestamp = $this->getTimestamp($values[$field]); $values[$field] = $keep_time ? $timestamp : $this->removeTimeFromTimestamp($timestamp); } } + + return $values; } } diff --git a/app/Core/Translator.php b/app/Core/Translator.php index 96a481f6..113c0dc6 100644 --- a/app/Core/Translator.php +++ b/app/Core/Translator.php @@ -146,32 +146,6 @@ class Translator return $str; } - /** - * Get a formatted datetime - * - * $translator->datetime('%Y-%m-%d', time()); - * - * @access public - * @param string $format Format defined by the strftime function - * @param integer $timestamp Unix timestamp - * @return string - */ - public function datetime($format, $timestamp) - { - if (! $timestamp) { - return ''; - } - - $format = $this->get($format, $format); - - if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { - $format = str_replace('%e', '%d', $format); - $format = str_replace('%k', '%H', $format); - } - - return strftime($format, (int) $timestamp); - } - /** * Get an identifier from the translations or return the default * @@ -199,8 +173,6 @@ class Translator */ public static function load($language, $path = self::PATH) { - setlocale(LC_TIME, $language.'.UTF-8', $language); - $filename = $path.DIRECTORY_SEPARATOR.$language.DIRECTORY_SEPARATOR.'translations.php'; if (file_exists($filename)) { diff --git a/app/Helper/Dt.php b/app/Helper/Dt.php index 78002b1b..eb3f93b3 100644 --- a/app/Helper/Dt.php +++ b/app/Helper/Dt.php @@ -12,6 +12,50 @@ use DateTime; */ class Dt extends \Kanboard\Core\Base { + /** + * Get formatted time + * + * @access public + * @param integer $value + * @return string + */ + public function time($value) + { + return date($this->config->get('application_time_format', 'H:i'), $value); + } + + /** + * Get formatted date + * + * @access public + * @param integer $value + * @return string + */ + public function date($value) + { + if (empty($value)) { + return ''; + } + + if (! ctype_digit($value)) { + $value = strtotime($value); + } + + return date($this->config->get('application_date_format', 'm/d/Y'), $value); + } + + /** + * Get formatted datetime + * + * @access public + * @param integer $value + * @return string + */ + public function datetime($value) + { + return date($this->config->get('application_datetime_format', 'm/d/Y H:i'), $value); + } + /** * Get duration in seconds into human format * @@ -107,6 +151,6 @@ class Dt extends \Kanboard\Core\Base */ public function getWeekDay($day) { - return dt('%A', strtotime('next Monday +'.($day - 1).' days')); + return date('l', strtotime('next Monday +'.($day - 1).' days')); } } diff --git a/app/Helper/Task.php b/app/Helper/Task.php index 0ef3da2c..e85d6e30 100644 --- a/app/Helper/Task.php +++ b/app/Helper/Task.php @@ -142,7 +142,7 @@ class Task extends Base public function selectStartDate(array $values, array $errors = array(), array $attributes = array()) { - $placeholder = $this->helper->text->in($this->config->get('application_date_format'), $this->dateParser->getAvailableFormats()); + $placeholder = date($this->config->get('application_date_format', 'm/d/Y H:i')); $attributes = array_merge(array('tabindex="11"', 'placeholder="'.$placeholder.'"'), $attributes); $html = $this->helper->form->label(t('Start Date'), 'date_started'); @@ -153,12 +153,11 @@ class Task extends Base public function selectDueDate(array $values, array $errors = array(), array $attributes = array()) { - $placeholder = $this->helper->text->in($this->config->get('application_date_format'), $this->dateParser->getAvailableFormats()); + $placeholder = date($this->config->get('application_date_format', 'm/d/Y')); $attributes = array_merge(array('tabindex="12"', 'placeholder="'.$placeholder.'"'), $attributes); $html = $this->helper->form->label(t('Due Date'), 'date_due'); $html .= $this->helper->form->text('date_due', $values, $errors, $attributes, 'form-date'); - $html .= '
      '.t('Others formats accepted: %s and %s', date('Y-m-d'), date('Y_m_d')).'
      '; return $html; } diff --git a/app/Locale/bs_BA/translations.php b/app/Locale/bs_BA/translations.php index b8b6a10f..e331d4a1 100644 --- a/app/Locale/bs_BA/translations.php +++ b/app/Locale/bs_BA/translations.php @@ -102,7 +102,6 @@ return array( 'Open a task' => 'Otvori zadatak', 'Do you really want to open this task: "%s"?' => 'Da li zaista želiš da otvoriš zadatak: "%s"?', 'Back to the board' => 'Nazad na tablu', - 'Created on %B %e, %Y at %k:%M %p' => 'Kreiran %e %B %Y o %k:%M', 'There is nobody assigned' => 'Niko nije dodijeljen!', 'Column on the board:' => 'Kolona na tabli:', 'Close this task' => 'Zatvori ovaj zadatak', @@ -156,7 +155,6 @@ return array( 'Work in progress' => 'U izradi', 'Done' => 'Gotovo', 'Application version:' => 'Verzija aplikacije:', - '%B %e, %Y at %k:%M %p' => '%e %B %Y o %k:%M', 'Id' => 'Id', '%d closed tasks' => '%d zatvorenih zadataka', 'No task for this project' => 'Nema dodijeljenih zadataka ovom projektu', @@ -180,8 +178,6 @@ return array( 'Edit this task' => 'Uredi ovaj zadatak', 'Due Date' => 'Treba biti gotovo do dana', 'Invalid date' => 'Pogrešan datum', - 'Must be done before %B %e, %Y' => 'Mora biti gotovo prije %e %B %Y', - '%B %e, %Y' => '%e %B %Y', 'Automatic actions' => 'Automatske akcije', 'Your automatic action have been created successfully.' => 'Uspješno kreirana automatska akcija', 'Unable to create your automatic action.' => 'Nemoguće kreiranje automatske akcije', @@ -317,7 +313,6 @@ return array( 'Unable to upload the file.' => 'Nije moguće snimiti fajl.', 'Display another project' => 'Prikaži drugi projekat', 'Created by %s' => 'Kreirao %s', - 'Last modified on %B %e, %Y at %k:%M %p' => 'Posljednja izmjena %e %B %Y o %k:%M', 'Tasks Export' => 'Izvoz zadataka', 'Tasks exportation for "%s"' => 'Izvoz zadataka za "%s"', 'Start Date' => 'Početni datum', @@ -589,7 +584,6 @@ return array( 'This task' => 'Ovaj zadatak', '<1h' => '<1h', '%dh' => '%dh', - '%b %e' => '%b %e', 'Expand tasks' => 'Proširi zadatke', 'Collapse tasks' => 'Skupi zadatke', 'Expand/collapse tasks' => 'Proširi/skupi zadatke', @@ -758,10 +752,6 @@ return array( 'Only for tasks assigned to me' => 'Samo za zadatke na kojima sam izvršilac', 'Only for tasks created by me' => 'Samo za zadatke koje sam ja napravio', 'Only for tasks created by me and assigned to me' => 'Samo za zadatke koje sam ja napravio i na kojima sam izvršilac', - '%A' => '%A', - '%b %e, %Y, %k:%M %p' => '%b %e, %Y, %k:%M %p', - 'New due date: %B %e, %Y' => 'Novi datum završetka: %B %e, %Y', - 'Start date changed: %B %e, %Y' => 'Početni datum promijenjen: %B %e, %Y', '%%Y-%%m-%%d' => '%%Y-%%m-%%d', 'Total for all columns' => 'Ukupno za sve kolone', 'You need at least 2 days of data to show the chart.' => 'Da bi se prikazao ovaj grafik potrebni su podaci iz najmanje posljednja dva dana.', @@ -786,7 +776,6 @@ return array( 'Not assigned' => 'Bez izvršioca', 'View advanced search syntax' => 'Vidi naprednu sintaksu pretrage', 'Overview' => 'Opšti pregled', - '%b %e %Y' => '%b %e %Y', 'Board/Calendar/List view' => 'Pregle Table/Kalendara/Liste', 'Switch to the board view' => 'Promijeni da vidim tablu', 'Switch to the calendar view' => 'Promijeni da vidim kalendar', @@ -876,8 +865,6 @@ return array( 'End date:' => 'Datum završetka:', 'There is no start date or end date for this project.' => 'Nema početnog ili krajnjeg datuma za ovaj projekat.', 'Projects Gantt chart' => 'Gantogram projekata', - 'Start date: %s' => 'Početni datum: %s', - 'End date: %s' => 'Datum završetka: %s', 'Link type' => 'Tip veze', 'Change task color when using a specific task link' => 'Promijeni boju zadatka kada se koristi određena veza na zadatku', 'Task link creation or modification' => 'Veza na zadatku je napravljena ili izmijenjena', @@ -1125,4 +1112,10 @@ return array( // 'Moved:' => '', // 'Task #%d' => '', // 'Sub-tasks' => '', + // 'Date and time format' => '', + // 'Time format' => '', + // 'Start date: ' => '', + // 'End date: ' => '', + // 'New due date: ' => '', + // 'Start date changed: ' => '', ); diff --git a/app/Locale/cs_CZ/translations.php b/app/Locale/cs_CZ/translations.php index cb99e404..a495f01e 100644 --- a/app/Locale/cs_CZ/translations.php +++ b/app/Locale/cs_CZ/translations.php @@ -102,7 +102,6 @@ return array( 'Open a task' => 'Otevřít úkol', 'Do you really want to open this task: "%s"?' => 'Opravdu chcete znovuotevřít tento úkol: "%s"?', 'Back to the board' => 'Zpět na nástěnku', - 'Created on %B %e, %Y at %k:%M %p' => 'Vytvořeno dne %d.%m.%Y v čase %H:%M', 'There is nobody assigned' => 'Není přiřazeno žádnému uživateli', 'Column on the board:' => 'Sloupec:', 'Close this task' => 'Uzavřít úkol', @@ -156,7 +155,6 @@ return array( 'Work in progress' => 'V řešení', 'Done' => 'Dokončeno', 'Application version:' => 'Verze:', - '%B %e, %Y at %k:%M %p' => '%d.%m.%Y v %H:%M', 'Id' => 'ID', '%d closed tasks' => '%d dokončených úkolů', 'No task for this project' => 'Tento projekt nemá žádné úkoly', @@ -180,8 +178,6 @@ return array( 'Edit this task' => 'Editace úkolu', 'Due Date' => 'Datum splnění', 'Invalid date' => 'Neplatné datum', - 'Must be done before %B %e, %Y' => 'Musí být dokončeno do %d.%m.%Y ', - '%B %e, %Y' => '%d.%m.%Y', 'Automatic actions' => 'Automaticky vykonávané akce', 'Your automatic action have been created successfully.' => 'Vaše akce byla úspěšně vytvořena.', 'Unable to create your automatic action.' => 'Vaší akci nebylo možné vytvořit.', @@ -317,7 +313,6 @@ return array( 'Unable to upload the file.' => 'Soubor nelze nahrát.', 'Display another project' => 'Zobrazit jiný projekt', 'Created by %s' => 'Vytvořeno uživatelem %s', - 'Last modified on %B %e, %Y at %k:%M %p' => 'Poslední úprava dne %d.%m.%Y v čase %H:%M', 'Tasks Export' => 'Export úkolů', 'Tasks exportation for "%s"' => 'Export úkolů pro "%s"', 'Start Date' => 'Počáteční datum', @@ -589,7 +584,6 @@ return array( 'This task' => 'Tento úkol', '<1h' => '<1h', '%dh' => '%dh', - // '%b %e' => '', 'Expand tasks' => 'Rozpbalit úkoly', 'Collapse tasks' => 'Sbalit úkoly', 'Expand/collapse tasks' => 'Rozbalit / sbalit úkoly', @@ -758,10 +752,6 @@ return array( 'Only for tasks assigned to me' => 'pouze pro moje úkoly', 'Only for tasks created by me' => 'pouze pro mnou vytvořené úkoly', 'Only for tasks created by me and assigned to me' => 'pouze pro mnou vytvořené a mě přiřazené úkoly', - // '%A' => '', - // '%b %e, %Y, %k:%M %p' => '', - 'New due date: %B %e, %Y' => 'Neues Ablaufdatum: %B %e, %Y', - 'Start date changed: %B %e, %Y' => 'Neues Beginndatum: %B %e, %Y', // '%%Y-%%m-%%d' => '', 'Total for all columns' => 'S', 'You need at least 2 days of data to show the chart.' => 'Potřebujete nejméně data ze dvou dnů pro zobrazení grafu', @@ -786,7 +776,6 @@ return array( 'Not assigned' => 'Nepřiřazené', 'View advanced search syntax' => 'Zobrazit syntaxi rozšířeného vyhledávání', 'Overview' => 'Přehled', - '%b %e %Y' => '%b %e %Y', 'Board/Calendar/List view' => 'Nástěnka/Kalendář/Zobrazení seznamu', 'Switch to the board view' => 'Přepnout na nástěnku', 'Switch to the calendar view' => 'Přepnout na kalendář', @@ -876,8 +865,6 @@ return array( // 'End date:' => '', // 'There is no start date or end date for this project.' => '', // 'Projects Gantt chart' => '', - // 'Start date: %s' => '', - // 'End date: %s' => '', // 'Link type' => '', // 'Change task color when using a specific task link' => '', // 'Task link creation or modification' => '', @@ -1125,4 +1112,10 @@ return array( // 'Moved:' => '', // 'Task #%d' => '', // 'Sub-tasks' => '', + // 'Date and time format' => '', + // 'Time format' => '', + // 'Start date: ' => '', + // 'End date: ' => '', + // 'New due date: ' => '', + // 'Start date changed: ' => '', ); diff --git a/app/Locale/da_DK/translations.php b/app/Locale/da_DK/translations.php index b61fcd4a..537b6349 100644 --- a/app/Locale/da_DK/translations.php +++ b/app/Locale/da_DK/translations.php @@ -102,7 +102,6 @@ return array( 'Open a task' => 'Åben en opgave', 'Do you really want to open this task: "%s"?' => 'Vil du virkelig åbne denne opgave: "%s"?', 'Back to the board' => 'Tilbage til boardet', - 'Created on %B %e, %Y at %k:%M %p' => 'Oprettet %d.%m.%Y - %H:%M', 'There is nobody assigned' => 'Der er ingen tilføjet', 'Column on the board:' => 'Kolonne:', 'Close this task' => 'Luk denne opgave', @@ -156,7 +155,6 @@ return array( 'Work in progress' => 'Igangværende', 'Done' => 'Færdig', 'Application version:' => 'Version:', - '%B %e, %Y at %k:%M %p' => '%d.%m.%Y - %H:%M', 'Id' => 'ID', '%d closed tasks' => '%d lukket opgavet', 'No task for this project' => 'Ingen opgaver i dette projekt', @@ -180,8 +178,6 @@ return array( 'Edit this task' => 'Rediger denne opgave', 'Due Date' => 'Forfaldsdato', 'Invalid date' => 'Ugyldig dato', - 'Must be done before %B %e, %Y' => 'Skal være fuldført inden %d.%m.%Y', - '%B %e, %Y' => '%d.%m.%Y', 'Automatic actions' => 'Automatiske handlinger', 'Your automatic action have been created successfully.' => 'Din automatiske handling er oprettet.', 'Unable to create your automatic action.' => 'Din automatiske handling kunne ikke oprettes.', @@ -317,7 +313,6 @@ return array( 'Unable to upload the file.' => 'Filen kunne ikke uploades.', 'Display another project' => 'Vis et andet projekt...', 'Created by %s' => 'Oprettet af %s', - 'Last modified on %B %e, %Y at %k:%M %p' => 'Sidst redigeret %d.%m.%Y - %H:%M', 'Tasks Export' => 'Opgave eksport', 'Tasks exportation for "%s"' => 'Opgave eksport for "%s"', 'Start Date' => 'Start-dato', @@ -589,7 +584,6 @@ return array( // 'This task' => '', // '<1h' => '', // '%dh' => '', - // '%b %e' => '', // 'Expand tasks' => '', // 'Collapse tasks' => '', // 'Expand/collapse tasks' => '', @@ -758,10 +752,6 @@ return array( // 'Only for tasks assigned to me' => '', // 'Only for tasks created by me' => '', // 'Only for tasks created by me and assigned to me' => '', - // '%A' => '', - // '%b %e, %Y, %k:%M %p' => '', - // 'New due date: %B %e, %Y' => '', - // 'Start date changed: %B %e, %Y' => '', // '%%Y-%%m-%%d' => '', // 'Total for all columns' => '', // 'You need at least 2 days of data to show the chart.' => '', @@ -786,7 +776,6 @@ return array( // 'Not assigned' => '', // 'View advanced search syntax' => '', // 'Overview' => '', - // '%b %e %Y' => '', // 'Board/Calendar/List view' => '', // 'Switch to the board view' => '', // 'Switch to the calendar view' => '', @@ -876,8 +865,6 @@ return array( // 'End date:' => '', // 'There is no start date or end date for this project.' => '', // 'Projects Gantt chart' => '', - // 'Start date: %s' => '', - // 'End date: %s' => '', // 'Link type' => '', // 'Change task color when using a specific task link' => '', // 'Task link creation or modification' => '', @@ -1125,4 +1112,10 @@ return array( // 'Moved:' => '', // 'Task #%d' => '', // 'Sub-tasks' => '', + // 'Date and time format' => '', + // 'Time format' => '', + // 'Start date: ' => '', + // 'End date: ' => '', + // 'New due date: ' => '', + // 'Start date changed: ' => '', ); diff --git a/app/Locale/de_DE/translations.php b/app/Locale/de_DE/translations.php index a15b96d7..ba6a1fd4 100644 --- a/app/Locale/de_DE/translations.php +++ b/app/Locale/de_DE/translations.php @@ -102,7 +102,6 @@ return array( 'Open a task' => 'Öffne eine Aufgabe', 'Do you really want to open this task: "%s"?' => 'Soll diese Aufgabe wirklich wieder geöffnet werden: "%s"?', 'Back to the board' => 'Zurück zur Pinnwand', - 'Created on %B %e, %Y at %k:%M %p' => 'Erstellt am %d.%m.%Y um %H:%M', 'There is nobody assigned' => 'Die Aufgabe wurde niemandem zugewiesen', 'Column on the board:' => 'Spalte:', 'Close this task' => 'Aufgabe schließen', @@ -156,7 +155,6 @@ return array( 'Work in progress' => 'In Arbeit', 'Done' => 'Erledigt', 'Application version:' => 'Version:', - '%B %e, %Y at %k:%M %p' => '%d.%m.%Y um %H:%M', 'Id' => 'ID', '%d closed tasks' => '%d abgeschlossene Aufgaben', 'No task for this project' => 'Keine Aufgaben in diesem Projekt', @@ -180,8 +178,6 @@ return array( 'Edit this task' => 'Aufgabe bearbeiten', 'Due Date' => 'Fällig am', 'Invalid date' => 'Ungültiges Datum', - 'Must be done before %B %e, %Y' => 'Muss vor dem %d.%m.%Y erledigt werden', - '%B %e, %Y' => '%d.%m.%Y', 'Automatic actions' => 'Automatische Aktionen', 'Your automatic action have been created successfully.' => 'Die automatische Aktion wurde erfolgreich erstellt.', 'Unable to create your automatic action.' => 'Erstellen der automatischen Aktion nicht möglich.', @@ -317,7 +313,6 @@ return array( 'Unable to upload the file.' => 'Hochladen der Datei nicht möglich.', 'Display another project' => 'Zu Projekt wechseln', 'Created by %s' => 'Erstellt durch %s', - 'Last modified on %B %e, %Y at %k:%M %p' => 'Letzte Änderung am %d.%m.%Y um %H:%M', 'Tasks Export' => 'Aufgaben exportieren', 'Tasks exportation for "%s"' => 'Aufgaben exportieren für "%s"', 'Start Date' => 'Anfangsdatum', @@ -589,7 +584,6 @@ return array( 'This task' => 'Diese Aufgabe', '<1h' => '<1Std', '%dh' => '%dStd', - // '%b %e' => '', 'Expand tasks' => 'Aufgaben aufklappen', 'Collapse tasks' => 'Aufgaben zusammenklappen', 'Expand/collapse tasks' => 'Aufgaben auf/zuklappen', @@ -758,10 +752,6 @@ return array( 'Only for tasks assigned to me' => 'nur mir zugeordnete Aufgane', 'Only for tasks created by me' => 'nur von mir erstellte Aufgaben', 'Only for tasks created by me and assigned to me' => 'nur mir zugeordnete und von mir erstellte Aufgaben', - '%A' => '%A', - '%b %e, %Y, %k:%M %p' => '%b %e, %Y, %k:%M %p', - 'New due date: %B %e, %Y' => 'Neues Ablaufdatum: %B %e, %Y', - 'Start date changed: %B %e, %Y' => 'Neues Beginndatum: %B %e, %Y', '%%Y-%%m-%%d' => '%%d.%%m.%%Y', 'Total for all columns' => 'Gesamt für alle Spalten', 'You need at least 2 days of data to show the chart.' => 'Es werden mindestens 2 Tage zur Darstellung benötigt', @@ -786,7 +776,6 @@ return array( 'Not assigned' => 'Nicht zugewiesen', 'View advanced search syntax' => 'Zur erweiterten Suchsyntax', 'Overview' => 'Überblick', - '%b %e %Y' => '%b %e %Y', 'Board/Calendar/List view' => 'Board-/Kalender-/Listen-Ansicht', 'Switch to the board view' => 'Zur Board-Ansicht', 'Switch to the calendar view' => 'Zur Kalender-Ansicht', @@ -876,8 +865,6 @@ return array( 'End date:' => 'Endedatum:', 'There is no start date or end date for this project.' => 'Es gibt kein Startdatum oder Endedatum für dieses Projekt', 'Projects Gantt chart' => 'Projekt Gantt Diagramm', - 'Start date: %s' => 'Beginndatum: %s', - 'End date: %s' => 'Enddatum: %s', 'Link type' => 'Verbindungstyp', 'Change task color when using a specific task link' => 'Aufgabefarbe ändern bei bestimmter Aufgabenverbindung', 'Task link creation or modification' => 'Aufgabenverbindung erstellen oder bearbeiten', @@ -1125,4 +1112,10 @@ return array( // 'Moved:' => '', // 'Task #%d' => '', // 'Sub-tasks' => '', + // 'Date and time format' => '', + // 'Time format' => '', + // 'Start date: ' => '', + // 'End date: ' => '', + // 'New due date: ' => '', + // 'Start date changed: ' => '', ); diff --git a/app/Locale/el_GR/translations.php b/app/Locale/el_GR/translations.php index 9fb1e0bf..01b31819 100644 --- a/app/Locale/el_GR/translations.php +++ b/app/Locale/el_GR/translations.php @@ -102,7 +102,6 @@ return array( 'Open a task' => 'Άνοιγμα εργασίας', 'Do you really want to open this task: "%s"?' => 'Άνοιγμα της εργασίας : « %s » ?', 'Back to the board' => 'Επιστροφή στον κεντρικό πίνακα έργου', - 'Created on %B %e, %Y at %k:%M %p' => 'Δημιουργήθηκε στις %d/%m/%Y και ώρα %H:%M', 'There is nobody assigned' => 'Δεν έχει ανατεθεί σε κανένα', 'Column on the board:' => 'Στήλη στον κεντρικό πίνακα : ', 'Close this task' => 'Κλείσιμο εργασίας', @@ -156,7 +155,6 @@ return array( 'Work in progress' => 'Σε πρόοδο', 'Done' => 'Ολοκληρωμένα', 'Application version:' => 'Version εφαρμογής :', - '%B %e, %Y at %k:%M %p' => '%d/%m/%Y και ώρα %H:%M', 'Id' => 'Αναγνωριστικό.', '%d closed tasks' => '%d κλειστές εργασίες', 'No task for this project' => 'Αριθμός εργασιών για το έργο', @@ -180,8 +178,6 @@ return array( 'Edit this task' => 'Διόρθωση εργασίας', 'Due Date' => 'Μέχρι την ημερομηνία', 'Invalid date' => 'Μη ορθή ημερομηνία', - 'Must be done before %B %e, %Y' => 'Πρέπει να ολοκληρωθεί πριν τις %d/%m/%Y', - '%B %e, %Y' => '%d %B %Y', 'Automatic actions' => 'Αυτόματες ενέργειες', 'Your automatic action have been created successfully.' => 'Η αυτόματη ενέργεια δημιουργήθηκε με επιτυχία.', 'Unable to create your automatic action.' => 'Impossible de créer votre action automatisée.', @@ -317,7 +313,6 @@ return array( 'Unable to upload the file.' => 'Δεν είναι δυνατή η μεταφόρτωση του αρχείου.', 'Display another project' => 'Εμφάνιση άλλου έργου', 'Created by %s' => 'Δημιουργήθηκε από %s', - 'Last modified on %B %e, %Y at %k:%M %p' => 'Τελευταία ενημέρωση: %d/%m/%Y à %H:%M', 'Tasks Export' => 'Εξαγωγή εργασιών', 'Tasks exportation for "%s"' => 'Εξαγωγή εργασιών για το έργο « %s »', 'Start Date' => 'Ημερομηνία έναρξης', @@ -589,7 +584,6 @@ return array( 'This task' => 'Αυτή η εργασία', '<1h' => '<1ωρ', '%dh' => '%dωρ', - '%b %e' => '%e %b', 'Expand tasks' => 'Ανάπτυξη εργασιών', 'Collapse tasks' => 'Σύμπτυξη εργασιών', 'Expand/collapse tasks' => 'Ανάπτυξη/σύμπτυξη εργασιών', @@ -758,10 +752,6 @@ return array( 'Only for tasks assigned to me' => 'Μόνο σε εργασίες που μου έχουν ανατεθεί', 'Only for tasks created by me' => 'Μόνο σε εργασίες που έχουν δημιουργηθεί από εμένα', 'Only for tasks created by me and assigned to me' => 'Μόνο σε εργασίες που έχουν δημιουργηθεί από εμένα και μου έχουν ανατεθεί', - '%A' => '%A', - '%b %e, %Y, %k:%M %p' => '%d/%m/%Y %H:%M', - 'New due date: %B %e, %Y' => 'Νέα ημέρα καθηκόντων : %d/%m/%Y', - 'Start date changed: %B %e, %Y' => 'Ημέρα εκκίνησης άλλαξε : %d/%m/%Y', '%%Y-%%m-%%d' => '%%d/%%m/%%Y', 'Total for all columns' => 'Σύνολο για όλες τις στήλες', 'You need at least 2 days of data to show the chart.' => 'Έχετε τουλάχιστον 2 ημέρες δεδομένων για να εμφανιστούν στο διάγραμμα.', @@ -786,7 +776,6 @@ return array( 'Not assigned' => 'Δεν έχουν εκχωρηθεί', 'View advanced search syntax' => 'Δείτε τη σύνταξη "αναζήτησης για προχωρημένους"', 'Overview' => 'Επισκόπηση', - '%b %e %Y' => '%b %e %Y', 'Board/Calendar/List view' => 'Πίνακας / Ημερολόγιο / Προβολή λίστας', 'Switch to the board view' => 'Εναλλαγή στην προβολή του πίνακα', 'Switch to the calendar view' => 'Εναλλαγή στην προβολή ημερολογίου', @@ -876,8 +865,6 @@ return array( 'End date:' => 'Ημερομηνία λήξης :', 'There is no start date or end date for this project.' => 'Δεν υπάρχει ημερομηνία έναρξης ή λήξης για το έργο αυτό.', 'Projects Gantt chart' => 'Διάγραμμα Gantt έργων', - 'Start date: %s' => 'Ημερομηνία αρχής : %s', - 'End date: %s' => 'Ημερομηνία τέλους : %s', 'Link type' => 'Τύπος συνδέσμου', 'Change task color when using a specific task link' => 'Αλλαγή χρώματος εργασίας χρησιμοποιώντας συγκεκριμένο σύνδεσμο εργασίας', 'Task link creation or modification' => 'Σύνδεσμος δημιουργίας ή τροποποίησης εργασίας', @@ -1125,4 +1112,10 @@ return array( // 'Moved:' => '', // 'Task #%d' => '', // 'Sub-tasks' => '', + // 'Date and time format' => '', + // 'Time format' => '', + // 'Start date: ' => '', + // 'End date: ' => '', + // 'New due date: ' => '', + // 'Start date changed: ' => '', ); diff --git a/app/Locale/es_ES/translations.php b/app/Locale/es_ES/translations.php index e2bdd0da..9c7c6e41 100644 --- a/app/Locale/es_ES/translations.php +++ b/app/Locale/es_ES/translations.php @@ -102,7 +102,6 @@ return array( 'Open a task' => 'Abrir una tarea', 'Do you really want to open this task: "%s"?' => '¿Realmente desea abrir esta tarea: « %s » ?', 'Back to the board' => 'Volver al tablero', - 'Created on %B %e, %Y at %k:%M %p' => 'Creado el %e de %B de %Y a las %k:%M %p', 'There is nobody assigned' => 'No hay nadie asignado a esta tarea', 'Column on the board:' => 'Columna en el tablero: ', 'Close this task' => 'Cerrar esta tarea', @@ -156,7 +155,6 @@ return array( 'Work in progress' => 'En curso', 'Done' => 'Hecho', 'Application version:' => 'Versión de la aplicación:', - '%B %e, %Y at %k:%M %p' => '%e de %B de %Y a las %k:%M %p', 'Id' => 'Identificador', '%d closed tasks' => '%d tareas completadas', 'No task for this project' => 'Ninguna tarea para este proyecto', @@ -180,8 +178,6 @@ return array( 'Edit this task' => 'Editar esta tarea', 'Due Date' => 'Fecha límite', 'Invalid date' => 'Fecha no válida', - 'Must be done before %B %e, %Y' => 'Debe de estar hecho antes del %e de %B de %Y', - '%B %e, %Y' => '%e de %B de %Y', 'Automatic actions' => 'Acciones automatizadas', 'Your automatic action have been created successfully.' => 'La acción automatizada ha sido creada correctamente.', 'Unable to create your automatic action.' => 'No se puede crear esta acción automatizada.', @@ -317,7 +313,6 @@ return array( 'Unable to upload the file.' => 'No pude cargar el fichero.', 'Display another project' => 'Mostrar otro proyecto', 'Created by %s' => 'Creado por %s', - 'Last modified on %B %e, %Y at %k:%M %p' => 'Última modificación %e de %B de %Y a las %k:%M %p', 'Tasks Export' => 'Exportar tareas', 'Tasks exportation for "%s"' => 'Exportación de tareas para "%s"', 'Start Date' => 'Fecha de inicio', @@ -589,7 +584,6 @@ return array( 'This task' => 'Esta tarea', '<1h' => '<1h', '%dh' => '%dh', - '%b %e' => '%e de %b', 'Expand tasks' => 'Espande tareas', 'Collapse tasks' => 'Colapsa tareas', 'Expand/collapse tasks' => 'Expande/colapasa tareas', @@ -758,10 +752,6 @@ return array( 'Only for tasks assigned to me' => 'Sólo para las tareas que me han sido asignadas', 'Only for tasks created by me' => 'Sólo para las taread creadas por mí', 'Only for tasks created by me and assigned to me' => 'Sólo para las tareas credas por mí y que me han sido asignadas', - '%A' => '%A', - '%b %e, %Y, %k:%M %p' => '%e de %B de %Y a las %k:%M %p', - 'New due date: %B %e, %Y' => 'Nueva fecha de entrega: %B %e, %Y', - 'Start date changed: %B %e, %Y' => 'Cambiadad fecha de inicio: %B %e, %Y', '%%Y-%%m-%%d' => '%%d/%%M/%%Y', 'Total for all columns' => 'Total para todas las columnas', 'You need at least 2 days of data to show the chart.' => 'Necesitas al menos 2 días de datos para mostrar el gráfico.', @@ -786,7 +776,6 @@ return array( 'Not assigned' => 'No asignada', 'View advanced search syntax' => 'Ver sintáxis avanzada de búsqueda', 'Overview' => 'Resumen', - '%b %e %Y' => '%e de %B de %Y', 'Board/Calendar/List view' => 'Vista de Tablero/Calendario/Lista', 'Switch to the board view' => 'Cambiar a vista de tablero', 'Switch to the calendar view' => 'Cambiar a vista de calendario', @@ -876,8 +865,6 @@ return array( 'End date:' => 'Fecha final', 'There is no start date or end date for this project.' => 'No existe fecha de inicio o de fin para este proyecto.', 'Projects Gantt chart' => 'Diagramas de Gantt de los proyectos', - 'Start date: %s' => 'Fecha inicial: %s', - 'End date: %s' => 'Fecha final: %s', 'Link type' => 'Tipo de enlace', 'Change task color when using a specific task link' => 'Cambiar colo de la tarea al usar un enlace específico a tarea', 'Task link creation or modification' => 'Creación o modificación de enlace a tarea', @@ -1125,4 +1112,10 @@ return array( // 'Moved:' => '', // 'Task #%d' => '', // 'Sub-tasks' => '', + // 'Date and time format' => '', + // 'Time format' => '', + // 'Start date: ' => '', + // 'End date: ' => '', + // 'New due date: ' => '', + // 'Start date changed: ' => '', ); diff --git a/app/Locale/fi_FI/translations.php b/app/Locale/fi_FI/translations.php index f8b846bb..d50765b6 100644 --- a/app/Locale/fi_FI/translations.php +++ b/app/Locale/fi_FI/translations.php @@ -102,7 +102,6 @@ return array( 'Open a task' => 'Avaa tehtävä', 'Do you really want to open this task: "%s"?' => 'Haluatko varmasti avata tehtävän: "%s"?', 'Back to the board' => 'Takaisin tauluun', - 'Created on %B %e, %Y at %k:%M %p' => 'Luotu %d.%m.%Y kello %H:%M', 'There is nobody assigned' => 'Ei suorittajaa', 'Column on the board:' => 'Sarake taululla: ', 'Close this task' => 'Sulje tämä tehtävä', @@ -156,7 +155,6 @@ return array( 'Work in progress' => 'Työnalla', 'Done' => 'Tehty', 'Application version:' => 'Ohjelman versio:', - '%B %e, %Y at %k:%M %p' => '%d.%m.%Y kello %H:%M', 'Id' => 'Id', '%d closed tasks' => '%d suljettua tehtävää', 'No task for this project' => 'Ei tehtävää tälle projektille', @@ -180,8 +178,6 @@ return array( 'Edit this task' => 'Muokkaa tehtävää', 'Due Date' => 'Deadline', 'Invalid date' => 'Virheellinen päiväys', - 'Must be done before %B %e, %Y' => 'Täytyy suorittaa ennen %d.%m.%Y', - '%B %e, %Y' => '%d.%m.%Y', 'Automatic actions' => 'Automaattiset toiminnot', 'Your automatic action have been created successfully.' => 'Toiminto suoritettiin onnistuneesti.', 'Unable to create your automatic action.' => 'Automaattisen toiminnon luominen epäonnistui.', @@ -317,7 +313,6 @@ return array( 'Unable to upload the file.' => 'Tiedoston lataus epäonnistui.', 'Display another project' => 'Näytä toinen projekti', 'Created by %s' => 'Luonut: %s', - 'Last modified on %B %e, %Y at %k:%M %p' => 'Viimeksi muokattu %B %e, %Y kello %H:%M', 'Tasks Export' => 'Tehtävien vienti', 'Tasks exportation for "%s"' => 'Tehtävien vienti projektilta "%s"', 'Start Date' => 'Aloituspäivä', @@ -589,7 +584,6 @@ return array( // 'This task' => '', // '<1h' => '', // '%dh' => '', - // '%b %e' => '', // 'Expand tasks' => '', // 'Collapse tasks' => '', // 'Expand/collapse tasks' => '', @@ -758,10 +752,6 @@ return array( // 'Only for tasks assigned to me' => '', // 'Only for tasks created by me' => '', // 'Only for tasks created by me and assigned to me' => '', - // '%A' => '', - // '%b %e, %Y, %k:%M %p' => '', - // 'New due date: %B %e, %Y' => '', - // 'Start date changed: %B %e, %Y' => '', // '%%Y-%%m-%%d' => '', // 'Total for all columns' => '', // 'You need at least 2 days of data to show the chart.' => '', @@ -786,7 +776,6 @@ return array( // 'Not assigned' => '', // 'View advanced search syntax' => '', // 'Overview' => '', - // '%b %e %Y' => '', // 'Board/Calendar/List view' => '', // 'Switch to the board view' => '', // 'Switch to the calendar view' => '', @@ -876,8 +865,6 @@ return array( // 'End date:' => '', // 'There is no start date or end date for this project.' => '', // 'Projects Gantt chart' => '', - // 'Start date: %s' => '', - // 'End date: %s' => '', // 'Link type' => '', // 'Change task color when using a specific task link' => '', // 'Task link creation or modification' => '', @@ -1125,4 +1112,10 @@ return array( // 'Moved:' => '', // 'Task #%d' => '', // 'Sub-tasks' => '', + // 'Date and time format' => '', + // 'Time format' => '', + // 'Start date: ' => '', + // 'End date: ' => '', + // 'New due date: ' => '', + // 'Start date changed: ' => '', ); diff --git a/app/Locale/fr_FR/translations.php b/app/Locale/fr_FR/translations.php index 1beef993..90d6dc3d 100644 --- a/app/Locale/fr_FR/translations.php +++ b/app/Locale/fr_FR/translations.php @@ -102,7 +102,6 @@ return array( 'Open a task' => 'Ouvrir une tâche', 'Do you really want to open this task: "%s"?' => 'Voulez-vous vraiment ouvrir cette tâche : « %s » ?', 'Back to the board' => 'Retour au tableau', - 'Created on %B %e, %Y at %k:%M %p' => 'Créé le %d/%m/%Y à %H:%M', 'There is nobody assigned' => 'Il n\'y a personne d\'assigné à cette tâche', 'Column on the board:' => 'Colonne sur le tableau : ', 'Close this task' => 'Fermer cette tâche', @@ -156,7 +155,6 @@ return array( 'Work in progress' => 'En cours', 'Done' => 'Terminé', 'Application version:' => 'Version de l\'application :', - '%B %e, %Y at %k:%M %p' => '%d/%m/%Y à %H:%M', 'Id' => 'Id.', '%d closed tasks' => '%d tâches terminées', 'No task for this project' => 'Aucune tâche pour ce projet', @@ -180,8 +178,6 @@ return array( 'Edit this task' => 'Modifier cette tâche', 'Due Date' => 'Date d\'échéance', 'Invalid date' => 'Date invalide', - 'Must be done before %B %e, %Y' => 'Doit être fait avant le %d/%m/%Y', - '%B %e, %Y' => '%d %B %Y', 'Automatic actions' => 'Actions automatisées', 'Your automatic action have been created successfully.' => 'Votre action automatisée a été ajoutée avec succès.', 'Unable to create your automatic action.' => 'Impossible de créer votre action automatisée.', @@ -317,7 +313,6 @@ return array( 'Unable to upload the file.' => 'Impossible de transférer le fichier.', 'Display another project' => 'Afficher un autre projet', 'Created by %s' => 'Créé par %s', - 'Last modified on %B %e, %Y at %k:%M %p' => 'Modifié le %d/%m/%Y à %H:%M', 'Tasks Export' => 'Exportation des tâches', 'Tasks exportation for "%s"' => 'Exportation des tâches pour « %s »', 'Start Date' => 'Date de début', @@ -591,7 +586,6 @@ return array( 'This task' => 'Cette tâche', '<1h' => '<1h', '%dh' => '%dh', - '%b %e' => '%e %b', 'Expand tasks' => 'Déplier les tâches', 'Collapse tasks' => 'Replier les tâches', 'Expand/collapse tasks' => 'Plier/déplier les tâches', @@ -760,10 +754,6 @@ return array( 'Only for tasks assigned to me' => 'Seulement les tâches qui me sont assignées', 'Only for tasks created by me' => 'Seulement les tâches que j\'ai créées', 'Only for tasks created by me and assigned to me' => 'Seulement les tâches créées par moi-même et celles qui me sont assignées', - '%A' => '%A', - '%b %e, %Y, %k:%M %p' => '%d/%m/%Y %H:%M', - 'New due date: %B %e, %Y' => 'Nouvelle date d\'échéance : %d/%m/%Y', - 'Start date changed: %B %e, %Y' => 'Date de début modifiée : %d/%m/%Y', '%%Y-%%m-%%d' => '%%d/%%m/%%Y', 'Total for all columns' => 'Total pour toutes les colonnes', 'You need at least 2 days of data to show the chart.' => 'Vous avez besoin d\'au minimum 2 jours de données pour afficher le graphique.', @@ -788,7 +778,6 @@ return array( 'Not assigned' => 'Non assignées', 'View advanced search syntax' => 'Voir la syntaxe pour la recherche avancée', 'Overview' => 'Vue d\'ensemble', - '%b %e %Y' => '%b %e %Y', 'Board/Calendar/List view' => 'Vue Tableau/Calendrier/Liste', 'Switch to the board view' => 'Basculer vers le tableau', 'Switch to the calendar view' => 'Basculer vers le calendrier', @@ -878,8 +867,6 @@ return array( 'End date:' => 'Date de fin :', 'There is no start date or end date for this project.' => 'Il n\'y a pas de date de début ou de date de fin pour ce projet.', 'Projects Gantt chart' => 'Diagramme de Gantt des projets', - 'Start date: %s' => 'Date de début : %s', - 'End date: %s' => 'Date de fin : %s', 'Link type' => 'Type de lien', 'Change task color when using a specific task link' => 'Changer la couleur de la tâche lorsqu\'un lien spécifique est utilisé', 'Task link creation or modification' => 'Création ou modification d\'un lien sur une tâche', @@ -1128,4 +1115,10 @@ return array( 'Moved:' => 'Déplacé le : ', 'Task #%d' => 'Tâche n°%d', 'Sub-tasks' => 'Sous-tâches', + 'Date and time format' => 'Format de la date et de l\'heure', + 'Time format' => 'Format de l\'heure', + 'Start date: ' => 'Date de début : ', + 'End date: ' => 'Date de fin : ', + 'New due date: ' => 'Nouvelle date d\'échéance : ', + 'Start date changed: ' => 'Date de début modifiée : ', ); diff --git a/app/Locale/hu_HU/translations.php b/app/Locale/hu_HU/translations.php index f9113270..447dafc9 100644 --- a/app/Locale/hu_HU/translations.php +++ b/app/Locale/hu_HU/translations.php @@ -102,7 +102,6 @@ return array( 'Open a task' => 'Feladat felnyitás', 'Do you really want to open this task: "%s"?' => 'Tényleg meg akarja nyitni ezt a feladatot: "%s"?', 'Back to the board' => 'Vissza a táblához', - 'Created on %B %e, %Y at %k:%M %p' => 'Létrehozva: %Y. %m. %d. %H:%M', 'There is nobody assigned' => 'Nincs felelős', 'Column on the board:' => 'Tábla oszlopa: ', 'Close this task' => 'Feladat lezárása', @@ -156,7 +155,6 @@ return array( 'Work in progress' => 'Folyamatban', 'Done' => 'Kész', 'Application version:' => 'Alkalmazás verzió:', - '%B %e, %Y at %k:%M %p' => '%Y. %m. %d. %H:%M', 'Id' => 'ID', '%d closed tasks' => '%d lezárt feladat', 'No task for this project' => 'Nincs feladat ebben a projektben', @@ -180,8 +178,6 @@ return array( 'Edit this task' => 'Feladat módosítása', 'Due Date' => 'Határidő', 'Invalid date' => 'Érvénytelen dátum', - 'Must be done before %B %e, %Y' => 'Kész kell lennie %Y. %m. %d. előtt', - '%B %e, %Y' => '%Y. %m. %d.', 'Automatic actions' => 'Automatikus intézkedések', 'Your automatic action have been created successfully.' => 'Az automatikus intézkedés sikeresen elkészült.', 'Unable to create your automatic action.' => 'Automatikus intézkedés létrehozása nem lehetséges.', @@ -317,7 +313,6 @@ return array( 'Unable to upload the file.' => 'Fájl feltöltése nem lehetséges.', 'Display another project' => 'Másik projekt megjelenítése', 'Created by %s' => 'Készítette: %s', - 'Last modified on %B %e, %Y at %k:%M %p' => 'Utolsó módosítás: %Y. %m. %d. %H:%M', 'Tasks Export' => 'Feladatok exportálása', 'Tasks exportation for "%s"' => 'Feladatok exportálása: "%s"', 'Start Date' => 'Kezdés dátuma', @@ -589,7 +584,6 @@ return array( 'This task' => 'Ez a feladat', '<1h' => '<1ó', '%dh' => '%dó', - '%b %e' => '%b %e', 'Expand tasks' => 'Feladatok lenyitása', 'Collapse tasks' => 'Feladatok összecsukása', 'Expand/collapse tasks' => 'Feladatok lenyitása/összecsukása', @@ -758,10 +752,6 @@ return array( // 'Only for tasks assigned to me' => '', // 'Only for tasks created by me' => '', // 'Only for tasks created by me and assigned to me' => '', - // '%A' => '', - // '%b %e, %Y, %k:%M %p' => '', - // 'New due date: %B %e, %Y' => '', - // 'Start date changed: %B %e, %Y' => '', // '%%Y-%%m-%%d' => '', // 'Total for all columns' => '', // 'You need at least 2 days of data to show the chart.' => '', @@ -786,7 +776,6 @@ return array( // 'Not assigned' => '', // 'View advanced search syntax' => '', // 'Overview' => '', - // '%b %e %Y' => '', // 'Board/Calendar/List view' => '', // 'Switch to the board view' => '', // 'Switch to the calendar view' => '', @@ -876,8 +865,6 @@ return array( // 'End date:' => '', // 'There is no start date or end date for this project.' => '', // 'Projects Gantt chart' => '', - // 'Start date: %s' => '', - // 'End date: %s' => '', // 'Link type' => '', // 'Change task color when using a specific task link' => '', // 'Task link creation or modification' => '', @@ -1125,4 +1112,10 @@ return array( // 'Moved:' => '', // 'Task #%d' => '', // 'Sub-tasks' => '', + // 'Date and time format' => '', + // 'Time format' => '', + // 'Start date: ' => '', + // 'End date: ' => '', + // 'New due date: ' => '', + // 'Start date changed: ' => '', ); diff --git a/app/Locale/id_ID/translations.php b/app/Locale/id_ID/translations.php index 0bbdd686..8c6db849 100644 --- a/app/Locale/id_ID/translations.php +++ b/app/Locale/id_ID/translations.php @@ -102,7 +102,6 @@ return array( 'Open a task' => 'Buka tugas', 'Do you really want to open this task: "%s"?' => 'Apakah anda yakin akan membuka tugas ini : « %s » ?', 'Back to the board' => 'Kembali ke papan', - 'Created on %B %e, %Y at %k:%M %p' => 'Dibuat pada tanggal %d/%m/%Y à %H:%M', 'There is nobody assigned' => 'Tidak ada orang yand ditugaskan', 'Column on the board:' => 'Kolom di dalam papan : ', 'Close this task' => 'Tutup tugas ini', @@ -156,7 +155,6 @@ return array( 'Work in progress' => 'Sedang dalam pengerjaan', 'Done' => 'Selesai', 'Application version:' => 'Versi aplikasi :', - '%B %e, %Y at %k:%M %p' => '%d/%m/%Y à %H:%M', 'Id' => 'Id.', '%d closed tasks' => '%d tugas yang ditutup', 'No task for this project' => 'Tidak ada tugas dalam proyek ini', @@ -180,8 +178,6 @@ return array( 'Edit this task' => 'Modifikasi tugas ini', 'Due Date' => 'Batas Tanggal Terakhir', 'Invalid date' => 'Tanggal tidak valid', - 'Must be done before %B %e, %Y' => 'Harus diselesaikan sebelum tanggal %d/%m/%Y', - '%B %e, %Y' => '%d %B %Y', 'Automatic actions' => 'Tindakan otomatis', 'Your automatic action have been created successfully.' => 'Tindakan otomatis anda berhasil dibuat.', 'Unable to create your automatic action.' => 'Tidak dapat membuat tindakan otomatis anda.', @@ -317,7 +313,6 @@ return array( 'Unable to upload the file.' => 'Tidak dapat mengunggah berkas.', 'Display another project' => 'Lihat proyek lain', 'Created by %s' => 'Dibuat oleh %s', - 'Last modified on %B %e, %Y at %k:%M %p' => 'Modifikasi terakhir pada tanggal %d/%m/%Y à %H:%M', 'Tasks Export' => 'Ekspor Tugas', 'Tasks exportation for "%s"' => 'Tugas di ekspor untuk « %s »', 'Start Date' => 'Tanggal Mulai', @@ -589,7 +584,6 @@ return array( 'This task' => 'Tugas ini', '<1h' => '<1h', '%dh' => '%dh', - '%b %e' => '%e %b', 'Expand tasks' => 'Perluas tugas', 'Collapse tasks' => 'Lipat tugas', 'Expand/collapse tasks' => 'Perluas/lipat tugas', @@ -758,10 +752,6 @@ return array( 'Only for tasks assigned to me' => 'Hanya untuk tugas yang ditugaskan ke saya', 'Only for tasks created by me' => 'Hanya untuk tugas yang dibuat oleh saya', 'Only for tasks created by me and assigned to me' => 'Hanya untuk tugas yang dibuat oleh saya dan ditugaskan ke saya', - '%A' => '%A', - '%b %e, %Y, %k:%M %p' => '%d/%m/%Y %H:%M', - 'New due date: %B %e, %Y' => 'Tanggal jatuh tempo baru : %d/%m/%Y', - 'Start date changed: %B %e, %Y' => 'Tanggal mulai berubah : %d/%m/%Y', '%%Y-%%m-%%d' => '%%d/%%m/%%Y', 'Total for all columns' => 'Total untuk semua kolom', 'You need at least 2 days of data to show the chart.' => 'Anda memerlukan setidaknya 2 hari dari data yang menunjukkan grafik.', @@ -786,7 +776,6 @@ return array( 'Not assigned' => 'Tidak ditugaskan', 'View advanced search syntax' => 'Lihat sintaks pencarian lanjutan', 'Overview' => 'Ikhtisar', - '%b %e %Y' => '%b %e %Y', 'Board/Calendar/List view' => 'Tampilan Papan/Kalender/Daftar', 'Switch to the board view' => 'Beralih ke tampilan papan', 'Switch to the calendar view' => 'Beralih ke tampilan kalender', @@ -876,8 +865,6 @@ return array( 'End date:' => 'Waktu berakhir :', 'There is no start date or end date for this project.' => 'Tidak ada waktu mulai atau waktu berakhir untuk proyek ini', 'Projects Gantt chart' => 'Proyek grafik Gantt', - 'Start date: %s' => 'Waktu mulai : %s', - 'End date: %s' => 'Waktu berakhir : %s', 'Link type' => 'Tipe tautan', 'Change task color when using a specific task link' => 'Rubah warna tugas ketika menggunakan tautan tugas yang spesifik', 'Task link creation or modification' => 'Tautan pembuatan atau modifikasi tugas ', @@ -1125,4 +1112,10 @@ return array( // 'Moved:' => '', // 'Task #%d' => '', // 'Sub-tasks' => '', + // 'Date and time format' => '', + // 'Time format' => '', + // 'Start date: ' => '', + // 'End date: ' => '', + // 'New due date: ' => '', + // 'Start date changed: ' => '', ); diff --git a/app/Locale/it_IT/translations.php b/app/Locale/it_IT/translations.php index 3957319e..661e6c86 100644 --- a/app/Locale/it_IT/translations.php +++ b/app/Locale/it_IT/translations.php @@ -102,7 +102,6 @@ return array( 'Open a task' => 'Apri un task', 'Do you really want to open this task: "%s"?' => 'Veramente desideri aprire questo task: "%s" ?', 'Back to the board' => 'Torna alla bacheca', - 'Created on %B %e, %Y at %k:%M %p' => 'Creato il %B %e, %Y alle %k:%M %p', 'There is nobody assigned' => 'Nessuno è assegnato a questo task', 'Column on the board:' => 'Colonna sulla bacheca: ', 'Close this task' => 'Chiudi questo task', @@ -156,7 +155,6 @@ return array( 'Work in progress' => 'In corso', 'Done' => 'Fatto', 'Application version:' => 'Versione dell\'applicazione:', - // '%B %e, %Y at %k:%M %p' => '', // 'Id' => '', '%d closed tasks' => '%d task chiusi', 'No task for this project' => 'Nessun task per questo progetto', @@ -180,8 +178,6 @@ return array( 'Edit this task' => 'Modifica questo task', 'Due Date' => 'Data di scadenza', 'Invalid date' => 'Data non valida', - 'Must be done before %B %e, %Y' => 'Deve essere completato prima del %B %e, %Y', - // '%B %e, %Y' => '', 'Automatic actions' => 'Azioni automatiche', 'Your automatic action have been created successfully.' => 'l\'azione automatica è stata creata correttamente.', 'Unable to create your automatic action.' => 'Impossibile creare quest\'azione automatica.', @@ -317,7 +313,6 @@ return array( 'Unable to upload the file.' => 'Impossibile caricare il file.', 'Display another project' => 'Mostra un altro progetto', 'Created by %s' => 'Creato da %s', - 'Last modified on %B %e, %Y at %k:%M %p' => 'Ultima modifica il %d/%m/%Y alle %H:%M', 'Tasks Export' => 'Export dei task', 'Tasks exportation for "%s"' => 'Export dei task per "%s"', 'Start Date' => 'Data d\'inizio', @@ -589,7 +584,6 @@ return array( 'This task' => 'Questo task', // '<1h' => '', // '%dh' => '', - // '%b %e' => '', 'Expand tasks' => 'Espandi i task', 'Collapse tasks' => 'Minimizza i task', 'Expand/collapse tasks' => 'Espandi/minimizza i task', @@ -758,10 +752,6 @@ return array( 'Only for tasks assigned to me' => 'Solo per i task assegnati a me', 'Only for tasks created by me' => 'Solo per i task creati da me', 'Only for tasks created by me and assigned to me' => 'Solo per i task creati da me e assegnati a me', - // '%A' => '', - // '%b %e, %Y, %k:%M %p' => '', - 'New due date: %B %e, %Y' => 'Nuova data di scadenza: %B %e, %Y', - 'Start date changed: %B %e, %Y' => 'Data di inizio cambiata: %B %e, %Y', // '%%Y-%%m-%%d' => '', 'Total for all columns' => 'Totale per tutte le colonne', 'You need at least 2 days of data to show the chart.' => 'Hai bisogno di almeno 2 giorni di dati per mostrare il grafico.', @@ -786,7 +776,6 @@ return array( 'Not assigned' => 'Non assegnato', 'View advanced search syntax' => 'Visualizza la sintassi di ricerca avanzata', 'Overview' => 'Panoramica', - // '%b %e %Y' => '', // 'Board/Calendar/List view' => '', 'Switch to the board view' => 'Passa alla vista "bacheca"', 'Switch to the calendar view' => 'Passa alla vista "calendario"', @@ -876,8 +865,6 @@ return array( 'End date:' => 'Data di fine:', 'There is no start date or end date for this project.' => 'Non è prevista una data di inzio o fine per questo progetto.', 'Projects Gantt chart' => 'Grafico Gantt dei progetti', - 'Start date: %s' => 'Data di inizio: %s', - 'End date: %s' => 'Data di fine: %s', 'Link type' => 'Tipo di link', 'Change task color when using a specific task link' => 'Cambia colore del task quando si un utilizza una determinata relazione di task', 'Task link creation or modification' => 'Creazione o modifica di relazione di task', @@ -1125,4 +1112,10 @@ return array( // 'Moved:' => '', // 'Task #%d' => '', // 'Sub-tasks' => '', + // 'Date and time format' => '', + // 'Time format' => '', + // 'Start date: ' => '', + // 'End date: ' => '', + // 'New due date: ' => '', + // 'Start date changed: ' => '', ); diff --git a/app/Locale/ja_JP/translations.php b/app/Locale/ja_JP/translations.php index 4ecf039b..3609836b 100644 --- a/app/Locale/ja_JP/translations.php +++ b/app/Locale/ja_JP/translations.php @@ -102,7 +102,6 @@ return array( 'Open a task' => 'タスクをオープンする', 'Do you really want to open this task: "%s"?' => 'タスク「%s」をオープンしますか?', 'Back to the board' => 'ボードに戻る', - 'Created on %B %e, %Y at %k:%M %p' => '%Y/%m/%d %H:%M に作成', 'There is nobody assigned' => '担当者がいません', 'Column on the board:' => 'カラム: ', 'Close this task' => 'タスクをクローズする', @@ -156,7 +155,6 @@ return array( 'Work in progress' => 'Work in progress', 'Done' => 'Done', 'Application version:' => 'アプリケーションのバージョン:', - '%B %e, %Y at %k:%M %p' => '%Y/%m/%d %H:%M', 'Id' => 'ID', '%d closed tasks' => '%d 個のクローズしたタスク', 'No task for this project' => 'このプロジェクトにタスクがありません', @@ -180,8 +178,6 @@ return array( 'Edit this task' => 'タスクを変更する', 'Due Date' => '期限', 'Invalid date' => '日付が無効です', - 'Must be done before %B %e, %Y' => '%Y/%m/%d までに完了', - '%B %e, %Y' => '%Y %B %e', 'Automatic actions' => '自動アクションを管理する', 'Your automatic action have been created successfully.' => '自動アクションを作成しました。', 'Unable to create your automatic action.' => '自動アクションの作成に失敗しました。', @@ -317,7 +313,6 @@ return array( 'Unable to upload the file.' => 'ファイルのアップロードに失敗しました。', 'Display another project' => '別のプロジェクトを表示', 'Created by %s' => '%s が作成', - 'Last modified on %B %e, %Y at %k:%M %p' => ' %Y/%m/%d %H:%M に変更', 'Tasks Export' => 'タスクの出力', 'Tasks exportation for "%s"' => '「%s」のタスク出力', 'Start Date' => '開始日', @@ -589,7 +584,6 @@ return array( 'This task' => 'このタスクは', '<1h' => '<1時間', '%dh' => '%d 時間', - '%b %e' => '%b/%e', 'Expand tasks' => 'タスクを展開する', 'Collapse tasks' => 'タスクを閉じる', 'Expand/collapse tasks' => 'タスクの展開/閉じる', @@ -758,10 +752,6 @@ return array( // 'Only for tasks assigned to me' => '', // 'Only for tasks created by me' => '', // 'Only for tasks created by me and assigned to me' => '', - // '%A' => '', - // '%b %e, %Y, %k:%M %p' => '', - // 'New due date: %B %e, %Y' => '', - // 'Start date changed: %B %e, %Y' => '', // '%%Y-%%m-%%d' => '', // 'Total for all columns' => '', // 'You need at least 2 days of data to show the chart.' => '', @@ -786,7 +776,6 @@ return array( // 'Not assigned' => '', // 'View advanced search syntax' => '', // 'Overview' => '', - // '%b %e %Y' => '', // 'Board/Calendar/List view' => '', // 'Switch to the board view' => '', // 'Switch to the calendar view' => '', @@ -876,8 +865,6 @@ return array( // 'End date:' => '', // 'There is no start date or end date for this project.' => '', // 'Projects Gantt chart' => '', - // 'Start date: %s' => '', - // 'End date: %s' => '', // 'Link type' => '', // 'Change task color when using a specific task link' => '', // 'Task link creation or modification' => '', @@ -1125,4 +1112,10 @@ return array( // 'Moved:' => '', // 'Task #%d' => '', // 'Sub-tasks' => '', + // 'Date and time format' => '', + // 'Time format' => '', + // 'Start date: ' => '', + // 'End date: ' => '', + // 'New due date: ' => '', + // 'Start date changed: ' => '', ); diff --git a/app/Locale/my_MY/translations.php b/app/Locale/my_MY/translations.php index 36b15495..5b1da7cd 100644 --- a/app/Locale/my_MY/translations.php +++ b/app/Locale/my_MY/translations.php @@ -102,7 +102,6 @@ return array( 'Open a task' => 'Buka tugas', 'Do you really want to open this task: "%s"?' => 'Anda yakin untuk buka tugas ini : « %s » ?', 'Back to the board' => 'Kembali ke papan', - 'Created on %B %e, %Y at %k:%M %p' => 'Dicipta pada tanggal %d/%m/%Y à %H:%M', 'There is nobody assigned' => 'Tidak ada orang yand ditugaskan', 'Column on the board:' => 'Kolom di dalam papan : ', 'Close this task' => 'Tutup tugas ini', @@ -156,7 +155,6 @@ return array( 'Work in progress' => 'Sedang dalam pengerjaan', 'Done' => 'Selesai', 'Application version:' => 'Versi aplikasi :', - '%B %e, %Y at %k:%M %p' => '%d/%m/%Y à %H:%M', 'Id' => 'Id.', '%d closed tasks' => '%d tugas yang ditutup', 'No task for this project' => 'Tidak ada tugas dalam projek ini', @@ -180,8 +178,6 @@ return array( 'Edit this task' => 'Modifikasi tugas ini', 'Due Date' => 'Batas Tanggal Terakhir', 'Invalid date' => 'Tanggal tidak valid', - 'Must be done before %B %e, %Y' => 'Harus diselesaikan sebelum tanggal %d/%m/%Y', - '%B %e, %Y' => '%d %B %Y', 'Automatic actions' => 'Tindakan otomatis', 'Your automatic action have been created successfully.' => 'Tindakan otomatis anda berhasil dibuat.', 'Unable to create your automatic action.' => 'Tidak dapat membuat tindakan otomatis anda.', @@ -317,7 +313,6 @@ return array( 'Unable to upload the file.' => 'Tidak dapat mengunggah berkas.', 'Display another project' => 'Lihat projek lain', 'Created by %s' => 'Dibuat oleh %s', - 'Last modified on %B %e, %Y at %k:%M %p' => 'Modifikasi terakhir pada tanggal %d/%m/%Y à %H:%M', 'Tasks Export' => 'Ekspor Tugas', 'Tasks exportation for "%s"' => 'Tugas di ekspor untuk « %s »', 'Start Date' => 'Tanggal Mulai', @@ -589,7 +584,6 @@ return array( 'This task' => 'Tugas ini', '<1h' => '<1h', '%dh' => '%dh', - '%b %e' => '%e %b', 'Expand tasks' => 'Perluas tugas', 'Collapse tasks' => 'Lipat tugas', 'Expand/collapse tasks' => 'Perluas/lipat tugas', @@ -758,10 +752,6 @@ return array( 'Only for tasks assigned to me' => 'Hanya untuk tugas yang ditugaskan ke saya', 'Only for tasks created by me' => 'Hanya untuk tugas yang dibuat oleh saya', 'Only for tasks created by me and assigned to me' => 'Hanya untuk tugas yang dibuat oleh saya dan ditugaskan ke saya', - '%A' => '%A', - '%b %e, %Y, %k:%M %p' => '%d/%m/%Y %H:%M', - 'New due date: %B %e, %Y' => 'Tanggal jatuh tempo baru : %d/%m/%Y', - 'Start date changed: %B %e, %Y' => 'Tanggal mulai berubah : %d/%m/%Y', '%%Y-%%m-%%d' => '%%d/%%m/%%Y', 'Total for all columns' => 'Total untuk semua kolom', 'You need at least 2 days of data to show the chart.' => 'Anda memerlukan setidaknya 2 hari dari data yang menunjukkan grafik.', @@ -786,7 +776,6 @@ return array( 'Not assigned' => 'Tidak ditugaskan', 'View advanced search syntax' => 'Lihat sintaks pencarian lanjutan', 'Overview' => 'Ikhtisar', - '%b %e %Y' => '%b %e %Y', 'Board/Calendar/List view' => 'Tampilan Papan/Kalender/Daftar', 'Switch to the board view' => 'Beralih ke tampilan papan', 'Switch to the calendar view' => 'Beralih ke tampilan kalender', @@ -876,8 +865,6 @@ return array( 'End date:' => 'Waktu berakhir :', 'There is no start date or end date for this project.' => 'Tidak ada waktu mula atau waktu berakhir pada projek ini', 'Projects Gantt chart' => 'projekkan carta Gantt', - 'Start date: %s' => 'Waktu mulai: %s', - 'End date: %s' => 'Waktu berakhir: %s', 'Link type' => 'Jenis pautan', 'Change task color when using a specific task link' => 'Rubah warna tugas ketika menggunakan Pautan tugas yang spesifik', 'Task link creation or modification' => 'Pautan tugas pada penciptaan atau penyuntingan', @@ -1125,4 +1112,10 @@ return array( // 'Moved:' => '', // 'Task #%d' => '', // 'Sub-tasks' => '', + // 'Date and time format' => '', + // 'Time format' => '', + // 'Start date: ' => '', + // 'End date: ' => '', + // 'New due date: ' => '', + // 'Start date changed: ' => '', ); diff --git a/app/Locale/nb_NO/translations.php b/app/Locale/nb_NO/translations.php index 6048107d..335c699b 100644 --- a/app/Locale/nb_NO/translations.php +++ b/app/Locale/nb_NO/translations.php @@ -102,7 +102,6 @@ return array( 'Open a task' => 'Åpne en oppgave', 'Do you really want to open this task: "%s"?' => 'Vil du åpne denne oppgaven: "%s"?', 'Back to the board' => 'Tilbake til prosjektsiden', - 'Created on %B %e, %Y at %k:%M %p' => 'Opprettet %d.%m.%Y - %H:%M', 'There is nobody assigned' => 'Mangler tildeling', 'Column on the board:' => 'Kolonne:', 'Close this task' => 'Lukk oppgaven', @@ -156,7 +155,6 @@ return array( 'Work in progress' => 'Under arbeid', 'Done' => 'Utført', 'Application version:' => 'Versjon:', - '%B %e, %Y at %k:%M %p' => '%d.%m.%Y - %H:%M', 'Id' => 'ID', '%d closed tasks' => '%d lukkede oppgaver', 'No task for this project' => 'Ingen oppgaver i dette prosjektet', @@ -180,8 +178,6 @@ return array( 'Edit this task' => 'Rediger oppgaven', 'Due Date' => 'Forfallsdato', 'Invalid date' => 'Ugyldig dato', - 'Must be done before %B %e, %Y' => 'Skal være utført innen %d.%m.%Y', - '%B %e, %Y' => '%d.%m.%Y', 'Automatic actions' => 'Automatiske handlinger', 'Your automatic action have been created successfully.' => 'Din automatiske handling er opprettet.', 'Unable to create your automatic action.' => 'Din automatiske handling kunne ikke opprettes.', @@ -317,7 +313,6 @@ return array( 'Unable to upload the file.' => 'Filen kunne ikke lastes opp.', 'Display another project' => 'Vis annet prosjekt...', 'Created by %s' => 'Opprettet av %s', - 'Last modified on %B %e, %Y at %k:%M %p' => 'Sist endret %d.%m.%Y - %H:%M', 'Tasks Export' => 'Oppgave eksport', 'Tasks exportation for "%s"' => 'Oppgaveeksportering for "%s"', 'Start Date' => 'Start-dato', @@ -589,7 +584,6 @@ return array( 'This task' => 'Denne oppgaven', // '<1h' => '', // '%dh' => '', - // '%b %e' => '', 'Expand tasks' => 'Utvid oppgavevisning', 'Collapse tasks' => 'Komprimer oppgavevisning', 'Expand/collapse tasks' => 'Utvide/komprimere oppgavevisning', @@ -758,10 +752,6 @@ return array( 'Only for tasks assigned to me' => 'Kun oppgaver som er tildelt meg', 'Only for tasks created by me' => 'Kun oppgaver som er opprettet av meg', 'Only for tasks created by me and assigned to me' => 'Kun oppgaver som er opprettet av meg og tildelt meg', - // '%A' => '', - // '%b %e, %Y, %k:%M %p' => '', - // 'New due date: %B %e, %Y' => '', - // 'Start date changed: %B %e, %Y' => '', // '%%Y-%%m-%%d' => '', 'Total for all columns' => 'Totalt for alle kolonner', // 'You need at least 2 days of data to show the chart.' => '', @@ -786,7 +776,6 @@ return array( 'Not assigned' => 'Ikke tildelt', 'View advanced search syntax' => 'Vis hjelp for avansert søk ', 'Overview' => 'Oversikt', - // '%b %e %Y' => '', 'Board/Calendar/List view' => 'Oversikt/kalender/listevisning', 'Switch to the board view' => 'Oversiktsvisning', 'Switch to the calendar view' => 'Kalendevisning', @@ -876,8 +865,6 @@ return array( 'End date:' => 'Sluttdato:', // 'There is no start date or end date for this project.' => '', 'Projects Gantt chart' => 'Gantt skjema for prosjekter', - 'Start date: %s' => 'Startdato: %s', - 'End date: %s' => 'Sluttdato: %s', 'Link type' => 'Relasjonstype', // 'Change task color when using a specific task link' => '', // 'Task link creation or modification' => '', @@ -1125,4 +1112,10 @@ return array( // 'Moved:' => '', // 'Task #%d' => '', // 'Sub-tasks' => '', + // 'Date and time format' => '', + // 'Time format' => '', + // 'Start date: ' => '', + // 'End date: ' => '', + // 'New due date: ' => '', + // 'Start date changed: ' => '', ); diff --git a/app/Locale/nl_NL/translations.php b/app/Locale/nl_NL/translations.php index 57fcae4e..0d9d9608 100644 --- a/app/Locale/nl_NL/translations.php +++ b/app/Locale/nl_NL/translations.php @@ -102,7 +102,6 @@ return array( 'Open a task' => 'Een taak openen', 'Do you really want to open this task: "%s"?' => 'Weet u zeker dat u deze taak wil openen : « %s » ?', 'Back to the board' => 'Terug naar het bord', - 'Created on %B %e, %Y at %k:%M %p' => 'Aangemaakt op %d/%m/%Y à %H:%M', 'There is nobody assigned' => 'Er is niemand toegewezen', 'Column on the board:' => 'Kolom op het bord : ', 'Close this task' => 'Deze taak sluiten', @@ -156,7 +155,6 @@ return array( 'Work in progress' => 'In behandeling', 'Done' => 'Afgewerkt', 'Application version:' => 'Applicatie versie :', - '%B %e, %Y at %k:%M %p' => '%d/%m/%Y op %H:%M', 'Id' => 'Id', '%d closed tasks' => '%d gesloten taken', 'No task for this project' => 'Geen taken voor dit project', @@ -180,8 +178,6 @@ return array( 'Edit this task' => 'Deze taak aanpassen', 'Due Date' => 'Vervaldag', 'Invalid date' => 'Ongeldige datum', - 'Must be done before %B %e, %Y' => 'Moet voltooid zijn voor %d/%m/%Y', - '%B %e, %Y' => '%d %B %Y', 'Automatic actions' => 'Geautomatiseerd acties', 'Your automatic action have been created successfully.' => 'Geautomatiseerde actie succesvol aangemaakt.', 'Unable to create your automatic action.' => 'Geautomatiseerde actie aanmaken niet gelukt.', @@ -317,7 +313,6 @@ return array( 'Unable to upload the file.' => 'Uploaden van bestand niet gelukt.', 'Display another project' => 'Een ander project weergeven', 'Created by %s' => 'Aangemaakt door %s', - 'Last modified on %B %e, %Y at %k:%M %p' => 'Laatst gewijzigd op %d/%m/%Y à %H:%M', 'Tasks Export' => 'Taken exporteren', 'Tasks exportation for "%s"' => 'Taken exporteren voor « %s »', 'Start Date' => 'Startdatum', @@ -589,7 +584,6 @@ return array( 'This task' => 'Deze taal', '<1h' => '<1h', '%dh' => '%dh', - '%b %e' => '%e %b', 'Expand tasks' => 'Taken uitklappen', 'Collapse tasks' => 'Taken inklappen', 'Expand/collapse tasks' => 'Taken in/uiklappen', @@ -758,10 +752,6 @@ return array( // 'Only for tasks assigned to me' => '', // 'Only for tasks created by me' => '', // 'Only for tasks created by me and assigned to me' => '', - // '%A' => '', - // '%b %e, %Y, %k:%M %p' => '', - // 'New due date: %B %e, %Y' => '', - // 'Start date changed: %B %e, %Y' => '', // '%%Y-%%m-%%d' => '', // 'Total for all columns' => '', // 'You need at least 2 days of data to show the chart.' => '', @@ -786,7 +776,6 @@ return array( // 'Not assigned' => '', // 'View advanced search syntax' => '', // 'Overview' => '', - // '%b %e %Y' => '', // 'Board/Calendar/List view' => '', // 'Switch to the board view' => '', // 'Switch to the calendar view' => '', @@ -876,8 +865,6 @@ return array( // 'End date:' => '', // 'There is no start date or end date for this project.' => '', // 'Projects Gantt chart' => '', - // 'Start date: %s' => '', - // 'End date: %s' => '', // 'Link type' => '', // 'Change task color when using a specific task link' => '', // 'Task link creation or modification' => '', @@ -1125,4 +1112,10 @@ return array( // 'Moved:' => '', // 'Task #%d' => '', // 'Sub-tasks' => '', + // 'Date and time format' => '', + // 'Time format' => '', + // 'Start date: ' => '', + // 'End date: ' => '', + // 'New due date: ' => '', + // 'Start date changed: ' => '', ); diff --git a/app/Locale/pl_PL/translations.php b/app/Locale/pl_PL/translations.php index 6d0a0b93..28ed2b3a 100644 --- a/app/Locale/pl_PL/translations.php +++ b/app/Locale/pl_PL/translations.php @@ -102,7 +102,6 @@ return array( 'Open a task' => 'Otwórz zadanie', 'Do you really want to open this task: "%s"?' => 'Na pewno chcesz otworzyć zadanie: "%s"?', 'Back to the board' => 'Powrót do tablicy', - 'Created on %B %e, %Y at %k:%M %p' => 'Utworzono dnia %e %B %Y o %k:%M', 'There is nobody assigned' => 'Nikt nie jest przypisany', 'Column on the board:' => 'Kolumna na tablicy:', 'Close this task' => 'Zamknij zadanie', @@ -156,7 +155,6 @@ return array( 'Work in progress' => 'W trakcie', 'Done' => 'Zakończone', 'Application version:' => 'Wersja aplikacji:', - '%B %e, %Y at %k:%M %p' => '%e %B %Y o %k:%M', 'Id' => 'Id', '%d closed tasks' => '%d zamkniętych zadań', 'No task for this project' => 'Brak zadań dla tego projektu', @@ -180,8 +178,6 @@ return array( 'Edit this task' => 'Edytuj zadanie', 'Due Date' => 'Termin', 'Invalid date' => 'Błędna data', - 'Must be done before %B %e, %Y' => 'Termin do %e %B %Y', - '%B %e, %Y' => '%e %B %Y', 'Automatic actions' => 'Akcje automatyczne', 'Your automatic action have been created successfully.' => 'Twoja akcja została dodana', 'Unable to create your automatic action.' => 'Nie udało się utworzyć akcji', @@ -317,7 +313,6 @@ return array( 'Unable to upload the file.' => 'Nie można wczytać pliku.', 'Display another project' => 'Wyświetl inny projekt', 'Created by %s' => 'Utworzone przez %s', - 'Last modified on %B %e, %Y at %k:%M %p' => 'Ostatnio zmienione %e %B %Y o %k:%M', 'Tasks Export' => 'Eksport zadań', 'Tasks exportation for "%s"' => 'Eksport zadań dla "%s"', 'Start Date' => 'Data początkowa', @@ -589,7 +584,6 @@ return array( 'This task' => 'To zadanie', // '<1h' => '', // '%dh' => '', - '%b %e' => '%e %b', 'Expand tasks' => 'Rozwiń zadania', 'Collapse tasks' => 'Zwiń zadania', 'Expand/collapse tasks' => 'Zwiń/Rozwiń zadania', @@ -758,10 +752,6 @@ return array( 'Only for tasks assigned to me' => 'Tylko zadań przypisanych do mnie', 'Only for tasks created by me' => 'Tylko zadań utworzonych przeze mnie', 'Only for tasks created by me and assigned to me' => 'Tylko zadań przypisanych lub utworzonych przeze mnie', - // '%A' => '', - // '%b %e, %Y, %k:%M %p' => '', - 'New due date: %B %e, %Y' => 'Nowy termin: %B %e, %Y', - 'Start date changed: %B %e, %Y' => 'Zmiana daty rozpoczęcia: %B %e, %Y', // '%%Y-%%m-%%d' => '', // 'Total for all columns' => '', // 'You need at least 2 days of data to show the chart.' => '', @@ -786,7 +776,6 @@ return array( 'Not assigned' => 'Nieprzypisane zadania', 'View advanced search syntax' => 'Pomoc dotycząca budowania filtrów', 'Overview' => 'Przegląd', - // '%b %e %Y' => '', 'Board/Calendar/List view' => 'Widok: Tablica/Kalendarz/Lista', 'Switch to the board view' => 'Przełącz na tablicę', 'Switch to the calendar view' => 'Przełącz na kalendarz', @@ -876,8 +865,6 @@ return array( 'End date:' => 'Data zakończenia:', 'There is no start date or end date for this project.' => 'Nie zdefiniowano czasu trwania projektu', 'Projects Gantt chart' => 'Wykres Gantta dla projektów', - 'Start date: %s' => 'Data rozpoczęcia: %s', - 'End date: %s' => 'Data zakończenia: %s', 'Link type' => 'Typ adresu URL', 'Change task color when using a specific task link' => 'Zmień kolor zadania używając specjalnego adresu URL', 'Task link creation or modification' => 'Adres URL do utworzenia zadania lub modyfikacji', @@ -1125,4 +1112,10 @@ return array( // 'Moved:' => '', // 'Task #%d' => '', // 'Sub-tasks' => '', + // 'Date and time format' => '', + // 'Time format' => '', + // 'Start date: ' => '', + // 'End date: ' => '', + // 'New due date: ' => '', + // 'Start date changed: ' => '', ); diff --git a/app/Locale/pt_BR/translations.php b/app/Locale/pt_BR/translations.php index eef52d48..d29f9f88 100644 --- a/app/Locale/pt_BR/translations.php +++ b/app/Locale/pt_BR/translations.php @@ -102,7 +102,6 @@ return array( 'Open a task' => 'Abrir uma tarefa', 'Do you really want to open this task: "%s"?' => 'Você realmente deseja abrir esta tarefa: "%s"?', 'Back to the board' => 'Voltar ao board', - 'Created on %B %e, %Y at %k:%M %p' => 'Criado em %d %B %Y às %H:%M', 'There is nobody assigned' => 'Não há ninguém designado', 'Column on the board:' => 'Coluna no board:', 'Close this task' => 'Finalizar esta tarefa', @@ -156,7 +155,6 @@ return array( 'Work in progress' => 'Em andamento', 'Done' => 'Finalizado', 'Application version:' => 'Versão da aplicação:', - '%B %e, %Y at %k:%M %p' => '%d %B %Y às %H:%M', 'Id' => 'Id', '%d closed tasks' => '%d tarefas finalizadas', 'No task for this project' => 'Não há tarefa para este projeto', @@ -180,8 +178,6 @@ return array( 'Edit this task' => 'Editar esta tarefa', 'Due Date' => 'Data de vencimento', 'Invalid date' => 'Data inválida', - 'Must be done before %B %e, %Y' => 'Deve ser finalizado antes de %d %B %Y', - '%B %e, %Y' => '%d %B %Y', 'Automatic actions' => 'Ações automáticas', 'Your automatic action have been created successfully.' => 'Sua ação automética foi criada com sucesso.', 'Unable to create your automatic action.' => 'Não é possível criar sua ação automática.', @@ -317,7 +313,6 @@ return array( 'Unable to upload the file.' => 'Não foi possível carregar o arquivo.', 'Display another project' => 'Exibir outro projeto', 'Created by %s' => 'Criado por %s', - 'Last modified on %B %e, %Y at %k:%M %p' => 'Última modificação em %B %e, %Y às %k: %M %p', 'Tasks Export' => 'Exportar Tarefas', 'Tasks exportation for "%s"' => 'As tarefas foram exportadas para "%s"', 'Start Date' => 'Data inicial', @@ -589,7 +584,6 @@ return array( 'This task' => 'Esta tarefa', '<1h' => '<1h', '%dh' => '%dh', - '%b %e' => '%e %b', 'Expand tasks' => 'Expandir tarefas', 'Collapse tasks' => 'Contrair tarefas', 'Expand/collapse tasks' => 'Expandir/Contrair tarefas', @@ -758,10 +752,6 @@ return array( 'Only for tasks assigned to me' => 'Somente as tarefas atribuídas a mim', 'Only for tasks created by me' => 'Apenas as tarefas que eu criei', 'Only for tasks created by me and assigned to me' => 'Apenas as tarefas que eu criei e aquelas atribuídas a mim', - '%A' => '%A', - '%b %e, %Y, %k:%M %p' => '%d/%m/%Y %H:%M', - 'New due date: %B %e, %Y' => 'Nova data limite: %d/%m/%Y', - 'Start date changed: %B %e, %Y' => 'Data de início alterada: %d/%m/%Y', '%%Y-%%m-%%d' => '%%d/%%m/%%Y', 'Total for all columns' => 'Total para todas as colunas', 'You need at least 2 days of data to show the chart.' => 'Você precisa de pelo menos 2 dias de dados para visualizar o gráfico.', @@ -786,7 +776,6 @@ return array( 'Not assigned' => 'Não designada', 'View advanced search syntax' => 'Ver a sintaxe para pesquisa avançada', 'Overview' => 'Visão global', - '%b %e %Y' => '%b %e %Y', 'Board/Calendar/List view' => 'Vista Painel/Calendário/Lista', 'Switch to the board view' => 'Mudar para o modo Painel', 'Switch to the calendar view' => 'Mudar par o modo Calendário', @@ -876,8 +865,6 @@ return array( 'End date:' => 'Data de término:', 'There is no start date or end date for this project.' => 'Não há data de início ou data de término para este projeto.', 'Projects Gantt chart' => 'Gráfico de Gantt dos projetos', - 'Start date: %s' => 'Data de início: %s', - 'End date: %s' => 'Data de término: %s', 'Link type' => 'Tipo de link', 'Change task color when using a specific task link' => 'Mudar a cor da tarefa quando um link específico é utilizado', 'Task link creation or modification' => 'Criação ou modificação de um link em uma tarefa', @@ -1125,4 +1112,10 @@ return array( // 'Moved:' => '', // 'Task #%d' => '', // 'Sub-tasks' => '', + // 'Date and time format' => '', + // 'Time format' => '', + // 'Start date: ' => '', + // 'End date: ' => '', + // 'New due date: ' => '', + // 'Start date changed: ' => '', ); diff --git a/app/Locale/pt_PT/translations.php b/app/Locale/pt_PT/translations.php index e952df35..3ffaba0e 100644 --- a/app/Locale/pt_PT/translations.php +++ b/app/Locale/pt_PT/translations.php @@ -102,7 +102,6 @@ return array( 'Open a task' => 'Abrir uma tarefa', 'Do you really want to open this task: "%s"?' => 'Tem a certeza que quer abrir esta tarefa: "%s"?', 'Back to the board' => 'Voltar ao quadro', - 'Created on %B %e, %Y at %k:%M %p' => 'Criado em %d %B %Y às %H:%M', 'There is nobody assigned' => 'Não há ninguém assignado', 'Column on the board:' => 'Coluna no quadro:', 'Close this task' => 'Finalizar esta tarefa', @@ -156,7 +155,6 @@ return array( 'Work in progress' => 'Em andamento', 'Done' => 'Finalizado', 'Application version:' => 'Versão da aplicação:', - '%B %e, %Y at %k:%M %p' => '%d %B %Y às %H:%M', 'Id' => 'Id', '%d closed tasks' => '%d tarefas finalizadas', 'No task for this project' => 'Não há tarefa para este projecto', @@ -180,8 +178,6 @@ return array( 'Edit this task' => 'Editar esta tarefa', 'Due Date' => 'Data de vencimento', 'Invalid date' => 'Data inválida', - 'Must be done before %B %e, %Y' => 'Deve ser finalizado antes de %d %B %Y', - '%B %e, %Y' => '%d %B %Y', 'Automatic actions' => 'Acções automáticas', 'Your automatic action have been created successfully.' => 'A sua acção automática foi criada com sucesso.', 'Unable to create your automatic action.' => 'Não é possível criar a sua acção automática.', @@ -317,7 +313,6 @@ return array( 'Unable to upload the file.' => 'Não foi possível carregar o arquivo.', 'Display another project' => 'Mostrar outro projecto', 'Created by %s' => 'Criado por %s', - 'Last modified on %B %e, %Y at %k:%M %p' => 'Última modificação em %B %e, %Y às %k: %M %p', 'Tasks Export' => 'Exportar Tarefas', 'Tasks exportation for "%s"' => 'As tarefas foram exportadas para "%s"', 'Start Date' => 'Data inicial', @@ -589,7 +584,6 @@ return array( 'This task' => 'Esta tarefa', '<1h' => '<1h', '%dh' => '%dh', - '%b %e' => '%e %b', 'Expand tasks' => 'Expandir tarefas', 'Collapse tasks' => 'Contrair tarefas', 'Expand/collapse tasks' => 'Expandir/Contrair tarefas', @@ -758,10 +752,6 @@ return array( 'Only for tasks assigned to me' => 'Somente as tarefas atribuídas a mim', 'Only for tasks created by me' => 'Apenas as tarefas que eu criei', 'Only for tasks created by me and assigned to me' => 'Apenas as tarefas que eu criei e aquelas atribuídas a mim', - '%A' => '%A', - '%b %e, %Y, %k:%M %p' => '%d/%m/%Y %H:%M', - 'New due date: %B %e, %Y' => 'Nova data de vencimento: %d/%m/%Y', - 'Start date changed: %B %e, %Y' => 'Data de início alterada: %d/%m/%Y', '%%Y-%%m-%%d' => '%%d/%%m/%%Y', 'Total for all columns' => 'Total para todas as colunas', 'You need at least 2 days of data to show the chart.' => 'Precisa de pelo menos 2 dias de dados para visualizar o gráfico.', @@ -786,7 +776,6 @@ return array( 'Not assigned' => 'Não assignada', 'View advanced search syntax' => 'Ver sintaxe avançada de pesquisa', 'Overview' => 'Visão global', - '%b %e %Y' => '%b %e %Y', 'Board/Calendar/List view' => 'Vista Painel/Calendário/Lista', 'Switch to the board view' => 'Mudar para o modo Painel', 'Switch to the calendar view' => 'Mudar para o modo Calendário', @@ -876,8 +865,6 @@ return array( 'End date:' => 'Data de fim:', 'There is no start date or end date for this project.' => 'Não existe data de inicio ou fim para este projecto.', 'Projects Gantt chart' => 'Gráfico de Gantt dos projectos', - 'Start date: %s' => 'Data de inicio: %s', - 'End date: %s' => 'Data de fim: %s', 'Link type' => 'Tipo de ligação', 'Change task color when using a specific task link' => 'Alterar cor da tarefa quando se usar um tipo especifico de ligação de tarefa', 'Task link creation or modification' => 'Criação ou modificação de ligação de tarefa', @@ -1125,4 +1112,10 @@ return array( // 'Moved:' => '', // 'Task #%d' => '', // 'Sub-tasks' => '', + // 'Date and time format' => '', + // 'Time format' => '', + // 'Start date: ' => '', + // 'End date: ' => '', + // 'New due date: ' => '', + // 'Start date changed: ' => '', ); diff --git a/app/Locale/ru_RU/translations.php b/app/Locale/ru_RU/translations.php index 1708dcba..f971acc1 100755 --- a/app/Locale/ru_RU/translations.php +++ b/app/Locale/ru_RU/translations.php @@ -102,7 +102,6 @@ return array( 'Open a task' => 'Открыть задачу', 'Do you really want to open this task: "%s"?' => 'Вы уверены что хотите открыть задачу: "%s" ?', 'Back to the board' => 'Вернуться на доску', - 'Created on %B %e, %Y at %k:%M %p' => 'Создано %B /%e /%Y в %k:%M %p', 'There is nobody assigned' => 'Никто не назначен', 'Column on the board:' => 'Колонка на доске: ', 'Close this task' => 'Закрыть задачу', @@ -156,7 +155,6 @@ return array( 'Work in progress' => 'В процессе', 'Done' => 'Выполнено', 'Application version:' => 'Версия приложения:', - '%B %e, %Y at %k:%M %p' => '%d/%m/%Y в %H:%M', 'Id' => 'ID', '%d closed tasks' => '%d завершенных задач', 'No task for this project' => 'Нет задач для этого проекта', @@ -180,8 +178,6 @@ return array( 'Edit this task' => 'Изменить задачу', 'Due Date' => 'Сделать до', 'Invalid date' => 'Неверная дата', - 'Must be done before %B %e, %Y' => 'Должно быть сделано до %B %e %Y', - '%B %e, %Y' => '%B, %e, %Y', 'Automatic actions' => 'Автоматические действия', 'Your automatic action have been created successfully.' => 'Автоматизированное действие успешно настроено.', 'Unable to create your automatic action.' => 'Не удалось создать автоматизированное действие.', @@ -317,7 +313,6 @@ return array( 'Unable to upload the file.' => 'Не удалось загрузить файл.', 'Display another project' => 'Показать другой проект', 'Created by %s' => 'Создано %s', - 'Last modified on %B %e, %Y at %k:%M %p' => 'Последнее изменение %d/%m/%Y в %H:%M', 'Tasks Export' => 'Экспорт задач', 'Tasks exportation for "%s"' => 'Задача экспортирована для « %s »', 'Start Date' => 'Дата начала', @@ -589,7 +584,6 @@ return array( 'This task' => 'Эта задача', '<1h' => '<1ч', '%dh' => '%dh', - '%b %e' => '%b %e', 'Expand tasks' => 'Развернуть задачи', 'Collapse tasks' => 'Свернуть задачи', 'Expand/collapse tasks' => 'Развернуть/свернуть задачи', @@ -758,10 +752,6 @@ return array( 'Only for tasks assigned to me' => 'Только для задач, назначенных на меня', 'Only for tasks created by me' => 'Только для задач, созданных мной', 'Only for tasks created by me and assigned to me' => 'Только для задач, созданных мной и назначенных мной', - '%A' => '%A', - '%b %e, %Y, %k:%M %p' => '%b %e, %Y, %k:%M %p', - 'New due date: %B %e, %Y' => 'Новая дата завершения: %B %e, %Y', - 'Start date changed: %B %e, %Y' => 'Изменить дату начала: %B %e, %Y', '%%Y-%%m-%%d' => '%%Y-%%m-%%d', 'Total for all columns' => 'Суммарно для всех колонок', 'You need at least 2 days of data to show the chart.' => 'Для отображения диаграммы нужно по крайней мере 2 дня.', @@ -786,7 +776,6 @@ return array( 'Not assigned' => 'Не назначенные', 'View advanced search syntax' => 'Просмотр расширенного синтаксиса поиска', 'Overview' => 'Обзор', - '%b %e %Y' => '%b %e %Y', 'Board/Calendar/List view' => 'Просмотр Доска/Календарь/Список', 'Switch to the board view' => 'Переключиться в режим доски', 'Switch to the calendar view' => 'Переключиться в режим календаря', @@ -876,8 +865,6 @@ return array( 'End date:' => 'Дата завершения:', 'There is no start date or end date for this project.' => 'В проекте не указаны дата начала или завершения.', 'Projects Gantt chart' => 'Диаграмма Ганта проектов', - 'Start date: %s' => 'Дата начала: %s', - 'End date: %s' => 'Дата завершения: %s', 'Link type' => 'Тип ссылки', 'Change task color when using a specific task link' => 'Изменение цвета задач при использовании ссылки на определенные задачи', 'Task link creation or modification' => 'Ссылка на создание или модификацию задачи', @@ -1125,4 +1112,10 @@ return array( 'Moved:' => 'Перемещена:', 'Task #%d' => 'Задача #%d', 'Sub-tasks' => 'Подзадачи', + // 'Date and time format' => '', + // 'Time format' => '', + // 'Start date: ' => '', + // 'End date: ' => '', + // 'New due date: ' => '', + // 'Start date changed: ' => '', ); diff --git a/app/Locale/sr_Latn_RS/translations.php b/app/Locale/sr_Latn_RS/translations.php index 47ae3d7b..3551c281 100644 --- a/app/Locale/sr_Latn_RS/translations.php +++ b/app/Locale/sr_Latn_RS/translations.php @@ -102,7 +102,6 @@ return array( 'Open a task' => 'Otvori zadatak', 'Do you really want to open this task: "%s"?' => 'Da li zaista želiš da otvoriš zadatak: "%s"?', 'Back to the board' => 'Nazad na tablu', - 'Created on %B %e, %Y at %k:%M %p' => 'Kreiran %e %B %Y o %k:%M', 'There is nobody assigned' => 'Niko nije dodeljen!', 'Column on the board:' => 'Kolona na tabli:', 'Close this task' => 'Zatvori ovaj zadatak', @@ -156,7 +155,6 @@ return array( 'Work in progress' => 'U radu', 'Done' => 'Gotovo', 'Application version:' => 'Verzija aplikacije:', - '%B %e, %Y at %k:%M %p' => '%e %B %Y o %k:%M', 'Id' => 'Id', '%d closed tasks' => '%d zatvorenih zadataka', 'No task for this project' => 'Nema dodeljenih zadataka ovom projektu', @@ -180,8 +178,6 @@ return array( 'Edit this task' => 'Izmeni ovaj zadatak', 'Due Date' => 'Termin', 'Invalid date' => 'Loš datum', - 'Must be done before %B %e, %Y' => 'Termin do %e %B %Y', - '%B %e, %Y' => '%e %B %Y', 'Automatic actions' => 'Automatske akcije', 'Your automatic action have been created successfully.' => 'Uspešno kreirana automatska akcija', 'Unable to create your automatic action.' => 'Nemoguće kreiranje automatske akcije', @@ -317,7 +313,6 @@ return array( 'Unable to upload the file.' => 'Nije moguće snimiti fajl.', 'Display another project' => 'Prikaži drugi projekat', 'Created by %s' => 'Kreirao %s', - 'Last modified on %B %e, %Y at %k:%M %p' => 'Poslednja izmena %e %B %Y o %k:%M', 'Tasks Export' => 'Izvoz zadataka', 'Tasks exportation for "%s"' => 'Izvoz zadataka za "%s"', 'Start Date' => 'Početni datum', @@ -589,7 +584,6 @@ return array( // 'This task' => '', // '<1h' => '', // '%dh' => '', - // '%b %e' => '', // 'Expand tasks' => '', // 'Collapse tasks' => '', // 'Expand/collapse tasks' => '', @@ -758,10 +752,6 @@ return array( // 'Only for tasks assigned to me' => '', // 'Only for tasks created by me' => '', // 'Only for tasks created by me and assigned to me' => '', - // '%A' => '', - // '%b %e, %Y, %k:%M %p' => '', - // 'New due date: %B %e, %Y' => '', - // 'Start date changed: %B %e, %Y' => '', // '%%Y-%%m-%%d' => '', // 'Total for all columns' => '', // 'You need at least 2 days of data to show the chart.' => '', @@ -786,7 +776,6 @@ return array( // 'Not assigned' => '', // 'View advanced search syntax' => '', // 'Overview' => '', - // '%b %e %Y' => '', // 'Board/Calendar/List view' => '', // 'Switch to the board view' => '', // 'Switch to the calendar view' => '', @@ -876,8 +865,6 @@ return array( // 'End date:' => '', // 'There is no start date or end date for this project.' => '', // 'Projects Gantt chart' => '', - // 'Start date: %s' => '', - // 'End date: %s' => '', // 'Link type' => '', // 'Change task color when using a specific task link' => '', // 'Task link creation or modification' => '', @@ -1125,4 +1112,10 @@ return array( // 'Moved:' => '', // 'Task #%d' => '', // 'Sub-tasks' => '', + // 'Date and time format' => '', + // 'Time format' => '', + // 'Start date: ' => '', + // 'End date: ' => '', + // 'New due date: ' => '', + // 'Start date changed: ' => '', ); diff --git a/app/Locale/sv_SE/translations.php b/app/Locale/sv_SE/translations.php index 4218561a..c0aa883c 100644 --- a/app/Locale/sv_SE/translations.php +++ b/app/Locale/sv_SE/translations.php @@ -102,7 +102,6 @@ return array( 'Open a task' => 'Öppna en uppgift', 'Do you really want to open this task: "%s"?' => 'Vill du verkligen öppna denna uppgift: "%s"?', 'Back to the board' => 'Tillbaka till tavlan', - 'Created on %B %e, %Y at %k:%M %p' => 'Skapad %Y-%m-%d kl %H:%M', 'There is nobody assigned' => 'Det finns ingen tilldelad', 'Column on the board:' => 'Kolumn på tavlan:', 'Close this task' => 'Stäng uppgiften', @@ -156,7 +155,6 @@ return array( 'Work in progress' => 'Pågående', 'Done' => 'Slutfört', 'Application version:' => 'Version:', - '%B %e, %Y at %k:%M %p' => '%Y-%m-%d kl %H:%M', 'Id' => 'ID', '%d closed tasks' => '%d stängda uppgifter', 'No task for this project' => 'Inga uppgifter i detta projekt', @@ -180,8 +178,6 @@ return array( 'Edit this task' => 'Ändra denna uppgift', 'Due Date' => 'Måldatum', 'Invalid date' => 'Ej tillåtet datum', - 'Must be done before %B %e, %Y' => 'Måste vara klart innan %Y-%m-%d', - '%B %e, %Y' => '%Y-%m-%d', 'Automatic actions' => 'Automatiska åtgärder', 'Your automatic action have been created successfully.' => 'Din automatiska åtgärd har skapats.', 'Unable to create your automatic action.' => 'Kunde inte skapa din automatiska åtgärd.', @@ -317,7 +313,6 @@ return array( 'Unable to upload the file.' => 'Kunde inte ladda upp filen.', 'Display another project' => 'Visa ett annat projekt', 'Created by %s' => 'Skapad av %s', - 'Last modified on %B %e, %Y at %k:%M %p' => 'Senaste ändring %Y-%m-%d kl %H:%M', 'Tasks Export' => 'Exportera uppgifter', 'Tasks exportation for "%s"' => 'Exportera uppgifter för "%s"', 'Start Date' => 'Startdatum', @@ -589,7 +584,6 @@ return array( 'This task' => 'Denna uppgift', '<1h' => '<1h', '%dh' => '%dh', - '%b %e' => '%b %e', 'Expand tasks' => 'Expandera uppgifter', 'Collapse tasks' => 'Minimera uppgifter', 'Expand/collapse tasks' => 'Expandera/minimera uppgifter', @@ -758,10 +752,6 @@ return array( 'Only for tasks assigned to me' => 'Bara för uppgifter tilldelade mig', 'Only for tasks created by me' => 'Bara för uppgifter skapade av mig', 'Only for tasks created by me and assigned to me' => 'Bara för uppgifter skapade av mig och tilldelade till mig', - '%A' => '%A', - '%b %e, %Y, %k:%M %p' => '%b %e, %Y, %k:%M %p', - 'New due date: %B %e, %Y' => 'Nytt förfallodatum: %B %e, %Y', - 'Start date changed: %B %e, %Y' => 'Startdatum ändrat: %B %e, %Y', '%%Y-%%m-%%d' => '%%Y-%%m-%%d', 'Total for all columns' => 'Totalt för alla kolumner', 'You need at least 2 days of data to show the chart.' => 'Du behöver minst två dagars data för att visa diagrammet.', @@ -786,7 +776,6 @@ return array( 'Not assigned' => 'Inte tilldelad', 'View advanced search syntax' => 'Visa avancerad söksyntax', 'Overview' => 'Översikts', - '%b %e %Y' => '%b %e %Y', 'Board/Calendar/List view' => 'Tavla/Kalender/Listvy', 'Switch to the board view' => 'Växla till tavelvy', 'Switch to the calendar view' => 'Växla till kalendervy', @@ -876,8 +865,6 @@ return array( // 'End date:' => '', // 'There is no start date or end date for this project.' => '', // 'Projects Gantt chart' => '', - // 'Start date: %s' => '', - // 'End date: %s' => '', // 'Link type' => '', // 'Change task color when using a specific task link' => '', // 'Task link creation or modification' => '', @@ -1125,4 +1112,10 @@ return array( // 'Moved:' => '', // 'Task #%d' => '', // 'Sub-tasks' => '', + // 'Date and time format' => '', + // 'Time format' => '', + // 'Start date: ' => '', + // 'End date: ' => '', + // 'New due date: ' => '', + // 'Start date changed: ' => '', ); diff --git a/app/Locale/th_TH/translations.php b/app/Locale/th_TH/translations.php index eb4c0e77..1ccc2d90 100644 --- a/app/Locale/th_TH/translations.php +++ b/app/Locale/th_TH/translations.php @@ -102,7 +102,6 @@ return array( 'Open a task' => 'เปิดงาน', 'Do you really want to open this task: "%s"?' => 'คุณต้องการเปิดงาน: « %s » ใช่หรือไม่?', 'Back to the board' => 'กลับไปที่บอร์ด', - 'Created on %B %e, %Y at %k:%M %p' => 'สร้างวันที่ %d/%m/%Y เวลา %H:%M', 'There is nobody assigned' => 'ไม่มีใครถูกกำหนด', 'Column on the board:' => 'คอลัมน์บนบอร์ด:', 'Close this task' => 'ปิดงานนี้', @@ -156,7 +155,6 @@ return array( 'Work in progress' => 'กำลังทำ', 'Done' => 'เสร็จ', 'Application version:' => 'แอพเวอร์ชัน:', - '%B %e, %Y at %k:%M %p' => '%d/%m/%Y เวลา %H:%M', 'Id' => 'ไอดี', '%d closed tasks' => '%d งานที่ปิด', 'No task for this project' => 'ไม่มีงานสำหรับโปรเจคนี้', @@ -180,8 +178,6 @@ return array( 'Edit this task' => 'แก้ไขงาน', 'Due Date' => 'วันที่ครบกำหนด', 'Invalid date' => 'วันที่ผิด', - 'Must be done before %B %e, %Y' => 'ต้องทำให้เสร็จก่อน %d/%m/%Y', - '%B %e, %Y' => '%d/%m/%Y', 'Automatic actions' => 'การกระทำอัตโนมัติ', 'Your automatic action have been created successfully.' => 'การกระทำอัตโนมัติสร้างเรียบร้อยแล้ว', 'Unable to create your automatic action.' => 'ไม่สามารถสร้างการกระทำอัตโนมัติได้', @@ -317,7 +313,6 @@ return array( 'Unable to upload the file.' => 'ไม่สามารถอัพโหลดไฟล์ได้', 'Display another project' => 'แสดงโปรเจคอื่น', 'Created by %s' => 'สร้างโดย %s', - 'Last modified on %B %e, %Y at %k:%M %p' => 'แก้ไขล่าสุดวันที่ %B %e, %Y เวลา %k:%M %p', 'Tasks Export' => 'ส่งออกงาน', 'Tasks exportation for "%s"' => 'ส่งออกงานสำหรับ "%s"', 'Start Date' => 'เริ่มวันที่', @@ -589,7 +584,6 @@ return array( 'This task' => 'งานนี้', // '<1h' => '', // '%dh' => '', - // '%b %e' => '', 'Expand tasks' => 'ขยายงาน', 'Collapse tasks' => 'ย่องาน', 'Expand/collapse tasks' => 'ขยาย/ย่อ งาน', @@ -758,10 +752,6 @@ return array( // 'Only for tasks assigned to me' => '', // 'Only for tasks created by me' => '', // 'Only for tasks created by me and assigned to me' => '', - // '%A' => '', - // '%b %e, %Y, %k:%M %p' => '', - // 'New due date: %B %e, %Y' => '', - // 'Start date changed: %B %e, %Y' => '', // '%%Y-%%m-%%d' => '', // 'Total for all columns' => '', // 'You need at least 2 days of data to show the chart.' => '', @@ -786,7 +776,6 @@ return array( // 'Not assigned' => '', // 'View advanced search syntax' => '', // 'Overview' => '', - // '%b %e %Y' => '', // 'Board/Calendar/List view' => '', // 'Switch to the board view' => '', // 'Switch to the calendar view' => '', @@ -876,8 +865,6 @@ return array( // 'End date:' => '', // 'There is no start date or end date for this project.' => '', // 'Projects Gantt chart' => '', - // 'Start date: %s' => '', - // 'End date: %s' => '', // 'Link type' => '', // 'Change task color when using a specific task link' => '', // 'Task link creation or modification' => '', @@ -1125,4 +1112,10 @@ return array( // 'Moved:' => '', // 'Task #%d' => '', // 'Sub-tasks' => '', + // 'Date and time format' => '', + // 'Time format' => '', + // 'Start date: ' => '', + // 'End date: ' => '', + // 'New due date: ' => '', + // 'Start date changed: ' => '', ); diff --git a/app/Locale/tr_TR/translations.php b/app/Locale/tr_TR/translations.php index 6fecb37a..10153331 100644 --- a/app/Locale/tr_TR/translations.php +++ b/app/Locale/tr_TR/translations.php @@ -102,7 +102,6 @@ return array( 'Open a task' => 'Bir görevi aç', 'Do you really want to open this task: "%s"?' => 'Bu görevi gerçekten açmak istiyor musunuz: "%s"?', 'Back to the board' => 'Tabloya dön', - 'Created on %B %e, %Y at %k:%M %p' => '%B %e, %Y, saat %k:%M %p da oluşturuldu', 'There is nobody assigned' => 'Kimse atanmamış', 'Column on the board:' => 'Tablodaki sütun:', 'Close this task' => 'Görevi kapat', @@ -156,7 +155,6 @@ return array( 'Work in progress' => 'İşlemde', 'Done' => 'Tamamlandı', 'Application version:' => 'Uygulama versiyonu:', - '%B %e, %Y at %k:%M %p' => '%B %e, %Y saat %k:%M %p', 'Id' => 'Kod', '%d closed tasks' => '%d kapatılmış görevler', 'No task for this project' => 'Bu proje için görev yok', @@ -180,8 +178,6 @@ return array( 'Edit this task' => 'Bu görevi değiştir', 'Due Date' => 'Bitiş Tarihi', 'Invalid date' => 'Geçersiz tarihi', - 'Must be done before %B %e, %Y' => '%B %e, %Y tarihinden önce yapılmalı', - '%B %e, %Y' => '%d %B %Y', 'Automatic actions' => 'Otomatik işlemler', 'Your automatic action have been created successfully.' => 'Otomatik işlem başarıyla oluşturuldu', 'Unable to create your automatic action.' => 'Otomatik işleminiz oluşturulamadı', @@ -317,7 +313,6 @@ return array( 'Unable to upload the file.' => 'Dosya yüklenemiyor.', 'Display another project' => 'Başka bir proje göster', 'Created by %s' => '%s tarafından oluşturuldu', - 'Last modified on %B %e, %Y at %k:%M %p' => 'Son değişiklik tarihi %d.%m.%Y, saati %H:%M', 'Tasks Export' => 'Görevleri dışa aktar', 'Tasks exportation for "%s"' => '"%s" için görevleri dışa aktar', 'Start Date' => 'Başlangıç tarihi', @@ -589,7 +584,6 @@ return array( 'This task' => 'Bu görev', '<1h' => '<1s', '%dh' => '%ds', - // '%b %e' => '', 'Expand tasks' => 'Görevleri genişlet', 'Collapse tasks' => 'Görevleri daralt', 'Expand/collapse tasks' => 'Görevleri genişlet/daralt', @@ -758,10 +752,6 @@ return array( 'Only for tasks assigned to me' => 'Yalnızca bana atanmış görevler için', 'Only for tasks created by me' => 'Yalnızca benim oluşturduğum görevler için', 'Only for tasks created by me and assigned to me' => 'Yalnızca benim oluşturduğum ve bana atanmış görevler için', - '%A' => '%A', - '%b %e, %Y, %k:%M %p' => '%b %e, %Y, %k:%M %p', - 'New due date: %B %e, %Y' => 'Yeni tamamlanma tarihi: %B %e, %Y', - 'Start date changed: %B %e, %Y' => 'Başlangıç tarihi değiştirildi: %B %e, %Y', '%%Y-%%m-%%d' => '%%Y-%%m-%%d', 'Total for all columns' => 'Tüm sütunlar için toplam', 'You need at least 2 days of data to show the chart.' => 'Grafiği göstermek için en az iki günlük veriye ihtiyaç var.', @@ -786,7 +776,6 @@ return array( 'Not assigned' => 'Atanmamış', 'View advanced search syntax' => 'Gelişmiş arama kodlarını göster', 'Overview' => 'Genel bakış', - '%b %e %Y' => '%b %e %Y', 'Board/Calendar/List view' => 'Tablo/Takvim/Liste görünümü', 'Switch to the board view' => 'Tablo görünümüne geç', 'Switch to the calendar view' => 'Takvim görünümüne geç', @@ -876,8 +865,6 @@ return array( 'End date:' => 'Bitiş tarihi:', 'There is no start date or end date for this project.' => 'Bu proje için başlangıç veya bitiş tarihi yok.', 'Projects Gantt chart' => 'Projeler Gantt diyagramı', - 'Start date: %s' => 'Başlangıç tarihi: %s', - 'End date: %s' => 'Bitiş tarihi: %s', 'Link type' => 'Bağlantı türü', 'Change task color when using a specific task link' => 'Belirli bir görev bağlantısı kullanıldığında görevin rengini değiştir', 'Task link creation or modification' => 'Görev bağlantısı oluşturulması veya değiştirilmesi', @@ -1125,4 +1112,10 @@ return array( // 'Moved:' => '', // 'Task #%d' => '', // 'Sub-tasks' => '', + // 'Date and time format' => '', + // 'Time format' => '', + // 'Start date: ' => '', + // 'End date: ' => '', + // 'New due date: ' => '', + // 'Start date changed: ' => '', ); diff --git a/app/Locale/zh_CN/translations.php b/app/Locale/zh_CN/translations.php index ae90468d..acdcdf30 100644 --- a/app/Locale/zh_CN/translations.php +++ b/app/Locale/zh_CN/translations.php @@ -102,7 +102,6 @@ return array( 'Open a task' => '开一个任务', 'Do you really want to open this task: "%s"?' => '你确定要开这个任务吗?"%s"', 'Back to the board' => '回到看板', - 'Created on %B %e, %Y at %k:%M %p' => '创建时间:%Y/%m/%d %H:%M', 'There is nobody assigned' => '当前无人被指派', 'Column on the board:' => '看板上的栏目:', 'Close this task' => '关闭该任务', @@ -156,7 +155,6 @@ return array( 'Work in progress' => '工作进行中', 'Done' => '完成', 'Application version:' => '应用程序版本:', - '%B %e, %Y at %k:%M %p' => '%Y/%m/%d %H:%M', 'Id' => '编号', '%d closed tasks' => '%d个已关闭任务', 'No task for this project' => '该项目尚无任务', @@ -180,8 +178,6 @@ return array( 'Edit this task' => '编辑该任务', 'Due Date' => '到期时间', 'Invalid date' => '无效日期', - 'Must be done before %B %e, %Y' => '必须在%Y/%m/%d前完成', - '%B %e, %Y' => '%Y/%m/%d', 'Automatic actions' => '自动动作', 'Your automatic action have been created successfully.' => '您的自动动作已成功创建', 'Unable to create your automatic action.' => '无法为您创建自动动作。', @@ -317,7 +313,6 @@ return array( 'Unable to upload the file.' => '无法上传文件', 'Display another project' => '显示其它项目', 'Created by %s' => '创建者:%s', - 'Last modified on %B %e, %Y at %k:%M %p' => '最后修改:%Y/%m/%d/ %H:%M', 'Tasks Export' => '任务导出', 'Tasks exportation for "%s"' => '导出"%s"的任务', 'Start Date' => '开始时间', @@ -589,7 +584,6 @@ return array( 'This task' => '此任务', '<1h' => '<1h', '%dh' => '%dh', - // '%b %e' => '', 'Expand tasks' => '展开任务', 'Collapse tasks' => '收缩任务', 'Expand/collapse tasks' => '展开/收缩任务', @@ -758,10 +752,6 @@ return array( 'Only for tasks assigned to me' => '所有指派给我的任务', 'Only for tasks created by me' => '所有我创建的任务', 'Only for tasks created by me and assigned to me' => '所有我创建的并且指派给我的任务', - // '%A' => '', - // '%b %e, %Y, %k:%M %p' => '', - 'New due date: %B %e, %Y' => '新的超期时间:%B %e, %Y', - 'Start date changed: %B %e, %Y' => '开始时间已变为:%B %e, %Y', // '%%Y-%%m-%%d' => '', 'Total for all columns' => '所有栏目下的', 'You need at least 2 days of data to show the chart.' => '当前柱状图至少需要2天的数据。', @@ -786,7 +776,6 @@ return array( 'Not assigned' => '未指派', 'View advanced search syntax' => '查看高级搜索语法', 'Overview' => '概览', - // '%b %e %Y' => '', 'Board/Calendar/List view' => '看板/日程/列表视图', 'Switch to the board view' => '切换到看板视图', 'Switch to the calendar view' => '切换到日程视图', @@ -876,8 +865,6 @@ return array( 'End date:' => '结束日期', 'There is no start date or end date for this project.' => '当前项目没有开始或结束日期', 'Projects Gantt chart' => '项目甘特图', - 'Start date: %s' => '开始日期%s', - 'End date: %s' => '结束日期%s', 'Link type' => '关联类型', 'Change task color when using a specific task link' => '当任务关联到指定任务时改变颜色', 'Task link creation or modification' => '任务链接创建或更新时间', @@ -1125,4 +1112,10 @@ return array( // 'Moved:' => '', // 'Task #%d' => '', // 'Sub-tasks' => '', + // 'Date and time format' => '', + // 'Time format' => '', + // 'Start date: ' => '', + // 'End date: ' => '', + // 'New due date: ' => '', + // 'Start date changed: ' => '', ); diff --git a/app/Model/File.php b/app/Model/File.php index be62cdb3..46fc4bb9 100644 --- a/app/Model/File.php +++ b/app/Model/File.php @@ -308,7 +308,7 @@ class File extends Base */ public function uploadScreenshot($project_id, $task_id, $blob) { - $original_filename = e('Screenshot taken %s', dt('%B %e, %Y at %k:%M %p', time())).'.png'; + $original_filename = e('Screenshot taken %s', $this->helper->dt->datetime(time())).'.png'; return $this->uploadContent($project_id, $task_id, $original_filename, $blob); } diff --git a/app/Model/TaskCreation.php b/app/Model/TaskCreation.php index 975cc7a3..f7d981fa 100644 --- a/app/Model/TaskCreation.php +++ b/app/Model/TaskCreation.php @@ -49,8 +49,9 @@ class TaskCreation extends Base */ public function prepare(array &$values) { - $this->dateParser->convert($values, array('date_due')); - $this->dateParser->convert($values, array('date_started'), true); + $values = $this->dateParser->convert($values, array('date_due')); + $values = $this->dateParser->convert($values, array('date_started'), true); + $this->removeFields($values, array('another_task')); $this->resetFields($values, array('date_started', 'creator_id', 'owner_id', 'swimlane_id', 'date_due', 'score', 'category_id', 'time_estimated')); diff --git a/app/Model/TaskExport.php b/app/Model/TaskExport.php index 278c0897..ed179a4f 100644 --- a/app/Model/TaskExport.php +++ b/app/Model/TaskExport.php @@ -106,7 +106,7 @@ class TaskExport extends Base $task['score'] = $task['score'] ?: 0; $task['swimlane_id'] = isset($swimlanes[$task['swimlane_id']]) ? $swimlanes[$task['swimlane_id']] : '?'; - $this->dateParser->format($task, array('date_due', 'date_modification', 'date_creation', 'date_started', 'date_completed'), 'Y-m-d'); + $task = $this->dateParser->format($task, array('date_due', 'date_modification', 'date_creation', 'date_started', 'date_completed'), 'Y-m-d'); return $task; } diff --git a/app/Model/TaskModification.php b/app/Model/TaskModification.php index eee7b2e0..8e59b3fe 100644 --- a/app/Model/TaskModification.php +++ b/app/Model/TaskModification.php @@ -84,8 +84,9 @@ class TaskModification extends Base */ public function prepare(array &$values) { - $this->dateParser->convert($values, array('date_due')); - $this->dateParser->convert($values, array('date_started'), true); + $values = $this->dateParser->convert($values, array('date_due')); + $values = $this->dateParser->convert($values, array('date_started'), true); + $this->removeFields($values, array('another_task', 'id')); $this->resetFields($values, array('date_due', 'date_started', 'score', 'category_id', 'time_estimated', 'time_spent')); $this->convertIntegerFields($values, array('priority', 'is_active', 'recurrence_status', 'recurrence_trigger', 'recurrence_factor', 'recurrence_timeframe', 'recurrence_basedate')); diff --git a/app/Template/app/notifications.php b/app/Template/app/notifications.php index 511f377b..4cb3c571 100644 --- a/app/Template/app/notifications.php +++ b/app/Template/app/notifications.php @@ -49,7 +49,7 @@ - + dt->datetime($notification['date_creation']) ?> diff --git a/app/Template/app/tasks.php b/app/Template/app/tasks.php index f4f9b2ad..786d7159 100644 --- a/app/Template/app/tasks.php +++ b/app/Template/app/tasks.php @@ -33,7 +33,7 @@ - + dt->date($task['date_due']) ?> diff --git a/app/Template/board/task_footer.php b/app/Template/board/task_footer.php index 1912dd83..73e68602 100644 --- a/app/Template/board/task_footer.php +++ b/app/Template/board/task_footer.php @@ -22,7 +22,7 @@ - + dt->date($task['date_due']) ?> diff --git a/app/Template/board/tooltip_comments.php b/app/Template/board/tooltip_comments.php index 2e2c0c1e..ca91e13f 100644 --- a/app/Template/board/tooltip_comments.php +++ b/app/Template/board/tooltip_comments.php @@ -4,7 +4,7 @@ e($comment['name'] ?: $comment['username']) ?> @ - + dt->datetime($comment['date_creation']) ?>

      diff --git a/app/Template/comment/show.php b/app/Template/comment/show.php index 26b300e8..2c9b426f 100644 --- a/app/Template/comment/show.php +++ b/app/Template/comment/show.php @@ -9,7 +9,7 @@ e($comment['name'] ?: $comment['username']) ?> @ - + dt->datetime($comment['date_creation']) ?>

      diff --git a/app/Template/config/application.php b/app/Template/config/application.php index 35d85dd8..48b04cf9 100644 --- a/app/Template/config/application.php +++ b/app/Template/config/application.php @@ -19,6 +19,12 @@ form->select('application_date_format', $date_formats, $values, $errors) ?>

      + form->label(t('Date and time format'), 'application_datetime_format') ?> + form->select('application_datetime_format', $datetime_formats, $values, $errors) ?> + + form->label(t('Time format'), 'application_time_format') ?> + form->select('application_time_format', $time_formats, $values, $errors) ?> + form->checkbox('password_reset', t('Enable "Forget Password"'), 1, $values['password_reset'] == 1) ?> form->label(t('Custom Stylesheet'), 'application_stylesheet') ?> diff --git a/app/Template/event/events.php b/app/Template/event/events.php index aec0b29e..bbb01be4 100644 --- a/app/Template/event/events.php +++ b/app/Template/event/events.php @@ -14,7 +14,7 @@ text->contains($event['event_name'], 'comment')): ?> -   +  dt->datetime($event['date_creation']) ?>

      diff --git a/app/Template/file/show.php b/app/Template/file/show.php index b87739a8..7a4d38b4 100644 --- a/app/Template/file/show.php +++ b/app/Template/file/show.php @@ -14,7 +14,7 @@

      e($file['name']) ?> - '.t('uploaded on: %s', dt('%B %e, %Y at %k:%M %p', $file['date'])).'
      '.t('size: %s', $this->text->bytes($file['size'])) ?>'> + '.t('uploaded on: %s', $this->dt->datetime($file['date'])).'
      '.t('size: %s', $this->text->bytes($file['size'])) ?>'>

      @@ -38,7 +38,7 @@ e($file['name']) ?> - '.t('uploaded on: %s', dt('%B %e, %Y at %k:%M %p', $file['date'])).'
      '.t('size: %s', $this->text->bytes($file['size'])) ?>'> + '.t('uploaded on: %s', $this->dt->datetime($file['date'])).'
      '.t('size: %s', $this->text->bytes($file['size'])) ?>'>
      diff --git a/app/Template/listing/show.php b/app/Template/listing/show.php index c24110f4..e7aa5947 100644 --- a/app/Template/listing/show.php +++ b/app/Template/listing/show.php @@ -43,7 +43,7 @@ - + dt->date($task['date_due']) ?> diff --git a/app/Template/notification/task_create.php b/app/Template/notification/task_create.php index 1d834d44..fb7898fc 100644 --- a/app/Template/notification/task_create.php +++ b/app/Template/notification/task_create.php @@ -2,11 +2,11 @@
      • - + dt->datetime($task['date_creation']) ?>
      • - + dt->date($task['date_due']) ?>
      • diff --git a/app/Template/notification/task_overdue.php b/app/Template/notification/task_overdue.php index a231937b..d7e6ff5a 100644 --- a/app/Template/notification/task_overdue.php +++ b/app/Template/notification/task_overdue.php @@ -9,7 +9,7 @@ e($task['title']) ?> - () + (dt->date($task['date_due']) ?>) () diff --git a/app/Template/project/index.php b/app/Template/project/index.php index c5dd267c..8d384e58 100644 --- a/app/Template/project/index.php +++ b/app/Template/project/index.php @@ -57,10 +57,10 @@ url->link($this->e($project['name']), 'project', 'show', array('project_id' => $project['id'])) ?> - + dt->date($project['start_date']) ?> - + dt->date($project['end_date']) ?> 0): ?> diff --git a/app/Template/project/show.php b/app/Template/project/show.php index 5f1aefc1..166b8902 100644 --- a/app/Template/project/show.php +++ b/app/Template/project/show.php @@ -21,15 +21,15 @@ -
      • +
      • dt->datetime($project['last_modified']) ?>
      • -
      • +
      • dt->date($project['start_date']) ?>
      • -
      • +
      • dt->date($project['end_date']) ?>
      • 0): ?> diff --git a/app/Template/project_user/tasks.php b/app/Template/project_user/tasks.php index f4fc2723..8d1cbd96 100644 --- a/app/Template/project_user/tasks.php +++ b/app/Template/project_user/tasks.php @@ -33,10 +33,10 @@ - + dt->date($task['date_started']) ?> - + dt->date($task['date_due']) ?> diff --git a/app/Template/search/results.php b/app/Template/search/results.php index 88eed87c..3bb0e603 100644 --- a/app/Template/search/results.php +++ b/app/Template/search/results.php @@ -38,7 +38,7 @@ - + dt->date($task['date_due']) ?> diff --git a/app/Template/task/changes.php b/app/Template/task/changes.php index f288a8f4..db844857 100644 --- a/app/Template/task/changes.php +++ b/app/Template/task/changes.php @@ -31,7 +31,7 @@ if (empty($task['date_due'])) { echo '
      • '.t('The due date have been removed').'
      • '; } else { - echo '
      • '.dt('New due date: %B %e, %Y', $task['date_due']).'
      • '; + echo '
      • '.t('New due date: ').$this->dt->date($task['date_due']).'
      • '; } break; case 'description': @@ -56,7 +56,7 @@ break; case 'date_started': if ($value != 0) { - echo '
      • '.dt('Start date changed: %B %e, %Y', $task['date_started']).'
      • '; + echo '
      • '.t('Start date changed: ').$this->dt->datetime($task['date_started']).'
      • '; } break; default: diff --git a/app/Template/task/details.php b/app/Template/task/details.php index 838eb260..5c2e3cff 100644 --- a/app/Template/task/details.php +++ b/app/Template/task/details.php @@ -79,7 +79,7 @@
      • - + dt->date($task['date_due']) ?>
      • @@ -100,28 +100,28 @@
        • - + dt->datetime($task['date_creation']) ?>
        • - + dt->datetime($task['date_modification']) ?>
        • - + dt->datetime($task['date_completed']) ?>
        • - + dt->datetime($task['date_started']) ?>
        • - + dt->datetime($task['date_moved']) ?>
        diff --git a/app/Template/task/time_tracking_details.php b/app/Template/task/time_tracking_details.php index 85ad5567..147c5109 100644 --- a/app/Template/task/time_tracking_details.php +++ b/app/Template/task/time_tracking_details.php @@ -16,8 +16,8 @@ url->link($this->e($record['user_fullname'] ?: $record['username']), 'user', 'show', array('user_id' => $record['user_id'])) ?> - - + dt->datetime($record['start']) ?> + dt->datetime($record['end']) ?> diff --git a/app/Template/task/transitions.php b/app/Template/task/transitions.php index 2ca2387f..d79c2fd9 100644 --- a/app/Template/task/transitions.php +++ b/app/Template/task/transitions.php @@ -15,7 +15,7 @@ - + dt->datetime($transition['date']) ?> e($transition['src_column']) ?> e($transition['dst_column']) ?> url->link($this->e($transition['name'] ?: $transition['username']), 'user', 'show', array('user_id' => $transition['user_id'])) ?> diff --git a/app/Template/task_external_link/show.php b/app/Template/task_external_link/show.php index 2dc3d1dd..12d8e4f6 100644 --- a/app/Template/task_external_link/show.php +++ b/app/Template/task_external_link/show.php @@ -31,7 +31,7 @@ e($link['creator_name'] ?: $link['creator_username']) ?> - + dt->date($link['date_creation']) ?> user->hasProjectAccess('TaskExternalLink', 'edit', $task['project_id'])): ?> diff --git a/app/Template/user/last.php b/app/Template/user/last.php index 8879466e..d6c86391 100644 --- a/app/Template/user/last.php +++ b/app/Template/user/last.php @@ -14,7 +14,7 @@ - + dt->datetime($login['date_creation']) ?> e($login['auth_type']) ?> e($login['ip']) ?> e($login['user_agent']) ?> diff --git a/app/Template/user/password_reset.php b/app/Template/user/password_reset.php index b4c9a0c4..4e9063ef 100644 --- a/app/Template/user/password_reset.php +++ b/app/Template/user/password_reset.php @@ -15,8 +15,8 @@ - - + dt->datetime($token['date_creation']) ?> + dt->datetime($token['date_expiration']) ?> e($token['ip']) ?> e($token['user_agent']) ?> diff --git a/app/Template/user/sessions.php b/app/Template/user/sessions.php index 7a66c5ad..8db02430 100644 --- a/app/Template/user/sessions.php +++ b/app/Template/user/sessions.php @@ -15,8 +15,8 @@ - - + dt->datetime($session['date_creation']) ?> + dt->datetime($session['expiration']) ?> e($session['ip']) ?> e($session['user_agent']) ?> url->link(t('Remove'), 'User', 'removeSession', array('user_id' => $user['id'], 'id' => $session['id']), true) ?> diff --git a/app/Template/user/timesheet.php b/app/Template/user/timesheet.php index 5c0d3af8..4a6e42c5 100644 --- a/app/Template/user/timesheet.php +++ b/app/Template/user/timesheet.php @@ -18,8 +18,8 @@ url->link($this->e($record['task_title']), 'task', 'show', array('project_id' => $record['project_id'], 'task_id' => $record['task_id'])) ?> url->link($this->e($record['subtask_title']), 'task', 'show', array('project_id' => $record['project_id'], 'task_id' => $record['task_id'])) ?> - - + dt->datetime($record['start']) ?> + dt->datetime($record['end']) ?> diff --git a/app/Validator/TaskValidator.php b/app/Validator/TaskValidator.php index 7b73aeba..1a77dd32 100644 --- a/app/Validator/TaskValidator.php +++ b/app/Validator/TaskValidator.php @@ -40,8 +40,8 @@ class TaskValidator extends Base new Validators\Integer('priority', t('This value must be an integer')), new Validators\MaxLength('title', t('The maximum length is %d characters', 200), 200), new Validators\MaxLength('reference', t('The maximum length is %d characters', 50), 50), - new Validators\Date('date_due', t('Invalid date'), $this->dateParser->getDateFormats()), - new Validators\Date('date_started', t('Invalid date'), $this->dateParser->getAllFormats()), + new Validators\Date('date_due', t('Invalid date'), $this->dateParser->getDateFormats(true)), + new Validators\Date('date_started', t('Invalid date'), $this->dateParser->getDateTimeFormats(true)), new Validators\Numeric('time_spent', t('This value must be numeric')), new Validators\Numeric('time_estimated', t('This value must be numeric')), ); diff --git a/app/functions.php b/app/functions.php index fe6e6757..b9b30834 100644 --- a/app/functions.php +++ b/app/functions.php @@ -31,13 +31,3 @@ function n($value) { return Translator::getInstance()->number($value); } - -/** - * Translate a date - * - * @return string - */ -function dt($format, $timestamp) -{ - return Translator::getInstance()->datetime($format, $timestamp); -} diff --git a/doc/translations.markdown b/doc/translations.markdown index 629c4355..00707e1c 100644 --- a/doc/translations.markdown +++ b/doc/translations.markdown @@ -5,7 +5,7 @@ How to translate Kanboard to a new language? -------------------------------------------- - Translations are stored inside the directory `app/Locale` -- There is subdirectory for each language, for example in French we have `fr_FR`, Italian `it_IT` etc. +- There is a subdirectory for each language, for example in French we have `fr_FR`, Italian `it_IT` etc. - A translation is a PHP file that returns an Array with a key-value pairs - The key is the original text in English and the value is the translation of the corresponding language - **French translations are always up to date** @@ -34,20 +34,9 @@ Translations are displayed with the following functions in the source code: - `t()`: display text with HTML escaping - `e()`: display text without HTML escaping -- `dt()`: display date and time using the `strftime()` function formats Always use the english version in the source code. -### Date and time translation - -Date strings use the function `strftime()` to format the date. - -For example, the original English version can be defined like that `Created on %B %e, %Y at %k:%M %p` and that will output something like that `Created on January 11, 2015 at 15:19 PM`. The French version can be modified to display a different format, `Créé le %d/%m/%Y à %H:%M` and the result will be `Créé le 11/01/2015 à 15:19`. - -All formats are available in the [PHP documentation](http://php.net/strftime). - -### Placeholders - Text strings use the function `sprintf()` to replace elements: - `%s` is used to replace a string diff --git a/tests/units/Core/DateParserTest.php b/tests/units/Core/DateParserTest.php index 0d345784..dc3366b3 100644 --- a/tests/units/Core/DateParserTest.php +++ b/tests/units/Core/DateParserTest.php @@ -6,79 +6,167 @@ use Kanboard\Core\DateParser; class DateParserTest extends Base { + public function testGetTimeFormats() + { + $dateParser = new DateParser($this->container); + $this->assertCount(2, $dateParser->getTimeFormats()); + $this->assertContains('H:i', $dateParser->getTimeFormats()); + $this->assertContains('g:i a', $dateParser->getTimeFormats()); + } + + public function testGetDateFormats() + { + $dateParser = new DateParser($this->container); + $this->assertCount(4, $dateParser->getDateFormats()); + $this->assertCount(6, $dateParser->getDateFormats(true)); + $this->assertContains('d/m/Y', $dateParser->getDateFormats()); + $this->assertNotContains('Y-m-d', $dateParser->getDateFormats()); + $this->assertContains('Y-m-d', $dateParser->getDateFormats(true)); + } + + public function testGetDateTimeFormats() + { + $dateParser = new DateParser($this->container); + $this->assertCount(8, $dateParser->getDateTimeFormats()); + $this->assertCount(12, $dateParser->getDateTimeFormats(true)); + $this->assertContains('d/m/Y H:i', $dateParser->getDateTimeFormats()); + $this->assertNotContains('Y-m-d H:i', $dateParser->getDateTimeFormats()); + $this->assertContains('Y-m-d g:i a', $dateParser->getDateTimeFormats(true)); + } + + public function testGetAllDateFormats() + { + $dateParser = new DateParser($this->container); + $this->assertCount(12, $dateParser->getAllDateFormats()); + $this->assertCount(18, $dateParser->getAllDateFormats(true)); + $this->assertContains('d/m/Y', $dateParser->getAllDateFormats()); + $this->assertContains('d/m/Y H:i', $dateParser->getAllDateFormats()); + $this->assertNotContains('Y-m-d H:i', $dateParser->getAllDateFormats()); + $this->assertContains('Y-m-d g:i a', $dateParser->getAllDateFormats(true)); + $this->assertContains('Y-m-d', $dateParser->getAllDateFormats(true)); + } + + public function testGetAllAvailableFormats() + { + $dateParser = new DateParser($this->container); + + $formats = $dateParser->getAvailableFormats($dateParser->getDateFormats()); + $this->assertArrayHasKey('d/m/Y', $formats); + $this->assertContains(date('d/m/Y'), $formats); + + $formats = $dateParser->getAvailableFormats($dateParser->getDateTimeFormats()); + $this->assertArrayHasKey('d/m/Y H:i', $formats); + $this->assertContains(date('d/m/Y H:i'), $formats); + + $formats = $dateParser->getAvailableFormats($dateParser->getAllDateFormats()); + $this->assertArrayHasKey('d/m/Y', $formats); + $this->assertContains(date('d/m/Y'), $formats); + $this->assertArrayHasKey('d/m/Y H:i', $formats); + $this->assertContains(date('d/m/Y H:i'), $formats); + } + + public function testGetTimestamp() + { + $dateParser = new DateParser($this->container); + + $this->assertEquals(1393995600, $dateParser->getTimestamp(1393995600)); + $this->assertEquals('2014-03-05', date('Y-m-d', $dateParser->getTimestamp('2014-03-05'))); + $this->assertEquals('2014-03-05', date('Y-m-d', $dateParser->getTimestamp('2014_03_05'))); + $this->assertEquals('2014-03-05', date('Y-m-d', $dateParser->getTimestamp('03/05/2014'))); + $this->assertEquals('2014-03-25 17:18', date('Y-m-d H:i', $dateParser->getTimestamp('03/25/2014 5:18 pm'))); + $this->assertEquals('2014-03-25 05:18', date('Y-m-d H:i', $dateParser->getTimestamp('03/25/2014 5:18 am'))); + $this->assertEquals('2014-03-25 17:18', date('Y-m-d H:i', $dateParser->getTimestamp('03/25/2014 5:18pm'))); + $this->assertEquals('2014-03-25 23:14', date('Y-m-d H:i', $dateParser->getTimestamp('03/25/2014 23:14'))); + $this->assertEquals('2014-03-29 23:14', date('Y-m-d H:i', $dateParser->getTimestamp('2014_03_29 23:14'))); + $this->assertEquals('2014-03-29 23:14', date('Y-m-d H:i', $dateParser->getTimestamp('2014-03-29 23:14'))); + } + public function testDateRange() { - $d = new DateParser($this->container); + $dateParser = new DateParser($this->container); - $this->assertTrue($d->withinDateRange(new DateTime('2015-03-14 15:30:00'), new DateTime('2015-03-14 15:00:00'), new DateTime('2015-03-14 16:00:00'))); - $this->assertFalse($d->withinDateRange(new DateTime('2015-03-14 15:30:00'), new DateTime('2015-03-14 16:00:00'), new DateTime('2015-03-14 17:00:00'))); + $this->assertTrue($dateParser->withinDateRange(new DateTime('2015-03-14 15:30:00'), new DateTime('2015-03-14 15:00:00'), new DateTime('2015-03-14 16:00:00'))); + $this->assertFalse($dateParser->withinDateRange(new DateTime('2015-03-14 15:30:00'), new DateTime('2015-03-14 16:00:00'), new DateTime('2015-03-14 17:00:00'))); + } + + public function testGetHours() + { + $dateParser = new DateParser($this->container); + + $this->assertEquals(1, $dateParser->getHours(new DateTime('2015-03-14 15:00:00'), new DateTime('2015-03-14 16:00:00'))); + $this->assertEquals(2.5, $dateParser->getHours(new DateTime('2015-03-14 15:00:00'), new DateTime('2015-03-14 17:30:00'))); + $this->assertEquals(2.75, $dateParser->getHours(new DateTime('2015-03-14 15:00:00'), new DateTime('2015-03-14 17:45:00'))); + $this->assertEquals(3, $dateParser->getHours(new DateTime('2015-03-14 14:57:00'), new DateTime('2015-03-14 17:58:00'))); + $this->assertEquals(3, $dateParser->getHours(new DateTime('2015-03-14 14:57:00'), new DateTime('2015-03-14 11:58:00'))); } public function testRoundSeconds() { - $d = new DateParser($this->container); - $this->assertEquals('16:30', date('H:i', $d->getRoundedSeconds(strtotime('16:28')))); - $this->assertEquals('16:00', date('H:i', $d->getRoundedSeconds(strtotime('16:02')))); - $this->assertEquals('16:15', date('H:i', $d->getRoundedSeconds(strtotime('16:14')))); - $this->assertEquals('17:00', date('H:i', $d->getRoundedSeconds(strtotime('16:58')))); + $dateParser = new DateParser($this->container); + $this->assertEquals('16:30', date('H:i', $dateParser->getRoundedSeconds(strtotime('16:28')))); + $this->assertEquals('16:00', date('H:i', $dateParser->getRoundedSeconds(strtotime('16:02')))); + $this->assertEquals('16:15', date('H:i', $dateParser->getRoundedSeconds(strtotime('16:14')))); + $this->assertEquals('17:00', date('H:i', $dateParser->getRoundedSeconds(strtotime('16:58')))); } - public function testGetHours() + public function testGetIsoDate() { - $d = new DateParser($this->container); + $dateParser = new DateParser($this->container); + + $this->assertEquals('2016-02-06', $dateParser->getIsoDate(1454786217)); + $this->assertEquals('2014-03-05', $dateParser->getIsoDate('2014-03-05')); + $this->assertEquals('2014-03-05', $dateParser->getIsoDate('2014_03_05')); + $this->assertEquals('2014-03-05', $dateParser->getIsoDate('03/05/2014')); + $this->assertEquals('2014-03-25', $dateParser->getIsoDate('03/25/2014 5:18 pm')); + $this->assertEquals('2014-03-25', $dateParser->getIsoDate('03/25/2014 5:18 am')); + $this->assertEquals('2014-03-25', $dateParser->getIsoDate('03/25/2014 5:18pm')); + $this->assertEquals('2014-03-25', $dateParser->getIsoDate('03/25/2014 23:14')); + $this->assertEquals('2014-03-29', $dateParser->getIsoDate('2014_03_29 23:14')); + $this->assertEquals('2014-03-29', $dateParser->getIsoDate('2014-03-29 23:14')); + } - $this->assertEquals(1, $d->getHours(new DateTime('2015-03-14 15:00:00'), new DateTime('2015-03-14 16:00:00'))); - $this->assertEquals(2.5, $d->getHours(new DateTime('2015-03-14 15:00:00'), new DateTime('2015-03-14 17:30:00'))); - $this->assertEquals(2.75, $d->getHours(new DateTime('2015-03-14 15:00:00'), new DateTime('2015-03-14 17:45:00'))); - $this->assertEquals(3, $d->getHours(new DateTime('2015-03-14 14:57:00'), new DateTime('2015-03-14 17:58:00'))); - $this->assertEquals(3, $d->getHours(new DateTime('2015-03-14 14:57:00'), new DateTime('2015-03-14 11:58:00'))); + public function testGetTimestampFromIsoFormat() + { + $dateParser = new DateParser($this->container); + $this->assertEquals('2014-03-05 00:00', date('Y-m-d H:i', $dateParser->getTimestampFromIsoFormat('2014-03-05'))); + $this->assertEquals(date('Y-m-d 00:00', strtotime('+2 days')), date('Y-m-d H:i', $dateParser->getTimestampFromIsoFormat(strtotime('+2 days')))); } - public function testValidDate() + public function testRemoveTimeFromTimestamp() { - $d = new DateParser($this->container); - - $this->assertEquals('2014-03-05', date('Y-m-d', $d->getValidDate('2014-03-05', 'Y-m-d'))); - $this->assertEquals('2014-03-05', date('Y-m-d', $d->getValidDate('2014_03_05', 'Y_m_d'))); - $this->assertEquals('2014-03-05', date('Y-m-d', $d->getValidDate('05/03/2014', 'd/m/Y'))); - $this->assertEquals('2014-03-05', date('Y-m-d', $d->getValidDate('03/05/2014', 'm/d/Y'))); - $this->assertEquals('2014-03-05', date('Y-m-d', $d->getValidDate('3/5/2014', 'm/d/Y'))); - $this->assertEquals('2014-03-05', date('Y-m-d', $d->getValidDate('5/3/2014', 'd/m/Y'))); - $this->assertEquals('2014-03-05', date('Y-m-d', $d->getValidDate('5/3/14', 'd/m/y'))); - $this->assertEquals(0, $d->getValidDate('5/3/14', 'd/m/Y')); - $this->assertEquals(0, $d->getValidDate('5-3-2014', 'd/m/Y')); + $dateParser = new DateParser($this->container); + $this->assertEquals('2016-02-06 00:00', date('Y-m-d H:i', $dateParser->removeTimeFromTimestamp(1454786217))); } - public function testGetTimestamp() + public function testFormat() { - $d = new DateParser($this->container); - - $this->assertEquals('2014-03-05', date('Y-m-d', $d->getTimestamp('2014-03-05'))); - $this->assertEquals('2014-03-05', date('Y-m-d', $d->getTimestamp('2014_03_05'))); - $this->assertEquals('2014-03-05', date('Y-m-d', $d->getTimestamp('03/05/2014'))); - $this->assertEquals('2014-03-25 17:18', date('Y-m-d H:i', $d->getTimestamp('03/25/2014 5:18 pm'))); - $this->assertEquals('2014-03-25 05:18', date('Y-m-d H:i', $d->getTimestamp('03/25/2014 5:18 am'))); - $this->assertEquals('2014-03-25 05:18', date('Y-m-d H:i', $d->getTimestamp('03/25/2014 5:18am'))); - $this->assertEquals('2014-03-25 23:14', date('Y-m-d H:i', $d->getTimestamp('03/25/2014 23:14'))); - $this->assertEquals('2014-03-29 23:14', date('Y-m-d H:i', $d->getTimestamp('2014_03_29 23:14'))); - $this->assertEquals('2014-03-29 23:14', date('Y-m-d H:i', $d->getTimestamp('2014-03-29 23:14'))); + $dateParser = new DateParser($this->container); + $values['date'] = '1454787006'; + + $this->assertEquals(array('date' => '06/02/2016'), $dateParser->format($values, array('date'), 'd/m/Y')); + $this->assertEquals(array('date' => '02/06/2016 7:30 pm'), $dateParser->format($values, array('date'), 'm/d/Y g:i a')); } public function testConvert() { - $d = new DateParser($this->container); - + $dateParser = new DateParser($this->container); $values = array( 'date_due' => '2015-01-25', - 'date_started' => '2015_01_25', + 'date_started' => '2015-01-25 17:25', ); - $d->convert($values, array('date_due', 'date_started')); + $this->assertEquals( + array('date_due' => 1422144000, 'date_started' => 1422144000), + $dateParser->convert($values, array('date_due', 'date_started')) + ); - $this->assertEquals(mktime(0, 0, 0, 1, 25, 2015), $values['date_due']); - $this->assertEquals('2015-01-25', date('Y-m-d', $values['date_due'])); + $values = array( + 'date_started' => '2015-01-25 17:25', + ); - $this->assertEquals(mktime(0, 0, 0, 1, 25, 2015), $values['date_started']); - $this->assertEquals('2015-01-25', date('Y-m-d', $values['date_started'])); + $this->assertEquals( + array('date_started' => 1422206700), + $dateParser->convert($values, array('date_due', 'date_started'), true) + ); } } diff --git a/tests/units/Helper/DatetimeHelperTest.php b/tests/units/Helper/DatetimeHelperTest.php index 8e9c461b..f27a2eb9 100644 --- a/tests/units/Helper/DatetimeHelperTest.php +++ b/tests/units/Helper/DatetimeHelperTest.php @@ -6,23 +6,45 @@ use Kanboard\Helper\Dt; class DatetimeHelperTest extends Base { + public function testGetTime() + { + $helper = new Dt($this->container); + $this->assertEquals('17:25', $helper->time(1422206700)); + } + + public function testGetDate() + { + $helper = new Dt($this->container); + $this->assertEquals('01/25/2015', $helper->date(1422206700)); + $this->assertEquals('01/25/2015', $helper->date('2015-01-25')); + $this->assertEquals('', $helper->date('0')); + $this->assertEquals('', $helper->date(0)); + $this->assertEquals('', $helper->date('')); + } + + public function testGetDatetime() + { + $helper = new Dt($this->container); + $this->assertEquals('01/25/2015 17:25', $helper->datetime(1422206700)); + } + public function testAge() { - $h = new Dt($this->container); - - $this->assertEquals('<15m', $h->age(0, 30)); - $this->assertEquals('<30m', $h->age(0, 1000)); - $this->assertEquals('<1h', $h->age(0, 3000)); - $this->assertEquals('~2h', $h->age(0, 2*3600)); - $this->assertEquals('1d', $h->age(0, 30*3600)); - $this->assertEquals('2d', $h->age(0, 65*3600)); + $helper = new Dt($this->container); + + $this->assertEquals('<15m', $helper->age(0, 30)); + $this->assertEquals('<30m', $helper->age(0, 1000)); + $this->assertEquals('<1h', $helper->age(0, 3000)); + $this->assertEquals('~2h', $helper->age(0, 2*3600)); + $this->assertEquals('1d', $helper->age(0, 30*3600)); + $this->assertEquals('2d', $helper->age(0, 65*3600)); } public function testGetDayHours() { - $h = new Dt($this->container); + $helper = new Dt($this->container); - $slots = $h->getDayHours(); + $slots = $helper->getDayHours(); $this->assertNotEmpty($slots); $this->assertCount(48, $slots); @@ -36,9 +58,9 @@ class DatetimeHelperTest extends Base public function testGetWeekDays() { - $h = new Dt($this->container); + $helper = new Dt($this->container); - $slots = $h->getWeekDays(); + $slots = $helper->getWeekDays(); $this->assertNotEmpty($slots); $this->assertCount(7, $slots); @@ -48,9 +70,9 @@ class DatetimeHelperTest extends Base public function testGetWeekDay() { - $h = new Dt($this->container); + $helper = new Dt($this->container); - $this->assertEquals('Monday', $h->getWeekDay(1)); - $this->assertEquals('Sunday', $h->getWeekDay(7)); + $this->assertEquals('Monday', $helper->getWeekDay(1)); + $this->assertEquals('Sunday', $helper->getWeekDay(7)); } } diff --git a/tests/units/Model/TaskCreationTest.php b/tests/units/Model/TaskCreationTest.php index 19b3b0d1..781a7147 100644 --- a/tests/units/Model/TaskCreationTest.php +++ b/tests/units/Model/TaskCreationTest.php @@ -295,7 +295,7 @@ class TaskCreationTest extends Base $task = $tf->getById(2); $this->assertNotEmpty($task); $this->assertEquals(2, $task['id']); - $this->assertEquals($timestamp, $task['date_due']); + $this->assertEquals(date('Y-m-d 00:00', $timestamp), date('Y-m-d 00:00', $task['date_due'])); $task = $tf->getById(3); $this->assertEquals(3, $task['id']); @@ -422,6 +422,6 @@ class TaskCreationTest extends Base $task = $tf->getById(1); $this->assertNotEmpty($task); - $this->assertEquals('2050-01-10 12:30', date('Y-m-d H:i', $task['date_due'])); + $this->assertEquals('2050-01-10 00:00', date('Y-m-d H:i', $task['date_due'])); } } diff --git a/tests/units/Model/TaskDuplicationTest.php b/tests/units/Model/TaskDuplicationTest.php index 798b3835..8649c6b0 100644 --- a/tests/units/Model/TaskDuplicationTest.php +++ b/tests/units/Model/TaskDuplicationTest.php @@ -2,6 +2,7 @@ require_once __DIR__.'/../Base.php'; +use Kanboard\Core\DateParser; use Kanboard\Model\Task; use Kanboard\Model\TaskCreation; use Kanboard\Model\TaskDuplication; @@ -665,6 +666,7 @@ class TaskDuplicationTest extends Base $tf = new TaskFinder($this->container); $p = new Project($this->container); $c = new Category($this->container); + $dp = new DateParser($this->container); $this->assertEquals(1, $p->create(array('name' => 'test1'))); @@ -685,7 +687,7 @@ class TaskDuplicationTest extends Base $this->assertNotEmpty($task); $this->assertEquals(Task::RECURRING_STATUS_PROCESSED, $task['recurrence_status']); $this->assertEquals(2, $task['recurrence_child']); - $this->assertEquals(1436561776, $task['date_due'], '', 2); + $this->assertEquals(1436486400, $task['date_due'], '', 2); $task = $tf->getById(2); $this->assertNotEmpty($task); @@ -695,6 +697,6 @@ class TaskDuplicationTest extends Base $this->assertEquals(Task::RECURRING_BASEDATE_TRIGGERDATE, $task['recurrence_basedate']); $this->assertEquals(1, $task['recurrence_parent']); $this->assertEquals(2, $task['recurrence_factor']); - $this->assertEquals(strtotime('+2 days'), $task['date_due'], '', 2); + $this->assertEquals($dp->removeTimeFromTimestamp(strtotime('+2 days')), $task['date_due'], '', 2); } } -- cgit v1.2.3 From 2d27c36a71f08bea60a992b051bfb8a2d8bd06b6 Mon Sep 17 00:00:00 2001 From: Frederic Guillot Date: Sat, 20 Feb 2016 10:46:10 -0500 Subject: Use inline popup to create new columns --- ChangeLog | 1 + app/Controller/Column.php | 50 +++++++++++++++++++++++---------------- app/Template/column/create.php | 41 ++++++++++++++++++++++++++++++++ app/Template/column/index.php | 44 +++++----------------------------- app/Validator/ColumnValidator.php | 35 ++++++++++++++++++++------- 5 files changed, 103 insertions(+), 68 deletions(-) create mode 100644 app/Template/column/create.php (limited to 'app/Validator') diff --git a/ChangeLog b/ChangeLog index fa2c2597..31b089b0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -11,6 +11,7 @@ New features: Improvements: +* Use inline popup to create new columns * Improve filter box design * Improve image thumbnails and files table * Add confirmation inline popup to remove custom filter diff --git a/app/Controller/Column.php b/app/Controller/Column.php index 3201c549..77204164 100644 --- a/app/Controller/Column.php +++ b/app/Controller/Column.php @@ -15,20 +15,12 @@ class Column extends Base * * @access public */ - public function index(array $values = array(), array $errors = array()) + public function index() { $project = $this->getProject(); $columns = $this->board->getColumns($project['id']); - foreach ($columns as $column) { - $values['title['.$column['id'].']'] = $column['title']; - $values['description['.$column['id'].']'] = $column['description']; - $values['task_limit['.$column['id'].']'] = $column['task_limit'] ?: null; - } - $this->response->html($this->helper->layout->project('column/index', array( - 'errors' => $errors, - 'values' => $values + array('project_id' => $project['id']), 'columns' => $columns, 'project' => $project, 'title' => t('Edit board') @@ -36,33 +28,49 @@ class Column extends Base } /** - * Validate and add a new column + * Show form to create a new column * * @access public */ - public function create() + public function create(array $values = array(), array $errors = array()) { $project = $this->getProject(); $columns = $this->board->getColumnsList($project['id']); - $data = $this->request->getValues(); - $values = array(); - foreach ($columns as $column_id => $column_title) { - $values['title['.$column_id.']'] = $column_title; + if (empty($values)) { + $values = array('project_id' => $project['id']); } - list($valid, $errors) = $this->columnValidator->validateCreation($data); + $this->response->html($this->template->render('column/create', array( + 'values' => $values, + 'errors' => $errors, + 'project' => $project, + 'title' => t('Add a new column') + ))); + } + + /** + * Validate and add a new column + * + * @access public + */ + public function save() + { + $project = $this->getProject(); + $values = $this->request->getValues(); + + list($valid, $errors) = $this->columnValidator->validateCreation($values); if ($valid) { - if ($this->board->addColumn($project['id'], $data['title'], $data['task_limit'], $data['description'])) { - $this->flash->success(t('Board updated successfully.')); - $this->response->redirect($this->helper->url->to('column', 'index', array('project_id' => $project['id']))); + if ($this->board->addColumn($project['id'], $values['title'], $values['task_limit'], $values['description'])) { + $this->flash->success(t('Column created successfully.')); + return $this->response->redirect($this->helper->url->to('column', 'index', array('project_id' => $project['id'])), true); } else { - $this->flash->failure(t('Unable to update this board.')); + $errors['title'] = array(t('Another column with the same title exists in the project')); } } - $this->index($values, $errors); + $this->create($values, $errors); } /** diff --git a/app/Template/column/create.php b/app/Template/column/create.php new file mode 100644 index 00000000..58b130f5 --- /dev/null +++ b/app/Template/column/create.php @@ -0,0 +1,41 @@ + +
        + + form->csrf() ?> + + form->hidden('project_id', $values) ?> + + form->label(t('Title'), 'title') ?> + form->text('title', $values, $errors, array('autofocus', 'required', 'maxlength="50"')) ?> + + form->label(t('Task limit'), 'task_limit') ?> + form->number('task_limit', $values, $errors) ?> + + form->label(t('Description'), 'description') ?> + +
        +
        + form->textarea('description', $values, $errors) ?> +
        +
        +
        +
        +
          +
        • + +
        • +
        • + +
        • +
        +
        +
        url->doc(t('Write your text in Markdown'), 'syntax-guide') ?>
        + +
        + + + url->link(t('cancel'), 'column', 'index', array('project_id' => $project['id']), false, 'close-popover') ?> +
        +
        \ No newline at end of file diff --git a/app/Template/column/index.php b/app/Template/column/index.php index 85d46f26..8d95dd48 100644 --- a/app/Template/column/index.php +++ b/app/Template/column/index.php @@ -1,5 +1,11 @@ @@ -52,41 +58,3 @@ - -

        -
        - - form->csrf() ?> - - form->hidden('project_id', $values) ?> - - form->label(t('Title'), 'title') ?> - form->text('title', $values, $errors, array('required', 'maxlength="50"')) ?> - - form->label(t('Task limit'), 'task_limit') ?> - form->number('task_limit', $values, $errors) ?> - - form->label(t('Description'), 'description') ?> - -
        -
        - form->textarea('description', $values, $errors) ?> -
        -
        -
        -
        -
          -
        • - -
        • -
        • - -
        • -
        -
        -
        url->doc(t('Write your text in Markdown'), 'syntax-guide') ?>
        - -
        - -
        -
        \ No newline at end of file diff --git a/app/Validator/ColumnValidator.php b/app/Validator/ColumnValidator.php index 4c644e8a..f0f1659b 100644 --- a/app/Validator/ColumnValidator.php +++ b/app/Validator/ColumnValidator.php @@ -22,11 +22,12 @@ class ColumnValidator extends Base */ public function validateModification(array $values) { - $v = new Validator($values, array( - new Validators\Integer('task_limit', t('This value must be an integer')), - new Validators\Required('title', t('The title is required')), - new Validators\MaxLength('title', t('The maximum length is %d characters', 50), 50), - )); + $rules = array( + new Validators\Required('id', t('This value is required')), + new Validators\Integer('id', t('This value must be an integer')), + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); return array( $v->execute(), @@ -43,16 +44,32 @@ class ColumnValidator extends Base */ public function validateCreation(array $values) { - $v = new Validator($values, array( + $rules = array( new Validators\Required('project_id', t('The project id is required')), new Validators\Integer('project_id', t('This value must be an integer')), - new Validators\Required('title', t('The title is required')), - new Validators\MaxLength('title', t('The maximum length is %d characters', 50), 50), - )); + ); + + $v = new Validator($values, array_merge($rules, $this->commonValidationRules())); return array( $v->execute(), $v->getErrors() ); } + + /** + * Common validation rules + * + * @access private + * @return array + */ + private function commonValidationRules() + { + return array( + new Validators\Integer('task_limit', t('This value must be an integer')), + new Validators\GreaterThan('task_limit', t('This value must be greater than %d', -1), -1), + new Validators\Required('title', t('The title is required')), + new Validators\MaxLength('title', t('The maximum length is %d characters', 50), 50), + ); + } } -- cgit v1.2.3