diff --git a/sources/app/Action/Base.php b/sources/app/Action/Base.php
index a2b07e3..0d8bd56 100644
--- a/sources/app/Action/Base.php
+++ b/sources/app/Action/Base.php
@@ -2,9 +2,8 @@
namespace Action;
+use Event\GenericEvent;
use Pimple\Container;
-use Core\Listener;
-use Core\Tool;
/**
* Base class for automatic actions
@@ -12,7 +11,7 @@ use Core\Tool;
* @package action
* @author Frederic Guillot
*
- * @property \Model\Acl $acl
+ * @property \Model\UserSession $userSession
* @property \Model\Comment $comment
* @property \Model\Task $task
* @property \Model\TaskCreation $taskCreation
@@ -21,8 +20,16 @@ use Core\Tool;
* @property \Model\TaskFinder $taskFinder
* @property \Model\TaskStatus $taskStatus
*/
-abstract class Base implements Listener
+abstract class Base
{
+ /**
+ * Flag for called listener
+ *
+ * @access private
+ * @var boolean
+ */
+ private $called = false;
+
/**
* Project id
*
@@ -114,6 +121,7 @@ abstract class Base implements Listener
$this->container = $container;
$this->project_id = $project_id;
$this->event_name = $event_name;
+ $this->called = false;
}
/**
@@ -136,7 +144,7 @@ abstract class Base implements Listener
*/
public function __get($name)
{
- return Tool::loadModel($this->container, $name);
+ return $this->container[$name];
}
/**
@@ -183,7 +191,6 @@ abstract class Base implements Listener
* Check if the event is compatible with the action
*
* @access public
- * @param array $data Event data dictionary
* @return bool
*/
public function hasCompatibleEvent()
@@ -225,12 +232,20 @@ abstract class Base implements Listener
* Execute the action
*
* @access public
- * @param array $data Event data dictionary
- * @return bool True if the action was executed or false when not executed
+ * @param \Event\GenericEvent $event Event data dictionary
+ * @return bool True if the action was executed or false when not executed
*/
- public function execute(array $data)
+ public function execute(GenericEvent $event)
{
+ // Avoid infinite loop, a listener instance can be called only one time
+ if ($this->called) {
+ return false;
+ }
+
+ $data = $event->getAll();
+
if ($this->isExecutable($data)) {
+ $this->called = true;
return $this->doAction($data);
}
diff --git a/sources/app/Action/CommentCreation.php b/sources/app/Action/CommentCreation.php
index 5dbe32f..54d7be7 100644
--- a/sources/app/Action/CommentCreation.php
+++ b/sources/app/Action/CommentCreation.php
@@ -2,7 +2,7 @@
namespace Action;
-use Model\GithubWebhook;
+use Integration\GithubWebhook;
/**
* Create automatically a comment from a webhook
@@ -16,7 +16,7 @@ class CommentCreation extends Base
* Get the list of compatible events
*
* @access public
- * @return array
+ * @return string[]
*/
public function getCompatibleEvents()
{
@@ -29,7 +29,7 @@ class CommentCreation extends Base
* Get the required parameter for the action (defined by the user)
*
* @access public
- * @return array
+ * @return string[]
*/
public function getActionRequiredParameters()
{
diff --git a/sources/app/Action/TaskAssignCategoryColor.php b/sources/app/Action/TaskAssignCategoryColor.php
index 4134b58..ba319a1 100644
--- a/sources/app/Action/TaskAssignCategoryColor.php
+++ b/sources/app/Action/TaskAssignCategoryColor.php
@@ -67,7 +67,7 @@ class TaskAssignCategoryColor extends Base
'category_id' => $this->getParam('category_id'),
);
- return $this->taskModification->update($values, false);
+ return $this->taskModification->update($values);
}
/**
diff --git a/sources/app/Action/TaskAssignCategoryLabel.php b/sources/app/Action/TaskAssignCategoryLabel.php
index da41a31..1383d49 100644
--- a/sources/app/Action/TaskAssignCategoryLabel.php
+++ b/sources/app/Action/TaskAssignCategoryLabel.php
@@ -2,7 +2,7 @@
namespace Action;
-use Model\GithubWebhook;
+use Integration\GithubWebhook;
/**
* Set a category automatically according to a label
@@ -67,7 +67,7 @@ class TaskAssignCategoryLabel extends Base
'category_id' => isset($data['category_id']) ? $data['category_id'] : $this->getParam('category_id'),
);
- return $this->taskModification->update($values, false);
+ return $this->taskModification->update($values);
}
/**
diff --git a/sources/app/Action/TaskAssignColorCategory.php b/sources/app/Action/TaskAssignColorCategory.php
index 68bca5d..a362c68 100644
--- a/sources/app/Action/TaskAssignColorCategory.php
+++ b/sources/app/Action/TaskAssignColorCategory.php
@@ -67,7 +67,7 @@ class TaskAssignColorCategory extends Base
'color_id' => $this->getParam('color_id'),
);
- return $this->taskModification->update($values, false);
+ return $this->taskModification->update($values);
}
/**
diff --git a/sources/app/Action/TaskAssignColorUser.php b/sources/app/Action/TaskAssignColorUser.php
index d419ab4..6161514 100644
--- a/sources/app/Action/TaskAssignColorUser.php
+++ b/sources/app/Action/TaskAssignColorUser.php
@@ -68,7 +68,7 @@ class TaskAssignColorUser extends Base
'color_id' => $this->getParam('color_id'),
);
- return $this->taskModification->update($values, false);
+ return $this->taskModification->update($values);
}
/**
diff --git a/sources/app/Action/TaskAssignCurrentUser.php b/sources/app/Action/TaskAssignCurrentUser.php
index 9317bf8..ff3aaee 100644
--- a/sources/app/Action/TaskAssignCurrentUser.php
+++ b/sources/app/Action/TaskAssignCurrentUser.php
@@ -62,12 +62,16 @@ class TaskAssignCurrentUser extends Base
*/
public function doAction(array $data)
{
+ if (! $this->userSession->isLogged()) {
+ return false;
+ }
+
$values = array(
'id' => $data['task_id'],
- 'owner_id' => $this->acl->getUserId(),
+ 'owner_id' => $this->userSession->getId(),
);
- return $this->taskModification->update($values, false);
+ return $this->taskModification->update($values);
}
/**
diff --git a/sources/app/Action/TaskAssignSpecificUser.php b/sources/app/Action/TaskAssignSpecificUser.php
index c3b979c..4c96f7f 100644
--- a/sources/app/Action/TaskAssignSpecificUser.php
+++ b/sources/app/Action/TaskAssignSpecificUser.php
@@ -68,7 +68,7 @@ class TaskAssignSpecificUser extends Base
'owner_id' => $this->getParam('user_id'),
);
- return $this->taskModification->update($values, false);
+ return $this->taskModification->update($values);
}
/**
diff --git a/sources/app/Action/TaskAssignUser.php b/sources/app/Action/TaskAssignUser.php
index d01c407..cf2a9a4 100644
--- a/sources/app/Action/TaskAssignUser.php
+++ b/sources/app/Action/TaskAssignUser.php
@@ -2,7 +2,7 @@
namespace Action;
-use Model\GithubWebhook;
+use Integration\GithubWebhook;
/**
* Assign a task to someone
@@ -64,7 +64,7 @@ class TaskAssignUser extends Base
'owner_id' => $data['owner_id'],
);
- return $this->taskModification->update($values, false);
+ return $this->taskModification->update($values);
}
/**
diff --git a/sources/app/Action/TaskClose.php b/sources/app/Action/TaskClose.php
index 6cf9be0..760dfd8 100644
--- a/sources/app/Action/TaskClose.php
+++ b/sources/app/Action/TaskClose.php
@@ -2,7 +2,8 @@
namespace Action;
-use Model\GithubWebhook;
+use Integration\GitlabWebhook;
+use Integration\GithubWebhook;
use Model\Task;
/**
@@ -25,6 +26,8 @@ class TaskClose extends Base
Task::EVENT_MOVE_COLUMN,
GithubWebhook::EVENT_COMMIT,
GithubWebhook::EVENT_ISSUE_CLOSED,
+ GitlabWebhook::EVENT_COMMIT,
+ GitlabWebhook::EVENT_ISSUE_CLOSED,
);
}
@@ -39,6 +42,8 @@ class TaskClose extends Base
switch ($this->event_name) {
case GithubWebhook::EVENT_COMMIT:
case GithubWebhook::EVENT_ISSUE_CLOSED:
+ case GitlabWebhook::EVENT_COMMIT:
+ case GitlabWebhook::EVENT_ISSUE_CLOSED:
return array();
default:
return array('column_id' => t('Column'));
@@ -56,6 +61,8 @@ class TaskClose extends Base
switch ($this->event_name) {
case GithubWebhook::EVENT_COMMIT:
case GithubWebhook::EVENT_ISSUE_CLOSED:
+ case GitlabWebhook::EVENT_COMMIT:
+ case GitlabWebhook::EVENT_ISSUE_CLOSED:
return array('task_id');
default:
return array('task_id', 'column_id');
@@ -86,6 +93,8 @@ class TaskClose extends Base
switch ($this->event_name) {
case GithubWebhook::EVENT_COMMIT:
case GithubWebhook::EVENT_ISSUE_CLOSED:
+ case GitlabWebhook::EVENT_COMMIT:
+ case GitlabWebhook::EVENT_ISSUE_CLOSED:
return true;
default:
return $data['column_id'] == $this->getParam('column_id');
diff --git a/sources/app/Action/TaskCreation.php b/sources/app/Action/TaskCreation.php
index 0c79168..1c093ee 100644
--- a/sources/app/Action/TaskCreation.php
+++ b/sources/app/Action/TaskCreation.php
@@ -2,7 +2,8 @@
namespace Action;
-use Model\GithubWebhook;
+use Integration\GithubWebhook;
+use Integration\GitlabWebhook;
/**
* Create automatically a task from a webhook
@@ -22,6 +23,7 @@ class TaskCreation extends Base
{
return array(
GithubWebhook::EVENT_ISSUE_OPENED,
+ GitlabWebhook::EVENT_ISSUE_OPENED,
);
}
@@ -63,7 +65,7 @@ class TaskCreation extends Base
'project_id' => $data['project_id'],
'title' => $data['title'],
'reference' => $data['reference'],
- 'description' => $data['description'],
+ 'description' => isset($data['description']) ? $data['description'] : '',
));
}
diff --git a/sources/app/Action/TaskOpen.php b/sources/app/Action/TaskOpen.php
index fc29e9e..73f1fad 100644
--- a/sources/app/Action/TaskOpen.php
+++ b/sources/app/Action/TaskOpen.php
@@ -2,7 +2,7 @@
namespace Action;
-use Model\GithubWebhook;
+use Integration\GithubWebhook;
/**
* Open automatically a task
diff --git a/sources/app/Auth/Base.php b/sources/app/Auth/Base.php
index 9633af4..e023e4f 100644
--- a/sources/app/Auth/Base.php
+++ b/sources/app/Auth/Base.php
@@ -2,7 +2,6 @@
namespace Auth;
-use Core\Tool;
use Pimple\Container;
/**
@@ -14,6 +13,7 @@ use Pimple\Container;
* @property \Model\Acl $acl
* @property \Model\LastLogin $lastLogin
* @property \Model\User $user
+ * @property \Model\UserSession $userSession
*/
abstract class Base
{
@@ -54,6 +54,6 @@ abstract class Base
*/
public function __get($name)
{
- return Tool::loadModel($this->container, $name);
+ return $this->container[$name];
}
}
diff --git a/sources/app/Auth/Database.php b/sources/app/Auth/Database.php
index 47dc8e6..2804b9a 100644
--- a/sources/app/Auth/Database.php
+++ b/sources/app/Auth/Database.php
@@ -3,7 +3,7 @@
namespace Auth;
use Model\User;
-use Core\Request;
+use Event\AuthEvent;
/**
* Database authentication
@@ -33,18 +33,8 @@ class Database extends Base
$user = $this->db->table(User::TABLE)->eq('username', $username)->eq('is_ldap_user', 0)->findOne();
if ($user && password_verify($password, $user['password'])) {
-
- // Update user session
- $this->user->updateSession($user);
-
- // Update login history
- $this->lastLogin->create(
- self::AUTH_NAME,
- $user['id'],
- Request::getIpAddress(),
- Request::getUserAgent()
- );
-
+ $this->userSession->refresh($user);
+ $this->container['dispatcher']->dispatch('auth.success', new AuthEvent(self::AUTH_NAME, $user['id']));
return true;
}
diff --git a/sources/app/Auth/GitHub.php b/sources/app/Auth/GitHub.php
index 034f926..0e335fb 100644
--- a/sources/app/Auth/GitHub.php
+++ b/sources/app/Auth/GitHub.php
@@ -2,7 +2,7 @@
namespace Auth;
-use Core\Request;
+use Event\AuthEvent;
use OAuth\Common\Storage\Session;
use OAuth\Common\Consumer\Credentials;
use OAuth\Common\Http\Uri\UriFactory;
@@ -35,18 +35,8 @@ class GitHub extends Base
$user = $this->user->getByGitHubId($github_id);
if ($user) {
-
- // Create the user session
- $this->user->updateSession($user);
-
- // Update login history
- $this->lastLogin->create(
- self::AUTH_NAME,
- $user['id'],
- Request::getIpAddress(),
- Request::getUserAgent()
- );
-
+ $this->userSession->refresh($user);
+ $this->container['dispatcher']->dispatch('auth.success', new AuthEvent(self::AUTH_NAME, $user['id']));
return true;
}
diff --git a/sources/app/Auth/Google.php b/sources/app/Auth/Google.php
index 587ecde..e7abae0 100644
--- a/sources/app/Auth/Google.php
+++ b/sources/app/Auth/Google.php
@@ -2,7 +2,7 @@
namespace Auth;
-use Core\Request;
+use Event\AuthEvent;
use OAuth\Common\Storage\Session;
use OAuth\Common\Consumer\Credentials;
use OAuth\Common\Http\Uri\UriFactory;
@@ -36,18 +36,8 @@ class Google extends Base
$user = $this->user->getByGoogleId($google_id);
if ($user) {
-
- // Create the user session
- $this->user->updateSession($user);
-
- // Update login history
- $this->lastLogin->create(
- self::AUTH_NAME,
- $user['id'],
- Request::getIpAddress(),
- Request::getUserAgent()
- );
-
+ $this->userSession->refresh($user);
+ $this->container['dispatcher']->dispatch('auth.success', new AuthEvent(self::AUTH_NAME, $user['id']));
return true;
}
diff --git a/sources/app/Auth/Ldap.php b/sources/app/Auth/Ldap.php
index 82307e8..b344061 100644
--- a/sources/app/Auth/Ldap.php
+++ b/sources/app/Auth/Ldap.php
@@ -2,7 +2,7 @@
namespace Auth;
-use Core\Request;
+use Event\AuthEvent;
/**
* LDAP model
@@ -54,15 +54,8 @@ class Ldap extends Base
}
// We open the session
- $this->user->updateSession($user);
-
- // Update login history
- $this->lastLogin->create(
- self::AUTH_NAME,
- $user['id'],
- Request::getIpAddress(),
- Request::getUserAgent()
- );
+ $this->userSession->refresh($user);
+ $this->container['dispatcher']->dispatch('auth.success', new AuthEvent(self::AUTH_NAME, $user['id']));
return true;
}
diff --git a/sources/app/Auth/RememberMe.php b/sources/app/Auth/RememberMe.php
index cb8a9b4..4736442 100644
--- a/sources/app/Auth/RememberMe.php
+++ b/sources/app/Auth/RememberMe.php
@@ -3,6 +3,7 @@
namespace Auth;
use Core\Request;
+use Event\AuthEvent;
use Core\Security;
/**
@@ -100,15 +101,11 @@ class RememberMe extends Base
);
// Create the session
- $this->user->updateSession($this->user->getById($record['user_id']));
- $this->acl->isRememberMe(true);
+ $this->userSession->refresh($this->user->getById($record['user_id']));
- // Update last login infos
- $this->lastLogin->create(
- self::AUTH_NAME,
- $this->acl->getUserId(),
- Request::getIpAddress(),
- Request::getUserAgent()
+ $this->container['dispatcher']->dispatch(
+ 'auth.success',
+ new AuthEvent(self::AUTH_NAME, $this->userSession->getId())
);
return true;
diff --git a/sources/app/Auth/ReverseProxy.php b/sources/app/Auth/ReverseProxy.php
index 5aca881..b84550c 100644
--- a/sources/app/Auth/ReverseProxy.php
+++ b/sources/app/Auth/ReverseProxy.php
@@ -2,8 +2,7 @@
namespace Auth;
-use Core\Request;
-use Core\Security;
+use Event\AuthEvent;
/**
* ReverseProxy backend
@@ -38,16 +37,8 @@ class ReverseProxy extends Base
$user = $this->user->getByUsername($login);
}
- // Create the user session
- $this->user->updateSession($user);
-
- // Update login history
- $this->lastLogin->create(
- self::AUTH_NAME,
- $user['id'],
- Request::getIpAddress(),
- Request::getUserAgent()
- );
+ $this->userSession->refresh($user);
+ $this->container['dispatcher']->dispatch('auth.success', new AuthEvent(self::AUTH_NAME, $user['id']));
return true;
}
diff --git a/sources/app/Console/Base.php b/sources/app/Console/Base.php
index f955b42..aeafbef 100644
--- a/sources/app/Console/Base.php
+++ b/sources/app/Console/Base.php
@@ -2,7 +2,6 @@
namespace Console;
-use Core\Tool;
use Pimple\Container;
use Symfony\Component\Console\Command\Command;
@@ -17,6 +16,7 @@ use Symfony\Component\Console\Command\Command;
* @property \Model\ProjectPermission $projectPermission
* @property \Model\ProjectAnalytic $projectAnalytic
* @property \Model\ProjectDailySummary $projectDailySummary
+ * @property \Model\SubtaskExport $subtaskExport
* @property \Model\Task $task
* @property \Model\TaskExport $taskExport
* @property \Model\TaskFinder $taskFinder
@@ -52,6 +52,6 @@ abstract class Base extends Command
*/
public function __get($name)
{
- return Tool::loadModel($this->container, $name);
+ return $this->container[$name];
}
}
diff --git a/sources/app/Console/ProjectDailySummaryCalculation.php b/sources/app/Console/ProjectDailySummaryCalculation.php
index 04c4083..b2ada1b 100644
--- a/sources/app/Console/ProjectDailySummaryCalculation.php
+++ b/sources/app/Console/ProjectDailySummaryCalculation.php
@@ -3,9 +3,7 @@
namespace Console;
use Model\Project;
-use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
-use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class ProjectDailySummaryCalculation extends Base
diff --git a/sources/app/Console/ProjectDailySummaryExport.php b/sources/app/Console/ProjectDailySummaryExport.php
index 6b96fdd..07841d5 100644
--- a/sources/app/Console/ProjectDailySummaryExport.php
+++ b/sources/app/Console/ProjectDailySummaryExport.php
@@ -5,7 +5,6 @@ namespace Console;
use Core\Tool;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
-use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class ProjectDailySummaryExport extends Base
diff --git a/sources/app/Console/SubtaskExport.php b/sources/app/Console/SubtaskExport.php
new file mode 100644
index 0000000..167a922
--- /dev/null
+++ b/sources/app/Console/SubtaskExport.php
@@ -0,0 +1,34 @@
+setName('export:subtasks')
+ ->setDescription('Subtasks CSV export')
+ ->addArgument('project_id', InputArgument::REQUIRED, 'Project id')
+ ->addArgument('start_date', InputArgument::REQUIRED, 'Start date (YYYY-MM-DD)')
+ ->addArgument('end_date', InputArgument::REQUIRED, 'End date (YYYY-MM-DD)');
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $data = $this->subtaskExport->export(
+ $input->getArgument('project_id'),
+ $input->getArgument('start_date'),
+ $input->getArgument('end_date')
+ );
+
+ if (is_array($data)) {
+ Tool::csv($data);
+ }
+ }
+}
diff --git a/sources/app/Console/TaskExport.php b/sources/app/Console/TaskExport.php
index dea71fe..2ecd45e 100644
--- a/sources/app/Console/TaskExport.php
+++ b/sources/app/Console/TaskExport.php
@@ -5,7 +5,6 @@ namespace Console;
use Core\Tool;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
-use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class TaskExport extends Base
diff --git a/sources/app/Console/TaskOverdueNotification.php b/sources/app/Console/TaskOverdueNotification.php
index aa70fd0..86a7d1b 100644
--- a/sources/app/Console/TaskOverdueNotification.php
+++ b/sources/app/Console/TaskOverdueNotification.php
@@ -3,7 +3,6 @@
namespace Console;
use Symfony\Component\Console\Helper\Table;
-use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
diff --git a/sources/app/Controller/Action.php b/sources/app/Controller/Action.php
index 22358cb..2b58dca 100644
--- a/sources/app/Controller/Action.php
+++ b/sources/app/Controller/Action.php
@@ -17,7 +17,7 @@ class Action extends Base
*/
public function index()
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
$this->response->html($this->projectLayout('action/index', array(
'values' => array('project_id' => $project['id']),
@@ -42,7 +42,7 @@ class Action extends Base
*/
public function event()
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
$values = $this->request->getValues();
if (empty($values['action_name']) || empty($values['project_id'])) {
@@ -64,7 +64,7 @@ class Action extends Base
*/
public function params()
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
$values = $this->request->getValues();
if (empty($values['action_name']) || empty($values['project_id']) || empty($values['event_name'])) {
@@ -101,7 +101,7 @@ class Action extends Base
*/
public function create()
{
- $this->doCreation($this->getProjectManagement(), $this->request->getValues());
+ $this->doCreation($this->getProject(), $this->request->getValues());
}
/**
@@ -135,7 +135,7 @@ class Action extends Base
*/
public function confirm()
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
$this->response->html($this->projectLayout('action/remove', array(
'action' => $this->action->getById($this->request->getIntegerParam('action_id')),
@@ -154,7 +154,7 @@ class Action extends Base
public function remove()
{
$this->checkCSRFParam();
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
$action = $this->action->getById($this->request->getIntegerParam('action_id'));
if ($action && $this->action->remove($action['id'])) {
diff --git a/sources/app/Controller/Analytic.php b/sources/app/Controller/Analytic.php
index 6c49089..8b0684d 100644
--- a/sources/app/Controller/Analytic.php
+++ b/sources/app/Controller/Analytic.php
@@ -20,8 +20,8 @@ class Analytic extends Base
*/
private function layout($template, array $params)
{
- $params['board_selector'] = $this->projectPermission->getAllowedProjects($this->acl->getUserId());
- $params['analytic_content_for_layout'] = $this->template->load($template, $params);
+ $params['board_selector'] = $this->projectPermission->getAllowedProjects($this->userSession->getId());
+ $params['analytic_content_for_layout'] = $this->template->render($template, $params);
return $this->template->layout('analytic/layout', $params);
}
diff --git a/sources/app/Controller/App.php b/sources/app/Controller/App.php
index c88fd92..aa2673a 100644
--- a/sources/app/Controller/App.php
+++ b/sources/app/Controller/App.php
@@ -2,9 +2,7 @@
namespace Controller;
-use Model\Project as ProjectModel;
use Model\SubTask as SubTaskModel;
-use Helper;
/**
* Application controller
@@ -36,7 +34,7 @@ class App extends Base
$direction = $this->request->getStringParam('direction');
$order = $this->request->getStringParam('order');
- $user_id = $this->acl->getUserId();
+ $user_id = $this->userSession->getId();
$projects = $this->projectPermission->getMemberProjects($user_id);
$project_ids = array_keys($projects);
@@ -57,6 +55,11 @@ class App extends Base
* Get tasks pagination
*
* @access public
+ * @param integer $user_id
+ * @param string $paginate
+ * @param integer $offset
+ * @param string $order
+ * @param string $direction
*/
private function getTaskPagination($user_id, $paginate, $offset, $order, $direction)
{
@@ -94,6 +97,11 @@ class App extends Base
* Get subtasks pagination
*
* @access public
+ * @param integer $user_id
+ * @param string $paginate
+ * @param integer $offset
+ * @param string $order
+ * @param string $direction
*/
private function getSubtaskPagination($user_id, $paginate, $offset, $order, $direction)
{
@@ -132,10 +140,15 @@ class App extends Base
* Get projects pagination
*
* @access public
+ * @param array $project_ids
+ * @param string $paginate
+ * @param integer $offset
+ * @param string $order
+ * @param string $direction
*/
- private function getProjectPagination($project_ids, $paginate, $offset, $order, $direction)
+ private function getProjectPagination(array $project_ids, $paginate, $offset, $order, $direction)
{
- $limit = 5;
+ $limit = 10;
if (! in_array($order, array('id', 'name'))) {
$order = 'name';
@@ -178,8 +191,9 @@ class App extends Base
$this->response->html('
'.t('Nothing to preview...').'
');
}
else {
- $this->response->html(Helper\markdown($payload['text']));
+ $this->response->html(
+ $this->template->markdown($payload['text'])
+ );
}
}
-
}
diff --git a/sources/app/Controller/Base.php b/sources/app/Controller/Base.php
index 5027cf3..8a5354a 100644
--- a/sources/app/Controller/Base.php
+++ b/sources/app/Controller/Base.php
@@ -3,13 +3,13 @@
namespace Controller;
use Pimple\Container;
-use Core\Tool;
use Core\Security;
use Core\Request;
use Core\Response;
use Core\Template;
use Core\Session;
use Model\LastLogin;
+use Symfony\Component\EventDispatcher\Event;
/**
* Base controller
@@ -17,6 +17,8 @@ use Model\LastLogin;
* @package controller
* @author Frederic Guillot
*
+ * @property \Core\Session $session
+ * @property \Core\Template $template
* @property \Model\Acl $acl
* @property \Model\Authentication $authentication
* @property \Model\Action $action
@@ -49,6 +51,7 @@ use Model\LastLogin;
* @property \Model\SubtaskHistory $subtaskHistory
* @property \Model\TimeTracking $timeTracking
* @property \Model\User $user
+ * @property \Model\UserSession $userSession
* @property \Model\Webhook $webhook
*/
abstract class Base
@@ -69,22 +72,6 @@ abstract class Base
*/
protected $response;
- /**
- * Template instance
- *
- * @accesss protected
- * @var \Core\Template
- */
- protected $template;
-
- /**
- * Session instance
- *
- * @accesss public
- * @var \Core\Session
- */
- protected $session;
-
/**
* Container instance
*
@@ -104,8 +91,6 @@ abstract class Base
$this->container = $container;
$this->request = new Request;
$this->response = new Response;
- $this->session = new Session;
- $this->template = new Template;
}
/**
@@ -115,9 +100,15 @@ abstract class Base
*/
public function __destruct()
{
- // foreach ($this->container['db']->getLogMessages() as $message) {
- // $this->container['logger']->addDebug($message);
- // }
+ if (DEBUG) {
+
+ foreach ($this->container['db']->getLogMessages() as $message) {
+ $this->container['logger']->debug($message);
+ }
+
+ $this->container['logger']->debug('SQL_QUERIES={nb}', array('nb' => $this->container['db']->nb_queries));
+ $this->container['logger']->debug('RENDERING={time}', array('time' => microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']));
+ }
}
/**
@@ -129,19 +120,16 @@ abstract class Base
*/
public function __get($name)
{
- return Tool::loadModel($this->container, $name);
+ return $this->container[$name];
}
/**
- * Method executed before each action
+ * Send HTTP headers
*
- * @access public
+ * @access private
*/
- public function beforeAction($controller, $action)
+ private function sendHeaders($action)
{
- // Start the session
- $this->session->open(BASE_URL_DIRECTORY);
-
// HTTP secure headers
$this->response->csp(array('style-src' => "'self' 'unsafe-inline'"));
$this->response->nosniff();
@@ -155,12 +143,33 @@ abstract class Base
if (ENABLE_HSTS) {
$this->response->hsts();
}
+ }
- $this->config->setupTranslations();
- $this->config->setupTimezone();
+ /**
+ * Method executed before each action
+ *
+ * @access public
+ */
+ public function beforeAction($controller, $action)
+ {
+ // Start the session
+ $this->session->open(BASE_URL_DIRECTORY);
+ $this->sendHeaders($action);
+ $this->container['dispatcher']->dispatch('session.bootstrap', new Event);
- // Authentication
- if (! $this->authentication->isAuthenticated($controller, $action)) {
+ if (! $this->acl->isPublicAction($controller, $action)) {
+ $this->handleAuthenticatedUser($controller, $action);
+ }
+ }
+
+ /**
+ * Check page access and authentication
+ *
+ * @access public
+ */
+ public function handleAuthenticatedUser($controller, $action)
+ {
+ if (! $this->authentication->isAuthenticated()) {
if ($this->request->isAjax()) {
$this->response->text('Not Authorized', 401);
@@ -169,33 +178,8 @@ abstract class Base
$this->response->redirect('?controller=user&action=login&redirect_query='.urlencode($this->request->getQueryString()));
}
- // Check if the user is allowed to see this page
- if (! $this->acl->isPageAccessAllowed($controller, $action)) {
- $this->response->redirect('?controller=user&action=forbidden');
- }
-
- // Attach events
- $this->attachEvents();
- }
-
- /**
- * Attach events
- *
- * @access private
- */
- private function attachEvents()
- {
- $models = array(
- 'projectActivity', // Order is important
- 'projectDailySummary',
- 'action',
- 'project',
- 'webhook',
- 'notification',
- );
-
- foreach ($models as $model) {
- $this->$model->attachEvents();
+ if (! $this->acl->isAllowed($controller, $action, $this->request->getIntegerParam('project_id', 0))) {
+ $this->forbidden();
}
}
@@ -239,19 +223,6 @@ abstract class Base
}
}
- /**
- * Check if the current user have access to the given project
- *
- * @access protected
- * @param integer $project_id Project id
- */
- protected function checkProjectPermissions($project_id)
- {
- if ($this->acl->isRegularUser() && ! $this->projectPermission->isUserAllowed($project_id, $this->acl->getUserId())) {
- $this->forbidden();
- }
- }
-
/**
* Redirection when there is no project in the database
*
@@ -273,14 +244,10 @@ abstract class Base
*/
protected function taskLayout($template, array $params)
{
- if (isset($params['task']) && $this->taskPermission->canRemoveTask($params['task']) === false) {
- $params['hide_remove_menu'] = true;
- }
-
- $content = $this->template->load($template, $params);
+ $content = $this->template->render($template, $params);
$params['task_content_for_layout'] = $content;
$params['title'] = $params['task']['project_name'].' > '.$params['task']['title'];
- $params['board_selector'] = $this->projectPermission->getAllowedProjects($this->acl->getUserId());
+ $params['board_selector'] = $this->projectPermission->getAllowedProjects($this->userSession->getId());
return $this->template->layout('task/layout', $params);
}
@@ -295,10 +262,10 @@ abstract class Base
*/
protected function projectLayout($template, array $params)
{
- $content = $this->template->load($template, $params);
+ $content = $this->template->render($template, $params);
$params['project_content_for_layout'] = $content;
$params['title'] = $params['project']['name'] === $params['title'] ? $params['title'] : $params['project']['name'].' > '.$params['title'];
- $params['board_selector'] = $this->projectPermission->getAllowedProjects($this->acl->getUserId());
+ $params['board_selector'] = $this->projectPermission->getAllowedProjects($this->userSession->getId());
return $this->template->layout('project/layout', $params);
}
@@ -313,12 +280,10 @@ abstract class Base
{
$task = $this->taskFinder->getDetails($this->request->getIntegerParam('task_id'));
- if (! $task) {
+ if (! $task || $task['project_id'] != $this->request->getIntegerParam('project_id')) {
$this->notfound();
}
- $this->checkProjectPermissions($task['project_id']);
-
return $task;
}
@@ -339,29 +304,6 @@ abstract class Base
$this->response->redirect('?controller=project');
}
- $this->checkProjectPermissions($project['id']);
-
- return $project;
- }
-
- /**
- * Common method to get a project with administration rights
- *
- * @access protected
- * @return array
- */
- protected function getProjectManagement()
- {
- $project = $this->project->getById($this->request->getIntegerParam('project_id'));
-
- if (! $project) {
- $this->notfound();
- }
-
- if ($this->acl->isRegularUser() && ! $this->projectPermission->adminAllowed($project['id'], $this->acl->getUserId())) {
- $this->forbidden();
- }
-
return $project;
}
}
diff --git a/sources/app/Controller/Board.php b/sources/app/Controller/Board.php
index 7d498f8..48f2b51 100644
--- a/sources/app/Controller/Board.php
+++ b/sources/app/Controller/Board.php
@@ -2,10 +2,6 @@
namespace Controller;
-use Model\Project as ProjectModel;
-use Model\User as UserModel;
-use Core\Security;
-
/**
* Board controller
*
@@ -22,7 +18,7 @@ class Board extends Base
public function moveColumn()
{
$this->checkCSRFParam();
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
$column_id = $this->request->getIntegerParam('column_id');
$direction = $this->request->getStringParam('direction');
@@ -43,7 +39,7 @@ class Board extends Base
$task = $this->getTask();
$project = $this->project->getById($task['project_id']);
- $this->response->html($this->template->load('board/assignee', array(
+ $this->response->html($this->template->render('board/assignee', array(
'values' => $task,
'users_list' => $this->projectPermission->getMemberList($project['id']),
'project' => $project,
@@ -58,7 +54,6 @@ class Board extends Base
public function updateAssignee()
{
$values = $this->request->getValues();
- $this->checkProjectPermissions($values['project_id']);
list($valid,) = $this->taskValidator->validateAssigneeModification($values);
@@ -82,7 +77,7 @@ class Board extends Base
$task = $this->getTask();
$project = $this->project->getById($task['project_id']);
- $this->response->html($this->template->load('board/category', array(
+ $this->response->html($this->template->render('board/category', array(
'values' => $task,
'categories_list' => $this->category->getList($project['id']),
'project' => $project,
@@ -97,7 +92,6 @@ class Board extends Base
public function updateCategory()
{
$values = $this->request->getValues();
- $this->checkProjectPermissions($values['project_id']);
list($valid,) = $this->taskValidator->validateCategoryModification($values);
@@ -130,12 +124,14 @@ class Board extends Base
// Display the board with a specific layout
$this->response->html($this->template->layout('board/public', array(
'project' => $project,
- 'columns' => $this->board->get($project['id']),
+ 'swimlanes' => $this->board->getBoard($project['id']),
'categories' => $this->category->getList($project['id'], false),
'title' => $project['name'],
'no_layout' => true,
'not_editable' => true,
'board_public_refresh_interval' => $this->config->get('board_public_refresh_interval'),
+ 'board_private_refresh_interval' => $this->config->get('board_private_refresh_interval'),
+ 'board_highlight_period' => $this->config->get('board_highlight_period'),
)));
}
@@ -146,16 +142,16 @@ class Board extends Base
*/
public function index()
{
- $last_seen_project_id = $this->user->getLastSeenProjectId();
- $favorite_project_id = $this->user->getFavoriteProjectId();
+ $last_seen_project_id = $this->userSession->getLastSeenProjectId();
+ $favorite_project_id = $this->userSession->getFavoriteProjectId();
$project_id = $last_seen_project_id ?: $favorite_project_id;
if (! $project_id) {
- $projects = $this->projectPermission->getAllowedProjects($this->acl->getUserId());
+ $projects = $this->projectPermission->getAllowedProjects($this->userSession->getId());
if (empty($projects)) {
- if ($this->acl->isAdminUser()) {
+ if ($this->userSession->isAdmin()) {
$this->redirectNoProject();
}
@@ -177,18 +173,18 @@ class Board extends Base
public function show($project_id = 0)
{
$project = $this->getProject($project_id);
- $projects = $this->projectPermission->getAllowedProjects($this->acl->getUserId());
+ $projects = $this->projectPermission->getAllowedProjects($this->userSession->getId());
$board_selector = $projects;
unset($board_selector[$project['id']]);
- $this->user->storeLastSeenProjectId($project['id']);
+ $this->userSession->storeLastSeenProjectId($project['id']);
$this->response->html($this->template->layout('board/index', array(
'users' => $this->projectPermission->getMemberList($project['id'], true, true),
'projects' => $projects,
'project' => $project,
- 'board' => $this->board->get($project['id']),
+ 'swimlanes' => $this->board->getBoard($project['id']),
'categories' => $this->category->getList($project['id'], true, true),
'title' => $project['name'],
'board_selector' => $board_selector,
@@ -202,11 +198,10 @@ class Board extends Base
*
* @access public
*/
- public function edit()
+ public function edit(array $values = array(), array $errors = array())
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
$columns = $this->board->getColumns($project['id']);
- $values = array();
foreach ($columns as $column) {
$values['title['.$column['id'].']'] = $column['title'];
@@ -214,7 +209,7 @@ class Board extends Base
}
$this->response->html($this->projectLayout('board/edit', array(
- 'errors' => array(),
+ 'errors' => $errors,
'values' => $values + array('project_id' => $project['id']),
'columns' => $columns,
'project' => $project,
@@ -229,7 +224,7 @@ class Board extends Base
*/
public function update()
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
$columns = $this->board->getColumns($project['id']);
$data = $this->request->getValues();
$values = $columns_list = array();
@@ -253,13 +248,7 @@ class Board extends Base
}
}
- $this->response->html($this->projectLayout('board/edit', array(
- 'errors' => $errors,
- 'values' => $values + array('project_id' => $project['id']),
- 'columns' => $columns,
- 'project' => $project,
- 'title' => t('Edit board')
- )));
+ $this->edit($values, $errors);
}
/**
@@ -269,7 +258,7 @@ class Board extends Base
*/
public function add()
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
$columns = $this->board->getColumnsList($project['id']);
$data = $this->request->getValues();
$values = array();
@@ -291,13 +280,7 @@ class Board extends Base
}
}
- $this->response->html($this->projectLayout('board/edit', array(
- 'errors' => $errors,
- 'values' => $values + $data,
- 'columns' => $columns,
- 'project' => $project,
- 'title' => t('Edit board')
- )));
+ $this->edit($values, $errors);
}
/**
@@ -307,7 +290,7 @@ class Board extends Base
*/
public function remove()
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
if ($this->request->getStringParam('remove') === 'yes') {
@@ -339,35 +322,38 @@ class Board extends Base
{
$project_id = $this->request->getIntegerParam('project_id');
- if ($project_id > 0 && $this->request->isAjax()) {
-
- if (! $this->projectPermission->isUserAllowed($project_id, $this->acl->getUserId())) {
- $this->response->text('Forbidden', 403);
- }
-
- $values = $this->request->getJson();
-
- if ($this->taskPosition->movePosition($project_id, $values['task_id'], $values['column_id'], $values['position'])) {
-
- $this->response->html(
- $this->template->load('board/show', array(
- 'project' => $this->project->getById($project_id),
- 'board' => $this->board->get($project_id),
- 'categories' => $this->category->getList($project_id, false),
- 'board_private_refresh_interval' => $this->config->get('board_private_refresh_interval'),
- 'board_highlight_period' => $this->config->get('board_highlight_period'),
- )),
- 201
- );
- }
- else {
-
- $this->response->status(400);
- }
+ if (! $project_id || ! $this->request->isAjax()) {
+ return $this->response->status(403);
}
- else {
- $this->response->status(403);
+
+ if (! $this->projectPermission->isUserAllowed($project_id, $this->userSession->getId())) {
+ $this->response->text('Forbidden', 403);
}
+
+ $values = $this->request->getJson();
+
+ $result =$this->taskPosition->movePosition(
+ $project_id,
+ $values['task_id'],
+ $values['column_id'],
+ $values['position'],
+ $values['swimlane_id']
+ );
+
+ if (! $result) {
+ return $this->response->status(400);
+ }
+
+ $this->response->html(
+ $this->template->render('board/show', array(
+ 'project' => $this->project->getById($project_id),
+ 'swimlanes' => $this->board->getBoard($project_id),
+ 'categories' => $this->category->getList($project_id, false),
+ 'board_private_refresh_interval' => $this->config->get('board_private_refresh_interval'),
+ 'board_highlight_period' => $this->config->get('board_highlight_period'),
+ )),
+ 201
+ );
}
/**
@@ -377,33 +363,30 @@ class Board extends Base
*/
public function check()
{
- if ($this->request->isAjax()) {
-
- $project_id = $this->request->getIntegerParam('project_id');
- $timestamp = $this->request->getIntegerParam('timestamp');
-
- if ($project_id > 0 && ! $this->projectPermission->isUserAllowed($project_id, $this->acl->getUserId())) {
- $this->response->text('Forbidden', 403);
- }
-
- if ($this->project->isModifiedSince($project_id, $timestamp)) {
- $this->response->html(
- $this->template->load('board/show', array(
- 'project' => $this->project->getById($project_id),
- 'board' => $this->board->get($project_id),
- 'categories' => $this->category->getList($project_id, false),
- 'board_private_refresh_interval' => $this->config->get('board_private_refresh_interval'),
- 'board_highlight_period' => $this->config->get('board_highlight_period'),
- ))
- );
- }
- else {
- $this->response->status(304);
- }
+ if (! $this->request->isAjax()) {
+ return $this->response->status(403);
}
- else {
- $this->response->status(403);
+
+ $project_id = $this->request->getIntegerParam('project_id');
+ $timestamp = $this->request->getIntegerParam('timestamp');
+
+ if (! $this->projectPermission->isUserAllowed($project_id, $this->userSession->getId())) {
+ $this->response->text('Forbidden', 403);
}
+
+ if (! $this->project->isModifiedSince($project_id, $timestamp)) {
+ return $this->response->status(304);
+ }
+
+ $this->response->html(
+ $this->template->render('board/show', array(
+ 'project' => $this->project->getById($project_id),
+ 'swimlanes' => $this->board->getBoard($project_id),
+ 'categories' => $this->category->getList($project_id, false),
+ 'board_private_refresh_interval' => $this->config->get('board_private_refresh_interval'),
+ 'board_highlight_period' => $this->config->get('board_highlight_period'),
+ ))
+ );
}
/**
@@ -414,8 +397,9 @@ class Board extends Base
public function subtasks()
{
$task = $this->getTask();
- $this->response->html($this->template->load('board/subtasks', array(
- 'subtasks' => $this->subTask->getAll($task['id'])
+ $this->response->html($this->template->render('board/subtasks', array(
+ 'subtasks' => $this->subTask->getAll($task['id']),
+ 'task' => $task,
)));
}
@@ -429,8 +413,9 @@ class Board extends Base
$task = $this->getTask();
$this->subTask->toggleStatus($this->request->getIntegerParam('subtask_id'));
- $this->response->html($this->template->load('board/subtasks', array(
- 'subtasks' => $this->subTask->getAll($task['id'])
+ $this->response->html($this->template->render('board/subtasks', array(
+ 'subtasks' => $this->subTask->getAll($task['id']),
+ 'task' => $task,
)));
}
@@ -443,8 +428,9 @@ class Board extends Base
{
$task = $this->getTask();
- $this->response->html($this->template->load('board/files', array(
- 'files' => $this->file->getAll($task['id'])
+ $this->response->html($this->template->render('board/files', array(
+ 'files' => $this->file->getAll($task['id']),
+ 'task' => $task,
)));
}
@@ -457,7 +443,7 @@ class Board extends Base
{
$task = $this->getTask();
- $this->response->html($this->template->load('board/comments', array(
+ $this->response->html($this->template->render('board/comments', array(
'comments' => $this->comment->getAll($task['id'])
)));
}
@@ -471,7 +457,7 @@ class Board extends Base
{
$task = $this->getTask();
- $this->response->html($this->template->load('board/description', array(
+ $this->response->html($this->template->render('board/description', array(
'task' => $task
)));
}
diff --git a/sources/app/Controller/Category.php b/sources/app/Controller/Category.php
index 27c0d9f..68961a0 100644
--- a/sources/app/Controller/Category.php
+++ b/sources/app/Controller/Category.php
@@ -14,7 +14,7 @@ class Category extends Base
* Get the category (common method between actions)
*
* @access private
- * @param $project_id
+ * @param integer $project_id
* @return array
*/
private function getCategory($project_id)
@@ -36,7 +36,7 @@ class Category extends Base
*/
public function index(array $values = array(), array $errors = array())
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
$this->response->html($this->projectLayout('category/index', array(
'categories' => $this->category->getList($project['id'], false),
@@ -48,13 +48,13 @@ class Category extends Base
}
/**
- * Validate and save a new project
+ * Validate and save a new category
*
* @access public
*/
public function save()
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
$values = $this->request->getValues();
list($valid, $errors) = $this->category->validateCreation($values);
@@ -80,7 +80,7 @@ class Category extends Base
*/
public function edit(array $values = array(), array $errors = array())
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
$category = $this->getCategory($project['id']);
$this->response->html($this->projectLayout('category/edit', array(
@@ -98,7 +98,7 @@ class Category extends Base
*/
public function update()
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
$values = $this->request->getValues();
list($valid, $errors) = $this->category->validateModification($values);
@@ -124,7 +124,7 @@ class Category extends Base
*/
public function confirm()
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
$category = $this->getCategory($project['id']);
$this->response->html($this->projectLayout('category/remove', array(
@@ -142,7 +142,7 @@ class Category extends Base
public function remove()
{
$this->checkCSRFParam();
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
$category = $this->getCategory($project['id']);
if ($this->category->remove($category['id'])) {
diff --git a/sources/app/Controller/Comment.php b/sources/app/Controller/Comment.php
index fb21353..9796ea3 100644
--- a/sources/app/Controller/Comment.php
+++ b/sources/app/Controller/Comment.php
@@ -24,7 +24,7 @@ class Comment extends Base
$this->notfound();
}
- if (! $this->acl->isAdminUser() && $comment['user_id'] != $this->acl->getUserId()) {
+ if (! $this->userSession->isAdmin() && $comment['user_id'] != $this->userSession->getId()) {
$this->response->html($this->template->layout('comment/forbidden', array(
'title' => t('Access Forbidden')
)));
@@ -44,7 +44,7 @@ class Comment extends Base
if (empty($values)) {
$values = array(
- 'user_id' => $this->acl->getUserId(),
+ 'user_id' => $this->userSession->getId(),
'task_id' => $task['id'],
);
}
@@ -78,7 +78,7 @@ class Comment extends Base
$this->session->flashError(t('Unable to create your comment.'));
}
- $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'#comments');
+ $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id'].'#comments');
}
$this->create($values, $errors);
@@ -125,7 +125,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'].'#comment-'.$comment['id']);
+ $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id'].'#comment-'.$comment['id']);
}
$this->edit($values, $errors);
@@ -166,6 +166,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'].'#comments');
+ $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id'].'#comments');
}
}
diff --git a/sources/app/Controller/Config.php b/sources/app/Controller/Config.php
index 199259d..9005c30 100644
--- a/sources/app/Controller/Config.php
+++ b/sources/app/Controller/Config.php
@@ -20,10 +20,10 @@ class Config extends Base
*/
private function layout($template, array $params)
{
- $params['board_selector'] = $this->projectPermission->getAllowedProjects($this->acl->getUserId());
+ $params['board_selector'] = $this->projectPermission->getAllowedProjects($this->userSession->getId());
$params['values'] = $this->config->getAll();
$params['errors'] = array();
- $params['config_content_for_layout'] = $this->template->load($template, $params);
+ $params['config_content_for_layout'] = $this->template->render($template, $params);
return $this->template->layout('config/layout', $params);
}
diff --git a/sources/app/Controller/Export.php b/sources/app/Controller/Export.php
new file mode 100644
index 0000000..1997a4e
--- /dev/null
+++ b/sources/app/Controller/Export.php
@@ -0,0 +1,75 @@
+getProject();
+ $from = $this->request->getStringParam('from');
+ $to = $this->request->getStringParam('to');
+
+ if ($from && $to) {
+ $data = $this->$model->$method($project['id'], $from, $to);
+ $this->response->forceDownload($filename.'.csv');
+ $this->response->csv($data);
+ }
+
+ $this->response->html($this->projectLayout('export/'.$action, array(
+ 'values' => array(
+ 'controller' => 'export',
+ 'action' => $action,
+ 'project_id' => $project['id'],
+ 'from' => $from,
+ 'to' => $to,
+ ),
+ 'errors' => array(),
+ 'date_format' => $this->config->get('application_date_format'),
+ 'date_formats' => $this->dateParser->getAvailableFormats(),
+ 'project' => $project,
+ 'title' => $page_title,
+ )));
+ }
+
+ /**
+ * Task export
+ *
+ * @access public
+ */
+ public function tasks()
+ {
+ $this->common('taskExport', 'export', t('Tasks'), 'tasks', t('Tasks Export'));
+ }
+
+ /**
+ * Subtask export
+ *
+ * @access public
+ */
+ public function subtasks()
+ {
+ $this->common('subtaskExport', 'export', t('Subtasks'), 'subtasks', t('Subtasks Export'));
+ }
+
+ /**
+ * Daily project summary export
+ *
+ * @access public
+ */
+ public function summary()
+ {
+ $this->common('projectDailySummary', 'getAggregatedMetrics', t('Summary'), 'summary', t('Daily project summary export'));
+ }
+}
diff --git a/sources/app/Controller/File.php b/sources/app/Controller/File.php
index ae44cac..6305261 100644
--- a/sources/app/Controller/File.php
+++ b/sources/app/Controller/File.php
@@ -37,11 +37,11 @@ class File extends Base
$task = $this->getTask();
if ($this->file->upload($task['project_id'], $task['id'], 'files') === true) {
- $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'#attachments');
+ $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id'].'#attachments');
}
else {
$this->session->flashError(t('Unable to upload the file.'));
- $this->response->redirect('?controller=file&action=create&task_id='.$task['id']);
+ $this->response->redirect('?controller=file&action=create&task_id='.$task['id'].'&project_id='.$task['project_id']);
}
}
@@ -61,7 +61,7 @@ class File extends Base
$this->response->binary(file_get_contents($filename));
}
- $this->response->redirect('?controller=task&action=show&task_id='.$task['id']);
+ $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id']);
}
/**
@@ -75,8 +75,9 @@ class File extends Base
$file = $this->file->getById($this->request->getIntegerParam('file_id'));
if ($file['task_id'] == $task['id']) {
- $this->response->html($this->template->load('file/open', array(
- 'file' => $file
+ $this->response->html($this->template->render('file/open', array(
+ 'file' => $file,
+ 'task' => $task,
)));
}
}
@@ -119,7 +120,7 @@ class File extends Base
$this->session->flashError(t('Unable to remove this file.'));
}
- $this->response->redirect('?controller=task&action=show&task_id='.$task['id']);
+ $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id']);
}
/**
diff --git a/sources/app/Controller/Project.php b/sources/app/Controller/Project.php
index 83c81ca..d0da53d 100644
--- a/sources/app/Controller/Project.php
+++ b/sources/app/Controller/Project.php
@@ -2,8 +2,6 @@
namespace Controller;
-use Model\Task as TaskModel;
-
/**
* Project controller
*
@@ -19,7 +17,7 @@ class Project extends Base
*/
public function index()
{
- $projects = $this->project->getAll($this->acl->isRegularUser());
+ $projects = $this->project->getAll(! $this->userSession->isAdmin());
$nb_projects = count($projects);
$active_projects = array();
$inactive_projects = array();
@@ -34,7 +32,7 @@ class Project extends Base
}
$this->response->html($this->template->layout('project/index', array(
- 'board_selector' => $this->projectPermission->getAllowedProjects($this->acl->getUserId()),
+ 'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
'active_projects' => $active_projects,
'inactive_projects' => $inactive_projects,
'nb_projects' => $nb_projects,
@@ -54,77 +52,10 @@ class Project extends Base
$this->response->html($this->projectLayout('project/show', array(
'project' => $project,
'stats' => $this->project->getStats($project['id']),
- 'webhook_token' => $this->config->get('webhook_token'),
'title' => $project['name'],
)));
}
- /**
- * Task export
- *
- * @access public
- */
- public function exportTasks()
- {
- $project = $this->getProjectManagement();
- $from = $this->request->getStringParam('from');
- $to = $this->request->getStringParam('to');
-
- if ($from && $to) {
- $data = $this->taskExport->export($project['id'], $from, $to);
- $this->response->forceDownload('Tasks_'.date('Y_m_d_H_i').'.csv');
- $this->response->csv($data);
- }
-
- $this->response->html($this->projectLayout('project/export_tasks', array(
- 'values' => array(
- 'controller' => 'project',
- 'action' => 'exportTasks',
- 'project_id' => $project['id'],
- 'from' => $from,
- 'to' => $to,
- ),
- 'errors' => array(),
- 'date_format' => $this->config->get('application_date_format'),
- 'date_formats' => $this->dateParser->getAvailableFormats(),
- 'project' => $project,
- 'title' => t('Tasks Export')
- )));
- }
-
- /**
- * Daily project summary export
- *
- * @access public
- */
- public function exportDailyProjectSummary()
- {
- $project = $this->getProjectManagement();
- $from = $this->request->getStringParam('from');
- $to = $this->request->getStringParam('to');
-
- if ($from && $to) {
- $data = $this->projectDailySummary->getAggregatedMetrics($project['id'], $from, $to);
- $this->response->forceDownload('Daily_Summary_'.date('Y_m_d_H_i').'.csv');
- $this->response->csv($data);
- }
-
- $this->response->html($this->projectLayout('project/export_daily_summary', array(
- 'values' => array(
- 'controller' => 'project',
- 'action' => 'exportDailyProjectSummary',
- 'project_id' => $project['id'],
- 'from' => $from,
- 'to' => $to,
- ),
- 'errors' => array(),
- 'date_format' => $this->config->get('application_date_format'),
- 'date_formats' => $this->dateParser->getAvailableFormats(),
- 'project' => $project,
- 'title' => t('Daily project summary export')
- )));
- }
-
/**
* Public access management
*
@@ -132,7 +63,7 @@ class Project extends Base
*/
public function share()
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
$switch = $this->request->getStringParam('switch');
if ($switch === 'enable' || $switch === 'disable') {
@@ -154,6 +85,22 @@ class Project extends Base
)));
}
+ /**
+ * Integrations page
+ *
+ * @access public
+ */
+ public function integration()
+ {
+ $project = $this->getProject();
+
+ $this->response->html($this->projectLayout('project/integrations', array(
+ 'project' => $project,
+ 'title' => t('Integrations'),
+ 'webhook_token' => $this->config->get('webhook_token'),
+ )));
+ }
+
/**
* Display a form to edit a project
*
@@ -161,7 +108,7 @@ class Project extends Base
*/
public function edit(array $values = array(), array $errors = array())
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
$this->response->html($this->projectLayout('project/edit', array(
'values' => empty($values) ? $project : $values,
@@ -178,7 +125,7 @@ class Project extends Base
*/
public function update()
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
$values = $this->request->getValues();
list($valid, $errors) = $this->project->validateModification($values);
@@ -203,7 +150,7 @@ class Project extends Base
*/
public function users()
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
$this->response->html($this->projectLayout('project/users', array(
'project' => $project,
@@ -219,7 +166,7 @@ class Project extends Base
*/
public function allowEverybody()
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
$values = $this->request->getValues() + array('is_everybody_allowed' => 0);
list($valid,) = $this->projectPermission->validateProjectModification($values);
@@ -248,7 +195,37 @@ class Project extends Base
if ($valid) {
- if ($this->projectPermission->allowUser($values['project_id'], $values['user_id'])) {
+ if ($this->projectPermission->addMember($values['project_id'], $values['user_id'])) {
+ $this->session->flash(t('Project updated successfully.'));
+ }
+ else {
+ $this->session->flashError(t('Unable to update this project.'));
+ }
+ }
+
+ $this->response->redirect('?controller=project&action=users&project_id='.$values['project_id']);
+ }
+
+ /**
+ * Change the role of a project member
+ *
+ * @access public
+ */
+ public function role()
+ {
+ $this->checkCSRFParam();
+
+ $values = array(
+ 'project_id' => $this->request->getIntegerParam('project_id'),
+ 'user_id' => $this->request->getIntegerParam('user_id'),
+ 'is_owner' => $this->request->getIntegerParam('is_owner'),
+ );
+
+ list($valid,) = $this->projectPermission->validateUserModification($values);
+
+ if ($valid) {
+
+ if ($this->projectPermission->changeRole($values['project_id'], $values['user_id'], $values['is_owner'])) {
$this->session->flash(t('Project updated successfully.'));
}
else {
@@ -277,7 +254,7 @@ class Project extends Base
if ($valid) {
- if ($this->projectPermission->revokeUser($values['project_id'], $values['user_id'])) {
+ if ($this->projectPermission->revokeMember($values['project_id'], $values['user_id'])) {
$this->session->flash(t('Project updated successfully.'));
}
else {
@@ -295,7 +272,7 @@ class Project extends Base
*/
public function remove()
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
if ($this->request->getStringParam('remove') === 'yes') {
@@ -324,7 +301,7 @@ class Project extends Base
*/
public function duplicate()
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
if ($this->request->getStringParam('duplicate') === 'yes') {
@@ -352,7 +329,7 @@ class Project extends Base
*/
public function disable()
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
if ($this->request->getStringParam('disable') === 'yes') {
@@ -380,7 +357,7 @@ class Project extends Base
*/
public function enable()
{
- $project = $this->getProjectManagement();
+ $project = $this->getProject();
if ($this->request->getStringParam('enable') === 'yes') {
@@ -416,7 +393,7 @@ class Project extends Base
$this->forbidden(true);
}
- $this->response->xml($this->template->load('project/feed', array(
+ $this->response->xml($this->template->render('project/feed', array(
'events' => $this->projectActivity->getProject($project['id']),
'project' => $project,
)));
@@ -432,7 +409,7 @@ class Project extends Base
$project = $this->getProject();
$this->response->html($this->template->layout('project/activity', array(
- 'board_selector' => $this->projectPermission->getAllowedProjects($this->acl->getUserId()),
+ 'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
'events' => $this->projectActivity->getProject($project['id']),
'project' => $project,
'title' => t('%s\'s activity', $project['name'])
@@ -461,7 +438,7 @@ class Project extends Base
}
$this->response->html($this->template->layout('project/search', array(
- 'board_selector' => $this->projectPermission->getAllowedProjects($this->acl->getUserId()),
+ 'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
'tasks' => $tasks,
'nb_tasks' => $nb_tasks,
'pagination' => array(
@@ -504,7 +481,7 @@ class Project extends Base
$nb_tasks = $this->taskPaginator->countClosedTasks($project['id']);
$this->response->html($this->template->layout('project/tasks', array(
- 'board_selector' => $this->projectPermission->getAllowedProjects($this->acl->getUserId()),
+ 'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
'pagination' => array(
'controller' => 'project',
'action' => 'tasks',
@@ -531,10 +508,10 @@ class Project extends Base
*/
public function create(array $values = array(), array $errors = array())
{
- $is_private = $this->request->getIntegerParam('private', $this->acl->isRegularUser());
+ $is_private = $this->request->getIntegerParam('private', $this->userSession->isAdmin() ? 0 : 1);
$this->response->html($this->template->layout('project/new', array(
- 'board_selector' => $this->projectPermission->getAllowedProjects($this->acl->getUserId()),
+ 'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
'values' => empty($values) ? array('is_private' => $is_private) : $values,
'errors' => $errors,
'title' => $is_private ? t('New private project') : t('New project'),
@@ -553,7 +530,7 @@ class Project extends Base
if ($valid) {
- $project_id = $this->project->create($values, $this->acl->getUserId(), true);
+ $project_id = $this->project->create($values, $this->userSession->getId(), true);
if ($project_id) {
$this->session->flash(t('Your project have been created successfully.'));
diff --git a/sources/app/Controller/Subtask.php b/sources/app/Controller/Subtask.php
index 948f3c7..0521b89 100644
--- a/sources/app/Controller/Subtask.php
+++ b/sources/app/Controller/Subtask.php
@@ -73,10 +73,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');
+ $this->response->redirect('?controller=subtask&action=create&task_id='.$task['id'].'&another_subtask=1&project_id='.$task['project_id']);
}
- $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'#subtasks');
+ $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id'].'#subtasks');
}
$this->create($values, $errors);
@@ -110,7 +110,7 @@ class Subtask extends Base
public function update()
{
$task = $this->getTask();
- $subtask = $this->getSubtask();
+ $this->getSubtask();
$values = $this->request->getValues();
list($valid, $errors) = $this->subTask->validateModification($values);
@@ -124,7 +124,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'].'#subtasks');
+ $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id'].'#subtasks');
}
$this->edit($values, $errors);
@@ -164,7 +164,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'].'#subtasks');
+ $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id'].'#subtasks');
}
/**
@@ -181,6 +181,6 @@ 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'].'#subtasks');
+ $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id'].'#subtasks');
}
}
diff --git a/sources/app/Controller/Swimlane.php b/sources/app/Controller/Swimlane.php
new file mode 100644
index 0000000..de2f1f1
--- /dev/null
+++ b/sources/app/Controller/Swimlane.php
@@ -0,0 +1,256 @@
+swimlane->getById($this->request->getIntegerParam('swimlane_id'));
+
+ if (! $swimlane) {
+ $this->session->flashError(t('Swimlane not found.'));
+ $this->response->redirect('?controller=swimlane&action=index&project_id='.$project_id);
+ }
+
+ return $swimlane;
+ }
+
+ /**
+ * List of swimlanes for a given project
+ *
+ * @access public
+ */
+ public function index(array $values = array(), array $errors = array())
+ {
+ $project = $this->getProject();
+
+ $this->response->html($this->projectLayout('swimlane/index', array(
+ 'default_swimlane' => $this->swimlane->getDefault($project['id']),
+ 'active_swimlanes' => $this->swimlane->getAllByStatus($project['id'], SwimlaneModel::ACTIVE),
+ 'inactive_swimlanes' => $this->swimlane->getAllByStatus($project['id'], SwimlaneModel::INACTIVE),
+ 'values' => $values + array('project_id' => $project['id']),
+ 'errors' => $errors,
+ 'project' => $project,
+ 'title' => t('Swimlanes')
+ )));
+ }
+
+ /**
+ * Validate and save a new swimlane
+ *
+ * @access public
+ */
+ public function save()
+ {
+ $project = $this->getProject();
+
+ $values = $this->request->getValues();
+ list($valid, $errors) = $this->swimlane->validateCreation($values);
+
+ if ($valid) {
+
+ 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']);
+ }
+ else {
+ $this->session->flashError(t('Unable to create your swimlane.'));
+ }
+ }
+
+ $this->index($values, $errors);
+ }
+
+ /**
+ * Change the default swimlane
+ *
+ * @access public
+ */
+ public function change()
+ {
+ $project = $this->getProject();
+
+ $values = $this->request->getValues();
+ list($valid,) = $this->swimlane->validateDefaultModification($values);
+
+ if ($valid) {
+
+ 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']);
+ }
+ else {
+ $this->session->flashError(t('Unable to update this swimlane.'));
+ }
+ }
+
+ $this->index();
+ }
+
+ /**
+ * Edit a swimlane (display the form)
+ *
+ * @access public
+ */
+ public function edit(array $values = array(), array $errors = array())
+ {
+ $project = $this->getProject();
+ $swimlane = $this->getSwimlane($project['id']);
+
+ $this->response->html($this->projectLayout('swimlane/edit', array(
+ 'values' => empty($values) ? $swimlane : $values,
+ 'errors' => $errors,
+ 'project' => $project,
+ 'title' => t('Swimlanes')
+ )));
+ }
+
+ /**
+ * Edit a swimlane (validate the form and update the database)
+ *
+ * @access public
+ */
+ public function update()
+ {
+ $project = $this->getProject();
+
+ $values = $this->request->getValues();
+ list($valid, $errors) = $this->swimlane->validateModification($values);
+
+ if ($valid) {
+
+ 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']);
+ }
+ else {
+ $this->session->flashError(t('Unable to update this swimlane.'));
+ }
+ }
+
+ $this->edit($values, $errors);
+ }
+
+ /**
+ * Confirmation dialog before removing a swimlane
+ *
+ * @access public
+ */
+ public function confirm()
+ {
+ $project = $this->getProject();
+ $swimlane = $this->getSwimlane($project['id']);
+
+ $this->response->html($this->projectLayout('swimlane/remove', array(
+ 'project' => $project,
+ 'swimlane' => $swimlane,
+ 'title' => t('Remove a swimlane')
+ )));
+ }
+
+ /**
+ * Remove a swimlane
+ *
+ * @access public
+ */
+ public function remove()
+ {
+ $this->checkCSRFParam();
+ $project = $this->getProject();
+ $swimlane_id = $this->request->getIntegerParam('swimlane_id');
+
+ if ($this->swimlane->remove($project['id'], $swimlane_id)) {
+ $this->session->flash(t('Swimlane removed successfully.'));
+ } else {
+ $this->session->flashError(t('Unable to remove this swimlane.'));
+ }
+
+ $this->response->redirect('?controller=swimlane&action=index&project_id='.$project['id']);
+ }
+
+ /**
+ * Disable a swimlane
+ *
+ * @access public
+ */
+ public function disable()
+ {
+ $this->checkCSRFParam();
+ $project = $this->getProject();
+ $swimlane_id = $this->request->getIntegerParam('swimlane_id');
+
+ if ($this->swimlane->disable($project['id'], $swimlane_id)) {
+ $this->session->flash(t('Swimlane updated successfully.'));
+ } else {
+ $this->session->flashError(t('Unable to update this swimlane.'));
+ }
+
+ $this->response->redirect('?controller=swimlane&action=index&project_id='.$project['id']);
+ }
+
+ /**
+ * Enable a swimlane
+ *
+ * @access public
+ */
+ public function enable()
+ {
+ $this->checkCSRFParam();
+ $project = $this->getProject();
+ $swimlane_id = $this->request->getIntegerParam('swimlane_id');
+
+ if ($this->swimlane->enable($project['id'], $swimlane_id)) {
+ $this->session->flash(t('Swimlane updated successfully.'));
+ } else {
+ $this->session->flashError(t('Unable to update this swimlane.'));
+ }
+
+ $this->response->redirect('?controller=swimlane&action=index&project_id='.$project['id']);
+ }
+
+ /**
+ * Move up a swimlane
+ *
+ * @access public
+ */
+ public function moveup()
+ {
+ $this->checkCSRFParam();
+ $project = $this->getProject();
+ $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']);
+ }
+
+ /**
+ * Move down a swimlane
+ *
+ * @access public
+ */
+ public function movedown()
+ {
+ $this->checkCSRFParam();
+ $project = $this->getProject();
+ $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']);
+ }
+}
diff --git a/sources/app/Controller/Task.php b/sources/app/Controller/Task.php
index 8d38317..7f85f36 100644
--- a/sources/app/Controller/Task.php
+++ b/sources/app/Controller/Task.php
@@ -89,11 +89,12 @@ class Task extends Base
public function create(array $values = array(), array $errors = array())
{
$project = $this->getProject();
- $method = $this->request->isAjax() ? 'load' : 'layout';
+ $method = $this->request->isAjax() ? 'render' : 'layout';
if (empty($values)) {
$values = array(
+ 'swimlane_id' => $this->request->getIntegerParam('swimlane_id'),
'column_id' => $this->request->getIntegerParam('column_id'),
'color_id' => $this->request->getStringParam('color_id'),
'owner_id' => $this->request->getIntegerParam('owner_id'),
@@ -125,9 +126,7 @@ class Task extends Base
{
$project = $this->getProject();
$values = $this->request->getValues();
- $values['creator_id'] = $this->acl->getUserId();
-
- $this->checkProjectPermissions($project['id']);
+ $values['creator_id'] = $this->userSession->getId();
list($valid, $errors) = $this->taskValidator->validateCreation($values);
@@ -142,7 +141,7 @@ class Task extends Base
$this->response->redirect('?controller=task&action=create&'.http_build_query($values));
}
else {
- $this->response->redirect('?controller=board&action=show&project_id='.$values['project_id']);
+ $this->response->redirect('?controller=board&action=show&project_id='.$project['id']);
}
}
else {
@@ -158,16 +157,20 @@ class Task extends Base
*
* @access public
*/
- public function edit()
+ public function edit(array $values = array(), array $errors = array())
{
$task = $this->getTask();
$ajax = $this->request->isAjax();
- $this->dateParser->format($task, array('date_due'));
+ if (empty($values)) {
+ $values = $task;
+ }
+
+ $this->dateParser->format($values, array('date_due'));
$params = array(
- 'values' => $task,
- 'errors' => array(),
+ 'values' => $values,
+ 'errors' => $errors,
'task' => $task,
'users_list' => $this->projectPermission->getMemberList($task['project_id']),
'colors_list' => $this->color->getList(),
@@ -178,7 +181,7 @@ class Task extends Base
);
if ($ajax) {
- $this->response->html($this->template->load('task/edit', $params));
+ $this->response->html($this->template->render('task/edit', $params));
}
else {
$this->response->html($this->taskLayout('task/edit', $params));
@@ -206,7 +209,7 @@ class Task extends Base
$this->response->redirect('?controller=board&action=show&project_id='.$task['project_id']);
}
else {
- $this->response->redirect('?controller=task&action=show&task_id='.$task['id']);
+ $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id']);
}
}
else {
@@ -214,18 +217,7 @@ class Task extends Base
}
}
- $this->response->html($this->taskLayout('task/edit', array(
- 'values' => $values,
- 'errors' => $errors,
- 'task' => $task,
- 'columns_list' => $this->board->getColumnsList($values['project_id']),
- 'users_list' => $this->projectPermission->getMemberList($values['project_id']),
- 'colors_list' => $this->color->getList(),
- 'categories_list' => $this->category->getList($values['project_id']),
- 'date_format' => $this->config->get('application_date_format'),
- 'date_formats' => $this->dateParser->getAvailableFormats(),
- 'ajax' => $this->request->isAjax(),
- )));
+ $this->edit($values, $errors);
}
/**
@@ -247,7 +239,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']);
+ $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id']);
}
/**
@@ -269,7 +261,7 @@ class Task extends Base
$this->session->flashError(t('Unable to close this task.'));
}
- $this->response->redirect('?controller=task&action=show&task_id='.$task['id']);
+ $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id']);
}
$this->response->html($this->taskLayout('task/close', array(
@@ -296,7 +288,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']);
+ $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id']);
}
$this->response->html($this->taskLayout('task/open', array(
@@ -351,10 +343,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);
+ $this->response->redirect('?controller=task&action=show&task_id='.$task_id.'&project_id='.$task['project_id']);
} else {
$this->session->flashError(t('Unable to create this task.'));
- $this->response->redirect('?controller=task&action=duplicate&task_id='.$task['id']);
+ $this->response->redirect('?controller=task&action=duplicate&task_id='.$task['id'].'&project_id='.$task['project_id']);
}
}
@@ -392,7 +384,7 @@ class Task extends Base
$this->response->redirect('?controller=board&action=show&project_id='.$task['project_id']);
}
else {
- $this->response->redirect('?controller=task&action=show&task_id='.$task['id']);
+ $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id']);
}
}
}
@@ -409,7 +401,7 @@ class Task extends Base
);
if ($ajax) {
- $this->response->html($this->template->load('task/edit_description', $params));
+ $this->response->html($this->template->render('task/edit_description', $params));
}
else {
$this->response->html($this->taskLayout('task/edit_description', $params));
@@ -426,7 +418,7 @@ class Task extends Base
$task = $this->getTask();
$values = $task;
$errors = array();
- $projects_list = $this->projectPermission->getMemberProjects($this->acl->getUserId());
+ $projects_list = $this->projectPermission->getMemberProjects($this->userSession->getId());
unset($projects_list[$task['project_id']]);
@@ -439,7 +431,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']);
+ $this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$values['project_id']);
}
else {
$this->session->flashError(t('Unable to update your task.'));
@@ -465,7 +457,7 @@ class Task extends Base
$task = $this->getTask();
$values = $task;
$errors = array();
- $projects_list = $this->projectPermission->getMemberProjects($this->acl->getUserId());
+ $projects_list = $this->projectPermission->getMemberProjects($this->userSession->getId());
unset($projects_list[$task['project_id']]);
@@ -478,7 +470,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);
+ $this->response->redirect('?controller=task&action=show&task_id='.$task_id.'&project_id='.$values['project_id']);
}
else {
$this->session->flashError(t('Unable to create your task.'));
diff --git a/sources/app/Controller/User.php b/sources/app/Controller/User.php
index 93b5ca1..7fddf70 100644
--- a/sources/app/Controller/User.php
+++ b/sources/app/Controller/User.php
@@ -18,7 +18,7 @@ class User extends Base
public function logout()
{
$this->checkCSRFParam();
- $this->authentication->backend('rememberMe')->destroy($this->acl->getUserId());
+ $this->authentication->backend('rememberMe')->destroy($this->userSession->getId());
$this->session->close();
$this->response->redirect('?controller=user&action=login');
}
@@ -30,7 +30,7 @@ class User extends Base
*/
public function login(array $values = array(), array $errors = array())
{
- if ($this->acl->isLogged()) {
+ if ($this->userSession->isLogged()) {
$this->response->redirect('?controller=app');
}
@@ -76,9 +76,9 @@ class User extends Base
*/
private function layout($template, array $params)
{
- $content = $this->template->load($template, $params);
+ $content = $this->template->render($template, $params);
$params['user_content_for_layout'] = $content;
- $params['board_selector'] = $this->projectPermission->getAllowedProjects($this->acl->getUserId());
+ $params['board_selector'] = $this->projectPermission->getAllowedProjects($this->userSession->getId());
if (isset($params['user'])) {
$params['title'] = ($params['user']['name'] ?: $params['user']['username']).' (#'.$params['user']['id'].')';
@@ -101,7 +101,7 @@ class User extends Base
$this->notfound();
}
- if ($this->acl->isRegularUser() && $this->acl->getUserId() != $user['id']) {
+ if (! $this->userSession->isAdmin() && $this->userSession->getId() != $user['id']) {
$this->forbidden();
}
@@ -125,7 +125,7 @@ class User extends Base
$this->response->html(
$this->template->layout('user/index', array(
- 'board_selector' => $this->projectPermission->getAllowedProjects($this->acl->getUserId()),
+ 'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
'projects' => $this->project->getList(),
'nb_users' => $nb_users,
'users' => $users,
@@ -151,7 +151,9 @@ class User extends Base
public function create(array $values = array(), array $errors = array())
{
$this->response->html($this->template->layout('user/new', array(
- 'board_selector' => $this->projectPermission->getAllowedProjects($this->acl->getUserId()),
+ 'timezones' => $this->config->getTimezones(true),
+ 'languages' => $this->config->getLanguages(true),
+ 'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
'projects' => $this->project->getList(),
'errors' => $errors,
'values' => $values,
@@ -194,6 +196,8 @@ class User extends Base
$this->response->html($this->layout('user/show', array(
'projects' => $this->projectPermission->getAllowedProjects($user['id']),
'user' => $user,
+ 'timezones' => $this->config->getTimezones(true),
+ 'languages' => $this->config->getLanguages(true),
)));
}
@@ -328,7 +332,7 @@ class User extends Base
$values = $this->request->getValues();
- if ($this->acl->isAdminUser()) {
+ if ($this->userSession->isAdmin()) {
$values += array('is_admin' => 0);
}
else {
@@ -358,6 +362,8 @@ class User extends Base
'errors' => $errors,
'projects' => $this->projectPermission->filterProjects($this->project->getList(), $user['id']),
'user' => $user,
+ 'timezones' => $this->config->getTimezones(true),
+ 'languages' => $this->config->getLanguages(true),
)));
}
@@ -404,16 +410,16 @@ class User extends Base
if (is_array($profile)) {
// If the user is already logged, link the account otherwise authenticate
- if ($this->acl->isLogged()) {
+ if ($this->userSession->isLogged()) {
- if ($this->authentication->backend('google')->updateUser($this->acl->getUserId(), $profile)) {
+ 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.'));
}
- $this->response->redirect('?controller=user&action=external&user_id='.$this->acl->getUserId());
+ $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');
@@ -441,14 +447,14 @@ class User extends Base
public function unlinkGoogle()
{
$this->checkCSRFParam();
- if ($this->authentication->backend('google')->unlink($this->acl->getUserId())) {
+ 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('?controller=user&action=external&user_id='.$this->acl->getUserId());
+ $this->response->redirect('?controller=user&action=external&user_id='.$this->userSession->getId());
}
/**
@@ -466,16 +472,16 @@ class User extends Base
if (is_array($profile)) {
// If the user is already logged, link the account otherwise authenticate
- if ($this->acl->isLogged()) {
+ if ($this->userSession->isLogged()) {
- if ($this->authentication->backend('gitHub')->updateUser($this->acl->getUserId(), $profile)) {
+ 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->response->redirect('?controller=user&action=external&user_id='.$this->acl->getUserId());
+ $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');
@@ -506,13 +512,13 @@ class User extends Base
$this->authentication->backend('gitHub')->revokeGitHubAccess();
- if ($this->authentication->backend('gitHub')->unlink($this->acl->getUserId())) {
+ 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('?controller=user&action=external&user_id='.$this->acl->getUserId());
+ $this->response->redirect('?controller=user&action=external&user_id='.$this->userSession->getId());
}
}
diff --git a/sources/app/Controller/Webhook.php b/sources/app/Controller/Webhook.php
index dcd66a1..1ae3b0a 100644
--- a/sources/app/Controller/Webhook.php
+++ b/sources/app/Controller/Webhook.php
@@ -57,7 +57,27 @@ class Webhook extends Base
$result = $this->githubWebhook->parsePayload(
$this->request->getHeader('X-Github-Event'),
- $this->request->getJson()
+ $this->request->getJson() ?: array()
+ );
+
+ echo $result ? 'PARSED' : 'IGNORED';
+ }
+
+ /**
+ * Handle Gitlab webhooks
+ *
+ * @access public
+ */
+ public function gitlab()
+ {
+ if ($this->config->get('webhook_token') !== $this->request->getStringParam('token')) {
+ $this->response->text('Not Authorized', 401);
+ }
+
+ $this->gitlabWebhook->setProjectId($this->request->getIntegerParam('project_id'));
+
+ $result = $this->gitlabWebhook->parsePayload(
+ $this->request->getJson() ?: array()
);
echo $result ? 'PARSED' : 'IGNORED';
diff --git a/sources/app/Core/Cache.php b/sources/app/Core/Cache.php
new file mode 100644
index 0000000..670a76e
--- /dev/null
+++ b/sources/app/Core/Cache.php
@@ -0,0 +1,58 @@
+container = $container;
+ $this->init();
+ }
+
+ /**
+ * Proxy cache
+ *
+ * Note: Arguments must be scalar types
+ *
+ * @access public
+ * @param string $container Container name
+ * @param string $method Container method
+ * @return mixed
+ */
+ public function proxy($container, $method)
+ {
+ $args = func_get_args();
+ $key = 'proxy_'.implode('_', $args);
+ $result = $this->get($key);
+
+ if ($result === null) {
+ $result = call_user_func_array(array($this->container[$container], $method), array_splice($args, 2));
+ $this->set($key, $result);
+ }
+
+ return $result;
+ }
+}
diff --git a/sources/app/Core/Event.php b/sources/app/Core/Event.php
deleted file mode 100644
index 935f8b9..0000000
--- a/sources/app/Core/Event.php
+++ /dev/null
@@ -1,175 +0,0 @@
-listeners[$eventName])) {
- $this->listeners[$eventName] = array();
- }
-
- $this->listeners[$eventName][] = $listener;
- }
-
- /**
- * Trigger an event
- *
- * @access public
- * @param string $eventName Event name
- * @param array $data Event data
- */
- public function trigger($eventName, array $data)
- {
- if (! $this->isEventTriggered($eventName)) {
-
- $this->events[$eventName] = $data;
-
- if (isset($this->listeners[$eventName])) {
-
- foreach ($this->listeners[$eventName] as $listener) {
-
- $this->lastEvent = $eventName;
-
- if ($listener->execute($data)) {
- $this->lastListener = get_class($listener);
- }
- }
- }
- }
- }
-
- /**
- * Get the last listener executed
- *
- * @access public
- * @return string Event name
- */
- public function getLastListenerExecuted()
- {
- return $this->lastListener;
- }
-
- /**
- * Get the last fired event
- *
- * @access public
- * @return string Event name
- */
- public function getLastTriggeredEvent()
- {
- return $this->lastEvent;
- }
-
- /**
- * Get a list of triggered events
- *
- * @access public
- * @return array
- */
- public function getTriggeredEvents()
- {
- return $this->events;
- }
-
- /**
- * Get a list of triggered events
- *
- * @access public
- * @return array
- */
- public function getEventData($eventName)
- {
- return isset($this->events[$eventName]) ? $this->events[$eventName] : array();
- }
-
- /**
- * Check if an event have been triggered
- *
- * @access public
- * @param string $eventName Event name
- * @return bool
- */
- public function isEventTriggered($eventName)
- {
- return isset($this->events[$eventName]);
- }
-
- /**
- * Flush the list of triggered events
- *
- * @access public
- */
- public function clearTriggeredEvents()
- {
- $this->events = array();
- $this->lastEvent = '';
- }
-
- /**
- * Check if a listener bind to an event
- *
- * @access public
- * @param string $eventName Event name
- * @param mixed $instance Instance name or object itself
- * @return bool Yes or no
- */
- public function hasListener($eventName, $instance)
- {
- if (isset($this->listeners[$eventName])) {
- foreach ($this->listeners[$eventName] as $listener) {
- if ($listener instanceof $instance) {
- return true;
- }
- }
- }
-
- return false;
- }
-}
diff --git a/sources/app/Core/FileCache.php b/sources/app/Core/FileCache.php
new file mode 100644
index 0000000..2037f27
--- /dev/null
+++ b/sources/app/Core/FileCache.php
@@ -0,0 +1,41 @@
+container = $container;
+ }
+
+ /**
+ * Load automatically models
+ *
+ * @access public
+ * @param string $name Model name
+ * @return mixed
+ */
+ public function __get($name)
+ {
+ return $this->container[$name];
+ }
+
+ /**
+ * Proxy cache helper for acl::isManagerActionAllowed()
+ *
+ * @access public
+ * @param integer $project_id
+ * @return boolean
+ */
+ public function isManager($project_id)
+ {
+ if ($this->userSession->isAdmin()) {
+ return true;
+ }
+
+ return $this->container['memoryCache']->proxy('acl', 'isManagerActionAllowed', $project_id);
+ }
+
+ /**
+ * Return the user full name
+ *
+ * @param array $user User properties
+ * @return string
+ */
+ public function getFullname(array $user = array())
+ {
+ return $this->user->getFullname(empty($user) ? $_SESSION['user'] : $user);
+ }
+
+ /**
+ * HTML escaping
+ *
+ * @param string $value Value to escape
+ * @return string
+ */
+ public function e($value)
+ {
+ return htmlspecialchars($value, ENT_QUOTES, 'UTF-8', false);
+ }
+
+ /**
+ * Add a Javascript asset
+ *
+ * @param string $filename Filename
+ * @return string
+ */
+ public function js($filename)
+ {
+ return '';
+ }
+
+ /**
+ * Add a stylesheet asset
+ *
+ * @param string $filename Filename
+ * @return string
+ */
+ public function css($filename)
+ {
+ return '';
+ }
+
+ /**
+ * Display the form error class
+ *
+ * @param array $errors Error list
+ * @param string $name Field name
+ * @return string
+ */
+ public function errorClass(array $errors, $name)
+ {
+ return ! isset($errors[$name]) ? '' : ' form-error';
+ }
+
+ /**
+ * Display a list of form errors
+ *
+ * @param array $errors List of errors
+ * @param string $name Field name
+ * @return string
+ */
+ public function errorList(array $errors, $name)
+ {
+ $html = '';
+
+ if (isset($errors[$name])) {
+
+ $html .= '';
+ }
+
+ return $html;
+ }
+
+ /**
+ * Get an escaped form value
+ *
+ * @param mixed $values Values
+ * @param string $name Field name
+ * @return string
+ */
+ public function formValue($values, $name)
+ {
+ if (isset($values->$name)) {
+ return 'value="'.$this->e($values->$name).'"';
+ }
+
+ return isset($values[$name]) ? 'value="'.$this->e($values[$name]).'"' : '';
+ }
+
+ /**
+ * Hidden CSRF token field
+ *
+ * @return string
+ */
+ public function formCsrf()
+ {
+ return '';
+ }
+
+ /**
+ * Display a hidden form field
+ *
+ * @param string $name Field name
+ * @param array $values Form values
+ * @return string
+ */
+ public function formHidden($name, array $values = array())
+ {
+ return 'formValue($values, $name).'/>';
+ }
+
+ /**
+ * Display a select field
+ *
+ * @param string $name Field name
+ * @param array $options Options
+ * @param array $values Form values
+ * @param array $errors Form errors
+ * @param string $class CSS class
+ * @return string
+ */
+ public function formSelect($name, array $options, array $values = array(), array $errors = array(), $class = '')
+ {
+ $html = '';
+ $html .= $this->errorList($errors, $name);
+
+ return $html;
+ }
+
+ /**
+ * Display a radio field group
+ *
+ * @param string $name Field name
+ * @param array $options Options
+ * @param array $values Form values
+ * @return string
+ */
+ public function formRadios($name, array $options, array $values = array())
+ {
+ $html = '';
+
+ foreach ($options as $value => $label) {
+ $html .= $this->formRadio($name, $label, $value, isset($values[$name]) && $values[$name] == $value);
+ }
+
+ return $html;
+ }
+
+ /**
+ * Display a radio field
+ *
+ * @param string $name Field name
+ * @param string $label Form label
+ * @param string $value Form value
+ * @param boolean $selected Field selected or not
+ * @param string $class CSS class
+ * @return string
+ */
+ public function formRadio($name, $label, $value, $selected = false, $class = '')
+ {
+ return '';
+ }
+
+ /**
+ * Display a checkbox field
+ *
+ * @param string $name Field name
+ * @param string $label Form label
+ * @param string $value Form value
+ * @param boolean $checked Field selected or not
+ * @param string $class CSS class
+ * @return string
+ */
+ public function formCheckbox($name, $label, $value, $checked = false, $class = '')
+ {
+ return '';
+ }
+
+ /**
+ * Display a form label
+ *
+ * @param string $name Field name
+ * @param string $label Form label
+ * @param array $attributes HTML attributes
+ * @return string
+ */
+ public function formLabel($label, $name, array $attributes = array())
+ {
+ return '';
+ }
+
+ /**
+ * Display a textarea
+ *
+ * @param string $name Field name
+ * @param array $values Form values
+ * @param array $errors Form errors
+ * @param array $attributes HTML attributes
+ * @param string $class CSS class
+ * @return string
+ */
+ public function formTextarea($name, $values = array(), array $errors = array(), array $attributes = array(), $class = '')
+ {
+ $class .= $this->errorClass($errors, $name);
+
+ $html = '';
+ $html .= $this->errorList($errors, $name);
+
+ return $html;
+ }
+
+ /**
+ * Display a input field
+ *
+ * @param string $type HMTL input tag type
+ * @param string $name Field name
+ * @param array $values Form values
+ * @param array $errors Form errors
+ * @param array $attributes HTML attributes
+ * @param string $class CSS class
+ * @return string
+ */
+ public function formInput($type, $name, $values = array(), array $errors = array(), array $attributes = array(), $class = '')
+ {
+ $class .= $this->errorClass($errors, $name);
+
+ $html = 'formValue($values, $name).' class="'.$class.'" ';
+ $html .= implode(' ', $attributes).'/>';
+ if (in_array('required', $attributes)) $html .= '*';
+ $html .= $this->errorList($errors, $name);
+
+ return $html;
+ }
+
+ /**
+ * Display a text field
+ *
+ * @param string $name Field name
+ * @param array $values Form values
+ * @param array $errors Form errors
+ * @param array $attributes HTML attributes
+ * @param string $class CSS class
+ * @return string
+ */
+ public function formText($name, $values = array(), array $errors = array(), array $attributes = array(), $class = '')
+ {
+ return $this->formInput('text', $name, $values, $errors, $attributes, $class);
+ }
+
+ /**
+ * Display a password field
+ *
+ * @param string $name Field name
+ * @param array $values Form values
+ * @param array $errors Form errors
+ * @param array $attributes HTML attributes
+ * @param string $class CSS class
+ * @return string
+ */
+ public function formPassword($name, $values = array(), array $errors = array(), array $attributes = array(), $class = '')
+ {
+ return $this->formInput('password', $name, $values, $errors, $attributes, $class);
+ }
+
+ /**
+ * Display an email field
+ *
+ * @param string $name Field name
+ * @param array $values Form values
+ * @param array $errors Form errors
+ * @param array $attributes HTML attributes
+ * @param string $class CSS class
+ * @return string
+ */
+ public function formEmail($name, $values = array(), array $errors = array(), array $attributes = array(), $class = '')
+ {
+ return $this->formInput('email', $name, $values, $errors, $attributes, $class);
+ }
+
+ /**
+ * Display a number field
+ *
+ * @param string $name Field name
+ * @param array $values Form values
+ * @param array $errors Form errors
+ * @param array $attributes HTML attributes
+ * @param string $class CSS class
+ * @return string
+ */
+ public function formNumber($name, $values = array(), array $errors = array(), array $attributes = array(), $class = '')
+ {
+ return $this->formInput('number', $name, $values, $errors, $attributes, $class);
+ }
+
+ /**
+ * Display a numeric field (allow decimal number)
+ *
+ * @param string $name Field name
+ * @param array $values Form values
+ * @param array $errors Form errors
+ * @param array $attributes HTML attributes
+ * @param string $class CSS class
+ * @return string
+ */
+ public function formNumeric($name, $values = array(), array $errors = array(), array $attributes = array(), $class = '')
+ {
+ return $this->formInput('text', $name, $values, $errors, $attributes, $class.' form-numeric');
+ }
+
+ /**
+ * Link
+ *
+ * a('link', 'task', 'show', array('task_id' => $task_id))
+ *
+ * @param string $label Link label
+ * @param string $controller Controller name
+ * @param string $action Action name
+ * @param array $params Url parameters
+ * @param boolean $csrf Add a CSRF token
+ * @param string $class CSS class attribute
+ * @param boolean $new_tab Open the link in a new tab
+ * @return string
+ */
+ public function a($label, $controller, $action, array $params = array(), $csrf = false, $class = '', $title = '', $new_tab = false)
+ {
+ return ''.$label.'';
+ }
+
+ /**
+ * URL query string
+ *
+ * u('task', 'show', array('task_id' => $task_id))
+ *
+ * @param string $controller Controller name
+ * @param string $action Action name
+ * @param array $params Url parameters
+ * @param boolean $csrf Add a CSRF token
+ * @return string
+ */
+ public function u($controller, $action, array $params = array(), $csrf = false)
+ {
+ $html = '?controller='.$controller.'&action='.$action;
+
+ if ($csrf) {
+ $params['csrf_token'] = Security::getCSRFToken();
+ }
+
+ foreach ($params as $key => $value) {
+ $html .= '&'.$key.'='.$value;
+ }
+
+ return $html;
+ }
+
+ /**
+ * Pagination links
+ *
+ * @param array $pagination Pagination information
+ * @return string
+ */
+ public function paginate(array $pagination)
+ {
+ extract($pagination);
+
+ if ($pagination['offset'] === 0 && ($total - $pagination['offset']) <= $limit) {
+ return '';
+ }
+
+ $html = '';
+
+ return $html;
+ }
+
+ /**
+ * Column sorting (work with pagination)
+ *
+ * @param string $label Column title
+ * @param string $column SQL column name
+ * @param array $pagination Pagination information
+ * @return string
+ */
+ public function order($label, $column, array $pagination)
+ {
+ extract($pagination);
+
+ $prefix = '';
+
+ if ($order === $column) {
+ $prefix = $direction === 'DESC' ? '▼ ' : '▲ ';
+ $direction = $direction === 'DESC' ? 'ASC' : 'DESC';
+ }
+
+ $order = $column;
+
+ return $prefix.$this->a($label, $controller, $action, $params + compact('offset', 'order', 'direction'));
+ }
+
+ /**
+ * Markdown transformation
+ *
+ * @param string $text Markdown content
+ * @param array $link Link parameters for replacement
+ * @return string
+ */
+ public function markdown($text, array $link = array())
+ {
+ $html = Parsedown::instance()
+ ->setMarkupEscaped(true) # escapes markup (HTML)
+ ->text($text);
+
+ // Replace task #123 by a link to the task
+ if (! empty($link) && preg_match_all('!#(\d+)!i', $html, $matches, PREG_SET_ORDER)) {
+
+ foreach ($matches as $match) {
+
+ $html = str_replace(
+ $match[0],
+ $this->a($match[0], $link['controller'], $link['action'], $link['params'] + array('task_id' => $match[1])),
+ $html
+ );
+ }
+ }
+
+ return $html;
+ }
+
+ /**
+ * Get the current URL without the querystring
+ *
+ * @return string
+ */
+ public function getCurrentBaseUrl()
+ {
+ $url = Request::isHTTPS() ? 'https://' : 'http://';
+ $url .= $_SERVER['SERVER_NAME'];
+ $url .= $_SERVER['SERVER_PORT'] == 80 || $_SERVER['SERVER_PORT'] == 443 ? '' : ':'.$_SERVER['SERVER_PORT'];
+ $url .= dirname($_SERVER['PHP_SELF']) !== '/' ? dirname($_SERVER['PHP_SELF']).'/' : '/';
+
+ return $url;
+ }
+
+ /**
+ * Dispplay the flash session message
+ *
+ * @param string $html HTML wrapper
+ * @return string
+ */
+ public function flash($html)
+ {
+ return $this->flashMessage('flash_message', $html);
+ }
+
+ /**
+ * Display the flash session error message
+ *
+ * @param string $html HTML wrapper
+ * @return string
+ */
+ public function flashError($html)
+ {
+ return $this->flashMessage('flash_error_message', $html);
+ }
+
+ /**
+ * Fetch and remove a flash session message
+ *
+ * @access private
+ * @param string $name Message name
+ * @param string $html HTML wrapper
+ * @return string
+ */
+ private function flashMessage($name, $html)
+ {
+ $data = '';
+
+ if (isset($this->session[$name])) {
+ $data = sprintf($html, $this->e($this->session[$name]));
+ unset($this->session[$name]);
+ }
+
+ return $data;
+ }
+
+ /**
+ * Format a file size
+ *
+ * @param integer $size Size in bytes
+ * @param integer $precision Precision
+ * @return string
+ */
+ public function formatBytes($size, $precision = 2)
+ {
+ $base = log($size) / log(1024);
+ $suffixes = array('', 'k', 'M', 'G', 'T');
+
+ return round(pow(1024, $base - floor($base)), $precision).$suffixes[(int)floor($base)];
+ }
+
+ /**
+ * Truncate a long text
+ *
+ * @param string $value Text
+ * @param integer $max_length Max Length
+ * @param string $end Text end
+ * @return string
+ */
+ public function summary($value, $max_length = 85, $end = '[...]')
+ {
+ $length = strlen($value);
+
+ if ($length > $max_length) {
+ return substr($value, 0, $max_length).' '.$end;
+ }
+
+ return $value;
+ }
+
+ /**
+ * Return true if needle is contained in the haystack
+ *
+ * @param string $haystack Haystack
+ * @param string $needle Needle
+ * @return boolean
+ */
+ public function contains($haystack, $needle)
+ {
+ return strpos($haystack, $needle) !== false;
+ }
+
+ /**
+ * Return a value from a dictionary
+ *
+ * @param mixed $id Key
+ * @param array $listing Dictionary
+ * @param string $default_value Value displayed when the key doesn't exists
+ * @return string
+ */
+ public function inList($id, array $listing, $default_value = '?')
+ {
+ if (isset($listing[$id])) {
+ return $this->e($listing[$id]);
+ }
+
+ return $default_value;
+ }
+}
diff --git a/sources/app/Core/Listener.php b/sources/app/Core/Listener.php
deleted file mode 100644
index 9c96cd5..0000000
--- a/sources/app/Core/Listener.php
+++ /dev/null
@@ -1,21 +0,0 @@
-storage[$key] = $value;
+ }
+
+ public function get($key)
+ {
+ return isset($this->storage[$key]) ? $this->storage[$key] : null;
+ }
+
+ public function flush()
+ {
+ $this->storage = array();
+ }
+
+ public function remove($key)
+ {
+ unset($this->storage[$key]);
+ }
+}
diff --git a/sources/app/Core/Session.php b/sources/app/Core/Session.php
index 3305eca..0e5f742 100644
--- a/sources/app/Core/Session.php
+++ b/sources/app/Core/Session.php
@@ -2,13 +2,15 @@
namespace Core;
+use ArrayAccess;
+
/**
* Session class
*
* @package core
* @author Frederic Guillot
*/
-class Session
+class Session implements ArrayAccess
{
/**
* Sesion lifetime
@@ -59,7 +61,7 @@ class Session
ini_set('session.entropy_length', '32');
ini_set('session.hash_bits_per_character', 6);
- // If session was autostarted with session.auto_start = 1 in php.ini destroy it
+ // If the session was autostarted with session.auto_start = 1 in php.ini destroy it
if (isset($_SESSION)) {
session_destroy();
}
@@ -88,19 +90,17 @@ class Session
$_SESSION = array();
// Destroy the session cookie
- if (ini_get('session.use_cookies')) {
- $params = session_get_cookie_params();
+ $params = session_get_cookie_params();
- setcookie(
- session_name(),
- '',
- time() - 42000,
- $params['path'],
- $params['domain'],
- $params['secure'],
- $params['httponly']
- );
- }
+ setcookie(
+ session_name(),
+ '',
+ time() - 42000,
+ $params['path'],
+ $params['domain'],
+ $params['secure'],
+ $params['httponly']
+ );
// Destroy session data
session_destroy();
@@ -127,4 +127,24 @@ class Session
{
$_SESSION['flash_error_message'] = $message;
}
+
+ public function offsetSet($offset, $value)
+ {
+ $_SESSION[$offset] = $value;
+ }
+
+ public function offsetExists($offset)
+ {
+ return isset($_SESSION[$offset]);
+ }
+
+ public function offsetUnset($offset)
+ {
+ unset($_SESSION[$offset]);
+ }
+
+ public function offsetGet($offset)
+ {
+ return isset($_SESSION[$offset]) ? $_SESSION[$offset] : null;
+ }
}
diff --git a/sources/app/Core/Template.php b/sources/app/Core/Template.php
index da61f57..9688c2a 100644
--- a/sources/app/Core/Template.php
+++ b/sources/app/Core/Template.php
@@ -10,7 +10,7 @@ use LogicException;
* @package core
* @author Frederic Guillot
*/
-class Template
+class Template extends Helper
{
/**
* Template path
@@ -20,18 +20,18 @@ class Template
const PATH = 'app/Template/';
/**
- * Load a template
+ * Render a template
*
* Example:
*
- * $template->load('template_name', ['bla' => 'value']);
+ * $template->render('template_name', ['bla' => 'value']);
*
* @access public
* @params string $__template_name Template name
* @params array $__template_args Key/Value map of template variables
* @return string
*/
- public function load($__template_name, array $__template_args = array())
+ public function render($__template_name, array $__template_args = array())
{
$__template_file = self::PATH.$__template_name.'.php';
@@ -57,9 +57,9 @@ class Template
*/
public function layout($template_name, array $template_args = array(), $layout_name = 'layout')
{
- return $this->load(
+ return $this->render(
$layout_name,
- $template_args + array('content_for_layout' => $this->load($template_name, $template_args))
+ $template_args + array('content_for_layout' => $this->render($template_name, $template_args))
);
}
}
diff --git a/sources/app/Core/Tool.php b/sources/app/Core/Tool.php
index c010d93..ade99ca 100644
--- a/sources/app/Core/Tool.php
+++ b/sources/app/Core/Tool.php
@@ -2,8 +2,6 @@
namespace Core;
-use Pimple\Container;
-
/**
* Tool class
*
@@ -33,23 +31,4 @@ class Tool
fclose($fp);
}
}
-
- /**
- * Load and register a model
- *
- * @static
- * @access public
- * @param Pimple\Container $container Container instance
- * @param string $name Model name
- * @return mixed
- */
- public static function loadModel(Container $container, $name)
- {
- if (! isset($container[$name])) {
- $class = '\Model\\'.ucfirst($name);
- $container[$name] = new $class($container);
- }
-
- return $container[$name];
- }
}
diff --git a/sources/app/Core/Translator.php b/sources/app/Core/Translator.php
index 1954ee2..0f5a77c 100644
--- a/sources/app/Core/Translator.php
+++ b/sources/app/Core/Translator.php
@@ -181,5 +181,8 @@ class Translator
if (file_exists($filename)) {
self::$locales = require $filename;
}
+ else {
+ self::$locales = array();
+ }
}
}
diff --git a/sources/app/Event/AuthEvent.php b/sources/app/Event/AuthEvent.php
new file mode 100644
index 0000000..ec1bec9
--- /dev/null
+++ b/sources/app/Event/AuthEvent.php
@@ -0,0 +1,27 @@
+auth_name = $auth_name;
+ $this->user_id = $user_id;
+ }
+
+ public function getUserId()
+ {
+ return $this->user_id;
+ }
+
+ public function getAuthType()
+ {
+ return $this->auth_name;
+ }
+}
diff --git a/sources/app/Event/Base.php b/sources/app/Event/Base.php
deleted file mode 100644
index 0217fa0..0000000
--- a/sources/app/Event/Base.php
+++ /dev/null
@@ -1,79 +0,0 @@
-container = $container;
- }
-
- /**
- * Return class information
- *
- * @access public
- * @return string
- */
- public function __toString()
- {
- return get_called_class();
- }
-
- /**
- * Load automatically models
- *
- * @access public
- * @param string $name Model name
- * @return mixed
- */
- public function __get($name)
- {
- return Tool::loadModel($this->container, $name);
- }
-
- /**
- * Get event namespace
- *
- * Event = task.close | Namespace = task
- *
- * @access public
- * @return string
- */
- public function getEventNamespace()
- {
- $event_name = $this->container['event']->getLastTriggeredEvent();
- return substr($event_name, 0, strpos($event_name, '.'));
- }
-}
diff --git a/sources/app/Event/CommentEvent.php b/sources/app/Event/CommentEvent.php
new file mode 100644
index 0000000..75a132d
--- /dev/null
+++ b/sources/app/Event/CommentEvent.php
@@ -0,0 +1,7 @@
+container = $values;
+ }
+
+ public function getAll()
+ {
+ return $this->container;
+ }
+
+ public function offsetSet($offset, $value)
+ {
+ if (is_null($offset)) {
+ $this->container[] = $value;
+ } else {
+ $this->container[$offset] = $value;
+ }
+ }
+
+ public function offsetExists($offset)
+ {
+ return isset($this->container[$offset]);
+ }
+
+ public function offsetUnset($offset)
+ {
+ unset($this->container[$offset]);
+ }
+
+ public function offsetGet($offset)
+ {
+ return isset($this->container[$offset]) ? $this->container[$offset] : null;
+ }
+}
diff --git a/sources/app/Event/NotificationListener.php b/sources/app/Event/NotificationListener.php
deleted file mode 100644
index 3c04932..0000000
--- a/sources/app/Event/NotificationListener.php
+++ /dev/null
@@ -1,83 +0,0 @@
-template = $template;
- }
-
- /**
- * Execute the action
- *
- * @access public
- * @param array $data Event data dictionary
- * @return bool True if the action was executed or false when not executed
- */
- public function execute(array $data)
- {
- $values = $this->getTemplateData($data);
- $users = $this->notification->getUsersList($values['task']['project_id']);
-
- if ($users) {
- $this->notification->sendEmails($this->template, $users, $values);
- return true;
- }
-
- return false;
- }
-
- /**
- * Fetch data for the mail template
- *
- * @access public
- * @param array $data Event data
- * @return array
- */
- public function getTemplateData(array $data)
- {
- $values = array();
-
- switch ($this->getEventNamespace()) {
- case 'task':
- $values['task'] = $this->taskFinder->getDetails($data['task_id']);
- break;
- case 'subtask':
- $values['subtask'] = $this->subtask->getById($data['id'], true);
- $values['task'] = $this->taskFinder->getDetails($data['task_id']);
- break;
- case 'file':
- $values['file'] = $data;
- $values['task'] = $this->taskFinder->getDetails($data['task_id']);
- break;
- case 'comment':
- $values['comment'] = $this->comment->getById($data['id']);
- $values['task'] = $this->taskFinder->getDetails($values['comment']['task_id']);
- break;
- }
-
- return $values;
- }
-}
diff --git a/sources/app/Event/ProjectActivityListener.php b/sources/app/Event/ProjectActivityListener.php
deleted file mode 100644
index 75efe65..0000000
--- a/sources/app/Event/ProjectActivityListener.php
+++ /dev/null
@@ -1,61 +0,0 @@
-getValues($data);
-
- return $this->projectActivity->createEvent(
- $values['task']['project_id'],
- $values['task']['id'],
- $this->acl->getUserId(),
- $this->container['event']->getLastTriggeredEvent(),
- $values
- );
- }
-
- return false;
- }
-
- /**
- * Get event activity data
- *
- * @access private
- * @param array $data Event data dictionary
- * @return array
- */
- private function getValues(array $data)
- {
- $values = array();
- $values['task'] = $this->taskFinder->getDetails($data['task_id']);
-
- switch ($this->getEventNamespace()) {
- case 'subtask':
- $values['subtask'] = $this->subTask->getById($data['id'], true);
- break;
- case 'comment':
- $values['comment'] = $this->comment->getById($data['id']);
- break;
- }
-
- return $values;
- }
-}
diff --git a/sources/app/Event/ProjectDailySummaryListener.php b/sources/app/Event/ProjectDailySummaryListener.php
deleted file mode 100644
index cd593ab..0000000
--- a/sources/app/Event/ProjectDailySummaryListener.php
+++ /dev/null
@@ -1,28 +0,0 @@
-projectDailySummary->updateTotals($data['project_id'], date('Y-m-d'));
- }
-
- return false;
- }
-}
diff --git a/sources/app/Event/ProjectModificationDateListener.php b/sources/app/Event/ProjectModificationDateListener.php
deleted file mode 100644
index abc176b..0000000
--- a/sources/app/Event/ProjectModificationDateListener.php
+++ /dev/null
@@ -1,30 +0,0 @@
-project->updateModificationDate($data['project_id']);
- }
-
- return false;
- }
-}
diff --git a/sources/app/Event/SubtaskEvent.php b/sources/app/Event/SubtaskEvent.php
new file mode 100644
index 0000000..229db86
--- /dev/null
+++ b/sources/app/Event/SubtaskEvent.php
@@ -0,0 +1,7 @@
+url = $url;
- }
-
- /**
- * Execute the action
- *
- * @access public
- * @param array $data Event data dictionary
- * @return bool True if the action was executed or false when not executed
- */
- public function execute(array $data)
- {
- $this->webhook->notify($this->url, $data);
- return true;
- }
-}
diff --git a/sources/app/Integration/Base.php b/sources/app/Integration/Base.php
new file mode 100644
index 0000000..babf8c8
--- /dev/null
+++ b/sources/app/Integration/Base.php
@@ -0,0 +1,49 @@
+container = $container;
+ }
+
+ /**
+ * Load automatically class from the container
+ *
+ * @access public
+ * @param string $name
+ * @return mixed
+ */
+ public function __get($name)
+ {
+ return $this->container[$name];
+ }
+}
diff --git a/sources/app/Model/GithubWebhook.php b/sources/app/Integration/GithubWebhook.php
similarity index 83%
rename from sources/app/Model/GithubWebhook.php
rename to sources/app/Integration/GithubWebhook.php
index 9c8bd36..fd0b49f 100644
--- a/sources/app/Model/GithubWebhook.php
+++ b/sources/app/Integration/GithubWebhook.php
@@ -1,11 +1,14 @@
event->trigger(self::EVENT_COMMIT, array('task_id' => $task_id) + $task);
+ if ($task['is_active'] == Task::STATUS_OPEN && $task['project_id'] == $this->project_id) {
+ $this->container['dispatcher']->dispatch(
+ self::EVENT_COMMIT,
+ new GenericEvent(array('task_id' => $task_id) + $task)
+ );
}
}
@@ -146,7 +152,11 @@ class GithubWebhook extends Base
'task_id' => $task['id'],
);
- $this->event->trigger(self::EVENT_ISSUE_COMMENT, $event);
+ $this->container['dispatcher']->dispatch(
+ self::EVENT_ISSUE_COMMENT,
+ new GenericEvent($event)
+ );
+
return true;
}
@@ -169,7 +179,11 @@ class GithubWebhook extends Base
'description' => $issue['body']."\n\n[".t('Github Issue').']('.$issue['html_url'].')',
);
- $this->event->trigger(self::EVENT_ISSUE_OPENED, $event);
+ $this->container['dispatcher']->dispatch(
+ self::EVENT_ISSUE_OPENED,
+ new GenericEvent($event)
+ );
+
return true;
}
@@ -191,7 +205,11 @@ class GithubWebhook extends Base
'reference' => $issue['number'],
);
- $this->event->trigger(self::EVENT_ISSUE_CLOSED, $event);
+ $this->container['dispatcher']->dispatch(
+ self::EVENT_ISSUE_CLOSED,
+ new GenericEvent($event)
+ );
+
return true;
}
@@ -216,7 +234,11 @@ class GithubWebhook extends Base
'reference' => $issue['number'],
);
- $this->event->trigger(self::EVENT_ISSUE_REOPENED, $event);
+ $this->container['dispatcher']->dispatch(
+ self::EVENT_ISSUE_REOPENED,
+ new GenericEvent($event)
+ );
+
return true;
}
@@ -244,7 +266,11 @@ class GithubWebhook extends Base
'reference' => $issue['number'],
);
- $this->event->trigger(self::EVENT_ISSUE_ASSIGNEE_CHANGE, $event);
+ $this->container['dispatcher']->dispatch(
+ self::EVENT_ISSUE_ASSIGNEE_CHANGE,
+ new GenericEvent($event)
+ );
+
return true;
}
@@ -271,7 +297,11 @@ class GithubWebhook extends Base
'reference' => $issue['number'],
);
- $this->event->trigger(self::EVENT_ISSUE_ASSIGNEE_CHANGE, $event);
+ $this->container['dispatcher']->dispatch(
+ self::EVENT_ISSUE_ASSIGNEE_CHANGE,
+ new GenericEvent($event)
+ );
+
return true;
}
@@ -299,7 +329,11 @@ class GithubWebhook extends Base
'label' => $label['name'],
);
- $this->event->trigger(self::EVENT_ISSUE_LABEL_CHANGE, $event);
+ $this->container['dispatcher']->dispatch(
+ self::EVENT_ISSUE_LABEL_CHANGE,
+ new GenericEvent($event)
+ );
+
return true;
}
@@ -328,7 +362,11 @@ class GithubWebhook extends Base
'category_id' => 0,
);
- $this->event->trigger(self::EVENT_ISSUE_LABEL_CHANGE, $event);
+ $this->container['dispatcher']->dispatch(
+ self::EVENT_ISSUE_LABEL_CHANGE,
+ new GenericEvent($event)
+ );
+
return true;
}
diff --git a/sources/app/Integration/GitlabWebhook.php b/sources/app/Integration/GitlabWebhook.php
new file mode 100644
index 0000000..f5df32a
--- /dev/null
+++ b/sources/app/Integration/GitlabWebhook.php
@@ -0,0 +1,213 @@
+project_id = $project_id;
+ }
+
+ /**
+ * Parse events
+ *
+ * @access public
+ * @param array $payload Gitlab event
+ * @return boolean
+ */
+ public function parsePayload(array $payload)
+ {
+ switch ($this->getType($payload)) {
+ case self::TYPE_PUSH:
+ return $this->handlePushEvent($payload);
+ case self::TYPE_ISSUE;
+ return $this->handleIssueEvent($payload);
+ }
+
+ return false;
+ }
+
+ /**
+ * Get event type
+ *
+ * @access public
+ * @param array $payload Gitlab event
+ * @return string
+ */
+ public function getType(array $payload)
+ {
+ if (isset($payload['object_kind']) && $payload['object_kind'] === 'issue') {
+ return self::TYPE_ISSUE;
+ }
+
+ if (isset($payload['commits'])) {
+ return self::TYPE_PUSH;
+ }
+
+ return '';
+ }
+
+ /**
+ * Parse push event
+ *
+ * @access public
+ * @param array $payload Gitlab event
+ * @return boolean
+ */
+ public function handlePushEvent(array $payload)
+ {
+ foreach ($payload['commits'] as $commit) {
+ $this->handleCommit($commit);
+ }
+
+ return true;
+ }
+
+ /**
+ * Parse commit
+ *
+ * @access public
+ * @param array $commit Gitlab commit
+ * @return boolean
+ */
+ public function handleCommit(array $commit)
+ {
+ $task_id = $this->task->getTaskIdFromText($commit['message']);
+
+ if (! $task_id) {
+ return false;
+ }
+
+ $task = $this->taskFinder->getById($task_id);
+
+ if (! $task) {
+ return false;
+ }
+
+ if ($task['is_active'] == Task::STATUS_OPEN && $task['project_id'] == $this->project_id) {
+
+ $this->container['dispatcher']->dispatch(
+ self::EVENT_COMMIT,
+ new TaskEvent(array('task_id' => $task_id) + $task)
+ );
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Parse issue event
+ *
+ * @access public
+ * @param array $payload Gitlab event
+ * @return boolean
+ */
+ public function handleIssueEvent(array $payload)
+ {
+ switch ($payload['object_attributes']['state']) {
+ case 'opened':
+ return $this->handleIssueOpened($payload['object_attributes']);
+ case 'closed':
+ return $this->handleIssueClosed($payload['object_attributes']);
+ }
+
+ return false;
+ }
+
+ /**
+ * Handle new issues
+ *
+ * @access public
+ * @param array $issue Issue data
+ * @return boolean
+ */
+ public function handleIssueOpened(array $issue)
+ {
+ $event = array(
+ 'project_id' => $this->project_id,
+ 'reference' => $issue['id'],
+ 'title' => $issue['title'],
+ 'description' => $issue['description']."\n\n[".t('Gitlab Issue').']('.$issue['url'].')',
+ );
+
+ $this->container['dispatcher']->dispatch(
+ self::EVENT_ISSUE_OPENED,
+ new GenericEvent($event)
+ );
+
+ return true;
+ }
+
+ /**
+ * Handle issue closing
+ *
+ * @access public
+ * @param array $issue Issue data
+ * @return boolean
+ */
+ public function handleIssueClosed(array $issue)
+ {
+ $task = $this->taskFinder->getByReference($issue['id']);
+
+ if ($task) {
+ $event = array(
+ 'project_id' => $this->project_id,
+ 'task_id' => $task['id'],
+ 'reference' => $issue['id'],
+ );
+
+ $this->container['dispatcher']->dispatch(
+ self::EVENT_ISSUE_CLOSED,
+ new GenericEvent($event)
+ );
+
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/sources/app/Locale/da_DK/translations.php b/sources/app/Locale/da_DK/translations.php
index c4fcc1c..3287aa5 100644
--- a/sources/app/Locale/da_DK/translations.php
+++ b/sources/app/Locale/da_DK/translations.php
@@ -182,7 +182,7 @@ return array(
'Change assignee' => 'Ændre ansvarlig',
'Change assignee for the task "%s"' => 'Ændre ansvarlig for opgaven: "%s"',
'Timezone' => 'Tidszone',
- 'Sorry, I didn\'t found this information in my database!' => 'Denne information kunne ikke findes i databasen!',
+ 'Sorry, I didn\'t find this information in my database!' => 'Denne information kunne ikke findes i databasen!',
'Page not found' => 'Siden er ikke fundet',
'Complexity' => 'Kompleksitet',
'limit' => 'Begrænsning',
@@ -194,7 +194,7 @@ return array(
'Allow this user' => 'Tillad denne bruger',
'Only those users have access to this project:' => 'Kunne disse brugere har adgang til dette projekt:',
'Don\'t forget that administrators have access to everything.' => 'Glem ikke at administratorer har adgang til alt.',
- 'revoke' => 'fjern',
+ 'Revoke' => 'Fjern',
'List of authorized users' => 'Liste over autoriserede brugere',
'User' => 'Bruger',
'Nobody have access to this project.' => 'Ingen har adgang til dette projekt.',
@@ -213,6 +213,7 @@ return array(
'Invalid date' => 'Ugyldig dato',
'Must be done before %B %e, %Y' => 'Skal være fuldført inden %d.%m.%Y',
'%B %e, %Y' => '%d.%m.%Y',
+ // '%b %e, %Y' => '',
'Automatic actions' => 'Automatiske handlinger',
'Your automatic action have been created successfully.' => 'Din automatiske handling er oprettet.',
'Unable to create your automatic action.' => 'Din automatiske handling kunne ikke oprettes.',
@@ -468,18 +469,18 @@ return array(
'Unable to change the password.' => 'Adgangskoden kunne ikke ændres.',
'Change category for the task "%s"' => 'Skift kategori for opgaven "%s"',
'Change category' => 'Skift kategori',
- '%s updated the task #%d' => '%s opdatert opgaven #%d',
- '%s open the task #%d' => '%s åben opgaven #%d',
- '%s moved the task #%d to the position #%d in the column "%s"' => '%s flyt opgaven #%d til positionen #%d i kolonnen "%s"',
- '%s moved the task #%d to the column "%s"' => '%s flyttede opgaven #%d til kolonnen "%s"',
- '%s created the task #%d' => '%s oprettede opgaven #%d',
- '%s closed the task #%d' => '',
- '%s created a subtask for the task #%d' => '%s oprettede en under-opgave for opgaven #%d',
- '%s updated a subtask for the task #%d' => '%s opdaterede en under-opgave for opgaven #%d',
+ '%s updated the task %s' => '%s opdatert opgaven %s',
+ '%s opened the task %s' => '%s åben opgaven %s',
+ '%s moved the task %s to the position #%d in the column "%s"' => '%s flyt opgaven %s til positionen #%d i kolonnen "%s"',
+ '%s moved the task %s to the column "%s"' => '%s flyttede opgaven %s til kolonnen "%s"',
+ '%s created the task %s' => '%s oprettede opgaven %s',
+ '%s closed the task %s' => '',
+ '%s created a subtask for the task %s' => '%s oprettede en under-opgave for opgaven %s',
+ '%s updated a subtask for the task %s' => '%s opdaterede en under-opgave for opgaven %s',
'Assigned to %s with an estimate of %s/%sh' => 'Tildelt til %s med en estimering på %s/%sh',
'Not assigned, estimate of %sh' => 'Ikke tildelt, estimeret til %sh',
- '%s updated a comment on the task #%d' => '%s opdateret en kommentar på opgaven #%d',
- '%s commented the task #%d' => '%s har kommenteret opgaven #%d',
+ '%s updated a comment on the task %s' => '%s opdateret en kommentar på opgaven %s',
+ '%s commented the task %s' => '%s har kommenteret opgaven %s',
'%s\'s activity' => '%s\'s aktvitet',
'No activity.' => 'Ingen aktivitet',
'RSS feed' => 'RSS feed',
@@ -498,7 +499,7 @@ return array(
'Default columns for new projects (Comma-separated)' => 'Standard kolonne for nye projekter (kommasepareret)',
'Task assignee change' => 'Opgaven ansvarlig ændring',
'%s change the assignee of the task #%d to %s' => '%s skrift ansvarlig for opgaven #%d til %s',
- '%s change the assignee of the task #%d to %s' => '%s skift ansvarlig for opgaven #%d til %s',
+ '%s changed the assignee of the task %s to %s' => '%s skift ansvarlig for opgaven %s til %s',
'[%s][Column Change] %s (#%d)' => '[%s][Kolonne Skift] %s (#%d)',
'[%s][Position Change] %s (#%d)' => '[%s][Position Skift] %s (#%d)',
'[%s][Assignee Change] %s (#%d)' => '[%s][Ansvarlig Skift] %s (#%d)',
@@ -555,8 +556,8 @@ return array(
// 'Webhooks' => '',
// 'API' => '',
// 'Integration' => '',
- // 'Github webhook' => '',
- // 'Help on Github webhook' => '',
+ // 'Github webhooks' => '',
+ // 'Help on Github webhooks' => '',
// 'Create a comment from an external provider' => '',
// 'Github issue comment created' => '',
// 'Configure' => '',
@@ -602,4 +603,49 @@ return array(
// 'Nothing to preview...' => '',
// 'Preview' => '',
// 'Write' => '',
+ // 'Active swimlanes' => '',
+ // 'Add a new swimlane' => '',
+ // 'Change default swimlane' => '',
+ // 'Default swimlane' => '',
+ // 'Do you really want to remove this swimlane: "%s"?' => '',
+ // 'Inactive swimlanes' => '',
+ // 'Set project manager' => '',
+ // 'Set project member' => '',
+ // 'Remove a swimlane' => '',
+ // 'Rename' => '',
+ // 'Show default swimlane' => '',
+ // 'Swimlane modification for the project "%s"' => '',
+ // 'Swimlane not found.' => '',
+ // 'Swimlane removed successfully.' => '',
+ // 'Swimlanes' => '',
+ // 'Swimlane updated successfully.' => '',
+ // 'The default swimlane have been updated successfully.' => '',
+ // 'Unable to create your swimlane.' => '',
+ // 'Unable to remove this swimlane.' => '',
+ // 'Unable to update this swimlane.' => '',
+ // 'Your swimlane have been created successfully.' => '',
+ // 'Example: "Bug, Feature Request, Improvement"' => '',
+ // 'Default categories for new projects (Comma-separated)' => '',
+ // 'Gitlab commit received' => '',
+ // 'Gitlab issue opened' => '',
+ // 'Gitlab issue closed' => '',
+ // 'Gitlab webhooks' => '',
+ // 'Help on Gitlab webhooks' => '',
+ // 'Integrations' => '',
+ // 'Integration with third-party services' => '',
+ // 'Role for this project' => '',
+ // 'Project manager' => '',
+ // 'Project member' => '',
+ // 'A project manager can change the settings of the project and have more privileges than a standard user.' => '',
+ // 'Gitlab Issue' => '',
+ // 'Subtask Id' => '',
+ // 'Subtasks' => '',
+ // 'Subtasks Export' => '',
+ // 'Subtasks exportation for "%s"' => '',
+ // 'Task Title' => '',
+ // 'Untitled' => '',
+ // 'Application default' => '',
+ // 'Language:' => '',
+ // 'Timezone:' => '',
+ // 'Next' => '',
);
diff --git a/sources/app/Locale/de_DE/translations.php b/sources/app/Locale/de_DE/translations.php
index 9881223..c85c97c 100644
--- a/sources/app/Locale/de_DE/translations.php
+++ b/sources/app/Locale/de_DE/translations.php
@@ -49,7 +49,7 @@ return array(
'No project' => 'Keine Projekte',
'Project' => 'Projekt',
'Status' => 'Status',
- 'Tasks' => 'Aufgabe',
+ 'Tasks' => 'Aufgaben',
'Board' => 'Pinnwand',
'Actions' => 'Aktionen',
'Inactive' => 'Inaktiv',
@@ -182,7 +182,7 @@ return array(
'Change assignee' => 'Zuständigkeit ändern',
'Change assignee for the task "%s"' => 'Zuständigkeit für diese Aufgabe ändern: "%s"',
'Timezone' => 'Zeitzone',
- 'Sorry, I didn\'t found this information in my database!' => 'Diese Information wurde in der Datenbank nicht gefunden!',
+ 'Sorry, I didn\'t find this information in my database!' => 'Diese Information wurde in der Datenbank nicht gefunden!',
'Page not found' => 'Seite nicht gefunden',
'Complexity' => 'Komplexität',
'limit' => 'Limit',
@@ -194,7 +194,7 @@ return array(
'Allow this user' => 'Diesen Benutzer autorisieren',
'Only those users have access to this project:' => 'Nur diese Benutzer haben Zugriff zum Projekt:',
'Don\'t forget that administrators have access to everything.' => 'Nicht vergessen: Administratoren haben überall Zugriff.',
- 'revoke' => 'entfernen',
+ 'Revoke' => 'Entfernen',
'List of authorized users' => 'Liste der autorisierten Benutzer',
'User' => 'Benutzer',
'Nobody have access to this project.' => 'Niemand hat Zugriff auf dieses Projekt.',
@@ -202,9 +202,9 @@ return array(
'Comments' => 'Kommentare',
'Post comment' => 'Kommentieren',
'Write your text in Markdown' => 'Schreibe deinen Text in Markdown-Syntax',
- 'Leave a comment' => 'Kommentar eingeben...',
+ 'Leave a comment' => 'Kommentar eingeben',
'Comment is required' => 'Ein Kommentar wird benötigt',
- 'Leave a description' => 'Beschreibung eingeben...',
+ 'Leave a description' => 'Beschreibung eingeben',
'Comment added successfully.' => 'Kommentar erfolgreich hinzugefügt.',
'Unable to create your comment.' => 'Hinzufügen eines Kommentars nicht möglich.',
'The description is required' => 'Eine Beschreibung wird benötigt',
@@ -213,8 +213,9 @@ return array(
'Invalid date' => 'Ungültiges Datum',
'Must be done before %B %e, %Y' => 'Muss vor dem %d.%m.%Y erledigt werden',
'%B %e, %Y' => '%d.%m.%Y',
+ // '%b %e, %Y' => '',
'Automatic actions' => 'Automatische Aktionen',
- 'Your automatic action have been created successfully.' => 'Die Automatische Aktion wurde erfolgreich erstellt.',
+ 'Your automatic action have been created successfully.' => 'Die automatische Aktion wurde erfolgreich erstellt.',
'Unable to create your automatic action.' => 'Erstellen der automatischen Aktion nicht möglich.',
'Remove an action' => 'Aktion löschen',
'Unable to remove this action.' => 'Löschen der Aktion nicht möglich.',
@@ -222,8 +223,8 @@ return array(
'Automatic actions for the project "%s"' => 'Automatische Aktionen für das Projekt "%s"',
'Defined actions' => 'Definierte Aktionen',
'Add an action' => 'Aktion hinzufügen',
- 'Event name' => 'Ereignis',
- 'Action name' => 'Aktion',
+ 'Event name' => 'Ereignisname',
+ 'Action name' => 'Aktionsname',
'Action parameters' => 'Aktionsparameter',
'Action' => 'Aktion',
'Event' => 'Ereignis',
@@ -331,7 +332,7 @@ return array(
'Remove a file' => 'Datei löschen',
'Unable to remove this file.' => 'Löschen der Datei nicht möglich.',
'File removed successfully.' => 'Datei erfolgreich gelöscht.',
- 'Attach a document' => 'Datei anhängen',
+ 'Attach a document' => 'Dokument anhängen',
'Do you really want to remove this file: "%s"?' => 'Soll diese Datei wirklich gelöscht werden: "%s"?',
'open' => 'öffnen',
'Attachments' => 'Anhänge',
@@ -366,7 +367,7 @@ return array(
'Sub-task added successfully.' => 'Teilaufgabe erfolgreich angelegt.',
'Maximum size: ' => 'Maximalgröße: ',
'Unable to upload the file.' => 'Hochladen der Datei nicht möglich.',
- 'Display another project' => 'Zu Projekt wechseln...',
+ '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',
@@ -420,7 +421,7 @@ return array(
'[Kanboard] Notification' => '[Kanboard] Benachrichtigung',
'I want to receive notifications only for those projects:' => 'Ich möchte nur für diese Projekte Benachrichtigungen erhalten:',
'view the task on Kanboard' => 'diese Aufgabe auf dem Kanboard zeigen',
- 'Public access' => 'Öffentlich',
+ 'Public access' => 'Öffentlicher Zugriff',
'Category management' => 'Kategorien verwalten',
'User management' => 'Benutzer verwalten',
'Active tasks' => 'Aktive Aufgaben',
@@ -448,12 +449,12 @@ return array(
'Username:' => 'Benutzername',
'Name:' => 'Name',
'Email:' => 'E-Mail',
- 'Default project:' => 'Standardprojekt',
- 'Notifications:' => 'Benachrichtigungen',
+ 'Default project:' => 'Standardprojekt:',
+ 'Notifications:' => 'Benachrichtigungen:',
'Notifications' => 'Benachrichtigungen',
'Group:' => 'Gruppe',
'Regular user' => 'Standardbenutzer',
- 'Account type:' => 'Accounttyp',
+ 'Account type:' => 'Accounttyp:',
'Edit profile' => 'Profil bearbeiten',
'Change password' => 'Passwort ändern',
'Password modification' => 'Passwortänderung',
@@ -468,18 +469,18 @@ return array(
'Unable to change the password.' => 'Passwort konnte nicht geändert werden.',
'Change category for the task "%s"' => 'Kategorie der Aufgabe "%s" ändern',
'Change category' => 'Kategorie ändern',
- '%s updated the task #%d' => '%s hat die Aufgabe #%d aktualisiert',
- '%s open the task #%d' => '%s hat die Aufgabe #%d geöffnet',
- '%s moved the task #%d to the position #%d in the column "%s"' => '%s hat die Aufgabe #%d auf die Position #%d in der Spalte "%s" verschoben',
- '%s moved the task #%d to the column "%s"' => '%s hat die Aufgabe #%d in die Spalte "%s" verschoben',
- '%s created the task #%d' => '%s hat die Aufgabe #%d angelegt',
- '%s closed the task #%d' => '%s hat die Aufgabe #%d geschlossen',
- '%s created a subtask for the task #%d' => '%s hat eine Teilaufgabe für die Aufgabe #%d angelegt',
- '%s updated a subtask for the task #%d' => '%s hat eine Teilaufgabe der Aufgabe #%d verändert',
+ '%s updated the task %s' => '%s hat die Aufgabe %s aktualisiert',
+ '%s opened the task %s' => '%s hat die Aufgabe %s geöffnet',
+ '%s moved the task %s to the position #%d in the column "%s"' => '%s hat die Aufgabe %s auf die Position #%d in der Spalte "%s" verschoben',
+ '%s moved the task %s to the column "%s"' => '%s hat die Aufgabe %s in die Spalte "%s" verschoben',
+ '%s created the task %s' => '%s hat die Aufgabe %s angelegt',
+ '%s closed the task %s' => '%s hat die Aufgabe %s geschlossen',
+ '%s created a subtask for the task %s' => '%s hat eine Teilaufgabe für die Aufgabe %s angelegt',
+ '%s updated a subtask for the task %s' => '%s hat eine Teilaufgabe der Aufgabe %s verändert',
'Assigned to %s with an estimate of %s/%sh' => 'An %s zugewiesen mit einer Schätzung von %s/%s Stunden',
'Not assigned, estimate of %sh' => 'Nicht zugewiesen, Schätzung von %s Stunden',
- '%s updated a comment on the task #%d' => '%s hat einen Kommentat der Aufgabe #%d aktualisiert',
- '%s commented the task #%d' => '%s hat die Aufgabe #%d kommentiert',
+ '%s updated a comment on the task %s' => '%s hat einen Kommentat der Aufgabe %s aktualisiert',
+ '%s commented the task %s' => '%s hat die Aufgabe %s kommentiert',
'%s\'s activity' => '%s\'s Aktivität',
'No activity.' => 'Keine Aktivität.',
'RSS feed' => 'RSS Feed',
@@ -498,7 +499,7 @@ return array(
'Default columns for new projects (Comma-separated)' => 'Standardspalten für neue Projekte (komma-getrennt)',
'Task assignee change' => 'Zuständigkeit geändert',
'%s change the assignee of the task #%d to %s' => '%s hat die Zusständigkeit der Aufgabe #%d geändert um %s',
- '%s change the assignee of the task #%d to %s' => '%s hat die Zuständigkeit der Aufgabe #%d geändert um %s',
+ '%s changed the assignee of the task %s to %s' => '%s hat die Zuständigkeit der Aufgabe %s geändert um %s',
'[%s][Column Change] %s (#%d)' => '[%s][Spaltenänderung] %s (#%d)',
'[%s][Position Change] %s (#%d)' => '[%s][Positionsänderung] %s (#%d)',
'[%s][Assignee Change] %s (#%d)' => '[%s][Zuständigkeitsänderung] %s (#%d)',
@@ -511,7 +512,7 @@ return array(
'Github issue assignee change' => 'Github Fehlerzuständigkeit geändert',
'Github issue label change' => 'Github Fehlerkennzeichnung verändert',
'Create a task from an external provider' => 'Eine Aufgabe durch einen externen Provider hinzufügen',
- // 'Change the assignee based on an external username' => '',
+ 'Change the assignee based on an external username' => 'Zuordnung ändern basierend auf externem Benutzernamen',
'Change the category based on an external label' => 'Kategorie basierend auf einer externen Kennzeichnung ändern',
'Reference' => 'Referenz',
'Reference: %s' => 'Referenz: %s',
@@ -544,8 +545,8 @@ return array(
'Time spent: %s hours' => 'Aufgewendete Zeit: %s Stunden',
'Started on %B %e, %Y' => 'Gestartet am %B %e %Y',
'Start date' => 'Startdatum',
- 'Time estimated' => 'Geplante Zeit',
- 'There is nothing assigned to you.' => 'Es ist nichts an Sie zugewiesen.',
+ 'Time estimated' => 'Geschätzte Zeit',
+ 'There is nothing assigned to you.' => 'Ihnen ist nichts zugewiesen.',
'My tasks' => 'Meine Aufgaben',
'Activity stream' => 'Letzte Aktivitäten',
'Dashboard' => 'Dashboard',
@@ -555,11 +556,11 @@ return array(
'Webhooks' => 'Webhooks',
'API' => 'API',
'Integration' => 'Integration',
- 'Github webhook' => 'Github Webhook',
- 'Help on Github webhook' => 'Hilfe bei einem Github Webhook',
+ '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',
- 'Configure' => 'konfigurieren',
+ 'Configure' => 'Einstellungen',
'Project management' => 'Projektmanagement',
'My projects' => 'Meine Projekte',
'Columns' => 'Spalten',
@@ -602,4 +603,49 @@ return array(
'Nothing to preview...' => 'Nichts in der Vorschau anzuzeigen ...',
'Preview' => 'Vorschau',
'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',
+ '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"',
+ '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.',
+ '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 Thema eröffnet',
+ 'Gitlab issue closed' => 'Gitlab Thema geschlossen',
+ 'Gitlab webhooks' => 'Gitlab Webhook',
+ 'Help on Gitlab webhooks' => 'Hilfe für Gitlab Webhooks',
+ 'Integrations' => 'Integration',
+ 'Integration with third-party services' => 'Integration von Fremdleistungen',
+ '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 Thema',
+ 'Subtask Id' => 'Teilaufgaben Id',
+ 'Subtasks' => 'Teilaufgaben',
+ 'Subtasks Export' => 'Teilaufgaben Export',
+ 'Subtasks exportation for "%s"' => 'Teilaufgaben Export für "%s"',
+ 'Task Title' => 'Aufgaben Titel',
+ 'Untitled' => 'unbetitelt',
+ 'Application default' => 'Anwendungsstandard',
+ 'Language:' => 'Sprache:',
+ 'Timezone:' => 'Zeitzone:',
+ // 'Next' => '',
);
diff --git a/sources/app/Locale/es_ES/translations.php b/sources/app/Locale/es_ES/translations.php
index a261832..5dafde9 100644
--- a/sources/app/Locale/es_ES/translations.php
+++ b/sources/app/Locale/es_ES/translations.php
@@ -182,7 +182,7 @@ return array(
'Change assignee' => 'Cambiar la persona asignada',
'Change assignee for the task "%s"' => 'Cambiar la persona asignada por la tarea « %s »',
'Timezone' => 'Zona horaria',
- 'Sorry, I didn\'t found this information in my database!' => 'Lo siento no he encontrado información en la base de datos!',
+ 'Sorry, I didn\'t find this information in my database!' => 'Lo siento no he encontrado información en la base de datos!',
'Page not found' => 'Página no encontrada',
'Complexity' => 'Complejidad',
'limit' => 'límite',
@@ -194,7 +194,7 @@ return array(
'Allow this user' => 'Autorizar este usuario',
'Only those users have access to this project:' => 'Solo estos usuarios tienen acceso a este proyecto:',
'Don\'t forget that administrators have access to everything.' => 'No olvide que los administradores tienen acceso a todo.',
- 'revoke' => 'revocar',
+ 'Revoke' => 'Revocar',
'List of authorized users' => 'Lista de los usuarios autorizados',
'User' => 'Usuario',
// 'Nobody have access to this project.' => '',
@@ -213,6 +213,7 @@ return array(
'Invalid date' => 'Fecha no válida',
'Must be done before %B %e, %Y' => 'Debe de estar hecho antes del %d/%m/%Y',
'%B %e, %Y' => '%d/%m/%Y',
+ // '%b %e, %Y' => '',
'Automatic actions' => 'Acciones automatizadas',
'Your automatic action have been created successfully.' => 'La acción automatizada ha sido creada correctamente.',
'Unable to create your automatic action.' => 'No se puede crear esta acción automatizada.',
@@ -452,7 +453,7 @@ return array(
'Notifications:' => 'Notificaciones:',
// 'Notifications' => '',
'Group:' => 'Grupo:',
- 'Regular user' => 'Usuario regular:',
+ 'Regular user' => 'Usuario regular',
'Account type:' => 'Tipo de Cuenta:',
'Edit profile' => 'Editar perfil',
'Change password' => 'Cambiar contraseña',
@@ -468,18 +469,18 @@ return array(
'Unable to change the password.' => 'No pude cambiar la contraseña.',
'Change category for the task "%s"' => 'Cambiar la categoría de la tarea "%s"',
'Change category' => 'Cambiar categoría',
- '%s updated the task #%d' => '%s actualizó la tarea #%d',
- '%s open the task #%d' => '%s abrió la tarea #%d',
- '%s moved the task #%d to the position #%d in the column "%s"' => '%s movió la tarea #%d a la posición #%d de la columna "%s"',
- '%s moved the task #%d to the column "%s"' => '%s movió la tarea #%d a la columna "%s"',
- '%s created the task #%d' => '%s creó la tarea #%d',
- '%s closed the task #%d' => '%s cerró la tarea #%d',
- '%s created a subtask for the task #%d' => '%s creó una subtarea para la tarea #%d',
- '%s updated a subtask for the task #%d' => '%s actualizó una subtarea para la tarea #%d',
+ '%s updated the task %s' => '%s actualizó la tarea %s',
+ '%s opened the task %s' => '%s abrió la tarea %s',
+ '%s moved the task %s to the position #%d in the column "%s"' => '%s movió la tarea %s a la posición #%d de la columna "%s"',
+ '%s moved the task %s to the column "%s"' => '%s movió la tarea %s a la columna "%s"',
+ '%s created the task %s' => '%s creó la tarea %s',
+ '%s closed the task %s' => '%s cerró la tarea %s',
+ '%s created a subtask for the task %s' => '%s creó una subtarea para la tarea %s',
+ '%s updated a subtask for the task %s' => '%s actualizó una subtarea para la tarea %s',
'Assigned to %s with an estimate of %s/%sh' => 'Asignada a %s con una estimación de %s/%sh',
'Not assigned, estimate of %sh' => 'No asignada, se estima en %sh',
- '%s updated a comment on the task #%d' => '%s actualizó un comentario de la tarea #%d',
- '%s commented the task #%d' => '%s comentó la tarea #%d',
+ '%s updated a comment on the task %s' => '%s actualizó un comentario de la tarea %s',
+ '%s commented the task %s' => '%s comentó la tarea %s',
'%s\'s activity' => 'Actividad de %s',
'No activity.' => 'Sin actividad',
'RSS feed' => 'Fichero RSS',
@@ -498,7 +499,7 @@ return array(
'Default columns for new projects (Comma-separated)' => 'Columnas por defecto de los nuevos proyectos (Separadas mediante comas)',
'Task assignee change' => 'Cambiar persona asignada a la tarea',
// '%s change the assignee of the task #%d to %s' => '',
- // '%s change the assignee of the task #%d to %s' => '',
+ // '%s changed the assignee of the task %s to %s' => '',
'[%s][Column Change] %s (#%d)' => '[%s][Cambia Columna] %s (#%d)',
'[%s][Position Change] %s (#%d)' => '[%s][Cambia Posición] %s (#%d)',
'[%s][Assignee Change] %s (#%d)' => '[%s][Cambia Persona Asignada] %s (#%d)',
@@ -555,8 +556,8 @@ return array(
// 'Webhooks' => '',
// 'API' => '',
// 'Integration' => '',
- // 'Github webhook' => '',
- // 'Help on Github webhook' => '',
+ // 'Github webhooks' => '',
+ // 'Help on Github webhooks' => '',
// 'Create a comment from an external provider' => '',
// 'Github issue comment created' => '',
// 'Configure' => '',
@@ -602,4 +603,49 @@ return array(
// 'Nothing to preview...' => '',
// 'Preview' => '',
// 'Write' => '',
+ // 'Active swimlanes' => '',
+ // 'Add a new swimlane' => '',
+ // 'Change default swimlane' => '',
+ // 'Default swimlane' => '',
+ // 'Do you really want to remove this swimlane: "%s"?' => '',
+ // 'Inactive swimlanes' => '',
+ // 'Set project manager' => '',
+ // 'Set project member' => '',
+ // 'Remove a swimlane' => '',
+ // 'Rename' => '',
+ // 'Show default swimlane' => '',
+ // 'Swimlane modification for the project "%s"' => '',
+ // 'Swimlane not found.' => '',
+ // 'Swimlane removed successfully.' => '',
+ // 'Swimlanes' => '',
+ // 'Swimlane updated successfully.' => '',
+ // 'The default swimlane have been updated successfully.' => '',
+ // 'Unable to create your swimlane.' => '',
+ // 'Unable to remove this swimlane.' => '',
+ // 'Unable to update this swimlane.' => '',
+ // 'Your swimlane have been created successfully.' => '',
+ // 'Example: "Bug, Feature Request, Improvement"' => '',
+ // 'Default categories for new projects (Comma-separated)' => '',
+ // 'Gitlab commit received' => '',
+ // 'Gitlab issue opened' => '',
+ // 'Gitlab issue closed' => '',
+ // 'Gitlab webhooks' => '',
+ // 'Help on Gitlab webhooks' => '',
+ // 'Integrations' => '',
+ // 'Integration with third-party services' => '',
+ // 'Role for this project' => '',
+ // 'Project manager' => '',
+ // 'Project member' => '',
+ // 'A project manager can change the settings of the project and have more privileges than a standard user.' => '',
+ // 'Gitlab Issue' => '',
+ // 'Subtask Id' => '',
+ // 'Subtasks' => '',
+ // 'Subtasks Export' => '',
+ // 'Subtasks exportation for "%s"' => '',
+ // 'Task Title' => '',
+ // 'Untitled' => '',
+ // 'Application default' => '',
+ // 'Language:' => '',
+ // 'Timezone:' => '',
+ // 'Next' => '',
);
diff --git a/sources/app/Locale/fi_FI/translations.php b/sources/app/Locale/fi_FI/translations.php
index c0bc4bb..c3f1fbd 100644
--- a/sources/app/Locale/fi_FI/translations.php
+++ b/sources/app/Locale/fi_FI/translations.php
@@ -182,19 +182,19 @@ return array(
'Change assignee' => 'Vaihda suorittajaa',
'Change assignee for the task "%s"' => 'Vaihda suorittajaa tehtävälle %s',
'Timezone' => 'Aikavyöhyke',
- 'Sorry, I didn\'t found this information in my database!' => 'Anteeksi, en löytänyt tätä tietoa tietokannastani',
+ 'Sorry, I didn\'t find this information in my database!' => 'Anteeksi, en löytänyt tätä tietoa tietokannastani',
'Page not found' => 'Sivua ei löydy',
'Complexity' => 'Monimutkaisuus',
'limit' => 'raja',
'Task limit' => 'Tehtävien maksimimäärä',
- // 'Task count' => '',
+ 'Task count' => 'Tehtävien määrä',
'This value must be greater than %d' => 'Arvon täytyy olla suurempi kuin %d',
'Edit project access list' => 'Muuta projektin käyttäjiä',
'Edit users access' => 'Muuta käyttäjien pääsyä',
'Allow this user' => 'Salli tämä projekti',
'Only those users have access to this project:' => 'Vain näillä käyttäjillä on pääsy projektiin:',
'Don\'t forget that administrators have access to everything.' => 'Muista että ylläpitäjät pääsevät kaikkialle.',
- 'revoke' => 'poista',
+ 'Revoke' => 'Poista',
'List of authorized users' => 'Sallittujen käyttäjien lista',
'User' => 'Käyttäjät',
// 'Nobody have access to this project.' => '',
@@ -213,6 +213,7 @@ return array(
'Invalid date' => 'Virheellinen päiväys',
'Must be done before %B %e, %Y' => 'Täytyy suorittaa ennen %d.%m.%Y',
'%B %e, %Y' => '%d.%m.%Y',
+ // '%b %e, %Y' => '',
'Automatic actions' => 'Automaattiset toiminnot',
'Your automatic action have been created successfully.' => 'Toiminto suoritettiin onnistuneesti.',
'Unable to create your automatic action.' => 'Automaattisen toiminnon luominen epäonnistui.',
@@ -367,14 +368,14 @@ 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.' => '',
- // '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' => '',
+ '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',
'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',
@@ -388,24 +389,24 @@ return array(
'Completion date' => 'Valmistumispäivä',
'Webhook URL for task creation' => 'Webhook URL tehtävän luomiselle',
'Webhook URL for task modification' => 'Webhook URL tehtävän muokkaamiselle',
- // 'Clone' => '',
- // 'Clone Project' => '',
- // 'Project cloned successfully.' => '',
- // 'Unable to clone this project.' => '',
- // 'Email notifications' => '',
- // 'Enable email notifications' => '',
- // 'Task position:' => '',
- // 'The task #%d have been opened.' => '',
- // 'The task #%d have been closed.' => '',
- // 'Sub-task updated' => '',
- // 'Title:' => '',
- // 'Status:' => '',
- // 'Assignee:' => '',
- // 'Time tracking:' => '',
- // 'New sub-task' => '',
- // 'New attachment added "%s"' => '',
- // 'Comment updated' => '',
- // 'New comment posted by %s' => '',
+ 'Clone' => 'Kahdenna',
+ 'Clone Project' => 'Kahdenna projekti',
+ 'Project cloned successfully.' => 'Projekti kahdennettu onnistuneesti',
+ 'Unable to clone this project.' => 'Projektin kahdennus epäonnistui',
+ 'Email notifications' => 'Sähköposti-ilmoitukset',
+ 'Enable email notifications' => 'Ota käyttöön sähköposti-ilmoitukset',
+ 'Task position:' => 'Tehtävän sijainti',
+ 'The task #%d have been opened.' => 'Tehtävä #%d on avattu',
+ 'The task #%d have been closed.' => 'Tehtävä #%d on suljettu',
+ 'Sub-task updated' => 'Alitehtävä päivitetty',
+ 'Title:' => 'Otsikko:',
+ 'Status:' => 'Tila:',
+ 'Assignee:' => 'Vastaanottaja:',
+ 'Time tracking:' => 'Ajan seuranta:',
+ 'New sub-task' => 'Uusi alitehtävä',
+ 'New attachment added "%s"' => 'Uusi liite lisätty "%s"',
+ 'Comment updated' => 'Kommentti päivitetty',
+ 'New comment posted by %s' => '%s lisäsi uuden kommentin',
// 'List of due tasks for the project "%s"' => '',
// '[%s][New attachment] %s (#%d)' => '',
// '[%s][New comment] %s (#%d)' => '',
@@ -418,188 +419,233 @@ return array(
// '[%s][Task opened] %s (#%d)' => '',
// '[%s][Due tasks]' => '',
// '[Kanboard] Notification' => '',
- // 'I want to receive notifications only for those projects:' => '',
- // 'view the task on Kanboard' => '',
- // 'Public access' => '',
- // 'Category management' => '',
- // 'User management' => '',
- // 'Active tasks' => '',
- // 'Disable public access' => '',
- // 'Enable public access' => '',
- // 'Active projects' => '',
- // 'Inactive projects' => '',
- // 'Public access disabled' => '',
- // 'Do you really want to disable this project: "%s"?' => '',
- // 'Do you really want to duplicate this project: "%s"?' => '',
- // 'Do you really want to enable this project: "%s"?' => '',
- // 'Project activation' => '',
- // 'Move the task to another project' => '',
- // 'Move to another project' => '',
- // 'Do you really want to duplicate this task?' => '',
- // 'Duplicate a task' => '',
- // 'External accounts' => '',
- // 'Account type' => '',
- // 'Local' => '',
- // 'Remote' => '',
- // 'Enabled' => '',
- // 'Disabled' => '',
- // 'Google account linked' => '',
- // 'Github account linked' => '',
- // 'Username:' => '',
- // 'Name:' => '',
- // 'Email:' => '',
- // 'Default project:' => '',
- // 'Notifications:' => '',
- // 'Notifications' => '',
- // 'Group:' => '',
- // 'Regular user' => '',
- // 'Account type:' => '',
- // 'Edit profile' => '',
- // 'Change password' => '',
- // 'Password modification' => '',
- // 'External authentications' => '',
- // 'Google Account' => '',
- // 'Github Account' => '',
- // 'Never connected.' => '',
- // 'No account linked.' => '',
- // 'Account linked.' => '',
- // 'No external authentication enabled.' => '',
- // 'Password modified successfully.' => '',
- // 'Unable to change the password.' => '',
- // 'Change category for the task "%s"' => '',
- // 'Change category' => '',
- // '%s updated the task #%d' => '',
- // '%s open the task #%d' => '',
- // '%s moved the task #%d to the position #%d in the column "%s"' => '',
- // '%s moved the task #%d to the column "%s"' => '',
- // '%s created the task #%d' => '',
- // '%s closed the task #%d' => '',
- // '%s created a subtask for the task #%d' => '',
- // '%s updated a subtask for the task #%d' => '',
- // 'Assigned to %s with an estimate of %s/%sh' => '',
- // 'Not assigned, estimate of %sh' => '',
- // '%s updated a comment on the task #%d' => '',
- // '%s commented the task #%d' => '',
- // '%s\'s activity' => '',
- // 'No activity.' => '',
- // 'RSS feed' => '',
- // '%s updated a comment on the task #%d' => '',
- // '%s commented on the task #%d' => '',
- // '%s updated a subtask for the task #%d' => '',
- // '%s created a subtask for the task #%d' => '',
- // '%s updated the task #%d' => '',
- // '%s created the task #%d' => '',
- // '%s closed the task #%d' => '',
- // '%s open the task #%d' => '',
- // '%s moved the task #%d to the column "%s"' => '',
- // '%s moved the task #%d to the position %d in the column "%s"' => '',
- // 'Activity' => '',
- // 'Default values are "%s"' => '',
- // 'Default columns for new projects (Comma-separated)' => '',
- // 'Task assignee change' => '',
- // '%s change the assignee of the task #%d to %s' => '',
- // '%s change the assignee of the task #%d to %s' => '',
+ 'I want to receive notifications only for those projects:' => 'Haluan vastaanottaa ilmoituksia ainoastaan näistä projekteista:',
+ 'view the task on Kanboard' => 'katso tehtävää Kanboardissa',
+ 'Public access' => 'Julkinen käyttöoikeus',
+ 'Category management' => 'Kategorioiden hallinta',
+ 'User management' => 'Käyttäjähallinta',
+ 'Active tasks' => 'Aktiiviset tehtävät',
+ 'Disable public access' => 'Poista käytöstä julkinen käyttöoikeus',
+ 'Enable public access' => 'Ota käyttöön ',
+ 'Active projects' => 'Aktiiviset projektit',
+ 'Inactive projects' => 'Passiiviset projektit',
+ 'Public access disabled' => 'Julkinen käyttöoikeus ei ole käytössä',
+ 'Do you really want to disable this project: "%s"?' => 'Haluatko varmasti tehdä projektista "%s" passiivisen?',
+ 'Do you really want to duplicate this project: "%s"?' => 'Haluatko varmasti kahdentaa projektin "%s"?',
+ 'Do you really want to enable this project: "%s"?' => 'Haluatko varmasti aktivoida projektinen "%s"',
+ 'Project activation' => 'Projektin aktivointi',
+ 'Move the task to another project' => 'Siirrä tehtävä toiseen projektiin',
+ 'Move to another project' => 'Siirrä toiseen projektiin',
+ 'Do you really want to duplicate this task?' => 'Haluatko varmasti kahdentaa tämän tehtävän?',
+ 'Duplicate a task' => 'Kahdenna tehtävä',
+ 'External accounts' => 'Muut tilit',
+ 'Account type' => 'Tilin tyyppi',
+ 'Local' => 'Paikallinen',
+ 'Remote' => 'Etä',
+ 'Enabled' => 'Käytössä',
+ 'Disabled' => 'Pois käytöstä',
+ 'Google account linked' => 'Google-tili liitetty',
+ 'Github account linked' => 'Github-tili liitetty',
+ 'Username:' => 'Käyttäjänimi:',
+ 'Name:' => 'Nimi:',
+ 'Email:' => 'Sähköpostiosoite:',
+ 'Default project:' => 'Oletusprojekti:',
+ 'Notifications:' => 'Ilmoitukset:',
+ 'Notifications' => 'Ilmoitukset',
+ 'Group:' => 'Ryhmä:',
+ 'Regular user' => 'Peruskäyttäjä',
+ 'Account type:' => 'Tilin tyyppi:',
+ 'Edit profile' => 'Muokkaa profiilia',
+ 'Change password' => 'Vaihda salasana',
+ 'Password modification' => 'Salasanan vaihto',
+ 'External authentications' => 'Muut tunnistautumistavat',
+ 'Google Account' => 'Google-tili',
+ 'Github Account' => 'Github-tili',
+ 'Never connected.' => 'Ei koskaan liitetty.',
+ 'No account linked.' => 'Tiliä ei ole liitetty.',
+ 'Account linked.' => 'Tili on liitetty.',
+ 'No external authentication enabled.' => 'Muita tunnistautumistapoja ei ole otettu käyttöön.',
+ 'Password modified successfully.' => 'Salasana vaihdettu onnistuneesti.',
+ 'Unable to change the password.' => 'Salasanan vaihto epäonnistui.',
+ 'Change category for the task "%s"' => 'Vaihda tehtävän "%s" kategoria',
+ 'Change category' => 'Vaihda kategoria',
+ '%s updated the task %s' => '%s päivitti tehtävän %s',
+ '%s opened the task %s' => '%s avasi tehtävän %s',
+ '%s moved the task %s to the position #%d in the column "%s"' => '%s siirsi tehtävän %s %d. sarakkeessa "%s"',
+ '%s moved the task %s to the column "%s"' => '%s siirsi tehtävän %s sarakkeeseen "%s"',
+ '%s created the task %s' => '%s loi tehtävän %s',
+ '%s closed the task %s' => '%s sulki tehtävän %s',
+ '%s created a subtask for the task %s' => '%s loi alitehtävän tehtävälle %s',
+ '%s updated a subtask for the task %s' => '%s päivitti tehtävän %s alitehtävää',
+ 'Assigned to %s with an estimate of %s/%sh' => 'Annettu henkilölle %s arviolla %s/%sh',
+ 'Not assigned, estimate of %sh' => 'Ei annettu kenellekään, arvio %sh',
+ '%s updated a comment on the task %s' => '%s päivitti kommentia tehtävässä %s',
+ '%s commented the task %s' => '%s kommentoi tehtävää %s',
+ '%s\'s activity' => 'Henkilön %s toiminta',
+ 'No activity.' => 'Ei toimintaa.',
+ 'RSS feed' => 'RSS-syöte',
+ '%s updated a comment on the task #%d' => '%s päivitti kommenttia tehtävässä #%d',
+ '%s commented on the task #%d' => '%s kommentoi tehtävää #%d',
+ '%s updated a subtask for the task #%d' => '%s päivitti tehtävän #%d alitehtävää',
+ '%s created a subtask for the task #%d' => '%s loi alitehtävän tehtävälle #%d',
+ '%s updated the task #%d' => '%s päivitti tehtävää #%d',
+ '%s created the task #%d' => '%s loi tehtävän #%d',
+ '%s closed the task #%d' => '%s sulki tehtävän #%d',
+ '%s open the task #%d' => '%s avasi tehtävän #%d',
+ '%s moved the task #%d to the column "%s"' => '%s siirsi tehtävän #%d sarakkeeseen "%s"',
+ '%s moved the task #%d to the position %d in the column "%s"' => '%s siirsi tehtävän #%d %d. sarakkeessa %s',
+ 'Activity' => 'Toiminta',
+ 'Default values are "%s"' => 'Oletusarvot ovat "%s"',
+ 'Default columns for new projects (Comma-separated)' => 'Oletussarakkeet uusille projekteille',
+ 'Task assignee change' => 'Tehtävän saajan vaihto',
+ '%s change the assignee of the task #%d to %s' => '%s vaihtoi tehtävän #%d saajaksi %s',
+ '%s changed the assignee of the task %s to %s' => '%s vaihtoi tehtävän %s saajaksi %s',
// '[%s][Column Change] %s (#%d)' => '',
// '[%s][Position Change] %s (#%d)' => '',
// '[%s][Assignee Change] %s (#%d)' => '',
- // 'New password for the user "%s"' => '',
- // 'Choose an event' => '',
- // 'Github commit received' => '',
- // 'Github issue opened' => '',
- // 'Github issue closed' => '',
- // 'Github issue reopened' => '',
- // 'Github issue assignee change' => '',
- // 'Github issue label change' => '',
- // 'Create a task from an external provider' => '',
- // 'Change the assignee based on an external username' => '',
- // 'Change the category based on an external label' => '',
- // 'Reference' => '',
- // 'Reference: %s' => '',
- // 'Label' => '',
- // 'Database' => '',
- // 'About' => '',
- // 'Database driver:' => '',
- // 'Board settings' => '',
- // 'URL and token' => '',
- // 'Webhook settings' => '',
- // 'URL for task creation:' => '',
- // 'Reset token' => '',
- // 'API endpoint:' => '',
- // 'Refresh interval for private board' => '',
- // 'Refresh interval for public board' => '',
- // 'Task highlight period' => '',
- // 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => '',
- // 'Frequency in second (60 seconds by default)' => '',
- // 'Frequency in second (0 to disable this feature, 10 seconds by default)' => '',
- // 'Application URL' => '',
- // 'Example: http://example.kanboard.net/ (used by email notifications)' => '',
- // 'Token regenerated.' => '',
- // 'Date format' => '',
- // 'ISO format is always accepted, example: "%s" and "%s"' => '',
- // 'New private project' => '',
- // 'This project is private' => '',
- // 'Type here to create a new sub-task' => '',
- // 'Add' => '',
- // 'Estimated time: %s hours' => '',
- // 'Time spent: %s hours' => '',
- // 'Started on %B %e, %Y' => '',
- // 'Start date' => '',
- // 'Time estimated' => '',
- // 'There is nothing assigned to you.' => '',
- // 'My tasks' => '',
- // 'Activity stream' => '',
- // 'Dashboard' => '',
- // 'Confirmation' => '',
- // 'Allow everybody to access to this project' => '',
- // 'Everybody have access to this project.' => '',
+ 'New password for the user "%s"' => 'Uusi salasana käyttäjälle "%s"',
+ 'Choose an event' => 'Valitse toiminta',
+ 'Github commit received' => 'Github-kommitti vastaanotettu',
+ 'Github issue opened' => 'Github-issue avattu',
+ 'Github issue closed' => 'Github-issue suljettu',
+ 'Github issue reopened' => 'Github-issue uudelleenavattu',
+ 'Github issue assignee change' => 'Github-issuen saajan vaihto',
+ 'Github issue label change' => 'Github-issuen labelin vaihto',
+ 'Create a task from an external provider' => 'Luo tehtävä ulkoiselta tarjoajalta',
+ 'Change the assignee based on an external username' => 'Vaihda tehtävän saajaa perustuen ulkoiseen käyttäjänimeen',
+ 'Change the category based on an external label' => 'Vaihda kategoriaa perustuen ulkoiseen labeliin',
+ 'Reference' => 'Viite',
+ 'Reference: %s' => 'Viite: %s',
+ 'Label' => 'Label',
+ 'Database' => 'Tietokanta',
+ 'About' => 'Tietoja',
+ 'Database driver:' => 'Tietokantaohjelmisto:',
+ 'Board settings' => 'Taulun asetukset',
+ 'URL and token' => 'URL ja token',
+ 'Webhook settings' => 'Webhookin asetukset',
+ 'URL for task creation:' => 'URL tehtävän luomiseksi:',
+ 'Reset token' => 'Vaihda token',
+ 'API endpoint:' => 'API päätepiste:',
+ 'Refresh interval for private board' => 'Päivitystiheys yksityisille tauluille',
+ 'Refresh interval for public board' => 'Päivitystiheys julkisille tauluille',
+ 'Task highlight period' => 'Tehtävän korostusaika',
+ 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Aika (sekunteina) kuinka kauan tehtävä voidaan katsoa äskettäin muokatuksi (0 poistaa toiminnon käytöstä, oletuksena 2 päivää)',
+ 'Frequency in second (60 seconds by default)' => 'Päivitystiheys sekunteina (60 sekuntia oletuksena)',
+ 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Päivitystiheys sekunteina (0 poistaa toiminnon käytöstä, oletuksena 10 sekuntia)',
+ 'Application URL' => 'Sovelluksen URL',
+ 'Example: http://example.kanboard.net/ (used by email notifications)' => 'Esimerkiksi: http://example.kanboard.net/ (käytetään sähköposti-ilmoituksissa)',
+ 'Token regenerated.' => 'Token uudelleenluotu.',
+ 'Date format' => 'Päiväyksen muoto',
+ 'ISO format is always accepted, example: "%s" and "%s"' => 'ISO-muoto on aina hyväksytty, esimerkiksi %s ja %s',
+ 'New private project' => 'Uusi yksityinen projekti',
+ 'This project is private' => 'Tämä projekti on yksityinen',
+ 'Type here to create a new sub-task' => 'Kirjoita tähän luodaksesi uuden alitehtävän',
+ 'Add' => 'Lisää',
+ 'Estimated time: %s hours' => 'Arvioitu aika: %s tuntia',
+ 'Time spent: %s hours' => 'Aikaa kulunut: %s tuntia',
+ 'Started on %B %e, %Y' => 'Aloitettu %B %e, %Y',
+ 'Start date' => 'Aloituspäivä',
+ 'Time estimated' => 'Arvioitu aika',
+ 'There is nothing assigned to you.' => 'Ei tehtäviä, joihin sinut olisi merkitty tekijäksi.',
+ 'My tasks' => 'Minun tehtävät',
+ 'Activity stream' => 'Toiminta',
+ 'Dashboard' => 'Työpöytä',
+ 'Confirmation' => 'Vahvistus',
+ 'Allow everybody to access to this project' => 'Anna kaikille käyttöoikeus tähän projektiin',
+ 'Everybody have access to this project.' => 'Kaikilla on käyttöoikeus projektiin.',
// 'Webhooks' => '',
// 'API' => '',
- // 'Integration' => '',
- // 'Github webhook' => '',
- // 'Help on Github webhook' => '',
+ 'Integration' => 'Integraatio',
+ // 'Github webhooks' => '',
+ // 'Help on Github webhooks' => '',
// 'Create a comment from an external provider' => '',
// 'Github issue comment created' => '',
- // 'Configure' => '',
- // 'Project management' => '',
- // 'My projects' => '',
- // 'Columns' => '',
- // 'Task' => '',
- // 'Your are not member of any project.' => '',
- // 'Percentage' => '',
- // 'Number of tasks' => '',
- // 'Task distribution' => '',
- // 'Reportings' => '',
+ 'Configure' => 'Konfiguroi',
+ 'Project management' => 'Projektin hallinta',
+ 'My projects' => 'Minun projektini',
+ 'Columns' => 'Sarakkeet',
+ 'Task' => 'Tehtävät',
+ 'Your are not member of any project.' => 'Et ole minkään projektin jäsen.',
+ 'Percentage' => 'Prosentti',
+ 'Number of tasks' => 'Tehtävien määrä',
+ 'Task distribution' => 'Tehtävien jakauma',
+ 'Reportings' => 'Raportoinnit',
// 'Task repartition for "%s"' => '',
- // 'Analytics' => '',
- // 'Subtask' => '',
- // 'My subtasks' => '',
+ 'Analytics' => 'Analytiikka',
+ 'Subtask' => 'Alitehtävä',
+ 'My subtasks' => 'Minun alitehtäväni',
// 'User repartition' => '',
// 'User repartition for "%s"' => '',
- // 'Clone this project' => '',
- // 'Column removed successfully.' => '',
- // 'Edit Project' => '',
- // 'Github Issue' => '',
- // 'Not enough data to show the graph.' => '',
- // 'Previous' => '',
- // 'The id must be an integer' => '',
- // 'The project id must be an integer' => '',
- // 'The status must be an integer' => '',
- // 'The subtask id is required' => '',
- // 'The subtask id must be an integer' => '',
- // 'The task id is required' => '',
- // 'The task id must be an integer' => '',
- // 'The user id must be an integer' => '',
- // 'This value is required' => '',
- // 'This value must be numeric' => '',
- // 'Unable to create this task.' => '',
- // 'Cumulative flow diagram' => '',
- // 'Cumulative flow diagram for "%s"' => '',
- // 'Daily project summary' => '',
- // 'Daily project summary export' => '',
- // 'Daily project summary export for "%s"' => '',
- // 'Exports' => '',
- // 'This export contains the number of tasks per column grouped per day.' => '',
- // 'Nothing to preview...' => '',
- // 'Preview' => '',
- // 'Write' => '',
+ 'Clone this project' => 'Kahdenna projekti',
+ 'Column removed successfully.' => 'Sarake poistettu onnstuneesti.',
+ 'Edit Project' => 'Muokkaa projektia',
+ 'Github Issue' => 'Github-issue',
+ 'Not enough data to show the graph.' => 'Ei riittävästi dataa graafin näyttämiseksi.',
+ 'Previous' => 'Edellinen',
+ 'The id must be an integer' => 'ID:n on oltava kokonaisluku',
+ 'The project id must be an integer' => 'Projektin ID:n on oltava kokonaisluku',
+ 'The status must be an integer' => 'Tilan on oltava kokonaisluku',
+ 'The subtask id is required' => 'Alitehtävän ID vaaditaan',
+ 'The subtask id must be an integer' => 'Alitehtävän ID:ntulee olla kokonaisluku',
+ 'The task id is required' => 'Tehtävän ID vaaditaan',
+ 'The task id must be an integer' => 'Tehtävän ID on oltava kokonaisluku',
+ 'The user id must be an integer' => 'Käyttäjän ID on oltava kokonaisluku',
+ 'This value is required' => 'Tämä arvo on pakollinen',
+ 'This value must be numeric' => 'Tämän arvon tulee olla numeerinen',
+ 'Unable to create this task.' => 'Tehtävän luonti epäonnistui',
+ 'Cumulative flow diagram' => 'Kumulatiivinen vuokaavio',
+ 'Cumulative flow diagram for "%s"' => 'Kumulatiivinen vuokaavio kohteelle "%s"',
+ 'Daily project summary' => 'Päivittäinen yhteenveto',
+ 'Daily project summary export' => 'Päivittäisen yhteenvedon vienti',
+ 'Daily project summary export for "%s"' => 'Päivittäisen yhteenvedon vienti kohteeseen "%s"',
+ 'Exports' => 'Viennit',
+ 'This export contains the number of tasks per column grouped per day.' => 'Tämä tiedosto sisältää tehtäviä sarakkeisiin päiväkohtaisesti ryhmilteltyinä',
+ 'Nothing to preview...' => 'Ei esikatselua...',
+ 'Preview' => 'Ei esikatselua',
+ 'Write' => 'Kirjoita',
+ 'Active swimlanes' => 'Aktiiviset kaistat',
+ 'Add a new swimlane' => 'Lisää uusi kaista',
+ 'Change default swimlane' => 'Vaihda oletuskaistaa',
+ 'Default swimlane' => 'Oletuskaista',
+ 'Do you really want to remove this swimlane: "%s"?' => 'Haluatko varmasti poistaa tämän kaistan: "%s"?',
+ 'Inactive swimlanes' => 'Passiiviset kaistat',
+ // 'Set project manager' => '',
+ // 'Set project member' => '',
+ 'Remove a swimlane' => 'Poista kaista',
+ 'Rename' => 'Uudelleennimeä',
+ 'Show default swimlane' => 'Näytä oletuskaista',
+ 'Swimlane modification for the project "%s"' => 'Kaistamuutos projektille "%s"',
+ 'Swimlane not found.' => 'Kaistaa ei löydy',
+ 'Swimlane removed successfully.' => 'Kaista poistettu onnistuneesti.',
+ 'Swimlanes' => 'Kaistat',
+ 'Swimlane updated successfully.' => 'Kaista päivitetty onnistuneesti.',
+ 'The default swimlane have been updated successfully.' => 'Oletuskaista päivitetty onnistuneesti.',
+ 'Unable to create your swimlane.' => 'Kaistan luonti epäonnistui.',
+ 'Unable to remove this swimlane.' => 'Kaistan poisto epäonnistui.',
+ 'Unable to update this swimlane.' => 'Kaistan päivittäminen epäonnistui.',
+ 'Your swimlane have been created successfully.' => 'Kaista luotu onnistuneesti.',
+ 'Example: "Bug, Feature Request, Improvement"' => 'Esimerkiksi: "Bugit, Ominaisuuspyynnöt, Parannukset"',
+ 'Default categories for new projects (Comma-separated)' => 'Oletuskategoriat uusille projekteille (pilkuin eroteltu)',
+ // 'Gitlab commit received' => '',
+ // 'Gitlab issue opened' => '',
+ // 'Gitlab issue closed' => '',
+ // 'Gitlab webhooks' => '',
+ // 'Help on Gitlab webhooks' => '',
+ // 'Integrations' => '',
+ // 'Integration with third-party services' => '',
+ // 'Role for this project' => '',
+ // 'Project manager' => '',
+ // 'Project member' => '',
+ // 'A project manager can change the settings of the project and have more privileges than a standard user.' => '',
+ // 'Gitlab Issue' => '',
+ // 'Subtask Id' => '',
+ // 'Subtasks' => '',
+ // 'Subtasks Export' => '',
+ // 'Subtasks exportation for "%s"' => '',
+ // 'Task Title' => '',
+ // 'Untitled' => '',
+ // 'Application default' => '',
+ // 'Language:' => '',
+ // 'Timezone:' => '',
+ // 'Next' => '',
);
diff --git a/sources/app/Locale/fr_FR/translations.php b/sources/app/Locale/fr_FR/translations.php
index 94ed70d..d067304 100644
--- a/sources/app/Locale/fr_FR/translations.php
+++ b/sources/app/Locale/fr_FR/translations.php
@@ -182,11 +182,11 @@ return array(
'Change assignee' => 'Changer la personne assignée',
'Change assignee for the task "%s"' => 'Changer la personne assignée pour la tâche « %s »',
'Timezone' => 'Fuseau horaire',
- 'Sorry, I didn\'t found this information in my database!' => 'Désolé, je n\'ai pas trouvé cette information dans ma base de données !',
+ 'Sorry, I didn\'t find this information in my database!' => 'Désolé, je n\'ai pas trouvé cette information dans ma base de données !',
'Page not found' => 'Page introuvable',
'Complexity' => 'Complexité',
'limit' => 'limite',
- 'Task limit' => 'Nombre maximum de tâches',
+ 'Task limit' => 'Tâches Max.',
'Task count' => 'Nombre de tâches',
'This value must be greater than %d' => 'Cette valeur doit être plus grande que %d',
'Edit project access list' => 'Modifier l\'accès au projet',
@@ -194,7 +194,7 @@ return array(
'Allow this user' => 'Autoriser cet utilisateur',
'Only those users have access to this project:' => 'Seulement ces utilisateurs ont accès à ce projet :',
'Don\'t forget that administrators have access to everything.' => 'N\'oubliez pas que les administrateurs ont accès à tout.',
- 'revoke' => 'révoquer',
+ 'Revoke' => 'Révoquer',
'List of authorized users' => 'Liste des utilisateurs autorisés',
'User' => 'Utilisateur',
'Nobody have access to this project.' => 'Personne n\'est autorisé à accéder au projet.',
@@ -213,6 +213,7 @@ return array(
'Invalid date' => 'Date invalide',
'Must be done before %B %e, %Y' => 'Doit être fait avant le %d/%m/%Y',
'%B %e, %Y' => '%d %B %Y',
+ '%b %e, %Y' => '%d/%m/%Y',
'Automatic actions' => 'Actions automatisées',
'Your automatic action have been created successfully.' => 'Votre action automatisée a été ajouté avec succès.',
'Unable to create your automatic action.' => 'Impossible de créer votre action automatisée.',
@@ -468,18 +469,18 @@ return array(
'Unable to change the password.' => 'Impossible de changer le mot de passe.',
'Change category for the task "%s"' => 'Changer la catégorie pour la tâche « %s »',
'Change category' => 'Changer de catégorie',
- '%s updated the task #%d' => '%s a mis à jour la tâche n°%d',
- '%s open the task #%d' => '%s a ouvert la tâche n°%d',
- '%s moved the task #%d to the position #%d in the column "%s"' => '%s a déplacé la tâche n°%d à la position n°%d dans la colonne « %s »',
- '%s moved the task #%d to the column "%s"' => '%s a déplacé la tâche n°%d dans la colonne « %s »',
- '%s created the task #%d' => '%s a créé la tâche n°%d',
- '%s closed the task #%d' => '%s a fermé la tâche n°%d',
- '%s created a subtask for the task #%d' => '%s a créé une sous-tâche pour la tâche n°%d',
- '%s updated a subtask for the task #%d' => '%s a mis à jour une sous-tâche appartenant à la tâche n°%d',
+ '%s updated the task %s' => '%s a mis à jour la tâche %s',
+ '%s opened the task %s' => '%s a ouvert la tâche %s',
+ '%s moved the task %s to the position #%d in the column "%s"' => '%s a déplacé la tâche %s à la position n°%d dans la colonne « %s »',
+ '%s moved the task %s to the column "%s"' => '%s a déplacé la tâche %s dans la colonne « %s »',
+ '%s created the task %s' => '%s a créé la tâche %s',
+ '%s closed the task %s' => '%s a fermé la tâche %s',
+ '%s created a subtask for the task %s' => '%s a créé une sous-tâche pour la tâche %s',
+ '%s updated a subtask for the task %s' => '%s a mis à jour une sous-tâche appartenant à la tâche %s',
'Assigned to %s with an estimate of %s/%sh' => 'Assigné à %s avec un estimé de %s/%sh',
'Not assigned, estimate of %sh' => 'Personne assigné, estimé de %sh',
- '%s updated a comment on the task #%d' => '%s a mis à jour un commentaire appartenant à la tâche n°%d',
- '%s commented the task #%d' => '%s a ajouté un commentaire sur la tâche n°%d',
+ '%s updated a comment on the task %s' => '%s a mis à jour un commentaire appartenant à la tâche %s',
+ '%s commented the task %s' => '%s a ajouté un commentaire sur la tâche %s',
'%s\'s activity' => 'Activité du projet %s',
'No activity.' => 'Aucune activité.',
'RSS feed' => 'Flux RSS',
@@ -497,11 +498,11 @@ return array(
'Default values are "%s"' => 'Les valeurs par défaut sont « %s »',
'Default columns for new projects (Comma-separated)' => 'Colonnes par défaut pour les nouveaux projets (séparé par des virgules)',
'Task assignee change' => 'Modification de la personne assignée sur une tâche',
- '%s change the assignee of the task #%d to %s' => '%s a changé la personne assignée sur la tâche #%d pour %s',
- '%s change the assignee of the task #%d to %s' => '%s a changé la personne assignée sur la tâche n°%d pour %s',
- '[%s][Column Change] %s (#%d)' => '[%s][Changement de colonne] %s (#%d)',
- '[%s][Position Change] %s (#%d)' => '[%s][Changement de position] %s (#%d)',
- '[%s][Assignee Change] %s (#%d)' => '[%s][Changement d\'assigné] %s (#%d)',
+ '%s change the assignee of the task #%d to %s' => '%s a changé la personne assignée sur la tâche n˚%d pour %s',
+ '%s changed the assignee of the task %s to %s' => '%s a changé la personne assignée sur la tâche %s pour %s',
+ '[%s][Column Change] %s (#%d)' => '[%s][Changement de colonne] %s (n˚%d)',
+ '[%s][Position Change] %s (#%d)' => '[%s][Changement de position] %s (n˚%d)',
+ '[%s][Assignee Change] %s (#%d)' => '[%s][Changement d\'assigné] %s (n˚%d)',
'New password for the user "%s"' => 'Nouveau mot de passe pour l\'utilisateur « %s »',
'Choose an event' => 'Choisir un événement',
'Github commit received' => '« Commit » reçu via Github',
@@ -555,8 +556,8 @@ return array(
'Webhooks' => 'Webhooks',
'API' => 'API',
'Integration' => 'Intégration',
- 'Github webhook' => 'Webhook Github',
- 'Help on Github webhook' => 'Aide sur les webhooks Github',
+ 'Github webhooks' => 'Webhook Github',
+ 'Help on Github webhooks' => 'Aide sur les webhooks Github',
'Create a comment from an external provider' => 'Créer un commentaire depuis un fournisseur externe',
'Github issue comment created' => 'Commentaire créé sur un ticket Github',
'Configure' => 'Configurer',
@@ -602,4 +603,50 @@ return array(
'Nothing to preview...' => 'Rien à prévisualiser...',
'Preview' => 'Prévisualiser',
'Write' => 'Écrire',
+ 'Active swimlanes' => 'Swimlanes actives',
+ 'Add a new swimlane' => 'Ajouter une nouvelle swimlane',
+ 'Change default swimlane' => 'Modifier la swimlane par défaut',
+ 'Default swimlane' => 'Swimlane par défaut',
+ 'Do you really want to remove this swimlane: "%s"?' => 'Voulez-vous vraiment supprimer cette swimlane : « %s » ?',
+ 'Inactive swimlanes' => 'Swimlanes inactives',
+ 'Set project manager' => 'Mettre chef de projet',
+ 'Set project member' => 'Mettre membre du projet',
+ 'Remove a swimlane' => 'Supprimer une swimlane',
+ 'Rename' => 'Renommer',
+ 'Show default swimlane' => 'Afficher la swimlane par défaut',
+ 'Swimlane modification for the project "%s"' => 'Modification d\'une swimlane pour le projet « %s »',
+ 'Swimlane not found.' => 'Cette swimlane est introuvable.',
+ 'Swimlane removed successfully.' => 'Swimlane supprimée avec succès.',
+ 'Swimlanes' => 'Swimlanes',
+ 'Swimlane updated successfully.' => 'Swimlane mise à jour avec succès.',
+ 'The default swimlane have been updated successfully.' => 'La swimlane par défaut a été mise à jour avec succès.',
+ 'Unable to create your swimlane.' => 'Impossible de créer votre swimlane.',
+ 'Unable to remove this swimlane.' => 'Impossible de supprimer cette swimlane.',
+ 'Unable to update this swimlane.' => 'Impossible de mettre à jour cette swimlane.',
+ 'Your swimlane have been created successfully.' => 'Votre swimlane a été créée avec succès.',
+ 'Example: "Bug, Feature Request, Improvement"' => 'Exemple: « Incident, Demande de fonctionnalité, Amélioration »',
+ 'Default categories for new projects (Comma-separated)' => 'Catégories par défaut pour les nouveaux projets (séparé par des virgules)',
+ 'Gitlab commit received' => '« Commit » reçu via Gitlab',
+ 'Gitlab issue opened' => 'Ouverture d\'un ticket sur Gitlab',
+ 'Gitlab issue closed' => 'Fermeture d\'un ticket sur Gitlab',
+ 'Gitlab webhooks' => 'Webhook Gitlab',
+ 'Help on Gitlab webhooks' => 'Aide sur les webhooks Gitlab',
+ 'Integrations' => 'Intégrations',
+ 'Integration with third-party services' => 'Intégration avec des services externes',
+ 'Role for this project' => 'Rôle pour ce projet',
+ 'Project manager' => 'Chef de projet',
+ 'Project member' => 'Membre du projet',
+ 'A project manager can change the settings of the project and have more privileges than a standard user.' => 'Un chef de projet peut changer les paramètres du projet et possède plus de privilèges qu\'un utilisateur standard.',
+ 'Gitlab Issue' => 'Ticket Gitlab',
+ 'Subtask Id' => 'Identifiant de la sous-tâche',
+ 'Subtasks' => 'Sous-tâches',
+ 'Subtasks Export' => 'Exportation des sous-tâches',
+ 'Subtasks exportation for "%s"' => 'Exportation des sous-tâches pour le projet « %s »',
+ 'Task Title' => 'Titre de la tâche',
+ 'Untitled' => 'Sans nom',
+ 'Application default' => 'Valeur par défaut de l\'application',
+ 'Language:' => 'Langue :',
+ 'Timezone:' => 'Fuseau horaire :',
+ 'Next' => 'Suivant',
+ '#%d' => 'n˚%d',
);
diff --git a/sources/app/Locale/hu_HU/translations.php b/sources/app/Locale/hu_HU/translations.php
new file mode 100644
index 0000000..b879b78
--- /dev/null
+++ b/sources/app/Locale/hu_HU/translations.php
@@ -0,0 +1,651 @@
+ 'Semelyik',
+ 'edit' => 'szerkesztés',
+ 'Edit' => 'Szerkesztés',
+ 'remove' => 'eltávolít',
+ 'Remove' => 'Eltávolít',
+ 'Update' => 'Frissítés',
+ 'Yes' => 'Igen',
+ 'No' => 'Nincs',
+ 'cancel' => 'mégsem',
+ 'or' => 'vagy',
+ 'Yellow' => 'sárga',
+ 'Blue' => 'kék',
+ 'Green' => 'zöld',
+ 'Purple' => 'ibolya',
+ 'Red' => 'piros',
+ 'Orange' => 'narancs',
+ 'Grey' => 'szürke',
+ 'Save' => 'Mentés',
+ 'Login' => 'Bejelentkezés',
+ 'Official website:' => 'Hivatalos honlap:',
+ 'Unassigned' => 'Nincs felelős',
+ 'View this task' => 'Feladat megtekintése',
+ 'Remove user' => 'Felhasználó törlése',
+ 'Do you really want to remove this user: "%s"?' => 'Tényleg törli ezt a felhasználót: "%s"?',
+ 'New user' => 'új felhasználó',
+ 'All users' => 'Minden felhasználó',
+ 'Username' => 'Felhasználónév',
+ 'Password' => 'Jelszó',
+ 'Default project' => 'Alapértelmezett projekt',
+ 'Administrator' => 'Rendszergazda',
+ 'Sign in' => 'Jelentkezzen be',
+ 'Users' => 'Felhasználók',
+ 'No user' => 'Nincs felhasználó',
+ 'Forbidden' => 'tiltott',
+ 'Access Forbidden' => 'Hozzáférés megtagadva',
+ 'Only administrators can access to this page.' => 'Csak a rendszergazdák férhetnek hozzá az oldalhoz.',
+ 'Edit user' => 'Felhasználó módosítása',
+ 'Logout' => 'Kilépés',
+ 'Bad username or password' => 'Rossz felhasználónév vagy jelszó',
+ 'users' => 'felhasználók',
+ 'projects' => 'projektek',
+ 'Edit project' => 'Projekt szerkesztése',
+ 'Name' => 'Név',
+ 'Activated' => 'Aktiválva',
+ 'Projects' => 'Projektek',
+ 'No project' => 'Nincs projekt',
+ 'Project' => 'Projekt',
+ 'Status' => 'Állapot',
+ 'Tasks' => 'Feladat',
+ 'Board' => 'Tábla',
+ 'Actions' => 'Műveletek',
+ 'Inactive' => 'Inaktív',
+ 'Active' => 'Aktív',
+ 'Column %d' => 'Oszlop %d',
+ 'Add this column' => 'Oszlop hozzáadása',
+ '%d tasks on the board' => 'A táblán %d feladat',
+ '%d tasks in total' => 'Összesen %d feladat',
+ 'Unable to update this board.' => 'Nem lehet frissíteni a táblát.',
+ 'Edit board' => 'Tábla szerkesztése',
+ 'Disable' => 'Letilt',
+ 'Enable' => 'Engedélyez',
+ 'New project' => 'Új projekt',
+ 'Do you really want to remove this project: "%s"?' => 'Valóban törölni akarja ezt a projektet: "%s"?',
+ 'Remove project' => 'Projekt törlése',
+ 'Boards' => 'Táblák',
+ 'Edit the board for "%s"' => 'Tábla szerkesztése "%s"',
+ 'All projects' => 'Minden projekt',
+ 'Change columns' => 'Oszlop módosítása',
+ 'Add a new column' => 'Új oszlop',
+ 'Title' => 'Cím',
+ 'Add Column' => 'Oszlopot hozzáad',
+ 'Project "%s"' => 'Projekt "%s"',
+ 'Nobody assigned' => 'Nincs felelős',
+ 'Assigned to %s' => 'Felelős: %s',
+ 'Remove a column' => 'Oszlop törlése',
+ 'Remove a column from a board' => 'Oszlop törlése a tábláról',
+ 'Unable to remove this column.' => 'Az oszlop törlése nem lehetséges.',
+ 'Do you really want to remove this column: "%s"?' => 'Valóban törölni akarja ezt az oszlopot: "%s"?',
+ 'This action will REMOVE ALL TASKS associated to this column!' => 'Az oszlophoz rendelt ÖSSZES FELADAT TÖRLŐDNI FOG!',
+ 'Settings' => 'Beállítások',
+ 'Application settings' => 'Alkalmazás beállítások',
+ 'Language' => 'Nyelv',
+ 'Webhook token:' => 'Webhook token:',
+ 'API token:' => 'API token:',
+ 'More information' => 'További információ',
+ 'Database size:' => 'Adatbázis méret:',
+ 'Download the database' => 'Adatbázis letöltése',
+ 'Optimize the database' => 'Adatbázis optimalizálása',
+ '(VACUUM command)' => '(VACUUM parancs)',
+ '(Gzip compressed Sqlite file)' => '(Gzip tömörített SQLite fájl)',
+ 'User settings' => 'Felhasználói beállítások',
+ 'My default project:' => 'Alapértelmezett project:',
+ 'Close a task' => 'Feladat lezárása',
+ 'Do you really want to close this task: "%s"?' => 'Tényleg le akarja zárni ezt a feladatot: "%s"?',
+ 'Edit a task' => 'Feladat módosítása',
+ 'Column' => 'Oszlop',
+ 'Color' => 'Szín',
+ 'Assignee' => 'Felelős',
+ 'Create another task' => 'Új feladat létrehozása',
+ 'New task' => 'új feladat',
+ 'Open a task' => 'Feladat megnyitása',
+ 'Do you really want to open this task: "%s"?' => 'Tényleg meg akarja nyitni ezt a feladatot: "%s"?',
+ 'Back to the board' => 'Vissza a táblához',
+ 'Created on %B %e, %Y at %k:%M %p' => 'Létrehozva: %Y.%m.%d %k:%M %p',
+ 'There is nobody assigned' => 'Nincs felelős',
+ 'Column on the board:' => 'Tábla oszlopa:',
+ 'Status is open' => 'Állapot nyitva',
+ 'Status is closed' => 'Állapot zárva',
+ 'Close this task' => 'Feladat bezárása',
+ 'Open this task' => 'Feladat megnyitása',
+ 'There is no description.' => 'Nincs elérhető leírás.',
+ 'Add a new task' => 'Új feladat hozzáadása',
+ 'The username is required' => 'Felhasználói név szükséges',
+ 'The maximum length is %d characters' => 'A maximális hossz %d karakter',
+ 'The minimum length is %d characters' => 'A minimális hossza %d karakter',
+ 'The password is required' => 'Jelszó szükséges',
+ 'This value must be an integer' => 'Ez az érték csak egész szám lehet',
+ 'The username must be unique' => 'A felhasználó nevének egyedinek kell lennie',
+ 'The username must be alphanumeric' => 'A felhasználói név csak alfanumerikus lehet (betűk és számok)',
+ 'The user id is required' => 'A felhasználói azonosítót meg kell adni',
+ 'Passwords don\'t match' => 'A jelszavak nem egyeznek',
+ 'The confirmation is required' => 'Megerősítés szükséges',
+ 'The column is required' => 'Az oszlopot meg kell adni',
+ 'The project is required' => 'A projektet meg kell adni',
+ 'The color is required' => 'A színt meg kell adni',
+ 'The id is required' => 'Az ID-t (azonosítót) meg kell adni',
+ 'The project id is required' => 'A projekt ID-t (azonosítót) meg kell adni',
+ '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',
+ 'The language is required' => 'A nyelvet 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.' => 'Beállítások mentése nem sikerült.',
+ 'Database optimization done.' => 'Adatbázis optimalizálás kész.',
+ 'Your project have been created successfully.' => 'A projekt sikeresen elkészült.',
+ 'Unable to create your project.' => 'Projekt létrehozása nem sikerült.',
+ 'Project updated successfully.' => 'Projekt sikeres frissítve.',
+ 'Unable to update this project.' => 'Projekt frissítése nem sikerült.',
+ 'Unable to remove this project.' => 'Projekt törlése nem sikerült.',
+ 'Project removed successfully.' => 'Projekt sikeresen törölve.',
+ 'Project activated successfully.' => 'Projekt sikeresen aktiválta.',
+ 'Unable to activate this project.' => 'Projekt aktiválása nem sikerült.',
+ 'Project disabled successfully.' => 'Projekt sikeresen letiltva.',
+ 'Unable to disable this project.' => 'Projekt letiltása nem sikerült.',
+ 'Unable to open this task.' => 'A feladat megnyitása nem sikerült.',
+ 'Task opened successfully.' => 'Feladat sikeresen megnyitva .',
+ 'Unable to close this task.' => 'A feladat lezárása nem sikerült.',
+ 'Task closed successfully.' => 'Feladat sikeresen lezárva.',
+ 'Unable to update your task.' => 'A feladat frissítése nem sikerült.',
+ 'Task updated successfully.' => 'Feladat sikeresen frissítve.',
+ 'Unable to create your task.' => 'A feladat létrehozása nem sikerült.',
+ 'Task created successfully.' => 'Feladat sikeresen létrehozva.',
+ 'User created successfully.' => 'Felhasználó létrehozva .',
+ 'Unable to create your user.' => 'Felhasználó létrehozása nem sikerült.',
+ 'User updated successfully.' => 'Felhasználó sikeresen frissítve.',
+ 'Unable to update your user.' => 'Felhasználó frissítése nem sikerült.',
+ 'User removed successfully.' => 'Felhasználó sikeresen törölve.',
+ 'Unable to remove this user.' => 'Felhasználó törlése nem sikerült.',
+ 'Board updated successfully.' => 'Tábla sikeresen frissítve.',
+ 'Ready' => 'Kész',
+ 'Backlog' => 'Napló',
+ 'Work in progress' => 'Dolgozom',
+ 'Done' => 'Kész',
+ 'Application version:' => 'Alkalmazás verzió:',
+ 'Completed on %B %e, %Y at %k:%M %p' => 'Elkészült %Y.%m.%d %H:%M ..',
+ '%B %e, %Y at %k:%M %p' => '%Y.%m.%d %H:%M',
+ 'Date created' => 'Létrehozás időpontja',
+ 'Date completed' => 'Befejezés időpontja',
+ 'Id' => 'ID',
+ 'No task' => 'Nincs feladat',
+ 'Completed tasks' => 'Elvégzett feladatok',
+ 'List of projects' => 'Projektek listája',
+ 'Completed tasks for "%s"' => 'Elvégzett feladatok "%s"',
+ '%d closed tasks' => '%d lezárt feladat',
+ 'No task for this project' => 'Nincs feladat ebben a projektben',
+ 'Public link' => 'Nyilvános link',
+ 'There is no column in your project!' => 'Nincs oszlop a projektben!',
+ 'Change assignee' => 'Felelős módosítása',
+ 'Change assignee for the task "%s"' => 'Feladat felelősének módosítása: "%s"',
+ 'Timezone' => 'Időzóna',
+ 'Sorry, I didn\'t find this information in my database!' => 'Ez az információ nem található az adatbázisban!',
+ 'Page not found' => 'Az oldal nem található',
+ 'Complexity' => 'Bonyolultság',
+ 'limit' => 'határ',
+ 'Task limit' => 'Maximális számú feladat',
+ 'Task count' => 'Feladatok száma',
+ 'This value must be greater than %d' => 'Az értéknek nagyobbnak kell lennie, mint %d',
+ 'Edit project access list' => 'Projekt hozzáférés módosítása',
+ 'Edit users access' => 'Felhasználók hozzáférésének módosítása',
+ 'Allow this user' => 'Engedélyezi ezt a felhasználót',
+ 'Only those users have access to this project:' => 'Csak ezek a felhasználók férhetnek hozzá a projekthez:',
+ 'Don\'t forget that administrators have access to everything.' => 'Ne felejtsük el: a rendszergazdák mindenhez hozzáférnek.',
+ 'Revoke' => 'Visszavon',
+ 'List of authorized users' => 'Az engedélyezett felhasználók',
+ 'User' => 'Felhasználó',
+ 'Nobody have access to this project.' => 'Senkinek sincs hozzáférése a projekthez.',
+ 'You are not allowed to access to this project.' => 'Nincs hozzáférési joga a projekthez.',
+ 'Comments' => 'Hozzászólások',
+ 'Post comment' => 'Hozzászólás elküldése',
+ 'Write your text in Markdown' => 'Írja be a szöveget Markdown szintaxissal',
+ 'Leave a comment' => 'Írjon hozzászólást ...',
+ 'Comment is required' => 'A hozzászólás mező kötelező',
+ 'Leave a description' => 'Írjon leírást ...',
+ 'Comment added successfully.' => 'Hozzászólás sikeresen elküldve.',
+ 'Unable to create your comment.' => 'Hozzászólás létrehozása nem lehetséges.',
+ 'The description is required' => 'A leírás szükséges',
+ 'Edit this task' => 'Feladat módosítása',
+ 'Due Date' => 'Határidő',
+ 'Invalid date' => 'Érvénytelen dátum',
+ 'Must be done before %B %e, %Y' => 'Kész kell lennie %Y.%m.%d előtt',
+ '%B %e, %Y' => '%Y.%m.%d',
+ // '%b %e, %Y' => '',
+ 'Automatic actions' => 'Automatikus intézkedések',
+ 'Your automatic action have been created successfully.' => 'Az automatikus intézkedés sikeresen elkészült.',
+ 'Unable to create your automatic action.' => 'Automatikus intézkedés létrehozása nem lehetséges.',
+ 'Remove an action' => 'Intézkedés törlése',
+ 'Unable to remove this action.' => 'Intézkedés törlése nem lehetséges.',
+ 'Action removed successfully.' => 'Intézkedés sikeresen törölve.',
+ 'Automatic actions for the project "%s"' => 'Automatikus intézkedések a projektben "%s"',
+ 'Defined actions' => 'Intézkedések',
+ 'Add an action' => 'Intézkedés létrehozása',
+ 'Event name' => 'Esemény neve',
+ 'Action name' => 'Intézkedés neve',
+ 'Action parameters' => 'Intézkedés paraméterei',
+ 'Action' => 'Intézkedés',
+ 'Event' => 'Esemény',
+ 'When the selected event occurs execute the corresponding action.' => 'Ha a kiválasztott esemény bekövetkezik, hajtsa végre a megfelelő intézkedéseket.',
+ 'Next step' => 'Következő lépés',
+ 'Define action parameters' => 'Határozza meg az intézkedés paramétereit',
+ 'Save this action' => 'Intézkedés mentése',
+ 'Do you really want to remove this action: "%s"?' => 'Valóban törölni akarja ezt az intézkedést: "%s"?',
+ 'Remove an automatic action' => 'Automatikus intézkedés törlése',
+ 'Close the task' => 'Feladat lezárása',
+ 'Assign the task to a specific user' => 'Feladat kiosztása megadott felhasználónak',
+ 'Assign the task to the person who does the action' => 'Feladat kiosztása az intézkedő személynek',
+ 'Duplicate the task to another project' => 'Feladat másolása másik projektbe',
+ 'Move a task to another column' => 'Feladat mozgatása másik oszlopba',
+ 'Move a task to another position in the same column' => 'Feladat mozgatása oszlopon belül',
+ 'Task modification' => 'Feladat módosítása',
+ 'Task creation' => 'Feladat létrehozása',
+ 'Open a closed task' => 'Lezárt feladat megnyitása',
+ 'Closing a task' => 'Feladat lezárása',
+ 'Assign a color to a specific user' => 'Szín hozzárendelése a felhasználóhoz',
+ 'Column title' => 'Oszlopfejléc',
+ 'Position' => 'Pozíció',
+ 'Move Up' => 'Fel',
+ 'Move Down' => 'Le',
+ 'Duplicate to another project' => 'Másold egy másik projektbe',
+ 'Duplicate' => 'Másolat',
+ 'link' => 'link',
+ 'Update this comment' => 'Hozzászólás frissítése',
+ 'Comment updated successfully.' => 'Megjegyzés sikeresen frissítve.',
+ 'Unable to update your comment.' => 'Megjegyzés frissítése nem sikerült.',
+ 'Remove a comment' => 'Megjegyzés törlése',
+ 'Comment removed successfully.' => 'Megjegyzés sikeresen törölve.',
+ 'Unable to remove this comment.' => 'Megjegyzés törölése nem lehetséges.',
+ 'Do you really want to remove this comment?' => 'Valóban törölni szeretné ezt a megjegyzést?',
+ 'Only administrators or the creator of the comment can access to this page.' => 'Csak a rendszergazdák és a megjegyzés létrehozója férhet hozzá az oldalhoz.',
+ 'Details' => 'Részletek',
+ 'Current password for the user "%s"' => 'Felhasználó jelenlegi jelszava "%s"',
+ 'The current password is required' => 'A jelenlegi jelszót meg kell adni',
+ 'Wrong password' => 'Hibás jelszó',
+ 'Reset all tokens' => 'Reseteld az összes tokent',
+ 'All tokens have been regenerated.' => 'Minden token újra lett generálva.',
+ 'Unknown' => 'Ismeretlen',
+ 'Last logins' => 'Legutóbbi bejelentkezések',
+ 'Login date' => 'Bejelentkezés dátuma',
+ 'Authentication method' => 'Azonosítási módszer',
+ 'IP address' => 'IP-cím',
+ 'User agent' => 'User Agent',
+ 'Persistent connections' => 'Tartós (perzisztens) kapcsolatok',
+ 'No session.' => 'Nincs session.',
+ 'Expiration date' => 'Lejárati dátum',
+ 'Remember Me' => 'Emlékezz rám',
+ 'Creation date' => 'Létrehozás dátuma',
+ 'Filter by user' => 'Szűrés felhasználó szerint',
+ 'Filter by due date' => 'Szűrés határidő szerint',
+ 'Everybody' => 'Mindenki',
+ 'Open' => 'Nyitott',
+ 'Closed' => 'Lezárt',
+ 'Search' => 'Keres',
+ 'Nothing found.' => 'Semmit sem találtam.',
+ 'Search in the project "%s"' => 'Keresés a projektben "%s"',
+ 'Due date' => 'Határidő',
+ 'Others formats accepted: %s and %s' => 'Egyéb érvényes formátumok: %s és %s',
+ 'Description' => 'Leírás',
+ '%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.' => 'Google profilhoz kapcsolás nem sikerült.',
+ 'Your Google Account is linked to your profile successfully.' => 'Sikeresen összekapcsolva a Google fiókkal.',
+ 'Email' => 'E-mail',
+ 'Link my Google Account' => 'Kapcsold össze a Google fiókkal',
+ 'Unlink my Google Account' => 'Válaszd le a Google fiókomat',
+ 'Login with my Google Account' => 'Jelentkezzen be Google fiókkal',
+ 'Project not found.' => 'A projekt nem található.',
+ 'Task #%d' => 'Feladat #%d.',
+ 'Task removed successfully.' => 'Feladat törlése sikerült.',
+ 'Unable to remove this task.' => 'A feladatot nem lehet törölni.',
+ 'Remove a task' => 'Feladat törlése',
+ 'Do you really want to remove this task: "%s"?' => 'Valóban törölni akarja ezt a feladatot: "%s"?',
+ 'Assign automatically a color based on a category' => 'Szín hozzárendelése automatikusan kategória alapján',
+ 'Assign automatically a category based on a color' => 'Kategória hozzárendelése automatikusan szín alapján',
+ 'Task creation or modification' => 'Feladat létrehozása vagy módosítása',
+ 'Category' => 'Kategória',
+ 'Category:' => 'Kategória:',
+ 'Categories' => 'Kategóriák',
+ 'Category not found.' => 'Kategória nem található.',
+ 'Your category have been created successfully.' => 'Kategória sikeresen létrejött.',
+ 'Unable to create your category.' => 'A kategória létrehozása nem lehetséges.',
+ 'Your category have been updated successfully.' => 'Kategória sikeresen frissítve.',
+ 'Unable to update your category.' => 'Kategória frissítése nem lehetséges.',
+ 'Remove a category' => 'Kategória törlése',
+ 'Category removed successfully.' => 'Kategória törlése megtörtént.',
+ 'Unable to remove this category.' => 'A kategória törlése nem lehetséges.',
+ 'Category modification for the project "%s"' => 'Kategória módosítása a projektben "%s"',
+ 'Category Name' => 'Kategória neve',
+ 'Categories for the project "%s"' => 'Projekt kategóriák "%s"',
+ 'Add a new category' => 'Új kategória',
+ 'Do you really want to remove this category: "%s"?' => 'Valóban törölni akarja ezt a kategóriát "%s"?',
+ 'Filter by category' => 'Szűrés kategóriára',
+ 'All categories' => 'Minden kategória',
+ 'No category' => 'Nincs kategória',
+ 'The name is required' => 'A név megadása kötelező',
+ 'Remove a file' => 'Fájl törlése',
+ 'Unable to remove this file.' => 'Fájl törlése nem lehetséges.',
+ 'File removed successfully.' => 'A fájl törlése sikerült.',
+ 'Attach a document' => 'Fájl csatolása',
+ 'Do you really want to remove this file: "%s"?' => 'Valóban törölni akarja a fájlt: "%s"?',
+ 'open' => 'nyitott',
+ 'Attachments' => 'Mellékletek',
+ 'Edit the task' => 'Feladat módosítása',
+ 'Edit the description' => 'Leírás szerkesztése',
+ 'Add a comment' => 'Új megjegyzés',
+ 'Edit a comment' => 'Megjegyzés szerkesztése',
+ 'Summary' => 'Összegzés',
+ 'Time tracking' => 'Idő követés',
+ 'Estimate:' => 'Becsült:',
+ 'Spent:' => 'Eltöltött:',
+ 'Do you really want to remove this sub-task?' => 'Valóban törölni akarja ezt a részfeladatot "%s"?',
+ 'Remaining:' => 'Hátralévő:',
+ 'hours' => 'óra',
+ 'spent' => 'eltöltött',
+ 'estimated' => 'becsült',
+ 'Sub-Tasks' => 'részfeladatok',
+ 'Add a sub-task' => 'Részfeladat létrehozása',
+ 'Original estimate' => 'Eredeti időbecslés',
+ 'Create another sub-task' => 'További részfeladat létrehozása',
+ 'Time spent' => 'Eltöltött idő',
+ 'Edit a sub-task' => 'Részfeladat szerkesztése',
+ 'Remove a sub-task' => 'Részfeladat törlése',
+ 'The time must be a numeric value' => 'Idő csak számérték lehet',
+ 'Todo' => 'Teendő',
+ 'In progress' => 'Folyamatban',
+ 'Sub-task removed successfully.' => 'Részfeladat sikeresen törölve.',
+ 'Unable to remove this sub-task.' => 'Részfeladat törlése nem lehetséges.',
+ 'Sub-task updated successfully.' => 'Részfeladat sikeresen frissítve.',
+ 'Unable to update your sub-task.' => 'Részfeladat frissítése nem lehetséges.',
+ 'Unable to create your sub-task.' => 'Részfeladat létrehozása nem lehetséges.',
+ 'Sub-task added successfully.' => 'Részfeladat sikeresen létrejött.',
+ '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',
+ 'Created by %s' => 'Készítette: %s',
+ 'Last modified on %B %e, %Y at %k:%M %p' => 'Utolsó módosítás %Y.%m.%d %H:%M',
+ 'Tasks Export' => 'Feladatok exportálása',
+ 'Tasks exportation for "%s"' => 'Feladatok exportálása "%s" részére',
+ 'Start Date' => 'Kezdés dátuma',
+ 'End Date' => 'Befejezés dátuma',
+ 'Execute' => 'Végrehajt',
+ 'Task Id' => 'Feladat ID',
+ 'Creator' => 'Készítette',
+ 'Modification date' => 'Módosítás dátuma',
+ 'Completion date' => 'Befejezés határideje',
+ 'Webhook URL for task creation' => 'Webhook URL a feladat létrehozásakor',
+ 'Webhook URL for task modification' => 'Webhook URL a feladatot módosításakor',
+ 'Clone' => 'Másolat',
+ 'Clone Project' => 'Projekt megkettőzése',
+ 'Project cloned successfully.' => 'A projekt sikeresen megkettőzve.',
+ 'Unable to clone this project.' => 'Projekt megkettőzése nem sikerült.',
+ 'Email notifications' => 'E-mail értesítések',
+ 'Enable email notifications' => 'Engedélyezze az e-mail értesítéseket',
+ 'Task position:' => 'Feladat helye:',
+ 'The task #%d have been opened.' => 'Feladat #%d megnyitva.',
+ 'The task #%d have been closed.' => 'Feladat #%d lezárva.',
+ 'Sub-task updated' => 'Részfeladat frissítve',
+ 'Title:' => 'Cím',
+ 'Status:' => 'Állapot',
+ 'Assignee:' => 'Felelős:',
+ 'Time tracking:' => 'Idő követés:',
+ 'New sub-task' => 'Új részfeladat',
+ 'New attachment added "%s"' => 'Új melléklet "%s" hozzáadva.',
+ 'Comment updated' => 'Megjegyzés frissítve',
+ 'New comment posted by %s' => 'Új megjegyzés %s',
+ 'List of due tasks for the project "%s"' => 'Projekt esedékes feladatai "%s"',
+ '[%s][New attachment] %s (#%d)' => '[%s] [Új csatolmány] %s (#%d)',
+ '[%s][New comment] %s (#%d)' => '[%s] [Új hozzászólás] %s (#%d)',
+ '[%s][Comment updated] %s (#%d)' => '[%s] [Megjegyzés frissítve] %s (#%d)',
+ '[%s][New subtask] %s (#%d)' => '[%s] [Új részfeladat] %s (#%d)',
+ '[%s][Subtask updated] %s (#%d)' => '[%s] [Részfeladat frissítve] %s (#%d)',
+ '[%s][New task] %s (#%d)' => '[%s] [Új feladat] %s (#%d)',
+ '[%s][Task updated] %s (#%d)' => '[%s] [Feladat frissítve] %s (#%d)',
+ '[%s][Task closed] %s (#%d)' => '[%s] [Feladat lezárva]%s (#%d)',
+ '[%s][Task opened] %s (#%d)' => '[%s] [Feladat megnyitva] %s (#%d)',
+ '[%s][Due tasks]' => '[%s] [Esedékes feladatok]',
+ '[Kanboard] Notification' => '[Kanboard] értesítés',
+ 'I want to receive notifications only for those projects:' => 'Csak ezekről a projektekről kérek értesítést:',
+ 'view the task on Kanboard' => 'feladat megtekintése a Kanboardon',
+ 'Public access' => 'Nyilvános hozzáférés',
+ 'Category management' => 'Kategóriák kezelése',
+ 'User management' => 'Felhasználók kezelése',
+ 'Active tasks' => 'Aktív feladatok',
+ 'Disable public access' => 'Nyilvános hozzáférés letiltása',
+ 'Enable public access' => 'Nyilvános hozzáférés engedélyezése',
+ 'Active projects' => 'Aktív projektek',
+ 'Inactive projects' => 'Inaktív projektek',
+ 'Public access disabled' => 'Nyilvános hozzáférés letiltva',
+ 'Do you really want to disable this project: "%s"?' => 'Tényleg szeretné letiltani ezt a projektet: "%s"',
+ 'Do you really want to duplicate this project: "%s"?' => 'Tényleg szeretné megkettőzni ezt a projektet: "%s"',
+ 'Do you really want to enable this project: "%s"?' => 'Tényleg szeretné engedélyezni ezt a projektet: "%s"',
+ 'Project activation' => 'Projekt aktiválás',
+ 'Move the task to another project' => 'Feladatot mozgatása másik projektbe',
+ 'Move to another project' => 'Másik projektbe',
+ 'Do you really want to duplicate this task?' => 'Tényleg szeretné megkettőzni ezt a feladatot?',
+ 'Duplicate a task' => 'Feladat megkettőzése',
+ 'External accounts' => 'Külső fiókok',
+ 'Account type' => 'Fiók típus',
+ 'Local' => 'Helyi',
+ 'Remote' => 'Távoli',
+ 'Enabled' => 'Engedélyezve',
+ 'Disabled' => 'Letiltva',
+ 'Google account linked' => 'Google fiók összekapcsolva',
+ 'Github account linked' => 'GitHub fiók összekapcsolva',
+ 'Username:' => 'Felhasználónév',
+ 'Name:' => 'Név',
+ 'Email:' => 'E-mail',
+ 'Default project:' => 'Alapértelmezett projekt:',
+ 'Notifications:' => 'Értesítések:',
+ 'Notifications' => 'Értesítések',
+ 'Group:' => 'Csoport:',
+ 'Regular user' => 'Default User',
+ 'Account type:' => 'Fiók típus:',
+ 'Edit profile' => 'Profil szerkesztése',
+ 'Change password' => 'Jelszó módosítása',
+ 'Password modification' => 'Jelszó módosítása',
+ 'External authentications' => 'Külső azonosítás',
+ 'Google Account' => 'Google fiók',
+ 'Github Account' => 'Github fiók',
+ 'Never connected.' => 'Sosem csatlakozva.',
+ 'No account linked.' => 'Nincs csatlakoztatott fiók.',
+ 'Account linked.' => 'Fiók csatlakoztatva.',
+ 'No external authentication enabled.' => 'Külső azonosítás nincs engedélyezve.',
+ 'Password modified successfully.' => 'Jelszó sikeresen módosítva.',
+ 'Unable to change the password.' => 'Jelszó módosítás sikertelen.',
+ 'Change category for the task "%s"' => 'Feladat kategória módosítása "%s"',
+ 'Change category' => 'Kategória módosítása',
+ '%s updated the task %s' => '%s frissítette a feladatot %s',
+ '%s opened the task %s' => '%s megnyitott a feladatot %s',
+ '%s moved the task %s to the position #%d in the column "%s"' => '%s átmozgatta a feladatot %s #%d pozícióba a "%s" oszlopban',
+ '%s moved the task %s to the column "%s"' => '%s átmozgatta a feladatot %s "%s" oszlopba',
+ '%s created the task %s' => '%s létrehozta a feladatot %s',
+ '%s closed the task %s' => '%s lezárta a feladatot %s',
+ '%s created a subtask for the task %s' => '%s létrehozott egy részfeladat a feladathoz %s',
+ '%s updated a subtask for the task %s' => '%s frissített egy részfeladatot a feladathoz %s',
+ 'Assigned to %s with an estimate of %s/%sh' => '%s-nek kiosztva %s/%s óra becsült idő mellett',
+ 'Not assigned, estimate of %sh' => 'Nincs kiosztva, becsült idő: %s óra',
+ '%s updated a comment on the task %s' => '%s frissítette a megjegyzését a feladatban %s',
+ '%s commented the task %s' => '%s megjegyzést fűzött a feladathoz %s',
+ '%s\'s activity' => '%s tevékenysége',
+ 'No activity.' => 'Nincs tevékenység.',
+ 'RSS feed' => 'RSS feed',
+ '%s updated a comment on the task #%d' => '%s frissített egy megjegyzést a feladatban #%d',
+ '%s commented on the task #%d' => '%s megjegyzést tett a feladathoz #%d',
+ '%s updated a subtask for the task #%d' => '%s frissített egy részfeladatot a feladatban #%d',
+ '%s created a subtask for the task #%d' => '%s létrehozott egy részfeladatot a feladatban #%d',
+ '%s updated the task #%d' => '%s frissítette a feladatot #%d',
+ '%s created the task #%d' => '%s létrehozta a feladatot #%d',
+ '%s closed the task #%d' => '%s lezárta a feladatot #%d',
+ '%s open the task #%d' => '%s megnyitotta a feladatot #%d',
+ '%s moved the task #%d to the column "%s"' => '%s átmozgatta a feladatot #%d a "%s" oszlopba',
+ '%s moved the task #%d to the position %d in the column "%s"' => '%s átmozgatta a feladatot #%d a %d pozícióba a "%s" oszlopban',
+ 'Activity' => 'Tevékenység',
+ 'Default values are "%s"' => 'Az alapértelmezett értékek "%s"',
+ 'Default columns for new projects (Comma-separated)' => 'Alapértelmezett oszlopok az új projektekben (vesszővel elválasztva)',
+ 'Task assignee change' => 'Felelős módosítása',
+ '%s change the assignee of the task #%d to %s' => '%s a felelőst módosította #%d %s',
+ '%s changed the assignee of the task %s to %s' => '%s a felelőst %s módosította: %s',
+ '[%s][Column Change] %s (#%d)' => '[%s] [Oszlop módosítás] %s (#%d)',
+ '[%s][Position Change] %s (#%d)' => '[%s] [Pozíció módosítás] %s (#%d)',
+ '[%s][Assignee Change] %s (#%d)' => '[%s] [Felelős módosítás] %s (#%d)',
+ '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 nyílt',
+ 'Github issue closed' => 'GitHub issue zárt',
+ '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',
+ 'Reference' => 'Hivatkozás',
+ 'Reference: %s' => 'Hivatkozás: %s',
+ 'Label' => 'Címke',
+ 'Database' => 'Adatbázis',
+ 'About' => 'Kanboard információ',
+ 'Database driver:' => 'Adatbázis driver:',
+ 'Board settings' => 'Tábla beállítások',
+ 'URL and token' => 'URL és tokenek',
+ 'Webhook settings' => 'Webhook beállítások',
+ 'URL for task creation:' => 'Feladat létrehozás URL:',
+ 'Reset token' => 'Reset token',
+ 'API endpoint:' => 'API endpoint:',
+ 'Refresh interval for private board' => 'Privát táblák frissítési intervalluma',
+ 'Refresh interval for public board' => 'Nyilvános táblák frissítési intervalluma',
+ 'Task highlight period' => 'Feladat kiemelés időtartama',
+ 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Mennyi ideig tekintendő egy feladat "mostanában" módosítottnak (másodpercben) (0: funkció letiltva, alapértelmezés szerint 2 nap)',
+ 'Frequency in second (60 seconds by default)' => 'Infó másodpercben (alapértelmezett 60 másodperc)',
+ 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Infó másodpercben (0 funkció letiltva, alapértelmezés szerint 10 másodperc)',
+ 'Application URL' => 'Alkalmazás URL',
+ 'Example: http://example.kanboard.net/ (used by email notifications)' => 'Példa: http://example.kanboard.net/ (e-mail értesítőben)',
+ 'Token regenerated.' => 'Token újragenerálva.',
+ 'Date format' => 'Dátum formátum',
+ 'ISO format is always accepted, example: "%s" and "%s"' => 'ISO formátum mindig elfogadott, pl: "%s" és "%s"',
+ 'New private project' => 'Új privát projekt',
+ 'This project is private' => 'Ez egy privát projekt',
+ 'Type here to create a new sub-task' => 'Ide írva létrehozhat egy új részfeladatot',
+ 'Add' => 'Hozzáad',
+ 'Estimated time: %s hours' => 'Becsült idő: %s óra',
+ 'Time spent: %s hours' => 'Eltöltött idő: %s óra',
+ 'Started on %B %e, %Y' => 'Elkezdve: %Y.%m.%d',
+ 'Start date' => 'Kezdés dátuma',
+ 'Time estimated' => 'Becsült időtartam',
+ 'There is nothing assigned to you.' => 'Nincs kiosztott feladat.',
+ 'My tasks' => 'Feladataim',
+ 'Activity stream' => 'Legutóbbi tevékenységek',
+ 'Dashboard' => 'Műszerfal',
+ 'Confirmation' => 'Megerősítés',
+ 'Allow everybody to access to this project' => 'Engedélyezze a projekt elérését mindenkinek',
+ 'Everybody have access to this project.' => 'Mindenki elérheti a projektet',
+ 'Webhooks' => 'Webhook',
+ 'API' => 'API',
+ 'Integration' => 'Integráció',
+ 'Github webhooks' => 'Github webhooks',
+ 'Help on Github webhooks' => 'Github Webhook súgó',
+ 'Create a comment from an external provider' => 'Megjegyzés létrehozása külső felhasználótól',
+ 'Github issue comment created' => 'Github issue megjegyzés létrehozva',
+ 'Configure' => 'Konfigurál',
+ 'Project management' => 'Projekt menedzsment',
+ 'My projects' => 'Projektjeim',
+ 'Columns' => 'Oszlopok',
+ 'Task' => 'Feladat',
+ 'Your are not member of any project.' => 'Ön nem tagja projektnek.',
+ 'Percentage' => 'Százalék',
+ 'Number of tasks' => 'A feladatok száma',
+ 'Task distribution' => 'Feladatelosztás',
+ 'Reportings' => 'Jelentések',
+ 'Task repartition for "%s"' => 'Feladat újraosztása "%s" számára',
+ 'Analytics' => 'Analitika',
+ 'Subtask' => 'Részfeladat',
+ 'My subtasks' => 'Részfeladataim',
+ 'User repartition' => 'Felhasználó újrafelosztás',
+ 'User repartition for "%s"' => 'Felhasználó újrafelosztás "%s" számára',
+ 'Clone this project' => 'Projekt megkettőzése',
+ 'Column removed successfully.' => 'Oszlop sikeresen eltávolítva.',
+ 'Edit Project' => 'Projekt szerkesztése',
+ 'Github Issue' => 'Github issue',
+ 'Not enough data to show the graph.' => 'Nincs elég adat a grafikonhoz.',
+ 'Previous' => 'Előző',
+ 'The id must be an integer' => 'Az ID csak egész szám lehet',
+ 'The project id must be an integer' => 'A projekt ID csak egész szám lehet',
+ 'The status must be an integer' => 'Az állapot csak egész szám lehet',
+ 'The subtask id is required' => 'A részfeladat ID-t meg kell adni',
+ 'The subtask id must be an integer' => 'A részfeladat ID csak egész szám lehet',
+ 'The task id is required' => 'A feladat ID-t meg kell adni',
+ 'The task id must be an integer' => 'A feladat ID csak egész szám lehet',
+ 'The user id must be an integer' => 'A felhasználói ID csak egész szám lehet',
+ 'This value is required' => 'Ez a mező kötelező',
+ 'This value must be numeric' => 'Ez a mező csak szám lehet',
+ 'Unable to create this task.' => 'A feladat nem hozható létre,',
+ 'Cumulative flow diagram' => 'Kumulatív Flow Diagram',
+ 'Cumulative flow diagram for "%s"' => 'Kumulatív Flow Diagram "%s" számára',
+ 'Daily project summary' => 'Napi projektösszefoglaló',
+ 'Daily project summary export' => 'Napi projektösszefoglaló exportálása',
+ 'Daily project summary export for "%s"' => 'Napi projektösszefoglaló exportálása "%s" számára',
+ 'Exports' => 'Exportálások',
+ 'This export contains the number of tasks per column grouped per day.' => 'Ez az export tartalmazza a feladatok számát oszloponként összesítve, napokra lebontva.',
+ 'Nothing to preview...' => 'Nincs semmi az előnézetben ...',
+ 'Preview' => 'Előnézet',
+ 'Write' => 'Írd',
+ // 'Active swimlanes' => '',
+ // 'Add a new swimlane' => '',
+ // 'Change default swimlane' => '',
+ // 'Default swimlane' => '',
+ // 'Do you really want to remove this swimlane: "%s"?' => '',
+ // 'Inactive swimlanes' => '',
+ // 'Set project manager' => '',
+ // 'Set project member' => '',
+ // 'Remove a swimlane' => '',
+ // 'Rename' => '',
+ // 'Show default swimlane' => '',
+ // 'Swimlane modification for the project "%s"' => '',
+ // 'Swimlane not found.' => '',
+ // 'Swimlane removed successfully.' => '',
+ // 'Swimlanes' => '',
+ // 'Swimlane updated successfully.' => '',
+ // 'The default swimlane have been updated successfully.' => '',
+ // 'Unable to create your swimlane.' => '',
+ // 'Unable to remove this swimlane.' => '',
+ // 'Unable to update this swimlane.' => '',
+ // 'Your swimlane have been created successfully.' => '',
+ // 'Example: "Bug, Feature Request, Improvement"' => '',
+ // 'Default categories for new projects (Comma-separated)' => '',
+ // 'Gitlab commit received' => '',
+ // 'Gitlab issue opened' => '',
+ // 'Gitlab issue closed' => '',
+ // 'Gitlab webhooks' => '',
+ // 'Help on Gitlab webhooks' => '',
+ // 'Integrations' => '',
+ // 'Integration with third-party services' => '',
+ // 'Role for this project' => '',
+ // 'Project manager' => '',
+ // 'Project member' => '',
+ // 'A project manager can change the settings of the project and have more privileges than a standard user.' => '',
+ // 'Gitlab Issue' => '',
+ // 'Subtask Id' => '',
+ // 'Subtasks' => '',
+ // 'Subtasks Export' => '',
+ // 'Subtasks exportation for "%s"' => '',
+ // 'Task Title' => '',
+ // 'Untitled' => '',
+ // 'Application default' => '',
+ // 'Language:' => '',
+ // 'Timezone:' => '',
+ // 'Next' => '',
+);
diff --git a/sources/app/Locale/it_IT/translations.php b/sources/app/Locale/it_IT/translations.php
index b7132bc..0777e17 100644
--- a/sources/app/Locale/it_IT/translations.php
+++ b/sources/app/Locale/it_IT/translations.php
@@ -182,7 +182,7 @@ return array(
'Change assignee' => 'Cambiare la persona assegnata',
'Change assignee for the task "%s"' => 'Cambiare la persona assegnata per il compito « %s »',
'Timezone' => 'Fuso orario',
- 'Sorry, I didn\'t found this information in my database!' => 'Mi dispiace, non ho trovato questa informazione sulla base dati!',
+ 'Sorry, I didn\'t find this information in my database!' => 'Mi dispiace, non ho trovato questa informazione sulla base dati!',
'Page not found' => 'Pagina non trovata',
// 'Complexity' => '',
'limit' => 'limite',
@@ -194,7 +194,7 @@ return array(
'Allow this user' => 'Permettere a questo utente',
'Only those users have access to this project:' => 'Solo questi utenti hanno accesso a questo progetto:',
'Don\'t forget that administrators have access to everything.' => 'Non dimenticare che gli amministratori hanno accesso a tutto.',
- 'revoke' => 'revocare',
+ 'Revoke' => 'Revocare',
'List of authorized users' => 'Lista di utenti autorizzati',
'User' => 'Utente',
// 'Nobody have access to this project.' => '',
@@ -213,6 +213,7 @@ return array(
'Invalid date' => 'Data sbagliata',
// 'Must be done before %B %e, %Y' => '',
// '%B %e, %Y' => '',
+ // '%b %e, %Y' => '',
'Automatic actions' => 'Azioni automatiche',
'Your automatic action have been created successfully.' => 'l\'azione automatica è stata creata correttamente.',
'Unable to create your automatic action.' => 'Non si può creare quest\'azione automatica.',
@@ -468,18 +469,18 @@ return array(
// 'Unable to change the password.' => '',
// 'Change category for the task "%s"' => '',
// 'Change category' => '',
- // '%s updated the task #%d' => '',
- // '%s open the task #%d' => '',
- // '%s moved the task #%d to the position #%d in the column "%s"' => '',
- // '%s moved the task #%d to the column "%s"' => '',
- // '%s created the task #%d' => '',
- // '%s closed the task #%d' => '',
- // '%s created a subtask for the task #%d' => '',
- // '%s updated a subtask for the task #%d' => '',
+ // '%s updated the task %s' => '',
+ // '%s opened the task %s' => '',
+ // '%s moved the task %s to the position #%d in the column "%s"' => '',
+ // '%s moved the task %s to the column "%s"' => '',
+ // '%s created the task %s' => '',
+ // '%s closed the task %s' => '',
+ // '%s created a subtask for the task %s' => '',
+ // '%s updated a subtask for the task %s' => '',
// 'Assigned to %s with an estimate of %s/%sh' => '',
// 'Not assigned, estimate of %sh' => '',
- // '%s updated a comment on the task #%d' => '',
- // '%s commented the task #%d' => '',
+ // '%s updated a comment on the task %s' => '',
+ // '%s commented the task %s' => '',
// '%s\'s activity' => '',
// 'No activity.' => '',
// 'RSS feed' => '',
@@ -498,7 +499,7 @@ return array(
// 'Default columns for new projects (Comma-separated)' => '',
// 'Task assignee change' => '',
// '%s change the assignee of the task #%d to %s' => '',
- // '%s change the assignee of the task #%d to %s' => '',
+ // '%s changed the assignee of the task %s to %s' => '',
// '[%s][Column Change] %s (#%d)' => '',
// '[%s][Position Change] %s (#%d)' => '',
// '[%s][Assignee Change] %s (#%d)' => '',
@@ -555,8 +556,8 @@ return array(
// 'Webhooks' => '',
// 'API' => '',
// 'Integration' => '',
- // 'Github webhook' => '',
- // 'Help on Github webhook' => '',
+ // 'Github webhooks' => '',
+ // 'Help on Github webhooks' => '',
// 'Create a comment from an external provider' => '',
// 'Github issue comment created' => '',
// 'Configure' => '',
@@ -602,4 +603,49 @@ return array(
// 'Nothing to preview...' => '',
// 'Preview' => '',
// 'Write' => '',
+ // 'Active swimlanes' => '',
+ // 'Add a new swimlane' => '',
+ // 'Change default swimlane' => '',
+ // 'Default swimlane' => '',
+ // 'Do you really want to remove this swimlane: "%s"?' => '',
+ // 'Inactive swimlanes' => '',
+ // 'Set project manager' => '',
+ // 'Set project member' => '',
+ // 'Remove a swimlane' => '',
+ // 'Rename' => '',
+ // 'Show default swimlane' => '',
+ // 'Swimlane modification for the project "%s"' => '',
+ // 'Swimlane not found.' => '',
+ // 'Swimlane removed successfully.' => '',
+ // 'Swimlanes' => '',
+ // 'Swimlane updated successfully.' => '',
+ // 'The default swimlane have been updated successfully.' => '',
+ // 'Unable to create your swimlane.' => '',
+ // 'Unable to remove this swimlane.' => '',
+ // 'Unable to update this swimlane.' => '',
+ // 'Your swimlane have been created successfully.' => '',
+ // 'Example: "Bug, Feature Request, Improvement"' => '',
+ // 'Default categories for new projects (Comma-separated)' => '',
+ // 'Gitlab commit received' => '',
+ // 'Gitlab issue opened' => '',
+ // 'Gitlab issue closed' => '',
+ // 'Gitlab webhooks' => '',
+ // 'Help on Gitlab webhooks' => '',
+ // 'Integrations' => '',
+ // 'Integration with third-party services' => '',
+ // 'Role for this project' => '',
+ // 'Project manager' => '',
+ // 'Project member' => '',
+ // 'A project manager can change the settings of the project and have more privileges than a standard user.' => '',
+ // 'Gitlab Issue' => '',
+ // 'Subtask Id' => '',
+ // 'Subtasks' => '',
+ // 'Subtasks Export' => '',
+ // 'Subtasks exportation for "%s"' => '',
+ // 'Task Title' => '',
+ // 'Untitled' => '',
+ // 'Application default' => '',
+ // 'Language:' => '',
+ // 'Timezone:' => '',
+ // 'Next' => '',
);
diff --git a/sources/app/Locale/ja_JP/translations.php b/sources/app/Locale/ja_JP/translations.php
index 7a59ec0..f4f5215 100644
--- a/sources/app/Locale/ja_JP/translations.php
+++ b/sources/app/Locale/ja_JP/translations.php
@@ -182,7 +182,7 @@ return array(
'Change assignee' => '担当を変更する',
'Change assignee for the task "%s"' => 'タスク「%s」の担当を変更する',
'Timezone' => 'タイムゾーン',
- 'Sorry, I didn\'t found this information in my database!' => 'データベース上で情報が見つかりませんでした!',
+ 'Sorry, I didn\'t find this information in my database!' => 'データベース上で情報が見つかりませんでした!',
'Page not found' => 'ページが見つかりません',
'Complexity' => '複雑さ',
'limit' => '制限',
@@ -194,7 +194,7 @@ return array(
'Allow this user' => 'このユーザを許可する',
'Only those users have access to this project:' => 'これらのユーザのみがプロジェクトにアクセスできます:',
'Don\'t forget that administrators have access to everything.' => '管理者には全ての権限が与えられます。',
- 'revoke' => '許可を取り下げる',
+ 'Revoke' => '許可を取り下げる',
'List of authorized users' => '許可されたユーザ',
'User' => 'ユーザ',
'Nobody have access to this project.' => 'だれもプロジェクトにアクセスできません。',
@@ -213,6 +213,7 @@ return array(
'Invalid date' => '日付が無効です',
'Must be done before %B %e, %Y' => '%Y/%m/%d までに完了',
'%B %e, %Y' => '%d %B %Y',
+ // '%b %e, %Y' => '',
'Automatic actions' => '自動アクションを管理する',
'Your automatic action have been created successfully.' => '自動アクションを作成しました。',
'Unable to create your automatic action.' => '自動アクションの作成に失敗しました。',
@@ -468,18 +469,18 @@ return array(
'Unable to change the password.' => 'パスワードが変更できませんでした。',
'Change category for the task "%s"' => 'タスク「%s」のカテゴリの変更',
'Change category' => 'カテゴリの変更',
- '%s updated the task #%d' => '%s がタスク #%d をアップデートしました',
- '%s open the task #%d' => '%s がタスク #%d をオープンしました',
- '%s moved the task #%d to the position #%d in the column "%s"' => '%s がタスク #%d をポジション #%d カラム %s に移動しました',
- '%s moved the task #%d to the column "%s"' => '%s がタスク #%d をカラム「%s」に移動しました',
- '%s created the task #%d' => '%s がタスク #%d を作成しました',
- '%s closed the task #%d' => '%s がタスク #%d をクローズしました',
- '%s created a subtask for the task #%d' => '%s がタスク #%d のサブタスクを追加しました',
- '%s updated a subtask for the task #%d' => '%s がタスク #%d のサブタスクを更新しました',
+ '%s updated the task %s' => '%s がタスク %s をアップデートしました',
+ '%s opened the task %s' => '%s がタスク %s をオープンしました',
+ '%s moved the task %s to the position #%d in the column "%s"' => '%s がタスク %s をポジション #%d カラム %s に移動しました',
+ '%s moved the task %s to the column "%s"' => '%s がタスク %s をカラム「%s」に移動しました',
+ '%s created the task %s' => '%s がタスク %s を作成しました',
+ '%s closed the task %s' => '%s がタスク %s をクローズしました',
+ '%s created a subtask for the task %s' => '%s がタスク %s のサブタスクを追加しました',
+ '%s updated a subtask for the task %s' => '%s がタスク %s のサブタスクを更新しました',
'Assigned to %s with an estimate of %s/%sh' => '担当者 %s に予想 %s/%sh に変更されました',
'Not assigned, estimate of %sh' => '担当者無しで予想 %sh に変更されました',
- '%s updated a comment on the task #%d' => '%s がタスク #%d のコメントを更新しました',
- '%s commented the task #%d' => '%s がタスク #%d にコメントしました',
+ '%s updated a comment on the task %s' => '%s がタスク %s のコメントを更新しました',
+ '%s commented the task %s' => '%s がタスク %s にコメントしました',
'%s\'s activity' => '%s のアクティビティ',
'No activity.' => 'アクティビティなし。',
'RSS feed' => 'RSS フィード',
@@ -498,7 +499,7 @@ return array(
'Default columns for new projects (Comma-separated)' => '新規プロジェクトのデフォルトカラム (コンマで区切って入力)',
'Task assignee change' => '担当者の変更',
'%s change the assignee of the task #%d to %s' => '%s がタスク #%d の担当を %s に変更しました',
- '%s change the assignee of the task #%d to %s' => '%s がタスク #%d の担当を %s に変更しました',
+ '%s changed the assignee of the task %s to %s' => '%s がタスク %s の担当を %s に変更しました',
'[%s][Column Change] %s (#%d)' => '[%s][カラムの変更] %s (#%d)',
'[%s][Position Change] %s (#%d)' => '[%s][位置の変更] %s (#%d)',
'[%s][Assignee Change] %s (#%d)' => '[%s][担当者変更] %s (#%d)',
@@ -555,8 +556,8 @@ return array(
// 'Webhooks' => '',
// 'API' => '',
// 'Integration' => '',
- // 'Github webhook' => '',
- // 'Help on Github webhook' => '',
+ // 'Github webhooks' => '',
+ // 'Help on Github webhooks' => '',
// 'Create a comment from an external provider' => '',
// 'Github issue comment created' => '',
// 'Configure' => '',
@@ -602,4 +603,49 @@ return array(
// 'Nothing to preview...' => '',
// 'Preview' => '',
// 'Write' => '',
+ // 'Active swimlanes' => '',
+ // 'Add a new swimlane' => '',
+ // 'Change default swimlane' => '',
+ // 'Default swimlane' => '',
+ // 'Do you really want to remove this swimlane: "%s"?' => '',
+ // 'Inactive swimlanes' => '',
+ // 'Set project manager' => '',
+ // 'Set project member' => '',
+ // 'Remove a swimlane' => '',
+ // 'Rename' => '',
+ // 'Show default swimlane' => '',
+ // 'Swimlane modification for the project "%s"' => '',
+ // 'Swimlane not found.' => '',
+ // 'Swimlane removed successfully.' => '',
+ // 'Swimlanes' => '',
+ // 'Swimlane updated successfully.' => '',
+ // 'The default swimlane have been updated successfully.' => '',
+ // 'Unable to create your swimlane.' => '',
+ // 'Unable to remove this swimlane.' => '',
+ // 'Unable to update this swimlane.' => '',
+ // 'Your swimlane have been created successfully.' => '',
+ // 'Example: "Bug, Feature Request, Improvement"' => '',
+ // 'Default categories for new projects (Comma-separated)' => '',
+ // 'Gitlab commit received' => '',
+ // 'Gitlab issue opened' => '',
+ // 'Gitlab issue closed' => '',
+ // 'Gitlab webhooks' => '',
+ // 'Help on Gitlab webhooks' => '',
+ // 'Integrations' => '',
+ // 'Integration with third-party services' => '',
+ // 'Role for this project' => '',
+ // 'Project manager' => '',
+ // 'Project member' => '',
+ // 'A project manager can change the settings of the project and have more privileges than a standard user.' => '',
+ // 'Gitlab Issue' => '',
+ // 'Subtask Id' => '',
+ // 'Subtasks' => '',
+ // 'Subtasks Export' => '',
+ // 'Subtasks exportation for "%s"' => '',
+ // 'Task Title' => '',
+ // 'Untitled' => '',
+ // 'Application default' => '',
+ // 'Language:' => '',
+ // 'Timezone:' => '',
+ // 'Next' => '',
);
diff --git a/sources/app/Locale/pl_PL/translations.php b/sources/app/Locale/pl_PL/translations.php
index edc696b..408828a 100644
--- a/sources/app/Locale/pl_PL/translations.php
+++ b/sources/app/Locale/pl_PL/translations.php
@@ -182,7 +182,7 @@ return array(
'Change assignee' => 'Zmień odpowiedzialną osobę',
'Change assignee for the task "%s"' => 'Zmień odpowiedzialną osobę dla zadania "%s"',
'Timezone' => 'Strefa czasowa',
- 'Sorry, I didn\'t found this information in my database!' => 'Niestety nie znaleziono tej informacji w bazie danych',
+ 'Sorry, I didn\'t find this information in my database!' => 'Niestety nie znaleziono tej informacji w bazie danych',
'Page not found' => 'Strona nie istnieje',
'Complexity' => 'Poziom trudności',
'limit' => 'limit',
@@ -194,7 +194,7 @@ return array(
'Allow this user' => 'Dodaj użytkownika',
'Only those users have access to this project:' => 'Użytkownicy mający dostęp:',
'Don\'t forget that administrators have access to everything.' => 'Pamiętaj: Administratorzy mają zawsze dostęp do wszystkiego!',
- 'revoke' => 'odbierz dostęp',
+ 'Revoke' => 'Odbierz dostęp',
'List of authorized users' => 'Lista użytkowników mających dostęp',
'User' => 'Użytkownik',
// 'Nobody have access to this project.' => '',
@@ -213,6 +213,7 @@ return array(
'Invalid date' => 'Błędna data',
'Must be done before %B %e, %Y' => 'Termin do %e %B %Y',
'%B %e, %Y' => '%e %B %Y',
+ // '%b %e, %Y' => '',
'Automatic actions' => 'Akcje automatyczne',
'Your automatic action have been created successfully.' => 'Twoja akcja została dodana',
'Unable to create your automatic action.' => 'Nie udało się utworzyć akcji',
@@ -468,18 +469,18 @@ return array(
// 'Unable to change the password.' => '',
// 'Change category for the task "%s"' => '',
// 'Change category' => '',
- // '%s updated the task #%d' => '',
- // '%s open the task #%d' => '',
- // '%s moved the task #%d to the position #%d in the column "%s"' => '',
- // '%s moved the task #%d to the column "%s"' => '',
- // '%s created the task #%d' => '',
- // '%s closed the task #%d' => '',
- // '%s created a subtask for the task #%d' => '',
- // '%s updated a subtask for the task #%d' => '',
+ // '%s updated the task %s' => '',
+ // '%s opened the task %s' => '',
+ // '%s moved the task %s to the position #%d in the column "%s"' => '',
+ // '%s moved the task %s to the column "%s"' => '',
+ // '%s created the task %s' => '',
+ // '%s closed the task %s' => '',
+ // '%s created a subtask for the task %s' => '',
+ // '%s updated a subtask for the task %s' => '',
// 'Assigned to %s with an estimate of %s/%sh' => '',
// 'Not assigned, estimate of %sh' => '',
- // '%s updated a comment on the task #%d' => '',
- // '%s commented the task #%d' => '',
+ // '%s updated a comment on the task %s' => '',
+ // '%s commented the task %s' => '',
// '%s\'s activity' => '',
// 'No activity.' => '',
// 'RSS feed' => '',
@@ -498,7 +499,7 @@ return array(
// 'Default columns for new projects (Comma-separated)' => '',
// 'Task assignee change' => '',
// '%s change the assignee of the task #%d to %s' => '',
- // '%s change the assignee of the task #%d to %s' => '',
+ // '%s changed the assignee of the task %s to %s' => '',
// '[%s][Column Change] %s (#%d)' => '',
// '[%s][Position Change] %s (#%d)' => '',
// '[%s][Assignee Change] %s (#%d)' => '',
@@ -555,8 +556,8 @@ return array(
// 'Webhooks' => '',
// 'API' => '',
// 'Integration' => '',
- // 'Github webhook' => '',
- // 'Help on Github webhook' => '',
+ // 'Github webhooks' => '',
+ // 'Help on Github webhooks' => '',
// 'Create a comment from an external provider' => '',
// 'Github issue comment created' => '',
// 'Configure' => '',
@@ -602,4 +603,49 @@ return array(
// 'Nothing to preview...' => '',
// 'Preview' => '',
// 'Write' => '',
+ // 'Active swimlanes' => '',
+ // 'Add a new swimlane' => '',
+ // 'Change default swimlane' => '',
+ // 'Default swimlane' => '',
+ // 'Do you really want to remove this swimlane: "%s"?' => '',
+ // 'Inactive swimlanes' => '',
+ // 'Set project manager' => '',
+ // 'Set project member' => '',
+ // 'Remove a swimlane' => '',
+ // 'Rename' => '',
+ // 'Show default swimlane' => '',
+ // 'Swimlane modification for the project "%s"' => '',
+ // 'Swimlane not found.' => '',
+ // 'Swimlane removed successfully.' => '',
+ // 'Swimlanes' => '',
+ // 'Swimlane updated successfully.' => '',
+ // 'The default swimlane have been updated successfully.' => '',
+ // 'Unable to create your swimlane.' => '',
+ // 'Unable to remove this swimlane.' => '',
+ // 'Unable to update this swimlane.' => '',
+ // 'Your swimlane have been created successfully.' => '',
+ // 'Example: "Bug, Feature Request, Improvement"' => '',
+ // 'Default categories for new projects (Comma-separated)' => '',
+ // 'Gitlab commit received' => '',
+ // 'Gitlab issue opened' => '',
+ // 'Gitlab issue closed' => '',
+ // 'Gitlab webhooks' => '',
+ // 'Help on Gitlab webhooks' => '',
+ // 'Integrations' => '',
+ // 'Integration with third-party services' => '',
+ // 'Role for this project' => '',
+ // 'Project manager' => '',
+ // 'Project member' => '',
+ // 'A project manager can change the settings of the project and have more privileges than a standard user.' => '',
+ // 'Gitlab Issue' => '',
+ // 'Subtask Id' => '',
+ // 'Subtasks' => '',
+ // 'Subtasks Export' => '',
+ // 'Subtasks exportation for "%s"' => '',
+ // 'Task Title' => '',
+ // 'Untitled' => '',
+ // 'Application default' => '',
+ // 'Language:' => '',
+ // 'Timezone:' => '',
+ // 'Next' => '',
);
diff --git a/sources/app/Locale/pt_BR/translations.php b/sources/app/Locale/pt_BR/translations.php
index 7525445..f155b04 100644
--- a/sources/app/Locale/pt_BR/translations.php
+++ b/sources/app/Locale/pt_BR/translations.php
@@ -4,8 +4,8 @@ return array(
'None' => 'Nenhum',
'edit' => 'editar',
'Edit' => 'Editar',
- 'remove' => 'apagar',
- 'Remove' => 'Apagar',
+ 'remove' => 'remover',
+ 'Remove' => 'Remover',
'Update' => 'Atualizar',
'Yes' => 'Sim',
'No' => 'Não',
@@ -14,175 +14,175 @@ return array(
'Yellow' => 'Amarelo',
'Blue' => 'Azul',
'Green' => 'Verde',
- 'Purple' => 'Violeta',
+ 'Purple' => 'Roxo',
'Red' => 'Vermelho',
'Orange' => 'Laranja',
'Grey' => 'Cinza',
'Save' => 'Salvar',
'Login' => 'Login',
- 'Official website:' => 'Site web oficial :',
+ 'Official website:' => 'Site oficial:',
'Unassigned' => 'Não Atribuída',
'View this task' => 'Ver esta tarefa',
'Remove user' => 'Remover usuário',
- 'Do you really want to remove this user: "%s"?' => 'Quer realmente remover este usuário: "%s"?',
+ 'Do you really want to remove this user: "%s"?' => 'Você realmente deseja remover este usuário: "%s"?',
'New user' => 'Novo usuário',
'All users' => 'Todos os usuários',
- 'Username' => 'Nome do usuário',
+ 'Username' => 'Nome de usuário',
'Password' => 'Senha',
- 'Default project' => 'Projeto default',
+ 'Default project' => 'Projeto padrão',
'Administrator' => 'Administrador',
- 'Sign in' => 'Logar',
+ 'Sign in' => 'Entrar',
'Users' => 'Usuários',
'No user' => 'Sem usuário',
'Forbidden' => 'Proibido',
'Access Forbidden' => 'Acesso negado',
'Only administrators can access to this page.' => 'Somente administradores têm acesso a esta página.',
'Edit user' => 'Editar usuário',
- 'Logout' => 'Logout',
+ 'Logout' => 'Sair',
'Bad username or password' => 'Usuário ou senha inválidos',
'users' => 'usuários',
'projects' => 'projetos',
'Edit project' => 'Editar projeto',
'Name' => 'Nome',
- 'Activated' => 'Ativo',
+ 'Activated' => 'Ativado',
'Projects' => 'Projetos',
'No project' => 'Nenhum projeto',
'Project' => 'Projeto',
'Status' => 'Status',
'Tasks' => 'Tarefas',
- 'Board' => 'Quadro',
+ 'Board' => 'Board',
'Actions' => 'Ações',
'Inactive' => 'Inativo',
'Active' => 'Ativo',
'Column %d' => 'Coluna %d',
'Add this column' => 'Adicionar esta coluna',
- '%d tasks on the board' => '%d tarefas no quadro',
+ '%d tasks on the board' => '%d tarefas no board',
'%d tasks in total' => '%d tarefas no total',
- 'Unable to update this board.' => 'Impossível atualizar este quadro.',
- 'Edit board' => 'Modificar quadro',
+ 'Unable to update this board.' => 'Não foi possível atualizar este board.',
+ 'Edit board' => 'Editar board',
'Disable' => 'Desativar',
'Enable' => 'Ativar',
'New project' => 'Novo projeto',
- 'Do you really want to remove this project: "%s"?' => 'Quer realmente remover este projeto: "%s" ?',
+ 'Do you really want to remove this project: "%s"?' => 'Você realmente deseja remover este projeto: "%s" ?',
'Remove project' => 'Remover projeto',
- 'Boards' => 'Quadros',
- 'Edit the board for "%s"' => 'Editar o quadro para "%s"',
+ 'Boards' => 'Boards',
+ 'Edit the board for "%s"' => 'Editar o board para "%s"',
'All projects' => 'Todos os projetos',
'Change columns' => 'Modificar colunas',
'Add a new column' => 'Adicionar uma nova coluna',
'Title' => 'Título',
- 'Add Column' => 'Adicionar coluna',
+ 'Add Column' => 'Adicionar Coluna',
'Project "%s"' => 'Projeto "%s"',
'Nobody assigned' => 'Ninguém designado',
'Assigned to %s' => 'Designado para %s',
'Remove a column' => 'Remover uma coluna',
- 'Remove a column from a board' => 'Remover uma coluna do quadro',
- 'Unable to remove this column.' => 'Impossível remover esta coluna.',
- 'Do you really want to remove this column: "%s"?' => 'Quer realmente remover esta coluna: "%s"?',
- 'This action will REMOVE ALL TASKS associated to this column!' => 'Esta ação vai REMOVER TODAS AS TAREFAS associadas a esta coluna!',
- 'Settings' => 'Preferências',
- 'Application settings' => 'Preferências da aplicação',
+ 'Remove a column from a board' => 'Remover uma coluna do board',
+ 'Unable to remove this column.' => 'Não foi possível remover esta coluna.',
+ 'Do you really want to remove this column: "%s"?' => 'Você realmente deseja remover esta coluna: "%s"?',
+ 'This action will REMOVE ALL TASKS associated to this column!' => 'Esta ação irá REMOVER TODAS AS TAREFAS associadas a esta coluna!',
+ 'Settings' => 'Configurações',
+ 'Application settings' => 'Configurações da aplicação',
'Language' => 'Idioma',
'Webhook token:' => 'Token de webhooks:',
'API token:' => 'API Token:',
- 'More information' => 'Mais informação',
+ 'More information' => 'Mais informações',
'Database size:' => 'Tamanho do banco de dados:',
'Download the database' => 'Download do banco de dados',
'Optimize the database' => 'Otimizar o banco de dados',
'(VACUUM command)' => '(Comando VACUUM)',
'(Gzip compressed Sqlite file)' => '(Arquivo Sqlite comprimido com Gzip)',
'User settings' => 'Configurações do usuário',
- 'My default project:' => 'Meu projeto default:',
- 'Close a task' => 'Encerrar uma tarefa',
- 'Do you really want to close this task: "%s"?' => 'Quer realmente encerrar esta tarefa: "%s"?',
+ 'My default project:' => 'Meu projeto padrão:',
+ 'Close a task' => 'Finalizar uma tarefa',
+ 'Do you really want to close this task: "%s"?' => 'Você realmente deseja finalizar esta tarefa: "%s"?',
'Edit a task' => 'Editar uma tarefa',
'Column' => 'Coluna',
'Color' => 'Cor',
'Assignee' => 'Designação',
- 'Create another task' => 'Criar uma outra tarefa (aproveitando os dados preenchidos)',
+ 'Create another task' => 'Criar outra tarefa',
'New task' => 'Nova tarefa',
'Open a task' => 'Abrir uma tarefa',
- 'Do you really want to open this task: "%s"?' => 'Quer realmente abrir esta tarefa: "%s"?',
- 'Back to the board' => 'Voltar ao quadro',
+ 'Do you really want to open this task: "%s"?' => 'Você realmente deseja abrir esta tarefa: "%s"?',
+ 'Back to the board' => 'Voltar ao board',
'Created on %B %e, %Y at %k:%M %p' => 'Criado em %d %B %Y às %H:%M',
'There is nobody assigned' => 'Não há ninguém designado',
- 'Column on the board:' => 'Coluna no quadro:',
+ 'Column on the board:' => 'Coluna no board:',
'Status is open' => 'Status está aberto',
- 'Status is closed' => 'Status está encerrado',
- 'Close this task' => 'Encerrar esta tarefa',
+ 'Status is closed' => 'Status está finalizado',
+ 'Close this task' => 'Finalizar esta tarefa',
'Open this task' => 'Abrir esta tarefa',
'There is no description.' => 'Não há descrição.',
'Add a new task' => 'Adicionar uma nova tarefa',
'The username is required' => 'O nome de usuário é obrigatório',
- 'The maximum length is %d characters' => 'O tamanho máximo são %d caracteres',
- 'The minimum length is %d characters' => 'O tamanho mínimo são %d caracteres',
+ 'The maximum length is %d characters' => 'O tamanho máximo é %d caracteres',
+ 'The minimum length is %d characters' => 'O tamanho mínimo é %d caracteres',
'The password is required' => 'A senha é obrigatória',
- 'This value must be an integer' => 'O valor deve ser um inteiro',
+ 'This value must be an integer' => 'O valor deve ser um número inteiro',
'The username must be unique' => 'O nome de usuário deve ser único',
- 'The username must be alphanumeric' => 'O nome de usuário deve ser alfanumérico, sem espaços ou _',
- 'The user id is required' => 'O id de usuário é obrigatório',
- 'Passwords don\'t match' => 'As senhas não conferem',
+ 'The username must be alphanumeric' => 'O nome de usuário deve ser alfanumérico',
+ 'The user id is required' => 'O ID de usuário é obrigatório',
+ 'Passwords don\'t match' => 'As senhas não coincidem',
'The confirmation is required' => 'A confirmação é obrigatória',
'The column is required' => 'A coluna é obrigatória',
'The project is required' => 'O projeto é obrigatório',
'The color is required' => 'A cor é obrigatória',
- 'The id is required' => 'O id é obrigatório',
- 'The project id is required' => 'O id do projeto é obrigatório',
+ 'The id is required' => 'O ID é obrigatório',
+ 'The project id is required' => 'O ID do projeto é obrigatório',
'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',
'The language is required' => 'O idioma é 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.' => 'Impossível salvar suas configurações.',
- 'Database optimization done.' => 'Otimização do banco de dados terminada.',
+ 'Unable to save your settings.' => 'Não é possível salvar suas configurações.',
+ 'Database optimization done.' => 'Otimização do banco de dados finalizada.',
'Your project have been created successfully.' => 'Seu projeto foi criado com sucesso.',
- 'Unable to create your project.' => 'Impossível criar seu projeto.',
+ 'Unable to create your project.' => 'Não é possível criar o seu projeto.',
'Project updated successfully.' => 'Projeto atualizado com sucesso.',
- 'Unable to update this project.' => 'Impossível atualizar este projeto.',
- 'Unable to remove this project.' => 'Impossível remover este projeto.',
+ 'Unable to update this project.' => 'Não é possível atualizar este projeto.',
+ 'Unable to remove this project.' => 'Não é possível remover este projeto.',
'Project removed successfully.' => 'Projeto removido com sucesso.',
'Project activated successfully.' => 'Projeto ativado com sucesso.',
- 'Unable to activate this project.' => 'Impossível ativar este projeto.',
- 'Project disabled successfully.' => 'Projeto desabilitado com sucesso.',
- 'Unable to disable this project.' => 'Impossível desabilitar este projeto.',
- 'Unable to open this task.' => 'Impossível abrir esta tarefa.',
+ 'Unable to activate this project.' => 'Não é possível ativar este projeto.',
+ 'Project disabled successfully.' => 'Projeto desativado com sucesso.',
+ 'Unable to disable this project.' => 'Não é possível desativar este projeto.',
+ 'Unable to open this task.' => 'Não é possível abrir esta tarefa.',
'Task opened successfully.' => 'Tarefa aberta com sucesso.',
- 'Unable to close this task.' => 'Impossível encerrar esta tarefa.',
- 'Task closed successfully.' => 'Tarefa encerrada com sucesso.',
- 'Unable to update your task.' => 'Impossível atualizar sua tarefa.',
+ 'Unable to close this task.' => 'Não é possível finalizar esta tarefa.',
+ 'Task closed successfully.' => 'Tarefa finalizada com sucesso.',
+ 'Unable to update your task.' => 'Não é possível atualizar a sua tarefa.',
'Task updated successfully.' => 'Tarefa atualizada com sucesso.',
- 'Unable to create your task.' => 'Impossível criar sua tarefa.',
+ 'Unable to create your task.' => 'Não é possível criar a sua tarefa.',
'Task created successfully.' => 'Tarefa criada com sucesso.',
'User created successfully.' => 'Usuário criado com sucesso.',
- 'Unable to create your user.' => 'Impossível criar seu usuário.',
+ 'Unable to create your user.' => 'Não é possível criar o seu usuário.',
'User updated successfully.' => 'Usuário atualizado com sucesso.',
- 'Unable to update your user.' => 'Impossível atualizar seu usuário.',
+ 'Unable to update your user.' => 'Não é possível atualizar o seu usuário.',
'User removed successfully.' => 'Usuário removido com sucesso.',
- 'Unable to remove this user.' => 'Impossível remover este usuário.',
- 'Board updated successfully.' => 'Quadro atualizado com sucesso.',
+ 'Unable to remove this user.' => 'Não é possível remover este usuário.',
+ 'Board updated successfully.' => 'Board atualizado com sucesso.',
'Ready' => 'Pronto',
'Backlog' => 'Backlog',
'Work in progress' => 'Em andamento',
- 'Done' => 'Encerrado',
+ 'Done' => 'Finalizado',
'Application version:' => 'Versão da aplicação:',
- 'Completed on %B %e, %Y at %k:%M %p' => 'Encerrado em %d %B %Y às %H:%M',
+ 'Completed on %B %e, %Y at %k:%M %p' => 'Finalizado em %d %B %Y às %H:%M',
'%B %e, %Y at %k:%M %p' => '%d %B %Y às %H:%M',
'Date created' => 'Data de criação',
- 'Date completed' => 'Data de encerramento',
+ 'Date completed' => 'Data da finalização',
'Id' => 'Id',
'No task' => 'Nenhuma tarefa',
- 'Completed tasks' => 'tarefas completadas',
+ 'Completed tasks' => 'Tarefas completadas',
'List of projects' => 'Lista de projetos',
'Completed tasks for "%s"' => 'Tarefas completadas por "%s"',
- '%d closed tasks' => '%d tarefas encerradas',
- 'No task for this project' => 'Nenhuma tarefa para este projeto',
+ '%d closed tasks' => '%d tarefas finalizadas',
+ 'No task for this project' => 'Não há tarefa para este projeto',
'Public link' => 'Link público',
'There is no column in your project!' => 'Não há colunas no seu projeto!',
'Change assignee' => 'Mudar a designação',
'Change assignee for the task "%s"' => 'Modificar designação para a tarefa "%s"',
'Timezone' => 'Fuso horário',
- 'Sorry, I didn\'t found this information in my database!' => 'Desculpe, não encontrei esta informação no meu banco de dados!',
+ 'Sorry, I didn\'t find this information in my database!' => 'Desculpe, não encontrei esta informação no meu banco de dados!',
'Page not found' => 'Página não encontrada',
'Complexity' => 'Complexidade',
'limit' => 'limite',
@@ -191,10 +191,10 @@ return array(
'This value must be greater than %d' => 'Este valor deve ser maior que %d',
'Edit project access list' => 'Editar lista de acesso ao projeto',
'Edit users access' => 'Editar acesso de usuários',
- 'Allow this user' => 'Permitir esse usuário',
- 'Only those users have access to this project:' => 'Somente estes usuários têm acesso a este projeto:',
+ 'Allow this user' => 'Permitir este usuário',
+ 'Only those users have access to this project:' => 'Somente esses usuários têm acesso a este projeto:',
'Don\'t forget that administrators have access to everything.' => 'Não esqueça que administradores têm acesso a tudo.',
- 'revoke' => 'revogar',
+ 'Revoke' => 'Revogar',
'List of authorized users' => 'Lista de usuários autorizados',
'User' => 'Usuário',
'Nobody have access to this project.' => 'Ninguém tem acesso a este projeto.',
@@ -205,19 +205,20 @@ return array(
'Leave a comment' => 'Deixe um comentário',
'Comment is required' => 'Comentário é obrigatório',
'Leave a description' => 'Deixe uma descrição',
- 'Comment added successfully.' => 'Cpmentário adicionado com sucesso.',
- 'Unable to create your comment.' => 'Impossível criar seu comentário.',
+ 'Comment added successfully.' => 'Comentário adicionado com sucesso.',
+ 'Unable to create your comment.' => 'Não é possível criar o seu comentário.',
'The description is required' => 'A descrição é obrigatória',
'Edit this task' => 'Editar esta tarefa',
'Due Date' => 'Data de vencimento',
'Invalid date' => 'Data inválida',
- 'Must be done before %B %e, %Y' => 'Deve ser feito antes de %d %B %Y',
+ 'Must be done before %B %e, %Y' => 'Deve ser finalizado antes de %d %B %Y',
'%B %e, %Y' => '%d %B %Y',
+ '%b %e, %Y' => '%d %B %Y',
'Automatic actions' => 'Ações automáticas',
'Your automatic action have been created successfully.' => 'Sua ação automética foi criada com sucesso.',
- 'Unable to create your automatic action.' => 'Impossível criar sua ação automática.',
+ 'Unable to create your automatic action.' => 'Não é possível criar sua ação automática.',
'Remove an action' => 'Remover uma ação',
- 'Unable to remove this action.' => 'Impossível remover esta ação.',
+ 'Unable to remove this action.' => 'Não é possível remover esta ação.',
'Action removed successfully.' => 'Ação removida com sucesso.',
'Automatic actions for the project "%s"' => 'Ações automáticas para o projeto "%s"',
'Defined actions' => 'Ações definidas',
@@ -227,22 +228,22 @@ return array(
'Action parameters' => 'Parâmetros da ação',
'Action' => 'Ação',
'Event' => 'Evento',
- 'When the selected event occurs execute the corresponding action.' => 'Quando o evento selecionado ocorrer, execute a ação correspondente.',
+ 'When the selected event occurs execute the corresponding action.' => 'Quando o evento selecionado ocorrer execute a ação correspondente.',
'Next step' => 'Próximo passo',
'Define action parameters' => 'Definir parêmetros da ação',
'Save this action' => 'Salvar esta ação',
- 'Do you really want to remove this action: "%s"?' => 'Você quer realmente remover esta ação: "%s"?',
- 'Remove an automatic action' => 'Remove uma ação automática',
- 'Close the task' => 'Encerrar tarefa',
+ 'Do you really want to remove this action: "%s"?' => 'Você realmente deseja remover esta ação: "%s"?',
+ 'Remove an automatic action' => 'Remover uma ação automática',
+ 'Close the task' => 'Finalizar tarefa',
'Assign the task to a specific user' => 'Designar a tarefa para um usuário específico',
'Assign the task to the person who does the action' => 'Designar a tarefa para a pessoa que executa a ação',
'Duplicate the task to another project' => 'Duplicar a tarefa para um outro projeto',
'Move a task to another column' => 'Mover a tarefa para outra coluna',
- 'Move a task to another position in the same column' => 'Mover a tarefa para outra posição, na mesma coluna',
+ 'Move a task to another position in the same column' => 'Mover a tarefa para outra posição na mesma coluna',
'Task modification' => 'Modificação de tarefa',
'Task creation' => 'Criação de tarefa',
- 'Open a closed task' => 'Reabrir uma tarefa encerrada',
- 'Closing a task' => 'Encerrando uma tarefa',
+ 'Open a closed task' => 'Reabrir uma tarefa finalizada',
+ 'Closing a task' => 'Finalizando uma tarefa',
'Assign a color to a specific user' => 'Designar uma cor para um usuário específico',
'Column title' => 'Título da coluna',
'Position' => 'Posição',
@@ -253,26 +254,26 @@ return array(
'link' => 'link',
'Update this comment' => 'Atualizar este comentário',
'Comment updated successfully.' => 'Comentário atualizado com sucesso.',
- 'Unable to update your comment.' => 'Impossível atualizar seu comentário.',
+ 'Unable to update your comment.' => 'Não é possível atualizar o seu comentário.',
'Remove a comment' => 'Remover um comentário',
'Comment removed successfully.' => 'Comentário removido com sucesso.',
- 'Unable to remove this comment.' => 'Impossível remover este comentário.',
- 'Do you really want to remove this comment?' => 'Você tem certeza de que quer remover este comentário?',
- 'Only administrators or the creator of the comment can access to this page.' => 'Somente administradores ou o criator deste comentário tem acesso a esta página.',
+ 'Unable to remove this comment.' => 'Não é possível remover este comentário.',
+ 'Do you really want to remove this comment?' => 'Você realmente deseja remover este comentário?',
+ 'Only administrators or the creator of the comment can access to this page.' => 'Somente os administradores ou o criator deste comentário possuem acesso a esta página.',
'Details' => 'Detalhes',
'Current password for the user "%s"' => 'Senha atual para o usuário "%s"',
'The current password is required' => 'A senha atual é obrigatória',
- 'Wrong password' => 'Senha errada',
- 'Reset all tokens' => 'Reiniciar todos os tokens',
+ 'Wrong password' => 'Senha incorreta',
+ 'Reset all tokens' => 'Resetar todos os tokens',
'All tokens have been regenerated.' => 'Todos os tokens foram gerados novamente.',
'Unknown' => 'Desconhecido',
'Last logins' => 'Últimos logins',
'Login date' => 'Data de login',
'Authentication method' => 'Método de autenticação',
'IP address' => 'Endereço IP',
- 'User agent' => 'Agente usuário',
+ 'User agent' => 'User Agent',
'Persistent connections' => 'Conexões persistentes',
- 'No session.' => 'Sem sessão.',
+ 'No session.' => 'Nenhuma sessão.',
'Expiration date' => 'Data de expiração',
'Remember Me' => 'Lembre-se de mim',
'Creation date' => 'Data de criação',
@@ -280,25 +281,25 @@ return array(
'Filter by due date' => 'Filtrar por data de vencimento',
'Everybody' => 'Todos',
'Open' => 'Abrir',
- 'Closed' => 'Encerrado',
+ 'Closed' => 'Finalizado',
'Search' => 'Pesquisar',
- 'Nothing found.' => 'Não encontrado.',
- 'Search in the project "%s"' => 'Procure no projeto "%s"',
+ 'Nothing found.' => 'Nada foi encontrado.',
+ 'Search in the project "%s"' => 'Pesquisar no projeto "%s"',
'Due date' => 'Data de vencimento',
'Others formats accepted: %s and %s' => 'Outros formatos permitidos: %s e %s',
'Description' => 'Descrição',
'%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 Google não está mais associada ao seu perfil.',
- 'Unable to unlink your Google Account.' => 'Impossível desassociar sua conta Google.',
+ '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.' => 'Impossível associar a sua conta do Google.',
- 'Your Google Account is linked to your profile successfully.' => 'Sua Conta do Google está ligada ao seu perfil com sucesso.',
+ '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.',
'Email' => 'E-mail',
- 'Link my Google Account' => 'Vincular minha conta Google',
- 'Unlink my Google Account' => 'Desvincular minha conta do Google',
- 'Login with my Google Account' => 'Entrar com minha conta do Google',
+ 'Link my Google Account' => 'Vincular minha Conta do Google',
+ 'Unlink my Google Account' => 'Desvincular minha Conta do Google',
+ 'Login with my Google Account' => 'Entrar com minha Conta do Google',
'Project not found.' => 'Projeto não encontrado.',
'Task #%d' => 'Tarefa #%d',
'Task removed successfully.' => 'Tarefa removida com sucesso.',
@@ -312,8 +313,8 @@ return array(
'Category:' => 'Categoria:',
'Categories' => 'Categorias',
'Category not found.' => 'Categoria não encontrada.',
- 'Your category have been created successfully.' => 'Seu categoria foi criada com sucesso.',
- 'Unable to create your category.' => 'Não é possível criar sua categoria.',
+ 'Your category have been created successfully.' => 'Sua categoria foi criada com sucesso.',
+ 'Unable to create your category.' => 'Não foi possível criar a sua categoria.',
'Your category have been updated successfully.' => 'A sua categoria foi atualizada com sucesso.',
'Unable to update your category.' => 'Não foi possível atualizar a sua categoria.',
'Remove a category' => 'Remover uma categoria',
@@ -326,7 +327,7 @@ return array(
'Do you really want to remove this category: "%s"?' => 'Você realmente deseja remover esta categoria: "%s"',
'Filter by category' => 'Filtrar por categoria',
'All categories' => 'Todas as categorias',
- 'No category' => 'Sem categoria',
+ 'No category' => 'Nenhum categoria',
'The name is required' => 'O nome é obrigatório',
'Remove a file' => 'Remover um arquivo',
'Unable to remove this file.' => 'Não foi possível remover este arquivo.',
@@ -343,60 +344,60 @@ return array(
'Time tracking' => 'Rastreamento de tempo',
'Estimate:' => 'Estimado:',
'Spent:' => 'Gasto:',
- 'Do you really want to remove this sub-task?' => 'Você realmente deseja remover esta sub-tarefa?',
+ 'Do you really want to remove this sub-task?' => 'Você realmente deseja remover esta subtarefa?',
'Remaining:' => 'Restante:',
'hours' => 'horas',
'spent' => 'gasto',
- 'estimated' => 'estimada',
- 'Sub-Tasks' => 'Sub-tarefas',
- 'Add a sub-task' => 'Adicionar uma sub-tarefa',
+ 'estimated' => 'estimado',
+ 'Sub-Tasks' => 'Subtarefas',
+ 'Add a sub-task' => 'Adicionar uma subtarefa',
'Original estimate' => 'Estimativa original',
- 'Create another sub-task' => 'Criar uma outra sub-tarefa',
+ 'Create another sub-task' => 'Criar uma outra subtarefa',
'Time spent' => 'Tempo gasto',
- 'Edit a sub-task' => 'Editar uma sub-tarefa',
- 'Remove a sub-task' => 'Remover uma sub-tarefa',
+ 'Edit a sub-task' => 'Editar uma subtarefa',
+ 'Remove a sub-task' => 'Remover uma subtarefa',
'The time must be a numeric value' => 'O tempo deve ser um valor numérico',
- 'Todo' => 'A fazer',
+ 'Todo' => 'À fazer',
'In progress' => 'Em andamento',
- 'Sub-task removed successfully.' => 'Sub-tarefa removido com sucesso.',
- 'Unable to remove this sub-task.' => 'Não foi possível remover esta sub-tarefa.',
- 'Sub-task updated successfully.' => 'Sub-tarefa atualizada com sucesso.',
- 'Unable to update your sub-task.' => 'Não foi possível atualizar sua sub-tarefa.',
- 'Unable to create your sub-task.' => 'Não é possível criar sua sub-tarefa.',
- 'Sub-task added successfully.' => 'Sub-tarefa adicionada com sucesso.',
- 'Maximum size: ' => 'O tamanho máximo:',
+ 'Sub-task removed successfully.' => 'Subtarefa removida com sucesso.',
+ 'Unable to remove this sub-task.' => 'Não foi possível remover esta subtarefa.',
+ 'Sub-task updated successfully.' => 'Subtarefa atualizada com sucesso.',
+ 'Unable to update your sub-task.' => 'Não foi possível atualizar a sua subtarefa.',
+ 'Unable to create your sub-task.' => 'Não é possível criar a sua subtarefa.',
+ 'Sub-task added successfully.' => 'Subtarefa adicionada com sucesso.',
+ 'Maximum size: ' => 'Tamanho máximo:',
'Unable to upload the file.' => 'Não foi possível carregar o arquivo.',
- 'Display another project' => 'Mostrar um outro projeto',
- 'Your GitHub account was successfully linked to your profile.' => 'A sua conta GitHub foi ligada com sucesso ao seu perfil.',
- 'Unable to link your GitHub Account.' => 'Não foi possível vincular sua conta GitHub.',
- 'GitHub authentication failed' => 'Falhou autenticação GitHub',
- 'Your GitHub account is no longer linked to your profile.' => 'A sua conta GitHub já não está ligada ao seu perfil.',
- 'Unable to unlink your GitHub Account.' => 'Não foi possível desvincular sua conta GitHub.',
- 'Login with my GitHub Account' => 'Entrar com minha conta do GitHub',
- 'Link my GitHub Account' => 'Vincular minha conta GitHub',
- 'Unlink my GitHub Account' => 'Desvincular minha conta do GitHub',
+ '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',
'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' => 'Tarefas Export',
- 'Tasks exportation for "%s"' => 'Tarefas exportação para "%s"',
+ 'Tasks Export' => 'Exportar Tarefas',
+ 'Tasks exportation for "%s"' => 'As tarefas foram exportadas para "%s"',
'Start Date' => 'Data inicial',
'End Date' => 'Data final',
'Execute' => 'Executar',
- 'Task Id' => 'Id da Tarefa',
- 'Creator' => 'Criador',
- 'Modification date' => 'Data de modificação',
- 'Completion date' => 'Data de conclusão',
+ 'Task Id' => 'ID da Tarefa',
+ 'Creator' => 'Criado por',
+ 'Modification date' => 'Data da modificação',
+ 'Completion date' => 'Data da finalização',
'Webhook URL for task creation' => 'Webhook URL para criação de tarefas',
- 'Webhook URL for task modification' => 'Webhook URL para modificação tarefa',
- 'Clone' => 'Clone',
+ 'Webhook URL for task modification' => 'Webhook URL para modificação de tarefa',
+ 'Clone' => 'Clonar',
'Clone Project' => 'Clonar Projeto',
'Project cloned successfully.' => 'Projeto clonado com sucesso.',
- 'Unable to clone this project.' => 'Impossível clonar este projeto.',
+ 'Unable to clone this project.' => 'Não foi possível clonar este projeto.',
'Email notifications' => 'Notificações por email',
'Enable email notifications' => 'Habilitar notificações por email',
'Task position:' => 'Posição da tarefa:',
'The task #%d have been opened.' => 'A tarefa #%d foi aberta.',
- 'The task #%d have been closed.' => 'A tarefa #%d foi encerrada.',
+ 'The task #%d have been closed.' => 'A tarefa #%d foi finalizada.',
'Sub-task updated' => 'Subtarefa atualizada',
'Title:' => 'Título:',
'Status:' => 'Status:',
@@ -407,18 +408,18 @@ return array(
'Comment updated' => 'Comentário atualizado',
'New comment posted by %s' => 'Novo comentário postado por %s',
'List of due tasks for the project "%s"' => 'Lista de tarefas pendentes para o projeto "%s"',
- // '[%s][New attachment] %s (#%d)' => '',
- // '[%s][New comment] %s (#%d)' => '',
- // '[%s][Comment updated] %s (#%d)' => '',
- // '[%s][New subtask] %s (#%d)' => '',
- // '[%s][Subtask updated] %s (#%d)' => '',
- // '[%s][New task] %s (#%d)' => '',
- // '[%s][Task updated] %s (#%d)' => '',
- // '[%s][Task closed] %s (#%d)' => '',
- // '[%s][Task opened] %s (#%d)' => '',
- // '[%s][Due tasks]' => '',
+ '[%s][New attachment] %s (#%d)' => '[%s][Novo anexo] %s (#%d)',
+ '[%s][New comment] %s (#%d)' => '[%s][Novo comentário] %s (#%d)',
+ '[%s][Comment updated] %s (#%d)' => '[%s][Comentário atualizado] %s (#%d)',
+ '[%s][New subtask] %s (#%d)' => '[%s][Nova subtarefa] %s (#%d)',
+ '[%s][Subtask updated] %s (#%d)' => '[%s][Subtarefa atualizada] %s (#%d)',
+ '[%s][New task] %s (#%d)' => '[%s][Nova tarefa] %s (#%d)',
+ '[%s][Task updated] %s (#%d)' => '[%s][Tarefa atualizada] %s (#%d)',
+ '[%s][Task closed] %s (#%d)' => '[%s][Tarefa finalizada] %s (#%d)',
+ '[%s][Task opened] %s (#%d)' => '[%s][Tarefa aberta] %s (#%d)',
+ '[%s][Due tasks]' => '[%s][Tarefas pendentes]',
'[Kanboard] Notification' => '[Kanboard] Notificação',
- 'I want to receive notifications only for those projects:' => 'Quero receber notificações somente para estes projetos:',
+ 'I want to receive notifications only for those projects:' => 'Quero receber notificações apenas destes projetos:',
'view the task on Kanboard' => 'ver a tarefa no Kanboard',
'Public access' => 'Acesso público',
'Category management' => 'Gerenciamento de categorias',
@@ -429,17 +430,17 @@ return array(
'Active projects' => 'Projetos ativos',
'Inactive projects' => 'Projetos inativos',
'Public access disabled' => 'Acesso público desabilitado',
- 'Do you really want to disable this project: "%s"?' => 'Deseja ralmente desabilitar este projeto: "%s"?',
- 'Do you really want to duplicate this project: "%s"?' => 'Deseja realmente duplicar este projeto: "%s"?',
- 'Do you really want to enable this project: "%s"?' => 'Deseja realmente habilitar este projeto: "%s"?',
- 'Project activation' => 'Avaliação do projeto',
+ 'Do you really want to disable this project: "%s"?' => 'Você realmente deseja desabilitar este projeto: "%s"?',
+ 'Do you really want to duplicate this project: "%s"?' => 'Você realmente deseja duplicar este projeto: "%s"?',
+ 'Do you really want to enable this project: "%s"?' => 'Você realmente deseja habilitar este projeto: "%s"?',
+ 'Project activation' => 'Ativação do projeto',
'Move the task to another project' => 'Mover a tarefa para outro projeto',
'Move to another project' => 'Mover para outro projeto',
- 'Do you really want to duplicate this task?' => 'Deseja realmente duplicar esta tarefa?',
- 'Duplicate a task' => 'Duplicar tarefa',
+ 'Do you really want to duplicate this task?' => 'Você realmente deseja duplicar esta tarefa?',
+ 'Duplicate a task' => 'Duplicar uma tarefa',
'External accounts' => 'Contas externas',
'Account type' => 'Tipo de conta',
- // 'Local' => '',
+ 'Local' => 'Local',
'Remote' => 'Remoto',
'Enabled' => 'Habilitado',
'Disabled' => 'Desabilitado',
@@ -447,12 +448,12 @@ return array(
'Github account linked' => 'Conta do Github associada',
'Username:' => 'Usuário:',
'Name:' => 'Nome:',
- // 'Email:' => '',
+ 'Email:' => 'E-mail:',
'Default project:' => 'Projeto padrão:',
'Notifications:' => 'Notificações:',
'Notifications' => 'Notificações',
- 'Group:' => 'Groupo:',
- 'Regular user' => 'Usuário habitual',
+ 'Group:' => 'Grupo:',
+ 'Regular user' => 'Usuário comum',
'Account type:' => 'Tipo de conta:',
'Edit profile' => 'Editar perfil',
'Change password' => 'Alterar senha',
@@ -463,33 +464,33 @@ return array(
'Never connected.' => 'Nunca conectado.',
'No account linked.' => 'Nenhuma conta associada.',
'Account linked.' => 'Conta associada.',
- 'No external authentication enabled.' => 'Nenhuma autenticação externa permitida.',
+ 'No external authentication enabled.' => 'Nenhuma autenticação externa habilitada.',
'Password modified successfully.' => 'Senha alterada com sucesso.',
'Unable to change the password.' => 'Não foi possível alterar a senha.',
- 'Change category for the task "%s"' => 'Mudar categoria para a tarefa "%s"',
+ 'Change category for the task "%s"' => 'Mudar categoria da tarefa "%s"',
'Change category' => 'Mudar categoria',
- // '%s updated the task #%d' => '',
- // '%s open the task #%d' => '',
- // '%s moved the task #%d to the position #%d in the column "%s"' => '',
- // '%s moved the task #%d to the column "%s"' => '',
- // '%s created the task #%d' => '',
- // '%s closed the task #%d' => '',
- '%s created a subtask for the task #%d' => '%s criou uma sub-tarefa para a tarefa #%d',
- '%s updated a subtask for the task #%d' => '%s atualizou uma sub-tarefa da tarefa #%d',
+ '%s updated the task %s' => '%s atualizou a tarefa %s',
+ '%s opened the task %s' => '%s abriu a tarefa %s',
+ '%s moved the task %s to the position #%d in the column "%s"' => '%s moveu a tarefa %s para a posição #%d na coluna "%s"',
+ '%s moved the task %s to the column "%s"' => '%s moveu a tarefa %s para a coluna "%s"',
+ '%s created the task %s' => '%s criou a tarefa %s',
+ '%s closed the task %s' => '%s finalizou a tarefa %s',
+ '%s created a subtask for the task %s' => '%s criou uma subtarefa para a tarefa %s',
+ '%s updated a subtask for the task %s' => '%s atualizou uma subtarefa da tarefa %s',
'Assigned to %s with an estimate of %s/%sh' => 'Designado para %s com tempo estimado de %s/%sh',
'Not assigned, estimate of %sh' => 'Não designado, estimado em %sh',
- '%s updated a comment on the task #%d' => '%s atualizou o comentário na tarefa #%d',
- '%s commented the task #%d' => '%s comentou a tarefa #%d',
+ '%s updated a comment on the task %s' => '%s atualizou o comentário na tarefa %s',
+ '%s commented the task %s' => '%s comentou a tarefa %s',
'%s\'s activity' => 'Atividades de%s',
'No activity.' => 'Sem atividade.',
- // 'RSS feed' => '',
- '%s updated a comment on the task #%d' => '%s atualizou um comentário na tarefa #%d',
- '%s commented on the task #%d' => '%s comentou na tarefa #%d',
- '%s updated a subtask for the task #%d' => '%s atualizou uma sub-tarefa para a tarefa #%d',
- '%s created a subtask for the task #%d' => '%s criou uma sub-tarefa para a tarefa #%d',
+ 'RSS feed' => 'Feed RSS',
+ '%s updated a comment on the task #%d' => '%s atualizou um comentário sobre a tarefa #%d',
+ '%s commented on the task #%d' => '%s comentou sobre a tarefa #%d',
+ '%s updated a subtask for the task #%d' => '%s atualizou uma subtarefa para a tarefa #%d',
+ '%s created a subtask for the task #%d' => '%s criou uma subtarefa para a tarefa #%d',
'%s updated the task #%d' => '%s atualizou a tarefa #%d',
'%s created the task #%d' => '%s criou a tarefa #%d',
- '%s closed the task #%d' => '%s encerrou a tarefa #%d',
+ '%s closed the task #%d' => '%s finalizou a tarefa #%d',
'%s open the task #%d' => '%s abriu a tarefa #%d',
'%s moved the task #%d to the column "%s"' => '%s moveu a tarefa #%d para a coluna "%s"',
'%s moved the task #%d to the position %d in the column "%s"' => '%s moveu a tarefa #%d para a posição %d na coluna "%s"',
@@ -498,108 +499,153 @@ return array(
'Default columns for new projects (Comma-separated)' => 'Colunas padrão para novos projetos (Separado por vírgula)',
'Task assignee change' => 'Mudar designação da tarefa',
'%s change the assignee of the task #%d to %s' => '%s mudou a designação da tarefa #%d para %s',
- '%s change the assignee of the task #%d to %s' => '%s mudou a designação da tarefa #%d para %s',
- // '[%s][Column Change] %s (#%d)' => '',
- // '[%s][Position Change] %s (#%d)' => '',
- // '[%s][Assignee Change] %s (#%d)' => '',
- 'New password for the user "%s"' => 'Novo password para o usuário "%s"',
+ '%s changed the assignee of the task %s to %s' => '%s mudou a designação da tarefa %s para %s',
+ '[%s][Column Change] %s (#%d)' => '[%s][Modificou Coluna] %s (#%d)',
+ '[%s][Position Change] %s (#%d)' => '[%s][Modificou Posição] %s (#%d)',
+ '[%s][Assignee Change] %s (#%d)' => '[%s][Modificou Designação] %s (#%d)',
+ 'New password for the user "%s"' => 'Nova senha para o usuário "%s"',
'Choose an event' => 'Escolher um evento',
- // 'Github commit received' => '',
- // 'Github issue opened' => '',
- // 'Github issue closed' => '',
- // 'Github issue reopened' => '',
- // 'Github issue assignee change' => '',
- // 'Github issue label change' => '',
- 'Create a task from an external provider' => 'Criar uma tarefa a partir de um provedor externo',
- 'Change the assignee based on an external username' => 'Alterar designação com vase em um usuário externo!',
+ 'Github commit received' => 'Github commit received',
+ 'Github issue opened' => 'Github issue opened',
+ 'Github issue closed' => 'Github issue closed',
+ 'Github issue reopened' => 'Github issue reopened',
+ 'Github issue assignee change' => 'Github issue assignee change',
+ 'Github issue label change' => 'Github issue label change',
+ 'Create a task from an external provider' => 'Criar uma tarefa por meio de um serviço externo',
+ 'Change the assignee based on an external username' => 'Alterar designação com base em um usuário externo',
'Change the category based on an external label' => 'Alterar categoria com base em um rótulo externo',
- 'Reference' => 'Referencia',
- 'Reference: %s' => 'Referencia: %s',
+ 'Reference' => 'Referência',
+ 'Reference: %s' => 'Referência: %s',
'Label' => 'Rótulo',
'Database' => 'Banco de dados',
'About' => 'Sobre',
- // 'Database driver:' => '',
- // 'Board settings' => '',
- // 'URL and token' => '',
- // 'Webhook settings' => '',
- // 'URL for task creation:' => '',
- // 'Reset token' => '',
- // 'API endpoint:' => '',
- // 'Refresh interval for private board' => '',
- // 'Refresh interval for public board' => '',
- // 'Task highlight period' => '',
- // 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => '',
- // 'Frequency in second (60 seconds by default)' => '',
- // 'Frequency in second (0 to disable this feature, 10 seconds by default)' => '',
+ 'Database driver:' => 'Driver do banco de dados:',
+ 'Board settings' => 'Configurações do Board',
+ 'URL and token' => 'URL e token',
+ 'Webhook settings' => 'Configurações do Webhook',
+ 'URL for task creation:' => 'URL para a criação da tarefa:',
+ 'Reset token' => 'Resetar token',
+ 'API endpoint:' => 'API endpoint:',
+ 'Refresh interval for private board' => 'Intervalo de atualização para um board privado',
+ 'Refresh interval for public board' => 'Intervalo de atualização para um board público',
+ 'Task highlight period' => 'Período de Tarefa em destaque',
+ 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Período (em segundos) para considerar que uma tarefa foi modificada recentemente (0 para desativar, 2 dias por padrão)',
+ 'Frequency in second (60 seconds by default)' => 'Frequência em segundos (60 segundos por padrão)',
+ 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Frequência em segundos (0 para desativar este recurso, 10 segundos por padrão)',
'Application URL' => 'URL da Aplicação',
- // 'Example: http://example.kanboard.net/ (used by email notifications)' => '',
- // 'Token regenerated.' => '',
+ 'Example: http://example.kanboard.net/ (used by email notifications)' => 'Exemplo: http://example.kanboard.net/ (utilizado nas notificações por e-mail)',
+ 'Token regenerated.' => 'Token ',
'Date format' => 'Formato de data',
'ISO format is always accepted, example: "%s" and "%s"' => 'O formato ISO é sempre aceito, exemplo: "%s" e "%s"',
'New private project' => 'Novo projeto privado',
'This project is private' => 'Este projeto é privado',
- 'Type here to create a new sub-task' => 'Digite aqui para criar uma nova sub-tarefa',
+ 'Type here to create a new sub-task' => 'Digite aqui para criar uma nova subtarefa',
'Add' => 'Adicionar',
'Estimated time: %s hours' => 'Tempo estimado: %s horas',
'Time spent: %s hours' => 'Tempo gasto: %s horas',
'Started on %B %e, %Y' => 'Iniciado em %B %e, %Y',
'Start date' => 'Data de início',
'Time estimated' => 'Tempo estimado',
- 'There is nothing assigned to you.' => 'Não há nada designado para você.',
+ 'There is nothing assigned to you.' => 'Não há nada designado à você.',
'My tasks' => 'Minhas tarefas',
- // 'Activity stream' => '',
- // 'Dashboard' => '',
+ 'Activity stream' => 'Atividades Recentes',
+ 'Dashboard' => 'Painel de Controle',
'Confirmation' => 'Confirmação',
'Allow everybody to access to this project' => 'Permitir que todos acessem este projeto',
'Everybody have access to this project.' => 'Todos possuem acesso a este projeto.',
- // 'Webhooks' => '',
- // 'API' => '',
+ 'Webhooks' => 'Webhooks',
+ 'API' => 'API',
'Integration' => 'Integração',
- // 'Github webhook' => '',
- 'Help on Github webhook' => 'Ajuda para o Github webhook',
- 'Create a comment from an external provider' => 'Criar um comentário de um provedor externo',
- // 'Github issue comment created' => '',
+ 'Github webhooks' => 'Github webhooks',
+ 'Help on Github webhooks' => 'Ajuda para o Github webhooks',
+ 'Create a comment from an external provider' => 'Criar um comentário por meio de um serviço externo',
+ 'Github issue comment created' => 'Github issue comment created',
'Configure' => 'Configurar',
'Project management' => 'Gerenciamento de projetos',
'My projects' => 'Meus projetos',
'Columns' => 'Colunas',
'Task' => 'Tarefas',
- 'Your are not member of any project.' => 'Você não é menmbro de nenhum projeto.',
+ 'Your are not member of any project.' => 'Você não é membro de nenhum projeto.',
'Percentage' => 'Porcentagem',
'Number of tasks' => 'Número de tarefas',
'Task distribution' => 'Distribuição de tarefas',
'Reportings' => 'Relatórios',
- // 'Task repartition for "%s"' => '',
+ 'Task repartition for "%s"' => 'Redistribuição da tarefa para "%s"',
'Analytics' => 'Estatísticas',
- 'Subtask' => 'Sub-tarefa',
- 'My subtasks' => 'Minhas sub-tarefas',
- 'User repartition' => 'Repartição de usuário',
- 'User repartition for "%s"' => 'Repartição de usuário para "%s"',
- 'Clone this project' => 'Clonar o projeto',
+ 'Subtask' => 'Subtarefa',
+ 'My subtasks' => 'Minhas subtarefas',
+ 'User repartition' => 'Redistribuição de usuário',
+ 'User repartition for "%s"' => 'Redistribuição de usuário para "%s"',
+ 'Clone this project' => 'Clonar este projeto',
'Column removed successfully.' => 'Coluna removida com sucesso.',
'Edit Project' => 'Editar projeto',
- // 'Github Issue' => '',
- 'Not enough data to show the graph.' => 'Dados insuficientes para exibir o gráfico.',
+ 'Github Issue' => 'Github Issue',
+ 'Not enough data to show the graph.' => 'Não há dados suficientes para mostrar o gráfico.',
'Previous' => 'Anterior',
- 'The id must be an integer' => 'A ID deve ser um inteiro',
- 'The project id must be an integer' => 'A ID do projeto deve ser um inteiro',
- 'The status must be an integer' => 'O status deve ser um inteiro',
- 'The subtask id is required' => 'A ID da sub-tarefa é requerida',
- 'The subtask id must be an integer' => 'A ID da sub-tarefa deve ser um inteiro',
- 'The task id is required' => 'A ID da tarefa é requerida',
- 'The task id must be an integer' => 'A ID da tarefa deve ser um inteiro',
- 'The user id must be an integer' => 'A ID de usuário deve ser um inteiro',
- 'This value is required' => 'Este valor é requerido',
+ 'The id must be an integer' => 'O ID deve ser um número inteiro',
+ 'The project id must be an integer' => 'O ID do projeto deve ser um inteiro',
+ 'The status must be an integer' => 'O status deve ser um número inteiro',
+ 'The subtask id is required' => 'O ID da subtarefa é obrigatório',
+ 'The subtask id must be an integer' => 'O ID da subtarefa deve ser um número inteiro',
+ 'The task id is required' => 'O ID da tarefa é obrigatório',
+ 'The task id must be an integer' => 'O ID da tarefa deve ser um número inteiro',
+ 'The user id must be an integer' => 'O ID do usuário deve ser um número inteiro',
+ 'This value is required' => 'Este valor é obrigatório',
'This value must be numeric' => 'Este valor deve ser numérico',
'Unable to create this task.' => 'Não foi possível criar esta tarefa.',
- // 'Cumulative flow diagram' => '',
- // 'Cumulative flow diagram for "%s"' => '',
- // 'Daily project summary' => '',
- // 'Daily project summary export' => '',
- // 'Daily project summary export for "%s"' => '',
- // 'Exports' => '',
- // 'This export contains the number of tasks per column grouped per day.' => '',
+ 'Cumulative flow diagram' => 'Fluxograma cumulativo',
+ 'Cumulative flow diagram for "%s"' => 'Fluxograma cumulativo para "%s"',
+ 'Daily project summary' => 'Resumo diário do projeto',
+ 'Daily project summary export' => 'Exportação diária do resumo do projeto',
+ 'Daily project summary export for "%s"' => 'Exportação diária do resumo do projeto para "%s"',
+ 'Exports' => 'Exportar',
+ 'This export contains the number of tasks per column grouped per day.' => '',
'Nothing to preview...' => 'Nada para pré-visualizar...',
'Preview' => 'Pré-visualizar',
- // 'Write' => '',
+ 'Write' => 'Escrever',
+ 'Active swimlanes' => 'Ativar swimlanes',
+ 'Add a new swimlane' => 'Adicionar novo swimlane',
+ 'Change default swimlane' => 'Alterar swimlane padrão',
+ 'Default swimlane' => 'Swimlane padrão',
+ 'Do you really want to remove this swimlane: "%s"?' => 'Você realmente deseja remover este swimlane: "%s"?',
+ 'Inactive swimlanes' => 'Desativar swimlanes',
+ 'Set project manager' => 'Definir gerente do projeto',
+ 'Set project member' => 'Definir membro do projeto',
+ 'Remove a swimlane' => 'Remover um swimlane',
+ 'Rename' => 'Renomear',
+ 'Show default swimlane' => 'Exibir swimlane padrão',
+ 'Swimlane modification for the project "%s"' => 'Modificação de swimlane para o projeto "%s"',
+ 'Swimlane not found.' => 'Swimlane não encontrado.',
+ 'Swimlane removed successfully.' => 'Swimlane removido com sucesso.',
+ 'Swimlanes' => 'Swimlanes',
+ 'Swimlane updated successfully.' => 'Swimlane atualizado com sucesso.',
+ 'The default swimlane have been updated successfully.' => 'O swimlane padrão foi atualizado com sucesso.',
+ 'Unable to create your swimlane.' => 'Não foi possível criar o seu swimlane.',
+ 'Unable to remove this swimlane.' => 'Não foi possível remover este swimlane.',
+ 'Unable to update this swimlane.' => 'Não foi possível atualizar este swimlane.',
+ 'Your swimlane have been created successfully.' => 'Seu swimlane foi criado com sucesso.',
+ 'Example: "Bug, Feature Request, Improvement"' => 'Exemplo: "Bug, Feature Request, Improvement"',
+ 'Default categories for new projects (Comma-separated)' => 'Categorias padrão para novos projetos (Separadas por vírgula)',
+ 'Gitlab commit received' => 'Gitlab commit received',
+ 'Gitlab issue opened' => 'Gitlab issue opened',
+ 'Gitlab issue closed' => 'Gitlab issue closed',
+ 'Gitlab webhooks' => 'Gitlab webhooks',
+ 'Help on Gitlab webhooks' => 'Ajuda sobre Gitlab webhooks',
+ 'Integrations' => 'Integrações',
+ 'Integration with third-party services' => 'Integração com serviços de terceiros',
+ 'Role for this project' => 'Função para este projeto',
+ 'Project manager' => 'Gerente do projeto',
+ 'Project member' => 'Membro do projeto',
+ 'A project manager can change the settings of the project and have more privileges than a standard user.' => 'Um gerente do projeto pode alterar as configurações do projeto e ter mais privilégios que um usuário padrão.',
+ 'Gitlab Issue' => 'Gitlab Issue',
+ 'Subtask Id' => 'ID da subtarefa',
+ 'Subtasks' => 'Subtarefas',
+ 'Subtasks Export' => 'Exportar subtarefas',
+ 'Subtasks exportation for "%s"' => 'Subtarefas exportadas para "%s"',
+ 'Task Title' => 'Título da Tarefa',
+ 'Untitled' => 'Sem título',
+ 'Application default' => 'Aplicação padrão',
+ 'Language:' => 'Idioma',
+ 'Timezone:' => 'Fuso horário',
+ 'Next' => 'Próximo',
);
diff --git a/sources/app/Locale/ru_RU/translations.php b/sources/app/Locale/ru_RU/translations.php
index a1cc7cf..a857de3 100644
--- a/sources/app/Locale/ru_RU/translations.php
+++ b/sources/app/Locale/ru_RU/translations.php
@@ -182,7 +182,7 @@ return array(
'Change assignee' => 'Сменить назначенного',
'Change assignee for the task "%s"' => 'Сменить назначенного для задачи « %s »',
'Timezone' => 'Часовой пояс',
- 'Sorry, I didn\'t found this information in my database!' => 'К сожалению, информация в базе данных не найдена !',
+ 'Sorry, I didn\'t find this information in my database!' => 'К сожалению, информация в базе данных не найдена !',
'Page not found' => 'Страница не найдена',
'Complexity' => 'Сложность',
'limit' => 'лимит',
@@ -194,7 +194,7 @@ return array(
'Allow this user' => 'Разрешить этого пользователя',
'Only those users have access to this project:' => 'Только эти пользователи имеют доступ к проекту :',
'Don\'t forget that administrators have access to everything.' => 'Помните, администратор имеет доступ ко всему.',
- 'revoke' => 'отозвать',
+ 'Revoke' => 'отозвать',
'List of authorized users' => 'Список авторизованных пользователей',
'User' => 'Пользователь',
'Nobody have access to this project.' => 'Ни у кого нет доступа к этому проекту',
@@ -213,6 +213,7 @@ return array(
'Invalid date' => 'Неверная дата',
'Must be done before %B %e, %Y' => 'Должно быть сделано до %d/%m/%Y',
'%B %e, %Y' => '%d/%m/%Y',
+ // '%b %e, %Y' => '',
'Automatic actions' => 'Автоматические действия',
'Your automatic action have been created successfully.' => 'Автоматика настроена.',
'Unable to create your automatic action.' => 'Не удалось создать автоматизированное действие.',
@@ -468,18 +469,18 @@ return array(
'Unable to change the password.' => 'Не удалось сменить пароль.',
'Change category for the task "%s"' => 'Сменить категорию для задачи "%s"',
'Change category' => 'Смена категории',
- '%s updated the task #%d' => '%s обновил задачу #%d',
- '%s open the task #%d' => '%s открыл задачу #%d',
- '%s moved the task #%d to the position #%d in the column "%s"' => '%s перместил задачу #%d на позицию #%d в колонке "%s"',
- '%s moved the task #%d to the column "%s"' => '%s переместил задачу #%d в колонку "%s"',
- '%s created the task #%d' => '%s создал задачу #%d',
- '%s closed the task #%d' => '%s закрыл задачу #%d',
- '%s created a subtask for the task #%d' => '%s создал подзадачу для задачи #%d',
- '%s updated a subtask for the task #%d' => '%s обновил подзадачу для задачи #%d',
+ '%s updated the task %s' => '%s обновил задачу %s',
+ '%s opened the task %s' => '%s открыл задачу %s',
+ '%s moved the task %s to the position #%d in the column "%s"' => '%s перместил задачу %s на позицию #%d в колонке "%s"',
+ '%s moved the task %s to the column "%s"' => '%s переместил задачу %s в колонку "%s"',
+ '%s created the task %s' => '%s создал задачу %s',
+ '%s closed the task %s' => '%s закрыл задачу %s',
+ '%s created a subtask for the task %s' => '%s создал подзадачу для задачи %s',
+ '%s updated a subtask for the task %s' => '%s обновил подзадачу для задачи %s',
'Assigned to %s with an estimate of %s/%sh' => 'Назначено %s с окончанием %s/%sh',
'Not assigned, estimate of %sh' => 'Не назначено, окончание %sh',
- '%s updated a comment on the task #%d' => '%s обновил комментарий к задаче #%d',
- '%s commented the task #%d' => '%s прокомментировал задачу #%d',
+ '%s updated a comment on the task %s' => '%s обновил комментарий к задаче %s',
+ '%s commented the task %s' => '%s прокомментировал задачу %s',
'%s\'s activity' => '%s активность',
'No activity.' => 'Нет активности',
'RSS feed' => 'RSS лента',
@@ -498,7 +499,7 @@ return array(
'Default columns for new projects (Comma-separated)' => 'Колонки по умолчанию для новых проектов (разделять запятой)',
'Task assignee change' => 'Изменен назначенный',
'%s change the assignee of the task #%d to %s' => '%s сменил назначенного для задачи #%d на %s',
- '%s change the assignee of the task #%d to %s' => '%s сменил назначенного для задачи #%d на %s',
+ '%s changed the assignee of the task %s to %s' => '%s сменил назначенного для задачи %s на %s',
'[%s][Column Change] %s (#%d)' => '[%s][Изменение колонки] %s (#%d)',
'[%s][Position Change] %s (#%d)' => '[%s][Изменение позиции] %s (#%d)',
'[%s][Assignee Change] %s (#%d)' => '[%s][Изменение назначеного] %s (#%d)',
@@ -555,8 +556,8 @@ return array(
// 'Webhooks' => '',
// 'API' => '',
// 'Integration' => '',
- // 'Github webhook' => '',
- // 'Help on Github webhook' => '',
+ // 'Github webhooks' => '',
+ // 'Help on Github webhooks' => '',
// 'Create a comment from an external provider' => '',
// 'Github issue comment created' => '',
// 'Configure' => '',
@@ -602,4 +603,49 @@ return array(
// 'Nothing to preview...' => '',
// 'Preview' => '',
// 'Write' => '',
+ // 'Active swimlanes' => '',
+ // 'Add a new swimlane' => '',
+ // 'Change default swimlane' => '',
+ // 'Default swimlane' => '',
+ // 'Do you really want to remove this swimlane: "%s"?' => '',
+ // 'Inactive swimlanes' => '',
+ // 'Set project manager' => '',
+ // 'Set project member' => '',
+ // 'Remove a swimlane' => '',
+ // 'Rename' => '',
+ // 'Show default swimlane' => '',
+ // 'Swimlane modification for the project "%s"' => '',
+ // 'Swimlane not found.' => '',
+ // 'Swimlane removed successfully.' => '',
+ // 'Swimlanes' => '',
+ // 'Swimlane updated successfully.' => '',
+ // 'The default swimlane have been updated successfully.' => '',
+ // 'Unable to create your swimlane.' => '',
+ // 'Unable to remove this swimlane.' => '',
+ // 'Unable to update this swimlane.' => '',
+ // 'Your swimlane have been created successfully.' => '',
+ // 'Example: "Bug, Feature Request, Improvement"' => '',
+ // 'Default categories for new projects (Comma-separated)' => '',
+ // 'Gitlab commit received' => '',
+ // 'Gitlab issue opened' => '',
+ // 'Gitlab issue closed' => '',
+ // 'Gitlab webhooks' => '',
+ // 'Help on Gitlab webhooks' => '',
+ // 'Integrations' => '',
+ // 'Integration with third-party services' => '',
+ // 'Role for this project' => '',
+ // 'Project manager' => '',
+ // 'Project member' => '',
+ // 'A project manager can change the settings of the project and have more privileges than a standard user.' => '',
+ // 'Gitlab Issue' => '',
+ // 'Subtask Id' => '',
+ // 'Subtasks' => '',
+ // 'Subtasks Export' => '',
+ // 'Subtasks exportation for "%s"' => '',
+ // 'Task Title' => '',
+ // 'Untitled' => '',
+ // 'Application default' => '',
+ // 'Language:' => '',
+ // 'Timezone:' => '',
+ // 'Next' => '',
);
diff --git a/sources/app/Locale/sv_SE/translations.php b/sources/app/Locale/sv_SE/translations.php
index ece0b2a..ae805d4 100644
--- a/sources/app/Locale/sv_SE/translations.php
+++ b/sources/app/Locale/sv_SE/translations.php
@@ -104,7 +104,7 @@ return array(
'Open a task' => 'Öppna en uppgift',
'Do you really want to open this task: "%s"?' => 'Vill du verkligen öppna denna uppgift: "%s"?',
'Back to the board' => 'Tillbaka till tavlan',
- 'Created on %B %e, %Y at %k:%M %p' => 'Skapad %d %B %Y kl %H:%M',
+ 'Created on %B %e, %Y at %k:%M %p' => 'Skapad %Y-%m-%d kl %H:%M',
'There is nobody assigned' => 'Det finns ingen tilldelad',
'Column on the board:' => 'Kolumn på tavlan:',
'Status is open' => 'Statusen är öppen',
@@ -166,8 +166,8 @@ return array(
'Work in progress' => 'Pågående',
'Done' => 'Slutfört',
'Application version:' => 'Version:',
- 'Completed on %B %e, %Y at %k:%M %p' => 'Slutfört %d %B %Y kl %H:%M',
- '%B %e, %Y at %k:%M %p' => '%d %B %Y kl %H:%M',
+ 'Completed on %B %e, %Y at %k:%M %p' => 'Slutfört %Y-%m-%d kl %H:%M',
+ '%B %e, %Y at %k:%M %p' => '%Y-%m-%d kl %H:%M',
'Date created' => 'Skapat datum',
'Date completed' => 'Slutfört datum',
'Id' => 'ID',
@@ -182,19 +182,19 @@ return array(
'Change assignee' => 'Ändra uppdragsinnehavare',
'Change assignee for the task "%s"' => 'Ändra uppdragsinnehavare för uppgiften "%s"',
'Timezone' => 'Tidszon',
- 'Sorry, I didn\'t found this information in my database!' => 'Informationen kunde inte hittas i databasen.',
+ 'Sorry, I didn\'t find this information in my database!' => 'Informationen kunde inte hittas i databasen.',
'Page not found' => 'Sidan hittas inte',
- 'Complexity' => 'Ungefärligt antal timmar',
+ 'Complexity' => 'Komplexitet',
'limit' => 'max',
'Task limit' => 'Uppgiftsbegränsning',
- // 'Task count' => '',
+ 'Task count' => 'Antal uppgifter',
'This value must be greater than %d' => 'Värdet måste vara större än %d',
'Edit project access list' => 'Ändra projektåtkomst lista',
'Edit users access' => 'Användaråtkomst',
'Allow this user' => 'Tillåt användare',
'Only those users have access to this project:' => 'Bara de användarna har tillgång till detta projekt.',
'Don\'t forget that administrators have access to everything.' => 'Glöm inte att administratörerna har rätt att göra allt.',
- 'revoke' => 'Dra tillbaka behörighet',
+ 'Revoke' => 'Dra tillbaka behörighet',
'List of authorized users' => 'Lista med behöriga användare',
'User' => 'Användare',
'Nobody have access to this project.' => 'Ingen har tillgång till detta projekt.',
@@ -211,8 +211,9 @@ return array(
'Edit this task' => 'Ändra denna uppgift',
'Due Date' => 'Måldatum',
'Invalid date' => 'Ej tillåtet datum',
- 'Must be done before %B %e, %Y' => 'Måste vara klart innan %B %e, %Y',
- '%B %e, %Y' => '%d %B %Y',
+ 'Must be done before %B %e, %Y' => 'Måste vara klart innan %Y-%m-%d',
+ '%B %e, %Y' => '%Y-%m-%d',
+ '%b %e, %Y' => '%Y-%m-%d',
'Automatic actions' => 'Automatiska åtgärder',
'Your automatic action have been created successfully.' => 'Din automatiska åtgärd har skapats.',
'Unable to create your automatic action.' => 'Kunde inte skapa din automatiska åtgärd.',
@@ -376,7 +377,7 @@ return array(
'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 %B %e, %Y kl %k:%M %p',
+ 'Last modified on %B %e, %Y at %k:%M %p' => 'Senaste ändring %Y-%m-%d kl %H:%M',
'Tasks Export' => 'Exportera uppgifter',
'Tasks exportation for "%s"' => 'Exportera uppgifter för "%s"',
'Start Date' => 'Startdatum',
@@ -468,18 +469,18 @@ return array(
'Unable to change the password.' => 'Kunde inte byta lösenord.',
'Change category for the task "%s"' => 'Byt kategori för uppgiften "%s"',
'Change category' => 'Byt kategori',
- '%s updated the task #%d' => '%s uppdaterade uppgiften #%d',
- '%s open the task #%d' => '%s öppna uppgiften #%d',
- '%s moved the task #%d to the position #%d in the column "%s"' => '%s flyttade uppgiften #%d till positionen #%d i kolumnen "%s"',
- '%s moved the task #%d to the column "%s"' => '%s flyttade uppgiften #%d till kolumnen "%s"',
- '%s created the task #%d' => '%s skapade uppgiften #%d',
- '%s closed the task #%d' => '%s stängde uppgiften #%d',
- '%s created a subtask for the task #%d' => '%s skapade en deluppgift för uppgiften #%d',
- '%s updated a subtask for the task #%d' => '%s uppdaterade en deluppgift för uppgiften #%d',
+ '%s updated the task %s' => '%s uppdaterade uppgiften %s',
+ '%s opened the task %s' => '%s öppna uppgiften %s',
+ '%s moved the task %s to the position #%d in the column "%s"' => '%s flyttade uppgiften %s till positionen #%d i kolumnen "%s"',
+ '%s moved the task %s to the column "%s"' => '%s flyttade uppgiften %s till kolumnen "%s"',
+ '%s created the task %s' => '%s skapade uppgiften %s',
+ '%s closed the task %s' => '%s stängde uppgiften %s',
+ '%s created a subtask for the task %s' => '%s skapade en deluppgift för uppgiften %s',
+ '%s updated a subtask for the task %s' => '%s uppdaterade en deluppgift för uppgiften %s',
'Assigned to %s with an estimate of %s/%sh' => 'Tilldelades %s med en uppskattning på %s/%sh',
'Not assigned, estimate of %sh' => 'Inte tilldelade, uppskattat %sh',
- '%s updated a comment on the task #%d' => '%s uppdaterade en kommentar till uppgiften #%d',
- '%s commented the task #%d' => '%s kommenterade uppgiften #%d',
+ '%s updated a comment on the task %s' => '%s uppdaterade en kommentar till uppgiften %s',
+ '%s commented the task %s' => '%s kommenterade uppgiften %s',
'%s\'s activity' => '%s\'s aktivitet',
'No activity.' => 'Ingen aktivitet.',
'RSS feed' => 'RSS flöde',
@@ -498,7 +499,7 @@ return array(
'Default columns for new projects (Comma-separated)' => 'Standardkolumner för nya projekt (kommaseparerade)',
'Task assignee change' => 'Ändra tilldelning av uppgiften',
'%s change the assignee of the task #%d to %s' => '%s byt tilldelning av uppgiften #%d till %s',
- '%s change the assignee of the task #%d to %s' => '%s byt tilldelning av uppgiften #%d till %s',
+ '%s changed the assignee of the task %s to %s' => '%s byt tilldelning av uppgiften %s till %s',
'[%s][Column Change] %s (#%d)' => '[%s][Byt kolumn] %s (#%d)',
'[%s][Position Change] %s (#%d)' => '[%s][Byt position] %s (#%d)',
'[%s][Assignee Change] %s (#%d)' => '[%s][Byt tilldelning] %s (#%d)',
@@ -542,64 +543,109 @@ return array(
'Add' => 'Lägg till',
'Estimated time: %s hours' => 'Uppskattad tid: %s timmar',
'Time spent: %s hours' => 'Nedlaggd tid: %s timmar',
- 'Started on %B %e, %Y' => 'Startad den %B %e, %Y',
+ 'Started on %B %e, %Y' => 'Startad %Y-%m-%d',
'Start date' => 'Startdatum',
'Time estimated' => 'Uppskattad tid',
'There is nothing assigned to you.' => 'Du har inget tilldelat till dig.',
- 'My tasks' => 'Mina uppgifte',
+ 'My tasks' => 'Mina uppgifter',
'Activity stream' => 'Aktivitetsström',
'Dashboard' => 'Instrumentpanel',
'Confirmation' => 'Bekräftelse',
- // 'Allow everybody to access to this project' => '',
- // 'Everybody have access to this project.' => '',
- // 'Webhooks' => '',
- // 'API' => '',
- // 'Integration' => '',
- // 'Github webhook' => '',
- // 'Help on Github webhook' => '',
- // 'Create a comment from an external provider' => '',
- // 'Github issue comment created' => '',
- // 'Configure' => '',
- // 'Project management' => '',
- // 'My projects' => '',
- // 'Columns' => '',
- // 'Task' => '',
- // 'Your are not member of any project.' => '',
- // 'Percentage' => '',
- // 'Number of tasks' => '',
- // 'Task distribution' => '',
- // 'Reportings' => '',
- // 'Task repartition for "%s"' => '',
- // 'Analytics' => '',
- // 'Subtask' => '',
- // 'My subtasks' => '',
- // 'User repartition' => '',
- // 'User repartition for "%s"' => '',
- // 'Clone this project' => '',
- // 'Column removed successfully.' => '',
- // 'Edit Project' => '',
- // 'Github Issue' => '',
- // 'Not enough data to show the graph.' => '',
- // 'Previous' => '',
- // 'The id must be an integer' => '',
- // 'The project id must be an integer' => '',
- // 'The status must be an integer' => '',
- // 'The subtask id is required' => '',
- // 'The subtask id must be an integer' => '',
- // 'The task id is required' => '',
- // 'The task id must be an integer' => '',
- // 'The user id must be an integer' => '',
- // 'This value is required' => '',
- // 'This value must be numeric' => '',
- // 'Unable to create this task.' => '',
- // 'Cumulative flow diagram' => '',
- // 'Cumulative flow diagram for "%s"' => '',
- // 'Daily project summary' => '',
- // 'Daily project summary export' => '',
- // 'Daily project summary export for "%s"' => '',
- // 'Exports' => '',
- // 'This export contains the number of tasks per column grouped per day.' => '',
- // 'Nothing to preview...' => '',
- // 'Preview' => '',
- // 'Write' => '',
+ 'Allow everybody to access to this project' => 'Ge alla tillgång till projektet',
+ 'Everybody have access to this project.' => 'Alla har tillgång till projektet',
+ 'Webhooks' => 'Webhooks',
+ 'API' => 'API',
+ 'Integration' => 'Integration',
+ 'Github webhooks' => 'Github webhooks',
+ 'Help on Github webhooks' => 'Hjälp för Github webhooks',
+ 'Create a comment from an external provider' => 'Skapa en kommentar från en extern leverantör',
+ 'Github issue comment created' => 'Github frågekommentar skapad',
+ 'Configure' => 'Konfigurera',
+ 'Project management' => 'Projekthantering',
+ 'My projects' => 'Mina projekt',
+ 'Columns' => 'Kolumner',
+ 'Task' => 'Uppgift',
+ 'Your are not member of any project.' => 'Du är inte medlem i något projekt',
+ 'Percentage' => 'Procent',
+ 'Number of tasks' => 'Antal uppgifter',
+ 'Task distribution' => 'Uppgiftsfördelning',
+ 'Reportings' => 'Rapportering',
+ 'Task repartition for "%s"' => 'Uppgiftsdeltagande för "%s"',
+ 'Analytics' => 'Analyser',
+ 'Subtask' => 'Deluppgift',
+ 'My subtasks' => 'Mina deluppgifter',
+ 'User repartition' => 'Användardeltagande',
+ 'User repartition for "%s"' => 'Användardeltagande för "%s"',
+ 'Clone this project' => 'Klona projektet',
+ 'Column removed successfully.' => 'Kolumnen togs bort',
+ 'Edit Project' => 'Ändra Projekt',
+ 'Github Issue' => 'Github fråga',
+ 'Not enough data to show the graph.' => 'Inte tillräckligt med data för att visa graf',
+ 'Previous' => 'Föregående',
+ 'The id must be an integer' => 'ID måste vara ett heltal',
+ 'The project id must be an integer' => 'Projekt-ID måste vara ett heltal',
+ 'The status must be an integer' => 'Status måste vara ett heltal',
+ 'The subtask id is required' => 'Deluppgifts-ID behövs',
+ 'The subtask id must be an integer' => 'Deluppgifts-ID måste vara ett heltal',
+ 'The task id is required' => 'Uppgifts-ID behövs',
+ 'The task id must be an integer' => 'Uppgifts-ID måste vara ett heltal',
+ 'The user id must be an integer' => 'Användar-ID måste vara ett heltal',
+ 'This value is required' => 'Värdet behövs',
+ 'This value must be numeric' => 'Värdet måste vara numeriskt',
+ 'Unable to create this task.' => 'Kunde inte skapa uppgiften.',
+ 'Cumulative flow diagram' => 'Diagram med kumulativt flöde',
+ 'Cumulative flow diagram for "%s"' => 'Diagram med kumulativt flöde för "%s"',
+ 'Daily project summary' => 'Daglig projektsummering',
+ 'Daily project summary export' => 'Export av daglig projektsummering',
+ 'Daily project summary export for "%s"' => 'Export av daglig projektsummering för "%s"',
+ 'Exports' => 'Exporter',
+ 'This export contains the number of tasks per column grouped per day.' => 'Denna export innehåller antalet uppgifter per kolumn grupperade per dag.',
+ 'Nothing to preview...' => 'Inget att förhandsgrandska...',
+ 'Preview' => 'Förhandsgranska',
+ 'Write' => 'Skriva',
+ 'Active swimlanes' => 'Aktiva swimlanes',
+ 'Add a new swimlane' => 'Lägg till en nytt swimlane',
+ 'Change default swimlane' => 'Ändra standard swimlane',
+ 'Default swimlane' => 'Standard swimlane',
+ 'Do you really want to remove this swimlane: "%s"?' => 'Vill du verkligen ta bort denna swimlane: "%s"?',
+ 'Inactive swimlanes' => 'Inaktiv swimlane',
+ 'Set project manager' => 'Sätt Projektadministratör',
+ 'Set project member' => 'Sätt projektmedlem',
+ 'Remove a swimlane' => 'Ta bort en swimlane',
+ 'Rename' => 'Byt namn',
+ 'Show default swimlane' => 'Visa standard swimlane',
+ 'Swimlane modification for the project "%s"' => 'Ändra swimlane för projektet "%s"',
+ 'Swimlane not found.' => 'Swimlane kunde inte hittas',
+ 'Swimlane removed successfully.' => 'Swimlane togs bort',
+ 'Swimlanes' => 'Swimlanes',
+ 'Swimlane updated successfully.' => 'Swimlane uppdaterad',
+ 'The default swimlane have been updated successfully.' => 'Standardswimlane har uppdaterats',
+ 'Unable to create your swimlane.' => 'Kunde inte skapa din swimlane',
+ 'Unable to remove this swimlane.' => 'Kunde inte ta bort swimlane',
+ 'Unable to update this swimlane.' => 'Kunde inte uppdatera swimlane',
+ 'Your swimlane have been created successfully.' => 'Din swimlane har skapats',
+ 'Example: "Bug, Feature Request, Improvement"' => 'Exempel: "Bug, ny funktionalitet, förbättringar"',
+ 'Default categories for new projects (Comma-separated)' => 'Standardkategorier för nya projekt (komma-separerade)',
+ 'Gitlab commit received' => 'Gitlab bidrag mottaget',
+ 'Gitlab issue opened' => 'Gitlab fråga öppnad',
+ 'Gitlab issue closed' => 'Gitlab fråga stängd',
+ 'Gitlab webhooks' => 'Gitlab webhooks',
+ 'Help on Gitlab webhooks' => 'Hjälp för Gitlab webhooks',
+ 'Integrations' => 'Integrationer',
+ 'Integration with third-party services' => 'Integration med tjänst från tredjepart',
+ 'Role for this project' => 'Roll för detta projekt',
+ 'Project manager' => 'Projektadministratör',
+ 'Project member' => 'Projektmedlem',
+ 'A project manager can change the settings of the project and have more privileges than a standard user.' => 'En projektadministratör kan ändra inställningar för projektet och har mer rättigheter än en standardanvändare.',
+ 'Gitlab Issue' => 'Gitlab fråga',
+ 'Subtask Id' => 'Deluppgifts-ID',
+ 'Subtasks' => 'Deluppgift',
+ 'Subtasks Export' => 'Export av deluppgifter',
+ 'Subtasks exportation for "%s"' => 'Export av deluppgifter för "%s"',
+ 'Task Title' => 'Uppgiftstitel',
+ 'Untitled' => 'Titel saknas',
+ 'Application default' => 'Applikationsstandard',
+ 'Language:' => 'Språk',
+ 'Timezone:' => 'Tidszon',
+ 'Next' => 'Nästa',
);
diff --git a/sources/app/Locale/th_TH/translations.php b/sources/app/Locale/th_TH/translations.php
index 42a1bcb..f091020 100644
--- a/sources/app/Locale/th_TH/translations.php
+++ b/sources/app/Locale/th_TH/translations.php
@@ -182,7 +182,7 @@ return array(
'Change assignee' => 'เปลี่ยนการกำหนด',
'Change assignee for the task "%s"' => 'เปลี่ยนการกำหนดสำหรับงาน « %s »',
'Timezone' => 'เขตเวลา',
- 'Sorry, I didn\'t found this information in my database!' => 'เสียใจด้วย ไม่สามารถหาข้อมูลในฐานข้อมูลได้',
+ 'Sorry, I didn\'t find this information in my database!' => 'เสียใจด้วย ไม่สามารถหาข้อมูลในฐานข้อมูลได้',
'Page not found' => 'ไม่พบหน้า',
'Complexity' => 'ความซับซ้อน',
'limit' => 'จำกัด',
@@ -194,7 +194,7 @@ return array(
'Allow this user' => 'อนุญาตผู้ใช้นี้',
'Only those users have access to this project:' => 'ผู้ใช้ที่สามารถเข้าถึงโปรเจคนี้:',
'Don\'t forget that administrators have access to everything.' => 'อย่าลืมผู้ดูแลระบบสามารถเข้าถึงได้ทุกอย่าง',
- 'revoke' => 'ยกเลิก',
+ 'Revoke' => 'ยกเลิก',
'List of authorized users' => 'รายชื่อผู้ใช้ที่ได้รับการยืนยัน',
'User' => 'ผู้ใช้',
// 'Nobody have access to this project.' => '',
@@ -213,6 +213,7 @@ return array(
'Invalid date' => 'วันที่ผิด',
'Must be done before %B %e, %Y' => 'ต้องทำให้เสร็จก่อน %d/%m/%Y',
'%B %e, %Y' => '%d/%m/%Y',
+ // '%b %e, %Y' => '',
'Automatic actions' => 'การกระทำอัตโนมัติ',
'Your automatic action have been created successfully.' => 'การกระทำอัตโนมัติสร้างเรียบร้อยแล้ว',
'Unable to create your automatic action.' => 'ไม่สามารถสร้างการกระทำอัตโนมัติได้',
@@ -468,18 +469,18 @@ return array(
'Unable to change the password.' => 'ไม่สามารถเปลี่ยนรหัสผ่านได้',
'Change category for the task "%s"' => 'เปลี่ยนกลุ่มสำหรับงาน "%s"',
'Change category' => 'เปลี่ยนกลุ่ม',
- '%s updated the task #%d' => '%s ปรับปรุงงานแล้ว #%d',
- '%s open the task #%d' => '%s เปิดงานแล้ว #%d',
- '%s moved the task #%d to the position #%d in the column "%s"' => '%s ย้ายงานแล้ว #%d ไปตำแหน่ง #%d ในคอลัมน์ "%s"',
- '%s moved the task #%d to the column "%s"' => '%s ย้ายงานแล้ว #%d ไปคอลัมน์ "%s"',
- '%s created the task #%d' => '%s สร้างงานแล้ว #%d',
- '%s closed the task #%d' => '%s ปิดงานแล้ว #%d',
- '%s created a subtask for the task #%d' => '%s สร้างงานย่อยสำหรับงานแล้ว #%d',
- '%s updated a subtask for the task #%d' => '%s ปรับปรุงงานย่อยสำหรับงานแล้ว #%d',
+ '%s updated the task %s' => '%s ปรับปรุงงานแล้ว %s',
+ '%s opened the task %s' => '%s เปิดงานแล้ว %s',
+ '%s moved the task %s to the position #%d in the column "%s"' => '%s ย้ายงานแล้ว %s ไปตำแหน่ง #%d ในคอลัมน์ "%s"',
+ '%s moved the task %s to the column "%s"' => '%s ย้ายงานแล้ว %s ไปคอลัมน์ "%s"',
+ '%s created the task %s' => '%s สร้างงานแล้ว %s',
+ '%s closed the task %s' => '%s ปิดงานแล้ว %s',
+ '%s created a subtask for the task %s' => '%s สร้างงานย่อยสำหรับงานแล้ว %s',
+ '%s updated a subtask for the task %s' => '%s ปรับปรุงงานย่อยสำหรับงานแล้ว %s',
'Assigned to %s with an estimate of %s/%sh' => 'กำหนดให้ %s โดยประมาณแล้ว %s/%sh',
'Not assigned, estimate of %sh' => 'ไม่กำหนดแล้ว, ประมาณเวลาที่ใช้ %s ชั่วโมง',
- '%s updated a comment on the task #%d' => '%s ปรับปรุงความคิดเห็นในงานแล้ว #%d',
- '%s commented the task #%d' => '%s แสดงความคิดเห็นของงานแล้ว #%d',
+ '%s updated a comment on the task %s' => '%s ปรับปรุงความคิดเห็นในงานแล้ว %s',
+ '%s commented the task %s' => '%s แสดงความคิดเห็นของงานแล้ว %s',
'%s\'s activity' => 'กิจกรรม %s',
'No activity.' => 'ไม่มีกิจกรรม',
'RSS feed' => 'RSS feed',
@@ -498,7 +499,7 @@ return array(
'Default columns for new projects (Comma-separated)' => 'คอลัมน์เริ่มต้นสำหรับโปรเจคใหม่ (Comma-separated)',
'Task assignee change' => 'เปลี่ยนการกำหนดบุคคลของงาน',
// '%s change the assignee of the task #%d to %s' => '',
- // '%s change the assignee of the task #%d to %s' => '',
+ // '%s changed the assignee of the task %s to %s' => '',
// '[%s][Column Change] %s (#%d)' => '',
// '[%s][Position Change] %s (#%d)' => '',
// '[%s][Assignee Change] %s (#%d)' => '',
@@ -555,8 +556,8 @@ return array(
// 'Webhooks' => '',
// 'API' => '',
// 'Integration' => '',
- // 'Github webhook' => '',
- // 'Help on Github webhook' => '',
+ // 'Github webhooks' => '',
+ // 'Help on Github webhooks' => '',
// 'Create a comment from an external provider' => '',
// 'Github issue comment created' => '',
// 'Configure' => '',
@@ -602,4 +603,49 @@ return array(
// 'Nothing to preview...' => '',
// 'Preview' => '',
// 'Write' => '',
+ // 'Active swimlanes' => '',
+ // 'Add a new swimlane' => '',
+ // 'Change default swimlane' => '',
+ // 'Default swimlane' => '',
+ // 'Do you really want to remove this swimlane: "%s"?' => '',
+ // 'Inactive swimlanes' => '',
+ // 'Set project manager' => '',
+ // 'Set project member' => '',
+ // 'Remove a swimlane' => '',
+ // 'Rename' => '',
+ // 'Show default swimlane' => '',
+ // 'Swimlane modification for the project "%s"' => '',
+ // 'Swimlane not found.' => '',
+ // 'Swimlane removed successfully.' => '',
+ // 'Swimlanes' => '',
+ // 'Swimlane updated successfully.' => '',
+ // 'The default swimlane have been updated successfully.' => '',
+ // 'Unable to create your swimlane.' => '',
+ // 'Unable to remove this swimlane.' => '',
+ // 'Unable to update this swimlane.' => '',
+ // 'Your swimlane have been created successfully.' => '',
+ // 'Example: "Bug, Feature Request, Improvement"' => '',
+ // 'Default categories for new projects (Comma-separated)' => '',
+ // 'Gitlab commit received' => '',
+ // 'Gitlab issue opened' => '',
+ // 'Gitlab issue closed' => '',
+ // 'Gitlab webhooks' => '',
+ // 'Help on Gitlab webhooks' => '',
+ // 'Integrations' => '',
+ // 'Integration with third-party services' => '',
+ // 'Role for this project' => '',
+ // 'Project manager' => '',
+ // 'Project member' => '',
+ // 'A project manager can change the settings of the project and have more privileges than a standard user.' => '',
+ // 'Gitlab Issue' => '',
+ // 'Subtask Id' => '',
+ // 'Subtasks' => '',
+ // 'Subtasks Export' => '',
+ // 'Subtasks exportation for "%s"' => '',
+ // 'Task Title' => '',
+ // 'Untitled' => '',
+ // 'Application default' => '',
+ // 'Language:' => '',
+ // 'Timezone:' => '',
+ // 'Next' => '',
);
diff --git a/sources/app/Locale/zh_CN/translations.php b/sources/app/Locale/zh_CN/translations.php
index d146459..cd423d0 100644
--- a/sources/app/Locale/zh_CN/translations.php
+++ b/sources/app/Locale/zh_CN/translations.php
@@ -24,7 +24,7 @@ return array(
'Unassigned' => '未指定',
'View this task' => '查看该任务',
'Remove user' => '移除用户',
- 'Do you really want to remove this user: "%s"?' => '你确定要移除这个用户吗:"%s"?',
+ 'Do you really want to remove this user: "%s"?' => '确定要删除用户"%s"吗?',
'New user' => '新建用户',
'All users' => '所有用户',
'Username' => '用户名',
@@ -63,7 +63,7 @@ return array(
'Disable' => '停用',
'Enable' => '启用',
'New project' => '新建项目',
- 'Do you really want to remove this project: "%s"?' => '你确定要移除该项目吗:"%s"?',
+ 'Do you really want to remove this project: "%s"?' => '确定要移除项目"%s"吗?',
'Remove project' => '移除项目',
'Boards' => '看板',
'Edit the board for "%s"' => '为"%s"修改看板',
@@ -78,7 +78,7 @@ return array(
'Remove a column' => '移除一个栏目',
'Remove a column from a board' => '从看板移除一个栏目',
'Unable to remove this column.' => '无法移除该栏目。',
- 'Do you really want to remove this column: "%s"?' => '你确定要移除该栏目:"%s"吗?',
+ 'Do you really want to remove this column: "%s"?' => '确定要移除栏目"%s"吗?',
'This action will REMOVE ALL TASKS associated to this column!' => '该动作将移除与该栏目相关的所有项目!',
'Settings' => '设置',
'Application settings' => '应用设置',
@@ -182,19 +182,19 @@ return array(
'Change assignee' => '变更负责人',
'Change assignee for the task "%s"' => '更改任务"%s"的负责人',
'Timezone' => '时区',
- 'Sorry, I didn\'t found this information in my database!' => '抱歉,无法在数据库中找到该信息!',
+ 'Sorry, I didn\'t find this information in my database!' => '抱歉,无法在数据库中找到该信息!',
'Page not found' => '页面未找到',
'Complexity' => '复杂度',
'limit' => '限制',
'Task limit' => '任务限制',
- // 'Task count' => '',
+ 'Task count' => '任务数',
'This value must be greater than %d' => '该数值必须大于%d',
'Edit project access list' => '编辑项目存取列表',
'Edit users access' => '编辑用户存取权限',
'Allow this user' => '允许该用户',
'Only those users have access to this project:' => '只有这些用户有该项目的存取权限:',
'Don\'t forget that administrators have access to everything.' => '别忘了管理员有一切的权限。',
- 'revoke' => '撤销',
+ 'Revoke' => '撤销',
'List of authorized users' => '已授权的用户列表',
'User' => '用户',
'Nobody have access to this project.' => '无用户可以访问此项目.',
@@ -213,6 +213,7 @@ return array(
'Invalid date' => '无效日期',
'Must be done before %B %e, %Y' => '必须在%Y/%m/%d前完成',
'%B %e, %Y' => '%Y/%m/%d',
+ // '%b %e, %Y' => '',
'Automatic actions' => '自动动作',
'Your automatic action have been created successfully.' => '您的自动动作已成功创建',
'Unable to create your automatic action.' => '无法为您创建自动动作。',
@@ -231,7 +232,7 @@ return array(
'Next step' => '下一步',
'Define action parameters' => '定义动作参数',
'Save this action' => '保存该动作',
- 'Do you really want to remove this action: "%s"?' => '确定要移除该动作"%s"吗?',
+ 'Do you really want to remove this action: "%s"?' => '确定要移除动作"%s"吗?',
'Remove an automatic action' => '移除一个自动动作',
'Close the task' => '关闭任务',
'Assign the task to a specific user' => '将该任务指派给一个用户',
@@ -285,7 +286,7 @@ return array(
'Nothing found.' => '没找到。',
'Search in the project "%s"' => '在项目"%s"中查找',
'Due date' => '到期时间',
- 'Others formats accepted: %s and %s' => '允许其他格式:%s 和 %s',
+ 'Others formats accepted: %s and %s' => '可以使用的其它格式:%s 和 %s',
'Description' => '描述',
'%d comments' => '%d个评论',
'%d comment' => '%d个评论',
@@ -323,7 +324,7 @@ return array(
'Category Name' => '分类名称',
'Categories for the project "%s"' => '项目"%s"的分类',
'Add a new category' => '加入新分类',
- 'Do you really want to remove this category: "%s"?' => '您确定要移除分类"%s"吗?',
+ 'Do you really want to remove this category: "%s"?' => '确定要移除分类"%s"吗?',
'Filter by category' => '按分类过滤',
'All categories' => '所有分类',
'No category' => '无分类',
@@ -332,7 +333,7 @@ return array(
'Unable to remove this file.' => '无法移除该文件。',
'File removed successfully.' => '文件成功移除。',
'Attach a document' => '附加文档',
- 'Do you really want to remove this file: "%s"?' => '您确定要移除文件"%s"吗?',
+ 'Do you really want to remove this file: "%s"?' => '确定要移除文件"%s"吗?',
'open' => '打开',
'Attachments' => '附件',
'Edit the task' => '修改任务',
@@ -375,7 +376,7 @@ return array(
'Login with my GitHub Account' => '用Github账号登录',
'Link my GitHub Account' => '链接GitHub账号',
'Unlink my GitHub Account' => '取消GitHub账号链接',
- 'Created by %s' => '创建者:',
+ 'Created by %s' => '创建者:%s',
'Last modified on %B %e, %Y at %k:%M %p' => '最后修改:%Y/%m/%d/ %H:%M',
'Tasks Export' => '任务导出',
'Tasks exportation for "%s"' => '导出任务"%s"',
@@ -468,20 +469,20 @@ return array(
'Unable to change the password.' => '无法修改密码。',
'Change category for the task "%s"' => '变更任务 "%s" 的分类',
'Change category' => '变更分类',
- '%s updated the task #%d' => '%s 更新了任务 #%d',
- '%s open the task #%d' => '%s 开启了任务 #%d',
- '%s moved the task #%d to the position #%d in the column "%s"' => '%s 将任务 #%d 移动到了"%s"的第#%d个位置',
- '%s moved the task #%d to the column "%s"' => '%s 移动任务 #%d 到栏目 "%s"',
- '%s created the task #%d' => '%s 创建了任务 #%d',
- '%s closed the task #%d' => '%s 关闭了任务 #%d',
- '%s created a subtask for the task #%d' => '%s 创建了 #%d的子任务',
- '%s updated a subtask for the task #%d' => '%s 更新了 #%d的子任务',
+ '%s updated the task %s' => '%s 更新了任务 %s',
+ '%s opened the task %s' => '%s 开启了任务 %s',
+ '%s moved the task %s to the position #%d in the column "%s"' => '%s 将任务 %s 移动到了"%s"的第#%d个位置',
+ '%s moved the task %s to the column "%s"' => '%s 移动任务 %s 到栏目 "%s"',
+ '%s created the task %s' => '%s 创建了任务 %s',
+ '%s closed the task %s' => '%s 关闭了任务 %s',
+ '%s created a subtask for the task %s' => '%s 创建了 %s的子任务',
+ '%s updated a subtask for the task %s' => '%s 更新了 %s的子任务',
'Assigned to %s with an estimate of %s/%sh' => '分配给 %s,预估需要 %s/%s 小时',
'Not assigned, estimate of %sh' => '未分配,预估需要 %s 小时',
- '%s updated a comment on the task #%d' => '%s 更新了任务 #%d的评论',
- '%s commented the task #%d' => '%s 评论了任务 #%d',
- '%s\'s activity' => '%s的活动',
- 'No activity.' => '无活动',
+ '%s updated a comment on the task %s' => '%s 更新了任务 %s的评论',
+ '%s commented the task %s' => '%s 评论了任务 %s',
+ '%s\'s activity' => '%s的动态',
+ 'No activity.' => '无动态',
'RSS feed' => 'RSS 链接',
'%s updated a comment on the task #%d' => '%s 更新了任务 #%d 的评论',
'%s commented on the task #%d' => '%s 评论了任务 #%d',
@@ -493,12 +494,12 @@ return array(
'%s open the task #%d' => '%s 开启了任务 #%d',
'%s moved the task #%d to the column "%s"' => '%s 将任务 #%d 移动到栏目 "%s"',
'%s moved the task #%d to the position %d in the column "%s"' => '%s将任务#%d移动到"%s"的第 %d 列',
- 'Activity' => '活动',
+ 'Activity' => '动态',
'Default values are "%s"' => '默认值为 "%s"',
'Default columns for new projects (Comma-separated)' => '新建项目的默认栏目(用逗号分开)',
'Task assignee change' => '任务分配变更',
'%s change the assignee of the task #%d to %s' => '%s 将任务 #%d 分配给了 %s',
- '%s change the assignee of the task #%d to %s' => '%s 将任务 #%d 分配给 %s',
+ '%s changed the assignee of the task %s to %s' => '%s 将任务 %s 分配给 %s',
'[%s][Column Change] %s (#%d)' => '[%s][栏目变更] %s (#%d)',
'[%s][Position Change] %s (#%d)' => '[%s][位置变更] %s (#%d)',
'[%s][Assignee Change] %s (#%d)' => '[%s][任务分配变更] %s (#%d)',
@@ -547,7 +548,7 @@ return array(
'Time estimated' => '预计时间',
'There is nothing assigned to you.' => '无任务指派给你。',
'My tasks' => '我的任务',
- 'Activity stream' => '活动流',
+ 'Activity stream' => '动态记录',
'Dashboard' => '面板',
'Confirmation' => '确认',
'Allow everybody to access to this project' => '允许所有人访问此项目',
@@ -555,8 +556,8 @@ return array(
'Webhooks' => '网络钩子',
'API' => '应用程序接口',
'Integration' => '整合',
- 'Github webhook' => 'Github 网络钩子',
- 'Help on Github webhook' => 'Github 网络钩子帮助',
+ 'Github webhooks' => 'Github 网络钩子',
+ 'Help on Github webhooks' => 'Github 网络钩子帮助',
'Create a comment from an external provider' => '从外部创建一个评论',
'Github issue comment created' => '已经创建了Github问题评论',
'Configure' => '配置',
@@ -602,4 +603,49 @@ return array(
'Nothing to preview...' => '没有需要预览的内容',
'Preview' => '预览',
'Write' => '书写',
+ 'Active swimlanes' => '活动泳道',
+ 'Add a new swimlane' => '添加新泳道',
+ 'Change default swimlane' => '修改默认泳道',
+ 'Default swimlane' => '默认泳道',
+ 'Do you really want to remove this swimlane: "%s"?' => '确定要删除泳道:"%s"?',
+ 'Inactive swimlanes' => '非活动泳道',
+ // 'Set project manager' => '',
+ // 'Set project member' => '',
+ 'Remove a swimlane' => '删除泳道',
+ 'Rename' => '重命名',
+ 'Show default swimlane' => '显示默认泳道',
+ 'Swimlane modification for the project "%s"' => '项目"%s"的泳道变更',
+ 'Swimlane not found.' => '未找到泳道。',
+ 'Swimlane removed successfully.' => '成功删除泳道',
+ 'Swimlanes' => '泳道',
+ 'Swimlane updated successfully.' => '成功更新了泳道。',
+ 'The default swimlane have been updated successfully.' => '成功更新了默认泳道。',
+ 'Unable to create your swimlane.' => '无法创建泳道。',
+ 'Unable to remove this swimlane.' => '无法删除此泳道',
+ 'Unable to update this swimlane.' => '无法更新此泳道',
+ 'Your swimlane have been created successfully.' => '已经成功创建泳道。',
+ // 'Example: "Bug, Feature Request, Improvement"' => '',
+ // 'Default categories for new projects (Comma-separated)' => '',
+ // 'Gitlab commit received' => '',
+ // 'Gitlab issue opened' => '',
+ // 'Gitlab issue closed' => '',
+ // 'Gitlab webhooks' => '',
+ // 'Help on Gitlab webhooks' => '',
+ // 'Integrations' => '',
+ // 'Integration with third-party services' => '',
+ // 'Role for this project' => '',
+ // 'Project manager' => '',
+ // 'Project member' => '',
+ // 'A project manager can change the settings of the project and have more privileges than a standard user.' => '',
+ // 'Gitlab Issue' => '',
+ // 'Subtask Id' => '',
+ // 'Subtasks' => '',
+ // 'Subtasks Export' => '',
+ // 'Subtasks exportation for "%s"' => '',
+ // 'Task Title' => '',
+ // 'Untitled' => '',
+ // 'Application default' => '',
+ // 'Language:' => '',
+ // 'Timezone:' => '',
+ // 'Next' => '',
);
diff --git a/sources/app/Model/Acl.php b/sources/app/Model/Acl.php
index 4a07d11..599ff05 100644
--- a/sources/app/Model/Acl.php
+++ b/sources/app/Model/Acl.php
@@ -3,7 +3,7 @@
namespace Model;
/**
- * Acl model
+ * Access List
*
* @package model
* @author Frederic Guillot
@@ -16,36 +16,59 @@ class Acl extends Base
* @access private
* @var array
*/
- private $public_actions = array(
+ private $public_acl = array(
'user' => array('login', 'check', 'google', 'github'),
'task' => array('readonly'),
'board' => array('readonly'),
'project' => array('feed'),
- 'webhook' => array('task', 'github'),
+ 'webhook' => '*',
);
/**
- * Controllers and actions allowed for regular users
+ * Controllers and actions for project members
*
* @access private
* @var array
*/
- private $user_actions = array(
- 'app' => array('index', 'preview', 'status'),
- 'project' => array('index', 'show', 'exporttasks', 'exportdaily', 'share', 'edit', 'update', 'users', 'remove', 'duplicate', 'disable', 'enable', 'activity', 'search', 'tasks', 'create', 'save'),
- 'board' => array('index', 'show', 'save', 'check', 'changeassignee', 'updateassignee', 'changecategory', 'updatecategory', 'movecolumn', 'edit', 'update', 'add', 'confirm', 'remove', 'subtasks', 'togglesubtask', 'attachments', 'comments', 'description'),
- 'user' => array('edit', 'forbidden', 'logout', 'show', 'external', 'unlinkgoogle', 'unlinkgithub', 'sessions', 'removesession', 'last', 'notifications', 'password'),
- 'comment' => array('create', 'save', 'confirm', 'remove', 'update', 'edit', 'forbidden'),
- 'file' => array('create', 'save', 'download', 'confirm', 'remove', 'open', 'image'),
- 'subtask' => array('create', 'save', 'edit', 'update', 'confirm', 'remove', 'togglestatus'),
- 'task' => array('show', 'create', 'save', 'edit', 'update', 'close', 'open', 'duplicate', 'remove', 'description', 'move', 'copy', 'time'),
- 'category' => array('index', 'save', 'edit', 'update', 'confirm', 'remove'),
- 'action' => array('index', 'event', 'params', 'create', 'confirm', 'remove'),
- 'analytic' => array('tasks', 'users', 'cfd'),
+ private $member_acl = array(
+ 'board' => '*',
+ 'comment' => '*',
+ 'file' => '*',
+ 'project' => array('show', 'tasks', 'search', 'activity'),
+ 'subtask' => '*',
+ 'task' => '*',
);
/**
- * Return true if the specified controller/action is allowed according to the given acl
+ * Controllers and actions for project managers
+ *
+ * @access private
+ * @var array
+ */
+ private $manager_acl = array(
+ 'action' => '*',
+ 'analytic' => '*',
+ 'board' => array('movecolumn', 'edit', 'update', 'add', 'remove'),
+ 'category' => '*',
+ 'export' => array('tasks', 'subtasks', 'summary'),
+ 'project' => array('edit', 'update', 'share', 'integration', 'users', 'alloweverybody', 'allow', 'setowner', 'revoke', 'duplicate', 'disable', 'enable'),
+ 'swimlane' => '*',
+ );
+
+ /**
+ * Controllers and actions for admins
+ *
+ * @access private
+ * @var array
+ */
+ private $admin_acl = array(
+ 'user' => array('index', 'create', 'save', 'remove'),
+ 'config' => '*',
+ 'project' => array('remove'),
+ );
+
+ /**
+ * Return true if the specified controller/action match the given acl
*
* @access public
* @param array $acl Acl list
@@ -53,13 +76,27 @@ class Acl extends Base
* @param string $action Action name
* @return bool
*/
- public function isAllowedAction(array $acl, $controller, $action)
+ public function matchAcl(array $acl, $controller, $action)
{
- if (isset($acl[$controller])) {
- return in_array($action, $acl[$controller]);
+ $action = strtolower($action);
+ return isset($acl[$controller]) && $this->hasAction($action, $acl[$controller]);
+ }
+
+ /**
+ * Return true if the specified action is inside the list of actions
+ *
+ * @access public
+ * @param string $action Action name
+ * @param mixed $action Actions list
+ * @return bool
+ */
+ public function hasAction($action, $actions)
+ {
+ if (is_array($actions)) {
+ return in_array($action, $actions);
}
- return false;
+ return $actions === '*';
}
/**
@@ -72,94 +109,90 @@ class Acl extends Base
*/
public function isPublicAction($controller, $action)
{
- return $this->isAllowedAction($this->public_actions, $controller, $action);
+ return $this->matchAcl($this->public_acl, $controller, $action);
}
/**
- * Return true if the given action is allowed for a regular user
+ * Return true if the given action is for admins
*
* @access public
* @param string $controller Controller name
* @param string $action Action name
* @return bool
*/
- public function isUserAction($controller, $action)
+ public function isAdminAction($controller, $action)
{
- return $this->isAllowedAction($this->user_actions, $controller, $action);
+ return $this->matchAcl($this->admin_acl, $controller, $action);
}
/**
- * Return true if the logged user is admin
+ * Return true if the given action is for project managers
*
* @access public
+ * @param string $controller Controller name
+ * @param string $action Action name
* @return bool
*/
- public function isAdminUser()
+ public function isManagerAction($controller, $action)
{
- return isset($_SESSION['user']['is_admin']) && $_SESSION['user']['is_admin'] === true;
+ return $this->matchAcl($this->manager_acl, $controller, $action);
}
/**
- * Return true if the logged user is not admin
+ * Return true if the given action is for project members
*
* @access public
+ * @param string $controller Controller name
+ * @param string $action Action name
* @return bool
*/
- public function isRegularUser()
+ public function isMemberAction($controller, $action)
{
- return isset($_SESSION['user']['is_admin']) && $_SESSION['user']['is_admin'] === false;
+ return $this->matchAcl($this->member_acl, $controller, $action);
}
/**
- * Get the connected user id
- *
- * @access public
- * @return integer
- */
- public function getUserId()
- {
- return isset($_SESSION['user']['id']) ? (int) $_SESSION['user']['id'] : 0;
- }
-
- /**
- * Check is the user is connected
+ * Return true if the visitor is allowed to access to the given page
+ * We suppose the user already authenticated
*
* @access public
+ * @param string $controller Controller name
+ * @param string $action Action name
+ * @param integer $project_id Project id
* @return bool
*/
- public function isLogged()
+ public function isAllowed($controller, $action, $project_id = 0)
{
- return ! empty($_SESSION['user']);
- }
-
- /**
- * Check is the user was authenticated with the RememberMe or set the value
- *
- * @access public
- * @param bool $value Set true if the user use the RememberMe
- * @return bool
- */
- public function isRememberMe($value = null)
- {
- if ($value !== null) {
- $_SESSION['is_remember_me'] = $value;
+ // If you are admin you have access to everything
+ if ($this->userSession->isAdmin()) {
+ return true;
}
- return empty($_SESSION['is_remember_me']) ? false : $_SESSION['is_remember_me'];
+ // If you access to an admin action, your are not allowed
+ if ($this->isAdminAction($controller, $action)) {
+ return false;
+ }
+
+ // Check project manager permissions
+ if ($this->isManagerAction($controller, $action)) {
+ return $this->isManagerActionAllowed($project_id);
+ }
+
+ // Check project member permissions
+ if ($this->isMemberAction($controller, $action)) {
+ return $project_id > 0 && $this->projectPermission->isMember($project_id, $this->userSession->getId());
+ }
+
+ // Other applications actions are allowed
+ return true;
}
- /**
- * Check if an action is allowed for the logged user
- *
- * @access public
- * @param string $controller Controller name
- * @param string $action Action name
- * @return bool
- */
- public function isPageAccessAllowed($controller, $action)
+ public function isManagerActionAllowed($project_id)
{
- return $this->isPublicAction($controller, $action) ||
- $this->isAdminUser() ||
- ($this->isRegularUser() && $this->isUserAction($controller, $action));
+ if ($this->userSession->isAdmin()) {
+ return true;
+ }
+
+ return $project_id > 0 && $this->projectPermission->isManager($project_id, $this->userSession->getId());
}
}
diff --git a/sources/app/Model/Action.php b/sources/app/Model/Action.php
index f8dbd88..2204ad3 100644
--- a/sources/app/Model/Action.php
+++ b/sources/app/Model/Action.php
@@ -2,7 +2,8 @@
namespace Model;
-use LogicException;
+use Integration\GitlabWebhook;
+use Integration\GithubWebhook;
use SimpleValidator\Validator;
use SimpleValidator\Validators;
@@ -80,6 +81,9 @@ class Action extends Base
GithubWebhook::EVENT_ISSUE_ASSIGNEE_CHANGE => t('Github issue assignee change'),
GithubWebhook::EVENT_ISSUE_LABEL_CHANGE => t('Github issue label change'),
GithubWebhook::EVENT_ISSUE_COMMENT => t('Github issue comment created'),
+ GitlabWebhook::EVENT_COMMIT => t('Gitlab commit received'),
+ GitlabWebhook::EVENT_ISSUE_OPENED => t('Gitlab issue opened'),
+ GitlabWebhook::EVENT_ISSUE_CLOSED => t('Gitlab issue closed'),
);
asort($values);
@@ -136,9 +140,17 @@ class Action extends Base
public function getAll()
{
$actions = $this->db->table(self::TABLE)->findAll();
+ $params = $this->db->table(self::TABLE_PARAMS)->findAll();
foreach ($actions as &$action) {
- $action['params'] = $this->db->table(self::TABLE_PARAMS)->eq('action_id', $action['id'])->findAll();
+
+ $action['params'] = array();
+
+ foreach ($params as $param) {
+ if ($param['action_id'] === $action['id']) {
+ $action['params'][] = $param;
+ }
+ }
}
return $actions;
@@ -187,6 +199,7 @@ class Action extends Base
*/
public function remove($action_id)
{
+ // $this->container['fileCache']->remove('proxy_action_getAll');
return $this->db->table(self::TABLE)->eq('id', $action_id)->remove();
}
@@ -230,6 +243,8 @@ class Action extends Base
$this->db->closeTransaction();
+ // $this->container['fileCache']->remove('proxy_action_getAll');
+
return true;
}
@@ -240,7 +255,10 @@ class Action extends Base
*/
public function attachEvents()
{
- foreach ($this->getAll() as $action) {
+ //$actions = $this->container['fileCache']->proxy('action', 'getAll');
+ $actions = $this->getAll();
+
+ foreach ($actions as $action) {
$listener = $this->load($action['action_name'], $action['project_id'], $action['event_name']);
@@ -248,7 +266,7 @@ class Action extends Base
$listener->setParam($param['name'], $param['value']);
}
- $this->event->attach($action['event_name'], $listener);
+ $this->container['dispatcher']->addListener($action['event_name'], array($listener, 'execute'));
}
}
@@ -303,6 +321,8 @@ class Action extends Base
}
}
+ // $this->container['fileCache']->remove('proxy_action_getAll');
+
return true;
}
diff --git a/sources/app/Model/Authentication.php b/sources/app/Model/Authentication.php
index a0e9684..92898cd 100644
--- a/sources/app/Model/Authentication.php
+++ b/sources/app/Model/Authentication.php
@@ -3,7 +3,6 @@
namespace Model;
use Core\Request;
-use Auth\Database;
use SimpleValidator\Validator;
use SimpleValidator\Validators;
@@ -36,19 +35,12 @@ class Authentication extends Base
* Check if the current user is authenticated
*
* @access public
- * @param string $controller Controller
- * @param string $action Action name
* @return bool
*/
- public function isAuthenticated($controller, $action)
+ public function isAuthenticated()
{
- // If the action is public we don't need to do any checks
- if ($this->acl->isPublicAction($controller, $action)) {
- return true;
- }
-
// If the user is already logged it's ok
- if ($this->acl->isLogged()) {
+ if ($this->userSession->isLogged()) {
// We update each time the RememberMe cookie tokens
if ($this->backend('rememberMe')->hasCookie()) {
@@ -118,7 +110,7 @@ class Authentication extends Base
if (! empty($values['remember_me'])) {
$credentials = $this->backend('rememberMe')
- ->create($this->acl->getUserId(), Request::getIpAddress(), Request::getUserAgent());
+ ->create($this->userSession->getId(), Request::getIpAddress(), Request::getUserAgent());
$this->backend('rememberMe')->writeCookie($credentials['token'], $credentials['sequence'], $credentials['expiration']);
}
diff --git a/sources/app/Model/Base.php b/sources/app/Model/Base.php
index 56a4d8e..3f847c2 100644
--- a/sources/app/Model/Base.php
+++ b/sources/app/Model/Base.php
@@ -2,10 +2,7 @@
namespace Model;
-use Core\Event;
-use Core\Tool;
use Pimple\Container;
-use PicoDb\Database;
/**
* Base model class
@@ -13,6 +10,8 @@ use PicoDb\Database;
* @package model
* @author Frederic Guillot
*
+ * @property \Core\Session $session
+ * @property \Core\Template $template
* @property \Model\Acl $acl
* @property \Model\Action $action
* @property \Model\Authentication $authentication
@@ -30,6 +29,7 @@ use PicoDb\Database;
* @property \Model\ProjectPermission $projectPermission
* @property \Model\SubTask $subTask
* @property \Model\SubtaskHistory $subtaskHistory
+ * @property \Model\Swimlane $swimlane
* @property \Model\Task $task
* @property \Model\TaskCreation $taskCreation
* @property \Model\TaskExport $taskExport
@@ -39,6 +39,7 @@ use PicoDb\Database;
* @property \Model\TaskValidator $taskValidator
* @property \Model\TimeTracking $timeTracking
* @property \Model\User $user
+ * @property \Model\UserSession $userSession
* @property \Model\Webhook $webhook
*/
abstract class Base
@@ -51,14 +52,6 @@ abstract class Base
*/
protected $db;
- /**
- * Event dispatcher instance
- *
- * @access public
- * @var \Core\Event
- */
- public $event;
-
/**
* Container instance
*
@@ -77,7 +70,6 @@ abstract class Base
{
$this->container = $container;
$this->db = $this->container['db'];
- $this->event = $this->container['event'];
}
/**
@@ -89,7 +81,7 @@ abstract class Base
*/
public function __get($name)
{
- return Tool::loadModel($this->container, $name);
+ return $this->container[$name];
}
/**
@@ -117,7 +109,7 @@ abstract class Base
*
* @access public
* @param array $values Input array
- * @param array $keys List of keys to remove
+ * @param string[] $keys List of keys to remove
*/
public function removeFields(array &$values, array $keys)
{
@@ -132,8 +124,8 @@ abstract class Base
* Force some fields to be at 0 if empty
*
* @access public
- * @param array $values Input array
- * @param array $keys List of keys
+ * @param array $values Input array
+ * @param string[] $keys List of keys
*/
public function resetFields(array &$values, array $keys)
{
@@ -148,8 +140,8 @@ abstract class Base
* Force some fields to be integer
*
* @access public
- * @param array $values Input array
- * @param array $keys List of keys
+ * @param array $values Input array
+ * @param string[] $keys List of keys
*/
public function convertIntegerFields(array &$values, array $keys)
{
diff --git a/sources/app/Model/Board.php b/sources/app/Model/Board.php
index 9ba2e06..550009f 100644
--- a/sources/app/Model/Board.php
+++ b/sources/app/Model/Board.php
@@ -24,7 +24,7 @@ class Board extends Base
* Get Kanboard default columns
*
* @access public
- * @return array
+ * @return string[]
*/
public function getDefaultColumns()
{
@@ -227,29 +227,47 @@ class Board extends Base
}
/**
- * Get all columns and tasks for a given project
+ * Get all tasks sorted by columns and swimlanes
*
* @access public
* @param integer $project_id Project id
* @return array
*/
- public function get($project_id)
+ public function getBoard($project_id)
{
+ $swimlanes = $this->swimlane->getSwimlanes($project_id);
$columns = $this->getColumns($project_id);
- $tasks = $this->taskFinder->getTasksOnBoard($project_id);
+ $nb_columns = count($columns);
- foreach ($columns as &$column) {
+ for ($i = 0, $ilen = count($swimlanes); $i < $ilen; $i++) {
- $column['tasks'] = array();
+ $swimlanes[$i]['columns'] = $columns;
+ $swimlanes[$i]['nb_columns'] = $nb_columns;
- foreach ($tasks as &$task) {
- if ($task['column_id'] == $column['id']) {
- $column['tasks'][] = $task;
- }
+ for ($j = 0; $j < $nb_columns; $j++) {
+ $swimlanes[$i]['columns'][$j]['tasks'] = $this->taskFinder->getTasksByColumnAndSwimlane($project_id, $columns[$j]['id'], $swimlanes[$i]['id']);
+ $swimlanes[$i]['columns'][$j]['nb_tasks'] = count($swimlanes[$i]['columns'][$j]['tasks']);
}
}
- return $columns;
+ return $swimlanes;
+ }
+
+ /**
+ * Get the total of tasks per column
+ *
+ * @access public
+ * @param integer $project_id
+ * @return array
+ */
+ public function getColumnStats($project_id)
+ {
+ return $this->db
+ ->table(Task::TABLE)
+ ->eq('project_id', $project_id)
+ ->eq('is_active', 1)
+ ->groupBy('column_id')
+ ->listing('column_id', 'COUNT(*) AS total');
}
/**
diff --git a/sources/app/Model/Category.php b/sources/app/Model/Category.php
index 54a0f55..cd60e7f 100644
--- a/sources/app/Model/Category.php
+++ b/sources/app/Model/Category.php
@@ -118,7 +118,30 @@ class Category extends Base
}
/**
- * Create a category
+ * Create default cetegories during project creation (transaction already started in Project::create())
+ *
+ * @access public
+ * @param integer $project_id
+ */
+ public function createDefaultCategories($project_id)
+ {
+ $categories = explode(',', $this->config->get('project_categories'));
+
+ foreach ($categories as $category) {
+
+ $category = trim($category);
+
+ if (! empty($category)) {
+ $this->db->table(self::TABLE)->insert(array(
+ 'project_id' => $project_id,
+ 'name' => $category,
+ ));
+ }
+ }
+ }
+
+ /**
+ * Create a category (run inside a transaction)
*
* @access public
* @param array $values Form values
diff --git a/sources/app/Model/Comment.php b/sources/app/Model/Comment.php
index 3b7dfbc..a36f2b4 100644
--- a/sources/app/Model/Comment.php
+++ b/sources/app/Model/Comment.php
@@ -2,6 +2,7 @@
namespace Model;
+use Event\CommentEvent;
use SimpleValidator\Validator;
use SimpleValidator\Validators;
@@ -107,7 +108,7 @@ class Comment extends Base
$comment_id = $this->persist(self::TABLE, $values);
if ($comment_id) {
- $this->event->trigger(self::EVENT_CREATE, array('id' => $comment_id) + $values);
+ $this->container['dispatcher']->dispatch(self::EVENT_CREATE, new CommentEvent(array('id' => $comment_id) + $values));
}
return $comment_id;
@@ -127,7 +128,7 @@ class Comment extends Base
->eq('id', $values['id'])
->update(array('comment' => $values['comment']));
- $this->event->trigger(self::EVENT_UPDATE, $values);
+ $this->container['dispatcher']->dispatch(self::EVENT_UPDATE, new CommentEvent($values));
return $result;
}
diff --git a/sources/app/Model/Config.php b/sources/app/Model/Config.php
index 1ad6dbd..e6d6673 100644
--- a/sources/app/Model/Config.php
+++ b/sources/app/Model/Config.php
@@ -2,8 +2,6 @@
namespace Model;
-use SimpleValidator\Validator;
-use SimpleValidator\Validators;
use Core\Translator;
use Core\Security;
use Core\Session;
@@ -27,30 +25,39 @@ class Config extends Base
* Get available timezones
*
* @access public
+ * @param boolean $prepend Prepend a default value
* @return array
*/
- public function getTimezones()
+ public function getTimezones($prepend = false)
{
$timezones = timezone_identifiers_list();
- return array_combine(array_values($timezones), $timezones);
+ $listing = array_combine(array_values($timezones), $timezones);
+
+ if ($prepend) {
+ return array('' => t('Application default')) + $listing;
+ }
+
+ return $listing;
}
/**
* Get available languages
*
* @access public
+ * @param boolean $prepend Prepend a default value
* @return array
*/
- public function getLanguages()
+ public function getLanguages($prepend = false)
{
// Sorted by value
- return array(
+ $languages = array(
'da_DK' => 'Dansk',
'de_DE' => 'Deutsch',
'en_US' => 'English',
'es_ES' => 'Español',
'fr_FR' => 'Français',
'it_IT' => 'Italiano',
+ 'hu_HU' => 'Magyar',
'pl_PL' => 'Polski',
'pt_BR' => 'Português (Brasil)',
'ru_RU' => 'Русский',
@@ -60,6 +67,12 @@ class Config extends Base
'ja_JP' => '日本語',
'th_TH' => 'ไทย',
);
+
+ if ($prepend) {
+ return array('' => t('Application default')) + $languages;
+ }
+
+ return $languages;
}
/**
@@ -77,12 +90,13 @@ class Config extends Base
return $value ?: $default_value;
}
- if (! isset($_SESSION['config'][$name])) {
- $_SESSION['config'] = $this->getAll();
+ // Cache config in session
+ if (! isset($this->session['config'][$name])) {
+ $this->session['config'] = $this->getAll();
}
- if (! empty($_SESSION['config'][$name])) {
- return $_SESSION['config'][$name];
+ if (! empty($this->session['config'][$name])) {
+ return $this->session['config'][$name];
}
return $default_value;
@@ -127,7 +141,7 @@ class Config extends Base
*/
public function reload()
{
- $_SESSION['config'] = $this->getAll();
+ $this->session['config'] = $this->getAll();
$this->setupTranslations();
}
@@ -138,10 +152,11 @@ class Config extends Base
*/
public function setupTranslations()
{
- $language = $this->get('application_language', 'en_US');
-
- if ($language !== 'en_US') {
- Translator::load($language);
+ if ($this->userSession->isLogged() && ! empty($this->session['user']['language'])) {
+ Translator::load($this->session['user']['language']);
+ }
+ else {
+ Translator::load($this->get('application_language', 'en_US'));
}
}
@@ -152,7 +167,12 @@ class Config extends Base
*/
public function setupTimezone()
{
- date_default_timezone_set($this->get('application_timezone', 'UTC'));
+ if ($this->userSession->isLogged() && ! empty($this->session['user']['timezone'])) {
+ date_default_timezone_set($this->session['user']['timezone']);
+ }
+ else {
+ date_default_timezone_set($this->get('application_timezone', 'UTC'));
+ }
}
/**
diff --git a/sources/app/Model/DateParser.php b/sources/app/Model/DateParser.php
index 38265f9..518a4f3 100644
--- a/sources/app/Model/DateParser.php
+++ b/sources/app/Model/DateParser.php
@@ -60,7 +60,7 @@ class DateParser extends Base
* Return the list of supported date formats (for the parser)
*
* @access public
- * @return array
+ * @return string[]
*/
public function getDateFormats()
{
@@ -103,7 +103,7 @@ class DateParser extends Base
*
* @access public
* @param array $values Database values
- * @param array $fields Date fields
+ * @param string[] $fields Date fields
* @param string $format Date format
*/
public function format(array &$values, array $fields, $format = '')
@@ -128,7 +128,7 @@ class DateParser extends Base
*
* @access public
* @param array $values Database values
- * @param array $fields Date fields
+ * @param string[] $fields Date fields
*/
public function convert(array &$values, array $fields)
{
diff --git a/sources/app/Model/File.php b/sources/app/Model/File.php
index d5a0c7c..20fba9b 100644
--- a/sources/app/Model/File.php
+++ b/sources/app/Model/File.php
@@ -2,6 +2,8 @@
namespace Model;
+use Event\FileEvent;
+
/**
* File model
*
@@ -89,7 +91,10 @@ class File extends Base
*/
public function create($task_id, $name, $path, $is_image)
{
- $this->event->trigger(self::EVENT_CREATE, array('task_id' => $task_id, 'name' => $name));
+ $this->container['dispatcher']->dispatch(
+ self::EVENT_CREATE,
+ new FileEvent(array('task_id' => $task_id, 'name' => $name))
+ );
return $this->db->table(self::TABLE)->save(array(
'task_id' => $task_id,
diff --git a/sources/app/Model/LastLogin.php b/sources/app/Model/LastLogin.php
index 3391db5..dd64284 100644
--- a/sources/app/Model/LastLogin.php
+++ b/sources/app/Model/LastLogin.php
@@ -32,7 +32,7 @@ class LastLogin extends Base
* @param integer $user_id User id
* @param string $ip IP Address
* @param string $user_agent User Agent
- * @return array
+ * @return boolean
*/
public function create($auth_type, $user_id, $ip, $user_agent)
{
diff --git a/sources/app/Model/Notification.php b/sources/app/Model/Notification.php
index 8d1fca0..95306e8 100644
--- a/sources/app/Model/Notification.php
+++ b/sources/app/Model/Notification.php
@@ -3,9 +3,6 @@
namespace Model;
use Core\Session;
-use Core\Translator;
-use Core\Template;
-use Event\NotificationListener;
use Swift_Message;
use Swift_Mailer;
use Swift_TransportException;
@@ -29,8 +26,8 @@ class Notification extends Base
* Get a list of people with notifications enabled
*
* @access public
- * @param integer $project_id Project id
- * @param array $exlude_users List of user_id to exclude
+ * @param integer $project_id Project id
+ * @param array $exclude_users List of user_id to exclude
* @return array
*/
public function getUsersWithNotification($project_id, array $exclude_users = array())
@@ -61,15 +58,15 @@ class Notification extends Base
* Get the list of users to send the notification for a given project
*
* @access public
- * @param integer $project_id Project id
- * @param array $exlude_users List of user_id to exclude
+ * @param integer $project_id Project id
+ * @param array $exclude_users List of user_id to exclude
* @return array
*/
public function getUsersList($project_id, array $exclude_users = array())
{
// Exclude the connected user
if (Session::isOpen()) {
- $exclude_users[] = $this->acl->getUserId();
+ $exclude_users[] = $this->userSession->getId();
}
$users = $this->getUsersWithNotification($project_id, $exclude_users);
@@ -93,37 +90,6 @@ class Notification extends Base
return $users;
}
- /**
- * Attach events
- *
- * @access public
- */
- public function attachEvents()
- {
- $events = array(
- Task::EVENT_CREATE => 'task_creation',
- Task::EVENT_UPDATE => 'task_update',
- Task::EVENT_CLOSE => 'task_close',
- Task::EVENT_OPEN => 'task_open',
- Task::EVENT_MOVE_COLUMN => 'task_move_column',
- Task::EVENT_MOVE_POSITION => 'task_move_position',
- Task::EVENT_ASSIGNEE_CHANGE => 'task_assignee_change',
- SubTask::EVENT_CREATE => 'subtask_creation',
- SubTask::EVENT_UPDATE => 'subtask_update',
- Comment::EVENT_CREATE => 'comment_creation',
- Comment::EVENT_UPDATE => 'comment_update',
- File::EVENT_CREATE => 'file_creation',
- );
-
- foreach ($events as $event_name => $template_name) {
-
- $listener = new NotificationListener($this->container);
- $listener->setTemplate($template_name);
-
- $this->event->attach($event_name, $listener);
- }
- }
-
/**
* Send the email notifications
*
@@ -148,7 +114,7 @@ class Notification extends Base
}
}
catch (Swift_TransportException $e) {
- $this->container['logger']->addError($e->getMessage());
+ $this->container['logger']->error($e->getMessage());
}
}
@@ -217,8 +183,10 @@ class Notification extends Base
*/
public function getMailContent($template, array $data)
{
- $tpl = new Template;
- return $tpl->load('notification/'.$template, $data + array('application_url' => $this->config->get('application_url')));
+ return $this->template->render(
+ 'notification/'.$template,
+ $data + array('application_url' => $this->config->get('application_url'))
+ );
}
/**
diff --git a/sources/app/Model/Project.php b/sources/app/Model/Project.php
index c657e82..f9c5c39 100644
--- a/sources/app/Model/Project.php
+++ b/sources/app/Model/Project.php
@@ -4,7 +4,6 @@ namespace Model;
use SimpleValidator\Validator;
use SimpleValidator\Validators;
-use Event\ProjectModificationDateListener;
use Core\Security;
/**
@@ -52,12 +51,12 @@ class Project extends Base
* Get a project by the name
*
* @access public
- * @param string $project_name Project name
+ * @param string $name Project name
* @return array
*/
- public function getByName($project_name)
+ public function getByName($name)
{
- return $this->db->table(self::TABLE)->eq('name', $project_name)->findOne();
+ return $this->db->table(self::TABLE)->eq('name', $name)->findOne();
}
/**
@@ -110,7 +109,7 @@ class Project extends Base
foreach ($projects as $key => $project) {
- if (! $this->projectPermission->isUserAllowed($project['id'], $this->acl->getUserId())) {
+ if (! $this->projectPermission->isUserAllowed($project['id'], $this->userSession->getId())) {
unset($projects[$key]);
}
}
@@ -192,11 +191,12 @@ class Project extends Base
public function getStats($project_id)
{
$stats = array();
- $columns = $this->board->getColumns($project_id);
$stats['nb_active_tasks'] = 0;
+ $columns = $this->board->getColumns($project_id);
+ $column_stats = $this->board->getColumnStats($project_id);
foreach ($columns as &$column) {
- $column['nb_active_tasks'] = $this->taskFinder->countByColumnId($project_id, $column['id']);
+ $column['nb_active_tasks'] = isset($column_stats[$column['id']]) ? $column_stats[$column['id']] : 0;
$stats['nb_active_tasks'] += $column['nb_active_tasks'];
}
@@ -228,7 +228,7 @@ class Project extends Base
);
if (! $this->db->table(self::TABLE)->save($values)) {
- return false;
+ return 0;
}
return $this->db->getConnection()->getLastId();
@@ -296,9 +296,11 @@ class Project extends Base
}
if ($add_user && $user_id) {
- $this->projectPermission->allowUser($project_id, $user_id);
+ $this->projectPermission->addManager($project_id, $user_id);
}
+ $this->category->createDefaultCategories($project_id);
+
$this->db->closeTransaction();
return (int) $project_id;
@@ -489,34 +491,4 @@ class Project extends Base
$v->getErrors()
);
}
-
- /**
- * Attach events
- *
- * @access public
- */
- public function attachEvents()
- {
- $events = array(
- Task::EVENT_CREATE_UPDATE,
- Task::EVENT_CLOSE,
- Task::EVENT_OPEN,
- Task::EVENT_MOVE_COLUMN,
- Task::EVENT_MOVE_POSITION,
- Task::EVENT_ASSIGNEE_CHANGE,
- GithubWebhook::EVENT_ISSUE_OPENED,
- GithubWebhook::EVENT_ISSUE_CLOSED,
- GithubWebhook::EVENT_ISSUE_REOPENED,
- GithubWebhook::EVENT_ISSUE_ASSIGNEE_CHANGE,
- GithubWebhook::EVENT_ISSUE_LABEL_CHANGE,
- GithubWebhook::EVENT_ISSUE_COMMENT,
- GithubWebhook::EVENT_COMMIT,
- );
-
- $listener = new ProjectModificationDateListener($this->container);
-
- foreach ($events as $event_name) {
- $this->event->attach($event_name, $listener);
- }
- }
}
diff --git a/sources/app/Model/ProjectActivity.php b/sources/app/Model/ProjectActivity.php
index 000dfa0..bbcb7f5 100644
--- a/sources/app/Model/ProjectActivity.php
+++ b/sources/app/Model/ProjectActivity.php
@@ -2,9 +2,6 @@
namespace Model;
-use Core\Template;
-use Event\ProjectActivityListener;
-
/**
* Project activity model
*
@@ -25,7 +22,7 @@ class ProjectActivity extends Base
*
* @var integer
*/
- const MAX_EVENTS = 5000;
+ const MAX_EVENTS = 1000;
/**
* Add a new event for the project
@@ -46,7 +43,7 @@ class ProjectActivity extends Base
'creator_id' => $creator_id,
'event_name' => $event_name,
'date_creation' => time(),
- 'data' => serialize($data),
+ 'data' => json_encode($data),
);
$this->cleanup(self::MAX_EVENTS - 1);
@@ -70,13 +67,13 @@ class ProjectActivity extends Base
* Get all events for the given projects list
*
* @access public
- * @param integer $project_id Project id
+ * @param integer[] $project_ids Projects id
* @param integer $limit Maximum events number
* @return array
*/
- public function getProjects(array $projects, $limit = 50)
+ public function getProjects(array $project_ids, $limit = 50)
{
- if (empty($projects)) {
+ if (empty($project_ids)) {
return array();
}
@@ -86,7 +83,7 @@ class ProjectActivity extends Base
User::TABLE.'.username AS author_username',
User::TABLE.'.name AS author_name'
)
- ->in('project_id', $projects)
+ ->in('project_id', $project_ids)
->join(User::TABLE, 'id', 'creator_id')
->desc('id')
->limit($limit)
@@ -94,7 +91,7 @@ class ProjectActivity extends Base
foreach ($events as &$event) {
- $event += unserialize($event['data']);
+ $event += $this->decode($event['data']);
unset($event['data']);
$event['author'] = $event['author_name'] ?: $event['author_username'];
@@ -126,34 +123,6 @@ class ProjectActivity extends Base
}
}
- /**
- * Attach events to be able to record the history
- *
- * @access public
- */
- public function attachEvents()
- {
- $events = array(
- Task::EVENT_ASSIGNEE_CHANGE,
- Task::EVENT_UPDATE,
- Task::EVENT_CREATE,
- Task::EVENT_CLOSE,
- Task::EVENT_OPEN,
- Task::EVENT_MOVE_COLUMN,
- Task::EVENT_MOVE_POSITION,
- Comment::EVENT_UPDATE,
- Comment::EVENT_CREATE,
- SubTask::EVENT_UPDATE,
- SubTask::EVENT_CREATE,
- );
-
- $listener = new ProjectActivityListener($this->container);
-
- foreach ($events as $event_name) {
- $this->event->attach($event_name, $listener);
- }
- }
-
/**
* Get the event html content
*
@@ -163,8 +132,10 @@ class ProjectActivity extends Base
*/
public function getContent(array $params)
{
- $tpl = new Template;
- return $tpl->load('event/'.str_replace('.', '_', $params['event_name']), $params);
+ return $this->template->render(
+ 'event/'.str_replace('.', '_', $params['event_name']),
+ $params
+ );
}
/**
@@ -203,4 +174,20 @@ class ProjectActivity extends Base
return '';
}
}
+
+ /**
+ * Decode event data, supports unserialize() and json_decode()
+ *
+ * @access public
+ * @param string $data Serialized data
+ * @return array
+ */
+ public function decode($data)
+ {
+ if ($data{0} === 'a') {
+ return unserialize($data);
+ }
+
+ return json_decode($data, true) ?: array();
+ }
}
diff --git a/sources/app/Model/ProjectDailySummary.php b/sources/app/Model/ProjectDailySummary.php
index 0ed3c02..0a06bbd 100644
--- a/sources/app/Model/ProjectDailySummary.php
+++ b/sources/app/Model/ProjectDailySummary.php
@@ -2,9 +2,6 @@
namespace Model;
-use Core\Template;
-use Event\ProjectDailySummaryListener;
-
/**
* Project daily summary
*
@@ -157,25 +154,4 @@ class ProjectDailySummary extends Base
return $metrics;
}
-
- /**
- * Attach events to be able to record the metrics
- *
- * @access public
- */
- public function attachEvents()
- {
- $events = array(
- Task::EVENT_CREATE,
- Task::EVENT_CLOSE,
- Task::EVENT_OPEN,
- Task::EVENT_MOVE_COLUMN,
- );
-
- $listener = new ProjectDailySummaryListener($this->container);
-
- foreach ($events as $event_name) {
- $this->event->attach($event_name, $listener);
- }
- }
}
diff --git a/sources/app/Model/ProjectPaginator.php b/sources/app/Model/ProjectPaginator.php
index 9f1c39f..68b216b 100644
--- a/sources/app/Model/ProjectPaginator.php
+++ b/sources/app/Model/ProjectPaginator.php
@@ -38,9 +38,10 @@ class ProjectPaginator extends Base
foreach ($projects as &$project) {
$project['columns'] = $this->board->getColumns($project['id']);
+ $stats = $this->board->getColumnStats($project['id']);
foreach ($project['columns'] as &$column) {
- $column['nb_tasks'] = $this->taskFinder->countByColumnId($project['id'], $column['id']);
+ $column['nb_tasks'] = isset($stats[$column['id']]) ? $stats[$column['id']] : 0;
}
}
diff --git a/sources/app/Model/ProjectPermission.php b/sources/app/Model/ProjectPermission.php
index 8984ef3..02f3b42 100644
--- a/sources/app/Model/ProjectPermission.php
+++ b/sources/app/Model/ProjectPermission.php
@@ -85,6 +85,27 @@ class ProjectPermission extends Base
return $this->user->prepareList($users);
}
+ /**
+ * Get a list of owners for a project
+ *
+ * @access public
+ * @param integer $project_id Project id
+ * @return array
+ */
+ public function getManagers($project_id)
+ {
+ $users = $this->db
+ ->table(self::TABLE)
+ ->join(User::TABLE, 'id', 'user_id')
+ ->eq('project_id', $project_id)
+ ->eq('is_owner', 1)
+ ->asc('username')
+ ->columns(User::TABLE.'.id', User::TABLE.'.username', User::TABLE.'.name')
+ ->findAll();
+
+ return $this->user->prepareList($users);
+ }
+
/**
* Get allowed and not allowed users for a project
*
@@ -97,11 +118,13 @@ class ProjectPermission extends Base
$users = array(
'allowed' => array(),
'not_allowed' => array(),
+ 'managers' => array(),
);
$all_users = $this->user->getList();
$users['allowed'] = $this->getMembers($project_id);
+ $users['managers'] = $this->getManagers($project_id);
foreach ($all_users as $user_id => $username) {
@@ -114,14 +137,14 @@ class ProjectPermission extends Base
}
/**
- * Allow a specific user for a given project
+ * Add a new project member
*
* @access public
* @param integer $project_id Project id
* @param integer $user_id User id
* @return bool
*/
- public function allowUser($project_id, $user_id)
+ public function addMember($project_id, $user_id)
{
return $this->db
->table(self::TABLE)
@@ -129,14 +152,14 @@ class ProjectPermission extends Base
}
/**
- * Revoke a specific user for a given project
+ * Remove a member
*
* @access public
* @param integer $project_id Project id
* @param integer $user_id User id
* @return bool
*/
- public function revokeUser($project_id, $user_id)
+ public function revokeMember($project_id, $user_id)
{
return $this->db
->table(self::TABLE)
@@ -145,6 +168,39 @@ class ProjectPermission extends Base
->remove();
}
+ /**
+ * Add a project manager
+ *
+ * @access public
+ * @param integer $project_id Project id
+ * @param integer $user_id User id
+ * @return bool
+ */
+ public function addManager($project_id, $user_id)
+ {
+ return $this->db
+ ->table(self::TABLE)
+ ->save(array('project_id' => $project_id, 'user_id' => $user_id, 'is_owner' => 1));
+ }
+
+ /**
+ * Change the role of a member
+ *
+ * @access public
+ * @param integer $project_id Project id
+ * @param integer $user_id User id
+ * @param integer $is_owner Is user owner of the project
+ * @return bool
+ */
+ public function changeRole($project_id, $user_id, $is_owner)
+ {
+ return $this->db
+ ->table(self::TABLE)
+ ->eq('project_id', $project_id)
+ ->eq('user_id', $user_id)
+ ->update(array('is_owner' => (int) $is_owner));
+ }
+
/**
* Check if a specific user is member of a project
*
@@ -159,11 +215,29 @@ class ProjectPermission extends Base
return true;
}
- return (bool) $this->db
+ return $this->db
->table(self::TABLE)
->eq('project_id', $project_id)
->eq('user_id', $user_id)
- ->count();
+ ->count() === 1;
+ }
+
+ /**
+ * Check if a specific user is manager of a given project
+ *
+ * @access public
+ * @param integer $project_id Project id
+ * @param integer $user_id User id
+ * @return bool
+ */
+ public function isManager($project_id, $user_id)
+ {
+ return $this->db
+ ->table(self::TABLE)
+ ->eq('project_id', $project_id)
+ ->eq('user_id', $user_id)
+ ->eq('is_owner', 1)
+ ->count() === 1;
}
/**
@@ -188,28 +262,11 @@ class ProjectPermission extends Base
*/
public function isEverybodyAllowed($project_id)
{
- return (bool) $this->db
+ return $this->db
->table(Project::TABLE)
->eq('id', $project_id)
->eq('is_everybody_allowed', 1)
- ->count();
- }
-
- /**
- * Check if a specific user is allowed to manage a project
- *
- * @access public
- * @param integer $project_id Project id
- * @param integer $user_id User id
- * @return bool
- */
- public function adminAllowed($project_id, $user_id)
- {
- if ($this->isUserAllowed($project_id, $user_id) && $this->project->isPrivate($project_id)) {
- return true;
- }
-
- return false;
+ ->count() === 1;
}
/**
@@ -241,7 +298,11 @@ class ProjectPermission extends Base
*/
public function getAllowedProjects($user_id)
{
- return $this->filterProjects($this->project->getListByStatus(Project::ACTIVE), $user_id, 'isUserAllowed');
+ if ($this->user->isAdmin($user_id)) {
+ return $this->project->getListByStatus(Project::ACTIVE);
+ }
+
+ return $this->getMemberProjects($user_id);
}
/**
@@ -253,23 +314,39 @@ class ProjectPermission extends Base
*/
public function getMemberProjects($user_id)
{
- return $this->filterProjects($this->project->getListByStatus(Project::ACTIVE), $user_id, 'isMember');
+ return $this->db
+ ->table(Project::TABLE)
+ ->eq('user_id', $user_id)
+ ->join(self::TABLE, 'project_id', 'id')
+ ->listing('projects.id', 'name');
}
/**
* Copy user access from a project to another one
*
- * @author Antonio Rabelo
- * @param integer $project_from Project Template
- * @return integer $project_to Project that receives the copy
+ * @param integer $project_src Project Template
+ * @return integer $project_dst Project that receives the copy
* @return boolean
*/
- public function duplicate($project_from, $project_to)
+ public function duplicate($project_src, $project_dst)
{
- $users = $this->getMembers($project_from);
+ $rows = $this->db
+ ->table(self::TABLE)
+ ->columns('project_id', 'user_id', 'is_owner')
+ ->eq('project_id', $project_src)
+ ->findAll();
- foreach ($users as $user_id => $name) {
- if (! $this->allowUser($project_to, $user_id)) {
+ foreach ($rows as $row) {
+
+ $result = $this->db
+ ->table(self::TABLE)
+ ->save(array(
+ 'project_id' => $project_dst,
+ 'user_id' => $row['user_id'],
+ 'is_owner' => (int) $row['is_owner'], // (int) for postgres
+ ));
+
+ if (! $result) {
return false;
}
}
@@ -291,6 +368,7 @@ class ProjectPermission extends Base
new Validators\Integer('project_id', t('This value must be an integer')),
new Validators\Required('user_id', t('The user id is required')),
new Validators\Integer('user_id', t('This value must be an integer')),
+ new Validators\Integer('is_owner', t('This value must be an integer')),
));
return array(
diff --git a/sources/app/Model/SubTask.php b/sources/app/Model/SubTask.php
index f301ad6..1c5d1bf 100644
--- a/sources/app/Model/SubTask.php
+++ b/sources/app/Model/SubTask.php
@@ -2,6 +2,7 @@
namespace Model;
+use Event\SubtaskEvent;
use SimpleValidator\Validator;
use SimpleValidator\Validators;
@@ -57,15 +58,11 @@ class SubTask extends Base
*/
public function getStatusList()
{
- $status = array(
+ return array(
self::STATUS_TODO => t('Todo'),
self::STATUS_INPROGRESS => t('In progress'),
self::STATUS_DONE => t('Done'),
);
-
- asort($status);
-
- return $status;
}
/**
@@ -146,7 +143,10 @@ class SubTask extends Base
$subtask_id = $this->persist(self::TABLE, $values);
if ($subtask_id) {
- $this->event->trigger(self::EVENT_CREATE, array('id' => $subtask_id) + $values);
+ $this->container['dispatcher']->dispatch(
+ self::EVENT_CREATE,
+ new SubtaskEvent(array('id' => $subtask_id) + $values)
+ );
}
return $subtask_id;
@@ -165,7 +165,10 @@ class SubTask extends Base
$result = $this->db->table(self::TABLE)->eq('id', $values['id'])->save($values);
if ($result) {
- $this->event->trigger(self::EVENT_UPDATE, $values);
+ $this->container['dispatcher']->dispatch(
+ self::EVENT_UPDATE,
+ new SubtaskEvent($values)
+ );
}
return $result;
@@ -220,6 +223,7 @@ class SubTask extends Base
$subtasks = $db->table(SubTask::TABLE)
->columns('title', 'time_estimated')
->eq('task_id', $src_task_id)
+ ->asc('id') // Explicit sorting for postgresql
->findAll();
foreach ($subtasks as &$subtask) {
diff --git a/sources/app/Model/SubtaskExport.php b/sources/app/Model/SubtaskExport.php
new file mode 100644
index 0000000..50b028e
--- /dev/null
+++ b/sources/app/Model/SubtaskExport.php
@@ -0,0 +1,119 @@
+subtask_status = $this->subTask->getStatusList();
+ $subtasks = $this->getSubtasks($project_id, $from, $to);
+ $results = array($this->getColumns());
+
+ foreach ($subtasks as $subtask) {
+ $results[] = $this->format($subtask);
+ }
+
+ return $results;
+ }
+
+ /**
+ * Get column titles
+ *
+ * @access public
+ * @return string[]
+ */
+ public function getColumns()
+ {
+ return array(
+ e('Subtask Id'),
+ e('Title'),
+ e('Status'),
+ e('Assignee'),
+ e('Time estimated'),
+ e('Time spent'),
+ e('Task Id'),
+ e('Task Title'),
+ );
+ }
+
+ /**
+ * Format the output of a subtask array
+ *
+ * @access public
+ * @param array $subtask Subtask properties
+ * @return array
+ */
+ public function format(array $subtask)
+ {
+ $values = array();
+ $values[] = $subtask['id'];
+ $values[] = $subtask['title'];
+ $values[] = $this->subtask_status[$subtask['status']];
+ $values[] = $subtask['assignee_name'] ?: $subtask['assignee_username'];
+ $values[] = $subtask['time_estimated'];
+ $values[] = $subtask['time_spent'];
+ $values[] = $subtask['task_id'];
+ $values[] = $subtask['task_title'];
+
+ return $values;
+ }
+
+ /**
+ * Get all subtasks for a given project
+ *
+ * @access public
+ * @param integer $task_id Task id
+ * @param mixed $from Start date (timestamp or user formatted date)
+ * @param mixed $to End date (timestamp or user formatted date)
+ * @return array
+ */
+ public function getSubtasks($project_id, $from, $to)
+ {
+ if (! is_numeric($from)) {
+ $from = $this->dateParser->resetDateToMidnight($this->dateParser->getTimestamp($from));
+ }
+
+ if (! is_numeric($to)) {
+ $to = $this->dateParser->resetDateToMidnight(strtotime('+1 day', $this->dateParser->getTimestamp($to)));
+ }
+
+ return $this->db->table(SubTask::TABLE)
+ ->eq('project_id', $project_id)
+ ->columns(
+ SubTask::TABLE.'.*',
+ User::TABLE.'.username AS assignee_username',
+ User::TABLE.'.name AS assignee_name',
+ Task::TABLE.'.title AS task_title'
+ )
+ ->gte('date_creation', $from)
+ ->lte('date_creation', $to)
+ ->join(Task::TABLE, 'id', 'task_id')
+ ->join(User::TABLE, 'id', 'user_id')
+ ->asc(SubTask::TABLE.'.id')
+ ->findAll();
+ }
+}
diff --git a/sources/app/Model/Swimlane.php b/sources/app/Model/Swimlane.php
new file mode 100644
index 0000000..069f14b
--- /dev/null
+++ b/sources/app/Model/Swimlane.php
@@ -0,0 +1,495 @@
+db->table(self::TABLE)->eq('id', $swimlane_id)->findOne();
+ }
+
+ /**
+ * Get the swimlane name by the id
+ *
+ * @access public
+ * @param integer $swimlane_id Swimlane id
+ * @return string
+ */
+ public function getNameById($swimlane_id)
+ {
+ return $this->db->table(self::TABLE)->eq('id', $swimlane_id)->findOneColumn('name') ?: '';
+ }
+
+ /**
+ * Get a swimlane id by the project and the name
+ *
+ * @access public
+ * @param integer $project_id Project id
+ * @param string $name Name
+ * @return integer
+ */
+ public function getIdByName($project_id, $name)
+ {
+ return (int) $this->db->table(self::TABLE)
+ ->eq('project_id', $project_id)
+ ->eq('name', $name)
+ ->findOneColumn('id');
+ }
+
+ /**
+ * Get default swimlane properties
+ *
+ * @access public
+ * @param integer $project_id Project id
+ * @return array
+ */
+ public function getDefault($project_id)
+ {
+ return $this->db->table(Project::TABLE)
+ ->eq('id', $project_id)
+ ->columns('id', 'default_swimlane', 'show_default_swimlane')
+ ->findOne();
+ }
+
+ /**
+ * Get all swimlanes for a given project
+ *
+ * @access public
+ * @param integer $project_id Project id
+ * @return array
+ */
+ public function getAll($project_id)
+ {
+ return $this->db->table(self::TABLE)
+ ->eq('project_id', $project_id)
+ ->orderBy('position', 'asc')
+ ->findAll();
+ }
+
+ /**
+ * Get the list of swimlanes by status
+ *
+ * @access public
+ * @param integer $project_id Project id
+ * @param integer $status Status
+ * @return array
+ */
+ public function getAllByStatus($project_id, $status = self::ACTIVE)
+ {
+ $query = $this->db->table(self::TABLE)
+ ->eq('project_id', $project_id)
+ ->eq('is_active', $status);
+
+ if ($status == self::ACTIVE) {
+ $query->asc('position');
+ }
+ else {
+ $query->asc('name');
+ }
+
+ return $query->findAll();
+ }
+
+ /**
+ * Get active swimlanes
+ *
+ * @access public
+ * @param integer $project_id Project id
+ * @return array
+ */
+ public function getSwimlanes($project_id)
+ {
+ $swimlanes = $this->db->table(self::TABLE)
+ ->columns('id', 'name')
+ ->eq('project_id', $project_id)
+ ->eq('is_active', self::ACTIVE)
+ ->orderBy('position', 'asc')
+ ->findAll();
+
+ $default_swimlane = $this->db->table(Project::TABLE)
+ ->eq('id', $project_id)
+ ->eq('show_default_swimlane', 1)
+ ->findOneColumn('default_swimlane');
+
+ if ($default_swimlane) {
+ array_unshift($swimlanes, array('id' => 0, 'name' => $default_swimlane));
+ }
+
+ return $swimlanes;
+ }
+
+ /**
+ * Get list of all swimlanes
+ *
+ * @access public
+ * @param integer $project_id Project id
+ * @return array
+ */
+ public function getSwimlanesList($project_id)
+ {
+ $swimlanes = $this->db->table(self::TABLE)
+ ->eq('project_id', $project_id)
+ ->orderBy('position', 'asc')
+ ->listing('id', 'name');
+
+ $swimlanes[0] = $this->db->table(Project::TABLE)
+ ->eq('id', $project_id)
+ ->findOneColumn('default_swimlane');
+
+ return $swimlanes;
+ }
+
+ /**
+ * Add a new swimlane
+ *
+ * @access public
+ * @param integer $project_id
+ * @param string $name
+ * @return bool
+ */
+ public function create($project_id, $name)
+ {
+ return $this->persist(self::TABLE, array(
+ 'project_id' => $project_id,
+ 'name' => $name,
+ 'position' => $this->getLastPosition($project_id),
+ ));
+ }
+
+ /**
+ * Rename a swimlane
+ *
+ * @access public
+ * @param integer $swimlane_id Swimlane id
+ * @param string $name Swimlane name
+ * @return bool
+ */
+ public function rename($swimlane_id, $name)
+ {
+ return $this->db->table(self::TABLE)
+ ->eq('id', $swimlane_id)
+ ->update(array('name' => $name));
+ }
+
+ /**
+ * Update the default swimlane
+ *
+ * @access public
+ * @param array $values Form values
+ * @return bool
+ */
+ public function updateDefault(array $values)
+ {
+ return $this->db
+ ->table(Project::TABLE)
+ ->eq('id', $values['id'])
+ ->update(array(
+ 'default_swimlane' => $values['default_swimlane'],
+ 'show_default_swimlane' => $values['show_default_swimlane'],
+ ));
+ }
+
+ /**
+ * Get the last position of a swimlane
+ *
+ * @access public
+ * @param integer $project_id
+ * @return integer
+ */
+ public function getLastPosition($project_id)
+ {
+ return $this->db->table(self::TABLE)
+ ->eq('project_id', $project_id)
+ ->eq('is_active', 1)
+ ->count() + 1;
+ }
+
+ /**
+ * Disable a swimlane
+ *
+ * @access public
+ * @param integer $project_id Project id
+ * @param integer $swimlane_id Swimlane id
+ * @return bool
+ */
+ public function disable($project_id, $swimlane_id)
+ {
+ $result = $this->db
+ ->table(self::TABLE)
+ ->eq('id', $swimlane_id)
+ ->update(array(
+ 'is_active' => self::INACTIVE,
+ 'position' => 0,
+ ));
+
+ if ($result) {
+ // Re-order positions
+ $this->updatePositions($project_id);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Enable a swimlane
+ *
+ * @access public
+ * @param integer $project_id Project id
+ * @param integer $swimlane_id Swimlane id
+ * @return bool
+ */
+ public function enable($project_id, $swimlane_id)
+ {
+ return $this->db
+ ->table(self::TABLE)
+ ->eq('id', $swimlane_id)
+ ->update(array(
+ 'is_active' => self::ACTIVE,
+ 'position' => $this->getLastPosition($project_id),
+ ));
+ }
+
+ /**
+ * Remove a swimlane
+ *
+ * @access public
+ * @param integer $project_id Project id
+ * @param integer $swimlane_id Swimlane id
+ * @return bool
+ */
+ public function remove($project_id, $swimlane_id)
+ {
+ $this->db->startTransaction();
+
+ // Tasks should not be assigned anymore to this swimlane
+ $this->db->table(Task::TABLE)->eq('swimlane_id', $swimlane_id)->update(array('swimlane_id' => 0));
+
+ if (! $this->db->table(self::TABLE)->eq('id', $swimlane_id)->remove()) {
+ $this->db->cancelTransaction();
+ return false;
+ }
+
+ // Re-order positions
+ $this->updatePositions($project_id);
+
+ $this->db->closeTransaction();
+
+ return true;
+ }
+
+ /**
+ * Update swimlane positions after disabling or removing a swimlane
+ *
+ * @access public
+ * @param integer $project_id Project id
+ * @return boolean
+ */
+ public function updatePositions($project_id)
+ {
+ $position = 0;
+ $swimlanes = $this->db->table(self::TABLE)
+ ->eq('project_id', $project_id)
+ ->eq('is_active', 1)
+ ->asc('position')
+ ->findAllByColumn('id');
+
+ if (! $swimlanes) {
+ return false;
+ }
+
+ foreach ($swimlanes as $swimlane_id) {
+ $this->db->table(self::TABLE)
+ ->eq('id', $swimlane_id)
+ ->update(array('position' => ++$position));
+ }
+
+ return true;
+ }
+
+ /**
+ * Move a swimlane down, increment the position value
+ *
+ * @access public
+ * @param integer $project_id Project id
+ * @param integer $swimlane_id Swimlane id
+ * @return boolean
+ */
+ public function moveDown($project_id, $swimlane_id)
+ {
+ $swimlanes = $this->db->table(self::TABLE)
+ ->eq('project_id', $project_id)
+ ->eq('is_active', self::ACTIVE)
+ ->asc('position')
+ ->listing('id', 'position');
+
+ $positions = array_flip($swimlanes);
+
+ if (isset($swimlanes[$swimlane_id]) && $swimlanes[$swimlane_id] < count($swimlanes)) {
+
+ $position = ++$swimlanes[$swimlane_id];
+ $swimlanes[$positions[$position]]--;
+
+ $this->db->startTransaction();
+ $this->db->table(self::TABLE)->eq('id', $swimlane_id)->update(array('position' => $position));
+ $this->db->table(self::TABLE)->eq('id', $positions[$position])->update(array('position' => $swimlanes[$positions[$position]]));
+ $this->db->closeTransaction();
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Move a swimlane up, decrement the position value
+ *
+ * @access public
+ * @param integer $project_id Project id
+ * @param integer $swimlane_id Swimlane id
+ * @return boolean
+ */
+ public function moveUp($project_id, $swimlane_id)
+ {
+ $swimlanes = $this->db->table(self::TABLE)
+ ->eq('project_id', $project_id)
+ ->eq('is_active', self::ACTIVE)
+ ->asc('position')
+ ->listing('id', 'position');
+
+ $positions = array_flip($swimlanes);
+
+ if (isset($swimlanes[$swimlane_id]) && $swimlanes[$swimlane_id] > 1) {
+
+ $position = --$swimlanes[$swimlane_id];
+ $swimlanes[$positions[$position]]++;
+
+ $this->db->startTransaction();
+ $this->db->table(self::TABLE)->eq('id', $swimlane_id)->update(array('position' => $position));
+ $this->db->table(self::TABLE)->eq('id', $positions[$position])->update(array('position' => $swimlanes[$positions[$position]]));
+ $this->db->closeTransaction();
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Validate creation
+ *
+ * @access public
+ * @param array $values Form values
+ * @return array $valid, $errors [0] = Success or not, [1] = List of errors
+ */
+ public function validateCreation(array $values)
+ {
+ $rules = array(
+ new Validators\Required('project_id', t('The project id is required')),
+ new Validators\Required('name', t('The name is required')),
+ );
+
+ $v = new Validator($values, array_merge($rules, $this->commonValidationRules()));
+
+ return array(
+ $v->execute(),
+ $v->getErrors()
+ );
+ }
+
+ /**
+ * Validate modification
+ *
+ * @access public
+ * @param array $values Form values
+ * @return array $valid, $errors [0] = Success or not, [1] = List of errors
+ */
+ public function validateModification(array $values)
+ {
+ $rules = array(
+ new Validators\Required('id', t('The id is required')),
+ new Validators\Required('name', t('The name is required')),
+ );
+
+ $v = new Validator($values, array_merge($rules, $this->commonValidationRules()));
+
+ return array(
+ $v->execute(),
+ $v->getErrors()
+ );
+ }
+
+ /**
+ * Validate default swimlane modification
+ *
+ * @access public
+ * @param array $values Form values
+ * @return array $valid, $errors [0] = Success or not, [1] = List of errors
+ */
+ public function validateDefaultModification(array $values)
+ {
+ $rules = array(
+ new Validators\Required('id', t('The id is required')),
+ new Validators\Required('default_swimlane', t('The name is required')),
+ );
+
+ $v = new Validator($values, array_merge($rules, $this->commonValidationRules()));
+
+ return array(
+ $v->execute(),
+ $v->getErrors()
+ );
+ }
+
+ /**
+ * Common validation rules
+ *
+ * @access private
+ * @return array
+ */
+ private function commonValidationRules()
+ {
+ return array(
+ new Validators\Integer('id', t('The id must be an integer')),
+ new Validators\Integer('project_id', t('The project id must be an integer')),
+ new Validators\MaxLength('name', t('The maximum length is %d characters', 50), 50)
+ );
+ }
+}
diff --git a/sources/app/Model/Task.php b/sources/app/Model/Task.php
index a745f30..bc2913e 100644
--- a/sources/app/Model/Task.php
+++ b/sources/app/Model/Task.php
@@ -30,8 +30,10 @@ class Task extends Base
*
* @var string
*/
+ const EVENT_MOVE_PROJECT = 'task.move.project';
const EVENT_MOVE_COLUMN = 'task.move.column';
const EVENT_MOVE_POSITION = 'task.move.position';
+ const EVENT_MOVE_SWIMLANE = 'task.move.swimlane';
const EVENT_UPDATE = 'task.update';
const EVENT_CREATE = 'task.create';
const EVENT_CLOSE = 'task.close';
diff --git a/sources/app/Model/TaskCreation.php b/sources/app/Model/TaskCreation.php
index 320bcb9..17e5ff7 100644
--- a/sources/app/Model/TaskCreation.php
+++ b/sources/app/Model/TaskCreation.php
@@ -2,6 +2,8 @@
namespace Model;
+use Event\TaskEvent;
+
/**
* Task Creation
*
@@ -19,10 +21,14 @@ class TaskCreation extends Base
*/
public function create(array $values)
{
+ if (! $this->project->exists($values['project_id'])) {
+ return 0;
+ }
+
$this->prepare($values);
$task_id = $this->persist(Task::TABLE, $values);
- if ($task_id) {
+ if ($task_id !== false) {
$this->fireEvents($task_id, $values);
}
@@ -39,7 +45,7 @@ class TaskCreation extends Base
{
$this->dateParser->convert($values, array('date_due', 'date_started'));
$this->removeFields($values, array('another_task'));
- $this->resetFields($values, array('owner_id', 'owner_id', 'date_due', 'score', 'category_id', 'time_estimated'));
+ $this->resetFields($values, array('owner_id', 'swimlane_id', 'date_due', 'score', 'category_id', 'time_estimated'));
if (empty($values['column_id'])) {
$values['column_id'] = $this->board->getFirstColumn($values['project_id']);
@@ -49,9 +55,14 @@ class TaskCreation extends Base
$values['color_id'] = $this->color->getDefaultColor();
}
+ if (empty($values['title'])) {
+ $values['title'] = t('Untitled');
+ }
+
+ $values['swimlane_id'] = empty($values['swimlane_id']) ? 0 : $values['swimlane_id'];
$values['date_creation'] = time();
$values['date_modification'] = $values['date_creation'];
- $values['position'] = $this->taskFinder->countByColumnId($values['project_id'], $values['column_id']) + 1;
+ $values['position'] = $this->taskFinder->countByColumnAndSwimlaneId($values['project_id'], $values['column_id'], $values['swimlane_id']) + 1;
}
/**
@@ -64,7 +75,7 @@ class TaskCreation extends Base
private function fireEvents($task_id, array $values)
{
$values['task_id'] = $task_id;
- $this->event->trigger(Task::EVENT_CREATE_UPDATE, $values);
- $this->event->trigger(Task::EVENT_CREATE, $values);
+ $this->container['dispatcher']->dispatch(Task::EVENT_CREATE_UPDATE, new TaskEvent($values));
+ $this->container['dispatcher']->dispatch(Task::EVENT_CREATE, new TaskEvent($values));
}
}
diff --git a/sources/app/Model/TaskDuplication.php b/sources/app/Model/TaskDuplication.php
index ab7a57f..172edb9 100644
--- a/sources/app/Model/TaskDuplication.php
+++ b/sources/app/Model/TaskDuplication.php
@@ -2,6 +2,8 @@
namespace Model;
+use Event\TaskEvent;
+
/**
* Task Duplication
*
@@ -27,6 +29,7 @@ class TaskDuplication extends Base
'score',
'category_id',
'time_estimated',
+ 'swimlane_id',
);
/**
@@ -79,10 +82,18 @@ class TaskDuplication extends Base
$values['position'] = $this->taskFinder->countByColumnId($project_id, $values['column_id']) + 1;
$values['owner_id'] = $task['owner_id'];
$values['category_id'] = $task['category_id'];
+ $values['swimlane_id'] = $task['swimlane_id'];
$this->checkDestinationProjectValues($values);
- return $this->db->table(Task::TABLE)->eq('id', $task['id'])->update($values);
+ if ($this->db->table(Task::TABLE)->eq('id', $task['id'])->update($values)) {
+ $this->container['dispatcher']->dispatch(
+ Task::EVENT_MOVE_PROJECT,
+ new TaskEvent(array_merge($task, $values, array('task_id' => $task['id'])))
+ );
+ }
+
+ return true;
}
/**
@@ -100,8 +111,18 @@ class TaskDuplication extends Base
// Check if the category exists for the destination project
if ($values['category_id'] > 0) {
- $category_name = $this->category->getNameById($values['category_id']);
- $values['category_id'] = $this->category->getIdByName($values['project_id'], $category_name);
+ $values['category_id'] = $this->category->getIdByName(
+ $values['project_id'],
+ $this->category->getNameById($values['category_id'])
+ );
+ }
+
+ // Check if the swimlane exists for the destination project
+ if ($values['swimlane_id'] > 0) {
+ $values['swimlane_id'] = $this->swimlane->getIdByName(
+ $values['project_id'],
+ $this->swimlane->getNameById($values['swimlane_id'])
+ );
}
}
diff --git a/sources/app/Model/TaskExport.php b/sources/app/Model/TaskExport.php
index b929823..1592deb 100644
--- a/sources/app/Model/TaskExport.php
+++ b/sources/app/Model/TaskExport.php
@@ -24,10 +24,11 @@ class TaskExport extends Base
public function export($project_id, $from, $to)
{
$tasks = $this->getTasks($project_id, $from, $to);
+ $swimlanes = $this->swimlane->getSwimlanesList($project_id);
$results = array($this->getColumns());
foreach ($tasks as &$task) {
- $results[] = array_values($this->format($task));
+ $results[] = array_values($this->format($task, $swimlanes));
}
return $results;
@@ -50,6 +51,7 @@ class TaskExport extends Base
projects.name AS project_name,
tasks.is_active,
project_has_categories.name AS category_name,
+ tasks.swimlane_id,
columns.title AS column_title,
tasks.position,
tasks.color_id,
@@ -71,6 +73,7 @@ class TaskExport extends Base
LEFT JOIN columns ON columns.id = tasks.column_id
LEFT JOIN projects ON projects.id = tasks.project_id
WHERE tasks.date_creation >= ? AND tasks.date_creation <= ? AND tasks.project_id = ?
+ ORDER BY tasks.id ASC
';
if (! is_numeric($from)) {
@@ -89,15 +92,18 @@ class TaskExport extends Base
* Format the output of a task array
*
* @access public
- * @param array $task Task properties
+ * @param array $task Task properties
+ * @param array $swimlanes List of swimlanes
* @return array
*/
- public function format(array &$task)
+ public function format(array &$task, array &$swimlanes)
{
$colors = $this->color->getList();
$task['is_active'] = $task['is_active'] == Task::STATUS_OPEN ? e('Open') : e('Closed');
$task['color_id'] = $colors[$task['color_id']];
+ $task['score'] = $task['score'] ?: 0;
+ $task['swimlane_id'] = isset($swimlanes[$task['swimlane_id']]) ? $swimlanes[$task['swimlane_id']] : '?';
$this->dateParser->format($task, array('date_due', 'date_modification', 'date_creation', 'date_started', 'date_completed'), 'Y-m-d');
@@ -108,7 +114,7 @@ class TaskExport extends Base
* Get column titles
*
* @access public
- * @return array
+ * @return string[]
*/
public function getColumns()
{
@@ -117,6 +123,7 @@ class TaskExport extends Base
e('Project'),
e('Status'),
e('Category'),
+ e('Swimlane'),
e('Column'),
e('Position'),
e('Color'),
diff --git a/sources/app/Model/TaskFinder.php b/sources/app/Model/TaskFinder.php
index 0e58102..eb86fe3 100644
--- a/sources/app/Model/TaskFinder.php
+++ b/sources/app/Model/TaskFinder.php
@@ -38,6 +38,7 @@ class TaskFinder extends Base
'tasks.color_id',
'tasks.project_id',
'tasks.column_id',
+ 'tasks.swimlane_id',
'tasks.owner_id',
'tasks.creator_id',
'tasks.position',
@@ -54,13 +55,17 @@ class TaskFinder extends Base
* Get all tasks shown on the board (sorted by position)
*
* @access public
- * @param integer $project_id Project id
+ * @param integer $project_id Project id
+ * @param integer $column_id Column id
+ * @param integer $swimlane_id Swimlane id
* @return array
*/
- public function getTasksOnBoard($project_id)
+ public function getTasksByColumnAndSwimlane($project_id, $column_id, $swimlane_id = 0)
{
return $this->getQuery()
->eq('project_id', $project_id)
+ ->eq('column_id', $column_id)
+ ->eq('swimlane_id', $swimlane_id)
->eq('is_active', Task::STATUS_OPEN)
->asc('tasks.position')
->findAll();
@@ -167,6 +172,7 @@ class TaskFinder extends Base
tasks.is_active,
tasks.score,
tasks.category_id,
+ tasks.swimlane_id,
project_has_categories.name AS category_name,
projects.name AS project_name,
columns.title AS column_title,
@@ -210,16 +216,35 @@ class TaskFinder extends Base
* @access public
* @param integer $project_id Project id
* @param integer $column_id Column id
- * @param array $status List of status id
* @return integer
*/
- public function countByColumnId($project_id, $column_id, array $status = array(Task::STATUS_OPEN))
+ public function countByColumnId($project_id, $column_id)
{
return $this->db
->table(Task::TABLE)
->eq('project_id', $project_id)
->eq('column_id', $column_id)
- ->in('is_active', $status)
+ ->in('is_active', 1)
+ ->count();
+ }
+
+ /**
+ * Count the number of tasks for a given column and swimlane
+ *
+ * @access public
+ * @param integer $project_id Project id
+ * @param integer $column_id Column id
+ * @param integer $swimlane_id Swimlane id
+ * @return integer
+ */
+ public function countByColumnAndSwimlaneId($project_id, $column_id, $swimlane_id)
+ {
+ return $this->db
+ ->table(Task::TABLE)
+ ->eq('project_id', $project_id)
+ ->eq('column_id', $column_id)
+ ->eq('swimlane_id', $swimlane_id)
+ ->in('is_active', 1)
->count();
}
diff --git a/sources/app/Model/TaskModification.php b/sources/app/Model/TaskModification.php
index b165ea2..dac5233 100644
--- a/sources/app/Model/TaskModification.php
+++ b/sources/app/Model/TaskModification.php
@@ -2,6 +2,8 @@
namespace Model;
+use Event\TaskEvent;
+
/**
* Task Modification
*
@@ -15,17 +17,16 @@ class TaskModification extends Base
*
* @access public
* @param array $values
- * @param boolean $fire_events
* @return boolean
*/
- public function update(array $values, $fire_events = true)
+ public function update(array $values)
{
$original_task = $this->taskFinder->getById($values['id']);
$this->prepare($values);
$result = $this->db->table(Task::TABLE)->eq('id', $original_task['id'])->update($values);
- if ($result && $fire_events) {
+ if ($result) {
$this->fireEvents($original_task, $values);
}
@@ -51,7 +52,7 @@ class TaskModification extends Base
}
foreach ($events as $event) {
- $this->event->trigger($event, $event_data);
+ $this->container['dispatcher']->dispatch($event, new TaskEvent($event_data));
}
}
diff --git a/sources/app/Model/TaskPaginator.php b/sources/app/Model/TaskPaginator.php
index 4ae3566..e810922 100644
--- a/sources/app/Model/TaskPaginator.php
+++ b/sources/app/Model/TaskPaginator.php
@@ -26,7 +26,7 @@ class TaskPaginator extends Base
{
return $this->taskFinder->getQuery()
->eq('project_id', $project_id)
- ->like('title', '%'.$search.'%')
+ ->ilike('title', '%'.$search.'%')
->offset($offset)
->limit($limit)
->orderBy($column, $direction)
@@ -45,7 +45,7 @@ class TaskPaginator extends Base
{
return $this->db->table(Task::TABLE)
->eq('project_id', $project_id)
- ->like('title', '%'.$search.'%')
+ ->ilike('title', '%'.$search.'%')
->count();
}
@@ -76,7 +76,6 @@ class TaskPaginator extends Base
*
* @access public
* @param integer $project_id Project id
- * @param array $status List of status id
* @return integer
*/
public function countClosedTasks($project_id)
diff --git a/sources/app/Model/TaskPermission.php b/sources/app/Model/TaskPermission.php
index 2ab154f..e2420e1 100644
--- a/sources/app/Model/TaskPermission.php
+++ b/sources/app/Model/TaskPermission.php
@@ -20,10 +20,10 @@ class TaskPermission extends Base
*/
public function canRemoveTask(array $task)
{
- if ($this->acl->isAdminUser()) {
+ if ($this->userSession->isAdmin() || $this->projectPermission->isManager($task['project_id'], $this->userSession->getId())) {
return true;
}
- else if (isset($task['creator_id']) && $task['creator_id'] == $this->acl->getUserId()) {
+ else if (isset($task['creator_id']) && $task['creator_id'] == $this->userSession->getId()) {
return true;
}
diff --git a/sources/app/Model/TaskPosition.php b/sources/app/Model/TaskPosition.php
index c23bc3b..2c271de 100644
--- a/sources/app/Model/TaskPosition.php
+++ b/sources/app/Model/TaskPosition.php
@@ -2,6 +2,8 @@
namespace Model;
+use Event\TaskEvent;
+
/**
* Task Position
*
@@ -18,20 +20,25 @@ class TaskPosition extends Base
* @param integer $task_id Task id
* @param integer $column_id Column id
* @param integer $position Position (must be >= 1)
+ * @param integer $swimlane_id Swimlane id
* @return boolean
*/
- public function movePosition($project_id, $task_id, $column_id, $position)
+ public function movePosition($project_id, $task_id, $column_id, $position, $swimlane_id = 0)
{
$original_task = $this->taskFinder->getById($task_id);
- $positions = $this->calculatePositions($project_id, $task_id, $column_id, $position);
- if ($positions === false || ! $this->savePositions($positions)) {
- return false;
+ $result = $this->calculateAndSave($project_id, $task_id, $column_id, $position, $swimlane_id);
+
+ if ($result) {
+
+ if ($original_task['swimlane_id'] != $swimlane_id) {
+ $this->calculateAndSave($project_id, 0, $column_id, 1, $original_task['swimlane_id']);
+ }
+
+ $this->fireEvents($original_task, $column_id, $position, $swimlane_id);
}
- $this->fireEvents($original_task, $column_id, $position);
-
- return true;
+ return $result;
}
/**
@@ -42,9 +49,10 @@ class TaskPosition extends Base
* @param integer $task_id Task id
* @param integer $column_id Column id
* @param integer $position Position (must be >= 1)
+ * @param integer $swimlane_id Swimlane id
* @return array|boolean
*/
- public function calculatePositions($project_id, $task_id, $column_id, $position)
+ public function calculatePositions($project_id, $task_id, $column_id, $position, $swimlane_id = 0)
{
// The position can't be lower than 1
if ($position < 1) {
@@ -59,10 +67,12 @@ class TaskPosition extends Base
$columns[$board_column_id] = $this->db->table(Task::TABLE)
->eq('is_active', 1)
+ ->eq('swimlane_id', $swimlane_id)
->eq('project_id', $project_id)
->eq('column_id', $board_column_id)
->neq('id', $task_id)
->asc('position')
+ ->asc('id') // Fix Postgresql unit test
->findAllByColumn('id');
}
@@ -72,7 +82,9 @@ class TaskPosition extends Base
}
// We put our task to the new position
- array_splice($columns[$column_id], $position - 1, 0, $task_id);
+ if ($task_id) {
+ array_splice($columns[$column_id], $position - 1, 0, $task_id);
+ }
return $columns;
}
@@ -82,11 +94,12 @@ class TaskPosition extends Base
*
* @access private
* @param array $columns Sorted tasks
+ * @param integer $swimlane_id Swimlane id
* @return boolean
*/
- private function savePositions(array $columns)
+ private function savePositions(array $columns, $swimlane_id)
{
- return $this->db->transaction(function ($db) use ($columns) {
+ return $this->db->transaction(function ($db) use ($columns, $swimlane_id) {
foreach ($columns as $column_id => $column) {
@@ -96,7 +109,8 @@ class TaskPosition extends Base
$result = $db->table(Task::TABLE)->eq('id', $task_id)->update(array(
'position' => $position,
- 'column_id' => $column_id
+ 'column_id' => $column_id,
+ 'swimlane_id' => $swimlane_id,
));
if (! $result) {
@@ -112,25 +126,52 @@ class TaskPosition extends Base
/**
* Fire events
*
- * @access public
+ * @access private
* @param array $task
* @param integer $new_column_id
* @param integer $new_position
+ * @param integer $new_swimlane_id
*/
- public function fireEvents(array $task, $new_column_id, $new_position)
+ private function fireEvents(array $task, $new_column_id, $new_position, $new_swimlane_id)
{
$event_data = array(
'task_id' => $task['id'],
'project_id' => $task['project_id'],
'position' => $new_position,
'column_id' => $new_column_id,
+ 'swimlane_id' => $new_swimlane_id,
);
- if ($task['column_id'] != $new_column_id) {
- $this->event->trigger(Task::EVENT_MOVE_COLUMN, $event_data);
+ if ($task['swimlane_id'] != $new_swimlane_id) {
+ $this->container['dispatcher']->dispatch(Task::EVENT_MOVE_SWIMLANE, new TaskEvent($event_data));
+ }
+ else if ($task['column_id'] != $new_column_id) {
+ $this->container['dispatcher']->dispatch(Task::EVENT_MOVE_COLUMN, new TaskEvent($event_data));
}
else if ($task['position'] != $new_position) {
- $this->event->trigger(Task::EVENT_MOVE_POSITION, $event_data);
+ $this->container['dispatcher']->dispatch(Task::EVENT_MOVE_POSITION, new TaskEvent($event_data));
}
}
+
+ /**
+ * Calculate the new position of all tasks
+ *
+ * @access private
+ * @param integer $project_id Project id
+ * @param integer $task_id Task id
+ * @param integer $column_id Column id
+ * @param integer $position Position (must be >= 1)
+ * @param integer $swimlane_id Swimlane id
+ * @return boolean
+ */
+ private function calculateAndSave($project_id, $task_id, $column_id, $position, $swimlane_id)
+ {
+ $positions = $this->calculatePositions($project_id, $task_id, $column_id, $position, $swimlane_id);
+
+ if ($positions === false || ! $this->savePositions($positions, $swimlane_id)) {
+ return false;
+ }
+
+ return true;
+ }
}
diff --git a/sources/app/Model/TaskStatus.php b/sources/app/Model/TaskStatus.php
index 99faffd..225b393 100644
--- a/sources/app/Model/TaskStatus.php
+++ b/sources/app/Model/TaskStatus.php
@@ -2,6 +2,8 @@
namespace Model;
+use Event\TaskEvent;
+
/**
* Task Status
*
@@ -84,9 +86,9 @@ class TaskStatus extends Base
));
if ($result) {
- $this->event->trigger(
+ $this->container['dispatcher']->dispatch(
$event,
- array('task_id' => $task_id) + $this->taskFinder->getById($task_id)
+ new TaskEvent(array('task_id' => $task_id) + $this->taskFinder->getById($task_id))
);
}
diff --git a/sources/app/Model/TaskValidator.php b/sources/app/Model/TaskValidator.php
index ecaf090..ae21ca2 100644
--- a/sources/app/Model/TaskValidator.php
+++ b/sources/app/Model/TaskValidator.php
@@ -29,6 +29,7 @@ class TaskValidator extends Base
new Validators\Integer('creator_id', t('This value must be an integer')),
new Validators\Integer('score', t('This value must be an integer')),
new Validators\Integer('category_id', t('This value must be an integer')),
+ new Validators\Integer('swimlane_id', t('This value must be an integer')),
new Validators\MaxLength('title', t('The maximum length is %d characters', 200), 200),
new Validators\Date('date_due', t('Invalid date'), $this->dateParser->getDateFormats()),
new Validators\Date('date_started', t('Invalid date'), $this->dateParser->getDateFormats()),
diff --git a/sources/app/Model/User.php b/sources/app/Model/User.php
index 8fdfa81..1bcc82b 100644
--- a/sources/app/Model/User.php
+++ b/sources/app/Model/User.php
@@ -28,6 +28,17 @@ class User extends Base
*/
const EVERYBODY_ID = -1;
+ /**
+ * Return the full name
+ *
+ * @param array $user User properties
+ * @return string
+ */
+ public function getFullname(array $user)
+ {
+ return $user['name'] ?: $user['username'];
+ }
+
/**
* Return true is the given user id is administrator
*
@@ -37,46 +48,12 @@ class User extends Base
*/
public function isAdmin($user_id)
{
- $result = $this->db
+ return $this->userSession->isAdmin() || // Avoid SQL query if connected
+ $this->db
->table(User::TABLE)
->eq('id', $user_id)
->eq('is_admin', 1)
- ->count();
-
- return $result > 0;
- }
-
- /**
- * Get the default project from the session
- *
- * @access public
- * @return integer
- */
- public function getFavoriteProjectId()
- {
- return isset($_SESSION['user']['default_project_id']) ? $_SESSION['user']['default_project_id'] : 0;
- }
-
- /**
- * Get the last seen project from the session
- *
- * @access public
- * @return integer
- */
- public function getLastSeenProjectId()
- {
- return empty($_SESSION['user']['last_show_project_id']) ? 0 : $_SESSION['user']['last_show_project_id'];
- }
-
- /**
- * Set the last seen project from the session
- *
- * @access public
- * @@param integer $project_id Project id
- */
- public function storeLastSeenProjectId($project_id)
- {
- $_SESSION['user']['last_show_project_id'] = (int) $project_id;
+ ->count() === 1;
}
/**
@@ -276,8 +253,8 @@ class User extends Base
$result = $this->db->table(self::TABLE)->eq('id', $values['id'])->update($values);
// If the user is connected refresh his session
- if (Session::isOpen() && $_SESSION['user']['id'] == $values['id']) {
- $this->updateSession();
+ if (Session::isOpen() && $this->userSession->getId() == $values['id']) {
+ $this->userSession->refresh();
}
return $result;
@@ -317,30 +294,6 @@ class User extends Base
});
}
- /**
- * Update user session information
- *
- * @access public
- * @param array $user User data
- */
- public function updateSession(array $user = array())
- {
- if (empty($user)) {
- $user = $this->getById($_SESSION['user']['id']);
- }
-
- if (isset($user['password'])) {
- unset($user['password']);
- }
-
- $user['id'] = (int) $user['id'];
- $user['default_project_id'] = (int) $user['default_project_id'];
- $user['is_admin'] = (bool) $user['is_admin'];
- $user['is_ldap_user'] = (bool) $user['is_ldap_user'];
-
- $_SESSION['user'] = $user;
- }
-
/**
* Common validation rules
*
@@ -457,7 +410,7 @@ class User extends Base
if ($v->execute()) {
// Check password
- if ($this->authentication->authenticate($_SESSION['user']['username'], $values['current_password'])) {
+ if ($this->authentication->authenticate($this->session['user']['username'], $values['current_password'])) {
return array(true, array());
}
else {
diff --git a/sources/app/Model/UserSession.php b/sources/app/Model/UserSession.php
new file mode 100644
index 0000000..6d9a2eb
--- /dev/null
+++ b/sources/app/Model/UserSession.php
@@ -0,0 +1,115 @@
+user->getById($this->userSession->getId());
+ }
+
+ if (isset($user['password'])) {
+ unset($user['password']);
+ }
+
+ $user['id'] = (int) $user['id'];
+ $user['default_project_id'] = (int) $user['default_project_id'];
+ $user['is_admin'] = (bool) $user['is_admin'];
+ $user['is_ldap_user'] = (bool) $user['is_ldap_user'];
+
+ $this->session['user'] = $user;
+ }
+
+ /**
+ * Return true if the logged user is admin
+ *
+ * @access public
+ * @return bool
+ */
+ public function isAdmin()
+ {
+ return isset($this->session['user']['is_admin']) && $this->session['user']['is_admin'] === true;
+ }
+
+ /**
+ * Get the connected user id
+ *
+ * @access public
+ * @return integer
+ */
+ public function getId()
+ {
+ return isset($this->session['user']['id']) ? (int) $this->session['user']['id'] : 0;
+ }
+
+ /**
+ * Check if the given user_id is the connected user
+ *
+ * @param integer $user_id User id
+ * @return boolean
+ */
+ public function isCurrentUser($user_id)
+ {
+ return $this->getId() == $user_id;
+ }
+
+ /**
+ * Check is the user is connected
+ *
+ * @access public
+ * @return bool
+ */
+ public function isLogged()
+ {
+ return ! empty($this->session['user']);
+ }
+
+ /**
+ * Get the last seen project from the session
+ *
+ * @access public
+ * @return integer
+ */
+ public function getLastSeenProjectId()
+ {
+ return empty($this->session['last_show_project_id']) ? 0 : $this->session['last_show_project_id'];
+ }
+
+ /**
+ * Get the default project from the session
+ *
+ * @access public
+ * @return integer
+ */
+ public function getFavoriteProjectId()
+ {
+ return isset($this->session['user']['default_project_id']) ? $this->session['user']['default_project_id'] : 0;
+ }
+
+ /**
+ * Set the last seen project from the session
+ *
+ * @access public
+ * @param integer $project_id Project id
+ */
+ public function storeLastSeenProjectId($project_id)
+ {
+ $this->session['last_show_project_id'] = (int) $project_id;
+ }
+}
diff --git a/sources/app/Model/Webhook.php b/sources/app/Model/Webhook.php
index 14d5068..7edffa6 100644
--- a/sources/app/Model/Webhook.php
+++ b/sources/app/Model/Webhook.php
@@ -2,8 +2,6 @@
namespace Model;
-use Event\WebhookListener;
-
/**
* Webhook model
*
@@ -33,87 +31,6 @@ class Webhook extends Base
*/
const HTTP_USER_AGENT = 'Kanboard Webhook';
- /**
- * URL to call for task creation
- *
- * @access private
- * @var string
- */
- private $url_task_creation = '';
-
- /**
- * URL to call for task modification
- *
- * @access private
- * @var string
- */
- private $url_task_modification = '';
-
- /**
- * Webook token
- *
- * @access private
- * @var string
- */
- private $token = '';
-
- /**
- * Attach events
- *
- * @access public
- */
- public function attachEvents()
- {
- $this->url_task_creation = $this->config->get('webhook_url_task_creation');
- $this->url_task_modification = $this->config->get('webhook_url_task_modification');
- $this->token = $this->config->get('webhook_token');
-
- if ($this->url_task_creation) {
- $this->attachCreateEvents();
- }
-
- if ($this->url_task_modification) {
- $this->attachUpdateEvents();
- }
- }
-
- /**
- * Attach events for task modification
- *
- * @access public
- */
- public function attachUpdateEvents()
- {
- $events = array(
- Task::EVENT_UPDATE,
- Task::EVENT_CLOSE,
- Task::EVENT_OPEN,
- Task::EVENT_MOVE_COLUMN,
- Task::EVENT_MOVE_POSITION,
- Task::EVENT_ASSIGNEE_CHANGE,
- );
-
- $listener = new WebhookListener($this->container);
- $listener->setUrl($this->url_task_modification);
-
- foreach ($events as $event_name) {
- $this->event->attach($event_name, $listener);
- }
- }
-
- /**
- * Attach events for task creation
- *
- * @access public
- */
- public function attachCreateEvents()
- {
- $listener = new WebhookListener($this->container);
- $listener->setUrl($this->url_task_creation);
-
- $this->event->attach(Task::EVENT_CREATE, $listener);
- }
-
/**
* Call the external URL
*
@@ -123,6 +40,8 @@ class Webhook extends Base
*/
public function notify($url, array $task)
{
+ $token = $this->config->get('webhook_token');
+
$headers = array(
'Connection: close',
'User-Agent: '.self::HTTP_USER_AGENT,
@@ -140,10 +59,10 @@ class Webhook extends Base
));
if (strpos($url, '?') !== false) {
- $url .= '&token='.$this->token;
+ $url .= '&token='.$token;
}
else {
- $url .= '?token='.$this->token;
+ $url .= '?token='.$token;
}
@file_get_contents($url, false, $context);
diff --git a/sources/app/Schema/Mysql.php b/sources/app/Schema/Mysql.php
index 52dbea5..05e8f14 100644
--- a/sources/app/Schema/Mysql.php
+++ b/sources/app/Schema/Mysql.php
@@ -5,7 +5,68 @@ namespace Schema;
use PDO;
use Core\Security;
-const VERSION = 36;
+const VERSION = 41;
+
+function version_41($pdo)
+{
+ $pdo->exec('ALTER TABLE users ADD COLUMN timezone VARCHAR(50)');
+ $pdo->exec('ALTER TABLE users ADD COLUMN language CHAR(5)');
+}
+
+function version_40($pdo)
+{
+ // Avoid some full table scans
+ $pdo->exec('CREATE INDEX users_admin_idx ON users(is_admin)');
+ $pdo->exec('CREATE INDEX columns_project_idx ON columns(project_id)');
+ $pdo->exec('CREATE INDEX tasks_project_idx ON tasks(project_id)');
+ $pdo->exec('CREATE INDEX swimlanes_project_idx ON swimlanes(project_id)');
+ $pdo->exec('CREATE INDEX categories_project_idx ON project_has_categories(project_id)');
+ $pdo->exec('CREATE INDEX subtasks_task_idx ON task_has_subtasks(task_id)');
+ $pdo->exec('CREATE INDEX files_task_idx ON task_has_files(task_id)');
+ $pdo->exec('CREATE INDEX comments_task_idx ON comments(task_id)');
+
+ // Set the ownership for all private projects
+ $rq = $pdo->prepare('SELECT id FROM projects WHERE is_private=1');
+ $rq->execute();
+ $project_ids = $rq->fetchAll(PDO::FETCH_COLUMN, 0);
+
+ $rq = $pdo->prepare('UPDATE project_has_users SET is_owner=1 WHERE project_id=?');
+
+ foreach ($project_ids as $project_id) {
+ $rq->execute(array($project_id));
+ }
+}
+
+function version_39($pdo)
+{
+ $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)');
+ $rq->execute(array('project_categories', ''));
+}
+
+function version_38($pdo)
+{
+ $pdo->exec("
+ CREATE TABLE swimlanes (
+ id INT NOT NULL AUTO_INCREMENT,
+ name VARCHAR(200) NOT NULL,
+ position INT DEFAULT 1,
+ is_active INT DEFAULT 1,
+ project_id INT,
+ PRIMARY KEY(id),
+ FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE,
+ UNIQUE (name, project_id)
+ ) ENGINE=InnoDB CHARSET=utf8
+ ");
+
+ $pdo->exec('ALTER TABLE tasks ADD COLUMN swimlane_id INT DEFAULT 0');
+ $pdo->exec("ALTER TABLE projects ADD COLUMN default_swimlane VARCHAR(200) DEFAULT '".t('Default swimlane')."'");
+ $pdo->exec("ALTER TABLE projects ADD COLUMN show_default_swimlane INT DEFAULT 1");
+}
+
+function version_37($pdo)
+{
+ $pdo->exec("ALTER TABLE project_has_users ADD COLUMN is_owner TINYINT(1) DEFAULT '0'");
+}
function version_36($pdo)
{
diff --git a/sources/app/Schema/Postgres.php b/sources/app/Schema/Postgres.php
index 9493e60..9d5aa7a 100644
--- a/sources/app/Schema/Postgres.php
+++ b/sources/app/Schema/Postgres.php
@@ -5,7 +5,67 @@ namespace Schema;
use PDO;
use Core\Security;
-const VERSION = 17;
+const VERSION = 22;
+
+function version_22($pdo)
+{
+ $pdo->exec('ALTER TABLE users ADD COLUMN timezone VARCHAR(50)');
+ $pdo->exec('ALTER TABLE users ADD COLUMN language CHAR(5)');
+}
+
+function version_21($pdo)
+{
+ // Avoid some full table scans
+ $pdo->exec('CREATE INDEX users_admin_idx ON users(is_admin)');
+ $pdo->exec('CREATE INDEX columns_project_idx ON columns(project_id)');
+ $pdo->exec('CREATE INDEX tasks_project_idx ON tasks(project_id)');
+ $pdo->exec('CREATE INDEX swimlanes_project_idx ON swimlanes(project_id)');
+ $pdo->exec('CREATE INDEX categories_project_idx ON project_has_categories(project_id)');
+ $pdo->exec('CREATE INDEX subtasks_task_idx ON task_has_subtasks(task_id)');
+ $pdo->exec('CREATE INDEX files_task_idx ON task_has_files(task_id)');
+ $pdo->exec('CREATE INDEX comments_task_idx ON comments(task_id)');
+
+ // Set the ownership for all private projects
+ $rq = $pdo->prepare("SELECT id FROM projects WHERE is_private='1'");
+ $rq->execute();
+ $project_ids = $rq->fetchAll(PDO::FETCH_COLUMN, 0);
+
+ $rq = $pdo->prepare('UPDATE project_has_users SET is_owner=1 WHERE project_id=?');
+
+ foreach ($project_ids as $project_id) {
+ $rq->execute(array($project_id));
+ }
+}
+
+function version_20($pdo)
+{
+ $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)');
+ $rq->execute(array('project_categories', ''));
+}
+
+function version_19($pdo)
+{
+ $pdo->exec("
+ CREATE TABLE swimlanes (
+ id SERIAL PRIMARY KEY,
+ name VARCHAR(200) NOT NULL,
+ position INTEGER DEFAULT 1,
+ is_active BOOLEAN DEFAULT '1',
+ project_id INTEGER,
+ FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE,
+ UNIQUE (name, project_id)
+ )
+ ");
+
+ $pdo->exec('ALTER TABLE tasks ADD COLUMN swimlane_id INTEGER DEFAULT 0');
+ $pdo->exec("ALTER TABLE projects ADD COLUMN default_swimlane VARCHAR(200) DEFAULT '".t('Default swimlane')."'");
+ $pdo->exec("ALTER TABLE projects ADD COLUMN show_default_swimlane BOOLEAN DEFAULT '1'");
+}
+
+function version_18($pdo)
+{
+ $pdo->exec("ALTER TABLE project_has_users ADD COLUMN is_owner BOOLEAN DEFAULT '0'");
+}
function version_17($pdo)
{
diff --git a/sources/app/Schema/Sqlite.php b/sources/app/Schema/Sqlite.php
index 82c2f41..c615606 100644
--- a/sources/app/Schema/Sqlite.php
+++ b/sources/app/Schema/Sqlite.php
@@ -5,7 +5,67 @@ namespace Schema;
use Core\Security;
use PDO;
-const VERSION = 35;
+const VERSION = 40;
+
+function version_40($pdo)
+{
+ $pdo->exec('ALTER TABLE users ADD COLUMN timezone TEXT');
+ $pdo->exec('ALTER TABLE users ADD COLUMN language TEXT');
+}
+
+function version_39($pdo)
+{
+ // Avoid some full table scans
+ $pdo->exec('CREATE INDEX users_admin_idx ON users(is_admin)');
+ $pdo->exec('CREATE INDEX columns_project_idx ON columns(project_id)');
+ $pdo->exec('CREATE INDEX tasks_project_idx ON tasks(project_id)');
+ $pdo->exec('CREATE INDEX swimlanes_project_idx ON swimlanes(project_id)');
+ $pdo->exec('CREATE INDEX categories_project_idx ON project_has_categories(project_id)');
+ $pdo->exec('CREATE INDEX subtasks_task_idx ON task_has_subtasks(task_id)');
+ $pdo->exec('CREATE INDEX files_task_idx ON task_has_files(task_id)');
+ $pdo->exec('CREATE INDEX comments_task_idx ON comments(task_id)');
+
+ // Set the ownership for all private projects
+ $rq = $pdo->prepare('SELECT id FROM projects WHERE is_private=1');
+ $rq->execute();
+ $project_ids = $rq->fetchAll(PDO::FETCH_COLUMN, 0);
+
+ $rq = $pdo->prepare('UPDATE project_has_users SET is_owner=1 WHERE project_id=?');
+
+ foreach ($project_ids as $project_id) {
+ $rq->execute(array($project_id));
+ }
+}
+
+function version_38($pdo)
+{
+ $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)');
+ $rq->execute(array('project_categories', ''));
+}
+
+function version_37($pdo)
+{
+ $pdo->exec("
+ CREATE TABLE swimlanes (
+ id INTEGER PRIMARY KEY,
+ name TEXT,
+ position INTEGER DEFAULT 1,
+ is_active INTEGER DEFAULT 1,
+ project_id INTEGER,
+ FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE,
+ UNIQUE (name, project_id)
+ )
+ ");
+
+ $pdo->exec('ALTER TABLE tasks ADD COLUMN swimlane_id INTEGER DEFAULT 0');
+ $pdo->exec("ALTER TABLE projects ADD COLUMN default_swimlane TEXT DEFAULT '".t('Default swimlane')."'");
+ $pdo->exec("ALTER TABLE projects ADD COLUMN show_default_swimlane INTEGER DEFAULT 1");
+}
+
+function version_36($pdo)
+{
+ $pdo->exec('ALTER TABLE project_has_users ADD COLUMN is_owner INTEGER DEFAULT "0"');
+}
function version_35($pdo)
{
@@ -457,7 +517,7 @@ function version_1($pdo)
$pdo->exec("
CREATE TABLE tasks (
id INTEGER PRIMARY KEY,
- title TEXT NOT NULL,
+ title TEXT NOCASE NOT NULL,
description TEXT,
date_creation INTEGER,
color_id TEXT,
diff --git a/sources/app/ServiceProvider/ClassProvider.php b/sources/app/ServiceProvider/ClassProvider.php
new file mode 100644
index 0000000..3177276
--- /dev/null
+++ b/sources/app/ServiceProvider/ClassProvider.php
@@ -0,0 +1,79 @@
+ array(
+ 'Acl',
+ 'Action',
+ 'Authentication',
+ 'Board',
+ 'Category',
+ 'Color',
+ 'Comment',
+ 'Config',
+ 'DateParser',
+ 'File',
+ 'LastLogin',
+ 'Notification',
+ 'Project',
+ 'ProjectActivity',
+ 'ProjectAnalytic',
+ 'ProjectDailySummary',
+ 'ProjectPaginator',
+ 'ProjectPermission',
+ 'SubTask',
+ 'SubtaskPaginator',
+ 'SubtaskExport',
+ 'Swimlane',
+ 'Task',
+ 'TaskCreation',
+ 'TaskDuplication',
+ 'TaskExport',
+ 'TaskFinder',
+ 'TaskModification',
+ 'TaskPaginator',
+ 'TaskPermission',
+ 'TaskPosition',
+ 'TaskStatus',
+ 'TaskValidator',
+ 'TimeTracking',
+ 'User',
+ 'UserSession',
+ 'Webhook',
+ ),
+ 'Core' => array(
+ 'Template',
+ 'Session',
+ 'MemoryCache',
+ 'FileCache',
+ ),
+ 'Integration' => array(
+ 'GitlabWebhook',
+ 'GithubWebhook',
+ )
+ );
+
+ public function register(Container $container)
+ {
+ foreach ($this->classes as $namespace => $classes) {
+
+ foreach ($classes as $name) {
+
+ $class = '\\'.$namespace.'\\'.$name;
+
+ $container[lcfirst($name)] = function ($c) use ($class) {
+ return new $class($c);
+ };
+ }
+ }
+ }
+}
diff --git a/sources/app/ServiceProvider/Database.php b/sources/app/ServiceProvider/DatabaseProvider.php
similarity index 83%
rename from sources/app/ServiceProvider/Database.php
rename to sources/app/ServiceProvider/DatabaseProvider.php
index 75e1f73..4218f5f 100644
--- a/sources/app/ServiceProvider/Database.php
+++ b/sources/app/ServiceProvider/DatabaseProvider.php
@@ -4,19 +4,21 @@ namespace ServiceProvider;
use Pimple\Container;
use Pimple\ServiceProviderInterface;
-use PicoDb\Database as Dbal;
+use PicoDb\Database;
-class Database implements ServiceProviderInterface
+class DatabaseProvider implements ServiceProviderInterface
{
public function register(Container $container)
{
$container['db'] = $this->getInstance();
+ $container['db']->stopwatch = DEBUG;
+ $container['db']->log_queries = DEBUG;
}
/**
* Setup the database driver and execute schema migration
*
- * @return PicoDb\Database
+ * @return \PicoDb\Database
*/
public function getInstance()
{
@@ -49,13 +51,13 @@ class Database implements ServiceProviderInterface
/**
* Setup the Sqlite database driver
*
- * @return PicoDb\Database
+ * @return \PicoDb\Database
*/
function getSqliteInstance()
{
require_once __DIR__.'/../Schema/Sqlite.php';
- return new Dbal(array(
+ return new Database(array(
'driver' => 'sqlite',
'filename' => DB_FILENAME
));
@@ -64,13 +66,13 @@ class Database implements ServiceProviderInterface
/**
* Setup the Mysql database driver
*
- * @return PicoDb\Database
+ * @return \PicoDb\Database
*/
function getMysqlInstance()
{
require_once __DIR__.'/../Schema/Mysql.php';
- return new Dbal(array(
+ return new Database(array(
'driver' => 'mysql',
'hostname' => DB_HOSTNAME,
'username' => DB_USERNAME,
@@ -83,13 +85,13 @@ class Database implements ServiceProviderInterface
/**
* Setup the Postgres database driver
*
- * @return PicoDb\Database
+ * @return \PicoDb\Database
*/
public function getPostgresInstance()
{
require_once __DIR__.'/../Schema/Postgres.php';
- return new Dbal(array(
+ return new Database(array(
'driver' => 'postgres',
'hostname' => DB_HOSTNAME,
'username' => DB_USERNAME,
diff --git a/sources/app/ServiceProvider/Event.php b/sources/app/ServiceProvider/Event.php
deleted file mode 100644
index 0436aa7..0000000
--- a/sources/app/ServiceProvider/Event.php
+++ /dev/null
@@ -1,15 +0,0 @@
-addSubscriber(new BootstrapSubscriber($container));
+ $container['dispatcher']->addSubscriber(new AuthSubscriber($container));
+ $container['dispatcher']->addSubscriber(new ProjectActivitySubscriber($container));
+ $container['dispatcher']->addSubscriber(new ProjectDailySummarySubscriber($container));
+ $container['dispatcher']->addSubscriber(new ProjectModificationDateSubscriber($container));
+ $container['dispatcher']->addSubscriber(new WebhookSubscriber($container));
+ $container['dispatcher']->addSubscriber(new NotificationSubscriber($container));
+
+ // Automatic actions
+ $container['action']->attachEvents();
+ }
+}
diff --git a/sources/app/ServiceProvider/Logging.php b/sources/app/ServiceProvider/Logging.php
deleted file mode 100644
index 9737cad..0000000
--- a/sources/app/ServiceProvider/Logging.php
+++ /dev/null
@@ -1,21 +0,0 @@
-pushHandler(new StreamHandler(__DIR__.'/../../data/debug.log', Logger::DEBUG));
- $logger->pushHandler(new SyslogHandler('kanboard', LOG_USER, Logger::DEBUG));
-
- $container['logger'] = $logger;
- }
-}
diff --git a/sources/app/ServiceProvider/LoggingProvider.php b/sources/app/ServiceProvider/LoggingProvider.php
new file mode 100644
index 0000000..5b2cf56
--- /dev/null
+++ b/sources/app/ServiceProvider/LoggingProvider.php
@@ -0,0 +1,24 @@
+setLogger(new Syslog('kanboard'));
+
+ if (DEBUG) {
+ $logger->setLogger(new File(__DIR__.'/../../data/debug.log'));
+ }
+
+ $container['logger'] = $logger;
+ }
+}
diff --git a/sources/app/ServiceProvider/Mailer.php b/sources/app/ServiceProvider/Mailer.php
deleted file mode 100644
index c82c16f..0000000
--- a/sources/app/ServiceProvider/Mailer.php
+++ /dev/null
@@ -1,36 +0,0 @@
-getInstance();
- }
-
- public function getInstance()
- {
- switch (MAIL_TRANSPORT) {
- case 'smtp':
- $transport = Swift_SmtpTransport::newInstance(MAIL_SMTP_HOSTNAME, MAIL_SMTP_PORT);
- $transport->setUsername(MAIL_SMTP_USERNAME);
- $transport->setPassword(MAIL_SMTP_PASSWORD);
- $transport->setEncryption(MAIL_SMTP_ENCRYPTION);
- break;
- case 'sendmail':
- $transport = Swift_SendmailTransport::newInstance(MAIL_SENDMAIL_COMMAND);
- break;
- default:
- $transport = Swift_MailTransport::newInstance();
- }
-
- return $transport;
- }
-}
diff --git a/sources/app/ServiceProvider/MailerProvider.php b/sources/app/ServiceProvider/MailerProvider.php
new file mode 100644
index 0000000..6469a73
--- /dev/null
+++ b/sources/app/ServiceProvider/MailerProvider.php
@@ -0,0 +1,33 @@
+setUsername(MAIL_SMTP_USERNAME);
+ $transport->setPassword(MAIL_SMTP_PASSWORD);
+ $transport->setEncryption(MAIL_SMTP_ENCRYPTION);
+ break;
+ case 'sendmail':
+ $transport = Swift_SendmailTransport::newInstance(MAIL_SENDMAIL_COMMAND);
+ break;
+ default:
+ $transport = Swift_MailTransport::newInstance();
+ }
+
+ return $transport;
+ };
+ }
+}
diff --git a/sources/app/Subscriber/AuthSubscriber.php b/sources/app/Subscriber/AuthSubscriber.php
new file mode 100644
index 0000000..161a7af
--- /dev/null
+++ b/sources/app/Subscriber/AuthSubscriber.php
@@ -0,0 +1,27 @@
+ array('onSuccess', 0),
+ );
+ }
+
+ public function onSuccess(AuthEvent $event)
+ {
+ $this->lastLogin->create(
+ $event->getAuthType(),
+ $event->getUserId(),
+ Request::getIpAddress(),
+ Request::getUserAgent()
+ );
+ }
+}
diff --git a/sources/app/Subscriber/Base.php b/sources/app/Subscriber/Base.php
new file mode 100644
index 0000000..abc051b
--- /dev/null
+++ b/sources/app/Subscriber/Base.php
@@ -0,0 +1,56 @@
+container = $container;
+ }
+
+ /**
+ * Load automatically models
+ *
+ * @access public
+ * @param string $name Model name
+ * @return mixed
+ */
+ public function __get($name)
+ {
+ return $this->container[$name];
+ }
+}
diff --git a/sources/app/Subscriber/BootstrapSubscriber.php b/sources/app/Subscriber/BootstrapSubscriber.php
new file mode 100644
index 0000000..35d0eff
--- /dev/null
+++ b/sources/app/Subscriber/BootstrapSubscriber.php
@@ -0,0 +1,23 @@
+ array('setup', 0),
+ 'api.bootstrap' => array('setup', 0),
+ 'console.bootstrap' => array('setup', 0),
+ );
+ }
+
+ public function setup()
+ {
+ $this->config->setupTranslations();
+ $this->config->setupTimezone();
+ }
+}
diff --git a/sources/app/Subscriber/NotificationSubscriber.php b/sources/app/Subscriber/NotificationSubscriber.php
new file mode 100644
index 0000000..1580f6d
--- /dev/null
+++ b/sources/app/Subscriber/NotificationSubscriber.php
@@ -0,0 +1,81 @@
+ 'task_creation',
+ Task::EVENT_UPDATE => 'task_update',
+ Task::EVENT_CLOSE => 'task_close',
+ Task::EVENT_OPEN => 'task_open',
+ Task::EVENT_MOVE_COLUMN => 'task_move_column',
+ Task::EVENT_MOVE_POSITION => 'task_move_position',
+ Task::EVENT_ASSIGNEE_CHANGE => 'task_assignee_change',
+ SubTask::EVENT_CREATE => 'subtask_creation',
+ SubTask::EVENT_UPDATE => 'subtask_update',
+ Comment::EVENT_CREATE => 'comment_creation',
+ Comment::EVENT_UPDATE => 'comment_update',
+ File::EVENT_CREATE => 'file_creation',
+ );
+
+ public static function getSubscribedEvents()
+ {
+ return array(
+ Task::EVENT_CREATE => array('execute', 0),
+ Task::EVENT_UPDATE => array('execute', 0),
+ Task::EVENT_CLOSE => array('execute', 0),
+ Task::EVENT_OPEN => array('execute', 0),
+ Task::EVENT_MOVE_COLUMN => array('execute', 0),
+ Task::EVENT_MOVE_POSITION => array('execute', 0),
+ Task::EVENT_ASSIGNEE_CHANGE => array('execute', 0),
+ SubTask::EVENT_CREATE => array('execute', 0),
+ SubTask::EVENT_UPDATE => array('execute', 0),
+ Comment::EVENT_CREATE => array('execute', 0),
+ Comment::EVENT_UPDATE => array('execute', 0),
+ File::EVENT_CREATE => array('execute', 0),
+ );
+ }
+
+ public function execute(GenericEvent $event, $event_name)
+ {
+ $values = $this->getTemplateData($event);
+ $users = $this->notification->getUsersList($values['task']['project_id']);
+
+ if ($users) {
+ $this->notification->sendEmails($this->templates[$event_name], $users, $values);
+ }
+ }
+
+ public function getTemplateData(GenericEvent $event)
+ {
+ $values = array();
+
+ switch (get_class($event)) {
+ case 'Event\TaskEvent':
+ $values['task'] = $this->taskFinder->getDetails($event['task_id']);
+ break;
+ case 'Event\SubtaskEvent':
+ $values['subtask'] = $this->subTask->getById($event['id'], true);
+ $values['task'] = $this->taskFinder->getDetails($event['task_id']);
+ break;
+ case 'Event\FileEvent':
+ $values['file'] = $event->getAll();
+ $values['task'] = $this->taskFinder->getDetails($event['task_id']);
+ break;
+ case 'Event\CommentEvent':
+ $values['comment'] = $this->comment->getById($event['id']);
+ $values['task'] = $this->taskFinder->getDetails($values['comment']['task_id']);
+ break;
+ }
+
+ return $values;
+ }
+}
diff --git a/sources/app/Subscriber/ProjectActivitySubscriber.php b/sources/app/Subscriber/ProjectActivitySubscriber.php
new file mode 100644
index 0000000..aae09ae
--- /dev/null
+++ b/sources/app/Subscriber/ProjectActivitySubscriber.php
@@ -0,0 +1,63 @@
+ array('execute', 0),
+ Task::EVENT_UPDATE => array('execute', 0),
+ Task::EVENT_CREATE => array('execute', 0),
+ Task::EVENT_CLOSE => array('execute', 0),
+ Task::EVENT_OPEN => array('execute', 0),
+ Task::EVENT_MOVE_COLUMN => array('execute', 0),
+ Task::EVENT_MOVE_POSITION => array('execute', 0),
+ Comment::EVENT_UPDATE => array('execute', 0),
+ Comment::EVENT_CREATE => array('execute', 0),
+ SubTask::EVENT_UPDATE => array('execute', 0),
+ SubTask::EVENT_CREATE => array('execute', 0),
+ );
+ }
+
+ public function execute(GenericEvent $event, $event_name)
+ {
+ // Executed only when someone is logged
+ if ($this->userSession->isLogged() && isset($event['task_id'])) {
+
+ $values = $this->getValues($event);
+
+ $this->projectActivity->createEvent(
+ $values['task']['project_id'],
+ $values['task']['id'],
+ $this->userSession->getId(),
+ $event_name,
+ $values
+ );
+ }
+ }
+
+ private function getValues(GenericEvent $event)
+ {
+ $values = array();
+ $values['task'] = $this->taskFinder->getDetails($event['task_id']);
+
+ switch (get_class($event)) {
+ case 'Event\SubtaskEvent':
+ $values['subtask'] = $this->subTask->getById($event['id'], true);
+ break;
+ case 'Event\CommentEvent':
+ $values['comment'] = $this->comment->getById($event['id']);
+ break;
+ }
+
+ return $values;
+ }
+}
diff --git a/sources/app/Subscriber/ProjectDailySummarySubscriber.php b/sources/app/Subscriber/ProjectDailySummarySubscriber.php
new file mode 100644
index 0000000..6d73773
--- /dev/null
+++ b/sources/app/Subscriber/ProjectDailySummarySubscriber.php
@@ -0,0 +1,27 @@
+ array('execute', 0),
+ Task::EVENT_CLOSE => array('execute', 0),
+ Task::EVENT_OPEN => array('execute', 0),
+ Task::EVENT_MOVE_COLUMN => array('execute', 0),
+ );
+ }
+
+ public function execute(TaskEvent $event)
+ {
+ if (isset($event['project_id'])) {
+ $this->projectDailySummary->updateTotals($event['project_id'], date('Y-m-d'));
+ }
+ }
+}
diff --git a/sources/app/Subscriber/ProjectModificationDateSubscriber.php b/sources/app/Subscriber/ProjectModificationDateSubscriber.php
new file mode 100644
index 0000000..4c5380f
--- /dev/null
+++ b/sources/app/Subscriber/ProjectModificationDateSubscriber.php
@@ -0,0 +1,31 @@
+ array('execute', 0),
+ Task::EVENT_CLOSE => array('execute', 0),
+ Task::EVENT_OPEN => array('execute', 0),
+ Task::EVENT_MOVE_SWIMLANE => array('execute', 0),
+ Task::EVENT_MOVE_COLUMN => array('execute', 0),
+ Task::EVENT_MOVE_POSITION => array('execute', 0),
+ Task::EVENT_MOVE_PROJECT => array('execute', 0),
+ Task::EVENT_ASSIGNEE_CHANGE => array('execute', 0),
+ );
+ }
+
+ public function execute(GenericEvent $event)
+ {
+ if (isset($event['project_id'])) {
+ $this->project->updateModificationDate($event['project_id']);
+ }
+ }
+}
diff --git a/sources/app/Subscriber/WebhookSubscriber.php b/sources/app/Subscriber/WebhookSubscriber.php
new file mode 100644
index 0000000..6b5abf1
--- /dev/null
+++ b/sources/app/Subscriber/WebhookSubscriber.php
@@ -0,0 +1,42 @@
+ array('onTaskCreation', 0),
+ Task::EVENT_UPDATE => array('onTaskModification', 0),
+ Task::EVENT_CLOSE => array('onTaskModification', 0),
+ Task::EVENT_OPEN => array('onTaskModification', 0),
+ Task::EVENT_MOVE_COLUMN => array('onTaskModification', 0),
+ Task::EVENT_MOVE_POSITION => array('onTaskModification', 0),
+ Task::EVENT_ASSIGNEE_CHANGE => array('onTaskModification', 0),
+ );
+ }
+
+ public function onTaskCreation(TaskEvent $event)
+ {
+ $this->executeRequest('webhook_url_task_creation', $event);
+ }
+
+ public function onTaskModification(TaskEvent $event)
+ {
+ $this->executeRequest('webhook_url_task_modification', $event);
+ }
+
+ public function executeRequest($parameter, TaskEvent $event)
+ {
+ $url = $this->config->get($parameter);
+
+ if (! empty($url)) {
+ $this->webhook->notify($url, $event->getAll());
+ }
+ }
+}
diff --git a/sources/app/Template/action/event.php b/sources/app/Template/action/event.php
index 565e900..67a65c1 100644
--- a/sources/app/Template/action/event.php
+++ b/sources/app/Template/action/event.php
@@ -3,15 +3,15 @@
= t('Choose an event') ?>
-