summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.htaccess9
-rw-r--r--app/Action/CommentCreation.php1
-rw-r--r--app/Api/Project.php6
-rw-r--r--app/Api/Task.php2
-rw-r--r--app/Auth/GitHub.php163
-rw-r--r--app/Auth/Github.php122
-rw-r--r--app/Auth/Google.php85
-rw-r--r--app/Auth/Ldap.php2
-rw-r--r--app/Auth/RememberMe.php4
-rw-r--r--app/Controller/Action.php8
-rw-r--r--app/Controller/Auth.php8
-rw-r--r--app/Controller/Base.php22
-rw-r--r--app/Controller/Board.php58
-rw-r--r--app/Controller/Category.php8
-rw-r--r--app/Controller/Comment.php8
-rw-r--r--app/Controller/Config.php6
-rw-r--r--app/Controller/Oauth.php123
-rw-r--r--app/Controller/Project.php25
-rw-r--r--app/Controller/Search.php2
-rw-r--r--app/Controller/Subtask.php12
-rw-r--r--app/Controller/Swimlane.php18
-rw-r--r--app/Controller/Task.php42
-rw-r--r--app/Controller/User.php168
-rw-r--r--app/Core/HttpClient.php21
-rw-r--r--app/Core/Lexer.php2
-rw-r--r--app/Core/OAuth2.php120
-rw-r--r--app/Core/Request.php11
-rw-r--r--app/Core/Router.php195
-rw-r--r--app/Core/Session.php2
-rw-r--r--app/Helper/Asset.php4
-rw-r--r--app/Helper/Url.php96
-rw-r--r--app/Integration/GitlabWebhook.php67
-rw-r--r--app/Integration/HipchatWebhook.php3
-rw-r--r--app/Integration/Jabber.php3
-rw-r--r--app/Integration/SlackWebhook.php3
-rw-r--r--app/Locale/da_DK/translations.php63
-rw-r--r--app/Locale/de_DE/translations.php315
-rw-r--r--app/Locale/es_ES/translations.php59
-rw-r--r--app/Locale/fi_FI/translations.php59
-rw-r--r--app/Locale/fr_FR/translations.php63
-rw-r--r--app/Locale/hu_HU/translations.php73
-rw-r--r--app/Locale/it_IT/translations.php59
-rw-r--r--app/Locale/ja_JP/translations.php63
-rw-r--r--app/Locale/nl_NL/translations.php59
-rw-r--r--app/Locale/pl_PL/translations.php59
-rw-r--r--app/Locale/pt_BR/translations.php303
-rw-r--r--app/Locale/ru_RU/translations.php79
-rw-r--r--app/Locale/sr_Latn_RS/translations.php59
-rw-r--r--app/Locale/sv_SE/translations.php63
-rw-r--r--app/Locale/th_TH/translations.php59
-rw-r--r--app/Locale/tr_TR/translations.php59
-rw-r--r--app/Locale/zh_CN/translations.php59
-rw-r--r--app/Model/Acl.php4
-rw-r--r--app/Model/Action.php1
-rw-r--r--app/Model/TaskFilter.php27
-rw-r--r--app/Model/TaskFinder.php3
-rw-r--r--app/Model/User.php14
-rw-r--r--app/ServiceProvider/ClassProvider.php6
-rw-r--r--app/Template/app/overview.php4
-rw-r--r--app/Template/auth/index.php22
-rw-r--r--app/Template/board/task_menu.php1
-rw-r--r--app/Template/config/integrations.php30
-rw-r--r--app/Template/config/webhook.php2
-rw-r--r--app/Template/feed/project.php6
-rw-r--r--app/Template/feed/user.php6
-rw-r--r--app/Template/layout.php10
-rw-r--r--app/Template/listing/show.php18
-rw-r--r--app/Template/notification/footer.php4
-rw-r--r--app/Template/notification/task_overdue.php2
-rw-r--r--app/Template/project/filters.php11
-rw-r--r--app/Template/project/integrations.php12
-rw-r--r--app/Template/search/index.php2
-rw-r--r--app/Template/search/results.php20
-rw-r--r--app/Template/task/edit_recurrence.php9
-rw-r--r--app/Template/user/authentication.php32
-rw-r--r--app/Template/user/create_local.php (renamed from app/Template/user/new.php)1
-rw-r--r--app/Template/user/create_remote.php57
-rw-r--r--app/Template/user/edit.php11
-rw-r--r--app/Template/user/external.php8
-rw-r--r--app/Template/user/index.php3
-rw-r--r--app/Template/user/layout.php3
-rw-r--r--app/Template/user/sidebar.php3
-rw-r--r--app/common.php96
-rw-r--r--app/constants.php12
-rw-r--r--assets/js/app.js42
-rw-r--r--assets/js/src/board.js14
-rw-r--r--composer.json1
-rw-r--r--composer.lock94
-rw-r--r--config.default.php9
-rw-r--r--docs/config.markdown13
-rw-r--r--docs/faq.markdown19
-rw-r--r--docs/github-authentication.markdown127
-rw-r--r--docs/gitlab-webhooks.markdown14
-rw-r--r--docs/google-authentication.markdown29
-rw-r--r--docs/index.markdown4
-rw-r--r--docs/ldap-authentication.markdown18
-rw-r--r--docs/nice-urls.markdown36
-rw-r--r--docs/recommended-configuration.markdown36
-rw-r--r--docs/search.markdown9
-rw-r--r--docs/user-management.markdown16
-rw-r--r--index.php7
-rw-r--r--tests/units/AclTest.php2
-rw-r--r--tests/units/GitlabWebhookTest.php131
-rw-r--r--tests/units/LexerTest.php25
-rw-r--r--tests/units/OAuth2Test.php43
-rw-r--r--tests/units/RouterTest.php79
-rw-r--r--tests/units/TaskFilterTest.php59
-rw-r--r--tests/units/UrlHelperTest.php18
-rw-r--r--tests/units/fixtures/gitlab_comment_created.json46
-rw-r--r--tests/units/fixtures/gitlab_issue_closed.json25
-rw-r--r--tests/units/fixtures/gitlab_issue_opened.json25
-rw-r--r--tests/units/fixtures/gitlab_push.json44
112 files changed, 2974 insertions, 1433 deletions
diff --git a/.htaccess b/.htaccess
new file mode 100644
index 00000000..0d873f58
--- /dev/null
+++ b/.htaccess
@@ -0,0 +1,9 @@
+<IfModule mod_rewrite.c>
+ Options -MultiViews
+
+ SetEnv HTTP_MOD_REWRITE On
+
+ RewriteEngine On
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteRule ^ index.php [QSA,L]
+</IfModule>
diff --git a/app/Action/CommentCreation.php b/app/Action/CommentCreation.php
index 5dfd1c89..4029c875 100644
--- a/app/Action/CommentCreation.php
+++ b/app/Action/CommentCreation.php
@@ -28,6 +28,7 @@ class CommentCreation extends Base
BitbucketWebhook::EVENT_ISSUE_COMMENT,
BitbucketWebhook::EVENT_COMMIT,
GitlabWebhook::EVENT_COMMIT,
+ GitlabWebhook::EVENT_ISSUE_COMMENT,
);
}
diff --git a/app/Api/Project.php b/app/Api/Project.php
index faf2a3da..4e4e10b8 100644
--- a/app/Api/Project.php
+++ b/app/Api/Project.php
@@ -87,9 +87,9 @@ class Project extends Base
{
if (! empty($project)) {
$project['url'] = array(
- 'board' => $this->helper->url->base().$this->helper->url->to('board', 'show', array('project_id' => $project['id'])),
- 'calendar' => $this->helper->url->base().$this->helper->url->to('calendar', 'show', array('project_id' => $project['id'])),
- 'list' => $this->helper->url->base().$this->helper->url->to('listing', 'show', array('project_id' => $project['id'])),
+ 'board' => $this->helper->url->to('board', 'show', array('project_id' => $project['id']), '', true),
+ 'calendar' => $this->helper->url->to('calendar', 'show', array('project_id' => $project['id']), '', true),
+ 'list' => $this->helper->url->to('listing', 'show', array('project_id' => $project['id']), '', true),
);
}
diff --git a/app/Api/Task.php b/app/Api/Task.php
index ade49a6d..3b8c1ec8 100644
--- a/app/Api/Task.php
+++ b/app/Api/Task.php
@@ -119,7 +119,7 @@ class Task extends Base
private function formatTask($task)
{
if (! empty($task)) {
- $task['url'] = $this->helper->url->base().$this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']));
+ $task['url'] = $this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), '', true);
}
return $task;
diff --git a/app/Auth/GitHub.php b/app/Auth/GitHub.php
deleted file mode 100644
index 816cc9c1..00000000
--- a/app/Auth/GitHub.php
+++ /dev/null
@@ -1,163 +0,0 @@
-<?php
-
-namespace Auth;
-
-use Event\AuthEvent;
-use OAuth\Common\Storage\Session;
-use OAuth\Common\Consumer\Credentials;
-use OAuth\Common\Http\Uri\UriFactory;
-use OAuth\ServiceFactory;
-use OAuth\Common\Http\Exception\TokenResponseException;
-
-/**
- * GitHub backend
- *
- * @package auth
- */
-class GitHub extends Base
-{
- /**
- * Backend name
- *
- * @var string
- */
- const AUTH_NAME = 'Github';
-
- /**
- * Authenticate a GitHub user
- *
- * @access public
- * @param string $github_id GitHub user id
- * @return boolean
- */
- public function authenticate($github_id)
- {
- $user = $this->user->getByGitHubId($github_id);
-
- if (! empty($user)) {
- $this->userSession->refresh($user);
- $this->container['dispatcher']->dispatch('auth.success', new AuthEvent(self::AUTH_NAME, $user['id']));
- return true;
- }
-
- return false;
- }
-
- /**
- * Unlink a GitHub account for a given user
- *
- * @access public
- * @param integer $user_id User id
- * @return boolean
- */
- public function unlink($user_id)
- {
- return $this->user->update(array(
- 'id' => $user_id,
- 'github_id' => '',
- ));
- }
-
- /**
- * Update the user table based on the GitHub profile information
- *
- * @access public
- * @param integer $user_id User id
- * @param array $profile GitHub profile
- * @return boolean
- * @todo Don't overwrite existing email/name with empty GitHub data
- */
- public function updateUser($user_id, array $profile)
- {
- return $this->user->update(array(
- 'id' => $user_id,
- 'github_id' => $profile['id'],
- 'email' => $profile['email'],
- 'name' => $profile['name'],
- ));
- }
-
- /**
- * Get the GitHub service instance
- *
- * @access public
- * @return \OAuth\OAuth2\Service\GitHub
- */
- public function getService()
- {
- $uriFactory = new UriFactory();
- $currentUri = $uriFactory->createFromSuperGlobalArray($_SERVER);
- $currentUri->setQuery('controller=user&action=gitHub');
-
- $storage = new Session(false);
-
- $credentials = new Credentials(
- GITHUB_CLIENT_ID,
- GITHUB_CLIENT_SECRET,
- $currentUri->getAbsoluteUri()
- );
-
- $serviceFactory = new ServiceFactory();
-
- return $serviceFactory->createService(
- 'gitHub',
- $credentials,
- $storage,
- array('')
- );
- }
-
- /**
- * Get the authorization URL
- *
- * @access public
- * @return \OAuth\Common\Http\Uri\Uri
- */
- public function getAuthorizationUrl()
- {
- return $this->getService()->getAuthorizationUri();
- }
-
- /**
- * Get GitHub profile information from the API
- *
- * @access public
- * @param string $code GitHub authorization code
- * @return bool|array
- */
- public function getGitHubProfile($code)
- {
- try {
- $gitHubService = $this->getService();
- $gitHubService->requestAccessToken($code);
-
- return json_decode($gitHubService->request('user'), true);
- }
- catch (TokenResponseException $e) {
- return false;
- }
- }
-
- /**
- * Revokes this user's GitHub tokens for Kanboard
- *
- * @access public
- * @return bool|array
- * @todo Currently this simply removes all our tokens for this user, ideally it should
- * restrict itself to the one in question
- */
- public function revokeGitHubAccess()
- {
- try {
- $gitHubService = $this->getService();
-
- $basicAuthHeader = array('Authorization' => 'Basic ' .
- base64_encode(GITHUB_CLIENT_ID.':'.GITHUB_CLIENT_SECRET));
-
- return json_decode($gitHubService->request('/applications/'.GITHUB_CLIENT_ID.'/tokens', 'DELETE', null, $basicAuthHeader), true);
- }
- catch (TokenResponseException $e) {
- return false;
- }
- }
-}
diff --git a/app/Auth/Github.php b/app/Auth/Github.php
new file mode 100644
index 00000000..44bcc6c8
--- /dev/null
+++ b/app/Auth/Github.php
@@ -0,0 +1,122 @@
+<?php
+
+namespace Auth;
+
+use Event\AuthEvent;
+
+/**
+ * Github backend
+ *
+ * @package auth
+ */
+class Github extends Base
+{
+ /**
+ * Backend name
+ *
+ * @var string
+ */
+ const AUTH_NAME = 'Github';
+
+ /**
+ * OAuth2 instance
+ *
+ * @access private
+ * @var \Core\OAuth2
+ */
+ private $service;
+
+ /**
+ * Authenticate a Github user
+ *
+ * @access public
+ * @param string $github_id Github user id
+ * @return boolean
+ */
+ public function authenticate($github_id)
+ {
+ $user = $this->user->getByGithubId($github_id);
+
+ if (! empty($user)) {
+ $this->userSession->refresh($user);
+ $this->container['dispatcher']->dispatch('auth.success', new AuthEvent(self::AUTH_NAME, $user['id']));
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Unlink a Github account for a given user
+ *
+ * @access public
+ * @param integer $user_id User id
+ * @return boolean
+ */
+ public function unlink($user_id)
+ {
+ return $this->user->update(array(
+ 'id' => $user_id,
+ 'github_id' => '',
+ ));
+ }
+
+ /**
+ * Update the user table based on the Github profile information
+ *
+ * @access public
+ * @param integer $user_id User id
+ * @param array $profile Github profile
+ * @return boolean
+ */
+ public function updateUser($user_id, array $profile)
+ {
+ $user = $this->user->getById($user_id);
+
+ return $this->user->update(array(
+ 'id' => $user_id,
+ 'github_id' => $profile['id'],
+ 'email' => $profile['email'] ?: $user['email'],
+ 'name' => $profile['name'] ?: $user['name'],
+ ));
+ }
+
+ /**
+ * Get OAuth2 configured service
+ *
+ * @access public
+ * @return \Core\OAuth2
+ */
+ public function getService()
+ {
+ if (empty($this->service)) {
+ $this->service = $this->oauth->createService(
+ GITHUB_CLIENT_ID,
+ GITHUB_CLIENT_SECRET,
+ $this->helper->url->to('oauth', 'github', array(), '', true),
+ 'https://github.com/login/oauth/authorize',
+ 'https://github.com/login/oauth/access_token',
+ array()
+ );
+ }
+
+ return $this->service;
+ }
+
+ /**
+ * Get Github profile
+ *
+ * @access public
+ * @param string $code
+ * @return array
+ */
+ public function getProfile($code)
+ {
+ $this->getService()->getAccessToken($code);
+
+ return $this->httpClient->getJson(
+ 'https://api.github.com/user',
+ array($this->getService()->getAuthorizationHeader())
+ );
+ }
+}
diff --git a/app/Auth/Google.php b/app/Auth/Google.php
index 9a977037..972dd748 100644
--- a/app/Auth/Google.php
+++ b/app/Auth/Google.php
@@ -3,11 +3,6 @@
namespace Auth;
use Event\AuthEvent;
-use OAuth\Common\Storage\Session;
-use OAuth\Common\Consumer\Credentials;
-use OAuth\Common\Http\Uri\UriFactory;
-use OAuth\ServiceFactory;
-use OAuth\Common\Http\Exception\TokenResponseException;
/**
* Google backend
@@ -25,6 +20,14 @@ class Google extends Base
const AUTH_NAME = 'Google';
/**
+ * OAuth2 instance
+ *
+ * @access private
+ * @var \Core\OAuth2
+ */
+ private $service;
+
+ /**
* Authenticate a Google user
*
* @access public
@@ -69,72 +72,52 @@ class Google extends Base
*/
public function updateUser($user_id, array $profile)
{
+ $user = $this->user->getById($user_id);
+
return $this->user->update(array(
'id' => $user_id,
'google_id' => $profile['id'],
- 'email' => $profile['email'],
- 'name' => $profile['name'],
+ 'email' => $profile['email'] ?: $user['email'],
+ 'name' => $profile['name'] ?: $user['name'],
));
}
/**
- * Get the Google service instance
+ * Get OAuth2 configured service
*
* @access public
- * @return \OAuth\OAuth2\Service\Google
+ * @return \Core\OAuth2
*/
public function getService()
{
- $uriFactory = new UriFactory();
- $currentUri = $uriFactory->createFromSuperGlobalArray($_SERVER);
- $currentUri->setQuery('controller=user&action=google');
-
- $storage = new Session(false);
-
- $credentials = new Credentials(
- GOOGLE_CLIENT_ID,
- GOOGLE_CLIENT_SECRET,
- $currentUri->getAbsoluteUri()
- );
-
- $serviceFactory = new ServiceFactory();
-
- return $serviceFactory->createService(
- 'google',
- $credentials,
- $storage,
- array('userinfo_email', 'userinfo_profile')
- );
- }
+ if (empty($this->service)) {
+ $this->service = $this->oauth->createService(
+ GOOGLE_CLIENT_ID,
+ GOOGLE_CLIENT_SECRET,
+ $this->helper->url->to('oauth', 'google', array(), '', true),
+ 'https://accounts.google.com/o/oauth2/auth',
+ 'https://accounts.google.com/o/oauth2/token',
+ array('https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/userinfo.profile')
+ );
+ }
- /**
- * Get the authorization URL
- *
- * @access public
- * @return \OAuth\Common\Http\Uri\Uri
- */
- public function getAuthorizationUrl()
- {
- return $this->getService()->getAuthorizationUri();
+ return $this->service;
}
/**
- * Get Google profile information from the API
+ * Get Google profile
*
* @access public
- * @param string $code Google authorization code
- * @return bool|array
+ * @param string $code
+ * @return array
*/
- public function getGoogleProfile($code)
+ public function getProfile($code)
{
- try {
+ $this->getService()->getAccessToken($code);
- $googleService = $this->getService();
- $googleService->requestAccessToken($code);
- return json_decode($googleService->request('https://www.googleapis.com/oauth2/v1/userinfo'), true);
- }
- catch (TokenResponseException $e) {
- return false;
- }
+ return $this->httpClient->getJson(
+ 'https://www.googleapis.com/oauth2/v1/userinfo',
+ array($this->getService()->getAuthorizationHeader())
+ );
}
}
diff --git a/app/Auth/Ldap.php b/app/Auth/Ldap.php
index 3ee6ec9b..c1459b4e 100644
--- a/app/Auth/Ldap.php
+++ b/app/Auth/Ldap.php
@@ -46,7 +46,7 @@ class Ldap extends Base
else {
// We create automatically a new user
- if ($this->createUser($username, $result['name'], $result['email'])) {
+ if (LDAP_ACCOUNT_CREATION && $this->createUser($username, $result['name'], $result['email'])) {
$user = $this->user->getByUsername($username);
}
else {
diff --git a/app/Auth/RememberMe.php b/app/Auth/RememberMe.php
index eebf4f4b..54e60422 100644
--- a/app/Auth/RememberMe.php
+++ b/app/Auth/RememberMe.php
@@ -282,7 +282,7 @@ class RememberMe extends Base
self::COOKIE_NAME,
$this->encodeCookie($token, $sequence),
$expiration,
- BASE_URL_DIRECTORY,
+ $this->helper->url->dir(),
null,
Request::isHTTPS(),
true
@@ -315,7 +315,7 @@ class RememberMe extends Base
self::COOKIE_NAME,
'',
time() - 3600,
- BASE_URL_DIRECTORY,
+ $this->helper->url->dir(),
null,
Request::isHTTPS(),
true
diff --git a/app/Controller/Action.php b/app/Controller/Action.php
index cd24453a..74a5326d 100644
--- a/app/Controller/Action.php
+++ b/app/Controller/Action.php
@@ -46,7 +46,7 @@ class Action extends Base
$values = $this->request->getValues();
if (empty($values['action_name']) || empty($values['project_id'])) {
- $this->response->redirect('?controller=action&action=index&project_id='.$project['id']);
+ $this->response->redirect($this->helper->url->to('action', 'index', array('project_id' => $project['id'])));
}
$this->response->html($this->projectLayout('action/event', array(
@@ -68,7 +68,7 @@ class Action extends Base
$values = $this->request->getValues();
if (empty($values['action_name']) || empty($values['project_id']) || empty($values['event_name'])) {
- $this->response->redirect('?controller=action&action=index&project_id='.$project['id']);
+ $this->response->redirect($this->helper->url->to('action', 'index', array('project_id' => $project['id'])));
}
$action = $this->action->load($values['action_name'], $values['project_id'], $values['event_name']);
@@ -125,7 +125,7 @@ class Action extends Base
}
}
- $this->response->redirect('?controller=action&action=index&project_id='.$project['id']);
+ $this->response->redirect($this->helper->url->to('action', 'index', array('project_id' => $project['id'])));
}
/**
@@ -163,6 +163,6 @@ class Action extends Base
$this->session->flashError(t('Unable to remove this action.'));
}
- $this->response->redirect('?controller=action&action=index&project_id='.$project['id']);
+ $this->response->redirect($this->helper->url->to('action', 'index', array('project_id' => $project['id'])));
}
}
diff --git a/app/Controller/Auth.php b/app/Controller/Auth.php
index 24e6e242..e8889b7f 100644
--- a/app/Controller/Auth.php
+++ b/app/Controller/Auth.php
@@ -25,7 +25,6 @@ class Auth extends Base
'errors' => $errors,
'values' => $values,
'no_layout' => true,
- 'redirect_query' => $this->request->getStringParam('redirect_query'),
'title' => t('Login')
)));
}
@@ -37,14 +36,15 @@ class Auth extends Base
*/
public function check()
{
- $redirect_query = $this->request->getStringParam('redirect_query');
$values = $this->request->getValues();
list($valid, $errors) = $this->authentication->validateForm($values);
if ($valid) {
- if ($redirect_query !== '') {
- $this->response->redirect('?'.urldecode($redirect_query));
+ if (! empty($this->session['login_redirect']) && ! filter_var($this->session['login_redirect'], FILTER_VALIDATE_URL)) {
+ $redirect = $this->session['login_redirect'];
+ unset($this->session['login_redirect']);
+ $this->response->redirect($redirect);
}
$this->response->redirect($this->helper->url->to('app', 'index'));
diff --git a/app/Controller/Base.php b/app/Controller/Base.php
index 9f5d6dc6..31eb023d 100644
--- a/app/Controller/Base.php
+++ b/app/Controller/Base.php
@@ -101,7 +101,7 @@ abstract class Base extends \Core\Base
public function beforeAction($controller, $action)
{
// Start the session
- $this->session->open(BASE_URL_DIRECTORY);
+ $this->session->open($this->helper->url->dir());
$this->sendHeaders($action);
$this->container['dispatcher']->dispatch('session.bootstrap', new Event);
@@ -127,7 +127,8 @@ abstract class Base extends \Core\Base
$this->response->text('Not Authorized', 401);
}
- $this->response->redirect($this->helper->url->to('auth', 'login', array('redirect_query' => urlencode($this->request->getQueryString()))));
+ $this->session['login_redirect'] = $this->request->getUri();
+ $this->response->redirect($this->helper->url->to('auth', 'login'));
}
}
@@ -223,17 +224,6 @@ abstract class Base extends \Core\Base
}
/**
- * Redirection when there is no project in the database
- *
- * @access protected
- */
- protected function redirectNoProject()
- {
- $this->session->flash(t('There is no active project, the first step is to create a new project.'));
- $this->response->redirect('?controller=project&action=create');
- }
-
- /**
* Common layout for task views
*
* @access protected
@@ -301,7 +291,7 @@ abstract class Base extends \Core\Base
if (empty($project)) {
$this->session->flashError(t('Project not found.'));
- $this->response->redirect('?controller=project');
+ $this->response->redirect($this->helper->url->to('project', 'index'));
}
return $project;
@@ -344,10 +334,10 @@ abstract class Base extends \Core\Base
'controller' => $controller,
'action' => $action,
'project_id' => $project['id'],
- 'search' => $search,
+ 'search' => urldecode($search),
);
- $this->userSession->setFilters($project['id'], $search);
+ $this->userSession->setFilters($project['id'], $filters['search']);
return array(
'project' => $project,
diff --git a/app/Controller/Board.php b/app/Controller/Board.php
index ac80a192..50d9c62e 100644
--- a/app/Controller/Board.php
+++ b/app/Controller/Board.php
@@ -88,15 +88,7 @@ class Board extends Base
return $this->response->status(400);
}
- $this->response->html(
- $this->template->render('board/table_container', array(
- 'project' => $this->project->getById($project_id),
- 'swimlanes' => $this->taskFilter->search($this->userSession->getFilters($project_id))->getBoard($project_id),
- 'board_private_refresh_interval' => $this->config->get('board_private_refresh_interval'),
- 'board_highlight_period' => $this->config->get('board_highlight_period'),
- )),
- 201
- );
+ $this->response->html($this->renderBoard($project_id), 201);
}
/**
@@ -121,14 +113,7 @@ class Board extends Base
return $this->response->status(304);
}
- $this->response->html(
- $this->template->render('board/table_container', array(
- 'project' => $this->project->getById($project_id),
- 'swimlanes' => $this->taskFilter->search($this->userSession->getFilters($project_id))->getBoard($project_id),
- 'board_private_refresh_interval' => $this->config->get('board_private_refresh_interval'),
- 'board_highlight_period' => $this->config->get('board_highlight_period'),
- ))
- );
+ $this->response->html($this->renderBoard($project_id));
}
/**
@@ -318,9 +303,7 @@ class Board extends Base
*/
public function collapse()
{
- $project_id = $this->request->getIntegerParam('project_id');
- $this->userSession->setBoardDisplayMode($project_id, true);
- $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $project_id)));
+ $this->changeDisplayMode(true);
}
/**
@@ -330,8 +313,39 @@ class Board extends Base
*/
public function expand()
{
+ $this->changeDisplayMode(false);
+ }
+
+ /**
+ * Change display mode
+ *
+ * @access private
+ */
+ private function changeDisplayMode($mode)
+ {
$project_id = $this->request->getIntegerParam('project_id');
- $this->userSession->setBoardDisplayMode($project_id, false);
- $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $project_id)));
+ $this->userSession->setBoardDisplayMode($project_id, $mode);
+
+ if ($this->request->isAjax()) {
+ $this->response->html($this->renderBoard($project_id));
+ }
+ else {
+ $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $project_id)));
+ }
+ }
+
+ /**
+ * Render board
+ *
+ * @access private
+ */
+ private function renderBoard($project_id)
+ {
+ return $this->template->render('board/table_container', array(
+ 'project' => $this->project->getById($project_id),
+ 'swimlanes' => $this->taskFilter->search($this->userSession->getFilters($project_id))->getBoard($project_id),
+ 'board_private_refresh_interval' => $this->config->get('board_private_refresh_interval'),
+ 'board_highlight_period' => $this->config->get('board_highlight_period'),
+ ));
}
}
diff --git a/app/Controller/Category.php b/app/Controller/Category.php
index 515cc9c8..e8d83f2d 100644
--- a/app/Controller/Category.php
+++ b/app/Controller/Category.php
@@ -23,7 +23,7 @@ class Category extends Base
if (empty($category)) {
$this->session->flashError(t('Category not found.'));
- $this->response->redirect('?controller=category&action=index&project_id='.$project_id);
+ $this->response->redirect($this->helper->url->to('category', 'index', array('project_id' => $project_id)));
}
return $category;
@@ -63,7 +63,7 @@ class Category extends Base
if ($this->category->create($values)) {
$this->session->flash(t('Your category have been created successfully.'));
- $this->response->redirect('?controller=category&action=index&project_id='.$project['id']);
+ $this->response->redirect($this->helper->url->to('category', 'index', array('project_id' => $project['id'])));
}
else {
$this->session->flashError(t('Unable to create your category.'));
@@ -107,7 +107,7 @@ class Category extends Base
if ($this->category->update($values)) {
$this->session->flash(t('Your category have been updated successfully.'));
- $this->response->redirect('?controller=category&action=index&project_id='.$project['id']);
+ $this->response->redirect($this->helper->url->to('category', 'index', array('project_id' => $project['id'])));
}
else {
$this->session->flashError(t('Unable to update your category.'));
@@ -151,6 +151,6 @@ class Category extends Base
$this->session->flashError(t('Unable to remove this category.'));
}
- $this->response->redirect('?controller=category&action=index&project_id='.$project['id']);
+ $this->response->redirect($this->helper->url->to('category', 'index', array('project_id' => $project['id'])));
}
}
diff --git a/app/Controller/Comment.php b/app/Controller/Comment.php
index a5f6b1f8..ca701a88 100644
--- a/app/Controller/Comment.php
+++ b/app/Controller/Comment.php
@@ -90,10 +90,10 @@ class Comment extends Base
}
if ($ajax) {
- $this->response->redirect('?controller=board&action=show&project_id='.$task['project_id']);
+ $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $task['project_id'])));
}
- $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id'].'#comments');
+ $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])), 'comments');
}
$this->create($values, $errors);
@@ -140,7 +140,7 @@ class Comment extends Base
$this->session->flashError(t('Unable to update your comment.'));
}
- $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id'].'#comment-'.$comment['id']);
+ $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])), 'comment-'.$comment['id']);
}
$this->edit($values, $errors);
@@ -181,6 +181,6 @@ class Comment extends Base
$this->session->flashError(t('Unable to remove this comment.'));
}
- $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id'].'#comments');
+ $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])), 'comments');
}
}
diff --git a/app/Controller/Config.php b/app/Controller/Config.php
index 19bc2767..1e6b3dc8 100644
--- a/app/Controller/Config.php
+++ b/app/Controller/Config.php
@@ -60,7 +60,7 @@ class Config extends Base
$this->session->flashError(t('Unable to save your settings.'));
}
- $this->response->redirect('?controller=config&action='.$redirect);
+ $this->response->redirect($this->helper->url->to('config', $redirect));
}
}
@@ -199,7 +199,7 @@ class Config extends Base
$this->checkCSRFParam();
$this->config->optimizeDatabase();
$this->session->flash(t('Database optimization done.'));
- $this->response->redirect('?controller=config');
+ $this->response->redirect($this->helper->url->to('config', 'index'));
}
/**
@@ -215,6 +215,6 @@ class Config extends Base
$this->config->regenerateToken($type.'_token');
$this->session->flash(t('Token regenerated.'));
- $this->response->redirect('?controller=config&action='.$type);
+ $this->response->redirect($this->helper->url->to('config', $type));
}
}
diff --git a/app/Controller/Oauth.php b/app/Controller/Oauth.php
new file mode 100644
index 00000000..8ba5b252
--- /dev/null
+++ b/app/Controller/Oauth.php
@@ -0,0 +1,123 @@
+<?php
+
+namespace Controller;
+
+/**
+ * OAuth controller
+ *
+ * @package controller
+ * @author Frederic Guillot
+ */
+class Oauth extends Base
+{
+ /**
+ * Link or authenticate a Google account
+ *
+ * @access public
+ */
+ public function google()
+ {
+ $this->step1('google');
+ }
+
+ /**
+ * Link or authenticate a Github account
+ *
+ * @access public
+ */
+ public function github()
+ {
+ $this->step1('github');
+ }
+
+ /**
+ * Unlink external account
+ *
+ * @access public
+ */
+ public function unlink($backend = '')
+ {
+ $backend = $this->request->getStringParam('backend', $backend);
+ $this->checkCSRFParam();
+
+ if ($this->authentication->backend($backend)->unlink($this->userSession->getId())) {
+ $this->session->flash(t('Your external account is not linked anymore to your profile.'));
+ }
+ else {
+ $this->session->flashError(t('Unable to unlink your external account.'));
+ }
+
+ $this->response->redirect($this->helper->url->to('user', 'external', array('user_id' => $this->userSession->getId())));
+ }
+
+ /**
+ * Redirect to the provider if no code received
+ *
+ * @access private
+ */
+ private function step1($backend)
+ {
+ $code = $this->request->getStringParam('code');
+
+ if (! empty($code)) {
+ $this->step2($backend, $code);
+ }
+ else {
+ $this->response->redirect($this->authentication->backend($backend)->getService()->getAuthorizationUrl());
+ }
+ }
+
+ /**
+ * Link or authenticate the user
+ *
+ * @access private
+ */
+ private function step2($backend, $code)
+ {
+ $profile = $this->authentication->backend($backend)->getProfile($code);
+
+ if ($this->userSession->isLogged()) {
+ $this->link($backend, $profile);
+ }
+
+ $this->authenticate($backend, $profile);
+ }
+
+ /**
+ * Link the account
+ *
+ * @access private
+ */
+ private function link($backend, $profile)
+ {
+ if (empty($profile)) {
+ $this->session->flashError(t('External authentication failed'));
+ }
+ else {
+ $this->session->flash(t('Your external account is linked to your profile successfully.'));
+ $this->authentication->backend($backend)->updateUser($this->userSession->getId(), $profile);
+ }
+
+ $this->response->redirect($this->helper->url->to('user', 'external', array('user_id' => $this->userSession->getId())));
+ }
+
+ /**
+ * Authenticate the account
+ *
+ * @access private
+ */
+ private function authenticate($backend, $profile)
+ {
+ if (! empty($profile) && $this->authentication->backend($backend)->authenticate($profile['id'])) {
+ $this->response->redirect($this->helper->url->to('app', 'index'));
+ }
+ else {
+ $this->response->html($this->template->layout('auth/index', array(
+ 'errors' => array('login' => t('External authentication failed')),
+ 'values' => array(),
+ 'no_layout' => true,
+ 'title' => t('Login')
+ )));
+ }
+ }
+}
diff --git a/app/Controller/Project.php b/app/Controller/Project.php
index faebac38..45bc2a46 100644
--- a/app/Controller/Project.php
+++ b/app/Controller/Project.php
@@ -73,11 +73,12 @@ class Project extends Base
if ($this->project->{$switch.'PublicAccess'}($project['id'])) {
$this->session->flash(t('Project updated successfully.'));
- } else {
+ }
+ else {
$this->session->flashError(t('Unable to update this project.'));
}
- $this->response->redirect('?controller=project&action=share&project_id='.$project['id']);
+ $this->response->redirect($this->helper->url->to('project', 'share', array('project_id' => $project['id'])));
}
$this->response->html($this->projectLayout('project/share', array(
@@ -150,7 +151,7 @@ class Project extends Base
if ($this->project->update($values)) {
$this->session->flash(t('Project updated successfully.'));
- $this->response->redirect('?controller=project&action=edit&project_id='.$project['id']);
+ $this->response->redirect($this->helper->url->to('project', 'edit', array('project_id' => $project['id'])));
}
else {
$this->session->flashError(t('Unable to update this project.'));
@@ -197,7 +198,7 @@ class Project extends Base
}
}
- $this->response->redirect('?controller=project&action=users&project_id='.$project['id']);
+ $this->response->redirect($this->helper->url->to('project', 'users', array('project_id' => $project['id'])));
}
/**
@@ -220,7 +221,7 @@ class Project extends Base
}
}
- $this->response->redirect('?controller=project&action=users&project_id='.$values['project_id']);
+ $this->response->redirect($this->helper->url->to('project', 'users', array('project_id' => $values['project_id'])));
}
/**
@@ -250,7 +251,7 @@ class Project extends Base
}
}
- $this->response->redirect('?controller=project&action=users&project_id='.$values['project_id']);
+ $this->response->redirect($this->helper->url->to('project', 'users', array('project_id' => $values['project_id'])));
}
/**
@@ -279,7 +280,7 @@ class Project extends Base
}
}
- $this->response->redirect('?controller=project&action=users&project_id='.$values['project_id']);
+ $this->response->redirect($this->helper->url->to('project', 'users', array('project_id' => $values['project_id'])));
}
/**
@@ -301,7 +302,7 @@ class Project extends Base
$this->session->flashError(t('Unable to remove this project.'));
}
- $this->response->redirect('?controller=project');
+ $this->response->redirect($this->helper->url->to('project', 'index'));
}
$this->response->html($this->projectLayout('project/remove', array(
@@ -329,7 +330,7 @@ class Project extends Base
$this->session->flashError(t('Unable to clone this project.'));
}
- $this->response->redirect('?controller=project');
+ $this->response->redirect($this->helper->url->to('project', 'index'));
}
$this->response->html($this->projectLayout('project/duplicate', array(
@@ -357,7 +358,7 @@ class Project extends Base
$this->session->flashError(t('Unable to disable this project.'));
}
- $this->response->redirect('?controller=project&action=show&project_id='.$project['id']);
+ $this->response->redirect($this->helper->url->to('project', 'show', array('project_id' => $project['id'])));
}
$this->response->html($this->projectLayout('project/disable', array(
@@ -385,7 +386,7 @@ class Project extends Base
$this->session->flashError(t('Unable to activate this project.'));
}
- $this->response->redirect('?controller=project&action=show&project_id='.$project['id']);
+ $this->response->redirect($this->helper->url->to('project', 'show', array('project_id' => $project['id'])));
}
$this->response->html($this->projectLayout('project/enable', array(
@@ -428,7 +429,7 @@ class Project extends Base
if ($project_id > 0) {
$this->session->flash(t('Your project have been created successfully.'));
- $this->response->redirect('?controller=project&action=show&project_id='.$project_id);
+ $this->response->redirect($this->helper->url->to('project', 'show', array('project_id' => $project_id)));
}
$this->session->flashError(t('Unable to create your project.'));
diff --git a/app/Controller/Search.php b/app/Controller/Search.php
index 519f9ce4..f6dc7a32 100644
--- a/app/Controller/Search.php
+++ b/app/Controller/Search.php
@@ -13,7 +13,7 @@ class Search extends Base
public function index()
{
$projects = $this->projectPermission->getAllowedProjects($this->userSession->getId());
- $search = $this->request->getStringParam('search');
+ $search = urldecode($this->request->getStringParam('search'));
$nb_tasks = 0;
$paginator = $this->paginator
diff --git a/app/Controller/Subtask.php b/app/Controller/Subtask.php
index 6ee94333..87f3fcb4 100644
--- a/app/Controller/Subtask.php
+++ b/app/Controller/Subtask.php
@@ -75,10 +75,10 @@ class Subtask extends Base
}
if (isset($values['another_subtask']) && $values['another_subtask'] == 1) {
- $this->response->redirect('?controller=subtask&action=create&task_id='.$task['id'].'&another_subtask=1&project_id='.$task['project_id']);
+ $this->response->redirect($this->helper->url->to('subtask', 'create', array('project_id' => $task['project_id'], 'task_id' => $task['id'], 'another_subtask' => 1)));
}
- $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id'].'#subtasks');
+ $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id']), 'subtasks'));
}
$this->create($values, $errors);
@@ -126,7 +126,7 @@ class Subtask extends Base
$this->session->flashError(t('Unable to update your sub-task.'));
}
- $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id'].'#subtasks');
+ $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id']), 'subtasks'));
}
$this->edit($values, $errors);
@@ -166,7 +166,7 @@ class Subtask extends Base
$this->session->flashError(t('Unable to remove this sub-task.'));
}
- $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id'].'#subtasks');
+ $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id']), 'subtasks'));
}
/**
@@ -256,7 +256,7 @@ class Subtask extends Base
case 'dashboard':
$this->response->redirect($this->helper->url->to('app', 'index'));
default:
- $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])).'#subtasks');
+ $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), 'subtasks'));
}
}
@@ -275,6 +275,6 @@ class Subtask extends Base
$method = $direction === 'up' ? 'moveUp' : 'moveDown';
$this->subtask->$method($task_id, $subtask_id);
- $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $project_id, 'task_id' => $task_id)).'#subtasks');
+ $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $project_id, 'task_id' => $task_id), 'subtasks'));
}
}
diff --git a/app/Controller/Swimlane.php b/app/Controller/Swimlane.php
index c6862d47..054fa4ba 100644
--- a/app/Controller/Swimlane.php
+++ b/app/Controller/Swimlane.php
@@ -25,7 +25,7 @@ class Swimlane extends Base
if (empty($swimlane)) {
$this->session->flashError(t('Swimlane not found.'));
- $this->response->redirect('?controller=swimlane&action=index&project_id='.$project_id);
+ $this->response->redirect($this->helper->url->to('swimlane', 'index', array('project_id' => $project_id)));
}
return $swimlane;
@@ -67,7 +67,7 @@ class Swimlane extends Base
if ($this->swimlane->create($project['id'], $values['name'])) {
$this->session->flash(t('Your swimlane have been created successfully.'));
- $this->response->redirect('?controller=swimlane&action=index&project_id='.$project['id']);
+ $this->response->redirect($this->helper->url->to('swimlane', 'index', array('project_id' => $project['id'])));
}
else {
$this->session->flashError(t('Unable to create your swimlane.'));
@@ -93,7 +93,7 @@ class Swimlane extends Base
if ($this->swimlane->updateDefault($values)) {
$this->session->flash(t('The default swimlane have been updated successfully.'));
- $this->response->redirect('?controller=swimlane&action=index&project_id='.$project['id']);
+ $this->response->redirect($this->helper->url->to('swimlane', 'index', array('project_id' => $project['id'])));
}
else {
$this->session->flashError(t('Unable to update this swimlane.'));
@@ -137,7 +137,7 @@ class Swimlane extends Base
if ($this->swimlane->rename($values['id'], $values['name'])) {
$this->session->flash(t('Swimlane updated successfully.'));
- $this->response->redirect('?controller=swimlane&action=index&project_id='.$project['id']);
+ $this->response->redirect($this->helper->url->to('swimlane', 'index', array('project_id' => $project['id'])));
}
else {
$this->session->flashError(t('Unable to update this swimlane.'));
@@ -181,7 +181,7 @@ class Swimlane extends Base
$this->session->flashError(t('Unable to remove this swimlane.'));
}
- $this->response->redirect('?controller=swimlane&action=index&project_id='.$project['id']);
+ $this->response->redirect($this->helper->url->to('swimlane', 'index', array('project_id' => $project['id'])));
}
/**
@@ -201,7 +201,7 @@ class Swimlane extends Base
$this->session->flashError(t('Unable to update this swimlane.'));
}
- $this->response->redirect('?controller=swimlane&action=index&project_id='.$project['id']);
+ $this->response->redirect($this->helper->url->to('swimlane', 'index', array('project_id' => $project['id'])));
}
/**
@@ -221,7 +221,7 @@ class Swimlane extends Base
$this->session->flashError(t('Unable to update this swimlane.'));
}
- $this->response->redirect('?controller=swimlane&action=index&project_id='.$project['id']);
+ $this->response->redirect($this->helper->url->to('swimlane', 'index', array('project_id' => $project['id'])));
}
/**
@@ -236,7 +236,7 @@ class Swimlane extends Base
$swimlane_id = $this->request->getIntegerParam('swimlane_id');
$this->swimlane->moveUp($project['id'], $swimlane_id);
- $this->response->redirect('?controller=swimlane&action=index&project_id='.$project['id']);
+ $this->response->redirect($this->helper->url->to('swimlane', 'index', array('project_id' => $project['id'])));
}
/**
@@ -251,6 +251,6 @@ class Swimlane extends Base
$swimlane_id = $this->request->getIntegerParam('swimlane_id');
$this->swimlane->moveDown($project['id'], $swimlane_id);
- $this->response->redirect('?controller=swimlane&action=index&project_id='.$project['id']);
+ $this->response->redirect($this->helper->url->to('swimlane', 'index', array('project_id' => $project['id'])));
}
}
diff --git a/app/Controller/Task.php b/app/Controller/Task.php
index 0d85f411..676dccbe 100644
--- a/app/Controller/Task.php
+++ b/app/Controller/Task.php
@@ -163,10 +163,10 @@ class Task extends Base
if (isset($values['another_task']) && $values['another_task'] == 1) {
unset($values['title']);
unset($values['description']);
- $this->response->redirect('?controller=task&action=create&'.http_build_query($values));
+ $this->response->redirect($this->helper->url->to('task', 'create', $values));
}
else {
- $this->response->redirect('?controller=board&action=show&project_id='.$project['id']);
+ $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $project['id'])));
}
}
else {
@@ -231,10 +231,10 @@ class Task extends Base
$this->session->flash(t('Task updated successfully.'));
if ($this->request->getIntegerParam('ajax')) {
- $this->response->redirect('?controller=board&action=show&project_id='.$task['project_id']);
+ $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $task['project_id'])));
}
else {
- $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id']);
+ $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id'])));
}
}
else {
@@ -264,7 +264,7 @@ class Task extends Base
$this->session->flashError(t('Unable to update your task.'));
}
- $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id']);
+ $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id'])));
}
/**
@@ -326,7 +326,7 @@ class Task extends Base
$this->session->flashError(t('Unable to open this task.'));
}
- $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id']);
+ $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id'])));
}
$this->response->html($this->taskLayout('task/open', array(
@@ -357,7 +357,7 @@ class Task extends Base
$this->session->flashError(t('Unable to remove this task.'));
}
- $this->response->redirect('?controller=board&action=show&project_id='.$task['project_id']);
+ $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $task['project_id'])));
}
$this->response->html($this->taskLayout('task/remove', array(
@@ -381,10 +381,10 @@ class Task extends Base
if ($task_id) {
$this->session->flash(t('Task created successfully.'));
- $this->response->redirect('?controller=task&action=show&task_id='.$task_id.'&project_id='.$task['project_id']);
+ $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id'])));
} else {
$this->session->flashError(t('Unable to create this task.'));
- $this->response->redirect('?controller=task&action=duplicate&task_id='.$task['id'].'&project_id='.$task['project_id']);
+ $this->response->redirect($this->helper->url->to('task', 'duplicate', array('project_id' => $task['project_id'], 'task_id' => $task['id'])));
}
}
@@ -419,10 +419,10 @@ class Task extends Base
}
if ($ajax) {
- $this->response->redirect('?controller=board&action=show&project_id='.$task['project_id']);
+ $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $task['project_id'])));
}
else {
- $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id']);
+ $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id'])));
}
}
}
@@ -454,7 +454,6 @@ class Task extends Base
public function recurrence()
{
$task = $this->getTask();
- $ajax = $this->request->isAjax() || $this->request->getIntegerParam('ajax');
if ($this->request->isPost()) {
@@ -471,12 +470,7 @@ class Task extends Base
$this->session->flashError(t('Unable to update your task.'));
}
- if ($ajax) {
- $this->response->redirect('?controller=board&action=show&project_id='.$task['project_id']);
- }
- else {
- $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id']);
- }
+ $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id'])));
}
}
else {
@@ -488,19 +482,13 @@ class Task extends Base
'values' => $values,
'errors' => $errors,
'task' => $task,
- 'ajax' => $ajax,
'recurrence_status_list' => $this->task->getRecurrenceStatusList(),
'recurrence_trigger_list' => $this->task->getRecurrenceTriggerList(),
'recurrence_timeframe_list' => $this->task->getRecurrenceTimeframeList(),
'recurrence_basedate_list' => $this->task->getRecurrenceBasedateList(),
);
- if ($ajax) {
- $this->response->html($this->template->render('task/edit_recurrence', $params));
- }
- else {
- $this->response->html($this->taskLayout('task/edit_recurrence', $params));
- }
+ $this->response->html($this->taskLayout('task/edit_recurrence', $params));
}
/**
@@ -526,7 +514,7 @@ class Task extends Base
if ($this->taskDuplication->moveToProject($task['id'], $values['project_id'])) {
$this->session->flash(t('Task updated successfully.'));
- $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$values['project_id']);
+ $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id'])));
}
else {
$this->session->flashError(t('Unable to update your task.'));
@@ -565,7 +553,7 @@ class Task extends Base
$task_id = $this->taskDuplication->duplicateToProject($task['id'], $values['project_id']);
if ($task_id) {
$this->session->flash(t('Task created successfully.'));
- $this->response->redirect('?controller=task&action=show&task_id='.$task_id.'&project_id='.$values['project_id']);
+ $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id'])));
}
else {
$this->session->flashError(t('Unable to create your task.'));
diff --git a/app/Controller/User.php b/app/Controller/User.php
index 119041e5..10a3a931 100644
--- a/app/Controller/User.php
+++ b/app/Controller/User.php
@@ -60,7 +60,9 @@ class User extends Base
*/
public function create(array $values = array(), array $errors = array())
{
- $this->response->html($this->template->layout('user/new', array(
+ $is_remote = $this->request->getIntegerParam('remote') == 1 || (isset($values['is_ldap_user']) && $values['is_ldap_user'] == 1);
+
+ $this->response->html($this->template->layout($is_remote ? 'user/create_remote' : 'user/create_local', array(
'timezones' => $this->config->getTimezones(true),
'languages' => $this->config->getLanguages(true),
'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
@@ -178,7 +180,7 @@ class User extends Base
$this->checkCSRFParam();
$user = $this->getUser();
$this->authentication->backend('rememberMe')->remove($this->request->getIntegerParam('id'));
- $this->response->redirect('?controller=user&action=sessions&user_id='.$user['id']);
+ $this->response->redirect($this->helper->url->to('user', 'session', array('user_id' => $user['id'])));
}
/**
@@ -194,7 +196,7 @@ class User extends Base
$values = $this->request->getValues();
$this->notification->saveSettings($user['id'], $values);
$this->session->flash(t('User updated successfully.'));
- $this->response->redirect('?controller=user&action=notifications&user_id='.$user['id']);
+ $this->response->redirect($this->helper->url->to('user', 'notifications', array('user_id' => $user['id'])));
}
$this->response->html($this->layout('user/notifications', array(
@@ -272,7 +274,7 @@ class User extends Base
$this->session->flashError(t('Unable to change the password.'));
}
- $this->response->redirect('?controller=user&action=show&user_id='.$user['id']);
+ $this->response->redirect($this->helper->url->to('user', 'show', array('user_id' => $user['id'])));
}
}
@@ -298,7 +300,7 @@ class User extends Base
if ($this->request->isPost()) {
- $values = $this->request->getValues() + array('disable_login_form' => 0);
+ $values = $this->request->getValues();
if ($this->userSession->isAdmin()) {
$values += array('is_admin' => 0);
@@ -321,7 +323,7 @@ class User extends Base
$this->session->flashError(t('Unable to update your user.'));
}
- $this->response->redirect('?controller=user&action=show&user_id='.$user['id']);
+ $this->response->redirect($this->helper->url->to('user', 'show', array('user_id' => $user['id'])));
}
}
@@ -335,157 +337,67 @@ class User extends Base
}
/**
- * Remove a user
+ * Display a form to edit authentication
*
* @access public
*/
- public function remove()
+ public function authentication()
{
$user = $this->getUser();
+ $values = $user;
+ $errors = array();
- if ($this->request->getStringParam('confirmation') === 'yes') {
-
- $this->checkCSRFParam();
-
- if ($this->user->remove($user['id'])) {
- $this->session->flash(t('User removed successfully.'));
- } else {
- $this->session->flashError(t('Unable to remove this user.'));
- }
-
- $this->response->redirect('?controller=user');
- }
-
- $this->response->html($this->layout('user/remove', array(
- 'user' => $user,
- )));
- }
-
- /**
- * Google authentication
- *
- * @access public
- */
- public function google()
- {
- $code = $this->request->getStringParam('code');
-
- if ($code) {
-
- $profile = $this->authentication->backend('google')->getGoogleProfile($code);
+ unset($values['password']);
- if (is_array($profile)) {
+ if ($this->request->isPost()) {
- // If the user is already logged, link the account otherwise authenticate
- if ($this->userSession->isLogged()) {
+ $values = $this->request->getValues() + array('disable_login_form' => 0, 'is_ldap_user' => 0);
+ list($valid, $errors) = $this->user->validateModification($values);
- if ($this->authentication->backend('google')->updateUser($this->userSession->getId(), $profile)) {
- $this->session->flash(t('Your Google Account is linked to your profile successfully.'));
- }
- else {
- $this->session->flashError(t('Unable to link your Google Account.'));
- }
+ if ($valid) {
- $this->response->redirect('?controller=user&action=external&user_id='.$this->userSession->getId());
- }
- else if ($this->authentication->backend('google')->authenticate($profile['id'])) {
- $this->response->redirect('?controller=app');
+ if ($this->user->update($values)) {
+ $this->session->flash(t('User updated successfully.'));
}
else {
- $this->response->html($this->template->layout('auth/index', array(
- 'errors' => array('login' => t('Google authentication failed')),
- 'values' => array(),
- 'no_layout' => true,
- 'redirect_query' => '',
- 'title' => t('Login')
- )));
+ $this->session->flashError(t('Unable to update your user.'));
}
- }
- }
-
- $this->response->redirect($this->authentication->backend('google')->getAuthorizationUrl());
- }
- /**
- * Unlink a Google account
- *
- * @access public
- */
- public function unlinkGoogle()
- {
- $this->checkCSRFParam();
- if ($this->authentication->backend('google')->unlink($this->userSession->getId())) {
- $this->session->flash(t('Your Google Account is not linked anymore to your profile.'));
- }
- else {
- $this->session->flashError(t('Unable to unlink your Google Account.'));
+ $this->response->redirect($this->helper->url->to('user', 'authentication', array('user_id' => $user['id'])));
+ }
}
- $this->response->redirect('?controller=user&action=external&user_id='.$this->userSession->getId());
+ $this->response->html($this->layout('user/authentication', array(
+ 'values' => $values,
+ 'errors' => $errors,
+ 'user' => $user,
+ )));
}
/**
- * GitHub authentication
+ * Remove a user
*
* @access public
*/
- public function github()
+ public function remove()
{
- $code = $this->request->getStringParam('code');
-
- if ($code) {
- $profile = $this->authentication->backend('gitHub')->getGitHubProfile($code);
-
- if (is_array($profile)) {
+ $user = $this->getUser();
- // If the user is already logged, link the account otherwise authenticate
- if ($this->userSession->isLogged()) {
+ if ($this->request->getStringParam('confirmation') === 'yes') {
- if ($this->authentication->backend('gitHub')->updateUser($this->userSession->getId(), $profile)) {
- $this->session->flash(t('Your GitHub account was successfully linked to your profile.'));
- }
- else {
- $this->session->flashError(t('Unable to link your GitHub Account.'));
- }
+ $this->checkCSRFParam();
- $this->response->redirect('?controller=user&action=external&user_id='.$this->userSession->getId());
- }
- else if ($this->authentication->backend('gitHub')->authenticate($profile['id'])) {
- $this->response->redirect('?controller=app');
- }
- else {
- $this->response->html($this->template->layout('auth/index', array(
- 'errors' => array('login' => t('GitHub authentication failed')),
- 'values' => array(),
- 'no_layout' => true,
- 'redirect_query' => '',
- 'title' => t('Login')
- )));
- }
+ if ($this->user->remove($user['id'])) {
+ $this->session->flash(t('User removed successfully.'));
+ } else {
+ $this->session->flashError(t('Unable to remove this user.'));
}
- }
-
- $this->response->redirect($this->authentication->backend('gitHub')->getAuthorizationUrl());
- }
- /**
- * Unlink a GitHub account
- *
- * @access public
- */
- public function unlinkGithub()
- {
- $this->checkCSRFParam();
-
- $this->authentication->backend('gitHub')->revokeGitHubAccess();
-
- if ($this->authentication->backend('gitHub')->unlink($this->userSession->getId())) {
- $this->session->flash(t('Your GitHub account is no longer linked to your profile.'));
- }
- else {
- $this->session->flashError(t('Unable to unlink your GitHub Account.'));
+ $this->response->redirect($this->helper->url->to('user', 'index'));
}
- $this->response->redirect('?controller=user&action=external&user_id='.$this->userSession->getId());
+ $this->response->html($this->layout('user/remove', array(
+ 'user' => $user,
+ )));
}
}
diff --git a/app/Core/HttpClient.php b/app/Core/HttpClient.php
index 805c1e5a..b808f756 100644
--- a/app/Core/HttpClient.php
+++ b/app/Core/HttpClient.php
@@ -32,6 +32,20 @@ class HttpClient extends Base
const HTTP_USER_AGENT = 'Kanboard';
/**
+ * Send a GET HTTP request and parse JSON response
+ *
+ * @access public
+ * @param string $url
+ * @param string[] $headers
+ * @return array
+ */
+ public function getJson($url, array $headers = array())
+ {
+ $response = $this->doRequest('GET', $url, '', array_merge(array('Accept: application/json'), $headers));
+ return json_decode($response, true) ?: array();
+ }
+
+ /**
* Send a POST HTTP request encoded in JSON
*
* @access public
@@ -43,6 +57,7 @@ class HttpClient extends Base
public function postJson($url, array $data, array $headers = array())
{
return $this->doRequest(
+ 'POST',
$url,
json_encode($data),
array_merge(array('Content-type: application/json'), $headers)
@@ -61,6 +76,7 @@ class HttpClient extends Base
public function postForm($url, array $data, array $headers = array())
{
return $this->doRequest(
+ 'POST',
$url,
http_build_query($data),
array_merge(array('Content-type: application/x-www-form-urlencoded'), $headers)
@@ -71,12 +87,13 @@ class HttpClient extends Base
* Make the HTTP request
*
* @access private
+ * @param string $method
* @param string $url
* @param string $content
* @param string[] $headers
* @return string
*/
- private function doRequest($url, $content, array $headers)
+ private function doRequest($method, $url, $content, array $headers)
{
if (empty($url)) {
return '';
@@ -86,7 +103,7 @@ class HttpClient extends Base
$context = stream_context_create(array(
'http' => array(
- 'method' => 'POST',
+ 'method' => $method,
'protocol_version' => 1.1,
'timeout' => self::HTTP_TIMEOUT,
'max_redirects' => self::HTTP_MAX_REDIRECTS,
diff --git a/app/Core/Lexer.php b/app/Core/Lexer.php
index 3887dc82..0a237254 100644
--- a/app/Core/Lexer.php
+++ b/app/Core/Lexer.php
@@ -33,6 +33,7 @@ class Lexer
"/^(category:)/" => 'T_CATEGORY',
"/^(column:)/" => 'T_COLUMN',
"/^(project:)/" => 'T_PROJECT',
+ "/^(swimlane:)/" => 'T_SWIMLANE',
"/^(ref:)/" => 'T_REFERENCE',
"/^(reference:)/" => 'T_REFERENCE',
"/^(\s+)/" => 'T_WHITESPACE',
@@ -116,6 +117,7 @@ class Lexer
case 'T_CATEGORY':
case 'T_COLUMN':
case 'T_PROJECT':
+ case 'T_SWIMLANE':
$next = next($tokens);
if ($next !== false && $next['token'] === 'T_STRING') {
diff --git a/app/Core/OAuth2.php b/app/Core/OAuth2.php
new file mode 100644
index 00000000..a7d04f33
--- /dev/null
+++ b/app/Core/OAuth2.php
@@ -0,0 +1,120 @@
+<?php
+
+namespace Core;
+
+/**
+ * OAuth2 client
+ *
+ * @package core
+ * @author Frederic Guillot
+ */
+class OAuth2 extends Base
+{
+ private $clientId;
+ private $secret;
+ private $callbackUrl;
+ private $authUrl;
+ private $tokenUrl;
+ private $scopes;
+ private $tokenType;
+ private $accessToken;
+
+ /**
+ * Create OAuth2 service
+ *
+ * @access public
+ * @param string $clientId
+ * @param string $secret
+ * @param string $callbackUrl
+ * @param string $authUrl
+ * @param string $tokenUrl
+ * @param array $scopes
+ * @return OAuth2
+ */
+ public function createService($clientId, $secret, $callbackUrl, $authUrl, $tokenUrl, array $scopes)
+ {
+ $this->clientId = $clientId;
+ $this->secret = $secret;
+ $this->callbackUrl = $callbackUrl;
+ $this->authUrl = $authUrl;
+ $this->tokenUrl = $tokenUrl;
+ $this->scopes = $scopes;
+
+ return $this;
+ }
+
+ /**
+ * Get authorization url
+ *
+ * @access public
+ * @return string
+ */
+ public function getAuthorizationUrl()
+ {
+ $params = array(
+ 'response_type' => 'code',
+ 'client_id' => $this->clientId,
+ 'redirect_uri' => $this->callbackUrl,
+ 'scope' => implode(' ', $this->scopes),
+ );
+
+ return $this->authUrl.'?'.http_build_query($params);
+ }
+
+ /**
+ * Get authorization header
+ *
+ * @access public
+ * @return string
+ */
+ public function getAuthorizationHeader()
+ {
+ if (strtolower($this->tokenType) === 'bearer') {
+ return 'Authorization: Bearer '.$this->accessToken;
+ }
+
+ return '';
+ }
+
+ /**
+ * Get access token
+ *
+ * @access public
+ * @param string $code
+ * @return string
+ */
+ public function getAccessToken($code)
+ {
+ if (empty($this->accessToken) && ! empty($code)) {
+
+ $params = array(
+ 'code' => $code,
+ 'client_id' => $this->clientId,
+ 'client_secret' => $this->secret,
+ 'redirect_uri' => $this->callbackUrl,
+ 'grant_type' => 'authorization_code',
+ );
+
+ $response = json_decode($this->httpClient->postForm($this->tokenUrl, $params, array('Accept: application/json')), true);
+
+ $this->tokenType = isset($response['token_type']) ? $response['token_type'] : '';
+ $this->accessToken = isset($response['access_token']) ? $response['access_token'] : '';
+ }
+
+ return $this->accessToken;
+ }
+
+ /**
+ * Set access token
+ *
+ * @access public
+ * @param string $token
+ * @param string $type
+ * @return string
+ */
+ public function setAccessToken($token, $type = 'bearer')
+ {
+ $this->accessToken = $token;
+ $this->tokenType = $type;
+ }
+}
diff --git a/app/Core/Request.php b/app/Core/Request.php
index b399a1f0..1eff66fa 100644
--- a/app/Core/Request.php
+++ b/app/Core/Request.php
@@ -163,6 +163,17 @@ class Request
}
/**
+ * Returns uri
+ *
+ * @access public
+ * @return string
+ */
+ public function getUri()
+ {
+ return isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
+ }
+
+ /**
* Get the user agent
*
* @static
diff --git a/app/Core/Router.php b/app/Core/Router.php
index 36c11a0a..ae989de5 100644
--- a/app/Core/Router.php
+++ b/app/Core/Router.php
@@ -2,53 +2,151 @@
namespace Core;
-use Pimple\Container;
-
/**
* Router class
*
* @package core
* @author Frederic Guillot
*/
-class Router
+class Router extends Base
{
/**
- * Controller name
+ * Store routes for path lookup
*
* @access private
- * @var string
+ * @var array
*/
- private $controller = '';
+ private $paths = array();
/**
- * Action name
+ * Store routes for url lookup
*
* @access private
- * @var string
+ * @var array
*/
- private $action = '';
+ private $urls = array();
/**
- * Container instance
+ * Get the path to compare patterns
*
- * @access private
- * @var \Pimple\Container
+ * @access public
+ * @param string $uri
+ * @param string $query_string
+ * @return string
*/
- private $container;
+ public function getPath($uri, $query_string = '')
+ {
+ $path = substr($uri, strlen($this->helper->url->dir()));
+
+ if (! empty($query_string)) {
+ $path = substr($path, 0, - strlen($query_string) - 1);
+ }
+
+ if ($path{0} === '/') {
+ $path = substr($path, 1);
+ }
+
+ return $path;
+ }
+
+ /**
+ * Add route
+ *
+ * @access public
+ * @param string $path
+ * @param string $controller
+ * @param string $action
+ * @param array $params
+ */
+ public function addRoute($path, $controller, $action, array $params = array())
+ {
+ $pattern = explode('/', $path);
+
+ $this->paths[] = array(
+ 'pattern' => $pattern,
+ 'count' => count($pattern),
+ 'controller' => $controller,
+ 'action' => $action,
+ );
+
+ $this->urls[$controller][$action][] = array(
+ 'path' => $path,
+ 'params' => array_flip($params),
+ 'count' => count($params),
+ );
+ }
+
+ /**
+ * Find a route according to the given path
+ *
+ * @access public
+ * @param string $path
+ * @return array
+ */
+ public function findRoute($path)
+ {
+ $parts = explode('/', $path);
+ $count = count($parts);
+
+ foreach ($this->paths as $route) {
+
+ if ($count === $route['count']) {
+
+ $params = array();
+
+ for ($i = 0; $i < $count; $i++) {
+
+ if ($route['pattern'][$i]{0} === ':') {
+ $params[substr($route['pattern'][$i], 1)] = $parts[$i];
+ }
+ else if ($route['pattern'][$i] !== $parts[$i]) {
+ break;
+ }
+ }
+
+ if ($i === $count) {
+ $_GET = array_merge($_GET, $params);
+ return array($route['controller'], $route['action']);
+ }
+ }
+ }
+
+ return array('app', 'index');
+ }
/**
- * Constructor
+ * Find route url
*
* @access public
- * @param \Pimple\Container $container Container instance
- * @param string $controller Controller name
- * @param string $action Action name
+ * @param string $controller
+ * @param string $action
+ * @param array $params
+ * @return string
*/
- public function __construct(Container $container, $controller = '', $action = '')
+ public function findUrl($controller, $action, array $params = array())
{
- $this->container = $container;
- $this->controller = empty($_GET['controller']) ? $controller : $_GET['controller'];
- $this->action = empty($_GET['action']) ? $action : $_GET['action'];
+ if (! isset($this->urls[$controller][$action])) {
+ return '';
+ }
+
+ foreach ($this->urls[$controller][$action] as $pattern) {
+
+ if (array_diff_key($params, $pattern['params']) === array()) {
+ $url = $pattern['path'];
+ $i = 0;
+
+ foreach ($params as $variable => $value) {
+ $url = str_replace(':'.$variable, $value, $url);
+ $i++;
+ }
+
+ if ($i === $pattern['count']) {
+ return $url;
+ }
+ }
+ }
+
+ return '';
}
/**
@@ -65,15 +163,42 @@ class Router
}
/**
- * Load a controller and execute the action
+ * Find controller/action from the route table or from get arguments
*
* @access public
- * @param string $filename Controller filename
- * @param string $class Class name
- * @param string $method Method name
+ * @param string $uri
+ * @param string $query_string
+ * @return boolean
+ */
+ public function dispatch($uri, $query_string = '')
+ {
+ if (! empty($_GET['controller']) && ! empty($_GET['action'])) {
+ $controller = $this->sanitize($_GET['controller'], 'app');
+ $action = $this->sanitize($_GET['action'], 'index');
+ }
+ else {
+ list($controller, $action) = $this->findRoute($this->getPath($uri, $query_string));
+ }
+
+ return $this->load(
+ __DIR__.'/../Controller/'.ucfirst($controller).'.php',
+ $controller,
+ '\Controller\\'.ucfirst($controller),
+ $action
+ );
+ }
+
+ /**
+ * Load a controller and execute the action
+ *
+ * @access private
+ * @param string $filename
+ * @param string $controller
+ * @param string $class
+ * @param string $method
* @return bool
*/
- public function load($filename, $class, $method)
+ private function load($filename, $controller, $class, $method)
{
if (file_exists($filename)) {
@@ -84,7 +209,7 @@ class Router
}
$instance = new $class($this->container);
- $instance->beforeAction($this->controller, $this->action);
+ $instance->beforeAction($controller, $method);
$instance->$method();
return true;
@@ -92,20 +217,4 @@ class Router
return false;
}
-
- /**
- * Find a route
- *
- * @access public
- */
- public function execute()
- {
- $this->controller = $this->sanitize($this->controller, 'app');
- $this->action = $this->sanitize($this->action, 'index');
- $filename = __DIR__.'/../Controller/'.ucfirst($this->controller).'.php';
-
- if (! $this->load($filename, '\Controller\\'.$this->controller, $this->action)) {
- die('Page not found!');
- }
- }
}
diff --git a/app/Core/Session.php b/app/Core/Session.php
index c35014cd..0e5f7426 100644
--- a/app/Core/Session.php
+++ b/app/Core/Session.php
@@ -41,8 +41,6 @@ class Session implements ArrayAccess
*/
public function open($base_path = '/')
{
- $base_path = str_replace('\\', '/', $base_path);
-
// HttpOnly and secure flags for session cookie
session_set_cookie_params(
self::SESSION_LIFETIME,
diff --git a/app/Helper/Asset.php b/app/Helper/Asset.php
index 1b1e47c5..fd555e07 100644
--- a/app/Helper/Asset.php
+++ b/app/Helper/Asset.php
@@ -18,7 +18,7 @@ class Asset extends \Core\Base
*/
public function js($filename, $async = false)
{
- return '<script '.($async ? 'async' : '').' type="text/javascript" src="'.$filename.'?'.filemtime($filename).'"></script>';
+ return '<script '.($async ? 'async' : '').' type="text/javascript" src="'.$this->helper->url->dir().$filename.'?'.filemtime($filename).'"></script>';
}
/**
@@ -31,7 +31,7 @@ class Asset extends \Core\Base
*/
public function css($filename, $is_file = true, $media = 'screen')
{
- return '<link rel="stylesheet" href="'.$filename.($is_file ? '?'.filemtime($filename) : '').'" media="'.$media.'">';
+ return '<link rel="stylesheet" href="'.$this->helper->url->dir().$filename.($is_file ? '?'.filemtime($filename) : '').'" media="'.$media.'">';
}
/**
diff --git a/app/Helper/Url.php b/app/Helper/Url.php
index 8de63f8d..964e0762 100644
--- a/app/Helper/Url.php
+++ b/app/Helper/Url.php
@@ -13,6 +13,9 @@ use Core\Security;
*/
class Url extends \Core\Base
{
+ private $base = '';
+ private $directory = '';
+
/**
* HTML Link tag
*
@@ -33,7 +36,7 @@ class Url extends \Core\Base
}
/**
- * Hyperlink
+ * HTML Hyperlink
*
* @access public
* @param string $controller Controller name
@@ -41,22 +44,12 @@ class Url extends \Core\Base
* @param array $params Url parameters
* @param boolean $csrf Add a CSRF token
* @param string $anchor Link Anchor
+ * @param boolean $absolute Absolute or relative link
* @return string
*/
- public function href($controller, $action, array $params = array(), $csrf = false, $anchor = '')
+ public function href($controller, $action, array $params = array(), $csrf = false, $anchor = '', $absolute = false)
{
- $values = array(
- 'controller' => $controller,
- 'action' => $action,
- );
-
- if ($csrf) {
- $params['csrf_token'] = Security::getCSRFToken();
- }
-
- $values += $params;
-
- return '?'.http_build_query($values, '', '&amp;').(empty($anchor) ? '' : '#'.$anchor);
+ return $this->build('&amp;', $controller, $action, $params, $csrf, $anchor, $absolute);
}
/**
@@ -66,18 +59,13 @@ class Url extends \Core\Base
* @param string $controller Controller name
* @param string $action Action name
* @param array $params Url parameters
+ * @param string $anchor Link Anchor
+ * @param boolean $absolute Absolute or relative link
* @return string
*/
- public function to($controller, $action, array $params = array())
+ public function to($controller, $action, array $params = array(), $anchor = '', $absolute = false)
{
- $values = array(
- 'controller' => $controller,
- 'action' => $action,
- );
-
- $values += $params;
-
- return '?'.http_build_query($values, '', '&');
+ return $this->build('&', $controller, $action, $params, false, $anchor, $absolute);
}
/**
@@ -88,7 +76,28 @@ class Url extends \Core\Base
*/
public function base()
{
- return $this->config->get('application_url') ?: $this->server();
+ if (empty($this->base)) {
+ $this->base = $this->config->get('application_url') ?: $this->server();
+ }
+
+ return $this->base;
+ }
+
+ /**
+ * Get application base directory
+ *
+ * @access public
+ * @return string
+ */
+ public function dir()
+ {
+ if (empty($this->directory) && isset($_SERVER['REQUEST_METHOD'])) {
+ $this->directory = str_replace('\\', '/', dirname($_SERVER['PHP_SELF']));
+ $this->directory = $this->directory !== '/' ? $this->directory.'/' : '/';
+ $this->directory = str_replace('//', '/', $this->directory);
+ }
+
+ return $this->directory;
}
/**
@@ -103,13 +112,46 @@ class Url extends \Core\Base
return 'http://localhost/';
}
- $self = str_replace('\\', '/', dirname($_SERVER['PHP_SELF']));
-
$url = Request::isHTTPS() ? 'https://' : 'http://';
$url .= $_SERVER['SERVER_NAME'];
$url .= $_SERVER['SERVER_PORT'] == 80 || $_SERVER['SERVER_PORT'] == 443 ? '' : ':'.$_SERVER['SERVER_PORT'];
- $url .= $self !== '/' ? $self.'/' : '/';
+ $url .= $this->dir() ?: '/';
return $url;
}
+
+ /**
+ * Build relative url
+ *
+ * @access private
+ * @param string $separator Querystring argument separator
+ * @param string $controller Controller name
+ * @param string $action Action name
+ * @param array $params Url parameters
+ * @param boolean $csrf Add a CSRF token
+ * @param string $anchor Link Anchor
+ * @param boolean $absolute Absolute or relative link
+ * @return string
+ */
+ private function build($separator, $controller, $action, array $params = array(), $csrf = false, $anchor = '', $absolute = false)
+ {
+ $path = $this->router->findUrl($controller, $action, $params);
+ $qs = array();
+
+ if (empty($path)) {
+ $qs['controller'] = $controller;
+ $qs['action'] = $action;
+ $qs += $params;
+ }
+
+ if ($csrf) {
+ $qs['csrf_token'] = Security::getCSRFToken();
+ }
+
+ if (! empty($qs)) {
+ $path .= '?'.http_build_query($qs, '', $separator);
+ }
+
+ return ($absolute ? $this->base() : $this->dir()).$path.(empty($anchor) ? '' : '#'.$anchor);
+ }
}
diff --git a/app/Integration/GitlabWebhook.php b/app/Integration/GitlabWebhook.php
index dce7413a..b8925daf 100644
--- a/app/Integration/GitlabWebhook.php
+++ b/app/Integration/GitlabWebhook.php
@@ -21,14 +21,16 @@ class GitlabWebhook extends \Core\Base
const EVENT_ISSUE_OPENED = 'gitlab.webhook.issue.opened';
const EVENT_ISSUE_CLOSED = 'gitlab.webhook.issue.closed';
const EVENT_COMMIT = 'gitlab.webhook.commit';
+ const EVENT_ISSUE_COMMENT = 'gitlab.webhook.issue.commented';
/**
* Supported webhook events
*
* @var string
*/
- const TYPE_PUSH = 'push';
- const TYPE_ISSUE = 'issue';
+ const TYPE_PUSH = 'push';
+ const TYPE_ISSUE = 'issue';
+ const TYPE_COMMENT = 'comment';
/**
* Project id
@@ -63,6 +65,8 @@ class GitlabWebhook extends \Core\Base
return $this->handlePushEvent($payload);
case self::TYPE_ISSUE;
return $this->handleIssueEvent($payload);
+ case self::TYPE_COMMENT;
+ return $this->handleCommentEvent($payload);
}
return false;
@@ -77,15 +81,20 @@ class GitlabWebhook extends \Core\Base
*/
public function getType(array $payload)
{
- if (isset($payload['object_kind']) && $payload['object_kind'] === 'issue') {
- return self::TYPE_ISSUE;
+ if (empty($payload['object_kind'])) {
+ return '';
}
- if (isset($payload['commits'])) {
- return self::TYPE_PUSH;
+ switch ($payload['object_kind']) {
+ case 'issue':
+ return self::TYPE_ISSUE;
+ case 'note':
+ return self::TYPE_COMMENT;
+ case 'push':
+ return self::TYPE_PUSH;
+ default:
+ return '';
}
-
- return '';
}
/**
@@ -213,4 +222,46 @@ class GitlabWebhook extends \Core\Base
return false;
}
+
+ /**
+ * Parse comment issue events
+ *
+ * @access public
+ * @param array $payload Event data
+ * @return boolean
+ */
+ public function handleCommentEvent(array $payload)
+ {
+ if (! isset($payload['issue'])) {
+ return false;
+ }
+
+ $task = $this->taskFinder->getByReference($this->project_id, $payload['issue']['id']);
+
+ if (! empty($task)) {
+
+ $user = $this->user->getByUsername($payload['user']['username']);
+
+ if (! empty($user) && ! $this->projectPermission->isMember($this->project_id, $user['id'])) {
+ $user = array();
+ }
+
+ $event = array(
+ 'project_id' => $this->project_id,
+ 'reference' => $payload['object_attributes']['id'],
+ 'comment' => $payload['object_attributes']['note']."\n\n[".t('By @%s on Gitlab', $payload['user']['username']).']('.$payload['object_attributes']['url'].')',
+ 'user_id' => ! empty($user) ? $user['id'] : 0,
+ 'task_id' => $task['id'],
+ );
+
+ $this->container['dispatcher']->dispatch(
+ self::EVENT_ISSUE_COMMENT,
+ new GenericEvent($event)
+ );
+
+ return true;
+ }
+
+ return false;
+ }
}
diff --git a/app/Integration/HipchatWebhook.php b/app/Integration/HipchatWebhook.php
index f1be0f34..1d08e514 100644
--- a/app/Integration/HipchatWebhook.php
+++ b/app/Integration/HipchatWebhook.php
@@ -72,8 +72,7 @@ class HipchatWebhook extends \Core\Base
$html .= $this->projectActivity->getTitle($event);
if ($this->config->get('application_url')) {
- $html .= '<br/><a href="'.$this->config->get('application_url');
- $html .= $this->helper->url->href('task', 'show', array('task_id' => $task_id, 'project_id' => $project_id)).'">';
+ $html .= '<br/><a href="'.$this->helper->url->href('task', 'show', array('task_id' => $task_id, 'project_id' => $project_id), false, '', true).'">';
$html .= t('view the task on Kanboard').'</a>';
}
diff --git a/app/Integration/Jabber.php b/app/Integration/Jabber.php
index a1191662..3e403aab 100644
--- a/app/Integration/Jabber.php
+++ b/app/Integration/Jabber.php
@@ -81,8 +81,7 @@ class Jabber extends \Core\Base
$payload = '['.$project['name'].'] '.str_replace('&quot;', '"', $this->projectActivity->getTitle($event)).(isset($event['task']['title']) ? ' ('.$event['task']['title'].')' : '');
if ($this->config->get('application_url')) {
- $payload .= ' '.$this->config->get('application_url');
- $payload .= $this->helper->url->to('task', 'show', array('task_id' => $task_id, 'project_id' => $project_id));
+ $payload .= ' '.$this->helper->url->to('task', 'show', array('task_id' => $task_id, 'project_id' => $project_id), false, '', true);
}
$this->sendMessage($project_id, $payload);
diff --git a/app/Integration/SlackWebhook.php b/app/Integration/SlackWebhook.php
index 498cea09..d238652f 100644
--- a/app/Integration/SlackWebhook.php
+++ b/app/Integration/SlackWebhook.php
@@ -83,8 +83,7 @@ class SlackWebhook extends \Core\Base
);
if ($this->config->get('application_url')) {
- $payload['text'] .= ' - <'.$this->config->get('application_url');
- $payload['text'] .= $this->helper->url->href('task', 'show', array('task_id' => $task_id, 'project_id' => $project_id));
+ $payload['text'] .= ' - <'.$this->helper->url->href('task', 'show', array('task_id' => $task_id, 'project_id' => $project_id), false, '', true);
$payload['text'] .= '|'.t('view the task on Kanboard').'>';
}
diff --git a/app/Locale/da_DK/translations.php b/app/Locale/da_DK/translations.php
index 91d7b98a..5f55f3e8 100644
--- a/app/Locale/da_DK/translations.php
+++ b/app/Locale/da_DK/translations.php
@@ -126,7 +126,6 @@ return array(
'The project name is required' => 'Projektets navn er krævet',
'This project must be unique' => 'Projektets navn skal være unikt',
'The title is required' => 'Titel er krævet',
- 'There is no active project, the first step is to create a new project.' => 'Der er ingen aktive projekter. Første step er at oprette et nyt projekt.',
'Settings saved successfully.' => 'Indstillinger gemt.',
'Unable to save your settings.' => 'Indstillinger kunne ikke gemmes.',
'Database optimization done.' => 'Databaseoptimeringen er fuldført.',
@@ -264,11 +263,10 @@ return array(
'%d comments' => '%d kommentarer',
'%d comment' => '%d kommentar',
'Email address invalid' => 'Ugyldig email',
- 'Your Google Account is not linked anymore to your profile.' => 'Din Google-konto er ikke længere forbundet til din profil.',
- 'Unable to unlink your Google Account.' => 'Det var ikke muligt at fjerne din Google-konto.',
- 'Google authentication failed' => 'Google autentificering mislykkedes',
- 'Unable to link your Google Account.' => 'Det var ikke muligt at forbinde til din Google-konto.',
- 'Your Google Account is linked to your profile successfully.' => 'Din Google-konto er forbundet til din profil.',
+ // 'Your external account is not linked anymore to your profile.' => '',
+ // 'Unable to unlink your external account.' => '',
+ // 'External authentication failed' => '',
+ // 'Your external account is linked to your profile successfully.' => '',
'Email' => 'E-Mail',
'Link my Google Account' => 'Forbind min Google-konto',
'Unlink my Google Account' => 'Fjern forbindelsen til min Google-konto',
@@ -338,14 +336,9 @@ return array(
'Maximum size: ' => 'Maksimum størrelse: ',
'Unable to upload the file.' => 'Filen kunne ikke uploades.',
'Display another project' => 'Vis et andet projekt...',
- 'Your GitHub account was successfully linked to your profile.' => 'Din GitHub-konto er forbundet til din profil.',
- 'Unable to link your GitHub Account.' => 'Det var ikke muligt er forbinde til din GitHub-konto.',
- 'GitHub authentication failed' => 'GitHub autentificering mislykkedes',
- 'Your GitHub account is no longer linked to your profile.' => 'Din GitHub-konto er ikke længere forbundet til din profil.',
- 'Unable to unlink your GitHub Account.' => 'Det var ikke muligt at fjerne forbindelsen til din GitHub-konto.',
- 'Login with my GitHub Account' => 'Login med min GitHub-konto',
- 'Link my GitHub Account' => 'Forbind min GitHub-konto',
- 'Unlink my GitHub Account' => 'Fjern forbindelsen til min GitHub-konto',
+ 'Login with my Github Account' => 'Login med min Github-konto',
+ 'Link my Github Account' => 'Forbind min Github-konto',
+ 'Unlink my Github Account' => 'Fjern forbindelsen til min Github-konto',
'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',
@@ -403,7 +396,7 @@ return array(
'Enabled' => 'Aktiv',
'Disabled' => 'Deaktiveret',
'Google account linked' => 'Google-konto forbundet',
- 'Github account linked' => 'GitHub-konto forbundet',
+ 'Github account linked' => 'Github-konto forbundet',
'Username:' => 'Brugernavn',
'Name:' => 'Navn:',
'Email:' => 'Email:',
@@ -417,7 +410,7 @@ return array(
'Password modification' => 'Adgangskode ændring',
'External authentications' => 'Ekstern autentificering',
'Google Account' => 'Google-konto',
- 'Github Account' => 'GitHub-konto',
+ 'Github Account' => 'Github-konto',
'Never connected.' => 'Aldrig forbundet.',
'No account linked.' => 'Ingen kontoer forfundet.',
'Account linked.' => 'Konto forbundet.',
@@ -615,8 +608,7 @@ return array(
// 'Time Tracking' => '',
// 'You already have one subtask in progress' => '',
// 'Which parts of the project do you want to duplicate?' => '',
- // 'Disable login form' => '',
- // 'Show/hide calendar' => '',
+ // 'Disallow login form' => '',
// 'Bitbucket commit received' => '',
// 'Bitbucket webhooks' => '',
// 'Help on Bitbucket webhooks' => '',
@@ -971,4 +963,39 @@ return array(
// 'Search by category: ' => '',
// 'Search by description: ' => '',
// 'Search by due date: ' => '',
+ // 'Lead and Cycle time for "%s"' => '',
+ // 'Average time spent into each column for "%s"' => '',
+ // 'Average time spent into each column' => '',
+ // 'Average time spent' => '',
+ // 'This chart show the average time spent into each column for the last %d tasks.' => '',
+ // 'Average Lead and Cycle time' => '',
+ // 'Average lead time: ' => '',
+ // 'Average cycle time: ' => '',
+ // 'Cycle Time' => '',
+ // 'Lead Time' => '',
+ // 'This chart show the average lead and cycle time for the last %d tasks over the time.' => '',
+ // 'Average time into each column' => '',
+ // 'Lead and cycle time' => '',
+ // 'Google Authentication' => '',
+ // 'Help on Google authentication' => '',
+ // 'Github Authentication' => '',
+ // 'Help on Github authentication' => '',
+ // 'Channel/Group/User (Optional)' => '',
+ // 'Lead time: ' => '',
+ // 'Cycle time: ' => '',
+ // 'Time spent into each column' => '',
+ // 'The lead time is the duration between the task creation and the completion.' => '',
+ // 'The cycle time is the duration between the start date and the completion.' => '',
+ // 'If the task is not closed the current time is used instead of the completion date.' => '',
+ // 'Set automatically the start date' => '',
+ // 'Edit Authentication' => '',
+ // 'Google Id' => '',
+ // 'Github Id' => '',
+ // 'Remote user' => '',
+ // 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => '',
+ // 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => '',
+ // 'By @%s on Gitlab' => '',
+ // 'Gitlab issue comment created' => '',
+ // 'New remote user' => '',
+ // 'New local user' => '',
);
diff --git a/app/Locale/de_DE/translations.php b/app/Locale/de_DE/translations.php
index 100335c5..90c029ba 100644
--- a/app/Locale/de_DE/translations.php
+++ b/app/Locale/de_DE/translations.php
@@ -91,7 +91,7 @@ return array(
'Download the database' => 'Datenbank herunterladen',
'Optimize the database' => 'Datenbank optimieren',
'(VACUUM command)' => '(VACUUM Befehl)',
- '(Gzip compressed Sqlite file)' => '(Gzip-komprimierte Sqlite Datei)',
+ '(Gzip compressed Sqlite file)' => '(Gzip-komprimierte SQLite-Datei)',
'Close a task' => 'Aufgabe abschließen',
'Edit a task' => 'Aufgabe bearbeiten',
'Column' => 'Spalte',
@@ -117,7 +117,7 @@ return array(
'The password is required' => 'Das Passwort wird benötigt',
'This value must be an integer' => 'Dieser Wert muss eine ganze Zahl sein',
'The username must be unique' => 'Der Benutzername muss eindeutig sein',
- 'The user id is required' => 'Die Benutzer ID ist anzugeben',
+ 'The user id is required' => 'Die Benutzer-ID ist anzugeben',
'Passwords don\'t match' => 'Passwörter nicht gleich',
'The confirmation is required' => 'Die Bestätigung ist erforderlich',
'The project is required' => 'Das Projekt ist anzugeben',
@@ -126,7 +126,6 @@ return array(
'The project name is required' => 'Der Projektname ist anzugeben',
'This project must be unique' => 'Der Projektname muss eindeutig sein',
'The title is required' => 'Der Titel ist anzugeben',
- 'There is no active project, the first step is to create a new project.' => 'Es gibt kein aktives Projekt. Zunächst muss ein Projekt erstellt werden.',
'Settings saved successfully.' => 'Einstellungen erfolgreich gespeichert.',
'Unable to save your settings.' => 'Speichern der Einstellungen nicht möglich.',
'Database optimization done.' => 'Optimieren der Datenbank abgeschlossen.',
@@ -246,8 +245,8 @@ return array(
'Last logins' => 'Letzte Anmeldungen',
'Login date' => 'Anmeldedatum',
'Authentication method' => 'Authentisierungsmethode',
- 'IP address' => 'IP Adresse',
- 'User agent' => 'User Agent',
+ 'IP address' => 'IP-Adresse',
+ 'User agent' => 'User-Agent',
'Persistent connections' => 'Bestehende Verbindungen',
'No session.' => 'Keine Sitzung.',
'Expiration date' => 'Ablaufdatum',
@@ -264,15 +263,14 @@ return array(
'%d comments' => '%d Kommentare',
'%d comment' => '%d Kommentar',
'Email address invalid' => 'Ungültige E-Mail-Adresse',
- 'Your Google Account is not linked anymore to your profile.' => 'Google Account nicht mehr mit dem Profil verbunden.',
- 'Unable to unlink your Google Account.' => 'Trennung der Verbindung zum Google Account nicht möglich.',
- 'Google authentication failed' => 'Zugriff mit Google fehlgeschlagen',
- 'Unable to link your Google Account.' => 'Verbindung mit diesem Google Account nicht möglich.',
- 'Your Google Account is linked to your profile successfully.' => 'Der Google Account wurde erfolgreich verbunden.',
+ // 'Your external account is not linked anymore to your profile.' => '',
+ // 'Unable to unlink your external account.' => '',
+ // 'External authentication failed' => '',
+ // 'Your external account is linked to your profile successfully.' => '',
'Email' => 'E-Mail',
- 'Link my Google Account' => 'Verbinde meinen Google Account',
- 'Unlink my Google Account' => 'Verbindung mit meinem Google Account trennen',
- 'Login with my Google Account' => 'Anmelden mit meinem Google Account',
+ 'Link my Google Account' => 'Verbinde meinen Google-Account',
+ 'Unlink my Google Account' => 'Verbindung mit meinem Google-Account trennen',
+ 'Login with my Google Account' => 'Anmelden mit meinem Google-Account',
'Project not found.' => 'Das Projekt wurde nicht gefunden.',
'Task removed successfully.' => 'Aufgabe erfolgreich gelöscht.',
'Unable to remove this task.' => 'Löschen der Aufgabe nicht möglich.',
@@ -338,14 +336,9 @@ return array(
'Maximum size: ' => 'Maximalgröße: ',
'Unable to upload the file.' => 'Hochladen der Datei nicht möglich.',
'Display another project' => 'Zu Projekt wechseln',
- 'Your GitHub account was successfully linked to your profile.' => 'GitHub Account erfolgreich mit dem Profil verbunden.',
- 'Unable to link your GitHub Account.' => 'Verbindung mit diesem GitHub Account nicht möglich.',
- 'GitHub authentication failed' => 'Zugriff mit GitHub fehlgeschlagen',
- 'Your GitHub account is no longer linked to your profile.' => 'GitHub Account nicht mehr mit dem Profil verbunden.',
- 'Unable to unlink your GitHub Account.' => 'Trennung der Verbindung zum GitHub Account nicht möglich.',
- 'Login with my GitHub Account' => 'Anmelden mit meinem GitHub Account',
- 'Link my GitHub Account' => 'Mit meinem GitHub Account verbinden',
- 'Unlink my GitHub Account' => 'Verbindung mit meinem GitHub Account trennen',
+ 'Login with my Github Account' => 'Anmelden mit meinem Github-Account',
+ 'Link my Github Account' => 'Mit meinem Github-Account verbinden',
+ 'Unlink my Github Account' => 'Verbindung mit meinem Github-Account trennen',
'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',
@@ -360,8 +353,8 @@ return array(
'Clone' => 'duplizieren',
'Project cloned successfully.' => 'Projekt wurde dupliziert.',
'Unable to clone this project.' => 'Duplizieren dieses Projekts schlug fehl.',
- 'Email notifications' => 'E-Mail Benachrichtigungen',
- 'Enable email notifications' => 'E-Mail Benachrichtigungen einschalten',
+ 'Email notifications' => 'E-Mail-Benachrichtigungen',
+ 'Enable email notifications' => 'E-Mail-Benachrichtigungen einschalten',
'Task position:' => 'Position der Aufgabe',
'The task #%d have been opened.' => 'Die Aufgabe #%d wurde geöffnet.',
'The task #%d have been closed.' => 'Die Aufgabe #%d wurde geschlossen.',
@@ -402,8 +395,8 @@ return array(
'Remote' => 'Remote',
'Enabled' => 'angeschaltet',
'Disabled' => 'abgeschaltet',
- 'Google account linked' => 'Mit Googleaccount verbunden',
- 'Github account linked' => 'Mit Githubaccount verbunden',
+ 'Google account linked' => 'Mit Google-Account verbunden',
+ 'Github account linked' => 'Mit Github-Account verbunden',
'Username:' => 'Benutzername',
'Name:' => 'Name',
'Email:' => 'E-Mail',
@@ -416,8 +409,8 @@ return array(
'Change password' => 'Passwort ändern',
'Password modification' => 'Passwortänderung',
'External authentications' => 'Externe Authentisierungsmethoden',
- 'Google Account' => 'Googleaccount',
- 'Github Account' => 'Githubaccount',
+ 'Google Account' => 'Google-Account',
+ 'Github Account' => 'Github-Account',
'Never connected.' => 'Noch nie verbunden.',
'No account linked.' => 'Kein Account verbunden.',
'Account linked.' => 'Account verbunden',
@@ -475,17 +468,17 @@ return array(
'Database driver:' => 'Datenbanktreiber',
'Board settings' => 'Pinnwandeinstellungen',
'URL and token' => 'URL und Token',
- 'Webhook settings' => 'Webhook Einstellungen',
+ 'Webhook settings' => 'Webhook-Einstellungen',
'URL for task creation:' => 'URL zur Aufgabenerstellung',
'Reset token' => 'Token zurücksetzen',
- 'API endpoint:' => 'API Endpunkt',
+ 'API endpoint:' => 'API-Endpunkt',
'Refresh interval for private board' => 'Aktualisierungsintervall für private Pinnwände',
'Refresh interval for public board' => 'Aktualisierungsintervall für öffentliche Pinnwände',
'Task highlight period' => 'Aufgaben-Hervorhebungsdauer',
'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Dauer (in Sekunden), wie lange eine Aufgabe als kürzlich verändert gilt (0 um diese Funktion zu deaktivieren, standardmäßig 2 Tage)',
'Frequency in second (60 seconds by default)' => 'Frequenz in Sekunden (standardmäßig 60 Sekunden)',
'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Frequenz in Sekunden (0 um diese Funktion zu deaktivieren, standardmäßig 10 Sekunden)',
- 'Application URL' => 'Applikations URL',
+ 'Application URL' => 'Applikations-URL',
'Example: http://example.kanboard.net/ (used by email notifications)' => 'Beispiel: http://example.kanboard.net/ (wird für E-Mail-Benachrichtigungen verwendet)',
'Token regenerated.' => 'Token wurde neu generiert.',
'Date format' => 'Datumsformat',
@@ -508,10 +501,10 @@ return array(
'Everybody have access to this project.' => 'Jeder hat Zugriff zu diesem Projekt',
'Webhooks' => 'Webhooks',
'API' => 'API',
- 'Github webhooks' => 'Github Webhook',
- 'Help on Github webhooks' => 'Hilfe für Github Webhooks',
+ 'Github webhooks' => 'Github-Webhook',
+ 'Help on Github webhooks' => 'Hilfe für Github-Webhooks',
'Create a comment from an external provider' => 'Kommentar eines externen Providers hinzufügen',
- 'Github issue comment created' => 'Github Fehler Kommentar hinzugefügt',
+ 'Github issue comment created' => 'Kommentar zum Github-Issue hinzugefügt',
'Project management' => 'Projektmanagement',
'My projects' => 'Meine Projekte',
'Columns' => 'Spalten',
@@ -533,15 +526,15 @@ return array(
'Not enough data to show the graph.' => 'Nicht genügend Daten, um die Grafik zu zeigen.',
'Previous' => 'Vorherige',
'The id must be an integer' => 'Die Id muss eine ganze Zahl sein',
- 'The project id must be an integer' => 'Der Projektid muss eine ganze Zahl sein',
+ 'The project id must be an integer' => 'Der Projekt-ID muss eine ganze Zahl sein',
'The status must be an integer' => 'Der Status muss eine ganze Zahl sein',
- 'The subtask id is required' => 'Die Teilaufgabenid ist benötigt',
- 'The subtask id must be an integer' => 'Die Teilaufgabenid muss eine ganze Zahl sein',
- 'The task id is required' => 'Die Aufgabenid ist benötigt',
- 'The task id must be an integer' => 'Die Aufgabenid muss eine ganze Zahl sein',
- 'The user id must be an integer' => 'Die Userid muss eine ganze Zahl sein',
+ 'The subtask id is required' => 'Die Teilaufgaben-ID ist benötigt',
+ 'The subtask id must be an integer' => 'Die Teilaufgaben-ID muss eine ganze Zahl sein',
+ 'The task id is required' => 'Die Aufgaben-ID ist benötigt',
+ 'The task id must be an integer' => 'Die Aufgaben-ID muss eine ganze Zahl sein',
+ 'The user id must be an integer' => 'Die User-ID muss eine ganze Zahl sein',
'This value is required' => 'Dieser Wert ist erforderlich',
- 'This value must be numeric' => 'Dieser Wert muss numerisch sein',
+ 'This value must be numeric' => 'Dieser Wert muss nummerisch sein',
'Unable to create this task.' => 'Diese Aufgabe kann nicht erstellt werden',
'Cumulative flow diagram' => 'Kumulatives Flussdiagramm',
'Cumulative flow diagram for "%s"' => 'Kumulatives Flussdiagramm für "%s"',
@@ -555,44 +548,44 @@ return array(
'Write' => 'Ändern',
'Active swimlanes' => 'Aktive Swimlane',
'Add a new swimlane' => 'Eine neue Swimlane hinzufügen',
- 'Change default swimlane' => 'Standard Swimlane ändern',
- 'Default swimlane' => 'Standard Swimlane',
+ 'Change default swimlane' => 'Standard-Swimlane ändern',
+ 'Default swimlane' => 'Standard-Swimlane',
'Do you really want to remove this swimlane: "%s"?' => 'Diese Swimlane wirklich ändern: "%s"?',
'Inactive swimlanes' => 'Inaktive Swimlane',
'Set project manager' => 'zum Projektmanager machen',
'Set project member' => 'zum Projektmitglied machen',
'Remove a swimlane' => 'Swimlane entfernen',
'Rename' => 'umbenennen',
- 'Show default swimlane' => 'Standard Swimlane anzeigen',
- 'Swimlane modification for the project "%s"' => 'Swimlane Änderung für das Projekt "%s"',
+ 'Show default swimlane' => 'Standard-Swimlane anzeigen',
+ 'Swimlane modification for the project "%s"' => 'Swimlane-Änderung für das Projekt "%s"',
'Swimlane not found.' => 'Swimlane nicht gefunden',
'Swimlane removed successfully.' => 'Swimlane erfolgreich entfernt.',
'Swimlanes' => 'Swimlanes',
'Swimlane updated successfully.' => 'Swimlane erfolgreich geändert.',
- 'The default swimlane have been updated successfully.' => 'Die standard Swimlane wurden erfolgreich aktualisiert. Die standard Swimlane wurden erfolgreich aktualisiert.',
- 'Unable to create your swimlane.' => 'Es ist nicht möglich die Swimlane zu erstellen.',
- 'Unable to remove this swimlane.' => 'Es ist nicht möglich die Swimlane zu entfernen.',
- 'Unable to update this swimlane.' => 'Es ist nicht möglich die Swimöane zu ändern.',
+ 'The default swimlane have been updated successfully.' => 'Die Standard-Swimlane wurden erfolgreich aktualisiert. Die Standard-Swimlane wurden erfolgreich aktualisiert.',
+ 'Unable to create your swimlane.' => 'Es ist nicht möglich, Swimlane zu erstellen.',
+ 'Unable to remove this swimlane.' => 'Es ist nicht möglich, die Swimlane zu entfernen.',
+ 'Unable to update this swimlane.' => 'Es ist nicht möglich, die Swimlane zu ändern.',
'Your swimlane have been created successfully.' => 'Die Swimlane wurde erfolgreich angelegt.',
'Example: "Bug, Feature Request, Improvement"' => 'Beispiel: "Bug, Funktionswünsche, Verbesserung"',
- 'Default categories for new projects (Comma-separated)' => 'Standard Kategorien für neue Projekte (Komma-getrennt)',
- 'Gitlab commit received' => 'Gitlab commit erhalten',
- 'Gitlab issue opened' => 'Gitlab Fehler eröffnet',
- 'Gitlab issue closed' => 'Gitlab Fehler geschlossen',
- 'Gitlab webhooks' => 'Gitlab Webhook',
- 'Help on Gitlab webhooks' => 'Hilfe für Gitlab Webhooks',
+ 'Default categories for new projects (Comma-separated)' => 'Standard-Kategorien für neue Projekte (Komma-getrennt)',
+ 'Gitlab commit received' => 'Gitlab-Commit erhalten',
+ 'Gitlab issue opened' => 'Gitlab-Issue eröffnet',
+ 'Gitlab issue closed' => 'Gitlab-Issue geschlossen',
+ 'Gitlab webhooks' => 'Gitlab-Webhook',
+ 'Help on Gitlab webhooks' => 'Hilfe für Gitlab-Webhooks',
'Integrations' => 'Integration',
- 'Integration with third-party services' => 'Integration von Fremdleistungen',
+ 'Integration with third-party services' => 'Integration von externen Diensten',
'Role for this project' => 'Rolle für dieses Projekt',
'Project manager' => 'Projektmanager',
'Project member' => 'Projektmitglied',
'A project manager can change the settings of the project and have more privileges than a standard user.' => 'Ein Projektmanager kann die Projekteinstellungen ändern und hat mehr Rechte als ein normaler Benutzer.',
- 'Gitlab Issue' => 'Gitlab Fehler',
- 'Subtask Id' => 'Teilaufgaben Id',
+ 'Gitlab Issue' => 'Gitlab-Issue',
+ 'Subtask Id' => 'Teilaufgaben-ID',
'Subtasks' => 'Teilaufgaben',
- 'Subtasks Export' => 'Teilaufgaben Export',
- 'Subtasks exportation for "%s"' => 'Teilaufgaben Export für "%s"',
- 'Task Title' => 'Aufgaben Titel',
+ 'Subtasks Export' => 'Export von Teilaufgaben',
+ 'Subtasks exportation for "%s"' => 'Export von Teilaufgaben für "%s"',
+ 'Task Title' => 'Aufgaben-Titel',
'Untitled' => 'unbetitelt',
'Application default' => 'Anwendungsstandard',
'Language:' => 'Sprache:',
@@ -606,20 +599,19 @@ return array(
'All status' => 'Alle Status',
'Moved to column %s' => 'In Spalte %s verschoben',
'Change description' => 'Beschreibung ändern',
- 'User dashboard' => 'Benutzer Dashboard',
+ 'User dashboard' => 'Benutzer-Dashboard',
'Allow only one subtask in progress at the same time for a user' => 'Erlaube nur eine Teilaufgabe pro Benutzer zu bearbeiten',
'Edit column "%s"' => 'Spalte "%s" bearbeiten',
'Select the new status of the subtask: "%s"' => 'Wähle einen neuen Status für Teilaufgabe: "%s"',
'Subtask timesheet' => 'Teilaufgaben Zeiterfassung',
'There is nothing to show.' => 'Es ist nichts zum Anzeigen vorhanden.',
'Time Tracking' => 'Zeiterfassung',
- 'You already have one subtask in progress' => 'Bereits eine Teilaufgabe in bearbeitung',
+ 'You already have one subtask in progress' => 'Bereits eine Teilaufgabe in Bearbeitung',
'Which parts of the project do you want to duplicate?' => 'Welcher Teil des Projekts soll kopiert werden?',
- 'Disable login form' => 'Anmeldeformular deaktivieren',
- 'Show/hide calendar' => 'Kalender anzeigen/verbergen',
- 'Bitbucket commit received' => 'Bitbucket commit erhalten',
- 'Bitbucket webhooks' => 'Bitbucket webhooks',
- 'Help on Bitbucket webhooks' => 'Hilfe für Bitbucket webhooks',
+ // 'Disallow login form' => '',
+ 'Bitbucket commit received' => 'Bitbucket-Commit erhalten',
+ 'Bitbucket webhooks' => 'Bitbucket-Webhooks',
+ 'Help on Bitbucket webhooks' => 'Hilfe für Bitbucket-Webhooks',
'Start' => 'Start',
'End' => 'Ende',
'Task age in days' => 'Aufgabenalter in Tagen',
@@ -639,7 +631,7 @@ return array(
'Link settings' => 'Verbindungseinstellungen',
'Opposite label' => 'Gegenteil',
'Remove a link' => 'Verbindung entfernen',
- 'Task\'s links' => 'Aufgaben Verbindungen',
+ 'Task\'s links' => 'Aufgaben-Verbindungen',
'The labels must be different' => 'Die Beschriftung muss unterschiedlich sein',
'There is no link.' => 'Es gibt keine Verbindung',
'This label must be unique' => 'Die Beschriftung muss einzigartig sein',
@@ -707,18 +699,18 @@ return array(
'Do you really want to remove this time slot?' => 'Soll diese Zeitfenster wirklich gelöscht werden?',
'Remove time slot' => 'Zeitfenster entfernen',
'Add new time slot' => 'Neues Zeitfenster hinzufügen',
- 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => 'Dieses Zeitfenster wird verwendet, wenn die Checkbox "gantägig" für Freizeit und Überstunden angeklickt ist.',
+ 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => 'Dieses Zeitfenster wird verwendet, wenn die Checkbox "ganztägig" für Freizeit und Überstunden angeklickt ist.',
'Files' => 'Dateien',
'Images' => 'Bilder',
'Private project' => 'privates Projekt',
'Amount' => 'Betrag',
- // 'AUD - Australian Dollar' => '',
+ 'AUD - Australian Dollar' => 'AUD - Australische Dollar',
'Budget' => 'Budget',
'Budget line' => 'Budgetlinie',
'Budget line removed successfully.' => 'Budgetlinie erfolgreich entfernt',
'Budget lines' => 'Budgetlinien',
- // 'CAD - Canadian Dollar' => '',
- // 'CHF - Swiss Francs' => '',
+ 'CAD - Canadian Dollar' => 'CAD - Kanadische Dollar',
+ 'CHF - Swiss Francs' => 'CHF - Schweizer Franken',
'Cost' => 'Kosten',
'Cost breakdown' => 'Kostenaufschlüsselung',
'Custom Stylesheet' => 'benutzerdefiniertes Stylesheet',
@@ -726,9 +718,9 @@ return array(
'Do you really want to remove this budget line?' => 'Soll diese Budgetlinie wirklich entfernt werden?',
'EUR - Euro' => 'EUR - Euro',
'Expenses' => 'Kosten',
- 'GBP - British Pound' => 'GBP - Britische Pfung',
+ 'GBP - British Pound' => 'GBP - Britische Pfund',
'INR - Indian Rupee' => 'INR - Indische Rupien',
- 'JPY - Japanese Yen' => 'JPY - Japanischer Yen',
+ 'JPY - Japanese Yen' => 'JPY - Japanische Yen',
'New budget line' => 'Neue Budgetlinie',
'NZD - New Zealand Dollar' => 'NZD - Neuseeland-Dollar',
'Remove a budget line' => 'Budgetlinie entfernen',
@@ -737,37 +729,37 @@ return array(
'The budget line have been created successfully.' => 'Die Budgetlinie wurde erfolgreich angelegt.',
'Unable to create the budget line.' => 'Budgetlinie konnte nicht erstellt werden.',
'Unable to remove this budget line.' => 'Budgetlinie konnte nicht gelöscht werden.',
- 'USD - US Dollar' => 'USD - US Dollar',
+ 'USD - US Dollar' => 'USD - US-Dollar',
'Remaining' => 'Verbleibend',
'Destination column' => 'Zielspalte',
'Move the task to another column when assigned to a user' => 'Aufgabe in eine andere Spalte verschieben, wenn ein User zugeordnet wurde.',
'Move the task to another column when assignee is cleared' => 'Aufgabe in eine andere Spalte verschieben, wenn die Zuordnung gelöscht wurde.',
'Source column' => 'Quellspalte',
- 'Show subtask estimates (forecast of future work)' => 'Teilaufgaben Schätzungen anzeigen (Prognose)',
+ 'Show subtask estimates (forecast of future work)' => 'Teilaufgaben-Schätzungen anzeigen (Prognose)',
'Transitions' => 'Übergänge',
'Executer' => 'Ausführender',
'Time spent in the column' => 'Zeit in Spalte verbracht',
- 'Task transitions' => 'Aufgaben Übergänge',
- 'Task transitions export' => 'Aufgaben Übergänge exportieren',
+ 'Task transitions' => 'Aufgaben-Übergänge',
+ 'Task transitions export' => 'Aufgaben-Übergänge exportieren',
'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => 'Diese Auswertung enthält alle Spaltenbewegungen für jede Aufgabe mit Datum, Benutzer und Zeit vor jedem Wechsel.',
'Currency rates' => 'Währungskurse',
'Rate' => 'Kurse',
'Change reference currency' => 'Referenzwährung ändern',
'Add a new currency rate' => 'Neuen Währungskurs hinzufügen',
- 'Currency rates are used to calculate project budget.' => 'Währungskurse werden verwendet um das Projektbudget zu berechnen.',
+ 'Currency rates are used to calculate project budget.' => 'Währungskurse werden verwendet, um das Projektbudget zu berechnen.',
'Reference currency' => 'Referenzwährung',
'The currency rate have been added successfully.' => 'Der Währungskurs wurde erfolgreich hinzugefügt.',
'Unable to add this currency rate.' => 'Währungskurs konnte nicht hinzugefügt werden',
'Send notifications to a Slack channel' => 'Benachrichtigung an einen Slack-Kanal senden',
- 'Webhook URL' => 'Webhook URL',
- 'Help on Slack integration' => 'Hilfe für Slack integration.',
+ 'Webhook URL' => 'Webhook-URL',
+ 'Help on Slack integration' => 'Hilfe für Slack-Integration.',
'%s remove the assignee of the task %s' => '%s Zuordnung für die Aufgabe %s entfernen',
'Send notifications to Hipchat' => 'Sende Benachrichtigung an Hipchat',
- 'API URL' => 'API URL',
- 'Room API ID or name' => 'Raum API ID oder Name',
- 'Room notification token' => 'Raum Benachrichtigungstoken',
- 'Help on Hipchat integration' => 'Hilfe bei Hipchat Integration',
- 'Enable Gravatar images' => 'Aktiviere Gravatar Bilder',
+ 'API URL' => 'API-URL',
+ 'Room API ID or name' => 'Raum-API-ID oder -Name',
+ 'Room notification token' => 'Raum-Benachrichtigungstoken',
+ 'Help on Hipchat integration' => 'Hilfe bei Hipchat-Integration',
+ 'Enable Gravatar images' => 'Aktiviere Gravatar-Bilder',
'Information' => 'Information',
'Check two factor authentication code' => 'Prüfe Zwei-Faktor-Authentifizierungscode',
'The two factor authentication code is not valid.' => 'Der Zwei-Faktor-Authentifizierungscode ist ungültig.',
@@ -810,7 +802,7 @@ return array(
'Recurrent task is scheduled to be generated' => 'Wiederkehrende Aufgabe ist zur Generierung eingeplant',
'Recurring information' => 'Wiederkehrende Information',
'Score' => 'Wertung',
- 'The identifier must be unique' => 'Der Schlüssel miss einzigartig sein',
+ 'The identifier must be unique' => 'Der Schlüssel muss einzigartig sein',
'This linked task id doesn\'t exists' => 'Die verbundene Aufgabe existiert nicht',
'This value must be alphanumeric' => 'Der Wert muss alphanumerisch sein',
'Edit recurrence' => 'Wiederholung bearbeiten',
@@ -836,40 +828,40 @@ return array(
'When task is moved to last column' => 'Wenn Aufgabe in letzte Spalte verschoben wird',
'Year(s)' => 'Jahr(e)',
// 'Jabber (XMPP)' => '',
- // 'Send notifications to Jabber' => '',
- // 'XMPP server address' => '',
- // 'Jabber domain' => '',
- // 'Jabber nickname' => '',
- // 'Multi-user chat room' => '',
- // 'Help on Jabber integration' => '',
- // 'The server address must use this format: "tcp://hostname:5222"' => '',
- 'Calendar settings' => 'Kalendar Einstellungen',
+ 'Send notifications to Jabber' => 'Benachrichtigungen an Jabber senden',
+ 'XMPP server address' => 'XMPP-Server-Adresse',
+ 'Jabber domain' => 'Jabber-Domain',
+ 'Jabber nickname' => 'Jabber-Nickname',
+ 'Multi-user chat room' => 'Multi-User-Chatroom',
+ 'Help on Jabber integration' => 'Hilfe zur Jabber-Integration',
+ 'The server address must use this format: "tcp://hostname:5222"' => 'Die Server-Adresse muss in diesem Format sein: "tcp://hostname:5222"',
+ 'Calendar settings' => 'Kalender-Einstellungen',
'Project calendar view' => 'Projekt-Kalendarsicht',
'Project settings' => 'Projekteinstellungen',
'Show subtasks based on the time tracking' => 'Zeige Teilaufgaben basierend auf Zeiterfassung',
'Show tasks based on the creation date' => 'Zeige Aufgaben basierend auf Erstelldatum',
'Show tasks based on the start date' => 'Zeige Aufgaben basierend auf Beginndatum',
- 'Subtasks time tracking' => 'Teilaufgaben Zeiterfassung',
- 'User calendar view' => 'Benutzer-Kalendarsicht',
+ 'Subtasks time tracking' => 'Teilaufgaben-Zeiterfassung',
+ 'User calendar view' => 'Benutzer-Kalendersicht',
'Automatically update the start date' => 'Beginndatum automatisch aktualisieren',
// 'iCal feed' => '',
'Preferences' => 'Einstellungen',
'Security' => 'Sicherheit',
- 'Two factor authentication disabled' => 'Zweifaktorauthentifizierung deaktiviert',
- 'Two factor authentication enabled' => 'Zweifaktorauthentifizierung aktiviert',
- 'Unable to update this user.' => 'User kann nicht bearbeitet werden',
+ 'Two factor authentication disabled' => 'Zwei-Faktor-Authentifizierung deaktiviert',
+ 'Two factor authentication enabled' => 'Zwei-Faktor-Authentifizierung aktiviert',
+ 'Unable to update this user.' => 'Benutzer kann nicht bearbeitet werden',
'There is no user management for private projects.' => 'Es gibt keine Benutzerverwaltung für private Projekte',
'User that will receive the email' => 'Empfänger der E-Mail',
- 'Email subject' => 'E-Mail Betreff',
+ 'Email subject' => 'E-Mail-Betreff',
'Date' => 'Datum',
// 'By @%s on Bitbucket' => '',
- // 'Bitbucket Issue' => '',
+ 'Bitbucket Issue' => 'Bitbucket-Issue',
// 'Commit made by @%s on Bitbucket' => '',
// 'Commit made by @%s on Github' => '',
// 'By @%s on Github' => '',
// 'Commit made by @%s on Gitlab' => '',
- 'Add a comment log when moving the task between columns' => 'Kommentar hinzufügen, wenn Aufgabe in andere Spalte geschoben wird',
- 'Move the task to another column when the category is changed' => 'Aufgabe in andere Spalte verschieben, wenn Kategorie geändert',
+ 'Add a comment log when moving the task between columns' => 'Kommentar hinzufügen, wenn Aufgabe in andere Spalte verschoben wird',
+ 'Move the task to another column when the category is changed' => 'Aufgabe in andere Spalte verschieben, wenn Kategorie geändert wird',
'Send a task by email to someone' => 'Aufgabe per E-Mail versenden',
'Reopen a task' => 'Aufgabe wieder öffnen',
// 'Bitbucket issue opened' => '',
@@ -886,16 +878,16 @@ return array(
'%s moved the task #%d to the first swimlane' => '%s hat die Aufgabe #%d in die erste Swimlane verschoben',
'%s moved the task #%d to the swimlane "%s"' => '%s hat die Aufgabe #%d in die Swimlane "%s" verschoben',
// 'Swimlane' => '',
- 'Budget overview' => 'Budget Übersicht',
+ 'Budget overview' => 'Budget-Übersicht',
'Type' => 'Typ',
- 'There is not enough data to show something.' => 'Es gibt nicht genug Daten für die Anzeige',
+ 'There is not enough data to show something.' => 'Es gibt nicht genügend Daten für diese Anzeige',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
'%s moved the task %s to the first swimlane' => '%s hat die Aufgabe %s in die erste Swimlane verschoben',
'%s moved the task %s to the swimlane "%s"' => '%s hat die Aufgaben %s in die Swimlane "%s" verschoben',
- 'This report contains all subtasks information for the given date range.' => 'Der Bericht beinhaltet alle Teilaufgaben im ausgewählten Zeitraum',
- 'This report contains all tasks information for the given date range.' => 'Der Bericht beinhaltet alle Aufgaben im ausgewählten Zeitraum',
+ 'This report contains all subtasks information for the given date range.' => 'Der Bericht beinhaltet alle Teilaufgaben im gewählten Zeitraum',
+ 'This report contains all tasks information for the given date range.' => 'Der Bericht beinhaltet alle Aufgaben im gewählten Zeitraum',
'Project activities for %s' => 'Projektaktivitäten für %s',
'view the board on Kanboard' => 'Pinnwand in Kanboard anzeigen',
'The task have been moved to the first swimlane' => 'Die Aufgabe wurde in die erste Swimlane verschoben',
@@ -909,7 +901,7 @@ return array(
'New color: %s' => 'Neue Farbe: %s',
'New complexity: %d' => 'Neue Komplexität: %d',
'The due date have been removed' => 'Das Ablaufdatum wurde entfernt',
- 'There is no description anymore' => 'Es gibt keine BEschreibung mehr',
+ 'There is no description anymore' => 'Es gibt keine Beschreibung mehr',
'Recurrence settings have been modified' => 'Die Einstellungen für Wiederholung wurden geändert',
'Time spent changed: %sh' => 'Verbrauchte Zeit geändert: %sh',
'Time estimated changed: %sh' => 'Geschätzte Zeit geändert: %sh',
@@ -917,7 +909,7 @@ return array(
'The description have been modified' => 'Die Beschreibung wurde geändert',
'Do you really want to close the task "%s" as well as all subtasks?' => 'Soll die Aufgabe "%s" wirklich geschlossen werden? (einschließlich Teilaufgaben)',
// 'Swimlane: %s' => '',
- 'I want to receive notifications for:' => 'Ich möchte Benachrichtigungn erhalten für:',
+ 'I want to receive notifications for:' => 'Ich möchte Benachrichtigungen erhalten für:',
'All tasks' => 'Alle Aufgaben',
'Only for tasks assigned to me' => 'nur mir zugeordnete Aufgane',
'Only for tasks created by me' => 'nur von mir erstellte Aufgaben',
@@ -936,39 +928,74 @@ return array(
'Start timer' => 'Starte Timer',
'Add project member' => 'Projektmitglied hinzufügen',
'Enable notifications' => 'Benachrichtigung aktivieren',
- // 'My activity stream' => '',
- // 'My calendar' => '',
- // 'Search tasks' => '',
- // 'Back to the calendar' => '',
- // 'Filters' => '',
- // 'Reset filters' => '',
- // 'My tasks due tomorrow' => '',
- // 'Tasks due today' => '',
- // 'Tasks due tomorrow' => '',
- // 'Tasks due yesterday' => '',
- // 'Closed tasks' => '',
- // 'Open tasks' => '',
- // 'Not assigned' => '',
- // 'View advanced search syntax' => '',
- // 'Overview' => '',
+ 'My activity stream' => 'Aktivitätsstream',
+ 'My calendar' => 'Mein Kalender',
+ 'Search tasks' => 'Suche nach Aufgaben',
+ 'Back to the calendar' => 'Zurück zum Kalender',
+ 'Filters' => 'Filter',
+ 'Reset filters' => 'Filter zurücksetzen',
+ 'My tasks due tomorrow' => 'Meine morgen fälligen Aufgaben',
+ 'Tasks due today' => 'Heute fällige Aufgaben',
+ 'Tasks due tomorrow' => 'Morgen fällige Aufgaben',
+ 'Tasks due yesterday' => 'Gestern fällige Aufgaben',
+ 'Closed tasks' => 'Abgeschlossene Aufgaben',
+ 'Open tasks' => 'Offene Aufgaben',
+ 'Not assigned' => 'Nicht zugewiesen',
+ 'View advanced search syntax' => 'Zur erweiterten Suchsyntax',
+ 'Overview' => 'Überblick',
// '%b %e %Y' => '',
- // 'Board/Calendar/List view' => '',
- // 'Switch to the board view' => '',
- // 'Switch to the calendar view' => '',
- // 'Switch to the list view' => '',
- // 'Go to the search/filter box' => '',
- // 'There is no activity yet.' => '',
- // 'No tasks found.' => '',
- // 'Keyboard shortcut: "%s"' => '',
- // 'List' => '',
- // 'Filter' => '',
- // 'Advanced search' => '',
- // 'Example of query: ' => '',
- // 'Search by project: ' => '',
- // 'Search by column: ' => '',
- // 'Search by assignee: ' => '',
- // 'Search by color: ' => '',
- // 'Search by category: ' => '',
- // 'Search by description: ' => '',
- // 'Search by due date: ' => '',
+ 'Board/Calendar/List view' => 'Board-/Kalender-/Listen-Ansicht',
+ 'Switch to the board view' => 'Zur Board-Ansicht',
+ 'Switch to the calendar view' => 'Zur Kalender-Ansicht',
+ 'Switch to the list view' => 'Zur Listen-Ansicht',
+ 'Go to the search/filter box' => 'Zum Such- und Filterfeld',
+ 'There is no activity yet.' => 'Es gibt bislang keine Aktivitäten.',
+ 'No tasks found.' => 'Keine Aufgaben gefunden.',
+ 'Keyboard shortcut: "%s"' => 'Tastaturkürzel: "%s"',
+ 'List' => 'Liste',
+ 'Filter' => 'Filter',
+ 'Advanced search' => 'Fortgeschrittene Suche',
+ 'Example of query: ' => 'Beispiel einer Abfrage: ',
+ 'Search by project: ' => 'Suche nach Projekt: ',
+ 'Search by column: ' => 'Suche nach Spalte: ',
+ 'Search by assignee: ' => 'Suche nach zugeordnetem Benutzer: ',
+ 'Search by color: ' => 'Suche nach Farbe: ',
+ 'Search by category: ' => 'Suche nach Kategorie: ',
+ 'Search by description: ' => 'Suche nach Beschreibung: ',
+ 'Search by due date: ' => 'Suche nach Fälligkeitsdatum: ',
+ // 'Lead and Cycle time for "%s"' => '',
+ // 'Average time spent into each column for "%s"' => '',
+ // 'Average time spent into each column' => '',
+ // 'Average time spent' => '',
+ // 'This chart show the average time spent into each column for the last %d tasks.' => '',
+ // 'Average Lead and Cycle time' => '',
+ // 'Average lead time: ' => '',
+ // 'Average cycle time: ' => '',
+ // 'Cycle Time' => '',
+ // 'Lead Time' => '',
+ // 'This chart show the average lead and cycle time for the last %d tasks over the time.' => '',
+ // 'Average time into each column' => '',
+ // 'Lead and cycle time' => '',
+ // 'Google Authentication' => '',
+ // 'Help on Google authentication' => '',
+ // 'Github Authentication' => '',
+ // 'Help on Github authentication' => '',
+ // 'Channel/Group/User (Optional)' => '',
+ // 'Lead time: ' => '',
+ // 'Cycle time: ' => '',
+ // 'Time spent into each column' => '',
+ // 'The lead time is the duration between the task creation and the completion.' => '',
+ // 'The cycle time is the duration between the start date and the completion.' => '',
+ // 'If the task is not closed the current time is used instead of the completion date.' => '',
+ // 'Set automatically the start date' => '',
+ // 'Edit Authentication' => '',
+ // 'Google Id' => '',
+ // 'Github Id' => '',
+ // 'Remote user' => '',
+ // 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => '',
+ // 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => '',
+ // 'By @%s on Gitlab' => '',
+ // 'Gitlab issue comment created' => '',
+ // 'New remote user' => '',
+ // 'New local user' => '',
);
diff --git a/app/Locale/es_ES/translations.php b/app/Locale/es_ES/translations.php
index 36259a8a..ecaf8a23 100644
--- a/app/Locale/es_ES/translations.php
+++ b/app/Locale/es_ES/translations.php
@@ -126,7 +126,6 @@ return array(
'The project name is required' => 'El nombre del proyecto es obligatorio',
'This project must be unique' => 'El nombre del proyecto debe ser único',
'The title is required' => 'El titulo es obligatorio',
- 'There is no active project, the first step is to create a new project.' => 'No hay proyectos activados, la primera etapa consiste en crear un nuevo proyecto.',
'Settings saved successfully.' => 'Parámetros guardados correctamente.',
'Unable to save your settings.' => 'No se pueden guardar sus parámetros.',
'Database optimization done.' => 'Optimización de la base de datos terminada.',
@@ -264,11 +263,10 @@ return array(
'%d comments' => '%d comentarios',
'%d comment' => '%d comentario',
'Email address invalid' => 'Dirección de correo inválida',
- 'Your Google Account is not linked anymore to your profile.' => 'Tu Cuenta en Google ya no se encuentra vinculada con tu perfil',
- 'Unable to unlink your Google Account.' => 'No puedo desvincular tu Cuenta en Google.',
- 'Google authentication failed' => 'Ha fallado tu autenticación en Google',
- 'Unable to link your Google Account.' => 'No puedo vincular con tu Cuenta en Google.',
- 'Your Google Account is linked to your profile successfully.' => 'Se ha vinculado correctamente tu Cuenta en Google con tu perfil.',
+ // 'Your external account is not linked anymore to your profile.' => '',
+ // 'Unable to unlink your external account.' => '',
+ // 'External authentication failed' => '',
+ // 'Your external account is linked to your profile successfully.' => '',
'Email' => 'Correo',
'Link my Google Account' => 'Vincular con mi Cuenta en Google',
'Unlink my Google Account' => 'Desvincular de mi Cuenta en Google',
@@ -338,14 +336,9 @@ return array(
'Maximum size: ' => 'Tamaño máximo',
'Unable to upload the file.' => 'No pude cargar el fichero.',
'Display another project' => 'Mostrar otro proyecto',
- 'Your GitHub account was successfully linked to your profile.' => 'Tu cuenta de GitHub ha sido correctamente vinculada con tu perfil',
- 'Unable to link your GitHub Account.' => 'Imposible vincular tu cuenta de GitHub',
- 'GitHub authentication failed' => 'Falló la autenticación de GitHub',
- 'Your GitHub account is no longer linked to your profile.' => 'Tu cuenta de GitHub ya no está vinculada a tu perfil',
- 'Unable to unlink your GitHub Account.' => 'Imposible desvincular tu cuenta de GitHub',
- 'Login with my GitHub Account' => 'Ingresar con mi cuenta de GitHub',
- 'Link my GitHub Account' => 'Vincular mi cuenta de GitHub',
- 'Unlink my GitHub Account' => 'Desvincular mi cuenta de GitHub',
+ 'Login with my Github Account' => 'Ingresar con mi cuenta de Github',
+ 'Link my Github Account' => 'Vincular mi cuenta de Github',
+ 'Unlink my Github Account' => 'Desvincular mi cuenta de Github',
'Created by %s' => 'Creado por %s',
'Last modified on %B %e, %Y at %k:%M %p' => 'Última modificación %B %e, %Y a las %k:%M %p',
'Tasks Export' => 'Exportar tareas',
@@ -615,8 +608,7 @@ return array(
'Time Tracking' => 'Seguimiento Temporal',
'You already have one subtask in progress' => 'Ya dispones de una subtarea en progreso',
'Which parts of the project do you want to duplicate?' => '¿Qué partes del proyecto deseas duplicar?',
- 'Disable login form' => 'Desactivar formulario de ingreso',
- 'Show/hide calendar' => 'Mostrar/ocultar calendario',
+ // 'Disallow login form' => '',
'Bitbucket commit received' => 'Recibida envío desde Bitbucket',
'Bitbucket webhooks' => 'Disparadores Web (webhooks) de Bitbucket',
'Help on Bitbucket webhooks' => 'Ayuda sobre disparadores web (webhooks) de Bitbucket',
@@ -971,4 +963,39 @@ return array(
// 'Search by category: ' => '',
// 'Search by description: ' => '',
// 'Search by due date: ' => '',
+ // 'Lead and Cycle time for "%s"' => '',
+ // 'Average time spent into each column for "%s"' => '',
+ // 'Average time spent into each column' => '',
+ // 'Average time spent' => '',
+ // 'This chart show the average time spent into each column for the last %d tasks.' => '',
+ // 'Average Lead and Cycle time' => '',
+ // 'Average lead time: ' => '',
+ // 'Average cycle time: ' => '',
+ // 'Cycle Time' => '',
+ // 'Lead Time' => '',
+ // 'This chart show the average lead and cycle time for the last %d tasks over the time.' => '',
+ // 'Average time into each column' => '',
+ // 'Lead and cycle time' => '',
+ // 'Google Authentication' => '',
+ // 'Help on Google authentication' => '',
+ // 'Github Authentication' => '',
+ // 'Help on Github authentication' => '',
+ // 'Channel/Group/User (Optional)' => '',
+ // 'Lead time: ' => '',
+ // 'Cycle time: ' => '',
+ // 'Time spent into each column' => '',
+ // 'The lead time is the duration between the task creation and the completion.' => '',
+ // 'The cycle time is the duration between the start date and the completion.' => '',
+ // 'If the task is not closed the current time is used instead of the completion date.' => '',
+ // 'Set automatically the start date' => '',
+ // 'Edit Authentication' => '',
+ // 'Google Id' => '',
+ // 'Github Id' => '',
+ // 'Remote user' => '',
+ // 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => '',
+ // 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => '',
+ // 'By @%s on Gitlab' => '',
+ // 'Gitlab issue comment created' => '',
+ // 'New remote user' => '',
+ // 'New local user' => '',
);
diff --git a/app/Locale/fi_FI/translations.php b/app/Locale/fi_FI/translations.php
index beb61d96..ddbb26c3 100644
--- a/app/Locale/fi_FI/translations.php
+++ b/app/Locale/fi_FI/translations.php
@@ -126,7 +126,6 @@ return array(
'The project name is required' => 'Projektin nimi on pakollinen',
'This project must be unique' => 'Projektin nimi täytyy olla uniikki',
'The title is required' => 'Otsikko vaaditaan',
- 'There is no active project, the first step is to create a new project.' => 'Aktiivista projektia ei ole, ensimmäinen vaihe on luoda uusi projekti.',
'Settings saved successfully.' => 'Asetukset tallennettu onnistuneesti.',
'Unable to save your settings.' => 'Asetusten tallentaminen epäonnistui.',
'Database optimization done.' => 'Tietokannan optimointi suoritettu.',
@@ -264,11 +263,10 @@ return array(
'%d comments' => '%d kommenttia',
'%d comment' => '%d kommentti',
'Email address invalid' => 'Email ei kelpaa',
- 'Your Google Account is not linked anymore to your profile.' => 'Google tunnustasi ei ole enää linkattu profiiliisi',
- 'Unable to unlink your Google Account.' => 'Google tunnuksen linkkaamisen poistaminen epäonnistui.',
- 'Google authentication failed' => 'Google autentikointi epäonnistui',
- 'Unable to link your Google Account.' => 'Google tunnuksen linkkaaminen epäonnistui.',
- 'Your Google Account is linked to your profile successfully.' => 'Google tunnuksesi linkitettiin profiiliisi onnistuneesti.',
+ // 'Your external account is not linked anymore to your profile.' => '',
+ // 'Unable to unlink your external account.' => '',
+ // 'External authentication failed' => '',
+ // 'Your external account is linked to your profile successfully.' => '',
'Email' => 'Sähköposti',
'Link my Google Account' => 'Linkitä Google-tili',
'Unlink my Google Account' => 'Poista Google-tilin linkitys',
@@ -338,14 +336,9 @@ return array(
'Maximum size: ' => 'Maksimikoko: ',
'Unable to upload the file.' => 'Tiedoston lataus epäonnistui.',
'Display another project' => 'Näytä toinen projekti',
- 'Your GitHub account was successfully linked to your profile.' => 'Github-tilisi on onnistuneesti liitetty profiiliisi',
- 'Unable to link your GitHub Account.' => 'Github-tilin liittäminen epäonnistui',
- 'GitHub authentication failed' => 'Github-todennus epäonnistui',
- 'Your GitHub account is no longer linked to your profile.' => 'Github-tiliäsi ei ole enää liitetty profiiliisi.',
- 'Unable to unlink your GitHub Account.' => 'Github-tilisi liitoksen poisto epäonnistui',
- 'Login with my GitHub Account' => 'Kirjaudu sisään Github-tililläni',
- 'Link my GitHub Account' => 'Liitä Github-tilini',
- 'Unlink my GitHub Account' => 'Poista liitos Github-tiliini',
+ 'Login with my Github Account' => 'Kirjaudu sisään Github-tililläni',
+ 'Link my Github Account' => 'Liitä Github-tilini',
+ 'Unlink my Github Account' => 'Poista liitos Github-tiliini',
'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',
@@ -615,8 +608,7 @@ return array(
// 'Time Tracking' => '',
// 'You already have one subtask in progress' => '',
// 'Which parts of the project do you want to duplicate?' => '',
- // 'Disable login form' => '',
- // 'Show/hide calendar' => '',
+ // 'Disallow login form' => '',
// 'Bitbucket commit received' => '',
// 'Bitbucket webhooks' => '',
// 'Help on Bitbucket webhooks' => '',
@@ -971,4 +963,39 @@ return array(
// 'Search by category: ' => '',
// 'Search by description: ' => '',
// 'Search by due date: ' => '',
+ // 'Lead and Cycle time for "%s"' => '',
+ // 'Average time spent into each column for "%s"' => '',
+ // 'Average time spent into each column' => '',
+ // 'Average time spent' => '',
+ // 'This chart show the average time spent into each column for the last %d tasks.' => '',
+ // 'Average Lead and Cycle time' => '',
+ // 'Average lead time: ' => '',
+ // 'Average cycle time: ' => '',
+ // 'Cycle Time' => '',
+ // 'Lead Time' => '',
+ // 'This chart show the average lead and cycle time for the last %d tasks over the time.' => '',
+ // 'Average time into each column' => '',
+ // 'Lead and cycle time' => '',
+ // 'Google Authentication' => '',
+ // 'Help on Google authentication' => '',
+ // 'Github Authentication' => '',
+ // 'Help on Github authentication' => '',
+ // 'Channel/Group/User (Optional)' => '',
+ // 'Lead time: ' => '',
+ // 'Cycle time: ' => '',
+ // 'Time spent into each column' => '',
+ // 'The lead time is the duration between the task creation and the completion.' => '',
+ // 'The cycle time is the duration between the start date and the completion.' => '',
+ // 'If the task is not closed the current time is used instead of the completion date.' => '',
+ // 'Set automatically the start date' => '',
+ // 'Edit Authentication' => '',
+ // 'Google Id' => '',
+ // 'Github Id' => '',
+ // 'Remote user' => '',
+ // 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => '',
+ // 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => '',
+ // 'By @%s on Gitlab' => '',
+ // 'Gitlab issue comment created' => '',
+ // 'New remote user' => '',
+ // 'New local user' => '',
);
diff --git a/app/Locale/fr_FR/translations.php b/app/Locale/fr_FR/translations.php
index 34839a12..1c4c76ee 100644
--- a/app/Locale/fr_FR/translations.php
+++ b/app/Locale/fr_FR/translations.php
@@ -126,7 +126,6 @@ return array(
'The project name is required' => 'Le nom du projet est obligatoire',
'This project must be unique' => 'Le nom du projet doit être unique',
'The title is required' => 'Le titre est obligatoire',
- 'There is no active project, the first step is to create a new project.' => 'Il n\'y a aucun projet actif, la première étape est de créer un nouveau projet.',
'Settings saved successfully.' => 'Paramètres sauvegardés avec succès.',
'Unable to save your settings.' => 'Impossible de sauvegarder vos réglages.',
'Database optimization done.' => 'Optmisation de la base de données terminée.',
@@ -264,11 +263,10 @@ return array(
'%d comments' => '%d commentaires',
'%d comment' => '%d commentaire',
'Email address invalid' => 'Adresse email invalide',
- 'Your Google Account is not linked anymore to your profile.' => 'Votre compte Google n\'est plus relié à votre profile.',
- 'Unable to unlink your Google Account.' => 'Impossible de supprimer votre compte Google.',
- 'Google authentication failed' => 'Authentification Google échouée',
- 'Unable to link your Google Account.' => 'Impossible de lier votre compte Google.',
- 'Your Google Account is linked to your profile successfully.' => 'Votre compte Google est désormais lié à votre profile.',
+ 'Your external account is not linked anymore to your profile.' => 'Votre compte externe n\'est plus relié à votre profile.',
+ 'Unable to unlink your external account.' => 'Impossible de supprimer votre compte externe.',
+ 'External authentication failed' => 'Authentification externe échouée',
+ 'Your external account is linked to your profile successfully.' => 'Votre compte externe est désormais lié à votre profile.',
'Email' => 'Email',
'Link my Google Account' => 'Lier mon compte Google',
'Unlink my Google Account' => 'Ne plus utiliser mon compte Google',
@@ -338,14 +336,9 @@ return array(
'Maximum size: ' => 'Taille maximum : ',
'Unable to upload the file.' => 'Impossible de transférer le fichier.',
'Display another project' => 'Afficher un autre projet',
- 'Your GitHub account was successfully linked to your profile.' => 'Votre compte Github est désormais lié avec votre profile.',
- 'Unable to link your GitHub Account.' => 'Impossible de lier votre compte Github.',
- 'GitHub authentication failed' => 'L\'authentification avec Github à échouée',
- 'Your GitHub account is no longer linked to your profile.' => 'Votre compte Github n\'est plus relié avec votre profile.',
- 'Unable to unlink your GitHub Account.' => 'Impossible de déconnecter votre compte Github.',
- 'Login with my GitHub Account' => 'Se connecter avec mon compte Github',
- 'Link my GitHub Account' => 'Lier mon compte Github',
- 'Unlink my GitHub Account' => 'Ne plus utiliser mon compte Github',
+ 'Login with my Github Account' => 'Se connecter avec mon compte Github',
+ 'Link my Github Account' => 'Lier mon compte Github',
+ 'Unlink my Github Account' => 'Ne plus utiliser mon compte Github',
'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',
@@ -617,8 +610,7 @@ return array(
'Time Tracking' => 'Feuille de temps',
'You already have one subtask in progress' => 'Vous avez déjà une sous-tâche en progrès',
'Which parts of the project do you want to duplicate?' => 'Quelles parties du projet voulez-vous dupliquer ?',
- 'Disable login form' => 'Désactiver le formulaire d\'authentification',
- 'Show/hide calendar' => 'Afficher/cacher le calendrier',
+ 'Disallow login form' => 'Interdir le formulaire d\'authentification',
'Bitbucket commit received' => 'Commit reçu via Bitbucket',
'Bitbucket webhooks' => 'Webhook Bitbucket',
'Help on Bitbucket webhooks' => 'Aide sur les webhooks Bitbucket',
@@ -899,7 +891,7 @@ return array(
'This report contains all subtasks information for the given date range.' => 'Ce rapport contient les informations de toutes les sous-tâches pour la période selectionnée.',
'This report contains all tasks information for the given date range.' => 'Ce rapport contient les informations de toutes les tâches pour la période selectionnée.',
'Project activities for %s' => 'Activité des projets pour « %s »',
- 'view the board on Kanboard' => 'voir la tableau sur Kanboard',
+ 'view the board on Kanboard' => 'voir le tableau sur Kanboard',
'The task have been moved to the first swimlane' => 'La tâche a été déplacée dans la première swimlane',
'The task have been moved to another swimlane:' => 'La tâche a été déplacée dans une autre swimlane :',
'Overdue tasks for the project "%s"' => 'Tâches en retard pour le projet « %s »',
@@ -966,11 +958,46 @@ return array(
'Filter' => 'Filtre',
'Advanced search' => 'Recherche avancée',
'Example of query: ' => 'Exemple de requête : ',
- 'Search by project: ' => 'Rechercher par project : ',
+ 'Search by project: ' => 'Rechercher par projet : ',
'Search by column: ' => 'Rechercher par colonne : ',
'Search by assignee: ' => 'Rechercher par assigné : ',
'Search by color: ' => 'Rechercher par couleur : ',
'Search by category: ' => 'Rechercher par catégorie : ',
'Search by description: ' => 'Rechercher par description : ',
'Search by due date: ' => 'Rechercher par date d\'échéance : ',
+ 'Lead and Cycle time for "%s"' => 'Lead et cycle time pour « %s »',
+ 'Average time spent into each column for "%s"' => 'Temps passé moyen dans chaque colonne pour « %s »',
+ 'Average time spent into each column' => 'Temps moyen passé dans chaque colonne',
+ 'Average time spent' => 'Temps moyen passé',
+ 'This chart show the average time spent into each column for the last %d tasks.' => 'Ce graphique montre le temps passé moyen dans chaque colonne pour les %d dernières tâches.',
+ 'Average Lead and Cycle time' => 'Durée moyenne du lead et cycle time',
+ 'Average lead time: ' => 'Lead time moyen : ',
+ 'Average cycle time: ' => 'Cycle time moyen : ',
+ 'Cycle Time' => 'Cycle time',
+ 'Lead Time' => 'Lead time',
+ 'This chart show the average lead and cycle time for the last %d tasks over the time.' => 'Ce graphique montre la durée moyenne du lead et cycle time pour les %d dernières tâches.',
+ 'Average time into each column' => 'Temps moyen dans chaque colonne',
+ 'Lead and cycle time' => 'Lead et cycle time',
+ 'Google Authentication' => 'Authentification Google',
+ 'Help on Google authentication' => 'Aide sur l\'authentification Google',
+ 'Github Authentication' => 'Authentification Github',
+ 'Help on Github authentication' => 'Aide sur l\'authentification Github',
+ 'Channel/Group/User (Optional)' => 'Cannal/Groupe/Utilisateur (Optionnel)',
+ 'Lead time: ' => 'Lead time : ',
+ 'Cycle time: ' => 'Temps de cycle : ',
+ 'Time spent into each column' => 'Temps passé dans chaque colonne',
+ 'The lead time is the duration between the task creation and the completion.' => 'Le lead time est la durée entre la création de la tâche et sa complétion.',
+ 'The cycle time is the duration between the start date and the completion.' => 'Le cycle time est la durée entre la date de début et la complétion.',
+ 'If the task is not closed the current time is used instead of the completion date.' => 'Si la tâche n\'est pas fermée, l\'heure courante est utilisée à la place de la date de complétion.',
+ 'Set automatically the start date' => 'Définir automatiquement la date de début',
+ 'Edit Authentication' => 'Modifier l\'authentification',
+ 'Google Id' => 'Identifiant Google',
+ 'Github Id' => 'Identifiant Github',
+ 'Remote user' => 'Utilisateur distant',
+ 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'Les utilisateurs distants ne stockent pas leur mot de passe dans la base de données de Kanboard, exemples : comptes LDAP, Github ou Google.',
+ 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Si vous cochez la case « Interdir le formulaire d\'authentification », les identifiants entrés dans le formulaire d\'authentification seront ignorés.',
+ 'By @%s on Gitlab' => 'Par @%s sur Gitlab',
+ 'Gitlab issue comment created' => 'Commentaire créé sur un ticket Gitlab',
+ 'New remote user' => 'Créer un utilisateur distant',
+ 'New local user' => 'Créer un utilisateur local',
);
diff --git a/app/Locale/hu_HU/translations.php b/app/Locale/hu_HU/translations.php
index 73215f89..f1bd1453 100644
--- a/app/Locale/hu_HU/translations.php
+++ b/app/Locale/hu_HU/translations.php
@@ -126,7 +126,6 @@ return array(
'The project name is required' => 'A projekt nevét meg kell adni',
'This project must be unique' => 'A projekt nevének egyedinek kell lennie',
'The title is required' => 'A címet meg kell adni',
- 'There is no active project, the first step is to create a new project.' => 'Nincs aktív projekt. Először létre kell hozni egy projektet.',
'Settings saved successfully.' => 'A beállítások sikeresen mentve.',
'Unable to save your settings.' => 'A beállítások mentése sikertelen.',
'Database optimization done.' => 'Adatbázis optimalizálás kész.',
@@ -264,11 +263,10 @@ return array(
'%d comments' => '%d megjegyzés',
'%d comment' => '%d megjegyzés',
'Email address invalid' => 'Érvénytelen e-mail cím',
- 'Your Google Account is not linked anymore to your profile.' => 'Google Fiók már nincs a profilhoz kapcsolva.',
- 'Unable to unlink your Google Account.' => 'Leválasztás a Google fiókról nem lehetséges.',
- 'Google authentication failed' => 'Google azonosítás sikertelen',
- 'Unable to link your Google Account.' => 'A Google profilhoz kapcsolás sikertelen.',
- 'Your Google Account is linked to your profile successfully.' => 'Google fiókkal sikeresen összekapcsolva.',
+ // 'Your external account is not linked anymore to your profile.' => '',
+ // 'Unable to unlink your external account.' => '',
+ // 'External authentication failed' => '',
+ // 'Your external account is linked to your profile successfully.' => '',
'Email' => 'E-mail',
'Link my Google Account' => 'Kapcsold össze a Google fiókkal',
'Unlink my Google Account' => 'Válaszd le a Google fiókomat',
@@ -338,14 +336,9 @@ return array(
'Maximum size: ' => 'Maximális méret: ',
'Unable to upload the file.' => 'Fájl feltöltése nem lehetséges.',
'Display another project' => 'Másik projekt megjelenítése',
- 'Your GitHub account was successfully linked to your profile.' => 'GitHub fiók sikeresen csatolva a profilhoz.',
- 'Unable to link your GitHub Account.' => 'Nem lehet csatolni a GitHub fiókot.',
- 'GitHub authentication failed' => 'GitHub azonosítás sikertelen',
- 'Your GitHub account is no longer linked to your profile.' => 'GitHub fiók már nincs profilhoz kapcsolva.',
- 'Unable to unlink your GitHub Account.' => 'GitHub fiók leválasztása nem lehetséges.',
- 'Login with my GitHub Account' => 'Jelentkezzen be GitHub fiókkal',
- 'Link my GitHub Account' => 'GitHub fiók csatolása',
- 'Unlink my GitHub Account' => 'GitHub fiók leválasztása',
+ 'Login with my Github Account' => 'Jelentkezzen be Github fiókkal',
+ 'Link my Github Account' => 'Github fiók csatolása',
+ 'Unlink my Github Account' => 'Github fiók leválasztása',
'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',
@@ -403,7 +396,7 @@ return array(
'Enabled' => 'Engedélyezve',
'Disabled' => 'Letiltva',
'Google account linked' => 'Google fiók összekapcsolva',
- 'Github account linked' => 'GitHub fiók összekapcsolva',
+ 'Github account linked' => 'Github fiók összekapcsolva',
'Username:' => 'Felhasználónév:',
'Name:' => 'Név:',
'Email:' => 'E-mail:',
@@ -458,12 +451,12 @@ return array(
'%s changed the assignee of the task %s to %s' => '%s a felelőst %s módosította: %s',
'New password for the user "%s"' => 'Felhasználó új jelszava: %s',
'Choose an event' => 'Válasszon eseményt',
- 'Github commit received' => 'GitHub commit érkezett',
- 'Github issue opened' => 'GitHub issue nyitás',
- 'Github issue closed' => 'GitHub issue zárás',
- 'Github issue reopened' => 'GitHub issue újranyitva',
- 'Github issue assignee change' => 'GitHub issue felelős változás',
- 'Github issue label change' => 'GitHub issue címke változás',
+ 'Github commit received' => 'Github commit érkezett',
+ 'Github issue opened' => 'Github issue nyitás',
+ 'Github issue closed' => 'Github issue zárás',
+ 'Github issue reopened' => 'Github issue újranyitva',
+ 'Github issue assignee change' => 'Github issue felelős változás',
+ 'Github issue label change' => 'Github issue címke változás',
'Create a task from an external provider' => 'Feladat létrehozása külsős számára',
'Change the assignee based on an external username' => 'Felelős módosítása külső felhasználónév alapján',
'Change the category based on an external label' => 'Kategória módosítása külső címke alapján',
@@ -615,8 +608,7 @@ return array(
'Time Tracking' => 'Idő követés',
'You already have one subtask in progress' => 'Már van egy folyamatban levő részfeladata',
'Which parts of the project do you want to duplicate?' => 'A projekt mely részeit szeretné másolni?',
- 'Disable login form' => 'Bejelentkező képernyő tiltása',
- 'Show/hide calendar' => 'Naptár megjelenítés/elrejtés',
+ // 'Disallow login form' => '',
'Bitbucket commit received' => 'Bitbucket commit érkezett',
'Bitbucket webhooks' => 'Bitbucket webhooks',
'Help on Bitbucket webhooks' => 'Bitbucket webhooks súgó',
@@ -971,4 +963,39 @@ return array(
// 'Search by category: ' => '',
// 'Search by description: ' => '',
// 'Search by due date: ' => '',
+ // 'Lead and Cycle time for "%s"' => '',
+ // 'Average time spent into each column for "%s"' => '',
+ // 'Average time spent into each column' => '',
+ // 'Average time spent' => '',
+ // 'This chart show the average time spent into each column for the last %d tasks.' => '',
+ // 'Average Lead and Cycle time' => '',
+ // 'Average lead time: ' => '',
+ // 'Average cycle time: ' => '',
+ // 'Cycle Time' => '',
+ // 'Lead Time' => '',
+ // 'This chart show the average lead and cycle time for the last %d tasks over the time.' => '',
+ // 'Average time into each column' => '',
+ // 'Lead and cycle time' => '',
+ // 'Google Authentication' => '',
+ // 'Help on Google authentication' => '',
+ // 'Github Authentication' => '',
+ // 'Help on Github authentication' => '',
+ // 'Channel/Group/User (Optional)' => '',
+ // 'Lead time: ' => '',
+ // 'Cycle time: ' => '',
+ // 'Time spent into each column' => '',
+ // 'The lead time is the duration between the task creation and the completion.' => '',
+ // 'The cycle time is the duration between the start date and the completion.' => '',
+ // 'If the task is not closed the current time is used instead of the completion date.' => '',
+ // 'Set automatically the start date' => '',
+ // 'Edit Authentication' => '',
+ // 'Google Id' => '',
+ // 'Github Id' => '',
+ // 'Remote user' => '',
+ // 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => '',
+ // 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => '',
+ // 'By @%s on Gitlab' => '',
+ // 'Gitlab issue comment created' => '',
+ // 'New remote user' => '',
+ // 'New local user' => '',
);
diff --git a/app/Locale/it_IT/translations.php b/app/Locale/it_IT/translations.php
index 0fe5547b..6dd60c18 100644
--- a/app/Locale/it_IT/translations.php
+++ b/app/Locale/it_IT/translations.php
@@ -126,7 +126,6 @@ return array(
'The project name is required' => 'Si richiede il nome del progetto',
'This project must be unique' => 'Il nome del progetto deve essere unico',
'The title is required' => 'Si richiede un titolo',
- 'There is no active project, the first step is to create a new project.' => 'Non ci sono progetti attivi, il primo passo consiste in creare un nuovo progetto.',
'Settings saved successfully.' => 'Impostazioni salvate correttamente.',
'Unable to save your settings.' => 'Non si possono salvare le impostazioni.',
'Database optimization done.' => 'Ottimizzazione della base dati conclusa.',
@@ -264,11 +263,10 @@ return array(
'%d comments' => '%d commenti',
'%d comment' => '%d commento',
'Email address invalid' => 'Indirizzo e-mail sbagliato',
- 'Your Google Account is not linked anymore to your profile.' => 'Il suo account Google non è più collegato al suo profilo',
- 'Unable to unlink your Google Account.' => 'Non si può svincolare l\'account di Google.',
- 'Google authentication failed' => 'Autenticazione con Google non riuscita',
- 'Unable to link your Google Account.' => 'Non si può collegare il tuo account di Google.',
- 'Your Google Account is linked to your profile successfully.' => 'Il tuo account di Google è stato collegato correttamente al tuo profilo.',
+ // 'Your external account is not linked anymore to your profile.' => '',
+ // 'Unable to unlink your external account.' => '',
+ // 'External authentication failed' => '',
+ // 'Your external account is linked to your profile successfully.' => '',
'Email' => 'E-mail',
'Link my Google Account' => 'Collegare il mio Account di Google',
'Unlink my Google Account' => 'Scollegare il mio account di Google',
@@ -338,14 +336,9 @@ return array(
'Maximum size: ' => 'Dimensioni massime',
'Unable to upload the file.' => 'Non si può caricare il file.',
'Display another project' => 'Mostrare un altro progetto',
- 'Your GitHub account was successfully linked to your profile.' => 'Il suo account di Github è stato collegato correttamente col tuo profilo.',
- 'Unable to link your GitHub Account.' => 'Non si può collegarre il tuo account di Github.',
- 'GitHub authentication failed' => 'Autenticazione con GitHub non riuscita',
- 'Your GitHub account is no longer linked to your profile.' => 'Il tuo account di Github non è più collegato al tuo profilo.',
- 'Unable to unlink your GitHub Account.' => 'Non si può collegare il tuo account di Github.',
- 'Login with my GitHub Account' => 'Entrare col tuo account di Github',
- 'Link my GitHub Account' => 'Collegare il mio account Github',
- 'Unlink my GitHub Account' => 'Scollegare il mio account di Github',
+ 'Login with my Github Account' => 'Entrare col tuo account di Github',
+ 'Link my Github Account' => 'Collegare il mio account Github',
+ 'Unlink my Github Account' => 'Scollegare il mio account di Github',
'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' => 'Esportazione di compiti',
@@ -615,8 +608,7 @@ return array(
'Time Tracking' => 'Gestione del tempo',
'You already have one subtask in progress' => 'Hai già un sotto-compito in progresso',
'Which parts of the project do you want to duplicate?' => 'Quali parti del progetto vuoi duplicare?',
- 'Disable login form' => 'Disabilita form di login',
- 'Show/hide calendar' => 'Mostra/nascondi calendario',
+ // 'Disallow login form' => '',
'Bitbucket commit received' => 'Commit ricevuto da Bitbucket',
'Bitbucket webhooks' => 'Webhooks di Bitbucket',
'Help on Bitbucket webhooks' => 'Guida ai Webhooks di Bitbucket',
@@ -971,4 +963,39 @@ return array(
// 'Search by category: ' => '',
// 'Search by description: ' => '',
// 'Search by due date: ' => '',
+ // 'Lead and Cycle time for "%s"' => '',
+ // 'Average time spent into each column for "%s"' => '',
+ // 'Average time spent into each column' => '',
+ // 'Average time spent' => '',
+ // 'This chart show the average time spent into each column for the last %d tasks.' => '',
+ // 'Average Lead and Cycle time' => '',
+ // 'Average lead time: ' => '',
+ // 'Average cycle time: ' => '',
+ // 'Cycle Time' => '',
+ // 'Lead Time' => '',
+ // 'This chart show the average lead and cycle time for the last %d tasks over the time.' => '',
+ // 'Average time into each column' => '',
+ // 'Lead and cycle time' => '',
+ // 'Google Authentication' => '',
+ // 'Help on Google authentication' => '',
+ // 'Github Authentication' => '',
+ // 'Help on Github authentication' => '',
+ // 'Channel/Group/User (Optional)' => '',
+ // 'Lead time: ' => '',
+ // 'Cycle time: ' => '',
+ // 'Time spent into each column' => '',
+ // 'The lead time is the duration between the task creation and the completion.' => '',
+ // 'The cycle time is the duration between the start date and the completion.' => '',
+ // 'If the task is not closed the current time is used instead of the completion date.' => '',
+ // 'Set automatically the start date' => '',
+ // 'Edit Authentication' => '',
+ // 'Google Id' => '',
+ // 'Github Id' => '',
+ // 'Remote user' => '',
+ // 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => '',
+ // 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => '',
+ // 'By @%s on Gitlab' => '',
+ // 'Gitlab issue comment created' => '',
+ // 'New remote user' => '',
+ // 'New local user' => '',
);
diff --git a/app/Locale/ja_JP/translations.php b/app/Locale/ja_JP/translations.php
index 6c9a205e..e52f82b6 100644
--- a/app/Locale/ja_JP/translations.php
+++ b/app/Locale/ja_JP/translations.php
@@ -126,7 +126,6 @@ return array(
'The project name is required' => 'プロジェクト名が必要です',
'This project must be unique' => 'プロジェクト名がすでに使われています',
'The title is required' => 'タイトルが必要です',
- 'There is no active project, the first step is to create a new project.' => '有効なプロジェクトがありません。まず新しいプロジェクトを作ります。',
'Settings saved successfully.' => '設定を保存しました。',
'Unable to save your settings.' => '設定の保存に失敗しました。',
'Database optimization done.' => 'データベースの最適化が終わりました。',
@@ -264,11 +263,10 @@ return array(
'%d comments' => '%d 個のコメント',
'%d comment' => '%d 個のコメント',
'Email address invalid' => 'メールアドレスが正しくありません',
- 'Your Google Account is not linked anymore to your profile.' => 'Google アカウントとのリンクが解除されました',
- 'Unable to unlink your Google Account.' => 'Google アカウントとのリンク解除に失敗しました',
- 'Google authentication failed' => 'Google の認証に失敗しました',
- 'Unable to link your Google Account.' => 'Google アカウントとのリンクに失敗しました。',
- 'Your Google Account is linked to your profile successfully.' => 'Google アカウントとリンクしました',
+ // 'Your external account is not linked anymore to your profile.' => '',
+ // 'Unable to unlink your external account.' => '',
+ // 'External authentication failed' => '',
+ // 'Your external account is linked to your profile successfully.' => '',
'Email' => 'Email',
'Link my Google Account' => 'Google アカウントをリンクする',
'Unlink my Google Account' => 'Google アカウントのリンクを解除する',
@@ -338,14 +336,9 @@ return array(
'Maximum size: ' => '最大: ',
'Unable to upload the file.' => 'ファイルのアップロードに失敗しました。',
'Display another project' => '別のプロジェクトを表示',
- 'Your GitHub account was successfully linked to your profile.' => 'GitHub アカウントとリンクしました。',
- 'Unable to link your GitHub Account.' => 'GitHub アカウントとリンクできませんでした。',
- 'GitHub authentication failed' => 'GitHub アカウントの認証に失敗しました。',
- 'Your GitHub account is no longer linked to your profile.' => 'GitHub アカウントへのリンクが解除されました。',
- 'Unable to unlink your GitHub Account.' => 'GitHub アカウントのリンク解除に失敗しました。',
- 'Login with my GitHub Account' => 'Github アカウントでログインする',
- 'Link my GitHub Account' => 'Github アカウントをリンクする',
- 'Unlink my GitHub Account' => 'Github アカウントとのリンクを解除する',
+ 'Login with my Github Account' => 'Github アカウントでログインする',
+ 'Link my Github Account' => 'Github アカウントをリンクする',
+ 'Unlink my Github Account' => 'Github アカウントとのリンクを解除する',
'Created by %s' => '%s が作成',
'Last modified on %B %e, %Y at %k:%M %p' => ' %Y/%m/%d %H:%M に変更',
'Tasks Export' => 'タスクの出力',
@@ -403,7 +396,7 @@ return array(
'Enabled' => '有効',
'Disabled' => '無効',
'Google account linked' => 'Google アカウントがリンク',
- 'Github account linked' => 'GitHub のアカウントがリンク',
+ 'Github account linked' => 'Github のアカウントがリンク',
'Username:' => 'ユーザ名:',
'Name:' => '名前:',
'Email:' => 'Email:',
@@ -417,7 +410,7 @@ return array(
'Password modification' => 'パスワードの変更',
'External authentications' => '外部認証',
'Google Account' => 'Google アカウント',
- 'Github Account' => 'GitHub アカウント',
+ 'Github Account' => 'Github アカウント',
'Never connected.' => '未接続。',
'No account linked.' => 'アカウントがリンクしていません。',
'Account linked.' => 'アカウントがリンクしました。',
@@ -615,8 +608,7 @@ return array(
'Time Tracking' => 'タイムトラッキング',
'You already have one subtask in progress' => 'すでに進行中のサブタスクがあります。',
'Which parts of the project do you want to duplicate?' => 'プロジェクトの何を複製しますか?',
- 'Disable login form' => 'ログインフォームの無効化',
- 'Show/hide calendar' => 'カレンダーの表示・非表示',
+ // 'Disallow login form' => '',
'Bitbucket commit received' => 'Bitbucket コミットを受信しました',
'Bitbucket webhooks' => 'Bitbucket Webhooks',
'Help on Bitbucket webhooks' => 'Bitbucket Webhooks のヘルプ',
@@ -971,4 +963,39 @@ return array(
// 'Search by category: ' => '',
// 'Search by description: ' => '',
// 'Search by due date: ' => '',
+ // 'Lead and Cycle time for "%s"' => '',
+ // 'Average time spent into each column for "%s"' => '',
+ // 'Average time spent into each column' => '',
+ // 'Average time spent' => '',
+ // 'This chart show the average time spent into each column for the last %d tasks.' => '',
+ // 'Average Lead and Cycle time' => '',
+ // 'Average lead time: ' => '',
+ // 'Average cycle time: ' => '',
+ // 'Cycle Time' => '',
+ // 'Lead Time' => '',
+ // 'This chart show the average lead and cycle time for the last %d tasks over the time.' => '',
+ // 'Average time into each column' => '',
+ // 'Lead and cycle time' => '',
+ // 'Google Authentication' => '',
+ // 'Help on Google authentication' => '',
+ // 'Github Authentication' => '',
+ // 'Help on Github authentication' => '',
+ // 'Channel/Group/User (Optional)' => '',
+ // 'Lead time: ' => '',
+ // 'Cycle time: ' => '',
+ // 'Time spent into each column' => '',
+ // 'The lead time is the duration between the task creation and the completion.' => '',
+ // 'The cycle time is the duration between the start date and the completion.' => '',
+ // 'If the task is not closed the current time is used instead of the completion date.' => '',
+ // 'Set automatically the start date' => '',
+ // 'Edit Authentication' => '',
+ // 'Google Id' => '',
+ // 'Github Id' => '',
+ // 'Remote user' => '',
+ // 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => '',
+ // 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => '',
+ // 'By @%s on Gitlab' => '',
+ // 'Gitlab issue comment created' => '',
+ // 'New remote user' => '',
+ // 'New local user' => '',
);
diff --git a/app/Locale/nl_NL/translations.php b/app/Locale/nl_NL/translations.php
index 7a31b975..ed8be05b 100644
--- a/app/Locale/nl_NL/translations.php
+++ b/app/Locale/nl_NL/translations.php
@@ -126,7 +126,6 @@ return array(
'The project name is required' => 'De projectnaam is verplicht',
'This project must be unique' => 'Dit project moet uniek zijn',
'The title is required' => 'De titel is verplicht',
- 'There is no active project, the first step is to create a new project.' => 'Er is geen actief project, de eerste stap is een nieuw project aanmaken.',
'Settings saved successfully.' => 'Instellingen succesvol opgeslagen.',
'Unable to save your settings.' => 'Instellingen opslaan niet gelukt.',
'Database optimization done.' => 'Database optimaliseren voltooid.',
@@ -264,11 +263,10 @@ return array(
'%d comments' => '%d commentaren',
'%d comment' => '%d commentaar',
'Email address invalid' => 'Ongeldig emailadres',
- 'Your Google Account is not linked anymore to your profile.' => 'Uw Google Account is niet meer aan uw profiel gelinkt.',
- 'Unable to unlink your Google Account.' => 'Verwijderen link met Google Account niet gelukt.',
- 'Google authentication failed' => 'Google authenticatie niet gelukt',
- 'Unable to link your Google Account.' => 'Linken met Google Account niet gelukt',
- 'Your Google Account is linked to your profile successfully.' => 'Linken met Google Account succesvol.',
+ // 'Your external account is not linked anymore to your profile.' => '',
+ // 'Unable to unlink your external account.' => '',
+ // 'External authentication failed' => '',
+ // 'Your external account is linked to your profile successfully.' => '',
'Email' => 'Email',
'Link my Google Account' => 'Link mijn Google Account',
'Unlink my Google Account' => 'Link met Google Account verwijderen',
@@ -338,14 +336,9 @@ return array(
'Maximum size: ' => 'Maximale grootte : ',
'Unable to upload the file.' => 'Uploaden van bestand niet gelukt.',
'Display another project' => 'Een ander project weergeven',
- 'Your GitHub account was successfully linked to your profile.' => 'Uw Github Account is succesvol gelinkt aan uw profiel.',
- 'Unable to link your GitHub Account.' => 'Linken van uw Github Account niet gelukt.',
- 'GitHub authentication failed' => 'Github Authenticatie niet gelukt',
- 'Your GitHub account is no longer linked to your profile.' => 'Uw Github Account is niet langer gelinkt aan uw profiel.',
- 'Unable to unlink your GitHub Account.' => 'Verwijdern van de link met uw Github Account niet gelukt.',
- 'Login with my GitHub Account' => 'Login met mijn Github Account',
- 'Link my GitHub Account' => 'Link met mijn Github',
- 'Unlink my GitHub Account' => 'Link met mijn Github verwijderen',
+ 'Login with my Github Account' => 'Login met mijn Github Account',
+ 'Link my Github Account' => 'Link met mijn Github',
+ 'Unlink my Github Account' => 'Link met mijn Github verwijderen',
'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',
@@ -615,8 +608,7 @@ return array(
'Time Tracking' => 'Tijdschrijven',
'You already have one subtask in progress' => 'U heeft al een subtaak in behandeling',
'Which parts of the project do you want to duplicate?' => 'Welke onderdelen van het project wilt u dupliceren?',
- 'Disable login form' => 'Schakel login scherm uit',
- 'Show/hide calendar' => 'Toon/verberg agenda',
+ // 'Disallow login form' => '',
'Bitbucket commit received' => 'Bitbucket commit ontvangen',
'Bitbucket webhooks' => 'Bitbucket webhooks',
'Help on Bitbucket webhooks' => 'Help bij Bitbucket webhooks',
@@ -971,4 +963,39 @@ return array(
// 'Search by category: ' => '',
// 'Search by description: ' => '',
// 'Search by due date: ' => '',
+ // 'Lead and Cycle time for "%s"' => '',
+ // 'Average time spent into each column for "%s"' => '',
+ // 'Average time spent into each column' => '',
+ // 'Average time spent' => '',
+ // 'This chart show the average time spent into each column for the last %d tasks.' => '',
+ // 'Average Lead and Cycle time' => '',
+ // 'Average lead time: ' => '',
+ // 'Average cycle time: ' => '',
+ // 'Cycle Time' => '',
+ // 'Lead Time' => '',
+ // 'This chart show the average lead and cycle time for the last %d tasks over the time.' => '',
+ // 'Average time into each column' => '',
+ // 'Lead and cycle time' => '',
+ // 'Google Authentication' => '',
+ // 'Help on Google authentication' => '',
+ // 'Github Authentication' => '',
+ // 'Help on Github authentication' => '',
+ // 'Channel/Group/User (Optional)' => '',
+ // 'Lead time: ' => '',
+ // 'Cycle time: ' => '',
+ // 'Time spent into each column' => '',
+ // 'The lead time is the duration between the task creation and the completion.' => '',
+ // 'The cycle time is the duration between the start date and the completion.' => '',
+ // 'If the task is not closed the current time is used instead of the completion date.' => '',
+ // 'Set automatically the start date' => '',
+ // 'Edit Authentication' => '',
+ // 'Google Id' => '',
+ // 'Github Id' => '',
+ // 'Remote user' => '',
+ // 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => '',
+ // 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => '',
+ // 'By @%s on Gitlab' => '',
+ // 'Gitlab issue comment created' => '',
+ // 'New remote user' => '',
+ // 'New local user' => '',
);
diff --git a/app/Locale/pl_PL/translations.php b/app/Locale/pl_PL/translations.php
index 637eefb4..9f8641a0 100644
--- a/app/Locale/pl_PL/translations.php
+++ b/app/Locale/pl_PL/translations.php
@@ -126,7 +126,6 @@ return array(
'The project name is required' => 'Nazwa projektu jest wymagana',
'This project must be unique' => 'Projekt musi być unikalny',
'The title is required' => 'Tutył jest wymagany',
- 'There is no active project, the first step is to create a new project.' => 'Brak aktywnych projektów. Pierwszym krokiem jest utworzenie nowego projektu.',
'Settings saved successfully.' => 'Ustawienia zapisane.',
'Unable to save your settings.' => 'Nie udało się zapisać ustawień.',
'Database optimization done.' => 'Optymalizacja bazy danych zakończona.',
@@ -264,11 +263,10 @@ return array(
'%d comments' => '%d Komentarzy',
'%d comment' => '%d Komentarz',
'Email address invalid' => 'Błędny adres email',
- 'Your Google Account is not linked anymore to your profile.' => 'Twoje konto Google nie jest już połączone',
- 'Unable to unlink your Google Account.' => 'Nie można odłączyć konta Google',
- 'Google authentication failed' => 'Autentykacja Google nieudana',
- 'Unable to link your Google Account.' => 'Nie można podłączyć konta Google',
- 'Your Google Account is linked to your profile successfully.' => 'Podłączanie konta Google ukończone pomyślnie',
+ // 'Your external account is not linked anymore to your profile.' => '',
+ // 'Unable to unlink your external account.' => '',
+ // 'External authentication failed' => '',
+ // 'Your external account is linked to your profile successfully.' => '',
'Email' => 'Email',
'Link my Google Account' => 'Połącz z kontem Google',
'Unlink my Google Account' => 'Rozłącz z kontem Google',
@@ -338,14 +336,9 @@ return array(
'Maximum size: ' => 'Maksymalny rozmiar: ',
'Unable to upload the file.' => 'Nie można wczytać pliku.',
'Display another project' => 'Wyświetl inny projekt',
- 'Your GitHub account was successfully linked to your profile.' => 'Konto Github podłączone pomyślnie.',
- 'Unable to link your GitHub Account.' => 'Nie można połączyć z kontem Github.',
- 'GitHub authentication failed' => 'Autentykacja Github nieudana',
- 'Your GitHub account is no longer linked to your profile.' => 'Konto Github nie jest już podłączone do twojego profilu.',
- 'Unable to unlink your GitHub Account.' => 'Nie można odłączyć konta Github.',
- 'Login with my GitHub Account' => 'Zaloguj przy użyciu konta Github',
- 'Link my GitHub Account' => 'Podłącz konto Github',
- 'Unlink my GitHub Account' => 'Odłącz konto Github',
+ 'Login with my Github Account' => 'Zaloguj przy użyciu konta Github',
+ 'Link my Github Account' => 'Podłącz konto Github',
+ 'Unlink my Github Account' => 'Odłącz konto Github',
'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ń',
@@ -615,8 +608,7 @@ return array(
'Time Tracking' => 'Śledzenie czasu',
'You already have one subtask in progress' => 'Masz już zadanie o statusie "w trakcie"',
'Which parts of the project do you want to duplicate?' => 'Które elementy projektu chcesz zduplikować?',
- 'Disable login form' => 'Wyłącz formularz logowania',
- 'Show/hide calendar' => 'Pokaż/Ukryj kalendarz',
+ // 'Disallow login form' => '',
// 'Bitbucket commit received' => '',
// 'Bitbucket webhooks' => '',
// 'Help on Bitbucket webhooks' => '',
@@ -971,4 +963,39 @@ return array(
// 'Search by category: ' => '',
// 'Search by description: ' => '',
// 'Search by due date: ' => '',
+ // 'Lead and Cycle time for "%s"' => '',
+ // 'Average time spent into each column for "%s"' => '',
+ // 'Average time spent into each column' => '',
+ // 'Average time spent' => '',
+ // 'This chart show the average time spent into each column for the last %d tasks.' => '',
+ // 'Average Lead and Cycle time' => '',
+ // 'Average lead time: ' => '',
+ // 'Average cycle time: ' => '',
+ // 'Cycle Time' => '',
+ // 'Lead Time' => '',
+ // 'This chart show the average lead and cycle time for the last %d tasks over the time.' => '',
+ // 'Average time into each column' => '',
+ // 'Lead and cycle time' => '',
+ // 'Google Authentication' => '',
+ // 'Help on Google authentication' => '',
+ // 'Github Authentication' => '',
+ // 'Help on Github authentication' => '',
+ // 'Channel/Group/User (Optional)' => '',
+ // 'Lead time: ' => '',
+ // 'Cycle time: ' => '',
+ // 'Time spent into each column' => '',
+ // 'The lead time is the duration between the task creation and the completion.' => '',
+ // 'The cycle time is the duration between the start date and the completion.' => '',
+ // 'If the task is not closed the current time is used instead of the completion date.' => '',
+ // 'Set automatically the start date' => '',
+ // 'Edit Authentication' => '',
+ // 'Google Id' => '',
+ // 'Github Id' => '',
+ // 'Remote user' => '',
+ // 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => '',
+ // 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => '',
+ // 'By @%s on Gitlab' => '',
+ // 'Gitlab issue comment created' => '',
+ // 'New remote user' => '',
+ // 'New local user' => '',
);
diff --git a/app/Locale/pt_BR/translations.php b/app/Locale/pt_BR/translations.php
index 1031f1e2..68de0368 100644
--- a/app/Locale/pt_BR/translations.php
+++ b/app/Locale/pt_BR/translations.php
@@ -20,15 +20,15 @@ return array(
'Red' => 'Vermelho',
'Orange' => 'Laranja',
'Grey' => 'Cinza',
- // 'Brown' => '',
- // 'Deep Orange' => '',
- // 'Dark Grey' => '',
- // 'Pink' => '',
- // 'Teal' => '',
- // 'Cyan' => '',
- // 'Lime' => '',
- // 'Light Green' => '',
- // 'Amber' => '',
+ 'Brown' => 'Marrom',
+ 'Deep Orange' => 'Laranja escuro',
+ 'Dark Grey' => 'Cinza escuro',
+ 'Pink' => 'Roza',
+ 'Teal' => 'Turquesa',
+ 'Cyan' => 'Azul intenso',
+ 'Lime' => 'Verde limão',
+ 'Light Green' => 'Verde claro',
+ 'Amber' => 'Âmbar',
'Save' => 'Salvar',
'Login' => 'Login',
'Official website:' => 'Site oficial:',
@@ -126,7 +126,6 @@ return array(
'The project name is required' => 'O nome do projeto é obrigatório',
'This project must be unique' => 'Este projeto deve ser único',
'The title is required' => 'O título é obrigatório',
- 'There is no active project, the first step is to create a new project.' => 'Não há projeto ativo. O primeiro passo é criar um novo projeto.',
'Settings saved successfully.' => 'Configurações salvas com sucesso.',
'Unable to save your settings.' => 'Não é possível salvar suas configurações.',
'Database optimization done.' => 'Otimização do banco de dados finalizada.',
@@ -264,11 +263,10 @@ return array(
'%d comments' => '%d comentários',
'%d comment' => '%d comentário',
'Email address invalid' => 'Endereço de e-mail inválido',
- 'Your Google Account is not linked anymore to your profile.' => 'Sua conta do Google não está mais associada ao seu perfil.',
- 'Unable to unlink your Google Account.' => 'Não foi possível desassociar a sua Conta do Google.',
- 'Google authentication failed' => 'Autenticação do Google falhou.',
- 'Unable to link your Google Account.' => 'Não foi possível associar a sua Conta do Google.',
- 'Your Google Account is linked to your profile successfully.' => 'Sua Conta do Google foi associada ao seu perfil com sucesso.',
+ // 'Your external account is not linked anymore to your profile.' => '',
+ // 'Unable to unlink your external account.' => '',
+ // 'External authentication failed' => '',
+ // 'Your external account is linked to your profile successfully.' => '',
'Email' => 'E-mail',
'Link my Google Account' => 'Vincular minha Conta do Google',
'Unlink my Google Account' => 'Desvincular minha Conta do Google',
@@ -338,14 +336,9 @@ return array(
'Maximum size: ' => 'Tamanho máximo:',
'Unable to upload the file.' => 'Não foi possível carregar o arquivo.',
'Display another project' => 'Exibir outro projeto',
- 'Your GitHub account was successfully linked to your profile.' => 'A sua Conta do GitHub foi associada com sucesso ao seu perfil.',
- 'Unable to link your GitHub Account.' => 'Não foi possível associar sua Conta do GitHub.',
- 'GitHub authentication failed' => 'Autenticação do GitHub falhou',
- 'Your GitHub account is no longer linked to your profile.' => 'A sua Conta do GitHub não está mais associada ao seu perfil.',
- 'Unable to unlink your GitHub Account.' => 'Não foi possível desassociar a sua Conta do GitHub.',
- 'Login with my GitHub Account' => 'Entrar com minha Conta do GitHub',
- 'Link my GitHub Account' => 'Associar à minha Conta do GitHub',
- 'Unlink my GitHub Account' => 'Desassociar a minha Conta do GitHub',
+ 'Login with my Github Account' => 'Entrar com minha Conta do Github',
+ 'Link my Github Account' => 'Associar à minha Conta do Github',
+ 'Unlink my Github Account' => 'Desassociar a minha Conta do Github',
'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',
@@ -615,8 +608,7 @@ return array(
'Time Tracking' => 'Gestão de tempo',
'You already have one subtask in progress' => 'Você já tem um subtarefa em andamento',
'Which parts of the project do you want to duplicate?' => 'Quais as partes do projeto você deseja duplicar?',
- 'Disable login form' => 'Desativar o formulário de login',
- 'Show/hide calendar' => 'Mostrar / ocultar calendário',
+ // 'Disallow login form' => '',
'Bitbucket commit received' => '"Commit" recebido via Bitbucket',
'Bitbucket webhooks' => 'Webhook Bitbucket',
'Help on Bitbucket webhooks' => 'Ajuda sobre os webhooks Bitbucket',
@@ -858,117 +850,152 @@ return array(
'Two factor authentication disabled' => 'Autenticação à fator duplo desativado',
'Two factor authentication enabled' => 'Autenticação à fator duplo activado',
'Unable to update this user.' => 'Impossível de atualizar esse usuário.',
- // 'There is no user management for private projects.' => '',
- // 'User that will receive the email' => '',
- // 'Email subject' => '',
- // 'Date' => '',
- // 'By @%s on Bitbucket' => '',
- // 'Bitbucket Issue' => '',
- // 'Commit made by @%s on Bitbucket' => '',
- // 'Commit made by @%s on Github' => '',
- // 'By @%s on Github' => '',
- // 'Commit made by @%s on Gitlab' => '',
- // 'Add a comment log when moving the task between columns' => '',
- // 'Move the task to another column when the category is changed' => '',
- // 'Send a task by email to someone' => '',
- // 'Reopen a task' => '',
- // 'Bitbucket issue opened' => '',
- // 'Bitbucket issue closed' => '',
- // 'Bitbucket issue reopened' => '',
- // 'Bitbucket issue assignee change' => '',
- // 'Bitbucket issue comment created' => '',
- // 'Column change' => '',
- // 'Position change' => '',
- // 'Swimlane change' => '',
- // 'Assignee change' => '',
- // '[%s] Overdue tasks' => '',
- // 'Notification' => '',
- // '%s moved the task #%d to the first swimlane' => '',
- // '%s moved the task #%d to the swimlane "%s"' => '',
- // 'Swimlane' => '',
- // 'Budget overview' => '',
- // 'Type' => '',
- // 'There is not enough data to show something.' => '',
- // 'Gravatar' => '',
- // 'Hipchat' => '',
- // 'Slack' => '',
- // '%s moved the task %s to the first swimlane' => '',
- // '%s moved the task %s to the swimlane "%s"' => '',
- // 'This report contains all subtasks information for the given date range.' => '',
- // 'This report contains all tasks information for the given date range.' => '',
- // 'Project activities for %s' => '',
- // 'view the board on Kanboard' => '',
- // 'The task have been moved to the first swimlane' => '',
- // 'The task have been moved to another swimlane:' => '',
- // 'Overdue tasks for the project "%s"' => '',
- // 'New title: %s' => '',
- // 'The task is not assigned anymore' => '',
- // 'New assignee: %s' => '',
- // 'There is no category now' => '',
- // 'New category: %s' => '',
- // 'New color: %s' => '',
- // 'New complexity: %d' => '',
- // 'The due date have been removed' => '',
- // 'There is no description anymore' => '',
- // 'Recurrence settings have been modified' => '',
- // 'Time spent changed: %sh' => '',
- // 'Time estimated changed: %sh' => '',
- // 'The field "%s" have been updated' => '',
- // 'The description have been modified' => '',
- // 'Do you really want to close the task "%s" as well as all subtasks?' => '',
- // 'Swimlane: %s' => '',
- // 'I want to receive notifications for:' => '',
- // 'All tasks' => '',
- // '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' => '',
- // '%k:%M %p' => '',
- // '%%Y-%%m-%%d' => '',
- // 'Total for all columns' => '',
- // 'You need at least 2 days of data to show the chart.' => '',
- // '<15m' => '',
- // '<30m' => '',
- // 'Stop timer' => '',
- // 'Start timer' => '',
- // 'Add project member' => '',
- // 'Enable notifications' => '',
- // 'My activity stream' => '',
- // 'My calendar' => '',
- // 'Search tasks' => '',
- // 'Back to the calendar' => '',
- // 'Filters' => '',
- // 'Reset filters' => '',
- // 'My tasks due tomorrow' => '',
- // 'Tasks due today' => '',
- // 'Tasks due tomorrow' => '',
- // 'Tasks due yesterday' => '',
- // 'Closed tasks' => '',
- // 'Open tasks' => '',
- // 'Not assigned' => '',
- // 'View advanced search syntax' => '',
- // 'Overview' => '',
- // '%b %e %Y' => '',
- // 'Board/Calendar/List view' => '',
- // 'Switch to the board view' => '',
- // 'Switch to the calendar view' => '',
- // 'Switch to the list view' => '',
- // 'Go to the search/filter box' => '',
- // 'There is no activity yet.' => '',
- // 'No tasks found.' => '',
- // 'Keyboard shortcut: "%s"' => '',
- // 'List' => '',
- // 'Filter' => '',
- // 'Advanced search' => '',
- // 'Example of query: ' => '',
- // 'Search by project: ' => '',
- // 'Search by column: ' => '',
- // 'Search by assignee: ' => '',
- // 'Search by color: ' => '',
- // 'Search by category: ' => '',
- // 'Search by description: ' => '',
- // 'Search by due date: ' => '',
+ 'There is no user management for private projects.' => 'Não há gerenciamento de usuários para projetos privados.',
+ 'User that will receive the email' => 'O usuário que vai receber o e-mail',
+ 'Email subject' => 'Assunto do e-mail',
+ 'Date' => 'Data',
+ 'By @%s on Bitbucket' => 'Por @%s no Bitbucket',
+ 'Bitbucket Issue' => 'Bitbucket Issue',
+ 'Commit made by @%s on Bitbucket' => 'Commit feito por @%s no Bitbucket',
+ 'Commit made by @%s on Github' => 'Commit feito por @%s no Github',
+ 'By @%s on Github' => 'Por @%s no Github',
+ 'Commit made by @%s on Gitlab' => 'Commit feito por @%s no Gitlab',
+ 'Add a comment log when moving the task between columns' => 'Adicionar um comentário de log quando uma tarefa é movida para uma outra coluna',
+ 'Move the task to another column when the category is changed' => 'Mover uma tarefa para outra coluna quando a categoria mudou',
+ 'Send a task by email to someone' => 'Enviar uma tarefa por e-mail a alguém',
+ 'Reopen a task' => 'Reabrir uma tarefa',
+ 'Bitbucket issue opened' => 'Bitbucket issue opened',
+ 'Bitbucket issue closed' => 'Bitbucket issue closed',
+ 'Bitbucket issue reopened' => 'Bitbucket issue reopened',
+ 'Bitbucket issue assignee change' => 'Bitbucket issue assignee change',
+ 'Bitbucket issue comment created' => 'Bitbucket issue comment created',
+ 'Column change' => 'Mudança de coluna',
+ 'Position change' => 'Mudança de posição',
+ 'Swimlane change' => 'Mudança de swimlane',
+ 'Assignee change' => 'Mudança do designado',
+ '[%s] Overdue tasks' => '[%s] Tarefas atrasadas',
+ 'Notification' => 'Notificação',
+ '%s moved the task #%d to the first swimlane' => '%s moveu a tarefa n° %d no primeiro swimlane',
+ '%s moved the task #%d to the swimlane "%s"' => '%s moveu a tarefa n° %d no swimlane "%s"',
+ 'Swimlane' => 'Swimlane',
+ 'Budget overview' => 'Visão geral do orçamento',
+ 'Type' => 'Tipo',
+ 'There is not enough data to show something.' => 'Não há dados suficientes para mostrar alguma coisa.',
+ 'Gravatar' => 'Gravatar',
+ 'Hipchat' => 'Hipchat',
+ 'Slack' => 'Slack',
+ '%s moved the task %s to the first swimlane' => '%s moveu a tarefa %s no primeiro swimlane',
+ '%s moved the task %s to the swimlane "%s"' => '%s moveu a tarefa %s no swimlane "%s"',
+ 'This report contains all subtasks information for the given date range.' => 'Este relatório contém informações de todas as sub-tarefas para o período selecionado.',
+ 'This report contains all tasks information for the given date range.' => 'Este relatório contém informações de todas as tarefas para o período selecionado.',
+ 'Project activities for %s' => 'Atividade do projeto "%s"',
+ 'view the board on Kanboard' => 'ver o painel no Kanboard',
+ 'The task have been moved to the first swimlane' => 'A tarefa foi movida para o primeiro Swimlane',
+ 'The task have been moved to another swimlane:' => 'A tarefa foi movida para outro Swimlane',
+ 'Overdue tasks for the project "%s"' => 'Tarefas atrasadas para o projeto "%s"',
+ 'New title: %s' => 'Novo título: %s',
+ 'The task is not assigned anymore' => 'Agora a tarefa não está mais atribuída',
+ 'New assignee: %s' => 'Novo designado: %s',
+ 'There is no category now' => 'Agora não tem mais categoria',
+ 'New category: %s' => 'Nova categoria: %s',
+ 'New color: %s' => 'Nova cor: %s',
+ 'New complexity: %d' => 'Nova complexidade: %d',
+ 'The due date have been removed' => 'A data limite foi retirada',
+ 'There is no description anymore' => 'Agora não tem mais descrição',
+ 'Recurrence settings have been modified' => 'As configurações da recorrência foram modificadas',
+ 'Time spent changed: %sh' => 'O tempo despendido foi mudado: %sh',
+ 'Time estimated changed: %sh' => 'O tempo estimado foi mudado/ %sh',
+ 'The field "%s" have been updated' => 'O campo "%s" foi atualizada',
+ 'The description have been modified' => 'A descrição foi modificada',
+ 'Do you really want to close the task "%s" as well as all subtasks?' => 'Você realmente quer fechar a tarefa "%s" e todas as suas sub-tarefas?',
+ 'Swimlane: %s' => 'Swimlane: %s',
+ 'I want to receive notifications for:' => 'Eu quero receber as notificações para:',
+ 'All tasks' => 'Todas as tarefas',
+ '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',
+ '%k:%M %p' => '%H:%M',
+ '%%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.',
+ '<15m' => '<15m',
+ '<30m' => '<30m',
+ 'Stop timer' => 'Stop timer',
+ 'Start timer' => 'Start timer',
+ 'Add project member' => 'Adicionar um membro ao projeto',
+ 'Enable notifications' => 'Ativar as notificações',
+ 'My activity stream' => 'Meu feed de atividade',
+ 'My calendar' => 'Minha agenda',
+ 'Search tasks' => 'Pesquisar tarefas',
+ 'Back to the calendar' => 'Voltar ao calendário',
+ 'Filters' => 'Filtros',
+ 'Reset filters' => 'Redefinir os filtros',
+ 'My tasks due tomorrow' => 'Minhas tarefas que expiram amanhã',
+ 'Tasks due today' => 'Tarefas que expiram hoje',
+ 'Tasks due tomorrow' => 'Tarefas que expiram amanhã',
+ 'Tasks due yesterday' => 'Tarefas que expiraram ontem',
+ 'Closed tasks' => 'Tarefas fechadas',
+ 'Open tasks' => 'Tarefas abertas',
+ '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',
+ 'Switch to the list view' => 'Mudar par o modo Lista',
+ 'Go to the search/filter box' => 'Ir para o campo de pesquisa',
+ 'There is no activity yet.' => 'Não há nenhuma atividade ainda.',
+ 'No tasks found.' => 'Nenhuma tarefa encontrada',
+ 'Keyboard shortcut: "%s"' => 'Tecla de atalho: "%s"',
+ 'List' => 'Lista',
+ 'Filter' => 'Filtro',
+ 'Advanced search' => 'Pesquisa avançada',
+ 'Example of query: ' => 'Exemplo de consulta: ',
+ 'Search by project: ' => 'Pesquisar por projet: ',
+ 'Search by column: ' => 'Pesquisar por coluna: ',
+ 'Search by assignee: ' => 'Pesquisar por designado: ',
+ 'Search by color: ' => 'Pesquisar por cor: ',
+ 'Search by category: ' => 'Pesquisar por categoria: ',
+ 'Search by description: ' => 'Pesquisar por descrição: ',
+ 'Search by due date: ' => 'Pesquisar por data de expiração: ',
+ // 'Lead and Cycle time for "%s"' => '',
+ // 'Average time spent into each column for "%s"' => '',
+ // 'Average time spent into each column' => '',
+ // 'Average time spent' => '',
+ // 'This chart show the average time spent into each column for the last %d tasks.' => '',
+ // 'Average Lead and Cycle time' => '',
+ // 'Average lead time: ' => '',
+ // 'Average cycle time: ' => '',
+ // 'Cycle Time' => '',
+ // 'Lead Time' => '',
+ // 'This chart show the average lead and cycle time for the last %d tasks over the time.' => '',
+ // 'Average time into each column' => '',
+ // 'Lead and cycle time' => '',
+ // 'Google Authentication' => '',
+ // 'Help on Google authentication' => '',
+ // 'Github Authentication' => '',
+ // 'Help on Github authentication' => '',
+ // 'Channel/Group/User (Optional)' => '',
+ // 'Lead time: ' => '',
+ // 'Cycle time: ' => '',
+ // 'Time spent into each column' => '',
+ // 'The lead time is the duration between the task creation and the completion.' => '',
+ // 'The cycle time is the duration between the start date and the completion.' => '',
+ // 'If the task is not closed the current time is used instead of the completion date.' => '',
+ // 'Set automatically the start date' => '',
+ // 'Edit Authentication' => '',
+ // 'Google Id' => '',
+ // 'Github Id' => '',
+ // 'Remote user' => '',
+ // 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => '',
+ // 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => '',
+ // 'By @%s on Gitlab' => '',
+ // 'Gitlab issue comment created' => '',
+ // 'New remote user' => '',
+ // 'New local user' => '',
);
diff --git a/app/Locale/ru_RU/translations.php b/app/Locale/ru_RU/translations.php
index 9b8b3e4f..3b7d678c 100644
--- a/app/Locale/ru_RU/translations.php
+++ b/app/Locale/ru_RU/translations.php
@@ -126,7 +126,6 @@ return array(
'The project name is required' => 'Требуется имя проекта',
'This project must be unique' => 'Проект должен быть уникальным',
'The title is required' => 'Требуется заголовок',
- 'There is no active project, the first step is to create a new project.' => 'Нет активного проекта, сначала создайте новый проект.',
'Settings saved successfully.' => 'Параметры успешно сохранены.',
'Unable to save your settings.' => 'Невозможно сохранить параметры.',
'Database optimization done.' => 'База данных оптимизирована.',
@@ -264,11 +263,10 @@ return array(
'%d comments' => '%d комментариев',
'%d comment' => '%d комментарий',
'Email address invalid' => 'Некорректный e-mail адрес',
- 'Your Google Account is not linked anymore to your profile.' => 'Ваш аккаунт в Google больше не привязан к вашему профилю.',
- 'Unable to unlink your Google Account.' => 'Не удалось отвязать ваш профиль от Google.',
- 'Google authentication failed' => 'Аутентификация Google не удалась',
- 'Unable to link your Google Account.' => 'Не удалось привязать ваш профиль к Google.',
- 'Your Google Account is linked to your profile successfully.' => 'Ваш профиль успешно привязан к Google.',
+ // 'Your external account is not linked anymore to your profile.' => '',
+ // 'Unable to unlink your external account.' => '',
+ // 'External authentication failed' => '',
+ // 'Your external account is linked to your profile successfully.' => '',
'Email' => 'E-mail',
'Link my Google Account' => 'Привязать мой профиль к Google',
'Unlink my Google Account' => 'Отвязать мой профиль от Google',
@@ -338,14 +336,9 @@ return array(
'Maximum size: ' => 'Максимальный размер: ',
'Unable to upload the file.' => 'Не удалось загрузить файл.',
'Display another project' => 'Показать другой проект',
- 'Your GitHub account was successfully linked to your profile.' => 'Ваш GitHub привязан к вашему профилю.',
- 'Unable to link your GitHub Account.' => 'Не удалось привязать ваш профиль к GitHub.',
- 'GitHub authentication failed' => 'Аутентификация в GitHub не удалась',
- 'Your GitHub account is no longer linked to your profile.' => 'Ваш GitHub отвязан от вашего профиля.',
- 'Unable to unlink your GitHub Account.' => 'Не удалось отвязать ваш профиль от GitHub.',
- 'Login with my GitHub Account' => 'Аутентификация через GitHub',
- 'Link my GitHub Account' => 'Привязать мой профиль к GitHub',
- 'Unlink my GitHub Account' => 'Отвязать мой профиль от GitHub',
+ 'Login with my Github Account' => 'Аутентификация через Github',
+ 'Link my Github Account' => 'Привязать мой профиль к Github',
+ 'Unlink my Github Account' => 'Отвязать мой профиль от Github',
'Created by %s' => 'Создано %s',
'Last modified on %B %e, %Y at %k:%M %p' => 'Последнее изменение %d/%m/%Y в %H:%M',
'Tasks Export' => 'Экспорт задач',
@@ -403,7 +396,7 @@ return array(
'Enabled' => 'Включен',
'Disabled' => 'Выключены',
'Google account linked' => 'Профиль Google связан',
- 'Github account linked' => 'Профиль GitHub связан',
+ 'Github account linked' => 'Профиль Github связан',
'Username:' => 'Имя пользователя:',
'Name:' => 'Имя:',
'Email:' => 'E-mail:',
@@ -417,7 +410,7 @@ return array(
'Password modification' => 'Изменение пароля',
'External authentications' => 'Внешняя аутентификация',
'Google Account' => 'Профиль Google',
- 'Github Account' => 'Профиль GitHub',
+ 'Github Account' => 'Профиль Github',
'Never connected.' => 'Ранее не соединялось.',
'No account linked.' => 'Нет связанных профилей.',
'Account linked.' => 'Профиль связан.',
@@ -458,12 +451,12 @@ return array(
'%s changed the assignee of the task %s to %s' => '%s сменил назначенного для задачи %s на %s',
'New password for the user "%s"' => 'Новый пароль для пользователя "%s"',
'Choose an event' => 'Выберите событие',
- 'Github commit received' => 'GitHub: коммит получен',
- 'Github issue opened' => 'GitHub: новая проблема',
- 'Github issue closed' => 'GitHub: проблема закрыта',
- 'Github issue reopened' => 'GitHub: проблема переоткрыта',
- 'Github issue assignee change' => 'GitHub: сменить ответственного за проблему',
- 'Github issue label change' => 'GitHub: ярлык проблемы изменен',
+ 'Github commit received' => 'Github: коммит получен',
+ 'Github issue opened' => 'Github: новая проблема',
+ 'Github issue closed' => 'Github: проблема закрыта',
+ 'Github issue reopened' => 'Github: проблема переоткрыта',
+ 'Github issue assignee change' => 'Github: сменить ответственного за проблему',
+ 'Github issue label change' => 'Github: ярлык проблемы изменен',
'Create a task from an external provider' => 'Создать задачу из внешнего источника',
'Change the assignee based on an external username' => 'Изменить назначенного основываясь на внешнем имени пользователя',
'Change the category based on an external label' => 'Изменить категорию основываясь на внешнем ярлыке',
@@ -508,8 +501,8 @@ return array(
'Everybody have access to this project.' => 'Любой может получить доступ к этому проекту.',
'Webhooks' => 'Webhooks',
'API' => 'API',
- 'Github webhooks' => 'GitHub webhooks',
- 'Help on Github webhooks' => 'Помощь по GitHub webhooks',
+ 'Github webhooks' => 'Github webhooks',
+ 'Help on Github webhooks' => 'Помощь по Github webhooks',
'Create a comment from an external provider' => 'Создать комментарий из внешнего источника',
'Github issue comment created' => 'Github issue комментарий создан',
'Project management' => 'Управление проектом',
@@ -615,8 +608,7 @@ return array(
'Time Tracking' => 'Учет времени',
'You already have one subtask in progress' => 'У вас уже есть одна задача в разработке',
'Which parts of the project do you want to duplicate?' => 'Какие части проекта должны быть дублированы?',
- 'Disable login form' => 'Выключить форму авторизации',
- 'Show/hide calendar' => 'Показать/скрыть календарь',
+ // 'Disallow login form' => '',
// 'Bitbucket commit received' => '',
'Bitbucket webhooks' => 'BitBucket webhooks',
'Help on Bitbucket webhooks' => 'Помощь по BitBucket webhooks',
@@ -971,4 +963,39 @@ return array(
// 'Search by category: ' => '',
// 'Search by description: ' => '',
// 'Search by due date: ' => '',
+ // 'Lead and Cycle time for "%s"' => '',
+ // 'Average time spent into each column for "%s"' => '',
+ // 'Average time spent into each column' => '',
+ // 'Average time spent' => '',
+ // 'This chart show the average time spent into each column for the last %d tasks.' => '',
+ // 'Average Lead and Cycle time' => '',
+ // 'Average lead time: ' => '',
+ // 'Average cycle time: ' => '',
+ // 'Cycle Time' => '',
+ // 'Lead Time' => '',
+ // 'This chart show the average lead and cycle time for the last %d tasks over the time.' => '',
+ // 'Average time into each column' => '',
+ // 'Lead and cycle time' => '',
+ // 'Google Authentication' => '',
+ // 'Help on Google authentication' => '',
+ // 'Github Authentication' => '',
+ // 'Help on Github authentication' => '',
+ // 'Channel/Group/User (Optional)' => '',
+ // 'Lead time: ' => '',
+ // 'Cycle time: ' => '',
+ // 'Time spent into each column' => '',
+ // 'The lead time is the duration between the task creation and the completion.' => '',
+ // 'The cycle time is the duration between the start date and the completion.' => '',
+ // 'If the task is not closed the current time is used instead of the completion date.' => '',
+ // 'Set automatically the start date' => '',
+ // 'Edit Authentication' => '',
+ // 'Google Id' => '',
+ // 'Github Id' => '',
+ // 'Remote user' => '',
+ // 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => '',
+ // 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => '',
+ // 'By @%s on Gitlab' => '',
+ // 'Gitlab issue comment created' => '',
+ // 'New remote user' => '',
+ // 'New local user' => '',
);
diff --git a/app/Locale/sr_Latn_RS/translations.php b/app/Locale/sr_Latn_RS/translations.php
index 758689c4..f3369341 100644
--- a/app/Locale/sr_Latn_RS/translations.php
+++ b/app/Locale/sr_Latn_RS/translations.php
@@ -126,7 +126,6 @@ return array(
'The project name is required' => 'Naziv projekta je obavezan',
'This project must be unique' => 'Projekat mora biti jedinstven',
'The title is required' => 'Naslov je obavezan',
- 'There is no active project, the first step is to create a new project.' => 'Nema aktivnih projekata. Potrebno je prvo napraviti novi projekat.',
'Settings saved successfully.' => 'Podešavanja uspešno snimljena.',
'Unable to save your settings.' => 'Nemoguće snimanje podešavanja.',
'Database optimization done.' => 'Optimizacija baze je završena.',
@@ -264,11 +263,10 @@ return array(
'%d comments' => '%d Komentara',
'%d comment' => '%d Komentar',
'Email address invalid' => 'Pogrešan e-mail',
- 'Your Google Account is not linked anymore to your profile.' => 'Tvoj google nalog više nije povezan sa profilom',
- 'Unable to unlink your Google Account.' => 'Neuspešno ukidanje veze od Google naloga',
- 'Google authentication failed' => 'Neuspešna Google autentikacija',
- 'Unable to link your Google Account.' => 'Neuspešno povezivanje sa Google nalogom',
- 'Your Google Account is linked to your profile successfully.' => 'Vaš Google nalog je uspešno povezan sa vašim profilom',
+ // 'Your external account is not linked anymore to your profile.' => '',
+ // 'Unable to unlink your external account.' => '',
+ // 'External authentication failed' => '',
+ // 'Your external account is linked to your profile successfully.' => '',
'Email' => 'E-mail',
'Link my Google Account' => 'Poveži sa Google nalogom',
'Unlink my Google Account' => 'Ukini vezu sa Google nalogom',
@@ -338,14 +336,9 @@ return array(
'Maximum size: ' => 'Maksimalna veličina: ',
'Unable to upload the file.' => 'Nije moguće snimiti fajl.',
'Display another project' => 'Prikaži drugi projekat',
- 'Your GitHub account was successfully linked to your profile.' => 'Konto Github podłączone pomyślnie.',
- 'Unable to link your GitHub Account.' => 'Nie można połączyć z kontem Github.',
- 'GitHub authentication failed' => 'Autentykacja Github nieudana',
- 'Your GitHub account is no longer linked to your profile.' => 'Konto Github nie jest już podłączone do twojego profilu.',
- 'Unable to unlink your GitHub Account.' => 'Nie można odłączyć konta Github.',
- 'Login with my GitHub Account' => 'Zaloguj przy użyciu konta Github',
- 'Link my GitHub Account' => 'Podłącz konto Github',
- 'Unlink my GitHub Account' => 'Odłącz konto Github',
+ 'Login with my Github Account' => 'Zaloguj przy użyciu konta Github',
+ 'Link my Github Account' => 'Podłącz konto Github',
+ 'Unlink my Github Account' => 'Odłącz konto Github',
'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',
@@ -615,8 +608,7 @@ return array(
'Time Tracking' => 'Praćenje vremena',
// 'You already have one subtask in progress' => '',
'Which parts of the project do you want to duplicate?' => 'Koje delove projekta želite da kopirate',
- // 'Disable login form' => '',
- // 'Show/hide calendar' => '',
+ // 'Disallow login form' => '',
// 'Bitbucket commit received' => '',
// 'Bitbucket webhooks' => '',
// 'Help on Bitbucket webhooks' => '',
@@ -971,4 +963,39 @@ return array(
// 'Search by category: ' => '',
// 'Search by description: ' => '',
// 'Search by due date: ' => '',
+ // 'Lead and Cycle time for "%s"' => '',
+ // 'Average time spent into each column for "%s"' => '',
+ // 'Average time spent into each column' => '',
+ // 'Average time spent' => '',
+ // 'This chart show the average time spent into each column for the last %d tasks.' => '',
+ // 'Average Lead and Cycle time' => '',
+ // 'Average lead time: ' => '',
+ // 'Average cycle time: ' => '',
+ // 'Cycle Time' => '',
+ // 'Lead Time' => '',
+ // 'This chart show the average lead and cycle time for the last %d tasks over the time.' => '',
+ // 'Average time into each column' => '',
+ // 'Lead and cycle time' => '',
+ // 'Google Authentication' => '',
+ // 'Help on Google authentication' => '',
+ // 'Github Authentication' => '',
+ // 'Help on Github authentication' => '',
+ // 'Channel/Group/User (Optional)' => '',
+ // 'Lead time: ' => '',
+ // 'Cycle time: ' => '',
+ // 'Time spent into each column' => '',
+ // 'The lead time is the duration between the task creation and the completion.' => '',
+ // 'The cycle time is the duration between the start date and the completion.' => '',
+ // 'If the task is not closed the current time is used instead of the completion date.' => '',
+ // 'Set automatically the start date' => '',
+ // 'Edit Authentication' => '',
+ // 'Google Id' => '',
+ // 'Github Id' => '',
+ // 'Remote user' => '',
+ // 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => '',
+ // 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => '',
+ // 'By @%s on Gitlab' => '',
+ // 'Gitlab issue comment created' => '',
+ // 'New remote user' => '',
+ // 'New local user' => '',
);
diff --git a/app/Locale/sv_SE/translations.php b/app/Locale/sv_SE/translations.php
index 4690edea..db552359 100644
--- a/app/Locale/sv_SE/translations.php
+++ b/app/Locale/sv_SE/translations.php
@@ -126,7 +126,6 @@ return array(
'The project name is required' => 'Ett projektnamn måste anges',
'This project must be unique' => 'Detta projekt måste vara unikt',
'The title is required' => 'En titel måste anges.',
- 'There is no active project, the first step is to create a new project.' => 'Inget projekt är aktiverat, första steget är att skapa ett nytt projekt',
'Settings saved successfully.' => 'Inställningarna har sparats.',
'Unable to save your settings.' => 'Kunde inte spara dina ändringar',
'Database optimization done.' => 'Databasen har optimerats.',
@@ -264,11 +263,10 @@ return array(
'%d comments' => '%d kommentarer',
'%d comment' => '%d kommentar',
'Email address invalid' => 'Epost-adressen ogiltig',
- 'Your Google Account is not linked anymore to your profile.' => 'Ditt Google-konto är inte längre länkat till din profil',
- 'Unable to unlink your Google Account.' => 'Kunde inte ta bort länken till ditt Google-konto.',
- 'Google authentication failed' => 'Autentiseringen för Google misslyckades',
- 'Unable to link your Google Account.' => 'Kunde inte länka till ditt Google-konto',
- 'Your Google Account is linked to your profile successfully.' => 'Ditt Google-konto har kopplats till din profil.',
+ // 'Your external account is not linked anymore to your profile.' => '',
+ // 'Unable to unlink your external account.' => '',
+ // 'External authentication failed' => '',
+ // 'Your external account is linked to your profile successfully.' => '',
'Email' => 'Epost',
'Link my Google Account' => 'Länka till mitt Google-konto',
'Unlink my Google Account' => 'Ta bort länken till mitt Google-konto',
@@ -338,14 +336,9 @@ return array(
'Maximum size: ' => 'Maxstorlek: ',
'Unable to upload the file.' => 'Kunde inte ladda upp filen.',
'Display another project' => 'Visa ett annat projekt',
- 'Your GitHub account was successfully linked to your profile.' => 'Ditt GitHub-konto har anslutits till din profil.',
- 'Unable to link your GitHub Account.' => 'Kunde inte ansluta ditt GitHub-konto.',
- 'GitHub authentication failed' => 'GitHub-verifiering misslyckades',
- 'Your GitHub account is no longer linked to your profile.' => 'Ditt GitHub-konto är inte längre anslutet till din profil.',
- 'Unable to unlink your GitHub Account.' => 'Kunde inte koppla ifrån ditt GitHub-konto.',
- 'Login with my GitHub Account' => 'Logga in med mitt GitHub-konto',
- 'Link my GitHub Account' => 'Anslut mitt GitHub-konto',
- 'Unlink my GitHub Account' => 'Koppla ifrån mitt GitHub-konto',
+ 'Login with my Github Account' => 'Logga in med mitt Github-konto',
+ 'Link my Github Account' => 'Anslut mitt Github-konto',
+ 'Unlink my Github Account' => 'Koppla ifrån mitt Github-konto',
'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',
@@ -615,8 +608,7 @@ return array(
'Time Tracking' => 'Tidsbevakning',
'You already have one subtask in progress' => 'Du har redan en deluppgift igång',
'Which parts of the project do you want to duplicate?' => 'Vilka delar av projektet vill du duplicera?',
- 'Disable login form' => 'Inaktivera loginformuläret',
- 'Show/hide calendar' => 'Visa/dölj kalender',
+ // 'Disallow login form' => '',
'Bitbucket commit received' => 'Bitbucket bidrag mottaget',
'Bitbucket webhooks' => 'Bitbucket webhooks',
'Help on Bitbucket webhooks' => 'Hjälp för Bitbucket webhooks',
@@ -865,8 +857,8 @@ return array(
'By @%s on Bitbucket' => 'Av @%s på Bitbucket',
'Bitbucket Issue' => 'Bitbucket fråga',
'Commit made by @%s on Bitbucket' => 'Bidrag gjort av @%s på Bitbucket',
- 'Commit made by @%s on Github' => 'Bidrag gjort av @%s på GitHub',
- 'By @%s on Github' => 'Av @%s på GitHub',
+ 'Commit made by @%s on Github' => 'Bidrag gjort av @%s på Github',
+ 'By @%s on Github' => 'Av @%s på Github',
'Commit made by @%s on Gitlab' => 'Bidrag gjort av @%s på Gitlab',
'Add a comment log when moving the task between columns' => 'Lägg till en kommentarslogg när en uppgift flyttas mellan kolumner',
'Move the task to another column when the category is changed' => 'Flyttas uppgiften till en annan kolumn när kategorin ändras',
@@ -971,4 +963,39 @@ return array(
'Search by category: ' => 'Sök efter kategori:',
'Search by description: ' => 'Sök efter beskrivning',
'Search by due date: ' => 'Sök efter förfallodatum',
+ // 'Lead and Cycle time for "%s"' => '',
+ // 'Average time spent into each column for "%s"' => '',
+ // 'Average time spent into each column' => '',
+ // 'Average time spent' => '',
+ // 'This chart show the average time spent into each column for the last %d tasks.' => '',
+ // 'Average Lead and Cycle time' => '',
+ // 'Average lead time: ' => '',
+ // 'Average cycle time: ' => '',
+ // 'Cycle Time' => '',
+ // 'Lead Time' => '',
+ // 'This chart show the average lead and cycle time for the last %d tasks over the time.' => '',
+ // 'Average time into each column' => '',
+ // 'Lead and cycle time' => '',
+ // 'Google Authentication' => '',
+ // 'Help on Google authentication' => '',
+ // 'Github Authentication' => '',
+ // 'Help on Github authentication' => '',
+ // 'Channel/Group/User (Optional)' => '',
+ // 'Lead time: ' => '',
+ // 'Cycle time: ' => '',
+ // 'Time spent into each column' => '',
+ // 'The lead time is the duration between the task creation and the completion.' => '',
+ // 'The cycle time is the duration between the start date and the completion.' => '',
+ // 'If the task is not closed the current time is used instead of the completion date.' => '',
+ // 'Set automatically the start date' => '',
+ // 'Edit Authentication' => '',
+ // 'Google Id' => '',
+ // 'Github Id' => '',
+ // 'Remote user' => '',
+ // 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => '',
+ // 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => '',
+ // 'By @%s on Gitlab' => '',
+ // 'Gitlab issue comment created' => '',
+ // 'New remote user' => '',
+ // 'New local user' => '',
);
diff --git a/app/Locale/th_TH/translations.php b/app/Locale/th_TH/translations.php
index c1eb3183..c4d608bd 100644
--- a/app/Locale/th_TH/translations.php
+++ b/app/Locale/th_TH/translations.php
@@ -126,7 +126,6 @@ return array(
'The project name is required' => 'ต้องการชื่อโปรเจค',
'This project must be unique' => 'ชื่อโปรเจคต้องไม่ซ้ำ',
'The title is required' => 'ต้องการหัวเรื่อง',
- 'There is no active project, the first step is to create a new project.' => 'ไม่มีโปรเจคที่ทำงานอยู่, ต้องการสร้างโปรเจคใหม่',
'Settings saved successfully.' => 'บันทึกการตั้งค่าเรียบร้อยแล้ว',
'Unable to save your settings.' => 'ไม่สามารถบันทึกการตั้งค่าได้',
'Database optimization done.' => 'ปรับปรุงฐานข้อมูลเรียบร้อยแล้ว',
@@ -264,11 +263,10 @@ return array(
'%d comments' => '%d ความคิดเห็น',
'%d comment' => '%d ความคิดเห็น',
'Email address invalid' => 'อีเมลผิด',
- 'Your Google Account is not linked anymore to your profile.' => 'กูเกิลแอคเคาท์ไม่ได้เชื่อมต่อกับประวัติของคุณ',
- 'Unable to unlink your Google Account.' => 'ไม่สามารถยกเลิกการเชื่อมต่อกับกูเกิลแอคเคาท์',
- 'Google authentication failed' => 'การยืนยันกับกูเกิลผิดพลาด',
- 'Unable to link your Google Account.' => 'ไม่สามารถเชื่อมต่อกับกูเกิลแอคเคาท์',
- 'Your Google Account is linked to your profile successfully.' => 'กูเกลิแอคเคาท์เชื่อมต่อกับประวัติของคุณเรียบร้อยแล้ว',
+ // 'Your external account is not linked anymore to your profile.' => '',
+ // 'Unable to unlink your external account.' => '',
+ // 'External authentication failed' => '',
+ // 'Your external account is linked to your profile successfully.' => '',
'Email' => 'อีเมล',
'Link my Google Account' => 'เชื่อมต่อกับกูเกิลแอคเคาท์',
'Unlink my Google Account' => 'ไม่เชื่อมต่อกับกูเกิลแอคเคาท์',
@@ -338,14 +336,9 @@ return array(
'Maximum size: ' => 'ขนาดสูงสุด:',
'Unable to upload the file.' => 'ไม่สามารถอัพโหลดไฟล์ได้',
'Display another project' => 'แสดงโปรเจคอื่น',
- 'Your GitHub account was successfully linked to your profile.' => 'กิทฮับแอคเคาท์เชื่อมต่อกับประวัติเรียบร้อยแล้ว',
- 'Unable to link your GitHub Account.' => 'ไม่สามารถเชื่อมต่อกับกิทฮับแอคเคาท์ได้',
- 'GitHub authentication failed' => 'การยืนยันกิทฮับผิดพลาด',
- 'Your GitHub account is no longer linked to your profile.' => 'กิทฮับแอคเคาท์ไม่ได้มีการเชื่อมโยงไปยังโปรไฟล์ของคุณ',
- 'Unable to unlink your GitHub Account.' => 'ไม่สามารถยกเลิกการเชื่อมต่อกิทฮับแอคเคาท์ได้',
- 'Login with my GitHub Account' => 'เข้าใช้ด้วยกิทฮับแอคเคาท์',
- 'Link my GitHub Account' => 'เชื่อมกับกิทฮับแอคเคาท์',
- 'Unlink my GitHub Account' => 'ยกเลิกการเชื่อมกับกิทอับแอคเคาท์',
+ 'Login with my Github Account' => 'เข้าใช้ด้วยกิทฮับแอคเคาท์',
+ 'Link my Github Account' => 'เชื่อมกับกิทฮับแอคเคาท์',
+ 'Unlink my Github Account' => 'ยกเลิกการเชื่อมกับกิทอับแอคเคาท์',
'Created by %s' => 'สร้างโดย %s',
'Last modified on %B %e, %Y at %k:%M %p' => 'แก้ไขล่าสุดวันที่ %B %e, %Y เวลา %k:%M %p',
'Tasks Export' => 'ส่งออกงาน',
@@ -615,8 +608,7 @@ return array(
'Time Tracking' => 'ติดตามเวลา',
'You already have one subtask in progress' => 'คุณมีหนึ่งงานย่อยที่กำลังทำงาน',
// 'Which parts of the project do you want to duplicate?' => '',
- // 'Disable login form' => '',
- 'Show/hide calendar' => 'แสดง/ซ่อน ปฎิทิน',
+ // 'Disallow login form' => '',
// 'Bitbucket commit received' => '',
// 'Bitbucket webhooks' => '',
// 'Help on Bitbucket webhooks' => '',
@@ -971,4 +963,39 @@ return array(
// 'Search by category: ' => '',
// 'Search by description: ' => '',
// 'Search by due date: ' => '',
+ // 'Lead and Cycle time for "%s"' => '',
+ // 'Average time spent into each column for "%s"' => '',
+ // 'Average time spent into each column' => '',
+ // 'Average time spent' => '',
+ // 'This chart show the average time spent into each column for the last %d tasks.' => '',
+ // 'Average Lead and Cycle time' => '',
+ // 'Average lead time: ' => '',
+ // 'Average cycle time: ' => '',
+ // 'Cycle Time' => '',
+ // 'Lead Time' => '',
+ // 'This chart show the average lead and cycle time for the last %d tasks over the time.' => '',
+ // 'Average time into each column' => '',
+ // 'Lead and cycle time' => '',
+ // 'Google Authentication' => '',
+ // 'Help on Google authentication' => '',
+ // 'Github Authentication' => '',
+ // 'Help on Github authentication' => '',
+ // 'Channel/Group/User (Optional)' => '',
+ // 'Lead time: ' => '',
+ // 'Cycle time: ' => '',
+ // 'Time spent into each column' => '',
+ // 'The lead time is the duration between the task creation and the completion.' => '',
+ // 'The cycle time is the duration between the start date and the completion.' => '',
+ // 'If the task is not closed the current time is used instead of the completion date.' => '',
+ // 'Set automatically the start date' => '',
+ // 'Edit Authentication' => '',
+ // 'Google Id' => '',
+ // 'Github Id' => '',
+ // 'Remote user' => '',
+ // 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => '',
+ // 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => '',
+ // 'By @%s on Gitlab' => '',
+ // 'Gitlab issue comment created' => '',
+ // 'New remote user' => '',
+ // 'New local user' => '',
);
diff --git a/app/Locale/tr_TR/translations.php b/app/Locale/tr_TR/translations.php
index 9335c201..afb88744 100644
--- a/app/Locale/tr_TR/translations.php
+++ b/app/Locale/tr_TR/translations.php
@@ -126,7 +126,6 @@ return array(
'The project name is required' => 'Proje adı gerekli',
'This project must be unique' => 'Bu projenin tekil olması gerekli',
'The title is required' => 'Başlık gerekli',
- 'There is no active project, the first step is to create a new project.' => 'Aktif bir proje yok. İlk aşama yeni bir proje oluşturmak olmalı.',
'Settings saved successfully.' => 'Ayarlar başarıyla kaydedildi.',
'Unable to save your settings.' => 'Ayarlarınız kaydedilemedi.',
'Database optimization done.' => 'Veritabanı optimizasyonu tamamlandı.',
@@ -264,11 +263,10 @@ return array(
'%d comments' => '%d yorumlar',
'%d comment' => '%d yorum',
'Email address invalid' => 'E-Posta adresi geçersiz',
- 'Your Google Account is not linked anymore to your profile.' => 'Google hesabınız artık profilinize bağlı değil',
- 'Unable to unlink your Google Account.' => 'Google hesabınızla bağ koparılamadı',
- 'Google authentication failed' => 'Google hesap doğrulaması başarısız',
- 'Unable to link your Google Account.' => 'Google hesabınızla bağ oluşturulamadı',
- 'Your Google Account is linked to your profile successfully.' => 'Google hesabınız profilinize başarıyla bağlandı',
+ // 'Your external account is not linked anymore to your profile.' => '',
+ // 'Unable to unlink your external account.' => '',
+ // 'External authentication failed' => '',
+ // 'Your external account is linked to your profile successfully.' => '',
'Email' => 'E-Posta',
'Link my Google Account' => 'Google hesabımla bağ oluştur',
'Unlink my Google Account' => 'Google hesabımla bağı kaldır',
@@ -338,14 +336,9 @@ return array(
'Maximum size: ' => 'Maksimum boyutu',
'Unable to upload the file.' => 'Karşıya yükleme başarısız',
'Display another project' => 'Başka bir proje göster',
- 'Your GitHub account was successfully linked to your profile.' => 'GitHub Hesabınız Profilinize bağlandı.',
- 'Unable to link your GitHub Account.' => 'GitHub hesabınızla bağ oluşturulamadı.',
- // 'GitHub authentication failed' => '',
- // 'Your GitHub account is no longer linked to your profile.' => '',
- // 'Unable to unlink your GitHub Account.' => '',
- // 'Login with my GitHub Account' => '',
- // 'Link my GitHub Account' => '',
- // 'Unlink my GitHub Account' => '',
+ // 'Login with my Github Account' => '',
+ // 'Link my Github Account' => '',
+ // 'Unlink my Github Account' => '',
'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',
@@ -615,8 +608,7 @@ return array(
'Time Tracking' => 'Zaman takibi',
'You already have one subtask in progress' => 'Zaten işlemde olan bir alt görev var',
'Which parts of the project do you want to duplicate?' => 'Projenin hangi kısımlarının kopyasını oluşturmak istiyorsunuz?',
- 'Disable login form' => 'Giriş formunu devre dışı bırak',
- 'Show/hide calendar' => 'Takvimi göster/gizle',
+ // 'Disallow login form' => '',
'Bitbucket commit received' => 'Bitbucket commit alındı',
'Bitbucket webhooks' => 'Bitbucket webhooks',
'Help on Bitbucket webhooks' => 'Bitbucket webhooks için yardım',
@@ -971,4 +963,39 @@ return array(
// 'Search by category: ' => '',
// 'Search by description: ' => '',
// 'Search by due date: ' => '',
+ // 'Lead and Cycle time for "%s"' => '',
+ // 'Average time spent into each column for "%s"' => '',
+ // 'Average time spent into each column' => '',
+ // 'Average time spent' => '',
+ // 'This chart show the average time spent into each column for the last %d tasks.' => '',
+ // 'Average Lead and Cycle time' => '',
+ // 'Average lead time: ' => '',
+ // 'Average cycle time: ' => '',
+ // 'Cycle Time' => '',
+ // 'Lead Time' => '',
+ // 'This chart show the average lead and cycle time for the last %d tasks over the time.' => '',
+ // 'Average time into each column' => '',
+ // 'Lead and cycle time' => '',
+ // 'Google Authentication' => '',
+ // 'Help on Google authentication' => '',
+ // 'Github Authentication' => '',
+ // 'Help on Github authentication' => '',
+ // 'Channel/Group/User (Optional)' => '',
+ // 'Lead time: ' => '',
+ // 'Cycle time: ' => '',
+ // 'Time spent into each column' => '',
+ // 'The lead time is the duration between the task creation and the completion.' => '',
+ // 'The cycle time is the duration between the start date and the completion.' => '',
+ // 'If the task is not closed the current time is used instead of the completion date.' => '',
+ // 'Set automatically the start date' => '',
+ // 'Edit Authentication' => '',
+ // 'Google Id' => '',
+ // 'Github Id' => '',
+ // 'Remote user' => '',
+ // 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => '',
+ // 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => '',
+ // 'By @%s on Gitlab' => '',
+ // 'Gitlab issue comment created' => '',
+ // 'New remote user' => '',
+ // 'New local user' => '',
);
diff --git a/app/Locale/zh_CN/translations.php b/app/Locale/zh_CN/translations.php
index 155628dc..cd2518fd 100644
--- a/app/Locale/zh_CN/translations.php
+++ b/app/Locale/zh_CN/translations.php
@@ -126,7 +126,6 @@ return array(
'The project name is required' => '需要指定项目名称',
'This project must be unique' => '项目名称必须唯一',
'The title is required' => '需要指定标题',
- 'There is no active project, the first step is to create a new project.' => '尚无活跃项目,请首先创建一个新项目。',
'Settings saved successfully.' => '设置成功保存。',
'Unable to save your settings.' => '无法保存你的设置。',
'Database optimization done.' => '数据库优化完成。',
@@ -264,11 +263,10 @@ return array(
'%d comments' => '%d个评论',
'%d comment' => '%d个评论',
'Email address invalid' => '电子邮件地址无效',
- 'Your Google Account is not linked anymore to your profile.' => '您的google帐号不再与您的账户配置关联。',
- 'Unable to unlink your Google Account.' => '无法去除您google帐号的关联',
- 'Google authentication failed' => 'google验证失败',
- 'Unable to link your Google Account.' => '无法关联您的google帐号。',
- 'Your Google Account is linked to your profile successfully.' => '您的google帐号已成功与账户配置关联。',
+ // 'Your external account is not linked anymore to your profile.' => '',
+ // 'Unable to unlink your external account.' => '',
+ // 'External authentication failed' => '',
+ // 'Your external account is linked to your profile successfully.' => '',
'Email' => '电子邮件',
'Link my Google Account' => '关联我的google帐号',
'Unlink my Google Account' => '去除我的google帐号关联',
@@ -338,14 +336,9 @@ return array(
'Maximum size: ' => '大小上限:',
'Unable to upload the file.' => '无法上传文件',
'Display another project' => '显示其它项目',
- 'Your GitHub account was successfully linked to your profile.' => 'GitHub账号已经成功链接到您的用户',
- 'Unable to link your GitHub Account.' => '无法链接到GitHub账户',
- 'GitHub authentication failed' => 'GitHub认证失败',
- 'Your GitHub account is no longer linked to your profile.' => 'Github账号已经不再链接到您的用户',
- 'Unable to unlink your GitHub Account.' => '无法链接GitHub账号',
- 'Login with my GitHub Account' => '用Github账号登录',
- 'Link my GitHub Account' => '链接GitHub账号',
- 'Unlink my GitHub Account' => '取消GitHub账号链接',
+ 'Login with my Github Account' => '用Github账号登录',
+ 'Link my Github Account' => '链接Github账号',
+ 'Unlink my Github Account' => '取消Github账号链接',
'Created by %s' => '创建者:%s',
'Last modified on %B %e, %Y at %k:%M %p' => '最后修改:%Y/%m/%d/ %H:%M',
'Tasks Export' => '任务导出',
@@ -615,8 +608,7 @@ return array(
'Time Tracking' => '时间记录',
'You already have one subtask in progress' => '你已经有了一个进行中的子任务',
'Which parts of the project do you want to duplicate?' => '要复制项目的哪些内容?',
- 'Disable login form' => '禁用登录界面',
- 'Show/hide calendar' => '显示/隐藏日程表',
+ // 'Disallow login form' => '',
'Bitbucket commit received' => '收到Bitbucket提交',
'Bitbucket webhooks' => 'Bitbucket网络钩子',
'Help on Bitbucket webhooks' => 'Bitbucket网络钩子帮助',
@@ -971,4 +963,39 @@ return array(
// 'Search by category: ' => '',
// 'Search by description: ' => '',
// 'Search by due date: ' => '',
+ // 'Lead and Cycle time for "%s"' => '',
+ // 'Average time spent into each column for "%s"' => '',
+ // 'Average time spent into each column' => '',
+ // 'Average time spent' => '',
+ // 'This chart show the average time spent into each column for the last %d tasks.' => '',
+ // 'Average Lead and Cycle time' => '',
+ // 'Average lead time: ' => '',
+ // 'Average cycle time: ' => '',
+ // 'Cycle Time' => '',
+ // 'Lead Time' => '',
+ // 'This chart show the average lead and cycle time for the last %d tasks over the time.' => '',
+ // 'Average time into each column' => '',
+ // 'Lead and cycle time' => '',
+ // 'Google Authentication' => '',
+ // 'Help on Google authentication' => '',
+ // 'Github Authentication' => '',
+ // 'Help on Github authentication' => '',
+ // 'Channel/Group/User (Optional)' => '',
+ // 'Lead time: ' => '',
+ // 'Cycle time: ' => '',
+ // 'Time spent into each column' => '',
+ // 'The lead time is the duration between the task creation and the completion.' => '',
+ // 'The cycle time is the duration between the start date and the completion.' => '',
+ // 'If the task is not closed the current time is used instead of the completion date.' => '',
+ // 'Set automatically the start date' => '',
+ // 'Edit Authentication' => '',
+ // 'Google Id' => '',
+ // 'Github Id' => '',
+ // 'Remote user' => '',
+ // 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => '',
+ // 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => '',
+ // 'By @%s on Gitlab' => '',
+ // 'Gitlab issue comment created' => '',
+ // 'New remote user' => '',
+ // 'New local user' => '',
);
diff --git a/app/Model/Acl.php b/app/Model/Acl.php
index 09638302..b9c06e98 100644
--- a/app/Model/Acl.php
+++ b/app/Model/Acl.php
@@ -18,12 +18,12 @@ class Acl extends Base
*/
private $public_acl = array(
'auth' => array('login', 'check'),
- 'user' => array('google', 'github'),
'task' => array('readonly'),
'board' => array('readonly'),
'webhook' => '*',
'ical' => '*',
'feed' => '*',
+ 'oauth' => array('google', 'github'),
);
/**
@@ -70,7 +70,7 @@ class Acl extends Base
* @var array
*/
private $admin_acl = array(
- 'user' => array('index', 'create', 'save', 'remove'),
+ 'user' => array('index', 'create', 'save', 'remove', 'authentication'),
'config' => '*',
'link' => '*',
'project' => array('remove'),
diff --git a/app/Model/Action.php b/app/Model/Action.php
index d0607794..5e994c99 100644
--- a/app/Model/Action.php
+++ b/app/Model/Action.php
@@ -92,6 +92,7 @@ class Action extends Base
GitlabWebhook::EVENT_COMMIT => t('Gitlab commit received'),
GitlabWebhook::EVENT_ISSUE_OPENED => t('Gitlab issue opened'),
GitlabWebhook::EVENT_ISSUE_CLOSED => t('Gitlab issue closed'),
+ GitlabWebhook::EVENT_ISSUE_COMMENT => t('Gitlab issue comment created'),
BitbucketWebhook::EVENT_COMMIT => t('Bitbucket commit received'),
BitbucketWebhook::EVENT_ISSUE_OPENED => t('Bitbucket issue opened'),
BitbucketWebhook::EVENT_ISSUE_CLOSED => t('Bitbucket issue closed'),
diff --git a/app/Model/TaskFilter.php b/app/Model/TaskFilter.php
index 8d7bdb44..d7d5148b 100644
--- a/app/Model/TaskFilter.php
+++ b/app/Model/TaskFilter.php
@@ -71,6 +71,9 @@ class TaskFilter extends Base
case 'T_REFERENCE':
$this->filterByReference($value);
break;
+ case 'T_SWIMLANE':
+ $this->filterBySwimlaneName($value);
+ break;
}
}
@@ -247,6 +250,30 @@ class TaskFilter extends Base
}
/**
+ * Filter by swimlane name
+ *
+ * @access public
+ * @param array $values List of swimlane name
+ * @return TaskFilter
+ */
+ public function filterBySwimlaneName(array $values)
+ {
+ $this->query->beginOr();
+
+ foreach ($values as $swimlane) {
+ if ($swimlane === 'default') {
+ $this->query->eq(Task::TABLE.'.swimlane_id', 0);
+ }
+ else {
+ $this->query->ilike(Swimlane::TABLE.'.name', $swimlane);
+ $this->query->addCondition(Task::TABLE.'.swimlane_id=0 AND '.Project::TABLE.'.default_swimlane '.$this->db->getDriver()->getOperator('ILIKE')." '$swimlane'");
+ }
+ }
+
+ $this->query->closeOr();
+ }
+
+ /**
* Filter by category id
*
* @access public
diff --git a/app/Model/TaskFinder.php b/app/Model/TaskFinder.php
index 2b0453a5..47a67a35 100644
--- a/app/Model/TaskFinder.php
+++ b/app/Model/TaskFinder.php
@@ -88,11 +88,14 @@ class TaskFinder extends Base
Category::TABLE.'.name AS category_name',
Category::TABLE.'.description AS category_description',
Board::TABLE.'.title AS column_name',
+ Swimlane::TABLE.'.name AS swimlane_name',
+ Project::TABLE.'.default_swimlane',
Project::TABLE.'.name AS project_name'
)
->join(User::TABLE, 'id', 'owner_id', Task::TABLE)
->join(Category::TABLE, 'id', 'category_id', Task::TABLE)
->join(Board::TABLE, 'id', 'column_id', Task::TABLE)
+ ->join(Swimlane::TABLE, 'id', 'swimlane_id', Task::TABLE)
->join(Project::TABLE, 'id', 'project_id', Task::TABLE);
}
diff --git a/app/Model/User.php b/app/Model/User.php
index 4c32942c..b6804abc 100644
--- a/app/Model/User.php
+++ b/app/Model/User.php
@@ -122,13 +122,13 @@ class User extends Base
}
/**
- * Get a specific user by the GitHub id
+ * Get a specific user by the Github id
*
* @access public
- * @param string $github_id GitHub user id
+ * @param string $github_id Github user id
* @return array|boolean
*/
- public function getByGitHubId($github_id)
+ public function getByGithubId($github_id)
{
if (empty($github_id)) {
return false;
@@ -377,6 +377,7 @@ class User extends Base
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_admin', t('This value must be an integer')),
+ new Validators\Integer('is_ldap_user', t('This value must be an integer')),
);
}
@@ -409,7 +410,12 @@ class User extends Base
new Validators\Required('username', t('The username is required')),
);
- $v = new Validator($values, array_merge($rules, $this->commonValidationRules(), $this->commonPasswordValidationRules()));
+ 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(),
diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php
index 8c55457d..ef7aa575 100644
--- a/app/ServiceProvider/ClassProvider.php
+++ b/app/ServiceProvider/ClassProvider.php
@@ -3,6 +3,7 @@
namespace ServiceProvider;
use Core\Paginator;
+use Core\OAuth2;
use Model\Config;
use Model\Project;
use Model\Webhook;
@@ -72,6 +73,7 @@ class ClassProvider implements ServiceProviderInterface
'Lexer',
'MemoryCache',
'Request',
+ 'Router',
'Session',
'Template',
),
@@ -106,5 +108,9 @@ class ClassProvider implements ServiceProviderInterface
$container['paginator'] = $container->factory(function ($c) {
return new Paginator($c);
});
+
+ $container['oauth'] = $container->factory(function ($c) {
+ return new OAuth2($c);
+ });
}
}
diff --git a/app/Template/app/overview.php b/app/Template/app/overview.php
index fa7866af..1b160496 100644
--- a/app/Template/app/overview.php
+++ b/app/Template/app/overview.php
@@ -1,7 +1,7 @@
<div class="search">
- <form method="get" action="?" class="search">
+ <form method="get" action="<?= $this->url->dir() ?>" class="search">
<?= $this->form->hidden('controller', array('controller' => 'search')) ?>
- <?= $this->form->hidden('action', array('controller' => 'index')) ?>
+ <?= $this->form->hidden('action', array('action' => 'index')) ?>
<?= $this->form->text('search', array(), array(), array('placeholder="'.t('Search').'"'), 'form-input-large') ?>
</form>
diff --git a/app/Template/auth/index.php b/app/Template/auth/index.php
index 39d007f5..ca303df9 100644
--- a/app/Template/auth/index.php
+++ b/app/Template/auth/index.php
@@ -4,7 +4,8 @@
<p class="alert alert-error"><?= $this->e($errors['login']) ?></p>
<?php endif ?>
- <form method="post" action="<?= $this->url->href('auth', 'check', array('redirect_query' => $redirect_query)) ?>">
+ <?php if (! HIDE_LOGIN_FORM): ?>
+ <form method="post" action="<?= $this->url->href('auth', 'check') ?>">
<?= $this->form->csrf() ?>
@@ -16,17 +17,22 @@
<?= $this->form->checkbox('remember_me', t('Remember Me'), 1, true) ?><br/>
+ <div class="form-actions">
+ <input type="submit" value="<?= t('Sign in') ?>" class="btn btn-blue"/>
+ </div>
+ </form>
+ <?php endif ?>
+
+ <?php if (GOOGLE_AUTH || GITHUB_AUTH): ?>
+ <ul class="no-bullet">
<?php if (GOOGLE_AUTH): ?>
- <?= $this->url->link(t('Login with my Google Account'), 'user', 'google') ?>
+ <li><?= $this->url->link(t('Login with my Google Account'), 'oauth', 'google') ?></li>
<?php endif ?>
<?php if (GITHUB_AUTH): ?>
- <?= $this->url->link(t('Login with my GitHub Account'), 'user', 'gitHub') ?>
+ <li><?= $this->url->link(t('Login with my Github Account'), 'oauth', 'gitHub') ?></li>
<?php endif ?>
-
- <div class="form-actions">
- <input type="submit" value="<?= t('Sign in') ?>" class="btn btn-blue"/>
- </div>
- </form>
+ </ul>
+ <?php endif ?>
</div> \ No newline at end of file
diff --git a/app/Template/board/task_menu.php b/app/Template/board/task_menu.php
index 97c0f8dc..a84b972d 100644
--- a/app/Template/board/task_menu.php
+++ b/app/Template/board/task_menu.php
@@ -9,7 +9,6 @@
<li><i class="fa fa-comment-o"></i> <?= $this->url->link(t('Add a comment'), 'comment', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'task-board-popover') ?></li>
<li><i class="fa fa-code-fork"></i> <?= $this->url->link(t('Add a link'), 'tasklink', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'task-board-popover') ?></li>
<li><i class="fa fa-camera"></i> <?= $this->url->link(t('Add a screenshot'), 'board', 'screenshot', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'task-board-popover') ?></li>
- <li><i class="fa fa-refresh fa-rotate-90"></i> <?= $this->url->link(t('Edit recurrence'), 'task', 'recurrence', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'task-board-popover') ?></li>
<li><i class="fa fa-close"></i> <?= $this->url->link(t('Close this task'), 'task', 'close', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'redirect' => 'board'), false, 'task-board-popover') ?></li>
</ul>
</span>
diff --git a/app/Template/config/integrations.php b/app/Template/config/integrations.php
index 9c80b499..47b45149 100644
--- a/app/Template/config/integrations.php
+++ b/app/Template/config/integrations.php
@@ -6,30 +6,42 @@
<?= $this->form->csrf() ?>
- <h3><img src="assets/img/mailgun-icon.png"/>&nbsp;<?= t('Mailgun (incoming emails)') ?></h3>
+ <h3><i class="fa fa-google"></i> <?= t('Google Authentication') ?></h3>
<div class="listing">
- <input type="text" class="auto-select" readonly="readonly" value="<?= $this->url->base().$this->url->href('webhook', 'mailgun', array('token' => $values['webhook_token'])) ?>"/><br/>
+ <input type="text" class="auto-select" readonly="readonly" value="<?= $this->url->href('oauth', 'google', array(), false, '', true) ?>"/><br/>
+ <p class="form-help"><a href="http://kanboard.net/documentation/google-authentication" target="_blank"><?= t('Help on Google authentication') ?></a></p>
+ </div>
+
+ <h3><i class="fa fa-github"></i> <?= t('Github Authentication') ?></h3>
+ <div class="listing">
+ <input type="text" class="auto-select" readonly="readonly" value="<?= $this->url->href('oauth', 'github', array(), false, '', true) ?>"/><br/>
+ <p class="form-help"><a href="http://kanboard.net/documentation/github-authentication" target="_blank"><?= t('Help on Github authentication') ?></a></p>
+ </div>
+
+ <h3><img src="<?= $this->url->dir() ?>assets/img/mailgun-icon.png"/>&nbsp;<?= t('Mailgun (incoming emails)') ?></h3>
+ <div class="listing">
+ <input type="text" class="auto-select" readonly="readonly" value="<?= $this->url->href('webhook', 'mailgun', array('token' => $values['webhook_token']), false, '', true) ?>"/><br/>
<p class="form-help"><a href="http://kanboard.net/documentation/mailgun" target="_blank"><?= t('Help on Mailgun integration') ?></a></p>
</div>
- <h3><img src="assets/img/sendgrid-icon.png"/>&nbsp;<?= t('Sendgrid (incoming emails)') ?></h3>
+ <h3><img src="<?= $this->url->dir() ?>assets/img/sendgrid-icon.png"/>&nbsp;<?= t('Sendgrid (incoming emails)') ?></h3>
<div class="listing">
- <input type="text" class="auto-select" readonly="readonly" value="<?= $this->url->base().$this->url->href('webhook', 'sendgrid', array('token' => $values['webhook_token'])) ?>"/><br/>
+ <input type="text" class="auto-select" readonly="readonly" value="<?= $this->url->href('webhook', 'sendgrid', array('token' => $values['webhook_token']), false, '', true) ?>"/><br/>
<p class="form-help"><a href="http://kanboard.net/documentation/sendgrid" target="_blank"><?= t('Help on Sendgrid integration') ?></a></p>
</div>
- <h3><img src="assets/img/postmark-icon.png"/>&nbsp;<?= t('Postmark (incoming emails)') ?></h3>
+ <h3><img src="<?= $this->url->dir() ?>assets/img/postmark-icon.png"/>&nbsp;<?= t('Postmark (incoming emails)') ?></h3>
<div class="listing">
- <input type="text" class="auto-select" readonly="readonly" value="<?= $this->url->base().$this->url->href('webhook', 'postmark', array('token' => $values['webhook_token'])) ?>"/><br/>
+ <input type="text" class="auto-select" readonly="readonly" value="<?= $this->url->href('webhook', 'postmark', array('token' => $values['webhook_token']), false, '', true) ?>"/><br/>
<p class="form-help"><a href="http://kanboard.net/documentation/postmark" target="_blank"><?= t('Help on Postmark integration') ?></a></p>
</div>
- <h3><img src="assets/img/gravatar-icon.png"/>&nbsp;<?= t('Gravatar') ?></h3>
+ <h3><img src="<?= $this->url->dir() ?>assets/img/gravatar-icon.png"/>&nbsp;<?= t('Gravatar') ?></h3>
<div class="listing">
<?= $this->form->checkbox('integration_gravatar', t('Enable Gravatar images'), 1, $values['integration_gravatar'] == 1) ?>
</div>
- <h3><img src="assets/img/jabber-icon.png"/> <?= t('Jabber (XMPP)') ?></h3>
+ <h3><img src="<?= $this->url->dir() ?>assets/img/jabber-icon.png"/> <?= t('Jabber (XMPP)') ?></h3>
<div class="listing">
<?= $this->form->checkbox('integration_jabber', t('Send notifications to Jabber'), 1, $values['integration_jabber'] == 1) ?>
@@ -55,7 +67,7 @@
<p class="form-help"><a href="http://kanboard.net/documentation/jabber" target="_blank"><?= t('Help on Jabber integration') ?></a></p>
</div>
- <h3><img src="assets/img/hipchat-icon.png"/> <?= t('Hipchat') ?></h3>
+ <h3><img src="<?= $this->url->dir() ?>assets/img/hipchat-icon.png"/> <?= t('Hipchat') ?></h3>
<div class="listing">
<?= $this->form->checkbox('integration_hipchat', t('Send notifications to Hipchat'), 1, $values['integration_hipchat'] == 1) ?>
diff --git a/app/Template/config/webhook.php b/app/Template/config/webhook.php
index 73ca3598..f1a98f8b 100644
--- a/app/Template/config/webhook.php
+++ b/app/Template/config/webhook.php
@@ -26,7 +26,7 @@
</li>
<li>
<?= t('URL for task creation:') ?>
- <input type="text" class="auto-select" readonly="readonly" value="<?= $this->url->base().$this->url->href('webhook', 'task', array('token' => $values['webhook_token'])) ?>">
+ <input type="text" class="auto-select" readonly="readonly" value="<?= $this->url->href('webhook', 'task', array('token' => $values['webhook_token']), false, '', true) ?>">
</li>
<li>
<?= $this->url->link(t('Reset token'), 'config', 'token', array('type' => 'webhook'), true) ?>
diff --git a/app/Template/feed/project.php b/app/Template/feed/project.php
index 60b7ee96..76cf6cf0 100644
--- a/app/Template/feed/project.php
+++ b/app/Template/feed/project.php
@@ -2,15 +2,15 @@
<feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom">
<title><?= t('%s\'s activity', $project['name']) ?></title>
<link rel="alternate" type="text/html" href="<?= $this->url->base() ?>"/>
- <link rel="self" type="application/atom+xml" href="<?= $this->url->base().$this->url->href('feed', 'project', array('token' => $project['token'])) ?>"/>
+ <link rel="self" type="application/atom+xml" href="<?= $this->url->href('feed', 'project', array('token' => $project['token']), false, '', true) ?>"/>
<updated><?= date(DATE_ATOM) ?></updated>
- <id><?= $this->url->base().$this->url->href('feed', 'project', array('token' => $project['token'])) ?></id>
+ <id><?= $this->url->href('feed', 'project', array('token' => $project['token']), false, '', true) ?></id>
<icon><?= $this->url->base() ?>assets/img/favicon.png</icon>
<?php foreach ($events as $e): ?>
<entry>
<title type="text"><?= $e['event_title'] ?></title>
- <link rel="alternate" href="<?= $this->url->base().$this->url->href('task', 'show', array('task_id' => $e['task_id'])) ?>"/>
+ <link rel="alternate" href="<?= $this->url->href('task', 'show', array('task_id' => $e['task_id']), false, '', true) ?>"/>
<id><?= $e['id'].'-'.$e['event_name'].'-'.$e['task_id'].'-'.$e['date_creation'] ?></id>
<published><?= date(DATE_ATOM, $e['date_creation']) ?></published>
<updated><?= date(DATE_ATOM, $e['date_creation']) ?></updated>
diff --git a/app/Template/feed/user.php b/app/Template/feed/user.php
index b3279a0c..3e9606c6 100644
--- a/app/Template/feed/user.php
+++ b/app/Template/feed/user.php
@@ -2,15 +2,15 @@
<feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom">
<title><?= t('Project activities for %s', $user['name'] ?: $user['username']) ?></title>
<link rel="alternate" type="text/html" href="<?= $this->url->base() ?>"/>
- <link rel="self" type="application/atom+xml" href="<?= $this->url->base().$this->url->href('feed', 'user', array('token' => $user['token'])) ?>"/>
+ <link rel="self" type="application/atom+xml" href="<?= $this->url->href('feed', 'user', array('token' => $user['token']), false, '', true) ?>"/>
<updated><?= date(DATE_ATOM) ?></updated>
- <id><?= $this->url->base().$this->url->href('feed', 'user', array('token' => $user['token'])) ?></id>
+ <id><?= $this->url->href('feed', 'user', array('token' => $user['token']), false, '', true) ?></id>
<icon><?= $this->url->base() ?>assets/img/favicon.png</icon>
<?php foreach ($events as $e): ?>
<entry>
<title type="text"><?= $e['event_title'] ?></title>
- <link rel="alternate" href="<?= $this->url->base().$this->url->href('task', 'show', array('task_id' => $e['task_id'])) ?>"/>
+ <link rel="alternate" href="<?= $this->url->href('task', 'show', array('task_id' => $e['task_id']), false, '', true) ?>"/>
<id><?= $e['id'].'-'.$e['event_name'].'-'.$e['task_id'].'-'.$e['date_creation'] ?></id>
<published><?= date(DATE_ATOM, $e['date_creation']) ?></published>
<updated><?= date(DATE_ATOM, $e['date_creation']) ?></updated>
diff --git a/app/Template/layout.php b/app/Template/layout.php
index a9f1cbc3..d02ba08d 100644
--- a/app/Template/layout.php
+++ b/app/Template/layout.php
@@ -20,11 +20,11 @@
<?= $this->asset->css('assets/css/print.css', true, 'print') ?>
<?= $this->asset->customCss() ?>
- <link rel="icon" type="image/png" href="assets/img/favicon.png">
- <link rel="apple-touch-icon" href="assets/img/touch-icon-iphone.png">
- <link rel="apple-touch-icon" sizes="72x72" href="assets/img/touch-icon-ipad.png">
- <link rel="apple-touch-icon" sizes="114x114" href="assets/img/touch-icon-iphone-retina.png">
- <link rel="apple-touch-icon" sizes="144x144" href="assets/img/touch-icon-ipad-retina.png">
+ <link rel="icon" type="image/png" href="<?= $this->url->dir() ?>assets/img/favicon.png">
+ <link rel="apple-touch-icon" href="<?= $this->url->dir() ?>assets/img/touch-icon-iphone.png">
+ <link rel="apple-touch-icon" sizes="72x72" href="<?= $this->url->dir() ?>assets/img/touch-icon-ipad.png">
+ <link rel="apple-touch-icon" sizes="114x114" href="<?= $this->url->dir() ?>assets/img/touch-icon-iphone-retina.png">
+ <link rel="apple-touch-icon" sizes="144x144" href="<?= $this->url->dir() ?>assets/img/touch-icon-ipad-retina.png">
<title><?= isset($title) ? $this->e($title) : 'Kanboard' ?></title>
</head>
diff --git a/app/Template/listing/show.php b/app/Template/listing/show.php
index 06940678..fc8a607b 100644
--- a/app/Template/listing/show.php
+++ b/app/Template/listing/show.php
@@ -10,13 +10,12 @@
<table class="table-fixed table-small">
<tr>
<th class="column-5"><?= $paginator->order(t('Id'), 'tasks.id') ?></th>
- <th class="column-8"><?= $paginator->order(t('Column'), 'tasks.column_id') ?></th>
- <th class="column-8"><?= $paginator->order(t('Category'), 'tasks.category_id') ?></th>
+ <th class="column-10"><?= $paginator->order(t('Swimlane'), 'tasks.swimlane_id') ?></th>
+ <th class="column-10"><?= $paginator->order(t('Column'), 'tasks.column_id') ?></th>
+ <th class="column-10"><?= $paginator->order(t('Category'), 'tasks.category_id') ?></th>
<th><?= $paginator->order(t('Title'), 'tasks.title') ?></th>
<th class="column-10"><?= $paginator->order(t('Assignee'), 'users.username') ?></th>
<th class="column-10"><?= $paginator->order(t('Due date'), 'tasks.date_due') ?></th>
- <th class="column-10"><?= $paginator->order(t('Date created'), 'tasks.date_creation') ?></th>
- <th class="column-10"><?= $paginator->order(t('Date completed'), 'tasks.date_completed') ?></th>
<th class="column-5"><?= $paginator->order(t('Status'), 'tasks.is_active') ?></th>
</tr>
<?php foreach ($paginator->getCollection() as $task): ?>
@@ -25,6 +24,9 @@
<?= $this->url->link('#'.$this->e($task['id']), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, '', t('View this task')) ?>
</td>
<td>
+ <?= $this->e($task['swimlane_name'] ?: $task['default_swimlane']) ?>
+ </td>
+ <td>
<?= $this->e($task['column_name']) ?>
</td>
<td>
@@ -44,14 +46,6 @@
<?= dt('%B %e, %Y', $task['date_due']) ?>
</td>
<td>
- <?= dt('%B %e, %Y', $task['date_creation']) ?>
- </td>
- <td>
- <?php if ($task['date_completed']): ?>
- <?= dt('%B %e, %Y', $task['date_completed']) ?>
- <?php endif ?>
- </td>
- <td>
<?php if ($task['is_active'] == \Model\Task::STATUS_OPEN): ?>
<?= t('Open') ?>
<?php else: ?>
diff --git a/app/Template/notification/footer.php b/app/Template/notification/footer.php
index 69d2cf82..c3b37884 100644
--- a/app/Template/notification/footer.php
+++ b/app/Template/notification/footer.php
@@ -2,6 +2,6 @@
Kanboard
<?php if (isset($application_url) && ! empty($application_url)): ?>
- - <a href="<?= $application_url.$this->url->href('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><?= t('view the task on Kanboard') ?></a>
- - <a href="<?= $application_url.$this->url->href('board', 'show', array('project_id' => $task['project_id'])) ?>"><?= t('view the board on Kanboard') ?></a>
+ - <a href="<?= $this->url->href('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, '', true) ?>"><?= t('view the task on Kanboard') ?></a>
+ - <a href="<?= $this->url->href('board', 'show', array('project_id' => $task['project_id']), false, '', true) ?>"><?= t('view the board on Kanboard') ?></a>
<?php endif ?>
diff --git a/app/Template/notification/task_overdue.php b/app/Template/notification/task_overdue.php
index dc2659dc..a231937b 100644
--- a/app/Template/notification/task_overdue.php
+++ b/app/Template/notification/task_overdue.php
@@ -5,7 +5,7 @@
<li>
(<strong>#<?= $task['id'] ?></strong>)
<?php if ($application_url): ?>
- <a href="<?= $application_url.$this->url->href('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><?= $this->e($task['title']) ?></a>
+ <a href="<?= $this->url->href('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, '', true) ?>"><?= $this->e($task['title']) ?></a>
<?php else: ?>
<?= $this->e($task['title']) ?>
<?php endif ?>
diff --git a/app/Template/project/filters.php b/app/Template/project/filters.php
index 3beb2f44..e2fdc751 100644
--- a/app/Template/project/filters.php
+++ b/app/Template/project/filters.php
@@ -5,13 +5,14 @@
<ul>
<?php if (isset($is_board)): ?>
<li>
- <?php if ($this->board->isCollapsed($project['id'])): ?>
+ <span class="filter-display-mode" <?= $this->board->isCollapsed($project['id']) ? '' : 'style="display: none;"' ?>>
<i class="fa fa-expand fa-fw"></i>
<?= $this->url->link(t('Expand tasks'), 'board', 'expand', array('project_id' => $project['id']), false, 'board-display-mode', t('Keyboard shortcut: "%s"', 's')) ?>
- <?php else: ?>
+ </span>
+ <span class="filter-display-mode" <?= $this->board->isCollapsed($project['id']) ? 'style="display: none;"' : '' ?>>
<i class="fa fa-compress fa-fw"></i>
<?= $this->url->link(t('Collapse tasks'), 'board', 'collapse', array('project_id' => $project['id']), false, 'board-display-mode', t('Keyboard shortcut: "%s"', 's')) ?>
- <?php endif ?>
+ </span>
</li>
<li>
<span class="filter-compact">
@@ -40,10 +41,10 @@
<?= $this->url->link(t('List'), 'listing', 'show', array('project_id' => $project['id'], 'search' => $filters['search']), false, 'view-listing', t('Keyboard shortcut: "%s"', 'v l')) ?>
</li>
</ul>
- <form method="get" action="?" class="search">
- <?= $this->form->hidden('project_id', $filters) ?>
+ <form method="get" action="<?= $this->url->dir() ?>" class="search">
<?= $this->form->hidden('controller', $filters) ?>
<?= $this->form->hidden('action', $filters) ?>
+ <?= $this->form->hidden('project_id', $filters) ?>
<?= $this->form->text('search', $filters, array(), array('placeholder="'.t('Filter').'"'), 'form-input-large') ?>
</form>
<?= $this->render('app/filters_helper', array('reset' => 'status:open')) ?>
diff --git a/app/Template/project/integrations.php b/app/Template/project/integrations.php
index 445e7bfb..12a7ee4e 100644
--- a/app/Template/project/integrations.php
+++ b/app/Template/project/integrations.php
@@ -8,26 +8,26 @@
<h3><i class="fa fa-github fa-fw"></i>&nbsp;<?= t('Github webhooks') ?></h3>
<div class="listing">
- <input type="text" class="auto-select" readonly="readonly" value="<?= $this->url->base().$this->url->href('webhook', 'github', array('token' => $webhook_token, 'project_id' => $project['id'])) ?>"/><br/>
+ <input type="text" class="auto-select" readonly="readonly" value="<?= $this->url->href('webhook', 'github', array('token' => $webhook_token, 'project_id' => $project['id']), false, '', true) ?>"/><br/>
<p class="form-help"><a href="http://kanboard.net/documentation/github-webhooks" target="_blank"><?= t('Help on Github webhooks') ?></a></p>
</div>
- <h3><img src="assets/img/gitlab-icon.png"/>&nbsp;<?= t('Gitlab webhooks') ?></h3>
+ <h3><img src="<?= $this->url->dir() ?>assets/img/gitlab-icon.png"/>&nbsp;<?= t('Gitlab webhooks') ?></h3>
<div class="listing">
- <input type="text" class="auto-select" readonly="readonly" value="<?= $this->url->base().$this->url->href('webhook', 'gitlab', array('token' => $webhook_token, 'project_id' => $project['id'])) ?>"/><br/>
+ <input type="text" class="auto-select" readonly="readonly" value="<?= $this->url->href('webhook', 'gitlab', array('token' => $webhook_token, 'project_id' => $project['id']), false, '', true) ?>"/><br/>
<p class="form-help"><a href="http://kanboard.net/documentation/gitlab-webhooks" target="_blank"><?= t('Help on Gitlab webhooks') ?></a></p>
</div>
<h3><i class="fa fa-bitbucket fa-fw"></i>&nbsp;<?= t('Bitbucket webhooks') ?></h3>
<div class="listing">
- <input type="text" class="auto-select" readonly="readonly" value="<?= $this->url->base().$this->url->href('webhook', 'bitbucket', array('token' => $webhook_token, 'project_id' => $project['id'])) ?>"/><br/>
+ <input type="text" class="auto-select" readonly="readonly" value="<?= $this->url->href('webhook', 'bitbucket', array('token' => $webhook_token, 'project_id' => $project['id']), false, '', true) ?>"/><br/>
<p class="form-help"><a href="http://kanboard.net/documentation/bitbucket-webhooks" target="_blank"><?= t('Help on Bitbucket webhooks') ?></a></p>
</div>
- <h3><img src="assets/img/jabber-icon.png"/> <?= t('Jabber (XMPP)') ?></h3>
+ <h3><img src="<?= $this->url->dir() ?>assets/img/jabber-icon.png"/> <?= t('Jabber (XMPP)') ?></h3>
<div class="listing">
<?= $this->form->checkbox('jabber', t('Send notifications to Jabber'), 1, isset($values['jabber']) && $values['jabber'] == 1) ?>
@@ -58,7 +58,7 @@
</div>
- <h3><img src="assets/img/hipchat-icon.png"/> <?= t('Hipchat') ?></h3>
+ <h3><img src="<?= $this->url->dir() ?>assets/img/hipchat-icon.png"/> <?= t('Hipchat') ?></h3>
<div class="listing">
<?= $this->form->checkbox('hipchat', t('Send notifications to Hipchat'), 1, isset($values['hipchat']) && $values['hipchat'] == 1) ?>
diff --git a/app/Template/search/index.php b/app/Template/search/index.php
index 3d0d6ff9..8940a24e 100644
--- a/app/Template/search/index.php
+++ b/app/Template/search/index.php
@@ -9,7 +9,7 @@
</div>
<div class="search">
- <form method="get" action="?" class="search">
+ <form method="get" action="<?= $this->url->dir() ?>" class="search">
<?= $this->form->hidden('controller', $values) ?>
<?= $this->form->hidden('action', $values) ?>
<?= $this->form->text('search', $values, array(), array(empty($values['search']) ? 'autofocus' : '', 'placeholder="'.t('Search').'"'), 'form-input-large') ?>
diff --git a/app/Template/search/results.php b/app/Template/search/results.php
index 1d8cc6e2..04cb6a19 100644
--- a/app/Template/search/results.php
+++ b/app/Template/search/results.php
@@ -1,14 +1,13 @@
<table class="table-fixed table-small">
<tr>
<th class="column-8"><?= $paginator->order(t('Project'), 'tasks.project_id') ?></th>
- <th class="column-8"><?= $paginator->order(t('Id'), 'tasks.id') ?></th>
- <th class="column-8"><?= $paginator->order(t('Column'), 'tasks.column_id') ?></th>
- <th class="column-8"><?= $paginator->order(t('Category'), 'tasks.category_id') ?></th>
+ <th class="column-5"><?= $paginator->order(t('Id'), 'tasks.id') ?></th>
+ <th class="column-10"><?= $paginator->order(t('Swimlane'), 'tasks.swimlane_id') ?></th>
+ <th class="column-10"><?= $paginator->order(t('Column'), 'tasks.column_id') ?></th>
+ <th class="column-10"><?= $paginator->order(t('Category'), 'tasks.category_id') ?></th>
<th><?= $paginator->order(t('Title'), 'tasks.title') ?></th>
<th class="column-10"><?= $paginator->order(t('Assignee'), 'users.username') ?></th>
<th class="column-10"><?= $paginator->order(t('Due date'), 'tasks.date_due') ?></th>
- <th class="column-10"><?= $paginator->order(t('Date created'), 'tasks.date_creation') ?></th>
- <th class="column-10"><?= $paginator->order(t('Date completed'), 'tasks.date_completed') ?></th>
<th class="column-5"><?= $paginator->order(t('Status'), 'tasks.is_active') ?></th>
</tr>
<?php foreach ($paginator->getCollection() as $task): ?>
@@ -20,6 +19,9 @@
<?= $this->url->link('#'.$this->e($task['id']), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, '', t('View this task')) ?>
</td>
<td>
+ <?= $this->e($task['swimlane_name'] ?: $task['default_swimlane']) ?>
+ </td>
+ <td>
<?= $this->e($task['column_name']) ?>
</td>
<td>
@@ -39,14 +41,6 @@
<?= dt('%B %e, %Y', $task['date_due']) ?>
</td>
<td>
- <?= dt('%B %e, %Y', $task['date_creation']) ?>
- </td>
- <td>
- <?php if ($task['date_completed']): ?>
- <?= dt('%B %e, %Y', $task['date_completed']) ?>
- <?php endif ?>
- </td>
- <td>
<?php if ($task['is_active'] == \Model\Task::STATUS_OPEN): ?>
<?= t('Open') ?>
<?php else: ?>
diff --git a/app/Template/task/edit_recurrence.php b/app/Template/task/edit_recurrence.php
index c261e368..e7c4c8f8 100644
--- a/app/Template/task/edit_recurrence.php
+++ b/app/Template/task/edit_recurrence.php
@@ -15,7 +15,7 @@
<?php if ($task['recurrence_status'] != \Model\Task::RECURRING_STATUS_PROCESSED): ?>
- <form method="post" action="<?= $this->url->href('task', 'recurrence', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'ajax' => $ajax)) ?>" autocomplete="off">
+ <form method="post" action="<?= $this->url->href('task', 'recurrence', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>" autocomplete="off">
<?= $this->form->csrf() ?>
@@ -40,12 +40,7 @@
<div class="form-actions">
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
<?= t('or') ?>
-
- <?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'])) ?>
- <?php endif ?>
+ <?= $this->url->link(t('cancel'), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
</div>
</form>
diff --git a/app/Template/user/authentication.php b/app/Template/user/authentication.php
new file mode 100644
index 00000000..a62c8f93
--- /dev/null
+++ b/app/Template/user/authentication.php
@@ -0,0 +1,32 @@
+<div class="page-header">
+ <h2><?= t('Edit Authentication') ?></h2>
+</div>
+<form method="post" action="<?= $this->url->href('user', 'authentication', array('user_id' => $user['id'])) ?>" autocomplete="off">
+
+ <?= $this->form->csrf() ?>
+
+ <?= $this->form->hidden('id', $values) ?>
+ <?= $this->form->hidden('username', $values) ?>
+
+ <?= $this->form->label(t('Google Id'), 'google_id') ?>
+ <?= $this->form->text('google_id', $values, $errors) ?>
+
+ <?= $this->form->label(t('Github Id'), 'github_id') ?>
+ <?= $this->form->text('github_id', $values, $errors) ?>
+
+ <?= $this->form->checkbox('is_ldap_user', t('Remote user'), 1, isset($values['is_ldap_user']) && $values['is_ldap_user'] == 1) ?>
+ <?= $this->form->checkbox('disable_login_form', t('Disallow login form'), 1, isset($values['disable_login_form']) && $values['disable_login_form'] == 1) ?>
+
+ <div class="form-actions">
+ <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
+ <?= t('or') ?>
+ <?= $this->url->link(t('cancel'), 'user', 'show', array('user_id' => $user['id'])) ?>
+ </div>
+
+ <div class="alert alert-info">
+ <ul>
+ <li><?= t('Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.') ?></li>
+ <li><?= t('If you check the box "Disallow login form", credentials entered in the login form will be ignored.') ?></li>
+ </ul>
+ </div>
+</form> \ No newline at end of file
diff --git a/app/Template/user/new.php b/app/Template/user/create_local.php
index 0db1e824..aeec300f 100644
--- a/app/Template/user/new.php
+++ b/app/Template/user/create_local.php
@@ -2,6 +2,7 @@
<div class="page-header">
<ul>
<li><i class="fa fa-user fa-fw"></i><?= $this->url->link(t('All users'), 'user', 'index') ?></li>
+ <li><i class="fa fa-plus fa-fw"></i><?= $this->url->link(t('New remote user'), 'user', 'create', array('remote' => 1)) ?></li>
</ul>
</div>
<section>
diff --git a/app/Template/user/create_remote.php b/app/Template/user/create_remote.php
new file mode 100644
index 00000000..52661585
--- /dev/null
+++ b/app/Template/user/create_remote.php
@@ -0,0 +1,57 @@
+<section id="main">
+ <div class="page-header">
+ <ul>
+ <li><i class="fa fa-user fa-fw"></i><?= $this->url->link(t('All users'), 'user', 'index') ?></li>
+ <li><i class="fa fa-plus fa-fw"></i><?= $this->url->link(t('New local user'), 'user', 'create') ?></li>
+ </ul>
+ </div>
+ <form method="post" action="<?= $this->url->href('user', 'save') ?>" autocomplete="off">
+
+ <?= $this->form->csrf() ?>
+ <?= $this->form->hidden('is_ldap_user', array('is_ldap_user' => 1)) ?>
+
+ <div class="form-column">
+ <?= $this->form->label(t('Username'), 'username') ?>
+ <?= $this->form->text('username', $values, $errors, array('autofocus', 'required', 'maxlength="50"')) ?><br/>
+
+ <?= $this->form->label(t('Name'), 'name') ?>
+ <?= $this->form->text('name', $values, $errors) ?><br/>
+
+ <?= $this->form->label(t('Email'), 'email') ?>
+ <?= $this->form->email('email', $values, $errors) ?><br/>
+
+ <?= $this->form->label(t('Google Id'), 'google_id') ?>
+ <?= $this->form->password('google_id', $values, $errors) ?><br/>
+
+ <?= $this->form->label(t('Github Id'), 'github_id') ?>
+ <?= $this->form->password('github_id', $values, $errors) ?><br/>
+ </div>
+
+ <div class="form-column">
+ <?= $this->form->label(t('Add project member'), 'project_id') ?>
+ <?= $this->form->select('project_id', $projects, $values, $errors) ?><br/>
+
+ <?= $this->form->label(t('Timezone'), 'timezone') ?>
+ <?= $this->form->select('timezone', $timezones, $values, $errors) ?><br/>
+
+ <?= $this->form->label(t('Language'), 'language') ?>
+ <?= $this->form->select('language', $languages, $values, $errors) ?><br/>
+
+ <?= $this->form->checkbox('notifications_enabled', t('Enable notifications'), 1, isset($values['notifications_enabled']) && $values['notifications_enabled'] == 1 ? true : false) ?>
+ <?= $this->form->checkbox('is_admin', t('Administrator'), 1, isset($values['is_admin']) && $values['is_admin'] == 1 ? true : false) ?>
+ <?= $this->form->checkbox('disable_login_form', t('Disallow login form'), 1, isset($values['disable_login_form']) && $values['disable_login_form'] == 1) ?>
+ </div>
+
+ <div class="form-actions">
+ <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
+ <?= t('or') ?>
+ <?= $this->url->link(t('cancel'), 'user', 'index') ?>
+ </div>
+ </form>
+ <div class="alert alert-info">
+ <ul>
+ <li><?= t('Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.') ?></li>
+ <li><?= t('If you check the box "Disallow login form", credentials entered in the login form will be ignored.') ?></li>
+ </ul>
+ </div>
+</section> \ No newline at end of file
diff --git a/app/Template/user/edit.php b/app/Template/user/edit.php
index 2462308f..ea7e3875 100644
--- a/app/Template/user/edit.php
+++ b/app/Template/user/edit.php
@@ -6,7 +6,6 @@
<?= $this->form->csrf() ?>
<?= $this->form->hidden('id', $values) ?>
- <?= $this->form->hidden('is_ldap_user', $values) ?>
<?= $this->form->label(t('Username'), 'username') ?>
<?= $this->form->text('username', $values, $errors, array('required', $values['is_ldap_user'] == 1 ? 'readonly' : '', 'maxlength="50"')) ?><br/>
@@ -23,13 +22,9 @@
<?= $this->form->label(t('Language'), 'language') ?>
<?= $this->form->select('language', $languages, $values, $errors) ?><br/>
- <div class="alert alert-error">
- <?= $this->form->checkbox('disable_login_form', t('Disable login form'), 1, isset($values['disable_login_form']) && $values['disable_login_form'] == 1) ?><br/>
-
- <?php if ($this->user->isAdmin()): ?>
- <?= $this->form->checkbox('is_admin', t('Administrator'), 1, isset($values['is_admin']) && $values['is_admin'] == 1) ?><br/>
- <?php endif ?>
- </div>
+ <?php if ($this->user->isAdmin()): ?>
+ <?= $this->form->checkbox('is_admin', t('Administrator'), 1, isset($values['is_admin']) && $values['is_admin'] == 1) ?><br/>
+ <?php endif ?>
<div class="form-actions">
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
diff --git a/app/Template/user/external.php b/app/Template/user/external.php
index df85ace7..3b872e85 100644
--- a/app/Template/user/external.php
+++ b/app/Template/user/external.php
@@ -8,9 +8,9 @@
<p class="listing">
<?php if ($this->user->isCurrentUser($user['id'])): ?>
<?php if (empty($user['google_id'])): ?>
- <?= $this->url->link(t('Link my Google Account'), 'user', 'google', array(), true) ?>
+ <?= $this->url->link(t('Link my Google Account'), 'oauth', 'google', array(), true) ?>
<?php else: ?>
- <?= $this->url->link(t('Unlink my Google Account'), 'user', 'unlinkGoogle', array(), true) ?>
+ <?= $this->url->link(t('Unlink my Google Account'), 'oauth', 'unlink', array('backend' => 'google'), true) ?>
<?php endif ?>
<?php else: ?>
<?= empty($user['google_id']) ? t('No account linked.') : t('Account linked.') ?>
@@ -24,9 +24,9 @@
<p class="listing">
<?php if ($this->user->isCurrentUser($user['id'])): ?>
<?php if (empty($user['github_id'])): ?>
- <?= $this->url->link(t('Link my GitHub Account'), 'user', 'github', array(), true) ?>
+ <?= $this->url->link(t('Link my Github Account'), 'oauth', 'github', array(), true) ?>
<?php else: ?>
- <?= $this->url->link(t('Unlink my GitHub Account'), 'user', 'unlinkGitHub', array(), true) ?>
+ <?= $this->url->link(t('Unlink my Github Account'), 'oauth', 'unlink', array('backend' => 'github'), true) ?>
<?php endif ?>
<?php else: ?>
<?= empty($user['github_id']) ? t('No account linked.') : t('Account linked.') ?>
diff --git a/app/Template/user/index.php b/app/Template/user/index.php
index fc575466..edf043a6 100644
--- a/app/Template/user/index.php
+++ b/app/Template/user/index.php
@@ -2,7 +2,8 @@
<div class="page-header">
<?php if ($this->user->isAdmin()): ?>
<ul>
- <li><i class="fa fa-plus fa-fw"></i><?= $this->url->link(t('New user'), 'user', 'create') ?></li>
+ <li><i class="fa fa-plus fa-fw"></i><?= $this->url->link(t('New local user'), 'user', 'create') ?></li>
+ <li><i class="fa fa-plus fa-fw"></i><?= $this->url->link(t('New remote user'), 'user', 'create', array('remote' => 1)) ?></li>
</ul>
<?php endif ?>
</div>
diff --git a/app/Template/user/layout.php b/app/Template/user/layout.php
index e60ab77d..a27f359b 100644
--- a/app/Template/user/layout.php
+++ b/app/Template/user/layout.php
@@ -3,7 +3,8 @@
<?php if ($this->user->isAdmin()): ?>
<ul>
<li><i class="fa fa-user fa-fw"></i><?= $this->url->link(t('All users'), 'user', 'index') ?></li>
- <li><i class="fa fa-plus fa-fw"></i><?= $this->url->link(t('New user'), 'user', 'create') ?></li>
+ <li><i class="fa fa-plus fa-fw"></i><?= $this->url->link(t('New local user'), 'user', 'create') ?></li>
+ <li><i class="fa fa-plus fa-fw"></i><?= $this->url->link(t('New remote user'), 'user', 'create', array('remote' => 1)) ?></li>
</ul>
<?php endif ?>
</div>
diff --git a/app/Template/user/sidebar.php b/app/Template/user/sidebar.php
index e61a43bf..3e79fda3 100644
--- a/app/Template/user/sidebar.php
+++ b/app/Template/user/sidebar.php
@@ -58,6 +58,9 @@
<?php if ($this->user->isAdmin()): ?>
<li>
+ <?= $this->url->link(t('Edit Authentication'), 'user', 'authentication', array('user_id' => $user['id'])) ?>
+ </li>
+ <li>
<?= $this->url->link(t('Hourly rates'), 'hourlyrate', 'index', array('user_id' => $user['id'])) ?>
</li>
<li>
diff --git a/app/common.php b/app/common.php
index b5871673..734f094b 100644
--- a/app/common.php
+++ b/app/common.php
@@ -12,7 +12,7 @@ if (getenv('DATABASE_URL')) {
define('DB_PASSWORD', $dbopts["pass"]);
define('DB_HOSTNAME', $dbopts["host"]);
define('DB_PORT', isset($dbopts["port"]) ? $dbopts["port"] : null);
- define('DB_NAME', ltrim($dbopts["path"],'/'));
+ define('DB_NAME', ltrim($dbopts["path"], '/'));
}
// Include custom config file
@@ -28,3 +28,97 @@ $container->register(new ServiceProvider\LoggingProvider);
$container->register(new ServiceProvider\DatabaseProvider);
$container->register(new ServiceProvider\ClassProvider);
$container->register(new ServiceProvider\EventDispatcherProvider);
+
+if (ENABLE_URL_REWRITE) {
+
+ // Dashboard
+ $container['router']->addRoute('dashboard', 'app', 'index');
+ $container['router']->addRoute('dashboard/:user_id', 'app', 'index', array('user_id'));
+ $container['router']->addRoute('dashboard/:user_id/projects', 'app', 'projects', array('user_id'));
+ $container['router']->addRoute('dashboard/:user_id/tasks', 'app', 'tasks', array('user_id'));
+ $container['router']->addRoute('dashboard/:user_id/subtasks', 'app', 'subtasks', array('user_id'));
+ $container['router']->addRoute('dashboard/:user_id/calendar', 'app', 'calendar', array('user_id'));
+ $container['router']->addRoute('dashboard/:user_id/activity', 'app', 'activity', array('user_id'));
+
+ // Search routes
+ $container['router']->addRoute('search', 'search', 'index');
+ $container['router']->addRoute('search/:search', 'search', 'index', array('search'));
+
+ // Project routes
+ $container['router']->addRoute('projects', 'project', 'index');
+ $container['router']->addRoute('project/create', 'project', 'create');
+ $container['router']->addRoute('project/create/:private', 'project', 'create', array('private'));
+ $container['router']->addRoute('project/:project_id', 'project', 'show', array('project_id'));
+ $container['router']->addRoute('p/:project_id', 'project', 'show', array('project_id'));
+ $container['router']->addRoute('project/:project_id/share', 'project', 'share', array('project_id'));
+ $container['router']->addRoute('project/:project_id/edit', 'project', 'edit', array('project_id'));
+ $container['router']->addRoute('project/:project_id/integration', 'project', 'integration', array('project_id'));
+ $container['router']->addRoute('project/:project_id/users', 'project', 'users', array('project_id'));
+ $container['router']->addRoute('project/:project_id/duplicate', 'project', 'duplicate', array('project_id'));
+ $container['router']->addRoute('project/:project_id/remove', 'project', 'remove', array('project_id'));
+ $container['router']->addRoute('project/:project_id/disable', 'project', 'disable', array('project_id'));
+ $container['router']->addRoute('project/:project_id/enable', 'project', 'enable', array('project_id'));
+
+ // Action routes
+ $container['router']->addRoute('project/:project_id/actions', 'action', 'index', array('project_id'));
+ $container['router']->addRoute('project/:project_id/action/:action_id/confirm', 'action', 'confirm', array('project_id', 'action_id'));
+
+ // Column routes
+ $container['router']->addRoute('project/:project_id/columns', 'column', 'index', array('project_id'));
+ $container['router']->addRoute('project/:project_id/column/:column_id/edit', 'column', 'edit', array('project_id', 'column_id'));
+ $container['router']->addRoute('project/:project_id/column/:column_id/confirm', 'column', 'confirm', array('project_id', 'column_id'));
+ $container['router']->addRoute('project/:project_id/column/:column_id/move/:direction', 'column', 'move', array('project_id', 'column_id', 'direction'));
+
+ // Swimlane routes
+ $container['router']->addRoute('project/:project_id/swimlanes', 'swimlane', 'index', array('project_id'));
+ $container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/edit', 'swimlane', 'edit', array('project_id', 'swimlane_id'));
+ $container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/confirm', 'swimlane', 'confirm', array('project_id', 'swimlane_id'));
+ $container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/disable', 'swimlane', 'disable', array('project_id', 'swimlane_id'));
+ $container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/enable', 'swimlane', 'enable', array('project_id', 'swimlane_id'));
+ $container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/up', 'swimlane', 'moveup', array('project_id', 'swimlane_id'));
+ $container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/down', 'swimlane', 'movedown', array('project_id', 'swimlane_id'));
+
+ // Category routes
+ $container['router']->addRoute('project/:project_id/categories', 'category', 'index', array('project_id'));
+ $container['router']->addRoute('project/:project_id/category/:category_id/edit', 'category', 'edit', array('project_id', 'category_id'));
+ $container['router']->addRoute('project/:project_id/category/:category_id/confirm', 'category', 'confirm', array('project_id', 'category_id'));
+
+ // Task routes
+ $container['router']->addRoute('project/:project_id/task/:task_id', 'task', 'show', array('project_id', 'task_id'));
+ $container['router']->addRoute('t/:task_id', 'task', 'show', array('task_id'));
+ $container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/column/:column_id', 'task', 'create', array('project_id', 'swimlane_id', 'column_id'));
+ $container['router']->addRoute('public/task/:task_id/:token', 'task', 'readonly', array('task_id', 'token'));
+
+ // Board routes
+ $container['router']->addRoute('board/:project_id', 'board', 'show', array('project_id'));
+ $container['router']->addRoute('b/:project_id', 'board', 'show', array('project_id'));
+ $container['router']->addRoute('board/:project_id/filter/:search', 'board', 'show', array('project_id', 'search'));
+ $container['router']->addRoute('public/board/:token', 'board', 'readonly', array('token'));
+
+ // Calendar routes
+ $container['router']->addRoute('calendar/:project_id', 'calendar', 'show', array('project_id'));
+ $container['router']->addRoute('c/:project_id', 'calendar', 'show', array('project_id'));
+ $container['router']->addRoute('calendar/:project_id/:search', 'calendar', 'show', array('project_id', 'search'));
+
+ // Listing routes
+ $container['router']->addRoute('list/:project_id', 'listing', 'show', array('project_id'));
+ $container['router']->addRoute('l/:project_id', 'listing', 'show', array('project_id'));
+ $container['router']->addRoute('list/:project_id/:search', 'listing', 'show', array('project_id', 'search'));
+
+ // Subtask routes
+ $container['router']->addRoute('project/:project_id/task/:task_id/subtask/:subtask_id', 'subtask', 'remove', array('project_id', 'task_id', 'subtask_id'));
+
+ // Feed routes
+ $container['router']->addRoute('feed/project/:token', 'feed', 'project', array('token'));
+ $container['router']->addRoute('feed/user/:token', 'feed', 'user', array('token'));
+
+ // Ical routes
+ $container['router']->addRoute('ical/project/:token', 'ical', 'project', array('token'));
+ $container['router']->addRoute('ical/user/:token', 'ical', 'user', array('token'));
+
+ // Auth routes
+ $container['router']->addRoute('oauth/google', 'oauth', 'google');
+ $container['router']->addRoute('oauth/github', 'oauth', 'github');
+ $container['router']->addRoute('login', 'auth', 'login');
+ $container['router']->addRoute('logout', 'auth', 'logout');
+}
diff --git a/app/constants.php b/app/constants.php
index 9b66b746..83fba468 100644
--- a/app/constants.php
+++ b/app/constants.php
@@ -7,9 +7,6 @@ defined('DEBUG_FILE') or define('DEBUG_FILE', __DIR__.'/../data/debug.log');
// Application version
defined('APP_VERSION') or define('APP_VERSION', 'master');
-// Base directory
-define('BASE_URL_DIRECTORY', dirname($_SERVER['PHP_SELF']));
-
// Database driver: sqlite, mysql or postgres
defined('DB_DRIVER') or define('DB_DRIVER', 'sqlite');
@@ -38,13 +35,14 @@ defined('LDAP_ACCOUNT_FULLNAME') or define('LDAP_ACCOUNT_FULLNAME', 'displayname
defined('LDAP_ACCOUNT_EMAIL') or define('LDAP_ACCOUNT_EMAIL', 'mail');
defined('LDAP_ACCOUNT_ID') or define('LDAP_ACCOUNT_ID', '');
defined('LDAP_USERNAME_CASE_SENSITIVE') or define('LDAP_USERNAME_CASE_SENSITIVE', false);
+defined('LDAP_ACCOUNT_CREATION') or define('LDAP_ACCOUNT_CREATION', true);
// Google authentication
defined('GOOGLE_AUTH') or define('GOOGLE_AUTH', false);
defined('GOOGLE_CLIENT_ID') or define('GOOGLE_CLIENT_ID', '');
defined('GOOGLE_CLIENT_SECRET') or define('GOOGLE_CLIENT_SECRET', '');
-// GitHub authentication
+// Github authentication
defined('GITHUB_AUTH') or define('GITHUB_AUTH', false);
defined('GITHUB_CLIENT_ID') or define('GITHUB_CLIENT_ID', '');
defined('GITHUB_CLIENT_SECRET') or define('GITHUB_CLIENT_SECRET', '');
@@ -84,3 +82,9 @@ defined('MARKDOWN_ESCAPE_HTML') or define('MARKDOWN_ESCAPE_HTML', true);
// API alternative authentication header, the default is HTTP Basic Authentication defined in RFC2617
defined('API_AUTHENTICATION_HEADER') or define('API_AUTHENTICATION_HEADER', '');
+
+// Enable/disable url rewrite
+defined('ENABLE_URL_REWRITE') or define('ENABLE_URL_REWRITE', isset($_SERVER['HTTP_MOD_REWRITE']));
+
+// Hide login form
+defined('HIDE_LOGIN_FORM') or define('HIDE_LOGIN_FORM', false);
diff --git a/assets/js/app.js b/assets/js/app.js
index c746e3a2..9dfa6eab 100644
--- a/assets/js/app.js
+++ b/assets/js/app.js
@@ -142,34 +142,34 @@ b[c[e].seq]=1,x(c[e].callback,d,c[e].combo,c[e].seq)):g||x(c[e].callback,d,c[e].
Mousetrap=function(a){var d={},e=a.stopCallback;a.stopCallback=function(b,c,a){return d[a]?!1:e(b,c,a)};a.bindGlobal=function(b,c,e){a.bind(b,c,e);if(b instanceof Array)for(c=0;c<b.length;c++)d[b[c]]=!0;else d[b]=!0};return a}(Mousetrap);
var Kanboard=function(){jQuery(document).ready(function(){Kanboard.Init()});return{Exists:function(b){return document.getElementById(b)?!0:!1},Popover:function(b,a){b.preventDefault();b.stopPropagation();var c=b.target.getAttribute("href");c||(c=b.target.getAttribute("data-href"));c&&Kanboard.OpenPopover(c,a)},OpenPopover:function(b,a){$.get(b,function(c){$("body").append('<div id="popover-container"><div id="popover-content">'+c+"</div></div>");$("#popover-container").click(function(){$(this).remove()});
$("#popover-content").click(function(a){a.stopPropagation()});$(".close-popover").click(function(a){a.preventDefault();$("#popover-container").remove()});Mousetrap.bindGlobal("esc",function(){$("#popover-container").remove()});a&&a()})},IsVisible:function(){var b="";"undefined"!==typeof document.hidden?b="visibilityState":"undefined"!==typeof document.mozHidden?b="mozVisibilityState":"undefined"!==typeof document.msHidden?b="msVisibilityState":"undefined"!==typeof document.webkitHidden&&(b="webkitVisibilityState");
-return""!=b?"visible"==document[b]:!0},SetStorageItem:function(b,a){"undefined"!==typeof Storage&&localStorage.setItem(b,a)},GetStorageItem:function(b){return"undefined"!==typeof Storage?localStorage.getItem(b):""},MarkdownPreview:function(b){b.preventDefault();var a=$(this),c=$(this).closest("ul"),d=$(".write-area"),e=$(".preview-area"),h=$("textarea");$.ajax({url:"?controller=app&action=preview",contentType:"application/json",type:"POST",processData:!1,dataType:"html",data:JSON.stringify({text:h.val()})}).done(function(b){c.find("li").removeClass("form-tab-selected");
-a.parent().addClass("form-tab-selected");e.find(".markdown").html(b);e.css("height",h.css("height"));e.css("width",h.css("width"));d.hide();e.show()})},MarkdownWriter:function(b){b.preventDefault();$(this).closest("ul").find("li").removeClass("form-tab-selected");$(this).parent().addClass("form-tab-selected");$(".write-area").show();$(".preview-area").hide()},CheckSession:function(){$(".form-login").length||$.ajax({cache:!1,url:$("body").data("status-url"),statusCode:{401:function(){window.location=
+return""!=b?"visible"==document[b]:!0},SetStorageItem:function(b,a){"undefined"!==typeof Storage&&localStorage.setItem(b,a)},GetStorageItem:function(b){return"undefined"!==typeof Storage?localStorage.getItem(b):""},MarkdownPreview:function(b){b.preventDefault();var a=$(this),c=$(this).closest("ul"),e=$(".write-area"),f=$(".preview-area"),h=$("textarea");$.ajax({url:"?controller=app&action=preview",contentType:"application/json",type:"POST",processData:!1,dataType:"html",data:JSON.stringify({text:h.val()})}).done(function(b){c.find("li").removeClass("form-tab-selected");
+a.parent().addClass("form-tab-selected");f.find(".markdown").html(b);f.css("height",h.css("height"));f.css("width",h.css("width"));e.hide();f.show()})},MarkdownWriter:function(b){b.preventDefault();$(this).closest("ul").find("li").removeClass("form-tab-selected");$(this).parent().addClass("form-tab-selected");$(".write-area").show();$(".preview-area").hide()},CheckSession:function(){$(".form-login").length||$.ajax({cache:!1,url:$("body").data("status-url"),statusCode:{401:function(){window.location=
$("body").data("login-url")}}})},Init:function(){$(".chosen-select").chosen({width:"200px",no_results_text:$(".chosen-select").data("notfound"),disable_search_threshold:10});$("#board-selector").chosen({width:180,no_results_text:$("#board-selector").data("notfound")});$("#board-selector").change(function(){window.location=$(this).attr("data-board-url").replace(/PROJECT_ID/g,$(this).val())});window.setInterval(Kanboard.CheckSession,6E4);Mousetrap.bindGlobal("mod+enter",function(){$("form").submit()});
Mousetrap.bind("b",function(b){b.preventDefault();$("#board-selector").trigger("chosen:open")});Mousetrap.bind("f",function(b){b.preventDefault();(b=document.getElementById("form-search"))&&b.focus()});Mousetrap.bind("v b",function(b){b=$(".view-board");b.length&&(window.location=b.attr("href"))});Mousetrap.bind("v c",function(b){b=$(".view-calendar");b.length&&(window.location=b.attr("href"))});Mousetrap.bind("v l",function(b){b=$(".view-listing");b.length&&(window.location=b.attr("href"))});$(document).on("focus",
"#form-search",function(){$("#form-search")[0].setSelectionRange&&$("#form-search")[0].setSelectionRange($("#form-search").val().length,$("#form-search").val().length)});$(document).on("click",".filter-helper",function(b){b.preventDefault();$("#form-search").val($(this).data("filter"));$("form.search").submit()});$.datepicker.setDefaults($.datepicker.regional[$("body").data("js-lang")]);$(".alert-fade-out").delay(4E3).fadeOut(800,function(){$(this).remove()});Kanboard.InitAfterAjax()},InitAfterAjax:function(){$(document).on("click",
".popover",Kanboard.Popover);$("[autofocus]").each(function(b,a){$(this).focus()});$(".form-date").datepicker({showOtherMonths:!0,selectOtherMonths:!0,dateFormat:"yy-mm-dd",constrainInput:!1});$(".form-datetime").datetimepicker({controlType:"select",oneLine:!0,dateFormat:"yy-mm-dd",constrainInput:!1});$("#markdown-preview").click(Kanboard.MarkdownPreview);$("#markdown-write").click(Kanboard.MarkdownWriter);$(".auto-select").focus(function(){$(this).select()});$(".dropit-submenu").hide();$(".dropdown").not(".dropit").dropit({triggerParentEl:"span"});
$(".task-autocomplete").length&&(""==$(".opposite_task_id").val()&&$(".task-autocomplete").parent().find("input[type=submit]").attr("disabled","disabled"),$(".task-autocomplete").autocomplete({source:$(".task-autocomplete").data("search-url"),minLength:1,select:function(b,a){var c=$(".task-autocomplete").data("dst-field");$("input[name="+c+"]").val(a.item.id);$(".task-autocomplete").parent().find("input[type=submit]").removeAttr("disabled")}}));$(".tooltip").tooltip({content:function(){return'<div class="markdown">'+
$(this).attr("title")+"</div>"},position:{my:"left-20 top",at:"center bottom+9",using:function(b,a){$(this).css(b);var c=a.target.left+a.target.width/2-a.element.left-20;$("<div>").addClass("tooltip-arrow").addClass(a.vertical).addClass(1>c?"align-left":"align-right").appendTo(this)}}});Kanboard.Exists("screenshot-zone")&&Kanboard.Screenshot.Init()}}}();
-(function(){function b(a){a.preventDefault();a.stopPropagation();Kanboard.Popover(a,Kanboard.InitAfterAjax)}function a(){Mousetrap.bind("n",function(){Kanboard.OpenPopover($("#board").data("task-creation-url"),Kanboard.InitAfterAjax)});Mousetrap.bind("s",function(){window.location=$(".board-display-mode").attr("href")});Mousetrap.bind("c",function(){g()})}function c(){$(".column").sortable({delay:300,distance:5,connectWith:".column",placeholder:"draggable-placeholder",items:".draggable-item",stop:function(a,
-c){d(c.item.attr("data-task-id"),c.item.parent().attr("data-column-id"),c.item.index()+1,c.item.parent().attr("data-swimlane-id"))}});$("#board").on("click",".task-board-popover",b);$("#board").on("click",".task-board",function(){window.location=$(this).data("task-url")});$(".task-board-tooltip").tooltip({track:!1,position:{my:"left-20 top",at:"center bottom+9",using:function(a,c){$(this).css(a);var b=c.target.left+c.target.width/2-c.element.left-20;$("<div>").addClass("tooltip-arrow").addClass(c.vertical).addClass(1>
-b?"align-left":"align-right").appendTo(this)}},content:function(a){if(a=$(this).attr("data-href")){var c=this;$.get(a,function l(a){$(".ui-tooltip-content:visible").html(a);a=$(".ui-tooltip:visible");a.css({top:"",left:""});a.children(".tooltip-arrow").remove();var b=$(c).tooltip("option","position");b.of=$(c);a.position(b);$("#tooltip-subtasks a").not(".popover").click(function(a){a.preventDefault();a.stopPropagation();$(this).hasClass("popover-subtask-restriction")?(Kanboard.OpenPopover($(this).attr("href")),
-$(c).tooltip("close")):$.get($(this).attr("href"),l)})});return'<i class="fa fa-refresh fa-spin fa-2x"></i>'}}}).on("mouseenter",function(){var a=this;$(this).tooltip("open");$(".ui-tooltip").on("mouseleave",function(){$(a).tooltip("close")})}).on("mouseleave focusout",function(a){a.stopImmediatePropagation();var c=this;setTimeout(function(){$(".ui-tooltip:hover").length||$(c).tooltip("close")},100)});var a=parseInt($("#board").attr("data-check-interval"));0<a&&(k=window.setInterval(e,1E3*a))}function d(a,
-b,d,e){clearInterval(k);$.ajax({cache:!1,url:$("#board").attr("data-save-url"),contentType:"application/json",type:"POST",processData:!1,data:JSON.stringify({task_id:a,column_id:b,swimlane_id:e,position:d}),success:function(a){$("#board-container").remove();$("#main").append(a);Kanboard.InitAfterAjax();c();f()}})}function e(){Kanboard.IsVisible()&&$.ajax({cache:!1,url:$("#board").attr("data-check-url"),statusCode:{200:function(a){$("#board-container").remove();$("#main").append(a);Kanboard.InitAfterAjax();
-clearInterval(k);c();f()}}})}function h(){jQuery(document).on("click",".filter-toggle-scrolling",function(a){a.preventDefault();g()});f()}function g(){var a=Kanboard.GetStorageItem("horizontal_scroll")||1;Kanboard.SetStorageItem("horizontal_scroll",0==a?1:0);f()}function f(){0==Kanboard.GetStorageItem("horizontal_scroll")?($(".filter-wide").show(),$(".filter-compact").hide(),$("#board-container").addClass("board-container-compact"),$("#board th").addClass("board-column-compact")):($(".filter-wide").hide(),
-$(".filter-compact").show(),$("#board-container").removeClass("board-container-compact"),$("#board th").removeClass("board-column-compact"))}var k=null;jQuery(document).ready(function(){Kanboard.Exists("board")&&(c(),h(),a())})})();
+(function(){function b(a){a.preventDefault();a.stopPropagation();Kanboard.Popover(a,Kanboard.InitAfterAjax)}function a(){Mousetrap.bind("n",function(){Kanboard.OpenPopover($("#board").data("task-creation-url"),Kanboard.InitAfterAjax)});Mousetrap.bind("s",function(){$.ajax({cache:!1,url:$('.filter-display-mode:not([style="display: none;"]) a').attr("href"),success:function(a){$("#board-container").remove();$("#main").append(a);Kanboard.InitAfterAjax();clearInterval(k);c();d();$(".filter-display-mode").toggle()}})});
+Mousetrap.bind("c",function(){g()})}function c(){$(".column").sortable({delay:300,distance:5,connectWith:".column",placeholder:"draggable-placeholder",items:".draggable-item",stop:function(a,c){e(c.item.attr("data-task-id"),c.item.parent().attr("data-column-id"),c.item.index()+1,c.item.parent().attr("data-swimlane-id"))}});$("#board").on("click",".task-board-popover",b);$("#board").on("click",".task-board",function(){window.location=$(this).data("task-url")});$(".task-board-tooltip").tooltip({track:!1,
+position:{my:"left-20 top",at:"center bottom+9",using:function(a,c){$(this).css(a);var b=c.target.left+c.target.width/2-c.element.left-20;$("<div>").addClass("tooltip-arrow").addClass(c.vertical).addClass(1>b?"align-left":"align-right").appendTo(this)}},content:function(a){if(a=$(this).attr("data-href")){var c=this;$.get(a,function l(a){$(".ui-tooltip-content:visible").html(a);a=$(".ui-tooltip:visible");a.css({top:"",left:""});a.children(".tooltip-arrow").remove();var b=$(c).tooltip("option","position");
+b.of=$(c);a.position(b);$("#tooltip-subtasks a").not(".popover").click(function(a){a.preventDefault();a.stopPropagation();$(this).hasClass("popover-subtask-restriction")?(Kanboard.OpenPopover($(this).attr("href")),$(c).tooltip("close")):$.get($(this).attr("href"),l)})});return'<i class="fa fa-refresh fa-spin fa-2x"></i>'}}}).on("mouseenter",function(){var a=this;$(this).tooltip("open");$(".ui-tooltip").on("mouseleave",function(){$(a).tooltip("close")})}).on("mouseleave focusout",function(a){a.stopImmediatePropagation();
+var c=this;setTimeout(function(){$(".ui-tooltip:hover").length||$(c).tooltip("close")},100)});var a=parseInt($("#board").attr("data-check-interval"));0<a&&(k=window.setInterval(f,1E3*a))}function e(a,b,e,f){clearInterval(k);$.ajax({cache:!1,url:$("#board").attr("data-save-url"),contentType:"application/json",type:"POST",processData:!1,data:JSON.stringify({task_id:a,column_id:b,swimlane_id:f,position:e}),success:function(a){$("#board-container").remove();$("#main").append(a);Kanboard.InitAfterAjax();
+c();d()}})}function f(){Kanboard.IsVisible()&&$.ajax({cache:!1,url:$("#board").attr("data-check-url"),statusCode:{200:function(a){$("#board-container").remove();$("#main").append(a);Kanboard.InitAfterAjax();clearInterval(k);c();d()}}})}function h(){jQuery(document).on("click",".filter-toggle-scrolling",function(a){a.preventDefault();g()});d()}function g(){var a=Kanboard.GetStorageItem("horizontal_scroll")||1;Kanboard.SetStorageItem("horizontal_scroll",0==a?1:0);d()}function d(){0==Kanboard.GetStorageItem("horizontal_scroll")?
+($(".filter-wide").show(),$(".filter-compact").hide(),$("#board-container").addClass("board-container-compact"),$("#board th").addClass("board-column-compact")):($(".filter-wide").hide(),$(".filter-compact").show(),$("#board-container").removeClass("board-container-compact"),$("#board th").removeClass("board-column-compact"))}var k=null;jQuery(document).ready(function(){Kanboard.Exists("board")&&(c(),h(),a())})})();
(function(){jQuery(document).ready(function(){if(Kanboard.Exists("calendar")){var b=$("#calendar");b.fullCalendar({lang:$("body").data("js-lang"),editable:!0,eventLimit:!0,defaultView:"month",header:{left:"prev,next today",center:"title",right:"month,agendaWeek,agendaDay"},eventDrop:function(a){$.ajax({cache:!1,url:b.data("save-url"),contentType:"application/json",type:"POST",processData:!1,data:JSON.stringify({task_id:a.id,date_due:a.start.format()})})},viewRender:function(){var a=b.data("check-url"),
-c={start:b.fullCalendar("getView").start.format(),end:b.fullCalendar("getView").end.format()},d;for(d in c)a+="&"+d+"="+c[d];$.getJSON(a,function(a){b.fullCalendar("removeEvents");b.fullCalendar("addEventSource",a);b.fullCalendar("rerenderEvents")})}})}})})();
-(function(){function b(a){return 86400<=a?Math.round(a/86400)+"d":3600<=a?Math.round(a/3600)+"h":60<=a?Math.round(a/60)+"m":a+"s"}jQuery(document).ready(function(){if(Kanboard.Exists("analytic-task-repartition")){for(var a=$("#chart").data("metrics"),c=[],d=0;d<a.length;d++)c.push([a[d].column_title,a[d].nb_tasks]);c3.generate({data:{columns:c,type:"donut"}})}else if(Kanboard.Exists("analytic-user-repartition")){a=$("#chart").data("metrics");c=[];for(d=0;d<a.length;d++)c.push([a[d].user,a[d].nb_tasks]);
-c3.generate({data:{columns:c,type:"donut"}})}else if(Kanboard.Exists("analytic-cfd")){for(var a=$("#chart").data("metrics"),c=[],d=[],e=[],h=d3.time.format("%Y-%m-%d"),g=d3.time.format($("#chart").data("date-format")),f=0;f<a.length;f++)for(var k=0;k<a[f].length;k++)0==f?(c.push([a[f][k]]),0<k&&d.push(a[f][k])):(c[k].push(a[f][k]),0==k&&e.push(g(h.parse(a[f][k]))));c3.generate({data:{columns:c,type:"area-spline",groups:[d]},axis:{x:{type:"category",categories:e}}})}else if(Kanboard.Exists("analytic-burndown")){a=
-$("#chart").data("metrics");c=[[$("#chart").data("label-total")]];d=[];e=d3.time.format("%Y-%m-%d");h=d3.time.format($("#chart").data("date-format"));for(g=0;g<a.length;g++)for(f=0;f<a[g].length;f++)0==g?c.push([a[g][f]]):(c[f+1].push(a[g][f]),0<f&&(void 0==c[0][g]&&c[0].push(0),c[0][g]+=a[g][f]),0==f&&d.push(h(e.parse(a[g][f]))));c3.generate({data:{columns:c},axis:{x:{type:"category",categories:d}}})}else if(Kanboard.Exists("budget-chart")){a=[];c=$("#chart").data("metrics");f=$("#chart").data("labels");
-d=d3.time.format("%Y-%m-%d");e=d3.time.format($("#chart").data("date-format"));h=[[f["in"]],[f.left],[f.out]];g={};g[f["in"]]="#5858FA";g[f.left]="#04B404";g[f.out]="#DF3A01";for(f=0;f<c.length;f++)a.push(e(d.parse(c[f].date))),h[0].push(c[f]["in"]),h[1].push(c[f].left),h[2].push(c[f].out);c3.generate({data:{columns:h,colors:g,type:"bar"},bar:{width:{ratio:.25}},grid:{x:{show:!0},y:{show:!0}},axis:{x:{type:"category",categories:a}}})}else if(Kanboard.Exists("analytic-avg-time-column")){a=$("#chart").data("metrics");
-c=[$("#chart").data("label")];d=[];for(e in a)c.push(a[e].average),d.push(a[e].title);c3.generate({data:{columns:[c],type:"bar"},bar:{width:{ratio:.5}},axis:{x:{type:"category",categories:d},y:{tick:{format:b}}},legend:{show:!1}})}else if(Kanboard.Exists("analytic-task-time-column")){a=$("#chart").data("metrics");c=[$("#chart").data("label")];d=[];for(e=0;e<a.length;e++)c.push(a[e].time_spent),d.push(a[e].title);c3.generate({data:{columns:[c],type:"bar"},bar:{width:{ratio:.5}},axis:{x:{type:"category",
-categories:d},y:{tick:{format:b}}},legend:{show:!1}})}else if(Kanboard.Exists("analytic-lead-cycle-time")){a=$("#chart").data("metrics");c=[$("#chart").data("label-cycle")];d=[$("#chart").data("label-lead")];e=[];h={};h[$("#chart").data("label-cycle")]="area";h[$("#chart").data("label-lead")]="area-spline";g={};g[$("#chart").data("label-lead")]="#afb42b";g[$("#chart").data("label-cycle")]="#4e342e";for(f=0;f<a.length;f++)c.push(parseInt(a[f].avg_cycle_time)),d.push(parseInt(a[f].avg_lead_time)),e.push(a[f].day);
-c3.generate({data:{columns:[d,c],types:h,colors:g},axis:{x:{type:"category",categories:e},y:{tick:{format:b}}}})}})})();
+c={start:b.fullCalendar("getView").start.format(),end:b.fullCalendar("getView").end.format()},e;for(e in c)a+="&"+e+"="+c[e];$.getJSON(a,function(a){b.fullCalendar("removeEvents");b.fullCalendar("addEventSource",a);b.fullCalendar("rerenderEvents")})}})}})})();
+(function(){function b(a){return 86400<=a?Math.round(a/86400)+"d":3600<=a?Math.round(a/3600)+"h":60<=a?Math.round(a/60)+"m":a+"s"}jQuery(document).ready(function(){if(Kanboard.Exists("analytic-task-repartition")){for(var a=$("#chart").data("metrics"),c=[],e=0;e<a.length;e++)c.push([a[e].column_title,a[e].nb_tasks]);c3.generate({data:{columns:c,type:"donut"}})}else if(Kanboard.Exists("analytic-user-repartition")){a=$("#chart").data("metrics");c=[];for(e=0;e<a.length;e++)c.push([a[e].user,a[e].nb_tasks]);
+c3.generate({data:{columns:c,type:"donut"}})}else if(Kanboard.Exists("analytic-cfd")){for(var a=$("#chart").data("metrics"),c=[],e=[],f=[],h=d3.time.format("%Y-%m-%d"),g=d3.time.format($("#chart").data("date-format")),d=0;d<a.length;d++)for(var k=0;k<a[d].length;k++)0==d?(c.push([a[d][k]]),0<k&&e.push(a[d][k])):(c[k].push(a[d][k]),0==k&&f.push(g(h.parse(a[d][k]))));c3.generate({data:{columns:c,type:"area-spline",groups:[e]},axis:{x:{type:"category",categories:f}}})}else if(Kanboard.Exists("analytic-burndown")){a=
+$("#chart").data("metrics");c=[[$("#chart").data("label-total")]];e=[];f=d3.time.format("%Y-%m-%d");h=d3.time.format($("#chart").data("date-format"));for(g=0;g<a.length;g++)for(d=0;d<a[g].length;d++)0==g?c.push([a[g][d]]):(c[d+1].push(a[g][d]),0<d&&(void 0==c[0][g]&&c[0].push(0),c[0][g]+=a[g][d]),0==d&&e.push(h(f.parse(a[g][d]))));c3.generate({data:{columns:c},axis:{x:{type:"category",categories:e}}})}else if(Kanboard.Exists("budget-chart")){a=[];c=$("#chart").data("metrics");d=$("#chart").data("labels");
+e=d3.time.format("%Y-%m-%d");f=d3.time.format($("#chart").data("date-format"));h=[[d["in"]],[d.left],[d.out]];g={};g[d["in"]]="#5858FA";g[d.left]="#04B404";g[d.out]="#DF3A01";for(d=0;d<c.length;d++)a.push(f(e.parse(c[d].date))),h[0].push(c[d]["in"]),h[1].push(c[d].left),h[2].push(c[d].out);c3.generate({data:{columns:h,colors:g,type:"bar"},bar:{width:{ratio:.25}},grid:{x:{show:!0},y:{show:!0}},axis:{x:{type:"category",categories:a}}})}else if(Kanboard.Exists("analytic-avg-time-column")){a=$("#chart").data("metrics");
+c=[$("#chart").data("label")];e=[];for(f in a)c.push(a[f].average),e.push(a[f].title);c3.generate({data:{columns:[c],type:"bar"},bar:{width:{ratio:.5}},axis:{x:{type:"category",categories:e},y:{tick:{format:b}}},legend:{show:!1}})}else if(Kanboard.Exists("analytic-task-time-column")){a=$("#chart").data("metrics");c=[$("#chart").data("label")];e=[];for(f=0;f<a.length;f++)c.push(a[f].time_spent),e.push(a[f].title);c3.generate({data:{columns:[c],type:"bar"},bar:{width:{ratio:.5}},axis:{x:{type:"category",
+categories:e},y:{tick:{format:b}}},legend:{show:!1}})}else if(Kanboard.Exists("analytic-lead-cycle-time")){a=$("#chart").data("metrics");c=[$("#chart").data("label-cycle")];e=[$("#chart").data("label-lead")];f=[];h={};h[$("#chart").data("label-cycle")]="area";h[$("#chart").data("label-lead")]="area-spline";g={};g[$("#chart").data("label-lead")]="#afb42b";g[$("#chart").data("label-cycle")]="#4e342e";for(d=0;d<a.length;d++)c.push(parseInt(a[d].avg_cycle_time)),e.push(parseInt(a[d].avg_lead_time)),f.push(a[d].day);
+c3.generate({data:{columns:[e,c],types:h,colors:g},axis:{x:{type:"category",categories:f},y:{tick:{format:b}}}})}})})();
(function(){function b(a){$(".swimlane-row-"+a).css("display","none");$(".show-icon-swimlane-"+a).css("display","inline");$(".hide-icon-swimlane-"+a).css("display","none")}function a(){var a="hidden_swimlanes_"+$("#board").data("project-id");return JSON.parse(Kanboard.GetStorageItem(a))||[]}jQuery(document).ajaxComplete(function(){a().map(function(a){b(a)})});jQuery(document).ready(function(){a().map(function(a){b(a)})});jQuery(document).on("click",".board-swimlane-toggle",function(c){c.preventDefault();
-c=$(this).data("swimlane-id");if(-1<a().indexOf(c)){var d="hidden_swimlanes_"+$("#board").data("project-id"),e=JSON.parse(Kanboard.GetStorageItem(d))||[],h=e.indexOf(c);-1<h&&e.splice(h,1);Kanboard.SetStorageItem(d,JSON.stringify(e));$(".swimlane-row-"+c).css("display","table-row");$(".show-icon-swimlane-"+c).css("display","none");$(".hide-icon-swimlane-"+c).css("display","inline")}else d="hidden_swimlanes_"+$("#board").data("project-id"),e=JSON.parse(Kanboard.GetStorageItem(d))||[],e.push(c),Kanboard.SetStorageItem(d,
-JSON.stringify(e)),b(c)})})();
-Kanboard.Screenshot=function(){function b(){window.Clipboard||(e=document.createElement("div"),e.setAttribute("contenteditable",""),e.style.opacity=0,document.body.appendChild(e),e.focus(),document.addEventListener("click",function(){e.focus()}));window.addEventListener("paste",a)}function a(a){if(a.clipboardData&&a.clipboardData.items){if(a=a.clipboardData.items)for(var b=0;b<a.length;b++)if(-1!==a[b].type.indexOf("image")){var f=a[b].getAsFile(),e=new FileReader;e.onload=function(a){d(a.target.result)};
-e.readAsDataURL(f)}}else setTimeout(c,100)}function c(){var a=e.childNodes[0];e.innerHTML="";a&&"IMG"===a.tagName&&d(a.src)}function d(a){var b=new Image;b.src=a;b.onload=function(){var b=a.split("base64,")[1];$("input[name=screenshot]").val(b)};document.getElementById("screenshot-inner").style.display="none";document.getElementById("screenshot-zone").className="screenshot-pasted";document.getElementById("screenshot-zone").appendChild(b)}var e=null;jQuery(document).ready(function(){Kanboard.Exists("screenshot-zone")&&
+c=$(this).data("swimlane-id");if(-1<a().indexOf(c)){var e="hidden_swimlanes_"+$("#board").data("project-id"),f=JSON.parse(Kanboard.GetStorageItem(e))||[],h=f.indexOf(c);-1<h&&f.splice(h,1);Kanboard.SetStorageItem(e,JSON.stringify(f));$(".swimlane-row-"+c).css("display","table-row");$(".show-icon-swimlane-"+c).css("display","none");$(".hide-icon-swimlane-"+c).css("display","inline")}else e="hidden_swimlanes_"+$("#board").data("project-id"),f=JSON.parse(Kanboard.GetStorageItem(e))||[],f.push(c),Kanboard.SetStorageItem(e,
+JSON.stringify(f)),b(c)})})();
+Kanboard.Screenshot=function(){function b(){window.Clipboard||(f=document.createElement("div"),f.setAttribute("contenteditable",""),f.style.opacity=0,document.body.appendChild(f),f.focus(),document.addEventListener("click",function(){f.focus()}));window.addEventListener("paste",a)}function a(a){if(a.clipboardData&&a.clipboardData.items){if(a=a.clipboardData.items)for(var b=0;b<a.length;b++)if(-1!==a[b].type.indexOf("image")){var d=a[b].getAsFile(),f=new FileReader;f.onload=function(a){e(a.target.result)};
+f.readAsDataURL(d)}}else setTimeout(c,100)}function c(){var a=f.childNodes[0];f.innerHTML="";a&&"IMG"===a.tagName&&e(a.src)}function e(a){var b=new Image;b.src=a;b.onload=function(){var b=a.split("base64,")[1];$("input[name=screenshot]").val(b)};document.getElementById("screenshot-inner").style.display="none";document.getElementById("screenshot-zone").className="screenshot-pasted";document.getElementById("screenshot-zone").appendChild(b)}var f=null;jQuery(document).ready(function(){Kanboard.Exists("screenshot-zone")&&
b()});return{Init:b}}();
(function(t){"function"==typeof define&&define.amd?define(["jquery","moment"],t):t(jQuery,moment)})(function(t,e){(e.defineLocale||e.lang).call(e,"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}}),t.fullCalendar.datepickerLang("da","da",{closeText:"Luk",prevText:"&#x3C;Forrige",nextText:"Næste&#x3E;",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:""}),t.fullCalendar.lang("da",{defaultButtonText:{month:"Måned",week:"Uge",day:"Dag",list:"Agenda"},allDayText:"Hele dagen",eventLimitText:"flere"})});(function(t){"function"==typeof define&&define.amd?define(["jquery","moment"],t):t(jQuery,moment)})(function(t,e){function n(t,e,n){var i={m:["eine Minute","einer Minute"],h:["eine Stunde","einer Stunde"],d:["ein Tag","einem Tag"],dd:[t+" Tage",t+" Tagen"],M:["ein Monat","einem Monat"],MM:[t+" Monate",t+" Monaten"],y:["ein Jahr","einem Jahr"],yy:[t+" Jahre",t+" Jahren"]};return e?i[n][0]:i[n][1]}(e.defineLocale||e.lang).call(e,"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:n,mm:"%d Minuten",h:n,hh:"%d Stunden",d:n,dd:n,M:n,MM:n,y:n,yy:n},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),t.fullCalendar.datepickerLang("de","de",{closeText:"Schließen",prevText:"&#x3C;Zurück",nextText:"Vor&#x3E;",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:""}),t.fullCalendar.lang("de",{defaultButtonText:{month:"Monat",week:"Woche",day:"Tag",list:"Terminübersicht"},allDayText:"Ganztägig",eventLimitText:function(t){return"+ weitere "+t}})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){var n="ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.".split("_"),i="ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic".split("_");(t.defineLocale||t.lang).call(t,"es",{months:"enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre".split("_"),monthsShort:function(e,t){return/-MMM-/.test(t)?i[e.month()]:n[e.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}}),e.fullCalendar.datepickerLang("es","es",{closeText:"Cerrar",prevText:"&#x3C;Ant",nextText:"Sig&#x3E;",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:""}),e.fullCalendar.lang("es",{defaultButtonText:{month:"Mes",week:"Semana",day:"Día",list:"Agenda"},allDayHtml:"Todo<br/>el día",eventLimitText:"más"})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){function n(e,t,n,r){var s="";switch(n){case"s":return r?"muutaman sekunnin":"muutama sekunti";case"m":return r?"minuutin":"minuutti";case"mm":s=r?"minuutin":"minuuttia";break;case"h":return r?"tunnin":"tunti";case"hh":s=r?"tunnin":"tuntia";break;case"d":return r?"päivän":"päivä";case"dd":s=r?"päivän":"päivää";break;case"M":return r?"kuukauden":"kuukausi";case"MM":s=r?"kuukauden":"kuukautta";break;case"y":return r?"vuoden":"vuosi";case"yy":s=r?"vuoden":"vuotta"}return s=i(e,r)+" "+s}function i(e,t){return 10>e?t?s[e]:r[e]:e}var r="nolla yksi kaksi kolme neljä viisi kuusi seitsemän kahdeksan yhdeksän".split(" "),s=["nolla","yhden","kahden","kolmen","neljän","viiden","kuuden",r[7],r[8],r[9]];(t.defineLocale||t.lang).call(t,"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:n,m:n,mm:n,h:n,hh:n,d:n,dd:n,M:n,MM:n,y:n,yy:n},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("fi","fi",{closeText:"Sulje",prevText:"&#xAB;Edellinen",nextText:"Seuraava&#xBB;",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:""}),e.fullCalendar.lang("fi",{defaultButtonText:{month:"Kuukausi",week:"Viikko",day:"Päivä",list:"Tapahtumat"},allDayText:"Koko päivä",eventLimitText:"lisää"})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){(t.defineLocale||t.lang).call(t,"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(e){return e+(1===e?"er":"")},week:{dow:1,doy:4}}),e.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:""}),e.fullCalendar.lang("fr",{defaultButtonText:{month:"Mois",week:"Semaine",day:"Jour",list:"Mon planning"},allDayHtml:"Toute la<br/>journée",eventLimitText:"en plus"})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){function n(e,t,n,r){var i=e;switch(n){case"s":return r||t?"néhány másodperc":"néhány másodperce";case"m":return"egy"+(r||t?" perc":" perce");case"mm":return i+(r||t?" perc":" perce");case"h":return"egy"+(r||t?" óra":" órája");case"hh":return i+(r||t?" óra":" órája");case"d":return"egy"+(r||t?" nap":" napja");case"dd":return i+(r||t?" nap":" napja");case"M":return"egy"+(r||t?" hónap":" hónapja");case"MM":return i+(r||t?" hónap":" hónapja");case"y":return"egy"+(r||t?" év":" éve");case"yy":return i+(r||t?" év":" éve")}return""}function r(e){return(e?"":"[múlt] ")+"["+i[this.day()]+"] LT[-kor]"}var i="vasárnap hétfőn kedden szerdán csütörtökön pénteken szombaton".split(" ");(t.defineLocale||t.lang).call(t,"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(e){return"u"===e.charAt(1).toLowerCase()},meridiem:function(e,t,n){return 12>e?n===!0?"de":"DE":n===!0?"du":"DU"},calendar:{sameDay:"[ma] LT[-kor]",nextDay:"[holnap] LT[-kor]",nextWeek:function(){return r.call(this,!0)},lastDay:"[tegnap] LT[-kor]",lastWeek:function(){return r.call(this,!1)},sameElse:"L"},relativeTime:{future:"%s múlva",past:"%s",s:n,m:n,mm:n,h:n,hh:n,d:n,dd:n,M:n,MM:n,y:n,yy:n},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}}),e.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:""}),e.fullCalendar.lang("hu",{defaultButtonText:{month:"Hónap",week:"Hét",day:"Nap",list:"Napló"},allDayText:"Egész nap",eventLimitText:"további"})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){(t.defineLocale||t.lang).call(t,"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(e){return(/^[0-9].+$/.test(e)?"tra":"in")+" "+e},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}}),e.fullCalendar.datepickerLang("it","it",{closeText:"Chiudi",prevText:"&#x3C;Prec",nextText:"Succ&#x3E;",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:""}),e.fullCalendar.lang("it",{defaultButtonText:{month:"Mese",week:"Settimana",day:"Giorno",list:"Agenda"},allDayHtml:"Tutto il<br/>giorno",eventLimitText:function(e){return"+altri "+e}})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){(t.defineLocale||t.lang).call(t,"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(e){return"午後"===e},meridiem:function(e){return 12>e?"午前":"午後"},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年"}}),e.fullCalendar.datepickerLang("ja","ja",{closeText:"閉じる",prevText:"&#x3C;前",nextText:"次&#x3E;",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:"年"}),e.fullCalendar.lang("ja",{defaultButtonText:{month:"月",week:"週",day:"日",list:"予定リスト"},allDayText:"終日",eventLimitText:function(e){return"他 "+e+" 件"}})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){var n="jan._feb._mrt._apr._mei_jun._jul._aug._sep._okt._nov._dec.".split("_"),r="jan_feb_mrt_apr_mei_jun_jul_aug_sep_okt_nov_dec".split("_");(t.defineLocale||t.lang).call(t,"nl",{months:"januari_februari_maart_april_mei_juni_juli_augustus_september_oktober_november_december".split("_"),monthsShort:function(e,t){return/-MMM-/.test(t)?r[e.month()]:n[e.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(e){return e+(1===e||8===e||e>=20?"ste":"de")},week:{dow:1,doy:4}}),e.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:""}),e.fullCalendar.lang("nl",{defaultButtonText:{month:"Maand",week:"Week",day:"Dag",list:"Agenda"},allDayText:"Hele dag",eventLimitText:"extra"})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){function n(e){return 5>e%10&&e%10>1&&1!==~~(e/10)%10}function i(e,t,i){var r=e+" ";switch(i){case"m":return t?"minuta":"minutę";case"mm":return r+(n(e)?"minuty":"minut");case"h":return t?"godzina":"godzinę";case"hh":return r+(n(e)?"godziny":"godzin");case"MM":return r+(n(e)?"miesiące":"miesięcy");case"yy":return r+(n(e)?"lata":"lat")}}var r="styczeń_luty_marzec_kwiecień_maj_czerwiec_lipiec_sierpień_wrzesień_październik_listopad_grudzień".split("_"),a="stycznia_lutego_marca_kwietnia_maja_czerwca_lipca_sierpnia_września_października_listopada_grudnia".split("_");(t.defineLocale||t.lang).call(t,"pl",{months:function(e,t){return/D MMMM/.test(t)?a[e.month()]:r[e.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:i,mm:i,h:i,hh:i,d:"1 dzień",dd:"%d dni",M:"miesiąc",MM:i,y:"rok",yy:i},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("pl","pl",{closeText:"Zamknij",prevText:"&#x3C;Poprzedni",nextText:"Następny&#x3E;",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:""}),e.fullCalendar.lang("pl",{defaultButtonText:{month:"Miesiąc",week:"Tydzień",day:"Dzień",list:"Plan dnia"},allDayText:"Cały dzień",eventLimitText:"więcej"})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){(t.defineLocale||t.lang).call(t,"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º"}),e.fullCalendar.datepickerLang("pt-br","pt-BR",{closeText:"Fechar",prevText:"&#x3C;Anterior",nextText:"Próximo&#x3E;",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:""}),e.fullCalendar.lang("pt-br",{defaultButtonText:{month:"Mês",week:"Semana",day:"Dia",list:"Compromissos"},allDayText:"dia inteiro",eventLimitText:function(e){return"mais +"+e}})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){function n(e,t){var n=e.split("_");return 1===t%10&&11!==t%100?n[0]:t%10>=2&&4>=t%10&&(10>t%100||t%100>=20)?n[1]:n[2]}function i(e,t,i){var a={mm:t?"минута_минуты_минут":"минуту_минуты_минут",hh:"час_часа_часов",dd:"день_дня_дней",MM:"месяц_месяца_месяцев",yy:"год_года_лет"};return"m"===i?t?"минута":"минуту":e+" "+n(a[i],+e)}function a(e,t){var n={nominative:"январь_февраль_март_апрель_май_июнь_июль_август_сентябрь_октябрь_ноябрь_декабрь".split("_"),accusative:"января_февраля_марта_апреля_мая_июня_июля_августа_сентября_октября_ноября_декабря".split("_")},i=/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/.test(t)?"accusative":"nominative";return n[i][e.month()]}function r(e,t){var n={nominative:"янв_фев_март_апр_май_июнь_июль_авг_сен_окт_ноя_дек".split("_"),accusative:"янв_фев_мар_апр_мая_июня_июля_авг_сен_окт_ноя_дек".split("_")},i=/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/.test(t)?"accusative":"nominative";return n[i][e.month()]}function s(e,t){var n={nominative:"воскресенье_понедельник_вторник_среда_четверг_пятница_суббота".split("_"),accusative:"воскресенье_понедельник_вторник_среду_четверг_пятницу_субботу".split("_")},i=/\[ ?[Вв] ?(?:прошлую|следующую|эту)? ?\] ?dddd/.test(t)?"accusative":"nominative";return n[i][e.day()]}(t.defineLocale||t.lang).call(t,"ru",{months:a,monthsShort:r,weekdays:s,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(e){if(e.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:i,mm:i,h:"час",hh:i,d:"день",dd:i,M:"месяц",MM:i,y:"год",yy:i},meridiemParse:/ночи|утра|дня|вечера/i,isPM:function(e){return/^(дня|вечера)$/.test(e)},meridiem:function(e){return 4>e?"ночи":12>e?"утра":17>e?"дня":"вечера"},ordinalParse:/\d{1,2}-(й|го|я)/,ordinal:function(e,t){switch(t){case"M":case"d":case"DDD":return e+"-й";case"D":return e+"-го";case"w":case"W":return e+"-я";default:return e}},week:{dow:1,doy:7}}),e.fullCalendar.datepickerLang("ru","ru",{closeText:"Закрыть",prevText:"&#x3C;Пред",nextText:"След&#x3E;",currentText:"Сегодня",monthNames:["Январь","Февраль","Март","Апрель","Май","Июнь","Июль","Август","Сентябрь","Октябрь","Ноябрь","Декабрь"],monthNamesShort:["Янв","Фев","Мар","Апр","Май","Июн","Июл","Авг","Сен","Окт","Ноя","Дек"],dayNames:["воскресенье","понедельник","вторник","среда","четверг","пятница","суббота"],dayNamesShort:["вск","пнд","втр","срд","чтв","птн","сбт"],dayNamesMin:["Вс","Пн","Вт","Ср","Чт","Пт","Сб"],weekHeader:"Нед",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("ru",{defaultButtonText:{month:"Месяц",week:"Неделя",day:"День",list:"Повестка дня"},allDayText:"Весь день",eventLimitText:function(e){return"+ ещё "+e}})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){(t.defineLocale||t.lang).call(t,"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(e){var t=e%10,n=1===~~(e%100/10)?"e":1===t?"a":2===t?"a":3===t?"e":"e";return e+n},week:{dow:1,doy:4}}),e.fullCalendar.datepickerLang("sv","sv",{closeText:"Stäng",prevText:"&#xAB;Förra",nextText:"Nästa&#xBB;",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:""}),e.fullCalendar.lang("sv",{defaultButtonText:{month:"Månad",week:"Vecka",day:"Dag",list:"Program"},allDayText:"Heldag",eventLimitText:"till"})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){var n={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(e,t){return 1===e?t[0]:e>=2&&4>=e?t[1]:t[2]},translate:function(e,t,a){var r=n.words[a];return 1===a.length?t?r[0]:r[1]:e+" "+n.correctGrammaticalCase(e,r)}};(t.defineLocale||t.lang).call(t,"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 e=["[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 e[this.day()]},sameElse:"L"},relativeTime:{future:"za %s",past:"pre %s",s:"nekoliko sekundi",m:n.translate,mm:n.translate,h:n.translate,hh:n.translate,d:"dan",dd:n.translate,M:"mesec",MM:n.translate,y:"godinu",yy:n.translate},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}}),e.fullCalendar.datepickerLang("sr","sr",{closeText:"Затвори",prevText:"&#x3C;",nextText:"&#x3E;",currentText:"Данас",monthNames:["Јануар","Фебруар","Март","Април","Мај","Јун","Јул","Август","Септембар","Октобар","Новембар","Децембар"],monthNamesShort:["Јан","Феб","Мар","Апр","Мај","Јун","Јул","Авг","Сеп","Окт","Нов","Дец"],dayNames:["Недеља","Понедељак","Уторак","Среда","Четвртак","Петак","Субота"],dayNamesShort:["Нед","Пон","Уто","Сре","Чет","Пет","Суб"],dayNamesMin:["Не","По","Ут","Ср","Че","Пе","Су"],weekHeader:"Сед",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("sr",{defaultButtonText:{month:"Месец",week:"Недеља",day:"Дан",list:"Планер"},allDayText:"Цео дан",eventLimitText:function(e){return"+ још "+e}})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){(t.defineLocale||t.lang).call(t,"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(e){return"หลังเที่ยง"===e},meridiem:function(e){return 12>e?"ก่อนเที่ยง":"หลังเที่ยง"},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 ปี"}}),e.fullCalendar.datepickerLang("th","th",{closeText:"ปิด",prevText:"&#xAB;&#xA0;ย้อน",nextText:"ถัดไป&#xA0;&#xBB;",currentText:"วันนี้",monthNames:["มกราคม","กุมภาพันธ์","มีนาคม","เมษายน","พฤษภาคม","มิถุนายน","กรกฎาคม","สิงหาคม","กันยายน","ตุลาคม","พฤศจิกายน","ธันวาคม"],monthNamesShort:["ม.ค.","ก.พ.","มี.ค.","เม.ย.","พ.ค.","มิ.ย.","ก.ค.","ส.ค.","ก.ย.","ต.ค.","พ.ย.","ธ.ค."],dayNames:["อาทิตย์","จันทร์","อังคาร","พุธ","พฤหัสบดี","ศุกร์","เสาร์"],dayNamesShort:["อา.","จ.","อ.","พ.","พฤ.","ศ.","ส."],dayNamesMin:["อา.","จ.","อ.","พ.","พฤ.","ศ.","ส."],weekHeader:"Wk",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.lang("th",{defaultButtonText:{month:"เดือน",week:"สัปดาห์",day:"วัน",list:"แผนงาน"},allDayText:"ตลอดวัน",eventLimitText:"เพิ่มเติม"})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){var n={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ı"};(t.defineLocale||t.lang).call(t,"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(e){if(0===e)return e+"'ıncı";var t=e%10,a=e%100-t,r=e>=100?100:null;return e+(n[t]||n[a]||n[r])},week:{dow:1,doy:7}}),e.fullCalendar.datepickerLang("tr","tr",{closeText:"kapat",prevText:"&#x3C;geri",nextText:"ileri&#x3e",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:""}),e.fullCalendar.lang("tr",{defaultButtonText:{next:"ileri",month:"Ay",week:"Hafta",day:"Gün",list:"Ajanda"},allDayText:"Tüm gün",eventLimitText:"daha fazla"})});(function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):e(jQuery,moment)})(function(e,t){(t.defineLocale||t.lang).call(t,"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(e,t){return 12===e&&(e=0),"凌晨"===t||"早上"===t||"上午"===t?e:"下午"===t||"晚上"===t?e+12:e>=11?e:e+12},meridiem:function(e,t){var n=100*e+t;return 600>n?"凌晨":900>n?"早上":1130>n?"上午":1230>n?"中午":1800>n?"下午":"晚上"},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 e,n;return e=t().startOf("week"),n=this.unix()-e.unix()>=604800?"[下]":"[本]",0===this.minutes()?n+"dddAh点整":n+"dddAh点mm"},lastWeek:function(){var e,n;return e=t().startOf("week"),n=this.unix()<e.unix()?"[上]":"[本]",0===this.minutes()?n+"dddAh点整":n+"dddAh点mm"},sameElse:"LL"},ordinalParse:/\d{1,2}(日|月|周)/,ordinal:function(e,t){switch(t){case"d":case"D":case"DDD":return e+"日";case"M":return e+"月";case"w":case"W":return e+"周";default:return e}},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}}),e.fullCalendar.datepickerLang("zh-cn","zh-CN",{closeText:"关闭",prevText:"&#x3C;上月",nextText:"下月&#x3E;",currentText:"今天",monthNames:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],monthNamesShort:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],dayNames:["星期日","星期一","星期二","星期三","星期四","星期五","星期六"],dayNamesShort:["周日","周一","周二","周三","周四","周五","周六"],dayNamesMin:["日","一","二","三","四","五","六"],weekHeader:"周",dateFormat:"yy-mm-dd",firstDay:1,isRTL:!1,showMonthAfterYear:!0,yearSuffix:"年"}),e.fullCalendar.lang("zh-cn",{defaultButtonText:{month:"月",week:"周",day:"日",list:"日程"},allDayText:"全天",eventLimitText:function(e){return"另外 "+e+" 个"}})}); \ No newline at end of file
diff --git a/assets/js/src/board.js b/assets/js/src/board.js
index d86610ef..45dbd24a 100644
--- a/assets/js/src/board.js
+++ b/assets/js/src/board.js
@@ -20,7 +20,19 @@
});
Mousetrap.bind("s", function() {
- window.location = $(".board-display-mode").attr("href");
+ $.ajax({
+ cache: false,
+ url: $('.filter-display-mode:not([style="display: none;"]) a').attr('href'),
+ success: function(data) {
+ $("#board-container").remove();
+ $("#main").append(data);
+ Kanboard.InitAfterAjax();
+ board_unload_events();
+ board_load_events();
+ compactview_reload();
+ $('.filter-display-mode').toggle();
+ }
+ });
});
Mousetrap.bind("c", function() {
diff --git a/composer.json b/composer.json
index bca866e7..ced788c6 100644
--- a/composer.json
+++ b/composer.json
@@ -11,7 +11,6 @@
"fguillot/picodb" : "1.0.0",
"fguillot/simpleLogger" : "0.0.2",
"fguillot/simple-validator" : "0.0.3",
- "lusitanian/oauth" : "0.3.5",
"nickcernis/html-to-markdown" : "2.2.1",
"pimple/pimple" : "~3.0",
"swiftmailer/swiftmailer" : "@stable",
diff --git a/composer.lock b/composer.lock
index bc5e8e60..28a44f01 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
- "hash": "0048471872ea99cd30c53c0398c7d9f2",
+ "hash": "305f839bfc9c4acb5d9357e1174c42da",
"packages": [
{
"name": "christian-riesen/base32",
@@ -405,68 +405,6 @@
"time": "2015-05-30 19:25:09"
},
{
- "name": "lusitanian/oauth",
- "version": "v0.3.5",
- "source": {
- "type": "git",
- "url": "https://github.com/Lusitanian/PHPoAuthLib.git",
- "reference": "ac5a1cd5a4519143728dce2213936eea302edf8a"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/Lusitanian/PHPoAuthLib/zipball/ac5a1cd5a4519143728dce2213936eea302edf8a",
- "reference": "ac5a1cd5a4519143728dce2213936eea302edf8a",
- "shasum": ""
- },
- "require": {
- "php": ">=5.3.0"
- },
- "require-dev": {
- "phpunit/phpunit": "3.7.*",
- "predis/predis": "0.8.*@dev",
- "symfony/http-foundation": "~2.1"
- },
- "suggest": {
- "ext-openssl": "Allows for usage of secure connections with the stream-based HTTP client.",
- "predis/predis": "Allows using the Redis storage backend.",
- "symfony/http-foundation": "Allows using the Symfony Session storage backend."
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "0.1-dev"
- }
- },
- "autoload": {
- "psr-0": {
- "OAuth": "src",
- "OAuth\\Unit": "tests"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "David Desberg",
- "email": "david@daviddesberg.com"
- },
- {
- "name": "Pieter Hordijk",
- "email": "info@pieterhordijk.com"
- }
- ],
- "description": "PHP 5.3+ oAuth 1/2 Library",
- "keywords": [
- "Authentication",
- "authorization",
- "oauth",
- "security"
- ],
- "time": "2014-09-05 15:19:58"
- },
- {
"name": "nickcernis/html-to-markdown",
"version": "2.2.1",
"source": {
@@ -651,16 +589,16 @@
},
{
"name": "symfony/console",
- "version": "v2.7.1",
+ "version": "v2.7.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/Console.git",
- "reference": "564398bc1f33faf92fc2ec86859983d30eb81806"
+ "reference": "8cf484449130cabfd98dcb4694ca9945802a21ed"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/Console/zipball/564398bc1f33faf92fc2ec86859983d30eb81806",
- "reference": "564398bc1f33faf92fc2ec86859983d30eb81806",
+ "url": "https://api.github.com/repos/symfony/Console/zipball/8cf484449130cabfd98dcb4694ca9945802a21ed",
+ "reference": "8cf484449130cabfd98dcb4694ca9945802a21ed",
"shasum": ""
},
"require": {
@@ -704,20 +642,20 @@
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
- "time": "2015-06-10 15:30:22"
+ "time": "2015-07-09 16:07:40"
},
{
"name": "symfony/event-dispatcher",
- "version": "v2.7.1",
+ "version": "v2.7.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/EventDispatcher.git",
- "reference": "be3c5ff8d503c46768aeb78ce6333051aa6f26d9"
+ "reference": "9310b5f9a87ec2ea75d20fec0b0017c77c66dac3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/be3c5ff8d503c46768aeb78ce6333051aa6f26d9",
- "reference": "be3c5ff8d503c46768aeb78ce6333051aa6f26d9",
+ "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/9310b5f9a87ec2ea75d20fec0b0017c77c66dac3",
+ "reference": "9310b5f9a87ec2ea75d20fec0b0017c77c66dac3",
"shasum": ""
},
"require": {
@@ -762,22 +700,22 @@
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com",
- "time": "2015-06-08 09:37:21"
+ "time": "2015-06-18 19:21:56"
}
],
"packages-dev": [
{
"name": "symfony/stopwatch",
- "version": "v2.7.1",
+ "version": "v2.7.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/Stopwatch.git",
- "reference": "c653f1985f6c2b7dbffd04d48b9c0a96aaef814b"
+ "reference": "b07a866719bbac5294c67773340f97b871733310"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/Stopwatch/zipball/c653f1985f6c2b7dbffd04d48b9c0a96aaef814b",
- "reference": "c653f1985f6c2b7dbffd04d48b9c0a96aaef814b",
+ "url": "https://api.github.com/repos/symfony/Stopwatch/zipball/b07a866719bbac5294c67773340f97b871733310",
+ "reference": "b07a866719bbac5294c67773340f97b871733310",
"shasum": ""
},
"require": {
@@ -813,7 +751,7 @@
],
"description": "Symfony Stopwatch Component",
"homepage": "https://symfony.com",
- "time": "2015-06-04 20:11:48"
+ "time": "2015-07-01 18:23:16"
}
],
"aliases": [],
diff --git a/config.default.php b/config.default.php
index 7c6955e8..c392dcad 100644
--- a/config.default.php
+++ b/config.default.php
@@ -109,6 +109,9 @@ define('LDAP_ACCOUNT_ID', 'samaccountname');
// Set to true if you want to preserve the case
define('LDAP_USERNAME_CASE_SENSITIVE', false);
+// Automatically create user account
+define('LDAP_ACCOUNT_CREATION', true);
+
// Enable/disable Google authentication
define('GOOGLE_AUTH', false);
@@ -150,3 +153,9 @@ define('MARKDOWN_ESCAPE_HTML', true);
// API alternative authentication header, the default is HTTP Basic Authentication defined in RFC2617
define('API_AUTHENTICATION_HEADER', '');
+
+// Enable/disable url rewrite
+define('ENABLE_URL_REWRITE', false);
+
+// Hide login form, useful if all your users use Google/Github/ReverseProxy authentication
+define('HIDE_LOGIN_FORM', false);
diff --git a/docs/config.markdown b/docs/config.markdown
index 081f9c71..45ba7a91 100644
--- a/docs/config.markdown
+++ b/docs/config.markdown
@@ -32,6 +32,13 @@ define('FILES_DIR', 'data/files/');
Don't forget the trailing slash.
+Enable/disable url rewrite
+--------------------------
+
+```php
+define('ENABLE_URL_REWRITE', false);
+```
+
Email configuration
-------------------
@@ -128,6 +135,9 @@ define('LDAP_ACCOUNT_ID', 'samaccountname');
// By default Kanboard lowercase the ldap username to avoid duplicate users (the database is case sensitive)
// Set to true if you want to preserve the case
define('LDAP_USERNAME_CASE_SENSITIVE', false);
+
+// Automatically create user account
+define('LDAP_ACCOUNT_CREATION', true);
```
Google Authentication settings
@@ -195,4 +205,7 @@ define('MARKDOWN_ESCAPE_HTML', true);
// API alternative authentication header, the default is HTTP Basic Authentication defined in RFC2617
define('API_AUTHENTICATION_HEADER', '');
+
+// Hide login form, useful if all your users use Google/Github/ReverseProxy authentication
+define('HIDE_LOGIN_FORM', false);
```
diff --git a/docs/faq.markdown b/docs/faq.markdown
index b4cf9148..3d542a30 100644
--- a/docs/faq.markdown
+++ b/docs/faq.markdown
@@ -10,25 +10,6 @@ Kanboard works well with any great VPS hosting provider such as [Digital Ocean](
To have the best performances, choose a provider with fast disk I/O because Kanboard use Sqlite by default.
Avoid hosting providers that use a shared NFS mount point.
-
-Which web browsers are supported?
----------------------------------
-
-Kanboard have been tested on the following devices:
-
-### Desktop
-
-- Mozilla Firefox
-- Safari
-- Google Chrome
-- Internet Explorer 11
-
-### Tablets
-
-- iPad (iOS)
-- Nexus 7 (Android/Chrome)
-
-
I get a blank page after installing or upgrading Kanboard
---------------------------------------------------------
diff --git a/docs/github-authentication.markdown b/docs/github-authentication.markdown
index d6c1e1e8..8c3b622d 100644
--- a/docs/github-authentication.markdown
+++ b/docs/github-authentication.markdown
@@ -1,64 +1,63 @@
-GitHub Authentication
-=====================
-
-Requirements
-------------
-
-- OAuth GitHub API credentials (available in your [Settings > Applications > Developer applications](https://github.com/settings/applications))
-
-How does this work?
--------------------
-
-The GitHub authentication in Kanboard uses the [OAuth 2.0](http://oauth.net/2/) protocol, so any user of Kanboard can be linked to a GitHub account.
-
-When that is done, they no longer need to manually login with their Kanboard account, but can simply automatically login with their GitHub account.
-
-How to link a GitHub account
-----------------------------------
-
-1. Login to Kanboard with the desired user
-2. Go to the **Edit user** page and click on the link **Link my GitHub Account**
-3. You are redirected to the GitHub **Authorize application** form, authorize Kanboard by clicking on the button **Accept**
-4. Finally, you are redirected to Kanboard and now your user account is linked to your GitHub account
-5. During the process, Kanboard has updated your full name and your email address based on your GitHub profile, if either of those are publically available
-6. Log out of Kanboard and you should be able to login directly with GitHub by clicking on the link **Login with my GitHub Account**
-
-Installation instructions
--------------------------
-
-### Setting up OAuth 2.0
-
-On Github, go to the page ["Register a new OAuth application"](https://github.com/settings/applications/new).
-
-Just follow the [official GitHub documentation](https://developer.github.com/guides/basics-of-authentication/#registering-your-app):
-
-- Open your [Settings](https://github.com/settings), select [Applications](https://github.com/settings/applications) from the sidebar and click on [Register new application](https://github.com/settings/applications/new) on the top, next to where it says **Developer applications**
-- Fill out the form with whatever values you like, the **Authorization callback URL** _must_ be: **http://YOUR_SERVER/?controller=user&action=gitHub**
-
-### Setting up Kanboard
-
-Either create a new `config.php` file or rename the `config.default.php` file and set the following values:
-
-```php
-// Enable/disable GitHub authentication
-define('GITHUB_AUTH', true);
-
-// GitHub client id (Copy it from your settings -> Applications -> Developer applications)
-define('GITHUB_CLIENT_ID', 'YOUR_GITHUB_CLIENT_ID');
-
-// GitHub client secret key (Copy it from your settings -> Applications -> Developer applications)
-define('GITHUB_CLIENT_SECRET', 'YOUR_GITHUB_CLIENT_SECRET');
-
-```
-
-Notes
------
-**Important:** _*Never*_ store your GITHUB_CLIENT_ID or GITHUB_CLIENT_SECRET in GitHub or somewhere with full public access in general!
-
-Kanboard uses these information from your public GitHub profile:
-
-- Full name
-- Public email address
-- GitHub unique id
-
-The GitHub unique id is used to link the local user account and the GitHub account.
+Github Authentication
+=====================
+
+Requirements
+------------
+
+OAuth Github API credentials (available in your [Settings > Applications > Developer applications](https://github.com/settings/applications))
+
+How does this work?
+-------------------
+
+The Github authentication in Kanboard uses the [OAuth 2.0](http://oauth.net/2/) protocol, so any user of Kanboard can be linked to a Github account.
+
+When that is done, they no longer need to manually login with their Kanboard account, but can simply automatically login with their Github account.
+
+How to link a Github account
+----------------------------
+
+1. Go to your user profile
+2. Click on **External accounts**
+3. Click on the link **Link my Github Account**
+4. You are redirected to the **Github Authorize application form**
+5. Authorize Kanboard by clicking on the button **Accept**
+6. Your account is now linked
+
+Now, on the login page you can be authenticated in one click with the link **Login with my Github Account**.
+
+Your name and email are automatically updated from your Github Account if defined.
+
+Installation instructions
+-------------------------
+
+### Setting up OAuth 2.0
+
+- On Github, go to the page [Register a new OAuth application](https://github.com/settings/applications/new)
+- Just follow the [official Github documentation](https://developer.github.com/guides/basics-of-authentication/#registering-your-app)
+- In Kanboard, you can get the **callback url** in **Settings > Integrations > Github Authentication**
+
+### Setting up Kanboard
+
+Either create a new `config.php` file or rename the `config.default.php` file and set the following values:
+
+```php
+// Enable/disable Github authentication
+define('GITHUB_AUTH', true);
+
+// Github client id (Copy it from your settings -> Applications -> Developer applications)
+define('GITHUB_CLIENT_ID', 'YOUR_GITHUB_CLIENT_ID');
+
+// Github client secret key (Copy it from your settings -> Applications -> Developer applications)
+define('GITHUB_CLIENT_SECRET', 'YOUR_GITHUB_CLIENT_SECRET');
+```
+
+Notes
+-----
+
+Kanboard uses these information from your public Github profile:
+
+- Full name
+- Public email address
+- Github unique id
+
+The Github unique id is used to link the local user account and the Github account.
diff --git a/docs/gitlab-webhooks.markdown b/docs/gitlab-webhooks.markdown
index 9ef73f97..9d9ecaf5 100644
--- a/docs/gitlab-webhooks.markdown
+++ b/docs/gitlab-webhooks.markdown
@@ -9,22 +9,24 @@ List of supported events
- Gitlab commit received
- Gitlab issue opened
- Gitlab issue closed
+- Gitlab issue comment created
List of supported actions
-------------------------
- Create a task from an external provider
- Close a task
+- Create a comment from an external provider
Configuration
-------------
-![Github configuration](http://kanboard.net/screenshots/documentation/gitlab-webhooks.png)
+![Gitlab configuration](http://kanboard.net/screenshots/documentation/gitlab-webhooks.png)
1. On Kanboard, go to the project settings and choose the section **Integrations**
2. Copy the Gitlab webhook url
3. On Gitlab, go to the project settings and go to the section **Webhooks**
-4. Check the boxes **Push Events** and **Issues Events**
+4. Check the boxes **Push Events**, **Comments** and **Issues Events**
5. Paste the url and save
Examples
@@ -53,3 +55,11 @@ When a task is created from a Gitlab issue, the link to the issue is added to th
- Choose the event: **Gitlab issue closed**
- Choose the action: **Close the task**
+
+### Create a comment on Kanboard when an issue is commented on Gitlab
+
+- Choose the event: **Gitlab issue comment created**
+- Choose the action: **Create a comment from an external provider**
+
+If the username is the same between Gitlab and Kanboard the comment author will be assigned, otherwise there is no author.
+The user also have to be member of the project in Kanboard. \ No newline at end of file
diff --git a/docs/google-authentication.markdown b/docs/google-authentication.markdown
index 033cf4bb..0f4f3ec1 100644
--- a/docs/google-authentication.markdown
+++ b/docs/google-authentication.markdown
@@ -4,7 +4,7 @@ Google Authentication
Requirements
------------
-- OAuth Google API credentials (available in the Google Developer Console)
+OAuth Google API credentials (available in the Google Developer Console)
How does this work?
-------------------
@@ -16,24 +16,24 @@ How does this work?
Procedure to link a Google Account
----------------------------------
-1. The first step is to link an existing user account to a Google Account
-2. Login with the desired user
-3. Go to the **Edit user** page and click on the link **Link my Google Account**
-4. You are redirected to the **Google Consent screen**, authorize Kanboard by clicking on the button **Accept**
-5. Finally, you are redirected to Kanboard and now your user account is linked to your Google account
-6. During the process, Kanboard have updated your full name and your email address based on your Google profile
-7. Log out and you should be able to login directly with Google by clicking on the link **Login with my Google Account**
+1. Go to your user profile
+2. Click on **External accounts**
+3. Click on the link **Link my Google Account**
+4. You are redirected to the **Google Consent screen**
+5. Authorize Kanboard by clicking on the button **Accept**
+6. Your account is now linked
+
+Now, on the login page you can be authenticated in one click with the link **Login with my Google Account**.
+
+Your name and email are automatically updated from your Google Account.
Installation instructions
-------------------------
-### Setting up OAuth 2.0
+### Setting up OAuth 2.0 in Google Developer Console
-Follow the [official Google documentation](https://developers.google.com/accounts/docs/OAuth2Login#appsetup), in summary:
-
-- Go to the [Developer Console](https://console.developers.google.com)
-- On the sidebar, click on **Credentials** and choose **Create a new Client Id**
-- Fill the form, the redirect URL must be: **http://YOUR_SERVER/?controller=user&action=google**
+- Follow the [official Google documentation](https://developers.google.com/accounts/docs/OAuth2Login#appsetup) to create a new application
+- In Kanboard, you can get the **redirect url** in **Settings > Integrations > Google Authentication**
### Setting up Kanboad
@@ -50,7 +50,6 @@ define('GOOGLE_CLIENT_ID', 'YOUR_CLIENT_ID');
// Google client secret key (Get this value from the Google developer console)
define('GOOGLE_CLIENT_SECRET', 'YOUR_CLIENT_SECRET');
-
```
Notes
diff --git a/docs/index.markdown b/docs/index.markdown
index 410cc402..9277ea9b 100644
--- a/docs/index.markdown
+++ b/docs/index.markdown
@@ -82,6 +82,7 @@ Technical details
### Installation
+- [Recommended configuration](recommended-configuration.markdown)
- [Installation instructions](installation.markdown)
- [Upgrade Kanboard to a new version](update.markdown)
- [Installation on Ubuntu](ubuntu-installation.markdown)
@@ -97,6 +98,7 @@ Technical details
### Configuration
- [Email configuration](email-configuration.markdown)
+- [URL rewriting](nice-urls.markdown)
### Database
@@ -108,7 +110,7 @@ Technical details
- [LDAP authentication](ldap-authentication.markdown)
- [Google authentication](google-authentication.markdown)
-- [GitHub authentication](github-authentication.markdown)
+- [Github authentication](github-authentication.markdown)
- [Reverse proxy authentication](reverse-proxy-authentication.markdown)
### Contributors
diff --git a/docs/ldap-authentication.markdown b/docs/ldap-authentication.markdown
index 2428194d..8c7e5ff5 100644
--- a/docs/ldap-authentication.markdown
+++ b/docs/ldap-authentication.markdown
@@ -17,7 +17,7 @@ When the LDAP authentication is activated, the login process work like that:
1. Try first to authenticate the user by using the database
2. If the user is not found inside the database, a LDAP authentication is performed
-3. If the LDAP authentication is successful, a local user is created automatically with no password and marked as LDAP user.
+3. If the LDAP authentication is successful, by default a local user is created automatically with no password and marked as LDAP user.
### Differences between a local user and a LDAP user are the following:
@@ -85,6 +85,22 @@ define('LDAP_ACCOUNT_ID', 'samaccountname');
// By default Kanboard lowercase the ldap username to avoid duplicate users (the database is case sensitive)
// Set to true if you want to preserve the case
define('LDAP_USERNAME_CASE_SENSITIVE', false);
+
+// Automatically create user account
+define('LDAP_ACCOUNT_CREATION', true);
+```
+
+### Disable automatic account creation
+
+By default, Kanboard will create automatically a user account if nothing is found.
+
+You can disable this behavior if you prefer to create user accounts manually to restrict Kanboard to only some people.
+
+Just change the value of `LDAP_ACCOUNT_CREATION` to `false`:
+
+```php
+// Automatically create user account
+define('LDAP_ACCOUNT_CREATION', false);
```
### LDAP bind type
diff --git a/docs/nice-urls.markdown b/docs/nice-urls.markdown
new file mode 100644
index 00000000..38f7c41d
--- /dev/null
+++ b/docs/nice-urls.markdown
@@ -0,0 +1,36 @@
+URL rewriting
+=============
+
+Kanboard is able to work indifferently with url rewriting enabled or not.
+
+- Example of URL rewritten: `/board/123`
+- Otherwise: `?controller=board&action=show&project_id=123`
+
+If you use Kanboard with Apache and with the mode rewrite enabled, nice urls will be used automatically.
+
+URL Shortcuts
+-------------
+
+- Go to the task #123: **/t/123**
+- Go to the board of the project #2: **/b/2**
+- Go to the project calendar #5: **/c/5**
+- Go to the list view of the project #8: **/l/8**
+- Go to the project settings for the project id #42: **/p/42**
+
+Configuration
+-------------
+
+By default, Kanboard will check if the Apache mode rewrite is enabled.
+
+To avoid the automatic detection of url rewriting from the web server, you can enable this feature in your config file:
+
+```
+define('ENABLE_URL_REWRITE', true);
+```
+
+When this constant is at `true`:
+
+- URLs generated from command line tools will be also converted
+- If you use another web server than Apache, by example Nginx or Microsoft IIS, you have to configure yourself the url rewriting
+
+Note: Kanboard always fallback to old school urls when it's not configured, this configuration is optional.
diff --git a/docs/recommended-configuration.markdown b/docs/recommended-configuration.markdown
new file mode 100644
index 00000000..93b49723
--- /dev/null
+++ b/docs/recommended-configuration.markdown
@@ -0,0 +1,36 @@
+Recommended Configuration
+=========================
+
+Server side
+-----------
+
+- Modern Linux/Unix operating system: **Ubuntu/Debian or FreeBSD**
+- Most recent version of PHP and Apache
+- Use the Sqlite database only when you have a disk with fast I/O (SSD disks) otherwise use Mysql or Postgresql
+
+Client side
+-----------
+
+- Use a modern browser: **Mozilla Firefox or Google Chrome or Safari**
+
+Tested configurations
+---------------------
+
+The following configurations are tested with Kanboard but that doesn't mean all features are available:
+
+### Server
+
+- Ubuntu 14.04 LTS
+- Debian 6, 7 and 8
+- Centos 6.5 and 7.0
+- Windows 2012 Server
+- Windows 2008 Server
+
+### Desktops
+
+- Last version of Mozilla Firefox, Safari and Google Chrome
+- Microsoft Internet Explorer 11
+
+### Tablets
+
+- iPad mini 3
diff --git a/docs/search.markdown b/docs/search.markdown
index 3d00b158..d0e71203 100644
--- a/docs/search.markdown
+++ b/docs/search.markdown
@@ -151,6 +151,15 @@ Attribute: **column**
- Find tasks by column name: `column:"Work in progress"`
- Find tasks for several columns: `column:"Backlog" column:ready`
+Search by swimlane
+------------------
+
+Attribute: **swimlane**
+
+- Find tasks by swimlane: `swimlane:"Version 42"`
+- Find tasks in the default swimlane: `swimlane:default`
+- Find tasks into several swimlanes: `swimlane:"Version 1.2" swimlane:"Version 1.3"`
+
Search by external reference
----------------------------
diff --git a/docs/user-management.markdown b/docs/user-management.markdown
index bd9cdb17..98691ddd 100644
--- a/docs/user-management.markdown
+++ b/docs/user-management.markdown
@@ -1,8 +1,8 @@
User management
===============
-Type of users
--------------
+Group of users
+--------------
Kanboard use a basic permission system, there is two kind of users:
@@ -18,22 +18,30 @@ There is also permissions defined at the project level, users can be seen as:
Project managers have more privileges than a simple user member.
+Local and remote users
+----------------------
+
+- A local user is an account that use the database to store credentials. Local users use the login form for the authentication.
+- A remote user is an account that use an external system to store credentials. By example, it can be LDAP, Github or Google accounts. Authentication of these users can be done through the login form or not.
+
Add a new user
--------------
To add a new user, you must be administrator.
1. From the dashboard, go to the menu **User Management**
-2. On the top, you have a link **New user**
+2. On the top, you have a link **New local user** or **New remote user**
3. Fill the form and save
![New user](http://kanboard.net/screenshots/documentation/new-user.png)
-When you create a new user, you have to specify at least those values:
+When you create a **local user**, you have to specify at least those values:
- **username**: This is the unique identifier of your user (login)
- **password**: The password of your user must have at least 6 characters
+For **remote users**, only the username is mandatory. You can also pre-link Github or Google accounts if you already know their unique id.
+
Edit users
----------
diff --git a/index.php b/index.php
index 4c49416f..7c5fe6d1 100644
--- a/index.php
+++ b/index.php
@@ -2,7 +2,6 @@
require __DIR__.'/app/common.php';
-use Core\Router;
-
-$router = new Router($container);
-$router->execute();
+if (! $container['router']->dispatch($_SERVER['REQUEST_URI'], $_SERVER['QUERY_STRING'])) {
+ die('Page not found!');
+}
diff --git a/tests/units/AclTest.php b/tests/units/AclTest.php
index 72c897c0..05e8561e 100644
--- a/tests/units/AclTest.php
+++ b/tests/units/AclTest.php
@@ -39,6 +39,8 @@ class AclTest extends Base
$this->assertFalse($acl->isPublicAction('board', 'show'));
$this->assertTrue($acl->isPublicAction('feed', 'project'));
$this->assertTrue($acl->isPublicAction('feed', 'user'));
+ $this->assertTrue($acl->isPublicAction('oauth', 'github'));
+ $this->assertTrue($acl->isPublicAction('oauth', 'google'));
}
public function testAdminActions()
diff --git a/tests/units/GitlabWebhookTest.php b/tests/units/GitlabWebhookTest.php
index b69f7431..a2dc0d3a 100644
--- a/tests/units/GitlabWebhookTest.php
+++ b/tests/units/GitlabWebhookTest.php
@@ -6,20 +6,18 @@ use Integration\GitlabWebhook;
use Model\TaskCreation;
use Model\TaskFinder;
use Model\Project;
+use Model\ProjectPermission;
+use Model\User;
class GitlabWebhookTest extends Base
{
- private $push_payload = '{"before":"9187f41ba34a2b40d41c50ed4b624ce374c5e583","after":"b3caaee62ad27dc31497946065ac18299784aee4","ref":"refs/heads/master","user_id":74067,"user_name":"Fred","project_id":124474,"repository":{"name":"kanboard","url":"git@gitlab.com:minicoders/kanboard.git","description":"Test repo","homepage":"https://gitlab.com/minicoders/kanboard"},"commits":[{"id":"b3caaee62ad27dc31497946065ac18299784aee4","message":"Fix bug #2\n","timestamp":"2014-12-28T20:31:48-05:00","url":"https://gitlab.com/minicoders/kanboard/commit/b3caaee62ad27dc31497946065ac18299784aee4","author":{"name":"Frédéric Guillot","email":"git@localhost"}}],"total_commits_count":1}';
- private $issue_open_payload = '{"object_kind":"issue","user":{"name":"Fred","username":"minicoders","avatar_url":"https://secure.gravatar.com/avatar/3c44936e5a56f80711bff14987d2733f?s=40\u0026d=identicon"},"object_attributes":{"id":103356,"title":"Test Webhook","assignee_id":null,"author_id":74067,"project_id":124474,"created_at":"2014-12-29 01:24:24 UTC","updated_at":"2014-12-29 01:24:24 UTC","position":0,"branch_name":null,"description":"- test1\r\n- test2","milestone_id":null,"state":"opened","iid":1,"url":"https://gitlab.com/minicoders/kanboard/issues/1","action":"open"}}';
- private $issue_closed_payload = '{"object_kind":"issue","user":{"name":"Fred","username":"minicoders","avatar_url":"https://secure.gravatar.com/avatar/3c44936e5a56f80711bff14987d2733f?s=40\u0026d=identicon"},"object_attributes":{"id":103361,"title":"uu","assignee_id":null,"author_id":74067,"project_id":124474,"created_at":"2014-12-29 01:28:44 UTC","updated_at":"2014-12-29 01:34:47 UTC","position":0,"branch_name":null,"description":"","milestone_id":null,"state":"closed","iid":4,"url":"https://gitlab.com/minicoders/kanboard/issues/4","action":"update"}}';
-
public function testGetEventType()
{
$g = new GitlabWebhook($this->container);
- $this->assertEquals(GitlabWebhook::TYPE_PUSH, $g->getType(json_decode($this->push_payload, true)));
- $this->assertEquals(GitlabWebhook::TYPE_ISSUE, $g->getType(json_decode($this->issue_open_payload, true)));
- $this->assertEquals(GitlabWebhook::TYPE_ISSUE, $g->getType(json_decode($this->issue_closed_payload, true)));
+ $this->assertEquals(GitlabWebhook::TYPE_PUSH, $g->getType(json_decode(file_get_contents(__DIR__.'/fixtures/gitlab_push.json'), true)));
+ $this->assertEquals(GitlabWebhook::TYPE_ISSUE, $g->getType(json_decode(file_get_contents(__DIR__.'/fixtures/gitlab_issue_opened.json'), true)));
+ $this->assertEquals(GitlabWebhook::TYPE_COMMENT, $g->getType(json_decode(file_get_contents(__DIR__.'/fixtures/gitlab_comment_created.json'), true)));
$this->assertEquals('', $g->getType(array()));
}
@@ -35,7 +33,7 @@ class GitlabWebhookTest extends Base
$this->container['dispatcher']->addListener(GitlabWebhook::EVENT_COMMIT, array($this, 'onCommit'));
- $event = json_decode($this->push_payload, true);
+ $event = json_decode(file_get_contents(__DIR__.'/fixtures/gitlab_push.json'), true);
// No task
$this->assertFalse($g->handleCommit($event['commits'][0]));
@@ -59,7 +57,7 @@ class GitlabWebhookTest extends Base
$this->container['dispatcher']->addListener(GitlabWebhook::EVENT_ISSUE_OPENED, array($this, 'onOpen'));
- $event = json_decode($this->issue_open_payload, true);
+ $event = json_decode(file_get_contents(__DIR__.'/fixtures/gitlab_issue_opened.json'), true);
$this->assertTrue($g->handleIssueOpened($event['object_attributes']));
$called = $this->container['dispatcher']->getCalledListeners();
@@ -78,7 +76,7 @@ class GitlabWebhookTest extends Base
$this->container['dispatcher']->addListener(GitlabWebhook::EVENT_ISSUE_CLOSED, array($this, 'onClose'));
- $event = json_decode($this->issue_closed_payload, true);
+ $event = json_decode(file_get_contents(__DIR__.'/fixtures/gitlab_issue_closed.json'), true);
// Issue not there
$this->assertFalse($g->handleIssueClosed($event['object_attributes']));
@@ -87,11 +85,11 @@ class GitlabWebhookTest extends Base
$this->assertEmpty($called);
// Create a task with the issue reference
- $this->assertEquals(1, $tc->create(array('title' => 'A', 'project_id' => 1, 'reference' => 103361)));
- $task = $tf->getByReference(1, 103361);
+ $this->assertEquals(1, $tc->create(array('title' => 'A', 'project_id' => 1, 'reference' => 355691)));
+ $task = $tf->getByReference(1, 355691);
$this->assertNotEmpty($task);
- $task = $tf->getByReference(2, 103361);
+ $task = $tf->getByReference(2, 355691);
$this->assertEmpty($task);
$this->assertTrue($g->handleIssueClosed($event['object_attributes']));
@@ -100,13 +98,76 @@ class GitlabWebhookTest extends Base
$this->assertArrayHasKey(GitlabWebhook::EVENT_ISSUE_CLOSED.'.GitlabWebhookTest::onClose', $called);
}
+ public function testCommentCreatedWithNoUser()
+ {
+ $this->container['dispatcher']->addListener(GitlabWebhook::EVENT_ISSUE_COMMENT, array($this, 'onCommentCreatedWithNoUser'));
+
+ $p = new Project($this->container);
+ $this->assertEquals(1, $p->create(array('name' => 'foobar')));
+
+ $tc = new TaskCreation($this->container);
+ $this->assertEquals(1, $tc->create(array('title' => 'boo', 'reference' => 355691, 'project_id' => 1)));
+
+ $g = new GitlabWebhook($this->container);
+ $g->setProjectId(1);
+
+ $this->assertNotFalse($g->parsePayload(
+ json_decode(file_get_contents(__DIR__.'/fixtures/gitlab_comment_created.json'), true)
+ ));
+ }
+
+ public function testCommentCreatedWithNotMember()
+ {
+ $this->container['dispatcher']->addListener(GitlabWebhook::EVENT_ISSUE_COMMENT, array($this, 'onCommentCreatedWithNotMember'));
+
+ $p = new Project($this->container);
+ $this->assertEquals(1, $p->create(array('name' => 'foobar')));
+
+ $tc = new TaskCreation($this->container);
+ $this->assertEquals(1, $tc->create(array('title' => 'boo', 'reference' => 355691, 'project_id' => 1)));
+
+ $u = new User($this->container);
+ $this->assertEquals(2, $u->create(array('username' => 'minicoders')));
+
+ $g = new GitlabWebhook($this->container);
+ $g->setProjectId(1);
+
+ $this->assertNotFalse($g->parsePayload(
+ json_decode(file_get_contents(__DIR__.'/fixtures/gitlab_comment_created.json'), true)
+ ));
+ }
+
+ public function testCommentCreatedWithUser()
+ {
+ $this->container['dispatcher']->addListener(GitlabWebhook::EVENT_ISSUE_COMMENT, array($this, 'onCommentCreatedWithUser'));
+
+ $p = new Project($this->container);
+ $this->assertEquals(1, $p->create(array('name' => 'foobar')));
+
+ $tc = new TaskCreation($this->container);
+ $this->assertEquals(1, $tc->create(array('title' => 'boo', 'reference' => 355691, 'project_id' => 1)));
+
+ $u = new User($this->container);
+ $this->assertEquals(2, $u->create(array('username' => 'minicoders')));
+
+ $pp = new ProjectPermission($this->container);
+ $this->assertTrue($pp->addMember(1, 2));
+
+ $g = new GitlabWebhook($this->container);
+ $g->setProjectId(1);
+
+ $this->assertNotFalse($g->parsePayload(
+ json_decode(file_get_contents(__DIR__.'/fixtures/gitlab_comment_created.json'), true)
+ ));
+ }
+
public function onOpen($event)
{
$data = $event->getAll();
$this->assertEquals(1, $data['project_id']);
- $this->assertEquals(103356, $data['reference']);
- $this->assertEquals('Test Webhook', $data['title']);
- $this->assertEquals("- test1\r\n- test2\n\n[Gitlab Issue](https://gitlab.com/minicoders/kanboard/issues/1)", $data['description']);
+ $this->assertEquals(355691, $data['reference']);
+ $this->assertEquals('Bug', $data['title']);
+ $this->assertEquals("There is a bug somewhere.\r\n\r\nBye\n\n[Gitlab Issue](https://gitlab.com/minicoders/test-webhook/issues/1)", $data['description']);
}
public function onClose($event)
@@ -114,7 +175,7 @@ class GitlabWebhookTest extends Base
$data = $event->getAll();
$this->assertEquals(1, $data['project_id']);
$this->assertEquals(1, $data['task_id']);
- $this->assertEquals(103361, $data['reference']);
+ $this->assertEquals(355691, $data['reference']);
}
public function onCommit($event)
@@ -123,8 +184,38 @@ class GitlabWebhookTest extends Base
$this->assertEquals(1, $data['project_id']);
$this->assertEquals(2, $data['task_id']);
$this->assertEquals('test2', $data['title']);
- $this->assertEquals("Fix bug #2\n\n\n[Commit made by @Frédéric Guillot on Gitlab](https://gitlab.com/minicoders/kanboard/commit/b3caaee62ad27dc31497946065ac18299784aee4)", $data['commit_comment']);
- $this->assertEquals("Fix bug #2\n", $data['commit_message']);
- $this->assertEquals('https://gitlab.com/minicoders/kanboard/commit/b3caaee62ad27dc31497946065ac18299784aee4', $data['commit_url']);
+ $this->assertEquals("Fix bug #2\n\n[Commit made by @Fred on Gitlab](https://gitlab.com/minicoders/test-webhook/commit/48aafa75eef9ad253aa254b0c82c987a52ebea78)", $data['commit_comment']);
+ $this->assertEquals("Fix bug #2", $data['commit_message']);
+ $this->assertEquals('https://gitlab.com/minicoders/test-webhook/commit/48aafa75eef9ad253aa254b0c82c987a52ebea78', $data['commit_url']);
+ }
+
+ public function onCommentCreatedWithNoUser($event)
+ {
+ $data = $event->getAll();
+ $this->assertEquals(1, $data['project_id']);
+ $this->assertEquals(1, $data['task_id']);
+ $this->assertEquals(0, $data['user_id']);
+ $this->assertEquals(1642761, $data['reference']);
+ $this->assertEquals("Super comment!\n\n[By @minicoders on Gitlab](https://gitlab.com/minicoders/test-webhook/issues/1#note_1642761)", $data['comment']);
+ }
+
+ public function onCommentCreatedWithNotMember($event)
+ {
+ $data = $event->getAll();
+ $this->assertEquals(1, $data['project_id']);
+ $this->assertEquals(1, $data['task_id']);
+ $this->assertEquals(0, $data['user_id']);
+ $this->assertEquals(1642761, $data['reference']);
+ $this->assertEquals("Super comment!\n\n[By @minicoders on Gitlab](https://gitlab.com/minicoders/test-webhook/issues/1#note_1642761)", $data['comment']);
+ }
+
+ public function onCommentCreatedWithUser($event)
+ {
+ $data = $event->getAll();
+ $this->assertEquals(1, $data['project_id']);
+ $this->assertEquals(1, $data['task_id']);
+ $this->assertEquals(2, $data['user_id']);
+ $this->assertEquals(1642761, $data['reference']);
+ $this->assertEquals("Super comment!\n\n[By @minicoders on Gitlab](https://gitlab.com/minicoders/test-webhook/issues/1#note_1642761)", $data['comment']);
}
}
diff --git a/tests/units/LexerTest.php b/tests/units/LexerTest.php
index bf0ffdd0..8710f79a 100644
--- a/tests/units/LexerTest.php
+++ b/tests/units/LexerTest.php
@@ -6,6 +6,31 @@ use Core\Lexer;
class LexerTest extends Base
{
+ public function testSwimlaneQuery()
+ {
+ $lexer = new Lexer;
+
+ $this->assertEquals(
+ array(array('match' => 'swimlane:', 'token' => 'T_SWIMLANE'), array('match' => 'Version 42', 'token' => 'T_STRING')),
+ $lexer->tokenize('swimlane:"Version 42"')
+ );
+
+ $this->assertEquals(
+ array(array('match' => 'swimlane:', 'token' => 'T_SWIMLANE'), array('match' => 'v3', 'token' => 'T_STRING')),
+ $lexer->tokenize('swimlane:v3')
+ );
+
+ $this->assertEquals(
+ array('T_SWIMLANE' => array('v3')),
+ $lexer->map($lexer->tokenize('swimlane:v3'))
+ );
+
+ $this->assertEquals(
+ array('T_SWIMLANE' => array('Version 42', 'v3')),
+ $lexer->map($lexer->tokenize('swimlane:"Version 42" swimlane:v3'))
+ );
+ }
+
public function testAssigneeQuery()
{
$lexer = new Lexer;
diff --git a/tests/units/OAuth2Test.php b/tests/units/OAuth2Test.php
new file mode 100644
index 00000000..0275f426
--- /dev/null
+++ b/tests/units/OAuth2Test.php
@@ -0,0 +1,43 @@
+<?php
+
+require_once __DIR__.'/Base.php';
+
+use Core\OAuth2;
+
+class OAuth2Test extends Base
+{
+ public function testAuthUrl()
+ {
+ $oauth = new OAuth2($this->container);
+ $oauth->createService('A', 'B', 'C', 'D', 'E', array('f', 'g'));
+ $this->assertEquals('D?response_type=code&client_id=A&redirect_uri=C&scope=f+g', $oauth->getAuthorizationUrl());
+ }
+
+ public function testAuthHeader()
+ {
+ $oauth = new OAuth2($this->container);
+ $oauth->createService('A', 'B', 'C', 'D', 'E', array('f', 'g'));
+
+ $oauth->setAccessToken('foobar', 'BeaRer');
+ $this->assertEquals('Authorization: Bearer foobar', $oauth->getAuthorizationHeader());
+
+ $oauth->setAccessToken('foobar', 'unknown');
+ $this->assertEquals('', $oauth->getAuthorizationHeader());
+ }
+
+ public function testAccessToken()
+ {
+ $oauth = new OAuth2($this->container);
+ $oauth->createService('A', 'B', 'C', 'D', 'E', array('f', 'g'));
+ $oauth->getAccessToken('something');
+
+ $data = $this->container['httpClient']->getData();
+ $this->assertEquals('something', $data['code']);
+ $this->assertEquals('A', $data['client_id']);
+ $this->assertEquals('B', $data['client_secret']);
+ $this->assertEquals('C', $data['redirect_uri']);
+ $this->assertEquals('authorization_code', $data['grant_type']);
+
+ $this->assertEquals('E', $this->container['httpClient']->getUrl());
+ }
+}
diff --git a/tests/units/RouterTest.php b/tests/units/RouterTest.php
new file mode 100644
index 00000000..e4582121
--- /dev/null
+++ b/tests/units/RouterTest.php
@@ -0,0 +1,79 @@
+<?php
+
+require_once __DIR__.'/Base.php';
+
+use Core\Router;
+
+class RouterTest extends Base
+{
+ public function testSanitize()
+ {
+ $r = new Router($this->container);
+
+ $this->assertEquals('plop', $r->sanitize('PloP', 'default'));
+ $this->assertEquals('default', $r->sanitize('', 'default'));
+ $this->assertEquals('default', $r->sanitize('123-AB', 'default'));
+ $this->assertEquals('default', $r->sanitize('R&D', 'default'));
+ $this->assertEquals('default', $r->sanitize('Test123', 'default'));
+ }
+
+ public function testPath()
+ {
+ $r = new Router($this->container);
+
+ $this->assertEquals('a/b/c', $r->getPath('/a/b/c'));
+ $this->assertEquals('a/b/something', $r->getPath('/a/b/something?test=a', 'test=a'));
+
+ $_SERVER['REQUEST_METHOD'] = 'GET';
+ $_SERVER['PHP_SELF'] = '/a/index.php';
+
+ $this->assertEquals('b/c', $r->getPath('/a/b/c'));
+ $this->assertEquals('b/c', $r->getPath('/a/b/c?e=f', 'e=f'));
+ }
+
+ public function testFindRouteWithEmptyTable()
+ {
+ $r = new Router($this->container);
+ $this->assertEquals(array('app', 'index'), $r->findRoute(''));
+ $this->assertEquals(array('app', 'index'), $r->findRoute('/'));
+ }
+
+ public function testFindRouteWithoutPlaceholders()
+ {
+ $r = new Router($this->container);
+ $r->addRoute('a/b', 'controller', 'action');
+ $this->assertEquals(array('app', 'index'), $r->findRoute('a/b/c'));
+ $this->assertEquals(array('controller', 'action'), $r->findRoute('a/b'));
+ }
+
+ public function testFindRouteWithPlaceholders()
+ {
+ $r = new Router($this->container);
+ $r->addRoute('a/:myvar1/b/:myvar2', 'controller', 'action');
+ $this->assertEquals(array('app', 'index'), $r->findRoute('a/123/b'));
+ $this->assertEquals(array('controller', 'action'), $r->findRoute('a/456/b/789'));
+ $this->assertEquals(array('myvar1' => 456, 'myvar2' => 789), $_GET);
+ }
+
+ public function testFindMultipleRoutes()
+ {
+ $r = new Router($this->container);
+ $r->addRoute('a/b', 'controller1', 'action1');
+ $r->addRoute('a/b', 'duplicate', 'duplicate');
+ $r->addRoute('a', 'controller2', 'action2');
+ $this->assertEquals(array('controller1', 'action1'), $r->findRoute('a/b'));
+ $this->assertEquals(array('controller2', 'action2'), $r->findRoute('a'));
+ }
+
+ public function testFindUrl()
+ {
+ $r = new Router($this->container);
+ $r->addRoute('a/b', 'controller1', 'action1');
+ $r->addRoute('a/:myvar1/b/:myvar2', 'controller2', 'action2', array('myvar1', 'myvar2'));
+
+ $this->assertEquals('a/1/b/2', $r->findUrl('controller2', 'action2', array('myvar1' => 1, 'myvar2' => 2)));
+ $this->assertEquals('', $r->findUrl('controller2', 'action2', array('myvar1' => 1)));
+ $this->assertEquals('a/b', $r->findUrl('controller1', 'action1'));
+ $this->assertEquals('', $r->findUrl('controller1', 'action2'));
+ }
+}
diff --git a/tests/units/TaskFilterTest.php b/tests/units/TaskFilterTest.php
index fede157e..cf65198c 100644
--- a/tests/units/TaskFilterTest.php
+++ b/tests/units/TaskFilterTest.php
@@ -10,6 +10,7 @@ use Model\DateParser;
use Model\Category;
use Model\Subtask;
use Model\Config;
+use Model\Swimlane;
class TaskFilterTest extends Base
{
@@ -287,6 +288,64 @@ class TaskFilterTest extends Base
$this->assertEmpty($tasks);
}
+ public function testSearchWithSwimlane()
+ {
+ $p = new Project($this->container);
+ $tc = new TaskCreation($this->container);
+ $tf = new TaskFilter($this->container);
+ $s = new Swimlane($this->container);
+
+ $this->assertEquals(1, $p->create(array('name' => 'My project A')));
+ $this->assertEquals(1, $s->create(1, 'Version 1.1'));
+ $this->assertEquals(2, $s->create(1, 'Version 1.2'));
+ $this->assertNotFalse($tc->create(array('project_id' => 1, 'title' => 'task1', 'swimlane_id' => 1)));
+ $this->assertNotFalse($tc->create(array('project_id' => 1, 'title' => 'task2', 'swimlane_id' => 2)));
+ $this->assertNotFalse($tc->create(array('project_id' => 1, 'title' => 'task3', 'swimlane_id' => 0)));
+
+ $tf->search('swimlane:"Version 1.1"');
+ $tasks = $tf->findAll();
+ $this->assertNotEmpty($tasks);
+ $this->assertCount(1, $tasks);
+ $this->assertEquals('task1', $tasks[0]['title']);
+ $this->assertEquals('Version 1.1', $tasks[0]['swimlane_name']);
+
+ $tf->search('swimlane:"versioN 1.2"');
+ $tasks = $tf->findAll();
+ $this->assertNotEmpty($tasks);
+ $this->assertCount(1, $tasks);
+ $this->assertEquals('task2', $tasks[0]['title']);
+ $this->assertEquals('Version 1.2', $tasks[0]['swimlane_name']);
+
+ $tf->search('swimlane:"Default swimlane"');
+ $tasks = $tf->findAll();
+ $this->assertNotEmpty($tasks);
+ $this->assertCount(1, $tasks);
+ $this->assertEquals('task3', $tasks[0]['title']);
+ $this->assertEquals('Default swimlane', $tasks[0]['default_swimlane']);
+ $this->assertEquals('', $tasks[0]['swimlane_name']);
+
+ $tf->search('swimlane:default');
+ $tasks = $tf->findAll();
+ $this->assertNotEmpty($tasks);
+ $this->assertCount(1, $tasks);
+ $this->assertEquals('task3', $tasks[0]['title']);
+ $this->assertEquals('Default swimlane', $tasks[0]['default_swimlane']);
+ $this->assertEquals('', $tasks[0]['swimlane_name']);
+
+ $tf->search('swimlane:"Version 1.1" swimlane:"Version 1.2"');
+ $tasks = $tf->findAll();
+ $this->assertNotEmpty($tasks);
+ $this->assertCount(2, $tasks);
+ $this->assertEquals('task1', $tasks[0]['title']);
+ $this->assertEquals('Version 1.1', $tasks[0]['swimlane_name']);
+ $this->assertEquals('task2', $tasks[1]['title']);
+ $this->assertEquals('Version 1.2', $tasks[1]['swimlane_name']);
+
+ $tf->search('swimlane:"not found"');
+ $tasks = $tf->findAll();
+ $this->assertEmpty($tasks);
+ }
+
public function testSearchWithColumn()
{
$p = new Project($this->container);
diff --git a/tests/units/UrlHelperTest.php b/tests/units/UrlHelperTest.php
index 3ef3402a..ebfe9c99 100644
--- a/tests/units/UrlHelperTest.php
+++ b/tests/units/UrlHelperTest.php
@@ -34,6 +34,22 @@ class UrlHelperTest extends Base
);
}
+ public function testDir()
+ {
+ $h = new Url($this->container);
+ $this->assertEquals('', $h->dir());
+
+ $_SERVER['REQUEST_METHOD'] = 'GET';
+ $_SERVER['PHP_SELF'] = '/plop/index.php';
+ $h = new Url($this->container);
+ $this->assertEquals('/plop/', $h->dir());
+
+ $_SERVER['REQUEST_METHOD'] = 'GET';
+ $_SERVER['PHP_SELF'] = '';
+ $h = new Url($this->container);
+ $this->assertEquals('/', $h->dir());
+ }
+
public function testServer()
{
$h = new Url($this->container);
@@ -57,11 +73,13 @@ class UrlHelperTest extends Base
$_SERVER['SERVER_NAME'] = 'kb';
$_SERVER['SERVER_PORT'] = 1234;
+ $h = new Url($this->container);
$this->assertEquals('http://kb:1234/', $h->base());
$c = new Config($this->container);
$c->save(array('application_url' => 'https://mykanboard/'));
+ $h = new Url($this->container);
$this->assertEquals('https://mykanboard/', $c->get('application_url'));
$this->assertEquals('https://mykanboard/', $h->base());
}
diff --git a/tests/units/fixtures/gitlab_comment_created.json b/tests/units/fixtures/gitlab_comment_created.json
new file mode 100644
index 00000000..b6599419
--- /dev/null
+++ b/tests/units/fixtures/gitlab_comment_created.json
@@ -0,0 +1,46 @@
+{
+ "object_kind": "note",
+ "user": {
+ "name": "Fred",
+ "username": "minicoders",
+ "avatar_url": "https://secure.gravatar.com/avatar/3c44936e5a56f80711bff14987d2733f?s=40&d=identicon"
+ },
+ "project_id": 320820,
+ "repository": {
+ "name": "test-webhook",
+ "url": "git@gitlab.com:minicoders/test-webhook.git",
+ "description": "",
+ "homepage": "https://gitlab.com/minicoders/test-webhook"
+ },
+ "object_attributes": {
+ "id": 1642761,
+ "note": "Super comment!",
+ "noteable_type": "Issue",
+ "author_id": 74067,
+ "created_at": "2015-07-17 21:37:48 UTC",
+ "updated_at": "2015-07-17 21:37:48 UTC",
+ "project_id": 320820,
+ "attachment": null,
+ "line_code": null,
+ "commit_id": "",
+ "noteable_id": 355691,
+ "st_diff": null,
+ "system": false,
+ "url": "https://gitlab.com/minicoders/test-webhook/issues/1#note_1642761"
+ },
+ "issue": {
+ "id": 355691,
+ "title": "Bug",
+ "assignee_id": null,
+ "author_id": 74067,
+ "project_id": 320820,
+ "created_at": "2015-07-17 21:31:47 UTC",
+ "updated_at": "2015-07-17 21:37:48 UTC",
+ "position": 0,
+ "branch_name": null,
+ "description": "There is a bug somewhere.\r\n\r\nBye",
+ "milestone_id": null,
+ "state": "opened",
+ "iid": 1
+ }
+} \ No newline at end of file
diff --git a/tests/units/fixtures/gitlab_issue_closed.json b/tests/units/fixtures/gitlab_issue_closed.json
new file mode 100644
index 00000000..82500b3c
--- /dev/null
+++ b/tests/units/fixtures/gitlab_issue_closed.json
@@ -0,0 +1,25 @@
+{
+ "object_kind": "issue",
+ "user": {
+ "name": "Fred",
+ "username": "minicoders",
+ "avatar_url": "https://secure.gravatar.com/avatar/3c44936e5a56f80711bff14987d2733f?s=40&d=identicon"
+ },
+ "object_attributes": {
+ "id": 355691,
+ "title": "Bug",
+ "assignee_id": null,
+ "author_id": 74067,
+ "project_id": 320820,
+ "created_at": "2015-07-17 21:31:47 UTC",
+ "updated_at": "2015-07-17 22:10:17 UTC",
+ "position": 0,
+ "branch_name": null,
+ "description": "There is a bug somewhere.\r\n\r\nBye",
+ "milestone_id": null,
+ "state": "closed",
+ "iid": 1,
+ "url": "https://gitlab.com/minicoders/test-webhook/issues/1",
+ "action": "close"
+ }
+} \ No newline at end of file
diff --git a/tests/units/fixtures/gitlab_issue_opened.json b/tests/units/fixtures/gitlab_issue_opened.json
new file mode 100644
index 00000000..3e75c138
--- /dev/null
+++ b/tests/units/fixtures/gitlab_issue_opened.json
@@ -0,0 +1,25 @@
+{
+ "object_kind": "issue",
+ "user": {
+ "name": "Fred",
+ "username": "minicoders",
+ "avatar_url": "https://secure.gravatar.com/avatar/3c44936e5a56f80711bff14987d2733f?s=40&d=identicon"
+ },
+ "object_attributes": {
+ "id": 355691,
+ "title": "Bug",
+ "assignee_id": null,
+ "author_id": 74067,
+ "project_id": 320820,
+ "created_at": "2015-07-17 21:31:47 UTC",
+ "updated_at": "2015-07-17 21:31:47 UTC",
+ "position": 0,
+ "branch_name": null,
+ "description": "There is a bug somewhere.\r\n\r\nBye",
+ "milestone_id": null,
+ "state": "opened",
+ "iid": 1,
+ "url": "https://gitlab.com/minicoders/test-webhook/issues/1",
+ "action": "open"
+ }
+} \ No newline at end of file
diff --git a/tests/units/fixtures/gitlab_push.json b/tests/units/fixtures/gitlab_push.json
new file mode 100644
index 00000000..ed77f041
--- /dev/null
+++ b/tests/units/fixtures/gitlab_push.json
@@ -0,0 +1,44 @@
+{
+ "object_kind": "push",
+ "before": "e4ec6156d208a45fc546fae73c28300b5af1692a",
+ "after": "48aafa75eef9ad253aa254b0c82c987a52ebea78",
+ "ref": "refs/heads/master",
+ "checkout_sha": "48aafa75eef9ad253aa254b0c82c987a52ebea78",
+ "message": null,
+ "user_id": 74067,
+ "user_name": "Fred",
+ "user_email": "f+gitlab@minicoders.com",
+ "project_id": 320820,
+ "repository": {
+ "name": "test-webhook",
+ "url": "git@gitlab.com:minicoders/test-webhook.git",
+ "description": "",
+ "homepage": "https://gitlab.com/minicoders/test-webhook",
+ "git_http_url": "https://gitlab.com/minicoders/test-webhook.git",
+ "git_ssh_url": "git@gitlab.com:minicoders/test-webhook.git",
+ "visibility_level": 0
+ },
+ "commits": [
+ {
+ "id": "48aafa75eef9ad253aa254b0c82c987a52ebea78",
+ "message": "Fix bug #2",
+ "timestamp": "2015-06-21T00:41:41+00:00",
+ "url": "https://gitlab.com/minicoders/test-webhook/commit/48aafa75eef9ad253aa254b0c82c987a52ebea78",
+ "author": {
+ "name": "Fred",
+ "email": "me@localhost"
+ }
+ },
+ {
+ "id": "e4ec6156d208a45fc546fae73c28300b5af1692a",
+ "message": "test",
+ "timestamp": "2015-06-21T00:35:55+00:00",
+ "url": "https://gitlab.com/localhost/test-webhook/commit/e4ec6156d208a45fc546fae73c28300b5af1692a",
+ "author": {
+ "name": "Fred",
+ "email": "me@localhost"
+ }
+ }
+ ],
+ "total_commits_count": 2
+} \ No newline at end of file