1
0
Fork 0
mirror of https://github.com/YunoHost-Apps/kanboard_ynh.git synced 2024-09-03 19:36:17 +02:00

Update kanboard v1.0.12

This commit is contained in:
mbugeia 2015-02-25 17:29:06 +01:00
parent 5188fc0571
commit 808c098171
392 changed files with 10259 additions and 5585 deletions

View file

@ -4,6 +4,7 @@ namespace Action;
use Integration\GitlabWebhook;
use Integration\GithubWebhook;
use Integration\BitbucketWebhook;
use Model\Task;
/**
@ -28,6 +29,7 @@ class TaskClose extends Base
GithubWebhook::EVENT_ISSUE_CLOSED,
GitlabWebhook::EVENT_COMMIT,
GitlabWebhook::EVENT_ISSUE_CLOSED,
BitbucketWebhook::EVENT_COMMIT,
);
}
@ -44,6 +46,7 @@ class TaskClose extends Base
case GithubWebhook::EVENT_ISSUE_CLOSED:
case GitlabWebhook::EVENT_COMMIT:
case GitlabWebhook::EVENT_ISSUE_CLOSED:
case BitbucketWebhook::EVENT_COMMIT:
return array();
default:
return array('column_id' => t('Column'));
@ -63,6 +66,7 @@ class TaskClose extends Base
case GithubWebhook::EVENT_ISSUE_CLOSED:
case GitlabWebhook::EVENT_COMMIT:
case GitlabWebhook::EVENT_ISSUE_CLOSED:
case BitbucketWebhook::EVENT_COMMIT:
return array('task_id');
default:
return array('task_id', 'column_id');
@ -95,6 +99,7 @@ class TaskClose extends Base
case GithubWebhook::EVENT_ISSUE_CLOSED:
case GitlabWebhook::EVENT_COMMIT:
case GitlabWebhook::EVENT_ISSUE_CLOSED:
case BitbucketWebhook::EVENT_COMMIT:
return true;
default:
return $data['column_id'] == $this->getParam('column_id');

View file

@ -0,0 +1,84 @@
<?php
namespace Action;
use Model\GithubWebhook;
use Model\Task;
/**
* Add a log of the triggering event to the task description.
*
* @package action
* @author Oren Ben-Kiki
*/
class TaskLogMoveAnotherColumn extends Base
{
/**
* Get the list of compatible events
*
* @access public
* @return array
*/
public function getCompatibleEvents()
{
return array(
Task::EVENT_MOVE_COLUMN,
);
}
/**
* Get the required parameter for the action (defined by the user)
*
* @access public
* @return array
*/
public function getActionRequiredParameters()
{
return array('column_id' => t('Column'));
}
/**
* Get the required parameter for the event
*
* @access public
* @return string[]
*/
public function getEventRequiredParameters()
{
return array('task_id', 'column_id');
}
/**
* Execute the action (append to the task description).
*
* @access public
* @param array $data Event data dictionary
* @return bool True if the action was executed or false when not executed
*/
public function doAction(array $data)
{
if (! $this->userSession->isLogged()) {
return false;
}
$column = $this->board->getColumn($data['column_id']);
return (bool) $this->comment->create(array(
'comment' => t('Moved to column %s', $column['title']),
'task_id' => $data['task_id'],
'user_id' => $this->userSession->getId(),
));
}
/**
* Check if the event data meet the action condition
*
* @access public
* @param array $data Event data dictionary
* @return bool
*/
public function hasRequiredCondition(array $data)
{
return $data['column_id'] == $this->getParam('column_id');
}
}

View file

@ -30,9 +30,14 @@ class Database extends Base
*/
public function authenticate($username, $password)
{
$user = $this->db->table(User::TABLE)->eq('username', $username)->eq('is_ldap_user', 0)->findOne();
$user = $this->db
->table(User::TABLE)
->eq('username', $username)
->eq('disable_login_form', 0)
->eq('is_ldap_user', 0)
->findOne();
if ($user && password_verify($password, $user['password'])) {
if (is_array($user) && password_verify($password, $user['password'])) {
$this->userSession->refresh($user);
$this->container['dispatcher']->dispatch('auth.success', new AuthEvent(self::AUTH_NAME, $user['id']));
return true;

View file

@ -29,6 +29,7 @@ class Ldap extends Base
*/
public function authenticate($username, $password)
{
$username = LDAP_USERNAME_CASE_SENSITIVE ? $username : strtolower($username);
$result = $this->findUser($username, $password);
if (is_array($result)) {
@ -199,11 +200,90 @@ class Ldap extends Base
return array(
'username' => $username,
'name' => isset($info[0][LDAP_ACCOUNT_FULLNAME][0]) ? $info[0][LDAP_ACCOUNT_FULLNAME][0] : '',
'email' => isset($info[0][LDAP_ACCOUNT_EMAIL][0]) ? $info[0][LDAP_ACCOUNT_EMAIL][0] : '',
'name' => $this->getFromInfo($info, LDAP_ACCOUNT_FULLNAME),
'email' => $this->getFromInfo($info, LDAP_ACCOUNT_EMAIL),
);
}
return false;
}
/**
* Retrieve info on LDAP user
*
* @param string $username Username
* @param string $email Email address
*/
public function lookup($username = null, $email = null)
{
$query = $this->getQuery($username, $email);
if ($query === false) {
return false;
}
// Connect and attempt anonymous bind
$ldap = $this->connect();
if (! is_resource($ldap) || ! $this->bind($ldap, null, null)) {
return false;
}
// Try to find user
$sr = @ldap_search($ldap, LDAP_ACCOUNT_BASE, $query, array(LDAP_ACCOUNT_FULLNAME, LDAP_ACCOUNT_EMAIL, LDAP_ACCOUNT_ID));
if ($sr === false) {
return false;
}
$info = ldap_get_entries($ldap, $sr);
// User not found
if (count($info) == 0 || $info['count'] == 0) {
return false;
}
// User id not retrieved: LDAP_ACCOUNT_ID not properly configured
if (! $username && ! isset($info[0][LDAP_ACCOUNT_ID][0])) {
return false;
}
return array(
'username' => $this->getFromInfo($info, LDAP_ACCOUNT_ID, $username),
'name' => $this->getFromInfo($info, LDAP_ACCOUNT_FULLNAME),
'email' => $this->getFromInfo($info, LDAP_ACCOUNT_EMAIL, $email),
);
}
/**
* Get the LDAP query to find a user
*
* @param string $username Username
* @param string $email Email address
*/
private function getQuery($username, $email)
{
if ($username && $email) {
return '(&('.sprintf(LDAP_USER_PATTERN, $username).')('.sprintf(LDAP_ACCOUNT_EMAIL, $email).')';
}
else if ($username) {
return sprintf(LDAP_USER_PATTERN, $username);
}
else if ($email) {
return '('.LDAP_ACCOUNT_EMAIL.'='.$email.')';
}
else {
return false;
}
}
/**
* Return a value from the LDAP info
*
* @param array $info LDAP info
* @param string $key Key
* @param string $default Default value if key not set in entry
* @return string
*/
private function getFromInfo($info, $key, $default = '')
{
return isset($info[0][$key][0]) ? $info[0][$key][0] : $default;
}
}

View file

@ -66,6 +66,7 @@ class ReverseProxy extends Base
'username' => $login,
'is_admin' => REVERSE_PROXY_DEFAULT_ADMIN === $login,
'is_ldap_user' => 1,
'disable_login_form' => 1,
));
}
}

View file

@ -2,7 +2,8 @@
namespace Controller;
use Model\SubTask as SubTaskModel;
use Model\Subtask as SubtaskModel;
use Model\Task as TaskModel;
/**
* Application controller
@ -22,164 +23,62 @@ class App extends Base
$this->response->text('OK');
}
/**
* User dashboard view for admins
*
* @access public
*/
public function dashboard()
{
$this->index($this->request->getIntegerParam('user_id'), 'dashboard');
}
/**
* Dashboard for the current user
*
* @access public
*/
public function index()
public function index($user_id = 0, $action = 'index')
{
$paginate = $this->request->getStringParam('paginate', 'userTasks');
$offset = $this->request->getIntegerParam('offset', 0);
$direction = $this->request->getStringParam('direction');
$order = $this->request->getStringParam('order');
$user_id = $this->userSession->getId();
$projects = $this->projectPermission->getMemberProjects($user_id);
$status = array(SubTaskModel::STATUS_TODO, SubtaskModel::STATUS_INPROGRESS);
$user_id = $user_id ?: $this->userSession->getId();
$projects = $this->projectPermission->getActiveMemberProjects($user_id);
$project_ids = array_keys($projects);
$params = array(
$task_paginator = $this->paginator
->setUrl('app', $action, array('pagination' => 'tasks'))
->setMax(10)
->setOrder('tasks.id')
->setQuery($this->taskFinder->getUserQuery($user_id))
->calculateOnlyIf($this->request->getStringParam('pagination') === 'tasks');
$subtask_paginator = $this->paginator
->setUrl('app', $action, array('pagination' => 'subtasks'))
->setMax(10)
->setOrder('tasks.id')
->setQuery($this->subtask->getUserQuery($user_id, $status))
->calculateOnlyIf($this->request->getStringParam('pagination') === 'subtasks');
$project_paginator = $this->paginator
->setUrl('app', $action, array('pagination' => 'projects'))
->setMax(10)
->setOrder('name')
->setQuery($this->project->getQueryColumnStats($project_ids))
->calculateOnlyIf($this->request->getStringParam('pagination') === 'projects');
$this->response->html($this->template->layout('app/dashboard', array(
'title' => t('Dashboard'),
'board_selector' => $this->projectPermission->getAllowedProjects($user_id),
'events' => $this->projectActivity->getProjects($project_ids, 10),
);
$params += $this->getTaskPagination($user_id, $paginate, $offset, $order, $direction);
$params += $this->getSubtaskPagination($user_id, $paginate, $offset, $order, $direction);
$params += $this->getProjectPagination($project_ids, $paginate, $offset, $order, $direction);
$this->response->html($this->template->layout('app/dashboard', $params));
'events' => $this->projectActivity->getProjects($project_ids, 5),
'task_paginator' => $task_paginator,
'subtask_paginator' => $subtask_paginator,
'project_paginator' => $project_paginator,
'user_id' => $user_id,
)));
}
/**
* 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)
{
$limit = 10;
if (! in_array($order, array('tasks.id', 'project_name', 'title', 'date_due'))) {
$order = 'tasks.id';
$direction = 'ASC';
}
if ($paginate === 'userTasks') {
$tasks = $this->taskPaginator->userTasks($user_id, $offset, $limit, $order, $direction);
}
else {
$offset = 0;
$tasks = $this->taskPaginator->userTasks($user_id, $offset, $limit);
}
return array(
'tasks' => $tasks,
'task_pagination' => array(
'controller' => 'app',
'action' => 'index',
'params' => array('paginate' => 'userTasks'),
'direction' => $direction,
'order' => $order,
'total' => $this->taskPaginator->countUserTasks($user_id),
'offset' => $offset,
'limit' => $limit,
)
);
}
/**
* 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)
{
$status = array(SubTaskModel::STATUS_TODO, SubTaskModel::STATUS_INPROGRESS);
$limit = 10;
if (! in_array($order, array('tasks.id', 'project_name', 'status', 'title'))) {
$order = 'tasks.id';
$direction = 'ASC';
}
if ($paginate === 'userSubtasks') {
$subtasks = $this->subtaskPaginator->userSubtasks($user_id, $status, $offset, $limit, $order, $direction);
}
else {
$offset = 0;
$subtasks = $this->subtaskPaginator->userSubtasks($user_id, $status, $offset, $limit);
}
return array(
'subtasks' => $subtasks,
'subtask_pagination' => array(
'controller' => 'app',
'action' => 'index',
'params' => array('paginate' => 'userSubtasks'),
'direction' => $direction,
'order' => $order,
'total' => $this->subtaskPaginator->countUserSubtasks($user_id, $status),
'offset' => $offset,
'limit' => $limit,
)
);
}
/**
* Get projects pagination
*
* @access public
* @param array $project_ids
* @param string $paginate
* @param integer $offset
* @param string $order
* @param string $direction
*/
private function getProjectPagination(array $project_ids, $paginate, $offset, $order, $direction)
{
$limit = 10;
if (! in_array($order, array('id', 'name'))) {
$order = 'name';
$direction = 'ASC';
}
if ($paginate === 'projectSummaries') {
$projects = $this->projectPaginator->projectSummaries($project_ids, $offset, $limit, $order, $direction);
}
else {
$offset = 0;
$projects = $this->projectPaginator->projectSummaries($project_ids, $offset, $limit);
}
return array(
'projects' => $projects,
'project_pagination' => array(
'controller' => 'app',
'action' => 'index',
'params' => array('paginate' => 'projectSummaries'),
'direction' => $direction,
'order' => $order,
'total' => count($project_ids),
'offset' => $offset,
'limit' => $limit,
)
);
}
/**
* Render Markdown Text and reply with the HTML Code
* Render Markdown text and reply with the HTML Code
*
* @access public
*/
@ -190,10 +89,34 @@ class App extends Base
if (empty($payload['text'])) {
$this->response->html('<p>'.t('Nothing to preview...').'</p>');
}
else {
$this->response->html(
$this->template->markdown($payload['text'])
);
}
$this->response->html($this->template->markdown($payload['text']));
}
/**
* Colors stylesheet
*
* @access public
*/
public function colors()
{
$this->response->css($this->color->getCss());
}
/**
* Task autocompletion (Ajax)
*
* @access public
*/
public function autocomplete()
{
$this->response->json(
$this->taskFilter
->create()
->filterByProjects($this->projectPermission->getActiveMemberProjectIds($this->userSession->getId()))
->excludeTasks(array($this->request->getIntegerParam('exclude_task_id')))
->filterByTitle($this->request->getStringParam('term'))
->toAutoCompletion()
);
}
}

View file

@ -17,8 +17,13 @@ use Symfony\Component\EventDispatcher\Event;
* @package controller
* @author Frederic Guillot
*
* @property \Core\Helper $helper
* @property \Core\Session $session
* @property \Core\Template $template
* @property \Core\Paginator $paginator
* @property \Integration\GithubWebhook $githubWebhook
* @property \Integration\GitlabWebhook $gitlabWebhook
* @property \Integration\BitbucketWebhook $bitbucketWebhook
* @property \Model\Acl $acl
* @property \Model\Authentication $authentication
* @property \Model\Action $action
@ -33,22 +38,29 @@ use Symfony\Component\EventDispatcher\Event;
* @property \Model\Notification $notification
* @property \Model\Project $project
* @property \Model\ProjectPermission $projectPermission
* @property \Model\ProjectDuplication $projectDuplication
* @property \Model\ProjectAnalytic $projectAnalytic
* @property \Model\ProjectActivity $projectActivity
* @property \Model\ProjectDailySummary $projectDailySummary
* @property \Model\SubTask $subTask
* @property \Model\Subtask $subtask
* @property \Model\Swimlane $swimlane
* @property \Model\Task $task
* @property \Model\Link $link
* @property \Model\TaskCreation $taskCreation
* @property \Model\TaskModification $taskModification
* @property \Model\TaskDuplication $taskDuplication
* @property \Model\TaskHistory $taskHistory
* @property \Model\TaskExport $taskExport
* @property \Model\TaskFinder $taskFinder
* @property \Model\TaskFilter $taskFilter
* @property \Model\TaskPosition $taskPosition
* @property \Model\TaskPermission $taskPermission
* @property \Model\TaskStatus $taskStatus
* @property \Model\TaskValidator $taskValidator
* @property \Model\TaskLink $taskLink
* @property \Model\CommentHistory $commentHistory
* @property \Model\SubtaskHistory $subtaskHistory
* @property \Model\SubtaskTimeTracking $subtaskTimeTracking
* @property \Model\TimeTracking $timeTracking
* @property \Model\User $user
* @property \Model\UserSession $userSession
@ -107,7 +119,7 @@ abstract class Base
}
$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']));
$this->container['logger']->debug('RENDERING={time}', array('time' => microtime(true) - @$_SERVER['REQUEST_TIME_FLOAT']));
}
}
@ -131,7 +143,7 @@ abstract class Base
private function sendHeaders($action)
{
// HTTP secure headers
$this->response->csp(array('style-src' => "'self' 'unsafe-inline'"));
$this->response->csp(array('style-src' => "'self' 'unsafe-inline'", 'img-src' => '*'));
$this->response->nosniff();
$this->response->xss();
@ -158,16 +170,19 @@ abstract class Base
$this->container['dispatcher']->dispatch('session.bootstrap', new Event);
if (! $this->acl->isPublicAction($controller, $action)) {
$this->handleAuthenticatedUser($controller, $action);
$this->handleAuthentication();
$this->handleAuthorization($controller, $action);
$this->session['has_subtask_inprogress'] = $this->subtask->hasSubtaskInProgress($this->userSession->getId());
}
}
/**
* Check page access and authentication
* Check authentication
*
* @access public
*/
public function handleAuthenticatedUser($controller, $action)
public function handleAuthentication()
{
if (! $this->authentication->isAuthenticated()) {
@ -177,8 +192,24 @@ abstract class Base
$this->response->redirect('?controller=user&action=login&redirect_query='.urlencode($this->request->getQueryString()));
}
}
if (! $this->acl->isAllowed($controller, $action, $this->request->getIntegerParam('project_id', 0))) {
/**
* Check page access and authorization
*
* @access public
*/
public function handleAuthorization($controller, $action)
{
$project_id = $this->request->getIntegerParam('project_id');
$task_id = $this->request->getIntegerParam('task_id');
// Allow urls without "project_id"
if ($task_id > 0 && $project_id === 0) {
$project_id = $this->taskFinder->getProjectId($task_id);
}
if (! $this->acl->isAllowed($controller, $action, $project_id)) {
$this->forbidden();
}
}
@ -280,7 +311,7 @@ abstract class Base
{
$task = $this->taskFinder->getDetails($this->request->getIntegerParam('task_id'));
if (! $task || $task['project_id'] != $this->request->getIntegerParam('project_id')) {
if (! $task) {
$this->notfound();
}

View file

@ -205,6 +205,7 @@ class Board extends Base
foreach ($columns as $column) {
$values['title['.$column['id'].']'] = $column['title'];
$values['description['.$column['id'].']'] = $column['description'];
$values['task_limit['.$column['id'].']'] = $column['task_limit'] ?: null;
}
@ -218,28 +219,39 @@ class Board extends Base
}
/**
* Validate and update a board
* Display a form to edit a board
*
* @access public
*/
public function update()
public function editColumn(array $values = array(), array $errors = array())
{
$project = $this->getProject();
$columns = $this->board->getColumns($project['id']);
$data = $this->request->getValues();
$values = $columns_list = array();
$column = $this->board->getColumn($this->request->getIntegerParam('column_id'));
foreach ($columns as $column) {
$columns_list[$column['id']] = $column['title'];
$values['title['.$column['id'].']'] = isset($data['title'][$column['id']]) ? $data['title'][$column['id']] : '';
$values['task_limit['.$column['id'].']'] = isset($data['task_limit'][$column['id']]) ? $data['task_limit'][$column['id']] : 0;
}
$this->response->html($this->projectLayout('board/edit_column', array(
'errors' => $errors,
'values' => $values ?: $column,
'project' => $project,
'column' => $column,
'title' => t('Edit column "%s"', $column['title'])
)));
}
list($valid, $errors) = $this->board->validateModification($columns_list, $values);
/**
* Validate and update a column
*
* @access public
*/
public function updateColumn()
{
$project = $this->getProject();
$values = $this->request->getValues();
list($valid, $errors) = $this->board->validateModification($values);
if ($valid) {
if ($this->board->update($data)) {
if ($this->board->updateColumn($values['id'], $values['title'], $values['task_limit'], $values['description'])) {
$this->session->flash(t('Board updated successfully.'));
$this->response->redirect('?controller=board&action=edit&project_id='.$project['id']);
}
@ -248,7 +260,7 @@ class Board extends Base
}
}
$this->edit($values, $errors);
$this->editcolumn($values, $errors);
}
/**
@ -271,7 +283,7 @@ class Board extends Base
if ($valid) {
if ($this->board->addColumn($project['id'], $data['title'])) {
if ($this->board->addColumn($project['id'], $data['title'], $data['task_limit'], $data['description'])) {
$this->session->flash(t('Board updated successfully.'));
$this->response->redirect('?controller=board&action=edit&project_id='.$project['id']);
}
@ -389,6 +401,20 @@ class Board extends Base
);
}
/**
* Get links on mouseover
*
* @access public
*/
public function tasklinks()
{
$task = $this->getTask();
$this->response->html($this->template->render('board/tasklinks', array(
'links' => $this->taskLink->getLinks($task['id']),
'task' => $task,
)));
}
/**
* Get subtasks on mouseover
*
@ -398,23 +424,7 @@ class Board extends Base
{
$task = $this->getTask();
$this->response->html($this->template->render('board/subtasks', array(
'subtasks' => $this->subTask->getAll($task['id']),
'task' => $task,
)));
}
/**
* Change the status of a subtask from the mouseover
*
* @access public
*/
public function toggleSubtask()
{
$task = $this->getTask();
$this->subTask->toggleStatus($this->request->getIntegerParam('subtask_id'));
$this->response->html($this->template->render('board/subtasks', array(
'subtasks' => $this->subTask->getAll($task['id']),
'subtasks' => $this->subtask->getAll($task['id']),
'task' => $task,
)));
}
@ -449,7 +459,7 @@ class Board extends Base
}
/**
* Display the description
* Display task description
*
* @access public
*/

View file

@ -0,0 +1,107 @@
<?php
namespace Controller;
use Model\Task as TaskModel;
/**
* Project Calendar controller
*
* @package controller
* @author Frederic Guillot
* @author Timo Litzbarski
*/
class Calendar extends Base
{
/**
* Show calendar view
*
* @access public
*/
public function show()
{
$project = $this->getProject();
$this->response->html($this->template->layout('calendar/show', array(
'check_interval' => $this->config->get('board_private_refresh_interval'),
'users_list' => $this->projectPermission->getMemberList($project['id'], true, true),
'categories_list' => $this->category->getList($project['id'], true, true),
'columns_list' => $this->board->getColumnsList($project['id'], true),
'swimlanes_list' => $this->swimlane->getList($project['id'], true),
'colors_list' => $this->color->getList(true),
'status_list' => $this->taskStatus->getList(true),
'project' => $project,
'title' => t('Calendar for "%s"', $project['name']),
'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
)));
}
/**
* Get tasks to display on the calendar (project view)
*
* @access public
*/
public function project()
{
$project_id = $this->request->getIntegerParam('project_id');
$start = $this->request->getStringParam('start');
$end = $this->request->getStringParam('end');
$due_tasks = $this->taskFilter
->create()
->filterByProject($project_id)
->filterByCategory($this->request->getIntegerParam('category_id', -1))
->filterByOwner($this->request->getIntegerParam('owner_id', -1))
->filterByColumn($this->request->getIntegerParam('column_id', -1))
->filterBySwimlane($this->request->getIntegerParam('swimlane_id', -1))
->filterByColor($this->request->getStringParam('color_id'))
->filterByStatus($this->request->getIntegerParam('is_active', -1))
->filterByDueDateRange($start, $end)
->toCalendarEvents();
$subtask_timeslots = $this->subtaskTimeTracking->getProjectCalendarEvents($project_id, $start, $end);
$this->response->json(array_merge($due_tasks, $subtask_timeslots));
}
/**
* Get tasks to display on the calendar (user view)
*
* @access public
*/
public function user()
{
$user_id = $this->request->getIntegerParam('user_id');
$start = $this->request->getStringParam('start');
$end = $this->request->getStringParam('end');
$due_tasks = $this->taskFilter
->create()
->filterByOwner($user_id)
->filterByStatus(TaskModel::STATUS_OPEN)
->filterByDueDateRange($start, $end)
->toCalendarEvents();
$subtask_timeslots = $this->subtaskTimeTracking->getUserCalendarEvents($user_id, $start, $end);
$this->response->json(array_merge($due_tasks, $subtask_timeslots));
}
/**
* Update task due date
*
* @access public
*/
public function save()
{
if ($this->request->isAjax() && $this->request->isPost()) {
$values = $this->request->getJson();
$this->taskModification->update(array(
'id' => $values['task_id'],
'date_due' => $values['date_due'],
));
}
}
}

View file

@ -41,6 +41,7 @@ class Comment extends Base
public function create(array $values = array(), array $errors = array())
{
$task = $this->getTask();
$ajax = $this->request->isAjax() || $this->request->getIntegerParam('ajax');
if (empty($values)) {
$values = array(
@ -49,11 +50,20 @@ class Comment extends Base
);
}
if ($ajax) {
$this->response->html($this->template->render('comment/create', array(
'values' => $values,
'errors' => $errors,
'task' => $task,
'ajax' => $ajax,
)));
}
$this->response->html($this->taskLayout('comment/create', array(
'values' => $values,
'errors' => $errors,
'task' => $task,
'title' => t('Add a comment')
'title' => t('Add a comment'),
)));
}
@ -66,6 +76,7 @@ class Comment extends Base
{
$task = $this->getTask();
$values = $this->request->getValues();
$ajax = $this->request->isAjax() || $this->request->getIntegerParam('ajax');
list($valid, $errors) = $this->comment->validateCreation($values);
@ -78,6 +89,10 @@ class Comment extends Base
$this->session->flashError(t('Unable to create your comment.'));
}
if ($ajax) {
$this->response->redirect('?controller=board&action=show&project_id='.$task['project_id']);
}
$this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id'].'#comments');
}

View file

@ -38,7 +38,7 @@ class Config extends Base
{
if ($this->request->isPost()) {
$values = $this->request->getValues();
$values = $this->request->getValues() + array('subtask_restriction' => 0, 'subtask_time_tracking' => 0);
if ($this->config->save($values)) {
$this->config->reload();

View file

@ -2,8 +2,6 @@
namespace Controller;
use Model\File as FileModel;
/**
* File controller
*
@ -54,7 +52,7 @@ class File extends Base
{
$task = $this->getTask();
$file = $this->file->getById($this->request->getIntegerParam('file_id'));
$filename = FileModel::BASE_PATH.$file['path'];
$filename = FILES_DIR.$file['path'];
if ($file['task_id'] == $task['id'] && file_exists($filename)) {
$this->response->forceDownload($file['name']);
@ -91,7 +89,7 @@ class File extends Base
{
$task = $this->getTask();
$file = $this->file->getById($this->request->getIntegerParam('file_id'));
$filename = FileModel::BASE_PATH.$file['path'];
$filename = FILES_DIR.$file['path'];
if ($file['task_id'] == $task['id'] && file_exists($filename)) {
$metadata = getimagesize($filename);

View file

@ -0,0 +1,162 @@
<?php
namespace Controller;
/**
* Link controller
*
* @package controller
* @author Olivier Maridat
* @author Frederic Guillot
*/
class Link extends Base
{
/**
* Common layout for config views
*
* @access private
* @param string $template Template name
* @param array $params Template parameters
* @return string
*/
private function layout($template, array $params)
{
$params['board_selector'] = $this->projectPermission->getAllowedProjects($this->userSession->getId());
$params['config_content_for_layout'] = $this->template->render($template, $params);
return $this->template->layout('config/layout', $params);
}
/**
* Get the current link
*
* @access private
* @return array
*/
private function getLink()
{
$link = $this->link->getById($this->request->getIntegerParam('link_id'));
if (! $link) {
$this->notfound();
}
return $link;
}
/**
* List of links
*
* @access public
*/
public function index(array $values = array(), array $errors = array())
{
$this->response->html($this->layout('link/index', array(
'links' => $this->link->getMergedList(),
'values' => $values,
'errors' => $errors,
'title' => t('Settings').' &gt; '.t('Task\'s links'),
)));
}
/**
* Validate and save a new link
*
* @access public
*/
public function save()
{
$values = $this->request->getValues();
list($valid, $errors) = $this->link->validateCreation($values);
if ($valid) {
if ($this->link->create($values['label'], $values['opposite_label'])) {
$this->session->flash(t('Link added successfully.'));
$this->response->redirect($this->helper->url('link', 'index'));
}
else {
$this->session->flashError(t('Unable to create your link.'));
}
}
$this->index($values, $errors);
}
/**
* Edit form
*
* @access public
*/
public function edit(array $values = array(), array $errors = array())
{
$link = $this->getLink();
$link['label'] = t($link['label']);
$this->response->html($this->layout('link/edit', array(
'values' => $values ?: $link,
'errors' => $errors,
'labels' => $this->link->getList($link['id']),
'link' => $link,
'title' => t('Link modification')
)));
}
/**
* Edit a link (validate the form and update the database)
*
* @access public
*/
public function update()
{
$values = $this->request->getValues();
list($valid, $errors) = $this->link->validateModification($values);
if ($valid) {
if ($this->link->update($values)) {
$this->session->flash(t('Link updated successfully.'));
$this->response->redirect($this->helper->url('link', 'index'));
}
else {
$this->session->flashError(t('Unable to update your link.'));
}
}
$this->edit($values, $errors);
}
/**
* Confirmation dialog before removing a link
*
* @access public
*/
public function confirm()
{
$link = $this->getLink();
$this->response->html($this->layout('link/remove', array(
'link' => $link,
'title' => t('Remove a link')
)));
}
/**
* Remove a link
*
* @access public
*/
public function remove()
{
$this->checkCSRFParam();
$link = $this->getLink();
if ($this->link->remove($link['id'])) {
$this->session->flash(t('Link removed successfully.'));
}
else {
$this->session->flashError(t('Unable to remove this link.'));
}
$this->response->redirect($this->helper->url('link', 'index'));
}
}

View file

@ -17,24 +17,25 @@ class Project extends Base
*/
public function index()
{
$projects = $this->project->getAll(! $this->userSession->isAdmin());
$nb_projects = count($projects);
$active_projects = array();
$inactive_projects = array();
foreach ($projects as $project) {
if ($project['is_active'] == 1) {
$active_projects[] = $project;
}
else {
$inactive_projects[] = $project;
}
if ($this->userSession->isAdmin()) {
$project_ids = $this->project->getAllIds();
}
else {
$project_ids = $this->projectPermission->getMemberProjectIds($this->userSession->getId());
}
$nb_projects = count($project_ids);
$paginator = $this->paginator
->setUrl('project', 'index')
->setMax(20)
->setOrder('name')
->setQuery($this->project->getQueryColumnStats($project_ids))
->calculate();
$this->response->html($this->template->layout('project/index', array(
'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
'active_projects' => $active_projects,
'inactive_projects' => $inactive_projects,
'paginator' => $paginator,
'nb_projects' => $nb_projects,
'title' => t('Projects').' ('.$nb_projects.')'
)));
@ -51,7 +52,7 @@ class Project extends Base
$this->response->html($this->projectLayout('project/show', array(
'project' => $project,
'stats' => $this->project->getStats($project['id']),
'stats' => $this->project->getTaskStats($project['id']),
'title' => $project['name'],
)));
}
@ -297,6 +298,7 @@ class Project extends Base
* Duplicate a project
*
* @author Antonio Rabelo
* @author Michael Lüpkes
* @access public
*/
public function duplicate()
@ -304,10 +306,8 @@ class Project extends Base
$project = $this->getProject();
if ($this->request->getStringParam('duplicate') === 'yes') {
$this->checkCSRFParam();
if ($this->project->duplicate($project['id'])) {
$values = array_keys($this->request->getValues());
if ($this->projectDuplication->duplicate($project['id'], $values)) {
$this->session->flash(t('Project cloned successfully.'));
} else {
$this->session->flashError(t('Unable to clone this project.'));
@ -425,38 +425,32 @@ class Project extends Base
{
$project = $this->getProject();
$search = $this->request->getStringParam('search');
$direction = $this->request->getStringParam('direction', 'DESC');
$order = $this->request->getStringParam('order', 'tasks.id');
$offset = $this->request->getIntegerParam('offset', 0);
$tasks = array();
$nb_tasks = 0;
$limit = 25;
$paginator = $this->paginator
->setUrl('project', 'search', array('search' => $search, 'project_id' => $project['id']))
->setMax(30)
->setOrder('tasks.id')
->setDirection('DESC');
if ($search !== '') {
$tasks = $this->taskPaginator->searchTasks($project['id'], $search, $offset, $limit, $order, $direction);
$nb_tasks = $this->taskPaginator->countSearchTasks($project['id'], $search);
$paginator
->setQuery($this->taskFinder->getSearchQuery($project['id'], $search))
->calculate();
$nb_tasks = $paginator->getTotal();
}
$this->response->html($this->template->layout('project/search', array(
'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
'tasks' => $tasks,
'nb_tasks' => $nb_tasks,
'pagination' => array(
'controller' => 'project',
'action' => 'search',
'params' => array('search' => $search, 'project_id' => $project['id']),
'direction' => $direction,
'order' => $order,
'total' => $nb_tasks,
'offset' => $offset,
'limit' => $limit,
),
'values' => array(
'search' => $search,
'controller' => 'project',
'action' => 'search',
'project_id' => $project['id'],
),
'paginator' => $paginator,
'project' => $project,
'columns' => $this->board->getColumnsList($project['id']),
'categories' => $this->category->getList($project['id'], false),
@ -472,32 +466,21 @@ class Project extends Base
public function tasks()
{
$project = $this->getProject();
$direction = $this->request->getStringParam('direction', 'DESC');
$order = $this->request->getStringParam('order', 'tasks.date_completed');
$offset = $this->request->getIntegerParam('offset', 0);
$limit = 25;
$tasks = $this->taskPaginator->closedTasks($project['id'], $offset, $limit, $order, $direction);
$nb_tasks = $this->taskPaginator->countClosedTasks($project['id']);
$paginator = $this->paginator
->setUrl('project', 'tasks', array('project_id' => $project['id']))
->setMax(30)
->setOrder('tasks.id')
->setDirection('DESC')
->setQuery($this->taskFinder->getClosedTaskQuery($project['id']))
->calculate();
$this->response->html($this->template->layout('project/tasks', array(
'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
'pagination' => array(
'controller' => 'project',
'action' => 'tasks',
'params' => array('project_id' => $project['id']),
'direction' => $direction,
'order' => $order,
'total' => $nb_tasks,
'offset' => $offset,
'limit' => $limit,
),
'project' => $project,
'columns' => $this->board->getColumnsList($project['id']),
'categories' => $this->category->getList($project['id'], false),
'tasks' => $tasks,
'nb_tasks' => $nb_tasks,
'title' => t('Completed tasks for "%s"', $project['name']).' ('.$nb_tasks.')'
'paginator' => $paginator,
'title' => t('Completed tasks for "%s"', $project['name']).' ('.$paginator->getTotal().')'
)));
}

View file

@ -2,8 +2,10 @@
namespace Controller;
use Model\Subtask as SubtaskModel;
/**
* SubTask controller
* Subtask controller
*
* @package controller
* @author Frederic Guillot
@ -18,7 +20,7 @@ class Subtask extends Base
*/
private function getSubtask()
{
$subtask = $this->subTask->getById($this->request->getIntegerParam('subtask_id'));
$subtask = $this->subtask->getById($this->request->getIntegerParam('subtask_id'));
if (! $subtask) {
$this->notfound();
@ -61,11 +63,11 @@ class Subtask extends Base
$task = $this->getTask();
$values = $this->request->getValues();
list($valid, $errors) = $this->subTask->validateCreation($values);
list($valid, $errors) = $this->subtask->validateCreation($values);
if ($valid) {
if ($this->subTask->create($values)) {
if ($this->subtask->create($values)) {
$this->session->flash(t('Sub-task added successfully.'));
}
else {
@ -96,7 +98,7 @@ class Subtask extends Base
'values' => empty($values) ? $subtask : $values,
'errors' => $errors,
'users_list' => $this->projectPermission->getMemberList($task['project_id']),
'status_list' => $this->subTask->getStatusList(),
'status_list' => $this->subtask->getStatusList(),
'subtask' => $subtask,
'task' => $task,
)));
@ -113,11 +115,11 @@ class Subtask extends Base
$this->getSubtask();
$values = $this->request->getValues();
list($valid, $errors) = $this->subTask->validateModification($values);
list($valid, $errors) = $this->subtask->validateModification($values);
if ($valid) {
if ($this->subTask->update($values)) {
if ($this->subtask->update($values)) {
$this->session->flash(t('Sub-task updated successfully.'));
}
else {
@ -157,7 +159,7 @@ class Subtask extends Base
$task = $this->getTask();
$subtask = $this->getSubtask();
if ($this->subTask->remove($subtask['id'])) {
if ($this->subtask->remove($subtask['id'])) {
$this->session->flash(t('Sub-task removed successfully.'));
}
else {
@ -175,12 +177,86 @@ class Subtask extends Base
public function toggleStatus()
{
$task = $this->getTask();
$subtask_id = $this->request->getIntegerParam('subtask_id');
$subtask = $this->getSubtask();
$redirect = $this->request->getStringParam('redirect', 'task');
if (! $this->subTask->toggleStatus($subtask_id)) {
$this->session->flashError(t('Unable to update your sub-task.'));
$this->subtask->toggleStatus($subtask['id']);
if ($redirect === 'board') {
$this->session['has_subtask_inprogress'] = $this->subtask->hasSubtaskInProgress($this->userSession->getId());
$this->response->html($this->template->render('board/subtasks', array(
'subtasks' => $this->subtask->getAll($task['id']),
'task' => $task,
)));
}
$this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id'].'#subtasks');
$this->toggleRedirect($task, $redirect);
}
/**
* Handle subtask restriction (popover)
*
* @access public
*/
public function subtaskRestriction()
{
$task = $this->getTask();
$subtask = $this->getSubtask();
$this->response->html($this->template->render('subtask/restriction_change_status', array(
'status_list' => array(
SubtaskModel::STATUS_TODO => t('Todo'),
SubtaskModel::STATUS_DONE => t('Done'),
),
'subtask_inprogress' => $this->subtask->getSubtaskInProgress($this->userSession->getId()),
'subtask' => $subtask,
'task' => $task,
'redirect' => $this->request->getStringParam('redirect'),
)));
}
/**
* Change status of the in progress subtask and the other subtask
*
* @access public
*/
public function changeRestrictionStatus()
{
$task = $this->getTask();
$subtask = $this->getSubtask();
$values = $this->request->getValues();
// Change status of the previous in progress subtask
$this->subtask->update(array(
'id' => $values['id'],
'status' => $values['status'],
));
// Set the current subtask to in pogress
$this->subtask->update(array(
'id' => $subtask['id'],
'status' => SubtaskModel::STATUS_INPROGRESS,
));
$this->toggleRedirect($task, $values['redirect']);
}
/**
* Redirect to the right page
*
* @access private
*/
private function toggleRedirect(array $task, $redirect)
{
switch ($redirect) {
case 'board':
$this->response->redirect($this->helper->url('board', 'show', array('project_id' => $task['project_id'])));
case 'dashboard':
$this->response->redirect($this->helper->url('app', 'index'));
default:
$this->response->redirect($this->helper->url('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])));
}
}
}

View file

@ -35,7 +35,8 @@ class Task extends Base
$this->response->html($this->template->layout('task/public', array(
'project' => $project,
'comments' => $this->comment->getAll($task['id']),
'subtasks' => $this->subTask->getAll($task['id']),
'subtasks' => $this->subtask->getAll($task['id']),
'links' => $this->taskLink->getLinks($task['id']),
'task' => $task,
'columns_list' => $this->board->getColumnsList($task['project_id']),
'colors_list' => $this->color->getList(),
@ -54,7 +55,7 @@ class Task extends Base
public function show()
{
$task = $this->getTask();
$subtasks = $this->subTask->getAll($task['id']);
$subtasks = $this->subtask->getAll($task['id']);
$values = array(
'id' => $task['id'],
@ -70,9 +71,9 @@ class Task extends Base
'files' => $this->file->getAll($task['id']),
'comments' => $this->comment->getAll($task['id']),
'subtasks' => $subtasks,
'links' => $this->taskLink->getLinks($task['id']),
'task' => $task,
'values' => $values,
'timesheet' => $this->timeTracking->getTaskTimesheet($task, $subtasks),
'columns_list' => $this->board->getColumnsList($task['project_id']),
'colors_list' => $this->color->getList(),
'date_format' => $this->config->get('application_date_format'),
@ -250,6 +251,7 @@ class Task extends Base
public function close()
{
$task = $this->getTask();
$redirect = $this->request->getStringParam('redirect');
if ($this->request->getStringParam('confirmation') === 'yes') {
@ -261,11 +263,23 @@ class Task extends Base
$this->session->flashError(t('Unable to close this task.'));
}
$this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'&project_id='.$task['project_id']);
if ($redirect === 'board') {
$this->response->redirect($this->helper->url('board', 'show', array('project_id' => $task['project_id'])));
}
$this->response->redirect($this->helper->url('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])));
}
if ($this->request->isAjax()) {
$this->response->html($this->template->render('task/close', array(
'task' => $task,
'redirect' => $redirect,
)));
}
$this->response->html($this->taskLayout('task/close', array(
'task' => $task,
'redirect' => $redirect,
)));
}
@ -418,7 +432,7 @@ class Task extends Base
$task = $this->getTask();
$values = $task;
$errors = array();
$projects_list = $this->projectPermission->getMemberProjects($this->userSession->getId());
$projects_list = $this->projectPermission->getActiveMemberProjects($this->userSession->getId());
unset($projects_list[$task['project_id']]);
@ -457,7 +471,7 @@ class Task extends Base
$task = $this->getTask();
$values = $task;
$errors = array();
$projects_list = $this->projectPermission->getMemberProjects($this->userSession->getId());
$projects_list = $this->projectPermission->getActiveMemberProjects($this->userSession->getId());
unset($projects_list[$task['project_id']]);
@ -485,4 +499,27 @@ class Task extends Base
'projects_list' => $projects_list,
)));
}
/**
* Display the time tracking details
*
* @access public
*/
public function timesheet()
{
$task = $this->getTask();
$subtask_paginator = $this->paginator
->setUrl('task', 'timesheet', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'pagination' => 'subtasks'))
->setMax(15)
->setOrder('start')
->setDirection('DESC')
->setQuery($this->subtaskTimeTracking->getTaskQuery($task['id']))
->calculateOnlyIf($this->request->getStringParam('pagination') === 'subtasks');
$this->response->html($this->taskLayout('task/time_tracking', array(
'task' => $task,
'subtask_paginator' => $subtask_paginator,
)));
}
}

View file

@ -0,0 +1,116 @@
<?php
namespace Controller;
/**
* TaskLink controller
*
* @package controller
* @author Olivier Maridat
* @author Frederic Guillot
*/
class Tasklink extends Base
{
/**
* Get the current link
*
* @access private
* @return array
*/
private function getTaskLink()
{
$link = $this->taskLink->getById($this->request->getIntegerParam('link_id'));
if (! $link) {
$this->notfound();
}
return $link;
}
/**
* Creation form
*
* @access public
*/
public function create(array $values = array(), array $errors = array())
{
$task = $this->getTask();
if (empty($values)) {
$values = array(
'task_id' => $task['id'],
);
}
$this->response->html($this->taskLayout('tasklink/create', array(
'values' => $values,
'errors' => $errors,
'task' => $task,
'labels' => $this->link->getList(0, false),
'title' => t('Add a new link')
)));
}
/**
* Validation and creation
*
* @access public
*/
public function save()
{
$task = $this->getTask();
$values = $this->request->getValues();
list($valid, $errors) = $this->taskLink->validateCreation($values);
if ($valid) {
if ($this->taskLink->create($values['task_id'], $values['opposite_task_id'], $values['link_id'])) {
$this->session->flash(t('Link added successfully.'));
$this->response->redirect($this->helper->url('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])).'#links');
}
else {
$this->session->flashError(t('Unable to create your link.'));
}
}
$this->create($values, $errors);
}
/**
* Confirmation dialog before removing a link
*
* @access public
*/
public function confirm()
{
$task = $this->getTask();
$link = $this->getTaskLink();
$this->response->html($this->taskLayout('tasklink/remove', array(
'link' => $link,
'task' => $task,
)));
}
/**
* Remove a link
*
* @access public
*/
public function remove()
{
$this->checkCSRFParam();
$task = $this->getTask();
if ($this->taskLink->remove($this->request->getIntegerParam('link_id'))) {
$this->session->flash(t('Link removed successfully.'));
}
else {
$this->session->flashError(t('Unable to remove this link.'));
}
$this->response->redirect($this->helper->url('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])));
}
}

View file

@ -56,7 +56,7 @@ class User extends Base
if ($valid) {
if ($redirect_query !== '') {
$this->response->redirect('?'.$redirect_query);
$this->response->redirect('?'.urldecode($redirect_query));
}
else {
$this->response->redirect('?controller=app');
@ -115,31 +115,19 @@ class User extends Base
*/
public function index()
{
$direction = $this->request->getStringParam('direction', 'ASC');
$order = $this->request->getStringParam('order', 'username');
$offset = $this->request->getIntegerParam('offset', 0);
$limit = 25;
$users = $this->user->paginate($offset, $limit, $order, $direction);
$nb_users = $this->user->count();
$paginator = $this->paginator
->setUrl('user', 'index')
->setMax(30)
->setOrder('username')
->setQuery($this->user->getQuery())
->calculate();
$this->response->html(
$this->template->layout('user/index', array(
'board_selector' => $this->projectPermission->getAllowedProjects($this->userSession->getId()),
'projects' => $this->project->getList(),
'nb_users' => $nb_users,
'users' => $users,
'title' => t('Users').' ('.$nb_users.')',
'pagination' => array(
'controller' => 'user',
'action' => 'index',
'direction' => $direction,
'order' => $order,
'total' => $nb_users,
'offset' => $offset,
'limit' => $limit,
'params' => array(),
),
'title' => t('Users').' ('.$paginator->getTotal().')',
'paginator' => $paginator,
)));
}
@ -201,6 +189,43 @@ class User extends Base
)));
}
/**
* Display user calendar
*
* @access public
*/
public function calendar()
{
$user = $this->getUser();
$this->response->html($this->layout('user/calendar', array(
'user' => $user,
)));
}
/**
* Display timesheet
*
* @access public
*/
public function timesheet()
{
$user = $this->getUser();
$subtask_paginator = $this->paginator
->setUrl('user', 'timesheet', array('user_id' => $user['id'], 'pagination' => 'subtasks'))
->setMax(20)
->setOrder('start')
->setDirection('DESC')
->setQuery($this->subtaskTimeTracking->getUserQuery($user['id']))
->calculateOnlyIf($this->request->getStringParam('pagination') === 'subtasks');
$this->response->html($this->layout('user/timesheet', array(
'subtask_paginator' => $subtask_paginator,
'user' => $user,
)));
}
/**
* Display last connections
*
@ -330,7 +355,7 @@ class User extends Base
if ($this->request->isPost()) {
$values = $this->request->getValues();
$values = $this->request->getValues() + array('disable_login_form' => 0);
if ($this->userSession->isAdmin()) {
$values += array('is_admin' => 0);
@ -462,7 +487,7 @@ class User extends Base
*
* @access public
*/
public function gitHub()
public function github()
{
$code = $this->request->getStringParam('code');
@ -506,7 +531,7 @@ class User extends Base
*
* @access public
*/
public function unlinkGitHub()
public function unlinkGithub()
{
$this->checkCSRFParam();

View file

@ -82,4 +82,22 @@ class Webhook extends Base
echo $result ? 'PARSED' : 'IGNORED';
}
/**
* Handle Bitbucket webhooks
*
* @access public
*/
public function bitbucket()
{
if ($this->config->get('webhook_token') !== $this->request->getStringParam('token')) {
$this->response->text('Not Authorized', 401);
}
$this->bitbucketWebhook->setProjectId($this->request->getIntegerParam('project_id'));
$result = $this->bitbucketWebhook->parsePayload(json_decode(@$_POST['payload'], true));
echo $result ? 'PARSED' : 'IGNORED';
}
}

View file

@ -3,7 +3,6 @@
namespace Core;
use Pimple\Container;
use Parsedown;
/**
* Template helpers
@ -13,6 +12,7 @@ use Parsedown;
*
* @property \Core\Session $session
* @property \Model\Acl $acl
* @property \Model\Config $config
* @property \Model\User $user
* @property \Model\UserSession $userSession
*/
@ -49,6 +49,33 @@ class Helper
return $this->container[$name];
}
/**
* Get the age of an item in quasi human readable format.
* It's in this format: <1h , NNh, NNd
*
* @access public
* @param integer $timestamp Unix timestamp of the artifact for which age will be calculated
* @param integer $now Compare with this timestamp (Default value is the current unix timestamp)
* @return string
*/
public function getTaskAge($timestamp, $now = null)
{
if ($now === null) {
$now = time();
}
$diff = $now - $timestamp;
if ($diff < 3600) {
return t('<1h');
}
else if ($diff < 86400) {
return t('%dh', $diff / 3600);
}
return t('%dd', ($now - $timestamp) / 86400);
}
/**
* Proxy cache helper for acl::isManagerActionAllowed()
*
@ -104,9 +131,9 @@ class Helper
* @param string $filename Filename
* @return string
*/
public function css($filename)
public function css($filename, $is_file = true)
{
return '<link rel="stylesheet" href="'.$filename.'?'.filemtime($filename).'" media="screen">';
return '<link rel="stylesheet" href="'.$filename.($is_file ? '?'.filemtime($filename) : '').'" media="screen">';
}
/**
@ -194,9 +221,9 @@ class Helper
* @param string $class CSS class
* @return string
*/
public function formSelect($name, array $options, array $values = array(), array $errors = array(), $class = '')
public function formSelect($name, array $options, array $values = array(), array $errors = array(), array $attributes = array(), $class = '')
{
$html = '<select name="'.$name.'" id="form-'.$name.'" class="'.$class.'">';
$html = '<select name="'.$name.'" id="form-'.$name.'" class="'.$class.'" '.implode(' ', $attributes).'>';
foreach ($options as $id => $value) {
@ -245,7 +272,7 @@ class Helper
*/
public function formRadio($name, $label, $value, $selected = false, $class = '')
{
return '<label><input type="radio" name="'.$name.'" class="'.$class.'" value="'.$this->e($value).'" '.($selected ? 'selected="selected"' : '').'>'.$this->e($label).'</label>';
return '<label><input type="radio" name="'.$name.'" class="'.$class.'" value="'.$this->e($value).'" '.($selected ? 'selected="selected"' : '').'> '.$this->e($label).'</label>';
}
/**
@ -417,7 +444,7 @@ class Helper
}
/**
* URL query string
* Generate controller/action url for templates
*
* u('task', 'show', array('task_id' => $task_id))
*
@ -429,83 +456,40 @@ class Helper
*/
public function u($controller, $action, array $params = array(), $csrf = false)
{
$html = '?controller='.$controller.'&amp;action='.$action;
$values = array(
'controller' => $controller,
'action' => $action,
);
if ($csrf) {
$params['csrf_token'] = Security::getCSRFToken();
}
foreach ($params as $key => $value) {
$html .= '&amp;'.$key.'='.$value;
}
$values += $params;
return $html;
return '?'.http_build_query($values, '', '&amp;');
}
/**
* Pagination links
* Generate controller/action url
*
* @param array $pagination Pagination information
* l('task', 'show', array('task_id' => $task_id))
*
* @param string $controller Controller name
* @param string $action Action name
* @param array $params Url parameters
* @return string
*/
public function paginate(array $pagination)
public function url($controller, $action, array $params = array())
{
extract($pagination);
$values = array(
'controller' => $controller,
'action' => $action,
);
if ($pagination['offset'] === 0 && ($total - $pagination['offset']) <= $limit) {
return '';
}
$values += $params;
$html = '<div class="pagination">';
$html .= '<span class="pagination-previous">';
if ($pagination['offset'] > 0) {
$offset = $pagination['offset'] - $limit;
$html .= $this->a('&larr; '.t('Previous'), $controller, $action, $params + compact('offset', 'order', 'direction'));
}
else {
$html .= '&larr; '.t('Previous');
}
$html .= '</span>';
$html .= '<span class="pagination-next">';
if (($total - $pagination['offset']) > $limit) {
$offset = $pagination['offset'] + $limit;
$html .= $this->a(t('Next').' &rarr;', $controller, $action, $params + compact('offset', 'order', 'direction'));
}
else {
$html .= t('Next').' &rarr;';
}
$html .= '</span>';
$html .= '</div>';
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' ? '&#9660; ' : '&#9650; ';
$direction = $direction === 'DESC' ? 'ASC' : 'DESC';
}
$order = $column;
return $prefix.$this->a($label, $controller, $action, $params + compact('offset', 'order', 'direction'));
return '?'.http_build_query($values);
}
/**
@ -517,24 +501,9 @@ class Helper
*/
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;
$parser = new Markdown($link, $this);
$parser->setMarkupEscaped(true);
return $parser->text($text);
}
/**
@ -656,4 +625,56 @@ class Helper
return $default_value;
}
/**
* Get javascript language code
*
* @access public
* @return string
*/
public function jsLang()
{
return $this->config->getJsLanguageCode();
}
/**
* Get current timezone
*
* @access public
* @return string
*/
public function getTimezone()
{
return $this->config->getCurrentTimezone();
}
/**
* Get the link to toggle subtask status
*
* @access public
* @param array $subtask
* @param string $redirect
* @return string
*/
public function toggleSubtaskStatus(array $subtask, $redirect)
{
if ($subtask['status'] == 0 && isset($this->session['has_subtask_inprogress']) && $this->session['has_subtask_inprogress'] === true) {
return $this->a(
trim($this->render('subtask/icons', array('subtask' => $subtask))) . $this->e($subtask['title']),
'subtask',
'subtaskRestriction',
array('task_id' => $subtask['task_id'], 'subtask_id' => $subtask['id'], 'redirect' => $redirect),
false,
'popover task-board-popover'
);
}
return $this->a(
trim($this->render('subtask/icons', array('subtask' => $subtask))) . $this->e($subtask['title']),
'subtask',
'toggleStatus',
array('task_id' => $subtask['task_id'], 'subtask_id' => $subtask['id'], 'redirect' => $redirect)
);
}
}

View file

@ -0,0 +1,43 @@
<?php
namespace Core;
use Parsedown;
/**
* Specific Markdown rules for Kanboard
*
* @package core
* @author norcnorc
* @author Frederic Guillot
*/
class Markdown extends Parsedown
{
private $link;
private $helper;
public function __construct($link, Helper $helper)
{
$this->link = $link;
$this->helper = $helper;
$this->InlineTypes['#'][] = 'TaskLink';
$this->inlineMarkerList .= '#';
}
protected function inlineTaskLink($Excerpt)
{
// Replace task #123 by a link to the task
if (! empty($this->link) && preg_match('!#(\d+)!i', $Excerpt['text'], $matches)) {
$url = $this->helper->u($this->link['controller'],
$this->link['action'],
$this->link['params'] + array('task_id' => $matches[1]));
return array(
'extent' => strlen($matches[0]),
'element' => array(
'name' => 'a',
'text' => $matches[0],
'attributes' => array('href' => $url)));
}
}
}

View file

@ -0,0 +1,461 @@
<?php
namespace Core;
use Pimple\Container;
use PicoDb\Table;
/**
* Paginator helper
*
* @package core
* @author Frederic Guillot
*/
class Paginator
{
/**
* Container instance
*
* @access private
* @var \Pimple\Container
*/
private $container;
/**
* Total number of items
*
* @access private
* @var integer
*/
private $total = 0;
/**
* Page number
*
* @access private
* @var integer
*/
private $page = 1;
/**
* Offset
*
* @access private
* @var integer
*/
private $offset = 0;
/**
* Limit
*
* @access private
* @var integer
*/
private $limit = 0;
/**
* Sort by this column
*
* @access private
* @var string
*/
private $order = '';
/**
* Sorting direction
*
* @access private
* @var string
*/
private $direction = 'ASC';
/**
* Slice of items
*
* @access private
* @var array
*/
private $items = array();
/**
* PicoDb Table instance
*
* @access private
* @var \Picodb\Table
*/
private $query = null;
/**
* Controller name
*
* @access private
* @var string
*/
private $controller = '';
/**
* Action name
*
* @access private
* @var string
*/
private $action = '';
/**
* Url params
*
* @access private
* @var array
*/
private $params = array();
/**
* Constructor
*
* @access public
* @param \Pimple\Container $container
*/
public function __construct(Container $container)
{
$this->container = $container;
}
/**
* Set a PicoDb query
*
* @access public
* @param \PicoDb\Table
* @return Paginator
*/
public function setQuery(Table $query)
{
$this->query = $query;
$this->total = $this->query->count();
return $this;
}
/**
* Execute a PicoDb query
*
* @access public
* @return array
*/
public function executeQuery()
{
if ($this->query !== null) {
return $this->query
->offset($this->offset)
->limit($this->limit)
->orderBy($this->order, $this->direction)
->findAll();
}
return array();
}
/**
* Set url parameters
*
* @access public
* @param string $controller
* @param string $action
* @param array $params
* @return Paginator
*/
public function setUrl($controller, $action, array $params = array())
{
$this->controller = $controller;
$this->action = $action;
$this->params = $params;
return $this;
}
/**
* Add manually items
*
* @access public
* @param array $items
* @return Paginator
*/
public function setCollection(array $items)
{
$this->items = $items;
return $this;
}
/**
* Return the items
*
* @access public
* @return array
*/
public function getCollection()
{
return $this->items ?: $this->executeQuery();
}
/**
* Set the total number of items
*
* @access public
* @param integer $total
* @return Paginator
*/
public function setTotal($total)
{
$this->total = $total;
return $this;
}
/**
* Get the total number of items
*
* @access public
* @return integer
*/
public function getTotal()
{
return $this->total;
}
/**
* Set the default page number
*
* @access public
* @param integer $page
* @return Paginator
*/
public function setPage($page)
{
$this->page = $page;
return $this;
}
/**
* Set the default column order
*
* @access public
* @param string $order
* @return Paginator
*/
public function setOrder($order)
{
$this->order = $order;
return $this;
}
/**
* Set the default sorting direction
*
* @access public
* @param string $direction
* @return Paginator
*/
public function setDirection($direction)
{
$this->direction = $direction;
return $this;
}
/**
* Set the maximum number of items per page
*
* @access public
* @param integer $limit
* @return Paginator
*/
public function setMax($limit)
{
$this->limit = $limit;
return $this;
}
/**
* Return true if the collection is empty
*
* @access public
* @return boolean
*/
public function isEmpty()
{
return $this->total === 0;
}
/**
* Execute the offset calculation only if the $condition is true
*
* @access public
* @param boolean $condition
* @return Paginator
*/
public function calculateOnlyIf($condition)
{
if ($condition) {
$this->calculate();
}
return $this;
}
/**
* Calculate the offset value accoring to url params and the page number
*
* @access public
* @return Paginator
*/
public function calculate()
{
$this->page = $this->container['request']->getIntegerParam('page', 1);
$this->direction = $this->container['request']->getStringParam('direction', $this->direction);
$this->order = $this->container['request']->getStringParam('order', $this->order);
if ($this->page < 1) {
$this->page = 1;
}
$this->offset = ($this->page - 1) * $this->limit;
return $this;
}
/**
* Get url params for link generation
*
* @access public
* @param integer $page
* @param string $order
* @param string $direction
* @return string
*/
public function getUrlParams($page, $order, $direction)
{
$params = array(
'page' => $page,
'order' => $order,
'direction' => $direction,
);
return array_merge($this->params, $params);
}
/**
* Generate the previous link
*
* @access public
* @return string
*/
public function generatePreviousLink()
{
$html = '<span class="pagination-previous">';
if ($this->offset > 0) {
$html .= $this->container['helper']->a(
'&larr; '.t('Previous'),
$this->controller,
$this->action,
$this->getUrlParams($this->page - 1, $this->order, $this->direction)
);
}
else {
$html .= '&larr; '.t('Previous');
}
$html .= '</span>';
return $html;
}
/**
* Generate the next link
*
* @access public
* @return string
*/
public function generateNextLink()
{
$html = '<span class="pagination-next">';
if (($this->total - $this->offset) > $this->limit) {
$html .= $this->container['helper']->a(
t('Next').' &rarr;',
$this->controller,
$this->action,
$this->getUrlParams($this->page + 1, $this->order, $this->direction)
);
}
else {
$html .= t('Next').' &rarr;';
}
$html .= '</span>';
return $html;
}
/**
* Return true if there is no pagination to show
*
* @access public
* @return boolean
*/
public function hasNothingtoShow()
{
return $this->offset === 0 && ($this->total - $this->offset) <= $this->limit;
}
/**
* Generation pagination links
*
* @access public
* @return string
*/
public function toHtml()
{
$html = '';
if (! $this->hasNothingtoShow()) {
$html .= '<div class="pagination">';
$html .= $this->generatePreviousLink();
$html .= $this->generateNextLink();
$html .= '</div>';
}
return $html;
}
/**
* Magic method to output pagination links
*
* @access public
* @return string
*/
public function __toString()
{
return $this->toHtml();
}
/**
* Column sorting
*
* @param string $label Column title
* @param string $column SQL column name
* @return string
*/
public function order($label, $column)
{
$prefix = '';
$direction = 'ASC';
if ($this->order === $column) {
$prefix = $this->direction === 'DESC' ? '&#9660; ' : '&#9650; ';
$direction = $this->direction === 'DESC' ? 'ASC' : 'DESC';
}
return $prefix.$this->container['helper']->a(
$label,
$this->controller,
$this->action,
$this->getUrlParams($this->page, $column, $direction)
);
}
}

View file

@ -167,6 +167,23 @@ class Response
exit;
}
/**
* Send a css response
*
* @access public
* @param string $data Raw data
* @param integer $status_code HTTP status code
*/
public function css($data, $status_code = 200)
{
$this->status($status_code);
header('Content-Type: text/css; charset=utf-8');
echo $data;
exit;
}
/**
* Send a binary response
*
@ -195,24 +212,7 @@ class Response
$policies['default-src'] = "'self'";
$values = '';
foreach ($policies as $policy => $hosts) {
if (is_array($hosts)) {
$acl = '';
foreach ($hosts as &$host) {
if ($host === '*' || $host === 'self' || strpos($host, 'http') === 0) {
$acl .= $host.' ';
}
}
}
else {
$acl = $hosts;
}
foreach ($policies as $policy => $acl) {
$values .= $policy.' '.trim($acl).'; ';
}

View file

@ -0,0 +1,97 @@
<?php
namespace Integration;
use Event\TaskEvent;
use Model\Task;
/**
* Bitbucket Webhook
*
* @package integration
* @author Frederic Guillot
*/
class BitbucketWebhook extends Base
{
/**
* Events
*
* @var string
*/
const EVENT_COMMIT = 'bitbucket.webhook.commit';
/**
* Project id
*
* @access private
* @var integer
*/
private $project_id = 0;
/**
* Set the project id
*
* @access public
* @param integer $project_id Project id
*/
public function setProjectId($project_id)
{
$this->project_id = $project_id;
}
/**
* Parse events
*
* @access public
* @param array $payload Gitlab event
* @return boolean
*/
public function parsePayload(array $payload)
{
if (! empty($payload['commits'])) {
foreach ($payload['commits'] as $commit) {
if ($this->handleCommit($commit)) {
return true;
}
}
}
return false;
}
/**
* 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;
}
}

View file

@ -148,10 +148,10 @@ class GitlabWebhook extends Base
*/
public function handleIssueEvent(array $payload)
{
switch ($payload['object_attributes']['state']) {
case 'opened':
switch ($payload['object_attributes']['action']) {
case 'open':
return $this->handleIssueOpened($payload['object_attributes']);
case 'closed':
case 'close':
return $this->handleIssueClosed($payload['object_attributes']);
}

View file

@ -0,0 +1,227 @@
<?php
/**
* A Compatibility library with PHP 5.5's simplified password hashing API.
*
* @author Anthony Ferrara <ircmaxell@php.net>
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @copyright 2012 The Authors
*/
if (!defined('PASSWORD_BCRYPT')) {
define('PASSWORD_BCRYPT', 1);
define('PASSWORD_DEFAULT', PASSWORD_BCRYPT);
if (version_compare(PHP_VERSION, '5.3.7', '<')) {
define('PASSWORD_PREFIX', '$2a$');
}
else {
define('PASSWORD_PREFIX', '$2y$');
}
/**
* Hash the password using the specified algorithm
*
* @param string $password The password to hash
* @param int $algo The algorithm to use (Defined by PASSWORD_* constants)
* @param array $options The options for the algorithm to use
*
* @return string|false The hashed password, or false on error.
*/
function password_hash($password, $algo, array $options = array()) {
if (!function_exists('crypt')) {
trigger_error("Crypt must be loaded for password_hash to function", E_USER_WARNING);
return null;
}
if (!is_string($password)) {
trigger_error("password_hash(): Password must be a string", E_USER_WARNING);
return null;
}
if (!is_int($algo)) {
trigger_error("password_hash() expects parameter 2 to be long, " . gettype($algo) . " given", E_USER_WARNING);
return null;
}
switch ($algo) {
case PASSWORD_BCRYPT:
// Note that this is a C constant, but not exposed to PHP, so we don't define it here.
$cost = 10;
if (isset($options['cost'])) {
$cost = $options['cost'];
if ($cost < 4 || $cost > 31) {
trigger_error(sprintf("password_hash(): Invalid bcrypt cost parameter specified: %d", $cost), E_USER_WARNING);
return null;
}
}
$required_salt_len = 22;
$hash_format = sprintf("%s%02d$", PASSWORD_PREFIX, $cost);
break;
default:
trigger_error(sprintf("password_hash(): Unknown password hashing algorithm: %s", $algo), E_USER_WARNING);
return null;
}
if (isset($options['salt'])) {
switch (gettype($options['salt'])) {
case 'NULL':
case 'boolean':
case 'integer':
case 'double':
case 'string':
$salt = (string) $options['salt'];
break;
case 'object':
if (method_exists($options['salt'], '__tostring')) {
$salt = (string) $options['salt'];
break;
}
case 'array':
case 'resource':
default:
trigger_error('password_hash(): Non-string salt parameter supplied', E_USER_WARNING);
return null;
}
if (strlen($salt) < $required_salt_len) {
trigger_error(sprintf("password_hash(): Provided salt is too short: %d expecting %d", strlen($salt), $required_salt_len), E_USER_WARNING);
return null;
} elseif (0 == preg_match('#^[a-zA-Z0-9./]+$#D', $salt)) {
$salt = str_replace('+', '.', base64_encode($salt));
}
} else {
$buffer = '';
$raw_length = (int) ($required_salt_len * 3 / 4 + 1);
$buffer_valid = false;
if (function_exists('mcrypt_create_iv') && !defined('PHALANGER')) {
$buffer = mcrypt_create_iv($raw_length, MCRYPT_DEV_URANDOM);
if ($buffer) {
$buffer_valid = true;
}
}
if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes')) {
$buffer = openssl_random_pseudo_bytes($raw_length);
if ($buffer) {
$buffer_valid = true;
}
}
if (!$buffer_valid && is_readable('/dev/urandom')) {
$f = fopen('/dev/urandom', 'r');
$read = strlen($buffer);
while ($read < $raw_length) {
$buffer .= fread($f, $raw_length - $read);
$read = strlen($buffer);
}
fclose($f);
if ($read >= $raw_length) {
$buffer_valid = true;
}
}
if (!$buffer_valid || strlen($buffer) < $raw_length) {
$bl = strlen($buffer);
for ($i = 0; $i < $raw_length; $i++) {
if ($i < $bl) {
$buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255));
} else {
$buffer .= chr(mt_rand(0, 255));
}
}
}
$salt = str_replace('+', '.', base64_encode($buffer));
}
$salt = substr($salt, 0, $required_salt_len);
$hash = $hash_format . $salt;
$ret = crypt($password, $hash);
if (!is_string($ret) || strlen($ret) <= 13) {
return false;
}
return $ret;
}
/**
* Get information about the password hash. Returns an array of the information
* that was used to generate the password hash.
*
* array(
* 'algo' => 1,
* 'algoName' => 'bcrypt',
* 'options' => array(
* 'cost' => 10,
* ),
* )
*
* @param string $hash The password hash to extract info from
*
* @return array The array of information about the hash.
*/
function password_get_info($hash) {
$return = array(
'algo' => 0,
'algoName' => 'unknown',
'options' => array(),
);
if (substr($hash, 0, 4) == PASSWORD_PREFIX && strlen($hash) == 60) {
$return['algo'] = PASSWORD_BCRYPT;
$return['algoName'] = 'bcrypt';
list($cost) = sscanf($hash, PASSWORD_PREFIX."%d$");
$return['options']['cost'] = $cost;
}
return $return;
}
/**
* Determine if the password hash needs to be rehashed according to the options provided
*
* If the answer is true, after validating the password using password_verify, rehash it.
*
* @param string $hash The hash to test
* @param int $algo The algorithm used for new password hashes
* @param array $options The options array passed to password_hash
*
* @return boolean True if the password needs to be rehashed.
*/
function password_needs_rehash($hash, $algo, array $options = array()) {
$info = password_get_info($hash);
if ($info['algo'] != $algo) {
return true;
}
switch ($algo) {
case PASSWORD_BCRYPT:
$cost = isset($options['cost']) ? $options['cost'] : 10;
if ($cost != $info['options']['cost']) {
return true;
}
break;
}
return false;
}
/**
* Verify a password against a hash using a timing attack resistant approach
*
* @param string $password The password to verify
* @param string $hash The hash to verify against
*
* @return boolean If the password matches the hash
*/
function password_verify($password, $hash) {
if (!function_exists('crypt')) {
trigger_error("Crypt must be loaded for password_verify to function", E_USER_WARNING);
return false;
}
$ret = crypt($password, $hash);
if (!is_string($ret) || strlen($ret) != strlen($hash) || strlen($ret) <= 13) {
return false;
}
$status = 0;
for ($i = 0; $i < strlen($ret); $i++) {
$status |= (ord($ret[$i]) ^ ord($hash[$i]));
}
return $status === 0;
}
}

View file

@ -408,15 +408,13 @@ return array(
'Comment updated' => 'Kommentar opdateret',
'New comment posted by %s' => 'Ny kommentar af %s',
'List of due tasks for the project "%s"' => 'Udestående opgaver for projektet "%s"',
'[%s][New attachment] %s (#%d)' => '[%s][Ny vedhæftning] %s (#%d)',
'[%s][New comment] %s (#%d)' => '[%s][Ny kommentar] %s (#%d)',
'[%s][Comment updated] %s (#%d)' => '[%s][Kommentar opdateret] %s (#%d)',
'[%s][New subtask] %s (#%d)' => '[%s][Ny under-opgave] %s (#%d)',
'[%s][Subtask updated] %s (#%d)' => '[%s][Under-opgave opdateret] %s (#%d)',
'[%s][New task] %s (#%d)' => '[%s][Ny opgave] %s (#%d)',
'[%s][Task updated] %s (#%d)' => '[%s][Opgave opdateret] %s (#%d)',
'[%s][Task closed] %s (#%d)' => '[%s][Opgave lukket] %s (#%d)',
'[%s][Task opened] %s (#%d)' => '[%s][Opgave åbnet] %s (#%d)',
// 'New attachment' => '',
// 'New comment' => '',
// 'New subtask' => '',
// 'Subtask updated' => '',
// 'Task updated' => '',
// 'Task closed' => '',
// 'Task opened' => '',
'[%s][Due tasks]' => 'Udestående opgaver',
'[Kanboard] Notification' => '[Kanboard] Notifikation',
'I want to receive notifications only for those projects:' => 'Jeg vil kun have notifikationer for disse projekter:',
@ -500,9 +498,9 @@ return array(
'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 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)',
// 'Column Change' => '',
// 'Position Change' => '',
// 'Assignee Change' => '',
'New password for the user "%s"' => 'Ny adgangskode for brugeren',
'Choose an event' => 'Vælg et event',
'Github commit received' => 'Github commit modtaget',
@ -647,5 +645,93 @@ return array(
// 'Application default' => '',
// 'Language:' => '',
// 'Timezone:' => '',
// 'All columns' => '',
// 'Calendar for "%s"' => '',
// 'Filter by column' => '',
// 'Filter by status' => '',
// 'Calendar' => '',
// 'Next' => '',
// '#%d' => '',
// 'Filter by color' => '',
// 'Filter by swimlane' => '',
// 'All swimlanes' => '',
// 'All colors' => '',
// 'All status' => '',
// 'Add a comment logging moving the task between columns' => '',
// 'Moved to column %s' => '',
// 'Change description' => '',
// 'User dashboard' => '',
// 'Allow only one subtask in progress at the same time for a user' => '',
// 'Edit column "%s"' => '',
// 'Enable time tracking for subtasks' => '',
// 'Select the new status of the subtask: "%s"' => '',
// 'Subtask timesheet' => '',
// 'There is nothing to show.' => '',
// 'Time Tracking' => '',
// 'You already have one subtask in progress' => '',
// 'Which parts of the project do you want to duplicate?' => '',
// 'Change dashboard view' => '',
// 'Show/hide activities' => '',
// 'Show/hide projects' => '',
// 'Show/hide subtasks' => '',
// 'Show/hide tasks' => '',
// 'Disable login form' => '',
// 'Show/hide calendar' => '',
// 'User calendar' => '',
// 'Bitbucket commit received' => '',
// 'Bitbucket webhooks' => '',
// 'Help on Bitbucket webhooks' => '',
// 'Start' => '',
// 'End' => '',
// 'Task age in days' => '',
// 'Days in this column' => '',
// '%dd' => '',
// 'Add a link' => '',
// 'Add a new link' => '',
// 'Do you really want to remove this link: "%s"?' => '',
// 'Do you really want to remove this link with task #%d?' => '',
// 'Field required' => '',
// 'Link added successfully.' => '',
// 'Link updated successfully.' => '',
// 'Link removed successfully.' => '',
// 'Link labels' => '',
// 'Link modification' => '',
// 'Links' => '',
// 'Link settings' => '',
// 'Opposite label' => '',
// 'Remove a link' => '',
// 'Task\'s links' => '',
// 'The labels must be different' => '',
// 'There is no link.' => '',
// 'This label must be unique' => '',
// 'Unable to create your link.' => '',
// 'Unable to update your link.' => '',
// 'Unable to remove this link.' => '',
// 'relates to' => '',
// 'blocks' => '',
// 'is blocked by' => '',
// 'duplicates' => '',
// 'is duplicated by' => '',
// 'is a child of' => '',
// 'is a parent of' => '',
// 'targets milestone' => '',
// 'is a milestone of' => '',
// 'fixes' => '',
// 'is fixed by' => '',
// 'This task' => '',
// '<1h' => '',
// '%dh' => '',
// '%b %e' => '',
// 'Expand tasks' => '',
// 'Collapse tasks' => '',
// 'Expand/collapse tasks' => '',
// 'Close dialog box' => '',
// 'Submit a form' => '',
// 'Board view' => '',
// 'Keyboard shortcuts' => '',
// 'Open board switcher' => '',
// 'Application' => '',
// 'Filter recently updated' => '',
// 'since %B %e, %Y at %k:%M %p' => '',
// 'More filters' => '',
);

View file

@ -408,15 +408,13 @@ return array(
'Comment updated' => 'Kommentar wurde aktualisiert',
'New comment posted by %s' => 'Neuer Kommentar verfasst durch %s',
'List of due tasks for the project "%s"' => 'Liste der fälligen Aufgaben für das Projekt "%s"',
'[%s][New attachment] %s (#%d)' => '[%s][Neuer Anhang] %s (#%d)',
'[%s][New comment] %s (#%d)' => '[%s][Neuer Kommentar] %s (#%d)',
'[%s][Comment updated] %s (#%d)' => '[%s][Kommentar aktualisisiert] %s (#%d)',
'[%s][New subtask] %s (#%d)' => '[%s][Neue Teilaufgabe] %s (#%d)',
'[%s][Subtask updated] %s (#%d)' => '[%s][Teilaufgabe aktualisisert] %s (#%d)',
'[%s][New task] %s (#%d)' => '[%s][Neue Aufgabe] %s (#%d)',
'[%s][Task updated] %s (#%d)' => '[%s][Aufgabe aktualisiert] %s (#%d)',
'[%s][Task closed] %s (#%d)' => '[%s][Aufgabe geschlossen] %s (#%d)',
'[%s][Task opened] %s (#%d)' => '[%s][Aufgabe geöffnet] %s (#%d)',
// 'New attachment' => '',
// 'New comment' => '',
// 'New subtask' => '',
// 'Subtask updated' => '',
// 'Task updated' => '',
// 'Task closed' => '',
// 'Task opened' => '',
'[%s][Due tasks]' => '[%s][Fällige Aufgaben]',
'[Kanboard] Notification' => '[Kanboard] Benachrichtigung',
'I want to receive notifications only for those projects:' => 'Ich möchte nur für diese Projekte Benachrichtigungen erhalten:',
@ -500,9 +498,9 @@ return array(
'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 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)',
// 'Column Change' => '',
// 'Position Change' => '',
// 'Assignee Change' => '',
'New password for the user "%s"' => 'Neues Passwort des Benutzers "%s"',
'Choose an event' => 'Aktion wählen',
'Github commit received' => 'Github commit empfangen',
@ -647,5 +645,93 @@ return array(
'Application default' => 'Anwendungsstandard',
'Language:' => 'Sprache:',
'Timezone:' => 'Zeitzone:',
// 'Next' => '',
'All columns' => 'Alle Spalten',
'Calendar for "%s"' => 'Kalender für "%s"',
'Filter by column' => 'Spalte filtern',
'Filter by status' => 'Status filtern',
'Calendar' => 'Kalender',
'Next' => 'Nächste',
// '#%d' => '',
'Filter by color' => 'Farbe filtern',
'Filter by swimlane' => 'Swimlane filtern',
'All swimlanes' => 'Alle Swimlanes',
'All colors' => 'Alle Farben',
'All status' => 'Alle Status',
'Add a comment logging moving the task between columns' => 'Kommentar hinzufügen wenn die Aufgabe verschoben wird',
'Moved to column %s' => 'In Spalte %s verschoben',
'Change description' => 'Beschreibung ändern',
'User dashboard' => 'Benutzer Dashboard',
'Allow only one subtask in progress at the same time for a user' => 'Erlaube nur eine Teilaufgabe pro Benutzer zu bearbeiten',
'Edit column "%s"' => 'Spalte "%s" bearbeiten',
'Enable time tracking for subtasks' => 'Aktiviere Zeiterfassung für Teilaufgaben',
'Select the new status of the subtask: "%s"' => 'Wähle einen neuen Status für Teilaufgabe: "%s"',
'Subtask timesheet' => 'Teilaufgaben Zeiterfassung',
'There is nothing to show.' => 'Es ist nichts zum Anzeigen vorhanden.',
'Time Tracking' => 'Zeiterfassung',
'You already have one subtask in progress' => 'Bereits eine Teilaufgabe in bearbeitung',
'Which parts of the project do you want to duplicate?' => 'Welcher Teil des Projekts soll kopiert werden?',
'Change dashboard view' => 'Dashboardansicht ändern',
'Show/hide activities' => 'Aktivitäten anzeigen/verbergen',
'Show/hide projects' => 'Projekte anzeigen/verbergen',
'Show/hide subtasks' => 'Teilaufgaben anzeigen/verbergen',
'Show/hide tasks' => 'Aufgaben anzeigen/verbergen',
'Disable login form' => 'Anmeldeformular deaktivieren',
'Show/hide calendar' => 'Kalender anzeigen/verbergen',
'User calendar' => 'Benutzer Kalender',
'Bitbucket commit received' => 'Bitbucket commit erhalten',
'Bitbucket webhooks' => 'Bitbucket webhooks',
'Help on Bitbucket webhooks' => 'Hilfe für Bitbucket webhooks',
'Start' => 'Start',
'End' => 'Ende',
'Task age in days' => 'Aufgabenalter in Tagen',
'Days in this column' => 'Tage in dieser Spalte',
'%dd' => '%dT',
'Add a link' => 'Verbindung hinzufügen',
'Add a new link' => 'Neue Verbindung hinzufügen',
'Do you really want to remove this link: "%s"?' => 'Die Verbindung "%s" wirklich löschen?',
'Do you really want to remove this link with task #%d?' => 'Die Verbindung mit der Aufgabe #%d wirklich löschen?',
'Field required' => 'Feld erforderlich',
'Link added successfully.' => 'Verbindung erfolgreich hinzugefügt.',
'Link updated successfully.' => 'Verbindung erfolgreich aktualisiert.',
'Link removed successfully.' => 'Verbindung erfolgreich gelöscht.',
'Link labels' => 'Verbindungsbeschriftung',
'Link modification' => 'Verbindung ändern',
'Links' => 'Verbindungen',
'Link settings' => 'Verbindungseinstellungen',
'Opposite label' => 'Gegenteil',
'Remove a link' => 'Verbindung entfernen',
'Task\'s links' => 'Aufgaben Verbindungen',
'The labels must be different' => 'Die Beschriftung muss unterschiedlich sein',
'There is no link.' => 'Es gibt keine Verbindung',
'This label must be unique' => 'Die Beschriftung muss einzigartig sein',
'Unable to create your link.' => 'Verbindung kann nicht erstellt werden.',
'Unable to update your link.' => 'Verbindung kann nicht aktualisiert werden.',
'Unable to remove this link.' => 'Verbindung kann nicht entfernt werden',
'relates to' => 'gehört zu',
'blocks' => 'blockiert',
'is blocked by' => 'ist blockiert von',
'duplicates' => 'doppelt',
'is duplicated by' => 'ist gedoppelt von',
'is a child of' => 'ist untergeordnet',
'is a parent of' => 'ist übergeordnet',
'targets milestone' => 'betrifft Meilenstein',
'is a milestone of' => 'ist ein Meilenstein von',
'fixes' => 'behebt',
'is fixed by' => 'wird behoben von',
'This task' => 'Diese Aufgabe',
'<1h' => '<1Std',
'%dh' => '%dStd',
// '%b %e' => '',
'Expand tasks' => 'Aufgaben aufklappen',
'Collapse tasks' => 'Aufgaben zusammenklappen',
'Expand/collapse tasks' => 'Aufgaben auf/zuklappen',
'Close dialog box' => 'Dialog schließen',
'Submit a form' => 'Formular abschicken',
'Board view' => 'Pinnwand Ansicht',
'Keyboard shortcuts' => 'Tastaturkürzel',
'Open board switcher' => 'Pinnwandauswahl öffnen',
'Application' => 'Anwendung',
'Filter recently updated' => 'Zuletzt geänderte anzeigen',
'since %B %e, %Y at %k:%M %p' => 'seit %B %e, %Y um %k:%M %p',
'More filters' => 'Mehr Filter',
);

View file

@ -408,15 +408,13 @@ return array(
'Comment updated' => 'Comentario actualizado',
'New comment posted by %s' => 'Nuevo comentario agregado por %s',
'List of due tasks for the project "%s"' => 'Lista de tareas para el proyecto "%s"',
'[%s][New attachment] %s (#%d)' => '[%s][uevo adjunto] %s (#%d)',
'[%s][New comment] %s (#%d)' => '[%s][Nuevo comentario] %s (#%d)',
'[%s][Comment updated] %s (#%d)' => '[%s][Comentario actualizado] %s (#%d)',
'[%s][New subtask] %s (#%d)' => '[%s][Nueva subtarea] %s (#%d)',
'[%s][Subtask updated] %s (#%d)' => '[%s][Subtarea actualizada] %s (#%d)',
'[%s][New task] %s (#%d)' => '[%s][Nueva tarea] %s (#%d)',
'[%s][Task updated] %s (#%d)' => '[%s][Tarea actualizada] %s (#%d)',
'[%s][Task closed] %s (#%d)' => '[%s][Tarea cerrada] %s (#%d)',
'[%s][Task opened] %s (#%d)' => '[%s][Tarea abierta] %s (#%d)',
// 'New attachment' => '',
// 'New comment' => '',
// 'New subtask' => '',
// 'Subtask updated' => '',
// 'Task updated' => '',
// 'Task closed' => '',
// 'Task opened' => '',
'[%s][Due tasks]' => '[%s][Tareas vencidas]',
'[Kanboard] Notification' => '[Kanboard] Notificación',
'I want to receive notifications only for those projects:' => 'Quiero recibir notificaciones sólo de estos proyectos:',
@ -500,131 +498,131 @@ return array(
'Task assignee change' => 'Cambiar persona asignada a la tarea',
// '%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)',
// 'Column Change' => '',
// 'Position Change' => '',
// 'Assignee Change' => '',
'New password for the user "%s"' => 'Nueva contraseña para el usuario "%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.' => '',
// 'Webhooks' => '',
// 'API' => '',
// 'Integration' => '',
// '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' => '',
// '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' => '',
// '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"' => '',
'Choose an event' => 'Escoga un evento',
'Github commit received' => 'Envío a Github recibido',
'Github issue opened' => 'Problema en Github abierto',
'Github issue closed' => 'Problema en Github cerrado',
'Github issue reopened' => 'Problema en Github reabierto',
'Github issue assignee change' => 'Cambio en signación de problema en Github',
'Github issue label change' => 'Cambio en etiqueta del problema',
'Create a task from an external provider' => 'Crear una tarea a partir de un proveedor externo',
'Change the assignee based on an external username' => 'Cambiar la asignación basado en un nombre de usuario externo',
'Change the category based on an external label' => 'Cambiar la categoría basado en una etiqueta externa',
'Reference' => 'Referencia',
'Reference: %s' => 'Referencia: %s',
'Label' => 'Etiqueta',
'Database' => 'Base de Datos',
'About' => 'Acerca de',
'Database driver:' => 'Driver de la base de datos',
'Board settings' => 'Configuraciones del Tablero',
'URL and token' => 'URL y token',
'Webhook settings' => 'Configuraciones del Webhook',
'URL for task creation:' => 'URL para la creación de tareas',
'Reset token' => 'Resetear token',
'API endpoint:' => 'Punto final del API',
'Refresh interval for private board' => 'Intervalo de refrescamiento del tablero privado',
'Refresh interval for public board' => 'Intervalo de refrescamiento del tablero público',
'Task highlight period' => 'Periodo del realce de la tarea',
'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Periodo (en segundos) para considerar que una tarea fué modificada recientemente (0 para deshabilitar, 2 días por defecto)',
'Frequency in second (60 seconds by default)' => 'Frecuencia en segundos (60 segundos por defecto)',
'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Frecuencia en segundos (0 para deshabilitar esta característica, 10 segundos por defecto)',
'Application URL' => 'URL de la aplicación',
'Example: http://example.kanboard.net/ (used by email notifications)' => 'Ejemplo: http://ejemplo.kanboard.net/ (usado por las notificaciones de correo)',
'Token regenerated.' => 'Token regenerado',
'Date format' => 'Formato de la fecha',
'ISO format is always accepted, example: "%s" and "%s"' => 'El formato ISO siempre es aceptado, ejemplo: "%s" y "%s"',
'New private project' => 'Nuevo proyecto privado',
'This project is private' => 'Este proyecto es privado',
'Type here to create a new sub-task' => 'Escriba aquí para crear una nueva sub-tarea',
'Add' => 'Añadir',
'Estimated time: %s hours' => 'Tiempo estimado: % horas',
'Time spent: %s hours' => 'Tiempo invertido: %s horas',
'Started on %B %e, %Y' => 'Iniciado en %B %e, %Y',
'Start date' => 'Fecha de inicio',
'Time estimated' => 'Tiempo estimado',
'There is nothing assigned to you.' => 'Esto no le está asignado',
'My tasks' => 'Mis tareas',
'Activity stream' => 'Flujo de actividad',
'Dashboard' => 'Tablero',
'Confirmation' => 'Confirmación',
'Allow everybody to access to this project' => 'Permitir a cualquier acceder a este proyecto',
'Everybody have access to this project.' => 'Cualquier tiene acceso a este proyecto',
'Webhooks' => 'Webhooks',
'API' => 'API',
'Integration' => 'Integración',
'Github webhooks' => 'Webhooks de Github',
'Help on Github webhooks' => 'Ayuda con los Webhook de Github',
'Create a comment from an external provider' => 'Crear un comentario a partir de un proveedor externo',
'Github issue comment created' => 'Creado el comentario del problema en Github',
'Configure' => 'Configurar',
'Project management' => 'Administración del proyecto',
'My projects' => 'Mis proyectos',
'Columns' => 'Columnas',
'Task' => 'Tarea',
'Your are not member of any project.' => 'No es miembro de ningún proyecto',
'Percentage' => 'Porcentaje',
'Number of tasks' => 'Número de tareas',
'Task distribution' => 'Distribución de tareas',
'Reportings' => 'Reportes',
'Task repartition for "%s"' => 'Repartición de tareas para "%s"',
'Analytics' => 'Analítica',
'Subtask' => 'Subtarea',
'My subtasks' => 'Mis subtareas',
'User repartition' => 'Repartición de usuarios',
'User repartition for "%s"' => 'Repartición para "%s"',
'Clone this project' => 'Clonar este proyecto',
'Column removed successfully.' => 'Columna removida correctamente',
'Edit Project' => 'Editar Proyecto',
'Github Issue' => 'Problema Github',
'Not enough data to show the graph.' => 'No hay suficiente información para mostrar el gráfico',
'Previous' => 'Anterior',
'The id must be an integer' => 'El id debe ser un entero',
'The project id must be an integer' => 'El id del proyecto debe ser un entero',
'The status must be an integer' => 'El estado debe ser un entero',
'The subtask id is required' => 'El id de la subtarea es requerido',
'The subtask id must be an integer' => 'El id de la subtarea debe ser un entero',
'The task id is required' => 'El id de la tarea es requerido',
'The task id must be an integer' => 'El id de la tarea debe ser un entero',
'The user id must be an integer' => 'El id del usuario debe ser un entero',
'This value is required' => 'El valor es requerido',
'This value must be numeric' => 'Este valor debe ser numérico',
'Unable to create this task.' => 'Imposible crear esta tarea',
'Cumulative flow diagram' => 'Diagrama de flujo acumulativo',
'Cumulative flow diagram for "%s"' => 'Diagrama de flujo acumulativo para "%s"',
'Daily project summary' => 'Sumario diario del proyecto',
'Daily project summary export' => 'Exportar sumario diario del proyecto',
'Daily project summary export for "%s"' => 'Exportar sumario diario del proyecto para "%s"',
'Exports' => 'Exportar',
'This export contains the number of tasks per column grouped per day.' => 'Esta exportación contiene el número de tereas por columna agrupada por día',
'Nothing to preview...' => 'Nada que previsualizar...',
'Preview' => 'Previsualizar',
'Write' => 'Escribir',
'Active swimlanes' => 'Carriles activos',
'Add a new swimlane' => 'Añadir nuevo carril',
'Change default swimlane' => 'Cambiar el carril por defecto',
'Default swimlane' => 'Carril por defecto',
'Do you really want to remove this swimlane: "%s"?' => '¿Realmente quiere remover este carril: "%s"?',
'Inactive swimlanes' => 'Carriles inactivos',
'Set project manager' => 'Asignar administrador del proyecto',
'Set project member' => 'Asignar miembro del proyecto',
'Remove a swimlane' => 'Remover un carril',
'Rename' => 'Renombrar',
'Show default swimlane' => 'Mostrar carril por defecto',
'Swimlane modification for the project "%s"' => '',
'Swimlane not found.' => 'Carril no encontrado',
'Swimlane removed successfully.' => 'Carril removido correctamente',
'Swimlanes' => 'Carriles',
'Swimlane updated successfully.' => 'Carril actualizado correctamente',
'The default swimlane have been updated successfully.' => 'El carril por defecto ha sido actualizado correctamente',
'Unable to create your swimlane.' => 'Imposible crear su carril',
'Unable to remove this swimlane.' => 'Imposible remover este carril',
'Unable to update this swimlane.' => 'Imposible actualizar este carril',
'Your swimlane have been created successfully.' => 'Su carril ha sido creado correctamente',
'Example: "Bug, Feature Request, Improvement"' => 'Ejemplo: "Error, Solicitud de característica, Mejora',
// 'Default categories for new projects (Comma-separated)' => '',
// 'Gitlab commit received' => '',
// 'Gitlab issue opened' => '',
@ -642,10 +640,98 @@ return array(
// 'Subtasks' => '',
// 'Subtasks Export' => '',
// 'Subtasks exportation for "%s"' => '',
// 'Task Title' => '',
// 'Untitled' => '',
// 'Application default' => '',
// 'Language:' => '',
// 'Timezone:' => '',
// 'Next' => '',
'Task Title' => 'Título de la tarea',
'Untitled' => 'Sin título',
'Application default' => 'Predefinido de la aplicación',
'Language:' => 'Idioma',
'Timezone:' => 'Zona horaria',
'All columns' => 'Todas las columnas',
'Calendar for "%s"' => 'Calendario para "%s"',
'Filter by column' => 'Filtrar por columna',
'Filter by status' => 'Filtrar por estado',
'Calendar' => 'Calendario',
'Next' => 'Siguiente',
'#%d' => '',
'Filter by color' => 'Filtrar por color',
'Filter by swimlane' => 'Filtrar por carril',
'All swimlanes' => 'Todos los carriles',
'All colors' => 'Todos los colores',
'All status' => 'Todos los estados',
// 'Add a comment logging moving the task between columns' => '',
'Moved to column %s' => 'Movido a columna %s',
// 'Change description' => '',
// 'User dashboard' => '',
// 'Allow only one subtask in progress at the same time for a user' => '',
// 'Edit column "%s"' => '',
// 'Enable time tracking for subtasks' => '',
// 'Select the new status of the subtask: "%s"' => '',
// 'Subtask timesheet' => '',
// 'There is nothing to show.' => '',
// 'Time Tracking' => '',
// 'You already have one subtask in progress' => '',
// 'Which parts of the project do you want to duplicate?' => '',
// 'Change dashboard view' => '',
// 'Show/hide activities' => '',
// 'Show/hide projects' => '',
// 'Show/hide subtasks' => '',
// 'Show/hide tasks' => '',
// 'Disable login form' => '',
// 'Show/hide calendar' => '',
// 'User calendar' => '',
// 'Bitbucket commit received' => '',
// 'Bitbucket webhooks' => '',
// 'Help on Bitbucket webhooks' => '',
// 'Start' => '',
// 'End' => '',
// 'Task age in days' => '',
// 'Days in this column' => '',
// '%dd' => '',
// 'Add a link' => '',
// 'Add a new link' => '',
// 'Do you really want to remove this link: "%s"?' => '',
// 'Do you really want to remove this link with task #%d?' => '',
// 'Field required' => '',
// 'Link added successfully.' => '',
// 'Link updated successfully.' => '',
// 'Link removed successfully.' => '',
// 'Link labels' => '',
// 'Link modification' => '',
// 'Links' => '',
// 'Link settings' => '',
// 'Opposite label' => '',
// 'Remove a link' => '',
// 'Task\'s links' => '',
// 'The labels must be different' => '',
// 'There is no link.' => '',
// 'This label must be unique' => '',
// 'Unable to create your link.' => '',
// 'Unable to update your link.' => '',
// 'Unable to remove this link.' => '',
// 'relates to' => '',
// 'blocks' => '',
// 'is blocked by' => '',
// 'duplicates' => '',
// 'is duplicated by' => '',
// 'is a child of' => '',
// 'is a parent of' => '',
// 'targets milestone' => '',
// 'is a milestone of' => '',
// 'fixes' => '',
// 'is fixed by' => '',
// 'This task' => '',
// '<1h' => '',
// '%dh' => '',
// '%b %e' => '',
// 'Expand tasks' => '',
// 'Collapse tasks' => '',
// 'Expand/collapse tasks' => '',
// 'Close dialog box' => '',
// 'Submit a form' => '',
// 'Board view' => '',
// 'Keyboard shortcuts' => '',
// 'Open board switcher' => '',
// 'Application' => '',
// 'Filter recently updated' => '',
// 'since %B %e, %Y at %k:%M %p' => '',
// 'More filters' => '',
);

View file

@ -408,15 +408,13 @@ return array(
'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)' => '',
// '[%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)' => '',
// 'New attachment' => '',
// 'New comment' => '',
// 'New subtask' => '',
// 'Subtask updated' => '',
// 'Task updated' => '',
// 'Task closed' => '',
// 'Task opened' => '',
// '[%s][Due tasks]' => '',
// '[Kanboard] Notification' => '',
'I want to receive notifications only for those projects:' => 'Haluan vastaanottaa ilmoituksia ainoastaan näistä projekteista:',
@ -500,9 +498,9 @@ return array(
'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)' => '',
// 'Column Change' => '',
// 'Position Change' => '',
// 'Assignee Change' => '',
'New password for the user "%s"' => 'Uusi salasana käyttäjälle "%s"',
'Choose an event' => 'Valitse toiminta',
'Github commit received' => 'Github-kommitti vastaanotettu',
@ -647,5 +645,93 @@ return array(
// 'Application default' => '',
// 'Language:' => '',
// 'Timezone:' => '',
// 'All columns' => '',
// 'Calendar for "%s"' => '',
// 'Filter by column' => '',
// 'Filter by status' => '',
// 'Calendar' => '',
// 'Next' => '',
// '#%d' => '',
// 'Filter by color' => '',
// 'Filter by swimlane' => '',
// 'All swimlanes' => '',
// 'All colors' => '',
// 'All status' => '',
// 'Add a comment logging moving the task between columns' => '',
// 'Moved to column %s' => '',
// 'Change description' => '',
// 'User dashboard' => '',
// 'Allow only one subtask in progress at the same time for a user' => '',
// 'Edit column "%s"' => '',
// 'Enable time tracking for subtasks' => '',
// 'Select the new status of the subtask: "%s"' => '',
// 'Subtask timesheet' => '',
// 'There is nothing to show.' => '',
// 'Time Tracking' => '',
// 'You already have one subtask in progress' => '',
// 'Which parts of the project do you want to duplicate?' => '',
// 'Change dashboard view' => '',
// 'Show/hide activities' => '',
// 'Show/hide projects' => '',
// 'Show/hide subtasks' => '',
// 'Show/hide tasks' => '',
// 'Disable login form' => '',
// 'Show/hide calendar' => '',
// 'User calendar' => '',
// 'Bitbucket commit received' => '',
// 'Bitbucket webhooks' => '',
// 'Help on Bitbucket webhooks' => '',
// 'Start' => '',
// 'End' => '',
// 'Task age in days' => '',
// 'Days in this column' => '',
// '%dd' => '',
// 'Add a link' => '',
// 'Add a new link' => '',
// 'Do you really want to remove this link: "%s"?' => '',
// 'Do you really want to remove this link with task #%d?' => '',
// 'Field required' => '',
// 'Link added successfully.' => '',
// 'Link updated successfully.' => '',
// 'Link removed successfully.' => '',
// 'Link labels' => '',
// 'Link modification' => '',
// 'Links' => '',
// 'Link settings' => '',
// 'Opposite label' => '',
// 'Remove a link' => '',
// 'Task\'s links' => '',
// 'The labels must be different' => '',
// 'There is no link.' => '',
// 'This label must be unique' => '',
// 'Unable to create your link.' => '',
// 'Unable to update your link.' => '',
// 'Unable to remove this link.' => '',
// 'relates to' => '',
// 'blocks' => '',
// 'is blocked by' => '',
// 'duplicates' => '',
// 'is duplicated by' => '',
// 'is a child of' => '',
// 'is a parent of' => '',
// 'targets milestone' => '',
// 'is a milestone of' => '',
// 'fixes' => '',
// 'is fixed by' => '',
// 'This task' => '',
// '<1h' => '',
// '%dh' => '',
// '%b %e' => '',
// 'Expand tasks' => '',
// 'Collapse tasks' => '',
// 'Expand/collapse tasks' => '',
// 'Close dialog box' => '',
// 'Submit a form' => '',
// 'Board view' => '',
// 'Keyboard shortcuts' => '',
// 'Open board switcher' => '',
// 'Application' => '',
// 'Filter recently updated' => '',
// 'since %B %e, %Y at %k:%M %p' => '',
// 'More filters' => '',
);

View file

@ -278,7 +278,7 @@ return array(
'Remember Me' => 'Connexion automatique',
'Creation date' => 'Date de création',
'Filter by user' => 'Filtrer par utilisateur',
'Filter by due date' => 'Filtrer par date d\'échéance',
'Filter by due date' => 'Avec une date d\'échéance',
'Everybody' => 'Tout le monde',
'Open' => 'Ouvert',
'Closed' => 'Fermé',
@ -341,7 +341,7 @@ return array(
'Add a comment' => 'Ajouter un commentaire',
'Edit a comment' => 'Modifier un commentaire',
'Summary' => 'Résumé',
'Time tracking' => 'Gestion du temps',
'Time tracking' => 'Suivi du temps',
'Estimate:' => 'Estimation :',
'Spent:' => 'Passé :',
'Do you really want to remove this sub-task?' => 'Voulez-vous vraiment supprimer cette sous-tâche ?',
@ -408,15 +408,15 @@ return array(
'Comment updated' => 'Commentaire ajouté',
'New comment posted by %s' => 'Nouveau commentaire ajouté par « %s »',
'List of due tasks for the project "%s"' => 'Liste des tâches expirées pour le projet « %s »',
'[%s][New attachment] %s (#%d)' => '[%s][Pièce-jointe] %s (#%d)',
'[%s][New comment] %s (#%d)' => '[%s][Nouveau commentaire] %s (#%d)',
'[%s][Comment updated] %s (#%d)' => '[%s][Commentaire mis à jour] %s (#%d)',
'[%s][New subtask] %s (#%d)' => '[%s][Nouvelle sous-tâche] %s (#%d)',
'[%s][Subtask updated] %s (#%d)' => '[%s][Sous-tâche mise à jour] %s (#%d)',
'[%s][New task] %s (#%d)' => '[%s][Nouvelle tâche] %s (#%d)',
'[%s][Task updated] %s (#%d)' => '[%s][Tâche mise à jour] %s (#%d)',
'[%s][Task closed] %s (#%d)' => '[%s][Tâche fermée] %s (#%d)',
'[%s][Task opened] %s (#%d)' => '[%s][Tâche ouverte] %s (#%d)',
'New attachment' => 'Nouveau document',
'New comment' => 'Nouveau commentaire',
'Comment updated' => 'Commentaire mis à jour',
'New subtask' => 'Nouvelle sous-tâche',
'Subtask updated' => 'Sous-tâche mise à jour',
'New task' => 'Nouvelle tâche',
'Task updated' => 'Tâche mise à jour',
'Task closed' => 'Tâche fermée',
'Task opened' => 'Tâche ouverte',
'[%s][Due tasks]' => '[%s][Tâches expirées]',
'[Kanboard] Notification' => '[Kanboard] Notification',
'I want to receive notifications only for those projects:' => 'Je souhaite reçevoir les notifications uniquement pour les projets sélectionnés :',
@ -500,9 +500,9 @@ return array(
'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 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)',
'Column Change' => 'Changement de colonne',
'Position Change' => 'Changement de position',
'Assignee Change' => 'Changement d\'assigné',
'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',
@ -647,6 +647,93 @@ return array(
'Application default' => 'Valeur par défaut de l\'application',
'Language:' => 'Langue :',
'Timezone:' => 'Fuseau horaire :',
'All columns' => 'Toutes les colonnes',
'Calendar for "%s"' => 'Agenda pour le projet « %s »',
'Filter by column' => 'Filtrer par colonne',
'Filter by status' => 'Filtrer par status',
'Calendar' => 'Agenda',
'Next' => 'Suivant',
'#%d' => 'n˚%d',
'Filter by color' => 'Filtrer par couleur',
'Filter by swimlane' => 'Filtrer par swimlanes',
'All swimlanes' => 'Toutes les swimlanes',
'All colors' => 'Toutes les couleurs',
'All status' => 'Tous les états',
'Add a comment logging moving the task between columns' => 'Ajouter un commentaire de log lorsqu\'une tâche est déplacée dans une autre colonne',
'Moved to column %s' => 'Tâche déplacée à la colonne %s',
'Change description' => 'Changer la description',
'User dashboard' => 'Tableau de bord de l\'utilisateur',
'Allow only one subtask in progress at the same time for a user' => 'Autoriser une seule sous-tâche en progrès en même temps pour un utilisateur',
'Edit column "%s"' => 'Modifier la colonne « %s »',
'Enable time tracking for subtasks' => 'Activer la feuille de temps pour les sous-tâches',
'Select the new status of the subtask: "%s"' => 'Selectionnez le nouveau statut de la sous-tâche : « %s »',
'Subtask timesheet' => 'Feuille de temps des sous-tâches',
'There is nothing to show.' => 'Il n\'y a rien à montrer.',
'Time Tracking' => 'Feuille de temps',
'You already have one subtask in progress' => 'Vous avez déjà une sous-tâche en progrès',
'Which parts of the project do you want to duplicate?' => 'Quelles parties du projet voulez-vous dupliquer ?',
'Change dashboard view' => 'Changer la vue du tableau de bord',
'Show/hide activities' => 'Afficher/cacher les activités',
'Show/hide projects' => 'Afficher/cacher les projets',
'Show/hide subtasks' => 'Afficher/cacher les sous-tâches',
'Show/hide tasks' => 'Afficher/cacher les tâches',
'Disable login form' => 'Désactiver le formulaire d\'authentification',
'Show/hide calendar' => 'Afficher/cacher le calendrier',
'User calendar' => 'Calendrier de l\'utilisateur',
'Bitbucket commit received' => '« Commit » reçu via Bitbucket',
'Bitbucket webhooks' => 'Webhook Bitbucket',
'Help on Bitbucket webhooks' => 'Aide sur les webhooks Bitbucket',
'Start' => 'Début',
'End' => 'Fin',
'Task age in days' => 'Age de la tâche en jours',
'Days in this column' => 'Jours dans cette colonne',
'%dd' => '%dj',
'Add a link' => 'Ajouter un lien',
'Add a new link' => 'Ajouter un nouveau lien',
'Do you really want to remove this link: "%s"?' => 'Voulez-vous vraiment supprimer ce lien : « %s » ?',
'Do you really want to remove this link with task #%d?' => 'Voulez-vous vraiment supprimer ce lien avec la tâche n°%d ?',
'Field required' => 'Champ obligatoire',
'Link added successfully.' => 'Lien créé avec succès.',
'Link updated successfully.' => 'Lien mis à jour avec succès.',
'Link removed successfully.' => 'Lien supprimé avec succès.',
'Link labels' => 'Libellé des liens',
'Link modification' => 'Modification d\'un lien',
'Links' => 'Liens',
'Link settings' => 'Paramètres des liens',
'Opposite label' => 'Nom du libellé opposé',
'Remove a link' => 'Supprimer un lien',
'Task\'s links' => 'Liens des tâches',
'The labels must be different' => 'Les libellés doivent être différents',
'There is no link.' => 'Il n\'y a aucun lien.',
'This label must be unique' => 'Ce libellé doit être unique',
'Unable to create your link.' => 'Impossible d\'ajouter ce lien.',
'Unable to update your link.' => 'Impossible de mettre à jour ce lien.',
'Unable to remove this link.' => 'Impossible de supprimer ce lien.',
'relates to' => 'est liée à',
'blocks' => 'bloque',
'is blocked by' => 'est bloquée par',
'duplicates' => 'duplique',
'is duplicated by' => 'est dupliquée par',
'is a child of' => 'est un enfant de',
'is a parent of' => 'est un parent de',
'targets milestone' => 'vise l\'étape importante',
'is a milestone of' => 'est une étape importante de',
'fixes' => 'corrige',
'is fixed by' => 'est corrigée par',
'This task' => 'Cette tâche',
'<1h' => '<1h',
'%dh' => '%dh',
'%b %e' => '%e %b',
'Expand tasks' => 'Déplier les tâches',
'Collapse tasks' => 'Replier les tâches',
'Expand/collapse tasks' => 'Plier/déplier les tâches',
'Close dialog box' => 'Fermer une boite de dialogue',
'Submit a form' => 'Enregistrer un formulaire',
'Board view' => 'Page du tableau',
'Keyboard shortcuts' => 'Raccourcis clavier',
'Open board switcher' => 'Ouvrir le sélecteur de tableau',
'Application' => 'Application',
'Filter recently updated' => 'Récemment modifié',
'since %B %e, %Y at %k:%M %p' => 'depuis le %d/%m/%Y à %H:%M',
'More filters' => 'Plus de filtres',
);

View file

@ -1,23 +1,23 @@
<?php
return array(
'None' => 'Semelyik',
'None' => 'Nincs',
'edit' => 'szerkesztés',
'Edit' => 'Szerkesztés',
'remove' => 'eltávolít',
'Remove' => 'Eltávolít',
'remove' => 'eltávolítás',
'Remove' => 'Eltávolítás',
'Update' => 'Frissítés',
'Yes' => 'Igen',
'No' => 'Nincs',
'cancel' => 'mégsem',
'No' => 'Nem',
'cancel' => 'Mégsem',
'or' => 'vagy',
'Yellow' => 'sárga',
'Blue' => 'kék',
'Green' => 'zöld',
'Purple' => 'ibolya',
'Red' => 'piros',
'Orange' => 'narancs',
'Grey' => 'szürke',
'Yellow' => 'Sárga',
'Blue' => 'Kék',
'Green' => 'Zöld',
'Purple' => 'Lila',
'Red' => 'Piros',
'Orange' => 'Narancs',
'Grey' => 'Szürke',
'Save' => 'Mentés',
'Login' => 'Bejelentkezés',
'Official website:' => 'Hivatalos honlap:',
@ -25,7 +25,7 @@ return array(
'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ó',
'New user' => 'Új felhasználó',
'All users' => 'Minden felhasználó',
'Username' => 'Felhasználónév',
'Password' => 'Jelszó',
@ -56,17 +56,17 @@ return array(
'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 on the board' => '%d feladat a táblán',
'%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',
'Disable' => 'Letiltás',
'Enable' => 'Engedélyezés',
'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"',
'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',
@ -92,7 +92,7 @@ return array(
'(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:',
'My default project:' => 'Alapértelmezett projekt: ',
'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',
@ -100,17 +100,17 @@ return array(
'Color' => 'Szín',
'Assignee' => 'Felelős',
'Create another task' => 'Új feladat létrehozása',
'New task' => 'új feladat',
'Open a task' => 'Feladat megnyitása',
'New task' => 'Új feladat',
'Open a task' => 'Feladat felnyitás',
'Do you really want to open this task: "%s"?' => 'Tényleg meg akarja nyitni ezt a feladatot: "%s"?',
'Back to the board' => 'Vissza a táblához',
'Created on %B %e, %Y at %k:%M %p' => 'Létrehozva: %Y.%m.%d %k:%M %p',
'Created on %B %e, %Y at %k:%M %p' => 'Létrehozva: %Y.%m.%d %H:%M',
'There is nobody assigned' => 'Nincs felelős',
'Column on the board:' => 'Tábla oszlopa:',
'Status is open' => 'Állapot nyitva',
'Status is closed' => 'Állapot zárva',
'Close this task' => 'Feladat bezárása',
'Open this task' => 'Feladat megnyitása',
'Column on the board:' => 'Tábla oszlopa: ',
'Status is open' => 'Nyitott állapot',
'Status is closed' => 'Zárt állapot',
'Close this task' => 'Feladat lezárása',
'Open this task' => 'Feladat felnyitá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',
@ -133,37 +133,37 @@ return array(
'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.',
'Settings saved successfully.' => 'A beállítások mentése sikeres.',
'Unable to save your settings.' => 'A beállítások mentése sikertelen.',
'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.',
'Your project have been created successfully.' => 'Projekt sikeresen létrehozva',
'Unable to create your project.' => 'Projekt létrehozása sikertelen.',
'Project updated successfully.' => 'Projekt frissítése sikeres.',
'Unable to update this project.' => 'Projekt frissítése sikertelen.',
'Unable to remove this project.' => 'Projekt törlése sikertelen.',
'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 activated successfully.' => 'Projekt sikeresen aktiválva.',
'Unable to activate this project.' => 'Projekt aktiválása sikertelen.',
'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.',
'Unable to disable this project.' => 'Projekt letiltása sikertelen.',
'Unable to open this task.' => 'A feladat felnyitása nem sikerült.',
'Task opened successfully.' => 'Feladat sikeresen megnyitva .',
'Unable to close this task.' => 'A feladat lezárása nem sikerült.',
'Unable to close this task.' => 'A feladat lezárása sikertelen.',
'Task closed successfully.' => 'Feladat sikeresen lezárva.',
'Unable to update your task.' => 'A feladat frissítése nem sikerült.',
'Unable to update your task.' => 'Feladat frissítése sikertelen.',
'Task updated successfully.' => 'Feladat sikeresen frissítve.',
'Unable to create your task.' => 'A feladat létrehozása nem sikerült.',
'Unable to create your task.' => 'Feladat létrehozása sikertelen.',
'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 created successfully.' => 'Felhasználó létrehozva.',
'Unable to create your user.' => 'Felhasználó létrehozása sikertelen.',
'User updated successfully.' => 'Felhasználó sikeresen frissítve.',
'Unable to update your user.' => 'Felhasználó frissítése nem sikerült.',
'Unable to update your user.' => 'Felhasználó frissítése sikertelen.',
'User removed successfully.' => 'Felhasználó sikeresen törölve.',
'Unable to remove this user.' => 'Felhasználó törlése nem sikerült.',
'Unable to remove this user.' => 'Felhasználó törlése sikertelen.',
'Board updated successfully.' => 'Tábla sikeresen frissítve.',
'Ready' => 'Kész',
'Ready' => 'Előkészítés',
'Backlog' => 'Napló',
'Work in progress' => 'Dolgozom',
'Work in progress' => 'Folyamatban',
'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 ..',
@ -174,7 +174,7 @@ return array(
'No task' => 'Nincs feladat',
'Completed tasks' => 'Elvégzett feladatok',
'List of projects' => 'Projektek listája',
'Completed tasks for "%s"' => 'Elvégzett feladatok "%s"',
'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',
@ -213,14 +213,14 @@ return array(
'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' => '',
'%b %e, %Y' => '%Y.%m.%d',
'Automatic actions' => 'Automatikus intézkedések',
'Your automatic action have been created successfully.' => 'Az automatikus intézkedés sikeresen elkészült.',
'Unable to create your automatic action.' => 'Automatikus intézkedés létrehozása nem lehetséges.',
'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"',
'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',
@ -242,15 +242,15 @@ return array(
'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',
'Open a closed task' => 'Lezárt feladat felnyitá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',
'Duplicate to another project' => 'Másolás másik projektbe',
'Duplicate' => 'Másolás',
'link' => 'link',
'Update this comment' => 'Hozzászólás frissítése',
'Comment updated successfully.' => 'Megjegyzés sikeresen frissítve.',
@ -261,7 +261,7 @@ return array(
'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"',
'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',
@ -279,14 +279,14 @@ return array(
'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',
'Everybody' => 'Minden felhasználó',
'Open' => 'Nyitott',
'Closed' => 'Lezárt',
'Search' => 'Keres',
'Nothing found.' => 'Semmit sem találtam.',
'Search in the project "%s"' => 'Keresés a projektben "%s"',
'Search' => 'Keresés',
'Nothing found.' => 'Nincs találat.',
'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',
'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',
@ -302,7 +302,7 @@ return array(
'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.',
'Task removed successfully.' => 'Feladat sikeresen törölve.',
'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"?',
@ -313,7 +313,7 @@ return array(
'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.',
'Your category have been created successfully.' => 'Kategória sikeresen létrehozva.',
'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.',
@ -324,14 +324,14 @@ return array(
'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',
'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ória szerint',
'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.',
'File removed successfully.' => 'Fájl sikeresen törölve.',
'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',
@ -344,12 +344,12 @@ return array(
'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"?',
'Do you really want to remove this sub-task?' => 'Valóban törölni akarja ezt a részfeladatot?',
'Remaining:' => 'Hátralévő:',
'hours' => 'óra',
'spent' => 'eltöltött',
'estimated' => 'becsült',
'Sub-Tasks' => 'részfeladatok',
'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',
@ -364,8 +364,8 @@ return array(
'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:',
'Sub-task added successfully.' => 'Részfeladat sikeresen létrehozva.',
'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.',
@ -377,9 +377,9 @@ return array(
'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',
'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',
'Tasks exportation for "%s"' => 'Feladatok exportálása: "%s"',
'Start Date' => 'Kezdés dátuma',
'End Date' => 'Befejezés dátuma',
'Execute' => 'Végrehajt',
@ -390,33 +390,31 @@ return array(
'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.',
'Clone Project' => 'Projekt másolása',
'Project cloned successfully.' => 'A projekt másolása sikeres',
'Unable to clone this project.' => 'A projekt másolása sikertelen.',
'Email notifications' => 'E-mail értesítések',
'Enable email notifications' => 'Engedélyezze az e-mail értesítéseket',
'Enable email notifications' => 'E-mail értesítések engedélyezése',
'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',
'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)',
'List of due tasks for the project "%s"' => 'Projekt esedékes feladatai: "%s"',
'New attachment' => 'Új melléklet',
'New comment' => 'Új megjegyzés',
'New subtask' => 'Új részfeladat',
'Subtask updated' => 'Részfeladat frissítve',
'Task updated' => 'Feladat frissítve',
'Task closed' => 'Feladat lezárva',
'Task opened' => 'Feladat megnyitva',
'[%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:',
@ -434,27 +432,27 @@ return array(
'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',
'Move the task to another project' => 'Feladat áthelyezése másik projektbe',
'Move to another project' => 'Áthelyezés 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',
'Account type' => 'Fiók típusa',
'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',
'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:',
'Account type:' => 'Fiók típusa:',
'Edit profile' => 'Profil szerkesztése',
'Change password' => 'Jelszó módosítása',
'Password modification' => 'Jelszó módosítása',
@ -464,9 +462,9 @@ return array(
'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.',
'No external authentication enabled.' => 'A külső azonosítás nincs engedélyezve.',
'Password modified successfully.' => 'A jelszó sikeresen módosítva.',
'Unable to change the password.' => 'A jelszó módosítása 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',
@ -481,7 +479,7 @@ return array(
'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',
'%s\'s activity' => 'Tevékenységek: %s',
'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',
@ -494,20 +492,20 @@ return array(
'%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"',
'Activity' => 'Tevékenységek',
'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"',
'Column Change' => 'Oszlop változtatás',
'Position Change' => 'Pozíció változtatás',
'Assignee Change' => 'Felelős változtatás',
'New password for the user "%s"' => 'Felhasználó új jelszava: %s',
'Choose an event' => 'Válasszon eseményt',
'Github commit received' => 'GitHub commit érkezett',
'Github issue opened' => 'GitHub issue nyílt',
'Github issue closed' => 'GitHub issue zárt',
'Github issue opened' => 'GitHub issue nyitás',
'Github issue closed' => 'GitHub issue zárás',
'Github issue reopened' => 'GitHub issue újranyitva',
'Github issue assignee change' => 'GitHub issue felelős változás',
'Github issue label change' => 'GitHub issue címke változás',
@ -524,23 +522,23 @@ return array(
'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:',
'Reset token' => 'Token újragenerálása',
'API endpoint:' => 'API végpont:',
'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)',
'Frequency in second (60 seconds by default)' => 'Gyakoriság másodpercben (alapértelmezetten 60 másodperc)',
'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Gyakoriság másodpercben (0 funkció letiltva, alapértelmezetten 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)',
'Example: http://example.kanboard.net/ (used by email notifications)' => 'Példa: http://example.kanboard.net/ (e-mail értesítőben használt)',
'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',
'Add' => 'Hozzáadás',
'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',
@ -549,9 +547,9 @@ return array(
'There is nothing assigned to you.' => 'Nincs kiosztott feladat.',
'My tasks' => 'Feladataim',
'Activity stream' => 'Legutóbbi tevékenységek',
'Dashboard' => 'Műszerfal',
'Dashboard' => 'Vezérlőpult',
'Confirmation' => 'Megerősítés',
'Allow everybody to access to this project' => 'Engedélyezze a projekt elérését mindenkinek',
'Allow everybody to access to this project' => 'A projekt elérése mindenkinek engedélyezett',
'Everybody have access to this project.' => 'Mindenki elérheti a projektet',
'Webhooks' => 'Webhook',
'API' => 'API',
@ -560,7 +558,7 @@ return array(
'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',
'Configure' => 'Beállítások',
'Project management' => 'Projekt menedzsment',
'My projects' => 'Projektjeim',
'Columns' => 'Oszlopok',
@ -570,12 +568,12 @@ return array(
'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',
'Task repartition for "%s"' => 'Feladat újraosztása: %s',
'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',
'User repartition for "%s"' => 'Felhasználó újrafelosztás: %s',
'Clone this project' => 'Projekt megkettőzése',
'Column removed successfully.' => 'Oszlop sikeresen eltávolítva.',
'Edit Project' => 'Projekt szerkesztése',
@ -594,58 +592,146 @@ return array(
'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',
'Cumulative flow diagram for "%s"' => 'Kumulatív Flow Diagram: %s',
'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',
'Daily project summary export for "%s"' => 'Napi projektösszefoglaló exportálása: %s',
'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' => '',
'Write' => 'Szerkesztés',
'Active swimlanes' => 'Aktív folyamatok',
'Add a new swimlane' => 'Új folyamat',
'Change default swimlane' => 'Alapértelmezett folyamat változtatás',
'Default swimlane' => 'Alapértelmezett folyamat',
'Do you really want to remove this swimlane: "%s"?' => 'Valóban törli a folyamatot:%s ?',
'Inactive swimlanes' => 'Inaktív folyamatok',
'Set project manager' => 'Beállítás projekt kezelőnek',
'Set project member' => 'Beállítás projekt felhasználónak',
'Remove a swimlane' => 'Folyamat törlés',
'Rename' => 'Átnevezés',
'Show default swimlane' => 'Alapértelmezett folyamat megjelenítése',
'Swimlane modification for the project "%s"' => '%s projekt folyamatainak módosítása',
'Swimlane not found.' => 'Folyamat nem található',
'Swimlane removed successfully.' => 'Folyamat sikeresen törölve.',
'Swimlanes' => 'Folyamatok',
'Swimlane updated successfully.' => 'Folyamat sikeresn frissítve',
'The default swimlane have been updated successfully.' => 'Az alapértelmezett folyamat sikeresen frissítve.',
'Unable to create your swimlane.' => 'A folyamat létrehozása sikertelen.',
'Unable to remove this swimlane.' => 'A folyamat törlése sikertelen.',
'Unable to update this swimlane.' => 'A folyamat frissítése sikertelen.',
'Your swimlane have been created successfully.' => 'A folyamat sikeresen létrehozva.',
'Example: "Bug, Feature Request, Improvement"' => 'Például: Hiba, Új funkció, Fejlesztés',
'Default categories for new projects (Comma-separated)' => 'Alapértelmezett kategóriák az új projektekben (Vesszővel elválasztva)',
'Gitlab commit received' => 'Gitlab commit érkezett',
'Gitlab issue opened' => 'Gitlab issue nyitás',
'Gitlab issue closed' => 'Gitlab issue zárás',
'Gitlab webhooks' => 'Gitlab webhooks',
'Help on Gitlab webhooks' => 'Gitlab webhooks súgó',
'Integrations' => 'Integráció',
'Integration with third-party services' => 'Integráció harmadik féllel',
'Role for this project' => 'Projekt szerepkör',
'Project manager' => 'Projekt kezelő',
'Project member' => 'Projekt felhasználó',
'A project manager can change the settings of the project and have more privileges than a standard user.' => 'A projekt kezelő képes megváltoztatni a projekt beállításait és több joggal rendelkezik mint az alap felhasználók.',
'Gitlab Issue' => 'Gitlab issue',
'Subtask Id' => 'Részfeladat id',
'Subtasks' => 'Részfeladatok',
'Subtasks Export' => 'Részfeladat exportálás',
'Subtasks exportation for "%s"' => 'Részfeladatok exportálása: %s',
'Task Title' => 'Feladat címe',
'Untitled' => 'Névtelen',
'Application default' => 'Alkalmazás alapértelmezett',
'Language:' => 'Nyelv:',
'Timezone:' => 'Időzóna:',
'All columns' => 'Minden oszlop',
'Calendar for "%s"' => 'Naptár: %s',
'Filter by column' => 'Szűrés oszlop szerint',
'Filter by status' => 'Szűrés állapot szerint',
'Calendar' => 'Naptár',
'Next' => 'Következő',
'#%d' => '#%d',
'Filter by color' => 'Szűrés szín szerint',
'Filter by swimlane' => 'Szűrés folyamat szerint',
'All swimlanes' => 'Minden folyamat',
'All colors' => 'Minden szín',
'All status' => 'Minden állapot',
'Add a comment logging moving the task between columns' => 'Feladat oszlopok közötti mozgatását megjegyzésben feltüntetni',
'Moved to column %s' => '%s oszlopba áthelyezve',
'Change description' => 'Leírás szerkesztés',
'User dashboard' => 'Felhasználói vezérlőpult',
'Allow only one subtask in progress at the same time for a user' => 'Egyszerre csak egy folyamatban levő részfeladat engedélyezése a felhasználóknak',
'Edit column "%s"' => 'Oszlop szerkesztés: %s',
'Enable time tracking for subtasks' => 'Idő követés engedélyezése a részfeladatokhoz',
'Select the new status of the subtask: "%s"' => 'Részfeladat állapot változtatás: %s',
'Subtask timesheet' => 'Részfeladat idővonal',
'There is nothing to show.' => 'Nincs megjelenítendő adat.',
'Time Tracking' => 'Idő követés',
'You already have one subtask in progress' => 'Már van egy folyamatban levő részfeladata',
'Which parts of the project do you want to duplicate?' => 'A projekt mely részeit szeretné duplikálni?',
'Change dashboard view' => 'Vezérlőpult megjelenítés változtatás',
'Show/hide activities' => 'Tevékenységek megjelenítése/elrejtése',
'Show/hide projects' => 'Projektek megjelenítése/elrejtése',
'Show/hide subtasks' => 'Részfeladatok megjelenítése/elrejtése',
'Show/hide tasks' => 'Feladatok megjelenítése/elrejtése',
'Disable login form' => 'Bejelentkező képernyő tiltása',
'Show/hide calendar' => 'Naptár megjelenítés/elrejtés',
'User calendar' => 'Naptár',
'Bitbucket commit received' => 'Bitbucket commit érkezett',
'Bitbucket webhooks' => 'Bitbucket webhooks',
'Help on Bitbucket webhooks' => 'Bitbucket webhooks súgó',
'Start' => 'Kezdet',
'End' => 'Vég',
'Task age in days' => 'Feladat életkora napokban',
'Days in this column' => 'Napok ebben az oszlopban',
'%dd' => '%dd',
'Add a link' => 'Hivatkozás hozzáadása',
'Add a new link' => 'Új hivatkozás hozzáadása',
'Do you really want to remove this link: "%s"?' => 'Biztos törölni akarja a hivatkozást: "%s"?',
'Do you really want to remove this link with task #%d?' => 'Biztos törölni akarja a(z) #%s. feladatra mutató hivatkozást?',
'Field required' => 'Kötelező mező',
'Link added successfully.' => 'Hivatkozás sikeresen létrehozva.',
'Link updated successfully.' => 'Hivatkozás sikeresen frissítve.',
'Link removed successfully.' => 'Hivatkozás sikeresen törölve.',
'Link labels' => 'Hivatkozás címkék',
'Link modification' => 'Hivatkozás módosítás',
'Links' => 'Hivatkozások',
'Link settings' => 'Hivatkozás beállítasok',
'Opposite label' => 'Ellenekező címke',
'Remove a link' => 'Hivatkozás törlése',
'Task\'s links' => 'Feladat hivatkozások',
'The labels must be different' => 'A címkék nem lehetnek azonosak',
'There is no link.' => 'Nincs hivatkozás.',
'This label must be unique' => 'A címkének egyedinek kell lennie.',
'Unable to create your link.' => 'Hivatkozás létrehozása sikertelen.',
'Unable to update your link.' => 'Hivatkozás frissítése sikertelen.',
'Unable to remove this link.' => 'Hivatkozás törlése sikertelen.',
'relates to' => 'hozzá tartozik:',
'blocks' => 'letiltva:',
'is blocked by' => 'letitoltta:',
'duplicates' => 'eredeti:',
'is duplicated by' => 'másolat:',
'is a child of' => 'szülője:',
'is a parent of' => 'gyermeke:',
'targets milestone' => 'megcélzott mérföldkő:',
'is a milestone of' => 'ehhez a mérföldkőhöz tartozik:',
'fixes' => 'javítás:',
'is fixed by' => 'javította:',
'This task' => 'Ez a feladat',
'<1h' => '<1ó',
'%dh' => '%dó',
'%b %e' => '%b %e',
'Expand tasks' => 'Feladatok lenyitása',
'Collapse tasks' => 'Feladatok összecsukása',
'Expand/collapse tasks' => 'Feladatok lenyitása/összecsukása',
'Close dialog box' => 'Ablak bezárása',
'Submit a form' => 'Űrlap beküldése',
'Board view' => 'Tábla nézet',
'Keyboard shortcuts' => 'Billentyű kombináció',
'Open board switcher' => 'Tábla választó lenyitása',
// 'Application' => '',
// 'Filter recently updated' => '',
// 'since %B %e, %Y at %k:%M %p' => '',
// 'More filters' => '',
);

View file

@ -408,15 +408,13 @@ return array(
'Comment updated' => 'Commento aggiornato',
'New comment posted by %s' => 'Nuovo commento aggiunto da « %s »',
'List of due tasks for the project "%s"' => 'Lista dei compiti scaduti per il progetto « %s »',
'[%s][New attachment] %s (#%d)' => '[%s][Nuovo allegato] %s (#%d)',
'[%s][New comment] %s (#%d)' => '[%s][Nuovo commento] %s (#%d)',
'[%s][Comment updated] %s (#%d)' => '[%s][Commento aggiornato] %s (#%d)',
'[%s][New subtask] %s (#%d)' => '[%s][Nuovo sotto-compito] %s (#%d)',
'[%s][Subtask updated] %s (#%d)' => '[%s][Sotto-compito aggiornato] %s (#%d)',
'[%s][New task] %s (#%d)' => '[%s][Nuovo compito] %s (#%d)',
'[%s][Task updated] %s (#%d)' => '[%s][Compito aggiornato] %s (#%d)',
'[%s][Task closed] %s (#%d)' => '[%s][Compito chiuso] %s (#%d)',
'[%s][Task opened] %s (#%d)' => '[%s][Compito aperto] %s (#%d)',
// 'New attachment' => '',
// 'New comment' => '',
// 'New subtask' => '',
// 'Subtask updated' => '',
// 'Task updated' => '',
// 'Task closed' => '',
// 'Task opened' => '',
'[%s][Due tasks]' => '[%s][Compiti scaduti]',
'[Kanboard] Notification' => '[Kanboard] Notifica',
'I want to receive notifications only for those projects:' => 'Vorrei ricevere le notifiche solo da questi progetti:',
@ -500,9 +498,9 @@ return array(
// 'Task assignee change' => '',
// '%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)' => '',
// 'Column Change' => '',
// 'Position Change' => '',
// 'Assignee Change' => '',
// 'New password for the user "%s"' => '',
// 'Choose an event' => '',
// 'Github commit received' => '',
@ -647,5 +645,93 @@ return array(
// 'Application default' => '',
// 'Language:' => '',
// 'Timezone:' => '',
// 'All columns' => '',
// 'Calendar for "%s"' => '',
// 'Filter by column' => '',
// 'Filter by status' => '',
// 'Calendar' => '',
// 'Next' => '',
// '#%d' => '',
// 'Filter by color' => '',
// 'Filter by swimlane' => '',
// 'All swimlanes' => '',
// 'All colors' => '',
// 'All status' => '',
// 'Add a comment logging moving the task between columns' => '',
// 'Moved to column %s' => '',
// 'Change description' => '',
// 'User dashboard' => '',
// 'Allow only one subtask in progress at the same time for a user' => '',
// 'Edit column "%s"' => '',
// 'Enable time tracking for subtasks' => '',
// 'Select the new status of the subtask: "%s"' => '',
// 'Subtask timesheet' => '',
// 'There is nothing to show.' => '',
// 'Time Tracking' => '',
// 'You already have one subtask in progress' => '',
// 'Which parts of the project do you want to duplicate?' => '',
// 'Change dashboard view' => '',
// 'Show/hide activities' => '',
// 'Show/hide projects' => '',
// 'Show/hide subtasks' => '',
// 'Show/hide tasks' => '',
// 'Disable login form' => '',
// 'Show/hide calendar' => '',
// 'User calendar' => '',
// 'Bitbucket commit received' => '',
// 'Bitbucket webhooks' => '',
// 'Help on Bitbucket webhooks' => '',
// 'Start' => '',
// 'End' => '',
// 'Task age in days' => '',
// 'Days in this column' => '',
// '%dd' => '',
// 'Add a link' => '',
// 'Add a new link' => '',
// 'Do you really want to remove this link: "%s"?' => '',
// 'Do you really want to remove this link with task #%d?' => '',
// 'Field required' => '',
// 'Link added successfully.' => '',
// 'Link updated successfully.' => '',
// 'Link removed successfully.' => '',
// 'Link labels' => '',
// 'Link modification' => '',
// 'Links' => '',
// 'Link settings' => '',
// 'Opposite label' => '',
// 'Remove a link' => '',
// 'Task\'s links' => '',
// 'The labels must be different' => '',
// 'There is no link.' => '',
// 'This label must be unique' => '',
// 'Unable to create your link.' => '',
// 'Unable to update your link.' => '',
// 'Unable to remove this link.' => '',
// 'relates to' => '',
// 'blocks' => '',
// 'is blocked by' => '',
// 'duplicates' => '',
// 'is duplicated by' => '',
// 'is a child of' => '',
// 'is a parent of' => '',
// 'targets milestone' => '',
// 'is a milestone of' => '',
// 'fixes' => '',
// 'is fixed by' => '',
// 'This task' => '',
// '<1h' => '',
// '%dh' => '',
// '%b %e' => '',
// 'Expand tasks' => '',
// 'Collapse tasks' => '',
// 'Expand/collapse tasks' => '',
// 'Close dialog box' => '',
// 'Submit a form' => '',
// 'Board view' => '',
// 'Keyboard shortcuts' => '',
// 'Open board switcher' => '',
// 'Application' => '',
// 'Filter recently updated' => '',
// 'since %B %e, %Y at %k:%M %p' => '',
// 'More filters' => '',
);

View file

@ -408,15 +408,13 @@ return array(
'Comment updated' => 'コメントが更新されました',
'New comment posted by %s' => '「%s」の新しいコメントが追加されました',
'List of due tasks for the project "%s"' => 'プロジェクト「%s」の期限切れのタスク',
'[%s][New attachment] %s (#%d)' => '[%s][新規添付ファイル] %s (#%d)',
'[%s][New comment] %s (#%d)' => '[%s][新規コメント] %s (#%d)',
'[%s][Comment updated] %s (#%d)' => '[%s][コメント更新] %s (#%d)',
'[%s][New subtask] %s (#%d)' => '[%s][新規サブタスク] %s (#%d)',
'[%s][Subtask updated] %s (#%d)' => '[%s][サブタスク更新] %s (#%d)',
'[%s][New task] %s (#%d)' => '[%s][新規タスク] %s (#%d)',
'[%s][Task updated] %s (#%d)' => '[%s][タスク更新] %s (#%d)',
'[%s][Task closed] %s (#%d)' => '[%s][タスククローズ] %s (#%d)',
'[%s][Task opened] %s (#%d)' => '[%s][タスクオープン] %s (#%d)',
// 'New attachment' => '',
// 'New comment' => '',
// 'New subtask' => '',
// 'Subtask updated' => '',
// 'Task updated' => '',
// 'Task closed' => '',
// 'Task opened' => '',
'[%s][Due tasks]' => '[%s][タスク期限切れ]',
'[Kanboard] Notification' => '[Kanboard] 通知',
'I want to receive notifications only for those projects:' => '以下のプロジェクトにのみ通知を受け取る:',
@ -500,9 +498,9 @@ return array(
'Task assignee change' => '担当者の変更',
'%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)',
// 'Column Change' => '',
// 'Position Change' => '',
// 'Assignee Change' => '',
'New password for the user "%s"' => 'ユーザ「%s」の新しいパスワード',
'Choose an event' => 'イベントの選択',
'Github commit received' => 'Github のコミットを受け取った',
@ -647,5 +645,93 @@ return array(
// 'Application default' => '',
// 'Language:' => '',
// 'Timezone:' => '',
// 'All columns' => '',
// 'Calendar for "%s"' => '',
// 'Filter by column' => '',
// 'Filter by status' => '',
// 'Calendar' => '',
// 'Next' => '',
// '#%d' => '',
// 'Filter by color' => '',
// 'Filter by swimlane' => '',
// 'All swimlanes' => '',
// 'All colors' => '',
// 'All status' => '',
// 'Add a comment logging moving the task between columns' => '',
// 'Moved to column %s' => '',
// 'Change description' => '',
// 'User dashboard' => '',
// 'Allow only one subtask in progress at the same time for a user' => '',
// 'Edit column "%s"' => '',
// 'Enable time tracking for subtasks' => '',
// 'Select the new status of the subtask: "%s"' => '',
// 'Subtask timesheet' => '',
// 'There is nothing to show.' => '',
// 'Time Tracking' => '',
// 'You already have one subtask in progress' => '',
// 'Which parts of the project do you want to duplicate?' => '',
// 'Change dashboard view' => '',
// 'Show/hide activities' => '',
// 'Show/hide projects' => '',
// 'Show/hide subtasks' => '',
// 'Show/hide tasks' => '',
// 'Disable login form' => '',
// 'Show/hide calendar' => '',
// 'User calendar' => '',
// 'Bitbucket commit received' => '',
// 'Bitbucket webhooks' => '',
// 'Help on Bitbucket webhooks' => '',
// 'Start' => '',
// 'End' => '',
// 'Task age in days' => '',
// 'Days in this column' => '',
// '%dd' => '',
// 'Add a link' => '',
// 'Add a new link' => '',
// 'Do you really want to remove this link: "%s"?' => '',
// 'Do you really want to remove this link with task #%d?' => '',
// 'Field required' => '',
// 'Link added successfully.' => '',
// 'Link updated successfully.' => '',
// 'Link removed successfully.' => '',
// 'Link labels' => '',
// 'Link modification' => '',
// 'Links' => '',
// 'Link settings' => '',
// 'Opposite label' => '',
// 'Remove a link' => '',
// 'Task\'s links' => '',
// 'The labels must be different' => '',
// 'There is no link.' => '',
// 'This label must be unique' => '',
// 'Unable to create your link.' => '',
// 'Unable to update your link.' => '',
// 'Unable to remove this link.' => '',
// 'relates to' => '',
// 'blocks' => '',
// 'is blocked by' => '',
// 'duplicates' => '',
// 'is duplicated by' => '',
// 'is a child of' => '',
// 'is a parent of' => '',
// 'targets milestone' => '',
// 'is a milestone of' => '',
// 'fixes' => '',
// 'is fixed by' => '',
// 'This task' => '',
// '<1h' => '',
// '%dh' => '',
// '%b %e' => '',
// 'Expand tasks' => '',
// 'Collapse tasks' => '',
// 'Expand/collapse tasks' => '',
// 'Close dialog box' => '',
// 'Submit a form' => '',
// 'Board view' => '',
// 'Keyboard shortcuts' => '',
// 'Open board switcher' => '',
// 'Application' => '',
// 'Filter recently updated' => '',
// 'since %B %e, %Y at %k:%M %p' => '',
// 'More filters' => '',
);

View file

@ -84,7 +84,7 @@ return array(
'Application settings' => 'Ustawienia aplikacji',
'Language' => 'Język',
'Webhook token:' => 'Token :',
// 'API token:' => '',
'API token:' => 'Token dla API',
'More information' => 'Więcej informacji',
'Database size:' => 'Rozmiar bazy danych :',
'Download the database' => 'Pobierz bazę danych',
@ -170,7 +170,7 @@ return array(
'%B %e, %Y at %k:%M %p' => '%e %B %Y o %k:%M',
'Date created' => 'Data utworzenia',
'Date completed' => 'Data zakończenia',
'Id' => 'Ident',
'Id' => 'Id',
'No task' => 'Brak zadań',
'Completed tasks' => 'Ukończone zadania',
'List of projects' => 'Lista projektów',
@ -187,7 +187,7 @@ return array(
'Complexity' => 'Poziom trudności',
'limit' => 'limit',
'Task limit' => 'Limit zadań',
// 'Task count' => '',
'Task count' => 'Liczba zadań',
'This value must be greater than %d' => 'Wartość musi być większa niż %d',
'Edit project access list' => 'Edycja list dostępu dla projektu',
'Edit users access' => 'Edytuj dostęp',
@ -197,14 +197,14 @@ return array(
'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.' => '',
'Nobody have access to this project.' => 'Żaden użytkownik nie ma dostępu do tego projektu',
'You are not allowed to access to this project.' => 'Nie masz dostępu do tego projektu.',
'Comments' => 'Komentarze',
'Post comment' => 'Dodaj komentarz',
'Write your text in Markdown' => 'Możesz użyć Markdown',
'Leave a comment' => 'Zostaw komentarz',
'Comment is required' => 'Komentarz jest wymagany',
// 'Leave a description' => '',
'Leave a description' => 'Dodaj opis',
'Comment added successfully.' => 'Komentarz dodany',
'Unable to create your comment.' => 'Nie udało się dodać komentarza',
'The description is required' => 'Opis jest wymagany',
@ -277,375 +277,461 @@ return array(
'Expiration date' => 'Data zakończenia',
'Remember Me' => 'Pamiętaj mnie',
'Creation date' => 'Data utworzenia',
// 'Filter by user' => '',
// 'Filter by due date' => '',
// 'Everybody' => '',
// 'Open' => '',
// 'Closed' => '',
// 'Search' => '',
// 'Nothing found.' => '',
// 'Search in the project "%s"' => '',
// 'Due date' => '',
// 'Others formats accepted: %s and %s' => '',
'Filter by user' => 'Filtruj według użytkowników',
'Filter by due date' => 'Filtruj według terminów',
'Everybody' => 'Wszyscy',
'Open' => 'Otwarto',
'Closed' => 'Zamknięto',
'Search' => 'Szukaj',
'Nothing found.' => 'Nic nie znaleziono',
'Search in the project "%s"' => 'Szukaj w projekcie "%s"',
'Due date' => 'Termin',
'Others formats accepted: %s and %s' => 'Inne akceptowane formaty: %s and %s',
'Description' => 'Opis',
// '%d comments' => '',
// '%d comment' => '',
// 'Email address invalid' => '',
// 'Your Google Account is not linked anymore to your profile.' => '',
// 'Unable to unlink your Google Account.' => '',
// 'Google authentication failed' => '',
// 'Unable to link your Google Account.' => '',
// 'Your Google Account is linked to your profile successfully.' => '',
// 'Email' => '',
// 'Link my Google Account' => '',
// 'Unlink my Google Account' => '',
// 'Login with my Google Account' => '',
// 'Project not found.' => '',
// 'Task #%d' => '',
// 'Task removed successfully.' => '',
// 'Unable to remove this task.' => '',
// 'Remove a task' => '',
// 'Do you really want to remove this task: "%s"?' => '',
// 'Assign automatically a color based on a category' => '',
// 'Assign automatically a category based on a color' => '',
// 'Task creation or modification' => '',
// 'Category' => '',
// 'Category:' => '',
// 'Categories' => '',
// 'Category not found.' => '',
// 'Your category have been created successfully.' => '',
// 'Unable to create your category.' => '',
// 'Your category have been updated successfully.' => '',
// 'Unable to update your category.' => '',
// 'Remove a category' => '',
// 'Category removed successfully.' => '',
// 'Unable to remove this category.' => '',
// 'Category modification for the project "%s"' => '',
// 'Category Name' => '',
// 'Categories for the project "%s"' => '',
// 'Add a new category' => '',
// 'Do you really want to remove this category: "%s"?' => '',
// 'Filter by category' => '',
// 'All categories' => '',
// 'No category' => '',
// 'The name is required' => '',
// 'Remove a file' => '',
// 'Unable to remove this file.' => '',
// 'File removed successfully.' => '',
// 'Attach a document' => '',
// 'Do you really want to remove this file: "%s"?' => '',
// 'open' => '',
// 'Attachments' => '',
// 'Edit the task' => '',
// 'Edit the description' => '',
// 'Add a comment' => '',
// 'Edit a comment' => '',
// 'Summary' => '',
// 'Time tracking' => '',
// 'Estimate:' => '',
// 'Spent:' => '',
// 'Do you really want to remove this sub-task?' => '',
// 'Remaining:' => '',
// 'hours' => '',
// 'spent' => '',
// 'estimated' => '',
// 'Sub-Tasks' => '',
// 'Add a sub-task' => '',
// 'Original estimate' => '',
// 'Create another sub-task' => '',
// 'Time spent' => '',
// 'Edit a sub-task' => '',
// 'Remove a sub-task' => '',
// 'The time must be a numeric value' => '',
// 'Todo' => '',
// 'In progress' => '',
// 'Sub-task removed successfully.' => '',
// 'Unable to remove this sub-task.' => '',
// 'Sub-task updated successfully.' => '',
// 'Unable to update your sub-task.' => '',
// 'Unable to create your sub-task.' => '',
// 'Sub-task added successfully.' => '',
// 'Maximum size: ' => '',
// 'Unable to upload the file.' => '',
// 'Display another project' => '',
// 'Your GitHub account was successfully linked to your profile.' => '',
// 'Unable to link your GitHub Account.' => '',
// 'GitHub authentication failed' => '',
// 'Your GitHub account is no longer linked to your profile.' => '',
// 'Unable to unlink your GitHub Account.' => '',
// 'Login with my GitHub Account' => '',
// 'Link my GitHub Account' => '',
// 'Unlink my GitHub Account' => '',
// 'Created by %s' => '',
// 'Last modified on %B %e, %Y at %k:%M %p' => '',
// 'Tasks Export' => '',
// 'Tasks exportation for "%s"' => '',
// 'Start Date' => '',
// 'End Date' => '',
// 'Execute' => '',
// 'Task Id' => '',
// 'Creator' => '',
// 'Modification date' => '',
// 'Completion date' => '',
// 'Webhook URL for task creation' => '',
// 'Webhook URL for task modification' => '',
// '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:' => '',
'%d comments' => '%d Komentarzy',
'%d comment' => '%d Komentarz',
'Email address invalid' => 'Błędny adres email',
'Your Google Account is not linked anymore to your profile.' => 'Twoje konto Google nie jest już połączone',
'Unable to unlink your Google Account.' => 'Nie można odłączyć konta Google',
'Google authentication failed' => 'Autentykacja Google nieudana',
'Unable to link your Google Account.' => 'Nie można podłączyć konta Google',
'Your Google Account is linked to your profile successfully.' => 'Podłączanie konta Google ukończone pomyślnie',
'Email' => 'Email',
'Link my Google Account' => 'Połącz z kontem Google',
'Unlink my Google Account' => 'Rozłącz z kontem Google',
'Login with my Google Account' => 'Zaloguj przy pomocy konta Google',
'Project not found.' => 'Projek nieznaleziony.',
'Task #%d' => 'Zadanie #%d',
'Task removed successfully.' => 'Zadanie usunięto pomyślnie.',
'Unable to remove this task.' => 'Nie można usunąć tego zadania.',
'Remove a task' => 'Usuń zadanie',
'Do you really want to remove this task: "%s"?' => 'Czy na pewno chcesz usunąć zadanie "%s"?',
'Assign automatically a color based on a category' => 'Przypisz kolor automatycznie na podstawie kategori',
'Assign automatically a category based on a color' => 'Przypisz kategorię automatycznie na podstawie koloru',
'Task creation or modification' => 'Tworzenie lub usuwanie zadania',
'Category' => 'Kategoria',
'Category:' => 'Kategoria:',
'Categories' => 'Kategorie',
'Category not found.' => 'Kategoria nie istnieje',
'Your category have been created successfully.' => 'Pomyślnie utworzono kategorię.',
'Unable to create your category.' => 'Nie można tworzyć kategorii.',
'Your category have been updated successfully.' => 'Pomyślnie zaktualizowano kategorię',
'Unable to update your category.' => 'Nie można zaktualizować kategorii',
'Remove a category' => 'Usuń kategorię',
'Category removed successfully.' => 'Pomyślnie usunięto kategorię.',
'Unable to remove this category.' => 'Nie można usunąć tej kategorii.',
'Category modification for the project "%s"' => 'Zmiania kategorii projektu "%s"',
'Category Name' => 'Nazwa kategorii',
'Categories for the project "%s"' => 'Kategorie projektu',
'Add a new category' => 'Utwórz nową kategorię',
'Do you really want to remove this category: "%s"?' => 'Czy na pewno chcesz usunąć kategorię: "%s"?',
'Filter by category' => 'Filtruj według kategorii',
'All categories' => 'Wszystkie kategorie',
'No category' => 'Brak kategorii',
'The name is required' => 'Nazwa jest wymagana',
'Remove a file' => 'Usuń plik',
'Unable to remove this file.' => 'Nie można usunąć tego pliku.',
'File removed successfully.' => 'Plik Usunięty pomyślnie.',
'Attach a document' => 'Dołącz plik',
'Do you really want to remove this file: "%s"?' => 'Czy na pewno chcesz usunąć plik: "%s"?',
'open' => 'otwórz',
'Attachments' => 'Załączniki',
'Edit the task' => 'Edytuj Zadanie',
'Edit the description' => 'Edytuj opis',
'Add a comment' => 'Dodaj komentarz',
'Edit a comment' => 'Edytuj komentarz',
'Summary' => 'Podsumowanie',
'Time tracking' => 'Śledzenie czasu',
'Estimate:' => 'Szacowany:',
'Spent:' => 'Przeznaczony:',
'Do you really want to remove this sub-task?' => 'Czy na pewno chcesz usunąć to pod-zadanie?',
'Remaining:' => 'Pozostało:',
'hours' => 'godzin',
'spent' => 'przeznaczono',
'estimated' => 'szacowany',
'Sub-Tasks' => 'Pod-zadanie',
'Add a sub-task' => 'Dodaj pod-zadanie',
'Original estimate' => 'Szacowanie początkowe',
'Create another sub-task' => 'Dodaj kolejne pod-zadanie',
'Time spent' => 'Przeznaczony czas',
'Edit a sub-task' => 'Edytuj pod-zadanie',
'Remove a sub-task' => 'Usuń pod-zadanie',
'The time must be a numeric value' => 'Czas musi być wartością liczbową',
'Todo' => 'Do zrobienia',
'In progress' => 'W trakcie',
'Sub-task removed successfully.' => 'Pod-zadanie usunięte pomyślnie.',
'Unable to remove this sub-task.' => 'Nie można usunąć tego pod-zadania.',
'Sub-task updated successfully.' => 'Pod-zadanie zaktualizowane pomyślnie.',
'Unable to update your sub-task.' => 'Nie można zaktalizować tego pod-zadania.',
'Unable to create your sub-task.' => 'Nie można utworzyć tego pod-zadania.',
'Sub-task added successfully.' => 'Pod-zadanie utworzone pomyślnie',
'Maximum size: ' => 'Maksymalny rozmiar: ',
'Unable to upload the file.' => 'Nie można wczytać pliku.',
'Display another project' => 'Wyświetl inny projekt',
'Your GitHub account was successfully linked to your profile.' => 'Konto Github podłączone pomyślnie.',
'Unable to link your GitHub Account.' => 'Nie można połączyć z kontem Github.',
'GitHub authentication failed' => 'Autentykacja Github nieudana',
'Your GitHub account is no longer linked to your profile.' => 'Konto Github nie jest już podłączone do twojego profilu.',
'Unable to unlink your GitHub Account.' => 'Nie można odłączyć konta Github.',
'Login with my GitHub Account' => 'Zaloguj przy użyciu konta Github',
'Link my GitHub Account' => 'Podłącz konto Github',
'Unlink my GitHub Account' => 'Odłącz konto Github',
'Created by %s' => 'Utworzone przez %s',
'Last modified on %B %e, %Y at %k:%M %p' => 'Ostatnio zmienione %e %B %Y o %k:%M',
'Tasks Export' => 'Eksport zadań',
'Tasks exportation for "%s"' => 'Eksport zadań dla "%s"',
'Start Date' => 'Data początkowa',
'End Date' => 'Data Końcowa',
'Execute' => 'Wykonaj',
'Task Id' => 'Identyfikator Zadania',
'Creator' => 'Autor',
'Modification date' => 'Data modyfyfikacji',
'Completion date' => 'Data ukończenia',
'Webhook URL for task creation' => 'Webhook URL do tworzenia zadań',
'Webhook URL for task modification' => 'Webhook URL do modyfikacji zadań',
'Clone' => 'Sklonuj',
'Clone Project' => 'Sklonuj projekt',
'Project cloned successfully.' => 'Projekt sklonowany pomyślnie.',
'Unable to clone this project.' => 'Nie można sklonować projektu.',
'Email notifications' => 'Powiadomienia email',
'Enable email notifications' => 'Włącz powiadomienia email',
'Task position:' => 'Pozycja zadania:',
'The task #%d have been opened.' => 'Zadania #%d zostały otwarte.',
'The task #%d have been closed.' => 'Zadania #$d zostały zamknięte.',
'Sub-task updated' => 'Pod-zadanie zaktualizowane',
'Title:' => 'Tytuł:',
// 'Status:' => '',
// 'Assignee:' => '',
// 'Time tracking:' => '',
// 'New sub-task' => '',
// 'New attachment added "%s"' => '',
// 'Comment updated' => '',
// 'New comment posted by %s' => '',
// 'List of due tasks for the project "%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]' => '',
// '[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 %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 %s' => '',
// '%s commented the task %s' => '',
// '%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 changed the assignee of the task %s to %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' => '',
'Assignee:' => 'Przypisano do:',
'Time tracking:' => 'Śledzenie czasu: ',
'New sub-task' => 'Nowe Pod-zadanie',
'New attachment added "%s"' => 'Nowy załącznik dodany "%s"',
'Comment updated' => 'Komentarz zaktualizowany',
'New comment posted by %s' => 'Nowy komentarz dodany przez %s',
'List of due tasks for the project "%s"' => 'Lista zadań oczekujących projektu "%s"',
// 'New attachment' => '',
// 'New comment' => '',
// 'New subtask' => '',
// 'Subtask updated' => '',
// 'Task updated' => '',
// 'Task closed' => '',
// 'Task opened' => '',
'[%s][Due tasks]' => '[%s][Zadania oczekujące]',
'[Kanboard] Notification' => '[Kanboard] Powiadomienie',
'I want to receive notifications only for those projects:' => 'Chcę otrzymywaćpowiadiomienia tylko dla tych projektów:',
'view the task on Kanboard' => 'Zobacz zadanie',
'Public access' => 'Dostęp publiczny',
'Category management' => 'Zarządzanie kategoriami',
'User management' => 'Zarządzanie użytkownikami',
'Active tasks' => 'Aktywne zadania',
'Disable public access' => 'Zablokuj dostęp publiczny',
'Enable public access' => 'Odblokuj dostęp publiczny',
'Active projects' => 'Aktywne projety',
'Inactive projects' => 'Nieaktywne projekty',
'Public access disabled' => 'Dostęp publiczny zablokowany',
'Do you really want to disable this project: "%s"?' => 'Czy napewno chcesz zablokować projekt: "%s"?',
'Do you really want to duplicate this project: "%s"?' => 'Czy napewno chcesz zduplikować projekt: "%s"?',
'Do you really want to enable this project: "%s"?' => 'Czy napewno chcesz odblokować projekt: "%s"?',
'Project activation' => 'Aktywacja projekt',
'Move the task to another project' => 'Przenieś zadanie do innego projektu',
'Move to another project' => 'Przenieś do innego projektu',
'Do you really want to duplicate this task?' => 'Czy napewno chcesz zduplikować to zadanie: "%s"?',
'Duplicate a task' => 'Zduplikuj zadanie',
'External accounts' => 'Konta zewnętrzne',
'Account type' => 'Typ konta',
'Local' => 'Lokalne',
'Remote' => 'Zdalne',
'Enabled' => 'Odblokowane',
'Disabled' => 'Zablokowane',
'Google account linked' => 'Połączone konto Google',
'Github account linked' => 'Połączone konto Github',
'Username:' => 'Nazwa Użytkownika:',
'Name:' => 'Imię i Nazwisko',
'Email:' => 'Email: ',
'Default project:' => 'Projekt domyślny:',
'Notifications:' => 'Powiadomienia: ',
'Notifications' => 'Powiadomienia',
'Group:' => 'Grupa:',
'Regular user' => 'Zwykły użytkownik',
'Account type:' => 'Typ konta:',
'Edit profile' => 'Edytuj profil',
'Change password' => 'Zmień hasło',
'Password modification' => 'Zmiania hasła',
'External authentications' => 'Autentykacja zewnętrzna',
'Google Account' => 'Konto Google',
'Github Account' => 'Konto Github',
'Never connected.' => 'Nigdy nie połączone.',
'No account linked.' => 'Brak połączonych kont.',
'Account linked.' => 'Konto połączone.',
'No external authentication enabled.' => 'Brak autentykacji zewnętrznych.',
'Password modified successfully.' => 'Hasło zmienione pomyślne.',
'Unable to change the password.' => 'Nie można zmienić hasła.',
'Change category for the task "%s"' => 'Zmień kategorię dla zadania "%s"',
'Change category' => 'Zmień kategorię',
'%s updated the task %s' => '%s zaktualizował zadanie %s',
'%s opened the task %s' => '%s otworzył zadanie %s',
'%s moved the task %s to the position #%d in the column "%s"' => '%s przeniósł zadanie %s na pozycję #%d w kolumnie "%s"',
'%s moved the task %s to the column "%s"' => '%s przeniósł zadanie %s do kolumny "%s"',
'%s created the task %s' => '%s utworzył zadanie %s',
'%s closed the task %s' => '%s zamknął zadanie %s',
'%s created a subtask for the task %s' => '%s utworzył pod-zadanie dla zadania %s',
'%s updated a subtask for the task %s' => '%s zaktualizował pod-zadanie dla zadania %s',
'Assigned to %s with an estimate of %s/%sh' => 'Przypisano do %s z szacowanym czasem wykonania %s/%sh',
'Not assigned, estimate of %sh' => 'Nie przypisane, szacowany czas wykonania %sh',
'%s updated a comment on the task %s' => '%s zaktualizował komentarz do zadania %s',
'%s commented the task %s' => '%s skomentował zadanie %s',
'%s\'s activity' => 'Aktywność %s',
'No activity.' => 'Brak aktywności.',
'RSS feed' => 'Kanał RSS',
'%s updated a comment on the task #%d' => '%s zaktualizował komentarz do zadania #%d',
'%s commented on the task #%d' => '%s skomentował zadanie #%d',
'%s updated a subtask for the task #%d' => '%s zaktualizował pod-zadanie dla zadania #%d',
'%s created a subtask for the task #%d' => '%s utworzył pod-zadanie dla zadania #%d',
'%s updated the task #%d' => '%s zaktualizował zadanie #%d',
'%s created the task #%d' => '%s utworzył zadanie #%d',
'%s closed the task #%d' => '%s zamknął zadanie #%d',
'%s open the task #%d' => '%s otworzył zadanie #%d',
'%s moved the task #%d to the column "%s"' => '%s przeniósł zadanie #%d do kolumny "%s"',
'%s moved the task #%d to the position %d in the column "%s"' => '%s przeniósł zadanie #%d na pozycję %d w kolmnie "%s"',
'Activity' => 'Aktywność',
'Default values are "%s"' => 'Domyślne wartości: "%s"',
'Default columns for new projects (Comma-separated)' => 'Domyślne kolmny dla nowych projektów (oddzielone przecinkiem)',
'Task assignee change' => 'Zmień osobę odpowiedzialną',
'%s change the assignee of the task #%d to %s' => '%s zmienił osobę odpowiedzialną za zadanie #%d na %s',
'%s changed the assignee of the task %s to %s' => '%s zmienił osobę odpowiedzialną za zadanie %s na %s',
// 'Column Change' => '',
// 'Position Change' => '',
// 'Assignee Change' => '',
'New password for the user "%s"' => 'Nowe hasło użytkownika "%s"',
'Choose an event' => 'Wybierz zdarzenie',
// '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' => '',
'Create a task from an external provider' => 'Utwórz zadanie z dostawcy zewnętrznego',
'Change the assignee based on an external username' => 'Zmień osobę odpowiedzialną na podstawie zewnętrznej nazwy użytkownika',
'Change the category based on an external label' => 'Zmień kategorię na podstawie zewnętrzenj etykiety',
// 'Reference' => '',
// 'Reference: %s' => '',
// 'Label' => '',
// 'Database' => '',
// 'About' => '',
// 'Database driver:' => '',
// 'Board settings' => '',
// 'URL and token' => '',
'Label' => 'Etykieta',
'Database' => 'Baza danych',
'About' => 'Informacje',
'Database driver:' => 'Silnik bazy danych:',
'Board settings' => 'Ustawienia tablicy',
'URL and token' => 'URL i token',
// 'Webhook settings' => '',
// 'URL for task creation:' => '',
// 'Reset token' => '',
'URL for task creation:' => 'URL do tworzenia zadań',
'Reset token' => 'Resetuj 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.' => '',
'Refresh interval for private board' => 'Częstotliwość odświerzania dla tablicy prywatnej',
'Refresh interval for public board' => 'Częstotliwość odświerzania dla tablicy publicznej',
'Task highlight period' => 'Okres wyróżniania zadań',
'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Okres (w sekundach) wymagany do uznania projektu za niedawno zmieniony (0 ab zablokować, domyślnie 2 dni)',
'Frequency in second (60 seconds by default)' => 'Częstotliwosć w sekundach (domyślnie 60 sekund)',
'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Częstotliwość w sekundach (0 aby zablokować, domyślnie 10 sekund)',
'Application URL' => 'Adres URL aplikacji',
'Example: http://example.kanboard.net/ (used by email notifications)' => 'Przykład: http://example.kanboard.net/ (Używane przez powiadomienia email)',
'Token regenerated.' => 'Token wygenerowany ponownie.',
'Date format' => 'Format daty',
'ISO format is always accepted, example: "%s" and "%s"' => 'Format ISO jest zawsze akceptowany, przykłady: "%s", "%s"',
'New private project' => 'Nowy projekt prywatny',
'This project is private' => 'Ten projekt jest prywatny',
'Type here to create a new sub-task' => 'Wpisz tutaj aby utworzyć pod-zadanie',
'Add' => 'Dodaj',
'Estimated time: %s hours' => 'Szacowany czas: %s godzin',
'Time spent: %s hours' => 'Przeznaczony czas: %s godzin',
'Started on %B %e, %Y' => 'Rozpoczęto %e %B %Y',
'Start date' => 'Data rozpoczęcia',
'Time estimated' => 'Szacowany czas',
'There is nothing assigned to you.' => 'Nie ma przypisanych zadań',
'My tasks' => 'Moje zadania',
'Activity stream' => 'Dziennik aktywności',
'Dashboard' => 'Panel',
'Confirmation' => 'Potwierdzenie',
'Allow everybody to access to this project' => 'Udostepnij ten projekt wszystkim',
'Everybody have access to this project.' => 'Wszyscy mają dostęp do tego projektu.',
// 'Webhooks' => '',
// 'API' => '',
// 'Integration' => '',
'Integration' => 'Integracja',
// 'Github webhooks' => '',
// 'Help on Github webhooks' => '',
// 'Create a comment from an external provider' => '',
'Create a comment from an external provider' => 'Utwórz komentarz od zewnętrznego dostawcy',
// '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' => '',
'Configure' => 'Konfiguruj',
'Project management' => 'Menadżer projektu',
'My projects' => 'Moje projekty',
'Columns' => 'Kolumny',
'Task' => 'zadania',
'Your are not member of any project.' => 'Nie bierzesz udziału w żadnym projekcie',
'Percentage' => 'Procent',
'Number of tasks' => 'Liczba zadań',
'Task distribution' => 'Rozmieszczenie zadań',
'Reportings' => 'Raporty',
'Task repartition for "%s"' => 'Przydział zadań dla "%s"',
'Analytics' => 'Analizy',
'Subtask' => 'Pod-zadanie',
'My subtasks' => 'Moje pod-zadania',
'User repartition' => 'Przydział użytkownika',
'User repartition for "%s"' => 'Przydział użytkownika dla "%s"',
'Clone this project' => 'Sklonuj ten projekt',
'Column removed successfully.' => 'Kolumna usunięta pomyslnie.',
'Edit Project' => 'Edytuj projekt',
// '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' => '',
// '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)' => '',
'Not enough data to show the graph.' => 'Za mało danych do utworzenia wykresu.',
'Previous' => 'Poprzedni',
'The id must be an integer' => 'ID musi być liczbą całkowitą',
'The project id must be an integer' => 'ID projektu musi być liczbą całkowitą',
'The status must be an integer' => 'Status musi być liczbą całkowitą',
'The subtask id is required' => 'ID pod-zadanie jest wymagane',
'The subtask id must be an integer' => 'ID pod-zadania musi być liczbą całkowitą',
'The task id is required' => 'ID zadania jest wymagane',
'The task id must be an integer' => 'ID zadania musi być liczbą całkowitą',
'The user id must be an integer' => 'ID użytkownika musi być liczbą całkowitą',
'This value is required' => 'Wymagana wartość',
'This value must be numeric' => 'Wartość musi być liczbą',
'Unable to create this task.' => 'Nie można tworzyć zadania.',
'Cumulative flow diagram' => 'Zbiorowy diagram przepływu',
'Cumulative flow diagram for "%s"' => 'Zbiorowy diagram przepływu dla "%s"',
'Daily project summary' => 'Dzienne podsumowanie projektu',
'Daily project summary export' => 'Eksport dziennego podsumowania projektu',
'Daily project summary export for "%s"' => 'Eksport dziennego podsumowania projektu dla "%s"',
'Exports' => 'Eksporty',
'This export contains the number of tasks per column grouped per day.' => 'Ten eksport zawiera ilość zadań zgrupowanych w kolumnach na dzień',
'Nothing to preview...' => 'Nic do podejrzenia...',
'Preview' => 'Podgląd',
'Write' => 'Edycja',
'Active swimlanes' => 'Aktywne procesy',
'Add a new swimlane' => 'Dodaj proces',
'Change default swimlane' => 'Zmień domyślny proces',
'Default swimlane' => 'Domyślny proces',
'Do you really want to remove this swimlane: "%s"?' => 'Czy na pewno chcesz usunąć proces: "%s"?',
'Inactive swimlanes' => 'Nieaktywne procesy',
'Set project manager' => 'Ustaw menadżera projektu',
'Set project member' => 'Ustaw członka projektu',
'Remove a swimlane' => 'Usuń proces',
'Rename' => 'Zmień nazwe',
'Show default swimlane' => 'Pokaż domyślny proces',
'Swimlane modification for the project "%s"' => 'Edycja procesów dla projektu "%s"',
'Swimlane not found.' => 'Nie znaleziono procesu.',
'Swimlane removed successfully.' => 'Proces usunięty pomyslnie.',
'Swimlanes' => 'Procesy',
'Swimlane updated successfully.' => 'Proces zaktualizowany pomyślnie.',
'The default swimlane have been updated successfully.' => 'Domyślny proces zaktualizowany pomyślnie.',
'Unable to create your swimlane.' => 'Nie można utworzyć procesu.',
'Unable to remove this swimlane.' => 'Nie można usunąć procesu.',
'Unable to update this swimlane.' => 'Nie można zaktualizować procesu.',
'Your swimlane have been created successfully.' => 'Proces tworzony pomyślnie.',
'Example: "Bug, Feature Request, Improvement"' => 'Przykład: "Błąd, Żądanie Funkcjonalnośći, Udoskonalenia"',
'Default categories for new projects (Comma-separated)' => 'Domyślne kategorie dla nowych projektów (oddzielone przecinkiem)',
// '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.' => '',
'Integrations' => 'Integracje',
'Integration with third-party services' => 'Integracja z usługami firm trzecich',
'Role for this project' => 'Rola w tym projekcie',
'Project manager' => 'Manadżer projektu',
'Project member' => 'Członek projektu',
'A project manager can change the settings of the project and have more privileges than a standard user.' => 'Menadżer projektu może zmieniać ustawienia projektu i posiada większe uprawnienia od zwykłego użytkownika',
// 'Gitlab Issue' => '',
// 'Subtask Id' => '',
// 'Subtasks' => '',
// 'Subtasks Export' => '',
// 'Subtasks exportation for "%s"' => '',
// 'Task Title' => '',
// 'Untitled' => '',
// 'Application default' => '',
// 'Language:' => '',
// 'Timezone:' => '',
// 'Next' => '',
'Subtask Id' => 'ID pod-zadania',
'Subtasks' => 'Pod-zadania',
'Subtasks Export' => 'Eksport pod-zadań',
'Subtasks exportation for "%s"' => 'Eksporty pod-zadań dla "%s"',
'Task Title' => 'Tytuł zadania',
'Untitled' => 'Bez tytułu',
'Application default' => 'Domyślne dla aplikacji',
'Language:' => 'Język:',
'Timezone:' => 'Strefa czasowa:',
'All columns' => 'Wszystkie kolumny',
'Calendar for "%s"' => 'Kalendarz dla "%s"',
'Filter by column' => 'Filtrj według kolumn',
'Filter by status' => 'Filtruj według statusu',
'Calendar' => 'Kalendarz',
'Next' => 'Następny',
// '#%d' => '',
'Filter by color' => 'Filtruj według koloru',
'Filter by swimlane' => 'Filtruj według procesu',
'All swimlanes' => 'Wszystkie procesy',
'All colors' => 'Wszystkie kolory',
'All status' => 'Wszystkie statusy',
'Add a comment logging moving the task between columns' => 'Dodaj komentarz dokumentujący przeniesienie zadania pomiędzy kolumnami',
'Moved to column %s' => 'Przeniosiono do kolumny %s',
// 'Change description' => '',
// 'User dashboard' => '',
// 'Allow only one subtask in progress at the same time for a user' => '',
// 'Edit column "%s"' => '',
// 'Enable time tracking for subtasks' => '',
// 'Select the new status of the subtask: "%s"' => '',
// 'Subtask timesheet' => '',
// 'There is nothing to show.' => '',
// 'Time Tracking' => '',
// 'You already have one subtask in progress' => '',
// 'Which parts of the project do you want to duplicate?' => '',
// 'Change dashboard view' => '',
// 'Show/hide activities' => '',
// 'Show/hide projects' => '',
// 'Show/hide subtasks' => '',
// 'Show/hide tasks' => '',
// 'Disable login form' => '',
// 'Show/hide calendar' => '',
// 'User calendar' => '',
// 'Bitbucket commit received' => '',
// 'Bitbucket webhooks' => '',
// 'Help on Bitbucket webhooks' => '',
// 'Start' => '',
// 'End' => '',
// 'Task age in days' => '',
// 'Days in this column' => '',
// '%dd' => '',
// 'Add a link' => '',
// 'Add a new link' => '',
// 'Do you really want to remove this link: "%s"?' => '',
// 'Do you really want to remove this link with task #%d?' => '',
// 'Field required' => '',
// 'Link added successfully.' => '',
// 'Link updated successfully.' => '',
// 'Link removed successfully.' => '',
// 'Link labels' => '',
// 'Link modification' => '',
// 'Links' => '',
// 'Link settings' => '',
// 'Opposite label' => '',
// 'Remove a link' => '',
// 'Task\'s links' => '',
// 'The labels must be different' => '',
// 'There is no link.' => '',
// 'This label must be unique' => '',
// 'Unable to create your link.' => '',
// 'Unable to update your link.' => '',
// 'Unable to remove this link.' => '',
// 'relates to' => '',
// 'blocks' => '',
// 'is blocked by' => '',
// 'duplicates' => '',
// 'is duplicated by' => '',
// 'is a child of' => '',
// 'is a parent of' => '',
// 'targets milestone' => '',
// 'is a milestone of' => '',
// 'fixes' => '',
// 'is fixed by' => '',
// 'This task' => '',
// '<1h' => '',
// '%dh' => '',
// '%b %e' => '',
// 'Expand tasks' => '',
// 'Collapse tasks' => '',
// 'Expand/collapse tasks' => '',
// 'Close dialog box' => '',
// 'Submit a form' => '',
// 'Board view' => '',
// 'Keyboard shortcuts' => '',
// 'Open board switcher' => '',
// 'Application' => '',
// 'Filter recently updated' => '',
// 'since %B %e, %Y at %k:%M %p' => '',
// 'More filters' => '',
);

View file

@ -408,15 +408,13 @@ 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][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)',
'New attachment' => 'Novo anexo',
'New comment' => 'Novo comentário',
'New subtask' => 'Nova subtarefa',
'Subtask updated' => 'Subtarefa alterada',
'Task updated' => 'Tarefa alterada',
'Task closed' => 'Tarefa finalizada',
'Task opened' => 'Tarefa aberta',
'[%s][Due tasks]' => '[%s][Tarefas pendentes]',
'[Kanboard] Notification' => '[Kanboard] Notificação',
'I want to receive notifications only for those projects:' => 'Quero receber notificações apenas destes projetos:',
@ -500,9 +498,9 @@ return array(
'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 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)',
'Column Change' => 'Mudança de coluna',
'Position Change' => 'Mudança de posição',
'Assignee Change' => 'Mudança de designado',
'New password for the user "%s"' => 'Nova senha para o usuário "%s"',
'Choose an event' => 'Escolher um evento',
'Github commit received' => 'Github commit received',
@ -647,5 +645,93 @@ return array(
'Application default' => 'Aplicação padrão',
'Language:' => 'Idioma',
'Timezone:' => 'Fuso horário',
'All columns' => 'Todas as colunas',
'Calendar for "%s"' => 'Calendário para "%s"',
'Filter by column' => 'Filtrar por coluna',
'Filter by status' => 'Filtrar por status',
'Calendar' => 'Calendário',
'Next' => 'Próximo',
// '#%d' => '',
'Filter by color' => 'Filtrar por cor',
'Filter by swimlane' => 'Filtrar por swimlane',
'All swimlanes' => 'Todos os swimlane',
'All colors' => 'Todas as cores',
'All status' => 'Todos os status',
'Add a comment logging moving the task between columns' => 'Adicionar un comentário de log ao mover uma tarefa em outra coluna',
'Moved to column %s' => 'Mover para a coluna %s',
'Change description' => 'Modificar a descrição',
'User dashboard' => 'Painel de Controle do usuário',
'Allow only one subtask in progress at the same time for a user' => 'Permitir apenas uma subtarefa em andamento ao mesmo tempo para um usuário',
'Edit column "%s"' => 'Editar a coluna "%s"',
'Enable time tracking for subtasks' => 'Ativar a gestão de tempo par a subtarefa',
'Select the new status of the subtask: "%s"' => 'Selecionar um novo status para a subtarefa',
'Subtask timesheet' => 'Gestão de tempo das subtarefas',
'There is nothing to show.' => 'Não há nada para mostrar',
'Time Tracking' => 'Gestão de tempo',
'You already have one subtask in progress' => 'Você já tem um subtarefa em andamento',
'Which parts of the project do you want to duplicate?' => 'Quais as partes do projeto você deseja duplicar?',
'Change dashboard view' => 'Alterar a vista do Painel de Controle',
'Show/hide activities' => 'Mostrar / ocultar as atividades',
'Show/hide projects' => 'Mostrar / ocultar os projetos',
'Show/hide subtasks' => 'Mostrar / ocultar as subtarefas',
'Show/hide tasks' => 'Mostrar / ocultar as tarefas',
'Disable login form' => 'Desativar o formulário de login',
'Show/hide calendar' => 'Mostrar / ocultar calendário',
'User calendar' => 'Calendário do usuário',
'Bitbucket commit received' => '"Commit" recebido via Bitbucket',
'Bitbucket webhooks' => 'Webhook Bitbucket',
'Help on Bitbucket webhooks' => 'Ajuda sobre os webhooks Bitbucket',
'Start' => 'Inicio',
'End' => 'Fim',
'Task age in days' => 'Idade da tarefa em dias',
'Days in this column' => 'Dias nesta coluna',
// '%dd' => '',
'Add a link' => 'Adicionar uma associação',
'Add a new link' => 'Adicionar uma nova associação',
'Do you really want to remove this link: "%s"?' => 'Você realmente deseja remover esta associação: "%s"?',
'Do you really want to remove this link with task #%d?' => 'Você realmente deseja remover esta associação com a tarefa n°%d?',
// 'Field required' => '',
'Link added successfully.' => 'Associação criada com sucesso.',
'Link updated successfully.' => 'Associação atualizada com sucesso.',
'Link removed successfully.' => 'Associação removida com sucesso.',
'Link labels' => 'Etiquetas das associações',
'Link modification' => 'Modificação de uma associação',
'Links' => 'Associações',
'Link settings' => 'Configuração das associações',
'Opposite label' => 'Nome da etiqueta oposta',
'Remove a link' => 'Remover uma associação',
'Task\'s links' => 'Associações das tarefas',
'The labels must be different' => 'As etiquetas devem ser diferentes',
'There is no link.' => 'Não há nenhuma associação.',
'This label must be unique' => 'Esta etiqueta deve ser unica',
'Unable to create your link.' => 'Impossível de adicionar sua associação.',
'Unable to update your link.' => 'Impossível de atualizar sua associação.',
'Unable to remove this link.' => 'Impossível de remover sua associação.',
'relates to' => 'é associado com',
'blocks' => 'blocos',
'is blocked by' => 'esta bloqueado por',
'duplicates' => 'duplica',
'is duplicated by' => 'é duplicado por',
'is a child of' => 'é um filho de',
'is a parent of' => 'é um parente do',
'targets milestone' => 'visa um milestone',
'is a milestone of' => 'é um milestone de',
'fixes' => 'corrige',
'is fixed by' => 'foi corrigido por',
'This task' => 'Esta tarefa',
// '<1h' => '',
// '%dh' => '',
// '%b %e' => '',
'Expand tasks' => 'Expandir tarefas',
'Collapse tasks' => 'Contrair tarefas',
'Expand/collapse tasks' => 'Expandir/Contrair tarefas',
// 'Close dialog box' => '',
'Submit a form' => 'Envia o formulário',
// 'Board view' => '',
// 'Keyboard shortcuts' => '',
// 'Open board switcher' => '',
'Application' => 'Aplicação',
'Filter recently updated' => 'Filtro recentemente atualizado',
// 'since %B %e, %Y at %k:%M %p' => '',
'More filters' => 'Mais filtros',
);

View file

@ -408,15 +408,13 @@ return array(
'Comment updated' => 'Комментарий обновлен',
'New comment posted by %s' => 'Новый комментарий написан « %s »',
'List of due tasks for the project "%s"' => 'Список сроков к проекту « %s »',
'[%s][New attachment] %s (#%d)' => '[%s][Новых вложений] %s (#%d)',
'[%s][New comment] %s (#%d)' => '[%s][Новых комментариев] %s (#%d)',
'[%s][Comment updated] %s (#%d)' => '[%s][Обновленых коментариев] %s (#%d)',
'[%s][New subtask] %s (#%d)' => '[%s][Новых подзадач] %s (#%d)',
'[%s][Subtask updated] %s (#%d)' => '[%s][Обновленных подзадач] %s (#%d)',
'[%s][New task] %s (#%d)' => '[%s][Новых задач] %s (#%d)',
'[%s][Task updated] %s (#%d)' => '[%s][Обновленных задач] %s (#%d)',
'[%s][Task closed] %s (#%d)' => '[%s][Закрытых задач] %s (#%d)',
'[%s][Task opened] %s (#%d)' => '[%s][Открытых задач] %s (#%d)',
// 'New attachment' => '',
// 'New comment' => '',
// 'New subtask' => '',
// 'Subtask updated' => '',
// 'Task updated' => '',
// 'Task closed' => '',
// 'Task opened' => '',
'[%s][Due tasks]' => '[%s][Текущие задачи]',
'[Kanboard] Notification' => '[Kanboard] Оповещение',
'I want to receive notifications only for those projects:' => 'Я хочу получать уведомления только по этим проектам :',
@ -500,9 +498,9 @@ return array(
'Task assignee change' => 'Изменен назначенный',
'%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)',
// 'Column Change' => '',
// 'Position Change' => '',
// 'Assignee Change' => '',
'New password for the user "%s"' => 'Новый пароль для пользователя %s"',
'Choose an event' => 'Выберите событие',
'Github commit received' => 'Github: коммит получен',
@ -647,5 +645,93 @@ return array(
// 'Application default' => '',
// 'Language:' => '',
// 'Timezone:' => '',
// 'All columns' => '',
// 'Calendar for "%s"' => '',
// 'Filter by column' => '',
// 'Filter by status' => '',
// 'Calendar' => '',
// 'Next' => '',
// '#%d' => '',
// 'Filter by color' => '',
// 'Filter by swimlane' => '',
// 'All swimlanes' => '',
// 'All colors' => '',
// 'All status' => '',
// 'Add a comment logging moving the task between columns' => '',
// 'Moved to column %s' => '',
// 'Change description' => '',
// 'User dashboard' => '',
// 'Allow only one subtask in progress at the same time for a user' => '',
// 'Edit column "%s"' => '',
// 'Enable time tracking for subtasks' => '',
// 'Select the new status of the subtask: "%s"' => '',
// 'Subtask timesheet' => '',
// 'There is nothing to show.' => '',
// 'Time Tracking' => '',
// 'You already have one subtask in progress' => '',
// 'Which parts of the project do you want to duplicate?' => '',
// 'Change dashboard view' => '',
// 'Show/hide activities' => '',
// 'Show/hide projects' => '',
// 'Show/hide subtasks' => '',
// 'Show/hide tasks' => '',
// 'Disable login form' => '',
// 'Show/hide calendar' => '',
// 'User calendar' => '',
// 'Bitbucket commit received' => '',
// 'Bitbucket webhooks' => '',
// 'Help on Bitbucket webhooks' => '',
// 'Start' => '',
// 'End' => '',
// 'Task age in days' => '',
// 'Days in this column' => '',
// '%dd' => '',
// 'Add a link' => '',
// 'Add a new link' => '',
// 'Do you really want to remove this link: "%s"?' => '',
// 'Do you really want to remove this link with task #%d?' => '',
// 'Field required' => '',
// 'Link added successfully.' => '',
// 'Link updated successfully.' => '',
// 'Link removed successfully.' => '',
// 'Link labels' => '',
// 'Link modification' => '',
// 'Links' => '',
// 'Link settings' => '',
// 'Opposite label' => '',
// 'Remove a link' => '',
// 'Task\'s links' => '',
// 'The labels must be different' => '',
// 'There is no link.' => '',
// 'This label must be unique' => '',
// 'Unable to create your link.' => '',
// 'Unable to update your link.' => '',
// 'Unable to remove this link.' => '',
// 'relates to' => '',
// 'blocks' => '',
// 'is blocked by' => '',
// 'duplicates' => '',
// 'is duplicated by' => '',
// 'is a child of' => '',
// 'is a parent of' => '',
// 'targets milestone' => '',
// 'is a milestone of' => '',
// 'fixes' => '',
// 'is fixed by' => '',
// 'This task' => '',
// '<1h' => '',
// '%dh' => '',
// '%b %e' => '',
// 'Expand tasks' => '',
// 'Collapse tasks' => '',
// 'Expand/collapse tasks' => '',
// 'Close dialog box' => '',
// 'Submit a form' => '',
// 'Board view' => '',
// 'Keyboard shortcuts' => '',
// 'Open board switcher' => '',
// 'Application' => '',
// 'Filter recently updated' => '',
// 'since %B %e, %Y at %k:%M %p' => '',
// 'More filters' => '',
);

View file

@ -408,15 +408,13 @@ return array(
'Comment updated' => 'Kommentaren har uppdaterats',
'New comment posted by %s' => 'Ny kommentar postad av %s',
'List of due tasks for the project "%s"' => 'Lista med uppgifter för projektet "%s"',
'[%s][New attachment] %s (#%d)' => '[%s][Ny bifogning] %s (#%d)',
'[%s][New comment] %s (#%d)' => '[%s][Ny kommentar] %s (#%d)',
'[%s][Comment updated] %s (#%d)' => '[%s][Uppdaterad kommentar] %s (#%d)',
'[%s][New subtask] %s (#%d)' => '[%s][Ny deluppgift] %s (#%d)',
'[%s][Subtask updated] %s (#%d)' => '[%s][Deluppgiften uppdaterad] %s (#%d)',
'[%s][New task] %s (#%d)' => '[%s][Ny uppgift] %s (#%d)',
'[%s][Task updated] %s (#%d)' => '[%s][Uppgiften uppdaterad] %s (#%d)',
'[%s][Task closed] %s (#%d)' => '[%s][Uppgiften stängd] %s (#%d)',
'[%s][Task opened] %s (#%d)' => '[%s][Uppgiften öppnad] %s (#%d)',
// 'New attachment' => '',
// 'New comment' => '',
// 'New subtask' => '',
// 'Subtask updated' => '',
// 'Task updated' => '',
// 'Task closed' => '',
// 'Task opened' => '',
'[%s][Due tasks]' => '[%s][Förfallen uppgift]',
'[Kanboard] Notification' => '[Kanboard] Notis',
'I want to receive notifications only for those projects:' => 'Jag vill endast få notiser för dessa projekt:',
@ -500,9 +498,9 @@ return array(
'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 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)',
// 'Column Change' => '',
// 'Position Change' => '',
// 'Assignee Change' => '',
'New password for the user "%s"' => 'Nytt lösenord för användaren "%s"',
'Choose an event' => 'Välj en händelse',
'Github commit received' => 'Github-bidrag mottaget',
@ -647,5 +645,93 @@ return array(
'Application default' => 'Applikationsstandard',
'Language:' => 'Språk',
'Timezone:' => 'Tidszon',
// 'All columns' => '',
// 'Calendar for "%s"' => '',
// 'Filter by column' => '',
// 'Filter by status' => '',
// 'Calendar' => '',
'Next' => 'Nästa',
// '#%d' => '',
// 'Filter by color' => '',
// 'Filter by swimlane' => '',
// 'All swimlanes' => '',
// 'All colors' => '',
// 'All status' => '',
// 'Add a comment logging moving the task between columns' => '',
// 'Moved to column %s' => '',
// 'Change description' => '',
// 'User dashboard' => '',
// 'Allow only one subtask in progress at the same time for a user' => '',
// 'Edit column "%s"' => '',
// 'Enable time tracking for subtasks' => '',
// 'Select the new status of the subtask: "%s"' => '',
// 'Subtask timesheet' => '',
// 'There is nothing to show.' => '',
// 'Time Tracking' => '',
// 'You already have one subtask in progress' => '',
// 'Which parts of the project do you want to duplicate?' => '',
// 'Change dashboard view' => '',
// 'Show/hide activities' => '',
// 'Show/hide projects' => '',
// 'Show/hide subtasks' => '',
// 'Show/hide tasks' => '',
// 'Disable login form' => '',
// 'Show/hide calendar' => '',
// 'User calendar' => '',
// 'Bitbucket commit received' => '',
// 'Bitbucket webhooks' => '',
// 'Help on Bitbucket webhooks' => '',
// 'Start' => '',
// 'End' => '',
// 'Task age in days' => '',
// 'Days in this column' => '',
// '%dd' => '',
// 'Add a link' => '',
// 'Add a new link' => '',
// 'Do you really want to remove this link: "%s"?' => '',
// 'Do you really want to remove this link with task #%d?' => '',
// 'Field required' => '',
// 'Link added successfully.' => '',
// 'Link updated successfully.' => '',
// 'Link removed successfully.' => '',
// 'Link labels' => '',
// 'Link modification' => '',
// 'Links' => '',
// 'Link settings' => '',
// 'Opposite label' => '',
// 'Remove a link' => '',
// 'Task\'s links' => '',
// 'The labels must be different' => '',
// 'There is no link.' => '',
// 'This label must be unique' => '',
// 'Unable to create your link.' => '',
// 'Unable to update your link.' => '',
// 'Unable to remove this link.' => '',
// 'relates to' => '',
// 'blocks' => '',
// 'is blocked by' => '',
// 'duplicates' => '',
// 'is duplicated by' => '',
// 'is a child of' => '',
// 'is a parent of' => '',
// 'targets milestone' => '',
// 'is a milestone of' => '',
// 'fixes' => '',
// 'is fixed by' => '',
// 'This task' => '',
// '<1h' => '',
// '%dh' => '',
// '%b %e' => '',
// 'Expand tasks' => '',
// 'Collapse tasks' => '',
// 'Expand/collapse tasks' => '',
// 'Close dialog box' => '',
// 'Submit a form' => '',
// 'Board view' => '',
// 'Keyboard shortcuts' => '',
// 'Open board switcher' => '',
// 'Application' => '',
// 'Filter recently updated' => '',
// 'since %B %e, %Y at %k:%M %p' => '',
// 'More filters' => '',
);

View file

@ -408,15 +408,13 @@ return array(
'Comment updated' => 'ปรับปรุงความคิดเห็น',
'New comment posted by %s' => 'ความคิดเห็นใหม่จาก %s',
'List of due tasks for the project "%s"' => 'รายการงานสำหรับโปรเจค "%s"',
'[%s][New attachment] %s (#%d)' => '[%s][แนบใหม่] %s (#%d)',
'[%s][New comment] %s (#%d)' => '[%s][ความคิดเห็นใหม่] %s (#%d)',
'[%s][Comment updated] %s (#%d)' => '[%s][ปรับปรุงความคิดเห็น] %s (#%d)',
'[%s][New subtask] %s (#%d)' => '[%s][งานย่อยใหม่] %s (#%d)',
'[%s][Subtask updated] %s (#%d)' => '[%s][ปรับปรุงงานย่อย] %s (#%d)',
'[%s][New task] %s (#%d)' => '[%s][งานใหม่] %s (#%d)',
'[%s][Task updated] %s (#%d)' => '[%s][ปรับปรุุงงาน] %s (#%d)',
'[%s][Task closed] %s (#%d)' => '[%s][งานที่ปิด] %s (#%d)',
'[%s][Task opened] %s (#%d)' => '[%s][งานที่เปิด] %s (#%d)',
// 'New attachment' => '',
// 'New comment' => '',
// 'New subtask' => '',
// 'Subtask updated' => '',
// 'Task updated' => '',
// 'Task closed' => '',
// 'Task opened' => '',
'[%s][Due tasks]' => '[%s][งานปัจจุบัน]',
'[Kanboard] Notification' => '[Kanboard] แจ้งเตือน',
'I want to receive notifications only for those projects:' => 'ฉันต้องการรับการแจ้งเตือนสำหรับโปรเจค:',
@ -500,9 +498,9 @@ return array(
'Task assignee change' => 'เปลี่ยนการกำหนดบุคคลของงาน',
// '%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)' => '',
// 'Column Change' => '',
// 'Position Change' => '',
// 'Assignee Change' => '',
'New password for the user "%s"' => 'รหัสผ่านใหม่สำหรับผู้ใช้ "%s"',
// 'Choose an event' => '',
// 'Github commit received' => '',
@ -647,5 +645,93 @@ return array(
// 'Application default' => '',
// 'Language:' => '',
// 'Timezone:' => '',
// 'All columns' => '',
// 'Calendar for "%s"' => '',
// 'Filter by column' => '',
// 'Filter by status' => '',
// 'Calendar' => '',
// 'Next' => '',
// '#%d' => '',
// 'Filter by color' => '',
// 'Filter by swimlane' => '',
// 'All swimlanes' => '',
// 'All colors' => '',
// 'All status' => '',
// 'Add a comment logging moving the task between columns' => '',
// 'Moved to column %s' => '',
// 'Change description' => '',
// 'User dashboard' => '',
// 'Allow only one subtask in progress at the same time for a user' => '',
// 'Edit column "%s"' => '',
// 'Enable time tracking for subtasks' => '',
// 'Select the new status of the subtask: "%s"' => '',
// 'Subtask timesheet' => '',
// 'There is nothing to show.' => '',
// 'Time Tracking' => '',
// 'You already have one subtask in progress' => '',
// 'Which parts of the project do you want to duplicate?' => '',
// 'Change dashboard view' => '',
// 'Show/hide activities' => '',
// 'Show/hide projects' => '',
// 'Show/hide subtasks' => '',
// 'Show/hide tasks' => '',
// 'Disable login form' => '',
// 'Show/hide calendar' => '',
// 'User calendar' => '',
// 'Bitbucket commit received' => '',
// 'Bitbucket webhooks' => '',
// 'Help on Bitbucket webhooks' => '',
// 'Start' => '',
// 'End' => '',
// 'Task age in days' => '',
// 'Days in this column' => '',
// '%dd' => '',
// 'Add a link' => '',
// 'Add a new link' => '',
// 'Do you really want to remove this link: "%s"?' => '',
// 'Do you really want to remove this link with task #%d?' => '',
// 'Field required' => '',
// 'Link added successfully.' => '',
// 'Link updated successfully.' => '',
// 'Link removed successfully.' => '',
// 'Link labels' => '',
// 'Link modification' => '',
// 'Links' => '',
// 'Link settings' => '',
// 'Opposite label' => '',
// 'Remove a link' => '',
// 'Task\'s links' => '',
// 'The labels must be different' => '',
// 'There is no link.' => '',
// 'This label must be unique' => '',
// 'Unable to create your link.' => '',
// 'Unable to update your link.' => '',
// 'Unable to remove this link.' => '',
// 'relates to' => '',
// 'blocks' => '',
// 'is blocked by' => '',
// 'duplicates' => '',
// 'is duplicated by' => '',
// 'is a child of' => '',
// 'is a parent of' => '',
// 'targets milestone' => '',
// 'is a milestone of' => '',
// 'fixes' => '',
// 'is fixed by' => '',
// 'This task' => '',
// '<1h' => '',
// '%dh' => '',
// '%b %e' => '',
// 'Expand tasks' => '',
// 'Collapse tasks' => '',
// 'Expand/collapse tasks' => '',
// 'Close dialog box' => '',
// 'Submit a form' => '',
// 'Board view' => '',
// 'Keyboard shortcuts' => '',
// 'Open board switcher' => '',
// 'Application' => '',
// 'Filter recently updated' => '',
// 'since %B %e, %Y at %k:%M %p' => '',
// 'More filters' => '',
);

View file

@ -408,15 +408,13 @@ return array(
'Comment updated' => '更新了评论',
'New comment posted by %s' => '%s 的新评论',
'List of due tasks for the project "%s"' => '项目"%s"的到期任务列表',
'[%s][New attachment] %s (#%d)' => '[%s][新附件] %s (#%d)',
'[%s][New comment] %s (#%d)' => '[%s][新评论] %s (#%d)',
'[%s][Comment updated] %s (#%d)' => '[%s][评论更新] %s (#%d)',
'[%s][New subtask] %s (#%d)' => '[%s][新子任务] %s (#%d)',
'[%s][Subtask updated] %s (#%d)' => '[%s][子任务更新] %s (#%d)',
'[%s][New task] %s (#%d)' => '[%s][新任务] %s (#%d)',
'[%s][Task updated] %s (#%d)' => '[%s][任务更新] %s (#%d)',
'[%s][Task closed] %s (#%d)' => '[%s][任务关闭] %s (#%d)',
'[%s][Task opened] %s (#%d)' => '[%s][任务开启] %s (#%d)',
// 'New attachment' => '',
// 'New comment' => '',
// 'New subtask' => '',
// 'Subtask updated' => '',
// 'Task updated' => '',
// 'Task closed' => '',
// 'Task opened' => '',
'[%s][Due tasks]' => '[%s][到期任务]',
'[Kanboard] Notification' => '[Kanboard] 通知',
'I want to receive notifications only for those projects:' => '我仅需要收到下面项目的通知:',
@ -500,9 +498,9 @@ return array(
'Task assignee change' => '任务分配变更',
'%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)',
// 'Column Change' => '',
// 'Position Change' => '',
// 'Assignee Change' => '',
'New password for the user "%s"' => '用户"%s"的新密码',
'Choose an event' => '选择一个事件',
'Github commit received' => '收到了Github提交',
@ -647,5 +645,93 @@ return array(
// 'Application default' => '',
// 'Language:' => '',
// 'Timezone:' => '',
// 'All columns' => '',
// 'Calendar for "%s"' => '',
// 'Filter by column' => '',
// 'Filter by status' => '',
// 'Calendar' => '',
// 'Next' => '',
// '#%d' => '',
// 'Filter by color' => '',
// 'Filter by swimlane' => '',
// 'All swimlanes' => '',
// 'All colors' => '',
// 'All status' => '',
// 'Add a comment logging moving the task between columns' => '',
// 'Moved to column %s' => '',
// 'Change description' => '',
// 'User dashboard' => '',
// 'Allow only one subtask in progress at the same time for a user' => '',
// 'Edit column "%s"' => '',
// 'Enable time tracking for subtasks' => '',
// 'Select the new status of the subtask: "%s"' => '',
// 'Subtask timesheet' => '',
// 'There is nothing to show.' => '',
// 'Time Tracking' => '',
// 'You already have one subtask in progress' => '',
// 'Which parts of the project do you want to duplicate?' => '',
// 'Change dashboard view' => '',
// 'Show/hide activities' => '',
// 'Show/hide projects' => '',
// 'Show/hide subtasks' => '',
// 'Show/hide tasks' => '',
// 'Disable login form' => '',
// 'Show/hide calendar' => '',
// 'User calendar' => '',
// 'Bitbucket commit received' => '',
// 'Bitbucket webhooks' => '',
// 'Help on Bitbucket webhooks' => '',
// 'Start' => '',
// 'End' => '',
// 'Task age in days' => '',
// 'Days in this column' => '',
// '%dd' => '',
// 'Add a link' => '',
// 'Add a new link' => '',
// 'Do you really want to remove this link: "%s"?' => '',
// 'Do you really want to remove this link with task #%d?' => '',
// 'Field required' => '',
// 'Link added successfully.' => '',
// 'Link updated successfully.' => '',
// 'Link removed successfully.' => '',
// 'Link labels' => '',
// 'Link modification' => '',
// 'Links' => '',
// 'Link settings' => '',
// 'Opposite label' => '',
// 'Remove a link' => '',
// 'Task\'s links' => '',
// 'The labels must be different' => '',
// 'There is no link.' => '',
// 'This label must be unique' => '',
// 'Unable to create your link.' => '',
// 'Unable to update your link.' => '',
// 'Unable to remove this link.' => '',
// 'relates to' => '',
// 'blocks' => '',
// 'is blocked by' => '',
// 'duplicates' => '',
// 'is duplicated by' => '',
// 'is a child of' => '',
// 'is a parent of' => '',
// 'targets milestone' => '',
// 'is a milestone of' => '',
// 'fixes' => '',
// 'is fixed by' => '',
// 'This task' => '',
// '<1h' => '',
// '%dh' => '',
// '%b %e' => '',
// 'Expand tasks' => '',
// 'Collapse tasks' => '',
// 'Expand/collapse tasks' => '',
// 'Close dialog box' => '',
// 'Submit a form' => '',
// 'Board view' => '',
// 'Keyboard shortcuts' => '',
// 'Open board switcher' => '',
// 'Application' => '',
// 'Filter recently updated' => '',
// 'since %B %e, %Y at %k:%M %p' => '',
// 'More filters' => '',
);

View file

@ -22,6 +22,7 @@ class Acl extends Base
'board' => array('readonly'),
'project' => array('feed'),
'webhook' => '*',
'app' => array('colors'),
);
/**
@ -37,6 +38,8 @@ class Acl extends Base
'project' => array('show', 'tasks', 'search', 'activity'),
'subtask' => '*',
'task' => '*',
'tasklink' => '*',
'calendar' => array('show', 'project'),
);
/**
@ -48,7 +51,7 @@ class Acl extends Base
private $manager_acl = array(
'action' => '*',
'analytic' => '*',
'board' => array('movecolumn', 'edit', 'update', 'add', 'remove'),
'board' => array('movecolumn', 'edit', 'editcolumn', 'updatecolumn', 'add', 'remove'),
'category' => '*',
'export' => array('tasks', 'subtasks', 'summary'),
'project' => array('edit', 'update', 'share', 'integration', 'users', 'alloweverybody', 'allow', 'setowner', 'revoke', 'duplicate', 'disable', 'enable'),
@ -62,8 +65,10 @@ class Acl extends Base
* @var array
*/
private $admin_acl = array(
'app' => array('dashboard'),
'user' => array('index', 'create', 'save', 'remove'),
'config' => '*',
'link' => '*',
'project' => array('remove'),
);

View file

@ -4,6 +4,7 @@ namespace Model;
use Integration\GitlabWebhook;
use Integration\GithubWebhook;
use Integration\BitbucketWebhook;
use SimpleValidator\Validator;
use SimpleValidator\Validators;
@ -49,6 +50,7 @@ class Action extends Base
'TaskAssignCategoryColor' => t('Assign automatically a category based on a color'),
'CommentCreation' => t('Create a comment from an external provider'),
'TaskCreation' => t('Create a task from an external provider'),
'TaskLogMoveAnotherColumn' => t('Add a comment logging moving the task between columns'),
'TaskAssignUser' => t('Change the assignee based on an external username'),
'TaskAssignCategoryLabel' => t('Change the category based on an external label'),
);
@ -84,6 +86,7 @@ class Action extends Base
GitlabWebhook::EVENT_COMMIT => t('Gitlab commit received'),
GitlabWebhook::EVENT_ISSUE_OPENED => t('Gitlab issue opened'),
GitlabWebhook::EVENT_ISSUE_CLOSED => t('Gitlab issue closed'),
BitbucketWebhook::EVENT_COMMIT => t('Bitbucket commit received'),
);
asort($values);

View file

@ -42,6 +42,13 @@ class Authentication extends Base
// If the user is already logged it's ok
if ($this->userSession->isLogged()) {
// Check if the user session match an existing user
if (! $this->user->exists($this->userSession->getId())) {
$this->backend('rememberMe')->destroy($this->userSession->getId());
$this->session->close();
return false;
}
// We update each time the RememberMe cookie tokens
if ($this->backend('rememberMe')->hasCookie()) {
$this->backend('rememberMe')->refresh();

View file

@ -10,37 +10,43 @@ use Pimple\Container;
* @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
* @property \Model\Board $board
* @property \Model\Category $category
* @property \Model\Comment $comment
* @property \Model\CommentHistory $commentHistory
* @property \Model\Color $color
* @property \Model\Config $config
* @property \Model\DateParser $dateParser
* @property \Model\File $file
* @property \Model\LastLogin $lastLogin
* @property \Model\Notification $notification
* @property \Model\Project $project
* @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
* @property \Model\TaskFinder $taskFinder
* @property \Model\TaskHistory $taskHistory
* @property \Model\TaskPosition $taskPosition
* @property \Model\TaskValidator $taskValidator
* @property \Model\TimeTracking $timeTracking
* @property \Model\User $user
* @property \Model\UserSession $userSession
* @property \Model\Webhook $webhook
* @property \Core\Session $session
* @property \Core\Template $template
* @property \Model\Acl $acl
* @property \Model\Action $action
* @property \Model\Authentication $authentication
* @property \Model\Board $board
* @property \Model\Category $category
* @property \Model\Comment $comment
* @property \Model\CommentHistory $commentHistory
* @property \Model\Color $color
* @property \Model\Config $config
* @property \Model\DateParser $dateParser
* @property \Model\File $file
* @property \Model\Helper $helper
* @property \Model\LastLogin $lastLogin
* @property \Model\Link $link
* @property \Model\Notification $notification
* @property \Model\Project $project
* @property \Model\ProjectDuplication $projectDuplication
* @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\TaskDuplication $taskDuplication
* @property \Model\TaskExport $taskExport
* @property \Model\TaskFinder $taskFinder
* @property \Model\TaskHistory $taskHistory
* @property \Model\TaskLink $taskLink
* @property \Model\TaskPosition $taskPosition
* @property \Model\TaskValidator $taskValidator
* @property \Model\TimeTracking $timeTracking
* @property \Model\SubtaskTimeTracking $subtaskTimeTracking
* @property \Model\User $user
* @property \Model\UserSession $userSession
* @property \Model\Webhook $webhook
*/
abstract class Base
{

View file

@ -47,7 +47,7 @@ class Board extends Base
$column_name = trim($column_name);
if (! empty($column_name)) {
$columns[] = array('title' => $column_name, 'task_limit' => 0);
$columns[] = array('title' => $column_name, 'task_limit' => 0, 'description' => '');
}
}
@ -73,6 +73,7 @@ class Board extends Base
'position' => ++$position,
'project_id' => $project_id,
'task_limit' => $column['task_limit'],
'description' => $column['description'],
);
if (! $this->db->table(self::TABLE)->save($values)) {
@ -94,7 +95,7 @@ class Board extends Base
public function duplicate($project_from, $project_to)
{
$columns = $this->db->table(Board::TABLE)
->columns('title', 'task_limit')
->columns('title', 'task_limit', 'description')
->eq('project_id', $project_from)
->asc('position')
->findAll();
@ -109,48 +110,22 @@ class Board extends Base
* @param integer $project_id Project id
* @param string $title Column title
* @param integer $task_limit Task limit
* @param string $description Column description
* @return boolean|integer
*/
public function addColumn($project_id, $title, $task_limit = 0)
public function addColumn($project_id, $title, $task_limit = 0, $description = '')
{
$values = array(
'project_id' => $project_id,
'title' => $title,
'task_limit' => $task_limit,
'task_limit' => intval($task_limit),
'position' => $this->getLastColumnPosition($project_id) + 1,
'description' => $description,
);
return $this->persist(self::TABLE, $values);
}
/**
* Update columns
*
* @access public
* @param array $values Form values
* @return boolean
*/
public function update(array $values)
{
$columns = array();
foreach (array('title', 'task_limit') as $field) {
foreach ($values[$field] as $column_id => $value) {
$columns[$column_id][$field] = $value;
}
}
$this->db->startTransaction();
foreach ($columns as $column_id => $values) {
$this->updateColumn($column_id, $values['title'], (int) $values['task_limit']);
}
$this->db->closeTransaction();
return true;
}
/**
* Update a column
*
@ -158,13 +133,15 @@ class Board extends Base
* @param integer $column_id Column id
* @param string $title Column title
* @param integer $task_limit Task limit
* @param string $description Optional description
* @return boolean
*/
public function updateColumn($column_id, $title, $task_limit = 0)
public function updateColumn($column_id, $title, $task_limit = 0, $description = '')
{
return $this->db->table(self::TABLE)->eq('id', $column_id)->update(array(
'title' => $title,
'task_limit' => $task_limit,
'task_limit' => intval($task_limit),
'description' => $description,
));
}
@ -178,7 +155,7 @@ class Board extends Base
*/
public function moveDown($project_id, $column_id)
{
$columns = $this->db->table(self::TABLE)->eq('project_id', $project_id)->asc('position')->listing('id', 'position');
$columns = $this->db->hashtable(self::TABLE)->eq('project_id', $project_id)->asc('position')->getAll('id', 'position');
$positions = array_flip($columns);
if (isset($columns[$column_id]) && $columns[$column_id] < count($columns)) {
@ -207,7 +184,7 @@ class Board extends Base
*/
public function moveUp($project_id, $column_id)
{
$columns = $this->db->table(self::TABLE)->eq('project_id', $project_id)->asc('position')->listing('id', 'position');
$columns = $this->db->hashtable(self::TABLE)->eq('project_id', $project_id)->asc('position')->getAll('id', 'position');
$positions = array_flip($columns);
if (isset($columns[$column_id]) && $columns[$column_id] > 1) {
@ -243,10 +220,12 @@ class Board extends Base
$swimlanes[$i]['columns'] = $columns;
$swimlanes[$i]['nb_columns'] = $nb_columns;
$swimlanes[$i]['nb_tasks'] = 0;
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']);
$swimlanes[$i]['nb_tasks'] += $swimlanes[$i]['columns'][$j]['nb_tasks'];
}
}
@ -258,16 +237,19 @@ class Board extends Base
*
* @access public
* @param integer $project_id
* @param boolean $prepend Prepend default value
* @return array
*/
public function getColumnStats($project_id)
public function getColumnStats($project_id, $prepend = false)
{
return $this->db
->table(Task::TABLE)
->eq('project_id', $project_id)
->eq('is_active', 1)
->groupBy('column_id')
->listing('column_id', 'COUNT(*) AS total');
$listing = $this->db
->hashtable(Task::TABLE)
->eq('project_id', $project_id)
->eq('is_active', 1)
->groupBy('column_id')
->getAll('column_id', 'COUNT(*) AS total');
return $prepend ? array(-1 => t('All columns')) + $listing : $listing;
}
/**
@ -287,11 +269,13 @@ class Board extends Base
*
* @access public
* @param integer $project_id Project id
* @param boolean $prepend Prepend a default value
* @return array
*/
public function getColumnsList($project_id)
public function getColumnsList($project_id, $prepend = false)
{
return $this->db->table(self::TABLE)->eq('project_id', $project_id)->asc('position')->listing('id', 'title');
$listing = $this->db->hashtable(self::TABLE)->eq('project_id', $project_id)->asc('position')->getAll('id', 'title');
return $prepend ? array(-1 => t('All columns')) + $listing : $listing;
}
/**
@ -362,22 +346,16 @@ class Board extends Base
* Validate column modification
*
* @access public
* @param array $columns Original columns List
* @param array $values Required parameters to update a column
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
*/
public function validateModification(array $columns, array $values)
public function validateModification(array $values)
{
$rules = array();
foreach ($columns as $column_id => $column_title) {
$rules[] = new Validators\Integer('task_limit['.$column_id.']', t('This value must be an integer'));
$rules[] = new Validators\GreaterThan('task_limit['.$column_id.']', t('This value must be greater than %d', 0), 0);
$rules[] = new Validators\Required('title['.$column_id.']', t('The title is required'));
$rules[] = new Validators\MaxLength('title['.$column_id.']', t('The maximum length is %d characters', 50), 50);
}
$v = new Validator($values, $rules);
$v = new Validator($values, array(
new Validators\Integer('task_limit', t('This value must be an integer')),
new Validators\Required('title', t('The title is required')),
new Validators\MaxLength('title', t('The maximum length is %d characters', 50), 50),
));
return array(
$v->execute(),

View file

@ -84,10 +84,10 @@ class Category extends Base
*/
public function getList($project_id, $prepend_none = true, $prepend_all = false)
{
$listing = $this->db->table(self::TABLE)
$listing = $this->db->hashtable(self::TABLE)
->eq('project_id', $project_id)
->asc('name')
->listing('id', 'name');
->getAll('id', 'name');
$prepend = array();

View file

@ -3,22 +3,68 @@
namespace Model;
/**
* Color model (TODO: model for the future color picker)
* Color model
*
* @package model
* @author Frederic Guillot
*/
class Color extends Base
{
/**
* Default colors
*
* @access private
* @var array
*/
private $default_colors = array(
'yellow' => array(
'name' => 'Yellow',
'background' => 'rgb(245, 247, 196)',
'border' => 'rgb(223, 227, 45)',
),
'blue' => array(
'name' => 'Blue',
'background' => 'rgb(219, 235, 255)',
'border' => 'rgb(168, 207, 255)',
),
'green' => array(
'name' => 'Green',
'background' => 'rgb(189, 244, 203)',
'border' => 'rgb(74, 227, 113)',
),
'purple' => array(
'name' => 'Purple',
'background' => 'rgb(223, 176, 255)',
'border' => 'rgb(205, 133, 254)',
),
'red' => array(
'name' => 'Red',
'background' => 'rgb(255, 187, 187)',
'border' => 'rgb(255, 151, 151)',
),
'orange' => array(
'name' => 'Orange',
'background' => 'rgb(255, 215, 179)',
'border' => 'rgb(255, 172, 98)',
),
'grey' => array(
'name' => 'Grey',
'background' => 'rgb(238, 238, 238)',
'border' => 'rgb(204, 204, 204)',
),
);
/**
* Get available colors
*
* @access public
* @return array
*/
public function getList()
public function getList($prepend = false)
{
return array(
$listing = $prepend ? array('' => t('All colors')) : array();
return $listing + array(
'yellow' => t('Yellow'),
'blue' => t('Blue'),
'green' => t('Green'),
@ -39,4 +85,57 @@ class Color extends Base
{
return 'yellow'; // TODO: make this parameter configurable
}
/**
* Get Bordercolor from string
*
* @access public
* @param string $color_id Color id
* @return string
*/
public function getBorderColor($color_id)
{
if (isset($this->default_colors[$color_id])) {
return $this->default_colors[$color_id]['border'];
}
return $this->default_colors[$this->getDefaultColor()]['border'];
}
/**
* Get background color from the color_id
*
* @access public
* @param string $color_id Color id
* @return string
*/
public function getBackgroundColor($color_id)
{
if (isset($this->default_colors[$color_id])) {
return $this->default_colors[$color_id]['background'];
}
return $this->default_colors[$this->getDefaultColor()]['background'];
}
/**
* Get CSS stylesheet of all colors
*
* @access public
* @return string
*/
public function getCss()
{
$buffer = '';
foreach ($this->default_colors as $color => $values) {
$buffer .= 'td.color-'.$color.',';
$buffer .= 'div.color-'.$color.' {';
$buffer .= 'background-color: '.$values['background'].';';
$buffer .= 'border-color: '.$values['border'];
$buffer .= '}';
}
return $buffer;
}
}

View file

@ -75,6 +75,52 @@ class Config extends Base
return $languages;
}
/**
* Get javascript language code
*
* @access public
* @return string
*/
public function getJsLanguageCode()
{
$languages = array(
'da_DK' => 'da',
'de_DE' => 'de',
'en_US' => 'en',
'es_ES' => 'es',
'fr_FR' => 'fr',
'it_IT' => 'it',
'hu_HU' => 'hu',
'pl_PL' => 'pl',
'pt_BR' => 'pt-br',
'ru_RU' => 'ru',
'fi_FI' => 'fi',
'sv_SE' => 'sv',
'zh_CN' => 'zh-cn',
'ja_JP' => 'ja',
'th_TH' => 'th',
);
$lang = $this->getCurrentLanguage();
return isset($languages[$lang]) ? $languages[$lang] : 'en';
}
/**
* Get current language
*
* @access public
* @return string
*/
public function getCurrentLanguage()
{
if ($this->userSession->isLogged() && ! empty($this->session['user']['language'])) {
return $this->session['user']['language'];
}
return $this->get('application_language', 'en_US');
}
/**
* Get a config variable from the session or the database
*
@ -110,7 +156,7 @@ class Config extends Base
*/
public function getAll()
{
return $this->db->table(self::TABLE)->listing('option', 'value');
return $this->db->hashtable(self::TABLE)->getAll('option', 'value');
}
/**
@ -152,12 +198,22 @@ class Config extends Base
*/
public function setupTranslations()
{
if ($this->userSession->isLogged() && ! empty($this->session['user']['language'])) {
Translator::load($this->session['user']['language']);
}
else {
Translator::load($this->get('application_language', 'en_US'));
Translator::load($this->getCurrentLanguage());
}
/**
* Get current timezone
*
* @access public
* @return string
*/
public function getCurrentTimezone()
{
if ($this->userSession->isLogged() && ! empty($this->session['user']['timezone'])) {
return $this->session['user']['timezone'];
}
return $this->get('application_timezone', 'UTC');
}
/**
@ -167,12 +223,7 @@ class Config extends Base
*/
public function setupTimezone()
{
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'));
}
date_default_timezone_set($this->getCurrentTimezone());
}
/**

View file

@ -87,17 +87,29 @@ class DateParser extends Base
}
/**
* For a given timestamp, reset the date to midnight
* Remove the time from a timestamp
*
* @access public
* @param integer $timestamp Timestamp
* @return integer
*/
public function resetDateToMidnight($timestamp)
public function removeTimeFromTimestamp($timestamp)
{
return mktime(0, 0, 0, date('m', $timestamp), date('d', $timestamp), date('Y', $timestamp));
}
/**
* Get a timetstamp from an ISO date format
*
* @access public
* @param string $date Date format
* @return integer
*/
public function getTimestampFromIsoFormat($date)
{
return $this->removeTimeFromTimestamp(strtotime($date));
}
/**
* Format date (form display)
*
@ -135,7 +147,7 @@ class DateParser extends Base
foreach ($fields as $field) {
if (! empty($values[$field]) && ! is_numeric($values[$field])) {
$values[$field] = $this->getTimestamp($values[$field]);
$values[$field] = $this->removeTimeFromTimestamp($this->getTimestamp($values[$field]));
}
}
}

View file

@ -19,13 +19,6 @@ class File extends Base
*/
const TABLE = 'task_has_files';
/**
* Directory where are stored files
*
* @var string
*/
const BASE_PATH = 'data/files/';
/**
* Events
*
@ -56,7 +49,7 @@ class File extends Base
{
$file = $this->getbyId($file_id);
if (! empty($file) && @unlink(self::BASE_PATH.$file['path'])) {
if (! empty($file) && @unlink(FILES_DIR.$file['path'])) {
return $this->db->table(self::TABLE)->eq('id', $file_id)->remove();
}
@ -152,14 +145,14 @@ class File extends Base
*/
public function setup()
{
if (! is_dir(self::BASE_PATH)) {
if (! mkdir(self::BASE_PATH, 0755, true)) {
die('Unable to create the upload directory: "'.self::BASE_PATH.'"');
if (! is_dir(FILES_DIR)) {
if (! mkdir(FILES_DIR, 0755, true)) {
die('Unable to create the upload directory: "'.FILES_DIR.'"');
}
}
if (! is_writable(self::BASE_PATH)) {
die('The directory "'.self::BASE_PATH.'" must be writeable by your webserver user');
if (! is_writable(FILES_DIR)) {
die('The directory "'.FILES_DIR.'" must be writeable by your webserver user');
}
}
@ -187,15 +180,15 @@ class File extends Base
$uploaded_filename = $_FILES[$form_name]['tmp_name'][$key];
$destination_filename = $this->generatePath($project_id, $task_id, $original_filename);
@mkdir(self::BASE_PATH.dirname($destination_filename), 0755, true);
@mkdir(FILES_DIR.dirname($destination_filename), 0755, true);
if (@move_uploaded_file($uploaded_filename, self::BASE_PATH.$destination_filename)) {
if (@move_uploaded_file($uploaded_filename, FILES_DIR.$destination_filename)) {
$result[] = $this->create(
$task_id,
$original_filename,
$destination_filename,
$this->isImage(self::BASE_PATH.$destination_filename)
$this->isImage(FILES_DIR.$destination_filename)
);
}
}

234
sources/app/Model/Link.php Normal file
View file

@ -0,0 +1,234 @@
<?php
namespace Model;
use PDO;
use SimpleValidator\Validator;
use SimpleValidator\Validators;
/**
* Link model
*
* @package model
* @author Olivier Maridat
* @author Frederic Guillot
*/
class Link extends Base
{
/**
* SQL table name
*
* @var string
*/
const TABLE = 'links';
/**
* Get a link by id
*
* @access public
* @param integer $link_id Link id
* @return array
*/
public function getById($link_id)
{
return $this->db->table(self::TABLE)->eq('id', $link_id)->findOne();
}
/**
* Get a link by name
*
* @access public
* @param string $label
* @return array
*/
public function getByLabel($label)
{
return $this->db->table(self::TABLE)->eq('label', $label)->findOne();
}
/**
* Get the opposite link id
*
* @access public
* @param integer $link_id Link id
* @return integer
*/
public function getOppositeLinkId($link_id)
{
$link = $this->getById($link_id);
return $link['opposite_id'] ?: $link_id;
}
/**
* Get all links
*
* @access public
* @return array
*/
public function getAll()
{
return $this->db->table(self::TABLE)->findAll();
}
/**
* Get merged links
*
* @access public
* @return array
*/
public function getMergedList()
{
return $this->db
->execute('
SELECT
links.id, links.label, opposite.label as opposite_label
FROM links
LEFT JOIN links AS opposite ON opposite.id=links.opposite_id
')
->fetchAll(PDO::FETCH_ASSOC);
}
/**
* Get label list
*
* @access public
* @param integer $exclude_id Exclude this link
* @param booelan $prepend Prepend default value
* @return array
*/
public function getList($exclude_id = 0, $prepend = true)
{
$labels = $this->db->hashtable(self::TABLE)->neq('id', $exclude_id)->asc('id')->getAll('id', 'label');
foreach ($labels as &$value) {
$value = t($value);
}
return $prepend ? array('') + $labels : $labels;
}
/**
* Create a new link label
*
* @access public
* @param string $label
* @param string $opposite_label
* @return boolean
*/
public function create($label, $opposite_label = '')
{
$this->db->startTransaction();
if (! $this->db->table(self::TABLE)->insert(array('label' => $label))) {
$this->db->cancelTransaction();
return false;
}
if ($opposite_label !== '') {
$this->createOpposite($opposite_label);
}
$this->db->closeTransaction();
return true;
}
/**
* Create the opposite label (executed inside create() method)
*
* @access private
* @param string $label
*/
private function createOpposite($label)
{
$label_id = $this->db->getConnection()->getLastId();
$this->db
->table(self::TABLE)
->insert(array(
'label' => $label,
'opposite_id' => $label_id,
));
$this->db
->table(self::TABLE)
->eq('id', $label_id)
->update(array(
'opposite_id' => $this->db->getConnection()->getLastId()
));
}
/**
* Update a link
*
* @access public
* @param array $values
* @return boolean
*/
public function update(array $values)
{
return $this->db
->table(self::TABLE)
->eq('id', $values['id'])
->update(array(
'label' => $values['label'],
'opposite_id' => $values['opposite_id'],
));
}
/**
* Remove a link a the relation to its opposite
*
* @access public
* @param integer $link_id
* @return boolean
*/
public function remove($link_id)
{
$this->db->table(self::TABLE)->eq('opposite_id', $link_id)->update(array('opposite_id' => 0));
return $this->db->table(self::TABLE)->eq('id', $link_id)->remove();
}
/**
* Validate creation
*
* @access public
* @param array $values Form values
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
*/
public function validateCreation(array $values)
{
$v = new Validator($values, array(
new Validators\Required('label', t('Field required')),
new Validators\Unique('label', t('This label must be unique'), $this->db->getConnection(), self::TABLE),
new Validators\NotEquals('label', 'opposite_label', t('The labels must be different')),
));
return array(
$v->execute(),
$v->getErrors()
);
}
/**
* Validate modification
*
* @access public
* @param array $values Form values
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
*/
public function validateModification(array $values)
{
$v = new Validator($values, array(
new Validators\Required('id', t('Field required')),
new Validators\Required('opposite_id', t('Field required')),
new Validators\Required('label', t('Field required')),
new Validators\Unique('label', t('This label must be unique'), $this->db->getConnection(), self::TABLE),
));
return array(
$v->execute(),
$v->getErrors()
);
}
}

View file

@ -118,6 +118,18 @@ class Notification extends Base
}
}
/**
* Get the mail subject for a given label
*
* @access private
* @param string $label Label
* @param array $data Template data
*/
private function getStandardMailSubject($label, array $data)
{
return sprintf('[%s][%s] %s (#%d)', $data['task']['project_name'], $label, $data['task']['title'], $data['task']['id']);
}
/**
* Get the mail subject for a given template name
*
@ -129,40 +141,40 @@ class Notification extends Base
{
switch ($template) {
case 'file_creation':
$subject = e('[%s][New attachment] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']);
$subject = $this->getStandardMailSubject(t('New attachment'), $data);
break;
case 'comment_creation':
$subject = e('[%s][New comment] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']);
$subject = $this->getStandardMailSubject(t('New comment'), $data);
break;
case 'comment_update':
$subject = e('[%s][Comment updated] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']);
$subject = $this->getStandardMailSubject(t('Comment updated'), $data);
break;
case 'subtask_creation':
$subject = e('[%s][New subtask] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']);
$subject = $this->getStandardMailSubject(t('New subtask'), $data);
break;
case 'subtask_update':
$subject = e('[%s][Subtask updated] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']);
$subject = $this->getStandardMailSubject(t('Subtask updated'), $data);
break;
case 'task_creation':
$subject = e('[%s][New task] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']);
$subject = $this->getStandardMailSubject(t('New task'), $data);
break;
case 'task_update':
$subject = e('[%s][Task updated] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']);
$subject = $this->getStandardMailSubject(t('Task updated'), $data);
break;
case 'task_close':
$subject = e('[%s][Task closed] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']);
$subject = $this->getStandardMailSubject(t('Task closed'), $data);
break;
case 'task_open':
$subject = e('[%s][Task opened] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']);
$subject = $this->getStandardMailSubject(t('Task opened'), $data);
break;
case 'task_move_column':
$subject = e('[%s][Column Change] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']);
$subject = $this->getStandardMailSubject(t('Column Change'), $data);
break;
case 'task_move_position':
$subject = e('[%s][Position Change] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']);
$subject = $this->getStandardMailSubject(t('Position Change'), $data);
break;
case 'task_assignee_change':
$subject = e('[%s][Assignee Change] %s (#%d)', $data['task']['project_name'], $data['task']['title'], $data['task']['id']);
$subject = $this->getStandardMailSubject(t('Assignee Change'), $data);
break;
case 'task_due':
$subject = e('[%s][Due tasks]', $data['project']);

View file

@ -95,27 +95,25 @@ class Project extends Base
}
/**
* Get all projects, optionaly fetch stats for each project and can check users permissions
* Get all projects
*
* @access public
* @param bool $filter_permissions If true, remove projects not allowed for the current user
* @return array
*/
public function getAll($filter_permissions = false)
public function getAll()
{
$projects = $this->db->table(self::TABLE)->asc('name')->findAll();
return $this->db->table(self::TABLE)->asc('name')->findAll();
}
if ($filter_permissions) {
foreach ($projects as $key => $project) {
if (! $this->projectPermission->isUserAllowed($project['id'], $this->userSession->getId())) {
unset($projects[$key]);
}
}
}
return $projects;
/**
* Get all project ids
*
* @access public
* @return array
*/
public function getAllIds()
{
return $this->db->table(self::TABLE)->asc('name')->findAllByColumn('id');
}
/**
@ -128,10 +126,10 @@ class Project extends Base
public function getList($prepend = true)
{
if ($prepend) {
return array(t('None')) + $this->db->table(self::TABLE)->asc('name')->listing('id', 'name');
return array(t('None')) + $this->db->hashtable(self::TABLE)->asc('name')->getAll('id', 'name');
}
return $this->db->table(self::TABLE)->asc('name')->listing('id', 'name');
return $this->db->hashtable(self::TABLE)->asc('name')->getAll('id', 'name');
}
/**
@ -160,10 +158,10 @@ class Project extends Base
public function getListByStatus($status)
{
return $this->db
->table(self::TABLE)
->hashtable(self::TABLE)
->asc('name')
->eq('is_active', $status)
->listing('id', 'name');
->getAll('id', 'name');
}
/**
@ -188,7 +186,7 @@ class Project extends Base
* @param integer $project_id Project id
* @return array
*/
public function getStats($project_id)
public function getTaskStats($project_id)
{
$stats = array();
$stats['nb_active_tasks'] = 0;
@ -208,62 +206,57 @@ class Project extends Base
}
/**
* Create a project from another one.
* Get stats for each column of a project
*
* @author Antonio Rabelo
* @param integer $project_id Project Id
* @return integer Cloned Project Id
* @access public
* @param array $project
* @return array
*/
public function createProjectFromAnotherProject($project_id)
public function getColumnStats(array &$project)
{
$project = $this->getById($project_id);
$project['columns'] = $this->board->getColumns($project['id']);
$stats = $this->board->getColumnStats($project['id']);
$values = array(
'name' => $project['name'].' ('.t('Clone').')',
'is_active' => true,
'last_modified' => 0,
'token' => '',
'is_public' => 0,
'is_private' => empty($project['is_private']) ? 0 : 1,
);
if (! $this->db->table(self::TABLE)->save($values)) {
return 0;
foreach ($project['columns'] as &$column) {
$column['nb_tasks'] = isset($stats[$column['id']]) ? $stats[$column['id']] : 0;
}
return $this->db->getConnection()->getLastId();
return $project;
}
/**
* Clone a project
* Apply column stats to a collection of projects (filter callback)
*
* @author Antonio Rabelo
* @param integer $project_id Project Id
* @return integer Cloned Project Id
* @access public
* @param array $projects
* @return array
*/
public function duplicate($project_id)
public function applyColumnStats(array $projects)
{
$this->db->startTransaction();
// Get the cloned project Id
$clone_project_id = $this->createProjectFromAnotherProject($project_id);
if (! $clone_project_id) {
$this->db->cancelTransaction();
return false;
foreach ($projects as &$project) {
$this->getColumnStats($project);
}
foreach (array('board', 'category', 'projectPermission', 'action') as $model) {
return $projects;
}
if (! $this->$model->duplicate($project_id, $clone_project_id)) {
$this->db->cancelTransaction();
return false;
}
/**
* Get project summary for a list of project
*
* @access public
* @param array $project_ids List of project id
* @return \PicoDb\Table
*/
public function getQueryColumnStats(array $project_ids)
{
if (empty($project_ids)) {
return $this->db->table(Project::TABLE)->limit(0);
}
$this->db->closeTransaction();
return (int) $clone_project_id;
return $this->db
->table(Project::TABLE)
->in('id', $project_ids)
->filter(array($this, 'applyColumnStats'));
}
/**

View file

@ -56,11 +56,13 @@ class ProjectActivity extends Base
* @access public
* @param integer $project_id Project id
* @param integer $limit Maximum events number
* @param integer $start Timestamp of earliest activity
* @param integer $end Timestamp of latest activity
* @return array
*/
public function getProject($project_id, $limit = 50)
public function getProject($project_id, $limit = 50, $start = null, $end = null)
{
return $this->getProjects(array($project_id), $limit);
return $this->getProjects(array($project_id), $limit, $start, $end);
}
/**
@ -69,15 +71,17 @@ class ProjectActivity extends Base
* @access public
* @param integer[] $project_ids Projects id
* @param integer $limit Maximum events number
* @param integer $start Timestamp of earliest activity
* @param integer $end Timestamp of latest activity
* @return array
*/
public function getProjects(array $project_ids, $limit = 50)
public function getProjects(array $project_ids, $limit = 50, $start = null, $end = null)
{
if (empty($project_ids)) {
return array();
}
$events = $this->db->table(self::TABLE)
$query = $this->db->table(self::TABLE)
->columns(
self::TABLE.'.*',
User::TABLE.'.username AS author_username',
@ -85,9 +89,18 @@ class ProjectActivity extends Base
)
->in('project_id', $project_ids)
->join(User::TABLE, 'id', 'creator_id')
->desc('id')
->limit($limit)
->findAll();
->desc(self::TABLE.'.id')
->limit($limit);
if(!is_null($start)){
$query->gte('date_creation', $start);
}
if(!is_null($end)){
$query->lte('date_creation', $end);
}
$events = $query->findAll();
foreach ($events as &$event) {
@ -162,9 +175,9 @@ class ProjectActivity extends Base
return t('%s moved the task #%d to the column "%s"', $event['author'], $event['task']['id'], $event['task']['column_title']);
case Task::EVENT_MOVE_POSITION:
return t('%s moved the task #%d to the position %d in the column "%s"', $event['author'], $event['task']['id'], $event['task']['position'], $event['task']['column_title']);
case SubTask::EVENT_UPDATE:
case Subtask::EVENT_UPDATE:
return t('%s updated a subtask for the task #%d', $event['author'], $event['task']['id']);
case SubTask::EVENT_CREATE:
case Subtask::EVENT_CREATE:
return t('%s created a subtask for the task #%d', $event['author'], $event['task']['id']);
case Comment::EVENT_UPDATE:
return t('%s updated a comment on the task #%d', $event['author'], $event['task']['id']);

View file

@ -0,0 +1,108 @@
<?php
namespace Model;
/**
* Project Duplication
*
* @package model
* @author Frederic Guillot
* @author Antonio Rabelo
*/
class ProjectDuplication extends Base
{
/**
* Get a valid project name for the duplication
*
* @access public
* @param string $name Project name
* @param integer $max_length Max length allowed
* @return string
*/
public function getClonedProjectName($name, $max_length = 50)
{
$suffix = ' ('.t('Clone').')';
if (strlen($name.$suffix) > $max_length) {
$name = substr($name, 0, $max_length - strlen($suffix));
}
return $name.$suffix;
}
/**
* Create a project from another one
*
* @param integer $project_id Project Id
* @return integer Cloned Project Id
*/
public function copy($project_id)
{
$project = $this->project->getById($project_id);
$values = array(
'name' => $this->getClonedProjectName($project['name']),
'is_active' => true,
'last_modified' => 0,
'token' => '',
'is_public' => 0,
'is_private' => empty($project['is_private']) ? 0 : 1,
);
if (! $this->db->table(Project::TABLE)->save($values)) {
return 0;
}
return $this->db->getConnection()->getLastId();
}
/**
* Clone a project with all settings
*
* @param integer $project_id Project Id
* @param array $part_selection Selection of optional project parts to duplicate. Possible options: 'swimlane', 'action', 'category', 'task'
* @return integer Cloned Project Id
*/
public function duplicate($project_id, $part_selection = array('category', 'action'))
{
$this->db->startTransaction();
// Get the cloned project Id
$clone_project_id = $this->copy($project_id);
if (! $clone_project_id) {
$this->db->cancelTransaction();
return false;
}
// Clone Columns, Categories, Permissions and Actions
$optional_parts = array('swimlane', 'action', 'category');
foreach (array('board', 'category', 'projectPermission', 'action', 'swimlane') as $model) {
// Skip if optional part has not been selected
if (in_array($model, $optional_parts) && ! in_array($model, $part_selection)) {
continue;
}
if (! $this->$model->duplicate($project_id, $clone_project_id)) {
$this->db->cancelTransaction();
return false;
}
}
$this->db->closeTransaction();
// Clone Tasks if in $part_selection
if (in_array('task', $part_selection)) {
$tasks = $this->taskFinder->getAll($project_id);
foreach ($tasks as $task) {
if (! $this->taskDuplication->duplicateToProject($task['id'], $clone_project_id)) {
return false;
}
}
}
return (int) $clone_project_id;
}
}

View file

@ -1,50 +0,0 @@
<?php
namespace Model;
/**
* Project Paginator
*
* @package model
* @author Frederic Guillot
*/
class ProjectPaginator extends Base
{
/**
* Get project summary for a list of project (number of tasks for each column)
*
* @access public
* @param array $project_ids List of project id
* @param integer $offset Offset
* @param integer $limit Limit
* @param string $column Sorting column
* @param string $direction Sorting direction
* @return array
*/
public function projectSummaries(array $project_ids, $offset = 0, $limit = 25, $column = 'name', $direction = 'asc')
{
if (empty($project_ids)) {
return array();
}
$projects = $this->db
->table(Project::TABLE)
->in('id', $project_ids)
->offset($offset)
->limit($limit)
->orderBy($column, $direction)
->findAll();
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'] = isset($stats[$column['id']]) ? $stats[$column['id']] : 0;
}
}
return $projects;
}
}

View file

@ -313,12 +313,62 @@ class ProjectPermission extends Base
* @return array
*/
public function getMemberProjects($user_id)
{
return $this->db
->hashtable(Project::TABLE)
->eq('user_id', $user_id)
->join(self::TABLE, 'project_id', 'id')
->getAll('projects.id', 'name');
}
/**
* Return a list of project ids where the user is member
*
* @access public
* @param integer $user_id User id
* @return array
*/
public function getMemberProjectIds($user_id)
{
return $this->db
->table(Project::TABLE)
->eq('user_id', $user_id)
->join(self::TABLE, 'project_id', 'id')
->listing('projects.id', 'name');
->findAllByColumn('projects.id');
}
/**
* Return a list of active project ids where the user is member
*
* @access public
* @param integer $user_id User id
* @return array
*/
public function getActiveMemberProjectIds($user_id)
{
return $this->db
->table(Project::TABLE)
->eq('user_id', $user_id)
->eq(Project::TABLE.'.is_active', Project::ACTIVE)
->join(self::TABLE, 'project_id', 'id')
->findAllByColumn('projects.id');
}
/**
* Return a list of active projects where the user is member
*
* @access public
* @param integer $user_id User id
* @return array
*/
public function getActiveMemberProjects($user_id)
{
return $this->db
->hashtable(Project::TABLE)
->eq('user_id', $user_id)
->eq(Project::TABLE.'.is_active', Project::ACTIVE)
->join(self::TABLE, 'project_id', 'id')
->getAll('projects.id', 'name');
}
/**

View file

@ -12,7 +12,7 @@ use SimpleValidator\Validators;
* @package model
* @author Frederic Guillot
*/
class SubTask extends Base
class Subtask extends Base
{
/**
* SQL table name
@ -65,6 +65,49 @@ class SubTask extends Base
);
}
/**
* Add subtask status status to the resultset
*
* @access public
* @param array $subtasks Subtasks
* @return array
*/
public function addStatusName(array $subtasks)
{
$status = $this->getStatusList();
foreach ($subtasks as &$subtask) {
$subtask['status_name'] = $status[$subtask['status']];
}
return $subtasks;
}
/**
* Get the query to fetch subtasks assigned to a user
*
* @access public
* @param integer $user_id User id
* @param array $status List of status
* @return \PicoDb\Table
*/
public function getUserQuery($user_id, array $status)
{
return $this->db->table(Subtask::TABLE)
->columns(
Subtask::TABLE.'.*',
Task::TABLE.'.project_id',
Task::TABLE.'.color_id',
Project::TABLE.'.name AS project_name'
)
->eq('user_id', $user_id)
->eq(Project::TABLE.'.is_active', Project::ACTIVE)
->in(Subtask::TABLE.'.status', $status)
->join(Task::TABLE, 'id', 'task_id')
->join(Project::TABLE, 'id', 'project_id', Task::TABLE)
->filter(array($this, 'addStatusName'));
}
/**
* Get all subtasks for a given task
*
@ -74,19 +117,14 @@ class SubTask extends Base
*/
public function getAll($task_id)
{
$status = $this->getStatusList();
$subtasks = $this->db->table(self::TABLE)
->eq('task_id', $task_id)
->columns(self::TABLE.'.*', User::TABLE.'.username', User::TABLE.'.name')
->join(User::TABLE, 'id', 'user_id')
->asc(self::TABLE.'.id')
->findAll();
foreach ($subtasks as &$subtask) {
$subtask['status_name'] = $status[$subtask['status']];
}
return $subtasks;
return $this->db
->table(self::TABLE)
->eq('task_id', $task_id)
->columns(self::TABLE.'.*', User::TABLE.'.username', User::TABLE.'.name')
->join(User::TABLE, 'id', 'user_id')
->asc(self::TABLE.'.id')
->filter(array($this, 'addStatusName'))
->findAll();
}
/**
@ -101,18 +139,13 @@ class SubTask extends Base
{
if ($more) {
$subtask = $this->db->table(self::TABLE)
->eq(self::TABLE.'.id', $subtask_id)
->columns(self::TABLE.'.*', User::TABLE.'.username', User::TABLE.'.name')
->join(User::TABLE, 'id', 'user_id')
->findOne();
if ($subtask) {
$status = $this->getStatusList();
$subtask['status_name'] = $status[$subtask['status']];
}
return $subtask;
return $this->db
->table(self::TABLE)
->eq(self::TABLE.'.id', $subtask_id)
->columns(self::TABLE.'.*', User::TABLE.'.username', User::TABLE.'.name')
->join(User::TABLE, 'id', 'user_id')
->filter(array($this, 'addStatusName'))
->findOne();
}
return $this->db->table(self::TABLE)->eq('id', $subtask_id)->findOne();
@ -165,6 +198,7 @@ class SubTask extends Base
$result = $this->db->table(self::TABLE)->eq('id', $values['id'])->save($values);
if ($result) {
$this->container['dispatcher']->dispatch(
self::EVENT_UPDATE,
new SubtaskEvent($values)
@ -196,6 +230,37 @@ class SubTask extends Base
return $this->update($values);
}
/**
* Get the subtask in progress for this user
*
* @access public
* @param integer $user_id
* @return array
*/
public function getSubtaskInProgress($user_id)
{
return $this->db->table(self::TABLE)
->eq('status', self::STATUS_INPROGRESS)
->eq('user_id', $user_id)
->findOne();
}
/**
* Return true if the user have a subtask in progress
*
* @access public
* @param integer $user_id
* @return boolean
*/
public function hasSubtaskInProgress($user_id)
{
return $this->config->get('subtask_restriction') == 1 &&
$this->db->table(self::TABLE)
->eq('status', self::STATUS_INPROGRESS)
->eq('user_id', $user_id)
->count() === 1;
}
/**
* Remove
*
@ -220,7 +285,7 @@ class SubTask extends Base
{
return $this->db->transaction(function ($db) use ($src_task_id, $dst_task_id) {
$subtasks = $db->table(SubTask::TABLE)
$subtasks = $db->table(Subtask::TABLE)
->columns('title', 'time_estimated')
->eq('task_id', $src_task_id)
->asc('id') // Explicit sorting for postgresql
@ -230,7 +295,7 @@ class SubTask extends Base
$subtask['task_id'] = $dst_task_id;
if (! $db->table(SubTask::TABLE)->save($subtask)) {
if (! $db->table(Subtask::TABLE)->save($subtask)) {
return false;
}
}

View file

@ -29,7 +29,7 @@ class SubtaskExport extends Base
*/
public function export($project_id, $from, $to)
{
$this->subtask_status = $this->subTask->getStatusList();
$this->subtask_status = $this->subtask->getStatusList();
$subtasks = $this->getSubtasks($project_id, $from, $to);
$results = array($this->getColumns());
@ -86,25 +86,25 @@ class SubtaskExport extends Base
* 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)
* @param integer $project_id Project 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));
$from = $this->dateParser->removeTimeFromTimestamp($this->dateParser->getTimestamp($from));
}
if (! is_numeric($to)) {
$to = $this->dateParser->resetDateToMidnight(strtotime('+1 day', $this->dateParser->getTimestamp($to)));
$to = $this->dateParser->removeTimeFromTimestamp(strtotime('+1 day', $this->dateParser->getTimestamp($to)));
}
return $this->db->table(SubTask::TABLE)
return $this->db->table(Subtask::TABLE)
->eq('project_id', $project_id)
->columns(
SubTask::TABLE.'.*',
Subtask::TABLE.'.*',
User::TABLE.'.username AS assignee_username',
User::TABLE.'.name AS assignee_name',
Task::TABLE.'.title AS task_title'
@ -113,7 +113,7 @@ class SubtaskExport extends Base
->lte('date_creation', $to)
->join(Task::TABLE, 'id', 'task_id')
->join(User::TABLE, 'id', 'user_id')
->asc(SubTask::TABLE.'.id')
->asc(Subtask::TABLE.'.id')
->findAll();
}
}

View file

@ -1,68 +0,0 @@
<?php
namespace Model;
/**
* Subtask Paginator
*
* @package model
* @author Frederic Guillot
*/
class SubtaskPaginator extends Base
{
/**
* Get all subtasks assigned to a user
*
* @access public
* @param integer $user_id User id
* @param array $status List of status
* @param integer $offset Offset
* @param integer $limit Limit
* @param string $column Sorting column
* @param string $direction Sorting direction
* @return array
*/
public function userSubtasks($user_id, array $status, $offset = 0, $limit = 25, $column = 'tasks.id', $direction = 'asc')
{
$status_list = $this->subTask->getStatusList();
$subtasks = $this->db->table(SubTask::TABLE)
->columns(
SubTask::TABLE.'.*',
Task::TABLE.'.project_id',
Task::TABLE.'.color_id',
Project::TABLE.'.name AS project_name'
)
->eq('user_id', $user_id)
->in(SubTask::TABLE.'.status', $status)
->join(Task::TABLE, 'id', 'task_id')
->join(Project::TABLE, 'id', 'project_id', Task::TABLE)
->offset($offset)
->limit($limit)
->orderBy($column, $direction)
->findAll();
foreach ($subtasks as &$subtask) {
$subtask['status_name'] = $status_list[$subtask['status']];
}
return $subtasks;
}
/**
* Count all subtasks assigned to the user
*
* @access public
* @param integer $user_id User id
* @param array $status List of status
* @return integer
*/
public function countUserSubtasks($user_id, array $status)
{
return $this->db
->table(SubTask::TABLE)
->eq('user_id', $user_id)
->in('status', $status)
->count();
}
}

View file

@ -0,0 +1,319 @@
<?php
namespace Model;
/**
* Subtask timesheet
*
* @package model
* @author Frederic Guillot
*/
class SubtaskTimeTracking extends Base
{
/**
* SQL table name
*
* @var string
*/
const TABLE = 'subtask_time_tracking';
/**
* Get query for user timesheet (pagination)
*
* @access public
* @param integer $user_id User id
* @return \PicoDb\Table
*/
public function getUserQuery($user_id)
{
return $this->db
->table(self::TABLE)
->columns(
self::TABLE.'.id',
self::TABLE.'.subtask_id',
self::TABLE.'.end',
self::TABLE.'.start',
Subtask::TABLE.'.task_id',
Subtask::TABLE.'.title AS subtask_title',
Task::TABLE.'.title AS task_title',
Task::TABLE.'.project_id',
Task::TABLE.'.color_id'
)
->join(Subtask::TABLE, 'id', 'subtask_id')
->join(Task::TABLE, 'id', 'task_id', Subtask::TABLE)
->eq(self::TABLE.'.user_id', $user_id);
}
/**
* Get query for task timesheet (pagination)
*
* @access public
* @param integer $task_id Task id
* @return \PicoDb\Table
*/
public function getTaskQuery($task_id)
{
return $this->db
->table(self::TABLE)
->columns(
self::TABLE.'.id',
self::TABLE.'.subtask_id',
self::TABLE.'.end',
self::TABLE.'.start',
self::TABLE.'.user_id',
Subtask::TABLE.'.task_id',
Subtask::TABLE.'.title AS subtask_title',
Task::TABLE.'.project_id',
User::TABLE.'.username',
User::TABLE.'.name AS user_fullname'
)
->join(Subtask::TABLE, 'id', 'subtask_id')
->join(Task::TABLE, 'id', 'task_id', Subtask::TABLE)
->join(User::TABLE, 'id', 'user_id', self::TABLE)
->eq(Task::TABLE.'.id', $task_id);
}
/**
* Get query for project timesheet (pagination)
*
* @access public
* @param integer $project_id Project id
* @return \PicoDb\Table
*/
public function getProjectQuery($project_id)
{
return $this->db
->table(self::TABLE)
->columns(
self::TABLE.'.id',
self::TABLE.'.subtask_id',
self::TABLE.'.end',
self::TABLE.'.start',
self::TABLE.'.user_id',
Subtask::TABLE.'.task_id',
Subtask::TABLE.'.title AS subtask_title',
Task::TABLE.'.project_id',
Task::TABLE.'.color_id',
User::TABLE.'.username',
User::TABLE.'.name AS user_fullname'
)
->join(Subtask::TABLE, 'id', 'subtask_id')
->join(Task::TABLE, 'id', 'task_id', Subtask::TABLE)
->join(User::TABLE, 'id', 'user_id', self::TABLE)
->eq(Task::TABLE.'.project_id', $project_id);
}
/**
* Get all recorded time slots for a given user
*
* @access public
* @param integer $user_id User id
* @return array
*/
public function getUserTimesheet($user_id)
{
return $this->db
->table(self::TABLE)
->eq('user_id', $user_id)
->findAll();
}
/**
* Get user calendar events
*
* @access public
* @param integer $user_id
* @param integer $start
* @param integer $end
* @return array
*/
public function getUserCalendarEvents($user_id, $start, $end)
{
$result = $this->getUserQuery($user_id)
->addCondition($this->getCalendarCondition($start, $end))
->findAll();
return $this->toCalendarEvents($result);
}
/**
* Get project calendar events
*
* @access public
* @param integer $project_id
* @param integer $start
* @param integer $end
* @return array
*/
public function getProjectCalendarEvents($project_id, $start, $end)
{
$result = $this->getProjectQuery($project_id)
->addCondition($this->getCalendarCondition($start, $end))
->findAll();
return $this->toCalendarEvents($result);
}
/**
* Get time slots that should be displayed in the calendar time range
*
* @access private
* @param string $start ISO8601 start date
* @param string $end ISO8601 end date
* @return string
*/
private function getCalendarCondition($start, $end)
{
$start_time = $this->dateParser->getTimestampFromIsoFormat($start);
$end_time = $this->dateParser->getTimestampFromIsoFormat($end);
$start_column = $this->db->escapeIdentifier('start');
$end_column = $this->db->escapeIdentifier('end');
$conditions = array(
"($start_column >= '$start_time' AND $start_column <= '$end_time')",
"($start_column <= '$start_time' AND $end_column >= '$start_time')",
"($start_column <= '$start_time' AND $end_column = '0')",
);
return '('.implode(' OR ', $conditions).')';
}
/**
* Convert a record set to calendar events
*
* @access private
* @param array $rows
* @return array
*/
private function toCalendarEvents(array $rows)
{
$events = array();
foreach ($rows as $row) {
$user = isset($row['username']) ? ' ('.($row['user_fullname'] ?: $row['username']).')' : '';
$events[] = array(
'id' => $row['id'],
'subtask_id' => $row['subtask_id'],
'title' => t('#%d', $row['task_id']).' '.$row['subtask_title'].$user,
'start' => date('Y-m-d\TH:i:s', $row['start']),
'end' => date('Y-m-d\TH:i:s', $row['end'] ?: time()),
'backgroundColor' => $this->color->getBackgroundColor($row['color_id']),
'borderColor' => $this->color->getBorderColor($row['color_id']),
'textColor' => 'black',
'url' => $this->helper->url('task', 'show', array('task_id' => $row['task_id'], 'project_id' => $row['project_id'])),
'editable' => false,
);
}
return $events;
}
/**
* Log start time
*
* @access public
* @param integer $subtask_id
* @param integer $user_id
* @return boolean
*/
public function logStartTime($subtask_id, $user_id)
{
return $this->db
->table(self::TABLE)
->insert(array('subtask_id' => $subtask_id, 'user_id' => $user_id, 'start' => time()));
}
/**
* Log end time
*
* @access public
* @param integer $subtask_id
* @param integer $user_id
* @return boolean
*/
public function logEndTime($subtask_id, $user_id)
{
$this->updateSubtaskTimeSpent($subtask_id, $user_id);
return $this->db
->table(self::TABLE)
->eq('subtask_id', $subtask_id)
->eq('user_id', $user_id)
->eq('end', 0)
->update(array(
'end' => time()
));
}
/**
* Update task time tracking based on subtasks time tracking
*
* @access public
* @param integer $task_id Task id
* @return bool
*/
public function updateTaskTimeTracking($task_id)
{
$result = $this->calculateSubtaskTime($task_id);
if (empty($result['total_spent']) && empty($result['total_estimated'])) {
return true;
}
return $this->db
->table(Task::TABLE)
->eq('id', $task_id)
->update(array(
'time_spent' => $result['total_spent'],
'time_estimated' => $result['total_estimated'],
));
}
/**
* Sum time spent and time estimated for all subtasks
*
* @access public
* @param integer $task_id Task id
* @return array
*/
public function calculateSubtaskTime($task_id)
{
return $this->db
->table(Subtask::TABLE)
->eq('task_id', $task_id)
->columns(
'SUM(time_spent) AS total_spent',
'SUM(time_estimated) AS total_estimated'
)
->findOne();
}
/**
* Update subtask time spent based on the punch clock table
*
* @access public
* @param integer $subtask_id
* @param integer $user_id
* @return bool
*/
public function updateSubtaskTimeSpent($subtask_id, $user_id)
{
$start_time = $this->db
->table(self::TABLE)
->eq('subtask_id', $subtask_id)
->eq('user_id', $user_id)
->eq('end', 0)
->findOneColumn('start');
$subtask = $this->subtask->getById($subtask_id);
return $start_time &&
$this->subtask->update(array( // Fire the event subtask.update
'id' => $subtask['id'],
'time_spent' => $subtask['time_spent'] + round((time() - $start_time) / 3600, 1),
'task_id' => $subtask['task_id'],
));
}
}

View file

@ -161,20 +161,20 @@ class Swimlane extends Base
*
* @access public
* @param integer $project_id Project id
* @param boolean $prepend Prepend default value
* @return array
*/
public function getSwimlanesList($project_id)
public function getList($project_id, $prepend = false)
{
$swimlanes = $this->db->table(self::TABLE)
->eq('project_id', $project_id)
->orderBy('position', 'asc')
->listing('id', 'name');
$swimlanes = array();
$swimlanes[] = $this->db->table(Project::TABLE)->eq('id', $project_id)->findOneColumn('default_swimlane');
$swimlanes[0] = $this->db->table(Project::TABLE)
->eq('id', $project_id)
->findOneColumn('default_swimlane');
$swimlanes = array_merge(
$swimlanes,
$this->db->hashtable(self::TABLE)->eq('project_id', $project_id)->orderBy('name', 'asc')->getAll('id', 'name')
);
return $swimlanes;
return $prepend ? array(-1 => t('All swimlanes')) + $swimlanes : $swimlanes;
}
/**
@ -183,7 +183,7 @@ class Swimlane extends Base
* @access public
* @param integer $project_id
* @param string $name
* @return bool
* @return integer|boolean
*/
public function create($project_id, $name)
{
@ -354,11 +354,11 @@ class Swimlane extends Base
*/
public function moveDown($project_id, $swimlane_id)
{
$swimlanes = $this->db->table(self::TABLE)
$swimlanes = $this->db->hashtable(self::TABLE)
->eq('project_id', $project_id)
->eq('is_active', self::ACTIVE)
->asc('position')
->listing('id', 'position');
->getAll('id', 'position');
$positions = array_flip($swimlanes);
@ -388,11 +388,11 @@ class Swimlane extends Base
*/
public function moveUp($project_id, $swimlane_id)
{
$swimlanes = $this->db->table(self::TABLE)
$swimlanes = $this->db->hashtable(self::TABLE)
->eq('project_id', $project_id)
->eq('is_active', self::ACTIVE)
->asc('position')
->listing('id', 'position');
->getAll('id', 'position');
$positions = array_flip($swimlanes);
@ -412,6 +412,37 @@ class Swimlane extends Base
return false;
}
/**
* Duplicate Swimlane to project
*
* @access public
* @param integer $project_from Project Template
* @param integer $project_to Project that receives the copy
* @return integer|boolean
*/
public function duplicate($project_from, $project_to)
{
$swimlanes = $this->getAll($project_from);
foreach ($swimlanes as $swimlane) {
unset($swimlane['id']);
$swimlane['project_id'] = $project_to;
if (! $this->db->table(self::TABLE)->save($swimlane)) {
return false;
}
}
$default_swimlane = $this->getDefault($project_from);
$default_swimlane['id'] = $project_to;
$this->updateDefault($default_swimlane);
return true;
}
/**
* Validate creation
*

View file

@ -62,6 +62,7 @@ class TaskCreation extends Base
$values['swimlane_id'] = empty($values['swimlane_id']) ? 0 : $values['swimlane_id'];
$values['date_creation'] = time();
$values['date_modification'] = $values['date_creation'];
$values['date_moved'] = $values['date_creation'];
$values['position'] = $this->taskFinder->countByColumnAndSwimlaneId($values['project_id'], $values['column_id'], $values['swimlane_id']) + 1;
}

View file

@ -158,7 +158,7 @@ class TaskDuplication extends Base
$new_task_id = $this->taskCreation->create($values);
if ($new_task_id) {
$this->subTask->duplicate($task_id, $new_task_id);
$this->subtask->duplicate($task_id, $new_task_id);
}
return $new_task_id;

View file

@ -24,7 +24,7 @@ class TaskExport extends Base
public function export($project_id, $from, $to)
{
$tasks = $this->getTasks($project_id, $from, $to);
$swimlanes = $this->swimlane->getSwimlanesList($project_id);
$swimlanes = $this->swimlane->getList($project_id);
$results = array($this->getColumns());
foreach ($tasks as &$task) {
@ -77,11 +77,11 @@ class TaskExport extends Base
';
if (! is_numeric($from)) {
$from = $this->dateParser->resetDateToMidnight($this->dateParser->getTimestamp($from));
$from = $this->dateParser->removeTimeFromTimestamp($this->dateParser->getTimestamp($from));
}
if (! is_numeric($to)) {
$to = $this->dateParser->resetDateToMidnight(strtotime('+1 day', $this->dateParser->getTimestamp($to)));
$to = $this->dateParser->removeTimeFromTimestamp(strtotime('+1 day', $this->dateParser->getTimestamp($to)));
}
$rq = $this->db->execute($sql, array($from, $to, $project_id));

View file

@ -0,0 +1,150 @@
<?php
namespace Model;
/**
* Task Filter
*
* @package model
* @author Frederic Guillot
*/
class TaskFilter extends Base
{
private $query;
public function create()
{
$this->query = $this->db->table(Task::TABLE);
return $this;
}
public function excludeTasks(array $task_ids)
{
$this->query->notin('id', $task_ids);
return $this;
}
public function filterByTitle($title)
{
$this->query->ilike('title', '%'.$title.'%');
return $this;
}
public function filterByProjects(array $project_ids)
{
$this->query->in('project_id', $project_ids);
return $this;
}
public function filterByProject($project_id)
{
if ($project_id > 0) {
$this->query->eq('project_id', $project_id);
}
return $this;
}
public function filterByCategory($category_id)
{
if ($category_id >= 0) {
$this->query->eq('category_id', $category_id);
}
return $this;
}
public function filterByOwner($owner_id)
{
if ($owner_id >= 0) {
$this->query->eq('owner_id', $owner_id);
}
return $this;
}
public function filterByColor($color_id)
{
if ($color_id !== '') {
$this->query->eq('color_id', $color_id);
}
return $this;
}
public function filterByColumn($column_id)
{
if ($column_id >= 0) {
$this->query->eq('column_id', $column_id);
}
return $this;
}
public function filterBySwimlane($swimlane_id)
{
if ($swimlane_id >= 0) {
$this->query->eq('swimlane_id', $swimlane_id);
}
return $this;
}
public function filterByStatus($is_active)
{
if ($is_active >= 0) {
$this->query->eq('is_active', $is_active);
}
return $this;
}
public function filterByDueDateRange($start, $end)
{
$this->query->gte('date_due', $this->dateParser->getTimestampFromIsoFormat($start));
$this->query->lte('date_due', $this->dateParser->getTimestampFromIsoFormat($end));
return $this;
}
public function findAll()
{
return $this->query->findAll();
}
public function toAutoCompletion()
{
return $this->query->columns('id', 'title')->filter(function(array $results) {
foreach ($results as &$result) {
$result['value'] = $result['title'];
$result['label'] = '#'.$result['id'].' - '.$result['title'];
}
return $results;
})->findAll();
}
public function toCalendarEvents()
{
$events = array();
foreach ($this->query->findAll() as $task) {
$events[] = array(
'timezoneParam' => $this->config->getCurrentTimezone(),
'id' => $task['id'],
'title' => t('#%d', $task['id']).' '.$task['title'],
'start' => date('Y-m-d', $task['date_due']),
'end' => date('Y-m-d', $task['date_due']),
'allday' => true,
'backgroundColor' => $this->color->getBackgroundColor($task['color_id']),
'borderColor' => $this->color->getBorderColor($task['color_id']),
'textColor' => 'black',
'url' => $this->helper->url('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])),
);
}
return $events;
}
}

View file

@ -13,12 +13,69 @@ use PDO;
class TaskFinder extends Base
{
/**
* Common request to fetch a list of tasks
* Get query for closed tasks
*
* @access public
* @param integer $project_id Project id
* @return \PicoDb\Table
*/
public function getClosedTaskQuery($project_id)
{
return $this->getExtendedQuery()
->eq('project_id', $project_id)
->eq('is_active', Task::STATUS_CLOSED);
}
/**
* Get query for task search
*
* @access public
* @param integer $project_id Project id
* @param string $search Search terms
* @return \PicoDb\Table
*/
public function getSearchQuery($project_id, $search)
{
return $this->getExtendedQuery()
->eq('project_id', $project_id)
->ilike('title', '%'.$search.'%');
}
/**
* Get query for assigned user tasks
*
* @access public
* @param integer $user_id User id
* @return \PicoDb\Table
*/
public function getUserQuery($user_id)
{
return $this->db
->table(Task::TABLE)
->columns(
'tasks.id',
'tasks.title',
'tasks.date_due',
'tasks.date_creation',
'tasks.project_id',
'tasks.color_id',
'tasks.time_spent',
'tasks.time_estimated',
'projects.name AS project_name'
)
->join(Project::TABLE, 'id', 'project_id')
->eq(Task::TABLE.'.owner_id', $user_id)
->eq(Task::TABLE.'.is_active', Task::STATUS_OPEN)
->eq(Project::TABLE.'.is_active', Project::ACTIVE);
}
/**
* Extended query
*
* @access public
* @return \PicoDb\Table
*/
public function getQuery()
public function getExtendedQuery()
{
return $this->db
->table(Task::TABLE)
@ -27,6 +84,7 @@ class TaskFinder extends Base
'(SELECT count(*) FROM task_has_files WHERE task_id=tasks.id) AS nb_files',
'(SELECT count(*) FROM task_has_subtasks WHERE task_id=tasks.id) AS nb_subtasks',
'(SELECT count(*) FROM task_has_subtasks WHERE task_id=tasks.id AND status=2) AS nb_completed_subtasks',
'(SELECT count(*) FROM ' . TaskLink::TABLE . ' WHERE ' . TaskLink::TABLE . '.task_id = tasks.id) AS nb_links',
'tasks.id',
'tasks.reference',
'tasks.title',
@ -45,6 +103,7 @@ class TaskFinder extends Base
'tasks.is_active',
'tasks.score',
'tasks.category_id',
'tasks.date_moved',
'users.username AS assignee_username',
'users.name AS assignee_name'
)
@ -62,7 +121,7 @@ class TaskFinder extends Base
*/
public function getTasksByColumnAndSwimlane($project_id, $column_id, $swimlane_id = 0)
{
return $this->getQuery()
return $this->getExtendedQuery()
->eq('project_id', $project_id)
->eq('column_id', $column_id)
->eq('swimlane_id', $swimlane_id)
@ -117,6 +176,18 @@ class TaskFinder extends Base
return $tasks;
}
/**
* Get project id for a given task
*
* @access public
* @param integer $task_id Task id
* @return integer
*/
public function getProjectId($task_id)
{
return (int) $this->db->table(Task::TABLE)->eq('id', $task_id)->findOneColumn('project_id') ?: 0;
}
/**
* Fetch a task by the id
*
@ -173,6 +244,7 @@ class TaskFinder extends Base
tasks.score,
tasks.category_id,
tasks.swimlane_id,
tasks.date_moved,
project_has_categories.name AS category_name,
projects.name AS project_name,
columns.title AS column_title,

View file

@ -0,0 +1,144 @@
<?php
namespace Model;
use SimpleValidator\Validator;
use SimpleValidator\Validators;
/**
* TaskLink model
*
* @package model
* @author Olivier Maridat
* @author Frederic Guillot
*/
class TaskLink extends Base
{
/**
* SQL table name
*
* @var string
*/
const TABLE = 'task_has_links';
/**
* Get a task link
*
* @access public
* @param integer $task_link_id Task link id
* @return array
*/
public function getById($task_link_id)
{
return $this->db->table(self::TABLE)->eq('id', $task_link_id)->findOne();
}
/**
* Get all links attached to a task
*
* @access public
* @param integer $task_id Task id
* @return array
*/
public function getLinks($task_id)
{
return $this->db
->table(self::TABLE)
->columns(
self::TABLE.'.id',
self::TABLE.'.opposite_task_id AS task_id',
Link::TABLE.'.label',
Task::TABLE.'.title',
Task::TABLE.'.is_active',
Task::TABLE.'.project_id',
Board::TABLE.'.title AS column_title'
)
->eq(self::TABLE.'.task_id', $task_id)
->join(Link::TABLE, 'id', 'link_id')
->join(Task::TABLE, 'id', 'opposite_task_id')
->join(Board::TABLE, 'id', 'column_id', Task::TABLE)
->findAll();
}
/**
* Create a new link
*
* @access public
* @param integer $task_id Task id
* @param integer $opposite_task_id Opposite task id
* @param integer $link_id Link id
* @return boolean
*/
public function create($task_id, $opposite_task_id, $link_id)
{
$this->db->startTransaction();
// Create the original link
$this->db->table(self::TABLE)->insert(array(
'task_id' => $task_id,
'opposite_task_id' => $opposite_task_id,
'link_id' => $link_id,
));
$link_id = $this->link->getOppositeLinkId($link_id);
// Create the opposite link
$this->db->table(self::TABLE)->insert(array(
'task_id' => $opposite_task_id,
'opposite_task_id' => $task_id,
'link_id' => $link_id,
));
$this->db->closeTransaction();
return true;
}
/**
* Remove a link between two tasks
*
* @access public
* @param integer $task_link_id
* @return boolean
*/
public function remove($task_link_id)
{
$this->db->startTransaction();
$link = $this->getById($task_link_id);
$link_id = $this->link->getOppositeLinkId($link['link_id']);
$this->db->table(self::TABLE)->eq('id', $task_link_id)->remove();
$this->db
->table(self::TABLE)
->eq('opposite_task_id', $link['task_id'])
->eq('task_id', $link['opposite_task_id'])
->eq('link_id', $link_id)->remove();
$this->db->closeTransaction();
return true;
}
/**
* Validate creation
*
* @access public
* @param array $values Form values
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
*/
public function validateCreation(array $values)
{
$v = new Validator($values, array(
new Validators\Required('task_id', t('Field required')),
new Validators\Required('link_id', t('Field required')),
new Validators\Required('title', t('Field required')),
));
return array(
$v->execute(),
$v->getErrors()
);
}
}

View file

@ -1,138 +0,0 @@
<?php
namespace Model;
/**
* Task Paginator model
*
* @package model
* @author Frederic Guillot
*/
class TaskPaginator extends Base
{
/**
* Task search with pagination
*
* @access public
* @param integer $project_id Project id
* @param string $search Search terms
* @param integer $offset Offset
* @param integer $limit Limit
* @param string $column Sorting column
* @param string $direction Sorting direction
* @return array
*/
public function searchTasks($project_id, $search, $offset = 0, $limit = 25, $column = 'tasks.id', $direction = 'DESC')
{
return $this->taskFinder->getQuery()
->eq('project_id', $project_id)
->ilike('title', '%'.$search.'%')
->offset($offset)
->limit($limit)
->orderBy($column, $direction)
->findAll();
}
/**
* Count the number of tasks for a custom search
*
* @access public
* @param integer $project_id Project id
* @param string $search Search terms
* @return integer
*/
public function countSearchTasks($project_id, $search)
{
return $this->db->table(Task::TABLE)
->eq('project_id', $project_id)
->ilike('title', '%'.$search.'%')
->count();
}
/**
* Get all completed tasks with pagination
*
* @access public
* @param integer $project_id Project id
* @param integer $offset Offset
* @param integer $limit Limit
* @param string $column Sorting column
* @param string $direction Sorting direction
* @return array
*/
public function closedTasks($project_id, $offset = 0, $limit = 25, $column = 'tasks.date_completed', $direction = 'DESC')
{
return $this->taskFinder->getQuery()
->eq('project_id', $project_id)
->eq('is_active', Task::STATUS_CLOSED)
->offset($offset)
->limit($limit)
->orderBy($column, $direction)
->findAll();
}
/**
* Count all closed tasks
*
* @access public
* @param integer $project_id Project id
* @return integer
*/
public function countClosedTasks($project_id)
{
return $this->db
->table(Task::TABLE)
->eq('project_id', $project_id)
->eq('is_active', Task::STATUS_CLOSED)
->count();
}
/**
* Get all open tasks for a given user
*
* @access public
* @param integer $user_id User id
* @param integer $offset Offset
* @param integer $limit Limit
* @param string $column Sorting column
* @param string $direction Sorting direction
* @return array
*/
public function userTasks($user_id, $offset = 0, $limit = 25, $column = 'tasks.id', $direction = 'ASC')
{
return $this->db
->table(Task::TABLE)
->columns(
'tasks.id',
'tasks.title',
'tasks.date_due',
'tasks.date_creation',
'tasks.project_id',
'tasks.color_id',
'projects.name AS project_name'
)
->join(Project::TABLE, 'id', 'project_id')
->eq('tasks.owner_id', $user_id)
->eq('tasks.is_active', Task::STATUS_OPEN)
->offset($offset)
->limit($limit)
->orderBy($column, $direction)
->findAll();
}
/**
* Count all tasks assigned to the user
*
* @access public
* @param integer $user_id User id
* @return integer
*/
public function countUserTasks($user_id)
{
return $this->db
->table(Task::TABLE)
->eq('owner_id', $user_id)
->eq('is_active', Task::STATUS_OPEN)
->count();
}
}

View file

@ -30,7 +30,7 @@ class TaskPosition extends Base
$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']);
}

View file

@ -12,6 +12,23 @@ use Event\TaskEvent;
*/
class TaskStatus extends Base
{
/**
* Return the list of statuses
*
* @access public
* @param boolean $prepend Prepend default value
* @return array
*/
public function getList($prepend = false)
{
$listing = $prepend ? array(-1 => t('All status')) : array();
return $listing + array(
Task::STATUS_OPEN => t('Open'),
Task::STATUS_CLOSED => t('Closed'),
);
}
/**
* Return true if the task is closed
*

View file

@ -1,45 +0,0 @@
<?php
namespace Model;
/**
* Time tracking model
*
* @package model
* @author Frederic Guillot
*/
class TimeTracking extends Base
{
/**
* Calculate time metrics for a task
*
* Use subtasks time metrics if not empty otherwise return task time metrics
*
* @access public
* @param array $task Task properties
* @param array $subtasks Subtasks list
* @return array
*/
public function getTaskTimesheet(array $task, array $subtasks)
{
$timesheet = array(
'time_spent' => 0,
'time_estimated' => 0,
'time_remaining' => 0,
);
foreach ($subtasks as &$subtask) {
$timesheet['time_estimated'] += $subtask['time_estimated'];
$timesheet['time_spent'] += $subtask['time_spent'];
}
if ($timesheet['time_estimated'] == 0 && $timesheet['time_spent'] == 0) {
$timesheet['time_estimated'] = $task['time_estimated'];
$timesheet['time_spent'] = $task['time_spent'];
}
$timesheet['time_remaining'] = $timesheet['time_estimated'] - $timesheet['time_spent'];
return $timesheet;
}
}

View file

@ -28,6 +28,42 @@ class User extends Base
*/
const EVERYBODY_ID = -1;
/**
* Return true if the user exists
*
* @access public
* @param integer $user_id User id
* @return boolean
*/
public function exists($user_id)
{
return $this->db->table(self::TABLE)->eq('id', $user_id)->count() === 1;
}
/**
* Get query to fetch all users
*
* @access public
* @return \PicoDb\Table
*/
public function getQuery()
{
return $this->db
->table(self::TABLE)
->columns(
'id',
'username',
'name',
'email',
'is_admin',
'default_project_id',
'is_ldap_user',
'notifications_enabled',
'google_id',
'github_id'
);
}
/**
* Return the full name
*
@ -112,54 +148,7 @@ class User extends Base
*/
public function getAll()
{
return $this->db
->table(self::TABLE)
->asc('username')
->columns(
'id',
'username',
'name',
'email',
'is_admin',
'default_project_id',
'is_ldap_user',
'notifications_enabled',
'google_id',
'github_id'
)
->findAll();
}
/**
* Get all users with pagination
*
* @access public
* @param integer $offset Offset
* @param integer $limit Limit
* @param string $column Sorting column
* @param string $direction Sorting direction
* @return array
*/
public function paginate($offset = 0, $limit = 25, $column = 'username', $direction = 'ASC')
{
return $this->db
->table(self::TABLE)
->columns(
'id',
'username',
'name',
'email',
'is_admin',
'default_project_id',
'is_ldap_user',
'notifications_enabled',
'google_id',
'github_id'
)
->offset($offset)
->limit($limit)
->orderBy($column, $direction)
->findAll();
return $this->getQuery()->asc('username')->findAll();
}
/**

View file

@ -4,8 +4,110 @@ namespace Schema;
use PDO;
use Core\Security;
use Model\Link;
const VERSION = 41;
const VERSION = 46;
function version_46($pdo)
{
$pdo->exec("CREATE TABLE links (
id INT NOT NULL AUTO_INCREMENT,
label VARCHAR(255) NOT NULL,
opposite_id INT DEFAULT 0,
PRIMARY KEY(id),
UNIQUE(label)
) ENGINE=InnoDB CHARSET=utf8");
$pdo->exec("CREATE TABLE task_has_links (
id INT NOT NULL AUTO_INCREMENT,
link_id INT NOT NULL,
task_id INT NOT NULL,
opposite_task_id INT NOT NULL,
FOREIGN KEY(link_id) REFERENCES links(id) ON DELETE CASCADE,
FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE,
FOREIGN KEY(opposite_task_id) REFERENCES tasks(id) ON DELETE CASCADE,
PRIMARY KEY(id)
) ENGINE=InnoDB CHARSET=utf8");
$pdo->exec("CREATE INDEX task_has_links_task_index ON task_has_links(task_id)");
$pdo->exec("CREATE UNIQUE INDEX task_has_links_unique ON task_has_links(link_id, task_id, opposite_task_id)");
$rq = $pdo->prepare('INSERT INTO links (label, opposite_id) VALUES (?, ?)');
$rq->execute(array('relates to', 0));
$rq->execute(array('blocks', 3));
$rq->execute(array('is blocked by', 2));
$rq->execute(array('duplicates', 5));
$rq->execute(array('is duplicated by', 4));
$rq->execute(array('is a child of', 7));
$rq->execute(array('is a parent of', 6));
$rq->execute(array('targets milestone', 9));
$rq->execute(array('is a milestone of', 8));
$rq->execute(array('fixes', 11));
$rq->execute(array('is fixed by', 10));
}
function version_45($pdo)
{
$pdo->exec('ALTER TABLE tasks ADD COLUMN date_moved INT DEFAULT 0');
/* Update tasks.date_moved from project_activities table if tasks.date_moved = null or 0.
* We take max project_activities.date_creation where event_name in task.create','task.move.column
* since creation date is always less than task moves
*/
$pdo->exec("UPDATE tasks
SET date_moved = (
SELECT md
FROM (
SELECT task_id, max(date_creation) md
FROM project_activities
WHERE event_name IN ('task.create', 'task.move.column')
GROUP BY task_id
) src
WHERE id = src.task_id
)
WHERE (date_moved IS NULL OR date_moved = 0) AND id IN (
SELECT task_id
FROM (
SELECT task_id, max(date_creation) md
FROM project_activities
WHERE event_name IN ('task.create', 'task.move.column')
GROUP BY task_id
) src
)");
// If there is no activities for some tasks use the date_creation
$pdo->exec("UPDATE tasks SET date_moved = date_creation WHERE date_moved IS NULL OR date_moved = 0");
}
function version_44($pdo)
{
$pdo->exec('ALTER TABLE users ADD COLUMN disable_login_form TINYINT(1) DEFAULT 0');
}
function version_43($pdo)
{
$rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)');
$rq->execute(array('subtask_restriction', '0'));
$rq->execute(array('subtask_time_tracking', '0'));
$pdo->exec("
CREATE TABLE subtask_time_tracking (
id INT NOT NULL AUTO_INCREMENT,
user_id INT NOT NULL,
subtask_id INT NOT NULL,
start INT DEFAULT 0,
end INT DEFAULT 0,
PRIMARY KEY(id),
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY(subtask_id) REFERENCES task_has_subtasks(id) ON DELETE CASCADE
) ENGINE=InnoDB CHARSET=utf8
");
}
function version_42($pdo)
{
$pdo->exec('ALTER TABLE columns ADD COLUMN description TEXT');
}
function version_41($pdo)
{
@ -59,7 +161,7 @@ function version_38($pdo)
");
$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 default_swimlane VARCHAR(200) DEFAULT 'Default swimlane'");
$pdo->exec("ALTER TABLE projects ADD COLUMN show_default_swimlane INT DEFAULT 1");
}

View file

@ -4,8 +4,107 @@ namespace Schema;
use PDO;
use Core\Security;
use Model\Link;
const VERSION = 22;
const VERSION = 27;
function version_27($pdo)
{
$pdo->exec('CREATE TABLE links (
"id" SERIAL PRIMARY KEY,
"label" VARCHAR(255) NOT NULL,
"opposite_id" INTEGER DEFAULT 0,
UNIQUE("label")
)');
$pdo->exec("CREATE TABLE task_has_links (
id SERIAL PRIMARY KEY,
link_id INTEGER NOT NULL,
task_id INTEGER NOT NULL,
opposite_task_id INTEGER NOT NULL,
FOREIGN KEY(link_id) REFERENCES links(id) ON DELETE CASCADE,
FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE,
FOREIGN KEY(opposite_task_id) REFERENCES tasks(id) ON DELETE CASCADE
)");
$pdo->exec("CREATE INDEX task_has_links_task_index ON task_has_links(task_id)");
$pdo->exec("CREATE UNIQUE INDEX task_has_links_unique ON task_has_links(link_id, task_id, opposite_task_id)");
$rq = $pdo->prepare('INSERT INTO links (label, opposite_id) VALUES (?, ?)');
$rq->execute(array('relates to', 0));
$rq->execute(array('blocks', 3));
$rq->execute(array('is blocked by', 2));
$rq->execute(array('duplicates', 5));
$rq->execute(array('is duplicated by', 4));
$rq->execute(array('is a child of', 7));
$rq->execute(array('is a parent of', 6));
$rq->execute(array('targets milestone', 9));
$rq->execute(array('is a milestone of', 8));
$rq->execute(array('fixes', 11));
$rq->execute(array('is fixed by', 10));
}
function version_26($pdo)
{
$pdo->exec('ALTER TABLE tasks ADD COLUMN date_moved INT DEFAULT 0');
/* Update tasks.date_moved from project_activities table if tasks.date_moved = null or 0.
* We take max project_activities.date_creation where event_name in task.create','task.move.column
* since creation date is always less than task moves
*/
$pdo->exec("UPDATE tasks
SET date_moved = (
SELECT md
FROM (
SELECT task_id, max(date_creation) md
FROM project_activities
WHERE event_name IN ('task.create', 'task.move.column')
GROUP BY task_id
) src
WHERE id = src.task_id
)
WHERE (date_moved IS NULL OR date_moved = 0) AND id IN (
SELECT task_id
FROM (
SELECT task_id, max(date_creation) md
FROM project_activities
WHERE event_name IN ('task.create', 'task.move.column')
GROUP BY task_id
) src
)");
// If there is no activities for some tasks use the date_creation
$pdo->exec("UPDATE tasks SET date_moved = date_creation WHERE date_moved IS NULL OR date_moved = 0");
}
function version_25($pdo)
{
$pdo->exec("ALTER TABLE users ADD COLUMN disable_login_form BOOLEAN DEFAULT '0'");
}
function version_24($pdo)
{
$rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)');
$rq->execute(array('subtask_restriction', '0'));
$rq->execute(array('subtask_time_tracking', '0'));
$pdo->exec('
CREATE TABLE subtask_time_tracking (
id SERIAL PRIMARY KEY,
"user_id" INTEGER NOT NULL,
"subtask_id" INTEGER NOT NULL,
"start" INTEGER DEFAULT 0,
"end" INTEGER DEFAULT 0,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY(subtask_id) REFERENCES task_has_subtasks(id) ON DELETE CASCADE
)
');
}
function version_23($pdo)
{
$pdo->exec('ALTER TABLE columns ADD COLUMN description TEXT');
}
function version_22($pdo)
{
@ -30,7 +129,7 @@ function version_21($pdo)
$rq->execute();
$project_ids = $rq->fetchAll(PDO::FETCH_COLUMN, 0);
$rq = $pdo->prepare('UPDATE project_has_users SET is_owner=1 WHERE project_id=?');
$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));
@ -58,7 +157,7 @@ function version_19($pdo)
");
$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 default_swimlane VARCHAR(200) DEFAULT 'Default swimlane'");
$pdo->exec("ALTER TABLE projects ADD COLUMN show_default_swimlane BOOLEAN DEFAULT '1'");
}

View file

@ -4,8 +4,107 @@ namespace Schema;
use Core\Security;
use PDO;
use Model\Link;
const VERSION = 40;
const VERSION = 45;
function version_45($pdo)
{
$pdo->exec("CREATE TABLE links (
id INTEGER PRIMARY KEY,
label TEXT NOT NULL,
opposite_id INTEGER DEFAULT 0,
UNIQUE(label)
)");
$pdo->exec("CREATE TABLE task_has_links (
id INTEGER PRIMARY KEY,
link_id INTEGER NOT NULL,
task_id INTEGER NOT NULL,
opposite_task_id INTEGER NOT NULL,
FOREIGN KEY(link_id) REFERENCES links(id) ON DELETE CASCADE,
FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE,
FOREIGN KEY(opposite_task_id) REFERENCES tasks(id) ON DELETE CASCADE
)");
$pdo->exec("CREATE INDEX task_has_links_task_index ON task_has_links(task_id)");
$pdo->exec("CREATE UNIQUE INDEX task_has_links_unique ON task_has_links(link_id, task_id, opposite_task_id)");
$rq = $pdo->prepare('INSERT INTO links (label, opposite_id) VALUES (?, ?)');
$rq->execute(array('relates to', 0));
$rq->execute(array('blocks', 3));
$rq->execute(array('is blocked by', 2));
$rq->execute(array('duplicates', 5));
$rq->execute(array('is duplicated by', 4));
$rq->execute(array('is a child of', 7));
$rq->execute(array('is a parent of', 6));
$rq->execute(array('targets milestone', 9));
$rq->execute(array('is a milestone of', 8));
$rq->execute(array('fixes', 11));
$rq->execute(array('is fixed by', 10));
}
function version_44($pdo)
{
$pdo->exec('ALTER TABLE tasks ADD COLUMN date_moved INTEGER DEFAULT 0');
/* Update tasks.date_moved from project_activities table if tasks.date_moved = null or 0.
* We take max project_activities.date_creation where event_name in task.create','task.move.column
* since creation date is always less than task moves
*/
$pdo->exec("UPDATE tasks
SET date_moved = (
SELECT md
FROM (
SELECT task_id, max(date_creation) md
FROM project_activities
WHERE event_name IN ('task.create', 'task.move.column')
GROUP BY task_id
) src
WHERE id = src.task_id
)
WHERE (date_moved IS NULL OR date_moved = 0) AND id IN (
SELECT task_id
FROM (
SELECT task_id, max(date_creation) md
FROM project_activities
WHERE event_name IN ('task.create', 'task.move.column')
GROUP BY task_id
) src
)");
// If there is no activities for some tasks use the date_creation
$pdo->exec("UPDATE tasks SET date_moved = date_creation WHERE date_moved IS NULL OR date_moved = 0");
}
function version_43($pdo)
{
$pdo->exec('ALTER TABLE users ADD COLUMN disable_login_form INTEGER DEFAULT 0');
}
function version_42($pdo)
{
$rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)');
$rq->execute(array('subtask_restriction', '0'));
$rq->execute(array('subtask_time_tracking', '0'));
$pdo->exec("
CREATE TABLE subtask_time_tracking (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL,
subtask_id INTEGER NOT NULL,
start INTEGER DEFAULT 0,
end INTEGER DEFAULT 0,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY(subtask_id) REFERENCES task_has_subtasks(id) ON DELETE CASCADE
)
");
}
function version_41($pdo)
{
$pdo->exec('ALTER TABLE columns ADD COLUMN description TEXT');
}
function version_40($pdo)
{
@ -58,7 +157,7 @@ function version_37($pdo)
");
$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 default_swimlane TEXT DEFAULT 'Default swimlane'");
$pdo->exec("ALTER TABLE projects ADD COLUMN show_default_swimlane INTEGER DEFAULT 1");
}

View file

@ -2,6 +2,7 @@
namespace ServiceProvider;
use Core\Paginator;
use Model\Config;
use Model\Project;
use Model\Webhook;
@ -23,24 +24,26 @@ class ClassProvider implements ServiceProviderInterface
'DateParser',
'File',
'LastLogin',
'Link',
'Notification',
'Project',
'ProjectActivity',
'ProjectAnalytic',
'ProjectDuplication',
'ProjectDailySummary',
'ProjectPaginator',
'ProjectPermission',
'SubTask',
'SubtaskPaginator',
'Subtask',
'SubtaskExport',
'SubtaskTimeTracking',
'Swimlane',
'Task',
'TaskCreation',
'TaskDuplication',
'TaskExport',
'TaskFinder',
'TaskFilter',
'TaskLink',
'TaskModification',
'TaskPaginator',
'TaskPermission',
'TaskPosition',
'TaskStatus',
@ -51,14 +54,17 @@ class ClassProvider implements ServiceProviderInterface
'Webhook',
),
'Core' => array(
'Helper',
'Template',
'Session',
'MemoryCache',
'FileCache',
'Request',
),
'Integration' => array(
'GitlabWebhook',
'GithubWebhook',
'BitbucketWebhook',
)
);
@ -75,5 +81,9 @@ class ClassProvider implements ServiceProviderInterface
};
}
}
$container['paginator'] = $container->factory(function ($c) {
return new Paginator($c);
});
}
}

View file

@ -12,6 +12,8 @@ use Subscriber\ProjectActivitySubscriber;
use Subscriber\ProjectDailySummarySubscriber;
use Subscriber\ProjectModificationDateSubscriber;
use Subscriber\WebhookSubscriber;
use Subscriber\SubtaskTimesheetSubscriber;
use Subscriber\TaskMovedDateSubscriber;
class EventDispatcherProvider implements ServiceProviderInterface
{
@ -25,6 +27,8 @@ class EventDispatcherProvider implements ServiceProviderInterface
$container['dispatcher']->addSubscriber(new ProjectModificationDateSubscriber($container));
$container['dispatcher']->addSubscriber(new WebhookSubscriber($container));
$container['dispatcher']->addSubscriber(new NotificationSubscriber($container));
$container['dispatcher']->addSubscriber(new SubtaskTimesheetSubscriber($container));
$container['dispatcher']->addSubscriber(new TaskMovedDateSubscriber($container));
// Automatic actions
$container['action']->attachEvents();

View file

@ -16,7 +16,7 @@ class LoggingProvider implements ServiceProviderInterface
$logger->setLogger(new Syslog('kanboard'));
if (DEBUG) {
$logger->setLogger(new File(__DIR__.'/../../data/debug.log'));
$logger->setLogger(new File(DEBUG_FILE));
}
$container['logger'] = $logger;

View file

@ -10,16 +10,23 @@ use Pimple\Container;
* @package subscriber
* @author Frederic Guillot
*
* @property \Model\Board $board
* @property \Model\Config $config
* @property \Model\Comment $comment
* @property \Model\LastLogin $lastLogin
* @property \Model\Notification $notification
* @property \Model\Project $project
* @property \Model\ProjectPermission $projectPermission
* @property \Model\ProjectActivity $projectActivity
* @property \Model\ProjectAnalytic $projectAnalytic
* @property \Model\ProjectDailySummary $projectDailySummary
* @property \Model\Subtask $subtask
* @property \Model\Task $task
* @property \Model\TaskExport $taskExport
* @property \Model\TaskFinder $taskFinder
* @property \Model\SubtaskTimeTracking $subtaskTimeTracking
* @property \Model\UserSession $userSession
* @property \Model\Webhook $webhook
*/
abstract class Base
{

View file

@ -5,7 +5,7 @@ namespace Subscriber;
use Event\GenericEvent;
use Model\Task;
use Model\Comment;
use Model\SubTask;
use Model\Subtask;
use Model\File;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
@ -19,8 +19,8 @@ class NotificationSubscriber extends Base implements EventSubscriberInterface
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',
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',
@ -36,8 +36,8 @@ class NotificationSubscriber extends Base implements EventSubscriberInterface
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),
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),
@ -63,7 +63,7 @@ class NotificationSubscriber extends Base implements EventSubscriberInterface
$values['task'] = $this->taskFinder->getDetails($event['task_id']);
break;
case 'Event\SubtaskEvent':
$values['subtask'] = $this->subTask->getById($event['id'], true);
$values['subtask'] = $this->subtask->getById($event['id'], true);
$values['task'] = $this->taskFinder->getDetails($event['task_id']);
break;
case 'Event\FileEvent':

View file

@ -5,7 +5,7 @@ namespace Subscriber;
use Event\GenericEvent;
use Model\Task;
use Model\Comment;
use Model\SubTask;
use Model\Subtask;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class ProjectActivitySubscriber extends Base implements EventSubscriberInterface
@ -22,8 +22,8 @@ class ProjectActivitySubscriber extends Base implements EventSubscriberInterface
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),
Subtask::EVENT_UPDATE => array('execute', 0),
Subtask::EVENT_CREATE => array('execute', 0),
);
}
@ -51,7 +51,7 @@ class ProjectActivitySubscriber extends Base implements EventSubscriberInterface
switch (get_class($event)) {
case 'Event\SubtaskEvent':
$values['subtask'] = $this->subTask->getById($event['id'], true);
$values['subtask'] = $this->subtask->getById($event['id'], true);
break;
case 'Event\CommentEvent':
$values['comment'] = $this->comment->getById($event['id']);

View file

@ -0,0 +1,47 @@
<?php
namespace Subscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Model\Subtask;
use Event\SubtaskEvent;
class SubtaskTimesheetSubscriber extends Base implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(
Subtask::EVENT_CREATE => array('updateTaskTime', 0),
Subtask::EVENT_UPDATE => array(
array('logStartEnd', 10),
array('updateTaskTime', 0),
)
);
}
public function updateTaskTime(SubtaskEvent $event)
{
if (isset($event['task_id'])) {
$this->subtaskTimeTracking->updateTaskTimeTracking($event['task_id']);
}
}
public function logStartEnd(SubtaskEvent $event)
{
if ($this->config->get('subtask_time_tracking') == 1 && isset($event['status'])) {
$subtask = $this->subtask->getById($event['id']);
if (empty($subtask['user_id'])) {
return false;
}
if ($subtask['status'] == Subtask::STATUS_INPROGRESS) {
return $this->subtaskTimeTracking->logStartTime($subtask['id'], $subtask['user_id']);
}
else {
return $this->subtaskTimeTracking->logEndTime($subtask['id'], $subtask['user_id']);
}
}
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace Subscriber;
use Event\TaskEvent;
use Model\Task;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class TaskMovedDateSubscriber extends Base implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return array(
Task::EVENT_MOVE_COLUMN => array('execute', 0),
);
}
public function execute(TaskEvent $event)
{
if (isset($event['task_id'])) {
$this->container['db']->table(Task::TABLE)->eq('id', $event['task_id'])->update(array('date_moved' => time()));
}
}
}

View file

@ -1,5 +1,5 @@
<?= $this->js('assets/js/d3.v3.4.8.min.js') ?>
<?= $this->js('assets/js/dimple.v2.1.0.min.js') ?>
<?= $this->js('assets/js/vendor/d3.v3.4.8.min.js') ?>
<?= $this->js('assets/js/vendor/dimple.v2.1.0.min.js') ?>
<section id="main">
<div class="page-header">

View file

@ -1,5 +1,5 @@
<section id="main">
<div class="page-header">
<div class="page-header page-header-mobile">
<ul>
<?php if ($this->userSession->isAdmin()): ?>
<li><i class="fa fa-plus fa-fw"></i><?= $this->a(t('New project'), 'project', 'create') ?></li>
@ -10,17 +10,51 @@
<li><i class="fa fa-user fa-fw"></i><?= $this->a(t('User management'), 'user', 'index') ?></li>
<li><i class="fa fa-cog fa-fw"></i><?= $this->a(t('Settings'), 'config', 'index') ?></li>
<?php endif ?>
<li>
<ul class="dropdown">
<li>
<i class="fa fa-caret-down"></i> <a href="#" class="dropdown-menu"><?= t('Change dashboard view') ?></a>
<ul>
<li>
<a href="#" class="dashboard-toggle" data-toggle="projects"><?= t('Show/hide projects') ?></a>
</li>
<li>
<a href="#" class="dashboard-toggle" data-toggle="tasks"><?= t('Show/hide tasks') ?></a>
</li>
<li>
<a href="#" class="dashboard-toggle" data-toggle="subtasks"><?= t('Show/hide subtasks') ?></a>
</li>
<li>
<a href="#" class="dashboard-toggle" data-toggle="calendar"><?= t('Show/hide calendar') ?></a>
</li>
<li>
<a href="#" class="dashboard-toggle" data-toggle="activities"><?= t('Show/hide activities') ?></a>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
<section id="dashboard">
<div class="dashboard-left-column">
<?= $this->render('app/projects', array('projects' => $projects, 'pagination' => $project_pagination)) ?>
<?= $this->render('app/tasks', array('tasks' => $tasks, 'pagination' => $task_pagination)) ?>
<?= $this->render('app/subtasks', array('subtasks' => $subtasks, 'pagination' => $subtask_pagination)) ?>
<div id="dashboard-projects"><?= $this->render('app/projects', array('paginator' => $project_paginator)) ?></div>
<div id="dashboard-tasks"><?= $this->render('app/tasks', array('paginator' => $task_paginator)) ?></div>
<div id="dashboard-subtasks"><?= $this->render('app/subtasks', array('paginator' => $subtask_paginator)) ?></div>
</div>
<div class="dashboard-right-column">
<h2><?= t('Activity stream') ?></h2>
<?= $this->render('project/events', array('events' => $events)) ?>
<div id="dashboard-calendar">
<div id="user-calendar"
data-check-url="<?= $this->u('calendar', 'user') ?>"
data-user-id="<?= $user_id ?>"
data-save-url="<?= $this->u('calendar', 'save') ?>"
>
</div>
</div>
<div id="dashboard-activities">
<h2><?= t('Activity stream') ?></h2>
<?= $this->render('project/events', array('events' => $events)) ?>
</div>
</div>
</section>
</section>

View file

@ -1,14 +1,14 @@
<h2><?= t('My projects') ?></h2>
<?php if (empty($projects)): ?>
<?php if ($paginator->isEmpty()): ?>
<p class="alert"><?= t('Your are not member of any project.') ?></p>
<?php else: ?>
<table class="table-fixed">
<tr>
<th class="column-8"><?= $this->order('Id', 'id', $pagination) ?></th>
<th class="column-20"><?= $this->order(t('Project'), 'name', $pagination) ?></th>
<th class="column-8"><?= $paginator->order('Id', 'id') ?></th>
<th class="column-20"><?= $paginator->order(t('Project'), 'name') ?></th>
<th><?= t('Columns') ?></th>
</tr>
<?php foreach ($projects as $project): ?>
<?php foreach ($paginator->getCollection() as $project): ?>
<tr>
<td>
<?= $this->a('#'.$project['id'], 'board', 'show', array('project_id' => $project['id']), false, 'dashboard-table-link') ?>
@ -17,6 +17,8 @@
<?php if ($this->isManager($project['id'])): ?>
<?= $this->a('<i class="fa fa-cog"></i>', 'project', 'show', array('project_id' => $project['id']), false, 'dashboard-table-link', t('Settings')) ?>&nbsp;
<?php endif ?>
<?= $this->a('<i class="fa fa-calendar"></i>', 'calendar', 'show', array('project_id' => $project['id']), false, 'dashboard-table-link', t('Calendar')) ?>&nbsp;
<?= $this->a($this->e($project['name']), 'board', 'show', array('project_id' => $project['id'])) ?>
</td>
<td class="dashboard-project-stats">
@ -29,5 +31,5 @@
<?php endforeach ?>
</table>
<?= $this->paginate($pagination) ?>
<?= $paginator ?>
<?php endif ?>

View file

@ -1,31 +1,37 @@
<h2><?= t('My subtasks') ?></h2>
<?php if (empty($subtasks)): ?>
<?php if ($paginator->isEmpty()): ?>
<p class="alert"><?= t('There is nothing assigned to you.') ?></p>
<?php else: ?>
<table class="table-fixed">
<tr>
<th class="column-10"><?= $this->order('Id', 'tasks.id', $pagination) ?></th>
<th class="column-20"><?= $this->order(t('Project'), 'project_name', $pagination) ?></th>
<th class="column-15"><?= $this->order(t('Status'), 'status', $pagination) ?></th>
<th><?= $this->order(t('Subtask'), 'title', $pagination) ?></th>
<th class="column-10"><?= $paginator->order('Id', 'tasks.id') ?></th>
<th class="column-20"><?= $paginator->order(t('Project'), 'project_name') ?></th>
<th><?= $paginator->order(t('Subtask'), 'title') ?></th>
<th class="column-20"><?= t('Time tracking') ?></th>
</tr>
<?php foreach ($subtasks as $subtask): ?>
<?php foreach ($paginator->getCollection() as $subtask): ?>
<tr>
<td class="task-table task-<?= $subtask['color_id'] ?>">
<td class="task-table color-<?= $subtask['color_id'] ?>">
<?= $this->a('#'.$subtask['task_id'], 'task', 'show', array('task_id' => $subtask['task_id'], 'project_id' => $subtask['project_id'])) ?>
</td>
<td>
<?= $this->a($this->e($subtask['project_name']), 'board', 'show', array('project_id' => $subtask['project_id'])) ?>
</td>
<td>
<?= $this->e($subtask['status_name']) ?>
<?= $this->toggleSubtaskStatus($subtask, 'dashboard') ?>
</td>
<td>
<?= $this->a($this->e($subtask['title']), 'task', 'show', array('task_id' => $subtask['task_id'], 'project_id' => $subtask['project_id'])) ?>
<?php if (! empty($subtask['time_spent'])): ?>
<strong><?= $this->e($subtask['time_spent']).'h' ?></strong> <?= t('spent') ?>
<?php endif ?>
<?php if (! empty($subtask['time_estimated'])): ?>
<strong><?= $this->e($subtask['time_estimated']).'h' ?></strong> <?= t('estimated') ?>
<?php endif ?>
</td>
</tr>
<?php endforeach ?>
</table>
<?= $this->paginate($pagination) ?>
<?= $paginator ?>
<?php endif ?>

View file

@ -1,17 +1,18 @@
<h2><?= t('My tasks') ?></h2>
<?php if (empty($tasks)): ?>
<?php if ($paginator->isEmpty()): ?>
<p class="alert"><?= t('There is nothing assigned to you.') ?></p>
<?php else: ?>
<table class="table-fixed">
<tr>
<th class="column-8"><?= $this->order('Id', 'tasks.id', $pagination) ?></th>
<th class="column-20"><?= $this->order(t('Project'), 'project_name', $pagination) ?></th>
<th><?= $this->order(t('Task'), 'title', $pagination) ?></th>
<th class="column-20"><?= $this->order(t('Due date'), 'date_due', $pagination) ?></th>
<th class="column-8"><?= $paginator->order('Id', 'tasks.id') ?></th>
<th class="column-20"><?= $paginator->order(t('Project'), 'project_name') ?></th>
<th><?= $paginator->order(t('Task'), 'title') ?></th>
<th class="column-20"><?= t('Time tracking') ?></th>
<th class="column-20"><?= $paginator->order(t('Due date'), 'date_due') ?></th>
</tr>
<?php foreach ($tasks as $task): ?>
<?php foreach ($paginator->getCollection() as $task): ?>
<tr>
<td class="task-table task-<?= $task['color_id'] ?>">
<td class="task-table color-<?= $task['color_id'] ?>">
<?= $this->a('#'.$task['id'], 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
</td>
<td>
@ -20,6 +21,15 @@
<td>
<?= $this->a($this->e($task['title']), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
</td>
<td>
<?php if (! empty($task['time_spent'])): ?>
<strong><?= $this->e($task['time_spent']).'h' ?></strong> <?= t('spent') ?>
<?php endif ?>
<?php if (! empty($task['time_estimated'])): ?>
<strong><?= $this->e($task['time_estimated']).'h' ?></strong> <?= t('estimated') ?>
<?php endif ?>
</td>
<td>
<?= dt('%B %e, %Y', $task['date_due']) ?>
</td>
@ -27,5 +37,5 @@
<?php endforeach ?>
</table>
<?= $this->paginate($pagination) ?>
<?= $paginator ?>
<?php endif ?>

View file

@ -14,7 +14,7 @@
<div class="form-actions">
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
<?= t('or') ?>
<?= $this->a(t('cancel'), 'board', 'show', array('project_id' => $project['id'])) ?>
<?= $this->a(t('cancel'), 'board', 'show', array('project_id' => $project['id']), false, 'close-popover') ?>
</div>
</form>
</section>

View file

@ -14,7 +14,7 @@
<div class="form-actions">
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
<?= t('or') ?>
<?= $this->a(t('cancel'), 'board', 'show', array('project_id' => $project['id'])) ?>
<?= $this->a(t('cancel'), 'board', 'show', array('project_id' => $project['id']), false, 'close-popover') ?>
</div>
</form>
</section>

View file

@ -1,50 +1,48 @@
<div class="page-header">
<h2><?= t('Edit the board for "%s"', $project['name']) ?></h2>
</div>
<section>
<h3><?= t('Change columns') ?></h3>
<form method="post" action="<?= $this->u('board', 'update', array('project_id' => $project['id'])) ?>" autocomplete="off">
<?= $this->formCsrf() ?>
<?php $i = 0; ?>
<table>
<tr>
<th><?= t('Position') ?></th>
<th><?= t('Column title') ?></th>
<th><?= t('Task limit') ?></th>
<th><?= t('Actions') ?></th>
</tr>
<?php foreach ($columns as $column): ?>
<tr>
<td><?= $this->formLabel('#'.++$i, 'title['.$column['id'].']', array('title="column_id='.$column['id'].'"')) ?></td>
<td><?= $this->formText('title['.$column['id'].']', $values, $errors, array('required')) ?></td>
<td><?= $this->formNumber('task_limit['.$column['id'].']', $values, $errors, array('placeholder="'.t('limit').'"')) ?></td>
<td>
<ul>
<?php if ($column['position'] != 1): ?>
<li>
<?= $this->a(t('Move Up'), 'board', 'moveColumn', array('project_id' => $project['id'], 'column_id' => $column['id'], 'direction' => 'up'), true) ?>
</li>
<?php endif ?>
<?php if ($column['position'] != count($columns)): ?>
<li>
<?= $this->a(t('Move Down'), 'board', 'moveColumn', array('project_id' => $project['id'], 'column_id' => $column['id'], 'direction' => 'down'), true) ?>
</li>
<?php endif ?>
<li>
<?= $this->a(t('Remove'), 'board', 'remove', array('project_id' => $project['id'], 'column_id' => $column['id'])) ?>
</li>
</ul>
</td>
</tr>
<?php endforeach ?>
</table>
<table>
<tr>
<th><?= t('Column title') ?></th>
<th><?= t('Task limit') ?></th>
<th><?= t('Actions') ?></th>
</tr>
<?php foreach ($columns as $column): ?>
<tr>
<td class="column-60"><?= $this->e($column['title']) ?>
<?php if (! empty($column['description'])): ?>
<span class="column-tooltip" title="<?= $this->markdown($column['description']) ?>">
<i class="fa fa-info-circle"></i>
</span>
<?php endif ?>
</td>
<td class="column-10"><?= $this->e($column['task_limit']) ?></td>
<td class="column-30">
<ul>
<li>
<?= $this->a(t('Edit'), 'board', 'editColumn', array('project_id' => $project['id'], 'column_id' => $column['id'])) ?>
</li>
<?php if ($column['position'] != 1): ?>
<li>
<?= $this->a(t('Move Up'), 'board', 'moveColumn', array('project_id' => $project['id'], 'column_id' => $column['id'], 'direction' => 'up'), true) ?>
</li>
<?php endif ?>
<?php if ($column['position'] != count($columns)): ?>
<li>
<?= $this->a(t('Move Down'), 'board', 'moveColumn', array('project_id' => $project['id'], 'column_id' => $column['id'], 'direction' => 'down'), true) ?>
</li>
<?php endif ?>
<li>
<?= $this->a(t('Remove'), 'board', 'remove', array('project_id' => $project['id'], 'column_id' => $column['id'])) ?>
</li>
</ul>
</td>
</tr>
<?php endforeach ?>
</table>
<div class="form-actions">
<input type="submit" value="<?= t('Update') ?>" class="btn btn-blue"/>
</div>
</form>
<hr/>
<h3><?= t('Add a new column') ?></h3>
<form method="post" action="<?= $this->u('board', 'add', array('project_id' => $project['id'])) ?>" autocomplete="off">
@ -53,7 +51,30 @@
<?= $this->formHidden('project_id', $values) ?>
<?= $this->formLabel(t('Title'), 'title') ?>
<?= $this->formText('title', $values, $errors, array('required')) ?>
<?= $this->formText('title', $values, $errors, array('required', 'maxlength="50"')) ?>
<?= $this->formLabel(t('Task limit'), 'task_limit') ?>
<?= $this->formNumber('task_limit', $values, $errors) ?>
<?= $this->formLabel(t('Description'), 'description') ?>
<div class="form-tabs">
<div class="write-area">
<?= $this->formTextarea('description', $values, $errors) ?>
</div>
<div class="preview-area">
<div class="markdown"></div>
</div>
<ul class="form-tabs-nav">
<li class="form-tab form-tab-selected">
<i class="fa fa-pencil-square-o fa-fw"></i><a id="markdown-write" href="#"><?= t('Write') ?></a>
</li>
<li class="form-tab">
<a id="markdown-preview" href="#"><i class="fa fa-eye fa-fw"></i><?= t('Preview') ?></a>
</li>
</ul>
</div>
<div class="form-help"><a href="http://kanboard.net/documentation/syntax-guide" target="_blank" rel="noreferrer"><?= t('Write your text in Markdown') ?></a></div>
<div class="form-actions">
<input type="submit" value="<?= t('Add this column') ?>" class="btn btn-blue"/>

View file

@ -0,0 +1,42 @@
<div class="page-header">
<h2><?= t('Edit column "%s"', $column['title']) ?></h2>
</div>
<form method="post" action="<?= $this->u('board', 'updateColumn', array('project_id' => $project['id'], 'column_id' => $column['id'])) ?>" autocomplete="off">
<?= $this->formCsrf() ?>
<?= $this->formHidden('id', $values) ?>
<?= $this->formHidden('project_id', $values) ?>
<?= $this->formLabel(t('Title'), 'title') ?>
<?= $this->formText('title', $values, $errors, array('autofocus', 'required', 'maxlength="50"')) ?>
<?= $this->formLabel(t('Task limit'), 'task_limit') ?>
<?= $this->formNumber('task_limit', $values, $errors) ?>
<?= $this->formLabel(t('Description'), 'description') ?>
<div class="form-tabs">
<div class="write-area">
<?= $this->formTextarea('description', $values, $errors) ?>
</div>
<div class="preview-area">
<div class="markdown"></div>
</div>
<ul class="form-tabs-nav">
<li class="form-tab form-tab-selected">
<i class="fa fa-pencil-square-o fa-fw"></i><a id="markdown-write" href="#"><?= t('Write') ?></a>
</li>
<li class="form-tab">
<a id="markdown-preview" href="#"><i class="fa fa-eye fa-fw"></i><?= t('Preview') ?></a>
</li>
</ul>
</div>
<div class="form-help"><a href="http://kanboard.net/documentation/syntax-guide" target="_blank" rel="noreferrer"><?= t('Write your text in Markdown') ?></a></div>
<div class="form-actions">
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
</div>
</form>

View file

@ -6,7 +6,7 @@
$this->e($file['name']),
'file',
'download',
array('file_id' => $file['id'], 'task_id' => $file['task_id'])
array('file_id' => $file['id'], 'task_id' => $file['task_id'], 'project_id' => $task['project_id'])
) ?>
<br/>

View file

@ -1,35 +1,65 @@
<div class="page-header">
<ul class="board-filters">
<li class="hide-tablet">
<?= t('Filter by user') ?>
<?= $this->formSelect('user_id', $users) ?>
</li>
<li class="hide-tablet">
<?= t('Filter by category') ?>
<?= $this->formSelect('category_id', $categories) ?>
</li>
<li class="hide-tablet">
<a href="#" id="filter-due-date"><?= t('Filter by due date') ?></a>
<li>
<ul class="dropdown">
<li>
<i class="fa fa-caret-down"></i> <a href="#" class="dropdown-menu"><?= t('Actions') ?></a>
<ul>
<li>
<span class="filter-collapse">
<i class="fa fa-compress fa-fw"></i> <a href="#" class="filter-collapse-link"><?= t('Collapse tasks') ?></a>
</span>
<span class="filter-expand" style="display: none">
<i class="fa fa-expand fa-fw"></i> <a href="#" class="filter-expand-link"><?= t('Expand tasks') ?></a>
</span>
</li>
<li>
<i class="fa fa-search fa-fw"></i>
<?= $this->a(t('Search'), 'project', 'search', array('project_id' => $project['id'])) ?>
</li>
<li>
<i class="fa fa-check-square-o fa-fw"></i>
<?= $this->a(t('Completed tasks'), 'project', 'tasks', array('project_id' => $project['id'])) ?>
</li>
<li>
<i class="fa fa-dashboard fa-fw"></i>
<?= $this->a(t('Activity'), 'project', 'activity', array('project_id' => $project['id'])) ?>
</li>
<li>
<i class="fa fa-calendar fa-fw"></i>
<?= $this->a(t('Calendar'), 'calendar', 'show', array('project_id' => $project['id'])) ?>
</li>
<?php if ($project['is_public']): ?>
<li>
<i class="fa fa-share-alt fa-fw"></i> <?= $this->a(t('Public link'), 'board', 'readonly', array('token' => $project['token']), false, '', '', true) ?>
</li>
<?php endif ?>
<?php if ($this->acl->isManagerActionAllowed($project['id'])): ?>
<li>
<i class="fa fa-line-chart fa-fw"></i>
<?= $this->a(t('Analytics'), 'analytic', 'tasks', array('project_id' => $project['id'])) ?>
</li>
<li>
<i class="fa fa-cog fa-fw"></i>
<?= $this->a(t('Configure'), 'project', 'show', array('project_id' => $project['id'])) ?>
</li>
<?php endif ?>
</ul>
</li>
</ul>
</li>
<li>
<i class="fa fa-search"></i>
<?= $this->a(t('Search'), 'project', 'search', array('project_id' => $project['id'])) ?>
<?= $this->formSelect('user_id', $users, array(), array(), array('data-placeholder="'.t('Filter by user').'"'), 'apply-filters chosen-select') ?>
</li>
<li>
<i class="fa fa-check-square-o fa-fw"></i>
<?= $this->a(t('Completed tasks'), 'project', 'tasks', array('project_id' => $project['id'])) ?>
<?= $this->formSelect('category_id', $categories, array(), array(), array('data-placeholder="'.t('Filter by category').'"'), 'apply-filters chosen-select') ?>
</li>
<li>
<i class="fa fa-dashboard fa-fw"></i>
<?= $this->a(t('Activity'), 'project', 'activity', array('project_id' => $project['id'])) ?>
<select id="more-filters" multiple data-placeholder="<?= t('More filters') ?>" class="apply-filters chosen-select hide-mobile">
<option value=""></option>
<option value="filter-due-date"><?= t('Filter by due date') ?></option>
<option value="filter-recent"><?= t('Filter recently updated') ?></option>
</select>
</li>
<?php if ($this->acl->isManagerActionAllowed($project['id'])): ?>
<li>
<i class="fa fa-line-chart fa-fw"></i>
<?= $this->a(t('Analytics'), 'analytic', 'tasks', array('project_id' => $project['id'])) ?>
</li>
<li><i class="fa fa-cog fa-fw"></i>
<?= $this->a(t('Configure'), 'project', 'show', array('project_id' => $project['id'])) ?>
<?php endif ?>
</ul>
</div>

View file

@ -1,28 +1,30 @@
<?php if (isset($not_editable)): ?>
<table id="board">
<?php else: ?>
<table id="board"
data-project-id="<?= $project['id'] ?>"
data-check-interval="<?= $board_private_refresh_interval ?>"
data-save-url="<?= $this->u('board', 'save', array('project_id' => $project['id'])) ?>"
data-check-url="<?= $this->u('board', 'check', array('project_id' => $project['id'], 'timestamp' => time())) ?>"
>
<?php endif ?>
<?php foreach ($swimlanes as $swimlane): ?>
<?php if (empty($swimlane['columns'])): ?>
<p class="alert alert-error"><?= t('There is no column in your project!') ?></p>
<?php break ?>
<div id="board-container">
<?php if (isset($not_editable)): ?>
<table id="board">
<?php else: ?>
<?= $this->render('board/swimlane', array(
'project' => $project,
'swimlane' => $swimlane,
'board_highlight_period' => $board_highlight_period,
'categories' => $categories,
'hide_swimlane' => count($swimlanes) === 1,
'not_editable' => isset($not_editable),
)) ?>
<table id="board"
data-project-id="<?= $project['id'] ?>"
data-check-interval="<?= $board_private_refresh_interval ?>"
data-save-url="<?= $this->u('board', 'save', array('project_id' => $project['id'])) ?>"
data-check-url="<?= $this->u('board', 'check', array('project_id' => $project['id'], 'timestamp' => time())) ?>"
data-task-creation-url="<?= $this->u('task', 'create', array('project_id' => $project['id'])) ?>"
>
<?php endif ?>
<?php endforeach ?>
</table>
<?php foreach ($swimlanes as $swimlane): ?>
<?php if (empty($swimlane['columns'])): ?>
<p class="alert alert-error"><?= t('There is no column in your project!') ?></p>
<?php break ?>
<?php else: ?>
<?= $this->render('board/swimlane', array(
'project' => $project,
'swimlane' => $swimlane,
'board_highlight_period' => $board_highlight_period,
'categories' => $categories,
'hide_swimlane' => count($swimlanes) === 1,
'not_editable' => isset($not_editable),
)) ?>
<?php endif ?>
<?php endforeach ?>
</table>
</div>

View file

@ -1,14 +1,7 @@
<section id="tooltip-subtasks">
<?php foreach ($subtasks as $subtask): ?>
<?= $this->a(
trim($this->render('subtask/icons', array('subtask' => $subtask))) . $this->e($subtask['title']),
'board',
'toggleSubtask',
array('task_id' => $subtask['task_id'], 'project_id' => $task['project_id'], 'subtask_id' => $subtask['id'])
) ?>
<?= $this->toggleSubtaskStatus($subtask, 'board') ?>
<?= $this->e(empty($subtask['username']) ? '' : ' ['.$this->getFullname($subtask).']') ?>
<br/>
<?php endforeach ?>
</section>

View file

@ -1,18 +1,38 @@
<tr>
<?php if (! $hide_swimlane): ?>
<td width="10%"></td>
<th>
<?php if (! $not_editable && $swimlane['nb_tasks'] > 0): ?>
<a href="#" class="board-swimlane-toggle" data-swimlane-id="<?= $swimlane['id'] ?>">
<i class="fa fa-minus-circle hide-icon-swimlane-<?= $swimlane['id'] ?>"></i>
<i class="fa fa-plus-circle show-icon-swimlane-<?= $swimlane['id'] ?>" style="display: none"></i>
</a>
<?php endif ?>
<?= $this->e($swimlane['name']) ?>
<span title="<?= t('Task count') ?>" class="task-count">
(<span><?= $swimlane['nb_tasks'] ?></span>)
</span>
</th>
<?php endif ?>
<?php foreach ($swimlane['columns'] as $column): ?>
<th>
<th class="board-column">
<?php if (! $not_editable): ?>
<div class="board-add-icon">
<?= $this->a('+', 'task', 'create', array('project_id' => $column['project_id'], 'column_id' => $column['id'], 'swimlane_id' => $swimlane['id']), false, 'task-creation-popover', t('Add a new task')) ?>
<?= $this->a('+', 'task', 'create', array('project_id' => $column['project_id'], 'column_id' => $column['id'], 'swimlane_id' => $swimlane['id']), false, 'task-board-popover', t('Add a new task')) ?>
</div>
<?php endif ?>
<?= $this->e($column['title']) ?>
<?php if (! empty($column['description'])): ?>
<span class="column-tooltip pull-right" title="<?= $this->markdown($column['description']) ?>">
<i class="fa fa-info-circle"></i>
</span>
<?php endif ?>
<?php if ($column['task_limit']): ?>
<span title="<?= t('Task limit') ?>" class="task-limit">
(<span id="task-number-column-<?= $column['id'] ?>"><?= $column['nb_tasks'] ?></span>/<?= $this->e($column['task_limit']) ?>)
@ -25,11 +45,10 @@
</th>
<?php endforeach ?>
</tr>
<tr>
<tr class="swimlane-row-<?= $swimlane['id'] ?>">
<?php if (! $hide_swimlane): ?>
<th class="board-swimlane-title">
<?= $this->e($swimlane['name']) ?>
</th>
<th></th>
<?php endif ?>
<?php foreach ($swimlane['columns'] as $column): ?>
@ -46,7 +65,7 @@
<?php endif ?>
<?php foreach ($column['tasks'] as $task): ?>
<?= $this->render('board/task', array(
<?= $this->render($not_editable ? 'board/task_public' : 'board/task_private', array(
'project' => $project,
'task' => $task,
'categories' => $categories,

View file

@ -1,125 +0,0 @@
<?php if ($not_editable): ?>
<div class="task-board task-<?= $task['color_id'] ?> <?= $task['date_modification'] > time() - $board_highlight_period ? 'task-board-recent' : '' ?>">
<?= $this->a('#'.$task['id'], 'task', 'readonly', array('task_id' => $task['id'], 'token' => $project['token'])) ?>
<?php if ($task['reference']): ?>
<span class="task-board-reference" title="<?= t('Reference') ?>">
(<?= $task['reference'] ?>)
</span>
<?php endif ?>
&nbsp;-&nbsp;
<span class="task-board-user">
<?php if (! empty($task['owner_id'])): ?>
<?= t('Assigned to %s', $task['assignee_name'] ?: $task['assignee_username']) ?>
<?php else: ?>
<span class="task-board-nobody"><?= t('Nobody assigned') ?></span>
<?php endif ?>
</span>
<?php if ($task['score']): ?>
<span class="task-score"><?= $this->e($task['score']) ?></span>
<?php endif ?>
<div class="task-board-title">
<?= $this->a($this->e($task['title']), 'task', 'readonly', array('task_id' => $task['id'], 'token' => $project['token'])) ?>
</div>
<?php else: ?>
<div class="task-board draggable-item task-<?= $task['color_id'] ?> <?= $task['date_modification'] > time() - $board_highlight_period ? 'task-board-recent' : '' ?>"
data-task-id="<?= $task['id'] ?>"
data-owner-id="<?= $task['owner_id'] ?>"
data-category-id="<?= $task['category_id'] ?>"
data-due-date="<?= $task['date_due'] ?>"
data-task-url="<?= $this->u('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"
title="<?= t('View this task') ?>">
<?= $this->a('#'.$task['id'], 'task', 'edit', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'task-edit-popover', t('Edit this task')) ?>
<?php if ($task['reference']): ?>
<span class="task-board-reference" title="<?= t('Reference') ?>">
(<?= $task['reference'] ?>)
</span>
<?php endif ?>
<span class="task-board-user <?= $this->userSession->isCurrentUser($task['owner_id']) ? 'task-board-current-user' : '' ?>">
<?= $this->a(
(! empty($task['owner_id']) ? t('Assigned to %s', $task['assignee_name'] ?: $task['assignee_username']) : t('Nobody assigned')),
'board',
'changeAssignee',
array('task_id' => $task['id'], 'project_id' => $task['project_id']),
false,
'assignee-popover',
t('Change assignee')
) ?>
</span>
<?php if ($task['score']): ?>
<span class="task-score"><?= $this->e($task['score']) ?></span>
<?php endif ?>
<div class="task-board-title">
<?= $this->a($this->e($task['title']), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, '', t('View this task')) ?>
</div>
<?php endif ?>
<?php if ($task['category_id']): ?>
<div class="task-board-category-container">
<span class="task-board-category">
<?= $this->a(
$this->inList($task['category_id'], $categories),
'board',
'changeCategory',
array('task_id' => $task['id'], 'project_id' => $task['project_id']),
false,
'category-popover',
t('Change category')
) ?>
</span>
</div>
<?php endif ?>
<?php if (! empty($task['date_due']) || ! empty($task['nb_files']) || ! empty($task['nb_comments']) || ! empty($task['description']) || ! empty($task['nb_subtasks'])): ?>
<div class="task-board-footer">
<?php if (! empty($task['date_due'])): ?>
<div class="task-board-date <?= time() > $task['date_due'] ? 'task-board-date-overdue' : '' ?>">
<i class="fa fa-calendar"></i>&nbsp;<?= dt('%b %e, %Y', $task['date_due']) ?>
</div>
<?php endif ?>
<div class="task-board-icons">
<?php if (! empty($task['nb_subtasks'])): ?>
<span title="<?= t('Sub-Tasks') ?>" class="task-board-tooltip" data-href="<?= $this->u('board', 'subtasks', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><?= $task['nb_completed_subtasks'].'/'.$task['nb_subtasks'] ?> <i class="fa fa-bars"></i></span>
<?php endif ?>
<?php if (! empty($task['nb_files'])): ?>
<span title="<?= t('Attachments') ?>" class="task-board-tooltip" data-href="<?= $this->u('board', 'attachments', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><?= $task['nb_files'] ?> <i class="fa fa-paperclip"></i></span>
<?php endif ?>
<?php if (! empty($task['nb_comments'])): ?>
<span title="<?= p($task['nb_comments'], t('%d comment', $task['nb_comments']), t('%d comments', $task['nb_comments'])) ?>" class="task-board-tooltip" data-href="<?= $this->u('board', 'comments', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><?= $task['nb_comments'] ?> <i class="fa fa-comment-o"></i></span>
<?php endif ?>
<?php if (! empty($task['description'])): ?>
<span title="<?= t('Description') ?>" class="task-board-tooltip" data-href="<?= $this->u('board', 'description', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>">
<?php if (! isset($not_editable)): ?>
<a class="task-description-popover" href="?controller=task&amp;action=description&amp;task_id=<?= $task['id'] ?>"><i class="fa fa-file-text-o" data-href="?controller=task&amp;action=description&amp;task_id=<?= $task['id'] ?>"></i></a>
<?php else: ?>
<i class="fa fa-file-text-o"></i>
<?php endif ?>
</span>
<?php endif ?>
</div>
</div>
<?php endif ?>
</div>

View file

@ -0,0 +1,45 @@
<?php if ($task['category_id']): ?>
<div class="task-board-category-container">
<span class="task-board-category">
<?= $this->a(
$this->inList($task['category_id'], $categories),
'board',
'changeCategory',
array('task_id' => $task['id'], 'project_id' => $task['project_id']),
false,
'task-board-popover',
t('Change category')
) ?>
</span>
</div>
<?php endif ?>
<div class="task-board-icons">
<?php if (! empty($task['date_due'])): ?>
<span class="task-board-date <?= time() > $task['date_due'] ? 'task-board-date-overdue' : '' ?>">
<i class="fa fa-calendar"></i>&nbsp;<?= dt('%b %e', $task['date_due']) ?>
</span>
<?php endif ?>
<?php if (! empty($task['nb_links'])): ?>
<span title="<?= t('Links') ?>" class="task-board-tooltip" data-href="<?= $this->u('board', 'tasklinks', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><?= $task['nb_links'] ?> <i class="fa fa-code-fork"></i></span>
<?php endif ?>
<?php if (! empty($task['nb_subtasks'])): ?>
<span title="<?= t('Sub-Tasks') ?>" class="task-board-tooltip" data-href="<?= $this->u('board', 'subtasks', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><?= round($task['nb_completed_subtasks']/$task['nb_subtasks']*100, 0).'%' ?> <i class="fa fa-bars"></i></span>
<?php endif ?>
<?php if (! empty($task['nb_files'])): ?>
<span title="<?= t('Attachments') ?>" class="task-board-tooltip" data-href="<?= $this->u('board', 'attachments', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><?= $task['nb_files'] ?> <i class="fa fa-paperclip"></i></span>
<?php endif ?>
<?php if (! empty($task['nb_comments'])): ?>
<span title="<?= p($task['nb_comments'], t('%d comment', $task['nb_comments']), t('%d comments', $task['nb_comments'])) ?>" class="task-board-tooltip" data-href="<?= $this->u('board', 'comments', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"><?= $task['nb_comments'] ?> <i class="fa fa-comment-o"></i></span>
<?php endif ?>
<?php if (! empty($task['description'])): ?>
<span title="<?= t('Description') ?>" class="task-board-tooltip" data-href="<?= $this->u('board', 'description', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>">
<i class="fa fa-file-text-o"></i>
</span>
<?php endif ?>
</div>

Some files were not shown because too many files have changed in this diff Show more