summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/Auth/Ldap.php298
-rw-r--r--app/Controller/Budget.php135
-rw-r--r--app/Controller/Doc.php20
-rw-r--r--app/Controller/File.php38
-rw-r--r--app/Controller/Hourlyrate.php89
-rw-r--r--app/Core/Base.php2
-rw-r--r--app/Core/ObjectStorage/FileStorage.php150
-rw-r--r--app/Core/ObjectStorage/ObjectStorageException.php9
-rw-r--r--app/Core/ObjectStorage/ObjectStorageInterface.php68
-rw-r--r--app/Core/PluginBase.php47
-rw-r--r--app/Core/PluginLoader.php137
-rw-r--r--app/Core/Router.php46
-rw-r--r--app/Core/Template.php52
-rw-r--r--app/Core/Tool.php101
-rw-r--r--app/Core/Translator.php23
-rw-r--r--app/Helper/Hook.php49
-rw-r--r--app/Integration/Mailgun.php4
-rw-r--r--app/Integration/Postmark.php5
-rw-r--r--app/Integration/Sendgrid.php4
-rw-r--r--app/Locale/cs_CZ/translations.php32
-rw-r--r--app/Locale/da_DK/translations.php32
-rw-r--r--app/Locale/de_DE/translations.php32
-rw-r--r--app/Locale/es_ES/translations.php32
-rw-r--r--app/Locale/fi_FI/translations.php32
-rw-r--r--app/Locale/fr_FR/translations.php32
-rw-r--r--app/Locale/hu_HU/translations.php32
-rw-r--r--app/Locale/it_IT/translations.php32
-rw-r--r--app/Locale/ja_JP/translations.php32
-rwxr-xr-x[-rw-r--r--]app/Locale/nb_NO/translations.php542
-rw-r--r--app/Locale/nl_NL/translations.php32
-rw-r--r--app/Locale/pl_PL/translations.php32
-rw-r--r--app/Locale/pt_BR/translations.php32
-rw-r--r--app/Locale/pt_PT/translations.php32
-rw-r--r--app/Locale/ru_RU/translations.php34
-rw-r--r--app/Locale/sr_Latn_RS/translations.php32
-rw-r--r--app/Locale/sv_SE/translations.php32
-rw-r--r--app/Locale/th_TH/translations.php32
-rw-r--r--app/Locale/tr_TR/translations.php32
-rw-r--r--app/Locale/zh_CN/translations.php32
-rw-r--r--app/Model/Acl.php13
-rw-r--r--app/Model/Budget.php214
-rw-r--r--app/Model/File.php244
-rw-r--r--app/Model/HourlyRate.php121
-rw-r--r--app/Model/Subtask.php41
-rw-r--r--app/Model/SubtaskTimeTracking.php19
-rw-r--r--app/Schema/Mysql.php39
-rw-r--r--app/Schema/Postgres.php36
-rw-r--r--app/Schema/Sqlite.php36
-rw-r--r--app/ServiceProvider/ClassProvider.php25
-rw-r--r--app/Subscriber/SubtaskTimeTrackingSubscriber.php1
-rw-r--r--app/Template/app/sidebar.php1
-rw-r--r--app/Template/budget/breakdown.php30
-rw-r--r--app/Template/budget/create.php47
-rw-r--r--app/Template/budget/index.php34
-rw-r--r--app/Template/budget/remove.php13
-rw-r--r--app/Template/budget/sidebar.php16
-rw-r--r--app/Template/config/sidebar.php1
-rw-r--r--app/Template/currency/index.php2
-rw-r--r--app/Template/export/sidebar.php1
-rw-r--r--app/Template/file/show.php2
-rw-r--r--app/Template/header.php33
-rw-r--r--app/Template/hourlyrate/index.php46
-rw-r--r--app/Template/hourlyrate/remove.php13
-rw-r--r--app/Template/layout.php42
-rw-r--r--app/Template/project/dropdown.php30
-rw-r--r--app/Template/project/sidebar.php2
-rw-r--r--app/Template/project_user/sidebar.php2
-rw-r--r--app/Template/task/sidebar.php4
-rw-r--r--app/Template/user/sidebar.php7
-rw-r--r--app/common.php120
-rw-r--r--app/routes.php117
71 files changed, 1581 insertions, 2200 deletions
diff --git a/app/Auth/Ldap.php b/app/Auth/Ldap.php
index 2f8f791e..0ccd09a4 100644
--- a/app/Auth/Ldap.php
+++ b/app/Auth/Ldap.php
@@ -20,6 +20,178 @@ class Ldap extends Base
const AUTH_NAME = 'LDAP';
/**
+ * Get LDAP server name
+ *
+ * @access public
+ * @return string
+ */
+ public function getLdapServer()
+ {
+ return LDAP_SERVER;
+ }
+
+ /**
+ * Get LDAP server port
+ *
+ * @access public
+ * @return integer
+ */
+ public function getLdapPort()
+ {
+ return LDAP_PORT;
+ }
+
+ /**
+ * Get LDAP username (proxy auth)
+ *
+ * @access public
+ * @return string
+ */
+ public function getLdapUsername()
+ {
+ return LDAP_USERNAME;
+ }
+
+ /**
+ * Get LDAP password (proxy auth)
+ *
+ * @access public
+ * @return string
+ */
+ public function getLdapPassword()
+ {
+ return LDAP_PASSWORD;
+ }
+
+ /**
+ * Get LDAP Base DN
+ *
+ * @access public
+ * @return string
+ */
+ public function getLdapBaseDn()
+ {
+ return LDAP_ACCOUNT_BASE;
+ }
+
+ /**
+ * Get LDAP account id attribute
+ *
+ * @access public
+ * @return string
+ */
+ public function getLdapAccountId()
+ {
+ return LDAP_ACCOUNT_ID;
+ }
+
+ /**
+ * Get LDAP account email attribute
+ *
+ * @access public
+ * @return string
+ */
+ public function getLdapAccountEmail()
+ {
+ return LDAP_ACCOUNT_EMAIL;
+ }
+
+ /**
+ * Get LDAP account name attribute
+ *
+ * @access public
+ * @return string
+ */
+ public function getLdapAccountName()
+ {
+ return LDAP_ACCOUNT_FULLNAME;
+ }
+
+ /**
+ * Get LDAP account memberof attribute
+ *
+ * @access public
+ * @return string
+ */
+ public function getLdapAccountMemberOf()
+ {
+ return LDAP_ACCOUNT_MEMBEROF;
+ }
+
+ /**
+ * Get LDAP admin group DN
+ *
+ * @access public
+ * @return string
+ */
+ public function getLdapGroupAdmin()
+ {
+ return LDAP_GROUP_ADMIN_DN;
+ }
+
+ /**
+ * Get LDAP project admin group DN
+ *
+ * @access public
+ * @return string
+ */
+ public function getLdapGroupProjectAdmin()
+ {
+ return LDAP_GROUP_PROJECT_ADMIN_DN;
+ }
+
+ /**
+ * Get LDAP username pattern
+ *
+ * @access public
+ * @return string
+ */
+ public function getLdapUserPattern($username)
+ {
+ return sprintf(LDAP_USER_PATTERN, $username);
+ }
+
+ /**
+ * Return true if the LDAP username is case sensitive
+ *
+ * @access public
+ * @return boolean
+ */
+ public function isLdapAccountCaseSensitive()
+ {
+ return LDAP_USERNAME_CASE_SENSITIVE;
+ }
+
+ /**
+ * Return true if the automatic account creation is enabled
+ *
+ * @access public
+ * @return boolean
+ */
+ public function isLdapAccountCreationEnabled()
+ {
+ return LDAP_ACCOUNT_CREATION;
+ }
+
+ /**
+ * Ge the list of attributes to fetch when reading the LDAP user entry
+ *
+ * Must returns array with index that start at 0 otherwise ldap_search returns a warning "Array initialization wrong"
+ *
+ * @access public
+ * @return array
+ */
+ public function getProfileAttributes()
+ {
+ return array_values(array_filter(array(
+ $this->getLdapAccountId(),
+ $this->getLdapAccountName(),
+ $this->getLdapAccountEmail(),
+ $this->getLdapAccountMemberOf()
+ )));
+ }
+
+ /**
* Authenticate the user
*
* @access public
@@ -29,7 +201,7 @@ class Ldap extends Base
*/
public function authenticate($username, $password)
{
- $username = LDAP_USERNAME_CASE_SENSITIVE ? $username : strtolower($username);
+ $username = $this->isLdapAccountCaseSensitive() ? $username : strtolower($username);
$result = $this->findUser($username, $password);
if (is_array($result)) {
@@ -46,7 +218,7 @@ class Ldap extends Base
else {
// We create automatically a new user
- if (LDAP_ACCOUNT_CREATION && $this->user->create($result) !== false) {
+ if ($this->isLdapAccountCreationEnabled() && $this->user->create($result) !== false) {
$user = $this->user->getByUsername($username);
}
else {
@@ -87,11 +259,9 @@ class Ldap extends Base
* LDAP connection
*
* @access public
- * @param string $ldap_hostname
- * @param integer $ldap_port
* @return resource|boolean
*/
- public function connect($ldap_hostname = LDAP_SERVER, $ldap_port = LDAP_PORT)
+ public function connect()
{
if (! function_exists('ldap_connect')) {
$this->logger->error('The PHP LDAP extension is required');
@@ -103,10 +273,10 @@ class Ldap extends Base
putenv('LDAPTLS_REQCERT=never');
}
- $ldap = ldap_connect($ldap_hostname, $ldap_port);
+ $ldap = ldap_connect($this->getLdapServer(), $this->getLdapPort());
if ($ldap === false) {
- $this->logger->error('Unable to connect to the LDAP server: "'.LDAP_SERVER.'"');
+ $this->logger->error('Unable to connect to the LDAP server');
return false;
}
@@ -131,19 +301,17 @@ class Ldap extends Base
* @param string $username
* @param string $password
* @param string $ldap_type
- * @param string $ldap_username
- * @param string $ldap_password
* @return boolean
*/
- public function bind($ldap, $username, $password, $ldap_type = LDAP_BIND_TYPE, $ldap_username = LDAP_USERNAME, $ldap_password = LDAP_PASSWORD)
+ public function bind($ldap, $username, $password, $ldap_type = LDAP_BIND_TYPE)
{
if ($ldap_type === 'user') {
- $ldap_username = sprintf($ldap_username, $username);
+ $ldap_username = $this->getLdapUserPattern($username);
$ldap_password = $password;
}
else if ($ldap_type === 'proxy') {
- $ldap_username = $ldap_username;
- $ldap_password = $ldap_password;
+ $ldap_username = $this->getLdapUsername();
+ $ldap_password = $this->getLdapPassword();
}
else {
$ldap_username = null;
@@ -164,21 +332,12 @@ class Ldap extends Base
* @param resource $ldap
* @param string $username
* @param string $password
- * @param string $base_dn
- * @param string $user_pattern
* @return boolean|array
*/
- public function getProfile($ldap, $username, $password, $base_dn = LDAP_ACCOUNT_BASE, $user_pattern = LDAP_USER_PATTERN)
+ public function getProfile($ldap, $username, $password)
{
- $sr = ldap_search($ldap, $base_dn, sprintf($user_pattern, $username), $this->getProfileAttributes());
-
- if ($sr === false) {
- return false;
- }
-
- $entries = ldap_get_entries($ldap, $sr);
-
- if ($entries === false || count($entries) === 0 || $entries['count'] == 0) {
+ $entries = $this->executeQuery($ldap, $this->getLdapUserPattern($username));
+ if ($entries === false) {
return false;
}
@@ -200,28 +359,21 @@ class Ldap extends Base
*/
public function prepareProfile($ldap, array $entries, $username)
{
+ if ($this->getLdapAccountId() !== '') {
+ $username = $this->getEntry($entries, $this->getLdapAccountId(), $username);
+ }
+
return array(
'username' => $username,
- 'name' => $this->getEntry($entries, LDAP_ACCOUNT_FULLNAME),
- 'email' => $this->getEntry($entries, LDAP_ACCOUNT_EMAIL),
- 'is_admin' => (int) $this->isMemberOf($this->getEntries($entries, LDAP_ACCOUNT_MEMBEROF), LDAP_GROUP_ADMIN_DN),
- 'is_project_admin' => (int) $this->isMemberOf($this->getEntries($entries, LDAP_ACCOUNT_MEMBEROF), LDAP_GROUP_PROJECT_ADMIN_DN),
+ 'name' => $this->getEntry($entries, $this->getLdapAccountName()),
+ 'email' => $this->getEntry($entries, $this->getLdapAccountEmail()),
+ 'is_admin' => (int) $this->isMemberOf($this->getEntries($entries, $this->getLdapAccountMemberOf()), $this->getLdapGroupAdmin()),
+ 'is_project_admin' => (int) $this->isMemberOf($this->getEntries($entries, $this->getLdapAccountMemberOf()), $this->getLdapGroupProjectAdmin()),
'is_ldap_user' => 1,
);
}
/**
- * Ge the list of attributes to fetch when reading the LDAP user entry
- *
- * @access public
- * @return array
- */
- public function getProfileAttributes()
- {
- return array(LDAP_ACCOUNT_FULLNAME, LDAP_ACCOUNT_EMAIL, LDAP_ACCOUNT_MEMBEROF);
- }
-
- /**
* Check group membership
*
* @access public
@@ -245,69 +397,81 @@ class Ldap extends Base
}
/**
- * Retrieve info on LDAP user
+ * Retrieve info on LDAP user by username or email
*
* @access public
- * @param string $username Username
- * @param string $email Email address
+ * @param string $username
+ * @param string $email
* @return boolean|array
*/
public function lookup($username = null, $email = null)
{
- $query = $this->getQuery($username, $email);
+ $query = $this->getLookupQuery($username, $email);
if ($query === '') {
return false;
}
- // Connect and attempt anonymous bind
+ // Connect and attempt anonymous or proxy binding
$ldap = $this->connect();
- if ($ldap === false || ! $this->bind($ldap, null, null, 'anonymous')) {
+ if ($ldap === false || ! $this->bind($ldap, null, null)) {
return false;
}
// Try to find user
- $sr = ldap_search($ldap, LDAP_ACCOUNT_BASE, $query, array(LDAP_ACCOUNT_FULLNAME, LDAP_ACCOUNT_EMAIL, LDAP_ACCOUNT_ID));
- if ($sr === false) {
+ $entries = $this->executeQuery($ldap, $query);
+ if ($entries === false) {
+ return false;
+ }
+
+ // User id not retrieved: LDAP_ACCOUNT_ID not properly configured
+ if (empty($username) && ! isset($entries[0][$this->getLdapAccountId()][0])) {
return false;
}
- $info = ldap_get_entries($ldap, $sr);
+ return $this->prepareProfile($ldap, $entries, $username);
+ }
- // User not found
- if (count($info) == 0 || $info['count'] == 0) {
+ /**
+ * Execute LDAP query
+ *
+ * @access private
+ * @param resource $ldap
+ * @param string $query
+ * @return boolean|array
+ */
+ private function executeQuery($ldap, $query)
+ {
+ $sr = ldap_search($ldap, $this->getLdapBaseDn(), $query, $this->getProfileAttributes());
+ if ($sr === false) {
return false;
}
- // User id not retrieved: LDAP_ACCOUNT_ID not properly configured
- if (empty($username) && ! isset($info[0][LDAP_ACCOUNT_ID][0])) {
+ $entries = ldap_get_entries($ldap, $sr);
+ if ($entries === false || count($entries) === 0 || $entries['count'] == 0) {
return false;
}
- return array(
- 'username' => $this->getEntry($info, LDAP_ACCOUNT_ID, $username),
- 'name' => $this->getEntry($info, LDAP_ACCOUNT_FULLNAME),
- 'email' => $this->getEntry($info, LDAP_ACCOUNT_EMAIL, $email),
- );
+ return $entries;
}
/**
* Get the LDAP query to find a user
*
* @access private
- * @param string $username Username
- * @param string $email Email address
+ * @param string $username
+ * @param string $email
* @return string
*/
- private function getQuery($username, $email)
+ private function getLookupQuery($username, $email)
{
- if ($username && $email) {
- return '(&('.sprintf(LDAP_USER_PATTERN, $username).')('.LDAP_ACCOUNT_EMAIL.'='.$email.'))';
+ if (! empty($username) && ! empty($email)) {
+ return '(&('.$this->getLdapUserPattern($username).')('.$this->getLdapAccountEmail().'='.$email.'))';
}
- else if ($username) {
- return sprintf(LDAP_USER_PATTERN, $username);
+ else if (! empty($username)) {
+ return $this->getLdapUserPattern($username);
}
- else if ($email) {
- return '('.LDAP_ACCOUNT_EMAIL.'='.$email.')';
+ else if (! empty($email)) {
+ return '('.$this->getLdapAccountEmail().'='.$email.')';
}
return '';
diff --git a/app/Controller/Budget.php b/app/Controller/Budget.php
deleted file mode 100644
index a2f7e0db..00000000
--- a/app/Controller/Budget.php
+++ /dev/null
@@ -1,135 +0,0 @@
-<?php
-
-namespace Controller;
-
-/**
- * Budget
- *
- * @package controller
- * @author Frederic Guillot
- */
-class Budget extends Base
-{
- /**
- * Budget index page
- *
- * @access public
- */
- public function index()
- {
- $project = $this->getProject();
-
- $this->response->html($this->projectLayout('budget/index', array(
- 'daily_budget' => $this->budget->getDailyBudgetBreakdown($project['id']),
- 'project' => $project,
- 'title' => t('Budget')
- ), 'budget/sidebar'));
- }
-
- /**
- * Cost breakdown by users/subtasks/tasks
- *
- * @access public
- */
- public function breakdown()
- {
- $project = $this->getProject();
-
- $paginator = $this->paginator
- ->setUrl('budget', 'breakdown', array('project_id' => $project['id']))
- ->setMax(30)
- ->setOrder('start')
- ->setDirection('DESC')
- ->setQuery($this->budget->getSubtaskBreakdown($project['id']))
- ->calculate();
-
- $this->response->html($this->projectLayout('budget/breakdown', array(
- 'paginator' => $paginator,
- 'project' => $project,
- 'title' => t('Budget')
- ), 'budget/sidebar'));
- }
-
- /**
- * Create budget lines
- *
- * @access public
- */
- public function create(array $values = array(), array $errors = array())
- {
- $project = $this->getProject();
-
- if (empty($values)) {
- $values['date'] = date('Y-m-d');
- }
-
- $this->response->html($this->projectLayout('budget/create', array(
- 'lines' => $this->budget->getAll($project['id']),
- 'values' => $values + array('project_id' => $project['id']),
- 'errors' => $errors,
- 'project' => $project,
- 'title' => t('Budget lines')
- ), 'budget/sidebar'));
- }
-
- /**
- * Validate and save a new budget
- *
- * @access public
- */
- public function save()
- {
- $project = $this->getProject();
-
- $values = $this->request->getValues();
- list($valid, $errors) = $this->budget->validateCreation($values);
-
- if ($valid) {
-
- if ($this->budget->create($values['project_id'], $values['amount'], $values['comment'], $values['date'])) {
- $this->session->flash(t('The budget line have been created successfully.'));
- $this->response->redirect($this->helper->url->to('budget', 'create', array('project_id' => $project['id'])));
- }
- else {
- $this->session->flashError(t('Unable to create the budget line.'));
- }
- }
-
- $this->create($values, $errors);
- }
-
- /**
- * Confirmation dialog before removing a budget
- *
- * @access public
- */
- public function confirm()
- {
- $project = $this->getProject();
-
- $this->response->html($this->projectLayout('budget/remove', array(
- 'project' => $project,
- 'budget_id' => $this->request->getIntegerParam('budget_id'),
- 'title' => t('Remove a budget line'),
- ), 'budget/sidebar'));
- }
-
- /**
- * Remove a budget
- *
- * @access public
- */
- public function remove()
- {
- $this->checkCSRFParam();
- $project = $this->getProject();
-
- if ($this->budget->remove($this->request->getIntegerParam('budget_id'))) {
- $this->session->flash(t('Budget line removed successfully.'));
- } else {
- $this->session->flashError(t('Unable to remove this budget line.'));
- }
-
- $this->response->redirect($this->helper->url->to('budget', 'create', array('project_id' => $project['id'])));
- }
-}
diff --git a/app/Controller/Doc.php b/app/Controller/Doc.php
index d9f7b5e7..f9f0a675 100644
--- a/app/Controller/Doc.php
+++ b/app/Controller/Doc.php
@@ -32,16 +32,24 @@ class Doc extends Base
public function show()
{
- $filename = $this->request->getStringParam('file', 'index');
+ $page = $this->request->getStringParam('file', 'index');
- if (! preg_match('/^[a-z0-9\-]+/', $filename)) {
- $filename = 'index';
+ if (! preg_match('/^[a-z0-9\-]+/', $page)) {
+ $page = 'index';
}
- $filename = __DIR__.'/../../doc/'.$filename.'.markdown';
+ $filenames = array(__DIR__.'/../../doc/'.$page.'.markdown');
+ $filename = __DIR__.'/../../doc/index.markdown';
- if (! file_exists($filename)) {
- $filename = __DIR__.'/../../doc/index.markdown';
+ if ($this->config->getCurrentLanguage() === 'fr_FR') {
+ array_unshift($filenames, __DIR__.'/../../doc/fr/'.$page.'.markdown');
+ }
+
+ foreach ($filenames as $file) {
+ if (file_exists($file)) {
+ $filename = $file;
+ break;
+ }
}
$this->response->html($this->template->layout('doc/show', $this->readFile($filename) + array(
diff --git a/app/Controller/File.php b/app/Controller/File.php
index f73a9de9..7b7c75ee 100644
--- a/app/Controller/File.php
+++ b/app/Controller/File.php
@@ -60,7 +60,7 @@ class File extends Base
{
$task = $this->getTask();
- if (! $this->file->upload($task['project_id'], $task['id'], 'files')) {
+ if (! $this->file->uploadFiles($task['project_id'], $task['id'], 'files')) {
$this->session->flashError(t('Unable to upload the file.'));
}
@@ -76,14 +76,13 @@ class File extends Base
{
$task = $this->getTask();
$file = $this->file->getById($this->request->getIntegerParam('file_id'));
- $filename = FILES_DIR.$file['path'];
- if ($file['task_id'] == $task['id'] && file_exists($filename)) {
- $this->response->forceDownload($file['name']);
- $this->response->binary(file_get_contents($filename));
+ if ($file['task_id'] != $task['id']) {
+ $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])));
}
- $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])));
+ $this->response->forceDownload($file['name']);
+ $this->objectStorage->passthru($file['path']);
}
/**
@@ -113,16 +112,13 @@ class File extends Base
{
$task = $this->getTask();
$file = $this->file->getById($this->request->getIntegerParam('file_id'));
- $filename = FILES_DIR.$file['path'];
-
- if ($file['task_id'] == $task['id'] && file_exists($filename)) {
- $metadata = getimagesize($filename);
- if (isset($metadata['mime'])) {
- $this->response->contentType($metadata['mime']);
- readfile($filename);
- }
+ if ($file['task_id'] != $task['id']) {
+ $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])));
}
+
+ $this->response->contentType($this->file->getImageMimeType($file['name']));
+ $this->objectStorage->passthru($file['path']);
}
/**
@@ -134,17 +130,13 @@ class File extends Base
{
$task = $this->getTask();
$file = $this->file->getById($this->request->getIntegerParam('file_id'));
- $filename = FILES_DIR.$file['path'];
-
- if ($file['task_id'] == $task['id'] && file_exists($filename)) {
- $this->response->contentType('image/jpeg');
- $this->file->generateThumbnail(
- $filename,
- $this->request->getIntegerParam('width'),
- $this->request->getIntegerParam('height')
- );
+ if ($file['task_id'] != $task['id']) {
+ $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])));
}
+
+ $this->response->contentType('image/jpeg');
+ $this->objectStorage->passthru($this->file->getThumbnailPath($file['path']));
}
/**
diff --git a/app/Controller/Hourlyrate.php b/app/Controller/Hourlyrate.php
deleted file mode 100644
index 19650ede..00000000
--- a/app/Controller/Hourlyrate.php
+++ /dev/null
@@ -1,89 +0,0 @@
-<?php
-
-namespace Controller;
-
-/**
- * Hourly Rate controller
- *
- * @package controller
- * @author Frederic Guillot
- */
-class Hourlyrate extends User
-{
- /**
- * Display rate and form
- *
- * @access public
- */
- public function index(array $values = array(), array $errors = array())
- {
- $user = $this->getUser();
-
- $this->response->html($this->layout('hourlyrate/index', array(
- 'rates' => $this->hourlyRate->getAllByUser($user['id']),
- 'currencies_list' => $this->config->getCurrencies(),
- 'values' => $values + array('user_id' => $user['id']),
- 'errors' => $errors,
- 'user' => $user,
- )));
- }
-
- /**
- * Validate and save a new rate
- *
- * @access public
- */
- public function save()
- {
- $values = $this->request->getValues();
- list($valid, $errors) = $this->hourlyRate->validateCreation($values);
-
- if ($valid) {
-
- if ($this->hourlyRate->create($values['user_id'], $values['rate'], $values['currency'], $values['date_effective'])) {
- $this->session->flash(t('Hourly rate created successfully.'));
- $this->response->redirect($this->helper->url->to('hourlyrate', 'index', array('user_id' => $values['user_id'])));
- }
- else {
- $this->session->flashError(t('Unable to save the hourly rate.'));
- }
- }
-
- $this->index($values, $errors);
- }
-
- /**
- * Confirmation dialag box to remove a row
- *
- * @access public
- */
- public function confirm()
- {
- $user = $this->getUser();
-
- $this->response->html($this->layout('hourlyrate/remove', array(
- 'rate_id' => $this->request->getIntegerParam('rate_id'),
- 'user' => $user,
- )));
- }
-
- /**
- * Remove a row
- *
- * @access public
- */
- public function remove()
- {
- $this->checkCSRFParam();
- $user = $this->getUser();
-
- if ($this->hourlyRate->remove($this->request->getIntegerParam('rate_id'))) {
- $this->session->flash(t('Rate removed successfully.'));
- }
- else {
- $this->session->flash(t('Unable to remove this rate.'));
- }
-
- $this->response->redirect($this->helper->url->to('hourlyrate', 'index', array('user_id' => $user['id'])));
- }
-}
diff --git a/app/Core/Base.php b/app/Core/Base.php
index 3db0cf74..5ed8f40a 100644
--- a/app/Core/Base.php
+++ b/app/Core/Base.php
@@ -35,7 +35,6 @@ use Pimple\Container;
* @property \Model\Action $action
* @property \Model\Authentication $authentication
* @property \Model\Board $board
- * @property \Model\Budget $budget
* @property \Model\Category $category
* @property \Model\Color $color
* @property \Model\Comment $comment
@@ -43,7 +42,6 @@ use Pimple\Container;
* @property \Model\Currency $currency
* @property \Model\DateParser $dateParser
* @property \Model\File $file
- * @property \Model\HourlyRate $hourlyRate
* @property \Model\LastLogin $lastLogin
* @property \Model\Link $link
* @property \Model\Notification $notification
diff --git a/app/Core/ObjectStorage/FileStorage.php b/app/Core/ObjectStorage/FileStorage.php
new file mode 100644
index 00000000..66c62334
--- /dev/null
+++ b/app/Core/ObjectStorage/FileStorage.php
@@ -0,0 +1,150 @@
+<?php
+
+namespace Core\ObjectStorage;
+
+/**
+ * Local File Storage
+ *
+ * @package ObjectStorage
+ * @author Frederic Guillot
+ */
+class FileStorage implements ObjectStorageInterface
+{
+ /**
+ * Base path
+ *
+ * @access private
+ * @var string
+ */
+ private $path = '';
+
+ /**
+ * Constructor
+ *
+ * @access public
+ * @param string $path
+ */
+ public function __construct($path)
+ {
+ $this->path = $path;
+ }
+
+ /**
+ * Fetch object contents
+ *
+ * @access public
+ * @param string $key
+ * @return string
+ */
+ public function get($key)
+ {
+ $filename = $this->path.DIRECTORY_SEPARATOR.$key;
+
+ if (! file_exists($filename)) {
+ throw new ObjectStorageException('File not found: '.$filename);
+ }
+
+ return file_get_contents($filename);
+ }
+
+ /**
+ * Save object
+ *
+ * @access public
+ * @param string $key
+ * @param string $blob
+ * @return string
+ */
+ public function put($key, &$blob)
+ {
+ $this->createFolder($key);
+
+ if (file_put_contents($this->path.DIRECTORY_SEPARATOR.$key, $blob) === false) {
+ throw new ObjectStorageException('Unable to write the file: '.$this->path.DIRECTORY_SEPARATOR.$key);
+ }
+ }
+
+ /**
+ * Output directly object content
+ *
+ * @access public
+ * @param string $key
+ */
+ public function passthru($key)
+ {
+ $filename = $this->path.DIRECTORY_SEPARATOR.$key;
+
+ if (! file_exists($filename)) {
+ throw new ObjectStorageException('File not found: '.$filename);
+ }
+
+ return readfile($filename);
+ }
+
+ /**
+ * Move local file to object storage
+ *
+ * @access public
+ * @param string $filename
+ * @param string $key
+ * @return boolean
+ */
+ public function moveFile($src_filename, $key)
+ {
+ $this->createFolder($key);
+ $dst_filename = $this->path.DIRECTORY_SEPARATOR.$key;
+
+ if (! rename($src_filename, $dst_filename)) {
+ throw new ObjectStorageException('Unable to move the file: '.$src_filename.' to '.$dst_filename);
+ }
+
+ return true;
+ }
+
+ /**
+ * Move uploaded file to object storage
+ *
+ * @access public
+ * @param string $filename
+ * @param string $key
+ * @return boolean
+ */
+ public function moveUploadedFile($filename, $key)
+ {
+ $this->createFolder($key);
+ return move_uploaded_file($filename, $this->path.DIRECTORY_SEPARATOR.$key);
+ }
+
+ /**
+ * Remove object
+ *
+ * @access public
+ * @param string $key
+ * @return boolean
+ */
+ public function remove($key)
+ {
+ $filename = $this->path.DIRECTORY_SEPARATOR.$key;
+
+ if (file_exists($filename)) {
+ return unlink($filename);
+ }
+
+ return false;
+ }
+
+ /**
+ * Create object folder
+ *
+ * @access private
+ * @param string $key
+ */
+ private function createFolder($key)
+ {
+ $folder = $this->path.DIRECTORY_SEPARATOR.dirname($key);
+
+ if (! is_dir($folder) && ! mkdir($folder, 0755, true)) {
+ throw new ObjectStorageException('Unable to create folder: '.$folder);
+ }
+ }
+}
diff --git a/app/Core/ObjectStorage/ObjectStorageException.php b/app/Core/ObjectStorage/ObjectStorageException.php
new file mode 100644
index 00000000..e89aeb25
--- /dev/null
+++ b/app/Core/ObjectStorage/ObjectStorageException.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace Core\ObjectStorage;
+
+use Exception;
+
+class ObjectStorageException extends Exception
+{
+}
diff --git a/app/Core/ObjectStorage/ObjectStorageInterface.php b/app/Core/ObjectStorage/ObjectStorageInterface.php
new file mode 100644
index 00000000..5440cf2b
--- /dev/null
+++ b/app/Core/ObjectStorage/ObjectStorageInterface.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace Core\ObjectStorage;
+
+/**
+ * Object Storage Interface
+ *
+ * @package ObjectStorage
+ * @author Frederic Guillot
+ */
+interface ObjectStorageInterface
+{
+ /**
+ * Fetch object contents
+ *
+ * @access public
+ * @param string $key
+ * @return string
+ */
+ public function get($key);
+
+ /**
+ * Save object
+ *
+ * @access public
+ * @param string $key
+ * @param string $blob
+ * @return string
+ */
+ public function put($key, &$blob);
+
+ /**
+ * Output directly object content
+ *
+ * @access public
+ * @param string $key
+ */
+ public function passthru($key);
+
+ /**
+ * Move local file to object storage
+ *
+ * @access public
+ * @param string $filename
+ * @param string $key
+ * @return boolean
+ */
+ public function moveFile($filename, $key);
+
+ /**
+ * Move uploaded file to object storage
+ *
+ * @access public
+ * @param string $filename
+ * @param string $key
+ * @return boolean
+ */
+ public function moveUploadedFile($filename, $key);
+
+ /**
+ * Remove object
+ *
+ * @access public
+ * @param string $key
+ * @return boolean
+ */
+ public function remove($key);
+}
diff --git a/app/Core/PluginBase.php b/app/Core/PluginBase.php
new file mode 100644
index 00000000..457afa03
--- /dev/null
+++ b/app/Core/PluginBase.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace Core;
+
+/**
+ * Plugin Base class
+ *
+ * @package core
+ * @author Frederic Guillot
+ */
+abstract class PluginBase extends Base
+{
+ /**
+ * Method called for each request
+ *
+ * @abstract
+ * @access public
+ */
+ abstract public function initialize();
+
+ /**
+ * Returns all classes that needs to be stored in the DI container
+ *
+ * @access public
+ * @return array
+ */
+ public function getClasses()
+ {
+ return array();
+ }
+
+ /**
+ * Listen on internal events
+ *
+ * @access public
+ * @param string $event
+ * @param callable $callback
+ */
+ public function on($event, $callback)
+ {
+ $container = $this->container;
+
+ $this->container['dispatcher']->addListener($event, function() use ($container, $callback) {
+ call_user_func($callback, $container);
+ });
+ }
+}
diff --git a/app/Core/PluginLoader.php b/app/Core/PluginLoader.php
new file mode 100644
index 00000000..c7c254f7
--- /dev/null
+++ b/app/Core/PluginLoader.php
@@ -0,0 +1,137 @@
+<?php
+
+namespace Core;
+
+use DirectoryIterator;
+use PDOException;
+
+/**
+ * Plugin Loader
+ *
+ * @package core
+ * @author Frederic Guillot
+ */
+class PluginLoader extends Base
+{
+ /**
+ * Schema version table for plugins
+ *
+ * @var string
+ */
+ const TABLE_SCHEMA = 'plugin_schema_versions';
+
+ /**
+ * Scan plugin folder and load plugins
+ *
+ * @access public
+ */
+ public function scan()
+ {
+ if (file_exists(__DIR__.'/../../plugins')) {
+ $dir = new DirectoryIterator(__DIR__.'/../../plugins');
+
+ foreach ($dir as $fileinfo) {
+ if (! $fileinfo->isDot() && $fileinfo->isDir()) {
+ $plugin = $fileinfo->getFilename();
+ $this->loadSchema($plugin);
+ $this->load($plugin);
+ }
+ }
+ }
+ }
+
+ /**
+ * Load plugin
+ *
+ * @access public
+ */
+ public function load($plugin)
+ {
+ $class = '\Plugin\\'.$plugin.'\\Plugin';
+ $instance = new $class($this->container);
+
+ Tool::buildDic($this->container, $instance->getClasses());
+
+ $instance->initialize();
+ }
+
+ /**
+ * Load plugin schema
+ *
+ * @access public
+ * @param string $plugin
+ */
+ public function loadSchema($plugin)
+ {
+ $filename = __DIR__.'/../../plugins/'.$plugin.'/Schema/'.ucfirst(DB_DRIVER).'.php';
+
+ if (file_exists($filename)) {
+ require($filename);
+ $this->migrateSchema($plugin);
+ }
+ }
+
+ /**
+ * Execute plugin schema migrations
+ *
+ * @access public
+ * @param string $plugin
+ */
+ public function migrateSchema($plugin)
+ {
+ $last_version = constant('\Plugin\\'.$plugin.'\Schema\VERSION');
+ $current_version = $this->getSchemaVersion($plugin);
+
+ try {
+
+ $this->db->startTransaction();
+ $this->db->getDriver()->disableForeignKeys();
+
+ for ($i = $current_version + 1; $i <= $last_version; $i++) {
+ $function_name = '\Plugin\\'.$plugin.'\Schema\version_'.$i;
+
+ if (function_exists($function_name)) {
+ call_user_func($function_name, $this->db->getConnection());
+ }
+ }
+
+ $this->db->getDriver()->enableForeignKeys();
+ $this->db->closeTransaction();
+ $this->setSchemaVersion($plugin, $i - 1);
+ }
+ catch (PDOException $e) {
+ $this->db->cancelTransaction();
+ $this->db->getDriver()->enableForeignKeys();
+ die('Unable to migrate schema for the plugin: '.$plugin.' => '.$e->getMessage());
+ }
+ }
+
+ /**
+ * Get current plugin schema version
+ *
+ * @access public
+ * @param string $plugin
+ * @return integer
+ */
+ public function getSchemaVersion($plugin)
+ {
+ return (int) $this->db->table(self::TABLE_SCHEMA)->eq('plugin', strtolower($plugin))->findOneColumn('version');
+ }
+
+ /**
+ * Save last plugin schema version
+ *
+ * @access public
+ * @param string $plugin
+ * @param integer $version
+ * @return boolean
+ */
+ public function setSchemaVersion($plugin, $version)
+ {
+ $dictionary = array(
+ strtolower($plugin) => $version
+ );
+
+ return $this->db->getDriver()->upsert(self::TABLE_SCHEMA, 'plugin', 'version', $dictionary);
+ }
+}
diff --git a/app/Core/Router.php b/app/Core/Router.php
index 6e7576d6..36bbfd55 100644
--- a/app/Core/Router.php
+++ b/app/Core/Router.php
@@ -213,49 +213,17 @@ class Router extends Base
if (! empty($_GET['controller']) && ! empty($_GET['action'])) {
$controller = $this->sanitize($_GET['controller'], 'app');
$action = $this->sanitize($_GET['action'], 'index');
+ $plugin = ! empty($_GET['plugin']) ? $this->sanitize($_GET['plugin'], '') : '';
}
else {
- list($controller, $action) = $this->findRoute($this->getPath($uri, $query_string));
+ list($controller, $action) = $this->findRoute($this->getPath($uri, $query_string)); // TODO: add plugin for routes
+ $plugin = '';
}
- return $this->load(
- __DIR__.'/../Controller/'.ucfirst($controller).'.php',
- $controller,
- '\Controller\\'.ucfirst($controller),
- $action
- );
- }
-
- /**
- * Load a controller and execute the action
- *
- * @access private
- * @param string $filename
- * @param string $controller
- * @param string $class
- * @param string $method
- * @return bool
- */
- private function load($filename, $controller, $class, $method)
- {
- if (file_exists($filename)) {
-
- require $filename;
-
- if (! method_exists($class, $method)) {
- return false;
- }
-
- $this->action = $method;
- $this->controller = $controller;
-
- $instance = new $class($this->container);
- $instance->beforeAction($controller, $method);
- $instance->$method();
-
- return true;
- }
+ $class = empty($plugin) ? '\Controller\\'.ucfirst($controller) : '\Plugin\\'.ucfirst($plugin).'\Controller\\'.ucfirst($controller);
- return false;
+ $instance = new $class($this->container);
+ $instance->beforeAction($controller, $action);
+ $instance->$action();
}
}
diff --git a/app/Core/Template.php b/app/Core/Template.php
index ba869ee6..b75f7da1 100644
--- a/app/Core/Template.php
+++ b/app/Core/Template.php
@@ -13,11 +13,12 @@ use LogicException;
class Template extends Helper
{
/**
- * Template path
+ * List of template overrides
*
- * @var string
+ * @access private
+ * @var array
*/
- const PATH = 'app/Template/';
+ private $overrides = array();
/**
* Render a template
@@ -33,16 +34,10 @@ class Template extends Helper
*/
public function render($__template_name, array $__template_args = array())
{
- $__template_file = self::PATH.$__template_name.'.php';
-
- if (! file_exists($__template_file)) {
- throw new LogicException('Unable to load the template: "'.$__template_name.'"');
- }
-
extract($__template_args);
ob_start();
- include $__template_file;
+ include $this->getTemplateFile($__template_name);
return ob_get_clean();
}
@@ -62,4 +57,41 @@ class Template extends Helper
$template_args + array('content_for_layout' => $this->render($template_name, $template_args))
);
}
+
+ /**
+ * Define a new template override
+ *
+ * @access public
+ * @param string $original_template
+ * @param string $new_template
+ */
+ public function setTemplateOverride($original_template, $new_template)
+ {
+ $this->overrides[$original_template] = $new_template;
+ }
+
+ /**
+ * Find template filename
+ *
+ * Core template name: 'task/show'
+ * Plugin template name: 'myplugin:task/show'
+ *
+ * @access public
+ * @param string $template_name
+ * @return string
+ */
+ public function getTemplateFile($template_name)
+ {
+ $template_name = isset($this->overrides[$template_name]) ? $this->overrides[$template_name] : $template_name;
+
+ if (strpos($template_name, ':') !== false) {
+ list($plugin, $template) = explode(':', $template_name);
+ $path = __DIR__.'/../../plugins/'.ucfirst($plugin).'/Template/'.$template.'.php';
+ }
+ else {
+ $path = __DIR__.'/../Template/'.$template_name.'.php';
+ }
+
+ return $path;
+ }
}
diff --git a/app/Core/Tool.php b/app/Core/Tool.php
index 84e42ba8..887c8fb3 100644
--- a/app/Core/Tool.php
+++ b/app/Core/Tool.php
@@ -2,6 +2,8 @@
namespace Core;
+use Pimple\Container;
+
/**
* Tool class
*
@@ -23,7 +25,6 @@ class Tool
$fp = fopen($filename, 'w');
if (is_resource($fp)) {
-
foreach ($rows as $fields) {
fputcsv($fp, $fields);
}
@@ -51,4 +52,102 @@ class Tool
return $identifier;
}
+
+ /**
+ * Build dependency injection container from an array
+ *
+ * @static
+ * @access public
+ * @param Container $container
+ * @param array $namespaces
+ */
+ public static function buildDIC(Container $container, array $namespaces)
+ {
+ foreach ($namespaces as $namespace => $classes) {
+ foreach ($classes as $name) {
+ $class = '\\'.$namespace.'\\'.$name;
+ $container[lcfirst($name)] = function ($c) use ($class) {
+ return new $class($c);
+ };
+ }
+ }
+ }
+
+ /**
+ * Generate a jpeg thumbnail from an image
+ *
+ * @static
+ * @access public
+ * @param string $src_file Source file image
+ * @param string $dst_file Destination file image
+ * @param integer $resize_width Desired image width
+ * @param integer $resize_height Desired image height
+ */
+ public static function generateThumbnail($src_file, $dst_file, $resize_width = 250, $resize_height = 100)
+ {
+ $metadata = getimagesize($src_file);
+ $src_width = $metadata[0];
+ $src_height = $metadata[1];
+ $dst_y = 0;
+ $dst_x = 0;
+
+ if (empty($metadata['mime'])) {
+ return;
+ }
+
+ if ($resize_width == 0 && $resize_height == 0) {
+ $resize_width = 100;
+ $resize_height = 100;
+ }
+
+ if ($resize_width > 0 && $resize_height == 0) {
+ $dst_width = $resize_width;
+ $dst_height = floor($src_height * ($resize_width / $src_width));
+ $dst_image = imagecreatetruecolor($dst_width, $dst_height);
+ }
+ elseif ($resize_width == 0 && $resize_height > 0) {
+ $dst_width = floor($src_width * ($resize_height / $src_height));
+ $dst_height = $resize_height;
+ $dst_image = imagecreatetruecolor($dst_width, $dst_height);
+ }
+ else {
+
+ $src_ratio = $src_width / $src_height;
+ $resize_ratio = $resize_width / $resize_height;
+
+ if ($src_ratio <= $resize_ratio) {
+ $dst_width = $resize_width;
+ $dst_height = floor($src_height * ($resize_width / $src_width));
+
+ $dst_y = ($dst_height - $resize_height) / 2 * (-1);
+ }
+ else {
+ $dst_width = floor($src_width * ($resize_height / $src_height));
+ $dst_height = $resize_height;
+
+ $dst_x = ($dst_width - $resize_width) / 2 * (-1);
+ }
+
+ $dst_image = imagecreatetruecolor($resize_width, $resize_height);
+ }
+
+ switch ($metadata['mime']) {
+ case 'image/jpeg':
+ case 'image/jpg':
+ $src_image = imagecreatefromjpeg($src_file);
+ break;
+ case 'image/png':
+ $src_image = imagecreatefrompng($src_file);
+ break;
+ case 'image/gif':
+ $src_image = imagecreatefromgif($src_file);
+ break;
+ default:
+ return;
+ }
+
+ imagecopyresampled($dst_image, $src_image, $dst_x, $dst_y, 0, 0, $dst_width, $dst_height, $src_width, $src_height);
+ imagejpeg($dst_image, $dst_file);
+ imagedestroy($dst_image);
+ }
}
diff --git a/app/Core/Translator.php b/app/Core/Translator.php
index e3d19692..e9aa1f3f 100644
--- a/app/Core/Translator.php
+++ b/app/Core/Translator.php
@@ -15,7 +15,7 @@ class Translator
*
* @var string
*/
- const PATH = 'app/Locale/';
+ const PATH = 'app/Locale';
/**
* Locale
@@ -196,18 +196,27 @@ class Translator
* @static
* @access public
* @param string $language Locale code: fr_FR
+ * @param string $path Locale folder
*/
- public static function load($language)
+ public static function load($language, $path = self::PATH)
{
setlocale(LC_TIME, $language.'.UTF-8', $language);
- $filename = self::PATH.$language.DIRECTORY_SEPARATOR.'translations.php';
+ $filename = $path.DIRECTORY_SEPARATOR.$language.DIRECTORY_SEPARATOR.'translations.php';
if (file_exists($filename)) {
- self::$locales = require $filename;
- }
- else {
- self::$locales = array();
+ self::$locales = array_merge(self::$locales, require($filename));
}
}
+
+ /**
+ * Clear locales stored in memory
+ *
+ * @static
+ * @access public
+ */
+ public static function unload()
+ {
+ self::$locales = array();
+ }
}
diff --git a/app/Helper/Hook.php b/app/Helper/Hook.php
new file mode 100644
index 00000000..77756757
--- /dev/null
+++ b/app/Helper/Hook.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace Helper;
+
+/**
+ * Template Hook helpers
+ *
+ * @package helper
+ * @author Frederic Guillot
+ */
+class Hook extends \Core\Base
+{
+ private $hooks = array();
+
+ /**
+ * Render all attached hooks
+ *
+ * @access public
+ * @param string $hook
+ * @param array $variables
+ * @return string
+ */
+ public function render($hook, array $variables = array())
+ {
+ $buffer = '';
+
+ foreach ($this->hooks as $name => $template) {
+ if ($hook === $name) {
+ $buffer .= $this->template->render($template, $variables);
+ }
+ }
+
+ return $buffer;
+ }
+
+ /**
+ * Attach a template to a hook
+ *
+ * @access public
+ * @param string $hook
+ * @param string $template
+ * @return \Helper\Hook
+ */
+ public function attach($hook, $template)
+ {
+ $this->hooks[$hook] = $template;
+ return $this;
+ }
+}
diff --git a/app/Integration/Mailgun.php b/app/Integration/Mailgun.php
index 1451b211..076c311a 100644
--- a/app/Integration/Mailgun.php
+++ b/app/Integration/Mailgun.php
@@ -2,7 +2,6 @@
namespace Integration;
-use HTML_To_Markdown;
use Core\Tool;
/**
@@ -76,8 +75,7 @@ class Mailgun extends \Core\Base
// Get the Markdown contents
if (! empty($payload['stripped-html'])) {
- $markdown = new HTML_To_Markdown($payload['stripped-html'], array('strip_tags' => true));
- $description = $markdown->output();
+ $description = $this->htmlConverter->convert($payload['stripped-html']);
}
else if (! empty($payload['stripped-text'])) {
$description = $payload['stripped-text'];
diff --git a/app/Integration/Postmark.php b/app/Integration/Postmark.php
index dbb70aee..05bdf58b 100644
--- a/app/Integration/Postmark.php
+++ b/app/Integration/Postmark.php
@@ -2,8 +2,6 @@
namespace Integration;
-use HTML_To_Markdown;
-
/**
* Postmark integration
*
@@ -76,8 +74,7 @@ class Postmark extends \Core\Base
// Get the Markdown contents
if (! empty($payload['HtmlBody'])) {
- $markdown = new HTML_To_Markdown($payload['HtmlBody'], array('strip_tags' => true));
- $description = $markdown->output();
+ $description = $this->htmlConverter->convert($payload['HtmlBody']);
}
else if (! empty($payload['TextBody'])) {
$description = $payload['TextBody'];
diff --git a/app/Integration/Sendgrid.php b/app/Integration/Sendgrid.php
index 902749f6..fd58342a 100644
--- a/app/Integration/Sendgrid.php
+++ b/app/Integration/Sendgrid.php
@@ -2,7 +2,6 @@
namespace Integration;
-use HTML_To_Markdown;
use Core\Tool;
/**
@@ -79,8 +78,7 @@ class Sendgrid extends \Core\Base
// Get the Markdown contents
if (! empty($payload['html'])) {
- $markdown = new HTML_To_Markdown($payload['html'], array('strip_tags' => true));
- $description = $markdown->output();
+ $description = $this->htmlConverter->convert($payload['html']);
}
else if (! empty($payload['text'])) {
$description = $payload['text'];
diff --git a/app/Locale/cs_CZ/translations.php b/app/Locale/cs_CZ/translations.php
index 557a62cc..8cea7367 100644
--- a/app/Locale/cs_CZ/translations.php
+++ b/app/Locale/cs_CZ/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Vzdálený',
'Enabled' => 'Povoleno',
'Disabled' => 'Zakázáno',
- 'Google account linked' => 'Google úÄet byl propojen',
- 'Github account linked' => 'Mit Githubaccount verbunden',
'Username:' => 'Uživatelské jméno:',
'Name:' => 'Jméno:',
'Email:' => 'e-mail',
@@ -667,17 +665,7 @@ return array(
'Horizontal scrolling' => 'Horizontální rolování',
'Compact/wide view' => 'Kompaktní/plné zobrazení',
'No results match:' => 'Žádná shoda:',
- 'Remove hourly rate' => 'Stundensatz entfernen',
- 'Do you really want to remove this hourly rate?' => 'Opravdu chcete odstranit tuto hodinovou sazbu?',
- 'Hourly rates' => 'Hodinové sazby',
- 'Hourly rate' => 'Hodinová sazba',
'Currency' => 'Měna',
- 'Effective date' => 'Datum úÄinnosti',
- 'Add new rate' => 'Přidat novou hodinovou sazbu',
- 'Rate removed successfully.' => 'Sazba byla úspěšně odstraněna',
- 'Unable to remove this rate.' => 'Sazbu nelze odstranit.',
- 'Unable to save the hourly rate.' => 'Hodinovou sazbu nelze uložit',
- 'Hourly rate created successfully.' => 'Hodinová sazba byla úspěšně vytvořena.',
'Start time' => 'PoÄáteÄní datum',
'End time' => 'KoneÄné datum',
'Comment' => 'Komentář',
@@ -703,34 +691,18 @@ return array(
'Files' => 'Soubory',
'Images' => 'Obrázky',
'Private project' => 'Soukromý projekt',
- 'Amount' => 'Částka',
// 'AUD - Australian Dollar' => '',
- 'Budget' => 'RozpoÄet',
- 'Budget line' => 'Položka rozpoÄtu',
- 'Budget line removed successfully.' => 'Položka rozpoÄtu byla odstranÄ›na',
- 'Budget lines' => 'Položky rozpoÄtu',
// 'CAD - Canadian Dollar' => '',
// 'CHF - Swiss Francs' => '',
- 'Cost' => 'Cena',
- 'Cost breakdown' => 'Rozpis nákladů',
'Custom Stylesheet' => 'Vlastní šablony stylů',
'download' => 'Stáhnout',
- 'Do you really want to remove this budget line?' => 'Opravdu chcete odstranit tuto rozpoÄtovou řádku?',
'EUR - Euro' => 'EUR - Euro',
- 'Expenses' => 'Náklady',
'GBP - British Pound' => 'GBP - Britská Libra',
'INR - Indian Rupee' => 'INR - Indische Rupien',
'JPY - Japanese Yen' => 'JPY - Japanischer Yen',
- 'New budget line' => 'Nová položka rozpoÄtu',
'NZD - New Zealand Dollar' => 'NZD - Neuseeland-Dollar',
- 'Remove a budget line' => 'Budgetlinie entfernen',
- 'Remove budget line' => 'Budgetlinie entfernen',
'RSD - Serbian dinar' => 'RSD - Serbische Dinar',
- 'The budget line have been created successfully.' => 'Položka rozpoÄtu byla úspěšnÄ› vytvoÅ™ena.',
- 'Unable to create the budget line.' => 'Nelze vytvoÅ™it rozpoÄtovou řádku.',
- 'Unable to remove this budget line.' => 'Nelze vyjmout rozpoÄtovou řádku.',
'USD - US Dollar' => 'USD - US Dollar',
- 'Remaining' => 'Zbývající',
'Destination column' => 'Cílový sloupec',
'Move the task to another column when assigned to a user' => 'Přesunout úkol do jiného sloupce, když je úkol přiřazen uživateli.',
'Move the task to another column when assignee is cleared' => 'Přesunout úkol do jiného sloupce, když je pověření uživatele vymazáno.',
@@ -746,7 +718,6 @@ return array(
'Rate' => 'Kurz',
'Change reference currency' => 'ZmÄ›nit referenÄní mÄ›nu',
'Add a new currency rate' => 'Přidat nový směnný kurz',
- 'Currency rates are used to calculate project budget.' => 'MÄ›nové sazby se používají k výpoÄtu rozpoÄtu projektu.',
'Reference currency' => 'ReferenÄní mÄ›na',
'The currency rate have been added successfully.' => 'Směnný kurz byl úspěšně přidán.',
'Unable to add this currency rate.' => 'Nelze přidat tento směnný kurz',
@@ -878,9 +849,6 @@ return array(
'%s moved the task #%d to the first swimlane' => '%s hat die Aufgabe #%d in die erste Swimlane verschoben',
'%s moved the task #%d to the swimlane "%s"' => '%s hat die Aufgabe #%d in die Swimlane "%s" verschoben',
// 'Swimlane' => '',
- 'Budget overview' => 'Budget Ãœbersicht',
- 'Type' => 'Typ',
- 'There is not enough data to show something.' => 'Es gibt nicht genug Daten für die Anzeige',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Locale/da_DK/translations.php b/app/Locale/da_DK/translations.php
index 6a41f065..027b22c5 100644
--- a/app/Locale/da_DK/translations.php
+++ b/app/Locale/da_DK/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Remote',
'Enabled' => 'Aktiv',
'Disabled' => 'Deaktiveret',
- 'Google account linked' => 'Google-konto forbundet',
- 'Github account linked' => 'Github-konto forbundet',
'Username:' => 'Brugernavn',
'Name:' => 'Navn:',
'Email:' => 'Email:',
@@ -667,17 +665,7 @@ return array(
// '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' => '',
@@ -703,34 +691,18 @@ return array(
// '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' => '',
@@ -746,7 +718,6 @@ return array(
// '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.' => '',
@@ -878,9 +849,6 @@ return array(
// '%s moved the task #%d to the first swimlane' => '',
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
- // 'Budget overview' => '',
- // 'Type' => '',
- // 'There is not enough data to show something.' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Locale/de_DE/translations.php b/app/Locale/de_DE/translations.php
index 7b38e9fc..0b1df2e7 100644
--- a/app/Locale/de_DE/translations.php
+++ b/app/Locale/de_DE/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Remote',
'Enabled' => 'angeschaltet',
'Disabled' => 'abgeschaltet',
- 'Google account linked' => 'Mit Google-Account verbunden',
- 'Github account linked' => 'Mit Github-Account verbunden',
'Username:' => 'Benutzername',
'Name:' => 'Name',
'Email:' => 'E-Mail',
@@ -667,17 +665,7 @@ return array(
'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',
@@ -703,34 +691,18 @@ return array(
'Files' => 'Dateien',
'Images' => 'Bilder',
'Private project' => 'privates Projekt',
- 'Amount' => 'Betrag',
'AUD - Australian Dollar' => 'AUD - Australische Dollar',
- 'Budget' => 'Budget',
- 'Budget line' => 'Budgetlinie',
- 'Budget line removed successfully.' => 'Budgetlinie erfolgreich entfernt',
- 'Budget lines' => 'Budgetlinien',
'CAD - Canadian Dollar' => 'CAD - Kanadische Dollar',
'CHF - Swiss Francs' => 'CHF - Schweizer Franken',
- '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' => 'EUR - Euro',
- 'Expenses' => 'Kosten',
'GBP - British Pound' => 'GBP - Britische Pfund',
'INR - Indian Rupee' => 'INR - Indische Rupien',
'JPY - Japanese Yen' => 'JPY - Japanische Yen',
- 'New budget line' => 'Neue Budgetlinie',
'NZD - New Zealand Dollar' => 'NZD - Neuseeland-Dollar',
- 'Remove a budget line' => 'Budgetlinie entfernen',
- 'Remove budget line' => 'Budgetlinie entfernen',
'RSD - Serbian dinar' => 'RSD - Serbische Dinar',
- 'The budget line have been created successfully.' => 'Die Budgetlinie wurde erfolgreich angelegt.',
- 'Unable to create the budget line.' => 'Budgetlinie konnte nicht erstellt werden.',
- 'Unable to remove this budget line.' => 'Budgetlinie konnte nicht gelöscht werden.',
'USD - US Dollar' => 'USD - US-Dollar',
- '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.',
@@ -746,7 +718,6 @@ return array(
'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',
@@ -878,9 +849,6 @@ return array(
'%s moved the task #%d to the first swimlane' => '%s hat die Aufgabe #%d in die erste Swimlane verschoben',
'%s moved the task #%d to the swimlane "%s"' => '%s hat die Aufgabe #%d in die Swimlane "%s" verschoben',
// 'Swimlane' => '',
- 'Budget overview' => 'Budget-Ãœbersicht',
- 'Type' => 'Typ',
- 'There is not enough data to show something.' => 'Es gibt nicht genügend Daten für diese Anzeige',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Locale/es_ES/translations.php b/app/Locale/es_ES/translations.php
index f2b744f3..1e15d8c0 100644
--- a/app/Locale/es_ES/translations.php
+++ b/app/Locale/es_ES/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Remota',
'Enabled' => 'Activada',
'Disabled' => 'Desactivada',
- 'Google account linked' => 'Vinculada con Cuenta de Google',
- 'Github account linked' => 'Vinculada con Cuenta de Gitgub',
'Username:' => 'Nombre de Usuario:',
'Name:' => 'Nombre:',
'Email:' => 'Correo electrónico:',
@@ -667,17 +665,7 @@ return array(
'Horizontal scrolling' => 'Desplazamiento horizontal',
'Compact/wide view' => 'Vista compacta/amplia',
'No results match:' => 'No hay resultados coincidentes:',
- 'Remove hourly rate' => 'Quitar cobro horario',
- 'Do you really want to remove this hourly rate?' => '¿Realmente quire quitar el cobro horario?',
- 'Hourly rates' => 'Cobros horarios',
- 'Hourly rate' => 'Cobro horario',
'Currency' => 'Moneda',
- 'Effective date' => 'Fecha efectiva',
- 'Add new rate' => 'Añadir nuevo cobro',
- 'Rate removed successfully.' => 'Cobro quitado con éxito.',
- 'Unable to remove this rate.' => 'No pude quitar este cobro.',
- 'Unable to save the hourly rate.' => 'No pude grabar el cobro horario.',
- 'Hourly rate created successfully.' => 'Cobro horario creado con éxito',
'Start time' => 'Tiempo de inicio',
'End time' => 'Tiempo de fin',
'Comment' => 'Comentario',
@@ -703,34 +691,18 @@ return array(
'Files' => 'Ficheros',
'Images' => 'Imágenes',
'Private project' => 'Proyecto privado',
- 'Amount' => 'Cantidad',
'AUD - Australian Dollar' => 'AUD - Dólar australiano',
- 'Budget' => 'Presupuesto',
- 'Budget line' => 'Línea de presupuesto',
- 'Budget line removed successfully.' => 'Línea de presupuesto quitada con éxito',
- 'Budget lines' => 'Líneas de presupuesto',
'CAD - Canadian Dollar' => 'CAD - Dólar canadiense',
'CHF - Swiss Francs' => 'CHF - Francos suizos',
- 'Cost' => 'Costo',
- 'Cost breakdown' => 'Desglose de costes',
'Custom Stylesheet' => 'Hoja de estilo Personalizada',
'download' => 'descargar',
- 'Do you really want to remove this budget line?' => '¿Realmente quiere quitar esta línea de presupuesto?',
'EUR - Euro' => 'EUR - Euro',
- 'Expenses' => 'Gastos',
'GBP - British Pound' => 'GBP - Libra británica',
'INR - Indian Rupee' => 'INR - Rupias indúes',
'JPY - Japanese Yen' => 'JPY - Yen japonés',
- 'New budget line' => 'Nueva línea de presupuesto',
'NZD - New Zealand Dollar' => 'NZD - Dóloar neocelandés',
- 'Remove a budget line' => 'Quitar una línea de presupuesto',
- 'Remove budget line' => 'Quitar línea de presupuesto',
'RSD - Serbian dinar' => 'RSD - Dinar serbio',
- 'The budget line have been created successfully.' => 'Se ha creado la línea de presupuesto con éxito.',
- 'Unable to create the budget line.' => 'No pude crear la línea de presupuesto.',
- 'Unable to remove this budget line.' => 'No pude quitar esta línea de presupuesto.',
'USD - US Dollar' => 'USD - Dólar Estadounidense',
- 'Remaining' => 'Restante',
'Destination column' => 'Columna destino',
'Move the task to another column when assigned to a user' => 'Mover la tarea a otra columna al asignarse al usuario',
'Move the task to another column when assignee is cleared' => 'Mover la tarea a otra columna al quitar el concesionario',
@@ -746,7 +718,6 @@ return array(
'Rate' => 'Cambio',
'Change reference currency' => 'Cambiar moneda de referencia',
'Add a new currency rate' => 'Añadir nuevo cambio de moneda',
- 'Currency rates are used to calculate project budget.' => 'Se usan los cambios de moneda para calcular el presupuesto del proyecto.',
'Reference currency' => 'Moneda de referencia',
'The currency rate have been added successfully.' => 'Se ha añadido el cambio de moneda con éxito',
'Unable to add this currency rate.' => 'No pude añadir este cambio de moneda.',
@@ -878,9 +849,6 @@ return array(
'%s moved the task #%d to the first swimlane' => '%s movió la tarea #%d a la primera calle',
'%s moved the task #%d to the swimlane "%s"' => '%s movió la tarea #%d a la calle "%s"',
'Swimlane' => 'Calle',
- 'Budget overview' => 'Resumen del Presupuesto',
- 'Type' => 'Tipo',
- 'There is not enough data to show something.' => 'No hay datos suficientes como para mostrar algo.',
'Gravatar' => 'Gravatar',
'Hipchat' => 'Hipchat',
'Slack' => 'Desatendida',
diff --git a/app/Locale/fi_FI/translations.php b/app/Locale/fi_FI/translations.php
index d8c749a3..da462831 100644
--- a/app/Locale/fi_FI/translations.php
+++ b/app/Locale/fi_FI/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Etä',
'Enabled' => 'Käytössä',
'Disabled' => 'Pois käytöstä',
- 'Google account linked' => 'Google-tili liitetty',
- 'Github account linked' => 'Github-tili liitetty',
'Username:' => 'Käyttäjänimi:',
'Name:' => 'Nimi:',
'Email:' => 'Sähköpostiosoite:',
@@ -667,17 +665,7 @@ return array(
// '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' => '',
@@ -703,34 +691,18 @@ return array(
// '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' => '',
@@ -746,7 +718,6 @@ return array(
// '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.' => '',
@@ -878,9 +849,6 @@ return array(
// '%s moved the task #%d to the first swimlane' => '',
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
- // 'Budget overview' => '',
- // 'Type' => '',
- // 'There is not enough data to show something.' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Locale/fr_FR/translations.php b/app/Locale/fr_FR/translations.php
index 93f7110d..848b7624 100644
--- a/app/Locale/fr_FR/translations.php
+++ b/app/Locale/fr_FR/translations.php
@@ -397,8 +397,6 @@ return array(
'Remote' => 'Distant',
'Enabled' => 'Activé',
'Disabled' => 'Désactivé',
- 'Google account linked' => 'Compte Google attaché',
- 'Github account linked' => 'Compte Github attaché',
'Username:' => 'Nom d\'utilisateur :',
'Name:' => 'Nom :',
'Email:' => 'Email :',
@@ -669,17 +667,7 @@ return array(
'Horizontal scrolling' => 'Défilement horizontal',
'Compact/wide view' => 'Basculer entre la vue compacte et étendue',
'No results match:' => 'Aucun résultat :',
- 'Remove hourly rate' => 'Supprimer un taux horaire',
- 'Do you really want to remove this hourly rate?' => 'Voulez-vous vraiment supprimer ce taux horaire ?',
- 'Hourly rates' => 'Taux horaires',
- 'Hourly rate' => 'Taux horaire',
'Currency' => 'Devise',
- 'Effective date' => 'Date d\'effet',
- 'Add new rate' => 'Ajouter un nouveau taux horaire',
- 'Rate removed successfully.' => 'Taux horaire supprimé avec succès.',
- 'Unable to remove this rate.' => 'Impossible de supprimer ce taux horaire.',
- 'Unable to save the hourly rate.' => 'Impossible de sauvegarder ce taux horaire.',
- 'Hourly rate created successfully.' => 'Taux horaire créé avec succès.',
'Start time' => 'Date de début',
'End time' => 'Date de fin',
'Comment' => 'Commentaire',
@@ -705,34 +693,18 @@ return array(
'Files' => 'Fichiers',
'Images' => 'Images',
'Private project' => 'Projet privé',
- 'Amount' => 'Montant',
'AUD - Australian Dollar' => 'AUD - Dollar australien',
- 'Budget' => 'Budget',
- 'Budget line' => 'Ligne budgétaire',
- 'Budget line removed successfully.' => 'Ligne budgétaire supprimée avec succès.',
- 'Budget lines' => 'Lignes budgétaire',
'CAD - Canadian Dollar' => 'CAD - Dollar canadien',
'CHF - Swiss Francs' => 'CHF - Franc suisse',
- 'Cost' => 'Coût',
- 'Cost breakdown' => 'Détail des coûts',
'Custom Stylesheet' => 'Feuille de style personalisée',
'download' => 'télécharger',
- 'Do you really want to remove this budget line?' => 'Voulez-vous vraiment supprimer cette ligne budgétaire ?',
'EUR - Euro' => 'EUR - Euro',
- 'Expenses' => 'Dépenses',
'GBP - British Pound' => 'GBP - Livre sterling',
'INR - Indian Rupee' => 'INR - Roupie indienne',
'JPY - Japanese Yen' => 'JPY - Yen',
- 'New budget line' => 'Nouvelle ligne budgétaire',
'NZD - New Zealand Dollar' => 'NZD - Dollar néo-zélandais',
- 'Remove a budget line' => 'Supprimer une ligne budgétaire',
- 'Remove budget line' => 'Supprimer une ligne budgétaire',
'RSD - Serbian dinar' => 'RSD - Dinar serbe',
- 'The budget line have been created successfully.' => 'La ligne de budgétaire a été créée avec succès.',
- 'Unable to create the budget line.' => 'Impossible de créer cette ligne budgétaire.',
- 'Unable to remove this budget line.' => 'Impossible de supprimer cette ligne budgétaire.',
'USD - US Dollar' => 'USD - Dollar américain',
- 'Remaining' => 'Restant',
'Destination column' => 'Colonne de destination',
'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',
@@ -748,7 +720,6 @@ return array(
'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',
@@ -880,9 +851,6 @@ return array(
'%s moved the task #%d to the first swimlane' => '%s a déplacé la tâche n°%d dans la première swimlane',
'%s moved the task #%d to the swimlane "%s"' => '%s a déplacé la tâche n°%d dans la swimlane « %s »',
'Swimlane' => 'Swimlane',
- 'Budget overview' => 'Vue d\'ensemble du budget',
- 'Type' => 'Type',
- 'There is not enough data to show something.' => 'Il n\'y a pas assez de données pour montrer quelque chose.',
'Gravatar' => 'Gravatar',
'Hipchat' => 'Hipchat',
'Slack' => 'Slack',
diff --git a/app/Locale/hu_HU/translations.php b/app/Locale/hu_HU/translations.php
index b346f1e3..a3bbd8f5 100644
--- a/app/Locale/hu_HU/translations.php
+++ b/app/Locale/hu_HU/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Távoli',
'Enabled' => 'Engedélyezve',
'Disabled' => 'Letiltva',
- 'Google account linked' => 'Google fiók összekapcsolva',
- 'Github account linked' => 'Github fiók összekapcsolva',
'Username:' => 'Felhasználónév:',
'Name:' => 'Név:',
'Email:' => 'E-mail:',
@@ -667,17 +665,7 @@ return array(
'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',
@@ -703,34 +691,18 @@ return array(
'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',
@@ -746,7 +718,6 @@ return array(
// '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.' => '',
@@ -878,9 +849,6 @@ return array(
// '%s moved the task #%d to the first swimlane' => '',
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
- // 'Budget overview' => '',
- // 'Type' => '',
- // 'There is not enough data to show something.' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Locale/it_IT/translations.php b/app/Locale/it_IT/translations.php
index 06e2c5ca..e27245f9 100644
--- a/app/Locale/it_IT/translations.php
+++ b/app/Locale/it_IT/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Remoto',
'Enabled' => 'Abilitato',
'Disabled' => 'Disabilitato',
- 'Google account linked' => 'Account Google collegato',
- 'Github account linked' => 'Account Github collegato',
// 'Username:' => '',
'Name:' => 'Nome:',
// 'Email:' => '',
@@ -667,17 +665,7 @@ return array(
'Horizontal scrolling' => 'Scrolling orizzontale',
'Compact/wide view' => 'Vista compatta/estesa',
'No results match:' => 'Nessun risultato trovato:',
- 'Remove hourly rate' => 'Rimuovi tariffa oraria',
- 'Do you really want to remove this hourly rate?' => 'Vuoi davvero rimuovere questa tariffa oraria?',
- 'Hourly rates' => 'Tariffe orarie',
- 'Hourly rate' => 'Tariffa oraria',
'Currency' => 'Valuta',
- 'Effective date' => 'Data effettiva',
- 'Add new rate' => 'Aggiungi una nuova tariffa',
- 'Rate removed successfully.' => 'Tariffa rimossa con successo.',
- 'Unable to remove this rate.' => 'Impossibile rimuovere questa tariffa.',
- 'Unable to save the hourly rate.' => 'Impossibile salvare la tariffa oraria.',
- 'Hourly rate created successfully.' => 'Tariffa oraria creata con successo.',
'Start time' => 'Data di inizio',
'End time' => 'Data di completamento',
'Comment' => 'Commento',
@@ -703,34 +691,18 @@ return array(
// 'Files' => '',
'Images' => 'Immagini',
'Private project' => 'Progetto privato',
- 'Amount' => 'Totale',
'AUD - Australian Dollar' => 'AUD - Dollari Australiani',
- 'Budget' => 'Bilancio',
- 'Budget line' => 'Limite di bilancio',
- 'Budget line removed successfully.' => 'Limite al bilancio rimosso con successo.',
- 'Budget lines' => 'Limiti al bilancio',
'CAD - Canadian Dollar' => 'CAD - Dollari Canadesi',
'CHF - Swiss Francs' => 'CHF - Franchi Svizzeri',
- 'Cost' => 'Costi',
- 'Cost breakdown' => 'Abbattimento dei costi',
'Custom Stylesheet' => 'CSS personalizzato',
// 'download' => '',
- 'Do you really want to remove this budget line?' => 'Vuoi davvero rimuovere questo limite al bilancio?',
// 'EUR - Euro' => '',
- 'Expenses' => 'Spese',
'GBP - British Pound' => 'GBP - Pound Inglesi',
'INR - Indian Rupee' => 'INR - Rupie Indiani',
'JPY - Japanese Yen' => 'JPY - Yen Giapponesi',
- 'New budget line' => 'Nuovo limite al bilancio',
'NZD - New Zealand Dollar' => 'NZD - Dollari della Nuova Zelanda',
- 'Remove a budget line' => 'Rimuovi un limite al bilancio',
- 'Remove budget line' => 'Rimuovi limite di bilancio',
'RSD - Serbian dinar' => 'RSD - Dinar Serbi',
- 'The budget line have been created successfully.' => 'Il limite al bilancio è stato creato correttamente',
- 'Unable to create the budget line.' => 'Impossibile creare il limite al bilancio',
- 'Unable to remove this budget line.' => 'Impossibile rimuovere questo limite al bilancio.',
'USD - US Dollar' => 'USD - Dollari Americani',
- 'Remaining' => 'Restanti',
'Destination column' => 'Colonna destinazione',
'Move the task to another column when assigned to a user' => 'Sposta il compito in un\'altra colonna quando viene assegnato ad un utente',
'Move the task to another column when assignee is cleared' => 'Sposta il compito in un\'altra colonna quando l\'assegnatario cancellato',
@@ -746,7 +718,6 @@ return array(
'Rate' => 'Cambio',
'Change reference currency' => 'Cambia la valuta di riferimento',
'Add a new currency rate' => 'Aggiungi un nuovo tasso di cambio',
- 'Currency rates are used to calculate project budget.' => 'I tassi di cambio sono utilizzati per calcolare i bilanci dei progetti',
'Reference currency' => 'Valuta di riferimento',
'The currency rate have been added successfully.' => 'Il tasso di cambio è stato aggiunto con successo.',
'Unable to add this currency rate.' => 'Impossibile aggiungere questo tasso di cambio.',
@@ -878,9 +849,6 @@ return array(
// '%s moved the task #%d to the first swimlane' => '',
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
- // 'Budget overview' => '',
- // 'Type' => '',
- // 'There is not enough data to show something.' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Locale/ja_JP/translations.php b/app/Locale/ja_JP/translations.php
index cb8c550b..49f92f27 100644
--- a/app/Locale/ja_JP/translations.php
+++ b/app/Locale/ja_JP/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'リモート',
'Enabled' => '有効',
'Disabled' => '無効',
- 'Google account linked' => 'Google アカウントãŒãƒªãƒ³ã‚¯',
- 'Github account linked' => 'Github ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆãŒãƒªãƒ³ã‚¯',
'Username:' => 'ユーザå:',
'Name:' => 'åå‰:',
'Email:' => 'Email:',
@@ -667,17 +665,7 @@ return array(
'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' => 'コメント',
@@ -703,34 +691,18 @@ return array(
'Files' => 'ファイル',
'Images' => 'ç”»åƒ',
'Private project' => 'プライベートプロジェクト',
- 'Amount' => 'æ•°é‡',
'AUD - Australian Dollar' => 'AUD - 豪ドル',
- 'Budget' => '予算',
- 'Budget line' => '予算ライン',
- 'Budget line removed successfully.' => '予算ラインを削除ã—ã¾ã—ãŸ.',
- 'Budget lines' => '予算ライン',
'CAD - Canadian Dollar' => 'CAD - 加ドル',
'CHF - Swiss Francs' => 'CHF - スイスフラン',
- 'Cost' => 'コスト',
- 'Cost breakdown' => 'コストブレークダウン',
'Custom Stylesheet' => 'カスタムスタイルシート',
'download' => 'ダウンロード',
- 'Do you really want to remove this budget line?' => 'ã“ã®äºˆç®—ラインを本当ã«å‰Šé™¤ã—ã¾ã™ã‹ï¼Ÿ',
'EUR - Euro' => 'EUR - ユーロ',
- 'Expenses' => '支出',
'GBP - British Pound' => 'GBP - 独ãƒãƒ³ãƒ‰',
'INR - Indian Rupee' => 'INR - 伊ルピー',
'JPY - Japanese Yen' => 'JPY - 日本円',
- 'New budget line' => 'æ–°ã—ã„予算ライン',
'NZD - New Zealand Dollar' => 'NZD - NZ ドル',
- 'Remove a budget line' => '予算ラインã®å‰Šé™¤',
- 'Remove budget line' => '予算ラインã®å‰Šé™¤',
'RSD - Serbian dinar' => 'RSD - セルビアデナール',
- 'The budget line have been created successfully.' => '予算ラインを作æˆã—ã¾ã—ãŸ',
- 'Unable to create the budget line.' => '予算ラインを作æˆã§ãã¾ã›ã‚“ã§ã—ãŸã€‚',
- 'Unable to remove this budget line.' => '予算ラインを削除ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚',
'USD - US Dollar' => 'USD - 米ドル',
- 'Remaining' => '残り',
'Destination column' => '移動先ã®ã‚«ãƒ©ãƒ ',
'Move the task to another column when assigned to a user' => 'ユーザã®å‰²ã‚Šå½“ã¦ã‚’ã—ãŸã‚‰ã‚¿ã‚¹ã‚¯ã‚’ä»–ã®ã‚«ãƒ©ãƒ ã«ç§»å‹•',
'Move the task to another column when assignee is cleared' => 'ユーザã®å‰²ã‚Šå½“ã¦ãŒãªããªã£ãŸã‚‰ã‚¿ã‚¹ã‚¯ã‚’ä»–ã®ã‚«ãƒ©ãƒ ã«ç§»å‹•',
@@ -746,7 +718,6 @@ return array(
'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.' => 'ã“ã®é€šè²¨ãƒ¬ãƒ¼ãƒˆã‚’追加ã§ãã¾ã›ã‚“。',
@@ -878,9 +849,6 @@ return array(
// '%s moved the task #%d to the first swimlane' => '',
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
- // 'Budget overview' => '',
- // 'Type' => '',
- // 'There is not enough data to show something.' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Locale/nb_NO/translations.php b/app/Locale/nb_NO/translations.php
index 155d49b4..9880b921 100644..100755
--- a/app/Locale/nb_NO/translations.php
+++ b/app/Locale/nb_NO/translations.php
@@ -1,8 +1,8 @@
<?php
return array(
- // 'number.decimals_separator' => '',
- // 'number.thousands_separator' => '',
+ 'number.decimals_separator' => ',',
+ 'number.thousands_separator' => '.',
'None' => 'Ingen',
'edit' => 'rediger',
'Edit' => 'Rediger',
@@ -14,12 +14,12 @@ return array(
'cancel' => 'avbryt',
'or' => 'eller',
'Yellow' => 'Gul',
- 'Blue' => 'Blå',
- 'Green' => 'Grønn',
+ 'Blue' => 'Blå',
+ 'Green' => 'Grønn',
'Purple' => 'Lilla',
- 'Red' => 'Rød',
+ 'Red' => 'Rød',
'Orange' => 'Orange',
- 'Grey' => 'Grå',
+ 'Grey' => 'Grå',
// 'Brown' => '',
// 'Deep Orange' => '',
// 'Dark Grey' => '',
@@ -61,7 +61,7 @@ return array(
'Inactive' => 'Inaktiv',
'Active' => 'Aktiv',
'Add this column' => 'Legg til denne kolonnen',
- '%d tasks on the board' => '%d Oppgaver på hovedsiden',
+ '%d tasks on the board' => '%d Oppgaver på hovedsiden',
'%d tasks in total' => '%d Oppgaver i alt',
'Unable to update this board.' => 'Ikke mulig at oppdatere hovedsiden',
'Edit board' => 'Endre prosjektsiden',
@@ -79,15 +79,15 @@ return array(
'Assigned to %s' => 'Tildelt: %s',
'Remove a column' => 'Fjern en kolonne',
'Remove a column from a board' => 'Fjern en kolonne fra et board',
- 'Unable to remove this column.' => 'Ikke mulig fjerne denne kolonnen',
+ 'Unable to remove this column.' => 'Ikke mulig ø fjerne denne kolonnen',
'Do you really want to remove this column: "%s"?' => 'Vil du fjerne denne kolonnen: "%s"?',
'This action will REMOVE ALL TASKS associated to this column!' => 'Denne handlingen vil SLETTE ALLE OPPGAVER tilknyttet denne kolonnen',
'Settings' => 'Innstillinger',
'Application settings' => 'Applikasjonsinnstillinger',
- 'Language' => 'Språk',
+ 'Language' => 'Språk',
'Webhook token:' => 'Webhook token:',
'API token:' => 'API Token:',
- 'Database size:' => 'Databasestørrelse:',
+ 'Database size:' => 'Databasestørrelse:',
'Download the database' => 'Last ned databasen',
'Optimize the database' => 'Optimaliser databasen',
'(VACUUM command)' => '(VACUUM kommando)',
@@ -99,36 +99,37 @@ return array(
'Assignee' => 'Tildelt',
'Create another task' => 'Opprett en annen oppgave',
'New task' => 'Ny oppgave',
- 'Open a task' => 'Ã…pne en oppgave',
- 'Do you really want to open this task: "%s"?' => 'Vil du åpe denne oppgaven: "%s"?',
+ 'Open a task' => 'Åpne en oppgave',
+ 'Do you really want to open this task: "%s"?' => 'Vil du åpne denne oppgaven: "%s"?',
'Back to the board' => 'Tilbake til prosjektsiden',
'Created on %B %e, %Y at %k:%M %p' => 'Opprettet %d.%m.%Y - %H:%M',
'There is nobody assigned' => 'Mangler tildeling',
'Column on the board:' => 'Kolonne:',
- 'Status is open' => 'Status: åpen',
+ 'Status is open' => 'Status: åpen',
'Status is closed' => 'Status: lukket',
'Close this task' => 'Lukk oppgaven',
- 'Open this task' => 'Ã…pne denne oppgaven',
+ 'Open this task' => 'Åpne denne oppgaven',
'There is no description.' => 'Det er ingen beskrivelse.',
'Add a new task' => 'Opprett ny oppgave',
- 'The username is required' => 'Brukernavn er påkrevd',
+ 'The username is required' => 'Brukernavn er påkrevd',
'The maximum length is %d characters' => 'Den maksimale lengden er %d tegn',
'The minimum length is %d characters' => 'Den minimale lengden er %d tegn',
- 'The password is required' => 'Passord er påkrevet',
- 'This value must be an integer' => 'Denne verdien skal være et tall',
- 'The username must be unique' => 'Brukernavnet skal være unikt',
- 'The user id is required' => 'Bruker-id er påkrevet',
+ 'The password is required' => 'Passord er påkrevet',
+ 'This value must be an integer' => 'Denne verdien skal være et tall',
+ 'The username must be unique' => 'Brukernavnet skal være unikt',
+ 'The user id is required' => 'Bruker-id er påkrevet',
'Passwords don\'t match' => 'Passordene stemmer ikke overens',
- 'The confirmation is required' => 'Bekreftelse er nødvendig',
- 'The project is required' => 'Prosjektet er påkrevet',
- 'The id is required' => 'Id\'en er påkrevd',
- 'The project id is required' => 'Prosjektet-id er påkrevet',
- 'The project name is required' => 'Prosjektnavn er påkrevet',
- 'This project must be unique' => 'Prosjektnavnet skal være unikt',
- 'The title is required' => 'Tittel er pårevet',
+ 'The confirmation is required' => 'Bekreftelse er nødvendig',
+ 'The project is required' => 'Prosjektet er påkrevet',
+ 'The id is required' => 'Id\'en er pøøkrevet',
+ 'The project id is required' => 'Prosjektet-id er påkrevet',
+ 'The project name is required' => 'Prosjektnavn er påkrevet',
+ 'This project must be unique' => 'Prosjektnavnet skal være unikt',
+ 'The title is required' => 'Tittel er pårevet',
+ 'There is no active project, the first step is to create a new project.' => 'Det er ingen aktive prosjekter. Førstesteg er åopprette et nytt prosjekt.',
'Settings saved successfully.' => 'Innstillinger lagret.',
'Unable to save your settings.' => 'Innstillinger kunne ikke lagres.',
- 'Database optimization done.' => 'Databaseoptimering er fullført.',
+ 'Database optimization done.' => 'Databaseoptimering er fullført.',
'Your project have been created successfully.' => 'Ditt prosjekt er opprettet.',
'Unable to create your project.' => 'Prosjektet kunne ikke opprettes',
'Project updated successfully.' => 'Prosjektet er oppdatert.',
@@ -139,9 +140,9 @@ return array(
'Unable to activate this project.' => 'Prosjektet kunne ikke aktiveres.',
'Project disabled successfully.' => 'Prosjektet er deaktiveret.',
'Unable to disable this project.' => 'Prosjektet kunne ikke deaktiveres.',
- 'Unable to open this task.' => 'Oppgaven kunne ikke åpnes.',
- 'Task opened successfully.' => 'Oppgaven er åpnet.',
- 'Unable to close this task.' => 'Oppgaven kunne ikke åpnes.',
+ 'Unable to open this task.' => 'Oppgaven kunne ikke åpnes.',
+ 'Task opened successfully.' => 'Oppgaven er åpnet.',
+ 'Unable to close this task.' => 'Oppgaven kunne ikke åpnes.',
'Task closed successfully.' => 'Oppgaven er lukket.',
'Unable to update your task.' => 'Oppgaven kunne ikke oppdateres.',
'Task updated successfully.' => 'Oppgaven er oppdatert.',
@@ -149,7 +150,7 @@ return array(
'Task created successfully.' => 'Oppgaven er opprettet.',
'User created successfully.' => 'Brukeren er opprettet.',
'Unable to create your user.' => 'Brukeren kunne ikke opprettes.',
- 'User updated successfully.' => 'Brukeren er opdateret',
+ 'User updated successfully.' => 'Brukeren er oppdatert',
'Unable to update your user.' => 'Din bruker kunne ikke oppdateres.',
'User removed successfully.' => 'Brukeren er fjernet.',
'Unable to remove this user.' => 'Brukeren kunne ikke slettes.',
@@ -157,13 +158,15 @@ return array(
'Ready' => 'Klar',
'Backlog' => 'Backlog',
'Work in progress' => 'Under arbeid',
- 'Done' => 'Utført',
+ 'Done' => 'Utført',
'Application version:' => 'Versjon:',
- 'Completed on %B %e, %Y at %k:%M %p' => 'Fullført %d.%m.%Y - %H:%M',
+ 'Completed on %B %e, %Y at %k:%M %p' => 'Fullført %d.%m.%Y - %H:%M',
'%B %e, %Y at %k:%M %p' => '%d.%m.%Y - %H:%M',
'Date created' => 'Dato for opprettelse',
- 'Date completed' => 'Dato for fullført',
+ 'Date completed' => 'Dato for fullført',
'Id' => 'ID',
+ 'Completed tasks' => 'Fullførte oppgaver',
+ 'Completed tasks for "%s"' => 'Fullførte oppgaver for "%s"',
'%d closed tasks' => '%d lukkede oppgaver',
'No task for this project' => 'Ingen oppgaver i dette prosjektet',
'Public link' => 'Offentligt lenke',
@@ -175,10 +178,10 @@ return array(
'Page not found' => 'Siden er ikke funnet',
'Complexity' => 'Kompleksitet',
'Task limit' => 'Oppgave begrensning',
- // 'Task count' => '',
+ 'Task count' => 'Antall oppgaver',
'Edit project access list' => 'Endre tillatelser for prosjektet',
'Allow this user' => 'Tillat denne brukeren',
- 'Don\'t forget that administrators have access to everything.' => 'Hust at administratorer har tilgang til alt.',
+ 'Don\'t forget that administrators have access to everything.' => 'Husk at administratorer har tilgang til alt.',
'Revoke' => 'Fjern',
'List of authorized users' => 'Liste over autoriserte brukere',
'User' => 'Bruker',
@@ -186,14 +189,14 @@ return array(
'Comments' => 'Kommentarer',
'Write your text in Markdown' => 'Skriv din tekst i markdown',
'Leave a comment' => 'Legg inn en kommentar',
- 'Comment is required' => 'Kommentar må legges inn',
+ 'Comment is required' => 'Kommentar må legges inn',
'Leave a description' => 'Legg inn en beskrivelse...',
'Comment added successfully.' => 'Kommentaren er lagt til.',
'Unable to create your comment.' => 'Din kommentar kunne ikke opprettes.',
'Edit this task' => 'Rediger oppgaven',
'Due Date' => 'Forfallsdato',
'Invalid date' => 'Ugyldig dato',
- 'Must be done before %B %e, %Y' => 'Skal være utført innen %d.%m.%Y',
+ 'Must be done before %B %e, %Y' => 'Skal være utført innen %d.%m.%Y',
'%B %e, %Y' => '%d.%m.%Y',
// '%b %e, %Y' => '',
'Automatic actions' => 'Automatiske handlinger',
@@ -205,19 +208,19 @@ return array(
'Automatic actions for the project "%s"' => 'Automatiske handlinger for prosjektet "%s"',
'Defined actions' => 'Definerte handlinger',
'Add an action' => 'Legg til en handling',
- 'Event name' => 'Begivenhet',
+ 'Event name' => 'Hendelsehet',
'Action name' => 'Handling',
'Action parameters' => 'Handlingsparametre',
'Action' => 'Handling',
- 'Event' => 'Begivenhet',
- 'When the selected event occurs execute the corresponding action.' => 'Når den valgtebegivenheten oppstår, utføre tilsvarende handlin.',
+ 'Event' => 'Hendelse',
+ 'When the selected event occurs execute the corresponding action.' => 'Når den valgte hendelsen oppstår, utfør tilsvarende handling.',
'Next step' => 'Neste',
'Define action parameters' => 'Definer handlingsparametre',
'Save this action' => 'Lagre handlingen',
- 'Do you really want to remove this action: "%s"?' => 'Vil du virkelig slette denne handlingen: "%s"?',
+ 'Do you really want to remove this action: "%s"?' => 'Vil du slette denne handlingen: "%s"?',
'Remove an automatic action' => 'Fjern en automatisk handling',
'Assign the task to a specific user' => 'Tildel oppgaven til en bestemt bruker',
- 'Assign the task to the person who does the action' => 'Tildel oppgaven til den person, som utfører handlingen',
+ 'Assign the task to the person who does the action' => 'Tildel oppgaven til den person, som utfører handlingen',
'Duplicate the task to another project' => 'Kopier oppgaven til et annet prosjekt',
'Move a task to another column' => 'Flytt oppgaven til en annen kolonne',
'Task modification' => 'Oppgaveendring',
@@ -236,37 +239,41 @@ return array(
'Remove a comment' => 'Fjern en kommentar',
'Comment removed successfully.' => 'Kommentaren ble fjernet.',
'Unable to remove this comment.' => 'Kommentaren kunne ikke fjernes.',
- 'Do you really want to remove this comment?' => 'Vil du virkelig fjerne denne kommentaren?',
+ 'Do you really want to remove this comment?' => 'Vil du fjerne denne kommentaren?',
'Only administrators or the creator of the comment can access to this page.' => 'Kun administrator eller brukeren, som har oprettet kommentaren har adgang til denne siden.',
'Current password for the user "%s"' => 'Aktivt passord for brukeren "%s"',
- 'The current password is required' => 'Passord er påkrevet',
+ 'The current password is required' => 'Passord er påkrevet',
'Wrong password' => 'Feil passord',
'Unknown' => 'Ukjent',
- 'Last logins' => 'Siste login',
+ 'Last logins' => 'Siste innlogging',
'Login date' => 'Login dato',
'Authentication method' => 'Godkjenningsmetode',
'IP address' => 'IP Adresse',
'User agent' => 'User Agent',
'Persistent connections' => 'Varige forbindelser',
'No session.' => 'Ingen session.',
- 'Expiration date' => 'Utløpsdato',
+ 'Expiration date' => 'Utløpsdato',
'Remember Me' => 'Husk meg',
'Creation date' => 'Opprettelsesdato',
+ 'Filter by user' => 'Filtrer efter bruker',
+ 'Filter by due date' => 'Filtrer etter forfallsdato',
'Everybody' => 'Alle',
- 'Open' => 'Ã…pen',
+ 'Open' => 'Åpen',
'Closed' => 'Lukket',
- 'Search' => 'Søk',
+ 'Search' => 'Søk',
'Nothing found.' => 'Intet funnet.',
+ 'Search in the project "%s"' => 'Søk i prosjektet "%s"',
'Due date' => 'Forfallsdato',
'Others formats accepted: %s and %s' => 'Andre formater: %s og %s',
'Description' => 'Beskrivelse',
'%d comments' => '%d kommentarer',
'%d comment' => '%d kommentar',
'Email address invalid' => 'Ugyldig epost',
- // 'Your external account is not linked anymore to your profile.' => '',
- // 'Unable to unlink your external account.' => '',
- // 'External authentication failed' => '',
- // 'Your external account is linked to your profile successfully.' => '',
+ 'Your Google Account is not linked anymore to your profile.' => 'Din Google-konto er ikke lengre knyttet til din profil.',
+ 'Unable to unlink your Google Account.' => 'Det var ikke mulig ø fjerne din Google-konto.',
+ 'Google authentication failed' => 'Google godjenning mislyktes',
+ 'Unable to link your Google Account.' => 'Det var ikke mulig åknytte opp til din Google-konto.',
+ 'Your Google Account is linked to your profile successfully.' => 'Din Google-konto er knyttet til din profil.',
'Email' => 'Epost',
'Link my Google Account' => 'Knytt til min Google-konto',
'Unlink my Google Account' => 'Fjern knytningen til min Google-konto',
@@ -275,9 +282,9 @@ return array(
'Task removed successfully.' => 'Oppgaven er fjernet.',
'Unable to remove this task.' => 'Oppgaven kunne ikke fjernes.',
'Remove a task' => 'Fjern en oppgave',
- 'Do you really want to remove this task: "%s"?' => 'Vil du virkelig fjerne denne opgave: "%s"?',
+ 'Do you really want to remove this task: "%s"?' => 'Vil du fjerne denne oppgaven: "%s"?',
'Assign automatically a color based on a category' => 'Tildel automatisk en farge baseret for en kategori',
- 'Assign automatically a category based on a color' => 'Tildel automatisk en kategori basert på en farve',
+ 'Assign automatically a category based on a color' => 'Tildel automatisk en kategori basert på en farve',
'Task creation or modification' => 'Oppgaveopprettelse eller endring',
'Category' => 'Kategori',
'Category:' => 'Kategori:',
@@ -293,17 +300,18 @@ return array(
'Category modification for the project "%s"' => 'Endring av kategori for prosjektet "%s"',
'Category Name' => 'Kategorinavn',
'Add a new category' => 'Legg til ny kategori',
- 'Do you really want to remove this category: "%s"?' => 'Vil du virkelig fjerne kategorien: "%s"?',
+ 'Do you really want to remove this category: "%s"?' => 'Vil du fjerne kategorien: "%s"?',
+ 'Filter by category' => 'Filter etter kategori',
'All categories' => 'Alle kategorier',
'No category' => 'Ingen kategori',
- 'The name is required' => 'Navnet er påkrevet',
+ 'The name is required' => 'Navnet er påkrevet',
'Remove a file' => 'Fjern en fil',
'Unable to remove this file.' => 'Filen kunne ikke fjernes.',
'File removed successfully.' => 'Filen er fjernet.',
'Attach a document' => 'Legg til et dokument',
- 'Do you really want to remove this file: "%s"?' => 'Vil du virkelig fjerne filen: "%s"?',
- 'open' => 'Ã¥pen',
- 'Attachments' => 'Vedleggr',
+ 'Do you really want to remove this file: "%s"?' => 'Vil du fjerne filen: "%s"?',
+ 'open' => 'øpen',
+ 'Attachments' => 'Vedlegg',
'Edit the task' => 'Rediger oppgaven',
'Edit the description' => 'Rediger beskrivelsen',
'Add a comment' => 'Legg til en kommentar',
@@ -312,8 +320,8 @@ return array(
'Time tracking' => 'Tidsregistrering',
'Estimate:' => 'Estimat:',
'Spent:' => 'Brukt:',
- 'Do you really want to remove this sub-task?' => 'Vil du virkelig fjerne denne deloppgaven?',
- 'Remaining:' => 'Gjenværende:',
+ 'Do you really want to remove this sub-task?' => 'Vil du fjerne denne deloppgaven?',
+ 'Remaining:' => 'Gjenværende:',
'hours' => 'timer',
'spent' => 'brukt',
'estimated' => 'estimat',
@@ -324,8 +332,8 @@ return array(
'Time spent' => 'Tidsforbruk',
'Edit a sub-task' => 'Rediger en deloppgave',
'Remove a sub-task' => 'Fjern en deloppgave',
- 'The time must be a numeric value' => 'Tiden skal være en nummerisk erdi',
- 'Todo' => 'Gjøremål',
+ 'The time must be a numeric value' => 'Tiden skal være en nummerisk erdi',
+ 'Todo' => 'Gjøremål',
'In progress' => 'Under arbeid',
'Sub-task removed successfully.' => 'Deloppgaven er fjernet.',
'Unable to remove this sub-task.' => 'Deloppgaven kunne ikke fjernes.',
@@ -333,19 +341,24 @@ return array(
'Unable to update your sub-task.' => 'Deloppgaven kunne ikke opdateres.',
'Unable to create your sub-task.' => 'Deloppgaven kunne ikke oprettes.',
'Sub-task added successfully.' => 'Deloppgaven er lagt til.',
- 'Maximum size: ' => 'Maksimum størrelse: ',
+ 'Maximum size: ' => 'Maksimum størrelse: ',
'Unable to upload the file.' => 'Filen kunne ikke lastes opp.',
'Display another project' => 'Vis annet prosjekt...',
- // 'Login with my Github Account' => '',
- // 'Link my Github Account' => '',
- // 'Unlink my Github Account' => '',
+ 'Your GitHub account was successfully linked to your profile.' => 'Din GitHub-konto er knyttet til din profil.',
+ 'Unable to link your GitHub Account.' => 'Det var ikke mulig å knytte din GitHub-konto.',
+ 'GitHub authentication failed' => 'GitHub godkjenning mislyktes',
+ 'Your GitHub account is no longer linked to your profile.' => 'Din GitHub-konto er ikke lengere knyttet til din profil.',
+ 'Unable to unlink your GitHub Account.' => 'Det var ikke muligt at fjerne forbindelsen til din GitHub-konto.',
+ 'Login with my GitHub Account' => 'Login med min GitHub-konto',
+ 'Link my GitHub Account' => 'Knytt min GitHub-konto',
+ 'Unlink my GitHub Account' => 'Fjern knytningen til min GitHub-konto',
'Created by %s' => 'Opprettet av %s',
'Last modified on %B %e, %Y at %k:%M %p' => 'Sist endret %d.%m.%Y - %H:%M',
'Tasks Export' => 'Oppgave eksport',
'Tasks exportation for "%s"' => 'Oppgaveeksportering for "%s"',
'Start Date' => 'Start-dato',
'End Date' => 'Slutt-dato',
- 'Execute' => 'KKjør',
+ 'Execute' => 'KKjør',
'Task Id' => 'Oppgave ID',
'Creator' => 'Laget av',
'Modification date' => 'Endringsdato',
@@ -354,15 +367,15 @@ return array(
'Project cloned successfully.' => 'Prosjektet er kopiert.',
'Unable to clone this project.' => 'Prosjektet kunne ikke kopieres',
'Email notifications' => 'Epostvarslinger',
- 'Enable email notifications' => 'Aktiver eposvarslinger',
+ 'Enable email notifications' => 'Aktiver epostvarslinger',
'Task position:' => 'Oppgaveposisjon:',
- 'The task #%d have been opened.' => 'Oppgaven #%d er åpnet.',
+ 'The task #%d have been opened.' => 'Oppgaven #%d er åpnet.',
'The task #%d have been closed.' => 'Oppgaven #%d er lukket.',
'Sub-task updated' => 'Deloppgaven er oppdatert',
'Title:' => 'Tittel:',
'Status:' => 'Status:',
'Assignee:' => 'Ansvarlig:',
- 'Time tracking:' => 'Tidsmåling:',
+ 'Time tracking:' => 'Tidsmåling:',
'New sub-task' => 'Ny deloppgave',
'New attachment added "%s"' => 'Nytt vedlegg er lagt tilet "%s"',
'Comment updated' => 'Kommentar oppdatert',
@@ -373,21 +386,21 @@ return array(
'Subtask updated' => 'Deloppgave oppdatert',
'Task updated' => 'Oppgave oppdatert',
'Task closed' => 'Oppgave lukket',
- 'Task opened' => 'Oppgave åpnet',
+ 'Task opened' => 'Oppgave åpnet',
'I want to receive notifications only for those projects:' => 'Jeg vil kun ha varslinger for disse prosjekter:',
- 'view the task on Kanboard' => 'se oppgaven påhovedsiden',
+ 'view the task on Kanboard' => 'se oppgaven påhovedsiden',
'Public access' => 'Offentlig tilgang',
'User management' => 'Brukere',
'Active tasks' => 'Aktive oppgaver',
'Disable public access' => 'Deaktiver offentlig tilgang',
'Enable public access' => 'Aktiver offentlig tilgang',
'Public access disabled' => 'Offentlig tilgang er deaktivert',
- 'Do you really want to disable this project: "%s"?' => 'Vil du virkelig deaktivere prosjektet: "%s"?',
- 'Do you really want to enable this project: "%s"?' => 'Vil du virkelig aktivere prosjektet: "%s"?',
+ 'Do you really want to disable this project: "%s"?' => 'Vil du deaktivere prosjektet: "%s"?',
+ 'Do you really want to enable this project: "%s"?' => 'Vil du aktivere prosjektet: "%s"?',
'Project activation' => 'Prosjekt aktivering',
'Move the task to another project' => 'Flytt oppgaven til et annet prosjekt',
'Move to another project' => 'Flytt til et annet prosjekt',
- 'Do you really want to duplicate this task?' => 'Vil du virkelig kopiere denne oppgaven?',
+ 'Do you really want to duplicate this task?' => 'Vil du kopiere denne oppgaven?',
'Duplicate a task' => 'Kopier en oppgave',
'External accounts' => 'Eksterne kontoer',
'Account type' => 'Kontotype',
@@ -411,7 +424,7 @@ return array(
'External authentications' => 'Ekstern godkjenning',
'Google Account' => 'Google-konto',
'Github Account' => 'GitHub-konto',
- 'Never connected.' => 'Aldri knyttet.',
+ 'Never connected.' => 'Aldri innlogget.',
'No account linked.' => 'Ingen kontoer knyttet.',
'Account linked.' => 'Konto knyttet.',
'No external authentication enabled.' => 'Ingen eksterne godkjenninger aktiveret.',
@@ -420,18 +433,19 @@ return array(
'Change category for the task "%s"' => 'Endre kategori for oppgaven "%s"',
'Change category' => 'Endre kategori',
'%s updated the task %s' => '%s oppdaterte oppgaven %s',
- '%s opened the task %s' => '%s åpnet oppgaven %s',
+ '%s opened the task %s' => '%s åpnet oppgaven %s',
'%s moved the task %s to the position #%d in the column "%s"' => '%s flyttet oppgaven %s til posisjonen #%d i kolonnen "%s"',
'%s moved the task %s to the column "%s"' => '%s flyttet oppgaven %s til kolonnen "%s"',
'%s created the task %s' => '%s opprettet oppgaven %s',
'%s closed the task %s' => '%s lukket oppgaven %s',
'%s created a subtask for the task %s' => '%s opprettet en deloppgave for oppgaven %s',
'%s updated a subtask for the task %s' => '%s oppdaterte en deloppgave for oppgaven %s',
- 'Assigned to %s with an estimate of %s/%sh' => 'Tildelt til %s med et estimat på %s/%sh',
+ 'Assigned to %s with an estimate of %s/%sh' => 'Tildelt til %s med et estimat på %s/%sh',
'Not assigned, estimate of %sh' => 'Ikke tildelt, estimert til %sh',
'%s updated a comment on the task %s' => '%s oppdaterte en kommentar til oppgaven %s',
'%s commented the task %s' => '%s har kommentert oppgaven %s',
'%s\'s activity' => '%s\'s aktvitet',
+ 'No activity.' => 'Ingen aktivitet',
'RSS feed' => 'RSS feed',
'%s updated a comment on the task #%d' => '%s oppdaterte en kommentar til oppgaven #%d',
'%s commented on the task #%d' => '%s kommenterte oppgaven #%d',
@@ -440,7 +454,7 @@ return array(
'%s updated the task #%d' => '%s oppdaterte oppgaven #%d',
'%s created the task #%d' => '%s opprettet oppgaven #%d',
'%s closed the task #%d' => '%s lukket oppgaven #%d',
- '%s open the task #%d' => '%s åpnet oppgaven #%d',
+ '%s open the task #%d' => '%s åpnet oppgaven #%d',
'%s moved the task #%d to the column "%s"' => '%s flyttet oppgaven #%d til kolonnen "%s"',
'%s moved the task #%d to the position %d in the column "%s"' => '%s flyttet oppgaven #%d til posisjonen %d i kolonnen "%s"',
'Activity' => 'Aktivitetslogg',
@@ -452,21 +466,21 @@ return array(
'New password for the user "%s"' => 'Nytt passord for brukeren "%s"',
'Choose an event' => 'Velg en hendelse',
'Github commit received' => 'Github forpliktelse mottatt',
- 'Github issue opened' => 'Github problem åpnet',
+ 'Github issue opened' => 'Github problem åpnet',
'Github issue closed' => 'Github problem lukket',
- 'Github issue reopened' => 'Github problem gjenåpnet',
+ 'Github issue reopened' => 'Github problem gjenåpnet',
'Github issue assignee change' => 'Endre ansvarlig for Github problem',
'Github issue label change' => 'Endre etikett for Github problem',
'Create a task from an external provider' => 'Oppret en oppgave fra en ekstern tilbyder',
- 'Change the assignee based on an external username' => 'Endre ansvarlige baseret på et eksternt brukernavn',
- 'Change the category based on an external label' => 'Endre kategorien basert på en ekstern etikett',
+ 'Change the assignee based on an external username' => 'Endre ansvarlige baseret på et eksternt brukernavn',
+ 'Change the category based on an external label' => 'Endre kategorien basert på en ekstern etikett',
'Reference' => 'Referanse',
'Reference: %s' => 'Referanse: %s',
'Label' => 'Etikett',
'Database' => 'Database',
'About' => 'Om',
'Database driver:' => 'Database driver:',
- 'Board settings' => 'Innstillinger for ptosjektside',
+ 'Board settings' => 'Innstillinger for prosjektside',
'URL and token' => 'URL og token',
'Webhook settings' => 'Webhook innstillinger',
'URL for task creation:' => 'URL for oppgaveopprettelse:',
@@ -475,9 +489,9 @@ return array(
'Refresh interval for private board' => 'Oppdateringsintervall for privat hovedside',
'Refresh interval for public board' => 'Oppdateringsintervall for offentlig hovedside',
'Task highlight period' => 'Fremhevingsperiode for oppgave',
- 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Periode for å anta at en oppgave nylig ble endretg (0 for å deaktivere, 2 dager som standard)',
+ 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Periode for ø anta at en oppgave nylig ble endretg (0 for å deaktivere, 2 dager som standard)',
'Frequency in second (60 seconds by default)' => 'Frekevens i sekunder (60 sekunder som standard)',
- 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Frekvens i sekunder (0 for å deaktivere denne funksjonen, 10 sekunder som standard)',
+ 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Frekvens i sekunder (0 for øt deaktivere denne funksjonen, 10 sekunder som standard)',
'Application URL' => 'Applikasjons URL',
'Example: http://example.kanboard.net/ (used by email notifications)' => 'Eksempel: http://example.kanboard.net/ (bruges til email notifikationer)',
'Token regenerated.' => 'Token regenerert.',
@@ -485,7 +499,7 @@ return array(
'ISO format is always accepted, example: "%s" and "%s"' => 'ISO format er alltid akseptert, eksempelvis: "%s" og "%s"',
'New private project' => 'Nytt privat prosjekt',
'This project is private' => 'Dette projektet er privat',
- 'Type here to create a new sub-task' => 'Skriv her for å opprette en ny deloppgave',
+ 'Type here to create a new sub-task' => 'Skriv her for ø opprette en ny deloppgave',
'Add' => 'Legg til',
'Estimated time: %s hours' => 'Estimert tid: %s timer',
'Time spent: %s hours' => 'Tid brukt: %s timer',
@@ -510,21 +524,21 @@ return array(
'Columns' => 'Kolonner',
'Task' => 'Oppgave',
// 'Your are not member of any project.' => '',
- // 'Percentage' => '',
- // 'Number of tasks' => '',
- // 'Task distribution' => '',
- // 'Reportings' => '',
+ 'Percentage' => 'Prosent',
+ 'Number of tasks' => 'Antall oppgaver',
+ 'Task distribution' => 'Kolonnefordeling',
+ 'Reportings' => 'Rapportering',
// 'Task repartition for "%s"' => '',
'Analytics' => 'Analyser',
- // 'Subtask' => '',
+ 'Subtask' => 'Deloppgave',
'My subtasks' => 'Mine deloppgaver',
- // 'User repartition' => '',
+ 'User repartition' => 'Brukerfordeling',
// 'User repartition for "%s"' => '',
'Clone this project' => 'Kopier dette prosjektet',
- // 'Column removed successfully.' => '',
+ 'Column removed successfully.' => 'Kolonne flyttet',
// 'Github Issue' => '',
// 'Not enough data to show the graph.' => '',
- // 'Previous' => '',
+ 'Previous' => 'Forrige',
// 'The id must be an integer' => '',
// 'The project id must be an integer' => '',
// 'The status must be an integer' => '',
@@ -536,32 +550,32 @@ return array(
// 'This value is required' => '',
// 'This value must be numeric' => '',
// 'Unable to create this task.' => '',
- // 'Cumulative flow diagram' => '',
+ 'Cumulative flow diagram' => 'Kumulativt flytdiagram',
// 'Cumulative flow diagram for "%s"' => '',
- // 'Daily project summary' => '',
+ 'Daily project summary' => 'Daglig prosjektsammendrag',
// 'Daily project summary export' => '',
// 'Daily project summary export for "%s"' => '',
'Exports' => 'Eksporter',
// 'This export contains the number of tasks per column grouped per day.' => '',
- 'Nothing to preview...' => 'Ingenting å forhåndsvise',
- 'Preview' => 'Forhåndsvisning',
+ 'Nothing to preview...' => 'Ingenting å forhåndsvise',
+ 'Preview' => 'Forhåndsvisning',
'Write' => 'Skriv',
- 'Active swimlanes' => 'Aktive svæmmebaner',
- 'Add a new swimlane' => 'Legg til en ny svømmebane',
- 'Change default swimlane' => 'Endre standard svømmebane',
- 'Default swimlane' => 'Standard svømmebane',
+ 'Active swimlanes' => 'Aktive svømmebaner',
+ 'Add a new swimlane' => 'Legg til en ny svømmebane',
+ 'Change default swimlane' => 'Endre standard svømmebane',
+ 'Default swimlane' => 'Standard svømmebane',
// 'Do you really want to remove this swimlane: "%s"?' => '',
// 'Inactive swimlanes' => '',
'Set project manager' => 'Velg prosjektleder',
'Set project member' => 'Velg prosjektmedlem',
- 'Remove a swimlane' => 'Fjern en svømmebane',
+ 'Remove a swimlane' => 'Fjern en svømmebane',
'Rename' => 'Endre navn',
- 'Show default swimlane' => 'Vis standard svømmebane',
+ 'Show default swimlane' => 'Vis standard svømmebane',
// 'Swimlane modification for the project "%s"' => '',
- // 'Swimlane not found.' => '',
- // 'Swimlane removed successfully.' => '',
- 'Swimlanes' => 'Svømmebaner',
- 'Swimlane updated successfully.' => 'Svæmmebane oppdatert',
+ 'Swimlane not found.' => 'Svømmebane ikke funnet',
+ 'Swimlane removed successfully.' => 'Svømmebane fjernet',
+ 'Swimlanes' => 'Svømmebaner',
+ 'Swimlane updated successfully.' => 'Svømmebane oppdatert',
// 'The default swimlane have been updated successfully.' => '',
// 'Unable to create your swimlane.' => '',
// 'Unable to remove this swimlane.' => '',
@@ -576,27 +590,32 @@ return array(
// 'Help on Gitlab webhooks' => '',
'Integrations' => 'Integrasjoner',
'Integration with third-party services' => 'Integrasjoner med tredje-parts tjenester',
- // 'Role for this project' => '',
+ 'Role for this project' => 'Rolle for dette prosjektet',
'Project manager' => 'Prosjektleder',
'Project member' => 'Prosjektmedlem',
'A project manager can change the settings of the project and have more privileges than a standard user.' => 'Prosjektlederen kan endre flere innstillinger for prosjektet enn den en vanlig bruker kan.',
// 'Gitlab Issue' => '',
- // 'Subtask Id' => '',
- // 'Subtasks' => '',
- // 'Subtasks Export' => '',
+ 'Subtask Id' => 'Deloppgave ID',
+ 'Subtasks' => 'Deloppgaver',
+ 'Subtasks Export' => 'Eksporter deloppgaver',
// 'Subtasks exportation for "%s"' => '',
- // 'Task Title' => '',
+ 'Task Title' => 'Oppgavetittel',
// 'Untitled' => '',
'Application default' => 'Standardinstilling',
- 'Language:' => 'Språk',
+ 'Language:' => 'Språk',
'Timezone:' => 'Tidssone',
- // 'All columns' => '',
+ 'All columns' => 'Alle kolonner',
+ 'Calendar for "%s"' => 'Kalender for "%s"',
+ 'Filter by column' => 'Filtrer etter kolonne',
+ 'Filter by status' => 'Filtrer etter status',
'Calendar' => 'Kalender',
- // 'Next' => '',
+ 'Next' => 'Neste',
// '#%d' => '',
- // 'All swimlanes' => '',
- // 'All colors' => '',
- // 'All status' => '',
+ 'Filter by color' => 'Filtrer etter farge',
+ 'Filter by swimlane' => 'Filtrer etter svømmebane',
+ 'All swimlanes' => 'Alle svømmebaner',
+ 'All colors' => 'Alle farger',
+ 'All status' => 'Alle statuser',
// 'Moved to column %s' => '',
'Change description' => 'Endre beskrivelse',
'User dashboard' => 'Brukerens hovedside',
@@ -604,16 +623,23 @@ return array(
// 'Edit column "%s"' => '',
// 'Select the new status of the subtask: "%s"' => '',
'Subtask timesheet' => 'Tidsskjema for deloppgaver',
- 'There is nothing to show.' => 'Ingen data å vise',
+ 'There is nothing to show.' => 'Ingen data å vise',
// 'Time Tracking' => '',
// 'You already have one subtask in progress' => '',
- 'Which parts of the project do you want to duplicate?' => 'Hvilke deler av dette prosjektet ønsker du å kopiere?',
- // 'Disallow login form' => '',
+ 'Which parts of the project do you want to duplicate?' => 'Hvilke deler av dette prosjektet ønsker du å kopiere?',
+ 'Change dashboard view' => 'Endre visning',
+ 'Show/hide activities' => 'Vis/skjul aktiviteter',
+ 'Show/hide projects' => 'Vis/skjul prosjekter',
+ 'Show/hide subtasks' => 'Vis/skjul deloppgaver',
+ 'Show/hide tasks' => 'Vis/skjul oppgaver',
+ 'Disable login form' => 'Deaktiver innlogging',
+ 'Show/hide calendar' => 'Vis/skjul kalender',
+ 'User calendar' => 'Brukerens kalender',
// 'Bitbucket commit received' => '',
// 'Bitbucket webhooks' => '',
// 'Help on Bitbucket webhooks' => '',
- // 'Start' => '',
- // 'End' => '',
+ 'Start' => 'Start',
+ 'End' => 'Slutt',
'Task age in days' => 'Dager siden oppgaven ble opprettet',
'Days in this column' => 'Dager siden oppgaven ble lagt i denne kolonnen',
// '%dd' => '',
@@ -621,7 +647,7 @@ return array(
'Add a new link' => 'Legg til en ny relasjon',
// 'Do you really want to remove this link: "%s"?' => '',
// 'Do you really want to remove this link with task #%d?' => '',
- // 'Field required' => '',
+ 'Field required' => 'Feltet må fylles ut',
'Link added successfully.' => 'Ny relasjon er lagt til',
'Link updated successfully.' => 'Relasjon er oppdatert',
'Link removed successfully.' => 'Relasjon er fjernet',
@@ -647,8 +673,8 @@ return array(
'is a parent of' => 'er en overordnet oppgave av',
'targets milestone' => 'milepel',
'is a milestone of' => 'er en milepel av',
- 'fixes' => 'løser',
- 'is fixed by' => 'løses av',
+ 'fixes' => 'løser',
+ 'is fixed by' => 'løses av',
'This task' => 'Denne oppgaven',
// '<1h' => '',
// '%dh' => '',
@@ -662,7 +688,9 @@ return array(
'Keyboard shortcuts' => 'Hurtigtaster',
// 'Open board switcher' => '',
// 'Application' => '',
+ 'Filter recently updated' => 'Filter nylig oppdatert',
'since %B %e, %Y at %k:%M %p' => 'siden %B %e, %Y at %k:%M %p',
+ 'More filters' => 'Flere filtre',
'Compact view' => 'Kompakt visning',
'Horizontal scrolling' => 'Bla horisontalt',
'Compact/wide view' => 'Kompakt/bred visning',
@@ -680,9 +708,9 @@ return array(
// 'Hourly rate created successfully.' => '',
// 'Start time' => '',
// 'End time' => '',
- // 'Comment' => '',
- // 'All day' => '',
- // 'Day' => '',
+ 'Comment' => 'Kommentar',
+ 'All day' => 'Alle dager',
+ 'Day' => 'Dag',
'Manage timetable' => 'Tidstabell',
'Overtime timetable' => 'Overtidstabell',
'Time off timetable' => 'Fritidstabell',
@@ -703,18 +731,18 @@ return array(
'Files' => 'Filer',
'Images' => 'Bilder',
'Private project' => 'Privat prosjekt',
- // 'Amount' => '',
+ 'Amount' => 'Beløp',
// 'AUD - Australian Dollar' => '',
'Budget' => 'Budsjett',
// 'Budget line' => '',
// 'Budget line removed successfully.' => '',
- // 'Budget lines' => '',
+ 'Budget lines' => 'Budsjettlinjer',
// 'CAD - Canadian Dollar' => '',
// 'CHF - Swiss Francs' => '',
- // 'Cost' => '',
- // 'Cost breakdown' => '',
+ 'Cost' => 'Kostnad',
+ 'Cost breakdown' => 'Kostnadsnedbryting',
// 'Custom Stylesheet' => '',
- // 'download' => '',
+ 'download' => 'last ned',
// 'Do you really want to remove this budget line?' => '',
// 'EUR - Euro' => '',
// 'Expenses' => '',
@@ -731,10 +759,10 @@ return array(
// 'Unable to remove this budget line.' => '',
// 'USD - US Dollar' => '',
// 'Remaining' => '',
- // 'Destination column' => '',
- 'Move the task to another column when assigned to a user' => 'Flytt oppgaven til en annen kolonne når den er tildelt en bruker',
- 'Move the task to another column when assignee is cleared' => 'Flytt oppgaven til en annen kolonne når ppgavetildeling fjernes ',
- // 'Source column' => '',
+ 'Destination column' => 'Ny kolonne',
+ 'Move the task to another column when assigned to a user' => 'Flytt oppgaven til en annen kolonne når den er tildelt en bruker',
+ 'Move the task to another column when assignee is cleared' => 'Flytt oppgaven til en annen kolonne når ppgavetildeling fjernes ',
+ 'Source column' => 'Opprinnelig kolonne',
// 'Show subtask estimates (forecast of future work)' => '',
'Transitions' => 'Statusendringer',
// 'Executer' => '',
@@ -782,10 +810,10 @@ return array(
// 'This chart show the task complexity over the time (Work Remaining).' => '',
// 'Screenshot taken %s' => '',
'Add a screenshot' => 'Legg til et skjermbilde',
- 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Ta et skjermbilde og trykk CTRL+V for å lime det inn her.',
+ 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Ta et skjermbilde og trykk CTRL+V for å lime det inn her.',
'Screenshot uploaded successfully.' => 'Skjermbilde opplastet',
// 'SEK - Swedish Krona' => '',
- 'The project identifier is an optional alphanumeric code used to identify your project.' => 'Prosjektkoden er en alfanumerisk kode som kan brukes for å identifisere prosjektet',
+ 'The project identifier is an optional alphanumeric code used to identify your project.' => 'Prosjektkoden er en alfanumerisk kode som kan brukes for å identifisere prosjektet',
'Identifier' => 'Prosjektkode',
// 'Postmark (incoming emails)' => '',
// 'Help on Postmark integration' => '',
@@ -807,26 +835,26 @@ return array(
// 'This value must be alphanumeric' => '',
'Edit recurrence' => 'Endre gjentakelser',
'Generate recurrent task' => 'Opprett gjentagende oppgave',
- 'Trigger to generate recurrent task' => 'Betingelse for å generere gjentakende oppgave',
- 'Factor to calculate new due date' => 'Faktor for å beregne ny tidsfrist',
- 'Timeframe to calculate new due date' => 'Tidsramme for å beregne ny tidsfrist',
- 'Base date to calculate new due date' => 'Grunnlagsdato for å beregne ny tidsfrist',
+ 'Trigger to generate recurrent task' => 'Betingelse for å generere gjentakende oppgave',
+ 'Factor to calculate new due date' => 'Faktor for å beregne ny tidsfrist',
+ 'Timeframe to calculate new due date' => 'Tidsramme for å beregne ny tidsfrist',
+ 'Base date to calculate new due date' => 'Grunnlagsdato for å beregne ny tidsfrist',
'Action date' => 'Hendelsesdato',
- 'Base date to calculate new due date: ' => 'Grunnlagsdato for å beregne ny tidsfrist',
+ 'Base date to calculate new due date: ' => 'Grunnlagsdato for å beregne ny tidsfrist',
'This task has created this child task: ' => 'Denne oppgaven har opprettet denne relaterte oppgaven',
'Day(s)' => 'Dager',
'Existing due date' => 'Eksisterende forfallsdato',
- 'Factor to calculate new due date: ' => 'Faktor for å beregne ny tidsfrist',
- 'Month(s)' => 'MÃ¥neder',
+ 'Factor to calculate new due date: ' => 'Faktor for å beregne ny tidsfrist',
+ 'Month(s)' => 'Måneder',
'Recurrence' => 'Gjentakelse',
'This task has been created by: ' => 'Denne oppgaven er opprettet av:',
// 'Recurrent task has been generated:' => '',
// 'Timeframe to calculate new due date: ' => '',
// 'Trigger to generate recurrent task: ' => '',
- 'When task is closed' => 'NÃ¥r oppgaven er lukket',
- 'When task is moved from first column' => 'Når oppgaven er flyttet fra første kolon',
- 'When task is moved to last column' => 'NÃ¥r oppgaven er flyttet til siste kolonne',
- 'Year(s)' => 'Ã¥r',
+ 'When task is closed' => 'Når oppgaven er lukket',
+ 'When task is moved from first column' => 'Når oppgaven er flyttet fra første kolon',
+ 'When task is moved to last column' => 'Når oppgaven er flyttet til siste kolonne',
+ 'Year(s)' => 'år',
// 'Jabber (XMPP)' => '',
// 'Send notifications to Jabber' => '',
// 'XMPP server address' => '',
@@ -853,16 +881,16 @@ return array(
// 'There is no user management for private projects.' => '',
// 'User that will receive the email' => '',
// 'Email subject' => '',
- // 'Date' => '',
+ 'Date' => 'Dato',
// 'By @%s on Bitbucket' => '',
// 'Bitbucket Issue' => '',
// 'Commit made by @%s on Bitbucket' => '',
// 'Commit made by @%s on Github' => '',
// 'By @%s on Github' => '',
// 'Commit made by @%s on Gitlab' => '',
- 'Add a comment log when moving the task between columns' => 'Legg til en kommentar i loggen når en oppgave flyttes mellom kolonnene',
- 'Move the task to another column when the category is changed' => 'Flytt oppgaven til en annen kolonne når kategorien endres',
- 'Send a task by email to someone' => 'Send en oppgave på epost til noen',
+ 'Add a comment log when moving the task between columns' => 'Legg til en kommentar i loggen når en oppgave flyttes mellom kolonnene',
+ 'Move the task to another column when the category is changed' => 'Flytt oppgaven til en annen kolonne når kategorien endres',
+ 'Send a task by email to someone' => 'Send en oppgave på epost til noen',
// 'Reopen a task' => '',
// 'Bitbucket issue opened' => '',
// 'Bitbucket issue closed' => '',
@@ -871,14 +899,14 @@ return array(
// 'Bitbucket issue comment created' => '',
'Column change' => 'Endret kolonne',
'Position change' => 'Posisjonsendring',
- 'Swimlane change' => 'Endret svømmebane',
+ 'Swimlane change' => 'Endret svømmebane',
'Assignee change' => 'Endret eier',
// '[%s] Overdue tasks' => '',
'Notification' => 'Varsel',
// '%s moved the task #%d to the first swimlane' => '',
// '%s moved the task #%d to the swimlane "%s"' => '',
- // 'Swimlane' => '',
- // 'Budget overview' => '',
+ 'Swimlane' => 'Svømmebane',
+ 'Budget overview' => 'Budsjettoversikt',
// 'Type' => '',
// 'There is not enough data to show something.' => '',
// 'Gravatar' => '',
@@ -893,6 +921,7 @@ return array(
// 'The task have been moved to the first swimlane' => '',
// 'The task have been moved to another swimlane:' => '',
// 'Overdue tasks for the project "%s"' => '',
+ 'There is no completed tasks at the moment.' => 'Ingen fullførte oppgaver funnet.',
// 'New title: %s' => '',
// 'The task is not assigned anymore' => '',
// 'New assignee: %s' => '',
@@ -908,61 +937,62 @@ return array(
// 'The field "%s" have been updated' => '',
// 'The description have been modified' => '',
// 'Do you really want to close the task "%s" as well as all subtasks?' => '',
- // 'Swimlane: %s' => '',
- // 'I want to receive notifications for:' => '',
+ 'Swimlane: %s' => 'Svømmebane: %s',
+ 'Project calendar' => 'Prosjektkalender',
+ 'I want to receive notifications for:' => 'Jeg vil motta varslinger om:',
'All tasks' => 'Alle oppgaver',
- // 'Only for tasks assigned to me' => '',
- // 'Only for tasks created by me' => '',
- // 'Only for tasks created by me and assigned to me' => '',
+ 'Only for tasks assigned to me' => 'Kun oppgaver som er tildelt meg',
+ 'Only for tasks created by me' => 'Kun oppgaver som er opprettet av meg',
+ 'Only for tasks created by me and assigned to me' => 'Kun oppgaver som er opprettet av meg og tildelt meg',
// '%A' => '',
// '%b %e, %Y, %k:%M %p' => '',
// 'New due date: %B %e, %Y' => '',
// 'Start date changed: %B %e, %Y' => '',
// '%k:%M %p' => '',
// '%%Y-%%m-%%d' => '',
- // 'Total for all columns' => '',
- // 'You need at least 2 days of data to show the chart.' => '',
+ 'Total for all columns' => 'Totalt for alle kolonner',
+ //'You need at least 2 days of data to show the chart.' => '',
// '<15m' => '',
// '<30m' => '',
'Stop timer' => 'Stopp timer',
'Start timer' => 'Start timer',
'Add project member' => 'Legg til prosjektmedlem',
'Enable notifications' => 'Aktiver varslinger',
- // 'My activity stream' => '',
- // 'My calendar' => '',
- // 'Search tasks' => '',
- // 'Back to the calendar' => '',
- // 'Filters' => '',
- // 'Reset filters' => '',
- // 'My tasks due tomorrow' => '',
- // 'Tasks due today' => '',
- // 'Tasks due tomorrow' => '',
- // 'Tasks due yesterday' => '',
- // 'Closed tasks' => '',
- // 'Open tasks' => '',
- // 'Not assigned' => '',
- // 'View advanced search syntax' => '',
- // 'Overview' => '',
+ 'My activity stream' => 'Aktivitetslogg',
+ 'My calendar' => 'Min kalender',
+ 'Search tasks' => 'Søk oppgave',
+ 'Back to the calendar' => 'Tilbake til kalender',
+ 'Filters' => 'Filtere',
+ 'Reset filters' => 'Nullstill filter',
+ 'My tasks due tomorrow' => 'Mine oppgaver med frist i morgen',
+ 'Tasks due today' => 'Oppgaver med frist i dag',
+ 'Tasks due tomorrow' => 'Oppgaver med frist i morgen',
+ 'Tasks due yesterday' => 'Oppgaver med frist i går',
+ 'Closed tasks' => 'Fullførte oppgaver',
+ 'Open tasks' => 'Åpne oppgaver',
+ 'Not assigned' => 'Ikke tildelt',
+ 'View advanced search syntax' => 'Vis hjelp for avansert søk ',
+ 'Overview' => 'Oversikt',
// '%b %e %Y' => '',
- // 'Board/Calendar/List view' => '',
- // 'Switch to the board view' => '',
- // 'Switch to the calendar view' => '',
- // 'Switch to the list view' => '',
- // 'Go to the search/filter box' => '',
- // 'There is no activity yet.' => '',
- // 'No tasks found.' => '',
- // 'Keyboard shortcut: "%s"' => '',
- // 'List' => '',
- // 'Filter' => '',
- // 'Advanced search' => '',
- // 'Example of query: ' => '',
- // 'Search by project: ' => '',
- // 'Search by column: ' => '',
- // 'Search by assignee: ' => '',
- // 'Search by color: ' => '',
- // 'Search by category: ' => '',
- // 'Search by description: ' => '',
- // 'Search by due date: ' => '',
+ 'Board/Calendar/List view' => 'Oversikt/kalender/listevisning',
+ 'Switch to the board view' => 'Oversiktsvisning',
+ 'Switch to the calendar view' => 'Kalendevisning',
+ 'Switch to the list view' => 'Listevisning',
+ 'Go to the search/filter box' => 'Gå til søk/filter',
+ 'There is no activity yet.' => 'Ingen aktiviteter ennå.',
+ 'No tasks found.' => 'Ingen oppgaver funnet',
+ 'Keyboard shortcut: "%s"' => 'Hurtigtaster: "%s"',
+ 'List' => 'Liste',
+ 'Filter' => 'Filter',
+ 'Advanced search' => 'Avansert søk',
+ 'Example of query: ' => 'Eksempel på spørring',
+ 'Search by project: ' => 'Søk etter prosjekt',
+ 'Search by column: ' => 'Søk etter kolonne',
+ 'Search by assignee: ' => 'Søk etter tildelt',
+ 'Search by color: ' => 'Søk etter farge',
+ 'Search by category: ' => 'Søk etter kategori',
+ 'Search by description: ' => 'Søk etter beskrivelse',
+ 'Search by due date: ' => 'Søk etter frist',
// 'Lead and Cycle time for "%s"' => '',
// 'Average time spent into each column for "%s"' => '',
// 'Average time spent into each column' => '',
@@ -996,75 +1026,75 @@ return array(
// 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => '',
// 'By @%s on Gitlab' => '',
// 'Gitlab issue comment created' => '',
- // 'New remote user' => '',
- // 'New local user' => '',
- // 'Default task color' => '',
- // 'Hide sidebar' => '',
- // 'Expand sidebar' => '',
+ 'New remote user' => 'Ny eksternbruker',
+ 'New local user' => 'Ny internbruker',
+ 'Default task color' => 'Standard oppgavefarge',
+ 'Hide sidebar' => 'Skjul sidemeny',
+ 'Expand sidebar' => 'Vis sidemeny',
// 'This feature does not work with all browsers.' => '',
// 'There is no destination project available.' => '',
// 'Trigger automatically subtask time tracking' => '',
// 'Include closed tasks in the cumulative flow diagram' => '',
- // 'Current swimlane: %s' => '',
- // 'Current column: %s' => '',
- // 'Current category: %s' => '',
- // 'no category' => '',
- // 'Current assignee: %s' => '',
- // 'not assigned' => '',
- // 'Author:' => '',
- // 'contributors' => '',
- // 'License:' => '',
- // 'License' => '',
- // 'Project Administrator' => '',
- // 'Enter the text below' => '',
- // 'Gantt chart for %s' => '',
+ 'Current swimlane: %s' => 'Nåværende svømmebane: %s',
+ 'Current column: %s' => 'Nåværende kolonne: %s',
+ 'Current category: %s' => ': %s',
+ 'no category' => 'ingen kategori',
+ 'Current assignee: %s' => 'Tildelt til %s',
+ 'not assigned' => 'ikke tildelt',
+ 'Author:' => 'Opprettet av',
+ 'contributors' => 'bidragsytere',
+ 'License:' => 'Lisens:',
+ 'License' => 'Lisens',
+ 'Project Administrator' => 'Prosjektadministrator',
+ 'Enter the text below' => 'Legg inn teksten nedenfor',
+ 'Gantt chart for %s' => 'Gantt skjema for %s',
// 'Sort by position' => '',
- // 'Sort by date' => '',
- // 'Add task' => '',
- // 'Start date:' => '',
- // 'Due date:' => '',
- // 'There is no start date or due date for this task.' => '',
+ 'Sort by date' => 'Sorter etter dato',
+ 'Add task' => 'Legg til oppgave',
+ 'Start date:' => 'Startdato:',
+ 'Due date:' => 'Frist:',
+ 'There is no start date or due date for this task.' => 'Det er ingen startdato eller frist for denne oppgaven',
// 'Moving or resizing a task will change the start and due date of the task.' => '',
- // 'There is no task in your project.' => '',
- // 'Gantt chart' => '',
- // 'People who are project managers' => '',
- // 'People who are project members' => '',
+ 'There is no task in your project.' => 'Det er ingen oppgaver i dette prosjektet',
+ 'Gantt chart' => 'Gantt skjema',
+ 'People who are project managers' => 'Prosjektledere',
+ 'People who are project members' => 'Prosjektmedlemmer',
// 'NOK - Norwegian Krone' => '',
- // 'Show this column' => '',
- // 'Hide this column' => '',
- // 'open file' => '',
- // 'End date' => '',
- // 'Users overview' => '',
- // 'Managers' => '',
- // 'Members' => '',
- // 'Shared project' => '',
- // 'Project managers' => '',
- // 'Project members' => '',
+ 'Show this column' => 'Vis denne kolonnen',
+ 'Hide this column' => 'Skjul denne kolonnen',
+ 'open file' => 'Åpne fil',
+ 'End date' => 'Sluttdato',
+ 'Users overview' => 'Brukeroversikt',
+ 'Managers' => 'Ledere',
+ 'Members' => 'Medlemmer',
+ 'Shared project' => 'Delt prosjekt',
+ 'Project managers' => 'Prosjektledere',
+ 'Project members' => 'Prosjektmedlemmer',
// 'Gantt chart for all projects' => '',
- // 'Projects list' => '',
- // 'Gantt chart for this project' => '',
- // 'Project board' => '',
- // 'End date:' => '',
+ 'Projects list' => 'Prosjektliste',
+ 'Gantt chart for this project' => 'Gantt skjema for dette prosjektet',
+ 'Project board' => 'Prosjektsiden',
+ 'End date:' => 'Sluttdato:',
// 'There is no start date or end date for this project.' => '',
- // 'Projects Gantt chart' => '',
- // 'Start date: %s' => '',
- // 'End date: %s' => '',
- // 'Link type' => '',
+ 'Projects Gantt chart' => 'Gantt skjema for prosjekter',
+ 'Start date: %s' => 'Startdato: %s',
+ 'End date: %s' => 'Sluttdato: %s',
+ 'Link type' => 'Relasjonstype',
// 'Change task color when using a specific task link' => '',
// 'Task link creation or modification' => '',
// 'Login with my Gitlab Account' => '',
- // 'Milestone' => '',
+ 'Milestone' => 'Milepæl',
// 'Gitlab Authentication' => '',
// 'Help on Gitlab authentication' => '',
// 'Gitlab Id' => '',
// 'Gitlab Account' => '',
// 'Link my Gitlab Account' => '',
// 'Unlink my Gitlab Account' => '',
- // 'Documentation: %s' => '',
- // 'Switch to the Gantt chart view' => '',
- // 'Reset the search/filter box' => '',
- // 'Documentation' => '',
- // 'Table of contents' => '',
- // 'Gantt' => '',
- // 'Help with project permissions' => '',
+ 'Documentation: %s' => 'Dokumentasjon: %s',
+ 'Switch to the Gantt chart view' => 'Gantt skjema visning',
+ 'Reset the search/filter box' => 'Nullstill søk/filter',
+ 'Documentation' => 'Dokumentasjon',
+ 'Table of contents' => 'Innholdsfortegnelse',
+ 'Gantt' => 'Gantt',
+ 'Help with project permissions' => 'Hjelp med prosjekttilganger',
);
diff --git a/app/Locale/nl_NL/translations.php b/app/Locale/nl_NL/translations.php
index 23d64163..8be0c61d 100644
--- a/app/Locale/nl_NL/translations.php
+++ b/app/Locale/nl_NL/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Remote',
'Enabled' => 'Actief',
'Disabled' => 'Inactief',
- 'Google account linked' => 'Gelinkt Google Account',
- 'Github account linked' => 'Gelinkt Github Account',
'Username:' => 'Gebruikersnaam :',
'Name:' => 'Naam :',
'Email:' => 'Email :',
@@ -667,17 +665,7 @@ return array(
// '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' => '',
@@ -703,34 +691,18 @@ return array(
// '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' => '',
@@ -746,7 +718,6 @@ return array(
// '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.' => '',
@@ -878,9 +849,6 @@ return array(
// '%s moved the task #%d to the first swimlane' => '',
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
- // 'Budget overview' => '',
- // 'Type' => '',
- // 'There is not enough data to show something.' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Locale/pl_PL/translations.php b/app/Locale/pl_PL/translations.php
index 9947cf31..d9cfcdbc 100644
--- a/app/Locale/pl_PL/translations.php
+++ b/app/Locale/pl_PL/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Zdalne',
'Enabled' => 'Odblokowane',
'Disabled' => 'Zablokowane',
- 'Google account linked' => 'Połączone konto Google',
- 'Github account linked' => 'Połączone konto Github',
'Username:' => 'Nazwa Użytkownika:',
'Name:' => 'ImiÄ™ i Nazwisko',
'Email:' => 'Email: ',
@@ -667,17 +665,7 @@ return array(
'Horizontal scrolling' => 'Przewijanie poziome',
'Compact/wide view' => 'Pełny/Kompaktowy widok',
'No results match:' => 'Brak wyników:',
- 'Remove hourly rate' => 'Usuń stawkę godzinową',
- 'Do you really want to remove this hourly rate?' => 'Czy na pewno chcesz usunąć stawkę godzinową?',
- 'Hourly rates' => 'Stawki godzinowe',
- 'Hourly rate' => 'Stawka godzinowa',
'Currency' => 'Waluta',
- 'Effective date' => 'Data efektywna',
- 'Add new rate' => 'Dodaj nowÄ… stawkÄ™',
- 'Rate removed successfully.' => 'Stawka usunięta.',
- 'Unable to remove this rate.' => 'Nie można usunąć tej stawki.',
- 'Unable to save the hourly rate.' => 'Nie można zapisać tej stawki godzinowej.',
- 'Hourly rate created successfully.' => 'Stawka godzinowa utworzona pomyślnie.',
'Start time' => 'Rozpoczęto',
'End time' => 'Zakończono',
'Comment' => 'Komentarz',
@@ -703,34 +691,18 @@ return array(
'Files' => 'Pliki',
'Images' => 'Obrazy',
'Private project' => 'Projekt prywatny',
- 'Amount' => 'Ilość',
'AUD - Australian Dollar' => 'AUD - Dolar australijski',
- 'Budget' => 'Budżet',
- 'Budget line' => 'Linia budżetowa',
- 'Budget line removed successfully.' => 'Linia budżetowa usunięta.',
- 'Budget lines' => 'Linie budżetowe',
'CAD - Canadian Dollar' => 'CAD - Dolar kanadyjski',
'CHF - Swiss Francs' => 'CHF - Frank szwajcarski',
- 'Cost' => 'Koszt',
- 'Cost breakdown' => 'Analiza kosztów',
'Custom Stylesheet' => 'Niestandardowy arkusz stylów',
'download' => 'pobierz',
- 'Do you really want to remove this budget line?' => 'Czy chcesz usunąć tą linię budżetową?',
// 'EUR - Euro' => '',
- 'Expenses' => 'Wydatki',
'GBP - British Pound' => 'GBP - Funt brytyjski',
'INR - Indian Rupee' => 'INR - Rupia indyjska',
'JPY - Japanese Yen' => 'JPY - Jen japoński',
- 'New budget line' => 'Nowa linia budżetowa',
'NZD - New Zealand Dollar' => 'NZD - Dolar nowozelandzki',
- 'Remove a budget line' => 'Usuń linię budżetową',
- 'Remove budget line' => 'Usuń linię budżetową',
'RSD - Serbian dinar' => 'RSD - Dinar serbski',
- // 'The budget line have been created successfully.' => '',
- 'Unable to create the budget line.' => 'Nie można utworzyć linii budżetowej',
- 'Unable to remove this budget line.' => 'Nie można usunąć tej linii budżetowej',
'USD - US Dollar' => 'USD - Dolar amerykański',
- 'Remaining' => 'Pozostało',
'Destination column' => 'Kolumna docelowa',
'Move the task to another column when assigned to a user' => 'PrzenieÅ› zadanie do innej kolumny gdy zostanie przypisane do osoby',
'Move the task to another column when assignee is cleared' => 'Przenieś zadanie do innej kolumny gdy osoba odpowiedzialna zostanie usunięta',
@@ -746,7 +718,6 @@ return array(
'Rate' => 'Kurs',
'Change reference currency' => 'Zmień walutę referencyjną',
'Add a new currency rate' => 'Dodaj nowy kurs waluty',
- 'Currency rates are used to calculate project budget.' => 'Kursy walut są używane do obliczeń budżetu projektu.',
'Reference currency' => 'Waluta referencyjna',
'The currency rate have been added successfully.' => 'Dodano kurs waluty',
'Unable to add this currency rate.' => 'Nie można dodać kursu waluty',
@@ -878,9 +849,6 @@ return array(
// '%s moved the task #%d to the first swimlane' => '',
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
- // 'Budget overview' => '',
- // 'Type' => '',
- // 'There is not enough data to show something.' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Locale/pt_BR/translations.php b/app/Locale/pt_BR/translations.php
index ed0c9b15..5f849457 100644
--- a/app/Locale/pt_BR/translations.php
+++ b/app/Locale/pt_BR/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Remoto',
'Enabled' => 'Habilitado',
'Disabled' => 'Desabilitado',
- 'Google account linked' => 'Conta do Google associada',
- 'Github account linked' => 'Conta do Github associada',
'Username:' => 'Usuário:',
'Name:' => 'Nome:',
'Email:' => 'E-mail:',
@@ -667,17 +665,7 @@ return array(
'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',
@@ -703,34 +691,18 @@ return array(
'Files' => 'Arquivos',
'Images' => 'Imagens',
'Private project' => 'Projeto privado',
- 'Amount' => 'Quantia',
'AUD - Australian Dollar' => 'AUD - Dólar australiano',
- 'Budget' => 'Orçamento',
- 'Budget line' => 'Rubrica orçamental',
- 'Budget line removed successfully.' => 'Rubrica orçamental removida com sucesso',
- 'Budget lines' => 'Rubricas orçamentais',
'CAD - Canadian Dollar' => 'CAD - Dólar canadense',
'CHF - Swiss Francs' => 'CHF - Francos Suíços',
- 'Cost' => 'Custo',
- 'Cost breakdown' => 'Repartição dos custos',
'Custom Stylesheet' => 'Folha de estilo personalizado',
'download' => 'baixar',
- 'Do you really want to remove this budget line?' => 'Você deseja realmente remover esta rubrica orçamental?',
'EUR - Euro' => 'EUR - Euro',
- 'Expenses' => 'Despesas',
'GBP - British Pound' => 'GBP - Libra Esterlina',
'INR - Indian Rupee' => 'INR - Rúpia indiana',
'JPY - Japanese Yen' => 'JPY - Iene japonês',
- 'New budget line' => 'Nova rubrica orçamental',
'NZD - New Zealand Dollar' => 'NZD - Dólar Neozelandês',
- 'Remove a budget line' => 'Remover uma rubrica orçamental',
- 'Remove budget line' => 'Remover uma rubrica orçamental',
'RSD - Serbian dinar' => 'RSD - Dinar sérvio',
- 'The budget line have been created successfully.' => 'A rubrica orçamental foi criada com sucesso.',
- 'Unable to create the budget line.' => 'Impossível de adicionar esta rubrica orçamental.',
- 'Unable to remove this budget line.' => 'Impossível de remover esta rubrica orçamental.',
'USD - US Dollar' => 'USD - Dólar norte-americano',
- 'Remaining' => 'Restante',
'Destination column' => 'Coluna de destino',
'Move the task to another column when assigned to a user' => 'Mover a tarefa para uma outra coluna quando esta está atribuída a um usuário',
'Move the task to another column when assignee is cleared' => 'Mover a tarefa para uma outra coluna quando esta não está atribuída',
@@ -746,7 +718,6 @@ return array(
'Rate' => 'Taxa',
'Change reference currency' => 'Mudar a moeda de referência',
'Add a new currency rate' => 'Adicionar uma nova taxa para uma moeda',
- 'Currency rates are used to calculate project budget.' => 'As taxas de câmbio são utilizadas para calcular o orçamento do projeto.',
'Reference currency' => 'Moeda de Referência',
'The currency rate have been added successfully.' => 'A taxa de câmbio foi adicionada com sucesso.',
'Unable to add this currency rate.' => 'Impossível de adicionar essa taxa de câmbio.',
@@ -878,9 +849,6 @@ return array(
'%s moved the task #%d to the first swimlane' => '%s moveu a tarefa n° %d no primeiro swimlane',
'%s moved the task #%d to the swimlane "%s"' => '%s moveu a tarefa n° %d no swimlane "%s"',
'Swimlane' => 'Swimlane',
- 'Budget overview' => 'Visão geral do orçamento',
- 'Type' => 'Tipo',
- 'There is not enough data to show something.' => 'Não há dados suficientes para mostrar alguma coisa.',
'Gravatar' => 'Gravatar',
'Hipchat' => 'Hipchat',
'Slack' => 'Slack',
diff --git a/app/Locale/pt_PT/translations.php b/app/Locale/pt_PT/translations.php
index ae73af1e..9e20b112 100644
--- a/app/Locale/pt_PT/translations.php
+++ b/app/Locale/pt_PT/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Remoto',
'Enabled' => 'Activado',
'Disabled' => 'Desactivado',
- 'Google account linked' => 'Conta do Google associada',
- 'Github account linked' => 'Conta do Github associada',
'Username:' => 'Utilizador:',
'Name:' => 'Nome:',
'Email:' => 'E-mail:',
@@ -667,17 +665,7 @@ return array(
'Horizontal scrolling' => 'Deslocamento 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?' => 'Tem a certeza que quer remover esta taxa horária?',
- 'Hourly rates' => 'Taxas horárias',
- 'Hourly rate' => 'Taxa horária',
'Currency' => 'Moeda',
- 'Effective date' => 'Data efectiva',
- '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',
@@ -703,34 +691,18 @@ return array(
'Files' => 'Arquivos',
'Images' => 'Imagens',
'Private project' => 'Projecto privado',
- 'Amount' => 'Quantia',
'AUD - Australian Dollar' => 'AUD - Dólar australiano',
- 'Budget' => 'Orçamento',
- 'Budget line' => 'Rubrica orçamental',
- 'Budget line removed successfully.' => 'Rubrica orçamental removida com sucesso',
- 'Budget lines' => 'Rubricas orçamentais',
'CAD - Canadian Dollar' => 'CAD - Dólar canadense',
'CHF - Swiss Francs' => 'CHF - Francos Suíços',
- 'Cost' => 'Custo',
- 'Cost breakdown' => 'Repartição dos custos',
'Custom Stylesheet' => 'Folha de estilos personalizada',
'download' => 'transferir',
- 'Do you really want to remove this budget line?' => 'Tem a certeza que quer remover esta rubrica orçamental?',
'EUR - Euro' => 'EUR - Euro',
- 'Expenses' => 'Despesas',
'GBP - British Pound' => 'GBP - Libra Esterlina',
'INR - Indian Rupee' => 'INR - Rúpia indiana',
'JPY - Japanese Yen' => 'JPY - Iene japonês',
- 'New budget line' => 'Nova rubrica orçamental',
'NZD - New Zealand Dollar' => 'NZD - Dólar Neozelandês',
- 'Remove a budget line' => 'Remover uma rubrica orçamental',
- 'Remove budget line' => 'Remover uma rubrica orçamental',
'RSD - Serbian dinar' => 'RSD - Dinar sérvio',
- 'The budget line have been created successfully.' => 'A rubrica orçamental foi criada com sucesso.',
- 'Unable to create the budget line.' => 'Impossível adicionar esta rubrica orçamental.',
- 'Unable to remove this budget line.' => 'Impossível remover esta rubrica orçamental.',
'USD - US Dollar' => 'USD - Dólar norte-americano',
- 'Remaining' => 'Restante',
'Destination column' => 'Coluna de destino',
'Move the task to another column when assigned to a user' => 'Mover a tarefa para uma outra coluna quando esta está atribuída a um utilizador',
'Move the task to another column when assignee is cleared' => 'Mover a tarefa para uma outra coluna quando esta não está atribuída',
@@ -746,7 +718,6 @@ return array(
'Rate' => 'Taxa',
'Change reference currency' => 'Mudar a moeda de referência',
'Add a new currency rate' => 'Adicionar uma nova taxa para uma moeda',
- 'Currency rates are used to calculate project budget.' => 'As taxas de câmbio são utilizadas para calcular o orçamento do projecto.',
'Reference currency' => 'Moeda de Referência',
'The currency rate have been added successfully.' => 'A taxa de câmbio foi adicionada com sucesso.',
'Unable to add this currency rate.' => 'Impossível adicionar essa taxa de câmbio.',
@@ -878,9 +849,6 @@ return array(
'%s moved the task #%d to the first swimlane' => '%s moveu a tarefa n° %d no primeiro swimlane',
'%s moved the task #%d to the swimlane "%s"' => '%s moveu a tarefa n° %d no swimlane "%s"',
'Swimlane' => 'Swimlane',
- 'Budget overview' => 'Visão geral do orçamento',
- 'Type' => 'Tipo',
- 'There is not enough data to show something.' => 'Não há dados suficientes para mostrar alguma coisa.',
'Gravatar' => 'Gravatar',
'Hipchat' => 'Hipchat',
'Slack' => 'Slack',
diff --git a/app/Locale/ru_RU/translations.php b/app/Locale/ru_RU/translations.php
index feb1b684..1619f308 100644
--- a/app/Locale/ru_RU/translations.php
+++ b/app/Locale/ru_RU/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Удаленный',
'Enabled' => 'Включен',
'Disabled' => 'Выключены',
- 'Google account linked' => 'Профиль Google ÑвÑзан',
- 'Github account linked' => 'Профиль Github ÑвÑзан',
'Username:' => 'Ð˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ:',
'Name:' => 'ИмÑ:',
'Email:' => 'E-mail:',
@@ -667,17 +665,7 @@ return array(
'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' => 'Комментарий',
@@ -703,34 +691,18 @@ return array(
'Files' => 'Файлы',
'Images' => 'ИзображениÑ',
'Private project' => 'Приватный проект',
- 'Amount' => 'КоличеÑтво',
'AUD - Australian Dollar' => 'AUD - ÐвÑтралийÑкий доллар',
- 'Budget' => 'Бюджет',
- 'Budget line' => 'Ð¡Ñ‚Ð°Ñ‚ÑŒÑ Ð±ÑŽÐ´Ð¶ÐµÑ‚Ð°',
- 'Budget line removed successfully.' => 'Ð‘ÑŽÐ´Ð¶ÐµÑ‚Ð½Ð°Ñ ÑÑ‚Ð°Ñ‚ÑŒÑ ÑƒÑпешно удалена.',
- 'Budget lines' => 'Статьи бюджета',
'CAD - Canadian Dollar' => 'CAD - КанадÑкий доллар',
'CHF - Swiss Francs' => 'CHF - ШвейцарÑкий Франк',
- 'Cost' => 'СтоимоÑÑ‚ÑŒ',
- 'Cost breakdown' => 'Ð”ÐµÑ‚Ð°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð·Ð°Ñ‚Ñ€Ð°Ñ‚',
'Custom Stylesheet' => 'ПользовательÑкий Ñтиль',
'download' => 'загрузить',
- 'Do you really want to remove this budget line?' => 'Ð’Ñ‹ дейÑтвительно хотите удалить Ñту Ñтатью бюджета?',
'EUR - Euro' => 'EUR - Евро',
- 'Expenses' => 'РаÑходы',
'GBP - British Pound' => 'GBP - БританÑкий фунт',
'INR - Indian Rupee' => 'INR - ИндийÑкий рупий',
'JPY - Japanese Yen' => 'JPY - ЯпонÑкай йена',
- 'New budget line' => 'ÐÐ¾Ð²Ð°Ñ ÑÑ‚Ð°Ñ‚ÑŒÑ Ð±ÑŽÐ´Ð¶ÐµÑ‚Ð°',
'NZD - New Zealand Dollar' => 'NZD - ÐовозеландÑкий доллар',
- 'Remove a budget line' => 'Удалить Ñтроку в бюджете',
- 'Remove budget line' => 'Удалить Ñтатью бюджета',
'RSD - Serbian dinar' => 'RSD - СербÑкий динар',
- 'The budget line have been created successfully.' => 'Ð¡Ñ‚Ð°Ñ‚ÑŒÑ Ð±ÑŽÐ´Ð¶ÐµÑ‚Ð° уÑпешно Ñоздана.',
- 'Unable to create the budget line.' => 'Ðе удаетÑÑ Ñоздать Ñту Ñтатью бюджета.',
- 'Unable to remove this budget line.' => 'Ðе удаетÑÑ ÑƒÐ´Ð°Ð»Ð¸Ñ‚ÑŒ Ñту Ñтатью бюджета.',
'USD - US Dollar' => 'USD - доллар СШÐ',
- 'Remaining' => 'Прочее',
'Destination column' => 'Колонка назначениÑ',
'Move the task to another column when assigned to a user' => 'ПеремеÑтить задачу в другую колонку, когда она назначена пользователю',
'Move the task to another column when assignee is cleared' => 'ПеремеÑтить задачу в другую колонку, когда назначение ÑнÑто ',
@@ -746,7 +718,6 @@ return array(
'Rate' => 'КурÑ',
'Change reference currency' => 'Изменить Ñправочник валют',
'Add a new currency rate' => '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.' => 'Ðевозможно добавить Ñтот ÐºÑƒÑ€Ñ Ð²Ð°Ð»ÑŽÑ‚Ñ‹.',
@@ -805,7 +776,7 @@ return array(
'The identifier must be unique' => 'Идентификатор должен быть уникальным',
'This linked task id doesn\'t exists' => 'Этот ID звÑзанной задачи не ÑущеÑтвует',
'This value must be alphanumeric' => 'Это значение должно быть буквенно-цифровым',
- 'Edit recurrence' => 'Завершить повторение',
+ 'Edit recurrence' => 'Редактировать циклы задачи',
'Generate recurrent task' => 'Создать повторÑющуюÑÑ Ð·Ð°Ð´Ð°Ñ‡Ñƒ',
'Trigger to generate recurrent task' => 'Триггер Ð´Ð»Ñ Ð³ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ð¸ периодичеÑкой задачи',
'Factor to calculate new due date' => 'КоÑффициент Ð´Ð»Ñ Ñ€Ð°ÑÑчета новой даты',
@@ -878,9 +849,6 @@ return array(
// '%s moved the task #%d to the first swimlane' => '',
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
- // 'Budget overview' => '',
- // 'Type' => '',
- // 'There is not enough data to show something.' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Locale/sr_Latn_RS/translations.php b/app/Locale/sr_Latn_RS/translations.php
index 0bc5c248..a05d67d3 100644
--- a/app/Locale/sr_Latn_RS/translations.php
+++ b/app/Locale/sr_Latn_RS/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Udaljno',
'Enabled' => 'Omogući',
'Disabled' => 'Onemogući',
- 'Google account linked' => 'Połączone konto Google',
- 'Github account linked' => 'Połączone konto Github',
'Username:' => 'KorisniÄko ime:',
'Name:' => 'Ime i Prezime',
'Email:' => 'Email: ',
@@ -667,17 +665,7 @@ return array(
// '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' => '',
@@ -703,34 +691,18 @@ return array(
// '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' => '',
@@ -746,7 +718,6 @@ return array(
// '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.' => '',
@@ -878,9 +849,6 @@ return array(
// '%s moved the task #%d to the first swimlane' => '',
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
- // 'Budget overview' => '',
- // 'Type' => '',
- // 'There is not enough data to show something.' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Locale/sv_SE/translations.php b/app/Locale/sv_SE/translations.php
index 9c769724..e8bdb9ce 100644
--- a/app/Locale/sv_SE/translations.php
+++ b/app/Locale/sv_SE/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Fjärr',
'Enabled' => 'Aktiverad',
'Disabled' => 'Inaktiverad',
- 'Google account linked' => 'Googlekonto länkat',
- 'Github account linked' => 'Githubkonto länkat',
'Username:' => 'Användarnam:',
'Name:' => 'Namn:',
'Email:' => 'E-post:',
@@ -667,17 +665,7 @@ return array(
'Horizontal scrolling' => 'Horisontell scroll',
'Compact/wide view' => 'Kompakt/bred vy',
'No results match:' => 'Inga matchande resultat',
- 'Remove hourly rate' => 'Ta bort timtaxa',
- 'Do you really want to remove this hourly rate?' => 'Vill du verkligen ta bort denna timtaxa?',
- 'Hourly rates' => 'Timtaxor',
- 'Hourly rate' => 'Timtaxa',
'Currency' => 'Valuta',
- 'Effective date' => 'Giltighetsdatum',
- 'Add new rate' => 'Lägg till ny taxa',
- 'Rate removed successfully.' => 'Taxan togs bort.',
- 'Unable to remove this rate.' => 'Kunde inte ta bort taxan.',
- 'Unable to save the hourly rate.' => 'Kunde inte spara timtaxan.',
- 'Hourly rate created successfully.' => 'Timtaxan skapades.',
'Start time' => 'Starttid',
'End time' => 'Sluttid',
'Comment' => 'Kommentar',
@@ -703,34 +691,18 @@ return array(
'Files' => 'Filer',
'Images' => 'Bilder',
'Private project' => 'Privat projekt',
- 'Amount' => 'Belopp',
'AUD - Australian Dollar' => 'AUD - Australiska dollar',
- 'Budget' => 'Budget',
- 'Budget line' => 'Budgetlinje',
- 'Budget line removed successfully.' => 'Budgetlinjen togs bort.',
- 'Budget lines' => 'Budgetlinjer',
'CAD - Canadian Dollar' => 'CAD - Kanadensiska dollar',
'CHF - Swiss Francs' => 'CHF - Schweiziska Franc',
- 'Cost' => 'Kostnad',
- 'Cost breakdown' => 'Kostnadssammanställning',
'Custom Stylesheet' => 'Anpassad stilmall',
'download' => 'ladda ned',
- 'Do you really want to remove this budget line?' => 'Vill du verkligen ta bort budgetlinjen?',
'EUR - Euro' => 'EUR - Euro',
- 'Expenses' => 'Utgifter',
'GBP - British Pound' => 'GBP - Brittiska Pund',
'INR - Indian Rupee' => 'INR - Indiska Rupier',
'JPY - Japanese Yen' => 'JPY - Japanska Yen',
- 'New budget line' => 'Ny budgetlinje',
'NZD - New Zealand Dollar' => 'NZD - Nya Zeeländska Dollar',
- 'Remove a budget line' => 'Ta bort en budgetlinje',
- 'Remove budget line' => 'Ta bort budgetlinje',
'RSD - Serbian dinar' => 'RSD - Serbiska Dinarer',
- 'The budget line have been created successfully.' => 'Budgetlinjen har skapats.',
- 'Unable to create the budget line.' => 'Kunde inte skapa budgetlinjen.',
- 'Unable to remove this budget line.' => 'Kunde inte ta bort budgetlinjen.',
'USD - US Dollar' => 'USD - Amerikanska Dollar',
- 'Remaining' => 'Återstående',
'Destination column' => 'MÃ¥lkolumn',
'Move the task to another column when assigned to a user' => 'Flytta uppgiften till en annan kolumn när den tilldelats en användare',
'Move the task to another column when assignee is cleared' => 'Flytta uppgiften till en annan kolumn när tilldelningen tas bort.',
@@ -746,7 +718,6 @@ return array(
'Rate' => 'Kurs',
'Change reference currency' => 'Ändra referenskurs',
'Add a new currency rate' => 'Lägg till ny valutakurs',
- 'Currency rates are used to calculate project budget.' => 'Valutakurser används för att beräkna projektbudget.',
'Reference currency' => 'Referensvaluta',
'The currency rate have been added successfully.' => 'Valutakursen har lagts till.',
'Unable to add this currency rate.' => 'Kunde inte lägga till valutakursen.',
@@ -878,9 +849,6 @@ return array(
'%s moved the task #%d to the first swimlane' => '%s flyttade uppgiften #%d till första swimlane',
'%s moved the task #%d to the swimlane "%s"' => '%s flyttade uppgiften #%d till swimlane "%s"',
'Swimlane' => 'Swimlane',
- 'Budget overview' => 'Budgetöversikt',
- 'Type' => 'Typ',
- 'There is not enough data to show something.' => 'Det finns inte tillräckligt mycket data för att visa något.',
'Gravatar' => 'Gravatar',
'Hipchat' => 'Hipchat',
'Slack' => 'Slack',
diff --git a/app/Locale/th_TH/translations.php b/app/Locale/th_TH/translations.php
index a5ed2474..d94107ad 100644
--- a/app/Locale/th_TH/translations.php
+++ b/app/Locale/th_TH/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'รีโมท',
'Enabled' => 'เปิดà¸à¸²à¸£à¹ƒà¸Šà¹‰',
'Disabled' => 'ปิดà¸à¸²à¸£à¹ƒà¸Šà¹‰',
- 'Google account linked' => 'เชื่อมà¸à¸±à¸šà¸à¸¹à¹€à¸à¸´à¸¥à¹à¸­à¸„เคาท์',
- 'Github account linked' => 'เชื่อมà¸à¸±à¸šà¸à¸´à¸—ฮับà¹à¸­à¸„เคาท์',
'Username:' => 'ชื่อผู้ใช้:',
'Name:' => 'ชื่อ:',
'Email:' => 'อีเมล:',
@@ -667,17 +665,7 @@ return array(
'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' => 'ความคิดเห็น',
@@ -703,34 +691,18 @@ return array(
'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' => 'ย้ายงานไปคอลัมน์อื่นเมื่อไม่à¸à¸³à¸«à¸™à¸”บุคคลรับผิดชอบ',
@@ -746,7 +718,6 @@ return array(
'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.' => '',
@@ -878,9 +849,6 @@ return array(
// '%s moved the task #%d to the first swimlane' => '',
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
- // 'Budget overview' => '',
- // 'Type' => '',
- // 'There is not enough data to show something.' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Locale/tr_TR/translations.php b/app/Locale/tr_TR/translations.php
index 9eb5c41e..87cccac7 100644
--- a/app/Locale/tr_TR/translations.php
+++ b/app/Locale/tr_TR/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Uzak',
'Enabled' => 'EtkinleÅŸtirildi',
'Disabled' => 'Devre dışı bırakıldı',
- 'Google account linked' => 'Google hesabıyla bağlı',
- 'Github account linked' => 'Github hesabıyla bağlı',
'Username:' => 'Kullanıcı adı',
'Name:' => 'Ad',
'Email:' => 'E-Posta',
@@ -667,17 +665,7 @@ return array(
'Horizontal scrolling' => 'Geniş görünüm',
'Compact/wide view' => 'Ekrana sığdır / Geniş görünüm',
// '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' => '',
@@ -703,34 +691,18 @@ return array(
// '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' => '',
@@ -746,7 +718,6 @@ return array(
// '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.' => '',
@@ -878,9 +849,6 @@ return array(
// '%s moved the task #%d to the first swimlane' => '',
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
- // 'Budget overview' => '',
- // 'Type' => '',
- // 'There is not enough data to show something.' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Locale/zh_CN/translations.php b/app/Locale/zh_CN/translations.php
index 910bc0b4..102252e5 100644
--- a/app/Locale/zh_CN/translations.php
+++ b/app/Locale/zh_CN/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => '远程',
'Enabled' => 'å¯ç”¨',
'Disabled' => 'åœç”¨',
- 'Google account linked' => 'å·²ç»é“¾æŽ¥è°·æ­Œè´¦å·',
- 'Github account linked' => 'å·²ç»é“¾æŽ¥Githubè´¦å·',
'Username:' => '用户å:',
'Name:' => '姓å:',
'Email:' => '电å­é‚®ä»¶ï¼š',
@@ -667,17 +665,7 @@ return array(
'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' => '结æŸæ—¶1é—´',
'Comment' => '注释',
@@ -703,34 +691,18 @@ return array(
'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' => '移除负责人时移动到其它æ ç›®',
@@ -746,7 +718,6 @@ return array(
'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.' => '无法添加此汇率',
@@ -878,9 +849,6 @@ return array(
// '%s moved the task #%d to the first swimlane' => '',
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
- 'Budget overview' => '预算概览',
- 'Type' => '类型',
- // 'There is not enough data to show something.' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Model/Acl.php b/app/Model/Acl.php
index 8c28cb1a..9a227cf5 100644
--- a/app/Model/Acl.php
+++ b/app/Model/Acl.php
@@ -64,7 +64,6 @@ class Acl extends Base
'export' => '*',
'project' => array('edit', 'update', 'share', 'integration', 'users', 'alloweverybody', 'allow', 'setowner', 'revoke', 'duplicate', 'disable', 'enable'),
'swimlane' => '*',
- 'budget' => '*',
'gantt' => array('project', 'savetaskdate', 'task', 'savetask'),
);
@@ -95,6 +94,18 @@ class Acl extends Base
);
/**
+ * Extend ACL rules
+ *
+ * @access public
+ * @param string $acl_name
+ * @param aray $rules
+ */
+ public function extend($acl_name, array $rules)
+ {
+ $this->$acl_name = array_merge($this->$acl_name, $rules);
+ }
+
+ /**
* Return true if the specified controller/action match the given acl
*
* @access public
diff --git a/app/Model/Budget.php b/app/Model/Budget.php
deleted file mode 100644
index 76c42ca9..00000000
--- a/app/Model/Budget.php
+++ /dev/null
@@ -1,214 +0,0 @@
-<?php
-
-namespace Model;
-
-use DateInterval;
-use DateTime;
-use SimpleValidator\Validator;
-use SimpleValidator\Validators;
-
-/**
- * Budget
- *
- * @package model
- * @author Frederic Guillot
- */
-class Budget extends Base
-{
- /**
- * SQL table name
- *
- * @var string
- */
- const TABLE = 'budget_lines';
-
- /**
- * Get all budget lines for a project
- *
- * @access public
- * @param integer $project_id
- * @return array
- */
- public function getAll($project_id)
- {
- return $this->db->table(self::TABLE)->eq('project_id', $project_id)->desc('date')->findAll();
- }
-
- /**
- * Get the current total of the budget
- *
- * @access public
- * @param integer $project_id
- * @return float
- */
- public function getTotal($project_id)
- {
- $result = $this->db->table(self::TABLE)->columns('SUM(amount) as total')->eq('project_id', $project_id)->findOne();
- return isset($result['total']) ? (float) $result['total'] : 0;
- }
-
- /**
- * Get breakdown by tasks/subtasks/users
- *
- * @access public
- * @param integer $project_id
- * @return \PicoDb\Table
- */
- public function getSubtaskBreakdown($project_id)
- {
- return $this->db
- ->table(SubtaskTimeTracking::TABLE)
- ->columns(
- SubtaskTimeTracking::TABLE.'.id',
- SubtaskTimeTracking::TABLE.'.user_id',
- SubtaskTimeTracking::TABLE.'.subtask_id',
- SubtaskTimeTracking::TABLE.'.start',
- SubtaskTimeTracking::TABLE.'.time_spent',
- Subtask::TABLE.'.task_id',
- Subtask::TABLE.'.title AS subtask_title',
- Task::TABLE.'.title AS task_title',
- Task::TABLE.'.project_id',
- User::TABLE.'.username',
- User::TABLE.'.name'
- )
- ->join(Subtask::TABLE, 'id', 'subtask_id')
- ->join(Task::TABLE, 'id', 'task_id', Subtask::TABLE)
- ->join(User::TABLE, 'id', 'user_id')
- ->eq(Task::TABLE.'.project_id', $project_id)
- ->callback(array($this, 'applyUserRate'));
- }
-
- /**
- * Gather necessary information to display the budget graph
- *
- * @access public
- * @param integer $project_id
- * @return array
- */
- public function getDailyBudgetBreakdown($project_id)
- {
- $out = array();
- $in = $this->db->hashtable(self::TABLE)->eq('project_id', $project_id)->gt('amount', 0)->asc('date')->getAll('date', 'amount');
- $time_slots = $this->getSubtaskBreakdown($project_id)->findAll();
-
- foreach ($time_slots as $slot) {
- $date = date('Y-m-d', $slot['start']);
-
- if (! isset($out[$date])) {
- $out[$date] = 0;
- }
-
- $out[$date] += $slot['cost'];
- }
-
- $start = key($in) ?: key($out);
- $end = new DateTime;
- $left = 0;
- $serie = array();
-
- for ($today = new DateTime($start); $today <= $end; $today->add(new DateInterval('P1D'))) {
-
- $date = $today->format('Y-m-d');
- $today_in = isset($in[$date]) ? (int) $in[$date] : 0;
- $today_out = isset($out[$date]) ? (int) $out[$date] : 0;
-
- 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;
- }
-
- /**
- * Filter callback to apply the rate according to the effective date
- *
- * @access public
- * @param array $records
- * @return array
- */
- public function applyUserRate(array $records)
- {
- $rates = $this->hourlyRate->getAllByProject($records[0]['project_id']);
-
- foreach ($records as &$record) {
-
- $hourly_price = 0;
-
- 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 = $this->currency->getPrice($rate['currency'], $rate['rate']);
- break;
- }
- }
-
- $record['cost'] = $hourly_price * $record['time_spent'];
- }
-
- return $records;
- }
-
- /**
- * Add a new budget line in the database
- *
- * @access public
- * @param integer $project_id
- * @param float $amount
- * @param string $comment
- * @param string $date
- * @return boolean|integer
- */
- public function create($project_id, $amount, $comment, $date = '')
- {
- $values = array(
- 'project_id' => $project_id,
- 'amount' => $amount,
- 'comment' => $comment,
- 'date' => $date ?: date('Y-m-d'),
- );
-
- return $this->persist(self::TABLE, $values);
- }
-
- /**
- * Remove a specific budget line
- *
- * @access public
- * @param integer $budget_id
- * @return boolean
- */
- public function remove($budget_id)
- {
- return $this->db->table(self::TABLE)->eq('id', $budget_id)->remove();
- }
-
- /**
- * Validate creation
- *
- * @access public
- * @param array $values Form values
- * @return array $valid, $errors [0] = Success or not, [1] = List of errors
- */
- public function validateCreation(array $values)
- {
- $v = new Validator($values, array(
- new Validators\Required('project_id', t('Field required')),
- new Validators\Required('amount', t('Field required')),
- ));
-
- return array(
- $v->execute(),
- $v->getErrors()
- );
- }
-} \ No newline at end of file
diff --git a/app/Model/File.php b/app/Model/File.php
index f884e460..7adab42b 100644
--- a/app/Model/File.php
+++ b/app/Model/File.php
@@ -3,6 +3,8 @@
namespace Model;
use Event\FileEvent;
+use Core\Tool;
+use Core\ObjectStorage\ObjectStorageException;
/**
* File model
@@ -47,14 +49,17 @@ class File extends Base
*/
public function remove($file_id)
{
- $file = $this->getbyId($file_id);
+ try {
- if (! empty($file)) {
- @unlink(FILES_DIR.$file['path']);
- return $this->db->table(self::TABLE)->eq('id', $file_id)->remove();
- }
+ $file = $this->getbyId($file_id);
+ $this->objectStorage->remove($file['path']);
- return false;
+ return $this->db->table(self::TABLE)->eq('id', $file['id'])->remove();
+ }
+ catch (ObjectStorageException $e) {
+ $this->logger->error($e->getMessage());
+ return false;
+ }
}
/**
@@ -66,11 +71,11 @@ class File extends Base
*/
public function removeAll($task_id)
{
- $files = $this->getAll($task_id);
+ $file_ids = $this->db->table(self::TABLE)->eq('task_id', $task_id)->asc('id')->findAllByColumn('id');
$results = array();
- foreach ($files as $file) {
- $results[] = $this->remove($file['id']);
+ foreach ($file_ids as $file_id) {
+ $results[] = $this->remove($file_id);
}
return ! in_array(false, $results, true);
@@ -196,6 +201,30 @@ class File extends Base
}
/**
+ * Return the image mimetype based on the file extension
+ *
+ * @access public
+ * @param $filename
+ * @return string
+ */
+ public function getImageMimeType($filename)
+ {
+ $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
+
+ switch ($extension) {
+ case 'jpeg':
+ case 'jpg':
+ return 'image/jpeg';
+ case 'png':
+ return 'image/png';
+ case 'gif':
+ return 'image/gif';
+ default:
+ return 'image/jpeg';
+ }
+ }
+
+ /**
* Generate the path for a new filename
*
* @access public
@@ -210,6 +239,18 @@ class File extends Base
}
/**
+ * Generate the path for a thumbnails
+ *
+ * @access public
+ * @param string $key Storage key
+ * @return string
+ */
+ public function getThumbnailPath($key)
+ {
+ return 'thumbnails'.DIRECTORY_SEPARATOR.$key;
+ }
+
+ /**
* Handle file upload
*
* @access public
@@ -218,11 +259,13 @@ class File extends Base
* @param string $form_name File form name
* @return bool
*/
- public function upload($project_id, $task_id, $form_name)
+ public function uploadFiles($project_id, $task_id, $form_name)
{
- $results = array();
+ try {
- if (! empty($_FILES[$form_name])) {
+ if (empty($_FILES[$form_name])) {
+ return false;
+ }
foreach ($_FILES[$form_name]['error'] as $key => $error) {
@@ -232,22 +275,27 @@ class File extends Base
$uploaded_filename = $_FILES[$form_name]['tmp_name'][$key];
$destination_filename = $this->generatePath($project_id, $task_id, $original_filename);
- @mkdir(FILES_DIR.dirname($destination_filename), 0755, true);
+ if ($this->isImage($original_filename)) {
+ $this->generateThumbnailFromFile($uploaded_filename, $destination_filename);
+ }
- if (@move_uploaded_file($uploaded_filename, FILES_DIR.$destination_filename)) {
+ $this->objectStorage->moveUploadedFile($uploaded_filename, $destination_filename);
- $results[] = $this->create(
- $task_id,
- $original_filename,
- $destination_filename,
- $_FILES[$form_name]['size'][$key]
- );
- }
+ $this->create(
+ $task_id,
+ $original_filename,
+ $destination_filename,
+ $_FILES[$form_name]['size'][$key]
+ );
}
}
- }
- return ! in_array(false, $results, true);
+ return true;
+ }
+ catch (ObjectStorageException $e) {
+ $this->logger->error($e->getMessage());
+ return false;
+ }
}
/**
@@ -261,129 +309,77 @@ class File extends Base
*/
public function uploadScreenshot($project_id, $task_id, $blob)
{
- $data = base64_decode($blob);
-
- if (empty($data)) {
- return false;
- }
-
$original_filename = e('Screenshot taken %s', dt('%B %e, %Y at %k:%M %p', time())).'.png';
- $destination_filename = $this->generatePath($project_id, $task_id, $original_filename);
-
- @mkdir(FILES_DIR.dirname($destination_filename), 0755, true);
- @file_put_contents(FILES_DIR.$destination_filename, $data);
-
- return $this->create(
- $task_id,
- $original_filename,
- $destination_filename,
- strlen($data)
- );
+ return $this->uploadContent($project_id, $task_id, $original_filename, $blob);
}
/**
* Handle file upload (base64 encoded content)
*
* @access public
- * @param integer $project_id Project id
- * @param integer $task_id Task id
- * @param string $filename Filename
- * @param string $blob Base64 encoded image
+ * @param integer $project_id Project id
+ * @param integer $task_id Task id
+ * @param string $original_filename Filename
+ * @param string $blob Base64 encoded file
* @return bool|integer
*/
- public function uploadContent($project_id, $task_id, $filename, $blob)
+ public function uploadContent($project_id, $task_id, $original_filename, $blob)
{
- $data = base64_decode($blob);
+ try {
- if (empty($data)) {
- return false;
- }
+ $data = base64_decode($blob);
- $destination_filename = $this->generatePath($project_id, $task_id, $filename);
+ if (empty($data)) {
+ return false;
+ }
+
+ $destination_filename = $this->generatePath($project_id, $task_id, $original_filename);
+ $this->objectStorage->put($destination_filename, $data);
- @mkdir(FILES_DIR.dirname($destination_filename), 0755, true);
- @file_put_contents(FILES_DIR.$destination_filename, $data);
+ if ($this->isImage($original_filename)) {
+ $this->generateThumbnailFromData($destination_filename, $data);
+ }
- return $this->create(
- $task_id,
- $filename,
- $destination_filename,
- strlen($data)
- );
+ return $this->create(
+ $task_id,
+ $original_filename,
+ $destination_filename,
+ strlen($data)
+ );
+ }
+ catch (ObjectStorageException $e) {
+ $this->logger->error($e->getMessage());
+ return false;
+ }
}
/**
- * Generate a jpeg thumbnail from an image (output directly the image)
+ * Generate thumbnail from a blob
*
* @access public
- * @param string $filename Source image
- * @param integer $resize_width Desired image width
- * @param integer $resize_height Desired image height
+ * @param string $destination_filename
+ * @param string $data
*/
- public function generateThumbnail($filename, $resize_width, $resize_height)
+ public function generateThumbnailFromData($destination_filename, &$data)
{
- $metadata = getimagesize($filename);
- $src_width = $metadata[0];
- $src_height = $metadata[1];
- $dst_y = 0;
- $dst_x = 0;
-
- if (empty($metadata['mime'])) {
- return;
- }
-
- if ($resize_width == 0 && $resize_height == 0) {
- $resize_width = 100;
- $resize_height = 100;
- }
+ $temp_filename = tempnam(sys_get_temp_dir(), 'datafile');
- if ($resize_width > 0 && $resize_height == 0) {
- $dst_width = $resize_width;
- $dst_height = floor($src_height * ($resize_width / $src_width));
- $dst_image = imagecreatetruecolor($dst_width, $dst_height);
- }
- elseif ($resize_width == 0 && $resize_height > 0) {
- $dst_width = floor($src_width * ($resize_height / $src_height));
- $dst_height = $resize_height;
- $dst_image = imagecreatetruecolor($dst_width, $dst_height);
- }
- else {
-
- $src_ratio = $src_width / $src_height;
- $resize_ratio = $resize_width / $resize_height;
-
- if ($src_ratio <= $resize_ratio) {
- $dst_width = $resize_width;
- $dst_height = floor($src_height * ($resize_width / $src_width));
-
- $dst_y = ($dst_height - $resize_height) / 2 * (-1);
- }
- else {
- $dst_width = floor($src_width * ($resize_height / $src_height));
- $dst_height = $resize_height;
-
- $dst_x = ($dst_width - $resize_width) / 2 * (-1);
- }
-
- $dst_image = imagecreatetruecolor($resize_width, $resize_height);
- }
-
- switch ($metadata['mime']) {
- case 'image/jpeg':
- case 'image/jpg':
- $src_image = imagecreatefromjpeg($filename);
- break;
- case 'image/png':
- $src_image = imagecreatefrompng($filename);
- break;
- case 'image/gif':
- $src_image = imagecreatefromgif($filename);
- break;
- default:
- return;
- }
+ file_put_contents($temp_filename, $data);
+ $this->generateThumbnailFromFile($temp_filename, $destination_filename);
+ unlink($temp_filename);
+ }
- imagecopyresampled($dst_image, $src_image, $dst_x, $dst_y, 0, 0, $dst_width, $dst_height, $src_width, $src_height);
- imagejpeg($dst_image);
+ /**
+ * Generate thumbnail from a blob
+ *
+ * @access public
+ * @param string $uploaded_filename
+ * @param string $destination_filename
+ */
+ public function generateThumbnailFromFile($uploaded_filename, $destination_filename)
+ {
+ $thumbnail_filename = tempnam(sys_get_temp_dir(), 'thumbnail');
+ Tool::generateThumbnail($uploaded_filename, $thumbnail_filename);
+ $this->objectStorage->moveFile($thumbnail_filename, $this->getThumbnailPath($destination_filename));
}
}
diff --git a/app/Model/HourlyRate.php b/app/Model/HourlyRate.php
deleted file mode 100644
index 1550bdae..00000000
--- a/app/Model/HourlyRate.php
+++ /dev/null
@@ -1,121 +0,0 @@
-<?php
-
-namespace Model;
-
-use SimpleValidator\Validator;
-use SimpleValidator\Validators;
-
-/**
- * Hourly Rate
- *
- * @package model
- * @author Frederic Guillot
- */
-class HourlyRate extends Base
-{
- /**
- * SQL table name
- *
- * @var string
- */
- const TABLE = 'hourly_rates';
-
- /**
- * Get all user rates for a given project
- *
- * @access public
- * @param integer $project_id
- * @return array
- */
- public function getAllByProject($project_id)
- {
- $members = $this->projectPermission->getMembers($project_id);
-
- if (empty($members)) {
- return array();
- }
-
- return $this->db->table(self::TABLE)->in('user_id', array_keys($members))->desc('date_effective')->findAll();
- }
-
- /**
- * Get all rates for a given user
- *
- * @access public
- * @param integer $user_id User id
- * @return array
- */
- public function getAllByUser($user_id)
- {
- return $this->db->table(self::TABLE)->eq('user_id', $user_id)->desc('date_effective')->findAll();
- }
-
- /**
- * Get current rate for a given user
- *
- * @access public
- * @param integer $user_id User id
- * @return float
- */
- public function getCurrentRate($user_id)
- {
- return $this->db->table(self::TABLE)->eq('user_id', $user_id)->desc('date_effective')->findOneColumn('rate') ?: 0;
- }
-
- /**
- * Add a new rate in the database
- *
- * @access public
- * @param integer $user_id User id
- * @param float $rate Hourly rate
- * @param string $currency Currency code
- * @param string $date ISO8601 date format
- * @return boolean|integer
- */
- public function create($user_id, $rate, $currency, $date)
- {
- $values = array(
- 'user_id' => $user_id,
- 'rate' => $rate,
- 'currency' => $currency,
- 'date_effective' => $this->dateParser->removeTimeFromTimestamp($this->dateParser->getTimestamp($date)),
- );
-
- return $this->persist(self::TABLE, $values);
- }
-
- /**
- * Remove a specific rate
- *
- * @access public
- * @param integer $rate_id
- * @return boolean
- */
- public function remove($rate_id)
- {
- return $this->db->table(self::TABLE)->eq('id', $rate_id)->remove();
- }
-
- /**
- * Validate creation
- *
- * @access public
- * @param array $values Form values
- * @return array $valid, $errors [0] = Success or not, [1] = List of errors
- */
- public function validateCreation(array $values)
- {
- $v = new Validator($values, array(
- new Validators\Required('user_id', t('Field required')),
- new Validators\Required('rate', t('Field required')),
- new Validators\Numeric('rate', t('This value must be numeric')),
- new Validators\Required('date_effective', t('Field required')),
- new Validators\Required('currency', t('Field required')),
- ));
-
- return array(
- $v->execute(),
- $v->getErrors()
- );
- }
-}
diff --git a/app/Model/Subtask.php b/app/Model/Subtask.php
index d8a44aff..24508c91 100644
--- a/app/Model/Subtask.php
+++ b/app/Model/Subtask.php
@@ -49,6 +49,7 @@ class Subtask extends Base
*/
const EVENT_UPDATE = 'subtask.update';
const EVENT_CREATE = 'subtask.create';
+ const EVENT_DELETE = 'subtask.delete';
/**
* Get available status
@@ -174,6 +175,23 @@ class Subtask extends Base
}
/**
+ * Prepare data before insert
+ *
+ * @access public
+ * @param array $values Form values
+ */
+ public function prepareCreation(array &$values)
+ {
+ $this->prepare($values);
+
+ $values['position'] = $this->getLastPosition($values['task_id']) + 1;
+ $values['status'] = isset($values['status']) ? $values['status'] : self::STATUS_TODO;
+ $values['time_estimated'] = isset($values['time_estimated']) ? $values['time_estimated'] : 0;
+ $values['time_spent'] = isset($values['time_spent']) ? $values['time_spent'] : 0;
+ $values['user_id'] = isset($values['user_id']) ? $values['user_id'] : 0;
+ }
+
+ /**
* Get the position of the last column for a given project
*
* @access public
@@ -198,9 +216,7 @@ class Subtask extends Base
*/
public function create(array $values)
{
- $this->prepare($values);
- $values['position'] = $this->getLastPosition($values['task_id']) + 1;
-
+ $this->prepareCreation($values);
$subtask_id = $this->persist(self::TABLE, $values);
if ($subtask_id) {
@@ -223,14 +239,13 @@ class Subtask extends Base
public function update(array $values)
{
$this->prepare($values);
+ $subtask = $this->getById($values['id']);
$result = $this->db->table(self::TABLE)->eq('id', $values['id'])->save($values);
if ($result) {
-
- $this->container['dispatcher']->dispatch(
- self::EVENT_UPDATE,
- new SubtaskEvent($values)
- );
+ $event = $subtask;
+ $event['changes'] = array_diff_assoc($values, $subtask);
+ $this->container['dispatcher']->dispatch(self::EVENT_UPDATE, new SubtaskEvent($event));
}
return $result;
@@ -302,7 +317,6 @@ class Subtask extends Base
$positions = array_flip($subtasks);
if (isset($subtasks[$subtask_id]) && $subtasks[$subtask_id] < count($subtasks)) {
-
$position = ++$subtasks[$subtask_id];
$subtasks[$positions[$position]]--;
@@ -402,7 +416,14 @@ class Subtask extends Base
*/
public function remove($subtask_id)
{
- return $this->db->table(self::TABLE)->eq('id', $subtask_id)->remove();
+ $subtask = $this->getById($subtask_id);
+ $result = $this->db->table(self::TABLE)->eq('id', $subtask_id)->remove();
+
+ if ($result) {
+ $this->container['dispatcher']->dispatch(self::EVENT_DELETE, new SubtaskEvent($subtask));
+ }
+
+ return $result;
}
/**
diff --git a/app/Model/SubtaskTimeTracking.php b/app/Model/SubtaskTimeTracking.php
index 997031e8..56998769 100644
--- a/app/Model/SubtaskTimeTracking.php
+++ b/app/Model/SubtaskTimeTracking.php
@@ -339,20 +339,7 @@ class SubtaskTimeTracking extends Base
*/
public function updateTaskTimeTracking($task_id)
{
- $result = $this->calculateSubtaskTime($task_id);
- $values = array();
-
- if ($result['total_spent'] > 0) {
- $values['time_spent'] = $result['total_spent'];
- }
-
- if ($result['total_estimated'] > 0) {
- $values['time_estimated'] = $result['total_estimated'];
- }
-
- if (empty($values)) {
- return true;
- }
+ $values = $this->calculateSubtaskTime($task_id);
return $this->db
->table(Task::TABLE)
@@ -373,8 +360,8 @@ class SubtaskTimeTracking extends Base
->table(Subtask::TABLE)
->eq('task_id', $task_id)
->columns(
- 'SUM(time_spent) AS total_spent',
- 'SUM(time_estimated) AS total_estimated'
+ 'SUM(time_spent) AS time_spent',
+ 'SUM(time_estimated) AS time_estimated'
)
->findOne();
}
diff --git a/app/Schema/Mysql.php b/app/Schema/Mysql.php
index efdb159b..e5dff0d5 100644
--- a/app/Schema/Mysql.php
+++ b/app/Schema/Mysql.php
@@ -6,7 +6,18 @@ use PDO;
use Core\Security;
use Model\Link;
-const VERSION = 86;
+const VERSION = 87;
+
+function version_87($pdo)
+{
+ $pdo->exec("
+ CREATE TABLE plugin_schema_versions (
+ plugin VARCHAR(80) NOT NULL,
+ version INT NOT NULL DEFAULT 0,
+ PRIMARY KEY(plugin)
+ ) ENGINE=InnoDB CHARSET=utf8
+ ");
+}
function version_86($pdo)
{
@@ -311,19 +322,6 @@ function version_53($pdo)
$pdo->exec("ALTER TABLE subtask_time_tracking ADD COLUMN time_spent FLOAT DEFAULT 0");
}
-function version_52($pdo)
-{
- $pdo->exec('CREATE TABLE budget_lines (
- `id` INT NOT NULL AUTO_INCREMENT,
- `project_id` INT NOT NULL,
- `amount` FLOAT NOT NULL,
- `date` VARCHAR(10) NOT NULL,
- `comment` TEXT,
- FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE,
- PRIMARY KEY(id)
- ) ENGINE=InnoDB CHARSET=utf8');
-}
-
function version_51($pdo)
{
$pdo->exec('CREATE TABLE timetable_day (
@@ -370,19 +368,6 @@ function version_51($pdo)
) ENGINE=InnoDB CHARSET=utf8');
}
-function version_50($pdo)
-{
- $pdo->exec("CREATE TABLE hourly_rates (
- id INT NOT NULL AUTO_INCREMENT,
- user_id INT NOT NULL,
- rate FLOAT DEFAULT 0,
- date_effective INTEGER NOT NULL,
- currency CHAR(3) NOT NULL,
- FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
- PRIMARY KEY(id)
- ) ENGINE=InnoDB CHARSET=utf8");
-}
-
function version_49($pdo)
{
$pdo->exec('ALTER TABLE subtasks ADD COLUMN position INTEGER DEFAULT 1');
diff --git a/app/Schema/Postgres.php b/app/Schema/Postgres.php
index a5d28dcf..e7422e8c 100644
--- a/app/Schema/Postgres.php
+++ b/app/Schema/Postgres.php
@@ -6,7 +6,17 @@ use PDO;
use Core\Security;
use Model\Link;
-const VERSION = 66;
+const VERSION = 67;
+
+function version_67($pdo)
+{
+ $pdo->exec("
+ CREATE TABLE plugin_schema_versions (
+ plugin VARCHAR(80) NOT NULL PRIMARY KEY,
+ version INTEGER NOT NULL DEFAULT 0
+ )
+ ");
+}
function version_66($pdo)
{
@@ -305,18 +315,6 @@ function version_34($pdo)
$pdo->exec("ALTER TABLE subtask_time_tracking ADD COLUMN time_spent REAL DEFAULT 0");
}
-function version_33($pdo)
-{
- $pdo->exec('CREATE TABLE budget_lines (
- "id" SERIAL PRIMARY KEY,
- "project_id" INTEGER NOT NULL,
- "amount" REAL NOT NULL,
- "date" VARCHAR(10) NOT NULL,
- "comment" TEXT,
- FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE
- )');
-}
-
function version_32($pdo)
{
$pdo->exec('CREATE TABLE timetable_day (
@@ -359,18 +357,6 @@ function version_32($pdo)
)');
}
-function version_31($pdo)
-{
- $pdo->exec("CREATE TABLE hourly_rates (
- id SERIAL PRIMARY KEY,
- user_id INTEGER NOT NULL,
- rate REAL DEFAULT 0,
- date_effective INTEGER NOT NULL,
- currency CHAR(3) NOT NULL,
- FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
- )");
-}
-
function version_30($pdo)
{
$pdo->exec('ALTER TABLE subtasks ADD COLUMN position INTEGER DEFAULT 1');
diff --git a/app/Schema/Sqlite.php b/app/Schema/Sqlite.php
index 8efa016c..b76e902a 100644
--- a/app/Schema/Sqlite.php
+++ b/app/Schema/Sqlite.php
@@ -6,7 +6,17 @@ use Core\Security;
use PDO;
use Model\Link;
-const VERSION = 82;
+const VERSION = 83;
+
+function version_83($pdo)
+{
+ $pdo->exec("
+ CREATE TABLE plugin_schema_versions (
+ plugin TEXT NOT NULL PRIMARY KEY,
+ version INTEGER NOT NULL DEFAULT 0
+ )
+ ");
+}
function version_82($pdo)
{
@@ -282,18 +292,6 @@ function version_52($pdo)
$pdo->exec("ALTER TABLE subtask_time_tracking ADD COLUMN time_spent REAL DEFAULT 0");
}
-function version_51($pdo)
-{
- $pdo->exec('CREATE TABLE budget_lines (
- "id" INTEGER PRIMARY KEY,
- "project_id" INTEGER NOT NULL,
- "amount" REAL NOT NULL,
- "date" TEXT NOT NULL,
- "comment" TEXT,
- FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE
- )');
-}
-
function version_50($pdo)
{
$pdo->exec('CREATE TABLE timetable_day (
@@ -336,18 +334,6 @@ function version_50($pdo)
)');
}
-function version_49($pdo)
-{
- $pdo->exec("CREATE TABLE hourly_rates (
- id INTEGER PRIMARY KEY,
- user_id INTEGER NOT NULL,
- rate REAL DEFAULT 0,
- date_effective INTEGER NOT NULL,
- currency TEXT NOT NULL,
- FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
- )");
-}
-
function version_48($pdo)
{
$pdo->exec('ALTER TABLE subtasks ADD COLUMN position INTEGER DEFAULT 1');
diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php
index ef7aa575..a5677948 100644
--- a/app/ServiceProvider/ClassProvider.php
+++ b/app/ServiceProvider/ClassProvider.php
@@ -2,13 +2,16 @@
namespace ServiceProvider;
+use Core\ObjectStorage\FileStorage;
use Core\Paginator;
use Core\OAuth2;
+use Core\Tool;
use Model\Config;
use Model\Project;
use Model\Webhook;
use Pimple\Container;
use Pimple\ServiceProviderInterface;
+use League\HTMLToMarkdown\HtmlConverter;
class ClassProvider implements ServiceProviderInterface
{
@@ -18,7 +21,6 @@ class ClassProvider implements ServiceProviderInterface
'Action',
'Authentication',
'Board',
- 'Budget',
'Category',
'Color',
'Comment',
@@ -26,7 +28,6 @@ class ClassProvider implements ServiceProviderInterface
'Currency',
'DateParser',
'File',
- 'HourlyRate',
'LastLogin',
'Link',
'Notification',
@@ -93,17 +94,7 @@ class ClassProvider implements ServiceProviderInterface
public function register(Container $container)
{
- foreach ($this->classes as $namespace => $classes) {
-
- foreach ($classes as $name) {
-
- $class = '\\'.$namespace.'\\'.$name;
-
- $container[lcfirst($name)] = function ($c) use ($class) {
- return new $class($c);
- };
- }
- }
+ Tool::buildDIC($container, $this->classes);
$container['paginator'] = $container->factory(function ($c) {
return new Paginator($c);
@@ -112,5 +103,13 @@ class ClassProvider implements ServiceProviderInterface
$container['oauth'] = $container->factory(function ($c) {
return new OAuth2($c);
});
+
+ $container['htmlConverter'] = function($c) {
+ return new HtmlConverter(array('strip_tags' => true));
+ };
+
+ $container['objectStorage'] = function($c) {
+ return new FileStorage(FILES_DIR);
+ };
}
}
diff --git a/app/Subscriber/SubtaskTimeTrackingSubscriber.php b/app/Subscriber/SubtaskTimeTrackingSubscriber.php
index e45b2c93..2d3b5f99 100644
--- a/app/Subscriber/SubtaskTimeTrackingSubscriber.php
+++ b/app/Subscriber/SubtaskTimeTrackingSubscriber.php
@@ -12,6 +12,7 @@ class SubtaskTimeTrackingSubscriber extends \Core\Base implements EventSubscribe
{
return array(
Subtask::EVENT_CREATE => array('updateTaskTime', 0),
+ Subtask::EVENT_DELETE => array('updateTaskTime', 0),
Subtask::EVENT_UPDATE => array(
array('logStartEnd', 10),
array('updateTaskTime', 0),
diff --git a/app/Template/app/sidebar.php b/app/Template/app/sidebar.php
index 2d966009..f4a455f8 100644
--- a/app/Template/app/sidebar.php
+++ b/app/Template/app/sidebar.php
@@ -19,6 +19,7 @@
<li <?= $this->app->getRouterAction() === 'activity' ? 'class="active"' : '' ?>>
<?= $this->url->link(t('My activity stream'), 'app', 'activity', array('user_id' => $user['id'])) ?>
</li>
+ <?= $this->hook->render('dashboard:sidebar') ?>
</ul>
<div class="sidebar-collapse"><a href="#" title="<?= t('Hide sidebar') ?>"><i class="fa fa-chevron-left"></i></a></div>
<div class="sidebar-expand" style="display: none"><a href="#" title="<?= t('Expand sidebar') ?>"><i class="fa fa-chevron-right"></i></a></div>
diff --git a/app/Template/budget/breakdown.php b/app/Template/budget/breakdown.php
deleted file mode 100644
index 92561188..00000000
--- a/app/Template/budget/breakdown.php
+++ /dev/null
@@ -1,30 +0,0 @@
-<div class="page-header">
- <h2><?= t('Cost breakdown') ?></h2>
-</div>
-
-<?php if ($paginator->isEmpty()): ?>
- <p class="alert"><?= t('There is nothing to show.') ?></p>
-<?php else: ?>
- <table class="table-fixed">
- <tr>
- <th class="column-20"><?= $paginator->order(t('Task'), 'task_title') ?></th>
- <th class="column-25"><?= $paginator->order(t('Subtask'), 'subtask_title') ?></th>
- <th class="column-20"><?= $paginator->order(t('User'), 'username') ?></th>
- <th class="column-10"><?= t('Cost') ?></th>
- <th class="column-10"><?= $paginator->order(t('Time spent'), \Model\SubtaskTimeTracking::TABLE.'.time_spent') ?></th>
- <th class="column-15"><?= $paginator->order(t('Date'), 'start') ?></th>
- </tr>
- <?php foreach ($paginator->getCollection() as $record): ?>
- <tr>
- <td><?= $this->url->link($this->e($record['task_title']), 'task', 'show', array('project_id' => $project['id'], 'task_id' => $record['task_id'])) ?></td>
- <td><?= $this->url->link($this->e($record['subtask_title']), 'task', 'show', array('project_id' => $project['id'], 'task_id' => $record['task_id'])) ?></td>
- <td><?= $this->url->link($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>
- </tr>
- <?php endforeach ?>
- </table>
-
- <?= $paginator ?>
-<?php endif ?> \ No newline at end of file
diff --git a/app/Template/budget/create.php b/app/Template/budget/create.php
deleted file mode 100644
index a563796d..00000000
--- a/app/Template/budget/create.php
+++ /dev/null
@@ -1,47 +0,0 @@
-<div class="page-header">
- <h2><?= t('Budget lines') ?></h2>
-</div>
-
-<?php if (! empty($lines)): ?>
-<table class="table-fixed table-stripped">
- <tr>
- <th class="column-20"><?= t('Budget line') ?></th>
- <th class="column-20"><?= t('Date') ?></th>
- <th><?= t('Comment') ?></th>
- <th><?= t('Action') ?></th>
- </tr>
- <?php foreach ($lines as $line): ?>
- <tr>
- <td><?= n($line['amount']) ?></td>
- <td><?= dt('%B %e, %Y', strtotime($line['date'])) ?></td>
- <td><?= $this->e($line['comment']) ?></td>
- <td>
- <?= $this->url->link(t('Remove'), 'budget', 'confirm', array('project_id' => $project['id'], 'budget_id' => $line['id'])) ?>
- </td>
- </tr>
- <?php endforeach ?>
-</table>
-
-<h3><?= t('New budget line') ?></h3>
-<?php endif ?>
-
-<form method="post" action="<?= $this->url->href('budget', 'save', array('project_id' => $project['id'])) ?>" autocomplete="off">
-
- <?= $this->form->csrf() ?>
-
- <?= $this->form->hidden('id', $values) ?>
- <?= $this->form->hidden('project_id', $values) ?>
-
- <?= $this->form->label(t('Amount'), 'amount') ?>
- <?= $this->form->text('amount', $values, $errors, array('required'), 'form-numeric') ?>
-
- <?= $this->form->label(t('Date'), 'date') ?>
- <?= $this->form->text('date', $values, $errors, array('required'), 'form-date') ?>
-
- <?= $this->form->label(t('Comment'), 'comment') ?>
- <?= $this->form->text('comment', $values, $errors) ?>
-
- <div class="form-actions">
- <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
- </div>
-</form> \ No newline at end of file
diff --git a/app/Template/budget/index.php b/app/Template/budget/index.php
deleted file mode 100644
index 51ef3d87..00000000
--- a/app/Template/budget/index.php
+++ /dev/null
@@ -1,34 +0,0 @@
-<div class="page-header">
- <h2><?= t('Budget overview') ?></h2>
-</div>
-
-<?php if (! empty($daily_budget)): ?>
-<div id="budget-chart">
- <div id="chart"
- data-date-format="<?= e('%%Y-%%m-%%d') ?>"
- data-metrics='<?= json_encode($daily_budget, JSON_HEX_APOS) ?>'
- data-labels='<?= json_encode(array('in' => t('Budget line'), 'out' => t('Expenses'), 'left' => t('Remaining'), 'value' => t('Amount'), 'date' => t('Date'), 'type' => t('Type')), JSON_HEX_APOS) ?>'></div>
-</div>
-<hr/>
-<table class="table-fixed table-stripped">
- <tr>
- <th><?= t('Date') ?></td>
- <th><?= t('Budget line') ?></td>
- <th><?= t('Expenses') ?></td>
- <th><?= t('Remaining') ?></td>
- </tr>
- <?php foreach ($daily_budget as $line): ?>
- <tr>
- <td><?= dt('%B %e, %Y', strtotime($line['date'])) ?></td>
- <td><?= n($line['in']) ?></td>
- <td><?= n($line['out']) ?></td>
- <td><?= n($line['left']) ?></td>
- </tr>
- <?php endforeach ?>
-</table>
-<?php else: ?>
- <p class="alert"><?= t('There is not enough data to show something.') ?></p>
-<?php endif ?>
-
-<?= $this->asset->js('assets/js/vendor/d3.v3.min.js') ?>
-<?= $this->asset->js('assets/js/vendor/c3.min.js') ?> \ No newline at end of file
diff --git a/app/Template/budget/remove.php b/app/Template/budget/remove.php
deleted file mode 100644
index a5b906a1..00000000
--- a/app/Template/budget/remove.php
+++ /dev/null
@@ -1,13 +0,0 @@
-<div class="page-header">
- <h2><?= t('Remove budget line') ?></h2>
-</div>
-
-<div class="confirm">
- <p class="alert alert-info"><?= t('Do you really want to remove this budget line?') ?></p>
-
- <div class="form-actions">
- <?= $this->url->link(t('Yes'), 'budget', 'remove', array('project_id' => $project['id'], 'budget_id' => $budget_id), true, 'btn btn-red') ?>
- <?= t('or') ?>
- <?= $this->url->link(t('cancel'), 'budget', 'create', array('project_id' => $project['id'])) ?>
- </div>
-</div> \ No newline at end of file
diff --git a/app/Template/budget/sidebar.php b/app/Template/budget/sidebar.php
deleted file mode 100644
index 8477c052..00000000
--- a/app/Template/budget/sidebar.php
+++ /dev/null
@@ -1,16 +0,0 @@
-<div class="sidebar">
- <h2><?= t('Budget') ?></h2>
- <ul>
- <li <?= $this->app->getRouterAction() === 'index' ? 'class="active"' : '' ?>>
- <?= $this->url->link(t('Budget overview'), 'budget', 'index', array('project_id' => $project['id'])) ?>
- </li>
- <li <?= $this->app->getRouterAction() === 'create' ? 'class="active"' : '' ?>>
- <?= $this->url->link(t('Budget lines'), 'budget', 'create', array('project_id' => $project['id'])) ?>
- </li>
- <li <?= $this->app->getRouterAction() === 'breakdown' ? 'class="active"' : '' ?>>
- <?= $this->url->link(t('Cost breakdown'), 'budget', 'breakdown', array('project_id' => $project['id'])) ?>
- </li>
- </ul>
- <div class="sidebar-collapse"><a href="#" title="<?= t('Hide sidebar') ?>"><i class="fa fa-chevron-left"></i></a></div>
- <div class="sidebar-expand" style="display: none"><a href="#" title="<?= t('Expand sidebar') ?>"><i class="fa fa-chevron-right"></i></a></div>
-</div> \ No newline at end of file
diff --git a/app/Template/config/sidebar.php b/app/Template/config/sidebar.php
index 3617979a..083da283 100644
--- a/app/Template/config/sidebar.php
+++ b/app/Template/config/sidebar.php
@@ -34,6 +34,7 @@
<li>
<?= $this->url->link(t('Documentation'), 'doc', 'show') ?>
</li>
+ <?= $this->hook->render('config:sidebar') ?>
</ul>
<div class="sidebar-collapse"><a href="#" title="<?= t('Hide sidebar') ?>"><i class="fa fa-chevron-left"></i></a></div>
<div class="sidebar-expand" style="display: none"><a href="#" title="<?= t('Expand sidebar') ?>"><i class="fa fa-chevron-right"></i></a></div>
diff --git a/app/Template/currency/index.php b/app/Template/currency/index.php
index f72c5700..1c78c47a 100644
--- a/app/Template/currency/index.php
+++ b/app/Template/currency/index.php
@@ -52,5 +52,3 @@
<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/export/sidebar.php b/app/Template/export/sidebar.php
index f204d29d..7e39a5af 100644
--- a/app/Template/export/sidebar.php
+++ b/app/Template/export/sidebar.php
@@ -13,6 +13,7 @@
<li <?= $this->app->getRouterAction() === 'summary' ? 'class="active"' : '' ?>>
<?= $this->url->link(t('Daily project summary'), 'export', 'summary', array('project_id' => $project['id'])) ?>
</li>
+ <?= $this->hook->render('export:sidebar') ?>
</ul>
<div class="sidebar-collapse"><a href="#" title="<?= t('Hide sidebar') ?>"><i class="fa fa-chevron-left"></i></a></div>
<div class="sidebar-expand" style="display: none"><a href="#" title="<?= t('Expand sidebar') ?>"><i class="fa fa-chevron-right"></i></a></div>
diff --git a/app/Template/file/show.php b/app/Template/file/show.php
index b1a0a813..a390c9fb 100644
--- a/app/Template/file/show.php
+++ b/app/Template/file/show.php
@@ -11,7 +11,7 @@
<li>
<?php if (function_exists('imagecreatetruecolor')): ?>
<div class="img_container">
- <img src="<?= $this->url->href('file', 'thumbnail', array('width' => 250, 'height' => 100, 'file_id' => $file['id'], 'project_id' => $task['project_id'], 'task_id' => $file['task_id'])) ?>" alt="<?= $this->e($file['name']) ?>"/>
+ <img src="<?= $this->url->href('file', 'thumbnail', array('file_id' => $file['id'], 'project_id' => $task['project_id'], 'task_id' => $file['task_id'])) ?>" alt="<?= $this->e($file['name']) ?>"/>
</div>
<?php endif ?>
<p>
diff --git a/app/Template/header.php b/app/Template/header.php
new file mode 100644
index 00000000..0bcfdbbc
--- /dev/null
+++ b/app/Template/header.php
@@ -0,0 +1,33 @@
+<header>
+ <nav>
+ <h1><?= $this->url->link('K<span>B</span>', 'app', 'index', array(), false, 'logo', t('Dashboard')).' '.$this->e($title) ?>
+ <?php if (! empty($description)): ?>
+ <span class="tooltip" title='<?= $this->e($this->text->markdown($description)) ?>'>
+ <i class="fa fa-info-circle"></i>
+ </span>
+ <?php endif ?>
+ </h1>
+ <ul>
+ <?php if (isset($board_selector) && ! empty($board_selector)): ?>
+ <li>
+ <select id="board-selector"
+ class="chosen-select select-auto-redirect"
+ tabindex="-1"
+ data-notfound="<?= t('No results match:') ?>"
+ data-placeholder="<?= t('Display another project') ?>"
+ data-redirect-regex="PROJECT_ID"
+ data-redirect-url="<?= $this->url->href('board', 'show', array('project_id' => 'PROJECT_ID')) ?>">
+ <option value=""></option>
+ <?php foreach($board_selector as $board_id => $board_name): ?>
+ <option value="<?= $board_id ?>"><?= $this->e($board_name) ?></option>
+ <?php endforeach ?>
+ </select>
+ </li>
+ <?php endif ?>
+ <li>
+ <?= $this->url->link(t('Logout'), 'auth', 'logout') ?>
+ <span class="username hide-tablet">(<?= $this->user->getProfileLink() ?>)</span>
+ </li>
+ </ul>
+ </nav>
+</header> \ No newline at end of file
diff --git a/app/Template/hourlyrate/index.php b/app/Template/hourlyrate/index.php
deleted file mode 100644
index af305d07..00000000
--- a/app/Template/hourlyrate/index.php
+++ /dev/null
@@ -1,46 +0,0 @@
-<div class="page-header">
- <h2><?= t('Hourly rates') ?></h2>
-</div>
-
-<?php if (! empty($rates)): ?>
-
-<table>
- <tr>
- <th><?= t('Hourly rate') ?></th>
- <th><?= t('Currency') ?></th>
- <th><?= t('Effective date') ?></th>
- <th><?= t('Action') ?></th>
- </tr>
- <?php foreach ($rates as $rate): ?>
- <tr>
- <td><?= n($rate['rate']) ?></td>
- <td><?= $rate['currency'] ?></td>
- <td><?= dt('%b %e, %Y', $rate['date_effective']) ?></td>
- <td>
- <?= $this->url->link(t('Remove'), 'hourlyrate', 'confirm', array('user_id' => $user['id'], 'rate_id' => $rate['id'])) ?>
- </td>
- </tr>
- <?php endforeach ?>
-</table>
-
-<h3><?= t('Add new rate') ?></h3>
-<?php endif ?>
-
-<form method="post" action="<?= $this->url->href('hourlyrate', 'save', array('user_id' => $user['id'])) ?>" autocomplete="off">
-
- <?= $this->form->hidden('user_id', $values) ?>
- <?= $this->form->csrf() ?>
-
- <?= $this->form->label(t('Hourly rate'), 'rate') ?>
- <?= $this->form->text('rate', $values, $errors, array('required'), 'form-numeric') ?>
-
- <?= $this->form->label(t('Currency'), 'currency') ?>
- <?= $this->form->select('currency', $currencies_list, $values, $errors, array('required')) ?>
-
- <?= $this->form->label(t('Effective date'), 'date_effective') ?>
- <?= $this->form->text('date_effective', $values, $errors, array('required'), 'form-date') ?>
-
- <div class="form-actions">
- <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
- </div>
-</form>
diff --git a/app/Template/hourlyrate/remove.php b/app/Template/hourlyrate/remove.php
deleted file mode 100644
index 121436e4..00000000
--- a/app/Template/hourlyrate/remove.php
+++ /dev/null
@@ -1,13 +0,0 @@
-<div class="page-header">
- <h2><?= t('Remove hourly rate') ?></h2>
-</div>
-
-<div class="confirm">
- <p class="alert alert-info"><?= t('Do you really want to remove this hourly rate?') ?></p>
-
- <div class="form-actions">
- <?= $this->url->link(t('Yes'), 'hourlyrate', 'remove', array('user_id' => $user['id'], 'rate_id' => $rate_id), true, 'btn btn-red') ?>
- <?= t('or') ?>
- <?= $this->url->link(t('cancel'), 'hourlyrate', 'index', array('user_id' => $user['id'])) ?>
- </div>
-</div> \ No newline at end of file
diff --git a/app/Template/layout.php b/app/Template/layout.php
index 60108175..934fb62c 100644
--- a/app/Template/layout.php
+++ b/app/Template/layout.php
@@ -28,6 +28,8 @@
<link rel="apple-touch-icon" sizes="144x144" href="<?= $this->url->dir() ?>assets/img/touch-icon-ipad-retina.png">
<title><?= isset($title) ? $this->e($title) : 'Kanboard' ?></title>
+
+ <?= $this->hook->render('layout:head') ?>
</head>
<body data-status-url="<?= $this->url->href('app', 'status') ?>"
data-login-url="<?= $this->url->href('auth', 'login') ?>"
@@ -38,43 +40,17 @@
<?php if (isset($no_layout) && $no_layout): ?>
<?= $content_for_layout ?>
<?php else: ?>
- <header>
- <nav>
- <h1><?= $this->url->link('K<span>B</span>', 'app', 'index', array(), false, 'logo', t('Dashboard')).' '.$this->e($title) ?>
- <?php if (! empty($description)): ?>
- <span class="tooltip" title='<?= $this->e($this->text->markdown($description)) ?>'>
- <i class="fa fa-info-circle"></i>
- </span>
- <?php endif ?>
- </h1>
- <ul>
- <?php if (isset($board_selector) && ! empty($board_selector)): ?>
- <li>
- <select id="board-selector"
- class="chosen-select select-auto-redirect"
- tabindex="-1"
- data-notfound="<?= t('No results match:') ?>"
- data-placeholder="<?= t('Display another project') ?>"
- data-redirect-regex="PROJECT_ID"
- data-redirect-url="<?= $this->url->href('board', 'show', array('project_id' => 'PROJECT_ID')) ?>">
- <option value=""></option>
- <?php foreach($board_selector as $board_id => $board_name): ?>
- <option value="<?= $board_id ?>"><?= $this->e($board_name) ?></option>
- <?php endforeach ?>
- </select>
- </li>
- <?php endif ?>
- <li>
- <?= $this->url->link(t('Logout'), 'auth', 'logout') ?>
- <span class="username hide-tablet">(<?= $this->user->getProfileLink() ?>)</span>
- </li>
- </ul>
- </nav>
- </header>
+ <?= $this->hook->render('layout:top') ?>
+ <?= $this->render('header', array(
+ 'title' => $title,
+ 'description' => isset($description) ? $description : '',
+ 'board_selector' => $board_selector,
+ )) ?>
<section class="page">
<?= $this->app->flashMessage() ?>
<?= $content_for_layout ?>
</section>
+ <?= $this->hook->render('layout:bottom') ?>
<?php endif ?>
</body>
</html>
diff --git a/app/Template/project/dropdown.php b/app/Template/project/dropdown.php
index 0a53cc05..0f1e1f6b 100644
--- a/app/Template/project/dropdown.php
+++ b/app/Template/project/dropdown.php
@@ -9,21 +9,19 @@
</li>
<?php endif ?>
+<?= $this->hook->render('project:dropdown', array('project' => $project)) ?>
+
<?php if ($this->user->isProjectManagementAllowed($project['id'])): ?>
-<li>
- <i class="fa fa-line-chart fa-fw"></i>
- <?= $this->url->link(t('Analytics'), 'analytic', 'tasks', array('project_id' => $project['id'])) ?>
-</li>
-<li>
- <i class="fa fa-pie-chart fa-fw"></i>
- <?= $this->url->link(t('Budget'), 'budget', 'index', array('project_id' => $project['id'])) ?>
-</li>
-<li>
- <i class="fa fa-download fa-fw"></i>
- <?= $this->url->link(t('Exports'), 'export', 'tasks', array('project_id' => $project['id'])) ?>
-</li>
-<li>
- <i class="fa fa-cog fa-fw"></i>
- <?= $this->url->link(t('Settings'), 'project', 'show', array('project_id' => $project['id'])) ?>
-</li>
+ <li>
+ <i class="fa fa-line-chart fa-fw"></i>
+ <?= $this->url->link(t('Analytics'), 'analytic', 'tasks', array('project_id' => $project['id'])) ?>
+ </li>
+ <li>
+ <i class="fa fa-download fa-fw"></i>
+ <?= $this->url->link(t('Exports'), 'export', 'tasks', array('project_id' => $project['id'])) ?>
+ </li>
+ <li>
+ <i class="fa fa-cog fa-fw"></i>
+ <?= $this->url->link(t('Settings'), 'project', 'show', array('project_id' => $project['id'])) ?>
+ </li>
<?php endif ?>
diff --git a/app/Template/project/sidebar.php b/app/Template/project/sidebar.php
index 7b5d976f..84bbb6b1 100644
--- a/app/Template/project/sidebar.php
+++ b/app/Template/project/sidebar.php
@@ -48,6 +48,8 @@
</li>
<?php endif ?>
<?php endif ?>
+
+ <?= $this->hook->render('project:sidebar') ?>
</ul>
<div class="sidebar-collapse"><a href="#" title="<?= t('Hide sidebar') ?>"><i class="fa fa-chevron-left"></i></a></div>
<div class="sidebar-expand" style="display: none"><a href="#" title="<?= t('Expand sidebar') ?>"><i class="fa fa-chevron-right"></i></a></div>
diff --git a/app/Template/project_user/sidebar.php b/app/Template/project_user/sidebar.php
index 8cc3f41b..98219a87 100644
--- a/app/Template/project_user/sidebar.php
+++ b/app/Template/project_user/sidebar.php
@@ -24,5 +24,7 @@
<li <?= $this->app->getRouterAction() === 'closed' ? 'class="active"' : '' ?>>
<?= $this->url->link(t('Closed tasks'), 'projectuser', 'closed', $filter) ?>
</li>
+
+ <?= $this->hook->render('project-user:sidebar') ?>
</ul>
</div> \ No newline at end of file
diff --git a/app/Template/task/sidebar.php b/app/Template/task/sidebar.php
index 1f06ab8c..cf0e9f76 100644
--- a/app/Template/task/sidebar.php
+++ b/app/Template/task/sidebar.php
@@ -18,6 +18,8 @@
<?= $this->url->link(t('Time tracking'), 'task', 'timetracking', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
</li>
<?php endif ?>
+
+ <?= $this->hook->render('task:sidebar:information') ?>
</ul>
<h2><?= t('Actions') ?></h2>
<ul>
@@ -66,6 +68,8 @@
<?= $this->url->link(t('Remove'), 'task', 'remove', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
</li>
<?php endif ?>
+
+ <?= $this->hook->render('task:sidebar:actions') ?>
</ul>
<div class="sidebar-collapse"><a href="#" title="<?= t('Hide sidebar') ?>"><i class="fa fa-chevron-left"></i></a></div>
<div class="sidebar-expand" style="display: none"><a href="#" title="<?= t('Expand sidebar') ?>"><i class="fa fa-chevron-right"></i></a></div>
diff --git a/app/Template/user/sidebar.php b/app/Template/user/sidebar.php
index cd1c85c1..80fe8684 100644
--- a/app/Template/user/sidebar.php
+++ b/app/Template/user/sidebar.php
@@ -20,6 +20,8 @@
<?= $this->url->link(t('Persistent connections'), 'user', 'sessions', array('user_id' => $user['id'])) ?>
</li>
<?php endif ?>
+
+ <?= $this->hook->render('user:sidebar:information') ?>
</ul>
<h2><?= t('Actions') ?></h2>
@@ -60,14 +62,13 @@
<li <?= $this->app->getRouterController() === 'user' && $this->app->getRouterAction() === 'authentication' ? 'class="active"' : '' ?>>
<?= $this->url->link(t('Edit Authentication'), 'user', 'authentication', array('user_id' => $user['id'])) ?>
</li>
- <li <?= $this->app->getRouterController() === 'hourlyrate' ? 'class="active"' : '' ?>>
- <?= $this->url->link(t('Hourly rates'), 'hourlyrate', 'index', array('user_id' => $user['id'])) ?>
- </li>
<li <?= $this->app->getRouterController() === 'timetable' ? 'class="active"' : '' ?>>
<?= $this->url->link(t('Manage timetable'), 'timetable', 'index', array('user_id' => $user['id'])) ?>
</li>
<?php endif ?>
+ <?= $this->hook->render('user:sidebar:actions', array('user' => $user)) ?>
+
<?php if ($this->user->isAdmin() && ! $this->user->isCurrentUser($user['id'])): ?>
<li <?= $this->app->getRouterController() === 'user' && $this->app->getRouterAction() === 'remove' ? 'class="active"' : '' ?>>
<?= $this->url->link(t('Remove'), 'user', 'remove', array('user_id' => $user['id'])) ?>
diff --git a/app/common.php b/app/common.php
index 1f1c7273..ea38ab36 100644
--- a/app/common.php
+++ b/app/common.php
@@ -30,120 +30,8 @@ $container->register(new ServiceProvider\ClassProvider);
$container->register(new ServiceProvider\EventDispatcherProvider);
if (ENABLE_URL_REWRITE) {
-
- // Dashboard
- $container['router']->addRoute('dashboard', 'app', 'index');
- $container['router']->addRoute('dashboard/:user_id', 'app', 'index', array('user_id'));
- $container['router']->addRoute('dashboard/:user_id/projects', 'app', 'projects', array('user_id'));
- $container['router']->addRoute('dashboard/:user_id/tasks', 'app', 'tasks', array('user_id'));
- $container['router']->addRoute('dashboard/:user_id/subtasks', 'app', 'subtasks', array('user_id'));
- $container['router']->addRoute('dashboard/:user_id/calendar', 'app', 'calendar', array('user_id'));
- $container['router']->addRoute('dashboard/:user_id/activity', 'app', 'activity', array('user_id'));
-
- // Search routes
- $container['router']->addRoute('search', 'search', 'index');
- $container['router']->addRoute('search/:search', 'search', 'index', array('search'));
-
- // Project routes
- $container['router']->addRoute('projects', 'project', 'index');
- $container['router']->addRoute('project/create', 'project', 'create');
- $container['router']->addRoute('project/create/:private', 'project', 'create', array('private'));
- $container['router']->addRoute('project/:project_id', 'project', 'show', array('project_id'));
- $container['router']->addRoute('p/:project_id', 'project', 'show', array('project_id'));
- $container['router']->addRoute('project/:project_id/share', 'project', 'share', array('project_id'));
- $container['router']->addRoute('project/:project_id/edit', 'project', 'edit', array('project_id'));
- $container['router']->addRoute('project/:project_id/integration', 'project', 'integration', array('project_id'));
- $container['router']->addRoute('project/:project_id/users', 'project', 'users', array('project_id'));
- $container['router']->addRoute('project/:project_id/duplicate', 'project', 'duplicate', array('project_id'));
- $container['router']->addRoute('project/:project_id/remove', 'project', 'remove', array('project_id'));
- $container['router']->addRoute('project/:project_id/disable', 'project', 'disable', array('project_id'));
- $container['router']->addRoute('project/:project_id/enable', 'project', 'enable', array('project_id'));
-
- // Action routes
- $container['router']->addRoute('project/:project_id/actions', 'action', 'index', array('project_id'));
- $container['router']->addRoute('project/:project_id/action/:action_id/confirm', 'action', 'confirm', array('project_id', 'action_id'));
-
- // Column routes
- $container['router']->addRoute('project/:project_id/columns', 'column', 'index', array('project_id'));
- $container['router']->addRoute('project/:project_id/column/:column_id/edit', 'column', 'edit', array('project_id', 'column_id'));
- $container['router']->addRoute('project/:project_id/column/:column_id/confirm', 'column', 'confirm', array('project_id', 'column_id'));
- $container['router']->addRoute('project/:project_id/column/:column_id/move/:direction', 'column', 'move', array('project_id', 'column_id', 'direction'));
-
- // Swimlane routes
- $container['router']->addRoute('project/:project_id/swimlanes', 'swimlane', 'index', array('project_id'));
- $container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/edit', 'swimlane', 'edit', array('project_id', 'swimlane_id'));
- $container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/confirm', 'swimlane', 'confirm', array('project_id', 'swimlane_id'));
- $container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/disable', 'swimlane', 'disable', array('project_id', 'swimlane_id'));
- $container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/enable', 'swimlane', 'enable', array('project_id', 'swimlane_id'));
- $container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/up', 'swimlane', 'moveup', array('project_id', 'swimlane_id'));
- $container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/down', 'swimlane', 'movedown', array('project_id', 'swimlane_id'));
-
- // Category routes
- $container['router']->addRoute('project/:project_id/categories', 'category', 'index', array('project_id'));
- $container['router']->addRoute('project/:project_id/category/:category_id/edit', 'category', 'edit', array('project_id', 'category_id'));
- $container['router']->addRoute('project/:project_id/category/:category_id/confirm', 'category', 'confirm', array('project_id', 'category_id'));
-
- // Task routes
- $container['router']->addRoute('project/:project_id/task/:task_id', 'task', 'show', array('project_id', 'task_id'));
- $container['router']->addRoute('t/:task_id', 'task', 'show', array('task_id'));
- $container['router']->addRoute('public/task/:task_id/:token', 'task', 'readonly', array('task_id', 'token'));
-
- $container['router']->addRoute('project/:project_id/task/:task_id/activity', 'activity', 'task', array('project_id', 'task_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/screenshot', 'file', 'screenshot', array('project_id', 'task_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/upload', 'file', 'create', array('project_id', 'task_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/comment', 'comment', 'create', array('project_id', 'task_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/link', 'tasklink', 'create', array('project_id', 'task_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/transitions', 'task', 'transitions', array('project_id', 'task_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/analytics', 'task', 'analytics', array('project_id', 'task_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/remove', 'task', 'remove', array('project_id', 'task_id'));
-
- $container['router']->addRoute('project/:project_id/task/:task_id/edit', 'taskmodification', 'edit', array('project_id', 'task_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/description', 'taskmodification', 'description', array('project_id', 'task_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/recurrence', 'taskmodification', 'recurrence', array('project_id', 'task_id'));
-
- $container['router']->addRoute('project/:project_id/task/:task_id/close', 'taskstatus', 'close', array('task_id', 'project_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/open', 'taskstatus', 'open', array('task_id', 'project_id'));
-
- $container['router']->addRoute('project/:project_id/task/:task_id/duplicate', 'taskduplication', 'duplicate', array('task_id', 'project_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/copy', 'taskduplication', 'copy', array('task_id', 'project_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/copy/:dst_project_id', 'taskduplication', 'copy', array('task_id', 'project_id', 'dst_project_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/move', 'taskduplication', 'move', array('task_id', 'project_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/move/:dst_project_id', 'taskduplication', 'move', array('task_id', 'project_id', 'dst_project_id'));
-
- // Board routes
- $container['router']->addRoute('board/:project_id', 'board', 'show', array('project_id'));
- $container['router']->addRoute('b/:project_id', 'board', 'show', array('project_id'));
- $container['router']->addRoute('public/board/:token', 'board', 'readonly', array('token'));
-
- // Calendar routes
- $container['router']->addRoute('calendar/:project_id', 'calendar', 'show', array('project_id'));
- $container['router']->addRoute('c/:project_id', 'calendar', 'show', array('project_id'));
-
- // Listing routes
- $container['router']->addRoute('list/:project_id', 'listing', 'show', array('project_id'));
- $container['router']->addRoute('l/:project_id', 'listing', 'show', array('project_id'));
-
- // Gantt routes
- $container['router']->addRoute('gantt/:project_id', 'gantt', 'project', array('project_id'));
- $container['router']->addRoute('gantt/:project_id/sort/:sorting', 'gantt', 'project', array('project_id', 'sorting'));
-
- // Subtask routes
- $container['router']->addRoute('project/:project_id/task/:task_id/subtask/create', 'subtask', 'create', array('project_id', 'task_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/subtask/:subtask_id/remove', 'subtask', 'confirm', array('project_id', 'task_id', 'subtask_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/subtask/:subtask_id/edit', 'subtask', 'edit', array('project_id', 'task_id', 'subtask_id'));
-
- // Feed routes
- $container['router']->addRoute('feed/project/:token', 'feed', 'project', array('token'));
- $container['router']->addRoute('feed/user/:token', 'feed', 'user', array('token'));
-
- // Ical routes
- $container['router']->addRoute('ical/project/:token', 'ical', 'project', array('token'));
- $container['router']->addRoute('ical/user/:token', 'ical', 'user', array('token'));
-
- // Auth routes
- $container['router']->addRoute('oauth/google', 'oauth', 'google');
- $container['router']->addRoute('oauth/github', 'oauth', 'github');
- $container['router']->addRoute('oauth/gitlab', 'oauth', 'gitlab');
- $container['router']->addRoute('login', 'auth', 'login');
- $container['router']->addRoute('logout', 'auth', 'logout');
+ require __DIR__.'/routes.php';
}
+
+$plugin = new Core\PluginLoader($container);
+$plugin->scan();
diff --git a/app/routes.php b/app/routes.php
new file mode 100644
index 00000000..159e8f6e
--- /dev/null
+++ b/app/routes.php
@@ -0,0 +1,117 @@
+<?php
+
+// Dashboard
+$container['router']->addRoute('dashboard', 'app', 'index');
+$container['router']->addRoute('dashboard/:user_id', 'app', 'index', array('user_id'));
+$container['router']->addRoute('dashboard/:user_id/projects', 'app', 'projects', array('user_id'));
+$container['router']->addRoute('dashboard/:user_id/tasks', 'app', 'tasks', array('user_id'));
+$container['router']->addRoute('dashboard/:user_id/subtasks', 'app', 'subtasks', array('user_id'));
+$container['router']->addRoute('dashboard/:user_id/calendar', 'app', 'calendar', array('user_id'));
+$container['router']->addRoute('dashboard/:user_id/activity', 'app', 'activity', array('user_id'));
+
+// Search routes
+$container['router']->addRoute('search', 'search', 'index');
+$container['router']->addRoute('search/:search', 'search', 'index', array('search'));
+
+// Project routes
+$container['router']->addRoute('projects', 'project', 'index');
+$container['router']->addRoute('project/create', 'project', 'create');
+$container['router']->addRoute('project/create/:private', 'project', 'create', array('private'));
+$container['router']->addRoute('project/:project_id', 'project', 'show', array('project_id'));
+$container['router']->addRoute('p/:project_id', 'project', 'show', array('project_id'));
+$container['router']->addRoute('project/:project_id/share', 'project', 'share', array('project_id'));
+$container['router']->addRoute('project/:project_id/edit', 'project', 'edit', array('project_id'));
+$container['router']->addRoute('project/:project_id/integration', 'project', 'integration', array('project_id'));
+$container['router']->addRoute('project/:project_id/users', 'project', 'users', array('project_id'));
+$container['router']->addRoute('project/:project_id/duplicate', 'project', 'duplicate', array('project_id'));
+$container['router']->addRoute('project/:project_id/remove', 'project', 'remove', array('project_id'));
+$container['router']->addRoute('project/:project_id/disable', 'project', 'disable', array('project_id'));
+$container['router']->addRoute('project/:project_id/enable', 'project', 'enable', array('project_id'));
+
+// Action routes
+$container['router']->addRoute('project/:project_id/actions', 'action', 'index', array('project_id'));
+$container['router']->addRoute('project/:project_id/action/:action_id/confirm', 'action', 'confirm', array('project_id', 'action_id'));
+
+// Column routes
+$container['router']->addRoute('project/:project_id/columns', 'column', 'index', array('project_id'));
+$container['router']->addRoute('project/:project_id/column/:column_id/edit', 'column', 'edit', array('project_id', 'column_id'));
+$container['router']->addRoute('project/:project_id/column/:column_id/confirm', 'column', 'confirm', array('project_id', 'column_id'));
+$container['router']->addRoute('project/:project_id/column/:column_id/move/:direction', 'column', 'move', array('project_id', 'column_id', 'direction'));
+
+// Swimlane routes
+$container['router']->addRoute('project/:project_id/swimlanes', 'swimlane', 'index', array('project_id'));
+$container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/edit', 'swimlane', 'edit', array('project_id', 'swimlane_id'));
+$container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/confirm', 'swimlane', 'confirm', array('project_id', 'swimlane_id'));
+$container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/disable', 'swimlane', 'disable', array('project_id', 'swimlane_id'));
+$container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/enable', 'swimlane', 'enable', array('project_id', 'swimlane_id'));
+$container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/up', 'swimlane', 'moveup', array('project_id', 'swimlane_id'));
+$container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/down', 'swimlane', 'movedown', array('project_id', 'swimlane_id'));
+
+// Category routes
+$container['router']->addRoute('project/:project_id/categories', 'category', 'index', array('project_id'));
+$container['router']->addRoute('project/:project_id/category/:category_id/edit', 'category', 'edit', array('project_id', 'category_id'));
+$container['router']->addRoute('project/:project_id/category/:category_id/confirm', 'category', 'confirm', array('project_id', 'category_id'));
+
+// Task routes
+$container['router']->addRoute('project/:project_id/task/:task_id', 'task', 'show', array('project_id', 'task_id'));
+$container['router']->addRoute('t/:task_id', 'task', 'show', array('task_id'));
+$container['router']->addRoute('public/task/:task_id/:token', 'task', 'readonly', array('task_id', 'token'));
+
+$container['router']->addRoute('project/:project_id/task/:task_id/activity', 'activity', 'task', array('project_id', 'task_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/screenshot', 'file', 'screenshot', array('project_id', 'task_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/upload', 'file', 'create', array('project_id', 'task_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/comment', 'comment', 'create', array('project_id', 'task_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/link', 'tasklink', 'create', array('project_id', 'task_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/transitions', 'task', 'transitions', array('project_id', 'task_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/analytics', 'task', 'analytics', array('project_id', 'task_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/remove', 'task', 'remove', array('project_id', 'task_id'));
+
+$container['router']->addRoute('project/:project_id/task/:task_id/edit', 'taskmodification', 'edit', array('project_id', 'task_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/description', 'taskmodification', 'description', array('project_id', 'task_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/recurrence', 'taskmodification', 'recurrence', array('project_id', 'task_id'));
+
+$container['router']->addRoute('project/:project_id/task/:task_id/close', 'taskstatus', 'close', array('task_id', 'project_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/open', 'taskstatus', 'open', array('task_id', 'project_id'));
+
+$container['router']->addRoute('project/:project_id/task/:task_id/duplicate', 'taskduplication', 'duplicate', array('task_id', 'project_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/copy', 'taskduplication', 'copy', array('task_id', 'project_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/copy/:dst_project_id', 'taskduplication', 'copy', array('task_id', 'project_id', 'dst_project_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/move', 'taskduplication', 'move', array('task_id', 'project_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/move/:dst_project_id', 'taskduplication', 'move', array('task_id', 'project_id', 'dst_project_id'));
+
+// Board routes
+$container['router']->addRoute('board/:project_id', 'board', 'show', array('project_id'));
+$container['router']->addRoute('b/:project_id', 'board', 'show', array('project_id'));
+$container['router']->addRoute('public/board/:token', 'board', 'readonly', array('token'));
+
+// Calendar routes
+$container['router']->addRoute('calendar/:project_id', 'calendar', 'show', array('project_id'));
+$container['router']->addRoute('c/:project_id', 'calendar', 'show', array('project_id'));
+
+// Listing routes
+$container['router']->addRoute('list/:project_id', 'listing', 'show', array('project_id'));
+$container['router']->addRoute('l/:project_id', 'listing', 'show', array('project_id'));
+
+// Gantt routes
+$container['router']->addRoute('gantt/:project_id', 'gantt', 'project', array('project_id'));
+$container['router']->addRoute('gantt/:project_id/sort/:sorting', 'gantt', 'project', array('project_id', 'sorting'));
+
+// Subtask routes
+$container['router']->addRoute('project/:project_id/task/:task_id/subtask/create', 'subtask', 'create', array('project_id', 'task_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/subtask/:subtask_id/remove', 'subtask', 'confirm', array('project_id', 'task_id', 'subtask_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/subtask/:subtask_id/edit', 'subtask', 'edit', array('project_id', 'task_id', 'subtask_id'));
+
+// Feed routes
+$container['router']->addRoute('feed/project/:token', 'feed', 'project', array('token'));
+$container['router']->addRoute('feed/user/:token', 'feed', 'user', array('token'));
+
+// Ical routes
+$container['router']->addRoute('ical/project/:token', 'ical', 'project', array('token'));
+$container['router']->addRoute('ical/user/:token', 'ical', 'user', array('token'));
+
+// Auth routes
+$container['router']->addRoute('oauth/google', 'oauth', 'google');
+$container['router']->addRoute('oauth/github', 'oauth', 'github');
+$container['router']->addRoute('oauth/gitlab', 'oauth', 'gitlab');
+$container['router']->addRoute('login', 'auth', 'login');
+$container['router']->addRoute('logout', 'auth', 'logout');