summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml22
-rw-r--r--README.markdown2
-rw-r--r--app/Console/Base.php1
-rw-r--r--app/Console/TransitionExport.php34
-rw-r--r--app/Controller/App.php6
-rw-r--r--app/Controller/Calendar.php4
-rw-r--r--app/Controller/Config.php19
-rw-r--r--app/Controller/Currency.php89
-rw-r--r--app/Controller/Export.php10
-rw-r--r--app/Controller/Swimlane.php2
-rw-r--r--app/Controller/Task.php15
-rw-r--r--app/Core/HttpClient.php68
-rw-r--r--app/Integration/Hipchat.php53
-rw-r--r--app/Integration/SlackWebhook.php43
-rw-r--r--app/Locale/da_DK/translations.php24
-rw-r--r--app/Locale/de_DE/translations.php172
-rw-r--r--app/Locale/es_ES/translations.php24
-rw-r--r--app/Locale/fi_FI/translations.php24
-rw-r--r--app/Locale/fr_FR/translations.php24
-rw-r--r--app/Locale/hu_HU/translations.php188
-rw-r--r--app/Locale/it_IT/translations.php24
-rw-r--r--app/Locale/ja_JP/translations.php24
-rw-r--r--app/Locale/nl_NL/translations.php24
-rw-r--r--app/Locale/pl_PL/translations.php24
-rw-r--r--app/Locale/pt_BR/translations.php118
-rw-r--r--app/Locale/ru_RU/translations.php228
-rw-r--r--app/Locale/sr_Latn_RS/translations.php24
-rw-r--r--app/Locale/sv_SE/translations.php24
-rw-r--r--app/Locale/th_TH/translations.php24
-rw-r--r--app/Locale/tr_TR/translations.php24
-rw-r--r--app/Locale/zh_CN/translations.php24
-rw-r--r--app/Model/Acl.php1
-rw-r--r--app/Model/Budget.php24
-rw-r--r--app/Model/Currency.php104
-rw-r--r--app/Model/ProjectActivity.php8
-rw-r--r--app/Model/Subtask.php1
-rw-r--r--app/Model/SubtaskForecast.php118
-rw-r--r--app/Model/TaskPosition.php3
-rw-r--r--app/Model/Transition.php170
-rw-r--r--app/Model/Webhook.php39
-rw-r--r--app/Schema/Mysql.php58
-rw-r--r--app/Schema/Postgres.php57
-rw-r--r--app/Schema/Sqlite.php55
-rw-r--r--app/ServiceProvider/ClassProvider.php6
-rw-r--r--app/ServiceProvider/EventDispatcherProvider.php2
-rw-r--r--app/Subscriber/ProjectActivitySubscriber.php27
-rw-r--r--app/Subscriber/TransitionSubscriber.php26
-rw-r--r--app/Template/app/subtasks.php4
-rw-r--r--app/Template/budget/breakdown.php2
-rw-r--r--app/Template/config/board.php1
-rw-r--r--app/Template/config/integrations.php38
-rw-r--r--app/Template/config/sidebar.php6
-rw-r--r--app/Template/currency/index.php56
-rw-r--r--app/Template/event/task_assignee_change.php16
-rw-r--r--app/Template/export/transitions.php26
-rw-r--r--app/Template/project/integrations.php2
-rw-r--r--app/Template/project/sidebar.php3
-rw-r--r--app/Template/task/sidebar.php3
-rw-r--r--app/Template/task/transitions.php26
-rw-r--r--assets/img/gitlab-icon.pngbin0 -> 661 bytes
-rw-r--r--assets/img/hipchat-icon.pngbin0 -> 1373 bytes
-rw-r--r--assets/js/app.js6
-rw-r--r--assets/js/src/base.js6
-rw-r--r--composer.json3
-rw-r--r--composer.lock43
-rw-r--r--docs/cli.markdown67
-rw-r--r--docs/hipchat.markdown31
-rw-r--r--docs/slack.markdown21
-rwxr-xr-xkanboard1
69 files changed, 2018 insertions, 428 deletions
diff --git a/.travis.yml b/.travis.yml
index 1596ec9a..398dda1b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,12 +1,20 @@
language: php
php:
- - "5.6"
- - "5.5"
- - "5.4"
- - "5.3"
+ - 7.0
+ - 5.6
+ - 5.5
+ - 5.4
+ - 5.3
-before_script: wget https://phar.phpunit.de/phpunit.phar
-script:
+matrix:
+ fast_finish: true
+ allow_failures:
+ - php: 7.0
+
+before_script:
- composer install
- - php phpunit.phar -c tests/units.sqlite.xml \ No newline at end of file
+
+script:
+ - phpunit -c tests/units.sqlite.xml
+
diff --git a/README.markdown b/README.markdown
index 52667d86..32a67c48 100644
--- a/README.markdown
+++ b/README.markdown
@@ -97,6 +97,8 @@ Documentation
- [Bitbucket webhooks](docs/bitbucket-webhooks.markdown)
- [Github webhooks](docs/github-webhooks.markdown)
- [Gitlab webhooks](docs/gitlab-webhooks.markdown)
+- [Hipchat](docs/hipchat.markdown)
+- [Slack](docs/slack.markdown)
#### More
diff --git a/app/Console/Base.php b/app/Console/Base.php
index aeafbefc..07243080 100644
--- a/app/Console/Base.php
+++ b/app/Console/Base.php
@@ -20,6 +20,7 @@ use Symfony\Component\Console\Command\Command;
* @property \Model\Task $task
* @property \Model\TaskExport $taskExport
* @property \Model\TaskFinder $taskFinder
+ * @property \Model\Transition $transition
*/
abstract class Base extends Command
{
diff --git a/app/Console/TransitionExport.php b/app/Console/TransitionExport.php
new file mode 100644
index 00000000..ad988c54
--- /dev/null
+++ b/app/Console/TransitionExport.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Console;
+
+use Core\Tool;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class TransitionExport extends Base
+{
+ protected function configure()
+ {
+ $this
+ ->setName('export:transitions')
+ ->setDescription('Task transitions CSV export')
+ ->addArgument('project_id', InputArgument::REQUIRED, 'Project id')
+ ->addArgument('start_date', InputArgument::REQUIRED, 'Start date (YYYY-MM-DD)')
+ ->addArgument('end_date', InputArgument::REQUIRED, 'End date (YYYY-MM-DD)');
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $data = $this->transition->export(
+ $input->getArgument('project_id'),
+ $input->getArgument('start_date'),
+ $input->getArgument('end_date')
+ );
+
+ if (is_array($data)) {
+ Tool::csv($data);
+ }
+ }
+}
diff --git a/app/Controller/App.php b/app/Controller/App.php
index 46731e7c..ebe39670 100644
--- a/app/Controller/App.php
+++ b/app/Controller/App.php
@@ -46,21 +46,21 @@ class App extends Base
$project_ids = array_keys($projects);
$task_paginator = $this->paginator
- ->setUrl('app', $action, array('pagination' => 'tasks'))
+ ->setUrl('app', $action, array('pagination' => 'tasks', 'user_id' => $user_id))
->setMax(10)
->setOrder('tasks.id')
->setQuery($this->taskFinder->getUserQuery($user_id))
->calculateOnlyIf($this->request->getStringParam('pagination') === 'tasks');
$subtask_paginator = $this->paginator
- ->setUrl('app', $action, array('pagination' => 'subtasks'))
+ ->setUrl('app', $action, array('pagination' => 'subtasks', 'user_id' => $user_id))
->setMax(10)
->setOrder('tasks.id')
->setQuery($this->subtask->getUserQuery($user_id, $status))
->calculateOnlyIf($this->request->getStringParam('pagination') === 'subtasks');
$project_paginator = $this->paginator
- ->setUrl('app', $action, array('pagination' => 'projects'))
+ ->setUrl('app', $action, array('pagination' => 'projects', 'user_id' => $user_id))
->setMax(10)
->setOrder('name')
->setQuery($this->project->getQueryColumnStats($project_ids))
diff --git a/app/Controller/Calendar.php b/app/Controller/Calendar.php
index 6cfa2bad..49c7f56e 100644
--- a/app/Controller/Calendar.php
+++ b/app/Controller/Calendar.php
@@ -82,7 +82,9 @@ class Calendar extends Base
$subtask_timeslots = $this->subtaskTimeTracking->getUserCalendarEvents($user_id, $start, $end);
- $this->response->json(array_merge($due_tasks, $subtask_timeslots));
+ $subtask_forcast = $this->config->get('subtask_forecast') == 1 ? $this->subtaskForecast->getCalendarEvents($user_id, $end) : array();
+
+ $this->response->json(array_merge($due_tasks, $subtask_timeslots, $subtask_forcast));
}
/**
diff --git a/app/Controller/Config.php b/app/Controller/Config.php
index bee897be..bb6e860a 100644
--- a/app/Controller/Config.php
+++ b/app/Controller/Config.php
@@ -41,7 +41,10 @@ class Config extends Base
$values = $this->request->getValues();
if ($redirect === 'board') {
- $values += array('subtask_restriction' => 0, 'subtask_time_tracking' => 0);
+ $values += array('subtask_restriction' => 0, 'subtask_time_tracking' => 0, 'subtask_forecast' => 0);
+ }
+ else if ($redirect === 'integrations') {
+ $values += array('integration_slack_webhook' => 0, 'integration_hipchat' => 0);
}
if ($this->config->save($values)) {
@@ -102,6 +105,20 @@ class Config extends Base
}
/**
+ * Display the integration settings page
+ *
+ * @access public
+ */
+ public function integrations()
+ {
+ $this->common('integrations');
+
+ $this->response->html($this->layout('config/integrations', array(
+ 'title' => t('Settings').' &gt; '.t('Integrations'),
+ )));
+ }
+
+ /**
* Display the webhook settings page
*
* @access public
diff --git a/app/Controller/Currency.php b/app/Controller/Currency.php
new file mode 100644
index 00000000..fac34a30
--- /dev/null
+++ b/app/Controller/Currency.php
@@ -0,0 +1,89 @@
+<?php
+
+namespace Controller;
+
+/**
+ * Currency controller
+ *
+ * @package controller
+ * @author Frederic Guillot
+ */
+class Currency 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);
+ }
+
+ /**
+ * Display all currency rates and form
+ *
+ * @access public
+ */
+ public function index(array $values = array(), array $errors = array())
+ {
+ $this->response->html($this->layout('currency/index', array(
+ 'config_values' => array('application_currency' => $this->config->get('application_currency')),
+ 'values' => $values,
+ 'errors' => $errors,
+ 'rates' => $this->currency->getAll(),
+ 'currencies' => $this->config->getCurrencies(),
+ 'title' => t('Settings').' &gt; '.t('Currency rates'),
+ )));
+ }
+
+ /**
+ * Validate and save a new currency rate
+ *
+ * @access public
+ */
+ public function create()
+ {
+ $values = $this->request->getValues();
+ list($valid, $errors) = $this->currency->validate($values);
+
+ if ($valid) {
+
+ if ($this->currency->create($values['currency'], $values['rate'])) {
+ $this->session->flash(t('The currency rate have been added successfully.'));
+ $this->response->redirect($this->helper->url('currency', 'index'));
+ }
+ else {
+ $this->session->flashError(t('Unable to add this currency rate.'));
+ }
+ }
+
+ $this->index($values, $errors);
+ }
+
+ /**
+ * Save reference currency
+ *
+ * @access public
+ */
+ public function reference()
+ {
+ $values = $this->request->getValues();
+
+ if ($this->config->save($values)) {
+ $this->config->reload();
+ $this->session->flash(t('Settings saved successfully.'));
+ }
+ else {
+ $this->session->flashError(t('Unable to save your settings.'));
+ }
+
+ $this->response->redirect($this->helper->url('currency', 'index'));
+ }
+}
diff --git a/app/Controller/Export.php b/app/Controller/Export.php
index 1997a4ea..b8f932c1 100644
--- a/app/Controller/Export.php
+++ b/app/Controller/Export.php
@@ -72,4 +72,14 @@ class Export extends Base
{
$this->common('projectDailySummary', 'getAggregatedMetrics', t('Summary'), 'summary', t('Daily project summary export'));
}
+
+ /**
+ * Transition export
+ *
+ * @access public
+ */
+ public function transitions()
+ {
+ $this->common('transition', 'export', t('Transitions'), 'transitions', t('Task transitions export'));
+ }
}
diff --git a/app/Controller/Swimlane.php b/app/Controller/Swimlane.php
index de2f1f12..e10d21f1 100644
--- a/app/Controller/Swimlane.php
+++ b/app/Controller/Swimlane.php
@@ -86,7 +86,7 @@ class Swimlane extends Base
{
$project = $this->getProject();
- $values = $this->request->getValues();
+ $values = $this->request->getValues() + array('show_default_swimlane' => 0);
list($valid,) = $this->swimlane->validateDefaultModification($values);
if ($valid) {
diff --git a/app/Controller/Task.php b/app/Controller/Task.php
index ace40a01..64017582 100644
--- a/app/Controller/Task.php
+++ b/app/Controller/Task.php
@@ -526,4 +526,19 @@ class Task extends Base
'subtask_paginator' => $subtask_paginator,
)));
}
+
+ /**
+ * Display the task transitions
+ *
+ * @access public
+ */
+ public function transitions()
+ {
+ $task = $this->getTask();
+
+ $this->response->html($this->taskLayout('task/transitions', array(
+ 'task' => $task,
+ 'transitions' => $this->transition->getAllByTask($task['id']),
+ )));
+ }
}
diff --git a/app/Core/HttpClient.php b/app/Core/HttpClient.php
new file mode 100644
index 00000000..e1d90858
--- /dev/null
+++ b/app/Core/HttpClient.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace Core;
+
+/**
+ * HTTP client
+ *
+ * @package core
+ * @author Frederic Guillot
+ */
+class HttpClient
+{
+ /**
+ * HTTP connection timeout in seconds
+ *
+ * @var integer
+ */
+ const HTTP_TIMEOUT = 2;
+
+ /**
+ * Number of maximum redirections for the HTTP client
+ *
+ * @var integer
+ */
+ const HTTP_MAX_REDIRECTS = 2;
+
+ /**
+ * HTTP client user agent
+ *
+ * @var string
+ */
+ const HTTP_USER_AGENT = 'Kanboard Webhook';
+
+ /**
+ * Send a POST HTTP request
+ *
+ * @static
+ * @access public
+ * @param string $url
+ * @param array $data
+ * @return string
+ */
+ public static function post($url, array $data)
+ {
+ if (empty($url)) {
+ return '';
+ }
+
+ $headers = array(
+ 'User-Agent: '.self::HTTP_USER_AGENT,
+ 'Content-Type: application/json',
+ 'Connection: close',
+ );
+
+ $context = stream_context_create(array(
+ 'http' => array(
+ 'method' => 'POST',
+ 'protocol_version' => 1.1,
+ 'timeout' => self::HTTP_TIMEOUT,
+ 'max_redirects' => self::HTTP_MAX_REDIRECTS,
+ 'header' => implode("\r\n", $headers),
+ 'content' => json_encode($data)
+ )
+ ));
+
+ return @file_get_contents(trim($url), false, $context);
+ }
+}
diff --git a/app/Integration/Hipchat.php b/app/Integration/Hipchat.php
new file mode 100644
index 00000000..036925f7
--- /dev/null
+++ b/app/Integration/Hipchat.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace Integration;
+
+/**
+ * Hipchat Webhook
+ *
+ * @package integration
+ * @author Frederic Guillot
+ */
+class Hipchat extends Base
+{
+ /**
+ * Send message to the Hipchat room
+ *
+ * @access public
+ * @param integer $project_id Project id
+ * @param integer $task_id Task id
+ * @param string $event_name Event name
+ * @param array $data Event data
+ */
+ public function notify($project_id, $task_id, $event_name, array $event)
+ {
+ $project = $this->project->getbyId($project_id);
+
+ $event['event_name'] = $event_name;
+ $event['author'] = $this->user->getFullname($this->session['user']);
+
+ $html = '<img src="http://kanboard.net/assets/img/favicon-32x32.png"/>';
+ $html .= '<strong>'.$project['name'].'</strong><br/>';
+ $html .= $this->projectActivity->getTitle($event);
+
+ if ($this->config->get('application_url')) {
+ $html .= '<br/><a href="'.$this->config->get('application_url');
+ $html .= $this->helper->u('task', 'show', array('task_id' => $task_id, 'project_id' => $project_id)).'">';
+ $html .= t('view the task on Kanboard').'</a>';
+ }
+
+ $payload = array(
+ 'message' => $html,
+ 'color' => 'yellow',
+ );
+
+ $url = sprintf(
+ '%s/v2/room/%s/notification?auth_token=%s',
+ $this->config->get('integration_hipchat_api_url'),
+ $this->config->get('integration_hipchat_room_id'),
+ $this->config->get('integration_hipchat_room_token')
+ );
+
+ $this->httpClient->post($url, $payload);
+ }
+}
diff --git a/app/Integration/SlackWebhook.php b/app/Integration/SlackWebhook.php
new file mode 100644
index 00000000..fc7daeb4
--- /dev/null
+++ b/app/Integration/SlackWebhook.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace Integration;
+
+/**
+ * Slack Webhook
+ *
+ * @package integration
+ * @author Frederic Guillot
+ */
+class SlackWebhook extends Base
+{
+ /**
+ * Send message to the incoming Slack webhook
+ *
+ * @access public
+ * @param integer $project_id Project id
+ * @param integer $task_id Task id
+ * @param string $event_name Event name
+ * @param array $data Event data
+ */
+ public function notify($project_id, $task_id, $event_name, array $event)
+ {
+ $project = $this->project->getbyId($project_id);
+
+ $event['event_name'] = $event_name;
+ $event['author'] = $this->user->getFullname($this->session['user']);
+
+ $payload = array(
+ 'text' => '*['.$project['name'].']* '.str_replace('&quot;', '"', $this->projectActivity->getTitle($event)),
+ 'username' => 'Kanboard',
+ 'icon_url' => 'http://kanboard.net/assets/img/favicon.png',
+ );
+
+ if ($this->config->get('application_url')) {
+ $payload['text'] .= ' - <'.$this->config->get('application_url');
+ $payload['text'] .= $this->helper->u('task', 'show', array('task_id' => $task_id, 'project_id' => $project_id));
+ $payload['text'] .= '|'.t('view the task on Kanboard').'>';
+ }
+
+ $this->httpClient->post($this->config->get('integration_slack_webhook_url'), $payload);
+ }
+}
diff --git a/app/Locale/da_DK/translations.php b/app/Locale/da_DK/translations.php
index c12e6d9b..a13aa3af 100644
--- a/app/Locale/da_DK/translations.php
+++ b/app/Locale/da_DK/translations.php
@@ -808,4 +808,28 @@ return array(
// 'Move the task to another column when assigned to a user' => '',
// 'Move the task to another column when assignee is cleared' => '',
// 'Source column' => '',
+ // 'Show subtask estimates in the user calendar' => '',
+ // 'Transitions' => '',
+ // 'Executer' => '',
+ // 'Time spent in the column' => '',
+ // 'Task transitions' => '',
+ // 'Task transitions export' => '',
+ // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '',
+ // 'Currency rates' => '',
+ // 'Rate' => '',
+ // 'Change reference currency' => '',
+ // 'Add a new currency rate' => '',
+ // 'Currency rates are used to calculate project budget.' => '',
+ // 'Reference currency' => '',
+ // 'The currency rate have been added successfully.' => '',
+ // 'Unable to add this currency rate.' => '',
+ // 'Send notifications to a Slack channel' => '',
+ // 'Webhook URL' => '',
+ // 'Help on Slack integration' => '',
+ // '%s remove the assignee of the task %s' => '',
+ // 'Send notifications to Hipchat' => '',
+ // 'API URL' => '',
+ // 'Room API ID or name' => '',
+ // 'Room notification token' => '',
+ // 'Help on Hipchat integration' => '',
);
diff --git a/app/Locale/de_DE/translations.php b/app/Locale/de_DE/translations.php
index 3a3d145e..2043215b 100644
--- a/app/Locale/de_DE/translations.php
+++ b/app/Locale/de_DE/translations.php
@@ -1,8 +1,8 @@
<?php
return array(
- // 'number.decimals_separator' => '',
- // 'number.thousands_separator' => '',
+ 'number.decimals_separator' => ',',
+ 'number.thousands_separator' => '.',
'None' => 'Keines',
'edit' => 'Bearbeiten',
'Edit' => 'Bearbeiten',
@@ -410,13 +410,13 @@ return array(
'Comment updated' => 'Kommentar wurde aktualisiert',
'New comment posted by %s' => 'Neuer Kommentar verfasst durch %s',
'List of due tasks for the project "%s"' => 'Liste der fälligen Aufgaben für das Projekt "%s"',
- // 'New attachment' => '',
- // 'New comment' => '',
- // 'New subtask' => '',
- // 'Subtask updated' => '',
- // 'Task updated' => '',
- // 'Task closed' => '',
- // 'Task opened' => '',
+ 'New attachment' => 'Neuer Anhang',
+ 'New comment' => 'Neuer Kommentar',
+ 'New subtask' => 'Neue Teilaufgabe',
+ 'Subtask updated' => 'Teilaufgabe aktualisiert',
+ 'Task updated' => 'Aufgabe aktualisiert',
+ 'Task closed' => 'Aufgabe geschlossen',
+ 'Task opened' => 'Aufgabe geöffnet',
'[%s][Due tasks]' => '[%s][Fällige Aufgaben]',
'[Kanboard] Notification' => '[Kanboard] Benachrichtigung',
'I want to receive notifications only for those projects:' => 'Ich möchte nur für diese Projekte Benachrichtigungen erhalten:',
@@ -500,9 +500,9 @@ return array(
'Task assignee change' => 'Zuständigkeit geändert',
'%s change the assignee of the task #%d to %s' => '%s hat die Zusständigkeit der Aufgabe #%d geändert um %s',
'%s changed the assignee of the task %s to %s' => '%s hat die Zuständigkeit der Aufgabe %s geändert um %s',
- // 'Column Change' => '',
- // 'Position Change' => '',
- // 'Assignee Change' => '',
+ 'Column Change' => 'Spalte ändern',
+ 'Position Change' => 'Position ändern',
+ 'Assignee Change' => 'Zuordnung ändern',
'New password for the user "%s"' => 'Neues Passwort des Benutzers "%s"',
'Choose an event' => 'Aktion wählen',
'Github commit received' => 'Github commit empfangen',
@@ -736,76 +736,100 @@ return array(
'Filter recently updated' => 'Zuletzt geänderte anzeigen',
'since %B %e, %Y at %k:%M %p' => 'seit %B %e, %Y um %k:%M %p',
'More filters' => 'Mehr Filter',
- // 'Compact view' => '',
- // 'Horizontal scrolling' => '',
- // 'Compact/wide view' => '',
- // 'No results match:' => '',
- // 'Remove hourly rate' => '',
- // 'Do you really want to remove this hourly rate?' => '',
- // 'Hourly rates' => '',
- // 'Hourly rate' => '',
- // 'Currency' => '',
- // 'Effective date' => '',
- // 'Add new rate' => '',
- // 'Rate removed successfully.' => '',
- // 'Unable to remove this rate.' => '',
- // 'Unable to save the hourly rate.' => '',
- // 'Hourly rate created successfully.' => '',
- // 'Start time' => '',
- // 'End time' => '',
- // 'Comment' => '',
- // 'All day' => '',
- // 'Day' => '',
- // 'Manage timetable' => '',
- // 'Overtime timetable' => '',
- // 'Time off timetable' => '',
- // 'Timetable' => '',
- // 'Work timetable' => '',
- // 'Week timetable' => '',
- // 'Day timetable' => '',
- // 'From' => '',
- // 'To' => '',
- // 'Time slot created successfully.' => '',
- // 'Unable to save this time slot.' => '',
- // 'Time slot removed successfully.' => '',
- // 'Unable to remove this time slot.' => '',
- // 'Do you really want to remove this time slot?' => '',
- // 'Remove time slot' => '',
- // 'Add new time slot' => '',
- // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '',
- // 'Files' => '',
- // 'Images' => '',
- // 'Private project' => '',
- // 'Amount' => '',
+ 'Compact view' => 'Kompaktansicht',
+ 'Horizontal scrolling' => 'Horizontales Scrollen',
+ 'Compact/wide view' => 'Kompakt/Breite-Ansicht',
+ 'No results match:' => 'Keine Ergebnisse:',
+ 'Remove hourly rate' => 'Stundensatz entfernen',
+ 'Do you really want to remove this hourly rate?' => 'Diesen Stundensatz wirklich entfernen?',
+ 'Hourly rates' => 'Stundensätze',
+ 'Hourly rate' => 'Stundensatz',
+ 'Currency' => 'Währung',
+ 'Effective date' => 'Inkraftsetzung',
+ 'Add new rate' => 'Neue Rate hinzufügen',
+ 'Rate removed successfully.' => 'Rate erfolgreich entfernt',
+ 'Unable to remove this rate.' => 'Nicht in der Lage, diese Rate zu entfernen.',
+ 'Unable to save the hourly rate.' => 'Nicht in der Lage, diese Rate zu speichern',
+ 'Hourly rate created successfully.' => 'Stundensatz erfolgreich angelegt.',
+ 'Start time' => 'Startzeit',
+ 'End time' => 'Endzeit',
+ 'Comment' => 'Kommentar',
+ 'All day' => 'ganztägig',
+ 'Day' => 'Tag',
+ 'Manage timetable' => 'Zeitplan verwalten',
+ 'Overtime timetable' => 'Überstunden Zeitplan',
+ 'Time off timetable' => 'Freizeit Zeitplan',
+ 'Timetable' => 'Zeitplan',
+ 'Work timetable' => 'Arbeitszeitplan',
+ 'Week timetable' => 'Wochenzeitplan',
+ 'Day timetable' => 'Tageszeitplan',
+ 'From' => 'von',
+ 'To' => 'bis',
+ 'Time slot created successfully.' => 'Zeitfenster erfolgreich erstellt.',
+ 'Unable to save this time slot.' => 'Nicht in der Lage, dieses Zeitfenster zu speichern.',
+ 'Time slot removed successfully.' => 'Zeitfenster erfolgreich entfernt.',
+ 'Unable to remove this time slot.' => 'Nicht in der Lage, dieses Zeitfenster zu entfernen',
+ 'Do you really want to remove this time slot?' => 'Soll diese Zeitfenster wirklich gelöscht werden?',
+ 'Remove time slot' => 'Zeitfenster entfernen',
+ 'Add new time slot' => 'Neues Zeitfenster hinzufügen',
+ 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => 'Dieses Zeitfenster wird verwendet, wenn die Checkbox "gantägig" für Freizeit und Überstunden angeklickt ist.',
+ 'Files' => 'Dateien',
+ 'Images' => 'Bilder',
+ 'Private project' => 'privates Projekt',
+ 'Amount' => 'Betrag',
// 'AUD - Australian Dollar' => '',
- // 'Budget' => '',
- // 'Budget line' => '',
- // 'Budget line removed successfully.' => '',
- // 'Budget lines' => '',
+ 'Budget' => 'Budget',
+ 'Budget line' => 'Budgetlinie',
+ 'Budget line removed successfully.' => 'Budgetlinie erfolgreich entfernt',
+ 'Budget lines' => 'Budgetlinien',
// 'CAD - Canadian Dollar' => '',
// 'CHF - Swiss Francs' => '',
- // 'Cost' => '',
- // 'Cost breakdown' => '',
- // 'Custom Stylesheet' => '',
- // 'download' => '',
- // 'Do you really want to remove this budget line?' => '',
+ 'Cost' => 'Kosten',
+ 'Cost breakdown' => 'Kostenaufschlüsselung',
+ 'Custom Stylesheet' => 'benutzerdefiniertes Stylesheet',
+ 'download' => 'Download',
+ 'Do you really want to remove this budget line?' => 'Soll diese Budgetlinie wirklich entfernt werden?',
// 'EUR - Euro' => '',
- // 'Expenses' => '',
+ 'Expenses' => 'Kosten',
// 'GBP - British Pound' => '',
// 'INR - Indian Rupee' => '',
// 'JPY - Japanese Yen' => '',
- // 'New budget line' => '',
+ 'New budget line' => 'Neue Budgetlinie',
// 'NZD - New Zealand Dollar' => '',
- // 'Remove a budget line' => '',
- // 'Remove budget line' => '',
+ 'Remove a budget line' => 'Budgetlinie entfernen',
+ 'Remove budget line' => 'Budgetlinie entfernen',
// 'RSD - Serbian dinar' => '',
- // 'The budget line have been created successfully.' => '',
- // 'Unable to create the budget line.' => '',
- // 'Unable to remove this budget line.' => '',
+ 'The budget line have been created successfully.' => 'Die Budgetlinie wurde erfolgreich angelegt.',
+ 'Unable to create the budget line.' => 'Budgetlinie konnte nicht erstellt werden.',
+ 'Unable to remove this budget line.' => 'Budgetlinie konnte nicht gelöscht werden.',
// 'USD - US Dollar' => '',
- // 'Remaining' => '',
- // 'Destination column' => '',
- // 'Move the task to another column when assigned to a user' => '',
- // 'Move the task to another column when assignee is cleared' => '',
- // 'Source column' => '',
+ 'Remaining' => 'Verbleibend',
+ 'Destination column' => 'Zielspalte',
+ 'Move the task to another column when assigned to a user' => 'Aufgabe in eine andere Spalte verschieben, wenn ein User zugeordnet wurde.',
+ 'Move the task to another column when assignee is cleared' => 'Aufgabe in eine andere Spalte verschieben, wenn die Zuordnung gelöscht wurde.',
+ 'Source column' => 'Quellspalte',
+ 'Show subtask estimates in the user calendar' => 'Teilaufgabenschätzung in Benutzerkalender anzeigen.',
+ 'Transitions' => 'Übergänge',
+ 'Executer' => 'Ausführender',
+ 'Time spent in the column' => 'Zeit in Spalte verbracht',
+ 'Task transitions' => 'Aufgaben Übergänge',
+ 'Task transitions export' => 'Aufgaben Übergänge exportieren',
+ 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => 'Diese Auswertung enthält alle Spaltenbewegungen für jede Aufgabe mit Datum, Benutzer und Zeit vor jedem Wechsel.',
+ 'Currency rates' => 'Währungskurse',
+ 'Rate' => 'Kurse',
+ 'Change reference currency' => 'Referenzwährung ändern',
+ 'Add a new currency rate' => 'Neuen Währungskurs hinzufügen',
+ 'Currency rates are used to calculate project budget.' => 'Währungskurse werden verwendet um das Projektbudget zu berechnen.',
+ 'Reference currency' => 'Referenzwährung',
+ 'The currency rate have been added successfully.' => 'Der Währungskurs wurde erfolgreich hinzugefügt.',
+ 'Unable to add this currency rate.' => 'Währungskurs konnte nicht hinzugefügt werden',
+ 'Send notifications to a Slack channel' => 'Benachrichtigung an einen Slack-Kanal senden',
+ 'Webhook URL' => 'Webhook URL',
+ 'Help on Slack integration' => 'Hilfe für Slack integration.',
+ '%s remove the assignee of the task %s' => '%s Zuordnung für die Aufgabe %s entfernen',
+ // 'Send notifications to Hipchat' => '',
+ // 'API URL' => '',
+ // 'Room API ID or name' => '',
+ // 'Room notification token' => '',
+ // 'Help on Hipchat integration' => '',
);
diff --git a/app/Locale/es_ES/translations.php b/app/Locale/es_ES/translations.php
index 00b342dd..33c062c6 100644
--- a/app/Locale/es_ES/translations.php
+++ b/app/Locale/es_ES/translations.php
@@ -808,4 +808,28 @@ return array(
// 'Move the task to another column when assigned to a user' => '',
// 'Move the task to another column when assignee is cleared' => '',
// 'Source column' => '',
+ // 'Show subtask estimates in the user calendar' => '',
+ // 'Transitions' => '',
+ // 'Executer' => '',
+ // 'Time spent in the column' => '',
+ // 'Task transitions' => '',
+ // 'Task transitions export' => '',
+ // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '',
+ // 'Currency rates' => '',
+ // 'Rate' => '',
+ // 'Change reference currency' => '',
+ // 'Add a new currency rate' => '',
+ // 'Currency rates are used to calculate project budget.' => '',
+ // 'Reference currency' => '',
+ // 'The currency rate have been added successfully.' => '',
+ // 'Unable to add this currency rate.' => '',
+ // 'Send notifications to a Slack channel' => '',
+ // 'Webhook URL' => '',
+ // 'Help on Slack integration' => '',
+ // '%s remove the assignee of the task %s' => '',
+ // 'Send notifications to Hipchat' => '',
+ // 'API URL' => '',
+ // 'Room API ID or name' => '',
+ // 'Room notification token' => '',
+ // 'Help on Hipchat integration' => '',
);
diff --git a/app/Locale/fi_FI/translations.php b/app/Locale/fi_FI/translations.php
index d1ae2412..856914ad 100644
--- a/app/Locale/fi_FI/translations.php
+++ b/app/Locale/fi_FI/translations.php
@@ -808,4 +808,28 @@ return array(
// 'Move the task to another column when assigned to a user' => '',
// 'Move the task to another column when assignee is cleared' => '',
// 'Source column' => '',
+ // 'Show subtask estimates in the user calendar' => '',
+ // 'Transitions' => '',
+ // 'Executer' => '',
+ // 'Time spent in the column' => '',
+ // 'Task transitions' => '',
+ // 'Task transitions export' => '',
+ // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '',
+ // 'Currency rates' => '',
+ // 'Rate' => '',
+ // 'Change reference currency' => '',
+ // 'Add a new currency rate' => '',
+ // 'Currency rates are used to calculate project budget.' => '',
+ // 'Reference currency' => '',
+ // 'The currency rate have been added successfully.' => '',
+ // 'Unable to add this currency rate.' => '',
+ // 'Send notifications to a Slack channel' => '',
+ // 'Webhook URL' => '',
+ // 'Help on Slack integration' => '',
+ // '%s remove the assignee of the task %s' => '',
+ // 'Send notifications to Hipchat' => '',
+ // 'API URL' => '',
+ // 'Room API ID or name' => '',
+ // 'Room notification token' => '',
+ // 'Help on Hipchat integration' => '',
);
diff --git a/app/Locale/fr_FR/translations.php b/app/Locale/fr_FR/translations.php
index 340d16b1..d389b0e1 100644
--- a/app/Locale/fr_FR/translations.php
+++ b/app/Locale/fr_FR/translations.php
@@ -810,4 +810,28 @@ return array(
'Move the task to another column when assigned to a user' => 'Déplacer la tâche dans une autre colonne lorsque celle-ci est assignée à quelqu\'un',
'Move the task to another column when assignee is cleared' => 'Déplacer la tâche dans une autre colonne lorsque celle-ci n\'est plus assignée',
'Source column' => 'Colonne d\'origine',
+ 'Show subtask estimates in the user calendar' => 'Afficher le temps estimé des sous-tâches dans le calendrier utilisateur',
+ 'Transitions' => 'Transitions',
+ 'Executer' => 'Exécutant',
+ 'Time spent in the column' => 'Temps passé dans la colonne',
+ 'Task transitions' => 'Transitions des tâches',
+ 'Task transitions export' => 'Export des transitions des tâches',
+ 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => 'Ce rapport contient tous les mouvements de colonne pour chaque tâche avec la date, l\'utilisateur et le temps passé pour chaque transition.',
+ 'Currency rates' => 'Taux de change des devises',
+ 'Rate' => 'Taux',
+ 'Change reference currency' => 'Changer la monnaie de référence',
+ 'Add a new currency rate' => 'Ajouter un nouveau taux pour une devise',
+ 'Currency rates are used to calculate project budget.' => 'Le cours des devises est utilisé pour calculer le budget des projets.',
+ 'Reference currency' => 'Devise de référence',
+ 'The currency rate have been added successfully.' => 'Le taux de change a été ajouté avec succès.',
+ 'Unable to add this currency rate.' => 'Impossible d\'ajouter ce taux de change',
+ 'Send notifications to a Slack channel' => 'Envoyer les notifications sur un salon de discussion Slack',
+ 'Webhook URL' => 'URL du webhook',
+ 'Help on Slack integration' => 'Aide sur l\'intégration avec Slack',
+ '%s remove the assignee of the task %s' => '%s a enlevé la personne assignée à la tâche %s',
+ 'Send notifications to Hipchat' => 'Envoyer les notifications vers Hipchat',
+ 'API URL' => 'URL de l\'api',
+ 'Room API ID or name' => 'Nom ou identifiant du salon de discussion',
+ 'Room notification token' => 'Jeton de sécurité du salon de discussion',
+ 'Help on Hipchat integration' => 'Aide sur l\'intégration avec Hipchat',
);
diff --git a/app/Locale/hu_HU/translations.php b/app/Locale/hu_HU/translations.php
index a8d5b637..29792417 100644
--- a/app/Locale/hu_HU/translations.php
+++ b/app/Locale/hu_HU/translations.php
@@ -1,13 +1,13 @@
<?php
return array(
- // 'number.decimals_separator' => '',
- // 'number.thousands_separator' => '',
+ 'number.decimals_separator' => ',',
+ 'number.thousands_separator' => ' ',
'None' => 'Nincs',
'edit' => 'szerkesztés',
'Edit' => 'Szerkesztés',
- 'remove' => 'eltávolítás',
- 'Remove' => 'Eltávolítás',
+ 'remove' => 'törlés',
+ 'Remove' => 'Törlés',
'Update' => 'Frissítés',
'Yes' => 'Igen',
'No' => 'Nem',
@@ -437,7 +437,7 @@ return array(
'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',
+ 'Duplicate a task' => 'Feladat másolása',
'External accounts' => 'Külső fiókok',
'Account type' => 'Fiók típusa',
'Local' => 'Helyi',
@@ -519,7 +519,7 @@ return array(
'Label' => 'Címke',
'Database' => 'Adatbázis',
'About' => 'Kanboard információ',
- 'Database driver:' => 'Adatbázis driver:',
+ 'Database driver:' => 'Adatbázis motor:',
'Board settings' => 'Tábla beállítások',
'URL and token' => 'URL és tokenek',
'Webhook settings' => 'Webhook beállítások',
@@ -576,8 +576,8 @@ return array(
'My subtasks' => 'Részfeladataim',
'User repartition' => 'Felhasználó újrafelosztás',
'User repartition for "%s"' => 'Felhasználó újrafelosztás: %s',
- 'Clone this project' => 'Projekt megkettőzése',
- 'Column removed successfully.' => 'Oszlop sikeresen eltávolítva.',
+ 'Clone this project' => 'Projekt másolása',
+ 'Column removed successfully.' => 'Oszlop sikeresen törölve.',
'Edit Project' => 'Projekt szerkesztése',
'Github Issue' => 'Github issue',
'Not enough data to show the graph.' => 'Nincs elég adat a grafikonhoz.',
@@ -671,7 +671,7 @@ return array(
'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?',
+ 'Which parts of the project do you want to duplicate?' => 'A projekt mely részeit szeretné másolni?',
'Change dashboard view' => 'Vezérlőpult megjelenés változtatás',
'Show/hide activities' => 'Tevékenységek megjelenítése/elrejtése',
'Show/hide projects' => 'Projektek megjelenítése/elrejtése',
@@ -730,82 +730,106 @@ return array(
'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ó',
+ 'Keyboard shortcuts' => 'Billentyű kombinációk',
'Open board switcher' => 'Tábla választó lenyitása',
'Application' => 'Alkalmazás',
'Filter recently updated' => 'Szűrés az utolsó módosítás ideje szerint',
'since %B %e, %Y at %k:%M %p' => '%Y. %m. %d. %H:%M óta',
'More filters' => 'További szűrők',
- // 'Compact view' => '',
- // 'Horizontal scrolling' => '',
- // 'Compact/wide view' => '',
- // 'No results match:' => '',
- // 'Remove hourly rate' => '',
- // 'Do you really want to remove this hourly rate?' => '',
- // 'Hourly rates' => '',
- // 'Hourly rate' => '',
- // 'Currency' => '',
- // 'Effective date' => '',
- // 'Add new rate' => '',
- // 'Rate removed successfully.' => '',
- // 'Unable to remove this rate.' => '',
- // 'Unable to save the hourly rate.' => '',
- // 'Hourly rate created successfully.' => '',
- // 'Start time' => '',
- // 'End time' => '',
- // 'Comment' => '',
- // 'All day' => '',
- // 'Day' => '',
- // 'Manage timetable' => '',
- // 'Overtime timetable' => '',
- // 'Time off timetable' => '',
- // 'Timetable' => '',
- // 'Work timetable' => '',
- // 'Week timetable' => '',
- // 'Day timetable' => '',
- // 'From' => '',
- // 'To' => '',
- // 'Time slot created successfully.' => '',
- // 'Unable to save this time slot.' => '',
- // 'Time slot removed successfully.' => '',
- // 'Unable to remove this time slot.' => '',
- // 'Do you really want to remove this time slot?' => '',
- // 'Remove time slot' => '',
- // 'Add new time slot' => '',
- // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '',
- // 'Files' => '',
- // 'Images' => '',
- // 'Private project' => '',
- // 'Amount' => '',
- // 'AUD - Australian Dollar' => '',
- // 'Budget' => '',
- // 'Budget line' => '',
- // 'Budget line removed successfully.' => '',
- // 'Budget lines' => '',
- // 'CAD - Canadian Dollar' => '',
- // 'CHF - Swiss Francs' => '',
- // 'Cost' => '',
- // 'Cost breakdown' => '',
- // 'Custom Stylesheet' => '',
- // 'download' => '',
- // 'Do you really want to remove this budget line?' => '',
- // 'EUR - Euro' => '',
- // 'Expenses' => '',
- // 'GBP - British Pound' => '',
- // 'INR - Indian Rupee' => '',
- // 'JPY - Japanese Yen' => '',
- // 'New budget line' => '',
- // 'NZD - New Zealand Dollar' => '',
- // 'Remove a budget line' => '',
- // 'Remove budget line' => '',
- // 'RSD - Serbian dinar' => '',
- // 'The budget line have been created successfully.' => '',
- // 'Unable to create the budget line.' => '',
- // 'Unable to remove this budget line.' => '',
- // 'USD - US Dollar' => '',
- // 'Remaining' => '',
- // 'Destination column' => '',
- // 'Move the task to another column when assigned to a user' => '',
- // 'Move the task to another column when assignee is cleared' => '',
- // 'Source column' => '',
+ 'Compact view' => 'Kompakt nézet',
+ 'Horizontal scrolling' => 'Vízszintes görgetés',
+ 'Compact/wide view' => 'Kompakt/széles nézet',
+ 'No results match:' => 'Nincs találat:',
+ 'Remove hourly rate' => 'Órabér törlése',
+ 'Do you really want to remove this hourly rate?' => 'Valóban törölni kívánja az órabért?',
+ 'Hourly rates' => 'Órabérek',
+ 'Hourly rate' => 'Órabér',
+ 'Currency' => 'Pénznem',
+ 'Effective date' => 'Hatálybalépés ideje',
+ 'Add new rate' => 'Új bér',
+ 'Rate removed successfully.' => 'Bér sikeresen törölve.',
+ 'Unable to remove this rate.' => 'Bér törlése sikertelen.',
+ 'Unable to save the hourly rate.' => 'Órabér mentése sikertelen.',
+ 'Hourly rate created successfully.' => 'Órabér sikeresen mentve.',
+ 'Start time' => 'Kezdés ideje',
+ 'End time' => 'Végzés ideje',
+ 'Comment' => 'Megjegyzés',
+ 'All day' => 'Egész nap',
+ 'Day' => 'Nap',
+ 'Manage timetable' => 'Időbeosztás kezelése',
+ 'Overtime timetable' => 'Túlóra időbeosztás',
+ 'Time off timetable' => 'Szabadság időbeosztás',
+ 'Timetable' => 'Időbeosztás',
+ 'Work timetable' => 'Munka időbeosztás',
+ 'Week timetable' => 'Heti időbeosztás',
+ 'Day timetable' => 'Napi időbeosztás',
+ 'From' => 'Feladó:',
+ 'To' => 'Címzett:',
+ 'Time slot created successfully.' => 'Időszelet sikeresen létrehozva.',
+ 'Unable to save this time slot.' => 'Időszelet mentése sikertelen.',
+ 'Time slot removed successfully.' => 'Időszelet sikeresen törölve.',
+ 'Unable to remove this time slot.' => 'Időszelet törlése sikertelen.',
+ 'Do you really want to remove this time slot?' => 'Biztos törli ezt az időszeletet?',
+ 'Remove time slot' => 'Időszelet törlése',
+ 'Add new time slot' => 'Új Időszelet',
+ 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => 'Ez az időbeosztás van használatban ha az "egész nap" jelölőnégyzet be van jelölve a tervezett szabadságnál és túlóránál.',
+ 'Files' => 'Fájlok',
+ 'Images' => 'Képek',
+ 'Private project' => 'Privát projekt',
+ 'Amount' => 'Összeg',
+ 'AUD - Australian Dollar' => 'AUD - Ausztrál dollár',
+ 'Budget' => 'Költségvetés',
+ 'Budget line' => 'Költségvetési tétel',
+ 'Budget line removed successfully.' => 'Költségvetési tétel sikeresen törölve.',
+ 'Budget lines' => 'Költségvetési tételek',
+ 'CAD - Canadian Dollar' => 'CAD - Kanadai dollár',
+ 'CHF - Swiss Francs' => 'CHF - Svájci frank',
+ 'Cost' => 'Költség',
+ 'Cost breakdown' => 'Költség visszaszámlálás',
+ 'Custom Stylesheet' => 'Egyéni sítluslap',
+ 'download' => 'letöltés',
+ 'Do you really want to remove this budget line?' => 'Biztos törölni akarja ezt a költségvetési tételt?',
+ 'EUR - Euro' => 'EUR - Euro',
+ 'Expenses' => 'Kiadások',
+ 'GBP - British Pound' => 'GBP - Angol font',
+ 'INR - Indian Rupee' => 'INR - Indiai rúpia',
+ 'JPY - Japanese Yen' => 'JPY - Japán Yen',
+ 'New budget line' => 'Új költségvetési tétel',
+ 'NZD - New Zealand Dollar' => 'NZD - Új-Zélandi dollár',
+ 'Remove a budget line' => 'Költségvetési tétel törlése',
+ 'Remove budget line' => 'Költségvetési tétel törlése',
+ 'RSD - Serbian dinar' => 'RSD - Szerb dínár',
+ 'The budget line have been created successfully.' => 'Költségvetési tétel sikeresen létrehozva.',
+ 'Unable to create the budget line.' => 'Költségvetési tétel létrehozása sikertelen.',
+ 'Unable to remove this budget line.' => 'Költségvetési tétel törlése sikertelen.',
+ 'USD - US Dollar' => 'USD - Amerikai ollár',
+ 'Remaining' => 'Maradék',
+ 'Destination column' => 'Cél oszlop',
+ 'Move the task to another column when assigned to a user' => 'Feladat másik oszlopba helyezése felhasználóhoz rendélés után',
+ 'Move the task to another column when assignee is cleared' => 'Feladat másik oszlopba helyezése felhasználóhoz rendélés törlésekor',
+ 'Source column' => 'Forrás oszlop',
+ // 'Show subtask estimates in the user calendar' => '',
+ // 'Transitions' => '',
+ // 'Executer' => '',
+ // 'Time spent in the column' => '',
+ // 'Task transitions' => '',
+ // 'Task transitions export' => '',
+ // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '',
+ // 'Currency rates' => '',
+ // 'Rate' => '',
+ // 'Change reference currency' => '',
+ // 'Add a new currency rate' => '',
+ // 'Currency rates are used to calculate project budget.' => '',
+ // 'Reference currency' => '',
+ // 'The currency rate have been added successfully.' => '',
+ // 'Unable to add this currency rate.' => '',
+ // 'Send notifications to a Slack channel' => '',
+ // 'Webhook URL' => '',
+ // 'Help on Slack integration' => '',
+ // '%s remove the assignee of the task %s' => '',
+ // 'Send notifications to Hipchat' => '',
+ // 'API URL' => '',
+ // 'Room API ID or name' => '',
+ // 'Room notification token' => '',
+ // 'Help on Hipchat integration' => '',
);
diff --git a/app/Locale/it_IT/translations.php b/app/Locale/it_IT/translations.php
index 6e704ca1..e8d4c4d1 100644
--- a/app/Locale/it_IT/translations.php
+++ b/app/Locale/it_IT/translations.php
@@ -808,4 +808,28 @@ return array(
// 'Move the task to another column when assigned to a user' => '',
// 'Move the task to another column when assignee is cleared' => '',
// 'Source column' => '',
+ // 'Show subtask estimates in the user calendar' => '',
+ // 'Transitions' => '',
+ // 'Executer' => '',
+ // 'Time spent in the column' => '',
+ // 'Task transitions' => '',
+ // 'Task transitions export' => '',
+ // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '',
+ // 'Currency rates' => '',
+ // 'Rate' => '',
+ // 'Change reference currency' => '',
+ // 'Add a new currency rate' => '',
+ // 'Currency rates are used to calculate project budget.' => '',
+ // 'Reference currency' => '',
+ // 'The currency rate have been added successfully.' => '',
+ // 'Unable to add this currency rate.' => '',
+ // 'Send notifications to a Slack channel' => '',
+ // 'Webhook URL' => '',
+ // 'Help on Slack integration' => '',
+ // '%s remove the assignee of the task %s' => '',
+ // 'Send notifications to Hipchat' => '',
+ // 'API URL' => '',
+ // 'Room API ID or name' => '',
+ // 'Room notification token' => '',
+ // 'Help on Hipchat integration' => '',
);
diff --git a/app/Locale/ja_JP/translations.php b/app/Locale/ja_JP/translations.php
index 43834145..52d5413d 100644
--- a/app/Locale/ja_JP/translations.php
+++ b/app/Locale/ja_JP/translations.php
@@ -808,4 +808,28 @@ return array(
// 'Move the task to another column when assigned to a user' => '',
// 'Move the task to another column when assignee is cleared' => '',
// 'Source column' => '',
+ // 'Show subtask estimates in the user calendar' => '',
+ // 'Transitions' => '',
+ // 'Executer' => '',
+ // 'Time spent in the column' => '',
+ // 'Task transitions' => '',
+ // 'Task transitions export' => '',
+ // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '',
+ // 'Currency rates' => '',
+ // 'Rate' => '',
+ // 'Change reference currency' => '',
+ // 'Add a new currency rate' => '',
+ // 'Currency rates are used to calculate project budget.' => '',
+ // 'Reference currency' => '',
+ // 'The currency rate have been added successfully.' => '',
+ // 'Unable to add this currency rate.' => '',
+ // 'Send notifications to a Slack channel' => '',
+ // 'Webhook URL' => '',
+ // 'Help on Slack integration' => '',
+ // '%s remove the assignee of the task %s' => '',
+ // 'Send notifications to Hipchat' => '',
+ // 'API URL' => '',
+ // 'Room API ID or name' => '',
+ // 'Room notification token' => '',
+ // 'Help on Hipchat integration' => '',
);
diff --git a/app/Locale/nl_NL/translations.php b/app/Locale/nl_NL/translations.php
index 00ef6023..b53f9c83 100644
--- a/app/Locale/nl_NL/translations.php
+++ b/app/Locale/nl_NL/translations.php
@@ -808,4 +808,28 @@ return array(
// 'Move the task to another column when assigned to a user' => '',
// 'Move the task to another column when assignee is cleared' => '',
// 'Source column' => '',
+ // 'Show subtask estimates in the user calendar' => '',
+ // 'Transitions' => '',
+ // 'Executer' => '',
+ // 'Time spent in the column' => '',
+ // 'Task transitions' => '',
+ // 'Task transitions export' => '',
+ // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '',
+ // 'Currency rates' => '',
+ // 'Rate' => '',
+ // 'Change reference currency' => '',
+ // 'Add a new currency rate' => '',
+ // 'Currency rates are used to calculate project budget.' => '',
+ // 'Reference currency' => '',
+ // 'The currency rate have been added successfully.' => '',
+ // 'Unable to add this currency rate.' => '',
+ // 'Send notifications to a Slack channel' => '',
+ // 'Webhook URL' => '',
+ // 'Help on Slack integration' => '',
+ // '%s remove the assignee of the task %s' => '',
+ // 'Send notifications to Hipchat' => '',
+ // 'API URL' => '',
+ // 'Room API ID or name' => '',
+ // 'Room notification token' => '',
+ // 'Help on Hipchat integration' => '',
);
diff --git a/app/Locale/pl_PL/translations.php b/app/Locale/pl_PL/translations.php
index 2f5be941..42a3dff7 100644
--- a/app/Locale/pl_PL/translations.php
+++ b/app/Locale/pl_PL/translations.php
@@ -808,4 +808,28 @@ return array(
// 'Move the task to another column when assigned to a user' => '',
// 'Move the task to another column when assignee is cleared' => '',
// 'Source column' => '',
+ // 'Show subtask estimates in the user calendar' => '',
+ // 'Transitions' => '',
+ // 'Executer' => '',
+ // 'Time spent in the column' => '',
+ // 'Task transitions' => '',
+ // 'Task transitions export' => '',
+ // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '',
+ // 'Currency rates' => '',
+ // 'Rate' => '',
+ // 'Change reference currency' => '',
+ // 'Add a new currency rate' => '',
+ // 'Currency rates are used to calculate project budget.' => '',
+ // 'Reference currency' => '',
+ // 'The currency rate have been added successfully.' => '',
+ // 'Unable to add this currency rate.' => '',
+ // 'Send notifications to a Slack channel' => '',
+ // 'Webhook URL' => '',
+ // 'Help on Slack integration' => '',
+ // '%s remove the assignee of the task %s' => '',
+ // 'Send notifications to Hipchat' => '',
+ // 'API URL' => '',
+ // 'Room API ID or name' => '',
+ // 'Room notification token' => '',
+ // 'Help on Hipchat integration' => '',
);
diff --git a/app/Locale/pt_BR/translations.php b/app/Locale/pt_BR/translations.php
index 6edf5d0d..b18f2143 100644
--- a/app/Locale/pt_BR/translations.php
+++ b/app/Locale/pt_BR/translations.php
@@ -1,8 +1,8 @@
<?php
return array(
- // 'number.decimals_separator' => '',
- // 'number.thousands_separator' => '',
+ 'number.decimals_separator' => ',',
+ 'number.thousands_separator' => ' ',
'None' => 'Nenhum',
'edit' => 'editar',
'Edit' => 'Editar',
@@ -721,58 +721,58 @@ return array(
'fixes' => 'corrige',
'is fixed by' => 'foi corrigido por',
'This task' => 'Esta tarefa',
- // '<1h' => '',
- // '%dh' => '',
- // '%b %e' => '',
+ '<1h' => '<1h',
+ '%dh' => '%dh',
+ '%b %e' => '%e %b',
'Expand tasks' => 'Expandir tarefas',
'Collapse tasks' => 'Contrair tarefas',
'Expand/collapse tasks' => 'Expandir/Contrair tarefas',
- // 'Close dialog box' => '',
+ 'Close dialog box' => 'Fechar a caixa de diálogo',
'Submit a form' => 'Envia o formulário',
- // 'Board view' => '',
- // 'Keyboard shortcuts' => '',
- // 'Open board switcher' => '',
+ 'Board view' => 'Página do painel',
+ 'Keyboard shortcuts' => 'Atalhos de teclado',
+ 'Open board switcher' => 'Abrir o comutador de painel',
'Application' => 'Aplicação',
'Filter recently updated' => 'Filtro recentemente atualizado',
- // 'since %B %e, %Y at %k:%M %p' => '',
+ 'since %B %e, %Y at %k:%M %p' => 'desde o %d/%m/%Y às %H:%M',
'More filters' => 'Mais filtros',
- // 'Compact view' => '',
- // 'Horizontal scrolling' => '',
- // 'Compact/wide view' => '',
- // 'No results match:' => '',
- // 'Remove hourly rate' => '',
- // 'Do you really want to remove this hourly rate?' => '',
- // 'Hourly rates' => '',
- // 'Hourly rate' => '',
- // 'Currency' => '',
- // 'Effective date' => '',
- // 'Add new rate' => '',
- // 'Rate removed successfully.' => '',
- // 'Unable to remove this rate.' => '',
- // 'Unable to save the hourly rate.' => '',
- // 'Hourly rate created successfully.' => '',
- // 'Start time' => '',
- // 'End time' => '',
- // 'Comment' => '',
- // 'All day' => '',
- // 'Day' => '',
- // 'Manage timetable' => '',
- // 'Overtime timetable' => '',
- // 'Time off timetable' => '',
- // 'Timetable' => '',
- // 'Work timetable' => '',
- // 'Week timetable' => '',
- // 'Day timetable' => '',
- // 'From' => '',
- // 'To' => '',
- // 'Time slot created successfully.' => '',
- // 'Unable to save this time slot.' => '',
- // 'Time slot removed successfully.' => '',
- // 'Unable to remove this time slot.' => '',
- // 'Do you really want to remove this time slot?' => '',
- // 'Remove time slot' => '',
- // 'Add new time slot' => '',
- // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '',
+ 'Compact view' => 'Vista reduzida',
+ 'Horizontal scrolling' => 'Rolagem horizontal',
+ 'Compact/wide view' => 'Alternar entre a vista compacta e ampliada',
+ 'No results match:' => 'Nenhum resultado:',
+ 'Remove hourly rate' => 'Retirar taxa horária',
+ 'Do you really want to remove this hourly rate?' => 'Você deseja realmente remover esta taxa horária?',
+ 'Hourly rates' => 'Taxas horárias',
+ 'Hourly rate' => 'Taxa horária',
+ 'Currency' => 'Moeda',
+ 'Effective date' => 'Data efetiva',
+ 'Add new rate' => 'Adicionar nova taxa',
+ 'Rate removed successfully.' => 'Taxa removido com sucesso.',
+ 'Unable to remove this rate.' => 'Impossível de remover esta taxa.',
+ 'Unable to save the hourly rate.' => 'Impossível salvar a taxa horária.',
+ 'Hourly rate created successfully.' => 'Taxa horária criada com sucesso.',
+ 'Start time' => 'Horário de início',
+ 'End time' => 'Horário de término',
+ 'Comment' => 'comentário',
+ 'All day' => 'Dia inteiro',
+ 'Day' => 'Dia',
+ 'Manage timetable' => 'Gestão dos horários',
+ 'Overtime timetable' => 'Horas extras',
+ 'Time off timetable' => 'Horas de ausência',
+ 'Timetable' => 'Horários',
+ 'Work timetable' => 'Horas trabalhadas',
+ 'Week timetable' => 'Horário da semana',
+ 'Day timetable' => 'Horário de un dia',
+ 'From' => 'Desde',
+ 'To' => 'A',
+ 'Time slot created successfully.' => 'Intervalo de tempo criado com sucesso.',
+ 'Unable to save this time slot.' => 'Impossível de guardar este intervalo de tempo.',
+ 'Time slot removed successfully.' => 'Intervalo de tempo removido com sucesso.',
+ 'Unable to remove this time slot.' => 'Impossível de remover esse intervalo de tempo.',
+ 'Do you really want to remove this time slot?' => 'Você deseja realmente remover este intervalo de tempo?',
+ 'Remove time slot' => 'Remover um intervalo de tempo',
+ 'Add new time slot' => 'Adicionar um intervalo de tempo',
+ 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => 'Esses horários são usados quando a caixa de seleção "Dia inteiro" está marcada para Horas de ausência ou Extras',
// 'Files' => '',
// 'Images' => '',
// 'Private project' => '',
@@ -808,4 +808,28 @@ return array(
// 'Move the task to another column when assigned to a user' => '',
// 'Move the task to another column when assignee is cleared' => '',
// 'Source column' => '',
+ // 'Show subtask estimates in the user calendar' => '',
+ // 'Transitions' => '',
+ // 'Executer' => '',
+ // 'Time spent in the column' => '',
+ // 'Task transitions' => '',
+ // 'Task transitions export' => '',
+ // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '',
+ // 'Currency rates' => '',
+ // 'Rate' => '',
+ // 'Change reference currency' => '',
+ // 'Add a new currency rate' => '',
+ // 'Currency rates are used to calculate project budget.' => '',
+ // 'Reference currency' => '',
+ // 'The currency rate have been added successfully.' => '',
+ // 'Unable to add this currency rate.' => '',
+ // 'Send notifications to a Slack channel' => '',
+ // 'Webhook URL' => '',
+ // 'Help on Slack integration' => '',
+ // '%s remove the assignee of the task %s' => '',
+ // 'Send notifications to Hipchat' => '',
+ // 'API URL' => '',
+ // 'Room API ID or name' => '',
+ // 'Room notification token' => '',
+ // 'Help on Hipchat integration' => '',
);
diff --git a/app/Locale/ru_RU/translations.php b/app/Locale/ru_RU/translations.php
index 8f597adc..b77960d3 100644
--- a/app/Locale/ru_RU/translations.php
+++ b/app/Locale/ru_RU/translations.php
@@ -22,7 +22,7 @@ return array(
'Grey' => 'Серый',
'Save' => 'Сохранить',
'Login' => 'Вход',
- 'Official website:' => 'Официальный сайт :',
+ 'Official website:' => 'Официальный сайт:',
'Unassigned' => 'Не назначена',
'View this task' => 'Посмотреть задачу',
'Remove user' => 'Удалить пользователя',
@@ -65,7 +65,7 @@ return array(
'Disable' => 'Деактивировать',
'Enable' => 'Активировать',
'New project' => 'Новый проект',
- 'Do you really want to remove this project: "%s"?' => 'Вы точно хотите удалить этот проект? : « %s » ?',
+ 'Do you really want to remove this project: "%s"?' => 'Вы точно хотите удалить проект: « %s » ?',
'Remove project' => 'Удалить проект',
'Boards' => 'Доски',
'Edit the board for "%s"' => 'Изменить доску для « %s »',
@@ -80,7 +80,7 @@ return array(
'Remove a column' => 'Удалить колонку',
'Remove a column from a board' => 'Удалить колонку с доски',
'Unable to remove this column.' => 'Не удалось удалить колонку.',
- 'Do you really want to remove this column: "%s"?' => 'Вы точно хотите удалить эту колонку : « %s » ?',
+ 'Do you really want to remove this column: "%s"?' => 'Вы точно хотите удалить эту колонку: « %s » ?',
'This action will REMOVE ALL TASKS associated to this column!' => 'Вы УДАЛИТЕ ВСЕ ЗАДАЧИ находящиеся в этой колонке !',
'Settings' => 'Настройки',
'Application settings' => 'Настройки приложения',
@@ -94,9 +94,9 @@ return array(
'(VACUUM command)' => '(Команда VACUUM)',
'(Gzip compressed Sqlite file)' => '(Сжать GZip файл SQLite)',
'User settings' => 'Настройки пользователя',
- 'My default project:' => 'Мой проект по умолчанию : ',
+ 'My default project:' => 'Мой проект по умолчанию:',
'Close a task' => 'Закрыть задачу',
- 'Do you really want to close this task: "%s"?' => 'Вы точно хотите закрыть задачу : « %s » ?',
+ 'Do you really want to close this task: "%s"?' => 'Вы точно хотите закрыть задачу: « %s » ?',
'Edit a task' => 'Изменить задачу',
'Column' => 'Колонка',
'Color' => 'Цвет',
@@ -104,7 +104,7 @@ return array(
'Create another task' => 'Создать другую задачу',
'New task' => 'Новая задача',
'Open a task' => 'Открыть задачу',
- 'Do you really want to open this task: "%s"?' => 'Вы уверены что хотите открыть задачу : « %s » ?',
+ 'Do you really want to open this task: "%s"?' => 'Вы уверены что хотите открыть задачу: « %s » ?',
'Back to the board' => 'Вернуться на доску',
'Created on %B %e, %Y at %k:%M %p' => 'Создано %d/%m/%Y в %H:%M',
'There is nobody assigned' => 'Никто не назначен',
@@ -121,7 +121,7 @@ return array(
'The password is required' => 'Требуется пароль',
'This value must be an integer' => 'Это значение должно быть целым',
'The username must be unique' => 'Требуется уникальное имя пользователя',
- 'The username must be alphanumeric' => 'Имя пользователя должно быть букво-цифровым',
+ 'The username must be alphanumeric' => 'Имя пользователя должно быть буквенно-цифровым',
'The user id is required' => 'Требуется ID пользователя',
'Passwords don\'t match' => 'Пароли не совпадают',
'The confirmation is required' => 'Требуется подтверждение',
@@ -146,13 +146,13 @@ return array(
'Project removed successfully.' => 'Проект удален.',
'Project activated successfully.' => 'Проект активирован.',
'Unable to activate this project.' => 'Невозможно активировать проект.',
- 'Project disabled successfully.' => 'Проект успешно выключен.',
- 'Unable to disable this project.' => 'Не удалось выключить проект.',
+ 'Project disabled successfully.' => 'Проект успешно деактивирован.',
+ 'Unable to disable this project.' => 'Не удалось деактивировать проект.',
'Unable to open this task.' => 'Не удалось открыть задачу.',
'Task opened successfully.' => 'Задача открыта.',
'Unable to close this task.' => 'Не удалось закрыть задачу.',
'Task closed successfully.' => 'Задача закрыта.',
- 'Unable to update your task.' => 'Не удалось обновить вашу задачу.',
+ 'Unable to update your task.' => 'Не удалось обновить задачу.',
'Task updated successfully.' => 'Задача обновлена.',
'Unable to create your task.' => 'Не удалось создать задачу.',
'Task created successfully.' => 'Задача создана.',
@@ -162,12 +162,12 @@ return array(
'Unable to update your user.' => 'Не удалось обновить пользователя.',
'User removed successfully.' => 'Пользователь удален.',
'Unable to remove this user.' => 'Не удалось удалить пользователя.',
- 'Board updated successfully.' => 'Доска обновлена.',
+ 'Board updated successfully.' => 'Доска успешно обновлена.',
'Ready' => 'Готовые',
'Backlog' => 'Ожидающие',
'Work in progress' => 'В процессе',
'Done' => 'Выполнена',
- 'Application version:' => 'Версия приложения :',
+ 'Application version:' => 'Версия приложения:',
'Completed on %B %e, %Y at %k:%M %p' => 'Завершен %d/%m/%Y в %H:%M',
'%B %e, %Y at %k:%M %p' => '%d/%m/%Y в %H:%M',
'Date created' => 'Дата создания',
@@ -176,11 +176,11 @@ return array(
'No task' => 'Нет задачи',
'Completed tasks' => 'Завершенные задачи',
'List of projects' => 'Список проектов',
- 'Completed tasks for "%s"' => 'Задачи завершенные для « %s »',
+ 'Completed tasks for "%s"' => 'Завершенные задачи для « %s »',
'%d closed tasks' => '%d завершенных задач',
- 'No task for this project' => 'нет задач для этого проекта',
+ 'No task for this project' => 'Нет задач для этого проекта',
'Public link' => 'Ссылка для просмотра',
- 'There is no column in your project!' => 'Нет колонки в вашем проекте !',
+ 'There is no column in your project!' => 'Нет колонки в вашем проекте!',
'Change assignee' => 'Сменить назначенного',
'Change assignee for the task "%s"' => 'Сменить назначенного для задачи « %s »',
'Timezone' => 'Часовой пояс',
@@ -194,19 +194,19 @@ return array(
'Edit project access list' => 'Изменить доступ к проекту',
'Edit users access' => 'Изменить доступ пользователей',
'Allow this user' => 'Разрешить этого пользователя',
- 'Only those users have access to this project:' => 'Только эти пользователи имеют доступ к проекту :',
- 'Don\'t forget that administrators have access to everything.' => 'Помните, администратор имеет доступ ко всему.',
- 'Revoke' => 'отозвать',
+ 'Only those users have access to this project:' => 'Только эти пользователи имеют доступ к проекту:',
+ 'Don\'t forget that administrators have access to everything.' => 'Помните, администратор имеет неограниченные права.',
+ 'Revoke' => 'Отозвать',
'List of authorized users' => 'Список авторизованных пользователей',
'User' => 'Пользователь',
'Nobody have access to this project.' => 'Ни у кого нет доступа к этому проекту',
- 'You are not allowed to access to this project.' => 'Вам запрешен доступ к этому проекту.',
+ 'You are not allowed to access to this project.' => 'Вам запрещен доступ к этому проекту.',
'Comments' => 'Комментарии',
'Post comment' => 'Оставить комментарий',
'Write your text in Markdown' => 'Справка по синтаксису Markdown',
'Leave a comment' => 'Оставить комментарий 2',
'Comment is required' => 'Нужен комментарий',
- 'Leave a description' => 'Оставьте описание',
+ 'Leave a description' => 'Напишите описание',
'Comment added successfully.' => 'Комментарий успешно добавлен.',
'Unable to create your comment.' => 'Невозможно создать комментарий.',
'The description is required' => 'Требуется описание',
@@ -217,7 +217,7 @@ return array(
'%B %e, %Y' => '%d/%m/%Y',
// '%b %e, %Y' => '',
'Automatic actions' => 'Автоматические действия',
- 'Your automatic action have been created successfully.' => 'Автоматика настроена.',
+ 'Your automatic action have been created successfully.' => 'Автоматика успешно настроена.',
'Unable to create your automatic action.' => 'Не удалось создать автоматизированное действие.',
'Remove an action' => 'Удалить действие',
'Unable to remove this action.' => 'Не удалось удалить действие',
@@ -260,8 +260,8 @@ return array(
'Remove a comment' => 'Удалить комментарий',
'Comment removed successfully.' => 'Комментарий удален.',
'Unable to remove this comment.' => 'Не удалось удалить этот комментарий.',
- 'Do you really want to remove this comment?' => 'Вы точно хотите удалить этот комментарий ?',
- 'Only administrators or the creator of the comment can access to this page.' => 'Только администратор или автор комментарий могут получить доступ.',
+ 'Do you really want to remove this comment?' => 'Вы точно хотите удалить этот комментарий?',
+ 'Only administrators or the creator of the comment can access to this page.' => 'Только администратор и автор комментария имеют доступ к этой странице.',
'Details' => 'Подробности',
'Current password for the user "%s"' => 'Текущий пароль для пользователя « %s »',
'The current password is required' => 'Требуется текущий пароль',
@@ -280,7 +280,7 @@ return array(
'Remember Me' => 'Запомнить меня',
'Creation date' => 'Дата создания',
'Filter by user' => 'Фильтр по пользователям',
- 'Filter by due date' => 'Фильтр по сроку',
+ 'Filter by due date' => 'Фильтр по дате',
'Everybody' => 'Все',
'Open' => 'Открытый',
'Closed' => 'Закрытый',
@@ -288,17 +288,17 @@ return array(
'Nothing found.' => 'Ничего не найдено.',
'Search in the project "%s"' => 'Искать в проекте « %s »',
'Due date' => 'Срок',
- 'Others formats accepted: %s and %s' => 'Другой формат приемлем : %s и %s',
+ 'Others formats accepted: %s and %s' => 'Другой формат приемлем: %s и %s',
'Description' => 'Описание',
'%d comments' => '%d комментариев',
'%d comment' => '%d комментарий',
- 'Email address invalid' => 'Adresse email invalide',
+ 'Email address invalid' => 'Некорректный e-mail адрес',
'Your Google Account is not linked anymore to your profile.' => 'Ваш аккаунт в Google больше не привязан к вашему профилю.',
'Unable to unlink your Google Account.' => 'Не удалось отвязать ваш профиль от Google.',
'Google authentication failed' => 'Аутентификация Google не удалась',
'Unable to link your Google Account.' => 'Не удалось привязать ваш профиль к Google.',
'Your Google Account is linked to your profile successfully.' => 'Ваш профиль успешно привязан к Google.',
- 'Email' => 'Email',
+ 'Email' => 'E-mail',
'Link my Google Account' => 'Привязать мой профиль к Google',
'Unlink my Google Account' => 'Отвязать мой профиль от Google',
'Login with my Google Account' => 'Аутентификация через Google',
@@ -309,10 +309,10 @@ return array(
'Remove a task' => 'Удалить задачу',
'Do you really want to remove this task: "%s"?' => 'Вы точно хотите удалить эту задачу « %s » ?',
'Assign automatically a color based on a category' => 'Автоматически назначать цвет по категории',
- 'Assign automatically a category based on a color' => 'Автоматически назначать категорию по цвету ',
+ 'Assign automatically a category based on a color' => 'Автоматически назначать категорию по цвету',
'Task creation or modification' => 'Создание или изменение задачи',
'Category' => 'Категория',
- 'Category:' => 'Категория :',
+ 'Category:' => 'Категория:',
'Categories' => 'Категории',
'Category not found.' => 'Категория не найдена',
'Your category have been created successfully.' => 'Категория создана.',
@@ -344,10 +344,10 @@ return array(
'Edit a comment' => 'Изменить комментарий',
'Summary' => 'Сводка',
'Time tracking' => 'Отслеживание времени',
- 'Estimate:' => 'Приблизительно :',
- 'Spent:' => 'Затрачено :',
- 'Do you really want to remove this sub-task?' => 'Вы точно хотите удалить подзадачу ?',
- 'Remaining:' => 'Осталось :',
+ 'Estimate:' => 'Приблизительно:',
+ 'Spent:' => 'Затрачено:',
+ 'Do you really want to remove this sub-task?' => 'Вы точно хотите удалить подзадачу?',
+ 'Remaining:' => 'Осталось:',
'hours' => 'часов',
'spent' => 'затрачено',
'estimated' => 'расчетное',
@@ -367,11 +367,11 @@ return array(
'Unable to update your sub-task.' => 'Не удалось обновить подзадачу.',
'Unable to create your sub-task.' => 'Не удалось создать подзадачу.',
'Sub-task added successfully.' => 'Подзадача добавлена.',
- 'Maximum size: ' => 'Максимальный размер : ',
+ 'Maximum size: ' => 'Максимальный размер: ',
'Unable to upload the file.' => 'Не удалось загрузить файл.',
'Display another project' => 'Показать другой проект',
'Your GitHub account was successfully linked to your profile.' => 'Ваш GitHub привязан к вашему профилю.',
- 'Unable to link your GitHub Account.' => 'Не удалось привязать ваш профиль к Github.',
+ 'Unable to link your GitHub Account.' => 'Не удалось привязать ваш профиль к GitHub.',
'GitHub authentication failed' => 'Аутентификация в GitHub не удалась',
'Your GitHub account is no longer linked to your profile.' => 'Ваш GitHub отвязан от вашего профиля.',
'Unable to unlink your GitHub Account.' => 'Не удалось отвязать ваш профиль от GitHub.',
@@ -395,16 +395,16 @@ return array(
'Clone Project' => 'Клонировать проект',
'Project cloned successfully.' => 'Проект клонирован.',
'Unable to clone this project.' => 'Не удалось клонировать проект.',
- 'Email notifications' => 'Уведомления по email',
- 'Enable email notifications' => 'Включить уведомления по email',
- 'Task position:' => 'Позиция задачи :',
+ 'Email notifications' => 'Уведомления по e-mail',
+ 'Enable email notifications' => 'Включить уведомления по e-mail',
+ 'Task position:' => 'Позиция задачи:',
'The task #%d have been opened.' => 'Задача #%d была открыта.',
'The task #%d have been closed.' => 'Задача #%d была закрыта.',
'Sub-task updated' => 'Подзадача обновлена',
- 'Title:' => 'Название :',
- 'Status:' => 'Статус :',
- 'Assignee:' => 'Назначена :',
- 'Time tracking:' => 'Отслеживание времени :',
+ 'Title:' => 'Название:',
+ 'Status:' => 'Статус:',
+ 'Assignee:' => 'Назначена:',
+ 'Time tracking:' => 'Отслеживание времени:',
'New sub-task' => 'Новая подзадача',
'New attachment added "%s"' => 'Добавлено вложение « %s »',
'Comment updated' => 'Комментарий обновлен',
@@ -419,7 +419,7 @@ return array(
// 'Task opened' => '',
'[%s][Due tasks]' => '[%s][Текущие задачи]',
'[Kanboard] Notification' => '[Kanboard] Оповещение',
- 'I want to receive notifications only for those projects:' => 'Я хочу получать уведомления только по этим проектам :',
+ 'I want to receive notifications only for those projects:' => 'Я хочу получать уведомления только по этим проектам:',
'view the task on Kanboard' => 'посмотреть задачу на Kanboard',
'Public access' => 'Общий доступ',
'Category management' => 'Управление категориями',
@@ -430,9 +430,9 @@ return array(
'Active projects' => 'Активные проекты',
'Inactive projects' => 'Неактивные проекты',
'Public access disabled' => 'Общий доступ отключен',
- 'Do you really want to disable this project: "%s"?' => 'Вы точно хотите отключить проект: "%s"?',
+ 'Do you really want to disable this project: "%s"?' => 'Вы точно хотите деактивировать проект: "%s"?',
'Do you really want to duplicate this project: "%s"?' => 'Вы точно хотите клонировать проект: "%s"?',
- 'Do you really want to enable this project: "%s"?' => 'Вы точно хотите включить проект: "%s"?',
+ 'Do you really want to enable this project: "%s"?' => 'Вы точно хотите активировать проект: "%s"?',
'Project activation' => 'Активация проекта',
'Move the task to another project' => 'Переместить задачу в другой проект',
'Move to another project' => 'Переместить в другой проект',
@@ -448,7 +448,7 @@ return array(
'Github account linked' => 'Профиль GitHub связан',
'Username:' => 'Имя пользователя:',
'Name:' => 'Имя:',
- 'Email:' => 'Email:',
+ 'Email:' => 'E-mail:',
'Default project:' => 'Проект по умолчанию:',
'Notifications:' => 'Уведомления:',
'Notifications' => 'Уведомления',
@@ -485,7 +485,7 @@ return array(
'No activity.' => 'Нет активности',
'RSS feed' => 'RSS лента',
'%s updated a comment on the task #%d' => '%s обновил комментарий задачи #%d',
- '%s commented on the task #%d' => '%s откомментировал задачу #%d',
+ '%s commented on the task #%d' => '%s прокомментировал задачу #%d',
'%s updated a subtask for the task #%d' => '%s обновил подзадачу задачи #%d',
'%s created a subtask for the task #%d' => '%s создал подзадачу для задачи #%d',
'%s updated the task #%d' => '%s обновил задачу #%d',
@@ -503,14 +503,14 @@ return array(
// 'Column Change' => '',
// 'Position Change' => '',
// 'Assignee Change' => '',
- 'New password for the user "%s"' => 'Новый пароль для пользователя %s"',
+ 'New password for the user "%s"' => 'Новый пароль для пользователя "%s"',
'Choose an event' => 'Выберите событие',
- 'Github commit received' => 'Github: коммит получен',
- 'Github issue opened' => 'Github: новая проблема',
- 'Github issue closed' => 'Github: проблема закрыта',
- 'Github issue reopened' => 'Github: проблема переоткрыта',
- 'Github issue assignee change' => 'Github: сменить ответственного за проблему',
- 'Github issue label change' => 'Github: ярлык проблемы изменен',
+ 'Github commit received' => 'GitHub: коммит получен',
+ 'Github issue opened' => 'GitHub: новая проблема',
+ 'Github issue closed' => 'GitHub: проблема закрыта',
+ 'Github issue reopened' => 'GitHub: проблема переоткрыта',
+ 'Github issue assignee change' => 'GitHub: сменить ответственного за проблему',
+ 'Github issue label change' => 'GitHub: ярлык проблемы изменен',
'Create a task from an external provider' => 'Создать задачу из внешнего источника',
'Change the assignee based on an external username' => 'Изменить назначенного основываясь на внешнем имени пользователя',
'Change the category based on an external label' => 'Изменить категорию основываясь на внешнем ярлыке',
@@ -562,9 +562,9 @@ return array(
// 'Github issue comment created' => '',
// 'Configure' => '',
// 'Project management' => '',
- // 'My projects' => '',
- // 'Columns' => '',
- // 'Task' => '',
+ 'My projects' => 'Мои проекты',
+ 'Columns' => 'Колонки',
+ 'Task' => 'Задача',
// 'Your are not member of any project.' => '',
// 'Percentage' => '',
// 'Number of tasks' => '',
@@ -572,8 +572,8 @@ return array(
// 'Reportings' => '',
// 'Task repartition for "%s"' => '',
// 'Analytics' => '',
- // 'Subtask' => '',
- // 'My subtasks' => '',
+ 'Subtask' => 'Подзадача',
+ 'My subtasks' => 'Мои подзадачи',
// 'User repartition' => '',
// 'User repartition for "%s"' => '',
// 'Clone this project' => '',
@@ -648,10 +648,10 @@ return array(
// 'Language:' => '',
// 'Timezone:' => '',
// 'All columns' => '',
- // 'Calendar for "%s"' => '',
- // 'Filter by column' => '',
- // 'Filter by status' => '',
- // 'Calendar' => '',
+ 'Calendar for "%s"' => 'Календарь для "%s"',
+ 'Filter by column' => 'Фильтр по колонке',
+ 'Filter by status' => 'Фильтр по статусу',
+ 'Calendar' => 'Календарь',
// 'Next' => '',
// '#%d' => '',
// 'Filter by color' => '',
@@ -688,18 +688,18 @@ 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' => '',
+ 'Add a link' => 'Добавить ссылку на другие задачи',
+ 'Add a new link' => 'Добавление новой ссылки',
+ 'Do you really want to remove this link: "%s"?' => 'Вы уверены что хотите удалить ссылку: "%s"?',
+ 'Do you really want to remove this link with task #%d?' => 'Вы уверены что хотите удалить ссылку вместе с задачей #%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' => '',
@@ -709,37 +709,37 @@ return array(
// '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' => '',
+ 'relates to' => 'связана с',
+ 'blocks' => 'блокирует',
+ 'is blocked by' => 'заблокирована в',
+ 'duplicates' => 'дублирует',
+ 'is duplicated by' => 'дублирована в',
+ 'is a child of' => 'наследник',
+ 'is a parent of' => 'родитель',
+ 'targets milestone' => 'часть этапа',
+ 'is a milestone of' => '',
+ 'fixes' => 'исправляет',
+ 'is fixed by' => 'исправлено в',
+ 'This task' => 'Эта задача',
// '<1h' => '',
// '%dh' => '',
// '%b %e' => '',
- // 'Expand tasks' => '',
- // 'Collapse tasks' => '',
- // 'Expand/collapse tasks' => '',
- // 'Close dialog box' => '',
- // 'Submit a form' => '',
- // 'Board view' => '',
- // 'Keyboard shortcuts' => '',
- // 'Open board switcher' => '',
- // 'Application' => '',
- // 'Filter recently updated' => '',
+ 'Expand tasks' => 'Развернуть задачи',
+ 'Collapse tasks' => 'Свернуть задачи',
+ 'Expand/collapse tasks' => 'Развернуть/свернуть задачи',
+ 'Close dialog box' => 'Закрыть диалог',
+ 'Submit a form' => 'Отправить форму',
+ 'Board view' => 'Просмотр доски',
+ 'Keyboard shortcuts' => 'Горячие клавиши',
+ 'Open board switcher' => 'Открыть переключатель доски',
+ 'Application' => 'Приложение',
+ 'Filter recently updated' => 'Сортировать по дате обновления',
// 'since %B %e, %Y at %k:%M %p' => '',
- // 'More filters' => '',
- // 'Compact view' => '',
- // 'Horizontal scrolling' => '',
- // 'Compact/wide view' => '',
- // 'No results match:' => '',
+ 'More filters' => 'Использовать фильтры',
+ 'Compact view' => 'Компактный вид',
+ 'Horizontal scrolling' => 'Горизонтальная прокрутка',
+ 'Compact/wide view' => 'Компактный/широкий вид',
+ 'No results match:' => 'Отсутствуют результаты:',
// 'Remove hourly rate' => '',
// 'Do you really want to remove this hourly rate?' => '',
// 'Hourly rates' => '',
@@ -808,4 +808,28 @@ return array(
// 'Move the task to another column when assigned to a user' => '',
// 'Move the task to another column when assignee is cleared' => '',
// 'Source column' => '',
+ // 'Show subtask estimates in the user calendar' => '',
+ // 'Transitions' => '',
+ // 'Executer' => '',
+ // 'Time spent in the column' => '',
+ // 'Task transitions' => '',
+ // 'Task transitions export' => '',
+ // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '',
+ // 'Currency rates' => '',
+ // 'Rate' => '',
+ // 'Change reference currency' => '',
+ // 'Add a new currency rate' => '',
+ // 'Currency rates are used to calculate project budget.' => '',
+ // 'Reference currency' => '',
+ // 'The currency rate have been added successfully.' => '',
+ // 'Unable to add this currency rate.' => '',
+ // 'Send notifications to a Slack channel' => '',
+ // 'Webhook URL' => '',
+ // 'Help on Slack integration' => '',
+ // '%s remove the assignee of the task %s' => '',
+ // 'Send notifications to Hipchat' => '',
+ // 'API URL' => '',
+ // 'Room API ID or name' => '',
+ // 'Room notification token' => '',
+ // 'Help on Hipchat integration' => '',
);
diff --git a/app/Locale/sr_Latn_RS/translations.php b/app/Locale/sr_Latn_RS/translations.php
index 9c7f63a8..0514faa4 100644
--- a/app/Locale/sr_Latn_RS/translations.php
+++ b/app/Locale/sr_Latn_RS/translations.php
@@ -808,4 +808,28 @@ return array(
// 'Move the task to another column when assigned to a user' => '',
// 'Move the task to another column when assignee is cleared' => '',
// 'Source column' => '',
+ // 'Show subtask estimates in the user calendar' => '',
+ // 'Transitions' => '',
+ // 'Executer' => '',
+ // 'Time spent in the column' => '',
+ // 'Task transitions' => '',
+ // 'Task transitions export' => '',
+ // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '',
+ // 'Currency rates' => '',
+ // 'Rate' => '',
+ // 'Change reference currency' => '',
+ // 'Add a new currency rate' => '',
+ // 'Currency rates are used to calculate project budget.' => '',
+ // 'Reference currency' => '',
+ // 'The currency rate have been added successfully.' => '',
+ // 'Unable to add this currency rate.' => '',
+ // 'Send notifications to a Slack channel' => '',
+ // 'Webhook URL' => '',
+ // 'Help on Slack integration' => '',
+ // '%s remove the assignee of the task %s' => '',
+ // 'Send notifications to Hipchat' => '',
+ // 'API URL' => '',
+ // 'Room API ID or name' => '',
+ // 'Room notification token' => '',
+ // 'Help on Hipchat integration' => '',
);
diff --git a/app/Locale/sv_SE/translations.php b/app/Locale/sv_SE/translations.php
index 212440a7..dede7364 100644
--- a/app/Locale/sv_SE/translations.php
+++ b/app/Locale/sv_SE/translations.php
@@ -808,4 +808,28 @@ return array(
// 'Move the task to another column when assigned to a user' => '',
// 'Move the task to another column when assignee is cleared' => '',
// 'Source column' => '',
+ // 'Show subtask estimates in the user calendar' => '',
+ // 'Transitions' => '',
+ // 'Executer' => '',
+ // 'Time spent in the column' => '',
+ // 'Task transitions' => '',
+ // 'Task transitions export' => '',
+ // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '',
+ // 'Currency rates' => '',
+ // 'Rate' => '',
+ // 'Change reference currency' => '',
+ // 'Add a new currency rate' => '',
+ // 'Currency rates are used to calculate project budget.' => '',
+ // 'Reference currency' => '',
+ // 'The currency rate have been added successfully.' => '',
+ // 'Unable to add this currency rate.' => '',
+ // 'Send notifications to a Slack channel' => '',
+ // 'Webhook URL' => '',
+ // 'Help on Slack integration' => '',
+ // '%s remove the assignee of the task %s' => '',
+ // 'Send notifications to Hipchat' => '',
+ // 'API URL' => '',
+ // 'Room API ID or name' => '',
+ // 'Room notification token' => '',
+ // 'Help on Hipchat integration' => '',
);
diff --git a/app/Locale/th_TH/translations.php b/app/Locale/th_TH/translations.php
index bc94adf4..86a4f79c 100644
--- a/app/Locale/th_TH/translations.php
+++ b/app/Locale/th_TH/translations.php
@@ -808,4 +808,28 @@ return array(
// 'Move the task to another column when assigned to a user' => '',
// 'Move the task to another column when assignee is cleared' => '',
// 'Source column' => '',
+ // 'Show subtask estimates in the user calendar' => '',
+ // 'Transitions' => '',
+ // 'Executer' => '',
+ // 'Time spent in the column' => '',
+ // 'Task transitions' => '',
+ // 'Task transitions export' => '',
+ // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '',
+ // 'Currency rates' => '',
+ // 'Rate' => '',
+ // 'Change reference currency' => '',
+ // 'Add a new currency rate' => '',
+ // 'Currency rates are used to calculate project budget.' => '',
+ // 'Reference currency' => '',
+ // 'The currency rate have been added successfully.' => '',
+ // 'Unable to add this currency rate.' => '',
+ // 'Send notifications to a Slack channel' => '',
+ // 'Webhook URL' => '',
+ // 'Help on Slack integration' => '',
+ // '%s remove the assignee of the task %s' => '',
+ // 'Send notifications to Hipchat' => '',
+ // 'API URL' => '',
+ // 'Room API ID or name' => '',
+ // 'Room notification token' => '',
+ // 'Help on Hipchat integration' => '',
);
diff --git a/app/Locale/tr_TR/translations.php b/app/Locale/tr_TR/translations.php
index 0272a7f8..eb971798 100644
--- a/app/Locale/tr_TR/translations.php
+++ b/app/Locale/tr_TR/translations.php
@@ -808,4 +808,28 @@ return array(
// 'Move the task to another column when assigned to a user' => '',
// 'Move the task to another column when assignee is cleared' => '',
// 'Source column' => '',
+ // 'Show subtask estimates in the user calendar' => '',
+ // 'Transitions' => '',
+ // 'Executer' => '',
+ // 'Time spent in the column' => '',
+ // 'Task transitions' => '',
+ // 'Task transitions export' => '',
+ // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '',
+ // 'Currency rates' => '',
+ // 'Rate' => '',
+ // 'Change reference currency' => '',
+ // 'Add a new currency rate' => '',
+ // 'Currency rates are used to calculate project budget.' => '',
+ // 'Reference currency' => '',
+ // 'The currency rate have been added successfully.' => '',
+ // 'Unable to add this currency rate.' => '',
+ // 'Send notifications to a Slack channel' => '',
+ // 'Webhook URL' => '',
+ // 'Help on Slack integration' => '',
+ // '%s remove the assignee of the task %s' => '',
+ // 'Send notifications to Hipchat' => '',
+ // 'API URL' => '',
+ // 'Room API ID or name' => '',
+ // 'Room notification token' => '',
+ // 'Help on Hipchat integration' => '',
);
diff --git a/app/Locale/zh_CN/translations.php b/app/Locale/zh_CN/translations.php
index e99fedab..70175e5f 100644
--- a/app/Locale/zh_CN/translations.php
+++ b/app/Locale/zh_CN/translations.php
@@ -808,4 +808,28 @@ return array(
// 'Move the task to another column when assigned to a user' => '',
// 'Move the task to another column when assignee is cleared' => '',
// 'Source column' => '',
+ // 'Show subtask estimates in the user calendar' => '',
+ // 'Transitions' => '',
+ // 'Executer' => '',
+ // 'Time spent in the column' => '',
+ // 'Task transitions' => '',
+ // 'Task transitions export' => '',
+ // 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => '',
+ // 'Currency rates' => '',
+ // 'Rate' => '',
+ // 'Change reference currency' => '',
+ // 'Add a new currency rate' => '',
+ // 'Currency rates are used to calculate project budget.' => '',
+ // 'Reference currency' => '',
+ // 'The currency rate have been added successfully.' => '',
+ // 'Unable to add this currency rate.' => '',
+ // 'Send notifications to a Slack channel' => '',
+ // 'Webhook URL' => '',
+ // 'Help on Slack integration' => '',
+ // '%s remove the assignee of the task %s' => '',
+ // 'Send notifications to Hipchat' => '',
+ // 'API URL' => '',
+ // 'Room API ID or name' => '',
+ // 'Room notification token' => '',
+ // 'Help on Hipchat integration' => '',
);
diff --git a/app/Model/Acl.php b/app/Model/Acl.php
index b52a7864..403c45d0 100644
--- a/app/Model/Acl.php
+++ b/app/Model/Acl.php
@@ -72,6 +72,7 @@ class Acl extends Base
'link' => '*',
'project' => array('remove'),
'hourlyrate' => '*',
+ 'currency' => '*',
);
/**
diff --git a/app/Model/Budget.php b/app/Model/Budget.php
index 84cadf6e..d74dd870 100644
--- a/app/Model/Budget.php
+++ b/app/Model/Budget.php
@@ -111,15 +111,19 @@ class Budget extends Base
$date = $today->format('Y-m-d');
$today_in = isset($in[$date]) ? (int) $in[$date] : 0;
$today_out = isset($out[$date]) ? (int) $out[$date] : 0;
- $left += $today_in;
- $left -= $today_out;
-
- $serie[] = array(
- 'date' => $date,
- 'in' => $today_in,
- 'out' => -$today_out,
- 'left' => $left,
- );
+
+ if ($today_in > 0 || $today_out > 0) {
+
+ $left += $today_in;
+ $left -= $today_out;
+
+ $serie[] = array(
+ 'date' => $date,
+ 'in' => $today_in,
+ 'out' => -$today_out,
+ 'left' => $left,
+ );
+ }
}
return $serie;
@@ -143,7 +147,7 @@ class Budget extends Base
foreach ($rates as $rate) {
if ($rate['user_id'] == $record['user_id'] && date('Y-m-d', $rate['date_effective']) <= date('Y-m-d', $record['start'])) {
- $hourly_price = $rate['rate'];
+ $hourly_price = $this->currency->getPrice($rate['currency'], $rate['rate']);
break;
}
}
diff --git a/app/Model/Currency.php b/app/Model/Currency.php
new file mode 100644
index 00000000..bc423337
--- /dev/null
+++ b/app/Model/Currency.php
@@ -0,0 +1,104 @@
+<?php
+
+namespace Model;
+
+use SimpleValidator\Validator;
+use SimpleValidator\Validators;
+
+/**
+ * Currency
+ *
+ * @package model
+ * @author Frederic Guillot
+ */
+class Currency extends Base
+{
+ /**
+ * SQL table name
+ *
+ * @var string
+ */
+ const TABLE = 'currencies';
+
+ /**
+ * Get all currency rates
+ *
+ * @access public
+ * @return array
+ */
+ public function getAll()
+ {
+ return $this->db->table(self::TABLE)->findAll();
+ }
+
+ /**
+ * Calculate the price for the reference currency
+ *
+ * @access public
+ * @return array
+ */
+ public function getPrice($currency, $price)
+ {
+ static $rates = null;
+ $reference = $this->config->get('application_currency', 'USD');
+
+ if ($reference !== $currency) {
+ $rates = $rates === null ? $this->db->hashtable(self::TABLE)->getAll('currency', 'rate') : array();
+ $rate = isset($rates[$currency]) ? $rates[$currency] : 1;
+
+ return $rate * $price;
+ }
+
+ return $price;
+ }
+
+ /**
+ * Add a new currency rate
+ *
+ * @access public
+ * @param string $currency
+ * @param float $rate
+ * @return boolean|integer
+ */
+ public function create($currency, $rate)
+ {
+ if ($this->db->table(self::TABLE)->eq('currency', $currency)->count() === 1) {
+ return $this->update($currency, $rate);
+ }
+
+ return $this->persist(self::TABLE, compact('currency', 'rate'));
+ }
+
+ /**
+ * Update a currency rate
+ *
+ * @access public
+ * @param string $currency
+ * @param float $rate
+ * @return boolean
+ */
+ public function update($currency, $rate)
+ {
+ return $this->db->table(self::TABLE)->eq('currency', $currency)->update(array('rate' => $rate));
+ }
+
+ /**
+ * Validate
+ *
+ * @access public
+ * @param array $values Form values
+ * @return array $valid, $errors [0] = Success or not, [1] = List of errors
+ */
+ public function validate(array $values)
+ {
+ $v = new Validator($values, array(
+ new Validators\Required('currency', t('Field required')),
+ new Validators\Required('rate', t('Field required')),
+ ));
+
+ return array(
+ $v->execute(),
+ $v->getErrors()
+ );
+ }
+}
diff --git a/app/Model/ProjectActivity.php b/app/Model/ProjectActivity.php
index 652cc842..c5fbbd38 100644
--- a/app/Model/ProjectActivity.php
+++ b/app/Model/ProjectActivity.php
@@ -162,7 +162,13 @@ class ProjectActivity extends Base
{
switch ($event['event_name']) {
case Task::EVENT_ASSIGNEE_CHANGE:
- return t('%s change the assignee of the task #%d to %s', $event['author'], $event['task']['id'], $event['task']['assignee_name'] ?: $event['task']['assignee_username']);
+ $assignee = $event['task']['assignee_name'] ?: $event['task']['assignee_username'];
+
+ if (! empty($assignee)) {
+ return t('%s change the assignee of the task #%d to %s', $event['author'], $event['task']['id'], $assignee);
+ }
+
+ return t('%s remove the assignee of the task %s', $event['author'], e('#%d', $event['task']['id']));
case Task::EVENT_UPDATE:
return t('%s updated the task #%d', $event['author'], $event['task']['id']);
case Task::EVENT_CREATE:
diff --git a/app/Model/Subtask.php b/app/Model/Subtask.php
index e33373e0..492f3a77 100644
--- a/app/Model/Subtask.php
+++ b/app/Model/Subtask.php
@@ -98,6 +98,7 @@ class Subtask extends Base
Subtask::TABLE.'.*',
Task::TABLE.'.project_id',
Task::TABLE.'.color_id',
+ Task::TABLE.'.title AS task_name',
Project::TABLE.'.name AS project_name'
)
->eq('user_id', $user_id)
diff --git a/app/Model/SubtaskForecast.php b/app/Model/SubtaskForecast.php
new file mode 100644
index 00000000..cb86f6d7
--- /dev/null
+++ b/app/Model/SubtaskForecast.php
@@ -0,0 +1,118 @@
+<?php
+
+namespace Model;
+
+use DateTime;
+use DateInterval;
+
+/**
+ * Subtask Forecast
+ *
+ * @package model
+ * @author Frederic Guillot
+ */
+class SubtaskForecast extends Base
+{
+ /**
+ * Get not completed subtasks with an estimate sorted by postition
+ *
+ * @access public
+ * @param integer $user_id
+ * @return array
+ */
+ public function getSubtasks($user_id)
+ {
+ return $this->db
+ ->table(Subtask::TABLE)
+ ->columns(Subtask::TABLE.'.id', Task::TABLE.'.project_id', Subtask::TABLE.'.task_id', Subtask::TABLE.'.title', Subtask::TABLE.'.time_estimated')
+ ->join(Task::TABLE, 'id', 'task_id')
+ ->asc(Task::TABLE.'.position')
+ ->asc(Subtask::TABLE.'.position')
+ ->gt(Subtask::TABLE.'.time_estimated', 0)
+ ->eq(Subtask::TABLE.'.status', Subtask::STATUS_TODO)
+ ->eq(Subtask::TABLE.'.user_id', $user_id)
+ ->findAll();
+ }
+
+ /**
+ * Get the start date for the forecast
+ *
+ * @access public
+ * @param integer $user_id
+ * @return array
+ */
+ public function getStartDate($user_id)
+ {
+ $subtask = $this->db->table(Subtask::TABLE)
+ ->columns(Subtask::TABLE.'.time_estimated', SubtaskTimeTracking::TABLE.'.start')
+ ->eq(SubtaskTimeTracking::TABLE.'.user_id', $user_id)
+ ->eq(SubtaskTimeTracking::TABLE.'.end', 0)
+ ->status('status', Subtask::STATUS_INPROGRESS)
+ ->join(SubtaskTimeTracking::TABLE, 'subtask_id', 'id')
+ ->findOne();
+
+ if ($subtask && $subtask['time_estimated'] && $subtask['start']) {
+ return date('Y-m-d H:i', $subtask['start'] + $subtask['time_estimated'] * 3600);
+ }
+
+ return date('Y-m-d H:i');
+ }
+
+ /**
+ * Get all calendar events according to the user timetable and the subtasks estimates
+ *
+ * @access public
+ * @param integer $user_id
+ * @param string $end End date of the calendar
+ * @return array
+ */
+ public function getCalendarEvents($user_id, $end)
+ {
+ $events = array();
+ $start_date = new DateTime($this->getStartDate($user_id));
+ $timetable = $this->timetable->calculate($user_id, $start_date, new DateTime($end));
+ $subtasks = $this->getSubtasks($user_id);
+ $total = count($subtasks);
+ $offset = 0;
+
+ foreach ($timetable as $slot) {
+
+ $interval = $this->dateParser->getHours($slot[0], $slot[1]);
+ $start = $slot[0]->getTimestamp();
+
+ if ($slot[0] < $start_date) {
+ continue;
+ }
+
+ while ($offset < $total) {
+
+ $event = array(
+ 'id' => $subtasks[$offset]['id'].'-'.$subtasks[$offset]['task_id'].'-'.$offset,
+ 'subtask_id' => $subtasks[$offset]['id'],
+ 'title' => t('#%d', $subtasks[$offset]['task_id']).' '.$subtasks[$offset]['title'],
+ 'url' => $this->helper->url('task', 'show', array('task_id' => $subtasks[$offset]['task_id'], 'project_id' => $subtasks[$offset]['project_id'])),
+ 'editable' => false,
+ 'start' => date('Y-m-d\TH:i:s', $start),
+ );
+
+ if ($subtasks[$offset]['time_estimated'] <= $interval) {
+
+ $start += $subtasks[$offset]['time_estimated'] * 3600;
+ $interval -= $subtasks[$offset]['time_estimated'];
+ $offset++;
+
+ $event['end'] = date('Y-m-d\TH:i:s', $start);
+ $events[] = $event;
+ }
+ else {
+ $subtasks[$offset]['time_estimated'] -= $interval;
+ $event['end'] = $slot[1]->format('Y-m-d\TH:i:s');
+ $events[] = $event;
+ break;
+ }
+ }
+ }
+
+ return $events;
+ }
+}
diff --git a/app/Model/TaskPosition.php b/app/Model/TaskPosition.php
index 6dd10b02..ab5fe43b 100644
--- a/app/Model/TaskPosition.php
+++ b/app/Model/TaskPosition.php
@@ -143,6 +143,9 @@ class TaskPosition extends Base
'position' => $new_position,
'column_id' => $new_column_id,
'swimlane_id' => $new_swimlane_id,
+ 'src_column_id' => $task['column_id'],
+ 'dst_column_id' => $new_column_id,
+ 'date_moved' => $task['date_moved'],
);
if ($task['swimlane_id'] != $new_swimlane_id) {
diff --git a/app/Model/Transition.php b/app/Model/Transition.php
new file mode 100644
index 00000000..cb759e4a
--- /dev/null
+++ b/app/Model/Transition.php
@@ -0,0 +1,170 @@
+<?php
+
+namespace Model;
+
+/**
+ * Transition model
+ *
+ * @package model
+ * @author Frederic Guillot
+ */
+class Transition extends Base
+{
+ /**
+ * SQL table name
+ *
+ * @var string
+ */
+ const TABLE = 'transitions';
+
+ /**
+ * Save transition event
+ *
+ * @access public
+ * @param integer $user_id
+ * @param array $task
+ * @return boolean
+ */
+ public function save($user_id, array $task)
+ {
+ return $this->db->table(self::TABLE)->insert(array(
+ 'user_id' => $user_id,
+ 'project_id' => $task['project_id'],
+ 'task_id' => $task['task_id'],
+ 'src_column_id' => $task['src_column_id'],
+ 'dst_column_id' => $task['dst_column_id'],
+ 'date' => time(),
+ 'time_spent' => time() - $task['date_moved']
+ ));
+ }
+
+ /**
+ * Get all transitions by task
+ *
+ * @access public
+ * @param integer $task_id
+ * @return array
+ */
+ public function getAllByTask($task_id)
+ {
+ return $this->db->table(self::TABLE)
+ ->columns(
+ 'src.title as src_column',
+ 'dst.title as dst_column',
+ User::TABLE.'.name',
+ User::TABLE.'.username',
+ self::TABLE.'.user_id',
+ self::TABLE.'.date',
+ self::TABLE.'.time_spent'
+ )
+ ->eq('task_id', $task_id)
+ ->desc('date')
+ ->join(User::TABLE, 'id', 'user_id')
+ ->join(Board::TABLE.' as src', 'id', 'src_column_id', self::TABLE, 'src')
+ ->join(Board::TABLE.' as dst', 'id', 'dst_column_id', self::TABLE, 'dst')
+ ->findAll();
+ }
+
+ /**
+ * Get all transitions by project
+ *
+ * @access public
+ * @param integer $project_id
+ * @param mixed $from Start date (timestamp or user formatted date)
+ * @param mixed $to End date (timestamp or user formatted date)
+ * @return array
+ */
+ public function getAllByProjectAndDate($project_id, $from, $to)
+ {
+ if (! is_numeric($from)) {
+ $from = $this->dateParser->removeTimeFromTimestamp($this->dateParser->getTimestamp($from));
+ }
+
+ if (! is_numeric($to)) {
+ $to = $this->dateParser->removeTimeFromTimestamp(strtotime('+1 day', $this->dateParser->getTimestamp($to)));
+ }
+
+ return $this->db->table(self::TABLE)
+ ->columns(
+ Task::TABLE.'.id',
+ Task::TABLE.'.title',
+ 'src.title as src_column',
+ 'dst.title as dst_column',
+ User::TABLE.'.name',
+ User::TABLE.'.username',
+ self::TABLE.'.user_id',
+ self::TABLE.'.date',
+ self::TABLE.'.time_spent'
+ )
+ ->gte('date', $from)
+ ->lte('date', $to)
+ ->eq(self::TABLE.'.project_id', $project_id)
+ ->desc('date')
+ ->join(Task::TABLE, 'id', 'task_id')
+ ->join(User::TABLE, 'id', 'user_id')
+ ->join(Board::TABLE.' as src', 'id', 'src_column_id', self::TABLE, 'src')
+ ->join(Board::TABLE.' as dst', 'id', 'dst_column_id', self::TABLE, 'dst')
+ ->findAll();
+ }
+
+ /**
+ * Get project export
+ *
+ * @access public
+ * @param integer $project_id Project id
+ * @param mixed $from Start date (timestamp or user formatted date)
+ * @param mixed $to End date (timestamp or user formatted date)
+ * @return array
+ */
+ public function export($project_id, $from, $to)
+ {
+ $results = array($this->getColumns());
+ $transitions = $this->getAllByProjectAndDate($project_id, $from, $to);
+
+ foreach ($transitions as $transition) {
+ $results[] = $this->format($transition);
+ }
+
+ return $results;
+ }
+
+ /**
+ * Get column titles
+ *
+ * @access public
+ * @return string[]
+ */
+ public function getColumns()
+ {
+ return array(
+ e('Id'),
+ e('Task Title'),
+ e('Source column'),
+ e('Destination column'),
+ e('Executer'),
+ e('Date'),
+ e('Time spent'),
+ );
+ }
+
+ /**
+ * Format the output of a transition array
+ *
+ * @access public
+ * @param array $transition
+ * @return array
+ */
+ public function format(array $transition)
+ {
+ $values = array();
+ $values[] = $transition['id'];
+ $values[] = $transition['title'];
+ $values[] = $transition['src_column'];
+ $values[] = $transition['dst_column'];
+ $values[] = $transition['name'] ?: $transition['username'];
+ $values[] = date('Y-m-d H:i', $transition['date']);
+ $values[] = round($transition['time_spent'] / 3600, 2);
+
+ return $values;
+ }
+}
diff --git a/app/Model/Webhook.php b/app/Model/Webhook.php
index 7edffa6e..b3603818 100644
--- a/app/Model/Webhook.php
+++ b/app/Model/Webhook.php
@@ -11,27 +11,6 @@ namespace Model;
class Webhook extends Base
{
/**
- * HTTP connection timeout in seconds
- *
- * @var integer
- */
- const HTTP_TIMEOUT = 1;
-
- /**
- * Number of maximum redirections for the HTTP client
- *
- * @var integer
- */
- const HTTP_MAX_REDIRECTS = 3;
-
- /**
- * HTTP client user agent
- *
- * @var string
- */
- const HTTP_USER_AGENT = 'Kanboard Webhook';
-
- /**
* Call the external URL
*
* @access public
@@ -42,22 +21,6 @@ class Webhook extends Base
{
$token = $this->config->get('webhook_token');
- $headers = array(
- 'Connection: close',
- 'User-Agent: '.self::HTTP_USER_AGENT,
- );
-
- $context = stream_context_create(array(
- 'http' => array(
- 'method' => 'POST',
- 'protocol_version' => 1.1,
- 'timeout' => self::HTTP_TIMEOUT,
- 'max_redirects' => self::HTTP_MAX_REDIRECTS,
- 'header' => implode("\r\n", $headers),
- 'content' => json_encode($task)
- )
- ));
-
if (strpos($url, '?') !== false) {
$url .= '&token='.$token;
}
@@ -65,6 +28,6 @@ class Webhook extends Base
$url .= '?token='.$token;
}
- @file_get_contents($url, false, $context);
+ return $this->httpClient->post($url, $task);
}
}
diff --git a/app/Schema/Mysql.php b/app/Schema/Mysql.php
index a78ffacf..f0e0d6b2 100644
--- a/app/Schema/Mysql.php
+++ b/app/Schema/Mysql.php
@@ -6,7 +6,61 @@ use PDO;
use Core\Security;
use Model\Link;
-const VERSION = 54;
+const VERSION = 59;
+
+function version_59($pdo)
+{
+ $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)');
+ $rq->execute(array('integration_hipchat', '0'));
+ $rq->execute(array('integration_hipchat_api_url', 'https://api.hipchat.com'));
+ $rq->execute(array('integration_hipchat_room_id', ''));
+ $rq->execute(array('integration_hipchat_room_token', ''));
+}
+
+function version_58($pdo)
+{
+ $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)');
+ $rq->execute(array('integration_slack_webhook', '0'));
+ $rq->execute(array('integration_slack_webhook_url', ''));
+}
+
+function version_57($pdo)
+{
+ $pdo->exec('CREATE TABLE currencies (`currency` CHAR(3) NOT NULL UNIQUE, `rate` FLOAT DEFAULT 0) ENGINE=InnoDB CHARSET=utf8');
+
+ $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)');
+ $rq->execute(array('application_currency', 'USD'));
+}
+
+function version_56($pdo)
+{
+ $pdo->exec('CREATE TABLE transitions (
+ `id` INT NOT NULL AUTO_INCREMENT,
+ `user_id` INT NOT NULL,
+ `project_id` INT NOT NULL,
+ `task_id` INT NOT NULL,
+ `src_column_id` INT NOT NULL,
+ `dst_column_id` INT NOT NULL,
+ `date` INT NOT NULL,
+ `time_spent` INT DEFAULT 0,
+ FOREIGN KEY(src_column_id) REFERENCES columns(id) ON DELETE CASCADE,
+ FOREIGN KEY(dst_column_id) REFERENCES columns(id) ON DELETE CASCADE,
+ FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
+ FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE,
+ FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE,
+ PRIMARY KEY(id)
+ ) ENGINE=InnoDB CHARSET=utf8');
+
+ $pdo->exec("CREATE INDEX transitions_task_index ON transitions(task_id)");
+ $pdo->exec("CREATE INDEX transitions_project_index ON transitions(project_id)");
+ $pdo->exec("CREATE INDEX transitions_user_index ON transitions(user_id)");
+}
+
+function version_55($pdo)
+{
+ $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)');
+ $rq->execute(array('subtask_forecast', '0'));
+}
function version_54($pdo)
{
@@ -85,7 +139,7 @@ function version_50($pdo)
user_id INT NOT NULL,
rate FLOAT DEFAULT 0,
date_effective INTEGER NOT NULL,
- currency TEXT NOT NULL,
+ currency CHAR(3) NOT NULL,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
PRIMARY KEY(id)
) ENGINE=InnoDB CHARSET=utf8");
diff --git a/app/Schema/Postgres.php b/app/Schema/Postgres.php
index 2396000f..f7a0453d 100644
--- a/app/Schema/Postgres.php
+++ b/app/Schema/Postgres.php
@@ -6,7 +6,60 @@ use PDO;
use Core\Security;
use Model\Link;
-const VERSION = 35;
+const VERSION = 40;
+
+function version_40($pdo)
+{
+ $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)');
+ $rq->execute(array('integration_hipchat', '0'));
+ $rq->execute(array('integration_hipchat_api_url', 'https://api.hipchat.com'));
+ $rq->execute(array('integration_hipchat_room_id', ''));
+ $rq->execute(array('integration_hipchat_room_token', ''));
+}
+
+function version_39($pdo)
+{
+ $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)');
+ $rq->execute(array('integration_slack_webhook', '0'));
+ $rq->execute(array('integration_slack_webhook_url', ''));
+}
+
+function version_38($pdo)
+{
+ $pdo->exec('CREATE TABLE currencies ("currency" CHAR(3) NOT NULL UNIQUE, "rate" REAL DEFAULT 0)');
+
+ $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)');
+ $rq->execute(array('application_currency', 'USD'));
+}
+
+function version_37($pdo)
+{
+ $pdo->exec('CREATE TABLE transitions (
+ "id" SERIAL PRIMARY KEY,
+ "user_id" INTEGER NOT NULL,
+ "project_id" INTEGER NOT NULL,
+ "task_id" INTEGER NOT NULL,
+ "src_column_id" INTEGER NOT NULL,
+ "dst_column_id" INTEGER NOT NULL,
+ "date" INTEGER NOT NULL,
+ "time_spent" INTEGER DEFAULT 0,
+ FOREIGN KEY(src_column_id) REFERENCES columns(id) ON DELETE CASCADE,
+ FOREIGN KEY(dst_column_id) REFERENCES columns(id) ON DELETE CASCADE,
+ FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
+ FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE,
+ FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE
+ )');
+
+ $pdo->exec("CREATE INDEX transitions_task_index ON transitions(task_id)");
+ $pdo->exec("CREATE INDEX transitions_project_index ON transitions(project_id)");
+ $pdo->exec("CREATE INDEX transitions_user_index ON transitions(user_id)");
+}
+
+function version_36($pdo)
+{
+ $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)');
+ $rq->execute(array('subtask_forecast', '0'));
+}
function version_35($pdo)
{
@@ -80,7 +133,7 @@ function version_31($pdo)
user_id INTEGER NOT NULL,
rate REAL DEFAULT 0,
date_effective INTEGER NOT NULL,
- currency TEXT NOT NULL,
+ currency CHAR(3) NOT NULL,
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
)");
}
diff --git a/app/Schema/Sqlite.php b/app/Schema/Sqlite.php
index 0e0512d0..3ad045e6 100644
--- a/app/Schema/Sqlite.php
+++ b/app/Schema/Sqlite.php
@@ -6,7 +6,60 @@ use Core\Security;
use PDO;
use Model\Link;
-const VERSION = 53;
+const VERSION = 58;
+
+function version_58($pdo)
+{
+ $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)');
+ $rq->execute(array('integration_hipchat', '0'));
+ $rq->execute(array('integration_hipchat_api_url', 'https://api.hipchat.com'));
+ $rq->execute(array('integration_hipchat_room_id', ''));
+ $rq->execute(array('integration_hipchat_room_token', ''));
+}
+
+function version_57($pdo)
+{
+ $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)');
+ $rq->execute(array('integration_slack_webhook', '0'));
+ $rq->execute(array('integration_slack_webhook_url', ''));
+}
+
+function version_56($pdo)
+{
+ $pdo->exec('CREATE TABLE currencies ("currency" TEXT NOT NULL UNIQUE, "rate" REAL DEFAULT 0)');
+
+ $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)');
+ $rq->execute(array('application_currency', 'USD'));
+}
+
+function version_55($pdo)
+{
+ $pdo->exec('CREATE TABLE transitions (
+ "id" INTEGER PRIMARY KEY,
+ "user_id" INTEGER NOT NULL,
+ "project_id" INTEGER NOT NULL,
+ "task_id" INTEGER NOT NULL,
+ "src_column_id" INTEGER NOT NULL,
+ "dst_column_id" INTEGER NOT NULL,
+ "date" INTEGER NOT NULL,
+ "time_spent" INTEGER DEFAULT 0,
+ FOREIGN KEY(src_column_id) REFERENCES columns(id) ON DELETE CASCADE,
+ FOREIGN KEY(dst_column_id) REFERENCES columns(id) ON DELETE CASCADE,
+ FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
+ FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE,
+ FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE
+ )');
+
+ $pdo->exec("CREATE INDEX transitions_task_index ON transitions(task_id)");
+ $pdo->exec("CREATE INDEX transitions_project_index ON transitions(project_id)");
+ $pdo->exec("CREATE INDEX transitions_user_index ON transitions(user_id)");
+}
+
+function version_54($pdo)
+{
+ $rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)');
+ $rq->execute(array('subtask_forecast', '0'));
+}
function version_53($pdo)
{
diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php
index fc71ebf9..6a12ea5a 100644
--- a/app/ServiceProvider/ClassProvider.php
+++ b/app/ServiceProvider/ClassProvider.php
@@ -22,6 +22,7 @@ class ClassProvider implements ServiceProviderInterface
'Color',
'Comment',
'Config',
+ 'Currency',
'DateParser',
'File',
'HourlyRate',
@@ -36,6 +37,7 @@ class ClassProvider implements ServiceProviderInterface
'ProjectPermission',
'Subtask',
'SubtaskExport',
+ 'SubtaskForecast',
'SubtaskTimeTracking',
'Swimlane',
'Task',
@@ -55,6 +57,7 @@ class ClassProvider implements ServiceProviderInterface
'TimetableWeek',
'TimetableOff',
'TimetableExtra',
+ 'Transition',
'User',
'UserSession',
'Webhook',
@@ -66,11 +69,14 @@ class ClassProvider implements ServiceProviderInterface
'MemoryCache',
'FileCache',
'Request',
+ 'HttpClient',
),
'Integration' => array(
'GitlabWebhook',
'GithubWebhook',
'BitbucketWebhook',
+ 'Hipchat',
+ 'SlackWebhook',
)
);
diff --git a/app/ServiceProvider/EventDispatcherProvider.php b/app/ServiceProvider/EventDispatcherProvider.php
index ec382206..f002db74 100644
--- a/app/ServiceProvider/EventDispatcherProvider.php
+++ b/app/ServiceProvider/EventDispatcherProvider.php
@@ -14,6 +14,7 @@ use Subscriber\ProjectModificationDateSubscriber;
use Subscriber\WebhookSubscriber;
use Subscriber\SubtaskTimesheetSubscriber;
use Subscriber\TaskMovedDateSubscriber;
+use Subscriber\TransitionSubscriber;
class EventDispatcherProvider implements ServiceProviderInterface
{
@@ -29,6 +30,7 @@ class EventDispatcherProvider implements ServiceProviderInterface
$container['dispatcher']->addSubscriber(new NotificationSubscriber($container));
$container['dispatcher']->addSubscriber(new SubtaskTimesheetSubscriber($container));
$container['dispatcher']->addSubscriber(new TaskMovedDateSubscriber($container));
+ $container['dispatcher']->addSubscriber(new TransitionSubscriber($container));
// Automatic actions
$container['action']->attachEvents();
diff --git a/app/Subscriber/ProjectActivitySubscriber.php b/app/Subscriber/ProjectActivitySubscriber.php
index 00f5b044..42314637 100644
--- a/app/Subscriber/ProjectActivitySubscriber.php
+++ b/app/Subscriber/ProjectActivitySubscriber.php
@@ -41,6 +41,33 @@ class ProjectActivitySubscriber extends Base implements EventSubscriberInterface
$event_name,
$values
);
+
+ $this->sendSlackNotification($event_name, $values);
+ $this->sendHipchatNotification($event_name, $values);
+ }
+ }
+
+ private function sendSlackNotification($event_name, array $values)
+ {
+ if ($this->config->get('integration_slack_webhook') == 1) {
+ $this->slackWebhook->notify(
+ $values['task']['project_id'],
+ $values['task']['id'],
+ $event_name,
+ $values
+ );
+ }
+ }
+
+ private function sendHipchatNotification($event_name, array $values)
+ {
+ if ($this->config->get('integration_hipchat') == 1) {
+ $this->hipchat->notify(
+ $values['task']['project_id'],
+ $values['task']['id'],
+ $event_name,
+ $values
+ );
}
}
diff --git a/app/Subscriber/TransitionSubscriber.php b/app/Subscriber/TransitionSubscriber.php
new file mode 100644
index 00000000..347dd37d
--- /dev/null
+++ b/app/Subscriber/TransitionSubscriber.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Subscriber;
+
+use Event\TaskEvent;
+use Model\Task;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+class TransitionSubscriber extends Base implements EventSubscriberInterface
+{
+ public static function getSubscribedEvents()
+ {
+ return array(
+ Task::EVENT_MOVE_COLUMN => array('execute', 0),
+ );
+ }
+
+ public function execute(TaskEvent $event)
+ {
+ $user_id = $this->userSession->getId();
+
+ if (! empty($user_id)) {
+ $this->transition->save($user_id, $event->getAll());
+ }
+ }
+} \ No newline at end of file
diff --git a/app/Template/app/subtasks.php b/app/Template/app/subtasks.php
index fdfbdf2f..487b66fc 100644
--- a/app/Template/app/subtasks.php
+++ b/app/Template/app/subtasks.php
@@ -6,6 +6,7 @@
<tr>
<th class="column-10"><?= $paginator->order('Id', 'tasks.id') ?></th>
<th class="column-20"><?= $paginator->order(t('Project'), 'project_name') ?></th>
+ <th><?= $paginator->order(t('Task'), 'task_name') ?></th>
<th><?= $paginator->order(t('Subtask'), 'title') ?></th>
<th class="column-20"><?= t('Time tracking') ?></th>
</tr>
@@ -18,6 +19,9 @@
<?= $this->a($this->e($subtask['project_name']), 'board', 'show', array('project_id' => $subtask['project_id'])) ?>
</td>
<td>
+ <?= $this->a($this->e($subtask['task_name']), 'task', 'show', array('task_id' => $subtask['task_id'], 'project_id' => $subtask['project_id'])) ?>
+ </td>
+ <td>
<?= $this->toggleSubtaskStatus($subtask, 'dashboard') ?>
</td>
<td>
diff --git a/app/Template/budget/breakdown.php b/app/Template/budget/breakdown.php
index d4168406..0a3c63d7 100644
--- a/app/Template/budget/breakdown.php
+++ b/app/Template/budget/breakdown.php
@@ -22,7 +22,7 @@
<tr>
<td><?= $this->a($this->e($record['task_title']), 'task', 'show', array('project_id' => $project['id'], 'task_id' => $record['task_id'])) ?></td>
<td><?= $this->a($this->e($record['subtask_title']), 'task', 'show', array('project_id' => $project['id'], 'task_id' => $record['task_id'])) ?></td>
- <td><?= $this->e($record['name'] ?: $record['username']) ?></td>
+ <td><?= $this->a($this->e($record['name'] ?: $record['username']), 'user', 'show', array('user_id' => $record['user_id'])) ?></td>
<td><?= n($record['cost']) ?></td>
<td><?= n($record['time_spent']).' '.t('hours') ?></td>
<td><?= dt('%B %e, %Y', $record['start']) ?></td>
diff --git a/app/Template/config/board.php b/app/Template/config/board.php
index 57efcd08..15e2b422 100644
--- a/app/Template/config/board.php
+++ b/app/Template/config/board.php
@@ -28,6 +28,7 @@
<?= $this->formCheckbox('subtask_restriction', t('Allow only one subtask in progress at the same time for a user'), 1, $values['subtask_restriction'] == 1) ?>
<?= $this->formCheckbox('subtask_time_tracking', t('Enable time tracking for subtasks'), 1, $values['subtask_time_tracking'] == 1) ?>
+ <?= $this->formCheckbox('subtask_forecast', t('Show subtask estimates in the user calendar'), 1, $values['subtask_forecast'] == 1) ?>
<div class="form-actions">
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
diff --git a/app/Template/config/integrations.php b/app/Template/config/integrations.php
new file mode 100644
index 00000000..6f90e0ab
--- /dev/null
+++ b/app/Template/config/integrations.php
@@ -0,0 +1,38 @@
+<div class="page-header">
+ <h2><?= t('Integration with third-party services') ?></h2>
+</div>
+
+<form method="post" action="<?= $this->u('config', 'integrations') ?>" autocomplete="off">
+
+ <?= $this->formCsrf() ?>
+
+ <h3><img src="assets/img/hipchat-icon.png"/> <?= t('Hipchat') ?></h3>
+ <div class="listing">
+ <?= $this->formCheckbox('integration_hipchat', t('Send notifications to Hipchat'), 1, $values['integration_hipchat'] == 1) ?>
+
+ <?= $this->formLabel(t('API URL'), 'integration_hipchat_api_url') ?>
+ <?= $this->formText('integration_hipchat_api_url', $values, $errors) ?>
+
+ <?= $this->formLabel(t('Room API ID or name'), 'integration_hipchat_room_id') ?>
+ <?= $this->formText('integration_hipchat_room_id', $values, $errors) ?>
+
+ <?= $this->formLabel(t('Room notification token'), 'integration_hipchat_room_token') ?>
+ <?= $this->formText('integration_hipchat_room_token', $values, $errors) ?>
+
+ <p class="form-help"><a href="http://kanboard.net/documentation/hipchat" target="_blank"><?= t('Help on Hipchat integration') ?></a></p>
+ </div>
+
+ <h3><i class="fa fa-slack fa-fw"></i>&nbsp;<?= t('Slack') ?></h3>
+ <div class="listing">
+ <?= $this->formCheckbox('integration_slack_webhook', t('Send notifications to a Slack channel'), 1, $values['integration_slack_webhook'] == 1) ?>
+
+ <?= $this->formLabel(t('Webhook URL'), 'integration_slack_webhook_url') ?>
+ <?= $this->formText('integration_slack_webhook_url', $values, $errors) ?>
+
+ <p class="form-help"><a href="http://kanboard.net/documentation/slack" target="_blank"><?= t('Help on Slack integration') ?></a></p>
+ </div>
+
+ <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/config/sidebar.php b/app/Template/config/sidebar.php
index 89f2c203..a4f9d8e3 100644
--- a/app/Template/config/sidebar.php
+++ b/app/Template/config/sidebar.php
@@ -14,6 +14,12 @@
<?= $this->a(t('Link settings'), 'link', 'index') ?>
</li>
<li>
+ <?= $this->a(t('Currency rates'), 'currency', 'index') ?>
+ </li>
+ <li>
+ <?= $this->a(t('Integrations'), 'config', 'integrations') ?>
+ </li>
+ <li>
<?= $this->a(t('Webhooks'), 'config', 'webhook') ?>
</li>
<li>
diff --git a/app/Template/currency/index.php b/app/Template/currency/index.php
new file mode 100644
index 00000000..7839a142
--- /dev/null
+++ b/app/Template/currency/index.php
@@ -0,0 +1,56 @@
+<div class="page-header">
+ <h2><?= t('Currency rates') ?></h2>
+</div>
+
+<?php if (! empty($rates)): ?>
+
+<table class="table-stripped">
+ <tr>
+ <th class="column-35"><?= t('Currency') ?></th>
+ <th><?= t('Rate') ?></th>
+ </tr>
+ <?php foreach ($rates as $rate): ?>
+ <tr>
+ <td>
+ <strong><?= $this->e($rate['currency']) ?></strong>
+ </td>
+ <td>
+ <?= n($rate['rate']) ?>
+ </td>
+ </tr>
+ <?php endforeach ?>
+</table>
+
+<hr/>
+<h3><?= t('Change reference currency') ?></h3>
+<?php endif ?>
+<form method="post" action="<?= $this->u('currency', 'reference') ?>" autocomplete="off">
+
+ <?= $this->formCsrf() ?>
+
+ <?= $this->formLabel(t('Reference currency'), 'application_currency') ?>
+ <?= $this->formSelect('application_currency', $currencies, $config_values, $errors) ?><br/>
+
+ <div class="form-actions">
+ <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
+ </div>
+</form>
+
+<hr/>
+<h3><?= t('Add a new currency rate') ?></h3>
+<form method="post" action="<?= $this->u('currency', 'create') ?>" autocomplete="off">
+
+ <?= $this->formCsrf() ?>
+
+ <?= $this->formLabel(t('Currency'), 'currency') ?>
+ <?= $this->formSelect('currency', $currencies, $values, $errors) ?><br/>
+
+ <?= $this->formLabel(t('Rate'), 'rate') ?>
+ <?= $this->formText('rate', $values, $errors, array(), 'form-numeric') ?><br/>
+
+ <div class="form-actions">
+ <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
+ </div>
+</form>
+
+<p class="alert alert-info"><?= t('Currency rates are used to calculate project budget.') ?></p>
diff --git a/app/Template/event/task_assignee_change.php b/app/Template/event/task_assignee_change.php
index 6eac412b..22ed936b 100644
--- a/app/Template/event/task_assignee_change.php
+++ b/app/Template/event/task_assignee_change.php
@@ -1,9 +1,15 @@
<p class="activity-title">
- <?= e('%s changed the assignee of the task %s to %s',
- $this->e($author),
- $this->a(t('#%d', $task['id']), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])),
- $this->e($task['assignee_name'] ?: $task['assignee_username'])
- ) ?>
+ <?php $assignee = $task['assignee_name'] ?: $task['assignee_username'] ?>
+
+ <?php if (! empty($assignee)): ?>
+ <?= e('%s changed the assignee of the task %s to %s',
+ $this->e($author),
+ $this->a(t('#%d', $task['id']), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])),
+ $this->e($assignee)
+ ) ?>
+ <?php else: ?>
+ <?= e('%s remove the assignee of the task %s', $this->e($author), $this->a(t('#%d', $task['id']), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']))) ?>
+ <?php endif ?>
</p>
<p class="activity-description">
<em><?= $this->e($task['title']) ?></em>
diff --git a/app/Template/export/transitions.php b/app/Template/export/transitions.php
new file mode 100644
index 00000000..7cd190e0
--- /dev/null
+++ b/app/Template/export/transitions.php
@@ -0,0 +1,26 @@
+<div class="page-header">
+ <h2>
+ <?= t('Task transitions export') ?>
+ </h2>
+</div>
+
+<p class="alert alert-info"><?= t('This report contains all column moves for each task with the date, the user and the time spent for each transition.') ?></p>
+
+<form method="get" action="?" autocomplete="off">
+
+ <?= $this->formHidden('controller', $values) ?>
+ <?= $this->formHidden('action', $values) ?>
+ <?= $this->formHidden('project_id', $values) ?>
+
+ <?= $this->formLabel(t('Start Date'), 'from') ?>
+ <?= $this->formText('from', $values, $errors, array('required', 'placeholder="'.$this->inList($date_format, $date_formats).'"'), 'form-date') ?><br/>
+
+ <?= $this->formLabel(t('End Date'), 'to') ?>
+ <?= $this->formText('to', $values, $errors, array('required', 'placeholder="'.$this->inList($date_format, $date_formats).'"'), 'form-date') ?>
+
+ <div class="form-help"><?= t('Others formats accepted: %s and %s', date('Y-m-d'), date('Y_m_d')) ?></div>
+
+ <div class="form-actions">
+ <input type="submit" value="<?= t('Execute') ?>" class="btn btn-blue"/>
+ </div>
+</form> \ No newline at end of file
diff --git a/app/Template/project/integrations.php b/app/Template/project/integrations.php
index 194bd672..4f6553ad 100644
--- a/app/Template/project/integrations.php
+++ b/app/Template/project/integrations.php
@@ -8,7 +8,7 @@
<p class="form-help"><a href="http://kanboard.net/documentation/github-webhooks" target="_blank"><?= t('Help on Github webhooks') ?></a></p>
</div>
-<h3><i class="fa fa-git fa-fw"></i>&nbsp;<?= t('Gitlab webhooks') ?></h3>
+<h3><img src="assets/img/gitlab-icon.png"/>&nbsp;<?= t('Gitlab webhooks') ?></h3>
<div class="listing">
<input type="text" class="auto-select" readonly="readonly" value="<?= $this->getCurrentBaseUrl().$this->u('webhook', 'gitlab', array('token' => $webhook_token, 'project_id' => $project['id'])) ?>"/><br/>
<p class="form-help"><a href="http://kanboard.net/documentation/gitlab-webhooks" target="_blank"><?= t('Help on Gitlab webhooks') ?></a></p>
diff --git a/app/Template/project/sidebar.php b/app/Template/project/sidebar.php
index 4afc8ba9..47458144 100644
--- a/app/Template/project/sidebar.php
+++ b/app/Template/project/sidebar.php
@@ -63,6 +63,9 @@
<?= $this->a(t('Subtasks'), 'export', 'subtasks', array('project_id' => $project['id'])) ?>
</li>
<li>
+ <?= $this->a(t('Task transitions'), 'export', 'transitions', array('project_id' => $project['id'])) ?>
+ </li>
+ <li>
<?= $this->a(t('Daily project summary'), 'export', 'summary', array('project_id' => $project['id'])) ?>
</li>
</ul>
diff --git a/app/Template/task/sidebar.php b/app/Template/task/sidebar.php
index f41be14d..cb3b3c69 100644
--- a/app/Template/task/sidebar.php
+++ b/app/Template/task/sidebar.php
@@ -4,6 +4,9 @@
<li>
<?= $this->a(t('Summary'), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
</li>
+ <li>
+ <?= $this->a(t('Transitions'), 'task', 'transitions', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
+ </li>
<?php if ($task['time_estimated'] > 0 || $task['time_spent'] > 0): ?>
<li>
<?= $this->a(t('Time tracking'), 'task', 'timesheet', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
diff --git a/app/Template/task/transitions.php b/app/Template/task/transitions.php
new file mode 100644
index 00000000..2f45eb39
--- /dev/null
+++ b/app/Template/task/transitions.php
@@ -0,0 +1,26 @@
+<div class="page-header">
+ <h2><?= t('Transitions') ?></h2>
+</div>
+
+<?php if (empty($transitions)): ?>
+ <p class="alert"><?= t('There is nothing to show.') ?></p>
+<?php else: ?>
+ <table class="table-stripped">
+ <tr>
+ <th><?= t('Date') ?></th>
+ <th><?= t('Source column') ?></th>
+ <th><?= t('Destination column') ?></th>
+ <th><?= t('Executer') ?></th>
+ <th><?= t('Time spent in the column') ?></th>
+ </tr>
+ <?php foreach ($transitions as $transition): ?>
+ <tr>
+ <td><?= dt('%B %e, %Y at %k:%M %p', $transition['date']) ?></td>
+ <td><?= $this->e($transition['src_column']) ?></td>
+ <td><?= $this->e($transition['dst_column']) ?></td>
+ <td><?= $this->a($this->e($transition['name'] ?: $transition['username']), 'user', 'show', array('user_id' => $transition['user_id'])) ?></td>
+ <td><?= n(round($transition['time_spent'] / 3600, 2)).' '.t('hours') ?></td>
+ </tr>
+ <?php endforeach ?>
+ </table>
+<?php endif ?> \ No newline at end of file
diff --git a/assets/img/gitlab-icon.png b/assets/img/gitlab-icon.png
new file mode 100644
index 00000000..7e1eaa5c
--- /dev/null
+++ b/assets/img/gitlab-icon.png
Binary files differ
diff --git a/assets/img/hipchat-icon.png b/assets/img/hipchat-icon.png
new file mode 100644
index 00000000..1b0a825f
--- /dev/null
+++ b/assets/img/hipchat-icon.png
Binary files differ
diff --git a/assets/js/app.js b/assets/js/app.js
index a1fc5d56..52152a87 100644
--- a/assets/js/app.js
+++ b/assets/js/app.js
@@ -141,9 +141,9 @@ $("#popover-content").click(function(a){a.stopPropagation()});$(".close-popover"
return""!=a?"visible"==document[a]:!0},SetStorageItem:function(a,c){"undefined"!==typeof Storage&&localStorage.setItem(a,c)},GetStorageItem:function(a){return"undefined"!==typeof Storage?localStorage.getItem(a):""},MarkdownPreview:function(a){a.preventDefault();var c=$(this),b=$(this).closest("ul"),d=$(".write-area"),e=$(".preview-area"),f=$("textarea");$.ajax({url:"?controller=app&action=preview",contentType:"application/json",type:"POST",processData:!1,dataType:"html",data:JSON.stringify({text:f.val()})}).done(function(a){b.find("li").removeClass("form-tab-selected");
c.parent().addClass("form-tab-selected");e.find(".markdown").html(a);e.css("height",f.css("height"));e.css("width",f.css("width"));d.hide();e.show()})},MarkdownWriter:function(a){a.preventDefault();$(this).closest("ul").find("li").removeClass("form-tab-selected");$(this).parent().addClass("form-tab-selected");$(".write-area").show();$(".preview-area").hide()},CheckSession:function(){$(".form-login").length||$.ajax({cache:!1,url:$("body").data("status-url"),statusCode:{401:function(){window.location=
$("body").data("login-url")}}})},Init:function(){$("#board-selector").chosen({width:180,no_results_text:$("#board-selector").data("notfound")});$("#board-selector").change(function(){window.location=$(this).attr("data-board-url").replace(/PROJECT_ID/g,$(this).val())});window.setInterval(Kanboard.CheckSession,6E4);Mousetrap.bindGlobal("mod+enter",function(){$("form").submit()});Mousetrap.bind("b",function(a){a.preventDefault();$("#board-selector").trigger("chosen:open")});$(".column-tooltip").tooltip({content:function(){return'<div class="markdown">'+
-$(this).attr("title")+"</div>"},position:{my:"left-20 top",at:"center bottom+9",using:function(a,c){$(this).css(a);var b=c.target.left+c.target.width/2-c.element.left-20;$("<div>").addClass("tooltip-arrow").addClass(c.vertical).addClass(0==b?"align-left":"align-right").appendTo(this)}}});$.datepicker.setDefaults($.datepicker.regional[$("body").data("js-lang")]);Kanboard.InitAfterAjax()},InitAfterAjax:function(){$(document).on("click",".popover",Kanboard.Popover);$(".form-date").datepicker({showOtherMonths:!0,
-selectOtherMonths:!0,dateFormat:"yy-mm-dd",constrainInput:!1});$("#markdown-preview").click(Kanboard.MarkdownPreview);$("#markdown-write").click(Kanboard.MarkdownWriter);$(".auto-select").focus(function(){$(this).select()});$(".dropit-submenu").hide();$(".dropdown").not(".dropit").dropit({triggerParentEl:"span"});$(".task-autocomplete").length&&($(".task-autocomplete").parent().find("input[type=submit]").attr("disabled","disabled"),$(".task-autocomplete").autocomplete({source:$(".task-autocomplete").data("search-url"),
-minLength:2,select:function(a,c){var b=$(".task-autocomplete").data("dst-field");$("input[name="+b+"]").val(c.item.id);$(".task-autocomplete").parent().find("input[type=submit]").removeAttr("disabled")}}))}}}();
+$(this).attr("title")+"</div>"},position:{my:"left-20 top",at:"center bottom+9",using:function(a,c){$(this).css(a);var b=c.target.left+c.target.width/2-c.element.left-20;$("<div>").addClass("tooltip-arrow").addClass(c.vertical).addClass(0==b?"align-left":"align-right").appendTo(this)}}});$.datepicker.setDefaults($.datepicker.regional[$("body").data("js-lang")]);Kanboard.InitAfterAjax()},InitAfterAjax:function(){$(document).on("click",".popover",Kanboard.Popover);$("input[autofocus]").each(function(a,
+c){$(this).focus()});$(".form-date").datepicker({showOtherMonths:!0,selectOtherMonths:!0,dateFormat:"yy-mm-dd",constrainInput:!1});$("#markdown-preview").click(Kanboard.MarkdownPreview);$("#markdown-write").click(Kanboard.MarkdownWriter);$(".auto-select").focus(function(){$(this).select()});$(".dropit-submenu").hide();$(".dropdown").not(".dropit").dropit({triggerParentEl:"span"});$(".task-autocomplete").length&&($(".task-autocomplete").parent().find("input[type=submit]").attr("disabled","disabled"),
+$(".task-autocomplete").autocomplete({source:$(".task-autocomplete").data("search-url"),minLength:2,select:function(a,c){var b=$(".task-autocomplete").data("dst-field");$("input[name="+b+"]").val(c.item.id);$(".task-autocomplete").parent().find("input[type=submit]").removeAttr("disabled")}}))}}}();
Kanboard.Board=function(){function a(a){a.preventDefault();a.stopPropagation();Kanboard.Popover(a,Kanboard.InitAfterAjax)}function c(){Mousetrap.bind("n",function(){Kanboard.OpenPopover($("#board").data("task-creation-url"),Kanboard.InitAfterAjax)});Mousetrap.bind("s",function(){"expanded"===(Kanboard.GetStorageItem(d())||"expanded")?(e(),Kanboard.SetStorageItem(d(),"collapsed")):(f(),Kanboard.SetStorageItem(d(),"expanded"))});Mousetrap.bind("c",function(){p()})}function b(){$(".filter-expand-link").click(function(a){a.preventDefault();
f();Kanboard.SetStorageItem(d(),"expanded")});$(".filter-collapse-link").click(function(a){a.preventDefault();e();Kanboard.SetStorageItem(d(),"collapsed")});g()}function d(){return"board_stacking_"+$("#board").data("project-id")}function e(){$(".filter-collapse").hide();$(".task-board-collapsed").show();$(".filter-expand").show();$(".task-board-expanded").hide()}function f(){$(".filter-collapse").show();$(".task-board-collapsed").hide();$(".filter-expand").hide();$(".task-board-expanded").show()}
function g(){"expanded"===(Kanboard.GetStorageItem(d())||"expanded")?f():e()}function h(){$(".column").sortable({delay:300,distance:5,connectWith:".column",placeholder:"draggable-placeholder",stop:function(a,b){q(b.item.attr("data-task-id"),b.item.parent().attr("data-column-id"),b.item.index()+1,b.item.parent().attr("data-swimlane-id"))}});$("#board").on("click",".task-board-popover",a);$("#board").on("click",".task-board",function(){window.location=$(this).data("task-url")});$(".task-board-tooltip").tooltip({track:!1,
diff --git a/assets/js/src/base.js b/assets/js/src/base.js
index 05846ab8..0cd0e7df 100644
--- a/assets/js/src/base.js
+++ b/assets/js/src/base.js
@@ -222,6 +222,11 @@ var Kanboard = (function() {
// Popover
$(document).on("click", ".popover", Kanboard.Popover);
+ // Autofocus fields (html5 autofocus works only with page onload)
+ $("input[autofocus]").each(function(index, element) {
+ $(this).focus();
+ })
+
// Datepicker
$(".form-date").datepicker({
showOtherMonths: true,
@@ -243,6 +248,7 @@ var Kanboard = (function() {
$(".dropit-submenu").hide();
$('.dropdown').not(".dropit").dropit({ triggerParentEl : "span" });
+ // Task auto-completion
if ($(".task-autocomplete").length) {
$(".task-autocomplete").parent().find("input[type=submit]").attr('disabled','disabled');
diff --git a/composer.json b/composer.json
index 0ab8f511..8e78ab39 100644
--- a/composer.json
+++ b/composer.json
@@ -1,5 +1,6 @@
{
"require" : {
+ "php": ">=5.3",
"ext-mbstring" : "*",
"fguillot/simple-validator" : "dev-master",
"swiftmailer/swiftmailer" : "@stable",
@@ -24,4 +25,4 @@
"require-dev" : {
"symfony/stopwatch" : "~2.6"
}
-} \ No newline at end of file
+}
diff --git a/composer.lock b/composer.lock
index 3d68e4eb..3331ecd1 100644
--- a/composer.lock
+++ b/composer.lock
@@ -88,12 +88,12 @@
"source": {
"type": "git",
"url": "https://github.com/fguillot/picoDb.git",
- "reference": "d7ef5561d6d76c50717492822813125f9699700a"
+ "reference": "cd6a571d2de5c0b30d538d7cd6603dc16b25b844"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/fguillot/picoDb/zipball/d7ef5561d6d76c50717492822813125f9699700a",
- "reference": "d7ef5561d6d76c50717492822813125f9699700a",
+ "url": "https://api.github.com/repos/fguillot/picoDb/zipball/cd6a571d2de5c0b30d538d7cd6603dc16b25b844",
+ "reference": "cd6a571d2de5c0b30d538d7cd6603dc16b25b844",
"shasum": ""
},
"require": {
@@ -117,7 +117,7 @@
],
"description": "Minimalist database query builder",
"homepage": "https://github.com/fguillot/picoDb",
- "time": "2015-03-15 21:03:40"
+ "time": "2015-03-27 02:21:18"
},
{
"name": "fguillot/simple-validator",
@@ -393,17 +393,17 @@
},
{
"name": "symfony/console",
- "version": "v2.6.4",
+ "version": "v2.6.5",
"target-dir": "Symfony/Component/Console",
"source": {
"type": "git",
"url": "https://github.com/symfony/Console.git",
- "reference": "e44154bfe3e41e8267d7a3794cd9da9a51cfac34"
+ "reference": "53f86497ccd01677e22435cfb7262599450a90d1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/Console/zipball/e44154bfe3e41e8267d7a3794cd9da9a51cfac34",
- "reference": "e44154bfe3e41e8267d7a3794cd9da9a51cfac34",
+ "url": "https://api.github.com/repos/symfony/Console/zipball/53f86497ccd01677e22435cfb7262599450a90d1",
+ "reference": "53f86497ccd01677e22435cfb7262599450a90d1",
"shasum": ""
},
"require": {
@@ -412,6 +412,7 @@
"require-dev": {
"psr/log": "~1.0",
"symfony/event-dispatcher": "~2.1",
+ "symfony/phpunit-bridge": "~2.7",
"symfony/process": "~2.1"
},
"suggest": {
@@ -446,21 +447,21 @@
],
"description": "Symfony Console Component",
"homepage": "http://symfony.com",
- "time": "2015-01-25 04:39:26"
+ "time": "2015-03-13 17:37:22"
},
{
"name": "symfony/event-dispatcher",
- "version": "v2.6.4",
+ "version": "v2.6.5",
"target-dir": "Symfony/Component/EventDispatcher",
"source": {
"type": "git",
"url": "https://github.com/symfony/EventDispatcher.git",
- "reference": "f75989f3ab2743a82fe0b03ded2598a2b1546813"
+ "reference": "70f7c8478739ad21e3deef0d977b38c77f1fb284"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/f75989f3ab2743a82fe0b03ded2598a2b1546813",
- "reference": "f75989f3ab2743a82fe0b03ded2598a2b1546813",
+ "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/70f7c8478739ad21e3deef0d977b38c77f1fb284",
+ "reference": "70f7c8478739ad21e3deef0d977b38c77f1fb284",
"shasum": ""
},
"require": {
@@ -471,6 +472,7 @@
"symfony/config": "~2.0,>=2.0.5",
"symfony/dependency-injection": "~2.6",
"symfony/expression-language": "~2.6",
+ "symfony/phpunit-bridge": "~2.7",
"symfony/stopwatch": "~2.3"
},
"suggest": {
@@ -504,28 +506,31 @@
],
"description": "Symfony EventDispatcher Component",
"homepage": "http://symfony.com",
- "time": "2015-02-01 16:10:57"
+ "time": "2015-03-13 17:37:22"
}
],
"packages-dev": [
{
"name": "symfony/stopwatch",
- "version": "v2.6.4",
+ "version": "v2.6.5",
"target-dir": "Symfony/Component/Stopwatch",
"source": {
"type": "git",
"url": "https://github.com/symfony/Stopwatch.git",
- "reference": "e8da5286132ba75ce4b4275fbf0f4cd369bfd71c"
+ "reference": "ba4e774f71e2ce3e3f65cabac4031b9029972af5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/Stopwatch/zipball/e8da5286132ba75ce4b4275fbf0f4cd369bfd71c",
- "reference": "e8da5286132ba75ce4b4275fbf0f4cd369bfd71c",
+ "url": "https://api.github.com/repos/symfony/Stopwatch/zipball/ba4e774f71e2ce3e3f65cabac4031b9029972af5",
+ "reference": "ba4e774f71e2ce3e3f65cabac4031b9029972af5",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
+ "require-dev": {
+ "symfony/phpunit-bridge": "~2.7"
+ },
"type": "library",
"extra": {
"branch-alias": {
@@ -553,7 +558,7 @@
],
"description": "Symfony Stopwatch Component",
"homepage": "http://symfony.com",
- "time": "2015-01-03 08:01:59"
+ "time": "2015-02-24 11:52:21"
}
],
"aliases": [],
diff --git a/docs/cli.markdown b/docs/cli.markdown
index 0e2a0203..b742e0ac 100644
--- a/docs/cli.markdown
+++ b/docs/cli.markdown
@@ -17,16 +17,16 @@ $ ./kanboard
Kanboard version master
Usage:
- [options] command [arguments]
+ command [options] [arguments]
Options:
- --help (-h) Display this help message.
- --quiet (-q) Do not output any message.
- --verbose (-v|vv|vvv) Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug.
- --version (-V) Display this application version.
- --ansi Force ANSI output.
- --no-ansi Disable ANSI output.
- --no-interaction (-n) Do not ask any interactive question.
+ --help (-h) Display this help message
+ --quiet (-q) Do not output any message
+ --verbose (-v|vv|vvv) Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
+ --version (-V) Display this application version
+ --ansi Force ANSI output
+ --no-ansi Disable ANSI output
+ --no-interaction (-n) Do not ask any interactive question
Available commands:
help Displays help for a command
@@ -35,6 +35,7 @@ export
export:daily-project-summary Daily project summary CSV export (number of tasks per column and per day)
export:subtasks Subtasks CSV export
export:tasks Tasks CSV export
+ export:transitions Task transitions CSV export
notification
notification:overdue-tasks Send notifications for overdue tasks
projects
@@ -44,6 +45,22 @@ projects
Available commands
------------------
+### Tasks CSV export
+
+Usage:
+
+```bash
+./kanboard export:tasks <project_id> <start_date> <end_date>
+```
+
+Example:
+
+```bash
+./kanboard export:tasks 1 2014-10-01 2014-11-30 > /tmp/my_custom_export.csv
+```
+
+CSV data are sent to `stdout`.
+
### Subtasks CSV export
Usage:
@@ -58,21 +75,33 @@ Example:
./kanboard export:subtasks 1 2014-10-01 2014-11-30 > /tmp/my_custom_export.csv
```
-### Tasks CSV export
+### Task transitions CSV export
Usage:
```bash
-./kanboard export:tasks <project_id> <start_date> <end_date>
+./kanboard export:transitions <project_id> <start_date> <end_date>
```
Example:
```bash
-./kanboard export:tasks 1 2014-10-01 2014-11-30 > /tmp/my_custom_export.csv
+./kanboard export:transitions 1 2014-10-01 2014-11-30 > /tmp/my_custom_export.csv
```
-CSV data are sent to stdout.
+### Export daily summaries data in CSV
+
+The exported data will be printed on the standard output:
+
+```bash
+./kanboard export:daily-project-summary <project_id> <start_date> <end_date>
+```
+
+Example:
+
+```bash
+./kanboard export:daily-project-summary 1 2014-10-01 2014-11-30 > /tmp/my_custom_export.csv
+```
### Send notifications for overdue tasks
@@ -111,17 +140,3 @@ Run calculation for Project #0
Run calculation for Project #1
Run calculation for Project #10
```
-
-### Export daily summaries data in CSV
-
-The exported data will be printed on the standard output:
-
-```bash
-./kanboard export:daily-project-summary <project_id> <start_date> <end_date>
-```
-
-Example:
-
-```bash
-./kanboard export:daily-project-summary 1 2014-10-01 2014-11-30 > /tmp/my_custom_export.csv
-```
diff --git a/docs/hipchat.markdown b/docs/hipchat.markdown
new file mode 100644
index 00000000..45d93eb2
--- /dev/null
+++ b/docs/hipchat.markdown
@@ -0,0 +1,31 @@
+Hipchat integration
+===================
+
+Send notifications to a room
+-----------------------------
+
+Example of notifications:
+
+![Hipchat notification](http://kanboard.net/screenshots/documentation/hipchat-notification.png)
+
+This feature use the room notification token system of Hipchat.
+
+### Hipchat configuration
+
+![Hipchat room token](http://kanboard.net/screenshots/documentation/hipchat-room-token.png)
+
+1. Go to to **My account**
+2. Click on the tab **Rooms** and select the room you want to send the notifications
+3. On the left, choose **Tokens**
+4. Enter a label, by example "Kanboard" and save
+
+### Kanboard configuration
+
+![Hipchat settings](http://kanboard.net/screenshots/documentation/hipchat-settings.png)
+
+1. Go to **Settings > Integrations > Hipchat**
+2. Replace the API url if you use the self-hosted version of Hipchat
+3. Set the room name or the room API ID
+4. Copy and paste the token generated previously
+
+Now, all Kanboard events will be sent to the Hipchat room.
diff --git a/docs/slack.markdown b/docs/slack.markdown
new file mode 100644
index 00000000..89e3006b
--- /dev/null
+++ b/docs/slack.markdown
@@ -0,0 +1,21 @@
+Slack integration
+=================
+
+Send notifications to a channel
+-------------------------------
+
+Example of notifications:
+
+![Slack notification](http://kanboard.net/screenshots/documentation/slack-notification.png)
+
+This feature use the [Incoming webhook](https://api.slack.com/incoming-webhooks) system of Slack.
+
+### Slack configuration
+
+![Slack webhook creation](http://kanboard.net/screenshots/documentation/slack-add-incoming-webhook.png)
+
+1. Click on the Team dropdown and choose **Configure Integrations**
+2. On the list of services, scroll-down and choose **DIY Integrations & Customizations > Incoming WebHooks**
+3. Copy the webhook url to the Kanboard settings page: **Settings > Integrations > Slack**
+
+Now, all Kanboard events will be sent to the Slack channel.
diff --git a/kanboard b/kanboard
index 8b772f84..f6456ea1 100755
--- a/kanboard
+++ b/kanboard
@@ -14,4 +14,5 @@ $application->add(new Console\SubtaskExport($container));
$application->add(new Console\TaskExport($container));
$application->add(new Console\ProjectDailySummaryCalculation($container));
$application->add(new Console\ProjectDailySummaryExport($container));
+$application->add(new Console\TransitionExport($container));
$application->run();