diff options
Diffstat (limited to 'app')
62 files changed, 2332 insertions, 427 deletions
diff --git a/app/Controller/App.php b/app/Controller/App.php index ef0a08a9..46731e7c 100644 --- a/app/Controller/App.php +++ b/app/Controller/App.php @@ -2,7 +2,8 @@ namespace Controller; -use Model\Subtask as SubTaskModel; +use Model\Subtask as SubtaskModel; +use Model\Task as TaskModel; /** * Application controller @@ -39,7 +40,7 @@ class App extends Base */ public function index($user_id = 0, $action = 'index') { - $status = array(SubTaskModel::STATUS_TODO, SubTaskModel::STATUS_INPROGRESS); + $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); @@ -88,11 +89,8 @@ 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'])); } /** @@ -104,4 +102,21 @@ class App extends Base { $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() + ); + } } diff --git a/app/Controller/Base.php b/app/Controller/Base.php index 7f65e882..548fdb40 100644 --- a/app/Controller/Base.php +++ b/app/Controller/Base.php @@ -17,11 +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 @@ -43,6 +45,7 @@ use Symfony\Component\EventDispatcher\Event; * @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 @@ -54,6 +57,7 @@ use Symfony\Component\EventDispatcher\Event; * @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 @@ -139,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(); @@ -199,7 +203,7 @@ abstract class Base { $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); diff --git a/app/Controller/Board.php b/app/Controller/Board.php index e859348e..90b7f357 100644 --- a/app/Controller/Board.php +++ b/app/Controller/Board.php @@ -402,6 +402,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 * * @access public diff --git a/app/Controller/Calendar.php b/app/Controller/Calendar.php index abbcab7f..1c7ac7c0 100644 --- a/app/Controller/Calendar.php +++ b/app/Controller/Calendar.php @@ -2,7 +2,7 @@ namespace Controller; -use Model\Task; +use Model\Task as TaskModel; /** * Project Calendar controller @@ -41,24 +41,27 @@ class Calendar extends Base * * @access public */ - public function events() + public function project() { - $this->response->json( - $this->taskFilter - ->create() - ->filterByProject($this->request->getIntegerParam('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( - $this->request->getStringParam('start'), - $this->request->getStringParam('end') - ) - ->toCalendarEvents() - ); + $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)); } /** @@ -69,18 +72,19 @@ class Calendar extends Base 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( - $this->taskFilter - ->create() - ->filterByOwner($user_id) - ->filterByStatus(Task::STATUS_OPEN) - ->filterByDueDateRange( - $this->request->getStringParam('start'), - $this->request->getStringParam('end') - ) - ->toCalendarEvents() - ); + $this->response->json(array_merge($due_tasks, $subtask_timeslots)); } /** diff --git a/app/Controller/Link.php b/app/Controller/Link.php new file mode 100644 index 00000000..ec9c6195 --- /dev/null +++ b/app/Controller/Link.php @@ -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').' > '.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')); + } +} diff --git a/app/Controller/Task.php b/app/Controller/Task.php index fdd20b5e..741db61e 100644 --- a/app/Controller/Task.php +++ b/app/Controller/Task.php @@ -36,6 +36,7 @@ class Task extends Base 'project' => $project, 'comments' => $this->comment->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(), @@ -70,6 +71,7 @@ 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, 'columns_list' => $this->board->getColumnsList($task['project_id']), @@ -249,6 +251,7 @@ class Task extends Base public function close() { $task = $this->getTask(); + $redirect = $this->request->getStringParam('redirect'); if ($this->request->getStringParam('confirmation') === 'yes') { @@ -260,15 +263,23 @@ class Task extends Base $this->session->flashError(t('Unable to close this task.')); } - if ($this->request->getStringParam('redirect') === 'board') { + 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, ))); } diff --git a/app/Controller/Tasklink.php b/app/Controller/Tasklink.php new file mode 100644 index 00000000..61b7fab8 --- /dev/null +++ b/app/Controller/Tasklink.php @@ -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']))); + } +} diff --git a/app/Core/Helper.php b/app/Core/Helper.php index d60e29d4..187fcfbb 100644 --- a/app/Core/Helper.php +++ b/app/Core/Helper.php @@ -50,6 +50,33 @@ class Helper } /** + * 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() * * @access public @@ -639,7 +666,7 @@ class Helper 'subtaskRestriction', array('task_id' => $subtask['task_id'], 'subtask_id' => $subtask['id'], 'redirect' => $redirect), false, - 'popover-subtask-restriction' + 'popover' ); } diff --git a/app/Locale/da_DK/translations.php b/app/Locale/da_DK/translations.php index d0bebe0c..e09810b2 100644 --- a/app/Locale/da_DK/translations.php +++ b/app/Locale/da_DK/translations.php @@ -686,4 +686,49 @@ return array( // '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' => '', ); diff --git a/app/Locale/de_DE/translations.php b/app/Locale/de_DE/translations.php index 42db9bcd..d4fbc8c8 100644 --- a/app/Locale/de_DE/translations.php +++ b/app/Locale/de_DE/translations.php @@ -686,4 +686,49 @@ return array( // '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' => '', ); diff --git a/app/Locale/es_ES/translations.php b/app/Locale/es_ES/translations.php index e921c10f..56b1a8c4 100644 --- a/app/Locale/es_ES/translations.php +++ b/app/Locale/es_ES/translations.php @@ -686,4 +686,49 @@ return array( // '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' => '', ); diff --git a/app/Locale/fi_FI/translations.php b/app/Locale/fi_FI/translations.php index 0ed1031b..8f9ecdfa 100644 --- a/app/Locale/fi_FI/translations.php +++ b/app/Locale/fi_FI/translations.php @@ -686,4 +686,49 @@ return array( // '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' => '', ); diff --git a/app/Locale/fr_FR/translations.php b/app/Locale/fr_FR/translations.php index 7fc5e842..5d32030e 100644 --- a/app/Locale/fr_FR/translations.php +++ b/app/Locale/fr_FR/translations.php @@ -688,4 +688,49 @@ return array( '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°%s ?', + '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', ); diff --git a/app/Locale/hu_HU/translations.php b/app/Locale/hu_HU/translations.php index 3ca6cf28..ea4b36b1 100644 --- a/app/Locale/hu_HU/translations.php +++ b/app/Locale/hu_HU/translations.php @@ -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,38 +133,38 @@ 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', - 'Done' => 'Csinált', + '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 ..', '%B %e, %Y at %k:%M %p' => '%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,31 +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"', - // 'New attachment' => '', - // 'New comment' => '', - // 'New subtask' => '', - // 'Subtask updated' => '', - // 'Task updated' => '', - // 'Task closed' => '', - // 'Task opened' => '', + '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:', @@ -432,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', @@ -462,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', @@ -479,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', @@ -492,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', - // 'Column Change' => '', - // 'Position Change' => '', - // 'Assignee Change' => '', - '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', @@ -522,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', @@ -547,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', @@ -558,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', @@ -568,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', @@ -592,98 +592,143 @@ 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:' => '', - // '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' => '', + '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' => '', ); diff --git a/app/Locale/it_IT/translations.php b/app/Locale/it_IT/translations.php index 5c3ef47b..6b392c2c 100644 --- a/app/Locale/it_IT/translations.php +++ b/app/Locale/it_IT/translations.php @@ -686,4 +686,49 @@ return array( // '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' => '', ); diff --git a/app/Locale/ja_JP/translations.php b/app/Locale/ja_JP/translations.php index 91030bee..b2f1a85f 100644 --- a/app/Locale/ja_JP/translations.php +++ b/app/Locale/ja_JP/translations.php @@ -686,4 +686,49 @@ return array( // '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' => '', ); diff --git a/app/Locale/pl_PL/translations.php b/app/Locale/pl_PL/translations.php index 7acb1497..274ecb80 100644 --- a/app/Locale/pl_PL/translations.php +++ b/app/Locale/pl_PL/translations.php @@ -686,4 +686,49 @@ return array( // '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' => '', ); diff --git a/app/Locale/pt_BR/translations.php b/app/Locale/pt_BR/translations.php index ac865046..7aa79c53 100644 --- a/app/Locale/pt_BR/translations.php +++ b/app/Locale/pt_BR/translations.php @@ -686,4 +686,49 @@ return array( // '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' => '', ); diff --git a/app/Locale/ru_RU/translations.php b/app/Locale/ru_RU/translations.php index 624a17f1..a31cd63b 100644 --- a/app/Locale/ru_RU/translations.php +++ b/app/Locale/ru_RU/translations.php @@ -686,4 +686,49 @@ return array( // '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' => '', ); diff --git a/app/Locale/sv_SE/translations.php b/app/Locale/sv_SE/translations.php index b77a8a5a..2083fc18 100644 --- a/app/Locale/sv_SE/translations.php +++ b/app/Locale/sv_SE/translations.php @@ -686,4 +686,49 @@ return array( // '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' => '', ); diff --git a/app/Locale/th_TH/translations.php b/app/Locale/th_TH/translations.php index 64e0170e..42d731d0 100644 --- a/app/Locale/th_TH/translations.php +++ b/app/Locale/th_TH/translations.php @@ -686,4 +686,49 @@ return array( // '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' => '', ); diff --git a/app/Locale/zh_CN/translations.php b/app/Locale/zh_CN/translations.php index addf7fa6..6868cd16 100644 --- a/app/Locale/zh_CN/translations.php +++ b/app/Locale/zh_CN/translations.php @@ -686,4 +686,49 @@ return array( // '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' => '', ); diff --git a/app/Model/Acl.php b/app/Model/Acl.php index b6c0e6b8..4d4a22a7 100644 --- a/app/Model/Acl.php +++ b/app/Model/Acl.php @@ -38,7 +38,8 @@ class Acl extends Base 'project' => array('show', 'tasks', 'search', 'activity'), 'subtask' => '*', 'task' => '*', - 'calendar' => array('show', 'events', 'save'), + 'tasklink' => '*', + 'calendar' => array('show', 'project', 'save'), ); /** @@ -67,6 +68,7 @@ class Acl extends Base 'app' => array('dashboard'), 'user' => array('index', 'create', 'save', 'remove'), 'config' => '*', + 'link' => '*', 'project' => array('remove'), ); diff --git a/app/Model/Authentication.php b/app/Model/Authentication.php index 92898cd5..86c1c43f 100644 --- a/app/Model/Authentication.php +++ b/app/Model/Authentication.php @@ -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(); diff --git a/app/Model/Base.php b/app/Model/Base.php index 319e53fc..f836231c 100644 --- a/app/Model/Base.php +++ b/app/Model/Base.php @@ -25,6 +25,7 @@ use Pimple\Container; * @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 @@ -38,6 +39,7 @@ use Pimple\Container; * @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 diff --git a/app/Model/Link.php b/app/Model/Link.php new file mode 100644 index 00000000..87ba49c4 --- /dev/null +++ b/app/Model/Link.php @@ -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() + ); + } +} diff --git a/app/Model/ProjectActivity.php b/app/Model/ProjectActivity.php index 7762d0fd..652cc842 100644 --- a/app/Model/ProjectActivity.php +++ b/app/Model/ProjectActivity.php @@ -62,7 +62,7 @@ class ProjectActivity extends Base */ public function getProject($project_id, $limit = 50, $start = null, $end = null) { - return $this->getProjects(array($project_id), $limit, $start = null, $end = null); + return $this->getProjects(array($project_id), $limit, $start, $end); } /** @@ -91,15 +91,15 @@ class ProjectActivity extends Base ->join(User::TABLE, 'id', 'creator_id') ->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) { diff --git a/app/Model/ProjectPermission.php b/app/Model/ProjectPermission.php index fb6316b6..12bd9309 100644 --- a/app/Model/ProjectPermission.php +++ b/app/Model/ProjectPermission.php @@ -326,7 +326,7 @@ class ProjectPermission extends Base * * @access public * @param integer $user_id User id - * @return []integer + * @return array */ public function getMemberProjectIds($user_id) { @@ -338,6 +338,23 @@ class ProjectPermission extends Base } /** + * 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 diff --git a/app/Model/SubtaskTimeTracking.php b/app/Model/SubtaskTimeTracking.php index 8093cf80..1e7e252e 100644 --- a/app/Model/SubtaskTimeTracking.php +++ b/app/Model/SubtaskTimeTracking.php @@ -21,7 +21,7 @@ class SubtaskTimeTracking extends Base * Get query for user timesheet (pagination) * * @access public - * @param integer $user_id User id + * @param integer $user_id User id * @return \PicoDb\Table */ public function getUserQuery($user_id) @@ -36,7 +36,8 @@ class SubtaskTimeTracking extends Base Subtask::TABLE.'.task_id', Subtask::TABLE.'.title AS subtask_title', Task::TABLE.'.title AS task_title', - Task::TABLE.'.project_id' + Task::TABLE.'.project_id', + Task::TABLE.'.color_id' ) ->join(Subtask::TABLE, 'id', 'subtask_id') ->join(Task::TABLE, 'id', 'task_id', Subtask::TABLE) @@ -44,10 +45,10 @@ class SubtaskTimeTracking extends Base } /** - * Get query for task (pagination) + * Get query for task timesheet (pagination) * * @access public - * @param integer $task_id Task id + * @param integer $task_id Task id * @return \PicoDb\Table */ public function getTaskQuery($task_id) @@ -73,6 +74,36 @@ class SubtaskTimeTracking extends Base } /** + * 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 @@ -88,6 +119,98 @@ class SubtaskTimeTracking extends Base } /** + * 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 @@ -133,7 +256,7 @@ class SubtaskTimeTracking extends Base */ public function updateTaskTimeTracking($task_id) { - $result = $this->calculateSubtaskTime($task_id); + $result = $this->calculateSubtaskTime($task_id); if (empty($result['total_spent']) && empty($result['total_estimated'])) { return true; diff --git a/app/Model/TaskFilter.php b/app/Model/TaskFilter.php index b5a90154..31de2795 100644 --- a/app/Model/TaskFilter.php +++ b/app/Model/TaskFilter.php @@ -18,6 +18,24 @@ class TaskFilter extends Base 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) { @@ -94,6 +112,20 @@ class TaskFilter extends Base 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(); diff --git a/app/Model/TaskFinder.php b/app/Model/TaskFinder.php index 27fa8150..98ece4e1 100644 --- a/app/Model/TaskFinder.php +++ b/app/Model/TaskFinder.php @@ -84,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', diff --git a/app/Model/TaskLink.php b/app/Model/TaskLink.php new file mode 100644 index 00000000..c712e5a8 --- /dev/null +++ b/app/Model/TaskLink.php @@ -0,0 +1,142 @@ +<?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' + ) + ->eq(self::TABLE.'.task_id', $task_id) + ->join(Link::TABLE, 'id', 'link_id') + ->join(Task::TABLE, 'id', 'opposite_task_id') + ->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() + ); + } +} diff --git a/app/Model/User.php b/app/Model/User.php index 01be8597..7586f3c4 100644 --- a/app/Model/User.php +++ b/app/Model/User.php @@ -29,6 +29,18 @@ 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 diff --git a/app/Schema/Mysql.php b/app/Schema/Mysql.php index 24bc2baf..947a62b3 100644 --- a/app/Schema/Mysql.php +++ b/app/Schema/Mysql.php @@ -4,13 +4,52 @@ namespace Schema; use PDO; use Core\Security; - -const VERSION = 45; +use Model\Link; + +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 @@ -122,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"); } diff --git a/app/Schema/Postgres.php b/app/Schema/Postgres.php index d3fb9fc4..027401ff 100644 --- a/app/Schema/Postgres.php +++ b/app/Schema/Postgres.php @@ -4,8 +4,45 @@ namespace Schema; use PDO; use Core\Security; +use Model\Link; -const VERSION = 26; +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) { @@ -66,7 +103,7 @@ function version_24($pdo) function version_23($pdo) { - $pdo->exec('ALTER TABLE columns ADD COLUMN description TEXT'); + $pdo->exec('ALTER TABLE columns ADD COLUMN description TEXT'); } function version_22($pdo) @@ -120,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'"); } diff --git a/app/Schema/Sqlite.php b/app/Schema/Sqlite.php index f027cf91..c6dec33f 100644 --- a/app/Schema/Sqlite.php +++ b/app/Schema/Sqlite.php @@ -4,13 +4,50 @@ namespace Schema; use Core\Security; use PDO; - -const VERSION = 44; +use Model\Link; + +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 @@ -66,7 +103,7 @@ function version_42($pdo) function version_41($pdo) { - $pdo->exec('ALTER TABLE columns ADD COLUMN description TEXT'); + $pdo->exec('ALTER TABLE columns ADD COLUMN description TEXT'); } function version_40($pdo) @@ -120,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"); } diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php index bee03184..213972ed 100644 --- a/app/ServiceProvider/ClassProvider.php +++ b/app/ServiceProvider/ClassProvider.php @@ -24,6 +24,7 @@ class ClassProvider implements ServiceProviderInterface 'DateParser', 'File', 'LastLogin', + 'Link', 'Notification', 'Project', 'ProjectActivity', @@ -41,6 +42,7 @@ class ClassProvider implements ServiceProviderInterface 'TaskExport', 'TaskFinder', 'TaskFilter', + 'TaskLink', 'TaskModification', 'TaskPermission', 'TaskPosition', diff --git a/app/Subscriber/Base.php b/app/Subscriber/Base.php index 489d49b0..f90d9604 100644 --- a/app/Subscriber/Base.php +++ b/app/Subscriber/Base.php @@ -10,6 +10,7 @@ use Pimple\Container; * @package subscriber * @author Frederic Guillot * + * @property \Model\Board $board * @property \Model\Config $config * @property \Model\Comment $comment * @property \Model\LastLogin $lastLogin diff --git a/app/Template/board/filters.php b/app/Template/board/filters.php index 0e9c4d5f..64a01b8f 100644 --- a/app/Template/board/filters.php +++ b/app/Template/board/filters.php @@ -6,6 +6,14 @@ <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> @@ -21,6 +29,11 @@ <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> diff --git a/app/Template/board/show.php b/app/Template/board/show.php index 5fbb576c..f1607d26 100644 --- a/app/Template/board/show.php +++ b/app/Template/board/show.php @@ -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>
\ No newline at end of file diff --git a/app/Template/board/swimlane.php b/app/Template/board/swimlane.php index f0a00fc1..08efdd11 100644 --- a/app/Template/board/swimlane.php +++ b/app/Template/board/swimlane.php @@ -2,7 +2,7 @@ <?php if (! $hide_swimlane): ?> <th> - <?php if ($swimlane['nb_tasks'] > 0): ?> + <?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> @@ -18,21 +18,21 @@ <?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']) ?>"> + <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']) ?>) @@ -65,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', array( 'project' => $project, 'task' => $task, 'categories' => $categories, diff --git a/app/Template/board/task.php b/app/Template/board/task.php index 5cad4004..a2a617a6 100644 --- a/app/Template/board/task.php +++ b/app/Template/board/task.php @@ -1,35 +1,3 @@ -<?php if ($not_editable): ?> - -<div class="task-board color-<?= $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 ?> - - - - - <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 color-<?= $task['color_id'] ?> <?= $task['date_modification'] > time() - $board_highlight_period ? 'task-board-recent' : '' ?>" data-task-id="<?= $task['id'] ?>" data-owner-id="<?= $task['owner_id'] ?>" @@ -38,98 +6,57 @@ data-task-url="<?= $this->u('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>" title="<?= t('View this task') ?>"> - <ul class="dropdown"> - <li> - <a href="#" class="dropdown-menu"><?= '#'.$task['id'] ?></a> - <ul> - <li><i class="fa fa-user"></i> <?= $this->a(t('Change assignee'), 'board', 'changeAssignee', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'assignee-popover') ?></li> - <li><i class="fa fa-tag"></i> <?= $this->a(t('Change category'), 'board', 'changeCategory', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'category-popover') ?></li> - <li><i class="fa fa-align-left"></i> <?= $this->a(t('Change description'), 'task', 'description', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'task-description-popover') ?></li> - <li><i class="fa fa-pencil-square-o"></i> <?= $this->a(t('Edit this task'), 'task', 'edit', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'task-edit-popover') ?></li> - <li><i class="fa fa-close"></i> <?= $this->a(t('Close this task'), 'task', 'close', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'confirmation' => 'yes', 'redirect' => 'board'), true) ?></li> - </li> - </li> - </ul> - - <?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> - - <span title="<?= t('Task age in days')?>" class="task-days-age"><?= t('%dd', floor(time()/86400) - floor($task['date_creation']/86400)) ?></span> - <span title="<?= t('Days in this column')?>" class="task-days-incolumn"><?= t('%dd', floor(time()/86400) - floor($task['date_moved']/86400)) ?></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 class="task-board-collapsed" style="display: none"> + <?= $this->a('#'.$task['id'], 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'task-board-collapsed-id') ?> + <?= $this->a($this->e($task['title']), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'task-board-collapsed-title') ?> </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> <?= dt('%b %e, %Y', $task['date_due']) ?> - </div> - <?php endif ?> - - <div class="task-board-icons"> + <div class="task-board-expanded"> + + <ul class="dropdown"> + <li> + <a href="#" class="dropdown-menu"><?= '#'.$task['id'] ?></a> + <ul> + <li><i class="fa fa-user"></i> <?= $this->a(t('Change assignee'), 'board', 'changeAssignee', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'task-board-popover') ?></li> + <li><i class="fa fa-tag"></i> <?= $this->a(t('Change category'), 'board', 'changeCategory', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'task-board-popover') ?></li> + <li><i class="fa fa-align-left"></i> <?= $this->a(t('Change description'), 'task', 'description', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'task-board-popover') ?></li> + <li><i class="fa fa-pencil-square-o"></i> <?= $this->a(t('Edit this task'), 'task', 'edit', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'task-board-popover') ?></li> + <li><i class="fa fa-close"></i> <?= $this->a(t('Close this task'), 'task', 'close', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'redirect' => 'board'), false, 'task-board-popover') ?></li> + </ul> + </li> + </ul> - <?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 if ($task['reference']): ?> + <span class="task-board-reference" title="<?= t('Reference') ?>"> + (<?= $task['reference'] ?>) + </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> + <span class="task-board-user <?= $this->userSession->isCurrentUser($task['owner_id']) ? 'task-board-current-user' : '' ?>"> + <?= $this->a( + (! empty($task['owner_id']) ? ($task['assignee_name'] ?: $task['assignee_username']) : t('Nobody assigned')), + 'board', + 'changeAssignee', + array('task_id' => $task['id'], 'project_id' => $task['project_id']), + false, + 'task-board-popover', + t('Change assignee') + ) ?> + </span> + + <?php if ($task['score']): ?> + <span class="task-score"><?= $this->e($task['score']) ?></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 ?> + <div class="task-board-days"> + <span title="<?= t('Task age in days')?>" class="task-days-age"><?= $this->getTaskAge($task['date_creation']) ?></span> + <span title="<?= t('Days in this column')?>" class="task-days-incolumn"><?= $this->getTaskAge($task['date_moved']) ?></span> + </div> - <?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 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> + + <?= $this->render('board/task_footer', array('task' => $task, 'categories' => $categories)) ?> </div> </div> -<?php endif ?> - -</div>
\ No newline at end of file diff --git a/app/Template/board/task_footer.php b/app/Template/board/task_footer.php new file mode 100644 index 00000000..d413692c --- /dev/null +++ b/app/Template/board/task_footer.php @@ -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> <?= 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>
\ No newline at end of file diff --git a/app/Template/board/task_public.php b/app/Template/board/task_public.php new file mode 100644 index 00000000..650b956d --- /dev/null +++ b/app/Template/board/task_public.php @@ -0,0 +1,30 @@ +<div class="task-board color-<?= $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 ?> + + - + + <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> + + <?= $this->render('board/task_footer', array('task' => $task, 'categories' => $categories)) ?> +</div>
\ No newline at end of file diff --git a/app/Template/board/tasklinks.php b/app/Template/board/tasklinks.php new file mode 100644 index 00000000..9c4f52ca --- /dev/null +++ b/app/Template/board/tasklinks.php @@ -0,0 +1,15 @@ +<div class="tooltip-tasklinks"> + <ul> + <?php foreach($links as $link): ?> + <li> + <strong><?= t($link['label']) ?></strong> + <?= $this->a( + $this->e('#'.$link['task_id'].' - '.$link['title']), + 'task', 'show', array('task_id' => $link['task_id'], 'project_id' => $link['project_id']), + false, + $link['is_active'] ? '' : 'task-link-closed' + ) ?> + </li> + <?php endforeach ?> + </ul> +</div>
\ No newline at end of file diff --git a/app/Template/calendar/show.php b/app/Template/calendar/show.php index b258e391..cb5a1109 100644 --- a/app/Template/calendar/show.php +++ b/app/Template/calendar/show.php @@ -35,7 +35,7 @@ <div id="calendar" data-project-id="<?= $project['id'] ?>" data-save-url="<?= $this->u('calendar', 'save', array('project_id' => $project['id'])) ?>" - data-check-url="<?= $this->u('calendar', 'events', array('project_id' => $project['id'])) ?>" + data-check-url="<?= $this->u('calendar', 'project', array('project_id' => $project['id'])) ?>" data-check-interval="<?= $check_interval ?>" > </div> diff --git a/app/Template/config/about.php b/app/Template/config/about.php index d96e6bdd..f6474e21 100644 --- a/app/Template/config/about.php +++ b/app/Template/config/about.php @@ -1,7 +1,7 @@ <div class="page-header"> <h2><?= t('About') ?></h2> </div> -<section class="listing"> +<div class="listing"> <ul> <li> <?= t('Official website:') ?> @@ -12,12 +12,11 @@ <strong><?= APP_VERSION ?></strong> </li> </ul> -</section> - +</div> <div class="page-header"> <h2><?= t('Database') ?></h2> </div> -<section class="listing"> +<div class="listing"> <ul> <li> <?= t('Database driver:') ?> @@ -38,4 +37,20 @@ </li> <?php endif ?> </ul> -</section>
\ No newline at end of file +</div> +<div class="page-header"> + <h2><?= t('Keyboard shortcuts') ?></h2> +</div> +<div class="listing"> + <h3><?= t('Board view') ?></h3> + <ul> + <li><?= t('New task') ?> = <strong>n</strong></li> + <li><?= t('Expand/collapse tasks') ?> = <strong>s</strong></li> + </ul> + <h3><?= t('Application') ?></h3> + <ul> + <li><?= t('Open board switcher') ?> = <strong>b</strong></li> + <li><?= t('Close dialog box') ?> = <strong>ESC</strong></li> + <li><?= t('Submit a form') ?> = <strong>CTRL+ENTER</strong> <?= t('or') ?> <strong>⌘+ENTER</strong></li> + </ul> +</div>
\ No newline at end of file diff --git a/app/Template/config/sidebar.php b/app/Template/config/sidebar.php index 8e6fa379..89f2c203 100644 --- a/app/Template/config/sidebar.php +++ b/app/Template/config/sidebar.php @@ -11,6 +11,9 @@ <?= $this->a(t('Board settings'), 'config', 'board') ?> </li> <li> + <?= $this->a(t('Link settings'), 'link', 'index') ?> + </li> + <li> <?= $this->a(t('Webhooks'), 'config', 'webhook') ?> </li> <li> diff --git a/app/Template/file/show.php b/app/Template/file/show.php index 179f5744..298976f6 100644 --- a/app/Template/file/show.php +++ b/app/Template/file/show.php @@ -11,7 +11,7 @@ <?= $this->a($this->e($file['name']), 'file', 'download', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id'])) ?> <span class="task-show-file-actions"> <?php if ($file['is_image']): ?> - <?= $this->a(t('open'), 'file', 'open', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id']), false, 'file-popover') ?>, + <?= $this->a(t('open'), 'file', 'open', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id']), false, 'popover') ?>, <?php endif ?> <?= $this->a(t('remove'), 'file', 'confirm', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id'])) ?> </span> diff --git a/app/Template/layout.php b/app/Template/layout.php index 9e85e9ae..ad4c4084 100644 --- a/app/Template/layout.php +++ b/app/Template/layout.php @@ -29,13 +29,13 @@ data-login-url="<?= $this->u('user', 'login') ?>" data-timezone="<?= $this->getTimezone() ?>" data-js-lang="<?= $this->jsLang() ?>"> - + <?php if (isset($no_layout) && $no_layout): ?> <?= $content_for_layout ?> <?php else: ?> <header> <nav> - <h1><?= $this->a('<i class="fa fa-home fa-fw"></i>', 'app', 'index', array(), false, 'home-link', t('Dashboard')).' '.$this->summary($this->e($title)) ?></h1> + <h1><?= $this->a('K<span>B</span>', 'app', 'index', array(), false, 'logo', t('Dashboard')).' '.$this->summary($this->e($title)) ?></h1> <ul> <?php if (isset($board_selector) && ! empty($board_selector)): ?> <li> diff --git a/app/Template/link/create.php b/app/Template/link/create.php new file mode 100644 index 00000000..12589574 --- /dev/null +++ b/app/Template/link/create.php @@ -0,0 +1,18 @@ +<div class="page-header"> + <h2><?= t('Add a new link') ?></h2> +</div> + +<form action="<?= $this->u('link', 'save') ?>" method="post" autocomplete="off"> + + <?= $this->formCsrf() ?> + + <?= $this->formLabel(t('Label'), 'label') ?> + <?= $this->formText('label', $values, $errors, array('required')) ?> + + <?= $this->formLabel(t('Opposite label'), 'opposite_label') ?> + <?= $this->formText('opposite_label', $values, $errors) ?> + + <div class="form-actions"> + <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> + </div> +</form>
\ No newline at end of file diff --git a/app/Template/link/edit.php b/app/Template/link/edit.php new file mode 100644 index 00000000..d9ce280c --- /dev/null +++ b/app/Template/link/edit.php @@ -0,0 +1,21 @@ +<div class="page-header"> + <h2><?= t('Link modification') ?></h2> +</div> + +<form action="<?= $this->u('link', 'update', array('link_id' => $link['id'])) ?>" method="post" autocomplete="off"> + + <?= $this->formCsrf() ?> + <?= $this->formHidden('id', $values) ?> + + <?= $this->formLabel(t('Label'), 'label') ?> + <?= $this->formText('label', $values, $errors, array('required')) ?> + + <?= $this->formLabel(t('Opposite label'), 'opposite_id') ?> + <?= $this->formSelect('opposite_id', $labels, $values, $errors) ?> + + <div class="form-actions"> + <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> + <?= t('or') ?> + <?= $this->a(t('cancel'), 'link', 'index') ?> + </div> +</form>
\ No newline at end of file diff --git a/app/Template/link/index.php b/app/Template/link/index.php new file mode 100644 index 00000000..90d1c357 --- /dev/null +++ b/app/Template/link/index.php @@ -0,0 +1,33 @@ +<div class="page-header"> + <h2><?= t('Link labels') ?></h2> +</div> +<?php if (! empty($links)): ?> +<table> + <tr> + <th class="column-70"><?= t('Link labels') ?></th> + <th><?= t('Actions') ?></th> + </tr> + <?php foreach ($links as $link): ?> + <tr> + <td> + <strong><?= t($link['label']) ?></strong> + + <?php if (! empty($link['opposite_label'])): ?> + | <?= t($link['opposite_label']) ?> + <?php endif ?> + </td> + <td> + <ul> + <?= $this->a(t('Edit'), 'link', 'edit', array('link_id' => $link['id'])) ?> + <?= t('or') ?> + <?= $this->a(t('Remove'), 'link', 'confirm', array('link_id' => $link['id'])) ?> + </ul> + </td> + </tr> + <?php endforeach ?> +</table> +<?php else: ?> + <?= t('There is no link.') ?> +<?php endif ?> + +<?= $this->render('link/create', array('values' => $values, 'errors' => $errors)) ?>
\ No newline at end of file diff --git a/app/Template/link/remove.php b/app/Template/link/remove.php new file mode 100644 index 00000000..a802feb0 --- /dev/null +++ b/app/Template/link/remove.php @@ -0,0 +1,15 @@ +<div class="page-header"> + <h2><?= t('Remove a link') ?></h2> +</div> + +<div class="confirm"> + <p class="alert alert-info"> + <?= t('Do you really want to remove this link: "%s"?', $link['label']) ?> + </p> + + <div class="form-actions"> + <?= $this->a(t('Yes'), 'link', 'remove', array('link_id' => $link['id']), true, 'btn btn-red') ?> + <?= t('or') ?> + <?= $this->a(t('cancel'), 'link', 'index') ?> + </div> +</div>
\ No newline at end of file diff --git a/app/Template/task/close.php b/app/Template/task/close.php index 316d58eb..a0a95787 100644 --- a/app/Template/task/close.php +++ b/app/Template/task/close.php @@ -8,8 +8,8 @@ </p> <div class="form-actions"> - <?= $this->a(t('Yes'), 'task', 'close', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'confirmation' => 'yes'), true, 'btn btn-red') ?> + <?= $this->a(t('Yes'), 'task', 'close', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'confirmation' => 'yes', 'redirect' => $redirect), true, 'btn btn-red') ?> <?= t('or') ?> - <?= $this->a(t('cancel'), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> + <?= $this->a(t('cancel'), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'close-popover') ?> </div> </div>
\ No newline at end of file diff --git a/app/Template/task/public.php b/app/Template/task/public.php index 2d95e6db..d7acef9f 100644 --- a/app/Template/task/public.php +++ b/app/Template/task/public.php @@ -10,6 +10,13 @@ 'is_public' => true )) ?> + <?= $this->render('tasklink/show', array( + 'task' => $task, + 'links' => $links, + 'project' => $project, + 'not_editable' => true + )) ?> + <?= $this->render('subtask/show', array( 'task' => $task, 'subtasks' => $subtasks, diff --git a/app/Template/task/show.php b/app/Template/task/show.php index b8243cc6..1ff2ef43 100644 --- a/app/Template/task/show.php +++ b/app/Template/task/show.php @@ -1,7 +1,8 @@ <?= $this->render('task/details', array('task' => $task, 'project' => $project)) ?> <?= $this->render('task/time', array('task' => $task, 'values' => $values, 'date_format' => $date_format, 'date_formats' => $date_formats)) ?> <?= $this->render('task/show_description', array('task' => $task)) ?> +<?= $this->render('tasklink/show', array('task' => $task, 'links' => $links)) ?> <?= $this->render('subtask/show', array('task' => $task, 'subtasks' => $subtasks)) ?> <?= $this->render('task/timesheet', array('task' => $task)) ?> <?= $this->render('file/show', array('task' => $task, 'files' => $files)) ?> -<?= $this->render('task/comments', array('task' => $task, 'comments' => $comments, 'project' => $project)) ?>
\ No newline at end of file +<?= $this->render('task/comments', array('task' => $task, 'comments' => $comments, 'project' => $project)) ?> diff --git a/app/Template/task/sidebar.php b/app/Template/task/sidebar.php index e85a1671..f41be14d 100644 --- a/app/Template/task/sidebar.php +++ b/app/Template/task/sidebar.php @@ -19,6 +19,9 @@ <?= $this->a(t('Add a sub-task'), 'subtask', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> </li> <li> + <?= $this->a(t('Add a link'), 'tasklink', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> + </li> + <li> <?= $this->a(t('Add a comment'), 'comment', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> </li> <li> diff --git a/app/Template/tasklink/create.php b/app/Template/tasklink/create.php new file mode 100644 index 00000000..fb438cd8 --- /dev/null +++ b/app/Template/tasklink/create.php @@ -0,0 +1,27 @@ +<div class="page-header"> + <h2><?= t('Add a new link') ?></h2> +</div> + +<form action="<?= $this->u('tasklink', 'save', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>" method="post" autocomplete="off"> + + <?= $this->formCsrf() ?> + <?= $this->formHidden('task_id', $values) ?> + <?= $this->formHidden('opposite_task_id', $values) ?> + + <?= $this->formLabel(t('Label'), 'link_id') ?> + <?= $this->formSelect('link_id', $labels, $values, $errors) ?> + + <?= $this->formLabel(t('Task'), 'title') ?> + <?= $this->formText( + 'title', + $values, + $errors, + array('required', 'data-dst-field="opposite_task_id"', 'data-search-url="'.$this->u('app', 'autocomplete', array('exclude_task_id' => $task['id'])).'"'), + 'task-autocomplete') ?> + + <div class="form-actions"> + <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> + <?= t('or') ?> + <?= $this->a(t('cancel'), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> + </div> +</form>
\ No newline at end of file diff --git a/app/Template/tasklink/remove.php b/app/Template/tasklink/remove.php new file mode 100644 index 00000000..9322ec24 --- /dev/null +++ b/app/Template/tasklink/remove.php @@ -0,0 +1,15 @@ +<div class="page-header"> + <h2><?= t('Remove a link') ?></h2> +</div> + +<div class="confirm"> + <p class="alert alert-info"> + <?= t('Do you really want to remove this link with task #%d?', $link['opposite_task_id']) ?> + </p> + + <div class="form-actions"> + <?= $this->a(t('Yes'), 'tasklink', 'remove', array('link_id' => $link['id'], 'task_id' => $task['id'], 'project_id' => $task['project_id']), true, 'btn btn-red') ?> + <?= t('or') ?> + <?= $this->a(t('cancel'), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> + </div> +</div>
\ No newline at end of file diff --git a/app/Template/tasklink/show.php b/app/Template/tasklink/show.php new file mode 100644 index 00000000..ca4e4383 --- /dev/null +++ b/app/Template/tasklink/show.php @@ -0,0 +1,41 @@ +<?php if (! empty($links)): ?> +<div class="page-header"> + <h2><?= t('Links') ?></h2> +</div> +<table class="table-fixed" id="links"> + <tr> + <th class="column-30"><?= t('Label') ?></th> + <th class="column-60"><?= t('Task') ?></th> + <?php if (! isset($not_editable)): ?> + <th><?= t('Action') ?></th> + <?php endif ?> + </tr> + <?php foreach ($links as $link): ?> + <tr> + <td><?= t('This task') ?> <strong><?= t($link['label']) ?></strong></td> + <?php if (! isset($not_editable)): ?> + <td> + <?= $this->a( + $this->e('#'.$link['task_id'].' - '.$link['title']), + 'task', 'show', array('task_id' => $link['task_id'], 'project_id' => $link['project_id']), + false, + $link['is_active'] ? '' : 'task-link-closed' + ) ?> + </td> + <td> + <?= $this->a(t('Remove'), 'tasklink', 'confirm', array('link_id' => $link['id'], 'task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> + </td> + <?php else: ?> + <td> + <?= $this->a( + $this->e('#'.$link['task_id'].' - '.$link['title']), + 'task', 'readonly', array('task_id' => $link['task_id'], 'token' => $project['token']), + false, + $link['is_active'] ? '' : 'task-link-closed' + ) ?> + </td> + <?php endif ?> + </tr> + <?php endforeach ?> +</table> +<?php endif ?>
\ No newline at end of file diff --git a/app/functions.php b/app/functions.php index d45e78e7..439375be 100644 --- a/app/functions.php +++ b/app/functions.php @@ -63,6 +63,9 @@ function dt($format, $timestamp) * @todo Improve this function * @return mixed */ -function p($value, $t1, $t2) { +function p($value, $t1, $t2) +{ return $value > 1 ? $t2 : $t1; } + + |