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

Update kanboard v1.0.9

This commit is contained in:
mbugeia 2014-11-23 20:13:38 +01:00
parent edc1a3fb31
commit 400ba25516
319 changed files with 10599 additions and 15353 deletions

57
sources/.gitignore vendored
View file

@ -1,57 +0,0 @@
# Compiled source #
###################
*.com
*.class
*.dll
*.exe
*.o
*.so
*.pyc
# Packages #
############
# it's better to unpack these files and commit the raw source
# git has its own built in compression methods
*.7z
*.dmg
*.gz
*.iso
*.jar
*.rar
*.tar
*.zip
# Logs and databases #
######################
*.log
*.sql
*.sqlite
*.sqlite-journal
# IDE generated files #
######################
.buildpath
.project
/.settings/
.idea
# OS generated files #
######################
.DS_Store
ehthumbs.db
Icon?
Thumbs.db
*.swp
.*.swp
*~
*.lock
*.out
# Vagrant #
###########
.vagrant
# App specific #
################
#config.php
#data/files

View file

@ -1,53 +0,0 @@
filter:
excluded_paths:
- 'vendor/*'
- 'tests/*'
- 'app/Templates/*'
paths: { }
tools:
php_sim:
enabled: true
min_mass: 16
filter:
excluded_paths:
- 'vendor/*'
- 'tests/*'
- 'app/Templates/*'
paths: { }
php_pdepend:
enabled: true
configuration_file: null
suffixes:
- php
excluded_dirs: { }
filter:
excluded_paths:
- 'vendor/*'
- 'tests/*'
- 'app/Templates/*'
paths: { }
php_analyzer:
enabled: true
extensions:
- php
dependency_paths: { }
filter:
excluded_paths:
- 'vendor/*'
- 'tests/*'
- 'app/Templates/*'
paths: { }
path_configs: { }
php_changetracking:
enabled: true
bug_patterns:
- '\bfix(?:es|ed)?\b'
feature_patterns:
- '\badd(?:s|ed)?\b'
- '\bimplement(?:s|ed)?\b'
filter:
excluded_paths:
- 'vendor/*'
- 'tests/*'
- 'app/Templates/*'
paths: { }

View file

@ -1,10 +0,0 @@
language: php
php:
- "5.6"
- "5.5"
- "5.4"
- "5.3"
before_script: wget https://phar.phpunit.de/phpunit.phar
script: php phpunit.phar -c tests/units.sqlite.xml

View file

@ -1,165 +0,0 @@
Kanboard
========
Kanboard is a simple visual task board web application.
Official website: <http://kanboard.net>
- Inspired by the [Kanban methodology](http://en.wikipedia.org/wiki/Kanban)
- Get a visual and clear overview of your project
- Multiple boards with the ability to drag and drop tasks
- Minimalist software, focus only on essential features (Less is more)
- Open source and self-hosted
- Super simple installation
[![Build Status](https://travis-ci.org/fguillot/kanboard.svg)](https://travis-ci.org/fguillot/kanboard)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/fguillot/kanboard/badges/quality-score.png?s=2b6490781608657cc8c43d02285bfafb4f489528)](https://scrutinizer-ci.com/g/fguillot/kanboard/)
Features
--------
- Multiple boards/projects
- Boards customization, rename/add/remove columns
- Tasks with different colors, categories, sub-tasks, attachments, comments and Markdown support for the description
- Automatic actions based on events
- Users management with a basic privileges separation (administrator or regular user)
- Email notifications
- External authentication: Google, GitHub, LDAP/ActiveDirectory and Reverse-Proxy
- Webhooks to create tasks from an external software
- A basic command line interface
- Host anywhere (shared hosting, VPS, Raspberry Pi or localhost)
- No external dependencies
- **Super easy setup**, copy and paste files and you are done!
- Translations in English, French, Brazilian Portuguese, Spanish, German, Polish, Swedish, Finnish, Italian, Chinese, Russian...
Known bugs and feature requests
-------------------------------
See Issues: <https://github.com/fguillot/kanboard/issues>
License
-------
GNU Affero General Public License version 3: <http://www.gnu.org/licenses/agpl-3.0.txt>
Documentation
-------------
### Using Kanboard
#### Introduction
- [Usage examples](docs/usage-examples.markdown)
#### Working with projects
- [Creating projects](docs/creating-projects.markdown)
- [Editing projects](docs/editing-projects.markdown)
- [Sharing boards and tasks](docs/sharing-projects.markdown)
- [Automatic actions](docs/automatic-actions.markdown)
#### Working with tasks
- [Creating tasks](docs/creating-tasks.markdown)
#### Working with users
- [User management](docs/manage-users.markdown)
#### More
- [Syntax guide](docs/syntax-guide.markdown)
### Technical details
#### Installation
- [Installation instructions](docs/installation.markdown)
- [Upgrade Kanboard to a new version](docs/update.markdown)
- [Installation on Ubuntu](docs/ubuntu-installation.markdown)
- [Installation on Debian](docs/debian-installation.markdown)
- [Installation on Centos](docs/centos-installation.markdown)
- [Installation on Windows Server with IIS](docs/windows-iis-installation.markdown)
- [Example with Nginx + HTTPS + SPDY + PHP-FPM](docs/nginx-ssl-php-fpm.markdown)
#### Database
- [Sqlite database management](docs/sqlite-database.markdown)
- [How to use Mysql](docs/mysql-configuration.markdown)
- [How to use Postgresql](docs/postgresql-configuration.markdown)
#### Authentication
- [LDAP authentication](docs/ldap-authentication.markdown)
- [Google authentication](docs/google-authentication.markdown)
- [GitHub authentication](docs/github-authentication.markdown)
- [Reverse proxy authentication](docs/reverse-proxy-authentication.markdown)
#### Developers and sysadmins
- [Board configuration](docs/board-configuration.markdown)
- [Email configuration](docs/email-configuration.markdown)
- [Command line interface](docs/cli.markdown)
- [Json-RPC API](docs/api-json-rpc.markdown)
- [Webhooks](docs/webhooks.markdown)
- [How to use Kanboard with Vagrant](docs/vagrant.markdown)
### Contributors
- [Translations](docs/translations.markdown)
- [Coding standards](docs/coding-standards.markdown)
- [Running tests](docs/tests.markdown)
The documentation is written in [Markdown](http://en.wikipedia.org/wiki/Markdown).
If you want to improve the documentation, just send a pull-request.
FAQ
---
Go to the official website: <http://kanboard.net/faq>
Authors
-------
Original author: [Frédéric Guillot](http://fredericguillot.com/)
Contributors:
- Alex Butum
- Ashish Kulkarni: https://github.com/ashkulz
- Claudio Lobo
- Cmer: https://github.com/chncsu
- Floaltvater: https://github.com/floaltvater
- Gavlepeter: https://github.com/gavlepeter
- Janne Mäntyharju: https://github.com/JanneMantyharju
- Jesusaplsoft: https://github.com/jesusaplsoft
- Kiswa: https://github.com/kiswa
- Kralo: https://github.com/kralo
- Levlaz: https://github.com/levlaz
- Lim Yuen Hoe: https://github.com/jasonmoofang
- Mathgl67: https://github.com/mathgl67
- Matthieu Keller: https://github.com/maggick
- Mauro Mariño: https://github.com/moromarino
- Maxime: https://github.com/EpocDotFr
- Moraxy: https://github.com/moraxy
- Nala Ginrut: https://github.com/NalaGinrut
- Nekohayo: https://github.com/nekohayo
- Nramel: https://github.com/nramel
- Null-Kelvin: https://github.com/Null-Kelvin
- Olivier Maridat: https://github.com/oliviermaridat
- Poikilotherm: https://github.com/poikilotherm
- Rafaelrossa: https://github.com/rafaelrossa
- Raphaël Doursenaud: https://github.com/rdoursenaud
- Rzeka: https://github.com/rzeka
- Sebastien pacilly: https://github.com/spacilly
- Sylvain Veyrié: https://github.com/turb
- Toomyem: https://github.com/Toomyem
- Tony G. Bolaño: https://github.com/tonybolanyo
- Torsten: https://github.com/misterfu
- Troloo: https://github.com/troloo
- Typz: https://github.com/Typz
- Vedovator: https://github.com/vedovator
- Ybarc: https://github.com/ybarc
There is also many people who have reported bugs or proposed awesome ideas.

29
sources/Vagrantfile vendored
View file

@ -1,29 +0,0 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
VAGRANTFILE_API_VERSION = "2"
$script = <<SCRIPT
# install packages
apt-get update
apt-get install -y apache2 php5 php5-sqlite php5-ldap php5-xdebug
service apache2 restart
rm -f /var/www/html/index.html
date > /etc/vagrant_provisioned_at
echo "Go to http://localhost:8080/ (admin/admin) !"
SCRIPT
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
# Image
config.vm.box = "ubuntu/trusty64"
config.vm.box_url = "http://cloud-images.ubuntu.com/vagrant/trusty/current/trusty-server-cloudimg-amd64-vagrant-disk1.box"
# Network
config.vm.network :forwarded_port, guest: 80, host: 8080
#config.vm.network "public_network", :bridge => "en0: Wi-Fi (AirPort)"
# Setup
config.vm.provision "shell", inline: $script
config.vm.synced_folder ".", "/var/www/html", owner: "www-data", group: "www-data"
end

View file

@ -3,12 +3,19 @@
namespace Action;
use Core\Listener;
use Core\Registry;
use Core\Tool;
/**
* Base class for automatic actions
*
* @package action
* @author Frederic Guillot
*
* @property \Model\Acl $acl
* @property \Model\Comment $comment
* @property \Model\Task $task
* @property \Model\TaskFinder $taskFinder
*/
abstract class Base implements Listener
{
@ -28,6 +35,22 @@ abstract class Base implements Listener
*/
private $params = array();
/**
* Attached event name
*
* @access protected
* @var string
*/
protected $event_name = '';
/**
* Registry instance
*
* @access protected
* @var \Core\Registry
*/
protected $registry;
/**
* Execute the action
*
@ -56,15 +79,60 @@ abstract class Base implements Listener
*/
abstract public function getEventRequiredParameters();
/**
* Get the compatible events
*
* @abstract
* @access public
* @return array
*/
abstract public function getCompatibleEvents();
/**
* Check if the event data meet the action condition
*
* @access public
* @param array $data Event data dictionary
* @return bool
*/
abstract public function hasRequiredCondition(array $data);
/**
* Constructor
*
* @access public
* @param integer $project_id Project id
* @param \Core\Registry $registry Regsitry instance
* @param integer $project_id Project id
* @param string $event_name Attached event name
*/
public function __construct($project_id)
public function __construct(Registry $registry, $project_id, $event_name)
{
$this->registry = $registry;
$this->project_id = $project_id;
$this->event_name = $event_name;
}
/**
* Return class information
*
* @access public
* @return string
*/
public function __toString()
{
return get_called_class();
}
/**
* Load automatically models
*
* @access public
* @param string $name Model name
* @return mixed
*/
public function __get($name)
{
return Tool::loadModel($this->registry, $name);
}
/**
@ -101,11 +169,34 @@ abstract class Base implements Listener
*/
public function isExecutable(array $data)
{
if (isset($data['project_id']) && $data['project_id'] == $this->project_id && $this->hasRequiredParameters($data)) {
return true;
}
return $this->hasCompatibleEvent() &&
$this->hasRequiredProject($data) &&
$this->hasRequiredParameters($data) &&
$this->hasRequiredCondition($data);
}
return false;
/**
* Check if the event is compatible with the action
*
* @access public
* @param array $data Event data dictionary
* @return bool
*/
public function hasCompatibleEvent()
{
return in_array($this->event_name, $this->getCompatibleEvents());
}
/**
* Check if the event data has the required project
*
* @access public
* @param array $data Event data dictionary
* @return bool
*/
public function hasRequiredProject(array $data)
{
return isset($data['project_id']) && $data['project_id'] == $this->project_id;
}
/**
@ -118,7 +209,9 @@ abstract class Base implements Listener
public function hasRequiredParameters(array $data)
{
foreach ($this->getEventRequiredParameters() as $parameter) {
if (! isset($data[$parameter])) return false;
if (! isset($data[$parameter])) {
return false;
}
}
return true;
@ -139,15 +232,4 @@ abstract class Base implements Listener
return false;
}
/**
* Return class information
*
* @access public
* @return string
*/
public function __toString()
{
return get_called_class();
}
}

View file

@ -0,0 +1,83 @@
<?php
namespace Action;
use Model\GithubWebhook;
/**
* Create automatically a comment from a webhook
*
* @package action
* @author Frederic Guillot
*/
class CommentCreation extends Base
{
/**
* Get the list of compatible events
*
* @access public
* @return array
*/
public function getCompatibleEvents()
{
return array(
GithubWebhook::EVENT_ISSUE_COMMENT,
);
}
/**
* Get the required parameter for the action (defined by the user)
*
* @access public
* @return array
*/
public function getActionRequiredParameters()
{
return array();
}
/**
* Get the required parameter for the event
*
* @access public
* @return array
*/
public function getEventRequiredParameters()
{
return array(
'reference',
'comment',
'user_id',
'task_id',
);
}
/**
* Execute the action (create a new comment)
*
* @access public
* @param array $data Event data dictionary
* @return bool True if the action was executed or false when not executed
*/
public function doAction(array $data)
{
return $this->comment->create(array(
'reference' => $data['reference'],
'comment' => $data['comment'],
'task_id' => $data['task_id'],
'user_id' => $data['user_id'],
));
}
/**
* Check if the event data meet the action condition
*
* @access public
* @param array $data Event data dictionary
* @return bool
*/
public function hasRequiredCondition(array $data)
{
return true;
}
}

View file

@ -13,24 +13,16 @@ use Model\Task;
class TaskAssignCategoryColor extends Base
{
/**
* Task model
*
* @accesss private
* @var \Model\Task
*/
private $task;
/**
* Constructor
* Get the list of compatible events
*
* @access public
* @param integer $project_id Project id
* @param \Model\Task $task Task model instance
* @return array
*/
public function __construct($project_id, Task $task)
public function getCompatibleEvents()
{
parent::__construct($project_id);
$this->task = $task;
return array(
Task::EVENT_CREATE_UPDATE,
);
}
/**
@ -62,7 +54,7 @@ class TaskAssignCategoryColor extends Base
}
/**
* Execute the action
* Execute the action (change the category)
*
* @access public
* @param array $data Event data dictionary
@ -70,16 +62,23 @@ class TaskAssignCategoryColor extends Base
*/
public function doAction(array $data)
{
if ($data['color_id'] == $this->getParam('color_id')) {
$values = array(
'id' => $data['task_id'],
'category_id' => $this->getParam('category_id'),
);
$this->task->update(array(
'id' => $data['task_id'],
'category_id' => $this->getParam('category_id'),
), false);
return $this->task->update($values, false);
}
return true;
}
return false;
/**
* Check if the event data meet the action condition
*
* @access public
* @param array $data Event data dictionary
* @return bool
*/
public function hasRequiredCondition(array $data)
{
return $data['color_id'] == $this->getParam('color_id');
}
}

View file

@ -0,0 +1,84 @@
<?php
namespace Action;
use Model\GithubWebhook;
/**
* Set a category automatically according to a label
*
* @package action
* @author Frederic Guillot
*/
class TaskAssignCategoryLabel extends Base
{
/**
* Get the list of compatible events
*
* @access public
* @return array
*/
public function getCompatibleEvents()
{
return array(
GithubWebhook::EVENT_ISSUE_LABEL_CHANGE,
);
}
/**
* Get the required parameter for the action (defined by the user)
*
* @access public
* @return array
*/
public function getActionRequiredParameters()
{
return array(
'label' => t('Label'),
'category_id' => t('Category'),
);
}
/**
* Get the required parameter for the event
*
* @access public
* @return string[]
*/
public function getEventRequiredParameters()
{
return array(
'task_id',
'label',
);
}
/**
* Execute the action (change the category)
*
* @access public
* @param array $data Event data dictionary
* @return bool True if the action was executed or false when not executed
*/
public function doAction(array $data)
{
$values = array(
'id' => $data['task_id'],
'category_id' => isset($data['category_id']) ? $data['category_id'] : $this->getParam('category_id'),
);
return $this->task->update($values, false);
}
/**
* Check if the event data meet the action condition
*
* @access public
* @param array $data Event data dictionary
* @return bool
*/
public function hasRequiredCondition(array $data)
{
return $data['label'] == $this->getParam('label');
}
}

View file

@ -13,24 +13,16 @@ use Model\Task;
class TaskAssignColorCategory extends Base
{
/**
* Task model
*
* @accesss private
* @var \Model\Task
*/
private $task;
/**
* Constructor
* Get the list of compatible events
*
* @access public
* @param integer $project_id Project id
* @param \Model\Task $task Task model instance
* @return array
*/
public function __construct($project_id, Task $task)
public function getCompatibleEvents()
{
parent::__construct($project_id);
$this->task = $task;
return array(
Task::EVENT_CREATE_UPDATE,
);
}
/**
@ -62,7 +54,7 @@ class TaskAssignColorCategory extends Base
}
/**
* Execute the action
* Execute the action (change the task color)
*
* @access public
* @param array $data Event data dictionary
@ -70,16 +62,23 @@ class TaskAssignColorCategory extends Base
*/
public function doAction(array $data)
{
if ($data['category_id'] == $this->getParam('category_id')) {
$values = array(
'id' => $data['task_id'],
'color_id' => $this->getParam('color_id'),
);
$this->task->update(array(
'id' => $data['task_id'],
'color_id' => $this->getParam('color_id'),
), false);
return $this->task->update($values, false);
}
return true;
}
return false;
/**
* Check if the event data meet the action condition
*
* @access public
* @param array $data Event data dictionary
* @return bool
*/
public function hasRequiredCondition(array $data)
{
return $data['category_id'] == $this->getParam('category_id');
}
}

View file

@ -13,24 +13,17 @@ use Model\Task;
class TaskAssignColorUser extends Base
{
/**
* Task model
*
* @accesss private
* @var \Model\Task
*/
private $task;
/**
* Constructor
* Get the list of compatible events
*
* @access public
* @param integer $project_id Project id
* @param \Model\Task $task Task model instance
* @return array
*/
public function __construct($project_id, Task $task)
public function getCompatibleEvents()
{
parent::__construct($project_id);
$this->task = $task;
return array(
Task::EVENT_CREATE,
Task::EVENT_ASSIGNEE_CHANGE,
);
}
/**
@ -62,7 +55,7 @@ class TaskAssignColorUser extends Base
}
/**
* Execute the action
* Execute the action (change the task color)
*
* @access public
* @param array $data Event data dictionary
@ -70,16 +63,23 @@ class TaskAssignColorUser extends Base
*/
public function doAction(array $data)
{
if ($data['owner_id'] == $this->getParam('user_id')) {
$values = array(
'id' => $data['task_id'],
'color_id' => $this->getParam('color_id'),
);
$this->task->update(array(
'id' => $data['task_id'],
'color_id' => $this->getParam('color_id'),
), false);
return $this->task->update($values, false);
}
return true;
}
return false;
/**
* Check if the event data meet the action condition
*
* @access public
* @param array $data Event data dictionary
* @return bool
*/
public function hasRequiredCondition(array $data)
{
return $data['owner_id'] == $this->getParam('user_id');
}
}

View file

@ -3,7 +3,6 @@
namespace Action;
use Model\Task;
use Model\Acl;
/**
* Assign a task to the logged user
@ -14,34 +13,17 @@ use Model\Acl;
class TaskAssignCurrentUser extends Base
{
/**
* Task model
*
* @accesss private
* @var \Model\Task
*/
private $task;
/**
* Acl model
*
* @accesss private
* @var \Model\Acl
*/
private $acl;
/**
* Constructor
* Get the list of compatible events
*
* @access public
* @param integer $project_id Project id
* @param \Model\Task $task Task model instance
* @param \Model\Acl $acl Acl model instance
* @return array
*/
public function __construct($project_id, Task $task, Acl $acl)
public function getCompatibleEvents()
{
parent::__construct($project_id);
$this->task = $task;
$this->acl = $acl;
return array(
Task::EVENT_CREATE,
Task::EVENT_MOVE_COLUMN,
);
}
/**
@ -80,16 +62,23 @@ class TaskAssignCurrentUser extends Base
*/
public function doAction(array $data)
{
if ($data['column_id'] == $this->getParam('column_id')) {
$values = array(
'id' => $data['task_id'],
'owner_id' => $this->acl->getUserId(),
);
$this->task->update(array(
'id' => $data['task_id'],
'owner_id' => $this->acl->getUserId(),
), false);
return $this->task->update($values, false);
}
return true;
}
return false;
/**
* Check if the event data meet the action condition
*
* @access public
* @param array $data Event data dictionary
* @return bool
*/
public function hasRequiredCondition(array $data)
{
return $data['column_id'] == $this->getParam('column_id');
}
}

View file

@ -13,24 +13,17 @@ use Model\Task;
class TaskAssignSpecificUser extends Base
{
/**
* Task model
*
* @accesss private
* @var \Model\Task
*/
private $task;
/**
* Constructor
* Get the list of compatible events
*
* @access public
* @param integer $project_id Project id
* @param \Model\Task $task Task model instance
* @return array
*/
public function __construct($project_id, Task $task)
public function getCompatibleEvents()
{
parent::__construct($project_id);
$this->task = $task;
return array(
Task::EVENT_CREATE_UPDATE,
Task::EVENT_MOVE_COLUMN,
);
}
/**
@ -62,7 +55,7 @@ class TaskAssignSpecificUser extends Base
}
/**
* Execute the action
* Execute the action (assign the given user)
*
* @access public
* @param array $data Event data dictionary
@ -70,16 +63,23 @@ class TaskAssignSpecificUser extends Base
*/
public function doAction(array $data)
{
if ($data['column_id'] == $this->getParam('column_id')) {
$values = array(
'id' => $data['task_id'],
'owner_id' => $this->getParam('user_id'),
);
$this->task->update(array(
'id' => $data['task_id'],
'owner_id' => $this->getParam('user_id'),
), false);
return $this->task->update($values, false);
}
return true;
}
return false;
/**
* Check if the event data meet the action condition
*
* @access public
* @param array $data Event data dictionary
* @return bool
*/
public function hasRequiredCondition(array $data)
{
return $data['column_id'] == $this->getParam('column_id');
}
}

View file

@ -0,0 +1,81 @@
<?php
namespace Action;
use Model\GithubWebhook;
/**
* Assign a task to someone
*
* @package action
* @author Frederic Guillot
*/
class TaskAssignUser extends Base
{
/**
* Get the list of compatible events
*
* @access public
* @return array
*/
public function getCompatibleEvents()
{
return array(
GithubWebhook::EVENT_ISSUE_ASSIGNEE_CHANGE,
);
}
/**
* Get the required parameter for the action (defined by the user)
*
* @access public
* @return array
*/
public function getActionRequiredParameters()
{
return array();
}
/**
* Get the required parameter for the event
*
* @access public
* @return string[]
*/
public function getEventRequiredParameters()
{
return array(
'task_id',
'owner_id',
);
}
/**
* Execute the action (assign the given user)
*
* @access public
* @param array $data Event data dictionary
* @return bool True if the action was executed or false when not executed
*/
public function doAction(array $data)
{
$values = array(
'id' => $data['task_id'],
'owner_id' => $data['owner_id'],
);
return $this->task->update($values, false);
}
/**
* Check if the event data meet the action condition
*
* @access public
* @param array $data Event data dictionary
* @return bool
*/
public function hasRequiredCondition(array $data)
{
return true;
}
}

View file

@ -2,6 +2,7 @@
namespace Action;
use Model\GithubWebhook;
use Model\Task;
/**
@ -13,24 +14,18 @@ use Model\Task;
class TaskClose extends Base
{
/**
* Task model
*
* @accesss private
* @var \Model\Task
*/
private $task;
/**
* Constructor
* Get the list of compatible events
*
* @access public
* @param integer $project_id Project id
* @param \Model\Task $task Task model instance
* @return array
*/
public function __construct($project_id, Task $task)
public function getCompatibleEvents()
{
parent::__construct($project_id);
$this->task = $task;
return array(
Task::EVENT_MOVE_COLUMN,
GithubWebhook::EVENT_COMMIT,
GithubWebhook::EVENT_ISSUE_CLOSED,
);
}
/**
@ -41,9 +36,13 @@ class TaskClose extends Base
*/
public function getActionRequiredParameters()
{
return array(
'column_id' => t('Column'),
);
switch ($this->event_name) {
case GithubWebhook::EVENT_COMMIT:
case GithubWebhook::EVENT_ISSUE_CLOSED:
return array();
default:
return array('column_id' => t('Column'));
}
}
/**
@ -54,14 +53,17 @@ class TaskClose extends Base
*/
public function getEventRequiredParameters()
{
return array(
'task_id',
'column_id',
);
switch ($this->event_name) {
case GithubWebhook::EVENT_COMMIT:
case GithubWebhook::EVENT_ISSUE_CLOSED:
return array('task_id');
default:
return array('task_id', 'column_id');
}
}
/**
* Execute the action
* Execute the action (close the task)
*
* @access public
* @param array $data Event data dictionary
@ -69,11 +71,24 @@ class TaskClose extends Base
*/
public function doAction(array $data)
{
if ($data['column_id'] == $this->getParam('column_id')) {
$this->task->close($data['task_id']);
return true;
}
return $this->task->close($data['task_id']);
}
return false;
/**
* Check if the event data meet the action condition
*
* @access public
* @param array $data Event data dictionary
* @return bool
*/
public function hasRequiredCondition(array $data)
{
switch ($this->event_name) {
case GithubWebhook::EVENT_COMMIT:
case GithubWebhook::EVENT_ISSUE_CLOSED:
return true;
default:
return $data['column_id'] == $this->getParam('column_id');
}
}
}

View file

@ -0,0 +1,81 @@
<?php
namespace Action;
use Model\GithubWebhook;
/**
* Create automatically a task from a webhook
*
* @package action
* @author Frederic Guillot
*/
class TaskCreation extends Base
{
/**
* Get the list of compatible events
*
* @access public
* @return array
*/
public function getCompatibleEvents()
{
return array(
GithubWebhook::EVENT_ISSUE_OPENED,
);
}
/**
* Get the required parameter for the action (defined by the user)
*
* @access public
* @return array
*/
public function getActionRequiredParameters()
{
return array();
}
/**
* Get the required parameter for the event
*
* @access public
* @return string[]
*/
public function getEventRequiredParameters()
{
return array(
'reference',
'title',
);
}
/**
* Execute the action (create a new task)
*
* @access public
* @param array $data Event data dictionary
* @return bool True if the action was executed or false when not executed
*/
public function doAction(array $data)
{
return $this->task->create(array(
'project_id' => $data['project_id'],
'title' => $data['title'],
'reference' => $data['reference'],
'description' => $data['description'],
));
}
/**
* Check if the event data meet the action condition
*
* @access public
* @param array $data Event data dictionary
* @return bool
*/
public function hasRequiredCondition(array $data)
{
return true;
}
}

View file

@ -13,24 +13,17 @@ use Model\Task;
class TaskDuplicateAnotherProject extends Base
{
/**
* Task model
*
* @accesss private
* @var \Model\Task
*/
private $task;
/**
* Constructor
* Get the list of compatible events
*
* @access public
* @param integer $project_id Project id
* @param \Model\Task $task Task model instance
* @return array
*/
public function __construct($project_id, Task $task)
public function getCompatibleEvents()
{
parent::__construct($project_id);
$this->task = $task;
return array(
Task::EVENT_MOVE_COLUMN,
Task::EVENT_CLOSE,
);
}
/**
@ -63,7 +56,7 @@ class TaskDuplicateAnotherProject extends Base
}
/**
* Execute the action
* Execute the action (duplicate the task to another project)
*
* @access public
* @param array $data Event data dictionary
@ -71,14 +64,20 @@ class TaskDuplicateAnotherProject extends Base
*/
public function doAction(array $data)
{
if ($data['column_id'] == $this->getParam('column_id') && $data['project_id'] != $this->getParam('project_id')) {
$task = $this->taskFinder->getById($data['task_id']);
$this->task->duplicateToAnotherProject($this->getParam('project_id'), $task);
return true;
}
$task = $this->task->getById($data['task_id']);
$this->task->duplicateToAnotherProject($this->getParam('project_id'), $task);
return true;
}
return false;
/**
* Check if the event data meet the action condition
*
* @access public
* @param array $data Event data dictionary
* @return bool
*/
public function hasRequiredCondition(array $data)
{
return $data['column_id'] == $this->getParam('column_id') && $data['project_id'] != $this->getParam('project_id');
}
}

View file

@ -13,24 +13,17 @@ use Model\Task;
class TaskMoveAnotherProject extends Base
{
/**
* Task model
*
* @accesss private
* @var \Model\Task
*/
private $task;
/**
* Constructor
* Get the list of compatible events
*
* @access public
* @param integer $project_id Project id
* @param \Model\Task $task Task model instance
* @return array
*/
public function __construct($project_id, Task $task)
public function getCompatibleEvents()
{
parent::__construct($project_id);
$this->task = $task;
return array(
Task::EVENT_MOVE_COLUMN,
Task::EVENT_CLOSE,
);
}
/**
@ -63,7 +56,7 @@ class TaskMoveAnotherProject extends Base
}
/**
* Execute the action
* Execute the action (move the task to another project)
*
* @access public
* @param array $data Event data dictionary
@ -71,14 +64,20 @@ class TaskMoveAnotherProject extends Base
*/
public function doAction(array $data)
{
if ($data['column_id'] == $this->getParam('column_id') && $data['project_id'] != $this->getParam('project_id')) {
$task = $this->taskFinder->getById($data['task_id']);
$this->task->moveToAnotherProject($this->getParam('project_id'), $task);
return true;
}
$task = $this->task->getById($data['task_id']);
$this->task->moveToAnotherProject($this->getParam('project_id'), $task);
return true;
}
return false;
/**
* Check if the event data meet the action condition
*
* @access public
* @param array $data Event data dictionary
* @return bool
*/
public function hasRequiredCondition(array $data)
{
return $data['column_id'] == $this->getParam('column_id') && $data['project_id'] != $this->getParam('project_id');
}
}

View file

@ -0,0 +1,73 @@
<?php
namespace Action;
use Model\GithubWebhook;
/**
* Open automatically a task
*
* @package action
* @author Frederic Guillot
*/
class TaskOpen extends Base
{
/**
* Get the list of compatible events
*
* @access public
* @return array
*/
public function getCompatibleEvents()
{
return array(
GithubWebhook::EVENT_ISSUE_REOPENED,
);
}
/**
* Get the required parameter for the action (defined by the user)
*
* @access public
* @return array
*/
public function getActionRequiredParameters()
{
return array();
}
/**
* Get the required parameter for the event
*
* @access public
* @return string[]
*/
public function getEventRequiredParameters()
{
return array('task_id');
}
/**
* Execute the action (close the task)
*
* @access public
* @param array $data Event data dictionary
* @return bool True if the action was executed or false when not executed
*/
public function doAction(array $data)
{
return $this->task->open($data['task_id']);
}
/**
* Check if the event data meet the action condition
*
* @access public
* @param array $data Event data dictionary
* @return bool
*/
public function hasRequiredCondition(array $data)
{
return true;
}
}

View file

@ -3,6 +3,7 @@
namespace Auth;
use Model\User;
use Core\Request;
/**
* Database authentication
@ -40,8 +41,8 @@ class Database extends Base
$this->lastLogin->create(
self::AUTH_NAME,
$user['id'],
$this->user->getIpAddress(),
$this->user->getUserAgent()
Request::getIpAddress(),
Request::getUserAgent()
);
return true;

View file

@ -4,6 +4,7 @@ namespace Auth;
require __DIR__.'/../../vendor/OAuth/bootstrap.php';
use Core\Request;
use OAuth\Common\Storage\Session;
use OAuth\Common\Consumer\Credentials;
use OAuth\Common\Http\Uri\UriFactory;
@ -44,8 +45,8 @@ class GitHub extends Base
$this->lastLogin->create(
self::AUTH_NAME,
$user['id'],
$this->user->getIpAddress(),
$this->user->getUserAgent()
Request::getIpAddress(),
Request::getUserAgent()
);
return true;

View file

@ -4,6 +4,7 @@ namespace Auth;
require __DIR__.'/../../vendor/OAuth/bootstrap.php';
use Core\Request;
use OAuth\Common\Storage\Session;
use OAuth\Common\Consumer\Credentials;
use OAuth\Common\Http\Uri\UriFactory;
@ -45,8 +46,8 @@ class Google extends Base
$this->lastLogin->create(
self::AUTH_NAME,
$user['id'],
$this->user->getIpAddress(),
$this->user->getUserAgent()
Request::getIpAddress(),
Request::getUserAgent()
);
return true;

View file

@ -2,6 +2,8 @@
namespace Auth;
use Core\Request;
/**
* LDAP model
*
@ -58,8 +60,8 @@ class Ldap extends Base
$this->lastLogin->create(
self::AUTH_NAME,
$user['id'],
$this->user->getIpAddress(),
$this->user->getUserAgent()
Request::getIpAddress(),
Request::getUserAgent()
);
return true;

View file

@ -2,6 +2,7 @@
namespace Auth;
use Core\Request;
use Core\Security;
use Core\Tool;
@ -107,8 +108,8 @@ class RememberMe extends Base
$this->lastLogin->create(
self::AUTH_NAME,
$this->acl->getUserId(),
$this->user->getIpAddress(),
$this->user->getUserAgent()
Request::getIpAddress(),
Request::getUserAgent()
);
return true;

View file

@ -2,6 +2,7 @@
namespace Auth;
use Core\Request;
use Core\Security;
/**
@ -44,8 +45,8 @@ class ReverseProxy extends Base
$this->lastLogin->create(
self::AUTH_NAME,
$user['id'],
$this->user->getIpAddress(),
$this->user->getUserAgent()
Request::getIpAddress(),
Request::getUserAgent()
);
return true;

View file

@ -17,7 +17,7 @@ class Action extends Base
*/
public function index()
{
$project = $this->getProject();
$project = $this->getProjectManagement();
$this->response->html($this->projectLayout('action_index', array(
'values' => array('project_id' => $project['id']),
@ -27,9 +27,9 @@ class Action extends Base
'available_events' => $this->action->getAvailableEvents(),
'available_params' => $this->action->getAllActionParameters(),
'columns_list' => $this->board->getColumnsList($project['id']),
'users_list' => $this->project->getUsersList($project['id']),
'users_list' => $this->projectPermission->getUsersList($project['id']),
'projects_list' => $this->project->getList(false),
'colors_list' => $this->task->getColors(),
'colors_list' => $this->color->getList(),
'categories_list' => $this->category->getList($project['id']),
'menu' => 'projects',
'title' => t('Automatic actions')
@ -37,23 +37,59 @@ class Action extends Base
}
/**
* Define action parameters (step 2)
* Choose the event according to the action (step 2)
*
* @access public
*/
public function event()
{
$project = $this->getProjectManagement();
$values = $this->request->getValues();
if (empty($values['action_name']) || empty($values['project_id'])) {
$this->response->redirect('?controller=action&action=index&project_id='.$project['id']);
}
$this->response->html($this->projectLayout('action_event', array(
'values' => $values,
'project' => $project,
'events' => $this->action->getCompatibleEvents($values['action_name']),
'menu' => 'projects',
'title' => t('Automatic actions')
)));
}
/**
* Define action parameters (step 3)
*
* @access public
*/
public function params()
{
$project = $this->getProject();
$project = $this->getProjectManagement();
$values = $this->request->getValues();
$action = $this->action->load($values['action_name'], $values['project_id']);
if (empty($values['action_name']) || empty($values['project_id']) || empty($values['event_name'])) {
$this->response->redirect('?controller=action&action=index&project_id='.$project['id']);
}
$action = $this->action->load($values['action_name'], $values['project_id'], $values['event_name']);
$action_params = $action->getActionRequiredParameters();
if (empty($action_params)) {
$this->doCreation($project, $values + array('params' => array()));
}
$projects_list = $this->project->getList(false);
unset($projects_list[$project['id']]);
$this->response->html($this->projectLayout('action_params', array(
'values' => $values,
'action_params' => $action->getActionRequiredParameters(),
'action_params' => $action_params,
'columns_list' => $this->board->getColumnsList($project['id']),
'users_list' => $this->project->getUsersList($project['id']),
'projects_list' => $this->project->getList(false),
'colors_list' => $this->task->getColors(),
'users_list' => $this->projectPermission->getUsersList($project['id']),
'projects_list' => $projects_list,
'colors_list' => $this->color->getList(),
'categories_list' => $this->category->getList($project['id']),
'project' => $project,
'menu' => 'projects',
@ -68,9 +104,18 @@ class Action extends Base
*/
public function create()
{
$project = $this->getProject();
$values = $this->request->getValues();
$this->doCreation($this->getProjectManagement(), $this->request->getValues());
}
/**
* Save the action
*
* @access private
* @param array $project Project properties
* @param array $values Form values
*/
private function doCreation(array $project, array $values)
{
list($valid,) = $this->action->validateCreation($values);
if ($valid) {
@ -93,7 +138,7 @@ class Action extends Base
*/
public function confirm()
{
$project = $this->getProject();
$project = $this->getProjectManagement();
$this->response->html($this->projectLayout('action_remove', array(
'action' => $this->action->getById($this->request->getIntegerParam('action_id')),
@ -113,6 +158,7 @@ class Action extends Base
public function remove()
{
$this->checkCSRFParam();
$project = $this->getProjectManagement();
$action = $this->action->getById($this->request->getIntegerParam('action_id'));
if ($action && $this->action->remove($action['id'])) {
@ -121,6 +167,6 @@ class Action extends Base
$this->session->flashError(t('Unable to remove this action.'));
}
$this->response->redirect('?controller=action&action=index&project_id='.$action['project_id']);
$this->response->redirect('?controller=action&action=index&project_id='.$project['id']);
}
}

View file

@ -13,17 +13,21 @@ use Model\Project as ProjectModel;
class App extends Base
{
/**
* Redirect to the project creation page or the board controller
* Dashboard for the current user
*
* @access public
*/
public function index()
{
if ($this->project->countByStatus(ProjectModel::ACTIVE)) {
$this->response->redirect('?controller=board');
}
else {
$this->redirectNoProject();
}
$user_id = $this->acl->getUserId();
$projects = $this->projectPermission->getAllowedProjects($user_id);
$this->response->html($this->template->layout('app_index', array(
'board_selector' => $projects,
'events' => $this->projectActivity->getProjects(array_keys($projects), 10),
'tasks' => $this->taskFinder->getAllTasksByUser($user_id),
'menu' => 'dashboard',
'title' => t('Dashboard'),
)));
}
}

View file

@ -5,7 +5,6 @@ namespace Controller;
use Core\Tool;
use Core\Registry;
use Core\Security;
use Core\Translator;
use Model\LastLogin;
/**
@ -19,17 +18,24 @@ use Model\LastLogin;
* @property \Model\Action $action
* @property \Model\Board $board
* @property \Model\Category $category
* @property \Model\Color $color
* @property \Model\Comment $comment
* @property \Model\Config $config
* @property \Model\File $file
* @property \Model\LastLogin $lastLogin
* @property \Model\Notification $notification
* @property \Model\Project $project
* @property \Model\ProjectPermission $projectPermission
* @property \Model\SubTask $subTask
* @property \Model\Task $task
* @property \Model\TaskHistory $taskHistory
* @property \Model\TaskExport $taskExport
* @property \Model\TaskFinder $taskFinder
* @property \Model\TaskPermission $taskPermission
* @property \Model\TaskValidator $taskValidator
* @property \Model\CommentHistory $commentHistory
* @property \Model\SubtaskHistory $subtaskHistory
* @property \Model\TimeTracking $timeTracking
* @property \Model\User $user
* @property \Model\Webhook $webhook
*/
@ -112,19 +118,22 @@ abstract class Base
$this->response->csp(array('style-src' => "'self' 'unsafe-inline'"));
$this->response->nosniff();
$this->response->xss();
$this->response->hsts();
$this->response->xframe();
// Load translations
$language = $this->config->get('language', 'en_US');
if ($language !== 'en_US') Translator::load($language);
// Allow the public board iframe inclusion
if ($action !== 'readonly') {
$this->response->xframe();
}
// Set timezone
date_default_timezone_set($this->config->get('timezone', 'UTC'));
if (ENABLE_HSTS) {
$this->response->hsts();
}
$this->config->setupTranslations();
$this->config->setupTimezone();
// Authentication
if (! $this->authentication->isAuthenticated($controller, $action)) {
$this->response->redirect('?controller=user&action=login');
$this->response->redirect('?controller=user&action=login&redirect_query='.urlencode($this->request->getQueryString()));
}
// Check if the user is allowed to see this page
@ -144,13 +153,11 @@ abstract class Base
private function attachEvents()
{
$models = array(
'projectActivity', // Order is important
'action',
'project',
'webhook',
'notification',
'taskHistory',
'commentHistory',
'subtaskHistory',
);
foreach ($models as $model) {
@ -206,11 +213,8 @@ abstract class Base
*/
protected function checkProjectPermissions($project_id)
{
if ($this->acl->isRegularUser()) {
if ($project_id > 0 && ! $this->project->isUserAllowed($project_id, $this->acl->getUserId())) {
$this->forbidden();
}
if ($this->acl->isRegularUser() && ! $this->projectPermission->isUserAllowed($project_id, $this->acl->getUserId())) {
$this->forbidden();
}
}
@ -235,6 +239,10 @@ abstract class Base
*/
protected function taskLayout($template, array $params)
{
if (isset($params['task']) && $this->taskPermission->canRemoveTask($params['task']) === false) {
$params['hide_remove_menu'] = true;
}
$content = $this->template->load($template, $params);
$params['task_content_for_layout'] = $content;
@ -253,6 +261,7 @@ abstract class Base
{
$content = $this->template->load($template, $params);
$params['project_content_for_layout'] = $content;
$params['menu'] = 'projects';
return $this->template->layout('project_layout', $params);
}
@ -265,7 +274,7 @@ abstract class Base
*/
protected function getTask()
{
$task = $this->task->getById($this->request->getIntegerParam('task_id'), true);
$task = $this->taskFinder->getDetails($this->request->getIntegerParam('task_id'));
if (! $task) {
$this->notfound();
@ -297,4 +306,25 @@ abstract class Base
return $project;
}
/**
* Common method to get a project with administration rights
*
* @access protected
* @return array
*/
protected function getProjectManagement()
{
$project = $this->project->getById($this->request->getIntegerParam('project_id'));
if (! $project) {
$this->notfound();
}
if ($this->acl->isRegularUser() && ! $this->projectPermission->adminAllowed($project['id'], $this->acl->getUserId())) {
$this->forbidden();
}
return $project;
}
}

View file

@ -15,35 +15,22 @@ use Core\Security;
class Board extends Base
{
/**
* Move a column up
* Move a column down or up
*
* @access public
*/
public function moveUp()
public function moveColumn()
{
$this->checkCSRFParam();
$project_id = $this->request->getIntegerParam('project_id');
$project = $this->getProjectManagement();
$column_id = $this->request->getIntegerParam('column_id');
$direction = $this->request->getStringParam('direction');
$this->board->moveUp($project_id, $column_id);
if ($direction === 'up' || $direction === 'down') {
$this->board->{'move'.$direction}($project['id'], $column_id);
}
$this->response->redirect('?controller=board&action=edit&project_id='.$project_id);
}
/**
* Move a column down
*
* @access public
*/
public function moveDown()
{
$this->checkCSRFParam();
$project_id = $this->request->getIntegerParam('project_id');
$column_id = $this->request->getIntegerParam('column_id');
$this->board->moveDown($project_id, $column_id);
$this->response->redirect('?controller=board&action=edit&project_id='.$project_id);
$this->response->redirect('?controller=board&action=edit&project_id='.$project['id']);
}
/**
@ -55,11 +42,11 @@ class Board extends Base
{
$task = $this->getTask();
$project = $this->project->getById($task['project_id']);
$projects = $this->project->getAvailableList($this->acl->getUserId());
$projects = $this->projectPermission->getAllowedProjects($this->acl->getUserId());
$params = array(
'errors' => array(),
'values' => $task,
'users_list' => $this->project->getUsersList($project['id']),
'users_list' => $this->projectPermission->getUsersList($project['id']),
'projects' => $projects,
'current_project_id' => $project['id'],
'current_project_name' => $project['name'],
@ -88,7 +75,7 @@ class Board extends Base
$values = $this->request->getValues();
$this->checkProjectPermissions($values['project_id']);
list($valid,) = $this->task->validateAssigneeModification($values);
list($valid,) = $this->taskValidator->validateAssigneeModification($values);
if ($valid && $this->task->update($values)) {
$this->session->flash(t('Task updated successfully.'));
@ -109,7 +96,7 @@ class Board extends Base
{
$task = $this->getTask();
$project = $this->project->getById($task['project_id']);
$projects = $this->project->getAvailableList($this->acl->getUserId());
$projects = $this->projectPermission->getAllowedProjects($this->acl->getUserId());
$params = array(
'errors' => array(),
'values' => $task,
@ -142,7 +129,7 @@ class Board extends Base
$values = $this->request->getValues();
$this->checkProjectPermissions($values['project_id']);
list($valid,) = $this->task->validateCategoryModification($values);
list($valid,) = $this->taskValidator->validateCategoryModification($values);
if ($valid && $this->task->update($values)) {
$this->session->flash(t('Task updated successfully.'));
@ -177,8 +164,8 @@ class Board extends Base
'categories' => $this->category->getList($project['id'], false),
'title' => $project['name'],
'no_layout' => true,
'auto_refresh' => true,
'not_editable' => true,
'board_public_refresh_interval' => $this->config->get('board_public_refresh_interval'),
)));
}
@ -194,7 +181,7 @@ class Board extends Base
$project_id = $last_seen_project_id ?: $favorite_project_id;
if (! $project_id) {
$projects = $this->project->getAvailableList($this->acl->getUserId());
$projects = $this->projectPermission->getAllowedProjects($this->acl->getUserId());
if (empty($projects)) {
@ -220,7 +207,7 @@ class Board extends Base
public function show($project_id = 0)
{
$project = $this->getProject($project_id);
$projects = $this->project->getAvailableList($this->acl->getUserId());
$projects = $this->projectPermission->getAllowedProjects($this->acl->getUserId());
$board_selector = $projects;
unset($board_selector[$project['id']]);
@ -228,16 +215,18 @@ class Board extends Base
$this->user->storeLastSeenProjectId($project['id']);
$this->response->html($this->template->layout('board_index', array(
'users' => $this->project->getUsersList($project['id'], true, true),
'users' => $this->projectPermission->getUsersList($project['id'], true, true),
'filters' => array('user_id' => UserModel::EVERYBODY_ID),
'projects' => $projects,
'current_project_id' => $project['id'],
'current_project_name' => $projects[$project['id']],
'current_project_name' => $project['name'],
'board' => $this->board->get($project['id']),
'categories' => $this->category->getList($project['id'], true, true),
'menu' => 'boards',
'title' => $projects[$project['id']],
'title' => $project['name'],
'board_selector' => $board_selector,
'board_private_refresh_interval' => $this->config->get('board_private_refresh_interval'),
'board_highlight_period' => $this->config->get('board_highlight_period'),
)));
}
@ -248,7 +237,7 @@ class Board extends Base
*/
public function edit()
{
$project = $this->getProject();
$project = $this->getProjectManagement();
$columns = $this->board->getColumns($project['id']);
$values = array();
@ -274,7 +263,7 @@ class Board extends Base
*/
public function update()
{
$project = $this->getProject();
$project = $this->getProjectManagement();
$columns = $this->board->getColumns($project['id']);
$data = $this->request->getValues();
$values = $columns_list = array();
@ -315,7 +304,7 @@ class Board extends Base
*/
public function add()
{
$project = $this->getProject();
$project = $this->getProjectManagement();
$columns = $this->board->getColumnsList($project['id']);
$data = $this->request->getValues();
$values = array();
@ -348,13 +337,27 @@ class Board extends Base
}
/**
* Confirmation dialog before removing a column
* Remove a column
*
* @access public
*/
public function confirm()
public function remove()
{
$project = $this->getProject();
$project = $this->getProjectManagement();
if ($this->request->getStringParam('remove') === 'yes') {
$this->checkCSRFParam();
$column = $this->board->getColumn($this->request->getIntegerParam('column_id'));
if ($column && $this->board->removeColumn($column['id'])) {
$this->session->flash(t('Column removed successfully.'));
} else {
$this->session->flashError(t('Unable to remove this column.'));
}
$this->response->redirect('?controller=board&action=edit&project_id='.$project['id']);
}
$this->response->html($this->projectLayout('board_remove', array(
'column' => $this->board->getColumn($this->request->getIntegerParam('column_id')),
@ -364,25 +367,6 @@ class Board extends Base
)));
}
/**
* Remove a column
*
* @access public
*/
public function remove()
{
$this->checkCSRFParam();
$column = $this->board->getColumn($this->request->getIntegerParam('column_id'));
if ($column && $this->board->removeColumn($column['id'])) {
$this->session->flash(t('Column removed successfully.'));
} else {
$this->session->flashError(t('Unable to remove this column.'));
}
$this->response->redirect('?controller=board&action=edit&project_id='.$column['project_id']);
}
/**
* Save the board (Ajax request made by the drag and drop)
*
@ -394,7 +378,7 @@ class Board extends Base
if ($project_id > 0 && $this->request->isAjax()) {
if (! $this->project->isUserAllowed($project_id, $this->acl->getUserId())) {
if (! $this->projectPermission->isUserAllowed($project_id, $this->acl->getUserId())) {
$this->response->status(401);
}
@ -407,6 +391,8 @@ class Board extends Base
'current_project_id' => $project_id,
'board' => $this->board->get($project_id),
'categories' => $this->category->getList($project_id, false),
'board_private_refresh_interval' => $this->config->get('board_private_refresh_interval'),
'board_highlight_period' => $this->config->get('board_highlight_period'),
)),
201
);
@ -433,7 +419,7 @@ class Board extends Base
$project_id = $this->request->getIntegerParam('project_id');
$timestamp = $this->request->getIntegerParam('timestamp');
if ($project_id > 0 && ! $this->project->isUserAllowed($project_id, $this->acl->getUserId())) {
if ($project_id > 0 && ! $this->projectPermission->isUserAllowed($project_id, $this->acl->getUserId())) {
$this->response->text('Not Authorized', 401);
}
@ -443,6 +429,8 @@ class Board extends Base
'current_project_id' => $project_id,
'board' => $this->board->get($project_id),
'categories' => $this->category->getList($project_id, false),
'board_private_refresh_interval' => $this->config->get('board_private_refresh_interval'),
'board_highlight_period' => $this->config->get('board_highlight_period'),
))
);
}

View file

@ -3,7 +3,7 @@
namespace Controller;
/**
* Categories management
* Category management
*
* @package controller
* @author Frederic Guillot
@ -36,7 +36,7 @@ class Category extends Base
*/
public function index()
{
$project = $this->getProject();
$project = $this->getProjectManagement();
$this->response->html($this->projectLayout('category_index', array(
'categories' => $this->category->getList($project['id'], false),
@ -55,7 +55,7 @@ class Category extends Base
*/
public function save()
{
$project = $this->getProject();
$project = $this->getProjectManagement();
$values = $this->request->getValues();
list($valid, $errors) = $this->category->validateCreation($values);
@ -88,7 +88,7 @@ class Category extends Base
*/
public function edit()
{
$project = $this->getProject();
$project = $this->getProjectManagement();
$category = $this->getCategory($project['id']);
$this->response->html($this->projectLayout('category_edit', array(
@ -107,7 +107,7 @@ class Category extends Base
*/
public function update()
{
$project = $this->getProject();
$project = $this->getProjectManagement();
$values = $this->request->getValues();
list($valid, $errors) = $this->category->validateModification($values);
@ -139,7 +139,7 @@ class Category extends Base
*/
public function confirm()
{
$project = $this->getProject();
$project = $this->getProjectManagement();
$category = $this->getCategory($project['id']);
$this->response->html($this->projectLayout('category_remove', array(
@ -158,7 +158,7 @@ class Category extends Base
public function remove()
{
$this->checkCSRFParam();
$project = $this->getProject();
$project = $this->getProjectManagement();
$category = $this->getCategory($project['id']);
if ($this->category->remove($category['id'])) {

View file

@ -11,55 +11,115 @@ namespace Controller;
class Config extends Base
{
/**
* Display the settings page
* Common layout for config views
*
* @access private
* @param string $template Template name
* @param array $params Template parameters
* @return string
*/
private function layout($template, array $params)
{
$params['values'] = $this->config->getAll();
$params['errors'] = array();
$params['menu'] = 'config';
$params['config_content_for_layout'] = $this->template->load($template, $params);
return $this->template->layout('config_layout', $params);
}
/**
* Common method between pages
*
* @access private
* @param string $redirect Action to redirect after saving the form
*/
private function common($redirect)
{
if ($this->request->isPost()) {
$values = $this->request->getValues();
if ($this->config->save($values)) {
$this->config->reload();
$this->session->flash(t('Settings saved successfully.'));
}
else {
$this->session->flashError(t('Unable to save your settings.'));
}
$this->response->redirect('?controller=config&action='.$redirect);
}
}
/**
* Display the about page
*
* @access public
*/
public function index()
{
$this->response->html($this->template->layout('config_index', array(
$this->response->html($this->layout('config_about', array(
'db_size' => $this->config->getDatabaseSize(),
'title' => t('About'),
)));
}
/**
* Display the application settings page
*
* @access public
*/
public function application()
{
$this->common('application');
$this->response->html($this->layout('config_application', array(
'title' => t('Application settings'),
'languages' => $this->config->getLanguages(),
'values' => $this->config->getAll(),
'errors' => array(),
'menu' => 'config',
'title' => t('Settings'),
'timezones' => $this->config->getTimezones(),
'date_formats' => $this->dateParser->getAvailableFormats(),
)));
}
/**
* Display the board settings page
*
* @access public
*/
public function board()
{
$this->common('board');
$this->response->html($this->layout('config_board', array(
'title' => t('Board settings'),
'default_columns' => implode(', ', $this->board->getDefaultColumns()),
)));
}
/**
* Validate and save settings
* Display the webhook settings page
*
* @access public
*/
public function save()
public function webhook()
{
$values = $this->request->getValues();
list($valid, $errors) = $this->config->validateModification($values);
$this->common('webhook');
if ($valid) {
$this->response->html($this->layout('config_webhook', array(
'title' => t('Webhook settings'),
)));
}
if ($this->config->save($values)) {
$this->config->reload();
$this->session->flash(t('Settings saved successfully.'));
} else {
$this->session->flashError(t('Unable to save your settings.'));
}
$this->response->redirect('?controller=config');
}
$this->response->html($this->template->layout('config_index', array(
'db_size' => $this->config->getDatabaseSize(),
'languages' => $this->config->getLanguages(),
'values' => $values,
'errors' => $errors,
'menu' => 'config',
'title' => t('Settings'),
'timezones' => $this->config->getTimezones(),
'default_columns' => implode(', ', $this->board->getDefaultColumns()),
/**
* Display the api settings page
*
* @access public
*/
public function api()
{
$this->response->html($this->layout('config_api', array(
'title' => t('API'),
)));
}
@ -89,15 +149,18 @@ class Config extends Base
}
/**
* Regenerate all application tokens
* Regenerate webhook token
*
* @access public
*/
public function tokens()
public function token()
{
$type = $this->request->getStringParam('type');
$this->checkCSRFParam();
$this->config->regenerateTokens();
$this->session->flash(t('All tokens have been regenerated.'));
$this->response->redirect('?controller=config');
$this->config->regenerateToken($type.'_token');
$this->session->flash(t('Token regenerated.'));
$this->response->redirect('?controller=config&action='.$type);
}
}

View file

@ -3,7 +3,6 @@
namespace Controller;
use Model\Task as TaskModel;
use Core\Translator;
/**
* Project controller
@ -55,7 +54,7 @@ class Project extends Base
$this->response->html($this->projectLayout('project_show', array(
'project' => $project,
'stats' => $this->project->getStats($project['id']),
'menu' => 'projects',
'webhook_token' => $this->config->get('webhook_token'),
'title' => $project['name'],
)));
}
@ -67,12 +66,12 @@ class Project extends Base
*/
public function export()
{
$project = $this->getProject();
$project = $this->getProjectManagement();
$from = $this->request->getStringParam('from');
$to = $this->request->getStringParam('to');
if ($from && $to) {
$data = $this->task->export($project['id'], $from, $to);
$data = $this->taskExport->export($project['id'], $from, $to);
$this->response->forceDownload('Export_'.date('Y_m_d_H_i_S').'.csv');
$this->response->csv($data);
}
@ -86,7 +85,8 @@ class Project extends Base
'to' => $to,
),
'errors' => array(),
'menu' => 'projects',
'date_format' => $this->config->get('application_date_format'),
'date_formats' => $this->dateParser->getAvailableFormats(),
'project' => $project,
'title' => t('Tasks Export')
)));
@ -99,53 +99,28 @@ class Project extends Base
*/
public function share()
{
$project = $this->getProject();
$project = $this->getProjectManagement();
$switch = $this->request->getStringParam('switch');
if ($switch === 'enable' || $switch === 'disable') {
$this->checkCSRFParam();
if ($this->project->{$switch.'PublicAccess'}($project['id'])) {
$this->session->flash(t('Project updated successfully.'));
} else {
$this->session->flashError(t('Unable to update this project.'));
}
$this->response->redirect('?controller=project&action=share&project_id='.$project['id']);
}
$this->response->html($this->projectLayout('project_share', array(
'project' => $project,
'menu' => 'projects',
'title' => t('Public access'),
)));
}
/**
* Enable public access for a project
*
* @access public
*/
public function enablePublic()
{
$this->checkCSRFParam();
$project_id = $this->request->getIntegerParam('project_id');
if ($project_id && $this->project->enablePublicAccess($project_id)) {
$this->session->flash(t('Project updated successfully.'));
} else {
$this->session->flashError(t('Unable to update this project.'));
}
$this->response->redirect('?controller=project&action=share&project_id='.$project_id);
}
/**
* Disable public access for a project
*
* @access public
*/
public function disablePublic()
{
$this->checkCSRFParam();
$project_id = $this->request->getIntegerParam('project_id');
if ($project_id && $this->project->disablePublicAccess($project_id)) {
$this->session->flash(t('Project updated successfully.'));
} else {
$this->session->flashError(t('Unable to update this project.'));
}
$this->response->redirect('?controller=project&action=share&project_id='.$project_id);
}
/**
* Display a form to edit a project
*
@ -153,13 +128,12 @@ class Project extends Base
*/
public function edit()
{
$project = $this->getProject();
$project = $this->getProjectManagement();
$this->response->html($this->projectLayout('project_edit', array(
'errors' => array(),
'values' => $project,
'project' => $project,
'menu' => 'projects',
'title' => t('Edit project')
)));
}
@ -171,7 +145,7 @@ class Project extends Base
*/
public function update()
{
$project = $this->getProject();
$project = $this->getProjectManagement();
$values = $this->request->getValues() + array('is_active' => 0);
list($valid, $errors) = $this->project->validateModification($values);
@ -190,41 +164,63 @@ class Project extends Base
'errors' => $errors,
'values' => $values,
'project' => $project,
'menu' => 'projects',
'title' => t('Edit Project')
)));
}
/**
/**
* Users list for the selected project
*
* @access public
*/
public function users()
{
$project = $this->getProject();
$project = $this->getProjectManagement();
$this->response->html($this->projectLayout('project_users', array(
'project' => $project,
'users' => $this->project->getAllUsers($project['id']),
'menu' => 'projects',
'users' => $this->projectPermission->getAllUsers($project['id']),
'title' => t('Edit project access list')
)));
}
/**
* Allow a specific user for the selected project
* Allow everybody
*
* @access public
*/
public function allowEverybody()
{
$project = $this->getProjectManagement();
$values = $this->request->getValues() + array('is_everybody_allowed' => 0);
list($valid,) = $this->projectPermission->validateProjectModification($values);
if ($valid) {
if ($this->project->update($values)) {
$this->session->flash(t('Project updated successfully.'));
}
else {
$this->session->flashError(t('Unable to update this project.'));
}
}
$this->response->redirect('?controller=project&action=users&project_id='.$project['id']);
}
/**
* Allow a specific user (admin only)
*
* @access public
*/
public function allow()
{
$values = $this->request->getValues();
list($valid,) = $this->project->validateUserAccess($values);
list($valid,) = $this->projectPermission->validateUserModification($values);
if ($valid) {
if ($this->project->allowUser($values['project_id'], $values['user_id'])) {
if ($this->projectPermission->allowUser($values['project_id'], $values['user_id'])) {
$this->session->flash(t('Project updated successfully.'));
}
else {
@ -236,7 +232,7 @@ class Project extends Base
}
/**
* Revoke user access
* Revoke user access (admin only)
*
* @access public
*/
@ -249,11 +245,11 @@ class Project extends Base
'user_id' => $this->request->getIntegerParam('user_id'),
);
list($valid,) = $this->project->validateUserAccess($values);
list($valid,) = $this->projectPermission->validateUserModification($values);
if ($valid) {
if ($this->project->revokeUser($values['project_id'], $values['user_id'])) {
if ($this->projectPermission->revokeUser($values['project_id'], $values['user_id'])) {
$this->session->flash(t('Project updated successfully.'));
}
else {
@ -264,22 +260,6 @@ class Project extends Base
$this->response->redirect('?controller=project&action=users&project_id='.$values['project_id']);
}
/**
* Confirmation dialog before to remove a project
*
* @access public
*/
public function confirmRemove()
{
$project = $this->getProject();
$this->response->html($this->projectLayout('project_remove', array(
'project' => $project,
'menu' => 'projects',
'title' => t('Remove project')
)));
}
/**
* Remove a project
*
@ -287,31 +267,24 @@ class Project extends Base
*/
public function remove()
{
$this->checkCSRFParam();
$project_id = $this->request->getIntegerParam('project_id');
$project = $this->getProjectManagement();
if ($project_id && $this->project->remove($project_id)) {
$this->session->flash(t('Project removed successfully.'));
} else {
$this->session->flashError(t('Unable to remove this project.'));
if ($this->request->getStringParam('remove') === 'yes') {
$this->checkCSRFParam();
if ($this->project->remove($project['id'])) {
$this->session->flash(t('Project removed successfully.'));
} else {
$this->session->flashError(t('Unable to remove this project.'));
}
$this->response->redirect('?controller=project');
}
$this->response->redirect('?controller=project');
}
/**
* Confirmation dialog before to clone a project
*
* @access public
*/
public function confirmDuplicate()
{
$project = $this->getProject();
$this->response->html($this->projectLayout('project_duplicate', array(
$this->response->html($this->projectLayout('project_remove', array(
'project' => $project,
'menu' => 'projects',
'title' => t('Clone this project')
'title' => t('Remove project')
)));
}
@ -323,31 +296,24 @@ class Project extends Base
*/
public function duplicate()
{
$this->checkCSRFParam();
$project_id = $this->request->getIntegerParam('project_id');
$project = $this->getProjectManagement();
if ($project_id && $this->project->duplicate($project_id)) {
$this->session->flash(t('Project cloned successfully.'));
} else {
$this->session->flashError(t('Unable to clone this project.'));
if ($this->request->getStringParam('duplicate') === 'yes') {
$this->checkCSRFParam();
if ($this->project->duplicate($project['id'])) {
$this->session->flash(t('Project cloned successfully.'));
} else {
$this->session->flashError(t('Unable to clone this project.'));
}
$this->response->redirect('?controller=project');
}
$this->response->redirect('?controller=project');
}
/**
* Confirmation dialog before to disable a project
*
* @access public
*/
public function confirmDisable()
{
$project = $this->getProject();
$this->response->html($this->projectLayout('project_disable', array(
$this->response->html($this->projectLayout('project_duplicate', array(
'project' => $project,
'menu' => 'projects',
'title' => t('Project activation')
'title' => t('Clone this project')
)));
}
@ -358,30 +324,23 @@ class Project extends Base
*/
public function disable()
{
$this->checkCSRFParam();
$project_id = $this->request->getIntegerParam('project_id');
$project = $this->getProjectManagement();
if ($project_id && $this->project->disable($project_id)) {
$this->session->flash(t('Project disabled successfully.'));
} else {
$this->session->flashError(t('Unable to disable this project.'));
if ($this->request->getStringParam('disable') === 'yes') {
$this->checkCSRFParam();
if ($this->project->disable($project['id'])) {
$this->session->flash(t('Project disabled successfully.'));
} else {
$this->session->flashError(t('Unable to disable this project.'));
}
$this->response->redirect('?controller=project&action=show&project_id='.$project['id']);
}
$this->response->redirect('?controller=project&action=show&project_id='.$project_id);
}
/**
* Confirmation dialog before to enable a project
*
* @access public
*/
public function confirmEnable()
{
$project = $this->getProject();
$this->response->html($this->projectLayout('project_enable', array(
$this->response->html($this->projectLayout('project_disable', array(
'project' => $project,
'menu' => 'projects',
'title' => t('Project activation')
)));
}
@ -393,20 +352,29 @@ class Project extends Base
*/
public function enable()
{
$this->checkCSRFParam();
$project_id = $this->request->getIntegerParam('project_id');
$project = $this->getProjectManagement();
if ($project_id && $this->project->enable($project_id)) {
$this->session->flash(t('Project activated successfully.'));
} else {
$this->session->flashError(t('Unable to activate this project.'));
if ($this->request->getStringParam('enable') === 'yes') {
$this->checkCSRFParam();
if ($this->project->enable($project['id'])) {
$this->session->flash(t('Project activated successfully.'));
} else {
$this->session->flashError(t('Unable to activate this project.'));
}
$this->response->redirect('?controller=project&action=show&project_id='.$project['id']);
}
$this->response->redirect('?controller=project&action=show&project_id='.$project_id);
$this->response->html($this->projectLayout('project_enable', array(
'project' => $project,
'title' => t('Project activation')
)));
}
/**
* RSS feed for a project
* RSS feed for a project (public)
*
* @access public
*/
@ -421,7 +389,7 @@ class Project extends Base
}
$this->response->xml($this->template->load('project_feed', array(
'events' => $this->project->getActivity($project['id']),
'events' => $this->projectActivity->getProject($project['id']),
'project' => $project,
)));
}
@ -436,7 +404,7 @@ class Project extends Base
$project = $this->getProject();
$this->response->html($this->template->layout('project_activity', array(
'events' => $this->project->getActivity($project['id']),
'events' => $this->projectActivity->getProject($project['id']),
'menu' => 'projects',
'project' => $project,
'title' => t('%s\'s activity', $project['name'])
@ -452,34 +420,39 @@ class Project extends Base
{
$project = $this->getProject();
$search = $this->request->getStringParam('search');
$direction = $this->request->getStringParam('direction', 'DESC');
$order = $this->request->getStringParam('order', 'tasks.id');
$offset = $this->request->getIntegerParam('offset', 0);
$tasks = array();
$nb_tasks = 0;
$limit = 25;
if ($search !== '') {
$filters = array(
array('column' => 'project_id', 'operator' => 'eq', 'value' => $project['id']),
'or' => array(
array('column' => 'title', 'operator' => 'like', 'value' => '%'.$search.'%'),
//array('column' => 'description', 'operator' => 'like', 'value' => '%'.$search.'%'),
)
);
$tasks = $this->task->find($filters);
$nb_tasks = count($tasks);
$tasks = $this->taskFinder->search($project['id'], $search, $offset, $limit, $order, $direction);
$nb_tasks = $this->taskFinder->countSearch($project['id'], $search);
}
$this->response->html($this->template->layout('project_search', array(
'tasks' => $tasks,
'nb_tasks' => $nb_tasks,
'pagination' => array(
'controller' => 'project',
'action' => 'search',
'params' => array('search' => $search, 'project_id' => $project['id']),
'direction' => $direction,
'order' => $order,
'total' => $nb_tasks,
'offset' => $offset,
'limit' => $limit,
),
'values' => array(
'search' => $search,
'controller' => 'project',
'action' => 'search',
'project_id' => $project['id'],
),
'menu' => 'projects',
'project' => $project,
'menu' => 'projects',
'columns' => $this->board->getColumnsList($project['id']),
'categories' => $this->category->getList($project['id'], false),
'title' => $project['name'].($nb_tasks > 0 ? ' ('.$nb_tasks.')' : '')
@ -494,18 +467,27 @@ class Project extends Base
public function tasks()
{
$project = $this->getProject();
$direction = $this->request->getStringParam('direction', 'DESC');
$order = $this->request->getStringParam('order', 'tasks.date_completed');
$offset = $this->request->getIntegerParam('offset', 0);
$limit = 25;
$filters = array(
array('column' => 'project_id', 'operator' => 'eq', 'value' => $project['id']),
array('column' => 'is_active', 'operator' => 'eq', 'value' => TaskModel::STATUS_CLOSED),
);
$tasks = $this->task->find($filters);
$nb_tasks = count($tasks);
$tasks = $this->taskFinder->getClosedTasks($project['id'], $offset, $limit, $order, $direction);
$nb_tasks = $this->taskFinder->countByProjectId($project['id'], array(TaskModel::STATUS_CLOSED));
$this->response->html($this->template->layout('project_tasks', array(
'menu' => 'projects',
'pagination' => array(
'controller' => 'project',
'action' => 'tasks',
'params' => array('project_id' => $project['id']),
'direction' => $direction,
'order' => $order,
'total' => $nb_tasks,
'offset' => $offset,
'limit' => $limit,
),
'project' => $project,
'menu' => 'projects',
'columns' => $this->board->getColumnsList($project['id']),
'categories' => $this->category->getList($project['id'], false),
'tasks' => $tasks,
@ -523,8 +505,9 @@ class Project extends Base
{
$this->response->html($this->template->layout('project_new', array(
'errors' => array(),
'values' => array(),
'menu' => 'projects',
'values' => array(
'is_private' => $this->request->getIntegerParam('private', $this->acl->isRegularUser()),
),
'title' => t('New project')
)));
}
@ -541,7 +524,7 @@ class Project extends Base
if ($valid) {
if ($this->project->create($values)) {
if ($this->project->create($values, $this->acl->getUserId())) {
$this->session->flash(t('Your project have been created successfully.'));
$this->response->redirect('?controller=project');
}
@ -553,7 +536,6 @@ class Project extends Base
$this->response->html($this->template->layout('project_new', array(
'errors' => $errors,
'values' => $values,
'menu' => 'projects',
'title' => t('New Project')
)));
}

View file

@ -39,9 +39,10 @@ class Subtask extends Base
$this->response->html($this->taskLayout('subtask_create', array(
'values' => array(
'task_id' => $task['id'],
'another_subtask' => $this->request->getIntegerParam('another_subtask', 0)
),
'errors' => array(),
'users_list' => $this->project->getUsersList($task['project_id']),
'users_list' => $this->projectPermission->getUsersList($task['project_id']),
'task' => $task,
'menu' => 'tasks',
'title' => t('Add a sub-task')
@ -70,7 +71,7 @@ class Subtask extends Base
}
if (isset($values['another_subtask']) && $values['another_subtask'] == 1) {
$this->response->redirect('?controller=subtask&action=create&task_id='.$task['id']);
$this->response->redirect('?controller=subtask&action=create&task_id='.$task['id'].'&another_subtask=1');
}
$this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'#subtasks');
@ -79,7 +80,7 @@ class Subtask extends Base
$this->response->html($this->taskLayout('subtask_create', array(
'values' => $values,
'errors' => $errors,
'users_list' => $this->project->getUsersList($task['project_id']),
'users_list' => $this->projectPermission->getUsersList($task['project_id']),
'task' => $task,
'menu' => 'tasks',
'title' => t('Add a sub-task')
@ -99,7 +100,7 @@ class Subtask extends Base
$this->response->html($this->taskLayout('subtask_edit', array(
'values' => $subtask,
'errors' => array(),
'users_list' => $this->project->getUsersList($task['project_id']),
'users_list' => $this->projectPermission->getUsersList($task['project_id']),
'status_list' => $this->subTask->getStatusList(),
'subtask' => $subtask,
'task' => $task,
@ -136,7 +137,7 @@ class Subtask extends Base
$this->response->html($this->taskLayout('subtask_edit', array(
'values' => $values,
'errors' => $errors,
'users_list' => $this->project->getUsersList($task['project_id']),
'users_list' => $this->projectPermission->getUsersList($task['project_id']),
'status_list' => $this->subTask->getStatusList(),
'subtask' => $subtask,
'task' => $task,
@ -183,4 +184,27 @@ class Subtask extends Base
$this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'#subtasks');
}
/**
* Change status to the next status: Toto -> In Progress -> Done
*
* @access public
*/
public function toggleStatus()
{
$task = $this->getTask();
$subtask = $this->getSubtask();
$value = array(
'id' => $subtask['id'],
'status' => ($subtask['status'] + 1) % 3,
'task_id' => $task['id'],
);
if (! $this->subTask->update($value)) {
$this->session->flashError(t('Unable to update your sub-task.'));
}
$this->response->redirect('?controller=task&action=show&task_id='.$task['id'].'#subtasks');
}
}

View file

@ -12,40 +12,6 @@ use Model\Project as ProjectModel;
*/
class Task extends Base
{
/**
* Webhook to create a task (useful for external software)
*
* @access public
*/
public function add()
{
$token = $this->request->getStringParam('token');
if ($this->config->get('webhooks_token') !== $token) {
$this->response->text('Not Authorized', 401);
}
$defaultProject = $this->project->getFirst();
$values = array(
'title' => $this->request->getStringParam('title'),
'description' => $this->request->getStringParam('description'),
'color_id' => $this->request->getStringParam('color_id'),
'project_id' => $this->request->getIntegerParam('project_id', $defaultProject['id']),
'owner_id' => $this->request->getIntegerParam('owner_id'),
'column_id' => $this->request->getIntegerParam('column_id'),
'category_id' => $this->request->getIntegerParam('category_id'),
);
list($valid,) = $this->task->validateCreation($values);
if ($valid && $this->task->create($values)) {
$this->response->text('OK');
}
$this->response->text('FAILED');
}
/**
* Public access (display a task)
*
@ -60,7 +26,7 @@ class Task extends Base
$this->forbidden(true);
}
$task = $this->task->getById($this->request->getIntegerParam('task_id'), true);
$task = $this->taskFinder->getDetails($this->request->getIntegerParam('task_id'));
if (! $task) {
$this->notfound(true);
@ -72,7 +38,7 @@ class Task extends Base
'subtasks' => $this->subTask->getAll($task['id']),
'task' => $task,
'columns_list' => $this->board->getColumnsList($task['project_id']),
'colors_list' => $this->task->getColors(),
'colors_list' => $this->color->getList(),
'title' => $task['title'],
'no_layout' => true,
'auto_refresh' => true,
@ -88,15 +54,29 @@ class Task extends Base
public function show()
{
$task = $this->getTask();
$subtasks = $this->subTask->getAll($task['id']);
$values = array(
'id' => $task['id'],
'date_started' => $task['date_started'],
'time_estimated' => $task['time_estimated'] ?: '',
'time_spent' => $task['time_spent'] ?: '',
);
$this->dateParser->format($values, array('date_started'));
$this->response->html($this->taskLayout('task_show', array(
'project' => $this->project->getById($task['project_id']),
'files' => $this->file->getAll($task['id']),
'comments' => $this->comment->getAll($task['id']),
'subtasks' => $this->subTask->getAll($task['id']),
'subtasks' => $subtasks,
'task' => $task,
'values' => $values,
'timesheet' => $this->timeTracking->getTaskTimesheet($task, $subtasks),
'columns_list' => $this->board->getColumnsList($task['project_id']),
'colors_list' => $this->task->getColors(),
'colors_list' => $this->color->getList(),
'date_format' => $this->config->get('application_date_format'),
'date_formats' => $this->dateParser->getAvailableFormats(),
'menu' => 'tasks',
'title' => $task['title'],
)));
@ -123,9 +103,11 @@ class Task extends Base
),
'projects_list' => $this->project->getListByStatus(ProjectModel::ACTIVE),
'columns_list' => $this->board->getColumnsList($project_id),
'users_list' => $this->project->getUsersList($project_id),
'colors_list' => $this->task->getColors(),
'users_list' => $this->projectPermission->getUsersList($project_id),
'colors_list' => $this->color->getList(),
'categories_list' => $this->category->getList($project_id),
'date_format' => $this->config->get('application_date_format'),
'date_formats' => $this->dateParser->getAvailableFormats(),
'menu' => 'tasks',
'title' => t('New task')
)));
@ -143,7 +125,7 @@ class Task extends Base
$this->checkProjectPermissions($values['project_id']);
list($valid, $errors) = $this->task->validateCreation($values);
list($valid, $errors) = $this->taskValidator->validateCreation($values);
if ($valid) {
@ -169,9 +151,11 @@ class Task extends Base
'values' => $values,
'projects_list' => $this->project->getListByStatus(ProjectModel::ACTIVE),
'columns_list' => $this->board->getColumnsList($values['project_id']),
'users_list' => $this->project->getUsersList($values['project_id']),
'colors_list' => $this->task->getColors(),
'users_list' => $this->projectPermission->getUsersList($values['project_id']),
'colors_list' => $this->color->getList(),
'categories_list' => $this->category->getList($values['project_id']),
'date_format' => $this->config->get('application_date_format'),
'date_formats' => $this->dateParser->getAvailableFormats(),
'menu' => 'tasks',
'title' => t('New task')
)));
@ -185,32 +169,29 @@ class Task extends Base
public function edit()
{
$task = $this->getTask();
$ajax = $this->request->isAjax();
if (! empty($task['date_due'])) {
$task['date_due'] = date(t('m/d/Y'), $task['date_due']);
}
else {
$task['date_due'] = '';
}
$task['score'] = $task['score'] ?: '';
$this->dateParser->format($task, array('date_due'));
$params = array(
'values' => $task,
'errors' => array(),
'task' => $task,
'users_list' => $this->project->getUsersList($task['project_id']),
'colors_list' => $this->task->getColors(),
'categories_list' => $this->category->getList($task['project_id']),
'ajax' => $this->request->isAjax(),
'menu' => 'tasks',
'title' => t('Edit a task')
);
if ($this->request->isAjax()) {
'values' => $task,
'errors' => array(),
'task' => $task,
'users_list' => $this->projectPermission->getUsersList($task['project_id']),
'colors_list' => $this->color->getList(),
'categories_list' => $this->category->getList($task['project_id']),
'date_format' => $this->config->get('application_date_format'),
'date_formats' => $this->dateParser->getAvailableFormats(),
'ajax' => $ajax,
'menu' => 'tasks',
'title' => t('Edit a task')
);
if ($ajax) {
$this->response->html($this->template->load('task_edit', $params));
}
else {
$this->response->html($this->template->layout('task_edit', $params));
$this->response->html($this->taskLayout('task_edit', $params));
}
}
@ -224,7 +205,7 @@ class Task extends Base
$task = $this->getTask();
$values = $this->request->getValues();
list($valid, $errors) = $this->task->validateModification($values);
list($valid, $errors) = $this->taskValidator->validateModification($values);
if ($valid) {
@ -235,7 +216,7 @@ class Task extends Base
$this->response->redirect('?controller=board&action=show&project_id='.$task['project_id']);
}
else {
$this->response->redirect('?controller=task&action=show&task_id='.$values['id']);
$this->response->redirect('?controller=task&action=show&task_id='.$task['id']);
}
}
else {
@ -243,19 +224,44 @@ class Task extends Base
}
}
$this->response->html($this->template->layout('task_edit', array(
$this->response->html($this->taskLayout('task_edit', array(
'values' => $values,
'errors' => $errors,
'task' => $task,
'columns_list' => $this->board->getColumnsList($values['project_id']),
'users_list' => $this->project->getUsersList($values['project_id']),
'colors_list' => $this->task->getColors(),
'users_list' => $this->projectPermission->getUsersList($values['project_id']),
'colors_list' => $this->color->getList(),
'categories_list' => $this->category->getList($values['project_id']),
'date_format' => $this->config->get('application_date_format'),
'date_formats' => $this->dateParser->getAvailableFormats(),
'menu' => 'tasks',
'title' => t('Edit a task')
'title' => t('Edit a task'),
'ajax' => $this->request->isAjax(),
)));
}
/**
* Update time tracking information
*
* @access public
*/
public function time()
{
$task = $this->getTask();
$values = $this->request->getValues();
list($valid, $errors) = $this->taskValidator->validateTimeModification($values);
if ($valid && $this->task->update($values)) {
$this->session->flash(t('Task updated successfully.'));
}
else {
$this->session->flashError(t('Unable to update your task.'));
}
$this->response->redirect('?controller=task&action=show&task_id='.$task['id']);
}
/**
* Hide a task
*
@ -323,6 +329,10 @@ class Task extends Base
{
$task = $this->getTask();
if (! $this->taskPermission->canRemoveTask($task)) {
$this->forbidden();
}
if ($this->request->getStringParam('confirmation') === 'yes') {
$this->checkCSRFParam();
@ -355,7 +365,7 @@ class Task extends Base
if ($this->request->getStringParam('confirmation') === 'yes') {
$this->checkCSRFParam();
$task_id = $this->task->duplicateSameProject($task);
$task_id = $this->task->duplicateToSameProject($task);
if ($task_id) {
$this->session->flash(t('Task created successfully.'));
@ -387,7 +397,7 @@ class Task extends Base
$values = $this->request->getValues();
list($valid, $errors) = $this->task->validateDescriptionCreation($values);
list($valid, $errors) = $this->taskValidator->validateDescriptionCreation($values);
if ($valid) {
@ -458,14 +468,14 @@ class Task extends Base
$task = $this->getTask();
$values = $task;
$errors = array();
$projects_list = $this->project->getAvailableList($this->acl->getUserId());
$projects_list = $this->projectPermission->getAllowedProjects($this->acl->getUserId());
unset($projects_list[$task['project_id']]);
if ($this->request->isPost()) {
$values = $this->request->getValues();
list($valid, $errors) = $this->task->validateProjectModification($values);
list($valid, $errors) = $this->taskValidator->validateProjectModification($values);
if ($valid) {
$task_id = $this->task->{$action.'ToAnotherProject'}($values['project_id'], $task);

View file

@ -38,6 +38,7 @@ class User extends Base
'errors' => array(),
'values' => array(),
'no_layout' => true,
'redirect_query' => $this->request->getStringParam('redirect_query'),
'title' => t('Login')
)));
}
@ -49,23 +50,30 @@ class User extends Base
*/
public function check()
{
$redirect_query = $this->request->getStringParam('redirect_query');
$values = $this->request->getValues();
list($valid, $errors) = $this->authentication->validateForm($values);
if ($valid) {
$this->response->redirect('?controller=board');
if ($redirect_query !== '') {
$this->response->redirect('?'.$redirect_query);
}
else {
$this->response->redirect('?controller=app');
}
}
$this->response->html($this->template->layout('user_login', array(
'errors' => $errors,
'values' => $values,
'no_layout' => true,
'redirect_query' => $redirect_query,
'title' => t('Login')
)));
}
/**
* Common layout for project views
* Common layout for user views
*
* @access private
* @param string $template Template name
@ -113,16 +121,31 @@ class User extends Base
*/
public function index()
{
$users = $this->user->getAll();
$nb_users = count($users);
$direction = $this->request->getStringParam('direction', 'ASC');
$order = $this->request->getStringParam('order', 'username');
$offset = $this->request->getIntegerParam('offset', 0);
$limit = 25;
$users = $this->user->paginate($offset, $limit, $order, $direction);
$nb_users = $this->user->count();
$this->response->html(
$this->template->layout('user_index', array(
'projects' => $this->project->getList(),
'users' => $users,
'nb_users' => $nb_users,
'users' => $users,
'menu' => 'users',
'title' => t('Users').' ('.$nb_users.')'
'title' => t('Users').' ('.$nb_users.')',
'pagination' => array(
'controller' => 'user',
'action' => 'index',
'direction' => $direction,
'order' => $order,
'total' => $nb_users,
'offset' => $offset,
'limit' => $limit,
'params' => array(),
),
)));
}
@ -181,7 +204,7 @@ class User extends Base
{
$user = $this->getUser();
$this->response->html($this->layout('user_show', array(
'projects' => $this->project->getAvailableList($user['id']),
'projects' => $this->projectPermission->getAllowedProjects($user['id']),
'user' => $user,
)));
}
@ -244,7 +267,7 @@ class User extends Base
}
$this->response->html($this->layout('user_notifications', array(
'projects' => $this->project->getAvailableList($user['id']),
'projects' => $this->projectPermission->getAllowedProjects($user['id']),
'notifications' => $this->notification->readSettings($user['id']),
'user' => $user,
)));
@ -345,7 +368,7 @@ class User extends Base
$this->response->html($this->layout('user_edit', array(
'values' => $values,
'errors' => $errors,
'projects' => $this->project->filterListByAccess($this->project->getList(), $user['id']),
'projects' => $this->projectPermission->filterProjects($this->project->getList(), $user['id']),
'user' => $user,
)));
}
@ -412,6 +435,7 @@ class User extends Base
'errors' => array('login' => t('Google authentication failed')),
'values' => array(),
'no_layout' => true,
'redirect_query' => '',
'title' => t('Login')
)));
}
@ -473,6 +497,7 @@ class User extends Base
'errors' => array('login' => t('GitHub authentication failed')),
'values' => array(),
'no_layout' => true,
'redirect_query' => '',
'title' => t('Login')
)));
}

View file

@ -0,0 +1,65 @@
<?php
namespace Controller;
/**
* Webhook controller
*
* @package controller
* @author Frederic Guillot
*/
class Webhook extends Base
{
/**
* Webhook to create a task
*
* @access public
*/
public function task()
{
if ($this->config->get('webhook_token') !== $this->request->getStringParam('token')) {
$this->response->text('Not Authorized', 401);
}
$defaultProject = $this->project->getFirst();
$values = array(
'title' => $this->request->getStringParam('title'),
'description' => $this->request->getStringParam('description'),
'color_id' => $this->request->getStringParam('color_id'),
'project_id' => $this->request->getIntegerParam('project_id', $defaultProject['id']),
'owner_id' => $this->request->getIntegerParam('owner_id'),
'column_id' => $this->request->getIntegerParam('column_id'),
'category_id' => $this->request->getIntegerParam('category_id'),
);
list($valid,) = $this->taskValidator->validateCreation($values);
if ($valid && $this->task->create($values)) {
$this->response->text('OK');
}
$this->response->text('FAILED');
}
/**
* Handle Github webhooks
*
* @access public
*/
public function github()
{
if ($this->config->get('webhook_token') !== $this->request->getStringParam('token')) {
$this->response->text('Not Authorized', 401);
}
$this->githubWebhook->setProjectId($this->request->getIntegerParam('project_id'));
$result = $this->githubWebhook->parsePayload(
$this->request->getHeader('X-Github-Event'),
$this->request->getBody()
);
echo $result ? 'PARSED' : 'IGNORED';
}
}

View file

@ -69,11 +69,14 @@ class Event
{
if (! $this->isEventTriggered($eventName)) {
$this->lastEvent = $eventName;
$this->events[] = $eventName;
if (isset($this->listeners[$eventName])) {
foreach ($this->listeners[$eventName] as $listener) {
$this->lastEvent = $eventName;
if ($listener->execute($data)) {
$this->lastListener = get_class($listener);
}

View file

@ -8,8 +8,8 @@ namespace Core;
* @package core
* @author Frederic Guillot
*/
interface Listener {
interface Listener
{
/**
* Execute the listener
*

View file

@ -50,26 +50,15 @@ class Request
}
/**
* Get form values or unserialized json request
* Get form values and check for CSRF token
*
* @access public
* @return array
*/
public function getValues()
{
if (! empty($_POST)) {
if (Security::validateCSRFFormToken($_POST)) {
return $_POST;
}
return array();
}
$result = json_decode($this->getBody(), true);
if ($result) {
return $result;
if (! empty($_POST) && Security::validateCSRFFormToken($_POST)) {
return $_POST;
}
return array();
@ -136,4 +125,73 @@ class Request
$name = 'HTTP_'.str_replace('-', '_', strtoupper($name));
return isset($_SERVER[$name]) ? $_SERVER[$name] : '';
}
/**
* Returns current request's query string, useful for redirecting
*
* @access public
* @return string
*/
public function getQueryString()
{
return isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '';
}
/**
* Get the user agent
*
* @static
* @access public
* @return string
*/
public static function getUserAgent()
{
return empty($_SERVER['HTTP_USER_AGENT']) ? t('Unknown') : $_SERVER['HTTP_USER_AGENT'];
}
/**
* Get the real IP address of the user
*
* @static
* @access public
* @param bool $only_public Return only public IP address
* @return string
*/
public static function getIpAddress($only_public = false)
{
$keys = array(
'HTTP_CLIENT_IP',
'HTTP_X_FORWARDED_FOR',
'HTTP_X_FORWARDED',
'HTTP_X_CLUSTER_CLIENT_IP',
'HTTP_FORWARDED_FOR',
'HTTP_FORWARDED',
'REMOTE_ADDR'
);
foreach ($keys as $key) {
if (isset($_SERVER[$key])) {
foreach (explode(',', $_SERVER[$key]) as $ip_address) {
$ip_address = trim($ip_address);
if ($only_public) {
// Return only public IP address
if (filter_var($ip_address, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) {
return $ip_address;
}
}
else {
return $ip_address;
}
}
}
}
return t('Unknown');
}
}

View file

@ -19,6 +19,18 @@ class Session
*/
const SESSION_LIFETIME = 0; // Until the browser is closed
/**
* Return true if the session is open
*
* @static
* @access public
* @return boolean
*/
public static function isOpen()
{
return session_id() !== '';
}
/**
* Open a session
*
@ -50,14 +62,14 @@ class Session
ini_set('session.hash_bits_per_character', 6);
// If session was autostarted with session.auto_start = 1 in php.ini destroy it, otherwise we cannot login
if (isset($_SESSION))
{
if (isset($_SESSION)) {
session_destroy();
}
// Custom session name
session_name('__S');
// Start the session
session_start();
// Regenerate the session id to avoid session fixation issue

View file

@ -17,7 +17,7 @@ class Template
*
* @var string
*/
const PATH = 'app/Templates/';
const PATH = 'app/Template/';
/**
* Load a template

View file

@ -11,14 +11,14 @@ namespace Core;
class Translator
{
/**
* Locales path
* Locale path
*
* @var string
*/
const PATH = 'app/Locales/';
const PATH = 'app/Locale/';
/**
* Locales
* Locale
*
* @static
* @access private

View file

@ -0,0 +1,79 @@
<?php
namespace Event;
use Core\Listener;
use Core\Registry;
use Core\Tool;
/**
* Base Listener
*
* @package event
* @author Frederic Guillot
*
* @property \Model\Comment $comment
* @property \Model\Project $project
* @property \Model\ProjectActivity $projectActivity
* @property \Model\SubTask $subTask
* @property \Model\Task $task
* @property \Model\TaskFinder $taskFinder
*/
abstract class Base implements Listener
{
/**
* Registry instance
*
* @access protected
* @var \Core\Registry
*/
protected $registry;
/**
* Constructor
*
* @access public
* @param \Core\Registry $registry Regsitry instance
*/
public function __construct(Registry $registry)
{
$this->registry = $registry;
}
/**
* Return class information
*
* @access public
* @return string
*/
public function __toString()
{
return get_called_class();
}
/**
* Load automatically models
*
* @access public
* @param string $name Model name
* @return mixed
*/
public function __get($name)
{
return Tool::loadModel($this->registry, $name);
}
/**
* Get event namespace
*
* Event = task.close | Namespace = task
*
* @access public
* @return string
*/
public function getEventNamespace()
{
$event_name = $this->registry->event->getLastTriggeredEvent();
return substr($event_name, 0, strpos($event_name, '.'));
}
}

View file

@ -1,87 +0,0 @@
<?php
namespace Event;
use Core\Listener;
use Model\Notification;
/**
* Base notification listener
*
* @package event
* @author Frederic Guillot
*/
abstract class BaseNotificationListener implements Listener
{
/**
* Notification model
*
* @accesss protected
* @var Model\Notification
*/
protected $notification;
/**
* Template name
*
* @accesss private
* @var string
*/
private $template = '';
/**
* Fetch data for the mail template
*
* @access public
* @param array $data Event data
* @return array
*/
abstract public function getTemplateData(array $data);
/**
* Constructor
*
* @access public
* @param \Model\Notification $notification Notification model instance
* @param string $template Template name
*/
public function __construct(Notification $notification, $template)
{
$this->template = $template;
$this->notification = $notification;
}
/**
* Return class information
*
* @access public
* @return string
*/
public function __toString()
{
return get_called_class();
}
/**
* Execute the action
*
* @access public
* @param array $data Event data dictionary
* @return bool True if the action was executed or false when not executed
*/
public function execute(array $data)
{
$values = $this->getTemplateData($data);
// Get the list of users to be notified
$users = $this->notification->getUsersList($values['task']['project_id']);
// Send notifications
if ($users) {
$this->notification->sendEmails($this->template, $users, $values);
return true;
}
return false;
}
}

View file

@ -1,73 +0,0 @@
<?php
namespace Event;
use Core\Listener;
use Model\CommentHistory;
/**
* Comment history listener
*
* @package event
* @author Frederic Guillot
*/
class CommentHistoryListener implements Listener
{
/**
* Comment History model
*
* @accesss private
* @var \Model\CommentHistory
*/
private $model;
/**
* Constructor
*
* @access public
* @param \Model\CommentHistory $model Comment History model instance
*/
public function __construct(CommentHistory $model)
{
$this->model = $model;
}
/**
* Return class information
*
* @access public
* @return string
*/
public function __toString()
{
return get_called_class();
}
/**
* Execute the action
*
* @access public
* @param array $data Event data dictionary
* @return bool True if the action was executed or false when not executed
*/
public function execute(array $data)
{
$creator_id = $this->model->acl->getUserId();
if ($creator_id && isset($data['task_id']) && isset($data['id'])) {
$task = $this->model->task->getById($data['task_id']);
$this->model->create(
$task['project_id'],
$data['task_id'],
$data['id'],
$creator_id,
$this->model->event->getLastTriggeredEvent(),
$data['comment']
);
}
return false;
}
}

View file

@ -1,30 +0,0 @@
<?php
namespace Event;
use Event\BaseNotificationListener;
/**
* Comment notification listener
*
* @package event
* @author Frederic Guillot
*/
class CommentNotificationListener extends BaseNotificationListener
{
/**
* Fetch data for the mail template
*
* @access public
* @param array $data Event data
* @return array
*/
public function getTemplateData(array $data)
{
$values = array();
$values['comment'] = $this->notification->comment->getById($data['id']);
$values['task'] = $this->notification->task->getById($values['comment']['task_id'], true);
return $values;
}
}

View file

@ -1,30 +0,0 @@
<?php
namespace Event;
use Event\BaseNotificationListener;
/**
* File notification listener
*
* @package event
* @author Frederic Guillot
*/
class FileNotificationListener extends BaseNotificationListener
{
/**
* Fetch data for the mail template
*
* @access public
* @param array $data Event data
* @return array
*/
public function getTemplateData(array $data)
{
$values = array();
$values['file'] = $data;
$values['task'] = $this->notification->task->getById($data['task_id'], true);
return $values;
}
}

View file

@ -0,0 +1,83 @@
<?php
namespace Event;
/**
* Notification listener
*
* @package event
* @author Frederic Guillot
*/
class NotificationListener extends Base
{
/**
* Template name
*
* @accesss private
* @var string
*/
private $template = '';
/**
* Set template name
*
* @access public
* @param string $template Template name
*/
public function setTemplate($template)
{
$this->template = $template;
}
/**
* Execute the action
*
* @access public
* @param array $data Event data dictionary
* @return bool True if the action was executed or false when not executed
*/
public function execute(array $data)
{
$values = $this->getTemplateData($data);
$users = $this->notification->getUsersList($values['task']['project_id']);
if ($users) {
$this->notification->sendEmails($this->template, $users, $values);
return true;
}
return false;
}
/**
* Fetch data for the mail template
*
* @access public
* @param array $data Event data
* @return array
*/
public function getTemplateData(array $data)
{
$values = array();
switch ($this->getEventNamespace()) {
case 'task':
$values['task'] = $this->taskFinder->getDetails($data['task_id']);
break;
case 'subtask':
$values['subtask'] = $this->subtask->getById($data['id'], true);
$values['task'] = $this->taskFinder->getDetails($data['task_id']);
break;
case 'file':
$values['file'] = $data;
$values['task'] = $this->taskFinder->getDetails($data['task_id']);
break;
case 'comment':
$values['comment'] = $this->comment->getById($data['id']);
$values['task'] = $this->taskFinder->getDetails($values['comment']['task_id']);
break;
}
return $values;
}
}

View file

@ -0,0 +1,61 @@
<?php
namespace Event;
/**
* Project activity listener
*
* @package event
* @author Frederic Guillot
*/
class ProjectActivityListener extends Base
{
/**
* Execute the action
*
* @access public
* @param array $data Event data dictionary
* @return bool True if the action was executed or false when not executed
*/
public function execute(array $data)
{
if (isset($data['task_id'])) {
$values = $this->getValues($data);
return $this->projectActivity->createEvent(
$values['task']['project_id'],
$values['task']['id'],
$this->acl->getUserId(),
$this->registry->event->getLastTriggeredEvent(),
$values
);
}
return false;
}
/**
* Get event activity data
*
* @access private
* @param array $data Event data dictionary
* @return array
*/
private function getValues(array $data)
{
$values = array();
$values['task'] = $this->taskFinder->getDetails($data['task_id']);
switch ($this->getEventNamespace()) {
case 'subtask':
$values['subtask'] = $this->subTask->getById($data['id'], true);
break;
case 'comment':
$values['comment'] = $this->comment->getById($data['id']);
break;
}
return $values;
}
}

View file

@ -1,63 +0,0 @@
<?php
namespace Event;
use Core\Listener;
use Model\Project;
/**
* Project modification date listener
*
* Update the last modified field for a project
*
* @package event
* @author Frederic Guillot
*/
class ProjectModificationDate implements Listener
{
/**
* Project model
*
* @accesss private
* @var \Model\Project
*/
private $project;
/**
* Constructor
*
* @access public
* @param \Model\Project $project Project model instance
*/
public function __construct(Project $project)
{
$this->project = $project;
}
/**
* Return class information
*
* @access public
* @return string
*/
public function __toString()
{
return get_called_class();
}
/**
* Execute the action
*
* @access public
* @param array $data Event data dictionary
* @return bool True if the action was executed or false when not executed
*/
public function execute(array $data)
{
if (isset($data['project_id'])) {
return $this->project->updateModificationDate($data['project_id']);
}
return false;
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace Event;
/**
* Project modification date listener
*
* Update the "last_modified" field for a project
*
* @package event
* @author Frederic Guillot
*/
class ProjectModificationDateListener extends Base
{
/**
* Execute the action
*
* @access public
* @param array $data Event data dictionary
* @return bool True if the action was executed or false when not executed
*/
public function execute(array $data)
{
if (isset($data['project_id'])) {
return $this->project->updateModificationDate($data['project_id']);
}
return false;
}
}

View file

@ -1,30 +0,0 @@
<?php
namespace Event;
use Event\BaseNotificationListener;
/**
* SubTask notification listener
*
* @package event
* @author Frederic Guillot
*/
class SubTaskNotificationListener extends BaseNotificationListener
{
/**
* Fetch data for the mail template
*
* @access public
* @param array $data Event data
* @return array
*/
public function getTemplateData(array $data)
{
$values = array();
$values['subtask'] = $this->notification->subtask->getById($data['id'], true);
$values['task'] = $this->notification->task->getById($data['task_id'], true);
return $values;
}
}

View file

@ -1,73 +0,0 @@
<?php
namespace Event;
use Core\Listener;
use Model\SubtaskHistory;
/**
* Subtask history listener
*
* @package event
* @author Frederic Guillot
*/
class SubtaskHistoryListener implements Listener
{
/**
* Comment History model
*
* @accesss private
* @var \Model\SubtaskHistory
*/
private $model;
/**
* Constructor
*
* @access public
* @param \Model\SubtaskHistory $model Subtask History model instance
*/
public function __construct(SubtaskHistory $model)
{
$this->model = $model;
}
/**
* Return class information
*
* @access public
* @return string
*/
public function __toString()
{
return get_called_class();
}
/**
* Execute the action
*
* @access public
* @param array $data Event data dictionary
* @return bool True if the action was executed or false when not executed
*/
public function execute(array $data)
{
$creator_id = $this->model->acl->getUserId();
if ($creator_id && isset($data['task_id']) && isset($data['id'])) {
$task = $this->model->task->getById($data['task_id']);
$this->model->create(
$task['project_id'],
$data['task_id'],
$data['id'],
$creator_id,
$this->model->event->getLastTriggeredEvent(),
''
);
}
return false;
}
}

View file

@ -1,63 +0,0 @@
<?php
namespace Event;
use Core\Listener;
use Model\TaskHistory;
/**
* Task history listener
*
* @package event
* @author Frederic Guillot
*/
class TaskHistoryListener implements Listener
{
/**
* Task History model
*
* @accesss private
* @var \Model\TaskHistory
*/
private $model;
/**
* Constructor
*
* @access public
* @param \Model\TaskHistory $model Task History model instance
*/
public function __construct(TaskHistory $model)
{
$this->model = $model;
}
/**
* Return class information
*
* @access public
* @return string
*/
public function __toString()
{
return get_called_class();
}
/**
* Execute the action
*
* @access public
* @param array $data Event data dictionary
* @return bool True if the action was executed or false when not executed
*/
public function execute(array $data)
{
$creator_id = $this->model->acl->getUserId();
if ($creator_id && isset($data['task_id']) && isset($data['project_id'])) {
$this->model->create($data['project_id'], $data['task_id'], $creator_id, $this->model->event->getLastTriggeredEvent());
}
return false;
}
}

View file

@ -1,29 +0,0 @@
<?php
namespace Event;
use Event\BaseNotificationListener;
/**
* Task notification listener
*
* @package event
* @author Frederic Guillot
*/
class TaskNotificationListener extends BaseNotificationListener
{
/**
* Fetch data for the mail template
*
* @access public
* @param array $data Event data
* @return array
*/
public function getTemplateData(array $data)
{
$values = array();
$values['task'] = $this->notification->task->getById($data['task_id'], true);
return $values;
}
}

View file

@ -2,25 +2,14 @@
namespace Event;
use Core\Listener;
use Model\Webhook;
/**
* Webhook task events
*
* @package event
* @author Frederic Guillot
*/
class WebhookListener implements Listener
class WebhookListener extends Base
{
/**
* Webhook model
*
* @accesss private
* @var \Model\Webhook
*/
private $webhook;
/**
* Url to call
*
@ -30,27 +19,14 @@ class WebhookListener implements Listener
private $url = '';
/**
* Constructor
* Set webhook url
*
* @access public
* @param string $url URL to call
* @param \Model\Webhook $webhook Webhook model instance
* @param string $url URL to call
*/
public function __construct($url, Webhook $webhook)
public function setUrl($url)
{
$this->url = $url;
$this->webhook = $webhook;
}
/**
* Return class information
*
* @access public
* @return string
*/
public function __toString()
{
return get_called_class();
}
/**

View file

@ -0,0 +1,561 @@
<?php
return array(
'None' => 'Ingen',
'edit' => 'rediger',
'Edit' => 'Rediger',
'remove' => 'fjern',
'Remove' => 'Fjern',
'Update' => 'Opdater',
'Yes' => 'Ja',
'No' => 'Nej',
'cancel' => 'annuller',
'or' => 'eller',
'Yellow' => 'Gul',
'Blue' => 'Blå',
'Green' => 'Grøn',
'Purple' => 'Lilla',
'Red' => 'Rød',
'Orange' => 'Orange',
'Grey' => 'Grå',
'Save' => 'Gem',
'Login' => 'Login',
'Official website:' => 'Officielt website:',
'Unassigned' => 'Ingen ansvarlig',
'View this task' => 'Se denne opgave',
'Remove user' => 'Fjern bruger',
'Do you really want to remove this user: "%s"?' => 'Ønsker du virkelig at fjerne denne bruger: "%s"?',
'New user' => 'Ny bruger',
'All users' => 'Alle brugere',
'Username' => 'Brugernavn',
'Password' => 'Password',
'Default project' => 'Standard projekt',
'Administrator' => 'Administrator',
'Sign in' => 'Log ind',
'Users' => 'Brugere',
'No user' => 'Ingen bruger',
'Forbidden' => 'Forbudt',
'Access Forbidden' => 'Adgang nægtet',
'Only administrators can access to this page.' => 'Kun administratorer har adgang til denne side.',
'Edit user' => 'Rediger bruger',
'Logout' => 'Log ud',
'Bad username or password' => 'Forkert brugernavn eller adgangskode',
'users' => 'Brugere',
'projects' => 'Projekter',
'Edit project' => 'Rediger projekt',
'Name' => 'Navn',
'Activated' => 'Aktiveret',
'Projects' => 'Projekter',
'No project' => 'Intet projekt',
'Project' => 'Projekt',
'Status' => 'Status',
'Tasks' => 'Opgave',
'Board' => 'Board',
'Actions' => 'Handlinger',
'Inactive' => 'Inaktiv',
'Active' => 'Aktiv',
'Column %d' => 'Kolonne %d',
'Add this column' => 'Tilføj denne kolonne',
'%d tasks on the board' => '%d Opgaver på boardet',
'%d tasks in total' => '%d Opgaver i alt',
'Unable to update this board.' => 'Ikke muligt at opdatere dette board',
'Edit board' => 'Rediger board',
'Disable' => 'Deaktiver',
'Enable' => 'Aktiver',
'New project' => 'Nyt projekt',
'Do you really want to remove this project: "%s"?' => 'Vil du virkelig fjerne dette projekt: "%s"?',
'Remove project' => 'Fjern projekt',
'Boards' => 'Boards',
'Edit the board for "%s"' => 'Rediger boardet for "%s"',
'All projects' => 'Alle Projekter',
'Change columns' => 'Ændre kolonner',
'Add a new column' => 'Tilføj en ny kolonne',
'Title' => 'Titel',
'Add Column' => 'Tilføj kolonne',
'Project "%s"' => 'Projekt "%s"',
'Nobody assigned' => 'Ingen ansvarlig',
'Assigned to %s' => 'Ansvarlig: %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 muligt at fjerne denne kolonne',
'Do you really want to remove this column: "%s"?' => 'Vil du virkelig fjerne denne kolonne: "%s"?',
'This action will REMOVE ALL TASKS associated to this column!' => 'Denne handling vil SLETTE ALLE OPGAVER tilknyttet denne kolonne',
'Settings' => 'Indstillinger',
'Application settings' => 'Applikationsindstillinger',
'Language' => 'Sprog',
'Webhook token:' => 'Webhook token:',
'API token:' => 'API Token:',
'More information' => 'Mere Information',
'Database size:' => 'Databasestørrelse:',
'Download the database' => 'Download databasen',
'Optimize the database' => 'Optimer databasen',
'(VACUUM command)' => '(VACUUM kommando)',
'(Gzip compressed Sqlite file)' => '(Gzip-komprimeret Sqlite fil)',
'User settings' => 'Brugerindstillinger',
'My default project:' => 'Mit standard projekt:',
'Close a task' => 'Luk en opgave',
'Do you really want to close this task: "%s"?' => 'Vil du virkelig lukke denne opgave: "%s"?',
'Edit a task' => 'Rediger en opgave',
'Column' => 'Kolonne',
'Color' => 'Farve',
'Assignee' => 'Ansvarlig',
'Create another task' => 'Opret en anden opgave',
'New task' => 'Ny opgave',
'Open a task' => 'Åben en opgave',
'Do you really want to open this task: "%s"?' => 'Vil du virkelig åbne denne opgave: "%s"?',
'Back to the board' => 'Tilbage til boardet',
'Created on %B %e, %Y at %k:%M %p' => 'Oprettet %d.%m.%Y - %H:%M',
'There is nobody assigned' => 'Der er ingen tilføjet',
'Column on the board:' => 'Kolonne:',
'Status is open' => 'Status er åbnet',
'Status is closed' => 'Status er lukket',
'Close this task' => 'Luk denne opgave',
'Open this task' => 'Åben denne opgave',
'There is no description.' => 'Der er ingen beskrivning.',
'Add a new task' => 'Tilføj en ny opgave',
'The username is required' => 'Brugernavn er krævet',
'The maximum length is %d characters' => 'Den maksimale længde er %d karakterer',
'The minimum length is %d characters' => 'Den minimale længde er %d karakterer',
'The password is required' => 'Adgangskode er krævet',
'This value must be an integer' => 'Denne værdig skal være et tal',
'The username must be unique' => 'Brugernavn skal være unikt',
'The username must be alphanumeric' => 'Brugernavnet skal være alfanumerisk',
'The user id is required' => 'Bruger id er krævet',
'Passwords don\'t match' => 'Adgangskoderne stemmer ikke overens',
'The confirmation is required' => 'Verifikation er nødvendigt',
'The column is required' => 'Kolonnen er krævet',
'The project is required' => 'Projektet er krævet',
'The color is required' => 'Farven er krævet',
'The id is required' => 'Id\'et er krævet',
'The project id is required' => 'Projektets id er krævet',
'The project name is required' => 'Projektets navn er krævet',
'This project must be unique' => 'Projektets navn skal være unikt',
'The title is required' => 'Titel er krævet',
'The language is required' => 'Sproget er krævet',
'There is no active project, the first step is to create a new project.' => 'Der er ingen aktive projekter. Første step er at oprette et nyt projekt.',
'Settings saved successfully.' => 'Indstillinger gemt.',
'Unable to save your settings.' => 'Indstillinger kunne ikke gemmes.',
'Database optimization done.' => 'Databaseoptimeringen er fuldført.',
'Your project have been created successfully.' => 'Dit projekt er oprettet.',
'Unable to create your project.' => 'Projektet kunne ikke oprettes',
'Project updated successfully.' => 'Projektet er opdateret.',
'Unable to update this project.' => 'Projektet kunne ikke opdateres.',
'Unable to remove this project.' => 'Projektet kunne ikke slettes.',
'Project removed successfully.' => 'Projektet er slettet.',
'Project activated successfully.' => 'Projektet er aktiveret.',
'Unable to activate this project.' => 'Projektet kunne ikke aktiveres.',
'Project disabled successfully.' => 'Projektet er deaktiveret.',
'Unable to disable this project.' => 'Projektet kunne ikke deaktiveres.',
'Unable to open this task.' => 'Opgaven kunne ikke ånnes.',
'Task opened successfully.' => 'Opgaven er åbnet.',
'Unable to close this task.' => 'Opgaven kunne ikke åbnes.',
'Task closed successfully.' => 'Opgaven er lukket.',
'Unable to update your task.' => 'Opgaven kunne ikke opdateres.',
'Task updated successfully.' => 'Opgaven er opdateret.',
'Unable to create your task.' => 'Opgave kunne ikke oprettes.',
'Task created successfully.' => 'Opgaven er oprettet.',
'User created successfully.' => 'Brugeren er oprettet.',
'Unable to create your user.' => 'Brugeren kunne ikke oprettes.',
'User updated successfully.' => 'Brugeren er opdateret',
'Unable to update your user.' => 'Din bruger kunne ikke opdateres.',
'User removed successfully.' => 'Brugeren er fjernet.',
'Unable to remove this user.' => 'Brugeren kunne ikke fjernes.',
'Board updated successfully.' => 'Boardet er opdateret.',
'Ready' => 'Klar',
'Backlog' => 'Backlog',
'Work in progress' => 'Igangværende',
'Done' => 'Færdig',
'Application version:' => 'Version:',
'Completed on %B %e, %Y at %k:%M %p' => 'Fuldført %d.%m.%Y - %H:%M',
'%B %e, %Y at %k:%M %p' => '%d.%m.%Y - %H:%M',
'Date created' => 'Dato for oprettelse',
'Date completed' => 'Dato for fuldført',
'Id' => 'ID',
'No task' => 'Ingen opgave',
'Completed tasks' => 'Fuldførte opgaver',
'List of projects' => 'Liste over projekter',
'Completed tasks for "%s"' => 'Fuldførte opgaver for "%s"',
'%d closed tasks' => '%d lukket opgavet',
'No task for this project' => 'Ingen opgaver i dette projekt',
'Public link' => 'Offentligt link',
'There is no column in your project!' => 'Der er ingen kolonner i dit projekt!',
'Change assignee' => 'Ændre ansvarlig',
'Change assignee for the task "%s"' => 'Ændre ansvarlig for opgaven: "%s"',
'Timezone' => 'Tidszone',
'Sorry, I didn\'t found this information in my database!' => 'Denne information kunne ikke findes i databasen!',
'Page not found' => 'Siden er ikke fundet',
'Complexity' => 'Kompleksitet',
'limit' => 'Begrænsning',
'Task limit' => 'Opgave begrænsning',
'This value must be greater than %d' => 'Denne værdi skal være større end %d',
'Edit project access list' => 'Rediger adgangstilladelser for projektet',
'Edit users access' => 'Rediger brugertilladelser',
'Allow this user' => 'Tillad denne bruger',
'Only those users have access to this project:' => 'Kunne disse brugere har adgang til dette projekt:',
'Don\'t forget that administrators have access to everything.' => 'Glem ikke at administratorer har adgang til alt.',
'revoke' => 'fjern',
'List of authorized users' => 'Liste over autoriserede brugere',
'User' => 'Bruger',
'Nobody have access to this project.' => 'Ingen har adgang til dette projekt.',
'You are not allowed to access to this project.' => 'Du har ikke tilladelse til at få adgang til dette projekt.',
'Comments' => 'Kommentarer',
'Post comment' => 'Skriv en kommentar',
'Write your text in Markdown' => 'Skriv din tekst i markdown',
'Leave a comment' => 'Efterlad en kommentar',
'Comment is required' => 'Kommentar er krævet',
'Leave a description' => 'Efterlad en beskrivelse...',
'Comment added successfully.' => 'Kommentaren er tilføjet.',
'Unable to create your comment.' => 'Din kommentar kunne ikke oprettes.',
'The description is required' => 'Beskrivelsen er krævet',
'Edit this task' => 'Rediger denne opgave',
'Due Date' => 'Forfaldsdato',
'Invalid date' => 'Ugyldig dato',
'Must be done before %B %e, %Y' => 'Skal være fuldført inden %d.%m.%Y',
'%B %e, %Y' => '%d.%m.%Y',
'Automatic actions' => 'Automatiske handlinger',
'Your automatic action have been created successfully.' => 'Din automatiske handling er oprettet.',
'Unable to create your automatic action.' => 'Din automatiske handling kunne ikke oprettes.',
'Remove an action' => 'Fjern an handling',
'Unable to remove this action.' => 'Handlingen kunne ikke fjernes.',
'Action removed successfully.' => 'Handlingen er fjernet.',
'Automatic actions for the project "%s"' => 'Automatiske handlinger for projektet "%s"',
'Defined actions' => 'Defineret handlinger',
'Add an action' => 'Tilføj en handling',
'Event name' => 'Begivenhed',
'Action name' => 'Handling',
'Action parameters' => 'Handlingsparametre',
'Action' => 'Handling',
'Event' => 'Begivenhed',
'When the selected event occurs execute the corresponding action.' => 'Når den valgte begivenhed opstår, udfør den tilsvarende handling.',
'Next step' => 'Næste',
'Define action parameters' => 'Definer Handlingsparametre',
'Save this action' => 'Gem denne handling',
'Do you really want to remove this action: "%s"?' => 'Vil du virkelig slette denne handling: "%s"?',
'Remove an automatic action' => 'Fjern en automatisk handling',
'Close the task' => 'Luk opgaven',
'Assign the task to a specific user' => 'Tildel opgaven til en bestem bruger',
'Assign the task to the person who does the action' => 'Tildel opgaven til den person, der udfører handlingen',
'Duplicate the task to another project' => 'Kopier opgaven til et andet projekt',
'Move a task to another column' => 'Flyt opgaven til en anden kolonne',
'Move a task to another position in the same column' => 'Flyt opgaven til en anden position, i den samme kolonne',
'Task modification' => 'Opgave forandring',
'Task creation' => 'Opgave oprettelse',
'Open a closed task' => 'Åbne en lukket opgave',
'Closing a task' => 'Lukke en opgave',
'Assign a color to a specific user' => 'Tildel en farve til en bestemt bruger',
'Column title' => 'Kolonne titel',
'Position' => 'Position',
'Move Up' => 'Ryk op',
'Move Down' => 'Ryk ned',
'Duplicate to another project' => 'Kopier til et andet projekt',
'Duplicate' => 'Kopier',
'link' => 'link',
'Update this comment' => 'Opdater denne kommentar',
'Comment updated successfully.' => 'Kommentar opdateret.',
'Unable to update your comment.' => 'Din kommentar kunne ikke opdateres.',
'Remove a comment' => 'Fjern en kommentar',
'Comment removed successfully.' => 'Kommentaren blev fjernet.',
'Unable to remove this comment.' => 'Kommentaren kunne ikke fjernes.',
'Do you really want to remove this comment?' => 'Vil du virkelig fjerne denne kommentar?',
'Only administrators or the creator of the comment can access to this page.' => 'Kun administratore eller brugeren, som har oprettet kommentaren har adgang til denne side.',
'Details' => 'Detaljer',
'Current password for the user "%s"' => 'Aktuelle adgangskode for brugeren "%s"',
'The current password is required' => 'Den aktuelle adgangskode er krævet',
'Wrong password' => 'Forkert adgangskode',
'Reset all tokens' => 'Nulstil alle tokens',
'All tokens have been regenerated.' => 'Alle tokens er blevet regenereret.',
'Unknown' => 'Ukendt',
'Last logins' => 'Sidste login',
'Login date' => 'Login dato',
'Authentication method' => 'Godkendelsesmetode',
'IP address' => 'IP Adresse',
'User agent' => 'User Agent',
'Persistent connections' => 'Vedvarende forbindelser',
'No session.' => 'Ingen session.',
'Expiration date' => 'Udløbsdato',
'Remember Me' => 'Husk mig',
'Creation date' => 'Oprettelsesdato',
'Filter by user' => 'Filtrer efter bruger',
'Filter by due date' => 'Filtrer efter forfaldsdato',
'Everybody' => 'Alle',
'Open' => 'Åben',
'Closed' => 'Lukket',
'Search' => 'Søg',
'Nothing found.' => 'Intet fundet.',
'Search in the project "%s"' => 'Søg i projektet "%s"',
'Due date' => 'Forfaldsdato',
'Others formats accepted: %s and %s' => 'Andre acceptable formater: %s und %s',
'Description' => 'Beskrivelse',
'%d comments' => '%d kommentarer',
'%d comment' => '%d kommentar',
'Email address invalid' => 'Ugyldig email',
'Your Google Account is not linked anymore to your profile.' => 'Din Google-konto er ikke længere forbundet til din profil.',
'Unable to unlink your Google Account.' => 'Det var ikke muligt at fjerne din Google-konto.',
'Google authentication failed' => 'Google autentificering mislykkedes',
'Unable to link your Google Account.' => 'Det var ikke muligt at forbinde til din Google-konto.',
'Your Google Account is linked to your profile successfully.' => 'Din Google-konto er forbundet til din profil.',
'Email' => 'E-Mail',
'Link my Google Account' => 'Forbind min Google-konto',
'Unlink my Google Account' => 'Fjern forbindelsen til min Google-konto',
'Login with my Google Account' => 'Login med min Google-konto',
'Project not found.' => 'Projekt ikke fundet.',
'Task #%d' => 'Opgave %d',
'Task removed successfully.' => 'Opgaven er fjernet.',
'Unable to remove this task.' => 'Opgaven kunne ikke fjernes.',
'Remove a task' => 'Fjern en opgave',
'Do you really want to remove this task: "%s"?' => 'Vil du virkelig fjerne denne opgave: "%s"?',
'Assign automatically a color based on a category' => 'Tildel automatisk en farve baseret for en kategori',
'Assign automatically a category based on a color' => 'Tildel automatisk en kategori baseret op en farve',
'Task creation or modification' => 'Opgave oprettelse eller forandring',
'Category' => 'Kategori',
'Category:' => 'Kategori:',
'Categories' => 'Kategorier',
'Category not found.' => 'Kategori ikke fundet.',
'Your category have been created successfully.' => 'Kategorien er oprettet.',
'Unable to create your category.' => 'Kategorien kunne ikke oprettes.',
'Your category have been updated successfully.' => 'Kategorien er opdateret.',
'Unable to update your category.' => 'Kategorien kunne ikke opdateres.',
'Remove a category' => 'Fjern en kategori',
'Category removed successfully.' => 'Kategorien er fjernet.',
'Unable to remove this category.' => 'Kategorien kunne ikke fjernes.',
'Category modification for the project "%s"' => 'Forandring af kategori for projektet "%s"',
'Category Name' => 'Kategorinavn',
'Categories for the project "%s"' => 'Kategorier i projektet "%s"',
'Add a new category' => 'Tilfæj en ny kategori',
'Do you really want to remove this category: "%s"?' => 'Vil du virkelig fjerne denne kategori: "%s"?',
'Filter by category' => 'Filter efter kategori',
'All categories' => 'Alle kategorier',
'No category' => 'Ingen kategori',
'The name is required' => 'Navnet er krævet',
'Remove a file' => 'Fjern en fil',
'Unable to remove this file.' => 'Filen kunne ikke fjernes.',
'File removed successfully.' => 'Filen er fjernet.',
'Attach a document' => 'Vedhæft et dokument',
'Do you really want to remove this file: "%s"?' => 'Vil du virkelig fjerne denne fil: "%s"?',
'open' => 'åben',
'Attachments' => 'Vedhæftninger',
'Edit the task' => 'Rediger opgaven',
'Edit the description' => 'Rediger beskrivelsen',
'Add a comment' => 'Tilføj en kommentar',
'Edit a comment' => 'Rediger en kommentar',
'Summary' => 'Resumé',
'Time tracking' => 'Tidsregistrering',
'Estimate:' => 'Estimering:',
'Spent:' => 'Brugt:',
'Do you really want to remove this sub-task?' => 'Vil du virkeligt fjerne denne under-opgave: "%s"?',
'Remaining:' => 'Tilbageværende:',
'hours' => 'timer',
'spent' => 'brugt',
'estimated' => 'estimeret',
'Sub-Tasks' => 'Under-opgave',
'Add a sub-task' => 'Tilføj en under-opgave',
'Original estimate' => 'Original estimering',
'Create another sub-task' => 'Tilføj endnu en under-opgave',
'Time spent' => 'Tidsforbrug',
'Edit a sub-task' => 'Rediger en under-opgave',
'Remove a sub-task' => 'Fjern en under-opgave',
'The time must be a numeric value' => 'Tiden skal være en nummerisk værdi',
'Todo' => 'Todo',
'In progress' => 'I gang',
'Sub-task removed successfully.' => 'Under-opgaven er fjernet.',
'Unable to remove this sub-task.' => 'Under-opgaven kunne ikke fjernes.',
'Sub-task updated successfully.' => 'Under-opgaven er opdateret.',
'Unable to update your sub-task.' => 'Under-opgaven kunne ikke opdateres.',
'Unable to create your sub-task.' => 'Under-opgaven kunne ikke oprettes.',
'Sub-task added successfully.' => 'Under-opgaven er tilføjet.',
'Maximum size: ' => 'Maksimum størrelse: ',
'Unable to upload the file.' => 'Filen kunne ikke uploades.',
'Display another project' => 'Vis et andet projekt...',
'Your GitHub account was successfully linked to your profile.' => 'Din GitHub-konto er forbundet til din profil.',
'Unable to link your GitHub Account.' => 'Det var ikke muligt er forbinde til din GitHub-konto.',
'GitHub authentication failed' => 'GitHub autentificering mislykkedes',
'Your GitHub account is no longer linked to your profile.' => 'Din GitHub-konto er ikke længere forbundet 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' => 'Forbind min GitHub-konto',
'Unlink my GitHub Account' => 'Fjern forbindelsen til min GitHub-konto',
'Created by %s' => 'Oprettet af %s',
'Last modified on %B %e, %Y at %k:%M %p' => 'Sidst redigeret %d.%m.%Y - %H:%M',
'Tasks Export' => 'Opgave eksport',
'Tasks exportation for "%s"' => 'Opgave eksport for "%s"',
'Start Date' => 'Start-dato',
'End Date' => 'Slut-dato',
'Execute' => 'Udfør',
'Task Id' => 'Opgave ID',
'Creator' => 'Skaber',
'Modification date' => 'Ændringsdato',
'Completion date' => 'Afslutningsdato',
'Webhook URL for task creation' => 'Webhook URL for opgave oprettelse',
'Webhook URL for task modification' => 'Webhook URL opgave redigering',
'Clone' => 'Kopier',
'Clone Project' => 'Kopier projekt',
'Project cloned successfully.' => 'Projektet er kopieret.',
'Unable to clone this project.' => 'Projektet kunne ikke kopieres',
'Email notifications' => 'Email notifikationer',
'Enable email notifications' => 'Aktivér email notifikationer',
'Task position:' => 'Opgave position:',
'The task #%d have been opened.' => 'Opgaven #%d er blevet åbnet.',
'The task #%d have been closed.' => 'Opgaven #%d er blevet lukket.',
'Sub-task updated' => 'Under-opgave opdateret',
'Title:' => 'Titel:',
'Status:' => 'Status:',
'Assignee:' => 'Ansvarlig:',
'Time tracking:' => 'Tidsmåling:',
'New sub-task' => 'Ny under-opgave',
'New attachment added "%s"' => 'Ny vedhæftning tilføjet "%s"',
'Comment updated' => 'Kommentar opdateret',
'New comment posted by %s' => 'Ny kommentar af %s',
'List of due tasks for the project "%s"' => 'Udestående opgaver for projektet "%s"',
'[%s][New attachment] %s (#%d)' => '[%s][Ny vedhæftning] %s (#%d)',
'[%s][New comment] %s (#%d)' => '[%s][Ny kommentar] %s (#%d)',
'[%s][Comment updated] %s (#%d)' => '[%s][Kommentar opdateret] %s (#%d)',
'[%s][New subtask] %s (#%d)' => '[%s][Ny under-opgave] %s (#%d)',
'[%s][Subtask updated] %s (#%d)' => '[%s][Under-opgave opdateret] %s (#%d)',
'[%s][New task] %s (#%d)' => '[%s][Ny opgave] %s (#%d)',
'[%s][Task updated] %s (#%d)' => '[%s][Opgave opdateret] %s (#%d)',
'[%s][Task closed] %s (#%d)' => '[%s][Opgave lukket] %s (#%d)',
'[%s][Task opened] %s (#%d)' => '[%s][Opgave åbnet] %s (#%d)',
'[%s][Due tasks]' => 'Udestående opgaver',
'[Kanboard] Notification' => '[Kanboard] Notifikation',
'I want to receive notifications only for those projects:' => 'Jeg vil kun have notifikationer for disse projekter:',
'view the task on Kanboard' => 'se opgaven på Kanboard',
'Public access' => 'Offentlig adgang',
'Category management' => 'Kategorier',
'User management' => 'Brugerstyring',
'Active tasks' => 'Aktive opgaver',
'Disable public access' => 'Deaktiver offentlig adgang',
'Enable public access' => 'Aktivér offentlig adgang',
'Active projects' => 'Aktive Projekter',
'Inactive projects' => 'Inaktive projekter',
'Public access disabled' => 'Offentlig adgang deaktiveret',
'Do you really want to disable this project: "%s"?' => 'Vil du virkelig deaktivere dette projekt: "%s"?',
'Do you really want to duplicate this project: "%s"?' => 'Vil du virkelig kopiere dette projekt: "%s"?',
'Do you really want to enable this project: "%s"?' => 'Vil du virkelig aktiverer dette projekt: "%s"?',
'Project activation' => 'Projekt aktivering',
'Move the task to another project' => 'Flyt opgaven til et andet projekt',
'Move to another project' => 'Flyt til et andet projekt',
'Do you really want to duplicate this task?' => 'Vil du virkelig kopiere denne opgave?',
'Duplicate a task' => 'Kopier en opgave',
'External accounts' => 'Eksterne kontoer',
'Account type' => 'Kontotype',
'Local' => 'Lokal',
'Remote' => 'Remote',
'Enabled' => 'Aktiv',
'Disabled' => 'Deaktiveret',
'Google account linked' => 'Google-konto forbundet',
'Github account linked' => 'GitHub-konto forbundet',
'Username:' => 'Brugernavn',
'Name:' => 'Navn:',
'Email:' => 'Email:',
'Default project:' => 'Standard projekt:',
'Notifications:' => 'Notifikationer:',
'Notifications' => 'Notifikationer',
'Group:' => 'Gruppe:',
'Regular user' => 'Normal bruger',
'Account type:' => 'Konto type:',
'Edit profile' => 'Rediger profil',
'Change password' => 'Skift adgangskode',
'Password modification' => 'Adgangskode ændring',
'External authentications' => 'Ekstern autentificering',
'Google Account' => 'Google-konto',
'Github Account' => 'GitHub-konto',
'Never connected.' => 'Aldrig forbundet.',
'No account linked.' => 'Ingen kontoer forfundet.',
'Account linked.' => 'Konto forbundet.',
'No external authentication enabled.' => 'Ingen eksterne autentificering aktiveret.',
'Password modified successfully.' => 'Adgangskode ændret.',
'Unable to change the password.' => 'Adgangskoden kunne ikke ændres.',
'Change category for the task "%s"' => 'Skift kategori for opgaven "%s"',
'Change category' => 'Skift kategori',
'%s updated the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s opdatert opgaven <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>',
'%s open the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s åben opgaven <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>',
'%s moved the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to the position #%d in the column "%s"' => '%s flyt opgaven <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> til positionen #%d i kolonnen "%s"',
'%s moved the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to the column "%s"' => '%s flyttede opgaven <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> til kolonnen "%s"',
'%s created the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s oprettede opgaven <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>',
'%s closed the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
'%s created a subtask for the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s oprettede en under-opgave for opgaven <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>',
'%s updated a subtask for the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s opdaterede en under-opgave for opgaven <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>',
'Assigned to %s with an estimate of %s/%sh' => 'Tildelt til %s med en estimering på %s/%sh',
'Not assigned, estimate of %sh' => 'Ikke tildelt, estimeret til %sh',
'%s updated a comment on the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s opdateret en kommentar på opgaven <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>',
'%s commented the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s har kommenteret opgaven <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>',
'%s\'s activity' => '%s\'s aktvitet',
'No activity.' => 'Ingen aktivitet',
'RSS feed' => 'RSS feed',
'%s updated a comment on the task #%d' => '%s opdaterede en kommentar på opgaven #%d',
'%s commented on the task #%d' => '%s kommenteret op opgaven #%d',
'%s updated a subtask for the task #%d' => '%s opdaterede en under-opgave for opgaven #%d',
'%s created a subtask for the task #%d' => '%s oprettede en under-opgave for opgaven #%d',
'%s updated the task #%d' => '%s opdaterede opgaven #%d',
'%s created the task #%d' => '%s oprettede opgaven #%d',
'%s closed the task #%d' => '%s lukkede opgaven #%d',
'%s open the task #%d' => '%s åbnede opgaven #%d',
'%s moved the task #%d to the column "%s"' => '%s flyttede opgaven #%d til kolonnen "%s"',
'%s moved the task #%d to the position %d in the column "%s"' => '%s flyttede opgaven #%d til position %d i kolonnen "%s"',
'Activity' => 'Aktivitet',
'Default values are "%s"' => 'Standard værdier er "%s"',
'Default columns for new projects (Comma-separated)' => 'Standard kolonne for nye projekter (kommasepareret)',
'Task assignee change' => 'Opgaven ansvarlig ændring',
'%s change the assignee of the task #%d to %s' => '%s skrift ansvarlig for opgaven #%d til %s',
'%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to %s' => '%s skift ansvarlig for opgaven <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> til %s',
'[%s][Column Change] %s (#%d)' => '[%s][Kolonne Skift] %s (#%d)',
'[%s][Position Change] %s (#%d)' => '[%s][Position Skift] %s (#%d)',
'[%s][Assignee Change] %s (#%d)' => '[%s][Ansvarlig Skift] %s (#%d)',
'New password for the user "%s"' => 'Ny adgangskode for brugeren',
'Choose an event' => 'Vælg et event',
'Github commit received' => 'Github commit modtaget',
'Github issue opened' => 'Github problem åbet',
'Github issue closed' => 'Github problem lukket',
'Github issue reopened' => 'Github problem genåbnet',
'Github issue assignee change' => 'Github problem ansvarlig skift',
'Github issue label change' => 'Github problem label skift',
'Create a task from an external provider' => 'Opret en opgave fra en ekstern udbyder',
'Change the assignee based on an external username' => 'Skift den ansvarlige baseret på et eksternt brugernavn',
'Change the category based on an external label' => 'Skift kategorien baseret på en ekstern label',
'Reference' => 'Reference',
'Reference: %s' => 'Reference:',
'Label' => 'Label',
'Database' => 'Database',
'About' => 'Om',
'Database driver:' => 'Database driver:',
'Board settings' => 'Baord indstillinger',
'URL and token' => 'URL og token',
'Webhook settings' => 'Webhook indstillinger',
'URL for task creation:' => 'URL for opgave oprettelse:',
'Reset token' => 'Reset endpoint',
'API endpoint:' => 'API endpoint:',
'Refresh interval for private board' => 'Refresh interval for privat board',
'Refresh interval for public board' => 'Refresh interval for offentligt board',
'Task highlight period' => 'Opgave fremhævet periode',
'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Periode for at antage en opgave er ændret fornylig (0 for at deaktivere, 2 dage 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 at deaktivere denne funktion, 10 sekunder som standard)',
'Application URL' => 'Applikation URL',
'Example: http://example.kanboard.net/ (used by email notifications)' => 'Eksempel: http://example.kanboard.net/ (bruges til email notifikationer)',
'Token regenerated.' => 'Token regenereret.',
'Date format' => 'Dato format',
'ISO format is always accepted, example: "%s" and "%s"' => 'ISO format er altid accepteret, eksempelvis: "%s" og "%s"',
'New private project' => 'Nyt privat projekt',
'This project is private' => 'Dette projekt er privat',
'Type here to create a new sub-task' => 'Skriv her for at tilføje en ny under-opgave',
'Add' => 'Tilføj',
'Estimated time: %s hours' => 'Estimeret tid: %s timer',
'Time spent: %s hours' => 'Tid brugt: %s timer',
'Started on %B %e, %Y' => 'Startet %d.%m.%Y ',
'Start date' => 'Start dato',
'Time estimated' => 'Tid estimeret',
'There is nothing assigned to you.' => 'Der er ingenting tildelt til dig.',
'My tasks' => 'Mine opgaver',
'Activity stream' => 'Aktivitets strøm',
'Dashboard' => 'Dashboard',
'Confirmation' => 'Bekræftelse',
// 'Allow everybody to access to this project' => '',
// 'Everybody have access to this project.' => '',
// 'Webhooks' => '',
// 'API' => '',
// 'Integration' => '',
// 'Github webhook' => '',
// 'Help on Github webhook' => '',
// 'Create a comment from an external provider' => '',
// 'Github issue comment created' => '',
);

View file

@ -35,8 +35,8 @@ return array(
'Users' => 'Benutzer',
'No user' => 'Kein Benutzer',
'Forbidden' => 'Verboten',
'Access Forbidden' => 'Zugang verboten',
'Only administrators can access to this page.' => 'Nur Administratoren haben Zugang zu dieser Seite.',
'Access Forbidden' => 'Zugriff verboten',
'Only administrators can access to this page.' => 'Nur Administratoren haben Zugriff zu dieser Seite.',
'Edit user' => 'Benutzer bearbeiten',
'Logout' => 'Abmelden',
'Bad username or password' => 'Falscher Benutzername oder Passwort',
@ -83,7 +83,7 @@ return array(
'Settings' => 'Einstellungen',
'Application settings' => 'Anwendungskonfiguration',
'Language' => 'Sprache',
'Webhooks token:' => 'Webhooks Token:',
'Webhook token:' => 'Webhook Token:',
'API token:' => 'API Token:',
'More information' => 'Mehr Informationen',
'Database size:' => 'Datenbankgröße:',
@ -184,19 +184,19 @@ return array(
'Timezone' => 'Zeitzone',
'Sorry, I didn\'t found this information in my database!' => 'Diese Information wurde in der Datenbank nicht gefunden!',
'Page not found' => 'Seite nicht gefunden',
// 'Complexity' => '',
'Complexity' => 'Komplexität',
'limit' => 'Limit',
'Task limit' => 'Maximale Anzahl von Aufgaben',
'This value must be greater than %d' => 'Dieser Wert muss größer sein als %d',
'Edit project access list' => 'Zugriffsberechtigungen des Projektes bearbeiten',
'Edit users access' => 'Benutzerzugriff ändern',
'Allow this user' => 'Diesen Benutzer autorisieren',
'Only those users have access to this project:' => 'Nur diese Benutzer haben Zugang zum Projekt:',
'Don\'t forget that administrators have access to everything.' => 'Nicht vergessen: Administratoren haben überall Zugang.',
'Only those users have access to this project:' => 'Nur diese Benutzer haben Zugriff zum Projekt:',
'Don\'t forget that administrators have access to everything.' => 'Nicht vergessen: Administratoren haben überall Zugriff.',
'revoke' => 'entfernen',
'List of authorized users' => 'Liste der autorisierten Benutzer',
'User' => 'Benutzer',
'Everybody have access to this project.' => 'Jeder hat Zugang zu diesem Projekt.',
'Nobody have access to this project.' => 'Niemand hat Zugriff auf dieses Projekt.',
'You are not allowed to access to this project.' => 'Unzureichende Zugriffsrechte zu diesem Projekt.',
'Comments' => 'Kommentare',
'Post comment' => 'Kommentieren',
@ -209,8 +209,6 @@ return array(
'The description is required' => 'Eine Beschreibung wird benötigt',
'Edit this task' => 'Aufgabe bearbeiten',
'Due Date' => 'Fällig am',
'm/d/Y' => 'd.m.Y',
'month/day/year' => 'TT.MM.JJJJ',
'Invalid date' => 'Ungültiges Datum',
'Must be done before %B %e, %Y' => 'Muss vor dem %d.%m.%Y erledigt werden',
'%B %e, %Y' => '%d.%m.%Y',
@ -261,7 +259,7 @@ return array(
'Do you really want to remove this comment?' => 'Soll dieser Kommentar wirklich gelöscht werden?',
'Only administrators or the creator of the comment can access to this page.' => 'Nur Administratoren und der Ersteller des Kommentars haben Zugriff auf diese Seite.',
'Details' => 'Details',
'Current password for the user "%s"' => 'Aktuelles Passwort für den Benutzer "%s"',
'Current password for the user "%s"' => 'Aktuelles Passwort des Benutzers "%s"',
'The current password is required' => 'Das aktuelle Passwort wird benötigt',
'Wrong password' => 'Falsches Passwort',
'Reset all tokens' => 'Alle Tokens zurücksetzen',
@ -293,7 +291,7 @@ return array(
'Email address invalid' => 'Ungültige E-Mail-Adresse',
'Your Google Account is not linked anymore to your profile.' => 'Google Account nicht mehr mit dem Profil verbunden.',
'Unable to unlink your Google Account.' => 'Trennung der Verbindung zum Google Account nicht möglich.',
'Google authentication failed' => 'Zugang mit Google fehl geschlagen',
'Google authentication failed' => 'Zugriff mit Google fehlgeschlagen',
'Unable to link your Google Account.' => 'Verbindung mit diesem Google Account nicht möglich.',
'Your Google Account is linked to your profile successfully.' => 'Der Google Account wurde erfolgreich verbunden.',
'Email' => 'E-Mail',
@ -351,9 +349,9 @@ return array(
'estimated' => 'geschätzt',
'Sub-Tasks' => 'Unteraufgaben',
'Add a sub-task' => 'Unteraufgabe anlegen',
'Original Estimate' => 'Geschätzter Aufwand',
'Original estimate' => 'Geschätzter Aufwand',
'Create another sub-task' => 'Weitere Unteraufgabe anlegen',
'Time Spent' => 'Aufgewendete Zeit',
'Time spent' => 'Aufgewendete Zeit',
'Edit a sub-task' => 'Unteraufgabe bearbeiten',
'Remove a sub-task' => 'Unteraufgabe löschen',
'The time must be a numeric value' => 'Zeit nur als nummerische Angabe',
@ -370,7 +368,7 @@ return array(
'Display another project' => 'Zu Projekt wechseln...',
'Your GitHub account was successfully linked to your profile.' => 'GitHub Account erfolgreich mit dem Profil verbunden.',
'Unable to link your GitHub Account.' => 'Verbindung mit diesem GitHub Account nicht möglich.',
'GitHub authentication failed' => 'Zugang mit GitHub fehl geschlagen',
'GitHub authentication failed' => 'Zugriff mit GitHub fehlgeschlagen',
'Your GitHub account is no longer linked to your profile.' => 'GitHub Account nicht mehr mit dem Profil verbunden.',
'Unable to unlink your GitHub Account.' => 'Trennung der Verbindung zum GitHub Account nicht möglich.',
'Login with my GitHub Account' => 'Anmelden mit meinem GitHub Account',
@ -388,119 +386,176 @@ return array(
'Modification date' => 'Änderungsdatum',
'Completion date' => 'Abschlussdatum',
'Webhook URL for task creation' => 'Webhook URL zur Aufgabenerstellung',
'Webhook URL for task modification' => 'Webhook URL zur Aufgabenänderung',
// 'Clone' => '',
// 'Clone Project' => '',
// 'Project cloned successfully.' => '',
// 'Unable to clone this project.' => '',
// 'Email notifications' => '',
// 'Enable email notifications' => '',
// 'Task position:' => '',
// 'The task #%d have been opened.' => '',
// 'The task #%d have been closed.' => '',
// 'Sub-task updated' => '',
// 'Title:' => '',
// 'Status:' => '',
// 'Assignee:' => '',
// 'Time tracking:' => '',
// 'New sub-task' => '',
// 'New attachment added "%s"' => '',
// 'Comment updated' => '',
// 'New comment posted by %s' => '',
// 'List of due tasks for the project "%s"' => '',
// '[%s][New attachment] %s (#%d)' => '',
// '[%s][New comment] %s (#%d)' => '',
// '[%s][Comment updated] %s (#%d)' => '',
// '[%s][New subtask] %s (#%d)' => '',
// '[%s][Subtask updated] %s (#%d)' => '',
// '[%s][New task] %s (#%d)' => '',
// '[%s][Task updated] %s (#%d)' => '',
// '[%s][Task closed] %s (#%d)' => '',
// '[%s][Task opened] %s (#%d)' => '',
// '[%s][Due tasks]' => '',
// '[Kanboard] Notification' => '',
// 'I want to receive notifications only for those projects:' => '',
// 'view the task on Kanboard' => '',
// 'Public access' => '',
// 'Categories management' => '',
// 'Users management' => '',
// 'Active tasks' => '',
// 'Disable public access' => '',
// 'Enable public access' => '',
// 'Active projects' => '',
// 'Inactive projects' => '',
// 'Public access disabled' => '',
// 'Do you really want to disable this project: "%s"?' => '',
// 'Do you really want to duplicate this project: "%s"?' => '',
// 'Do you really want to enable this project: "%s"?' => '',
// 'Project activation' => '',
'Webhook URL for task modification' => 'Webhook URL zur Aufgabenbearbeitung',
'Clone' => 'duplizieren',
'Clone Project' => 'Projekt duplizieren',
'Project cloned successfully.' => 'Projekt wurde dupliziert.',
'Unable to clone this project.' => 'Duplizieren dieses Projekts schlug fehl.',
'Email notifications' => 'E-Mail Benachrichtigungen',
'Enable email notifications' => 'E-Mail Benachrichtigungen einschalten',
'Task position:' => 'Position der Aufgabe',
'The task #%d have been opened.' => 'Die Aufgabe #%d wurde geöffnet.',
'The task #%d have been closed.' => 'Die Aufgabe #%d wurde geschlossen.',
'Sub-task updated' => 'Unteraufgabe aktualisiert',
'Title:' => 'Titel',
'Status:' => 'Status',
'Assignee:' => 'Zuständigkeit:',
'Time tracking:' => 'Zeittracking',
'New sub-task' => 'Neue Unteraufgabe',
'New attachment added "%s"' => 'Neuer Anhang "%s" wurde hinzugefügt.',
'Comment updated' => 'Kommentar wurde aktualisiert',
'New comment posted by %s' => 'Neuer Kommentar verfasst durch %s',
'List of due tasks for the project "%s"' => 'Liste der fälligen Aufgaben für das Projekt "%s"',
'[%s][New attachment] %s (#%d)' => '[%s][Neuer Anhang] %s (#%d)',
'[%s][New comment] %s (#%d)' => '[%s][Neuer Kommentar] %s (#%d)',
'[%s][Comment updated] %s (#%d)' => '[%s][Kommentar aktualisisiert] %s (#%d)',
'[%s][New subtask] %s (#%d)' => '[%s][Neue Unteraufgabe] %s (#%d)',
'[%s][Subtask updated] %s (#%d)' => '[%s][Unteraufgabe aktualisisert] %s (#%d)',
'[%s][New task] %s (#%d)' => '[%s][Neue Aufgabe] %s (#%d)',
'[%s][Task updated] %s (#%d)' => '[%s][Aufgabe aktualisiert] %s (#%d)',
'[%s][Task closed] %s (#%d)' => '[%s][Aufgabe geschlossen] %s (#%d)',
'[%s][Task opened] %s (#%d)' => '[%s][Aufgabe geöffnet] %s (#%d)',
'[%s][Due tasks]' => '[%s][Fällige Aufgaben]',
'[Kanboard] Notification' => '[Kanboard] Benachrichtigung',
'I want to receive notifications only for those projects:' => 'Ich möchte nur für diese Projekte Benachrichtigungen erhalten:',
'view the task on Kanboard' => 'diese Aufgabe auf dem Kanboard zeigen',
'Public access' => 'Öffentlich',
'Category management' => 'Kategorien verwalten',
'User management' => 'Benutzer verwalten',
'Active tasks' => 'Aktive Aufgaben',
'Disable public access' => 'Öffentlichen Zugriff deaktivieren',
'Enable public access' => 'Öffentlichen Zugriff aktivieren',
'Active projects' => 'Aktive Projekte',
'Inactive projects' => 'Inaktive Projekte',
'Public access disabled' => 'Öffentlicher Zugriff deaktiviert',
'Do you really want to disable this project: "%s"?' => 'Möchten Sie dieses Projekt wirklich deaktivieren: "%s"',
'Do you really want to duplicate this project: "%s"?' => 'Möchten Sie dieses Projekt wirklich duplizieren: "%s"',
'Do you really want to enable this project: "%s"?' => 'Möchten Sie dieses Projekt wirklich aktivieren: "%s"',
'Project activation' => 'Projektaktivierung',
'Move the task to another project' => 'Aufgabe in ein anderes Projekt verschieben',
'Move to another project' => 'In anderes Projekt verschieben',
// 'Do you really want to duplicate this task?' => '',
// 'Duplicate a task' => '',
// 'External accounts' => '',
// 'Account type' => '',
// 'Local' => '',
// 'Remote' => '',
// 'Enabled' => '',
// 'Disabled' => '',
// 'Google account linked' => '',
// 'Github account linked' => '',
// 'Username:' => '',
// 'Name:' => '',
// 'Email:' => '',
// 'Default project:' => '',
// 'Notifications:' => '',
// 'Group:' => '',
// 'Regular user' => '',
// 'Account type:' => '',
// 'Edit profile' => '',
// 'Change password' => '',
// 'Password modification' => '',
// 'External authentications' => '',
// 'Google Account' => '',
// 'Github Account' => '',
// 'Never connected.' => '',
// 'No account linked.' => '',
// 'Account linked.' => '',
// 'No external authentication enabled.' => '',
// 'Password modified successfully.' => '',
// 'Unable to change the password.' => '',
// 'Change category for the task "%s"' => '',
// 'Change category' => '',
// '%s updated the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// '%s open the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// '%s moved the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to the position #%d in the column "%s"' => '',
// '%s moved the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to the column "%s"' => '',
// '%s created the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// '%s closed the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// '%s created a subtask for the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// '%s updated a subtask for the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// 'Assigned to %s with an estimate of %s/%sh' => '',
// 'Not assigned, estimate of %sh' => '',
// '%s updated a comment on the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// '%s commented the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// '%s\'s activity' => '',
// 'No activity.' => '',
// 'RSS feed' => '',
// '%s updated a comment on the task #%d' => '',
// '%s commented on the task #%d' => '',
// '%s updated a subtask for the task #%d' => '',
// '%s created a subtask for the task #%d' => '',
// '%s updated the task #%d' => '',
// '%s created the task #%d' => '',
// '%s closed the task #%d' => '',
// '%s open the task #%d' => '',
// '%s moved the task #%d to the column "%s"' => '',
// '%s moved the task #%d to the position %d in the column "%s"' => '',
// 'Activity' => '',
// 'Default values are "%s"' => '',
// 'Default columns for new projects (Comma-separated)' => '',
// 'Task assignee change' => '',
// '%s change the assignee of the task #%d' => '',
// '%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// '[%s][Column Change] %s (#%d)' => '',
// '[%s][Position Change] %s (#%d)' => '',
// '[%s][Assignee Change] %s (#%d)' => '',
// 'New password for the user "%s"' => '',
'Do you really want to duplicate this task?' => 'Möchten Sie diese Aufgabe wirklich duplizieren?',
'Duplicate a task' => 'Aufgabe duplizieren',
'External accounts' => 'Externe Accounts',
'Account type' => 'Accounttyp',
'Local' => 'Lokal',
'Remote' => 'Remote',
'Enabled' => 'angeschaltet',
'Disabled' => 'abgeschaltet',
'Google account linked' => 'Mit Googleaccount verbunden',
'Github account linked' => 'Mit Githubaccount verbunden',
'Username:' => 'Benutzername',
'Name:' => 'Name',
'Email:' => 'E-Mail',
'Default project:' => 'Standardprojekt',
'Notifications:' => 'Benachrichtigungen',
'Notifications' => 'Benachrichtigungen',
'Group:' => 'Gruppe',
'Regular user' => 'Standardbenutzer',
'Account type:' => 'Accounttyp',
'Edit profile' => 'Profil bearbeiten',
'Change password' => 'Passwort ändern',
'Password modification' => 'Passwortänderung',
'External authentications' => 'Externe Authentisierungsmethoden',
'Google Account' => 'Googleaccount',
'Github Account' => 'Githubaccount',
'Never connected.' => 'Noch nie verbunden.',
'No account linked.' => 'Kein Account verbunden.',
'Account linked.' => 'Account verbunden',
'No external authentication enabled.' => 'Es sind keine externen Authentisierungsmethoden aktiv.',
'Password modified successfully.' => 'Passwort wurde erfolgreich geändert.',
'Unable to change the password.' => 'Passwort konnte nicht geändert werden.',
'Change category for the task "%s"' => 'Kategorie der Aufgabe "%s" ändern',
'Change category' => 'Kategorie ändern',
'%s updated the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s hat die Aufgabe <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> aktualisiert',
'%s open the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s hat die Aufgabe <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> geöffnet',
'%s moved the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to the position #%d in the column "%s"' => '%s hat die Aufgabe <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> auf die Position #%d in der Spalte "%s" verschoben',
'%s moved the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to the column "%s"' => '%s hat die Aufgabe <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> in die Spalte "%s" verschoben',
'%s created the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s hat die Aufgabe <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> angelegt',
'%s closed the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s hat die Aufgabe <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> geschlossen',
'%s created a subtask for the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s hat eine Unteraufgabe für die Aufgabe <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> angelegt',
'%s updated a subtask for the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s hat eine Unteraufgabe der Aufgabe <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> verändert',
'Assigned to %s with an estimate of %s/%sh' => 'An %s zugewiesen mit einer Schätzung von %s/%s Stunden',
'Not assigned, estimate of %sh' => 'Nicht zugewiesen, Schätzung von %s Stunden',
'%s updated a comment on the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s hat einen Kommentat der Aufgabe <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> aktualisiert',
'%s commented the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s hat die Aufgabe <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> kommentiert',
'%s\'s activity' => '%s\'s Aktivität',
'No activity.' => 'Keine Aktivität.',
'RSS feed' => 'RSS Feed',
'%s updated a comment on the task #%d' => '%s hat einen Kommentar der Aufgabe #%d aktualisiert',
'%s commented on the task #%d' => '%s hat die Aufgabe #%d kommentiert',
'%s updated a subtask for the task #%d' => '%s hat eine Unteraufgabe der Aufgabe #%d aktualisiert',
'%s created a subtask for the task #%d' => '%s hat eine Unteraufgabe der Aufgabe #%d angelegt',
'%s updated the task #%d' => '%s hat die Aufgabe #%d aktualisiert',
'%s created the task #%d' => '%s hat die Aufgabe #%d angelegt',
'%s closed the task #%d' => '%s hat die Aufgabe #%d geschlossen',
'%s open the task #%d' => '%s hat die Aufgabe #%d geöffnet',
'%s moved the task #%d to the column "%s"' => '%s hat die Aufgabe #%d in die Spalte "%s" verschoben',
'%s moved the task #%d to the position %d in the column "%s"' => '%s hat die Aufgabe #%d an die Position %d in der Spalte "%s" verschoben',
'Activity' => 'Aktivität',
'Default values are "%s"' => 'Die Standardwerte sind "%s"',
'Default columns for new projects (Comma-separated)' => 'Standardspalten für neue Projekte (komma-getrennt)',
'Task assignee change' => 'Zuständigkeit geändert',
'%s change the assignee of the task #%d to %s' => '%s hat die Zusständigkeit der Aufgabe #%d geändert um %s',
'%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to %s' => '%s hat die Zuständigkeit der Aufgabe <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> geändert um %s',
'[%s][Column Change] %s (#%d)' => '[%s][Spaltenänderung] %s (#%d)',
'[%s][Position Change] %s (#%d)' => '[%s][Positionsänderung] %s (#%d)',
'[%s][Assignee Change] %s (#%d)' => '[%s][Zuständigkeitsänderung] %s (#%d)',
'New password for the user "%s"' => 'Neues Passwort des Benutzers "%s"',
'Choose an event' => 'Aktion wählen',
'Github commit received' => 'Github commit empfangen',
'Github issue opened' => 'Github Fehler geöffnet',
'Github issue closed' => 'Github Fehler geschlossen',
'Github issue reopened' => 'Github Fehler erneut geöffnet',
'Github issue assignee change' => 'Github Fehlerzuständigkeit geändert',
'Github issue label change' => 'Github Fehlerkennzeichnung verändert',
'Create a task from an external provider' => 'Eine Aufgabe durch einen externen Provider hinzufügen',
// 'Change the assignee based on an external username' => '',
'Change the category based on an external label' => 'Kategorie basierend auf einer externen Kennzeichnung ändern',
'Reference' => 'Referenz',
'Reference: %s' => 'Referenz: %s',
'Label' => 'Kennzeichnung',
'Database' => 'Datenbank',
'About' => 'Über',
'Database driver:' => 'Datenbanktreiber',
'Board settings' => 'Pinnwandeinstellungen',
'URL and token' => 'URL und Token',
'Webhook settings' => 'Webhook Einstellungen',
'URL for task creation:' => 'URL zur Aufgabenerstellung',
'Reset token' => 'Token zurücksetzen',
'API endpoint:' => 'API Endpunkt',
'Refresh interval for private board' => 'Aktualisierungsintervall für private Pinnwände',
'Refresh interval for public board' => 'Aktualisierungsintervall für öffentliche Pinnwände',
'Task highlight period' => 'Aufgaben-Hervorhebungsdauer',
'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Dauer (in Sekunden), wie lange eine Aufgabe als kürzlich verändert gilt (0 um diese Funktion zu deaktivieren, standardmäßig 2 Tage)',
'Frequency in second (60 seconds by default)' => 'Frequenz in Sekunden (standardmäßig 60 Sekunden)',
'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Frequenz in Sekunden (0 um diese Funktion zu deaktivieren, standardmäßig 10 Sekunden)',
'Application URL' => 'Applikations URL',
'Example: http://example.kanboard.net/ (used by email notifications)' => 'Beispiel: http://example.kanboard.net/ (wird für E-Mail-Benachrichtigungen verwendet)',
'Token regenerated.' => 'Token wurde neu generiert.',
'Date format' => 'Datumsformat',
'ISO format is always accepted, example: "%s" and "%s"' => 'ISO Format wird immer akzeptiert, z.B.: "%s" und "%s"',
'New private project' => 'Neues privates Projekt',
'This project is private' => 'Dieses Projekt ist privat',
'Type here to create a new sub-task' => 'Hier tippen, um eine neue Unteraufgabe zu erstellen',
'Add' => 'Hinzufügen',
'Estimated time: %s hours' => 'Geplante Zeit: %s Stunden',
'Time spent: %s hours' => 'Aufgewendete Zeit: %s Stunden',
'Started on %B %e, %Y' => 'Gestartet am %B %e %Y',
'Start date' => 'Startdatum',
'Time estimated' => 'Geplante Zeit',
'There is nothing assigned to you.' => 'Es ist nichts an Sie zugewiesen.',
'My tasks' => 'Meine Aufgaben',
'Activity stream' => 'Letzte Aktivitäten',
'Dashboard' => 'Dashboard',
'Confirmation' => 'Wiederholung',
'Allow everybody to access to this project' => 'Jedem Zugriff zu diesem Projekt gewähren',
'Everybody have access to this project.' => 'Jeder hat Zugriff zu diesem Projekt',
'Webhooks' => 'Webhooks',
'API' => 'API',
'Integration' => 'Integration',
'Github webhook' => 'Github Webhook',
'Help on Github webhook' => 'Hilfe bei einem Github Webhook',
'Create a comment from an external provider' => 'Kommentar eines externen Providers hinzufügen',
'Github issue comment created' => 'Github Fehler Kommentar hinzugefügt',
);

View file

@ -83,7 +83,7 @@ return array(
'Settings' => 'Preferencias',
'Application settings' => 'Parámetros de la aplicación',
'Language' => 'Idioma',
'Webhooks token:' => 'Ficha de seguridad (token) para los webhooks :',
'Webhook token:' => 'Ficha de seguridad (token) para los webhooks :',
'API token:' => 'Ficha de seguridad (token) para API:',
'More information' => 'Más informaciones',
'Database size:' => 'Tamaño de la base de datos:',
@ -196,7 +196,7 @@ return array(
'revoke' => 'revocar',
'List of authorized users' => 'Lista de los usuarios autorizados',
'User' => 'Usuario',
'Everybody have access to this project.' => 'Todo el mundo tiene acceso al proyecto.',
// 'Nobody have access to this project.' => '',
'You are not allowed to access to this project.' => 'No está autorizado a acceder a este proyecto.',
'Comments' => 'Comentarios',
'Post comment' => 'Commentar',
@ -209,8 +209,6 @@ return array(
'The description is required' => 'La descripción es obligatoria',
'Edit this task' => 'Editar esta tarea',
'Due Date' => 'Fecha límite',
'm/d/Y' => 'd/m/Y',
'month/day/year' => 'día/mes/año',
'Invalid date' => 'Fecha no válida',
'Must be done before %B %e, %Y' => 'Debe de estar hecho antes del %d/%m/%Y',
'%B %e, %Y' => '%d/%m/%Y',
@ -351,9 +349,9 @@ return array(
'estimated' => 'estimado',
'Sub-Tasks' => 'Sub-Tareas',
'Add a sub-task' => 'Añadir una sub-tarea',
'Original Estimate' => 'Estimado Original',
'Original estimate' => 'Estimado Original',
'Create another sub-task' => 'Crear otra sub-tarea',
'Time Spent' => 'Tiempo Transcurrido',
'Time spent' => 'Tiempo Transcurrido',
'Edit a sub-task' => 'Editar una sub-tarea',
'Remove a sub-task' => 'Suprimir una sub-tarea',
'The time must be a numeric value' => 'El tiempo debe de ser un valor numérico',
@ -422,8 +420,8 @@ return array(
'I want to receive notifications only for those projects:' => 'Quiero recibir notificaciones sólo de estos proyectos:',
'view the task on Kanboard' => 'ver la tarea en Kanboard',
'Public access' => 'Acceso público',
'Categories management' => 'Gestión de Categorías',
'Users management' => 'Gestión de Usuarios',
'Category management' => 'Gestión de Categorías',
'User management' => 'Gestión de Usuarios',
'Active tasks' => 'Tareas activas',
'Disable public access' => 'Desactivar acceso público',
'Enable public access' => 'Activar acceso público',
@ -451,6 +449,7 @@ return array(
'Email:' => 'Correo electrónico:',
'Default project:' => 'Proyecto por defecto:',
'Notifications:' => 'Notificaciones:',
// 'Notifications' => '',
'Group:' => 'Grupo:',
'Regular user' => 'Usuario regular:',
'Account type:' => 'Tipo de Cuenta:',
@ -496,11 +495,67 @@ return array(
'Activity' => 'Actividad',
'Default values are "%s"' => 'Los valores por defecto son "%s"',
'Default columns for new projects (Comma-separated)' => 'Columnas por defecto de los nuevos proyectos (Separadas mediante comas)',
// 'Task assignee change' => '',
// '%s change the assignee of the task #%d' => '',
// '%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// '[%s][Column Change] %s (#%d)' => '',
// '[%s][Position Change] %s (#%d)' => '',
// '[%s][Assignee Change] %s (#%d)' => '',
// 'New password for the user "%s"' => '',
'Task assignee change' => 'Cambiar persona asignada a la tarea',
// '%s change the assignee of the task #%d to %s' => '',
// '%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to %s' => '',
'[%s][Column Change] %s (#%d)' => '[%s][Cambia Columna] %s (#%d)',
'[%s][Position Change] %s (#%d)' => '[%s][Cambia Posición] %s (#%d)',
'[%s][Assignee Change] %s (#%d)' => '[%s][Cambia Persona Asignada] %s (#%d)',
'New password for the user "%s"' => 'Nueva contraseña para el usuario "%s"',
// 'Choose an event' => '',
// 'Github commit received' => '',
// 'Github issue opened' => '',
// 'Github issue closed' => '',
// 'Github issue reopened' => '',
// 'Github issue assignee change' => '',
// 'Github issue label change' => '',
// 'Create a task from an external provider' => '',
// 'Change the assignee based on an external username' => '',
// 'Change the category based on an external label' => '',
// 'Reference' => '',
// 'Reference: %s' => '',
// 'Label' => '',
// 'Database' => '',
// 'About' => '',
// 'Database driver:' => '',
// 'Board settings' => '',
// 'URL and token' => '',
// 'Webhook settings' => '',
// 'URL for task creation:' => '',
// 'Reset token' => '',
// 'API endpoint:' => '',
// 'Refresh interval for private board' => '',
// 'Refresh interval for public board' => '',
// 'Task highlight period' => '',
// 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => '',
// 'Frequency in second (60 seconds by default)' => '',
// 'Frequency in second (0 to disable this feature, 10 seconds by default)' => '',
// 'Application URL' => '',
// 'Example: http://example.kanboard.net/ (used by email notifications)' => '',
// 'Token regenerated.' => '',
// 'Date format' => '',
// 'ISO format is always accepted, example: "%s" and "%s"' => '',
// 'New private project' => '',
// 'This project is private' => '',
// 'Type here to create a new sub-task' => '',
// 'Add' => '',
// 'Estimated time: %s hours' => '',
// 'Time spent: %s hours' => '',
// 'Started on %B %e, %Y' => '',
// 'Start date' => '',
// 'Time estimated' => '',
// 'There is nothing assigned to you.' => '',
// 'My tasks' => '',
// 'Activity stream' => '',
// 'Dashboard' => '',
// 'Confirmation' => '',
// 'Allow everybody to access to this project' => '',
// 'Everybody have access to this project.' => '',
// 'Webhooks' => '',
// 'API' => '',
// 'Integration' => '',
// 'Github webhook' => '',
// 'Help on Github webhook' => '',
// 'Create a comment from an external provider' => '',
// 'Github issue comment created' => '',
);

View file

@ -83,7 +83,7 @@ return array(
'Settings' => 'Asetukset',
'Application settings' => 'Ohjelman asetukset',
'Language' => 'Kieli',
'Webhooks token:' => 'Webhooks avain:',
'Webhook token:' => 'Webhooks avain:',
// 'API token:' => '',
'More information' => 'Lisätietoja',
'Database size:' => 'Tietokannan koko:',
@ -196,7 +196,7 @@ return array(
'revoke' => 'poista',
'List of authorized users' => 'Sallittujen käyttäjien lista',
'User' => 'Käyttäjät',
'Everybody have access to this project.' => 'Kaikilla on pääsy tähän projektiin.',
// 'Nobody have access to this project.' => '',
'You are not allowed to access to this project.' => 'Sinulla ei ole pääsyä tähän projektiin.',
'Comments' => 'Kommentit',
'Post comment' => 'Lisää kommentti',
@ -209,8 +209,6 @@ return array(
'The description is required' => 'Kuvaus vaaditaan',
'Edit this task' => 'Muokkaa tehtävää',
'Due Date' => 'Deadline',
'm/d/Y' => 'd.m.Y',
'month/day/year' => 'päivä.kuukausi.vuosi',
'Invalid date' => 'Virheellinen päiväys',
'Must be done before %B %e, %Y' => 'Täytyy suorittaa ennen %d.%m.%Y',
'%B %e, %Y' => '%d.%m.%Y',
@ -351,9 +349,9 @@ return array(
'estimated' => 'estimoitu',
'Sub-Tasks' => 'Alitehtävät',
'Add a sub-task' => 'Lisää alitehtävä',
'Original Estimate' => 'Alkuperäinen estimaatti',
'Original estimate' => 'Alkuperäinen estimaatti',
'Create another sub-task' => 'Lisää toinen alitehtävä',
'Time Spent' => 'Käytetty aika',
'Time spent' => 'Käytetty aika',
'Edit a sub-task' => 'Muokkaa alitehtävää',
'Remove a sub-task' => 'Poista alitehtävä',
'The time must be a numeric value' => 'Ajan pitää olla numero',
@ -422,8 +420,8 @@ return array(
// 'I want to receive notifications only for those projects:' => '',
// 'view the task on Kanboard' => '',
// 'Public access' => '',
// 'Categories management' => '',
// 'Users management' => '',
// 'Category management' => '',
// 'User management' => '',
// 'Active tasks' => '',
// 'Disable public access' => '',
// 'Enable public access' => '',
@ -451,6 +449,7 @@ return array(
// 'Email:' => '',
// 'Default project:' => '',
// 'Notifications:' => '',
// 'Notifications' => '',
// 'Group:' => '',
// 'Regular user' => '',
// 'Account type:' => '',
@ -497,10 +496,66 @@ return array(
// 'Default values are "%s"' => '',
// 'Default columns for new projects (Comma-separated)' => '',
// 'Task assignee change' => '',
// '%s change the assignee of the task #%d' => '',
// '%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// '%s change the assignee of the task #%d to %s' => '',
// '%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to %s' => '',
// '[%s][Column Change] %s (#%d)' => '',
// '[%s][Position Change] %s (#%d)' => '',
// '[%s][Assignee Change] %s (#%d)' => '',
// 'New password for the user "%s"' => '',
// 'Choose an event' => '',
// 'Github commit received' => '',
// 'Github issue opened' => '',
// 'Github issue closed' => '',
// 'Github issue reopened' => '',
// 'Github issue assignee change' => '',
// 'Github issue label change' => '',
// 'Create a task from an external provider' => '',
// 'Change the assignee based on an external username' => '',
// 'Change the category based on an external label' => '',
// 'Reference' => '',
// 'Reference: %s' => '',
// 'Label' => '',
// 'Database' => '',
// 'About' => '',
// 'Database driver:' => '',
// 'Board settings' => '',
// 'URL and token' => '',
// 'Webhook settings' => '',
// 'URL for task creation:' => '',
// 'Reset token' => '',
// 'API endpoint:' => '',
// 'Refresh interval for private board' => '',
// 'Refresh interval for public board' => '',
// 'Task highlight period' => '',
// 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => '',
// 'Frequency in second (60 seconds by default)' => '',
// 'Frequency in second (0 to disable this feature, 10 seconds by default)' => '',
// 'Application URL' => '',
// 'Example: http://example.kanboard.net/ (used by email notifications)' => '',
// 'Token regenerated.' => '',
// 'Date format' => '',
// 'ISO format is always accepted, example: "%s" and "%s"' => '',
// 'New private project' => '',
// 'This project is private' => '',
// 'Type here to create a new sub-task' => '',
// 'Add' => '',
// 'Estimated time: %s hours' => '',
// 'Time spent: %s hours' => '',
// 'Started on %B %e, %Y' => '',
// 'Start date' => '',
// 'Time estimated' => '',
// 'There is nothing assigned to you.' => '',
// 'My tasks' => '',
// 'Activity stream' => '',
// 'Dashboard' => '',
// 'Confirmation' => '',
// 'Allow everybody to access to this project' => '',
// 'Everybody have access to this project.' => '',
// 'Webhooks' => '',
// 'API' => '',
// 'Integration' => '',
// 'Github webhook' => '',
// 'Help on Github webhook' => '',
// 'Create a comment from an external provider' => '',
// 'Github issue comment created' => '',
);

View file

@ -83,7 +83,7 @@ return array(
'Settings' => 'Préférences',
'Application settings' => 'Paramètres de l\'application',
'Language' => 'Langue',
'Webhooks token:' => 'Jeton de securité pour les webhooks :',
'Webhook token:' => 'Jeton de securité pour les webhooks :',
'API token:' => 'Jeton de securité pour l\'API :',
'More information' => 'Plus d\'informations',
'Database size:' => 'Taille de la base de données :',
@ -196,7 +196,7 @@ return array(
'revoke' => 'révoquer',
'List of authorized users' => 'Liste des utilisateurs autorisés',
'User' => 'Utilisateur',
'Everybody have access to this project.' => 'Tout le monde a accès au projet.',
'Nobody have access to this project.' => 'Personne n\'est autorisé à accéder au projet.',
'You are not allowed to access to this project.' => 'Vous n\'êtes pas autorisé à accéder à ce projet.',
'Comments' => 'Commentaires',
'Post comment' => 'Commenter',
@ -209,11 +209,9 @@ return array(
'The description is required' => 'La description est obligatoire',
'Edit this task' => 'Modifier cette tâche',
'Due Date' => 'Date d\'échéance',
'm/d/Y' => 'd/m/Y', // Date format parsed with php
'month/day/year' => 'jour/mois/année', // Help shown to the user
'Invalid date' => 'Date invalide',
'Must be done before %B %e, %Y' => 'Doit être fait avant le %d/%m/%Y',
'%B %e, %Y' => '%d/%m/%Y',
'%B %e, %Y' => '%d %B %Y',
'Automatic actions' => 'Actions automatisées',
'Your automatic action have been created successfully.' => 'Votre action automatisée a été ajouté avec succès.',
'Unable to create your automatic action.' => 'Impossible de créer votre action automatisée.',
@ -351,9 +349,9 @@ return array(
'estimated' => 'estimé',
'Sub-Tasks' => 'Sous-Tâches',
'Add a sub-task' => 'Ajouter une sous-tâche',
'Original Estimate' => 'Estimation originale',
'Original estimate' => 'Estimation originale',
'Create another sub-task' => 'Créer une autre sous-tâche',
'Time Spent' => 'Temps passé',
'Time spent' => 'Temps passé',
'Edit a sub-task' => 'Modifier une sous-tâche',
'Remove a sub-task' => 'Supprimer une sous-tâche',
'The time must be a numeric value' => 'Le temps doit-être une valeur numérique',
@ -422,8 +420,8 @@ return array(
'I want to receive notifications only for those projects:' => 'Je souhaite reçevoir les notifications uniquement pour les projets sélectionnés :',
'view the task on Kanboard' => 'voir la tâche sur Kanboard',
'Public access' => 'Accès public',
'Categories management' => 'Gestion des catégories',
'Users management' => 'Gestion des utilisateurs',
'Category management' => 'Gestion des catégories',
'User management' => 'Gestion des utilisateurs',
'Active tasks' => 'Tâches actives',
'Disable public access' => 'Désactiver l\'accès public',
'Enable public access' => 'Activer l\'accès public',
@ -451,10 +449,11 @@ return array(
'Email:' => 'Email :',
'Default project:' => 'Projet par défaut :',
'Notifications:' => 'Notifications :',
'Notifications' => 'Notifications',
'Group:' => 'Groupe :',
'Regular user' => 'Utilisateur normal',
'Account type:' => 'Type de compte :',
'Edit profile' => 'Modifier le profile',
'Edit profile' => 'Modifier le profil',
'Change password' => 'Changer le mot de passe',
'Password modification' => 'Changement de mot de passe',
'External authentications' => 'Authentifications externe',
@ -497,10 +496,66 @@ return array(
'Default values are "%s"' => 'Les valeurs par défaut sont « %s »',
'Default columns for new projects (Comma-separated)' => 'Colonnes par défaut pour les nouveaux projets (séparé par des virgules)',
'Task assignee change' => 'Modification de la personne assignée sur une tâche',
'%s change the assignee of the task #%d' => '%s a changé la personne assignée sur la tâche #%d',
'%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s a changé la personne assignée sur la tâche <a href="?controller=task&amp;action=show&amp;task_id=%d">n°%d</a>',
'%s change the assignee of the task #%d to %s' => '%s a changé la personne assignée sur la tâche #%d pour %s',
'%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to %s' => '%s a changé la personne assignée sur la tâche <a href="?controller=task&amp;action=show&amp;task_id=%d">n°%d</a> pour %s',
'[%s][Column Change] %s (#%d)' => '[%s][Changement de colonne] %s (#%d)',
'[%s][Position Change] %s (#%d)' => '[%s][Changement de position] %s (#%d)',
'[%s][Assignee Change] %s (#%d)' => '[%s][Changement d\'assigné] %s (#%d)',
'New password for the user "%s"' => 'Nouveau mot de passe pour l\'utilisateur « %s »',
'Choose an event' => 'Choisir un événement',
'Github commit received' => '« Commit » reçu via Github',
'Github issue opened' => 'Ouverture d\'un ticket sur Github',
'Github issue closed' => 'Fermeture d\'un ticket sur Github',
'Github issue reopened' => 'Réouverture d\'un ticket sur Github',
'Github issue assignee change' => 'Changement d\'assigné sur un ticket Github',
'Github issue label change' => 'Changement de libellé sur un ticket Github',
'Create a task from an external provider' => 'Créer une tâche depuis un fournisseur externe',
'Change the assignee based on an external username' => 'Changer l\'assigné en fonction d\'un utilisateur externe',
'Change the category based on an external label' => 'Changer la catégorie en fonction d\'un libellé externe',
'Reference' => 'Référence',
'Reference: %s' => 'Référence : %s',
'Label' => 'Libellé',
'Database' => 'Base de données',
'About' => 'A propos',
'Database driver:' => 'Type de base de données :',
'Board settings' => 'Paramètres du tableau',
'URL and token' => 'URL et jeton de sécurité',
'Webhook settings' => 'Paramètres pour les webhooks',
'URL for task creation:' => 'URL pour la création de tâche :',
'Reset token' => 'Regénérer le jeton de sécurité',
'API endpoint:' => 'URL de l\'API :',
'Refresh interval for private board' => 'Intervalle pour rafraîchir un tableau privé',
'Refresh interval for public board' => 'Intervalle pour rafraîchir un tableau public',
'Task highlight period' => 'Durée pour mettre une tâche en évidence',
'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Durée en seconde pour considérer une tâche comme récemment modifiée (0 pour désactiver, 2 jours par défaut)',
'Frequency in second (60 seconds by default)' => 'Fréquence en seconde (60 secondes par défaut)',
'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Fréquence en seconde (0 pour désactiver, 10 secondes par défaut)',
'Application URL' => 'URL de l\'application',
'Example: http://example.kanboard.net/ (used by email notifications)' => 'Exemple : http://exemple.kanboard.net/ (utilisé pour les notifications)',
'Token regenerated.' => 'Jeton de sécurité regénéré.',
'Date format' => 'Format des dates',
'ISO format is always accepted, example: "%s" and "%s"' => 'Le format ISO est toujours accepté, exemple : « %s » et « %s »',
'New private project' => 'Nouveau projet privé',
'This project is private' => 'Ce projet est privé',
'Type here to create a new sub-task' => 'Créer une sous-tâche en écrivant le titre ici',
'Add' => 'Ajouter',
'Estimated time: %s hours' => 'Temps estimé: %s hours',
'Time spent: %s hours' => 'Temps passé : %s heures',
'Started on %B %e, %Y' => 'Commençé le %d/%m/%Y',
'Start date' => 'Date de début',
'Time estimated' => 'Temps estimé',
'There is nothing assigned to you.' => 'Aucune tâche assignée pour vous.',
'My tasks' => 'Mes tâches',
'Activity stream' => 'Flux d\'activité',
'Dashboard' => 'Tableau de bord',
'Confirmation' => 'Confirmation',
'Allow everybody to access to this project' => 'Autoriser tout le monde à accéder à ce projet',
'Everybody have access to this project.' => 'Tout le monde a acccès à ce projet.',
'Webhooks' => 'Webhooks',
'API' => 'API',
'Integration' => 'Intégration',
'Github webhook' => 'Webhook Github',
'Help on Github webhook' => 'Aide sur les webhooks Github',
'Create a comment from an external provider' => 'Créer un commentaire depuis un fournisseur externe',
'Github issue comment created' => 'Commentaire créé sur un ticket Github',
);

View file

@ -14,13 +14,13 @@ return array(
'Yellow' => 'Giallo',
'Blue' => 'Blu',
'Green' => 'Verde',
'Purple' => 'Porpora',
'Purple' => 'Viola',
'Red' => 'Rosso',
'Orange' => 'Arancione',
'Grey' => 'Grigio',
'Save' => 'Salvare',
'Login' => 'Entra',
'Official website:' => 'Sito web ufficiale :',
'Official website:' => 'Sito web ufficiale:',
'Unassigned' => 'Non assegnato',
'View this task' => 'Vedere questo compito',
'Remove user' => 'Cancellare un utente',
@ -39,7 +39,7 @@ return array(
'Only administrators can access to this page.' => 'Solo gli amministratori possono accedere a questa pagina.',
'Edit user' => 'Modificare un utente',
'Logout' => 'Uscire',
'Bad username or password' => 'Utente o password sbagliato',
'Bad username or password' => 'Utente o password errati',
'users' => 'utenti',
'projects' => 'progetti',
'Edit project' => 'Modificare progetto',
@ -63,7 +63,7 @@ return array(
'Disable' => 'Disattivare',
'Enable' => 'Attivare',
'New project' => 'Nuovo progetto',
'Do you really want to remove this project: "%s"?' => 'Vuoi veramente eliminare questo progetto: « %s » ?',
'Do you really want to remove this project: "%s"?' => 'Veramente vuoi eliminare questo progetto: « %s » ?',
'Remove project' => 'Cancellare il progetto',
'Boards' => 'Bacheche',
'Edit the board for "%s"' => 'Modificare la bacheca per « %s »',
@ -76,25 +76,25 @@ return array(
'Nobody assigned' => 'Nessuno assegnato',
'Assigned to %s' => 'Assegnato a %s',
'Remove a column' => 'Cancellare questa colonna',
'Remove a column from a board' => 'Cancellare una colonna di una bacheca',
'Remove a column from a board' => 'Cancellare una colonna da una bacheca',
'Unable to remove this column.' => 'Non si può cancellare questa colonna.',
'Do you really want to remove this column: "%s"?' => 'Veramente desideri cancellare questa colonna : « %s » ?',
'This action will REMOVE ALL TASKS associated to this column!' => 'Questa azione cancellerà TUTTI I COMPITI legati a questa colonna!',
'Settings' => 'Impostazioni',
'Application settings' => 'Impostazioni dell\'applicazione',
'Language' => 'Lingua',
'Webhooks token:' => 'Identificatore (token) per i webhooks :',
'Webhook token:' => 'Identificatore (token) per i webhooks :',
// 'API token:' => '',
'More information' => 'Più informazione',
'More information' => 'Più informazioni',
'Database size:' => 'Dimensioni della base dati:',
'Download the database' => 'Scaricare la base dati',
'Optimize the database' => 'Ottimizare la base dati',
'(VACUUM command)' => '(Comando VACUUM)',
'(Gzip compressed Sqlite file)' => '(File Sqlite compresso in Gzip)',
'User settings' => 'Impostazioni di utente',
'My default project:' => 'Il mio progetto predefinito: ',
'My default project:' => 'Il mio progetto predefinito:',
'Close a task' => 'Chiudere un compito',
'Do you really want to close this task: "%s"?' => 'Veramente desidera chiudere questo compito: « %s » ?',
'Do you really want to close this task: "%s"?' => 'Veramente desideri chiudere questo compito: « %s » ?',
'Edit a task' => 'Modificare un compito',
'Column' => 'colonna',
// 'Color' => '',
@ -102,7 +102,7 @@ return array(
'Create another task' => 'Creare un nuovo compito',
'New task' => 'Nuovo compito',
'Open a task' => 'Aprire un compito',
'Do you really want to open this task: "%s"?' => 'Veramente desidera aprire questo compito: « %s » ?',
'Do you really want to open this task: "%s"?' => 'Veramente desideri aprire questo compito: « %s » ?',
'Back to the board' => 'Tornare alla bacheca',
// 'Created on %B %e, %Y at %k:%M %p' => '',
'There is nobody assigned' => 'Non c\'è nessuno assegnato a questo compito',
@ -134,9 +134,9 @@ return array(
'The language is required' => 'Si richiede una lingua',
'There is no active project, the first step is to create a new project.' => 'Non ci sono progetti attivi, il primo passo consiste in creare un nuovo progetto.',
'Settings saved successfully.' => 'Impostazioni salvate correttamente.',
'Unable to save your settings.' => 'Non si possono salvare gli impostazioni.',
'Unable to save your settings.' => 'Non si possono salvare le impostazioni.',
'Database optimization done.' => 'Ottimizzazione della base dati conclusa.',
'Your project have been created successfully.' => 'Il suo progetto è stato creato correttamente.',
'Your project have been created successfully.' => 'Il tuo progetto è stato creato correttamente.',
'Unable to create your project.' => 'Non si può creare il progetto.',
'Project updated successfully.' => 'Progetto aggiornato correttamente.',
'Unable to update this project.' => 'Non si può aggiornare il progetto.',
@ -183,7 +183,7 @@ return array(
'Change assignee for the task "%s"' => 'Cambiare la persona assegnata per il compito « %s »',
'Timezone' => 'Fuso orario',
'Sorry, I didn\'t found this information in my database!' => 'Mi dispiace, non ho trovato questa informazione sulla base dati!',
'Page not found' => 'Página non trovata',
'Page not found' => 'Pagina non trovata',
// 'Complexity' => '',
'limit' => 'limite',
'Task limit' => 'Numero massimo di compiti',
@ -196,7 +196,7 @@ return array(
'revoke' => 'revocare',
'List of authorized users' => 'Lista di utenti autorizzati',
'User' => 'Utente',
'Everybody have access to this project.' => 'Tutti hanno accesso a questo progetto.',
// 'Nobody have access to this project.' => '',
'You are not allowed to access to this project.' => 'Non hai l\'accesso a questo progetto.',
'Comments' => 'Commenti',
'Post comment' => 'Mandare commento',
@ -209,8 +209,6 @@ return array(
'The description is required' => 'Si richiede una descrizione',
'Edit this task' => 'Modificare questo compito',
'Due Date' => 'Data di scadenza',
'm/d/Y' => 'd/m/Y',
'month/day/year' => 'giorno/mese/anno',
'Invalid date' => 'Data sbagliata',
// 'Must be done before %B %e, %Y' => '',
// '%B %e, %Y' => '',
@ -238,8 +236,8 @@ return array(
'Assign the task to a specific user' => 'Assegnare questo compito a un utente specifico',
'Assign the task to the person who does the action' => 'Assegnare il compito all\'utente che svolge l\'azione',
'Duplicate the task to another project' => 'Duplicare il compito in altro progetto',
'Move a task to another column' => 'Muovere un compito ad un altra colonna',
'Move a task to another position in the same column' => 'Muovere un compito ad altra posizione sulla stessa colonna',
'Move a task to another column' => 'Muovere un compito in un\'altra colonna',
'Move a task to another position in the same column' => 'Muovere un compito in un\'altra posizione sulla stessa colonna',
'Task modification' => 'Modifica di un compito',
'Task creation' => 'Creazione di un compito',
'Open a closed task' => 'Riaprire un compito',
@ -272,34 +270,34 @@ return array(
'Authentication method' => 'Metodo di autenticazzione',
'IP address' => 'Indirizzo IP',
'User agent' => 'Navigatore',
'Persistent connections' => 'Conessioni persistenti',
'No session.' => 'Non essiste sessione.',
'Persistent connections' => 'Connessioni persistenti',
'No session.' => 'Non esiste sessione.',
'Expiration date' => 'Data di scadenza',
'Remember Me' => 'Riccordami',
'Remember Me' => 'Ricordami',
'Creation date' => 'Data di creazione',
'Filter by user' => 'Filtrado mediante utente',
'Filter by user' => 'Filtrato mediante utente',
'Filter by due date' => 'Filtrare attraverso data di scadenza',
'Everybody' => 'Tutti',
'Open' => 'Aperto',
'Closed' => 'Chiuso',
'Search' => 'Cercare',
'Nothing found.' => 'Non si è trovato nulla.',
'Search in the project "%s"' => 'Cercare sul progetto "%s"',
'Search in the project "%s"' => 'Cercare nel progetto "%s"',
'Due date' => 'Data di scadenza',
'Others formats accepted: %s and %s' => 'Altri formati accettati: %s y %s',
'Description' => 'Descrizione',
'%d comments' => '%d commenti',
'%d comment' => '%d commento',
'Email address invalid' => 'Indirizzo e-mail sbagliato',
'Your Google Account is not linked anymore to your profile.' => 'Il suo account Google non i più collegato col suo profilo',
'Your Google Account is not linked anymore to your profile.' => 'Il suo account Google non è più collegato al suo profilo',
'Unable to unlink your Google Account.' => 'Non si può svincolare l\'account di Google.',
'Google authentication failed' => 'Non si è riuscito ad ingressare su Google',
'Unable to link your Google Account.' => 'Non si può collegare con il suo account di Google.',
'Your Google Account is linked to your profile successfully.' => 'Il suo account di Google è stato collegato correttamente al suo profilo.',
'Google authentication failed' => 'Autenticazione con Google non riuscita',
'Unable to link your Google Account.' => 'Non si può collegare il tuo account di Google.',
'Your Google Account is linked to your profile successfully.' => 'Il tuo account di Google è stato collegato correttamente al tuo profilo.',
'Email' => 'E-mail',
'Link my Google Account' => 'Collegare con il mio Account di Google',
'Unlink my Google Account' => 'Svincolare con il mio account di Google',
'Login with my Google Account' => 'Ingressa con il mio Account di Google',
'Link my Google Account' => 'Collegare il mio Account di Google',
'Unlink my Google Account' => 'Scollegare il mio account di Google',
'Login with my Google Account' => 'Entra con il mio Account di Google',
'Project not found.' => 'progetto non trovato.',
'Task #%d' => 'Compito numero %d',
'Task removed successfully.' => 'Compito cancellato correttamente.',
@ -308,15 +306,15 @@ return array(
'Do you really want to remove this task: "%s"?' => 'Veramente vuoi cancellare questo compito: "%s"?',
'Assign automatically a color based on a category' => 'Assegnare un colore in modo automatico basandosi sulla categoria',
'Assign automatically a category based on a color' => 'Assegnare una categoria in modo automatico basandosi sul colore',
'Task creation or modification' => 'Creazione o Modifica di compito',
'Task creation or modification' => 'Creazione o modifica di compito',
'Category' => 'Categoria',
'Category:' => 'Categoria:',
'Categories' => 'Categorie',
'Category not found.' => 'Categoria non trovata.',
'Your category have been created successfully.' => 'La sua categoria è stata creata correttamente.',
'Unable to create your category.' => 'Non si può creare la sua categoria.',
'Your category have been updated successfully.' => 'La sua categoria è stata aggiornata correttamente.',
'Unable to update your category.' => 'Non si può aggiornare la sua categoria.',
'Your category have been created successfully.' => 'La tua categoria è stata creata correttamente.',
'Unable to create your category.' => 'Non si può creare la tua categoria.',
'Your category have been updated successfully.' => 'La tua categoria è stata aggiornata correttamente.',
'Unable to update your category.' => 'Non si può aggiornare la tua categoria.',
'Remove a category' => 'Cancellare una categoria',
'Category removed successfully.' => 'Categoria cancellata correttamente.',
'Unable to remove this category.' => 'Non si può cancellare questa categoria.',
@ -344,45 +342,45 @@ return array(
'Time tracking' => 'Time tracking',
'Estimate:' => 'Stimato:',
'Spent:' => 'Trascorso:',
'Do you really want to remove this sub-task?' => 'Vuoi veramente cancellare questo sub-compito?',
'Do you really want to remove this sub-task?' => 'Vuoi veramente cancellare questo sotto-compito?',
'Remaining:' => 'Rimangono',
'hours' => 'ore',
'spent' => 'trascorse',
'estimated' => 'stimate',
'Sub-Tasks' => 'Sub-Compiti',
'Add a sub-task' => 'Aggiungere un sub-compito',
'Original Estimate' => 'Stima originale',
'Create another sub-task' => 'Crear un altro sub-compito',
'Time Spent' => 'Tempo Trascorso',
'Edit a sub-task' => 'Modificare un sub-compito',
'Remove a sub-task' => 'Cancellare un sub-compito',
'Sub-Tasks' => 'Sotto-compiti',
'Add a sub-task' => 'Aggiungere un sotto-compito',
'Original estimate' => 'Stima originale',
'Create another sub-task' => 'Creare un altro sotto-compito',
'Time spent' => 'Tempo Trascorso',
'Edit a sub-task' => 'Modificare un sotto-compito',
'Remove a sub-task' => 'Cancellare un sotto-compito',
'The time must be a numeric value' => 'Il tempo deve essere un valore numerico',
'Todo' => 'Da fare',
'In progress' => 'In corso',
'Sub-task removed successfully.' => 'Sub-compito cancellato correttamente.',
'Unable to remove this sub-task.' => 'Non si può cancellare questo sub-compito.',
'Sub-task updated successfully.' => 'Sub-compito aggiornato correttamente.',
'Unable to update your sub-task.' => 'Non si può aggiornare il suo sub-compito.',
'Unable to create your sub-task.' => 'Non si può creare il suo sub-compito.',
'Sub-task added successfully.' => 'Sub-compito aggiunto correttamente.',
'Sub-task removed successfully.' => 'Sotto-compito cancellato correttamente.',
'Unable to remove this sub-task.' => 'Non si può cancellare questo sotto-compito.',
'Sub-task updated successfully.' => 'Sotto-compito aggiornato correttamente.',
'Unable to update your sub-task.' => 'Non si può aggiornare il tuo sotto-compito.',
'Unable to create your sub-task.' => 'Non si può creare il tuo sotto-compito.',
'Sub-task added successfully.' => 'Sotto-compito aggiunto correttamente.',
'Maximum size: ' => 'Dimensioni massime',
'Unable to upload the file.' => 'Non si può caricare il file.',
'Display another project' => 'Mostrare un altro progetto',
'Your GitHub account was successfully linked to your profile.' => 'Il suo account di Github è stato collegato correttamente col suo profilo.',
'Unable to link your GitHub Account.' => 'Non si può collegarre col suo account di Github.',
'GitHub authentication failed' => 'L\'autenticazione non è stata possibile',
'Your GitHub account is no longer linked to your profile.' => 'Il suo account di Github non è più vincolato al suo profilo.',
'Unable to unlink your GitHub Account.' => 'Non si può svincolare il suo account di Github.',
'Login with my GitHub Account' => 'Ingressare col suo account di Github',
'Link my GitHub Account' => 'Lier mon compte Github',
'Unlink my GitHub Account' => 'Non impiegare più l\'account di Github',
'Your GitHub account was successfully linked to your profile.' => 'Il suo account di Github è stato collegato correttamente col tuo profilo.',
'Unable to link your GitHub Account.' => 'Non si può collegarre il tuo account di Github.',
'GitHub authentication failed' => 'Autenticazione con GitHub non riuscita',
'Your GitHub account is no longer linked to your profile.' => 'Il tuo account di Github non è più collegato al tuo profilo.',
'Unable to unlink your GitHub Account.' => 'Non si può collegare il tuo account di Github.',
'Login with my GitHub Account' => 'Entrare col tuo account di Github',
'Link my GitHub Account' => 'Collegare il mio account Github',
'Unlink my GitHub Account' => 'Scollegare il mio account di Github',
'Created by %s' => 'Creato da %s',
'Last modified on %B %e, %Y at %k:%M %p' => 'Ultima modifica il %d/%m/%Y alle %H:%M',
'Tasks Export' => 'Esportazione di compiti',
'Tasks exportation for "%s"' => 'Esportazione di compiti per « %s »',
'Start Date' => 'Data d\'inizio',
'End Date' => 'Data di fine',
'Execute' => 'Essecutare',
'Execute' => 'Eseguire',
'Task Id' => 'Identificatore del compito',
'Creator' => 'Creatore',
'Modification date' => 'Data di modifica',
@ -411,19 +409,19 @@ return array(
'[%s][New attachment] %s (#%d)' => '[%s][Nuovo allegato] %s (#%d)',
'[%s][New comment] %s (#%d)' => '[%s][Nuovo commento] %s (#%d)',
'[%s][Comment updated] %s (#%d)' => '[%s][Commento aggiornato] %s (#%d)',
'[%s][New subtask] %s (#%d)' => '[%s][Nuovo sub-compito] %s (#%d)',
'[%s][Subtask updated] %s (#%d)' => '[%s][Sub-compito aggiornato] %s (#%d)',
'[%s][New subtask] %s (#%d)' => '[%s][Nuovo sotto-compito] %s (#%d)',
'[%s][Subtask updated] %s (#%d)' => '[%s][Sotto-compito aggiornato] %s (#%d)',
'[%s][New task] %s (#%d)' => '[%s][Nuovo compito] %s (#%d)',
'[%s][Task updated] %s (#%d)' => '[%s][Compito aggiornato] %s (#%d)',
'[%s][Task closed] %s (#%d)' => '[%s][Compito chiuso] %s (#%d)',
'[%s][Task opened] %s (#%d)' => '[%s][Compito aperto] %s (#%d)',
'[%s][Due tasks]' => '[%s][Compiti scaduti]',
'[Kanboard] Notification' => '[Kanboard] Notification',
'[Kanboard] Notification' => '[Kanboard] Notifica',
'I want to receive notifications only for those projects:' => 'Vorrei ricevere le notifiche solo da questi progetti:',
'view the task on Kanboard' => 'vedi il compito su Kanboard',
// 'Public access' => '',
// 'Categories management' => '',
// 'Users management' => '',
// 'Category management' => '',
// 'User management' => '',
// 'Active tasks' => '',
// 'Disable public access' => '',
// 'Enable public access' => '',
@ -451,6 +449,7 @@ return array(
// 'Email:' => '',
// 'Default project:' => '',
// 'Notifications:' => '',
// 'Notifications' => '',
// 'Group:' => '',
// 'Regular user' => '',
// 'Account type:' => '',
@ -497,10 +496,66 @@ return array(
// 'Default values are "%s"' => '',
// 'Default columns for new projects (Comma-separated)' => '',
// 'Task assignee change' => '',
// '%s change the assignee of the task #%d' => '',
// '%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// '%s change the assignee of the task #%d to %s' => '',
// '%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to %s' => '',
// '[%s][Column Change] %s (#%d)' => '',
// '[%s][Position Change] %s (#%d)' => '',
// '[%s][Assignee Change] %s (#%d)' => '',
// 'New password for the user "%s"' => '',
// 'Choose an event' => '',
// 'Github commit received' => '',
// 'Github issue opened' => '',
// 'Github issue closed' => '',
// 'Github issue reopened' => '',
// 'Github issue assignee change' => '',
// 'Github issue label change' => '',
// 'Create a task from an external provider' => '',
// 'Change the assignee based on an external username' => '',
// 'Change the category based on an external label' => '',
// 'Reference' => '',
// 'Reference: %s' => '',
// 'Label' => '',
// 'Database' => '',
// 'About' => '',
// 'Database driver:' => '',
// 'Board settings' => '',
// 'URL and token' => '',
// 'Webhook settings' => '',
// 'URL for task creation:' => '',
// 'Reset token' => '',
// 'API endpoint:' => '',
// 'Refresh interval for private board' => '',
// 'Refresh interval for public board' => '',
// 'Task highlight period' => '',
// 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => '',
// 'Frequency in second (60 seconds by default)' => '',
// 'Frequency in second (0 to disable this feature, 10 seconds by default)' => '',
// 'Application URL' => '',
// 'Example: http://example.kanboard.net/ (used by email notifications)' => '',
// 'Token regenerated.' => '',
// 'Date format' => '',
// 'ISO format is always accepted, example: "%s" and "%s"' => '',
// 'New private project' => '',
// 'This project is private' => '',
// 'Type here to create a new sub-task' => '',
// 'Add' => '',
// 'Estimated time: %s hours' => '',
// 'Time spent: %s hours' => '',
// 'Started on %B %e, %Y' => '',
// 'Start date' => '',
// 'Time estimated' => '',
// 'There is nothing assigned to you.' => '',
// 'My tasks' => '',
// 'Activity stream' => '',
// 'Dashboard' => '',
// 'Confirmation' => '',
// 'Allow everybody to access to this project' => '',
// 'Everybody have access to this project.' => '',
// 'Webhooks' => '',
// 'API' => '',
// 'Integration' => '',
// 'Github webhook' => '',
// 'Help on Github webhook' => '',
// 'Create a comment from an external provider' => '',
// 'Github issue comment created' => '',
);

View file

@ -0,0 +1,561 @@
<?php
return array(
'None' => 'なし',
'edit' => '変更',
'Edit' => '変更',
'remove' => '削除する',
'Remove' => '削除する',
'Update' => '変更',
'Yes' => 'はい',
'No' => 'いいえ',
'cancel' => 'キャンセル',
'or' => 'または',
'Yellow' => 'イエロー',
'Blue' => 'ブルー',
'Green' => 'グリーン',
'Purple' => 'パープル',
'Red' => 'レッド',
'Orange' => 'オレンジ',
'Grey' => 'グレー',
'Save' => '保存',
'Login' => 'ログイン',
'Official website:' => '公式 Web サイト:',
'Unassigned' => '担当なし',
'View this task' => 'このタクスを見る',
'Remove user' => 'ユーザの削除',
'Do you really want to remove this user: "%s"?' => 'ユーザ「%s」を本当に削除しますか',
'New user' => 'ユーザを追加する',
'All users' => 'すべてのユーザ',
'Username' => 'ユーザ名',
'Password' => 'パスワード',
'Default project' => 'デフォルトプロジェクト',
'Administrator' => '管理者',
'Sign in' => 'ログイン',
'Users' => 'ユーザ',
'No user' => 'ユーザがいません',
'Forbidden' => 'アクセス拒否',
'Access Forbidden' => 'アクセスが拒否されました',
'Only administrators can access to this page.' => '管理者のみがこのページにアクセスできます。',
'Edit user' => 'ユーザを変更する',
'Logout' => 'ログアウト',
'Bad username or password' => 'ユーザ名またはパスワードが違います。',
'users' => 'ユーザ',
'projects' => 'プロジェクト',
'Edit project' => 'プロジェクトを変更する',
'Name' => '名前',
'Activated' => '有効',
'Projects' => 'プロジェクト',
'No project' => 'プロジェクトがありません',
'Project' => 'プロジェクト',
'Status' => 'ステータス',
'Tasks' => 'タスク',
'Board' => 'ボード',
'Actions' => 'アクション',
'Inactive' => '無効',
'Active' => '有効',
'Column %d' => 'カラム %d',
'Add this column' => 'カラムを追加する',
'%d tasks on the board' => '%d 個のタスク',
'%d tasks in total' => '合計 %d 個のタスク',
'Unable to update this board.' => 'ボードを更新できませんでした',
'Edit board' => 'ボードを変更する',
'Disable' => '無効にする',
'Enable' => '有効にする',
'New project' => 'プロジェクトを作る',
'Do you really want to remove this project: "%s"?' => 'プロジェクト「%s」を本当に削除しますか',
'Remove project' => 'プロジェクトの削除',
'Boards' => 'ボード',
'Edit the board for "%s"' => 'ボード「%s」を変更する',
'All projects' => 'すべてのプロジェクト',
'Change columns' => 'カラムの変更',
'Add a new column' => 'カラムの追加',
'Title' => 'タイトル',
'Add Column' => 'カラムの追加',
'Project "%s"' => 'プロジェクト「%s」',
'Nobody assigned' => '担当なし',
'Assigned to %s' => '%sが担当',
'Remove a column' => 'カラムの削除',
'Remove a column from a board' => 'ボードからカラムの削除',
'Unable to remove this column.' => 'カラムを削除できませんでした。',
'Do you really want to remove this column: "%s"?' => 'カラム「%s」を削除しますか',
'This action will REMOVE ALL TASKS associated to this column!' => 'この操作はこのカラムに割当てられた『全てのタスクを削除』します!',
'Settings' => '設定',
'Application settings' => 'アプリケーションの設定',
'Language' => '言語',
'Webhook token:' => 'Webhook トークン:',
'API token:' => 'API トークン:',
'More information' => '詳細',
'Database size:' => 'データベースのサイズ:',
'Download the database' => 'データベースのダウンロード',
'Optimize the database' => 'データベースの最適化',
'(VACUUM command)' => '(VACUUM コマンド)',
'(Gzip compressed Sqlite file)' => '(GZip コマンドで圧縮された Sqlite ファイル)',
'User settings' => 'ユーザ設定',
'My default project:' => '自分のデフォルトプロジェクト:',
'Close a task' => 'タスクをクロースする',
'Do you really want to close this task: "%s"?' => 'タスク「%s」をクローズしますか',
'Edit a task' => 'タスクを変更する',
'Column' => 'カラム',
'Color' => '色',
'Assignee' => '担当',
'Create another task' => '続けて別のタスクを追加する',
'New task' => 'タスクを追加する',
'Open a task' => 'タスクをオープンする',
'Do you really want to open this task: "%s"?' => 'タスク「%s」をオープンしますか',
'Back to the board' => 'ボードに戻る',
'Created on %B %e, %Y at %k:%M %p' => '%Y/%m/%d %H:%M に作成',
'There is nobody assigned' => '担当者がいません',
'Column on the board:' => 'カラム: ',
'Status is open' => 'ステータスはオープンです',
'Status is closed' => 'ステータスはクローズです',
'Close this task' => 'タスクをクローズする',
'Open this task' => 'タスクをオープンする',
'There is no description.' => '説明がありません',
'Add a new task' => 'タスクを追加する',
'The username is required' => 'ユーザ名が必要です',
'The maximum length is %d characters' => '最大 %d 文字です',
'The minimum length is %d characters' => '最小 %d 文字必要です',
'The password is required' => 'パスワードが必要です',
'This value must be an integer' => '整数で入力してください',
'The username must be unique' => 'ユーザ名がすでに使用されています',
'The username must be alphanumeric' => 'ユーザ名は英数字で入力してください',
'The user id is required' => 'ユーザ ID が必要です',
'Passwords don\'t match' => 'パスワードが一致しません',
'The confirmation is required' => '確認用のパスワードを入力してください',
'The column is required' => 'カラムが必要です',
'The project is required' => 'プロジェクトが必要です',
'The color is required' => '色が必要です',
'The id is required' => 'ID が必要です',
'The project id is required' => 'プロジェクト ID が必要です',
'The project name is required' => 'プロジェクト名が必要です',
'This project must be unique' => 'プロジェクト名がすでに使われています',
'The title is required' => 'タイトルが必要です',
'The language is required' => '言語が必要です',
'There is no active project, the first step is to create a new project.' => '有効なプロジェクトがありません。まず新しいプロジェクトを作ります。',
'Settings saved successfully.' => '設定を保存しました。',
'Unable to save your settings.' => '設定の保存に失敗しました。',
'Database optimization done.' => 'データベースの最適化が終わりました。',
'Your project have been created successfully.' => 'プロジェクトを作成しました。',
'Unable to create your project.' => 'プロジェクトの作成に失敗しました。',
'Project updated successfully.' => 'プロジェクトを更新しました。',
'Unable to update this project.' => 'プロジェクトの更新に失敗しました。',
'Unable to remove this project.' => 'プロジェクトの削除に失敗しました。',
'Project removed successfully.' => 'プロジェクトを削除しました。',
'Project activated successfully.' => 'プロジェクトを有効にしました。',
'Unable to activate this project.' => 'プロジェクトの有効にできませんでした。',
'Project disabled successfully.' => 'プロジェクトを無効にしました。',
'Unable to disable this project.' => 'プロジェクトの無効にできませんでした。',
'Unable to open this task.' => 'タスクのオープンに失敗しました。',
'Task opened successfully.' => 'タスクをオープンしました。',
'Unable to close this task.' => 'タスクのクローズに失敗しました。',
'Task closed successfully.' => 'タスクをクローズしました。',
'Unable to update your task.' => 'タスクの更新に失敗しました。',
'Task updated successfully.' => 'タスクを更新しました。',
'Unable to create your task.' => 'タスクの追加に失敗しました。',
'Task created successfully.' => 'タスクを追加しました。',
'User created successfully.' => 'ユーザを追加しました。',
'Unable to create your user.' => 'ユーザの追加に失敗しました。',
'User updated successfully.' => 'ユーザを更新しました。',
'Unable to update your user.' => 'ユーザの更新に失敗しました。',
'User removed successfully.' => 'ユーザを削除しました。',
'Unable to remove this user.' => 'ユーザの削除に失敗しました。',
'Board updated successfully.' => 'ボードを更新しました。',
'Ready' => 'Ready',
'Backlog' => 'Backlog',
'Work in progress' => 'Work in progress',
'Done' => 'Done',
'Application version:' => 'アプリケーションのバージョン:',
'Completed on %B %e, %Y at %k:%M %p' => '%Y/%m/%d %H:%M に完了',
'%B %e, %Y at %k:%M %p' => '%Y/%m/%d %H:%M',
'Date created' => '作成日',
'Date completed' => '完了日',
'Id' => 'ID',
'No task' => 'タスクなし',
'Completed tasks' => '完了したタスク',
'List of projects' => 'プロジェクトの一覧',
'Completed tasks for "%s"' => '「%s」の完了したタスク',
'%d closed tasks' => '%d 個のクローズしたタスク',
'No task for this project' => 'このプロジェクトにタスクがありません',
'Public link' => '公開アクセス用リンク',
'There is no column in your project!' => 'カラムにプロジェクトがありません!',
'Change assignee' => '担当を変更する',
'Change assignee for the task "%s"' => 'タスク「%s」の担当を変更する',
'Timezone' => 'タイムゾーン',
'Sorry, I didn\'t found this information in my database!' => 'データベース上で情報が見つかりませんでした!',
'Page not found' => 'ページが見つかりません',
'Complexity' => '複雑さ',
'limit' => '制限',
'Task limit' => 'タスク数制限',
'This value must be greater than %d' => '%d より大きな値を入力してください',
'Edit project access list' => 'プロジェクトのアクセス許可を変更',
'Edit users access' => 'ユーザのアクセス許可を変更',
'Allow this user' => 'このユーザを許可する',
'Only those users have access to this project:' => 'これらのユーザのみがプロジェクトにアクセスできます:',
'Don\'t forget that administrators have access to everything.' => '管理者には全ての権限が与えられます。',
'revoke' => '許可を取り下げる',
'List of authorized users' => '許可されたユーザ',
'User' => 'ユーザ',
'Nobody have access to this project.' => 'だれもプロジェクトにアクセスできません。',
'You are not allowed to access to this project.' => 'プロジェクトへのアクセスが許可されていません。',
'Comments' => 'コメント',
'Post comment' => 'コメントを書く',
'Write your text in Markdown' => 'Markdown 記法で書く',
'Leave a comment' => 'コメントを書く',
'Comment is required' => 'コメントを入力してください',
'Leave a description' => '説明を書く',
'Comment added successfully.' => 'コメントを追加しました。',
'Unable to create your comment.' => 'コメントの追加に失敗しました。',
'The description is required' => '説明を入力してください',
'Edit this task' => 'タスクを変更する',
'Due Date' => '期限',
'Invalid date' => '日付が無効です',
'Must be done before %B %e, %Y' => '%Y/%m/%d までに完了',
'%B %e, %Y' => '%d %B %Y',
'Automatic actions' => '自動アクションを管理する',
'Your automatic action have been created successfully.' => '自動アクションを作成しました。',
'Unable to create your automatic action.' => '自動アクションの作成に失敗しました。',
'Remove an action' => '自動アクションの削除',
'Unable to remove this action.' => '自動アクションの削除に失敗しました。',
'Action removed successfully.' => '自動アクションの削除に成功しました。',
'Automatic actions for the project "%s"' => 'プロジェクト「%s」の自動アクション',
'Defined actions' => '定義された自動アクション',
'Add an action' => '自動アクションの追加',
'Event name' => 'イベント名',
'Action name' => 'アクション名',
'Action parameters' => 'アクションのパラメーター',
'Action' => 'アクション',
'Event' => 'イベント',
'When the selected event occurs execute the corresponding action.' => '選択されたイベントが発生した時、対応するアクションを実行する。',
'Next step' => '次のステップ',
'Define action parameters' => 'アクションのパラメーター',
'Save this action' => 'このアクションを保存する',
'Do you really want to remove this action: "%s"?' => '自動アクション「%s」を削除しますか',
'Remove an automatic action' => '自動アクションの削除',
'Close the task' => 'タスクのクローズ',
'Assign the task to a specific user' => 'タスクの担当者を割り当てる',
'Assign the task to the person who does the action' => 'アクションを起こしたユーザを担当者にする',
'Duplicate the task to another project' => '別のプロジェクトにタスクを複製する',
'Move a task to another column' => 'タスクを別のカラムに移動する',
'Move a task to another position in the same column' => 'カラム上でのタスクの順序を変える',
'Task modification' => 'タスクの変更',
'Task creation' => 'タスクを作る',
'Open a closed task' => 'タスクを再オープンする',
'Closing a task' => 'タスクをクローズする',
'Assign a color to a specific user' => '色をユーザに割り当てる',
'Column title' => 'カラムのタイトル',
'Position' => '位置',
'Move Up' => '上に動かす',
'Move Down' => '下に動かす',
'Duplicate to another project' => '別のプロジェクトに複製する',
'Duplicate' => '複製する',
'link' => 'リンク',
'Update this comment' => 'コメントを更新する',
'Comment updated successfully.' => 'コメントを更新しました。',
'Unable to update your comment.' => 'コメントの更新に失敗しました。',
'Remove a comment' => 'コメントを削除する',
'Comment removed successfully.' => 'コメントを削除しました。',
'Unable to remove this comment.' => 'コメントの削除に失敗しました。',
'Do you really want to remove this comment?' => 'コメントを削除しますか?',
'Only administrators or the creator of the comment can access to this page.' => '管理者かコメントの作成者のみがこのページアクセスできます。',
'Details' => '詳細',
'Current password for the user "%s"' => 'ユーザ「%s」の現在のパスワード',
'The current password is required' => '現在のパスワードを入力してください',
'Wrong password' => 'パスワードが違います',
'Reset all tokens' => '全てのトークンを再生成する',
'All tokens have been regenerated.' => '全てのトークンが再生成されました',
'Unknown' => '不明',
'Last logins' => 'ログインの一覧',
'Login date' => 'ログイン日時',
'Authentication method' => '認証方法',
'IP address' => 'IP アドレス',
'User agent' => 'ユーザエージェント',
'Persistent connections' => '既存のコネクション',
'No session.' => 'セッションなし。',
'Expiration date' => '有効期限',
'Remember Me' => '次回から自動的にログインする',
'Creation date' => '作成日',
'Filter by user' => 'ユーザでフィルタリング',
'Filter by due date' => '期限でフィルタリング',
'Everybody' => '全員',
'Open' => 'オープン',
'Closed' => 'クローズ',
'Search' => '検索',
'Nothing found.' => '結果なし。',
'Search in the project "%s"' => 'プロジェクト「%s」で検索',
'Due date' => '期限',
'Others formats accepted: %s and %s' => '他の書式: %s または %s',
'Description' => '説明',
'%d comments' => '%d 個のコメント',
'%d comment' => '%d 個のコメント',
'Email address invalid' => 'メールアドレスが正しくありません',
'Your Google Account is not linked anymore to your profile.' => 'Google アカウントとのリンクが解除されました',
'Unable to unlink your Google Account.' => 'Google アカウントとのリンク解除に失敗しました',
'Google authentication failed' => 'Google の認証に失敗しました',
'Unable to link your Google Account.' => 'Google アカウントとのリンクに失敗しました。',
'Your Google Account is linked to your profile successfully.' => 'Google アカウントとリンクしました',
'Email' => 'Email',
'Link my Google Account' => 'Google アカウントをリンクする',
'Unlink my Google Account' => 'Google アカウントのリンクを解除する',
'Login with my Google Account' => 'Google アカウントでログインする',
'Project not found.' => 'プロジェクトが見つかりません。',
'Task #%d' => 'タスク #%d',
'Task removed successfully.' => 'タスクを削除しました。',
'Unable to remove this task.' => 'タスクの削除に失敗しました。',
'Remove a task' => 'タスクの削除',
'Do you really want to remove this task: "%s"?' => 'タスク「%s」を削除しますか',
'Assign automatically a color based on a category' => 'カテゴリに基いて色を変える',
'Assign automatically a category based on a color' => '色に基いてカテゴリを変える',
'Task creation or modification' => 'タスクの作成または変更',
'Category' => 'カテゴリ',
'Category:' => 'カテゴリ:',
'Categories' => 'カテゴリ',
'Category not found.' => 'カテゴリが見つかりません',
'Your category have been created successfully.' => 'カテゴリを作成しました。',
'Unable to create your category.' => 'カテゴリの作成に失敗しました。',
'Your category have been updated successfully.' => 'カテゴリを更新しました。',
'Unable to update your category.' => 'カテゴリの更新に失敗しました。',
'Remove a category' => 'カテゴリの削除',
'Category removed successfully.' => 'カテゴリを削除しました。',
'Unable to remove this category.' => 'カテゴリを削除できませんでした。',
'Category modification for the project "%s"' => 'プロジェクト「%s」のカテゴリの変更',
'Category Name' => 'カテゴリ名',
'Categories for the project "%s"' => 'プロジェクト「%s」のカテゴリ',
'Add a new category' => 'カテゴリの追加',
'Do you really want to remove this category: "%s"?' => 'カテゴリ「%s」を削除しますか',
'Filter by category' => 'カテゴリでフィルタリング',
'All categories' => 'すべてのカテゴリ',
'No category' => 'カテゴリなし',
'The name is required' => '名前を入力してください',
'Remove a file' => 'ファイルの削除',
'Unable to remove this file.' => 'ファイルの削除に失敗しました。',
'File removed successfully.' => 'ファイルを削除しました。',
'Attach a document' => 'ドキュメントを添付する',
'Do you really want to remove this file: "%s"?' => 'ファイル「%s」を削除しますか',
'open' => 'オープン',
'Attachments' => '添付',
'Edit the task' => 'タスクを変更する',
'Edit the description' => '説明を変更する',
'Add a comment' => 'コメントの追加',
'Edit a comment' => 'コメントを変更する',
'Summary' => '概要',
'Time tracking' => '時間の計測',
'Estimate:' => '予測:',
'Spent:' => '経過:',
'Do you really want to remove this sub-task?' => 'サブタスクを削除しますか?',
'Remaining:' => '残り:',
'hours' => '時間',
'spent' => '経過',
'estimated' => '予測',
'Sub-Tasks' => 'サブタスク',
'Add a sub-task' => 'サブタスクを追加する',
'Original estimate' => '初期の予測',
'Create another sub-task' => '続けて別のサブタスクを追加する',
'Time spent' => '経過時間',
'Edit a sub-task' => 'サブタスクを変更する',
'Remove a sub-task' => 'サブタスクを削除する',
'The time must be a numeric value' => '時間は数字で入力してください',
'Todo' => '作業予定',
'In progress' => '作業中',
'Sub-task removed successfully.' => 'サブタスクを削除しました。',
'Unable to remove this sub-task.' => 'サブタスクの削除に失敗しました。',
'Sub-task updated successfully.' => 'サブタスクを更新しました。',
'Unable to update your sub-task.' => 'サブタスクの更新に失敗しました。',
'Unable to create your sub-task.' => 'サブタスクの追加に失敗しました。',
'Sub-task added successfully.' => 'サブタスクを追加しました。',
'Maximum size: ' => '最大: ',
'Unable to upload the file.' => 'ファイルのアップロードに失敗しました。',
'Display another project' => '別のプロジェクトを表示',
'Your GitHub account was successfully linked to your profile.' => 'GitHub アカウントとリンクしました。',
'Unable to link your GitHub Account.' => 'GitHub アカウントとリンクできませんでした。',
'GitHub authentication failed' => 'GitHub アカウントの認証に失敗しました。',
'Your GitHub account is no longer linked to your profile.' => 'GitHub アカウントへのリンクが解除されました。',
'Unable to unlink your GitHub Account.' => 'GitHub アカウントのリンク解除に失敗しました。',
'Login with my GitHub Account' => 'Github アカウントでログインする',
'Link my GitHub Account' => 'Github アカウントをリンクする',
'Unlink my GitHub Account' => 'Github アカウントとのリンクを解除する',
'Created by %s' => '%s が作成',
'Last modified on %B %e, %Y at %k:%M %p' => ' %Y/%m/%d %H:%M に変更',
'Tasks Export' => 'タスクの出力',
'Tasks exportation for "%s"' => '「%s」のタスク出力',
'Start Date' => '開始日',
'End Date' => '終了日',
'Execute' => '実行',
'Task Id' => 'タスク ID',
'Creator' => '作成者',
'Modification date' => '変更日',
'Completion date' => '完了日',
'Webhook URL for task creation' => 'タスク作成の Webhook URL',
'Webhook URL for task modification' => 'タスク変更の Webhook URL',
'Clone' => '複製',
'Clone Project' => 'プロジェクトの複製',
'Project cloned successfully.' => 'プロジェクトを複製しました。',
'Unable to clone this project.' => 'プロジェクトの複製に失敗しました。',
'Email notifications' => 'メール通知',
'Enable email notifications' => 'メール通知を設定',
'Task position:' => 'タスクの位置:',
'The task #%d have been opened.' => 'タスク #%d をオープンしました。',
'The task #%d have been closed.' => 'タスク #%d をクローズしました。',
'Sub-task updated' => 'サブタスクの更新',
'Title:' => 'タイトル:',
'Status:' => 'ステータス:',
'Assignee:' => '担当:',
'Time tracking:' => '時間計測:',
'New sub-task' => '新しいサブタスク',
'New attachment added "%s"' => '添付ファイル「%s」が追加されました',
'Comment updated' => 'コメントが更新されました',
'New comment posted by %s' => '「%s」の新しいコメントが追加されました',
'List of due tasks for the project "%s"' => 'プロジェクト「%s」の期限切れのタスク',
'[%s][New attachment] %s (#%d)' => '[%s][新規添付ファイル] %s (#%d)',
'[%s][New comment] %s (#%d)' => '[%s][新規コメント] %s (#%d)',
'[%s][Comment updated] %s (#%d)' => '[%s][コメント更新] %s (#%d)',
'[%s][New subtask] %s (#%d)' => '[%s][新規サブタスク] %s (#%d)',
'[%s][Subtask updated] %s (#%d)' => '[%s][サブタスク更新] %s (#%d)',
'[%s][New task] %s (#%d)' => '[%s][新規タスク] %s (#%d)',
'[%s][Task updated] %s (#%d)' => '[%s][タスク更新] %s (#%d)',
'[%s][Task closed] %s (#%d)' => '[%s][タスククローズ] %s (#%d)',
'[%s][Task opened] %s (#%d)' => '[%s][タスクオープン] %s (#%d)',
'[%s][Due tasks]' => '[%s][タスク期限切れ]',
'[Kanboard] Notification' => '[Kanboard] 通知',
'I want to receive notifications only for those projects:' => '以下のプロジェクトにのみ通知を受け取る:',
'view the task on Kanboard' => 'Kanboard でタスクを見る',
'Public access' => '公開アクセス設定',
'Category management' => 'カテゴリを管理する',
'User management' => 'ユーザを管理する',
'Active tasks' => 'アクティブなタスク',
'Disable public access' => '公開アクセスを無効にする',
'Enable public access' => '公開アクセスを有効にする',
'Active projects' => 'プロジェクト',
'Inactive projects' => '無効化されたプロジェクト',
'Public access disabled' => '公開アクセスは無効化されています',
'Do you really want to disable this project: "%s"?' => '「%s」を無効にしますか',
'Do you really want to duplicate this project: "%s"?' => '「%s」を複製しますか',
'Do you really want to enable this project: "%s"?' => '「%s」を有効にしますか',
'Project activation' => 'プロジェクトのアクティベーション',
'Move the task to another project' => 'タスクを別プロジェクトに移す',
'Move to another project' => '別プロジェクトに移す',
'Do you really want to duplicate this task?' => 'タスクを複製しますか?',
'Duplicate a task' => 'タスクの複製',
'External accounts' => '外部アカウント',
'Account type' => 'アカウントの種類',
'Local' => 'ローカル',
'Remote' => 'リモート',
'Enabled' => '有効',
'Disabled' => '無効',
'Google account linked' => 'Google アカウントがリンク',
'Github account linked' => 'GitHub のアカウントがリンク',
'Username:' => 'ユーザ名:',
'Name:' => '名前:',
'Email:' => 'Email:',
'Default project:' => 'デフォルトプロジェクト:',
'Notifications:' => '通知:',
// 'Notifications' => '',
'Group:' => 'グループ:',
'Regular user' => '通常のユーザ',
'Account type:' => 'アカウントの種類:',
'Edit profile' => 'プロフィールの変更',
'Change password' => 'パスワードの変更',
'Password modification' => 'パスワードの変更',
'External authentications' => '外部認証',
'Google Account' => 'Google アカウント',
'Github Account' => 'GitHub アカウント',
'Never connected.' => '未接続。',
'No account linked.' => 'アカウントがリンクしていません。',
'Account linked.' => 'アカウントがリンクしました。',
'No external authentication enabled.' => '外部認証が設定されていません。',
'Password modified successfully.' => 'パスワードを変更しました。',
'Unable to change the password.' => 'パスワードが変更できませんでした。',
'Change category for the task "%s"' => 'タスク「%s」のカテゴリの変更',
'Change category' => 'カテゴリの変更',
'%s updated the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s がタスク <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> をアップデートしました',
'%s open the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s がタスク <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> をオープンしました',
'%s moved the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to the position #%d in the column "%s"' => '%s がタスク <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> をポジション #%d カラム %s に移動しました',
'%s moved the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to the column "%s"' => '%s がタスク <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> をカラム「%s」に移動しました',
'%s created the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s がタスク <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> を作成しました',
'%s closed the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s がタスク <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> をクローズしました',
'%s created a subtask for the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s がタスク <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> のサブタスクを追加しました',
'%s updated a subtask for the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s がタスク <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> のサブタスクを更新しました',
'Assigned to %s with an estimate of %s/%sh' => '担当者 %s に予想 %s/%sh に変更されました',
'Not assigned, estimate of %sh' => '担当者無しで予想 %sh に変更されました',
'%s updated a comment on the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s がタスク <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> のコメントを更新しました',
'%s commented the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s がタスク <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> にコメントしました',
'%s\'s activity' => '%s のアクティビティ',
'No activity.' => 'アクティビティなし。',
'RSS feed' => 'RSS フィード',
'%s updated a comment on the task #%d' => '%s がタスク #%d のコメントを更新しました',
'%s commented on the task #%d' => '%s がタスク #%d にコメントしました',
'%s updated a subtask for the task #%d' => '%s がタスク #%d のサブタスクを更新しました',
'%s created a subtask for the task #%d' => '%s がタスク #%d のサブタスクを追加しました',
'%s updated the task #%d' => '%s がタスク #%d を更新しました',
'%s created the task #%d' => '%s がタスク #%d を追加しました',
'%s closed the task #%d' => '%s がタスク #%d をクローズしました',
'%s open the task #%d' => '%s がタスク #%d をオープンしました',
'%s moved the task #%d to the column "%s"' => '%s がタスク #%d をカラム「%s」に移動しました',
'%s moved the task #%d to the position %d in the column "%s"' => '%s がタスク #%d を位置 %d カラム「%s」移動しました',
'Activity' => 'アクティビティ',
'Default values are "%s"' => 'デフォルト値は「%s」',
'Default columns for new projects (Comma-separated)' => '新規プロジェクトのデフォルトカラム (コンマで区切って入力)',
'Task assignee change' => '担当者の変更',
'%s change the assignee of the task #%d to %s' => '%s がタスク #%d の担当を %s に変更しました',
'%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to %s' => '%s がタスク <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> の担当を %s に変更しました',
'[%s][Column Change] %s (#%d)' => '[%s][カラムの変更] %s (#%d)',
'[%s][Position Change] %s (#%d)' => '[%s][位置の変更] %s (#%d)',
'[%s][Assignee Change] %s (#%d)' => '[%s][担当者変更] %s (#%d)',
'New password for the user "%s"' => 'ユーザ「%s」の新しいパスワード',
'Choose an event' => 'イベントの選択',
'Github commit received' => 'Github のコミットを受け取った',
'Github issue opened' => 'Github Issue がオープンされた',
'Github issue closed' => 'Github Issue がクローズされた',
'Github issue reopened' => 'Github Issue が再オープンされた',
'Github issue assignee change' => 'Github Issue の担当が変更された',
'Github issue label change' => 'Github のラベルが変更された',
'Create a task from an external provider' => 'タスクを外部サービスから作成する',
'Change the assignee based on an external username' => '担当者を外部サービスに基いて変更する',
'Change the category based on an external label' => 'カテゴリを外部サービスに基いて変更する',
'Reference' => '参照',
'Reference: %s' => '参照: %s',
'Label' => 'ラベル',
'Database' => 'データベース',
'About' => '情報',
'Database driver:' => 'データベースドライバ:',
'Board settings' => '基本設定',
'URL and token' => 'URL とトークン',
'Webhook settings' => 'Webhook の設定',
'URL for task creation:' => 'Task 作成の URL:',
'Reset token' => 'トークンのリセット',
'API endpoint:' => 'API エンドポイント:',
'Refresh interval for private board' => '非公開ボードの更新頻度',
'Refresh interval for public board' => '公開ボードの更新頻度',
'Task highlight period' => 'タスクのハイライト期間',
'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'タスクが最近更新されたとみなす期間(0 はハイライト無効、デフォルト 2 日)',
'Frequency in second (60 seconds by default)' => '秒数 (デフォルト 60 秒)',
'Frequency in second (0 to disable this feature, 10 seconds by default)' => '秒数 (0 は機能を無効化、デフォルト 10 秒)',
'Application URL' => 'アプリケーションの URL',
'Example: http://example.kanboard.net/ (used by email notifications)' => 'Exemple : http://exemple.kanboard.net/ (Email 通知に利用)',
'Token regenerated.' => 'トークンが再生成されました。',
'Date format' => 'データのフォーマット',
'ISO format is always accepted, example: "%s" and "%s"' => 'ISO フォーマットが入力できます(例: %s または %s)',
'New private project' => '非公開プロジェクトを作る',
'This project is private' => 'このプロジェクトは非公開です',
'Type here to create a new sub-task' => 'サブタスクを追加するにはここに入力してください',
'Add' => '追加',
'Estimated time: %s hours' => '予想時間: %s 時間',
'Time spent: %s hours' => '経過: %s 時間',
'Started on %B %e, %Y' => '開始 %Y/%m/%d',
'Start date' => '開始時間',
'Time estimated' => '予想時間',
// 'There is nothing assigned to you.' => '',
// 'My tasks' => '',
// 'Activity stream' => '',
// 'Dashboard' => '',
// 'Confirmation' => '',
// 'Allow everybody to access to this project' => '',
// 'Everybody have access to this project.' => '',
// 'Webhooks' => '',
// 'API' => '',
// 'Integration' => '',
// 'Github webhook' => '',
// 'Help on Github webhook' => '',
// 'Create a comment from an external provider' => '',
// 'Github issue comment created' => '',
);

View file

@ -83,7 +83,7 @@ return array(
'Settings' => 'Ustawienia',
'Application settings' => 'Ustawienia aplikacji',
'Language' => 'Język',
'Webhooks token:' => 'Token :',
'Webhook token:' => 'Token :',
// 'API token:' => '',
'More information' => 'Więcej informacji',
'Database size:' => 'Rozmiar bazy danych :',
@ -196,7 +196,7 @@ return array(
'revoke' => 'odbierz dostęp',
'List of authorized users' => 'Lista użytkowników mających dostęp',
'User' => 'Użytkownik',
'Everybody have access to this project.' => 'Każdy ma dostęp do tego projektu.',
// 'Nobody have access to this project.' => '',
'You are not allowed to access to this project.' => 'Nie masz dostępu do tego projektu.',
'Comments' => 'Komentarze',
'Post comment' => 'Dodaj komentarz',
@ -209,8 +209,6 @@ return array(
'The description is required' => 'Opis jest wymagany',
'Edit this task' => 'Edytuj zadanie',
'Due Date' => 'Termin',
'm/d/Y' => 'd/m/Y',
'month/day/year' => 'dzień/miesiąc/rok',
'Invalid date' => 'Błędna data',
'Must be done before %B %e, %Y' => 'Termin do %e %B %Y',
'%B %e, %Y' => '%e %B %Y',
@ -351,9 +349,9 @@ return array(
// 'estimated' => '',
// 'Sub-Tasks' => '',
// 'Add a sub-task' => '',
// 'Original Estimate' => '',
// 'Original estimate' => '',
// 'Create another sub-task' => '',
// 'Time Spent' => '',
// 'Time spent' => '',
// 'Edit a sub-task' => '',
// 'Remove a sub-task' => '',
// 'The time must be a numeric value' => '',
@ -422,8 +420,8 @@ return array(
// 'I want to receive notifications only for those projects:' => '',
// 'view the task on Kanboard' => '',
// 'Public access' => '',
// 'Categories management' => '',
// 'Users management' => '',
// 'Category management' => '',
// 'User management' => '',
// 'Active tasks' => '',
// 'Disable public access' => '',
// 'Enable public access' => '',
@ -451,6 +449,7 @@ return array(
// 'Email:' => '',
// 'Default project:' => '',
// 'Notifications:' => '',
// 'Notifications' => '',
// 'Group:' => '',
// 'Regular user' => '',
// 'Account type:' => '',
@ -497,10 +496,66 @@ return array(
// 'Default values are "%s"' => '',
// 'Default columns for new projects (Comma-separated)' => '',
// 'Task assignee change' => '',
// '%s change the assignee of the task #%d' => '',
// '%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// '%s change the assignee of the task #%d to %s' => '',
// '%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to %s' => '',
// '[%s][Column Change] %s (#%d)' => '',
// '[%s][Position Change] %s (#%d)' => '',
// '[%s][Assignee Change] %s (#%d)' => '',
// 'New password for the user "%s"' => '',
// 'Choose an event' => '',
// 'Github commit received' => '',
// 'Github issue opened' => '',
// 'Github issue closed' => '',
// 'Github issue reopened' => '',
// 'Github issue assignee change' => '',
// 'Github issue label change' => '',
// 'Create a task from an external provider' => '',
// 'Change the assignee based on an external username' => '',
// 'Change the category based on an external label' => '',
// 'Reference' => '',
// 'Reference: %s' => '',
// 'Label' => '',
// 'Database' => '',
// 'About' => '',
// 'Database driver:' => '',
// 'Board settings' => '',
// 'URL and token' => '',
// 'Webhook settings' => '',
// 'URL for task creation:' => '',
// 'Reset token' => '',
// 'API endpoint:' => '',
// 'Refresh interval for private board' => '',
// 'Refresh interval for public board' => '',
// 'Task highlight period' => '',
// 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => '',
// 'Frequency in second (60 seconds by default)' => '',
// 'Frequency in second (0 to disable this feature, 10 seconds by default)' => '',
// 'Application URL' => '',
// 'Example: http://example.kanboard.net/ (used by email notifications)' => '',
// 'Token regenerated.' => '',
// 'Date format' => '',
// 'ISO format is always accepted, example: "%s" and "%s"' => '',
// 'New private project' => '',
// 'This project is private' => '',
// 'Type here to create a new sub-task' => '',
// 'Add' => '',
// 'Estimated time: %s hours' => '',
// 'Time spent: %s hours' => '',
// 'Started on %B %e, %Y' => '',
// 'Start date' => '',
// 'Time estimated' => '',
// 'There is nothing assigned to you.' => '',
// 'My tasks' => '',
// 'Activity stream' => '',
// 'Dashboard' => '',
// 'Confirmation' => '',
// 'Allow everybody to access to this project' => '',
// 'Everybody have access to this project.' => '',
// 'Webhooks' => '',
// 'API' => '',
// 'Integration' => '',
// 'Github webhook' => '',
// 'Help on Github webhook' => '',
// 'Create a comment from an external provider' => '',
// 'Github issue comment created' => '',
);

View file

@ -83,7 +83,7 @@ return array(
'Settings' => 'Preferências',
'Application settings' => 'Preferências da aplicação',
'Language' => 'Idioma',
'Webhooks token:' => 'Token de webhooks:',
'Webhook token:' => 'Token de webhooks:',
'API token:' => 'API Token:',
'More information' => 'Mais informação',
'Database size:' => 'Tamanho do banco de dados:',
@ -196,7 +196,7 @@ return array(
'revoke' => 'revogar',
'List of authorized users' => 'Lista de usuários autorizados',
'User' => 'Usuário',
'Everybody have access to this project.' => 'Todos têm acesso a este projeto.',
// 'Nobody have access to this project.' => '',
'You are not allowed to access to this project.' => 'Você não está autorizado a acessar este projeto.',
'Comments' => 'Comentários',
'Post comment' => 'Postar comentário',
@ -209,8 +209,6 @@ return array(
'The description is required' => 'A descrição é obrigatória',
'Edit this task' => 'Editar esta tarefa',
'Due Date' => 'Data de vencimento',
'm/d/Y' => 'd/m/Y',
'month/day/year' => 'dia/mês/ano',
'Invalid date' => 'Data inválida',
'Must be done before %B %e, %Y' => 'Deve ser feito antes de %d %B %Y',
'%B %e, %Y' => '%d %B %Y',
@ -351,9 +349,9 @@ return array(
'estimated' => 'estimada',
'Sub-Tasks' => 'Sub-tarefas',
'Add a sub-task' => 'Adicionar uma sub-tarefa',
'Original Estimate' => 'Estimativa original',
'Original estimate' => 'Estimativa original',
'Create another sub-task' => 'Criar uma outra sub-tarefa',
'Time Spent' => 'Tempo gasto',
'Time spent' => 'Tempo gasto',
'Edit a sub-task' => 'Editar uma sub-tarefa',
'Remove a sub-task' => 'Remover uma sub-tarefa',
'The time must be a numeric value' => 'O tempo deve ser um valor numérico',
@ -422,8 +420,8 @@ return array(
// 'I want to receive notifications only for those projects:' => '',
// 'view the task on Kanboard' => '',
// 'Public access' => '',
// 'Categories management' => '',
// 'Users management' => '',
// 'Category management' => '',
// 'User management' => '',
// 'Active tasks' => '',
// 'Disable public access' => '',
// 'Enable public access' => '',
@ -451,6 +449,7 @@ return array(
// 'Email:' => '',
// 'Default project:' => '',
// 'Notifications:' => '',
// 'Notifications' => '',
// 'Group:' => '',
// 'Regular user' => '',
// 'Account type:' => '',
@ -497,10 +496,66 @@ return array(
// 'Default values are "%s"' => '',
// 'Default columns for new projects (Comma-separated)' => '',
// 'Task assignee change' => '',
// '%s change the assignee of the task #%d' => '',
// '%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// '%s change the assignee of the task #%d to %s' => '',
// '%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to %s' => '',
// '[%s][Column Change] %s (#%d)' => '',
// '[%s][Position Change] %s (#%d)' => '',
// '[%s][Assignee Change] %s (#%d)' => '',
// 'New password for the user "%s"' => '',
// 'Choose an event' => '',
// 'Github commit received' => '',
// 'Github issue opened' => '',
// 'Github issue closed' => '',
// 'Github issue reopened' => '',
// 'Github issue assignee change' => '',
// 'Github issue label change' => '',
// 'Create a task from an external provider' => '',
// 'Change the assignee based on an external username' => '',
// 'Change the category based on an external label' => '',
// 'Reference' => '',
// 'Reference: %s' => '',
// 'Label' => '',
// 'Database' => '',
// 'About' => '',
// 'Database driver:' => '',
// 'Board settings' => '',
// 'URL and token' => '',
// 'Webhook settings' => '',
// 'URL for task creation:' => '',
// 'Reset token' => '',
// 'API endpoint:' => '',
// 'Refresh interval for private board' => '',
// 'Refresh interval for public board' => '',
// 'Task highlight period' => '',
// 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => '',
// 'Frequency in second (60 seconds by default)' => '',
// 'Frequency in second (0 to disable this feature, 10 seconds by default)' => '',
// 'Application URL' => '',
// 'Example: http://example.kanboard.net/ (used by email notifications)' => '',
// 'Token regenerated.' => '',
// 'Date format' => '',
// 'ISO format is always accepted, example: "%s" and "%s"' => '',
// 'New private project' => '',
// 'This project is private' => '',
// 'Type here to create a new sub-task' => '',
// 'Add' => '',
// 'Estimated time: %s hours' => '',
// 'Time spent: %s hours' => '',
// 'Started on %B %e, %Y' => '',
// 'Start date' => '',
// 'Time estimated' => '',
// 'There is nothing assigned to you.' => '',
// 'My tasks' => '',
// 'Activity stream' => '',
// 'Dashboard' => '',
// 'Confirmation' => '',
// 'Allow everybody to access to this project' => '',
// 'Everybody have access to this project.' => '',
// 'Webhooks' => '',
// 'API' => '',
// 'Integration' => '',
// 'Github webhook' => '',
// 'Help on Github webhook' => '',
// 'Create a comment from an external provider' => '',
// 'Github issue comment created' => '',
);

View file

@ -83,7 +83,7 @@ return array(
'Settings' => 'Настройки',
'Application settings' => 'Настройки приложения',
'Language' => 'Язык',
'Webhooks token:' => 'Webhooks токен :',
'Webhook token:' => 'Webhooks токен :',
'API token:' => 'API токен :',
'More information' => 'Подробнее',
'Database size:' => 'Размер базы данных :',
@ -164,7 +164,7 @@ return array(
'Ready' => 'Готовые',
'Backlog' => 'Ожидающие',
'Work in progress' => 'В процессе',
'Done' => 'Завершенные',
'Done' => 'Выполнена',
'Application version:' => 'Версия приложения :',
'Completed on %B %e, %Y at %k:%M %p' => 'Завершен %d/%m/%Y в %H:%M',
'%B %e, %Y at %k:%M %p' => '%d/%m/%Y в %H:%M',
@ -192,11 +192,11 @@ return array(
'Edit users access' => 'Изменить доступ пользователей',
'Allow this user' => 'Разрешить этого пользователя',
'Only those users have access to this project:' => 'Только эти пользователи имеют доступ к проекту :',
'Don\'t forget that administrators have access to everything.' => 'Помните, администратор имеет доступ всюду.',
'Don\'t forget that administrators have access to everything.' => 'Помните, администратор имеет доступ ко всему.',
'revoke' => 'отозвать',
'List of authorized users' => 'Список авторизованных пользователей',
'User' => 'Пользователь',
'Everybody have access to this project.' => 'Кто угодно имеет доступ к этому проекту.',
'Nobody have access to this project.' => 'Ни у кого нет доступа к этому проекту',
'You are not allowed to access to this project.' => 'Вам запрешен доступ к этому проекту.',
'Comments' => 'Комментарии',
'Post comment' => 'Оставить комментарий',
@ -208,9 +208,7 @@ return array(
'Unable to create your comment.' => 'Невозможно создать комментарий.',
'The description is required' => 'Требуется описание',
'Edit this task' => 'Изменить задачу',
'Due Date' => 'Срок',
'm/d/Y' => 'м/д/Г',
'month/day/year' => 'месяц/день/год',
'Due Date' => 'Сделать до',
'Invalid date' => 'Неверная дата',
'Must be done before %B %e, %Y' => 'Должно быть сделано до %d/%m/%Y',
'%B %e, %Y' => '%d/%m/%Y',
@ -251,7 +249,7 @@ return array(
'Move Down' => 'Сдвинуть вниз',
'Duplicate to another project' => 'Клонировать в другой проект',
'Duplicate' => 'Клонировать',
'link' => 'связь',
'link' => 'ссылка',
'Update this comment' => 'Обновить комментарий',
'Comment updated successfully.' => 'Комментарий обновлен.',
'Unable to update your comment.' => 'Не удалось обновить ваш комментарий.',
@ -351,13 +349,13 @@ return array(
'estimated' => 'расчетное',
'Sub-Tasks' => 'Подзадачи',
'Add a sub-task' => 'Добавить подзадачу',
'Original Estimate' => 'Начальная оценка',
'Original estimate' => 'Первичная оценка',
'Create another sub-task' => 'Создать другую подзадачу',
'Time Spent' => 'Времени затрачено',
'Time spent' => 'Времени затрачено',
'Edit a sub-task' => 'Изменить подзадачу',
'Remove a sub-task' => 'Удалить подзадачу',
'The time must be a numeric value' => 'Время должно быть числом!',
'Todo' => 'TODO',
'Todo' => 'К исполнению',
'In progress' => 'В процессе',
'Sub-task removed successfully.' => 'Подзадача удалена.',
'Unable to remove this sub-task.' => 'Не удалось удалить подзадачу.',
@ -422,8 +420,8 @@ return array(
'I want to receive notifications only for those projects:' => 'Я хочу получать уведомления только по этим проектам :',
'view the task on Kanboard' => 'посмотреть задачу на Kanboard',
'Public access' => 'Общий доступ',
'Categories management' => 'Управление категориями',
'Users management' => 'Управление пользователями',
'Category management' => 'Управление категориями',
'User management' => 'Управление пользователями',
'Active tasks' => 'Активные задачи',
'Disable public access' => 'Отключить общий доступ',
'Enable public access' => 'Включить общий доступ',
@ -451,10 +449,11 @@ return array(
'Email:' => 'Email:',
'Default project:' => 'Проект по умолчанию:',
'Notifications:' => 'Уведомления:',
'Notifications' => 'Уведомления',
'Group:' => 'Группа:',
'Regular user' => 'Обычный пользователь',
'Account type:' => 'Тип профиля:',
'Edit profile' => 'Редактировать профиль:',
'Edit profile' => 'Редактировать профиль',
'Change password' => 'Сменить пароль',
'Password modification' => 'Изменение пароля',
'External authentications' => 'Внешняя аутентификация',
@ -496,11 +495,67 @@ return array(
'Activity' => 'Активность',
'Default values are "%s"' => 'Колонки по умолчанию: "%s"',
'Default columns for new projects (Comma-separated)' => 'Колонки по умолчанию для новых проектов (разделять запятой)',
// 'Task assignee change' => '',
// '%s change the assignee of the task #%d' => '',
// '%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// '[%s][Column Change] %s (#%d)' => '',
// '[%s][Position Change] %s (#%d)' => '',
// '[%s][Assignee Change] %s (#%d)' => '',
// 'New password for the user "%s"' => '',
'Task assignee change' => 'Изменен назначенный',
'%s change the assignee of the task #%d to %s' => '%s сменил назначенного для задачи #%d на %s',
'%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to %s' => '%s сменил назначенного для задачи <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> на %s',
'[%s][Column Change] %s (#%d)' => '[%s][Изменение колонки] %s (#%d)',
'[%s][Position Change] %s (#%d)' => '[%s][Изменение позиции] %s (#%d)',
'[%s][Assignee Change] %s (#%d)' => '[%s][Изменение назначеного] %s (#%d)',
'New password for the user "%s"' => 'Новый пароль для пользователя %s"',
'Choose an event' => 'Выберите событие',
'Github commit received' => 'Github: коммит получен',
'Github issue opened' => 'Github: новая проблема',
'Github issue closed' => 'Github: проблема закрыта',
'Github issue reopened' => 'Github: проблема переоткрыта',
'Github issue assignee change' => 'Github: сменить ответственного за проблему',
'Github issue label change' => 'Github: ярлык проблемы изменен',
'Create a task from an external provider' => 'Создать задачу из внешнего источника',
'Change the assignee based on an external username' => 'Изменить назначенного основываясь на внешнем имени пользователя',
'Change the category based on an external label' => 'Изменить категорию основываясь на внешнем ярлыке',
'Reference' => 'Ссылка',
'Reference: %s' => 'Ссылка: %s',
'Label' => 'Ярлык',
'Database' => 'База данных',
'About' => 'Информация',
'Database driver:' => 'Драйвер базы данных',
'Board settings' => 'Настройки доски',
'URL and token' => 'URL и токен',
'Webhook settings' => 'Параметры Webhook',
'URL for task creation:' => 'URL для создания задачи:',
'Reset token' => 'Перезагрузить токен',
'API endpoint:' => 'API endpoint:',
'Refresh interval for private board' => 'Период обновления для частных досок',
'Refresh interval for public board' => 'Период обновления для публичных досок',
'Task highlight period' => 'Время подсвечивания задачи',
'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Период (в секундах) в течении которого задача считается недавно измененной (0 для выключения, 2 дня по умолчанию)',
'Frequency in second (60 seconds by default)' => 'Частота в секундах (60 секунд по умолчанию)',
'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Частота в секундах (0 для выключения, 10 секунд по умолчанию)',
'Application URL' => 'URL приложения',
'Example: http://example.kanboard.net/ (used by email notifications)' => 'Пример: http://example.kanboard.net (используется в email уведомлениях)',
'Token regenerated.' => 'Токен пересоздан',
'Date format' => 'Формат даты',
'ISO format is always accepted, example: "%s" and "%s"' => 'Время должно быть в ISO-формате, например: "%s" или "%s"',
'New private project' => 'Новый проект с ограниченным доступом',
'This project is private' => 'Это проект с ограниченным доступом',
'Type here to create a new sub-task' => 'Печатайте сюда чтобы создать подзадачу',
'Add' => 'Добавить',
'Estimated time: %s hours' => 'Планируемое время: %s часов',
'Time spent: %s hours' => 'Потрачено времени: %s часов',
'Started on %B %e, %Y' => 'Начато %B %e, %Y',
'Start date' => 'Дата начала',
'Time estimated' => 'Планируемое время',
'There is nothing assigned to you.' => 'Вам ничего не назначено',
'My tasks' => 'Мои задачи',
'Activity stream' => 'Текущая активность',
'Dashboard' => 'Инфопанель',
'Confirmation' => 'Подтверждение пароля',
// 'Allow everybody to access to this project' => '',
// 'Everybody have access to this project.' => '',
// 'Webhooks' => '',
// 'API' => '',
// 'Integration' => '',
// 'Github webhook' => '',
// 'Help on Github webhook' => '',
// 'Create a comment from an external provider' => '',
// 'Github issue comment created' => '',
);

View file

@ -83,7 +83,7 @@ return array(
'Settings' => 'Inställningar',
'Application settings' => 'Applikationsinställningar',
'Language' => 'Språk',
'Webhooks token:' => 'Token för webhooks:',
'Webhook token:' => 'Token för webhooks:',
'API token:' => 'API token:',
'More information' => 'Mer information',
'Database size:' => 'Databasstorlek:',
@ -196,7 +196,7 @@ return array(
'revoke' => 'Dra tillbaka behörighet',
'List of authorized users' => 'Lista med behöriga användare',
'User' => 'Användare',
'Everybody have access to this project.' => 'Alla har tillgång till detta projekt.',
'Nobody have access to this project.' => 'Ingen har tillgång till detta projekt.',
'You are not allowed to access to this project.' => 'Du har inte tillgång till detta projekt.',
'Comments' => 'Kommentarer',
'Post comment' => 'Ladda upp kommentar',
@ -209,8 +209,6 @@ return array(
'The description is required' => 'En beskrivning måste lämnas',
'Edit this task' => 'Ändra denna uppgift',
'Due Date' => 'Måldatum',
'm/d/Y' => 'd/m/Y',
'month/day/year' => 'dag/månad/år',
'Invalid date' => 'Ej tillåtet datum',
'Must be done before %B %e, %Y' => 'Måste vara klart innan %B %e, %Y',
'%B %e, %Y' => '%d %B %Y',
@ -351,9 +349,9 @@ return array(
'estimated' => 'uppskattat',
'Sub-Tasks' => 'Deluppgifter',
'Add a sub-task' => 'Lägg till deluppgift',
'Original Estimate' => 'Ursprunglig uppskattning',
'Original estimate' => 'Ursprunglig uppskattning',
'Create another sub-task' => 'Skapa en till deluppgift',
'Time Spent' => 'Nedlagd tid',
'Time spent' => 'Nedlagd tid',
'Edit a sub-task' => 'Ändra en deluppgift',
'Remove a sub-task' => 'Ta bort en deluppgift',
'The time must be a numeric value' => 'Tiden måste ha ett numeriskt värde',
@ -408,99 +406,156 @@ return array(
'Comment updated' => 'Kommentaren har uppdaterats',
'New comment posted by %s' => 'Ny kommentar postad av %s',
'List of due tasks for the project "%s"' => 'Lista med uppgifter för projektet "%s"',
// '[%s][New attachment] %s (#%d)' => '',
// '[%s][New comment] %s (#%d)' => '',
// '[%s][Comment updated] %s (#%d)' => '',
// '[%s][New subtask] %s (#%d)' => '',
// '[%s][Subtask updated] %s (#%d)' => '',
// '[%s][New task] %s (#%d)' => '',
// '[%s][Task updated] %s (#%d)' => '',
// '[%s][Task closed] %s (#%d)' => '',
// '[%s][Task opened] %s (#%d)' => '',
// '[%s][Due tasks]' => '',
'[%s][New attachment] %s (#%d)' => '[%s][Ny bifogning] %s (#%d)',
'[%s][New comment] %s (#%d)' => '[%s][Ny kommentar] %s (#%d)',
'[%s][Comment updated] %s (#%d)' => '[%s][Uppdaterad kommentar] %s (#%d)',
'[%s][New subtask] %s (#%d)' => '[%s][Ny deluppgift] %s (#%d)',
'[%s][Subtask updated] %s (#%d)' => '[%s][Deluppgiften uppdaterad] %s (#%d)',
'[%s][New task] %s (#%d)' => '[%s][Ny uppgift] %s (#%d)',
'[%s][Task updated] %s (#%d)' => '[%s][Uppgiften uppdaterad] %s (#%d)',
'[%s][Task closed] %s (#%d)' => '[%s][Uppgiften stängd] %s (#%d)',
'[%s][Task opened] %s (#%d)' => '[%s][Uppgiften öppnad] %s (#%d)',
'[%s][Due tasks]' => '[%s][Förfallen uppgift]',
'[Kanboard] Notification' => '[Kanboard] Notis',
'I want to receive notifications only for those projects:' => 'Jag vill endast få notiser för dessa projekt:',
'view the task on Kanboard' => 'Visa uppgiften på Kanboard',
// 'Public access' => '',
// 'Categories management' => '',
// 'Users management' => '',
// 'Active tasks' => '',
// 'Disable public access' => '',
// 'Enable public access' => '',
// 'Active projects' => '',
// 'Inactive projects' => '',
// 'Public access disabled' => '',
// 'Do you really want to disable this project: "%s"?' => '',
// 'Do you really want to duplicate this project: "%s"?' => '',
// 'Do you really want to enable this project: "%s"?' => '',
// 'Project activation' => '',
// 'Move the task to another project' => '',
// 'Move to another project' => '',
// 'Do you really want to duplicate this task?' => '',
// 'Duplicate a task' => '',
// 'External accounts' => '',
// 'Account type' => '',
// 'Local' => '',
// 'Remote' => '',
// 'Enabled' => '',
// 'Disabled' => '',
// 'Google account linked' => '',
// 'Github account linked' => '',
// 'Username:' => '',
// 'Name:' => '',
// 'Email:' => '',
// 'Default project:' => '',
// 'Notifications:' => '',
// 'Group:' => '',
// 'Regular user' => '',
// 'Account type:' => '',
// 'Edit profile' => '',
// 'Change password' => '',
// 'Password modification' => '',
// 'External authentications' => '',
// 'Google Account' => '',
// 'Github Account' => '',
// 'Never connected.' => '',
// 'No account linked.' => '',
// 'Account linked.' => '',
// 'No external authentication enabled.' => '',
// 'Password modified successfully.' => '',
// 'Unable to change the password.' => '',
// 'Change category for the task "%s"' => '',
// 'Change category' => '',
// '%s updated the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// '%s open the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// '%s moved the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to the position #%d in the column "%s"' => '',
// '%s moved the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to the column "%s"' => '',
// '%s created the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// '%s closed the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// '%s created a subtask for the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// '%s updated a subtask for the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// 'Assigned to %s with an estimate of %s/%sh' => '',
// 'Not assigned, estimate of %sh' => '',
// '%s updated a comment on the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// '%s commented the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// '%s\'s activity' => '',
// 'No activity.' => '',
// 'RSS feed' => '',
// '%s updated a comment on the task #%d' => '',
// '%s commented on the task #%d' => '',
// '%s updated a subtask for the task #%d' => '',
// '%s created a subtask for the task #%d' => '',
// '%s updated the task #%d' => '',
// '%s created the task #%d' => '',
// '%s closed the task #%d' => '',
// '%s open the task #%d' => '',
// '%s moved the task #%d to the column "%s"' => '',
// '%s moved the task #%d to the position %d in the column "%s"' => '',
// 'Activity' => '',
// 'Default values are "%s"' => '',
// 'Default columns for new projects (Comma-separated)' => '',
// 'Task assignee change' => '',
// '%s change the assignee of the task #%d' => '',
// '%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// '[%s][Column Change] %s (#%d)' => '',
// '[%s][Position Change] %s (#%d)' => '',
// '[%s][Assignee Change] %s (#%d)' => '',
// 'New password for the user "%s"' => '',
'Public access' => 'Publik åtkomst',
'Category management' => 'Hantera kategorier',
'User management' => 'Hantera användare',
'Active tasks' => 'Aktiva uppgifter',
'Disable public access' => 'Inaktivera publik åtkomst',
'Enable public access' => 'Aktivera publik åtkomst',
'Active projects' => 'Aktiva projekt',
'Inactive projects' => 'Inaktiva projekt',
'Public access disabled' => 'Publik åtkomst har inaktiverats',
'Do you really want to disable this project: "%s"?' => 'Vill du verkligen inaktivera detta projekt: "%s"?',
'Do you really want to duplicate this project: "%s"?' => 'Vill du verkligen kopiera detta projekt: "%s"?',
'Do you really want to enable this project: "%s"?' => 'Vill du verkligen aktivera detta projekt: "%s"?',
'Project activation' => 'Projektaktivering',
'Move the task to another project' => 'Flytta uppgiften till ett annat projekt',
'Move to another project' => 'Flytta till ett annat projekt',
'Do you really want to duplicate this task?' => 'Vill du verkligen kopiera denna uppgift?',
'Duplicate a task' => 'Kopiera en uppgift',
'External accounts' => 'Externa konton',
'Account type' => 'Kontotyp',
'Local' => 'Lokal',
'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:',
'Default project:' => 'Standardprojekt',
'Notifications:' => 'Notiser:',
'Notifications' => 'Notiser',
'Group:' => 'Grupp:',
'Regular user' => 'Normal användare',
'Account type:' => 'Kontotyp:',
'Edit profile' => 'Ändra profil',
'Change password' => 'Byt lösenord',
'Password modification' => 'Ändra lösenord',
'External authentications' => 'Extern autentisering',
'Google Account' => 'Googlekonto',
'Github Account' => 'Githubkonto',
'Never connected.' => 'Inte ansluten.',
'No account linked.' => 'Inget konto länkat.',
'Account linked.' => 'Konto länkat.',
'No external authentication enabled.' => 'Ingen extern autentisering aktiverad.',
'Password modified successfully.' => 'Lösenordet har ändrats.',
'Unable to change the password.' => 'Kunde inte byta lösenord.',
'Change category for the task "%s"' => 'Byt kategori för uppgiften "%s"',
'Change category' => 'Byt kategori',
'%s updated the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s uppdaterade uppgiften <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>',
'%s open the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s öppna uppgiften <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>',
'%s moved the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to the position #%d in the column "%s"' => '%s flyttade uppgiften <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> till positionen #%d i kolumnen "%s"',
'%s moved the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to the column "%s"' => '%s flyttade uppgiften <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> till kolumnen "%s"',
'%s created the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s skapade uppgiften <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>',
'%s closed the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s stängde uppgiften <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>',
'%s created a subtask for the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s skapade en deluppgift för uppgiften <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>',
'%s updated a subtask for the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s uppdaterade en deluppgift för uppgiften <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>',
'Assigned to %s with an estimate of %s/%sh' => 'Tilldelades %s med en uppskattning på %s/%sh',
'Not assigned, estimate of %sh' => 'Inte tilldelade, uppskattat %sh',
'%s updated a comment on the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s uppdaterade en kommentar till uppgiften <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>',
'%s commented the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s kommenterade uppgiften <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>',
'%s\'s activity' => '%s\'s aktivitet',
'No activity.' => 'Ingen aktivitet.',
'RSS feed' => 'RSS flöde',
'%s updated a comment on the task #%d' => '%s uppdaterade en kommentar på uppgiften #%d',
'%s commented on the task #%d' => '%s kommenterade uppgiften #%d',
'%s updated a subtask for the task #%d' => '%s uppdaterade en deluppgift för uppgiften #%d',
'%s created a subtask for the task #%d' => '%s skapade en deluppgift för uppgiften #%d',
'%s updated the task #%d' => '%s uppdaterade uppgiften #%d',
'%s created the task #%d' => '%s skapade uppgiften #%d',
'%s closed the task #%d' => '%s stängde uppgiften #%d',
'%s open the task #%d' => '%s öppnade uppgiften #%d',
'%s moved the task #%d to the column "%s"' => '%s flyttade uppgiften #%d till kolumnen "%s"',
'%s moved the task #%d to the position %d in the column "%s"' => '%s flyttade uppgiften #%d till positionen %d i kolumnen "%s"',
'Activity' => 'Aktivitet',
'Default values are "%s"' => 'Standardvärden är "%s"',
'Default columns for new projects (Comma-separated)' => 'Standardkolumner för nya projekt (kommaseparerade)',
'Task assignee change' => 'Ändra tilldelning av uppgiften',
'%s change the assignee of the task #%d to %s' => '%s byt tilldelning av uppgiften #%d till %s',
'%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to %s' => '%s byt tilldelning av uppgiften <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> till %s',
'[%s][Column Change] %s (#%d)' => '[%s][Byt kolumn] %s (#%d)',
'[%s][Position Change] %s (#%d)' => '[%s][Byt position] %s (#%d)',
'[%s][Assignee Change] %s (#%d)' => '[%s][Byt tilldelning] %s (#%d)',
'New password for the user "%s"' => 'Nytt lösenord för användaren "%s"',
'Choose an event' => 'Välj en händelse',
'Github commit received' => 'Github-bidrag mottaget',
'Github issue opened' => 'Github-fråga öppnad',
'Github issue closed' => 'Github-fråga stängd',
'Github issue reopened' => 'Github-fråga öppnad på nytt',
'Github issue assignee change' => 'Github-fråga ny tilldelning',
'Github issue label change' => 'Github-fråga etikettförändring',
'Create a task from an external provider' => 'Skapa en uppgift från en extern leverantör',
'Change the assignee based on an external username' => 'Ändra tilldelning baserat på ett externt användarnamn',
'Change the category based on an external label' => 'Ändra kategori baserat på en extern etikett',
'Reference' => 'Referens',
'Reference: %s' => 'Referens: %s',
'Label' => 'Etikett',
'Database' => 'Databas',
'About' => 'Om',
'Database driver:' => 'Databasdrivrutin:',
'Board settings' => 'Inställningar för tavla',
'URL and token' => 'URL och token',
'Webhook settings' => 'Webhook inställningar',
'URL for task creation:' => 'URL för att skapa uppgift:',
'Reset token' => 'Nollställ token',
'API endpoint:' => 'API ändpunkt:',
'Refresh interval for private board' => 'Uppdateringsintervall för privat tavla',
'Refresh interval for public board' => 'Uppdateringsintervall för publik tavla',
'Task highlight period' => 'Period att framhäva ny uppgift',
'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Period (i sekunder) att betrakta en uppgifts ändring som ny (ange 0 för att inaktivera, 2 dagar är standard)',
'Frequency in second (60 seconds by default)' => 'Frekvens i sekunder (60 sekunder är standard)',
'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Frekvens i sekunder (ange 0 för att inaktivera, 10 sekunder är standard)',
'Application URL' => 'Applikations-URL',
'Example: http://example.kanboard.net/ (used by email notifications)' => 'Exempel: http://example.kanboard.net/ (används för e-postnotiser)',
'Token regenerated.' => 'Token nyskapad.',
'Date format' => 'Datumformat',
'ISO format is always accepted, example: "%s" and "%s"' => 'ISO-format är alltid tillåtet, exempel: "%s" och "%s"',
'New private project' => 'Nytt privat projekt',
'This project is private' => 'Det här projektet är privat',
'Type here to create a new sub-task' => 'Skriv här för att skapa en ny deluppgift',
'Add' => 'Lägg till',
'Estimated time: %s hours' => 'Uppskattad tid: %s timmar',
'Time spent: %s hours' => 'Nedlaggd tid: %s timmar',
'Started on %B %e, %Y' => 'Startad den %B %e, %Y',
'Start date' => 'Startdatum',
'Time estimated' => 'Uppskattad tid',
'There is nothing assigned to you.' => 'Du har inget tilldelat till dig.',
'My tasks' => 'Mina uppgifte',
'Activity stream' => 'Aktivitetsström',
'Dashboard' => 'Instrumentpanel',
'Confirmation' => 'Bekräftelse',
// 'Allow everybody to access to this project' => '',
// 'Everybody have access to this project.' => '',
// 'Webhooks' => '',
// 'API' => '',
// 'Integration' => '',
// 'Github webhook' => '',
// 'Help on Github webhook' => '',
// 'Create a comment from an external provider' => '',
// 'Github issue comment created' => '',
);

View file

@ -0,0 +1,561 @@
<?php
return array(
'None' => 'ไม่มี',
'edit' => 'แก้ไข',
'Edit' => 'แก้ไข',
'remove' => 'ลบ',
'Remove' => 'ลบ',
'Update' => 'ปรับปรุง',
'Yes' => 'ใช่',
'No' => 'ไม่',
'cancel' => 'ยกเลิก',
'or' => 'หรือ',
'Yellow' => 'สีเหลือง',
'Blue' => 'สีน้ำเงิน',
'Green' => 'สีเขียว',
'Purple' => 'สีม่วง',
'Red' => 'สีแดง',
'Orange' => 'สีส้ม',
'Grey' => 'สีเทา',
'Save' => 'บันทึก',
'Login' => 'เข้าสู่ระบบ',
'Official website:' => 'เวบไซต์อย่างเป็นทางการ:',
'Unassigned' => 'ไม่กำหนด',
'View this task' => 'รายละเอียดงานนี้',
'Remove user' => 'เอาผู้ใช้ออก',
'Do you really want to remove this user: "%s"?' => 'คุณต้องการเอาผู้ใช้ « %s » ออกใช่หรือไม่?',
'New user' => 'ผู้ใช้ใหม่',
'All users' => 'ผู้ใช้ทั้งหมด',
'Username' => 'ชื่อผู้ใช้',
'Password' => 'รหัสผ่าน',
'Default project' => 'โปรเจคเริ่มต้น',
'Administrator' => 'ผู้ดูแลระบบ',
'Sign in' => 'เข้าสู่ระบบ',
'Users' => 'ผู้ใช้',
'No user' => 'ไม่มีผู้ใช้',
'Forbidden' => 'ไม่อนุญาติ',
'Access Forbidden' => 'ไม่อนุญาติให้เข้า',
'Only administrators can access to this page.' => 'หน้าสำหรับผู้ดูแลระบบเท่านั้น',
'Edit user' => 'แก้ไขผู้ใช้',
'Logout' => 'ออกจากระบบ',
'Bad username or password' => 'ชื่อผู้ใช่หรือรหัสผ่านผิด',
'users' => 'ผู้ใช้',
'projects' => 'โปรเจค',
'Edit project' => 'แก้ไขโปรเจค',
'Name' => 'ชื่อ',
'Activated' => 'เปิดใช้งาน',
'Projects' => 'โปรเจค',
'No project' => 'ไม่มีโปรเจค',
'Project' => 'โปรเจค',
'Status' => 'สถานะ',
'Tasks' => 'งาน',
'Board' => 'บอร์ด',
'Actions' => 'การกระทำ',
'Inactive' => 'ไม่เปิดใช้งาน',
'Active' => 'เปิดใช้งาน',
'Column %d' => 'คอลัมน์ %d',
'Add this column' => 'เพิ่มคอลัมน์',
'%d tasks on the board' => '%d งานบนบอร์ด',
'%d tasks in total' => '%d งานทั้งหมด',
'Unable to update this board.' => 'ไม่สามารถปรับปรุงบอร์ดได้.',
'Edit board' => 'แก้ไขบอร์ด',
'Disable' => 'ปิด',
'Enable' => 'เปิด',
'New project' => 'โปรเจคใหม่',
'Do you really want to remove this project: "%s"?' => 'คุณต้องการเอาโปรเจค « %s » ออกใช่หรือไม่?',
'Remove project' => 'ลบโปรเจค',
'Boards' => 'บอร์ด',
'Edit the board for "%s"' => 'แก้ไขบอร์ดสำหรับ « %s »',
'All projects' => 'โปรเจคทั้งหมด',
'Change columns' => 'เปลี่ยนคอลัมน์',
'Add a new column' => 'เพิ่มคอลัมน์ใหม่',
'Title' => 'หัวเรื่อง',
'Add Column' => 'เพิ่มคอลัมน์',
'Project "%s"' => 'โปรเจค « %s »',
'Nobody assigned' => 'ไม่กำหนดใคร',
'Assigned to %s' => 'กำหนดให้ %s',
'Remove a column' => 'ลบคอลัมน์',
'Remove a column from a board' => 'ลบคอลัมน์ออกจากบอร์ด',
'Unable to remove this column.' => 'ไม่สามารถลบคอลัมน์นี้',
'Do you really want to remove this column: "%s"?' => 'คุณต้องการลบคอลัมน์ « %s » ออกใช่หรือไม่?',
'This action will REMOVE ALL TASKS associated to this column!' => 'การกระทำนี้จะลบงานที่เกี่ยวข้องกับคอลัมน์นี้',
'Settings' => 'ตั้งค่า',
'Application settings' => 'ตั้งค่าการทำงาน',
'Language' => 'ภาษา',
// 'Webhook token:' => '',
'API token:' => 'API token:',
'More information' => 'ข้อมูลเพิ่มเติม',
'Database size:' => 'ขนาดฐานข้อมูล:',
'Download the database' => 'ดาวน์โหลดฐานข้อมูล',
'Optimize the database' => 'ปรับปรุงฐานข้อมูล',
'(VACUUM command)' => '(VACUUM command)',
'(Gzip compressed Sqlite file)' => '(Gzip compressed Sqlite file)',
'User settings' => 'ตั้งค่าผู้ใช้',
'My default project:' => 'โปรเจคเริ่มต้นของฉัน:',
'Close a task' => 'ปิดงาน',
'Do you really want to close this task: "%s"?' => 'คุณต้องการปิดงาน « %s » ใช่หรือไม่?',
'Edit a task' => 'แก้ไขงาน',
'Column' => 'คอลัมน์',
'Color' => 'สี',
'Assignee' => 'กำหนดให้',
'Create another task' => 'สร้างงานอื่น',
'New task' => 'งานใหม่',
'Open a task' => 'เปิดงาน',
'Do you really want to open this task: "%s"?' => 'คุณต้องการเปิดงาน: « %s » ใช่หรือไม่?',
'Back to the board' => 'กลับไปที่บอร์ด',
'Created on %B %e, %Y at %k:%M %p' => 'สร้างวันที่ %d/%m/%Y เวลา %H:%M',
'There is nobody assigned' => 'ไม่มีใครถูกกำหนด',
'Column on the board:' => 'คอลัมน์บนบอร์ด:',
'Status is open' => 'สถานะเปิด',
'Status is closed' => 'สถานะปิด',
'Close this task' => 'ปิดงานนี้',
'Open this task' => 'เปิดงานนี้',
'There is no description.' => 'ไม่มีคำอธิบาย',
'Add a new task' => 'เพิ่มงานใหม่',
'The username is required' => 'ต้องการชื่อผู้ใช้',
'The maximum length is %d characters' => 'จำนวนตัวอักษรสูงสุด %d ตัวอักษร',
'The minimum length is %d characters' => 'จำนวนตัวอักษรน้อยสุด %d ตัวอักษร',
'The password is required' => 'ต้องการรหัสผ่าน',
'This value must be an integer' => 'ต้องเป็นตัวเลข',
'The username must be unique' => 'ชื่อผู้ใช้ต้องไม่ซ้ำ',
'The username must be alphanumeric' => 'ชื่อผู้ใช้ต้องเป็นตัวอักษรหรือตัวเลข',
'The user id is required' => 'ต้องการไอดีผู้ใช้',
'Passwords don\'t match' => 'รหัสผ่านไม่ถูกต้อง',
'The confirmation is required' => 'ต้องการการยืนยัน',
'The column is required' => 'ต้องการคอลัมน์',
'The project is required' => 'ต้องการโปรเจค',
'The color is required' => 'ต้องการสี',
'The id is required' => 'ต้องการไอดี',
'The project id is required' => 'ต้องการไอดีโปรเจค',
'The project name is required' => 'ต้องการชื่อโปรเจค',
'This project must be unique' => 'ชื่อโปรเจคต้องไม่ซ้ำ',
'The title is required' => 'ต้องการหัวเรื่อง',
'The language is required' => 'ต้องการภาษา',
'There is no active project, the first step is to create a new project.' => 'ไม่มีโปรเจคที่ทำงานอยู่, ต้องการสร้างโปรเจคใหม่',
'Settings saved successfully.' => 'บันทึกการตั้งค่าเรียบร้อยแล้ว',
'Unable to save your settings.' => 'ไม่สามารถบันทึกการตั้งค่าได้',
'Database optimization done.' => 'ปรับปรุงฐานข้อมูลเรียบร้อยแล้ว',
'Your project have been created successfully.' => 'สร้างโปรเจคเรียบร้อยแล้ว',
'Unable to create your project.' => 'ไม่สามารถสร้างโปรเจคได้',
'Project updated successfully.' => 'ปรับปรุงโปรเจคเรียบร้อยแล้ว',
'Unable to update this project.' => 'ไม่สามารถปรับปรุงโปรเจคได้',
'Unable to remove this project.' => 'ไม่สามารถลบโปรเจคได้',
'Project removed successfully.' => 'ลบโปรเจคเรียบร้อยแล้ว',
'Project activated successfully.' => 'เปิดใช้งานโปรเจคเรียบร้อยแล้ว',
'Unable to activate this project.' => 'ไม่สามารถเปิดใช้งานโปรเจคได้',
'Project disabled successfully.' => 'ปิดโปรเจคเรียบร้อยแล้ว',
'Unable to disable this project.' => 'ไม่สามารถปิดโปรเจคได้',
'Unable to open this task.' => 'ไม่สามารถเปิดงานนี้',
'Task opened successfully.' => 'เปิดงานเรียบร้อยแล้ว',
'Unable to close this task.' => 'ไม่สามารถปิดงานนี้',
'Task closed successfully.' => 'ปิดงานเรียบร้อยแล้ว',
'Unable to update your task.' => 'ไม่สามารถปรับปรุงงานได้',
'Task updated successfully.' => 'ปรับปรุงงานเรียบร้อยแล้ว',
'Unable to create your task.' => 'ไม่สามารถสร้างงานได้',
'Task created successfully.' => 'สร้างงานเรียบร้อยแล้ว',
'User created successfully.' => 'สร้างผู้ใช้เรียบร้อยแล้ว',
'Unable to create your user.' => 'ไม่สามารถสร้างผู้ใช้ได้',
'User updated successfully.' => 'ปรับปรุงผู้ใช้เรียบร้อยแล้ว',
'Unable to update your user.' => 'ไม่สามารถปรับปรุงผู้ใช้ได้',
'User removed successfully.' => 'ลบผู้ใช้เรียบร้อยแล้ว',
'Unable to remove this user.' => 'ไม่สามารถลบผู้ใช้ได้',
'Board updated successfully.' => 'ปรับปรุงบอร์ดเรียบร้อยแล้ว',
'Ready' => 'พร้อม',
'Backlog' => 'งานค้าง',
'Work in progress' => 'กำลังทำ',
'Done' => 'เสร็จ',
'Application version:' => 'แอพเวอร์ชัน:',
'Completed on %B %e, %Y at %k:%M %p' => 'เรียบร้อยวันที่ %d/%m/%Y เวลา %H:%M',
'%B %e, %Y at %k:%M %p' => '%d/%m/%Y เวลา %H:%M',
'Date created' => 'สร้างวันที่',
'Date completed' => 'เรียบร้อยวันที่',
'Id' => 'ไอดี',
'No task' => 'ไม่มีงาน',
'Completed tasks' => 'งานที่เสร็จแล้ว',
'List of projects' => 'รายชื่อโปรเจค',
'Completed tasks for "%s"' => 'งานที่เสร็จแล้วสำหรับ « %s »',
'%d closed tasks' => '%d งานที่ปิด',
'No task for this project' => 'ไม่มีงานสำหรับโปรเจคนี้',
'Public link' => 'ลิงค์สาธารณะ',
'There is no column in your project!' => 'ไม่มีคอลัมน์ในโปรเจคของคุณ',
'Change assignee' => 'เปลี่ยนการกำหนด',
'Change assignee for the task "%s"' => 'เปลี่ยนการกำหนดสำหรับงาน « %s »',
'Timezone' => 'เขตเวลา',
'Sorry, I didn\'t found this information in my database!' => 'เสียใจด้วย ไม่สามารถหาข้อมูลในฐานข้อมูลได้',
'Page not found' => 'ไม่พบหน้า',
'Complexity' => 'ความซับซ้อน',
'limit' => 'จำกัด',
'Task limit' => 'จำกัดงาน',
'This value must be greater than %d' => 'ค่าต้องมากกว่า %d',
'Edit project access list' => 'แก้ไขการเข้าถึงรายชื่อโปรเจค',
'Edit users access' => 'แก้ไขการเข้าถึงผู้ใช้',
'Allow this user' => 'อนุญาตผู้ใช้นี้',
'Only those users have access to this project:' => 'ผู้ใช้ที่สามารถเข้าถึงโปรเจคนี้:',
'Don\'t forget that administrators have access to everything.' => 'อย่าลืมผู้ดูแลระบบสามารถเข้าถึงได้ทุกอย่าง',
'revoke' => 'ยกเลิก',
'List of authorized users' => 'รายชื่อผู้ใช้ที่ได้รับการยืนยัน',
'User' => 'ผู้ใช้',
// 'Nobody have access to this project.' => '',
'You are not allowed to access to this project.' => 'คุณไม่ได้รับอนุญาตให้เข้าถึงโปรเจคนี้',
'Comments' => 'ความคิดเห็น',
'Post comment' => 'แสดงความคิดเห็น',
'Write your text in Markdown' => 'เขียนข้อความในรูปแบบ Markdown',
'Leave a comment' => 'ออกความคิดเห็น',
'Comment is required' => 'ต้องการความคิดเห็น',
'Leave a description' => 'แสดงคำอธิบาย',
'Comment added successfully.' => 'เพิ่มความคิดเห็นเรียบร้อยแล้ว',
'Unable to create your comment.' => 'ไม่สามารถสร้างความคิดเห็น',
'The description is required' => 'ต้องการคำอธิบาย',
'Edit this task' => 'แก้ไขงาน',
'Due Date' => 'วันที่ครบกำหนด',
'Invalid date' => 'วันที่ผิด',
'Must be done before %B %e, %Y' => 'ต้องทำให้เสร็จก่อน %d/%m/%Y',
'%B %e, %Y' => '%d/%m/%Y',
'Automatic actions' => 'การกระทำอัตโนมัติ',
'Your automatic action have been created successfully.' => 'การกระทำอัตโนมัติสร้างเรียบร้อยแล้ว',
'Unable to create your automatic action.' => 'ไม่สามารถสร้างการกระทำอัตโนมัติได้',
'Remove an action' => 'ลบการกระทำ',
'Unable to remove this action.' => 'ไม่สามารถลบการกระทำ',
'Action removed successfully.' => 'ลบการกระทำเรียบร้อยแล้ว',
'Automatic actions for the project "%s"' => 'การกระทำอัตโนมัติสำหรับโปรเจค « %s »',
'Defined actions' => 'กำหนดการกระทำ',
'Add an action' => 'เพิ่มการกระทำ',
'Event name' => 'ชื่อเหตุกาณ์',
'Action name' => 'ชื่อการกระทำ',
'Action parameters' => 'พารามิเตอร์ของการกระทำ',
'Action' => 'การกระทำ',
'Event' => 'เหตุการณ์',
'When the selected event occurs execute the corresponding action.' => 'เหตุการ์ที่เลือกจะเกิดขึ้นเมื่อมีการกระทำที่สอดคล้องกัน',
'Next step' => 'ขั้นตอนต่อไป',
'Define action parameters' => 'กำหนดพารามิเตอร์ของการกระทำ',
'Save this action' => 'บันทึกการกระทำนี้',
'Do you really want to remove this action: "%s"?' => 'คุณต้องการลบการกระทำ « %s » ใช่หรือไม่?',
'Remove an automatic action' => 'ลบการกระทำอัตโนมัติ',
'Close the task' => 'ปิดงาน',
'Assign the task to a specific user' => 'กำหนดงานให้ผู้ใช้แบบเจาะจง',
'Assign the task to the person who does the action' => 'กำหนดงานให้ผู้ใช้งานปัจจุบัน',
'Duplicate the task to another project' => 'ทำซ้ำงานนี้ในโปรเจคอื่น',
'Move a task to another column' => 'ย้ายงานไปคอลัมน์อื่น',
'Move a task to another position in the same column' => 'ย้ายงานไปตำแหน่งอื่นในคอลัมน์เดียวกัน',
'Task modification' => 'แก้ไขงาน',
'Task creation' => 'สร้างงาน',
'Open a closed task' => 'เปิดงานที่ปิดอยู่',
'Closing a task' => 'กำลังปิดงาน',
'Assign a color to a specific user' => 'กำหนดสีให้ผู้ใช้แบบเจาะจง',
'Column title' => 'หัวเรื่องคอลัมน์',
'Position' => 'ตำแหน่ง',
'Move Up' => 'ย้ายขึ้น',
'Move Down' => 'ย้ายลง',
'Duplicate to another project' => 'ทำซ้ำในโปรเจคอื่น',
'Duplicate' => 'ทำซ้ำ',
'link' => 'ลิงค์',
'Update this comment' => 'ปรับปรุงความคิดเห็นนี้',
'Comment updated successfully.' => 'ปรับปรุงความคิดเห็นเรียบร้อยแล้ว',
'Unable to update your comment.' => 'ไม่สามารถปรับปรุงความคิดเห็นได้',
'Remove a comment' => 'ลบความคิดเห็น',
'Comment removed successfully.' => 'ลบความคิดเห็นเรียบร้อยแล้ว',
'Unable to remove this comment.' => 'ไม่สามารถลบความคิดเห็นได้',
'Do you really want to remove this comment?' => 'คุณต้องการลบความคิดเห็น',
'Only administrators or the creator of the comment can access to this page.' => 'เฉพาะผู้ดูแลระบบหรือผู้สร้างความคิดเห็นเข้าถึงหน้านี้',
'Details' => 'รายละเอียด',
'Current password for the user "%s"' => 'รหัสผ่านปัจจุบันของผู้ใช้ « %s »',
'The current password is required' => 'ต้องการรหัสผ่านปัจจุบัน',
'Wrong password' => 'รหัสผ่านผิด',
'Reset all tokens' => 'รีเซตโทเคนทั้งหมด ',
'All tokens have been regenerated.' => 'โทเคนทั้งหมดทำการสร้างใหม่',
'Unknown' => 'ไม่ทราบ',
'Last logins' => 'เข้าใช้ล่าสุด',
'Login date' => 'วันที่เข้าใข้',
'Authentication method' => 'วิธีการยืนยันตัวตน',
'IP address' => 'ไอพี แอดเดรส',
'User agent' => 'User agent',
'Persistent connections' => 'Persistent connections',
'No session.' => 'No session.',
'Expiration date' => 'หมดอายุวันที่',
'Remember Me' => 'จดจำฉัน',
'Creation date' => 'สร้างวันที่',
'Filter by user' => 'กรองตามผู้ใช้',
'Filter by due date' => 'กรองตามวันครบกำหนด',
'Everybody' => 'ทุกคน',
'Open' => 'เปิด',
'Closed' => 'ปิด',
'Search' => 'ค้นหา',
'Nothing found.' => 'ค้นหาไม่พบ.',
'Search in the project "%s"' => 'ค้นหาในโปรเจค "%s"',
'Due date' => 'วันที่ครบกำหนด',
'Others formats accepted: %s and %s' => 'รูปแบบอื่นที่ได้รับการยอมรับ: %s และ %s',
'Description' => 'คำอธิบาย',
'%d comments' => '%d ความคิดเห็น',
'%d comment' => '%d ความคิดเห็น',
'Email address invalid' => 'อีเมลผิด',
'Your Google Account is not linked anymore to your profile.' => 'กูเกิลแอคเคาท์ไม่ได้เชื่อมต่อกับประวัติของคุณ',
'Unable to unlink your Google Account.' => 'ไม่สามารถยกเลิกการเชื่อมต่อกับกูเกิลแอคเคาท์',
'Google authentication failed' => 'การยืนยันกับกูเกิลผิดพลาด',
'Unable to link your Google Account.' => 'ไม่สามารถเชื่อมต่อกับกูเกิลแอคเคาท์',
'Your Google Account is linked to your profile successfully.' => 'กูเกลิแอคเคาท์เชื่อมต่อกับประวัติของคุณเรียบร้อยแล้ว',
'Email' => 'อีเมล',
'Link my Google Account' => 'เชื่อมต่อกับกูเกิลแอคเคาท์',
'Unlink my Google Account' => 'ไม่เชื่อมต่อกับกูเกิลแอคเคาท์',
'Login with my Google Account' => 'เข้าใช้ด้วยกูเกิลแอคเคาท์',
'Project not found.' => 'หาโปรเจคไม่พบ',
'Task #%d' => 'งานที่ %d',
'Task removed successfully.' => 'ลบงานเรียบร้อยแล้ว',
'Unable to remove this task.' => 'ไม่สามารถลบงานนี้',
'Remove a task' => 'ลบงาาน',
'Do you really want to remove this task: "%s"?' => 'คุณต้องการลบงาน "%s" ออกใช่หรือไม่?',
'Assign automatically a color based on a category' => 'กำหนดสีอัตโนมัติขึ้นอยู่กับกลุ่ม',
'Assign automatically a category based on a color' => 'กำหนดกลุ่มอัตโนมัติขึ้นอยู่กับสี',
'Task creation or modification' => 'สร้างหรือแก้ไขงาน',
'Category' => 'กลุ่ม',
'Category:' => 'กลุ่ม:',
'Categories' => 'กลุ่ม',
'Category not found.' => 'ไม่พบกลุ่ม.',
'Your category have been created successfully.' => 'สร้างกลุ่มเรียบร้อยแล้ว',
'Unable to create your category.' => 'ไม่สามารถสร้างกลุ่มได้',
'Your category have been updated successfully.' => 'ปรับปรุงกลุ่มเรียบร้อยแล้ว',
'Unable to update your category.' => 'ไม่สามารถปรับปรุงกลุ่มได้',
'Remove a category' => 'ลบกลุ่ม',
'Category removed successfully.' => 'ลบกลุ่มเรียบร้อยแล้ว',
'Unable to remove this category.' => 'ไม่สามารถลบกลุ่มได้',
'Category modification for the project "%s"' => 'แก้ไขกลุ่มสำหรับโปรเจค "%s"',
'Category Name' => 'ชื่อกลุ่ม',
'Categories for the project "%s"' => 'กลุ่มสำหรับโปรเจค "%s"',
'Add a new category' => 'เพิ่มกลุ่มใหม่',
'Do you really want to remove this category: "%s"?' => 'คุณต้องการลบกลุ่ม "%s" ใช่หรือไม่?',
'Filter by category' => 'กรองตามกลุ่ม',
'All categories' => 'กลุ่มทั้งหมด',
'No category' => 'ไม่มีกลุ่ม',
'The name is required' => 'ต้องการชื่อ',
'Remove a file' => 'ลบไฟล์',
'Unable to remove this file.' => 'ไม่สามารถลบไฟล์ได้',
'File removed successfully.' => 'ลบไฟล์เรียบร้อยแล้ว',
'Attach a document' => 'แนบเอกสาร',
'Do you really want to remove this file: "%s"?' => 'คุณต้องการลบไฟล์ "%s" ใช่หรือไม่?',
'open' => 'เปิด',
'Attachments' => 'แนบ',
'Edit the task' => 'แก้ไขงาน',
'Edit the description' => 'แก้ไขคำอธิบาย',
'Add a comment' => 'เพิ่มความคิดเห็น',
'Edit a comment' => 'แก้ไขความคิดเห็น',
'Summary' => 'สรุป',
'Time tracking' => 'การติดตามเวลา',
'Estimate:' => 'ประมาณ:',
'Spent:' => 'ใช้:',
'Do you really want to remove this sub-task?' => 'คุณต้องการลบงานย่อยใช่หรือไม่?',
'Remaining:' => 'เหลือ:',
'hours' => 'ชั่วโมง',
'spent' => 'ใช้',
'estimated' => 'ประมาณ',
'Sub-Tasks' => 'งานย่อย',
'Add a sub-task' => 'เพิ่มงานย่อย',
// 'Original estimate' => '',
'Create another sub-task' => 'สร้างงานย่อยอื่น',
// 'Time spent' => '',
'Edit a sub-task' => 'แก้ไขงานย่อย',
'Remove a sub-task' => 'ลบงานย่อย',
'The time must be a numeric value' => 'เวลาที่ต้องเป็นตัวเลข',
'Todo' => 'สิ่งที่ต้องทำ',
'In progress' => 'กำลังดำเนินการ',
'Sub-task removed successfully.' => 'ลบงานย่อยเรียบร้อยแล้ว',
'Unable to remove this sub-task.' => 'ไม่สามารถลบงานย่อยได้',
'Sub-task updated successfully.' => 'ปรับปรุงงานย่อย่่เรียบร้อยแล้ว',
'Unable to update your sub-task.' => 'ไม่สามารถปรับปรุงานย่อยได้',
'Unable to create your sub-task.' => 'ไม่สามารถสร้างงานย่อยได้',
'Sub-task added successfully.' => 'เพิ่มงานย่อยเรียบร้อยแล้ว',
'Maximum size: ' => 'ขนาดสูงสุด:',
'Unable to upload the file.' => 'ไม่สามารถอัพโหลดไฟล์ได้',
'Display another project' => 'แสดงโปรเจคอื่น',
'Your GitHub account was successfully linked to your profile.' => 'กิทฮับแอคเคาท์เชื่อมต่อกับประวัติเรียบร้อยแล้ว',
'Unable to link your GitHub Account.' => 'ไม่สามารถเชื่อมต่อกับกิทฮับแอคเคาท์ได้',
'GitHub authentication failed' => 'การยืนยันกิทฮับผิดพลาด',
'Your GitHub account is no longer linked to your profile.' => 'กิทฮับแอคเคาท์ไม่ได้มีการเชื่อมโยงไปยังโปรไฟล์ของคุณ',
'Unable to unlink your GitHub Account.' => 'ไม่สามารถยกเลิกการเชื่อมต่อกิทฮับแอคเคาท์ได้',
'Login with my GitHub Account' => 'เข้าใช้ด้วยกิทฮับแอคเคาท์',
'Link my GitHub Account' => 'เชื่อมกับกิทฮับแอคเคาท์',
'Unlink my GitHub Account' => 'ยกเลิกการเชื่อมกับกิทอับแอคเคาท์',
'Created by %s' => 'สร้างโดย %s',
'Last modified on %B %e, %Y at %k:%M %p' => 'แก้ไขล่าสุดวันที่ %B %e, %Y เวลา %k:%M %p',
'Tasks Export' => 'ส่งออกงาน',
'Tasks exportation for "%s"' => 'ส่งออกงานสำหรับ "%s"',
'Start Date' => 'เริ่มวันที่',
'End Date' => 'สิ้นสุดวันที่',
'Execute' => 'ประมวลผล',
'Task Id' => 'งาน ไอดี',
'Creator' => 'ผู้สร้าง',
'Modification date' => 'วันที่แก้ไข',
'Completion date' => 'วันที่เสร็จสิ้น',
'Webhook URL for task creation' => 'Webhook URL for task creation',
'Webhook URL for task modification' => 'Webhook URL for task modification',
'Clone' => 'เลียนแบบ',
// 'Clone Project' => '',
'Project cloned successfully.' => 'เลียนแบบโปรเจคเรียบร้อยแล้ว',
'Unable to clone this project.' => 'ไม่สามารถเลียบแบบโปรเจคได้',
'Email notifications' => 'อีเมลแจ้งเตือน',
'Enable email notifications' => 'เปิดอีเมลแจ้งเตือน',
'Task position:' => 'ตำแหน่งงาน',
'The task #%d have been opened.' => 'งานที่ #%d ถุกเปิด',
'The task #%d have been closed.' => 'งานที่ #%d ถูกปิด',
'Sub-task updated' => 'ปรับปรุงงานย่อย',
'Title:' => 'หัวเรื่อง:',
'Status:' => 'สถานะ:',
'Assignee:' => 'กำหนดให้:',
'Time tracking:' => 'การติดตามเวลา:',
'New sub-task' => 'งานย่อยใหม่',
'New attachment added "%s"' => 'เพิ่มการแนบใหม่ "%s"',
'Comment updated' => 'ปรับปรุงความคิดเห็น',
'New comment posted by %s' => 'ความคิดเห็นใหม่จาก %s',
'List of due tasks for the project "%s"' => 'รายการงานสำหรับโปรเจค "%s"',
'[%s][New attachment] %s (#%d)' => '[%s][แนบใหม่] %s (#%d)',
'[%s][New comment] %s (#%d)' => '[%s][ความคิดเห็นใหม่] %s (#%d)',
'[%s][Comment updated] %s (#%d)' => '[%s][ปรับปรุงความคิดเห็น] %s (#%d)',
'[%s][New subtask] %s (#%d)' => '[%s][งานย่อยใหม่] %s (#%d)',
'[%s][Subtask updated] %s (#%d)' => '[%s][ปรับปรุงงานย่อย] %s (#%d)',
'[%s][New task] %s (#%d)' => '[%s][งานใหม่] %s (#%d)',
'[%s][Task updated] %s (#%d)' => '[%s][ปรับปรุุงงาน] %s (#%d)',
'[%s][Task closed] %s (#%d)' => '[%s][งานที่ปิด] %s (#%d)',
'[%s][Task opened] %s (#%d)' => '[%s][งานที่เปิด] %s (#%d)',
'[%s][Due tasks]' => '[%s][งานปัจจุบัน]',
'[Kanboard] Notification' => '[Kanboard] แจ้งเตือน',
'I want to receive notifications only for those projects:' => 'ฉันต้องการรับการแจ้งเตือนสำหรับโปรเจค:',
'view the task on Kanboard' => 'แสดงงานบน Kanboard',
'Public access' => 'การเข้าถึงสาธารณะ',
// 'Category management' => '',
// 'User management' => '',
'Active tasks' => 'งานที่กำลังใช้งาน',
'Disable public access' => 'ปิดการเข้าถึงสาธารณะ',
'Enable public access' => 'เปิดการเข้าถึงสาธารณะ',
'Active projects' => 'เปิดโปรเจค',
'Inactive projects' => 'ปิดโปรเจค',
'Public access disabled' => 'การเข้าถึงสาธารณะถูกปิด',
'Do you really want to disable this project: "%s"?' => 'คุณต้องการปิดการใช้งานโปรเจคนี้: "%s" ใช่หรือไม่?',
'Do you really want to duplicate this project: "%s"?' => 'คุณต้องการทำซ้ำโปรเจคนี้ "%s" ใช่หรือไม่?',
'Do you really want to enable this project: "%s"?' => 'คุณต้องการเปิดการใช้งานโปรเจคนี้: "%s" ใช่หรือไม่?',
'Project activation' => 'การ เปิด/ปิด ใช้งานโปรเจค',
'Move the task to another project' => 'ย้ายงานไปโปรเจคอื่น',
'Move to another project' => 'ย้ายไปโปรเจคอื่น',
'Do you really want to duplicate this task?' => 'คุณต้องการทำซ้ำงานนี้ใช่หรือไม่?',
'Duplicate a task' => 'ทำซ้ำงาน',
'External accounts' => 'บัญชีภายนอก',
'Account type' => 'ประเภทบัญชี',
'Local' => 'ท้องถิ่น',
'Remote' => 'รีโมท',
'Enabled' => 'เปิดการใช้',
'Disabled' => 'ปิดการใช้',
'Google account linked' => 'เชื่อมกับกูเกิลแอคเคาท์',
'Github account linked' => 'เชื่อมกับกิทฮับแอคเคาท์',
'Username:' => 'ชื่อผู้ใช้:',
'Name:' => 'ชื่อ:',
'Email:' => 'อีเมล:',
'Default project:' => 'โปรเจคเริ่มต้น:',
'Notifications:' => 'แจ้งเตือน:',
'Notifications' => 'การแจ้งเตือน',
'Group:' => 'กลุ่ม:',
'Regular user' => 'ผู้ใช้ปกติ:',
'Account type:' => 'ชนิดบัญชี:',
'Edit profile' => 'แก้ไขประวัติ',
'Change password' => 'เปลี่ยนรหัสผ่าน',
'Password modification' => 'แก้ไขรหัสผ่าน',
'External authentications' => 'การยืนยันภายนอก',
'Google Account' => 'กูเกิลแอคเคาท์',
'Github Account' => 'กิทฮับแอคเคาท์',
'Never connected.' => 'ไม่เชื่อมต่อ',
'No account linked.' => 'แอคเคาท์ไม่มีการเชื่อม',
'Account linked.' => 'แอคเคาท์เชื่อมต่อแล้ว',
'No external authentication enabled.' => 'ไม่เปิดการใช้งานการยืนยันภายนอก',
'Password modified successfully.' => 'แก้ไขรหัสผ่านเรียบร้อยแล้ว',
'Unable to change the password.' => 'ไม่สามารถเปลี่ยนรหัสผ่านได้',
'Change category for the task "%s"' => 'เปลี่ยนกลุ่มสำหรับงาน "%s"',
'Change category' => 'เปลี่ยนกลุ่ม',
'%s updated the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s ปรับปรุงงานแล้ว <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>',
'%s open the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s เปิดงานแล้ว <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>',
'%s moved the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to the position #%d in the column "%s"' => '%s ย้ายงานแล้ว <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> ไปตำแหน่ง #%d ในคอลัมน์ "%s"',
'%s moved the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to the column "%s"' => '%s ย้ายงานแล้ว <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> ไปคอลัมน์ "%s"',
'%s created the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s สร้างงานแล้ว <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>',
'%s closed the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s ปิดงานแล้ว <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>',
'%s created a subtask for the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s สร้างงานย่อยสำหรับงานแล้ว <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>',
'%s updated a subtask for the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s ปรับปรุงงานย่อยสำหรับงานแล้ว <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>',
'Assigned to %s with an estimate of %s/%sh' => 'กำหนดให้ %s โดยประมาณแล้ว %s/%sh',
'Not assigned, estimate of %sh' => 'ไม่กำหนดแล้ว, ประมาณเวลาที่ใช้ %s ชั่วโมง',
'%s updated a comment on the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s ปรับปรุงความคิดเห็นในงานแล้ว <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>',
'%s commented the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s แสดงความคิดเห็นของงานแล้ว <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>',
'%s\'s activity' => 'กิจกรรม %s',
'No activity.' => 'ไม่มีกิจกรรม',
'RSS feed' => 'RSS feed',
'%s updated a comment on the task #%d' => '%s ปรับปรุงความคิดเห็นบนงานแล้ว #%d',
'%s commented on the task #%d' => '%s แสดงความคิดเห็นบนงานแล้ว #%d',
'%s updated a subtask for the task #%d' => '%s ปรับปรุงงานย่อยสำหรับงานแล้ว #%d',
'%s created a subtask for the task #%d' => '%s สร้างงานย่อยสำหรับงานแล้ว #%d',
'%s updated the task #%d' => '%s ปรับปรุงงานแล้ว #%d',
'%s created the task #%d' => '%s สร้างงานแล้ว #%d',
'%s closed the task #%d' => '%s ปิดงานแล้ว #%d',
'%s open the task #%d' => '%s เปิดงานแล้ว #%d',
'%s moved the task #%d to the column "%s"' => '%s ย้ายงานแล้ว #%d ไปที่คอลัมน์ "%s"',
'%s moved the task #%d to the position %d in the column "%s"' => '%s ย้ายงานแล้ว #%d ไปตำแหน่ง %d ในคอลัมน์ที่ "%s"',
'Activity' => 'กิจกรรม',
'Default values are "%s"' => 'ค่าเริ่มต้น "%s"',
'Default columns for new projects (Comma-separated)' => 'คอลัมน์เริ่มต้นสำหรับโปรเจคใหม่ (Comma-separated)',
'Task assignee change' => 'เปลี่ยนการกำหนดบุคคลของงาน',
// '%s change the assignee of the task #%d to %s' => '',
// '%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to %s' => '',
// '[%s][Column Change] %s (#%d)' => '',
// '[%s][Position Change] %s (#%d)' => '',
// '[%s][Assignee Change] %s (#%d)' => '',
'New password for the user "%s"' => 'รหัสผ่านใหม่สำหรับผู้ใช้ "%s"',
// 'Choose an event' => '',
// 'Github commit received' => '',
// 'Github issue opened' => '',
// 'Github issue closed' => '',
// 'Github issue reopened' => '',
// 'Github issue assignee change' => '',
// 'Github issue label change' => '',
// 'Create a task from an external provider' => '',
// 'Change the assignee based on an external username' => '',
// 'Change the category based on an external label' => '',
// 'Reference' => '',
// 'Reference: %s' => '',
// 'Label' => '',
// 'Database' => '',
// 'About' => '',
// 'Database driver:' => '',
// 'Board settings' => '',
// 'URL and token' => '',
// 'Webhook settings' => '',
// 'URL for task creation:' => '',
// 'Reset token' => '',
// 'API endpoint:' => '',
// 'Refresh interval for private board' => '',
// 'Refresh interval for public board' => '',
// 'Task highlight period' => '',
// 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => '',
// 'Frequency in second (60 seconds by default)' => '',
// 'Frequency in second (0 to disable this feature, 10 seconds by default)' => '',
// 'Application URL' => '',
// 'Example: http://example.kanboard.net/ (used by email notifications)' => '',
// 'Token regenerated.' => '',
// 'Date format' => '',
// 'ISO format is always accepted, example: "%s" and "%s"' => '',
// 'New private project' => '',
// 'This project is private' => '',
// 'Type here to create a new sub-task' => '',
// 'Add' => '',
// 'Estimated time: %s hours' => '',
// 'Time spent: %s hours' => '',
// 'Started on %B %e, %Y' => '',
// 'Start date' => '',
// 'Time estimated' => '',
// 'There is nothing assigned to you.' => '',
// 'My tasks' => '',
// 'Activity stream' => '',
// 'Dashboard' => '',
'Confirmation' => 'ยืนยันรหัสผ่าน',
// 'Allow everybody to access to this project' => '',
'Everybody have access to this project.' => 'ทุกคนสามารถเข้าถึงโปรเจคนี้',
// 'Webhooks' => '',
// 'API' => '',
// 'Integration' => '',
// 'Github webhook' => '',
// 'Help on Github webhook' => '',
// 'Create a comment from an external provider' => '',
// 'Github issue comment created' => '',
);

View file

@ -0,0 +1,561 @@
<?php
return array(
'None' => '无',
'edit' => '编辑',
'Edit' => '编辑',
'remove' => '移除',
'Remove' => '移除',
'Update' => '更新',
'Yes' => '是',
'No' => '否',
'cancel' => '取消',
'or' => '或者',
'Yellow' => '黄色',
'Blue' => '蓝色',
'Green' => '绿色',
'Purple' => '紫色',
'Red' => '红色',
'Orange' => '橘色',
'Grey' => '灰色',
'Save' => '保存',
'Login' => '登录',
'Official website:' => '官方网站:',
'Unassigned' => '未指定',
'View this task' => '查看该任务',
'Remove user' => '移除用户',
'Do you really want to remove this user: "%s"?' => '你确定要移除这个用户吗:"%s"',
'New user' => '新建用户',
'All users' => '所有用户',
'Username' => '用户名',
'Password' => '密码',
'Default project' => '默认项目',
'Administrator' => '管理员',
'Sign in' => '登录',
'Users' => '用户',
'No user' => '没有用户',
'Forbidden' => '禁止',
'Access Forbidden' => '禁止进入',
'Only administrators can access to this page.' => '只有管理员可以查看该页面。',
'Edit user' => '修改用户',
'Logout' => '退出',
'Bad username or password' => '用户名或密码错误',
'users' => '用户',
'projects' => '项目',
'Edit project' => '修改项目',
'Name' => '名称',
'Activated' => '已激活',
'Projects' => '项目',
'No project' => '无项目',
'Project' => '项目',
'Status' => '状态',
'Tasks' => '任务',
'Board' => '看板',
'Actions' => '动作',
'Inactive' => '未激活',
'Active' => '激活',
'Column %d' => '第%d栏目',
'Add this column' => '加入该栏目',
'%d tasks on the board' => '看板目前有%d个任务',
'%d tasks in total' => '总共有%d个任务',
'Unable to update this board.' => '无法更新该看板。',
'Edit board' => '修改看板',
'Disable' => '停用',
'Enable' => '启用',
'New project' => '新建项目',
'Do you really want to remove this project: "%s"?' => '你确定要移除该项目吗:"%s"',
'Remove project' => '移除项目',
'Boards' => '看板',
'Edit the board for "%s"' => '为"%s"修改看板',
'All projects' => '所有项目',
'Change columns' => '更改栏目',
'Add a new column' => '添加新栏目',
'Title' => '标题',
'Add Column' => '添加栏目',
'Project "%s"' => '项目 "%s"',
'Nobody assigned' => '无人被指派',
'Assigned to %s' => '指派给 %s',
'Remove a column' => '移除一个栏目',
'Remove a column from a board' => '从看板移除一个栏目',
'Unable to remove this column.' => '无法移除该栏目。',
'Do you really want to remove this column: "%s"?' => '你确定要移除该栏目:"%s"吗?',
'This action will REMOVE ALL TASKS associated to this column!' => '该动作将移除与该栏目相关的所有项目!',
'Settings' => '设置',
'Application settings' => '应用设置',
'Language' => '语言',
'Webhook token:' => '页面钩子令牌:',
'API token:' => 'API 令牌:',
'More information' => '更多信息',
'Database size:' => '数据库大小:',
'Download the database' => '下载数据库',
'Optimize the database' => '优化数据库',
'(VACUUM command)' => '(VACUUM 指令)',
'(Gzip compressed Sqlite file)' => '(用Gzip压缩的Sqlite文件)',
'User settings' => '用户设置',
'My default project:' => '我的默认项目:',
'Close a task' => '关闭一个任务',
'Do you really want to close this task: "%s"?' => '你确定要关闭任务: "%s"?',
'Edit a task' => '修改一个任务',
'Column' => '栏目',
'Color' => '颜色',
'Assignee' => '负责人',
'Create another task' => '创建另一个任务',
'New task' => '新进任务',
'Open a task' => '开一个任务',
'Do you really want to open this task: "%s"?' => '你确定要开这个任务吗?"%s"',
'Back to the board' => '回到看板',
'Created on %B %e, %Y at %k:%M %p' => '创建时间:%Y/%m/%d %H:%M',
'There is nobody assigned' => '无人负责',
'Column on the board:' => '看板上的栏目:',
'Status is open' => '开启状态',
'Status is closed' => '关闭状态',
'Close this task' => '关闭该任务',
'Open this task' => '开启该任务',
'There is no description.' => '无描述。',
'Add a new task' => '添加新任务',
'The username is required' => '需要用户名',
'The maximum length is %d characters' => '最长%d个英文字符',
'The minimum length is %d characters' => '最短%d个英文自负',
'The password is required' => '需要密码',
'This value must be an integer' => '该值必须为整数',
'The username must be unique' => '用户名必须唯一',
'The username must be alphanumeric' => '用户名必须是英文字符或数字组成',
'The user id is required' => '用户id是必须的',
'Passwords don\'t match' => '密码不匹配',
'The confirmation is required' => '需要确认',
'The column is required' => '需要指定栏目',
'The project is required' => '需要指定项目',
'The color is required' => '需要指定颜色',
'The id is required' => '需要指定id',
'The project id is required' => '需要指定项目id',
'The project name is required' => '需要指定项目名称',
'This project must be unique' => '项目名称必须唯一',
'The title is required' => '需要指定标题',
'The language is required' => '需要指定语言',
'There is no active project, the first step is to create a new project.' => '尚无活跃项目,请首先创建一个新项目。',
'Settings saved successfully.' => '设置成功保存。',
'Unable to save your settings.' => '无法保存你的设置。',
'Database optimization done.' => '数据库优化完成。',
'Your project have been created successfully.' => '您的项目已经成功创建。',
'Unable to create your project.' => '无法为您创建项目。',
'Project updated successfully.' => '成功更新项目。',
'Unable to update this project.' => '无法更新该项目。',
'Unable to remove this project.' => '无法移除该项目。',
'Project removed successfully.' => '成功移除项目。',
'Project activated successfully.' => '项目成功激活。',
'Unable to activate this project.' => '无法激活该项目。',
'Project disabled successfully.' => '成功停用项目。',
'Unable to disable this project.' => '无法停用该项目。',
'Unable to open this task.' => '无法开启该任务。',
'Task opened successfully.' => '任务开启成功。',
'Unable to close this task.' => '无法关闭该任务。',
'Task closed successfully.' => '成功关闭任务。',
'Unable to update your task.' => '无法更新您的任务。',
'Task updated successfully.' => '成功更新任务。',
'Unable to create your task.' => '无法为您创建任务。',
'Task created successfully.' => '成功创建任务。',
'User created successfully.' => '成功创建用户。',
'Unable to create your user.' => '无法创建用户。',
'User updated successfully.' => '成功更新用户。',
'Unable to update your user.' => '无法为您更新用户。',
'User removed successfully.' => '成功移除用户。',
'Unable to remove this user.' => '无法移除该用户。',
'Board updated successfully.' => '看板成功更新。',
'Ready' => '预备',
'Backlog' => '积压',
'Work in progress' => '工作进行中',
'Done' => '完成',
'Application version:' => '应用程序版本:',
'Completed on %B %e, %Y at %k:%M %p' => '于%Y/%m/%d %H:%M 完成',
'%B %e, %Y at %k:%M %p' => '%Y/%m/%d %H:%M',
'Date created' => '创建时间',
'Date completed' => '完成时间',
'Id' => '编号',
'No task' => '无任务',
'Completed tasks' => '已完成任务',
'List of projects' => '项目列表',
'Completed tasks for "%s"' => '"%s"已经完成的任务',
'%d closed tasks' => '%d个已关闭任务',
'No task for this project' => '该项目尚无任务',
'Public link' => '公开链接',
'There is no column in your project!' => '该项目尚无栏目项!',
'Change assignee' => '变更负责人',
'Change assignee for the task "%s"' => '更改任务"%s"的负责人',
'Timezone' => '时区',
'Sorry, I didn\'t found this information in my database!' => '抱歉,无法在数据库中找到该信息!',
'Page not found' => '页面未找到',
'Complexity' => '复杂度',
'limit' => '限制',
'Task limit' => '任务限制',
'This value must be greater than %d' => '该数值必须大于%d',
'Edit project access list' => '编辑项目存取列表',
'Edit users access' => '编辑用户存取权限',
'Allow this user' => '允许该用户',
'Only those users have access to this project:' => '只有这些用户有该项目的存取权限:',
'Don\'t forget that administrators have access to everything.' => '别忘了管理员有一切的权限。',
'revoke' => '撤销',
'List of authorized users' => '已授权的用户列表',
'User' => '用户',
'Nobody have access to this project.' => '无用户可以访问此项目.',
'You are not allowed to access to this project.' => '您对该项目没有权限。',
'Comments' => '评论',
'Post comment' => '发表评论',
'Write your text in Markdown' => '用Markdown格式编写',
'Leave a comment' => '留言',
'Comment is required' => '必须得有评论',
'Leave a description' => '给一个描述',
'Comment added successfully.' => '评论成功添加。',
'Unable to create your comment.' => '无法创建评论。',
'The description is required' => '必须得有描述',
'Edit this task' => '编辑该任务',
'Due Date' => '到期时间',
'Invalid date' => '无效日期',
'Must be done before %B %e, %Y' => '必须在%Y/%m/%d前完成',
'%B %e, %Y' => '%Y/%m/%d',
'Automatic actions' => '自动动作',
'Your automatic action have been created successfully.' => '您的自动动作已成功创建',
'Unable to create your automatic action.' => '无法为您创建自动动作。',
'Remove an action' => '移除一个动作。',
'Unable to remove this action.' => '无法移除该动作',
'Action removed successfully.' => '成功移除动作。',
'Automatic actions for the project "%s"' => '项目"%s"的自动动作',
'Defined actions' => '已定义的动作',
'Add an action' => '添加动作',
'Event name' => '事件名称',
'Action name' => '动作名称',
'Action parameters' => '动作参数',
'Action' => '动作',
'Event' => '事件',
'When the selected event occurs execute the corresponding action.' => '当所选事件发生时执行相应动作。',
'Next step' => '下一步',
'Define action parameters' => '定义动作参数',
'Save this action' => '保存该动作',
'Do you really want to remove this action: "%s"?' => '确定要移除该动作"%s"吗?',
'Remove an automatic action' => '移除一个自动动作',
'Close the task' => '关闭任务',
'Assign the task to a specific user' => '将该任务指派给一个用户',
'Assign the task to the person who does the action' => '将任务指派给产生该动作的用户',
'Duplicate the task to another project' => '复制该任务到另一项目',
'Move a task to another column' => '移动任务到另一栏目',
'Move a task to another position in the same column' => '将任务移到该栏目另一位置',
'Task modification' => '任务修改',
'Task creation' => '任务创建',
'Open a closed task' => '开启已关闭任务',
'Closing a task' => '正在关闭任务',
'Assign a color to a specific user' => '为特定用户指派颜色',
'Column title' => '栏目名称',
'Position' => '位置',
'Move Up' => '往上移',
'Move Down' => '往下移',
'Duplicate to another project' => '复制到另一项目',
'Duplicate' => '复制',
'link' => '连接',
'Update this comment' => '更新该评论',
'Comment updated successfully.' => '评论成功更新。',
'Unable to update your comment.' => '无法更新您的评论。',
'Remove a comment' => '移除评论',
'Comment removed successfully.' => '评论成功移除。',
'Unable to remove this comment.' => '无法移除该评论。',
'Do you really want to remove this comment?' => '确定要移除评论吗?',
'Only administrators or the creator of the comment can access to this page.' => '只有管理员或评论创建者可以进入该页面。',
'Details' => '细节',
'Current password for the user "%s"' => '用户"%s"的当前密码',
'The current password is required' => '需要输入当前密码',
'Wrong password' => '密码错误',
'Reset all tokens' => '重置所有令牌',
'All tokens have been regenerated.' => '所有令牌都重新生成了。',
'Unknown' => '未知',
'Last logins' => '上次登录',
'Login date' => '登录日期',
'Authentication method' => '认证方式',
'IP address' => 'IP地址',
'User agent' => '浏览器标识',
'Persistent connections' => '持续连接',
'No session.' => '无会话',
'Expiration date' => '过期',
'Remember Me' => '记住我',
'Creation date' => '创建日期',
'Filter by user' => '按用户过滤',
'Filter by due date' => '按到期时间过滤',
'Everybody' => '所有人',
'Open' => '打开',
'Closed' => '关闭',
'Search' => '查找',
'Nothing found.' => '没找到。',
'Search in the project "%s"' => '在项目"%s"中查找',
'Due date' => '到期时间',
'Others formats accepted: %s and %s' => '允许其他格式:%s 和 %s',
'Description' => '描述',
'%d comments' => '%d个评论',
'%d comment' => '%d个评论',
'Email address invalid' => 'Email地址无效',
'Your Google Account is not linked anymore to your profile.' => '您的google帐号不再与您的账户配置关联。',
'Unable to unlink your Google Account.' => '无法去除您google帐号的关联',
'Google authentication failed' => 'google验证失败',
'Unable to link your Google Account.' => '无法关联您的google帐号。',
'Your Google Account is linked to your profile successfully.' => '您的google帐号已成功与账户配置关联。',
'Email' => 'Email',
'Link my Google Account' => '关联我的google帐号',
'Unlink my Google Account' => '去除我的google帐号关联',
'Login with my Google Account' => '用我的google帐号登录',
'Project not found.' => '未发现项目',
'Task #%d' => '任务 #%d',
'Task removed successfully.' => '任务成功去除',
'Unable to remove this task.' => '无法移除该任务。',
'Remove a task' => '移除一个任务',
'Do you really want to remove this task: "%s"?' => '确定要移除任务"%s"吗?',
'Assign automatically a color based on a category' => '基于一个分类自动指派颜色',
'Assign automatically a category based on a color' => '基于一种颜色自动指派分类',
'Task creation or modification' => '任务创建或修改',
'Category' => '分类',
'Category:' => '分类:',
'Categories' => '分类',
'Category not found.' => '未找到分类。',
'Your category have been created successfully.' => '成功为您创建分类。',
'Unable to create your category.' => '无法为您创建分类。',
'Your category have been updated successfully.' => '成功为您更新分类。',
'Unable to update your category.' => '无法为您更新分类。',
'Remove a category' => '移除一个分类',
'Category removed successfully.' => '分类成功移除。',
'Unable to remove this category.' => '无法移除该分类。',
'Category modification for the project "%s"' => '为项目"%s"修改分类',
'Category Name' => '分类名称',
'Categories for the project "%s"' => '项目"%s"的分类',
'Add a new category' => '加入新分类',
'Do you really want to remove this category: "%s"?' => '您确定要移除分类"%s"吗?',
'Filter by category' => '按分类过滤',
'All categories' => '所有分类',
'No category' => '无分类',
'The name is required' => '必须要有名字',
'Remove a file' => '移除一个文件',
'Unable to remove this file.' => '无法移除该文件。',
'File removed successfully.' => '文件成功移除。',
'Attach a document' => '附加文档',
'Do you really want to remove this file: "%s"?' => '您确定要移除文件"%s"吗?',
'open' => '打开',
'Attachments' => '附件',
'Edit the task' => '修改任务',
'Edit the description' => '修改描述',
'Add a comment' => '添加评论',
'Edit a comment' => '编辑评论',
'Summary' => '概要',
'Time tracking' => '时间记录',
'Estimate:' => '评估:',
'Spent:' => '花费:',
'Do you really want to remove this sub-task?' => '请确认是否要删除此子任务?',
'Remaining:' => '剩余:',
'hours' => '小时',
'spent' => '花费',
'estimated' => '评估',
'Sub-Tasks' => '子任务',
'Add a sub-task' => '添加子任务',
'Original estimate' => '初步预估',
'Create another sub-task' => '创建另一个子任务',
'Time spent' => '时间花费',
'Edit a sub-task' => '编辑子任务',
'Remove a sub-task' => '删除子任务',
'The time must be a numeric value' => '时间必须为数字',
'Todo' => '待完成',
'In progress' => '正在进行',
'Sub-task removed successfully.' => '成功删除子任务',
'Unable to remove this sub-task.' => '无法删除此任务',
'Sub-task updated successfully.' => '成功创建子任务',
'Unable to update your sub-task.' => '无法更新子任务',
'Unable to create your sub-task.' => '无法创建子任务',
'Sub-task added successfully.' => '成功添加子任务',
'Maximum size: ' => '大小上限:',
'Unable to upload the file.' => '无法上传文件',
'Display another project' => '显示其它项目',
'Your GitHub account was successfully linked to your profile.' => 'GitHub账号已经成功链接到您的用户',
'Unable to link your GitHub Account.' => '无法链接到GitHub账户',
'GitHub authentication failed' => 'GitHub认证失败',
'Your GitHub account is no longer linked to your profile.' => 'Github账号已经不再链接到您的用户',
'Unable to unlink your GitHub Account.' => '无法链接GitHub账号',
'Login with my GitHub Account' => '用Github账号登录',
'Link my GitHub Account' => '链接GitHub账号',
'Unlink my GitHub Account' => '取消GitHub账号链接',
'Created by %s' => '创建者:',
'Last modified on %B %e, %Y at %k:%M %p' => '最后修改:%Y/%m/%d/ %H:%M',
'Tasks Export' => '任务导出',
'Tasks exportation for "%s"' => '导出任务"%s"',
'Start Date' => '开始时间',
'End Date' => '结束时间',
'Execute' => '执行',
'Task Id' => '任务ID',
'Creator' => '创建者',
'Modification date' => '修改日期',
'Completion date' => '完成日期',
'Webhook URL for task creation' => '创建任务的Webhook URL',
'Webhook URL for task modification' => '修改任务的Webhook URL',
'Clone' => '克隆',
'Clone Project' => '复制项目',
'Project cloned successfully.' => '成功复制项目。',
'Unable to clone this project.' => '无法复制此项目',
'Email notifications' => '邮件通知',
'Enable email notifications' => '启用邮件通知',
'Task position:' => '任务位置:',
'The task #%d have been opened.' => '任务#%d已经被开启.',
'The task #%d have been closed.' => '任务#%d已经被关闭.',
'Sub-task updated' => '子任务更新',
'Title:' => '标题:',
'Status:' => '状态:',
'Assignee:' => '负责人',
'Time tracking:' => '时间记录',
'New sub-task' => '新建子任务',
'New attachment added "%s"' => '新附件已添加"%s"',
'Comment updated' => '更新了评论',
'New comment posted by %s' => '%s 的新评论',
'List of due tasks for the project "%s"' => '项目"%s"的到期任务列表',
'[%s][New attachment] %s (#%d)' => '[%s][新附件] %s (#%d)',
'[%s][New comment] %s (#%d)' => '[%s][新评论] %s (#%d)',
'[%s][Comment updated] %s (#%d)' => '[%s][评论更新] %s (#%d)',
'[%s][New subtask] %s (#%d)' => '[%s][新子任务] %s (#%d)',
'[%s][Subtask updated] %s (#%d)' => '[%s][子任务更新] %s (#%d)',
'[%s][New task] %s (#%d)' => '[%s][新任务] %s (#%d)',
'[%s][Task updated] %s (#%d)' => '[%s][任务更新] %s (#%d)',
'[%s][Task closed] %s (#%d)' => '[%s][任务关闭] %s (#%d)',
'[%s][Task opened] %s (#%d)' => '[%s][任务开启] %s (#%d)',
'[%s][Due tasks]' => '[%s][到期任务]',
'[Kanboard] Notification' => '[Kanboard] 通知',
'I want to receive notifications only for those projects:' => '我仅需要收到下面项目的通知:',
'view the task on Kanboard' => '在看板中查看此任务',
'Public access' => '公开访问',
'Category management' => '分类管理',
'User management' => '用户管理',
'Active tasks' => '活动任务',
'Disable public access' => '停止公开访问',
'Enable public access' => '开启公开访问',
'Active projects' => '活动项目',
'Inactive projects' => '不活动项目',
'Public access disabled' => '已经禁止公开访问',
'Do you really want to disable this project: "%s"?' => '确认要禁用项目"%s"吗?',
'Do you really want to duplicate this project: "%s"?' => '确认要复制项目"%s"吗?',
'Do you really want to enable this project: "%s"?' => '确认要启用项目"%s"吗?',
'Project activation' => '项目启动',
'Move the task to another project' => '移动任务到其它项目',
'Move to another project' => '移动到其它项目',
'Do you really want to duplicate this task?' => '确定要复制此任务吗?',
'Duplicate a task' => '复制任务',
'External accounts' => '外部账户',
'Account type' => '账户类型',
'Local' => '本地',
'Remote' => '远程',
'Enabled' => '启用',
'Disabled' => '停用',
'Google account linked' => '已经链接谷歌账号',
'Github account linked' => '已经链接Github账号',
'Username:' => '用户名:',
'Name:' => '姓名:',
'Email:' => '电子邮件:',
'Default project:' => '默认项目:',
'Notifications:' => '通知:',
'Notifications' => '通知',
'Group:' => '群组:',
'Regular user' => '常规用户',
'Account type:' => '账户类型:',
'Edit profile' => '编辑属性',
'Change password' => '修改密码',
'Password modification' => '修改密码',
'External authentications' => '外部认证',
'Google Account' => '谷歌账号',
'Github Account' => 'Github 账号',
'Never connected.' => '从未连接。',
'No account linked.' => '未链接账号。',
'Account linked.' => '已经链接账号。',
'No external authentication enabled.' => '未启用外部认证。',
'Password modified successfully.' => '已经成功修改密码。',
'Unable to change the password.' => '无法修改密码。',
'Change category for the task "%s"' => '变更任务 "%s" 的分类',
'Change category' => '变更分类',
'%s updated the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s 更新了任务 <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>',
'%s open the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s 开启了任务 <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>',
'%s moved the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to the position #%d in the column "%s"' => '%s 将任务 <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> 移动到了"%s"的第#%d个位置',
'%s moved the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to the column "%s"' => '%s 移动任务 <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> 到栏目 "%s"',
'%s created the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s 创建了任务 <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>',
'%s closed the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s 关闭了任务 <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>',
'%s created a subtask for the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s 创建了 <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>的子任务',
'%s updated a subtask for the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s 更新了 <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>的子任务',
'Assigned to %s with an estimate of %s/%sh' => '分配给 %s预估需要 %s/%s 小时',
'Not assigned, estimate of %sh' => '未分配,预估需要 %s 小时',
'%s updated a comment on the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s 更新了任务 <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>的评论',
'%s commented the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '%s 评论了任务 <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>',
'%s\'s activity' => '%s的活动',
'No activity.' => '无活动',
'RSS feed' => 'RSS 链接',
'%s updated a comment on the task #%d' => '%s 更新了任务 #%d 的评论',
'%s commented on the task #%d' => '%s 评论了任务 #%d',
'%s updated a subtask for the task #%d' => '%s 更新了任务 #%d 的子任务',
'%s created a subtask for the task #%d' => '%s 创建了任务 #%d 的子任务',
'%s updated the task #%d' => '%s 更新了任务 #%d',
'%s created the task #%d' => '%s 创建了任务 #%d',
'%s closed the task #%d' => '%s 关闭了任务 #%d',
'%s open the task #%d' => '%s 开启了任务 #%d',
'%s moved the task #%d to the column "%s"' => '%s 将任务 #%d 移动到栏目 "%s"',
'%s moved the task #%d to the position %d in the column "%s"' => '%s将任务#%d移动到"%s"的第 %d 列',
'Activity' => '活动',
'Default values are "%s"' => '默认值为 "%s"',
'Default columns for new projects (Comma-separated)' => '新建项目的默认栏目(用逗号分开)',
'Task assignee change' => '任务分配变更',
'%s change the assignee of the task #%d to %s' => '%s 将任务 #%d 分配给了 %s',
'%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to %s' => '%s 将任务 <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> 分配给 %s',
'[%s][Column Change] %s (#%d)' => '[%s][栏目变更] %s (#%d)',
'[%s][Position Change] %s (#%d)' => '[%s][位置变更] %s (#%d)',
'[%s][Assignee Change] %s (#%d)' => '[%s][任务分配变更] %s (#%d)',
'New password for the user "%s"' => '用户"%s"的新密码',
'Choose an event' => '选择一个事件',
'Github commit received' => '收到了Github提交',
'Github issue opened' => '开启了Github问题报告',
'Github issue closed' => '关闭了Github问题报告',
'Github issue reopened' => '重新开启Github问题',
'Github issue assignee change' => 'Github问题负责人已经变更',
'Github issue label change' => 'Github任务标签修改',
'Create a task from an external provider' => '从外部创建任务',
'Change the assignee based on an external username' => '根据外部用户名修改任务分配',
'Change the category based on an external label' => '根据外部标签修改分类',
'Reference' => '参考',
'Reference: %s' => '参考:%s',
'Label' => '标签',
'Database' => '数据库',
'About' => '关于',
'Database driver:' => '数据库驱动:',
'Board settings' => '看板设置',
'URL and token' => 'URL和令牌',
'Webhook settings' => 'Webhook 设置',
'URL for task creation:' => '创建任务的URL',
'Reset token' => '重置令牌',
'API endpoint:' => 'API 端点:',
'Refresh interval for private board' => '私人看板的刷新时间',
'Refresh interval for public board' => '公共看板的刷新时间',
'Task highlight period' => '任务高亮时间',
'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => '多久内的任务视作刚刚修改(单位:秒,设置为0停用默认是2天',
'Frequency in second (60 seconds by default)' => '频率,单位为秒(默认是60秒)',
'Frequency in second (0 to disable this feature, 10 seconds by default)' => '频率,单位为秒(设置为0停用此功能默认是10秒)',
'Application URL' => '应用URL',
'Example: http://example.kanboard.net/ (used by email notifications)' => '例如http://example.kanboard.net/ (用于电子邮件通知)',
'Token regenerated.' => '重新生成令牌',
'Date format' => '日期格式',
'ISO format is always accepted, example: "%s" and "%s"' => 'ISO 格式总是允许的,例如:"%s" 和 "%s"',
'New private project' => '新建私有项目',
'This project is private' => '此项目为私有项目',
'Type here to create a new sub-task' => '要创建新的子任务,请在此输入',
'Add' => '添加',
'Estimated time: %s hours' => '预计时间:%s小时',
'Time spent: %s hours' => '花费时间:%s小时',
'Started on %B %e, %Y' => '开始时间: %Y/%m/%d %H:%M ',
'Start date' => '启动日期',
'Time estimated' => '预计时间',
'There is nothing assigned to you.' => '无任务指派给你。',
'My tasks' => '我的任务',
'Activity stream' => '活动流',
'Dashboard' => '面板',
'Confirmation' => '确认',
// 'Allow everybody to access to this project' => '',
// 'Everybody have access to this project.' => '',
// 'Webhooks' => '',
// 'API' => '',
// 'Integration' => '',
// 'Github webhook' => '',
// 'Help on Github webhook' => '',
// 'Create a comment from an external provider' => '',
// 'Github issue comment created' => '',
);

View file

@ -1,506 +0,0 @@
<?php
return array(
'None' => '未知',
'edit' => '修改',
'Edit' => '修改',
'remove' => '移除',
'Remove' => '移除',
'Update' => '更新',
'Yes' => '是',
'No' => '否',
'cancel' => '取消',
'or' => '或者',
'Yellow' => '黄色',
'Blue' => '蓝色',
'Green' => '绿色',
'Purple' => '紫色',
'Red' => '红色',
'Orange' => '橘色',
'Grey' => '灰色',
'Save' => '保存',
'Login' => '登陆',
'Official website:' => '官方网站:',
'Unassigned' => '未指定',
'View this task' => '查看该任务',
'Remove user' => '移除用户',
'Do you really want to remove this user: "%s"?' => '你确定要移除这个用户吗:"%s"',
'New user' => '新用户',
'All users' => '所有用户',
'Username' => '用户名',
'Password' => '密码',
'Default project' => '默认项目',
'Administrator' => '管理员',
'Sign in' => '登录',
'Users' => '用户组',
'No user' => '没有用户',
'Forbidden' => '禁止',
'Access Forbidden' => '禁止进入',
'Only administrators can access to this page.' => '只有管理员可以查看该页面。',
'Edit user' => '修改用户',
'Logout' => '登出',
'Bad username or password' => '用户名或密码错误',
'users' => '用户组',
'projects' => '项目群',
'Edit project' => '修改项目',
'Name' => '名称',
'Activated' => '已激活',
'Projects' => '项目群',
'No project' => '无项目',
'Project' => '项目',
'Status' => '状态',
'Tasks' => '任务群',
'Board' => '看板',
'Actions' => '行为',
'Inactive' => '未激活',
'Active' => '激活',
'Column %d' => '第%d栏目',
'Add this column' => '加入该栏目',
'%d tasks on the board' => '看板目前有%d个任务',
'%d tasks in total' => '总共有%d个任务',
'Unable to update this board.' => '无法更新该看板。',
'Edit board' => '修改看板',
'Disable' => '禁用',
'Enable' => '启用',
'New project' => '新项目',
'Do you really want to remove this project: "%s"?' => '你确定要移除该项目吗:"%s"',
'Remove project' => '移除项目',
'Boards' => '看板群',
'Edit the board for "%s"' => '为"%s"修改看板',
'All projects' => '所有项目',
'Change columns' => '更改栏目',
'Add a new column' => '添加新栏目',
'Title' => '标题',
'Add Column' => '添加栏目',
'Project "%s"' => '项目 "%s"',
'Nobody assigned' => '无人被指派',
'Assigned to %s' => '指派给 %s',
'Remove a column' => '移除一个栏目',
'Remove a column from a board' => '从看板移除一个栏目',
'Unable to remove this column.' => '无法移除该栏目。',
'Do you really want to remove this column: "%s"?' => '你确定要移除该栏目:"%s"吗?',
'This action will REMOVE ALL TASKS associated to this column!' => '该行为将移除与该栏目相关的所有项目!',
'Settings' => '设置',
'Application settings' => '应用设置',
'Language' => '语言',
'Webhooks token:' => '页面钩子令牌:',
// 'API token:' => '',
'More information' => '更多信息',
'Database size:' => '数据库大小:',
'Download the database' => '下载数据库',
'Optimize the database' => '优化数据库',
'(VACUUM command)' => '(数据库归整指令)',
'(Gzip compressed Sqlite file)' => '(用Gzip压缩Sqlite文件)',
'User settings' => '用户设置',
'My default project:' => '我的默认项目:',
'Close a task' => '关闭一个项目',
'Do you really want to close this task: "%s"?' => '你确定要关闭该项目?"%s"',
'Edit a task' => '修改一个项目',
'Column' => '栏目',
'Color' => '颜色',
'Assignee' => '负责人',
'Create another task' => '创建另一个项目',
'New task' => '新项目',
'Open a task' => '开一个项目',
'Do you really want to open this task: "%s"?' => '你确定要开这个项目吗?"%s"',
'Back to the board' => '回到看板',
'Created on %B %e, %Y at %k:%M %p' => '在%d/%m/%Y %H:%M创建',
'There is nobody assigned' => '无人负责',
'Column on the board:' => '看板上的栏目:',
'Status is open' => '开放状态',
'Status is closed' => '关闭状态',
'Close this task' => '关闭该项目',
'Open this task' => '开放该项目',
'There is no description.' => '无描述。',
'Add a new task' => '添加新任务',
'The username is required' => '需要用户名',
'The maximum length is %d characters' => '最长%d个英文字符',
'The minimum length is %d characters' => '最短%d个英文自负',
'The password is required' => '需要密码',
'This value must be an integer' => '该值必须为整数',
'The username must be unique' => '用户名必须唯一',
'The username must be alphanumeric' => '用户名必须是英文字符或数字组成',
'The user id is required' => '用户id是必须的',
// 'Passwords don\'t match' => '',
'The confirmation is required' => '需要确认',
'The column is required' => '需要指定栏目',
'The project is required' => '需要指定项目',
'The color is required' => '需要指定颜色',
'The id is required' => '需要指定id',
'The project id is required' => '需要指定项目id',
'The project name is required' => '需要指定项目名称',
'This project must be unique' => '项目名称必须唯一',
'The title is required' => '需要指定标题',
'The language is required' => '需要指定语言',
'There is no active project, the first step is to create a new project.' => '尚无活跃项目,请首先创建一个新项目。',
'Settings saved successfully.' => '设置成功保存。',
'Unable to save your settings.' => '无法保存你的设置。',
'Database optimization done.' => '数据库优化完成。',
'Your project have been created successfully.' => '您的项目已经成功创建。',
'Unable to create your project.' => '无法为您创建项目。',
'Project updated successfully.' => '项目成功更新。',
'Unable to update this project.' => '无法更新该项目。',
'Unable to remove this project.' => '无法移除该项目。',
'Project removed successfully.' => '项目成功移除。',
'Project activated successfully.' => '项目成功激活。',
'Unable to activate this project.' => '无法激活该项目。',
'Project disabled successfully.' => '项目成功禁用。',
'Unable to disable this project.' => '无法禁用该项目。',
'Unable to open this task.' => '无法开启该任务。',
'Task opened successfully.' => '任务开启成功。',
'Unable to close this task.' => '无法关闭该任务。',
'Task closed successfully.' => '任务成功关闭。',
'Unable to update your task.' => '无法更新您的任务。',
'Task updated successfully.' => '任务成功更新。',
'Unable to create your task.' => '无法为您创建项目。',
'Task created successfully.' => '任务成功创建。',
'User created successfully.' => '用户成功创建。',
'Unable to create your user.' => '无法创建用户。',
'User updated successfully.' => '用户成功更新。',
'Unable to update your user.' => '无法为您更新用户。',
'User removed successfully.' => '用户成功移除。',
'Unable to remove this user.' => '无法移除该用户。',
'Board updated successfully.' => '看板成功更新。',
'Ready' => '预备',
'Backlog' => '积压',
'Work in progress' => '工作进行中',
'Done' => '完成',
'Application version:' => '应用程序版本',
'Completed on %B %e, %Y at %k:%M %p' => '于%Y年%m月%d日%H时%M分完成',
'%B %e, %Y at %k:%M %p' => '%Y年%m月%d日%H时%M分',
'Date created' => '创建时间',
'Date completed' => '完成时间',
'Id' => '昵称',
'No task' => '无任务',
'Completed tasks' => '已完成任务',
'List of projects' => '项目列表',
'Completed tasks for "%s"' => '任务因"%s"原因完成',
'%d closed tasks' => '%d个已关闭任务',
'No task for this project' => '该项目尚无任务',
'Public link' => '公开链接',
'There is no column in your project!' => '该项目尚无栏目项!',
'Change assignee' => '被指派人变更',
'Change assignee for the task "%s"' => '为任务"%s"更改被指派人',
'Timezone' => '时区',
'Sorry, I didn\'t found this information in my database!' => '抱歉,无法在数据库中找到该信息!',
'Page not found' => '页面未找到',
'Complexity' => '评估分值',
'limit' => '限制',
'Task limit' => '任务限制',
'This value must be greater than %d' => '该数值必须大于%d',
'Edit project access list' => '编辑项目存取列表',
'Edit users access' => '编辑用户存取权限',
'Allow this user' => '允许该用户',
'Only those users have access to this project:' => '只有这些用户有该项目的存取权限:',
'Don\'t forget that administrators have access to everything.' => '别忘了管理员有一切的权限。',
'revoke' => '撤销',
'List of authorized users' => '已授权的用户列表',
'User' => '用户',
'Everybody have access to this project.' => '任何人都有该项目权限。',
'You are not allowed to access to this project.' => '您对该项目没有权限。',
'Comments' => '评论',
'Post comment' => '发表评论',
'Write your text in Markdown' => '用Markdown格式编写',
'Leave a comment' => '留言',
'Comment is required' => '必须得有评论',
'Leave a description' => '给一个描述',
'Comment added successfully.' => '评论成功添加。',
'Unable to create your comment.' => '无法创建评论。',
'The description is required' => '必须得有描述',
'Edit this task' => '编辑该任务',
'Due Date' => '到期',
'm/d/Y' => 'Y/m/d',
'month/day/year' => '年/月/日',
'Invalid date' => '无效日期',
'Must be done before %B %e, %Y' => '必须在%Y年%m月%d日前完成',
'%B %e, %Y' => '%Y/%m/%d',
'Automatic actions' => '自动行为',
'Your automatic action have been created successfully.' => '您的自动行为已成功创建',
'Unable to create your automatic action.' => '无法为您创建自动行为。',
'Remove an action' => '移除一个行为。',
'Unable to remove this action.' => '无法移除该行为',
'Action removed successfully.' => '成功移除行为。',
'Automatic actions for the project "%s"' => '项目"%s"的自动行为',
'Defined actions' => '已定义的行为',
'Add an action' => '添加一个行为',
'Event name' => '事件名称',
'Action name' => '行为名称',
'Action parameters' => '行为参数',
'Action' => '行为',
'Event' => '事件',
'When the selected event occurs execute the corresponding action.' => '当所选事件发生时执行相应行为。',
'Next step' => '下一步',
'Define action parameters' => '定义行为参数',
'Save this action' => '保存该行为',
'Do you really want to remove this action: "%s"?' => '确定要益处该行为"%s"吗?',
'Remove an automatic action' => '移除一个自动行为',
'Close the task' => '关闭任务',
'Assign the task to a specific user' => '将该任务指派给一个用户',
'Assign the task to the person who does the action' => '将任务指派给产生该行为的用户',
'Duplicate the task to another project' => '复制该任务到另一项目',
'Move a task to another column' => '移动任务到另一栏目',
'Move a task to another position in the same column' => '将任务移到该栏目另一位置',
'Task modification' => '任务修改',
'Task creation' => '任务创建',
'Open a closed task' => '开启已关闭任务',
'Closing a task' => '正在关闭任务',
'Assign a color to a specific user' => '为特定用户指派颜色',
'Column title' => '栏目名称',
'Position' => '位置',
'Move Up' => '往上移',
'Move Down' => '往下移',
'Duplicate to another project' => '复制到另一项目',
'Duplicate' => '复制',
'link' => '连接',
'Update this comment' => '更新该评论',
'Comment updated successfully.' => '评论成功更新。',
'Unable to update your comment.' => '无法更新您的评论。',
'Remove a comment' => '移除评论',
'Comment removed successfully.' => '评论成功移除。',
'Unable to remove this comment.' => '无法移除该评论。',
'Do you really want to remove this comment?' => '确定要移除评论吗?',
'Only administrators or the creator of the comment can access to this page.' => '只有管理员或评论创建者可以进入该页面。',
'Details' => '细节',
'Current password for the user "%s"' => '用户"%s"的当前密码',
'The current password is required' => '需要输入当前密码',
'Wrong password' => '密码错误',
'Reset all tokens' => '重置所有令牌',
'All tokens have been regenerated.' => '所有令牌都重新生成了。',
'Unknown' => '未知',
'Last logins' => '上次登录',
'Login date' => '登录日期',
'Authentication method' => '认证方式',
'IP address' => 'IP地址',
'User agent' => '用户代理',
'Persistent connections' => '持续连接',
'No session.' => '无会话',
'Expiration date' => '过期',
'Remember Me' => '记住我',
'Creation date' => '创建日期',
'Filter by user' => '按用户过滤',
'Filter by due date' => '按到期时间过滤',
'Everybody' => '所有人',
'Open' => '打开',
'Closed' => '关闭',
'Search' => '查找',
'Nothing found.' => '没找到。',
'Search in the project "%s"' => '在项目"%s"中查找',
'Due date' => '到期',
'Others formats accepted: %s and %s' => '允许其他格式:%s和%s',
'Description' => '描述',
'%d comments' => '%d个评论',
'%d comment' => '%d个评论',
'Email address invalid' => 'Email地址无效',
'Your Google Account is not linked anymore to your profile.' => '您的google帐号不再与您的账户配置关联。',
'Unable to unlink your Google Account.' => '无法去除您google帐号的关联',
'Google authentication failed' => 'google验证失败',
'Unable to link your Google Account.' => '无法关联您的google帐号。',
'Your Google Account is linked to your profile successfully.' => '您的google帐号已成功与账户配置关联。',
'Email' => 'Email',
'Link my Google Account' => '关联我的google帐号',
'Unlink my Google Account' => '去除我的google帐号关联',
'Login with my Google Account' => '用我的google帐号登录',
'Project not found.' => '未发现项目',
'Task #%d' => '任务 #%d',
'Task removed successfully.' => '任务成功去除',
'Unable to remove this task.' => '无法移除该任务。',
'Remove a task' => '移除一个任务',
'Do you really want to remove this task: "%s"?' => '确定要溢出该任务"%s"吗?',
'Assign automatically a color based on a category' => '基于一个分类自动指派颜色',
'Assign automatically a category based on a color' => '基于一种颜色自动指派分类',
'Task creation or modification' => '任务创建或修改',
'Category' => '分类',
'Category:' => '分类:',
'Categories' => '分类',
'Category not found.' => '未找到分类。',
'Your category have been created successfully.' => '成功为您创建分类。',
'Unable to create your category.' => '无法为您创建分类。',
'Your category have been updated successfully.' => '成功为您更新分类。',
'Unable to update your category.' => '无法为您更新分类。',
'Remove a category' => '移除一个分类',
'Category removed successfully.' => '分类成功移除。',
'Unable to remove this category.' => '无法移除该分类。',
'Category modification for the project "%s"' => '为项目"%s"修改分类',
'Category Name' => '分类名称',
'Categories for the project "%s"' => '项目"%s"的分类',
'Add a new category' => '加入一个新分类',
'Do you really want to remove this category: "%s"?' => '您确定要移除分类"%s"吗?',
'Filter by category' => '按分类过滤',
'All categories' => '所有分类',
'No category' => '无分类',
'The name is required' => '必须要有名字',
'Remove a file' => '移除一个文件',
'Unable to remove this file.' => '无法移除该文件。',
'File removed successfully.' => '文件成功移除。',
'Attach a document' => '附上一个文档',
'Do you really want to remove this file: "%s"?' => '您确定要移除文件"%s"吗?',
'open' => '打开',
'Attachments' => '附件',
'Edit the task' => '修改任务',
'Edit the description' => '修改描述',
'Add a comment' => '添加一个注释',
'Edit a comment' => '修改一个注释',
'Summary' => '概要',
// 'Time tracking' => '',
// 'Estimate:' => '',
// 'Spent:' => '',
// 'Do you really want to remove this sub-task?' => '',
// 'Remaining:' => '',
'hours' => '小时',
// 'spent' => '',
// 'estimated' => '',
// 'Sub-Tasks' => '',
'Add a sub-task' => '添加一个子任务',
'Original Estimate' => '初步预计耗时',
'Create another sub-task' => '创建另一个子任务',
// 'Time Spent' => '',
// 'Edit a sub-task' => '',
// 'Remove a sub-task' => '',
// 'The time must be a numeric value' => '',
// 'Todo' => '',
// 'In progress' => '',
// 'Sub-task removed successfully.' => '',
// 'Unable to remove this sub-task.' => '',
// 'Sub-task updated successfully.' => '',
// 'Unable to update your sub-task.' => '',
// 'Unable to create your sub-task.' => '',
// 'Sub-task added successfully.' => '',
// 'Maximum size: ' => '',
// 'Unable to upload the file.' => '',
// 'Display another project' => '',
// 'Your GitHub account was successfully linked to your profile.' => '',
// 'Unable to link your GitHub Account.' => '',
// 'GitHub authentication failed' => '',
// 'Your GitHub account is no longer linked to your profile.' => '',
// 'Unable to unlink your GitHub Account.' => '',
// 'Login with my GitHub Account' => '',
// 'Link my GitHub Account' => '',
// 'Unlink my GitHub Account' => '',
// 'Created by %s' => '',
// 'Last modified on %B %e, %Y at %k:%M %p' => '',
// 'Tasks Export' => '',
// 'Tasks exportation for "%s"' => '',
// 'Start Date' => '',
// 'End Date' => '',
// 'Execute' => '',
// 'Task Id' => '',
// 'Creator' => '',
// 'Modification date' => '',
// 'Completion date' => '',
// 'Webhook URL for task creation' => '',
// 'Webhook URL for task modification' => '',
// 'Clone' => '',
// 'Clone Project' => '',
// 'Project cloned successfully.' => '',
// 'Unable to clone this project.' => '',
// 'Email notifications' => '',
// 'Enable email notifications' => '',
// 'Task position:' => '',
// 'The task #%d have been opened.' => '',
// 'The task #%d have been closed.' => '',
// 'Sub-task updated' => '',
// 'Title:' => '',
// 'Status:' => '',
// 'Assignee:' => '',
// 'Time tracking:' => '',
// 'New sub-task' => '',
// 'New attachment added "%s"' => '',
// 'Comment updated' => '',
// 'New comment posted by %s' => '',
// 'List of due tasks for the project "%s"' => '',
// '[%s][New attachment] %s (#%d)' => '',
// '[%s][New comment] %s (#%d)' => '',
// '[%s][Comment updated] %s (#%d)' => '',
// '[%s][New subtask] %s (#%d)' => '',
// '[%s][Subtask updated] %s (#%d)' => '',
// '[%s][New task] %s (#%d)' => '',
// '[%s][Task updated] %s (#%d)' => '',
// '[%s][Task closed] %s (#%d)' => '',
// '[%s][Task opened] %s (#%d)' => '',
// '[%s][Due tasks]' => '',
// '[Kanboard] Notification' => '',
// 'I want to receive notifications only for those projects:' => '',
// 'view the task on Kanboard' => '',
// 'Public access' => '',
// 'Categories management' => '',
// 'Users management' => '',
// 'Active tasks' => '',
// 'Disable public access' => '',
// 'Enable public access' => '',
// 'Active projects' => '',
// 'Inactive projects' => '',
// 'Public access disabled' => '',
// 'Do you really want to disable this project: "%s"?' => '',
// 'Do you really want to duplicate this project: "%s"?' => '',
// 'Do you really want to enable this project: "%s"?' => '',
// 'Project activation' => '',
// 'Move the task to another project' => '',
// 'Move to another project' => '',
// 'Do you really want to duplicate this task?' => '',
// 'Duplicate a task' => '',
// 'External accounts' => '',
// 'Account type' => '',
// 'Local' => '',
// 'Remote' => '',
// 'Enabled' => '',
// 'Disabled' => '',
// 'Google account linked' => '',
// 'Github account linked' => '',
// 'Username:' => '',
// 'Name:' => '',
// 'Email:' => '',
// 'Default project:' => '',
// 'Notifications:' => '',
// 'Group:' => '',
// 'Regular user' => '',
// 'Account type:' => '',
// 'Edit profile' => '',
// 'Change password' => '',
// 'Password modification' => '',
// 'External authentications' => '',
// 'Google Account' => '',
// 'Github Account' => '',
// 'Never connected.' => '',
// 'No account linked.' => '',
// 'Account linked.' => '',
// 'No external authentication enabled.' => '',
// 'Password modified successfully.' => '',
// 'Unable to change the password.' => '',
// 'Change category for the task "%s"' => '',
// 'Change category' => '',
// '%s updated the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// '%s open the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// '%s moved the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to the position #%d in the column "%s"' => '',
// '%s moved the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a> to the column "%s"' => '',
// '%s created the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// '%s closed the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// '%s created a subtask for the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// '%s updated a subtask for the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// 'Assigned to %s with an estimate of %s/%sh' => '',
// 'Not assigned, estimate of %sh' => '',
// '%s updated a comment on the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// '%s commented the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// '%s\'s activity' => '',
// 'No activity.' => '',
// 'RSS feed' => '',
// '%s updated a comment on the task #%d' => '',
// '%s commented on the task #%d' => '',
// '%s updated a subtask for the task #%d' => '',
// '%s created a subtask for the task #%d' => '',
// '%s updated the task #%d' => '',
// '%s created the task #%d' => '',
// '%s closed the task #%d' => '',
// '%s open the task #%d' => '',
// '%s moved the task #%d to the column "%s"' => '',
// '%s moved the task #%d to the position %d in the column "%s"' => '',
// 'Activity' => '',
// 'Default values are "%s"' => '',
// 'Default columns for new projects (Comma-separated)' => '',
// 'Task assignee change' => '',
// '%s change the assignee of the task #%d' => '',
// '%s change the assignee of the task <a href="?controller=task&amp;action=show&amp;task_id=%d">#%d</a>' => '',
// '[%s][Column Change] %s (#%d)' => '',
// '[%s][Position Change] %s (#%d)' => '',
// '[%s][Assignee Change] %s (#%d)' => '',
// 'New password for the user "%s"' => '',
);

View file

@ -18,9 +18,10 @@ class Acl extends Base
*/
private $public_actions = array(
'user' => array('login', 'check', 'google', 'github'),
'task' => array('add', 'readonly'),
'task' => array('readonly'),
'board' => array('readonly'),
'project' => array('feed'),
'webhook' => array('task', 'github'),
);
/**
@ -31,13 +32,15 @@ class Acl extends Base
*/
private $user_actions = array(
'app' => array('index'),
'board' => array('index', 'show', 'save', 'check', 'changeassignee', 'updateassignee', 'changecategory', 'updatecategory'),
'project' => array('tasks', 'index', 'forbidden', 'search', 'export', 'show', 'activity'),
'user' => array('index', 'edit', 'forbidden', 'logout', 'index', 'show', 'external', 'unlinkgoogle', 'unlinkgithub', 'sessions', 'removesession', 'last', 'notifications', 'password'),
'board' => array('index', 'show', 'save', 'check', 'changeassignee', 'updateassignee', 'changecategory', 'updatecategory', 'movecolumn', 'edit', 'update', 'add', 'confirm', 'remove'),
'project' => array('index', 'show', 'export', 'share', 'edit', 'update', 'users', 'remove', 'duplicate', 'disable', 'enable', 'activity', 'search', 'tasks', 'create', 'save'),
'user' => array('edit', 'forbidden', 'logout', 'show', 'external', 'unlinkgoogle', 'unlinkgithub', 'sessions', 'removesession', 'last', 'notifications', 'password'),
'comment' => array('create', 'save', 'confirm', 'remove', 'update', 'edit', 'forbidden'),
'file' => array('create', 'save', 'download', 'confirm', 'remove', 'open', 'image'),
'subtask' => array('create', 'save', 'edit', 'update', 'confirm', 'remove'),
'task' => array('show', 'create', 'save', 'edit', 'update', 'close', 'open', 'duplicate', 'remove', 'description', 'move', 'copy'),
'subtask' => array('create', 'save', 'edit', 'update', 'confirm', 'remove', 'togglestatus'),
'task' => array('show', 'create', 'save', 'edit', 'update', 'close', 'open', 'duplicate', 'remove', 'description', 'move', 'copy', 'time'),
'category' => array('index', 'save', 'edit', 'update', 'confirm', 'remove'),
'action' => array('index', 'event', 'params', 'create', 'confirm', 'remove'),
);
/**

View file

@ -36,8 +36,9 @@ class Action extends Base
*/
public function getAvailableActions()
{
return array(
'TaskClose' => t('Close the task'),
$values = array(
'TaskClose' => t('Close a task'),
'TaskOpen' => t('Open a task'),
'TaskAssignSpecificUser' => t('Assign the task to a specific user'),
'TaskAssignCurrentUser' => t('Assign the task to the person who does the action'),
'TaskDuplicateAnotherProject' => t('Duplicate the task to another project'),
@ -45,7 +46,15 @@ class Action extends Base
'TaskAssignColorUser' => t('Assign a color to a specific user'),
'TaskAssignColorCategory' => t('Assign automatically a color based on a category'),
'TaskAssignCategoryColor' => t('Assign automatically a category based on a color'),
'CommentCreation' => t('Create a comment from an external provider'),
'TaskCreation' => t('Create a task from an external provider'),
'TaskAssignUser' => t('Change the assignee based on an external username'),
'TaskAssignCategoryLabel' => t('Change the category based on an external label'),
);
asort($values);
return $values;
}
/**
@ -56,16 +65,48 @@ class Action extends Base
*/
public function getAvailableEvents()
{
return array(
$values = array(
Task::EVENT_MOVE_COLUMN => t('Move a task to another column'),
Task::EVENT_MOVE_POSITION => t('Move a task to another position in the same column'),
Task::EVENT_UPDATE => t('Task modification'),
Task::EVENT_CREATE => t('Task creation'),
Task::EVENT_OPEN => t('Open a closed task'),
Task::EVENT_CLOSE => t('Closing a task'),
Task::EVENT_CREATE_UPDATE => t('Task creation or modification'),
Task::EVENT_ASSIGNEE_CHANGE => t('Task assignee change'),
GithubWebhook::EVENT_COMMIT => t('Github commit received'),
GithubWebhook::EVENT_ISSUE_OPENED => t('Github issue opened'),
GithubWebhook::EVENT_ISSUE_CLOSED => t('Github issue closed'),
GithubWebhook::EVENT_ISSUE_REOPENED => t('Github issue reopened'),
GithubWebhook::EVENT_ISSUE_ASSIGNEE_CHANGE => t('Github issue assignee change'),
GithubWebhook::EVENT_ISSUE_LABEL_CHANGE => t('Github issue label change'),
GithubWebhook::EVENT_ISSUE_COMMENT => t('Github issue comment created'),
);
asort($values);
return $values;
}
/**
* Return the name and description of compatible actions
*
* @access public
* @param string $action_name Action name
* @return array
*/
public function getCompatibleEvents($action_name)
{
$action = $this->load($action_name, 0, '');
$compatible_events = $action->getCompatibleEvents();
$events = array();
foreach ($this->getAvailableEvents() as $event_name => $event_description) {
if (in_array($event_name, $compatible_events)) {
$events[$event_name] = $event_description;
}
}
return $events;
}
/**
@ -115,7 +156,7 @@ class Action extends Base
foreach ($this->getAll() as $action) {
$action = $this->load($action['action_name'], $action['project_id']);
$action = $this->load($action['action_name'], $action['project_id'], $action['event_name']);
$params += $action->getActionRequiredParameters();
}
@ -201,7 +242,7 @@ class Action extends Base
{
foreach ($this->getAll() as $action) {
$listener = $this->load($action['action_name'], $action['project_id']);
$listener = $this->load($action['action_name'], $action['project_id'], $action['event_name']);
foreach ($action['params'] as $param) {
$listener->setParam($param['name'], $param['value']);
@ -215,21 +256,15 @@ class Action extends Base
* Load an action
*
* @access public
* @param string $name Action class name
* @param integer $project_id Project id
* @throws \LogicException
* @return \Core\Listener Action Instance
* @param string $name Action class name
* @param integer $project_id Project id
* @param string $event Event name
* @return \Core\Listener Action instance
*/
public function load($name, $project_id)
public function load($name, $project_id, $event)
{
$className = '\Action\\'.$name;
if ($name === 'TaskAssignCurrentUser') {
return new $className($project_id, new Task($this->registry), new Acl($this->registry));
}
else {
return new $className($project_id, new Task($this->registry));
}
return new $className($this->registry, $project_id, $event);
}
/**

View file

@ -2,6 +2,7 @@
namespace Model;
use Core\Request;
use Auth\Database;
use SimpleValidator\Validator;
use SimpleValidator\Validators;
@ -117,7 +118,7 @@ class Authentication extends Base
if (! empty($values['remember_me'])) {
$credentials = $this->backend('rememberMe')
->create($this->acl->getUserId(), $this->user->getIpAddress(), $this->user->getUserAgent());
->create($this->acl->getUserId(), Request::getIpAddress(), Request::getUserAgent());
$this->backend('rememberMe')->writeCookie($credentials['token'], $credentials['sequence'], $credentials['expiration']);
}

View file

@ -19,14 +19,23 @@ use PicoDb\Database;
* @property \Model\Board $board
* @property \Model\Category $category
* @property \Model\Comment $comment
* @property \Model\CommentHistory $commentHistory
* @property \Model\Color $color
* @property \Model\Config $config
* @property \Model\DateParser $dateParser
* @property \Model\File $file
* @property \Model\LastLogin $lastLogin
* @property \Model\Notification $notification
* @property \Model\Project $project
* @property \Model\ProjectPermission $projectPermission
* @property \Model\SubTask $subTask
* @property \Model\SubtaskHistory $subtaskHistory
* @property \Model\Task $task
* @property \Model\TaskExport $taskExport
* @property \Model\TaskFinder $taskFinder
* @property \Model\TaskHistory $taskHistory
* @property \Model\TaskValidator $taskValidator
* @property \Model\TimeTracking $timeTracking
* @property \Model\User $user
* @property \Model\Webhook $webhook
*/
@ -80,4 +89,52 @@ abstract class Base
{
return Tool::loadModel($this->registry, $name);
}
/**
* Remove keys from an array
*
* @access public
* @param array $values Input array
* @param array $keys List of keys to remove
*/
public function removeFields(array &$values, array $keys)
{
foreach ($keys as $key) {
if (isset($values[$key])) {
unset($values[$key]);
}
}
}
/**
* Force some fields to be at 0 if empty
*
* @access public
* @param array $values Input array
* @param array $keys List of keys
*/
public function resetFields(array &$values, array $keys)
{
foreach ($keys as $key) {
if (isset($values[$key]) && empty($values[$key])) {
$values[$key] = 0;
}
}
}
/**
* Force some fields to be integer
*
* @access public
* @param array $values Input array
* @param array $keys List of keys
*/
public function convertIntegerFields(array &$values, array $keys)
{
foreach ($keys as $key) {
if (isset($values[$key])) {
$values[$key] = (int) $values[$key];
}
}
}
}

View file

@ -1,70 +0,0 @@
<?php
namespace Model;
use PDO;
use Core\Template;
/**
* Task history model
*
* @package model
* @author Frederic Guillot
*/
abstract class BaseHistory extends Base
{
/**
* SQL table name
*
* @access protected
* @var string
*/
protected $table = '';
/**
* Remove old event entries to avoid a large table
*
* @access public
* @param integer $max Maximum number of items to keep in the table
*/
public function cleanup($max)
{
if ($this->db->table($this->table)->count() > $max) {
$this->db->execute('
DELETE FROM '.$this->table.'
WHERE id <= (
SELECT id FROM (
SELECT id FROM '.$this->table.' ORDER BY id DESC LIMIT 1 OFFSET '.$max.'
) foo
)');
}
}
/**
* Get all events for a given project
*
* @access public
* @return array
*/
public function getAllByProjectId($project_id)
{
return $this->db->table($this->table)
->eq('project_id', $project_id)
->desc('id')
->findAll();
}
/**
* Get the event html content
*
* @access public
* @param array $params Event properties
* @return string
*/
public function getContent(array $params)
{
$tpl = new Template;
return $tpl->load('event_'.str_replace('.', '_', $params['event_name']), $params);
}
}

View file

@ -31,6 +31,29 @@ class Board extends Base
return array(t('Backlog'), t('Ready'), t('Work in progress'), t('Done'));
}
/**
* Get user default columns
*
* @access public
* @return array
*/
public function getUserColumns()
{
$column_names = explode(',', $this->config->get('board_columns', implode(',', $this->getDefaultColumns())));
$columns = array();
foreach ($column_names as $column_name) {
$column_name = trim($column_name);
if (! empty($column_name)) {
$columns[] = array('title' => $column_name, 'task_limit' => 0);
}
}
return $columns;
}
/**
* Create a board with default columns, must be executed inside a transaction
*
@ -211,14 +234,8 @@ class Board extends Base
*/
public function get($project_id, array $filters = array())
{
$this->db->startTransaction();
$columns = $this->getColumns($project_id);
$filters[] = array('column' => 'project_id', 'operator' => 'eq', 'value' => $project_id);
$filters[] = array('column' => 'is_active', 'operator' => 'eq', 'value' => Task::STATUS_OPEN);
$tasks = $this->task->find($filters);
$tasks = $this->taskFinder->getTasksOnBoard($project_id);
foreach ($columns as &$column) {
@ -231,8 +248,6 @@ class Board extends Base
}
}
$this->db->closeTransaction();
return $columns;
}

View file

@ -0,0 +1,31 @@
<?php
namespace Model;
/**
* Color model (TODO: model for the future color picker)
*
* @package model
* @author Frederic Guillot
*/
class Color extends Base
{
/**
* Get available colors
*
* @access public
* @return array
*/
public function getList()
{
return array(
'yellow' => t('Yellow'),
'blue' => t('Blue'),
'green' => t('Green'),
'purple' => t('Purple'),
'red' => t('Red'),
'orange' => t('Orange'),
'grey' => t('Grey'),
);
}
}

View file

@ -1,152 +0,0 @@
<?php
namespace Model;
use PDO;
use Core\Registry;
use Event\CommentHistoryListener;
/**
* Comment history model
*
* @package model
* @author Frederic Guillot
*/
class CommentHistory extends BaseHistory
{
/**
* SQL table name
*
* @var string
*/
const TABLE = 'comment_has_events';
/**
* Maximum number of events
*
* @var integer
*/
const MAX_EVENTS = 5000;
/**
* Constructor
*
* @access public
* @param \Core\Registry $registry Registry instance
*/
public function __construct(Registry $registry)
{
parent::__construct($registry);
$this->table = self::TABLE;
}
/**
* Create a new event
*
* @access public
* @param integer $project_id Project id
* @param integer $task_id Task id
* @param integer $comment_id Comment id
* @param integer $creator_id Author of the event (user id)
* @param string $event_name Task event name
* @param string $data Current comment
* @return boolean
*/
public function create($project_id, $task_id, $comment_id, $creator_id, $event_name, $data)
{
$values = array(
'project_id' => $project_id,
'task_id' => $task_id,
'comment_id' => $comment_id,
'creator_id' => $creator_id,
'event_name' => $event_name,
'date_creation' => time(),
'data' => $data,
);
$this->db->startTransaction();
$this->cleanup(self::MAX_EVENTS - 1);
$result = $this->db->table(self::TABLE)->insert($values);
$this->db->closeTransaction();
return $result;
}
/**
* Get all necessary content to display activity feed
*
* $author_name
* $author_username
* $task['id', 'title', 'position', 'column_name']
*/
public function getAllContentByProjectId($project_id, $limit = 50)
{
$sql = '
SELECT
comment_has_events.id,
comment_has_events.date_creation,
comment_has_events.event_name,
comment_has_events.data as comment,
comment_has_events.task_id,
tasks.title as task_title,
users.username as author_username,
users.name as author_name
FROM comment_has_events
LEFT JOIN users ON users.id=comment_has_events.creator_id
LEFT JOIN tasks ON tasks.id=comment_has_events.task_id
WHERE comment_has_events.project_id = ?
ORDER BY comment_has_events.id DESC
LIMIT '.$limit.' OFFSET 0
';
$rq = $this->db->execute($sql, array($project_id));
$events = $rq->fetchAll(PDO::FETCH_ASSOC);
foreach ($events as &$event) {
$event['author'] = $event['author_name'] ?: $event['author_username'];
$event['event_title'] = $this->getTitle($event);
$event['event_content'] = $this->getContent($event);
$event['event_type'] = 'comment';
}
return $events;
}
/**
* Get the event title (translated)
*
* @access public
* @param array $event Event properties
* @return string
*/
public function getTitle(array $event)
{
$titles = array(
Comment::EVENT_UPDATE => t('%s updated a comment on the task #%d', $event['author'], $event['task_id']),
Comment::EVENT_CREATE => t('%s commented on the task #%d', $event['author'], $event['task_id']),
);
return isset($titles[$event['event_name']]) ? $titles[$event['event_name']] : '';
}
/**
* Attach events to be able to record the history
*
* @access public
*/
public function attachEvents()
{
$events = array(
Comment::EVENT_UPDATE,
Comment::EVENT_CREATE,
);
$listener = new CommentHistoryListener($this);
foreach ($events as $event_name) {
$this->event->attach($event_name, $listener);
}
}
}

View file

@ -6,6 +6,7 @@ use SimpleValidator\Validator;
use SimpleValidator\Validators;
use Core\Translator;
use Core\Security;
use Core\Session;
/**
* Config model
@ -20,7 +21,7 @@ class Config extends Base
*
* @var string
*/
const TABLE = 'config';
const TABLE = 'settings';
/**
* Get available timezones
@ -44,6 +45,7 @@ class Config extends Base
{
// Sorted by value
return array(
'da_DK' => 'Dansk',
'de_DE' => 'Deutsch',
'en_US' => 'English',
'es_ES' => 'Español',
@ -55,6 +57,8 @@ class Config extends Base
'fi_FI' => 'Suomi',
'sv_SE' => 'Svenska',
'zh_CN' => '中文(简体)',
'ja_JP' => '日本語',
'th_TH' => 'ไทย',
);
}
@ -68,6 +72,11 @@ class Config extends Base
*/
public function get($name, $default_value = '')
{
if (! Session::isOpen()) {
$value = $this->db->table(self::TABLE)->eq('option', $name)->findOneColumn('value');
return $value ?: $default_value;
}
if (! isset($_SESSION['config'][$name])) {
$_SESSION['config'] = $this->getAll();
}
@ -87,7 +96,7 @@ class Config extends Base
*/
public function getAll()
{
return $this->db->table(self::TABLE)->findOne();
return $this->db->table(self::TABLE)->listing('option', 'value');
}
/**
@ -99,8 +108,16 @@ class Config extends Base
*/
public function save(array $values)
{
$_SESSION['config'] = $values;
return $this->db->table(self::TABLE)->update($values);
foreach ($values as $option => $value) {
$result = $this->db->table(self::TABLE)->eq('option', $option)->update(array('value' => $value));
if (! $result) {
return false;
}
}
return true;
}
/**
@ -111,27 +128,31 @@ class Config extends Base
public function reload()
{
$_SESSION['config'] = $this->getAll();
Translator::load($this->get('language', 'en_US'));
$this->setupTranslations();
}
/**
* Validate settings modification
* Load translations
*
* @access public
* @param array $values Form values
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
*/
public function validateModification(array $values)
public function setupTranslations()
{
$v = new Validator($values, array(
new Validators\Required('language', t('The language is required')),
new Validators\Required('timezone', t('The timezone is required')),
));
$language = $this->get('application_language', 'en_US');
return array(
$v->execute(),
$v->getErrors()
);
if ($language !== 'en_US') {
Translator::load($language);
}
}
/**
* Set timezone
*
* @access public
*/
public function setupTimezone()
{
date_default_timezone_set($this->get('application_timezone', 'UTC'));
}
/**
@ -168,21 +189,15 @@ class Config extends Base
}
/**
* Regenerate all tokens (projects and webhooks)
* Regenerate a token
*
* @access public
* @param string $option Parameter name
*/
public function regenerateTokens()
public function regenerateToken($option)
{
$this->db->table(self::TABLE)->update(array(
'webhooks_token' => Security::generateToken(),
'api_token' => Security::generateToken(),
));
$projects = $this->db->table(Project::TABLE)->findAllByColumn('id');
foreach ($projects as $project_id) {
$this->db->table(Project::TABLE)->eq('id', $project_id)->update(array('token' => Security::generateToken()));
}
return $this->db->table(self::TABLE)
->eq('option', $option)
->update(array('value' => Security::generateToken()));
}
}

View file

@ -0,0 +1,142 @@
<?php
namespace Model;
use DateTime;
/**
* Date parser model
*
* @package model
* @author Frederic Guillot
*/
class DateParser extends Base
{
/**
* Return a timestamp if the given date format is correct otherwise return 0
*
* @access public
* @param string $value Date to parse
* @param string $format Date format
* @return integer
*/
public function getValidDate($value, $format)
{
$date = DateTime::createFromFormat($format, $value);
if ($date !== false) {
$errors = DateTime::getLastErrors();
if ($errors['error_count'] === 0 && $errors['warning_count'] === 0) {
$timestamp = $date->getTimestamp();
return $timestamp > 0 ? $timestamp : 0;
}
}
return 0;
}
/**
* Parse a date ad return a unix timestamp, try different date formats
*
* @access public
* @param string $value Date to parse
* @return integer
*/
public function getTimestamp($value)
{
foreach ($this->getDateFormats() as $format) {
$timestamp = $this->getValidDate($value, $format);
if ($timestamp !== 0) {
return $timestamp;
}
}
return 0;
}
/**
* Return the list of supported date formats (for the parser)
*
* @access public
* @return array
*/
public function getDateFormats()
{
return array(
$this->config->get('application_date_format', 'm/d/Y'),
'Y-m-d',
'Y_m_d',
);
}
/**
* Return the list of available date formats (for the config page)
*
* @access public
* @return array
*/
public function getAvailableFormats()
{
return array(
'm/d/Y' => date('m/d/Y'),
'd/m/Y' => date('d/m/Y'),
'Y/m/d' => date('Y/m/d'),
);
}
/**
* For a given timestamp, reset the date to midnight
*
* @access public
* @param integer $timestamp Timestamp
* @return integer
*/
public function resetDateToMidnight($timestamp)
{
return mktime(0, 0, 0, date('m', $timestamp), date('d', $timestamp), date('Y', $timestamp));
}
/**
* Format date (form display)
*
* @access public
* @param array $values Database values
* @param array $fields Date fields
* @param string $format Date format
*/
public function format(array &$values, array $fields, $format = '')
{
if ($format === '') {
$format = $this->config->get('application_date_format');
}
foreach ($fields as $field) {
if (! empty($values[$field])) {
$values[$field] = date($format, $values[$field]);
}
else {
$values[$field] = '';
}
}
}
/**
* Convert date (form input data)
*
* @access public
* @param array $values Database values
* @param array $fields Date fields
*/
public function convert(array &$values, array $fields)
{
foreach ($fields as $field) {
if (! empty($values[$field]) && ! is_numeric($values[$field])) {
$values[$field] = $this->getTimestamp($values[$field]);
}
}
}
}

View file

@ -0,0 +1,339 @@
<?php
namespace Model;
/**
* Github Webhook model
*
* @package model
* @author Frederic Guillot
*/
class GithubWebhook extends Base
{
/**
* Events
*
* @var string
*/
const EVENT_ISSUE_OPENED = 'github.webhook.issue.opened';
const EVENT_ISSUE_CLOSED = 'github.webhook.issue.closed';
const EVENT_ISSUE_REOPENED = 'github.webhook.issue.reopened';
const EVENT_ISSUE_ASSIGNEE_CHANGE = 'github.webhook.issue.assignee';
const EVENT_ISSUE_LABEL_CHANGE = 'github.webhook.issue.label';
const EVENT_ISSUE_COMMENT = 'github.webhook.issue.commented';
const EVENT_COMMIT = 'github.webhook.commit';
/**
* Project id
*
* @access private
* @var integer
*/
private $project_id = 0;
/**
* Set the project id
*
* @access public
* @param integer $project_id Project id
*/
public function setProjectId($project_id)
{
$this->project_id = $project_id;
}
/**
* Parse Github events
*
* @access public
* @param string $type Github event type
* @param string $payload Raw Github event (JSON)
* @return boolean
*/
public function parsePayload($type, $payload)
{
$payload = json_decode($payload, true);
switch ($type) {
case 'push':
return $this->parsePushEvent($payload);
case 'issues':
return $this->parseIssueEvent($payload);
case 'issue_comment':
return $this->parseCommentIssueEvent($payload);
}
return false;
}
/**
* Parse Push events (list of commits)
*
* @access public
* @param array $payload Event data
* @return boolean
*/
public function parsePushEvent(array $payload)
{
foreach ($payload['commits'] as $commit) {
$task_id = $this->task->getTaskIdFromText($commit['message']);
if (! $task_id) {
continue;
}
$task = $this->taskFinder->getById($task_id);
if (! $task) {
continue;
}
if ($task['is_active'] == Task::STATUS_OPEN) {
$this->event->trigger(self::EVENT_COMMIT, array('task_id' => $task_id) + $task);
}
}
return true;
}
/**
* Parse issue events
*
* @access public
* @param array $payload Event data
* @return boolean
*/
public function parseIssueEvent(array $payload)
{
switch ($payload['action']) {
case 'opened':
return $this->handleIssueOpened($payload['issue']);
case 'closed':
return $this->handleIssueClosed($payload['issue']);
case 'reopened':
return $this->handleIssueReopened($payload['issue']);
case 'assigned':
return $this->handleIssueAssigned($payload['issue']);
case 'unassigned':
return $this->handleIssueUnassigned($payload['issue']);
case 'labeled':
return $this->handleIssueLabeled($payload['issue'], $payload['label']);
case 'unlabeled':
return $this->handleIssueUnlabeled($payload['issue'], $payload['label']);
}
return false;
}
/**
* Parse comment issue events
*
* @access public
* @param array $payload Event data
* @return boolean
*/
public function parseCommentIssueEvent(array $payload)
{
$task = $this->taskFinder->getByReference($payload['issue']['number']);
$user = $this->user->getByUsername($payload['comment']['user']['login']);
if ($task && $user) {
$event = array(
'project_id' => $this->project_id,
'reference' => $payload['comment']['id'],
'comment' => $payload['comment']['body'],
'user_id' => $user['id'],
'task_id' => $task['id'],
);
$this->event->trigger(self::EVENT_ISSUE_COMMENT, $event);
return true;
}
return false;
}
/**
* Handle new issues
*
* @access public
* @param array $issue Issue data
* @return boolean
*/
public function handleIssueOpened(array $issue)
{
$event = array(
'project_id' => $this->project_id,
'reference' => $issue['number'],
'title' => $issue['title'],
'description' => $issue['body']."\n\n[".t('Github Issue').']('.$issue['html_url'].')',
);
$this->event->trigger(self::EVENT_ISSUE_OPENED, $event);
return true;
}
/**
* Handle issue closing
*
* @access public
* @param array $issue Issue data
* @return boolean
*/
public function handleIssueClosed(array $issue)
{
$task = $this->taskFinder->getByReference($issue['number']);
if ($task) {
$event = array(
'project_id' => $this->project_id,
'task_id' => $task['id'],
'reference' => $issue['number'],
);
$this->event->trigger(self::EVENT_ISSUE_CLOSED, $event);
return true;
}
return false;
}
/**
* Handle issue reopened
*
* @access public
* @param array $issue Issue data
* @return boolean
*/
public function handleIssueReopened(array $issue)
{
$task = $this->taskFinder->getByReference($issue['number']);
if ($task) {
$event = array(
'project_id' => $this->project_id,
'task_id' => $task['id'],
'reference' => $issue['number'],
);
$this->event->trigger(self::EVENT_ISSUE_REOPENED, $event);
return true;
}
return false;
}
/**
* Handle issue assignee change
*
* @access public
* @param array $issue Issue data
* @return boolean
*/
public function handleIssueAssigned(array $issue)
{
$user = $this->user->getByUsername($issue['assignee']['login']);
$task = $this->taskFinder->getByReference($issue['number']);
if ($user && $task) {
$event = array(
'project_id' => $this->project_id,
'task_id' => $task['id'],
'owner_id' => $user['id'],
'reference' => $issue['number'],
);
$this->event->trigger(self::EVENT_ISSUE_ASSIGNEE_CHANGE, $event);
return true;
}
return false;
}
/**
* Handle unassigned issue
*
* @access public
* @param array $issue Issue data
* @return boolean
*/
public function handleIssueUnassigned(array $issue)
{
$task = $this->taskFinder->getByReference($issue['number']);
if ($task) {
$event = array(
'project_id' => $this->project_id,
'task_id' => $task['id'],
'owner_id' => 0,
'reference' => $issue['number'],
);
$this->event->trigger(self::EVENT_ISSUE_ASSIGNEE_CHANGE, $event);
return true;
}
return false;
}
/**
* Handle labeled issue
*
* @access public
* @param array $issue Issue data
* @param array $label Label data
* @return boolean
*/
public function handleIssueLabeled(array $issue, array $label)
{
$task = $this->taskFinder->getByReference($issue['number']);
if ($task) {
$event = array(
'project_id' => $this->project_id,
'task_id' => $task['id'],
'reference' => $issue['number'],
'label' => $label['name'],
);
$this->event->trigger(self::EVENT_ISSUE_LABEL_CHANGE, $event);
return true;
}
return false;
}
/**
* Handle unlabeled issue
*
* @access public
* @param array $issue Issue data
* @param array $label Label data
* @return boolean
*/
public function handleIssueUnlabeled(array $issue, array $label)
{
$task = $this->taskFinder->getByReference($issue['number']);
if ($task) {
$event = array(
'project_id' => $this->project_id,
'task_id' => $task['id'],
'reference' => $issue['number'],
'label' => $label['name'],
'category_id' => 0,
);
$this->event->trigger(self::EVENT_ISSUE_LABEL_CHANGE, $event);
return true;
}
return false;
}
}

View file

@ -2,14 +2,13 @@
namespace Model;
use Core\Session;
use Core\Translator;
use Core\Template;
use Event\TaskNotificationListener;
use Event\CommentNotificationListener;
use Event\FileNotificationListener;
use Event\SubTaskNotificationListener;
use Event\NotificationListener;
use Swift_Message;
use Swift_Mailer;
use Swift_TransportException;
/**
* Notification model
@ -26,20 +25,54 @@ class Notification extends Base
*/
const TABLE = 'user_has_notifications';
/**
* Get a list of people with notifications enabled
*
* @access public
* @param integer $project_id Project id
* @param array $exlude_users List of user_id to exclude
* @return array
*/
public function getUsersWithNotification($project_id, array $exclude_users = array())
{
if ($this->projectPermission->isEverybodyAllowed($project_id)) {
return $this->db
->table(User::TABLE)
->columns(User::TABLE.'.id', User::TABLE.'.username', User::TABLE.'.name', User::TABLE.'.email')
->eq('notifications_enabled', '1')
->neq('email', '')
->notin(User::TABLE.'.id', $exclude_users)
->findAll();
}
return $this->db
->table(ProjectPermission::TABLE)
->columns(User::TABLE.'.id', User::TABLE.'.username', User::TABLE.'.name', User::TABLE.'.email')
->join(User::TABLE, 'id', 'user_id')
->eq('project_id', $project_id)
->eq('notifications_enabled', '1')
->neq('email', '')
->notin(User::TABLE.'.id', $exclude_users)
->findAll();
}
/**
* Get the list of users to send the notification for a given project
*
* @access public
* @param integer $project_id Project id
* @param integer $project_id Project id
* @param array $exlude_users List of user_id to exclude
* @return array
*/
public function getUsersList($project_id)
public function getUsersList($project_id, array $exclude_users = array())
{
$users = $this->db->table(User::TABLE)
->columns('id', 'username', 'name', 'email')
->eq('notifications_enabled', '1')
->neq('email', '')
->findAll();
// Exclude the connected user
if (Session::isOpen()) {
$exclude_users[] = $this->acl->getUserId();
}
$users = $this->getUsersWithNotification($project_id, $exclude_users);
foreach ($users as $index => $user) {
@ -67,21 +100,28 @@ class Notification extends Base
*/
public function attachEvents()
{
$this->event->attach(File::EVENT_CREATE, new FileNotificationListener($this, 'notification_file_creation'));
$events = array(
Task::EVENT_CREATE => 'notification_task_creation',
Task::EVENT_UPDATE => 'notification_task_update',
Task::EVENT_CLOSE => 'notification_task_close',
Task::EVENT_OPEN => 'notification_task_open',
Task::EVENT_MOVE_COLUMN => 'notification_task_move_column',
Task::EVENT_MOVE_POSITION => 'notification_task_move_position',
Task::EVENT_ASSIGNEE_CHANGE => 'notification_task_assignee_change',
SubTask::EVENT_CREATE => 'notification_subtask_creation',
SubTask::EVENT_UPDATE => 'notification_subtask_update',
Comment::EVENT_CREATE => 'notification_comment_creation',
Comment::EVENT_UPDATE => 'notification_comment_update',
File::EVENT_CREATE => 'notification_file_creation',
);
$this->event->attach(Comment::EVENT_CREATE, new CommentNotificationListener($this, 'notification_comment_creation'));
$this->event->attach(Comment::EVENT_UPDATE, new CommentNotificationListener($this, 'notification_comment_update'));
foreach ($events as $event_name => $template_name) {
$this->event->attach(SubTask::EVENT_CREATE, new SubTaskNotificationListener($this, 'notification_subtask_creation'));
$this->event->attach(SubTask::EVENT_UPDATE, new SubTaskNotificationListener($this, 'notification_subtask_update'));
$listener = new NotificationListener($this->registry);
$listener->setTemplate($template_name);
$this->event->attach(Task::EVENT_CREATE, new TaskNotificationListener($this, 'notification_task_creation'));
$this->event->attach(Task::EVENT_UPDATE, new TaskNotificationListener($this, 'notification_task_update'));
$this->event->attach(Task::EVENT_CLOSE, new TaskNotificationListener($this, 'notification_task_close'));
$this->event->attach(Task::EVENT_OPEN, new TaskNotificationListener($this, 'notification_task_open'));
$this->event->attach(Task::EVENT_MOVE_COLUMN, new TaskNotificationListener($this, 'notification_task_move_column'));
$this->event->attach(Task::EVENT_MOVE_POSITION, new TaskNotificationListener($this, 'notification_task_move_position'));
$this->event->attach(Task::EVENT_ASSIGNEE_CHANGE, new TaskNotificationListener($this, 'notification_task_assignee_change'));
$this->event->attach($event_name, $listener);
}
}
/**
@ -94,17 +134,22 @@ class Notification extends Base
*/
public function sendEmails($template, array $users, array $data)
{
$transport = $this->registry->shared('mailer');
$mailer = Swift_Mailer::newInstance($transport);
try {
$transport = $this->registry->shared('mailer');
$mailer = Swift_Mailer::newInstance($transport);
$message = Swift_Message::newInstance()
->setSubject($this->getMailSubject($template, $data))
->setFrom(array(MAIL_FROM => 'Kanboard'))
->setBody($this->getMailContent($template, $data), 'text/html');
$message = Swift_Message::newInstance()
->setSubject($this->getMailSubject($template, $data))
->setFrom(array(MAIL_FROM => 'Kanboard'))
->setBody($this->getMailContent($template, $data), 'text/html');
foreach ($users as $user) {
$message->setTo(array($user['email'] => $user['name'] ?: $user['username']));
$mailer->send($message);
foreach ($users as $user) {
$message->setTo(array($user['email'] => $user['name'] ?: $user['username']));
$mailer->send($message);
}
}
catch (Swift_TransportException $e) {
debug($e->getMessage());
}
}
@ -174,7 +219,7 @@ class Notification extends Base
public function getMailContent($template, array $data)
{
$tpl = new Template;
return $tpl->load($template, $data);
return $tpl->load($template, $data + array('application_url' => $this->config->get('application_url')));
}
/**

View file

@ -4,7 +4,7 @@ namespace Model;
use SimpleValidator\Validator;
use SimpleValidator\Validators;
use Event\ProjectModificationDate;
use Event\ProjectModificationDateListener;
use Core\Security;
/**
@ -22,13 +22,6 @@ class Project extends Base
*/
const TABLE = 'projects';
/**
* SQL table name for users
*
* @var string
*/
const TABLE_USERS = 'project_has_users';
/**
* Value for active project
*
@ -43,157 +36,6 @@ class Project extends Base
*/
const INACTIVE = 0;
/**
* Get a list of people that can be assigned for tasks
*
* @access public
* @param integer $project_id Project id
* @param bool $prepend_unassigned Prepend the 'Unassigned' value
* @param bool $prepend_everybody Prepend the 'Everbody' value
* @return array
*/
public function getUsersList($project_id, $prepend_unassigned = true, $prepend_everybody = false)
{
$allowed_users = $this->getAllowedUsers($project_id);
if (empty($allowed_users)) {
$allowed_users = $this->user->getList();
}
if ($prepend_unassigned) {
$allowed_users = array(t('Unassigned')) + $allowed_users;
}
if ($prepend_everybody) {
$allowed_users = array(User::EVERYBODY_ID => t('Everybody')) + $allowed_users;
}
return $allowed_users;
}
/**
* Get a list of allowed people for a project
*
* @access public
* @param integer $project_id Project id
* @return array
*/
public function getAllowedUsers($project_id)
{
$users = $this->db
->table(self::TABLE_USERS)
->join(User::TABLE, 'id', 'user_id')
->eq('project_id', $project_id)
->asc('username')
->columns(User::TABLE.'.id', User::TABLE.'.username', User::TABLE.'.name')
->findAll();
$result = array();
foreach ($users as $user) {
$result[$user['id']] = $user['name'] ?: $user['username'];
}
asort($result);
return $result;
}
/**
* Get allowed and not allowed users for a project
*
* @access public
* @param integer $project_id Project id
* @return array
*/
public function getAllUsers($project_id)
{
$users = array(
'allowed' => array(),
'not_allowed' => array(),
);
$all_users = $this->user->getList();
$users['allowed'] = $this->getAllowedUsers($project_id);
foreach ($all_users as $user_id => $username) {
if (! isset($users['allowed'][$user_id])) {
$users['not_allowed'][$user_id] = $username;
}
}
return $users;
}
/**
* Allow a specific user for a given project
*
* @access public
* @param integer $project_id Project id
* @param integer $user_id User id
* @return bool
*/
public function allowUser($project_id, $user_id)
{
return $this->db
->table(self::TABLE_USERS)
->save(array('project_id' => $project_id, 'user_id' => $user_id));
}
/**
* Revoke a specific user for a given project
*
* @access public
* @param integer $project_id Project id
* @param integer $user_id User id
* @return bool
*/
public function revokeUser($project_id, $user_id)
{
return $this->db
->table(self::TABLE_USERS)
->eq('project_id', $project_id)
->eq('user_id', $user_id)
->remove();
}
/**
* Check if a specific user is allowed to access to a given project
*
* @access public
* @param integer $project_id Project id
* @param integer $user_id User id
* @return bool
*/
public function isUserAllowed($project_id, $user_id)
{
// If there is nobody specified, everybody have access to the project
$nb_users = $this->db
->table(self::TABLE_USERS)
->eq('project_id', $project_id)
->count();
if ($nb_users < 1) return true;
// Check if user has admin rights
$nb_users = $this->db
->table(User::TABLE)
->eq('id', $user_id)
->eq('is_admin', 1)
->count();
if ($nb_users > 0) return true;
// Otherwise, allow only specific users
return (bool) $this->db
->table(self::TABLE_USERS)
->eq('project_id', $project_id)
->eq('user_id', $user_id)
->count();
}
/**
* Get a project by the id
*
@ -241,6 +83,18 @@ class Project extends Base
return $this->db->table(self::TABLE)->findOne();
}
/**
* Return true if the project is private
*
* @access public
* @param integer $project_id Project id
* @return boolean
*/
public function isPrivate($project_id)
{
return (bool) $this->db->table(self::TABLE)->eq('id', $project_id)->eq('is_private', 1)->count();
}
/**
* Get all projects, optionaly fetch stats for each project and can check users permissions
*
@ -256,7 +110,7 @@ class Project extends Base
foreach ($projects as $key => $project) {
if (! $this->isUserAllowed($project['id'], $this->acl->getUserId())) {
if (! $this->projectPermission->isUserAllowed($project['id'], $this->acl->getUserId())) {
unset($projects[$key]);
}
}
@ -328,37 +182,6 @@ class Project extends Base
->count();
}
/**
* Filter a list of projects for a given user
*
* @access public
* @param array $projects Project list: ['project_id' => 'project_name']
* @param integer $user_id User id
* @return array
*/
public function filterListByAccess(array $projects, $user_id)
{
foreach ($projects as $project_id => $project_name) {
if (! $this->isUserAllowed($project_id, $user_id)) {
unset($projects[$project_id]);
}
}
return $projects;
}
/**
* Return a list of projects for a given user
*
* @access public
* @param integer $user_id User id
* @return array
*/
public function getAvailableList($user_id)
{
return $this->filterListByAccess($this->getListByStatus(self::ACTIVE), $user_id);
}
/**
* Gather some task metrics for a given project
*
@ -373,12 +196,12 @@ class Project extends Base
$stats['nb_active_tasks'] = 0;
foreach ($columns as &$column) {
$column['nb_active_tasks'] = $this->task->countByColumnId($project_id, $column['id']);
$column['nb_active_tasks'] = $this->taskFinder->countByColumnId($project_id, $column['id']);
$stats['nb_active_tasks'] += $column['nb_active_tasks'];
}
$stats['columns'] = $columns;
$stats['nb_tasks'] = $this->task->countByProjectId($project_id);
$stats['nb_tasks'] = $this->taskFinder->countByProjectId($project_id);
$stats['nb_inactive_tasks'] = $stats['nb_tasks'] - $stats['nb_active_tasks'];
return $stats;
@ -393,43 +216,24 @@ class Project extends Base
*/
public function createProjectFromAnotherProject($project_id)
{
$project_name = $this->db->table(self::TABLE)->eq('id', $project_id)->findOneColumn('name');
$project = $this->getById($project_id);
$project = array(
'name' => $project_name.' ('.t('Clone').')',
$values = array(
'name' => $project['name'].' ('.t('Clone').')',
'is_active' => true,
'last_modified' => 0,
'token' => '',
'is_public' => 0,
'is_private' => empty($project['is_private']) ? 0 : 1,
);
if (! $this->db->table(self::TABLE)->save($project)) {
if (! $this->db->table(self::TABLE)->save($values)) {
return false;
}
return $this->db->getConnection()->getLastId();
}
/**
* Copy user access from a project to another one
*
* @author Antonio Rabelo
* @param integer $project_from Project Template
* @return integer $project_to Project that receives the copy
* @return boolean
*/
public function duplicateUsers($project_from, $project_to)
{
$users = $this->getAllowedUsers($project_from);
foreach ($users as $user_id => $name) {
if (! $this->allowUser($project_to, $user_id)) {
return false;
}
}
return true;
}
/**
* Clone a project
*
@ -443,33 +247,18 @@ class Project extends Base
// Get the cloned project Id
$clone_project_id = $this->createProjectFromAnotherProject($project_id);
if (! $clone_project_id) {
$this->db->cancelTransaction();
return false;
}
// Clone Board
if (! $this->board->duplicate($project_id, $clone_project_id)) {
$this->db->cancelTransaction();
return false;
}
foreach (array('board', 'category', 'projectPermission', 'action') as $model) {
// Clone Categories
if (! $this->category->duplicate($project_id, $clone_project_id)) {
$this->db->cancelTransaction();
return false;
}
// Clone Allowed Users
if (! $this->duplicateUsers($project_id, $clone_project_id)) {
$this->db->cancelTransaction();
return false;
}
// Clone Actions
if (! $this->action->duplicate($project_id, $clone_project_id)) {
$this->db->cancelTransaction();
return false;
if (! $this->$model->duplicate($project_id, $clone_project_id)) {
$this->db->cancelTransaction();
return false;
}
}
$this->db->closeTransaction();
@ -482,14 +271,16 @@ class Project extends Base
*
* @access public
* @param array $values Form values
* @param integer $user_id User who create the project
* @return integer Project id
*/
public function create(array $values)
public function create(array $values, $user_id = 0)
{
$this->db->startTransaction();
$values['token'] = '';
$values['last_modified'] = time();
$values['is_private'] = empty($values['is_private']) ? 0 : 1;
if (! $this->db->table(self::TABLE)->save($values)) {
$this->db->cancelTransaction();
@ -497,19 +288,16 @@ class Project extends Base
}
$project_id = $this->db->getConnection()->getLastId();
$column_names = explode(',', $this->config->get('default_columns', implode(',', $this->board->getDefaultColumns())));
$columns = array();
foreach ($column_names as $column_name) {
$column_name = trim($column_name);
if (! empty($column_name)) {
$columns[] = array('title' => $column_name, 'task_limit' => 0);
}
if (! $this->board->create($project_id, $this->board->getUserColumns())) {
$this->db->cancelTransaction();
return false;
}
if ($values['is_private'] && $user_id) {
$this->projectPermission->allowUser($project_id, $user_id);
}
$this->board->create($project_id, $columns);
$this->db->closeTransaction();
return (int) $project_id;
@ -701,28 +489,6 @@ class Project extends Base
);
}
/**
* Validate allowed users
*
* @access public
* @param array $values Form values
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
*/
public function validateUserAccess(array $values)
{
$v = new Validator($values, array(
new Validators\Required('project_id', t('The project id is required')),
new Validators\Integer('project_id', t('This value must be an integer')),
new Validators\Required('user_id', t('The user id is required')),
new Validators\Integer('user_id', t('This value must be an integer')),
));
return array(
$v->execute(),
$v->getErrors()
);
}
/**
* Attach events
*
@ -737,43 +503,19 @@ class Project extends Base
Task::EVENT_MOVE_COLUMN,
Task::EVENT_MOVE_POSITION,
Task::EVENT_ASSIGNEE_CHANGE,
GithubWebhook::EVENT_ISSUE_OPENED,
GithubWebhook::EVENT_ISSUE_CLOSED,
GithubWebhook::EVENT_ISSUE_REOPENED,
GithubWebhook::EVENT_ISSUE_ASSIGNEE_CHANGE,
GithubWebhook::EVENT_ISSUE_LABEL_CHANGE,
GithubWebhook::EVENT_ISSUE_COMMENT,
GithubWebhook::EVENT_COMMIT,
);
$listener = new ProjectModificationDate($this);
$listener = new ProjectModificationDateListener($this->registry);
foreach ($events as $event_name) {
$this->event->attach($event_name, $listener);
}
}
/**
* Get project activity
*
* @access public
* @param integer $project_id Project id
* @return array
*/
public function getActivity($project_id)
{
$activity = array();
$tasks = $this->taskHistory->getAllContentByProjectId($project_id, 25);
$comments = $this->commentHistory->getAllContentByProjectId($project_id, 25);
$subtasks = $this->subtaskHistory->getAllContentByProjectId($project_id, 25);
foreach ($tasks as &$task) {
$activity[$task['date_creation'].'-'.$task['id']] = $task;
}
foreach ($subtasks as &$subtask) {
$activity[$subtask['date_creation'].'-'.$subtask['id']] = $subtask;
}
foreach ($comments as &$comment) {
$activity[$comment['date_creation'].'-'.$comment['id']] = $comment;
}
krsort($activity);
return $activity;
}
}

View file

@ -0,0 +1,206 @@
<?php
namespace Model;
use Core\Template;
use Event\ProjectActivityListener;
/**
* Project activity model
*
* @package model
* @author Frederic Guillot
*/
class ProjectActivity extends Base
{
/**
* SQL table name
*
* @var string
*/
const TABLE = 'project_activities';
/**
* Maximum number of events
*
* @var integer
*/
const MAX_EVENTS = 5000;
/**
* Add a new event for the project
*
* @access public
* @param integer $project_id Project id
* @param integer $task_id Task id
* @param integer $creator_id User id
* @param string $event_name Event name
* @param array $data Event data (will be serialized)
* @return boolean
*/
public function createEvent($project_id, $task_id, $creator_id, $event_name, array $data)
{
$values = array(
'project_id' => $project_id,
'task_id' => $task_id,
'creator_id' => $creator_id,
'event_name' => $event_name,
'date_creation' => time(),
'data' => serialize($data),
);
$this->cleanup(self::MAX_EVENTS - 1);
return $this->db->table(self::TABLE)->insert($values);
}
/**
* Get all events for the given project
*
* @access public
* @param integer $project_id Project id
* @param integer $limit Maximum events number
* @return array
*/
public function getProject($project_id, $limit = 50)
{
return $this->getProjects(array($project_id), $limit);
}
/**
* Get all events for the given projects list
*
* @access public
* @param integer $project_id Project id
* @param integer $limit Maximum events number
* @return array
*/
public function getProjects(array $projects, $limit = 50)
{
if (empty($projects)) {
return array();
}
$events = $this->db->table(self::TABLE)
->columns(
self::TABLE.'.*',
User::TABLE.'.username AS author_username',
User::TABLE.'.name AS author_name'
)
->in('project_id', $projects)
->join(User::TABLE, 'id', 'creator_id')
->desc('id')
->limit($limit)
->findAll();
foreach ($events as &$event) {
$event += unserialize($event['data']);
unset($event['data']);
$event['author'] = $event['author_name'] ?: $event['author_username'];
$event['event_title'] = $this->getTitle($event);
$event['event_content'] = $this->getContent($event);
}
return $events;
}
/**
* Remove old event entries to avoid large table
*
* @access public
* @param integer $max Maximum number of items to keep in the table
*/
public function cleanup($max)
{
if ($this->db->table(self::TABLE)->count() > $max) {
$this->db->execute('
DELETE FROM '.self::TABLE.'
WHERE id <= (
SELECT id FROM (
SELECT id FROM '.self::TABLE.' ORDER BY id DESC LIMIT 1 OFFSET '.$max.'
) foo
)'
);
}
}
/**
* Attach events to be able to record the history
*
* @access public
*/
public function attachEvents()
{
$events = array(
Task::EVENT_ASSIGNEE_CHANGE,
Task::EVENT_UPDATE,
Task::EVENT_CREATE,
Task::EVENT_CLOSE,
Task::EVENT_OPEN,
Task::EVENT_MOVE_COLUMN,
Task::EVENT_MOVE_POSITION,
Comment::EVENT_UPDATE,
Comment::EVENT_CREATE,
SubTask::EVENT_UPDATE,
SubTask::EVENT_CREATE,
);
$listener = new ProjectActivityListener($this->registry);
foreach ($events as $event_name) {
$this->event->attach($event_name, $listener);
}
}
/**
* Get the event html content
*
* @access public
* @param array $params Event properties
* @return string
*/
public function getContent(array $params)
{
$tpl = new Template;
return $tpl->load('event_'.str_replace('.', '_', $params['event_name']), $params);
}
/**
* Get the event title (translated)
*
* @access public
* @param array $event Event properties
* @return string
*/
public function getTitle(array $event)
{
switch ($event['event_name']) {
case Task::EVENT_ASSIGNEE_CHANGE:
return t('%s change the assignee of the task #%d to %s', $event['author'], $event['task']['id'], $event['task']['assignee_name'] ?: $event['task']['assignee_username']);
case Task::EVENT_UPDATE:
return t('%s updated the task #%d', $event['author'], $event['task']['id']);
case Task::EVENT_CREATE:
return t('%s created the task #%d', $event['author'], $event['task']['id']);
case Task::EVENT_CLOSE:
return t('%s closed the task #%d', $event['author'], $event['task']['id']);
case Task::EVENT_OPEN:
return t('%s open the task #%d', $event['author'], $event['task']['id']);
case Task::EVENT_MOVE_COLUMN:
return t('%s moved the task #%d to the column "%s"', $event['author'], $event['task']['id'], $event['task']['column_title']);
case Task::EVENT_MOVE_POSITION:
return t('%s moved the task #%d to the position %d in the column "%s"', $event['author'], $event['task']['id'], $event['task']['position'], $event['task']['column_title']);
case SubTask::EVENT_UPDATE:
return t('%s updated a subtask for the task #%d', $event['author'], $event['task']['id']);
case SubTask::EVENT_CREATE:
return t('%s created a subtask for the task #%d', $event['author'], $event['task']['id']);
case Comment::EVENT_UPDATE:
return t('%s updated a comment on the task #%d', $event['author'], $event['task']['id']);
case Comment::EVENT_CREATE:
return t('%s commented on the task #%d', $event['author'], $event['task']['id']);
default:
return '';
}
}
}

View file

@ -0,0 +1,295 @@
<?php
namespace Model;
use SimpleValidator\Validator;
use SimpleValidator\Validators;
/**
* Project permission model
*
* @package model
* @author Frederic Guillot
*/
class ProjectPermission extends Base
{
/**
* SQL table name for permissions
*
* @var string
*/
const TABLE = 'project_has_users';
/**
* Get a list of people that can be assigned for tasks
*
* @access public
* @param integer $project_id Project id
* @param bool $prepend_unassigned Prepend the 'Unassigned' value
* @param bool $prepend_everybody Prepend the 'Everbody' value
* @return array
*/
public function getUsersList($project_id, $prepend_unassigned = true, $prepend_everybody = false)
{
$allowed_users = $this->getAllowedUsers($project_id);
if ($prepend_unassigned) {
$allowed_users = array(t('Unassigned')) + $allowed_users;
}
if ($prepend_everybody) {
$allowed_users = array(User::EVERYBODY_ID => t('Everybody')) + $allowed_users;
}
return $allowed_users;
}
/**
* Get a list of allowed people for a project
*
* @access public
* @param integer $project_id Project id
* @return array
*/
public function getAllowedUsers($project_id)
{
if ($this->isEverybodyAllowed($project_id)) {
return $this->user->getList();
}
return $this->getAssociatedUsers($project_id);
}
/**
* Get a list of people associated to the project
*
* @access public
* @param integer $project_id Project id
* @return array
*/
public function getAssociatedUsers($project_id)
{
$users = $this->db
->table(self::TABLE)
->join(User::TABLE, 'id', 'user_id')
->eq('project_id', $project_id)
->asc('username')
->columns(User::TABLE.'.id', User::TABLE.'.username', User::TABLE.'.name')
->findAll();
return $this->user->prepareList($users);
}
/**
* Get allowed and not allowed users for a project
*
* @access public
* @param integer $project_id Project id
* @return array
*/
public function getAllUsers($project_id)
{
$users = array(
'allowed' => array(),
'not_allowed' => array(),
);
$all_users = $this->user->getList();
$users['allowed'] = $this->getAllowedUsers($project_id);
foreach ($all_users as $user_id => $username) {
if (! isset($users['allowed'][$user_id])) {
$users['not_allowed'][$user_id] = $username;
}
}
return $users;
}
/**
* Allow a specific user for a given project
*
* @access public
* @param integer $project_id Project id
* @param integer $user_id User id
* @return bool
*/
public function allowUser($project_id, $user_id)
{
return $this->db
->table(self::TABLE)
->save(array('project_id' => $project_id, 'user_id' => $user_id));
}
/**
* Revoke a specific user for a given project
*
* @access public
* @param integer $project_id Project id
* @param integer $user_id User id
* @return bool
*/
public function revokeUser($project_id, $user_id)
{
return $this->db
->table(self::TABLE)
->eq('project_id', $project_id)
->eq('user_id', $user_id)
->remove();
}
/**
* Check if a specific user is allowed to access to a given project
*
* @access public
* @param integer $project_id Project id
* @param integer $user_id User id
* @return bool
*/
public function isUserAllowed($project_id, $user_id)
{
if ($this->user->isAdmin($user_id)) {
return true;
}
if ($this->isEverybodyAllowed($project_id)) {
return true;
}
return (bool) $this->db
->table(self::TABLE)
->eq('project_id', $project_id)
->eq('user_id', $user_id)
->count();
}
/**
* Return true if everybody is allowed for the project
*
* @access public
* @param integer $project_id Project id
* @return bool
*/
public function isEverybodyAllowed($project_id)
{
return (bool) $this->db
->table(Project::TABLE)
->eq('id', $project_id)
->eq('is_everybody_allowed', 1)
->count();
}
/**
* Check if a specific user is allowed to manage a project
*
* @access public
* @param integer $project_id Project id
* @param integer $user_id User id
* @return bool
*/
public function adminAllowed($project_id, $user_id)
{
if ($this->isUserAllowed($project_id, $user_id) && $this->project->isPrivate($project_id)) {
return true;
}
return false;
}
/**
* Filter a list of projects for a given user
*
* @access public
* @param array $projects Project list: ['project_id' => 'project_name']
* @param integer $user_id User id
* @return array
*/
public function filterProjects(array $projects, $user_id)
{
foreach ($projects as $project_id => $project_name) {
if (! $this->isUserAllowed($project_id, $user_id)) {
unset($projects[$project_id]);
}
}
return $projects;
}
/**
* Return a list of projects for a given user
*
* @access public
* @param integer $user_id User id
* @return array
*/
public function getAllowedProjects($user_id)
{
return $this->filterProjects($this->project->getListByStatus(Project::ACTIVE), $user_id);
}
/**
* Copy user access from a project to another one
*
* @author Antonio Rabelo
* @param integer $project_from Project Template
* @return integer $project_to Project that receives the copy
* @return boolean
*/
public function duplicate($project_from, $project_to)
{
$users = $this->getAllowedUsers($project_from);
foreach ($users as $user_id => $name) {
if (! $this->allowUser($project_to, $user_id)) {
return false;
}
}
return true;
}
/**
* Validate allow user
*
* @access public
* @param array $values Form values
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
*/
public function validateUserModification(array $values)
{
$v = new Validator($values, array(
new Validators\Required('project_id', t('The project id is required')),
new Validators\Integer('project_id', t('This value must be an integer')),
new Validators\Required('user_id', t('The user id is required')),
new Validators\Integer('user_id', t('This value must be an integer')),
));
return array(
$v->execute(),
$v->getErrors()
);
}
/**
* Validate allow everybody
*
* @access public
* @param array $values Form values
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
*/
public function validateProjectModification(array $values)
{
$v = new Validator($values, array(
new Validators\Required('id', t('The project id is required')),
new Validators\Integer('id', t('This value must be an integer')),
new Validators\Integer('is_everybody_allowed', t('This value must be an integer')),
));
return array(
$v->execute(),
$v->getErrors()
);
}
}

View file

@ -82,6 +82,7 @@ class SubTask extends Base
->eq('task_id', $task_id)
->columns(self::TABLE.'.*', User::TABLE.'.username', User::TABLE.'.name')
->join(User::TABLE, 'id', 'user_id')
->asc(self::TABLE.'.id')
->findAll();
foreach ($subtasks as &$subtask) {
@ -128,17 +129,8 @@ class SubTask extends Base
*/
public function prepare(array &$values)
{
if (isset($values['another_subtask'])) {
unset($values['another_subtask']);
}
if (isset($values['time_estimated']) && empty($values['time_estimated'])) {
$values['time_estimated'] = 0;
}
if (isset($values['time_spent']) && empty($values['time_spent'])) {
$values['time_spent'] = 0;
}
$this->removeFields($values, array('another_subtask'));
$this->resetFields($values, array('time_estimated', 'time_spent'));
}
/**

View file

@ -1,161 +0,0 @@
<?php
namespace Model;
use PDO;
use Core\Registry;
use Event\SubtaskHistoryListener;
/**
* Comment history model
*
* @package model
* @author Frederic Guillot
*/
class SubtaskHistory extends BaseHistory
{
/**
* SQL table name
*
* @var string
*/
const TABLE = 'subtask_has_events';
/**
* Maximum number of events
*
* @var integer
*/
const MAX_EVENTS = 5000;
/**
* Constructor
*
* @access public
* @param \Core\Registry $registry Registry instance
*/
public function __construct(Registry $registry)
{
parent::__construct($registry);
$this->table = self::TABLE;
}
/**
* Create a new event
*
* @access public
* @param integer $project_id Project id
* @param integer $task_id Task id
* @param integer $subtask_id Subtask id
* @param integer $creator_id Author of the event (user id)
* @param string $event_name Task event name
* @param string $data Current comment
* @return boolean
*/
public function create($project_id, $task_id, $subtask_id, $creator_id, $event_name, $data)
{
$values = array(
'project_id' => $project_id,
'task_id' => $task_id,
'subtask_id' => $subtask_id,
'creator_id' => $creator_id,
'event_name' => $event_name,
'date_creation' => time(),
'data' => $data,
);
$this->db->startTransaction();
$this->cleanup(self::MAX_EVENTS - 1);
$result = $this->db->table(self::TABLE)->insert($values);
$this->db->closeTransaction();
return $result;
}
/**
* Get all necessary content to display activity feed
*
* $author_name
* $author_username
* $task['id', 'title', 'position', 'column_name']
*/
public function getAllContentByProjectId($project_id, $limit = 50)
{
$sql = '
SELECT
subtask_has_events.id,
subtask_has_events.date_creation,
subtask_has_events.event_name,
subtask_has_events.task_id,
tasks.title as task_title,
users.username as author_username,
users.name as author_name,
assignees.name as subtask_assignee_name,
assignees.username as subtask_assignee_username,
task_has_subtasks.title as subtask_title,
task_has_subtasks.status as subtask_status,
task_has_subtasks.time_spent as subtask_time_spent,
task_has_subtasks.time_estimated as subtask_time_estimated
FROM subtask_has_events
LEFT JOIN users ON users.id=subtask_has_events.creator_id
LEFT JOIN tasks ON tasks.id=subtask_has_events.task_id
LEFT JOIN task_has_subtasks ON task_has_subtasks.id=subtask_has_events.subtask_id
LEFT JOIN users AS assignees ON assignees.id=task_has_subtasks.user_id
WHERE subtask_has_events.project_id = ?
ORDER BY subtask_has_events.id DESC
LIMIT '.$limit.' OFFSET 0
';
$rq = $this->db->execute($sql, array($project_id));
$events = $rq->fetchAll(PDO::FETCH_ASSOC);
foreach ($events as &$event) {
$event['author'] = $event['author_name'] ?: $event['author_username'];
$event['subtask_assignee'] = $event['subtask_assignee_name'] ?: $event['subtask_assignee_username'];
$event['subtask_status_list'] = $this->subTask->getStatusList();
$event['event_title'] = $this->getTitle($event);
$event['event_content'] = $this->getContent($event);
$event['event_type'] = 'subtask';
}
return $events;
}
/**
* Get the event title (translated)
*
* @access public
* @param array $event Event properties
* @return string
*/
public function getTitle(array $event)
{
$titles = array(
SubTask::EVENT_UPDATE => t('%s updated a subtask for the task #%d', $event['author'], $event['task_id']),
SubTask::EVENT_CREATE => t('%s created a subtask for the task #%d', $event['author'], $event['task_id']),
);
return isset($titles[$event['event_name']]) ? $titles[$event['event_name']] : '';
}
/**
* Attach events to be able to record the history
*
* @access public
*/
public function attachEvents()
{
$events = array(
SubTask::EVENT_UPDATE,
SubTask::EVENT_CREATE,
);
$listener = new SubtaskHistoryListener($this);
foreach ($events as $event_name) {
$this->event->attach($event_name, $listener);
}
}
}

View file

@ -2,11 +2,6 @@
namespace Model;
use SimpleValidator\Validator;
use SimpleValidator\Validators;
use DateTime;
use PDO;
/**
* Task model
*
@ -44,321 +39,6 @@ class Task extends Base
const EVENT_CREATE_UPDATE = 'task.create_update';
const EVENT_ASSIGNEE_CHANGE = 'task.assignee_change';
/**
* Get available colors
*
* @access public
* @return array
*/
public function getColors()
{
return array(
'yellow' => t('Yellow'),
'blue' => t('Blue'),
'green' => t('Green'),
'purple' => t('Purple'),
'red' => t('Red'),
'orange' => t('Orange'),
'grey' => t('Grey'),
);
}
/**
* Get a list of due tasks for all projects
*
* @access public
* @return array
*/
public function getOverdueTasks()
{
$tasks = $this->db->table(self::TABLE)
->columns(
self::TABLE.'.id',
self::TABLE.'.title',
self::TABLE.'.date_due',
self::TABLE.'.project_id',
Project::TABLE.'.name AS project_name',
User::TABLE.'.username AS assignee_username',
User::TABLE.'.name AS assignee_name'
)
->join(Project::TABLE, 'id', 'project_id')
->join(User::TABLE, 'id', 'owner_id')
->eq(Project::TABLE.'.is_active', 1)
->eq(self::TABLE.'.is_active', 1)
->neq(self::TABLE.'.date_due', 0)
->lte(self::TABLE.'.date_due', mktime(23, 59, 59))
->findAll();
return $tasks;
}
/**
* Fetch one task
*
* @access public
* @param integer $task_id Task id
* @param boolean $more If true, fetch all related information
* @return array
*/
public function getById($task_id, $more = false)
{
if ($more) {
$sql = '
SELECT
tasks.id,
tasks.title,
tasks.description,
tasks.date_creation,
tasks.date_completed,
tasks.date_modification,
tasks.date_due,
tasks.color_id,
tasks.project_id,
tasks.column_id,
tasks.owner_id,
tasks.creator_id,
tasks.position,
tasks.is_active,
tasks.score,
tasks.category_id,
project_has_categories.name AS category_name,
projects.name AS project_name,
columns.title AS column_title,
users.username AS assignee_username,
users.name AS assignee_name,
creators.username AS creator_username,
creators.name AS creator_name
FROM tasks
LEFT JOIN users ON users.id = tasks.owner_id
LEFT JOIN users AS creators ON creators.id = tasks.creator_id
LEFT JOIN project_has_categories ON project_has_categories.id = tasks.category_id
LEFT JOIN projects ON projects.id = tasks.project_id
LEFT JOIN columns ON columns.id = tasks.column_id
WHERE tasks.id = ?
';
$rq = $this->db->execute($sql, array($task_id));
return $rq->fetch(PDO::FETCH_ASSOC);
}
else {
return $this->db->table(self::TABLE)->eq('id', $task_id)->findOne();
}
}
/**
* Count all tasks for a given project and status
*
* @access public
* @param integer $project_id Project id
* @param integer $status_id Status id
* @return array
*/
public function getAll($project_id, $status_id = self::STATUS_OPEN)
{
return $this->db
->table(self::TABLE)
->eq('project_id', $project_id)
->eq('is_active', $status_id)
->findAll();
}
/**
* Count all tasks for a given project and status
*
* @access public
* @param integer $project_id Project id
* @param array $status List of status id
* @return integer
*/
public function countByProjectId($project_id, array $status = array(self::STATUS_OPEN, self::STATUS_CLOSED))
{
return $this->db
->table(self::TABLE)
->eq('project_id', $project_id)
->in('is_active', $status)
->count();
}
/**
* Get tasks that match defined filters
*
* @access public
* @param array $filters Filters: [ ['column' => '...', 'operator' => '...', 'value' => '...'], ... ]
* @param array $sorting Sorting: [ 'column' => 'date_creation', 'direction' => 'asc']
* @return array
*/
public function find(array $filters, array $sorting = array())
{
$table = $this->db
->table(self::TABLE)
->columns(
'(SELECT count(*) FROM comments WHERE task_id=tasks.id) AS nb_comments',
'(SELECT count(*) FROM task_has_files WHERE task_id=tasks.id) AS nb_files',
'(SELECT count(*) FROM task_has_subtasks WHERE task_id=tasks.id) AS nb_subtasks',
'(SELECT count(*) FROM task_has_subtasks WHERE task_id=tasks.id AND status=2) AS nb_completed_subtasks',
'tasks.id',
'tasks.title',
'tasks.description',
'tasks.date_creation',
'tasks.date_modification',
'tasks.date_completed',
'tasks.date_due',
'tasks.color_id',
'tasks.project_id',
'tasks.column_id',
'tasks.owner_id',
'tasks.creator_id',
'tasks.position',
'tasks.is_active',
'tasks.score',
'tasks.category_id',
'users.username AS assignee_username',
'users.name AS assignee_name'
)
->join(User::TABLE, 'id', 'owner_id');
foreach ($filters as $key => $filter) {
if ($key === 'or') {
$table->beginOr();
foreach ($filter as $subfilter) {
$table->$subfilter['operator']($subfilter['column'], $subfilter['value']);
}
$table->closeOr();
}
else if (isset($filter['operator']) && isset($filter['column']) && isset($filter['value'])) {
$table->$filter['operator']($filter['column'], $filter['value']);
}
}
if (empty($sorting)) {
$table->orderBy('tasks.position', 'ASC');
}
else {
$table->orderBy($sorting['column'], $sorting['direction']);
}
return $table->findAll();
}
/**
* Count the number of tasks for a given column and status
*
* @access public
* @param integer $project_id Project id
* @param integer $column_id Column id
* @param array $status List of status id
* @return integer
*/
public function countByColumnId($project_id, $column_id, array $status = array(self::STATUS_OPEN))
{
return $this->db
->table(self::TABLE)
->eq('project_id', $project_id)
->eq('column_id', $column_id)
->in('is_active', $status)
->count();
}
/**
* Generic method to duplicate a task
*
* @access public
* @param array $task Task data
* @param array $override Task properties to override
* @return integer|boolean
*/
public function copy(array $task, array $override = array())
{
// Values to override
if (! empty($override)) {
$task = $override + $task;
}
$this->db->startTransaction();
// Assign new values
$values = array();
$values['title'] = $task['title'];
$values['description'] = $task['description'];
$values['date_creation'] = time();
$values['date_modification'] = $values['date_creation'];
$values['date_due'] = $task['date_due'];
$values['color_id'] = $task['color_id'];
$values['project_id'] = $task['project_id'];
$values['column_id'] = $task['column_id'];
$values['owner_id'] = 0;
$values['creator_id'] = $task['creator_id'];
$values['position'] = $this->countByColumnId($values['project_id'], $values['column_id']) + 1;
$values['score'] = $task['score'];
$values['category_id'] = 0;
// Check if the assigned user is allowed for the new project
if ($task['owner_id'] && $this->project->isUserAllowed($values['project_id'], $task['owner_id'])) {
$values['owner_id'] = $task['owner_id'];
}
// Check if the category exists
if ($task['category_id'] && $this->category->exists($task['category_id'], $task['project_id'])) {
$values['category_id'] = $task['category_id'];
}
// Save task
if (! $this->db->table(self::TABLE)->save($values)) {
$this->db->cancelTransaction();
return false;
}
$task_id = $this->db->getConnection()->getLastId();
// Duplicate subtasks
if (! $this->subTask->duplicate($task['id'], $task_id)) {
$this->db->cancelTransaction();
return false;
}
$this->db->closeTransaction();
// Trigger events
$this->event->trigger(self::EVENT_CREATE_UPDATE, array('task_id' => $task_id) + $values);
$this->event->trigger(self::EVENT_CREATE, array('task_id' => $task_id) + $values);
return $task_id;
}
/**
* Duplicate a task to the same project
*
* @access public
* @param array $task Task data
* @return integer|boolean
*/
public function duplicateSameProject($task)
{
return $this->copy($task);
}
/**
* Duplicate a task to another project (always copy to the first column)
*
* @access public
* @param integer $project_id Destination project id
* @param array $task Task data
* @return integer|boolean
*/
public function duplicateToAnotherProject($project_id, array $task)
{
return $this->copy($task, array(
'project_id' => $project_id,
'column_id' => $this->board->getFirstColumn($project_id),
));
}
/**
* Prepare data before task creation or modification
*
@ -367,40 +47,20 @@ class Task extends Base
*/
public function prepare(array &$values)
{
if (isset($values['another_task'])) {
unset($values['another_task']);
}
if (! empty($values['date_due']) && ! is_numeric($values['date_due'])) {
$values['date_due'] = $this->parseDate($values['date_due']);
}
// Force integer fields at 0 (for Postgresql)
if (isset($values['date_due']) && empty($values['date_due'])) {
$values['date_due'] = 0;
}
if (isset($values['score']) && empty($values['score'])) {
$values['score'] = 0;
}
if (isset($values['is_active'])) {
$values['is_active'] = (int) $values['is_active'];
}
$this->dateParser->convert($values, array('date_due', 'date_started'));
$this->removeFields($values, array('another_task', 'id'));
$this->resetFields($values, array('date_due', 'date_started', 'score', 'category_id', 'time_estimated', 'time_spent'));
$this->convertIntegerFields($values, array('is_active'));
}
/**
* Create a task
* Prepare data before task creation
*
* @access public
* @param array $values Form values
* @return boolean
* @param array $values Form values
*/
public function create(array $values)
public function prepareCreation(array &$values)
{
$this->db->startTransaction();
// Prepare data
$this->prepare($values);
if (empty($values['column_id'])) {
@ -408,15 +68,40 @@ class Task extends Base
}
if (empty($values['color_id'])) {
$colors = $this->getColors();
$colors = $this->color->getList();
$values['color_id'] = key($colors);
}
$values['date_creation'] = time();
$values['date_modification'] = $values['date_creation'];
$values['position'] = $this->countByColumnId($values['project_id'], $values['column_id']) + 1;
$values['position'] = $this->taskFinder->countByColumnId($values['project_id'], $values['column_id']) + 1;
}
/**
* Prepare data before task modification
*
* @access public
* @param array $values Form values
*/
public function prepareModification(array &$values)
{
$this->prepare($values);
$values['date_modification'] = time();
}
/**
* Create a task
*
* @access public
* @param array $values Form values
* @return boolean|integer
*/
public function create(array $values)
{
$this->db->startTransaction();
$this->prepareCreation($values);
// Save task
if (! $this->db->table(self::TABLE)->save($values)) {
$this->db->cancelTransaction();
return false;
@ -444,17 +129,15 @@ class Task extends Base
public function update(array $values, $trigger_events = true)
{
// Fetch original task
$original_task = $this->getById($values['id']);
$original_task = $this->taskFinder->getById($values['id']);
if (! $original_task) {
return false;
}
// Prepare data
$this->prepare($values);
$updated_task = $values;
$updated_task['date_modification'] = time();
unset($updated_task['id']);
$this->prepareModification($updated_task);
$result = $this->db->table(self::TABLE)->eq('id', $values['id'])->update($updated_task);
@ -498,18 +181,6 @@ class Task extends Base
}
}
/**
* Return true if the project exists
*
* @access public
* @param integer $task_id Task id
* @return boolean
*/
public function exists($task_id)
{
return $this->db->table(self::TABLE)->eq('id', $task_id)->count() === 1;
}
/**
* Mark a task closed
*
@ -519,7 +190,7 @@ class Task extends Base
*/
public function close($task_id)
{
if (! $this->exists($task_id)) {
if (! $this->taskFinder->exists($task_id)) {
return false;
}
@ -532,7 +203,7 @@ class Task extends Base
));
if ($result) {
$this->event->trigger(self::EVENT_CLOSE, array('task_id' => $task_id) + $this->getById($task_id));
$this->event->trigger(self::EVENT_CLOSE, array('task_id' => $task_id) + $this->taskFinder->getById($task_id));
}
return $result;
@ -547,7 +218,7 @@ class Task extends Base
*/
public function open($task_id)
{
if (! $this->exists($task_id)) {
if (! $this->taskFinder->exists($task_id)) {
return false;
}
@ -560,7 +231,7 @@ class Task extends Base
));
if ($result) {
$this->event->trigger(self::EVENT_OPEN, array('task_id' => $task_id) + $this->getById($task_id));
$this->event->trigger(self::EVENT_OPEN, array('task_id' => $task_id) + $this->taskFinder->getById($task_id));
}
return $result;
@ -575,7 +246,7 @@ class Task extends Base
*/
public function remove($task_id)
{
if (! $this->exists($task_id)) {
if (! $this->taskFinder->exists($task_id)) {
return false;
}
@ -693,15 +364,18 @@ class Task extends Base
$values['owner_id'] = 0;
// Check if the assigned user is allowed for the new project
if ($task['owner_id'] && $this->project->isUserAllowed($project_id, $task['owner_id'])) {
if ($task['owner_id'] && $this->projectPermission->isUserAllowed($project_id, $task['owner_id'])) {
$values['owner_id'] = $task['owner_id'];
}
// We use the first column of the new project
$values['column_id'] = $this->board->getFirstColumn($project_id);
$values['position'] = $this->countByColumnId($project_id, $values['column_id']) + 1;
$values['position'] = $this->taskFinder->countByColumnId($project_id, $values['column_id']) + 1;
$values['project_id'] = $project_id;
// The task will be open (close event binding)
$values['is_active'] = 1;
if ($this->db->table(self::TABLE)->eq('id', $task['id'])->update($values)) {
return $task['id'];
}
@ -710,324 +384,114 @@ class Task extends Base
}
/**
* Common validation rules
* Generic method to duplicate a task
*
* @access private
* @return array
* @access public
* @param array $task Task data
* @param array $override Task properties to override
* @return integer|boolean
*/
private function commonValidationRules()
public function copy(array $task, array $override = array())
{
return array(
new Validators\Integer('id', t('This value must be an integer')),
new Validators\Integer('project_id', t('This value must be an integer')),
new Validators\Integer('column_id', t('This value must be an integer')),
new Validators\Integer('owner_id', t('This value must be an integer')),
new Validators\Integer('creator_id', t('This value must be an integer')),
new Validators\Integer('score', t('This value must be an integer')),
new Validators\Integer('category_id', t('This value must be an integer')),
new Validators\MaxLength('title', t('The maximum length is %d characters', 200), 200),
new Validators\Date('date_due', t('Invalid date'), $this->getDateFormats()),
);
// Values to override
if (! empty($override)) {
$task = $override + $task;
}
$this->db->startTransaction();
// Assign new values
$values = array();
$values['title'] = $task['title'];
$values['description'] = $task['description'];
$values['date_creation'] = time();
$values['date_modification'] = $values['date_creation'];
$values['date_due'] = $task['date_due'];
$values['color_id'] = $task['color_id'];
$values['project_id'] = $task['project_id'];
$values['column_id'] = $task['column_id'];
$values['owner_id'] = 0;
$values['creator_id'] = $task['creator_id'];
$values['position'] = $this->taskFinder->countByColumnId($values['project_id'], $values['column_id']) + 1;
$values['score'] = $task['score'];
$values['category_id'] = 0;
// Check if the assigned user is allowed for the new project
if ($task['owner_id'] && $this->projectPermission->isUserAllowed($values['project_id'], $task['owner_id'])) {
$values['owner_id'] = $task['owner_id'];
}
// Check if the category exists
if ($task['category_id'] && $this->category->exists($task['category_id'], $task['project_id'])) {
$values['category_id'] = $task['category_id'];
}
// Save task
if (! $this->db->table(Task::TABLE)->save($values)) {
$this->db->cancelTransaction();
return false;
}
$task_id = $this->db->getConnection()->getLastId();
// Duplicate subtasks
if (! $this->subTask->duplicate($task['id'], $task_id)) {
$this->db->cancelTransaction();
return false;
}
$this->db->closeTransaction();
// Trigger events
$this->event->trigger(Task::EVENT_CREATE_UPDATE, array('task_id' => $task_id) + $values);
$this->event->trigger(Task::EVENT_CREATE, array('task_id' => $task_id) + $values);
return $task_id;
}
/**
* Validate task creation
* Duplicate a task to the same project
*
* @access public
* @param array $values Form values
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
* @param array $task Task data
* @return integer|boolean
*/
public function validateCreation(array $values)
public function duplicateToSameProject($task)
{
$rules = array(
new Validators\Required('project_id', t('The project is required')),
new Validators\Required('title', t('The title is required')),
);
$v = new Validator($values, array_merge($rules, $this->commonValidationRules()));
return array(
$v->execute(),
$v->getErrors()
);
return $this->copy($task);
}
/**
* Validate description creation
* Duplicate a task to another project (always copy to the first column)
*
* @access public
* @param array $values Form values
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
* @param integer $project_id Destination project id
* @param array $task Task data
* @return integer|boolean
*/
public function validateDescriptionCreation(array $values)
public function duplicateToAnotherProject($project_id, array $task)
{
$rules = array(
new Validators\Required('id', t('The id is required')),
new Validators\Required('description', t('The description is required')),
);
$v = new Validator($values, array_merge($rules, $this->commonValidationRules()));
return array(
$v->execute(),
$v->getErrors()
);
return $this->copy($task, array(
'project_id' => $project_id,
'column_id' => $this->board->getFirstColumn($project_id),
));
}
/**
* Validate task modification
* Get a the task id from a text
*
* Example: "Fix bug #1234" will return 1234
*
* @access public
* @param array $values Form values
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
*/
public function validateModification(array $values)
{
$rules = array(
new Validators\Required('id', t('The id is required')),
);
$v = new Validator($values, array_merge($rules, $this->commonValidationRules()));
return array(
$v->execute(),
$v->getErrors()
);
}
/**
* Validate assignee change
*
* @access public
* @param array $values Form values
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
*/
public function validateAssigneeModification(array $values)
{
$rules = array(
new Validators\Required('id', t('The id is required')),
new Validators\Required('project_id', t('The project is required')),
new Validators\Required('owner_id', t('This value is required')),
);
$v = new Validator($values, array_merge($rules, $this->commonValidationRules()));
return array(
$v->execute(),
$v->getErrors()
);
}
/**
* Validate category change
*
* @access public
* @param array $values Form values
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
*/
public function validateCategoryModification(array $values)
{
$rules = array(
new Validators\Required('id', t('The id is required')),
new Validators\Required('project_id', t('The project is required')),
new Validators\Required('category_id', t('This value is required')),
);
$v = new Validator($values, array_merge($rules, $this->commonValidationRules()));
return array(
$v->execute(),
$v->getErrors()
);
}
/**
* Validate project modification
*
* @access public
* @param array $values Form values
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
*/
public function validateProjectModification(array $values)
{
$rules = array(
new Validators\Required('id', t('The id is required')),
new Validators\Required('project_id', t('The project is required')),
);
$v = new Validator($values, array_merge($rules, $this->commonValidationRules()));
return array(
$v->execute(),
$v->getErrors()
);
}
/**
* Return a timestamp if the given date format is correct otherwise return 0
*
* @access public
* @param string $value Date to parse
* @param string $format Date format
* @param string $message Text
* @return integer
*/
public function getValidDate($value, $format)
public function getTaskIdFromText($message)
{
$date = DateTime::createFromFormat($format, $value);
if ($date !== false) {
$errors = DateTime::getLastErrors();
if ($errors['error_count'] === 0 && $errors['warning_count'] === 0) {
$timestamp = $date->getTimestamp();
return $timestamp > 0 ? $timestamp : 0;
}
if (preg_match('!#(\d+)!i', $message, $matches) && isset($matches[1])) {
return $matches[1];
}
return 0;
}
/**
* Parse a date ad return a unix timestamp, try different date formats
*
* @access public
* @param string $value Date to parse
* @return integer
*/
public function parseDate($value)
{
foreach ($this->getDateFormats() as $format) {
$timestamp = $this->getValidDate($value, $format);
if ($timestamp !== 0) {
return $timestamp;
}
}
return null;
}
/**
* Return the list of supported date formats
*
* @access public
* @return array
*/
public function getDateFormats()
{
return array(
t('m/d/Y'),
'Y-m-d',
'Y_m_d',
);
}
/**
* For a given timestamp, reset the date to midnight
*
* @access public
* @param integer $timestamp Timestamp
* @return integer
*/
public function resetDateToMidnight($timestamp)
{
return mktime(0, 0, 0, date('m', $timestamp), date('d', $timestamp), date('Y', $timestamp));
}
/**
* Export a list of tasks for a given project and date range
*
* @access public
* @param integer $project_id Project id
* @param mixed $from Start date (timestamp or user formatted date)
* @param mixed $to End date (timestamp or user formatted date)
* @return array
*/
public function export($project_id, $from, $to)
{
$sql = '
SELECT
tasks.id,
projects.name AS project_name,
tasks.is_active,
project_has_categories.name AS category_name,
columns.title AS column_title,
tasks.position,
tasks.color_id,
tasks.date_due,
creators.username AS creator_username,
users.username AS assignee_username,
tasks.score,
tasks.title,
tasks.date_creation,
tasks.date_modification,
tasks.date_completed
FROM tasks
LEFT JOIN users ON users.id = tasks.owner_id
LEFT JOIN users AS creators ON creators.id = tasks.creator_id
LEFT JOIN project_has_categories ON project_has_categories.id = tasks.category_id
LEFT JOIN columns ON columns.id = tasks.column_id
LEFT JOIN projects ON projects.id = tasks.project_id
WHERE tasks.date_creation >= ? AND tasks.date_creation <= ? AND tasks.project_id = ?
';
if (! is_numeric($from)) {
$from = $this->resetDateToMidnight($this->parseDate($from));
}
if (! is_numeric($to)) {
$to = $this->resetDateToMidnight(strtotime('+1 day', $this->parseDate($to)));
}
$rq = $this->db->execute($sql, array($from, $to, $project_id));
$tasks = $rq->fetchAll(PDO::FETCH_ASSOC);
$columns = array(
e('Task Id'),
e('Project'),
e('Status'),
e('Category'),
e('Column'),
e('Position'),
e('Color'),
e('Due date'),
e('Creator'),
e('Assignee'),
e('Complexity'),
e('Title'),
e('Creation date'),
e('Modification date'),
e('Completion date'),
);
$results = array($columns);
foreach ($tasks as &$task) {
$results[] = array_values($this->formatOutput($task));
}
return $results;
}
/**
* Format the output of a task array
*
* @access public
* @param array $task Task properties
* @return array
*/
public function formatOutput(array &$task)
{
$colors = $this->getColors();
$task['score'] = $task['score'] ?: '';
$task['is_active'] = $task['is_active'] == self::STATUS_OPEN ? e('Open') : e('Closed');
$task['color_id'] = $colors[$task['color_id']];
$task['date_creation'] = date('Y-m-d', $task['date_creation']);
$task['date_due'] = $task['date_due'] ? date('Y-m-d', $task['date_due']) : '';
$task['date_modification'] = $task['date_modification'] ? date('Y-m-d', $task['date_modification']) : '';
$task['date_completed'] = $task['date_completed'] ? date('Y-m-d', $task['date_completed']) : '';
return $task;
}
}

View file

@ -0,0 +1,136 @@
<?php
namespace Model;
use PDO;
/**
* Task Export model
*
* @package model
* @author Frederic Guillot
*/
class TaskExport extends Base
{
/**
* Fetch tasks and return the prepared CSV
*
* @access public
* @param integer $project_id Project id
* @param mixed $from Start date (timestamp or user formatted date)
* @param mixed $to End date (timestamp or user formatted date)
* @return array
*/
public function export($project_id, $from, $to)
{
$tasks = $this->getTasks($project_id, $from, $to);
$results = array($this->getColumns());
foreach ($tasks as &$task) {
$results[] = array_values($this->format($task));
}
return $results;
}
/**
* Get the list of tasks for a given project and date range
*
* @access public
* @param integer $project_id Project id
* @param mixed $from Start date (timestamp or user formatted date)
* @param mixed $to End date (timestamp or user formatted date)
* @return array
*/
public function getTasks($project_id, $from, $to)
{
$sql = '
SELECT
tasks.id,
projects.name AS project_name,
tasks.is_active,
project_has_categories.name AS category_name,
columns.title AS column_title,
tasks.position,
tasks.color_id,
tasks.date_due,
creators.username AS creator_username,
users.username AS assignee_username,
tasks.score,
tasks.title,
tasks.date_creation,
tasks.date_modification,
tasks.date_completed,
tasks.date_started,
tasks.time_estimated,
tasks.time_spent
FROM tasks
LEFT JOIN users ON users.id = tasks.owner_id
LEFT JOIN users AS creators ON creators.id = tasks.creator_id
LEFT JOIN project_has_categories ON project_has_categories.id = tasks.category_id
LEFT JOIN columns ON columns.id = tasks.column_id
LEFT JOIN projects ON projects.id = tasks.project_id
WHERE tasks.date_creation >= ? AND tasks.date_creation <= ? AND tasks.project_id = ?
';
if (! is_numeric($from)) {
$from = $this->dateParser->resetDateToMidnight($this->dateParser->getTimestamp($from));
}
if (! is_numeric($to)) {
$to = $this->dateParser->resetDateToMidnight(strtotime('+1 day', $this->dateParser->getTimestamp($to)));
}
$rq = $this->db->execute($sql, array($from, $to, $project_id));
return $rq->fetchAll(PDO::FETCH_ASSOC);
}
/**
* Format the output of a task array
*
* @access public
* @param array $task Task properties
* @return array
*/
public function format(array &$task)
{
$colors = $this->color->getList();
$task['is_active'] = $task['is_active'] == Task::STATUS_OPEN ? e('Open') : e('Closed');
$task['color_id'] = $colors[$task['color_id']];
$this->dateParser->format($task, array('date_due', 'date_modification', 'date_creation', 'date_started', 'date_completed'), 'Y-m-d');
return $task;
}
/**
* Get column titles
*
* @access public
* @return array
*/
public function getColumns()
{
return array(
e('Task Id'),
e('Project'),
e('Status'),
e('Category'),
e('Column'),
e('Position'),
e('Color'),
e('Due date'),
e('Creator'),
e('Assignee'),
e('Complexity'),
e('Title'),
e('Creation date'),
e('Modification date'),
e('Completion date'),
e('Start date'),
e('Time estimated'),
e('Time spent'),
);
}
}

View file

@ -0,0 +1,325 @@
<?php
namespace Model;
use PDO;
/**
* Task Finder model
*
* @package model
* @author Frederic Guillot
*/
class TaskFinder extends Base
{
/**
* Common request to fetch a list of tasks
*
* @access private
* @return \PicoDb\Table
*/
private function prepareRequestList()
{
return $this->db
->table(Task::TABLE)
->columns(
'(SELECT count(*) FROM comments WHERE task_id=tasks.id) AS nb_comments',
'(SELECT count(*) FROM task_has_files WHERE task_id=tasks.id) AS nb_files',
'(SELECT count(*) FROM task_has_subtasks WHERE task_id=tasks.id) AS nb_subtasks',
'(SELECT count(*) FROM task_has_subtasks WHERE task_id=tasks.id AND status=2) AS nb_completed_subtasks',
'tasks.id',
'tasks.reference',
'tasks.title',
'tasks.description',
'tasks.date_creation',
'tasks.date_modification',
'tasks.date_completed',
'tasks.date_due',
'tasks.color_id',
'tasks.project_id',
'tasks.column_id',
'tasks.owner_id',
'tasks.creator_id',
'tasks.position',
'tasks.is_active',
'tasks.score',
'tasks.category_id',
'users.username AS assignee_username',
'users.name AS assignee_name'
)
->join(User::TABLE, 'id', 'owner_id');
}
/**
* Task search with pagination
*
* @access public
* @param integer $project_id Project id
* @param string $search Search terms
* @param integer $offset Offset
* @param integer $limit Limit
* @param string $column Sorting column
* @param string $direction Sorting direction
* @return array
*/
public function search($project_id, $search, $offset = 0, $limit = 25, $column = 'tasks.id', $direction = 'DESC')
{
return $this->prepareRequestList()
->eq('project_id', $project_id)
->like('title', '%'.$search.'%')
->offset($offset)
->limit($limit)
->orderBy($column, $direction)
->findAll();
}
/**
* Get all completed tasks with pagination
*
* @access public
* @param integer $project_id Project id
* @param integer $offset Offset
* @param integer $limit Limit
* @param string $column Sorting column
* @param string $direction Sorting direction
* @return array
*/
public function getClosedTasks($project_id, $offset = 0, $limit = 25, $column = 'tasks.date_completed', $direction = 'DESC')
{
return $this->prepareRequestList()
->eq('project_id', $project_id)
->eq('is_active', Task::STATUS_CLOSED)
->offset($offset)
->limit($limit)
->orderBy($column, $direction)
->findAll();
}
/**
* Get all tasks shown on the board (sorted by position)
*
* @access public
* @param integer $project_id Project id
* @return array
*/
public function getTasksOnBoard($project_id)
{
return $this->prepareRequestList()
->eq('project_id', $project_id)
->eq('is_active', Task::STATUS_OPEN)
->asc('tasks.position')
->findAll();
}
/**
* Get all open tasks for a given user
*
* @access public
* @param integer $user_id User id
* @return array
*/
public function getAllTasksByUser($user_id)
{
return $this->db
->table(Task::TABLE)
->columns(
'tasks.id',
'tasks.title',
'tasks.date_due',
'tasks.date_creation',
'tasks.project_id',
'tasks.color_id',
'projects.name AS project_name'
)
->join(Project::TABLE, 'id', 'project_id')
->eq('tasks.owner_id', $user_id)
->eq('tasks.is_active', Task::STATUS_OPEN)
->asc('tasks.id')
->findAll();
}
/**
* Get all tasks for a given project and status
*
* @access public
* @param integer $project_id Project id
* @param integer $status_id Status id
* @return array
*/
public function getAll($project_id, $status_id = Task::STATUS_OPEN)
{
return $this->db
->table(Task::TABLE)
->eq('project_id', $project_id)
->eq('is_active', $status_id)
->findAll();
}
/**
* Get a list of overdue tasks for all projects
*
* @access public
* @return array
*/
public function getOverdueTasks()
{
$tasks = $this->db->table(Task::TABLE)
->columns(
Task::TABLE.'.id',
Task::TABLE.'.title',
Task::TABLE.'.date_due',
Task::TABLE.'.project_id',
Project::TABLE.'.name AS project_name',
User::TABLE.'.username AS assignee_username',
User::TABLE.'.name AS assignee_name'
)
->join(Project::TABLE, 'id', 'project_id')
->join(User::TABLE, 'id', 'owner_id')
->eq(Project::TABLE.'.is_active', 1)
->eq(Task::TABLE.'.is_active', 1)
->neq(Task::TABLE.'.date_due', 0)
->lte(Task::TABLE.'.date_due', mktime(23, 59, 59))
->findAll();
return $tasks;
}
/**
* Fetch a task by the id
*
* @access public
* @param integer $task_id Task id
* @return array
*/
public function getById($task_id)
{
return $this->db->table(Task::TABLE)->eq('id', $task_id)->findOne();
}
/**
* Fetch a task by the reference (external id)
*
* @access public
* @param string $reference Task reference
* @return array
*/
public function getByReference($reference)
{
return $this->db->table(Task::TABLE)->eq('reference', $reference)->findOne();
}
/**
* Get task details (fetch more information from other tables)
*
* @access public
* @param integer $task_id Task id
* @return array
*/
public function getDetails($task_id)
{
$sql = '
SELECT
tasks.id,
tasks.reference,
tasks.title,
tasks.description,
tasks.date_creation,
tasks.date_completed,
tasks.date_modification,
tasks.date_due,
tasks.date_started,
tasks.time_estimated,
tasks.time_spent,
tasks.color_id,
tasks.project_id,
tasks.column_id,
tasks.owner_id,
tasks.creator_id,
tasks.position,
tasks.is_active,
tasks.score,
tasks.category_id,
project_has_categories.name AS category_name,
projects.name AS project_name,
columns.title AS column_title,
users.username AS assignee_username,
users.name AS assignee_name,
creators.username AS creator_username,
creators.name AS creator_name
FROM tasks
LEFT JOIN users ON users.id = tasks.owner_id
LEFT JOIN users AS creators ON creators.id = tasks.creator_id
LEFT JOIN project_has_categories ON project_has_categories.id = tasks.category_id
LEFT JOIN projects ON projects.id = tasks.project_id
LEFT JOIN columns ON columns.id = tasks.column_id
WHERE tasks.id = ?
';
$rq = $this->db->execute($sql, array($task_id));
return $rq->fetch(PDO::FETCH_ASSOC);
}
/**
* Count all tasks for a given project and status
*
* @access public
* @param integer $project_id Project id
* @param array $status List of status id
* @return integer
*/
public function countByProjectId($project_id, array $status = array(Task::STATUS_OPEN, Task::STATUS_CLOSED))
{
return $this->db
->table(Task::TABLE)
->eq('project_id', $project_id)
->in('is_active', $status)
->count();
}
/**
* Count the number of tasks for a given column and status
*
* @access public
* @param integer $project_id Project id
* @param integer $column_id Column id
* @param array $status List of status id
* @return integer
*/
public function countByColumnId($project_id, $column_id, array $status = array(Task::STATUS_OPEN))
{
return $this->db
->table(Task::TABLE)
->eq('project_id', $project_id)
->eq('column_id', $column_id)
->in('is_active', $status)
->count();
}
/**
* Count the number of tasks for a custom search
*
* @access public
* @param integer $project_id Project id
* @param string $search Search terms
* @return integer
*/
public function countSearch($project_id, $search)
{
return $this->db->table(Task::TABLE)
->eq('project_id', $project_id)
->like('title', '%'.$search.'%')
->count();
}
/**
* Return true if the task exists
*
* @access public
* @param integer $task_id Task id
* @return boolean
*/
public function exists($task_id)
{
return $this->db->table(Task::TABLE)->eq('id', $task_id)->count() === 1;
}
}

View file

@ -1,160 +0,0 @@
<?php
namespace Model;
use PDO;
use Core\Registry;
use Event\TaskHistoryListener;
/**
* Task history model
*
* @package model
* @author Frederic Guillot
*/
class TaskHistory extends BaseHistory
{
/**
* SQL table name
*
* @var string
*/
const TABLE = 'task_has_events';
/**
* Maximum number of events
*
* @var integer
*/
const MAX_EVENTS = 5000;
/**
* Constructor
*
* @access public
* @param \Core\Registry $registry Registry instance
*/
public function __construct(Registry $registry)
{
parent::__construct($registry);
$this->table = self::TABLE;
}
/**
* Create a new event
*
* @access public
* @param integer $project_id Project id
* @param integer $task_id Task id
* @param integer $creator_id Author of the event (user id)
* @param string $event_name Task event name
* @return boolean
*/
public function create($project_id, $task_id, $creator_id, $event_name)
{
$values = array(
'project_id' => $project_id,
'task_id' => $task_id,
'creator_id' => $creator_id,
'event_name' => $event_name,
'date_creation' => time(),
);
$this->db->startTransaction();
$this->cleanup(self::MAX_EVENTS - 1);
$result = $this->db->table(self::TABLE)->insert($values);
$this->db->closeTransaction();
return $result;
}
/**
* Get all necessary content to display activity feed
*
* $author_name
* $author_username
* $task['id', 'title', 'position', 'column_name']
*/
public function getAllContentByProjectId($project_id, $limit = 50)
{
$sql = '
SELECT
task_has_events.id,
task_has_events.date_creation,
task_has_events.event_name,
task_has_events.task_id,
tasks.title as task_title,
tasks.position as task_position,
columns.title as task_column_name,
users.username as author_username,
users.name as author_name
FROM task_has_events
LEFT JOIN users ON users.id=task_has_events.creator_id
LEFT JOIN tasks ON tasks.id=task_has_events.task_id
LEFT JOIN columns ON columns.id=tasks.column_id
WHERE task_has_events.project_id = ?
ORDER BY task_has_events.id DESC
LIMIT '.$limit.' OFFSET 0
';
$rq = $this->db->execute($sql, array($project_id));
$events = $rq->fetchAll(PDO::FETCH_ASSOC);
foreach ($events as &$event) {
$event['author'] = $event['author_name'] ?: $event['author_username'];
$event['event_title'] = $this->getTitle($event);
$event['event_content'] = $this->getContent($event);
$event['event_type'] = 'task';
}
return $events;
}
/**
* Get the event title (translated)
*
* @access public
* @param array $event Event properties
* @return string
*/
public function getTitle(array $event)
{
$titles = array(
Task::EVENT_ASSIGNEE_CHANGE => t('%s change the assignee of the task #%d', $event['author'], $event['task_id']),
Task::EVENT_UPDATE => t('%s updated the task #%d', $event['author'], $event['task_id']),
Task::EVENT_CREATE => t('%s created the task #%d', $event['author'], $event['task_id']),
Task::EVENT_CLOSE => t('%s closed the task #%d', $event['author'], $event['task_id']),
Task::EVENT_OPEN => t('%s open the task #%d', $event['author'], $event['task_id']),
Task::EVENT_MOVE_COLUMN => t('%s moved the task #%d to the column "%s"', $event['author'], $event['task_id'], $event['task_column_name']),
Task::EVENT_MOVE_POSITION => t('%s moved the task #%d to the position %d in the column "%s"', $event['author'], $event['task_id'], $event['task_position'], $event['task_column_name']),
);
return isset($titles[$event['event_name']]) ? $titles[$event['event_name']] : '';
}
/**
* Attach events to be able to record the history
*
* @access public
*/
public function attachEvents()
{
$events = array(
Task::EVENT_ASSIGNEE_CHANGE,
Task::EVENT_UPDATE,
Task::EVENT_CREATE,
Task::EVENT_CLOSE,
Task::EVENT_OPEN,
Task::EVENT_MOVE_COLUMN,
Task::EVENT_MOVE_POSITION,
);
$listener = new TaskHistoryListener($this);
foreach ($events as $event_name) {
$this->event->attach($event_name, $listener);
}
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace Model;
/**
* Task permission model
*
* @package model
* @author Frederic Guillot
*/
class TaskPermission extends Base
{
/**
* Return true if the user can remove a task
*
* Regular users can't remove tasks from other people
*
* @public
* @return boolean
*/
public function canRemoveTask(array $task)
{
if ($this->acl->isAdminUser()) {
return true;
}
else if (isset($task['creator_id']) && $task['creator_id'] == $this->acl->getUserId()) {
return true;
}
return false;
}
}

View file

@ -0,0 +1,216 @@
<?php
namespace Model;
use SimpleValidator\Validator;
use SimpleValidator\Validators;
/**
* Task validator model
*
* @package model
* @author Frederic Guillot
*/
class TaskValidator extends Base
{
/**
* Common validation rules
*
* @access private
* @return array
*/
private function commonValidationRules()
{
return array(
new Validators\Integer('id', t('This value must be an integer')),
new Validators\Integer('project_id', t('This value must be an integer')),
new Validators\Integer('column_id', t('This value must be an integer')),
new Validators\Integer('owner_id', t('This value must be an integer')),
new Validators\Integer('creator_id', t('This value must be an integer')),
new Validators\Integer('score', t('This value must be an integer')),
new Validators\Integer('category_id', t('This value must be an integer')),
new Validators\MaxLength('title', t('The maximum length is %d characters', 200), 200),
new Validators\Date('date_due', t('Invalid date'), $this->dateParser->getDateFormats()),
new Validators\Date('date_started', t('Invalid date'), $this->dateParser->getDateFormats()),
new Validators\Numeric('time_spent', t('This value must be numeric')),
new Validators\Numeric('time_estimated', t('This value must be numeric')),
);
}
/**
* Validate task creation
*
* @access public
* @param array $values Form values
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
*/
public function validateCreation(array $values)
{
$rules = array(
new Validators\Required('project_id', t('The project is required')),
new Validators\Required('title', t('The title is required')),
);
$v = new Validator($values, array_merge($rules, $this->commonValidationRules()));
return array(
$v->execute(),
$v->getErrors()
);
}
/**
* Validate description creation
*
* @access public
* @param array $values Form values
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
*/
public function validateDescriptionCreation(array $values)
{
$rules = array(
new Validators\Required('id', t('The id is required')),
new Validators\Required('description', t('The description is required')),
);
$v = new Validator($values, array_merge($rules, $this->commonValidationRules()));
return array(
$v->execute(),
$v->getErrors()
);
}
/**
* Validate task modification (form)
*
* @access public
* @param array $values Form values
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
*/
public function validateModification(array $values)
{
$rules = array(
new Validators\Required('id', t('The id is required')),
new Validators\Required('title', t('The title is required')),
);
$v = new Validator($values, array_merge($rules, $this->commonValidationRules()));
return array(
$v->execute(),
$v->getErrors()
);
}
/**
* Validate task modification (Api)
*
* @access public
* @param array $values Form values
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
*/
public function validateApiModification(array $values)
{
$rules = array(
new Validators\Required('id', t('The id is required')),
);
$v = new Validator($values, array_merge($rules, $this->commonValidationRules()));
return array(
$v->execute(),
$v->getErrors()
);
}
/**
* Validate assignee change
*
* @access public
* @param array $values Form values
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
*/
public function validateAssigneeModification(array $values)
{
$rules = array(
new Validators\Required('id', t('The id is required')),
new Validators\Required('project_id', t('The project is required')),
new Validators\Required('owner_id', t('This value is required')),
);
$v = new Validator($values, array_merge($rules, $this->commonValidationRules()));
return array(
$v->execute(),
$v->getErrors()
);
}
/**
* Validate category change
*
* @access public
* @param array $values Form values
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
*/
public function validateCategoryModification(array $values)
{
$rules = array(
new Validators\Required('id', t('The id is required')),
new Validators\Required('project_id', t('The project is required')),
new Validators\Required('category_id', t('This value is required')),
);
$v = new Validator($values, array_merge($rules, $this->commonValidationRules()));
return array(
$v->execute(),
$v->getErrors()
);
}
/**
* Validate project modification
*
* @access public
* @param array $values Form values
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
*/
public function validateProjectModification(array $values)
{
$rules = array(
new Validators\Required('id', t('The id is required')),
new Validators\Required('project_id', t('The project is required')),
);
$v = new Validator($values, array_merge($rules, $this->commonValidationRules()));
return array(
$v->execute(),
$v->getErrors()
);
}
/**
* Validate time tracking modification (form)
*
* @access public
* @param array $values Form values
* @return array $valid, $errors [0] = Success or not, [1] = List of errors
*/
public function validateTimeModification(array $values)
{
$rules = array(
new Validators\Required('id', t('The id is required')),
);
$v = new Validator($values, array_merge($rules, $this->commonValidationRules()));
return array(
$v->execute(),
$v->getErrors()
);
}
}

View file

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

View file

@ -4,6 +4,7 @@ namespace Model;
use SimpleValidator\Validator;
use SimpleValidator\Validators;
use Core\Session;
/**
* User model
@ -27,6 +28,24 @@ class User extends Base
*/
const EVERYBODY_ID = -1;
/**
* Return true is the given user id is administrator
*
* @access public
* @param integer $user_id User id
* @return boolean
*/
public function isAdmin($user_id)
{
$result = $this->db
->table(User::TABLE)
->eq('id', $user_id)
->eq('is_admin', 1)
->count();
return $result > 0;
}
/**
* Get the default project from the session
*
@ -119,10 +138,64 @@ class User extends Base
return $this->db
->table(self::TABLE)
->asc('username')
->columns('id', 'username', 'name', 'email', 'is_admin', 'default_project_id', 'is_ldap_user', 'notifications_enabled', 'google_id', 'github_id')
->columns(
'id',
'username',
'name',
'email',
'is_admin',
'default_project_id',
'is_ldap_user',
'notifications_enabled',
'google_id',
'github_id'
)
->findAll();
}
/**
* Get all users with pagination
*
* @access public
* @param integer $offset Offset
* @param integer $limit Limit
* @param string $column Sorting column
* @param string $direction Sorting direction
* @return array
*/
public function paginate($offset = 0, $limit = 25, $column = 'username', $direction = 'ASC')
{
return $this->db
->table(self::TABLE)
->columns(
'id',
'username',
'name',
'email',
'is_admin',
'default_project_id',
'is_ldap_user',
'notifications_enabled',
'google_id',
'github_id'
)
->offset($offset)
->limit($limit)
->orderBy($column, $direction)
->findAll();
}
/**
* Get the number of users
*
* @access public
* @return integer
*/
public function count()
{
return $this->db->table(self::TABLE)->count();
}
/**
* List all users (key-value pairs with id/username)
*
@ -132,7 +205,18 @@ class User extends Base
public function getList()
{
$users = $this->db->table(self::TABLE)->columns('id', 'username', 'name')->findAll();
return $this->prepareList($users);
}
/**
* Common method to prepare a user list
*
* @access public
* @param array $users Users list (from database)
* @return array Formated list
*/
public function prepareList(array $users)
{
$result = array();
foreach ($users as $user) {
@ -162,21 +246,8 @@ class User extends Base
}
}
if (isset($values['confirmation'])) {
unset($values['confirmation']);
}
if (isset($values['current_password'])) {
unset($values['current_password']);
}
if (isset($values['is_admin']) && empty($values['is_admin'])) {
$values['is_admin'] = 0;
}
if (isset($values['is_ldap_user']) && empty($values['is_ldap_user'])) {
$values['is_ldap_user'] = 0;
}
$this->removeFields($values, array('confirmation', 'current_password'));
$this->resetFields($values, array('is_admin', 'is_ldap_user'));
}
/**
@ -205,7 +276,7 @@ class User extends Base
$result = $this->db->table(self::TABLE)->eq('id', $values['id'])->update($values);
// If the user is connected refresh his session
if (session_id() !== '' && $_SESSION['user']['id'] == $values['id']) {
if (Session::isOpen() && $_SESSION['user']['id'] == $values['id']) {
$this->updateSession();
}
@ -382,60 +453,4 @@ class User extends Base
return array(false, $v->getErrors());
}
/**
* Get the user agent of the connected user
*
* @access public
* @return string
*/
public function getUserAgent()
{
return empty($_SERVER['HTTP_USER_AGENT']) ? t('Unknown') : $_SERVER['HTTP_USER_AGENT'];
}
/**
* Get the real IP address of the connected user
*
* @access public
* @param bool $only_public Return only public IP address
* @return string
*/
public function getIpAddress($only_public = false)
{
$keys = array(
'HTTP_CLIENT_IP',
'HTTP_X_FORWARDED_FOR',
'HTTP_X_FORWARDED',
'HTTP_X_CLUSTER_CLIENT_IP',
'HTTP_FORWARDED_FOR',
'HTTP_FORWARDED',
'REMOTE_ADDR'
);
foreach ($keys as $key) {
if (isset($_SERVER[$key])) {
foreach (explode(',', $_SERVER[$key]) as $ip_address) {
$ip_address = trim($ip_address);
if ($only_public) {
// Return only public IP address
if (filter_var($ip_address, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) {
return $ip_address;
}
}
else {
return $ip_address;
}
}
}
}
return t('Unknown');
}
}

View file

@ -64,9 +64,9 @@ class Webhook extends Base
*/
public function attachEvents()
{
$this->url_task_creation = $this->config->get('webhooks_url_task_creation');
$this->url_task_modification = $this->config->get('webhooks_url_task_modification');
$this->token = $this->config->get('webhooks_token');
$this->url_task_creation = $this->config->get('webhook_url_task_creation');
$this->url_task_modification = $this->config->get('webhook_url_task_modification');
$this->token = $this->config->get('webhook_token');
if ($this->url_task_creation) {
$this->attachCreateEvents();
@ -88,9 +88,13 @@ class Webhook extends Base
Task::EVENT_UPDATE,
Task::EVENT_CLOSE,
Task::EVENT_OPEN,
Task::EVENT_MOVE_COLUMN,
Task::EVENT_MOVE_POSITION,
Task::EVENT_ASSIGNEE_CHANGE,
);
$listener = new WebhookListener($this->url_task_modification, $this);
$listener = new WebhookListener($this->registry);
$listener->setUrl($this->url_task_modification);
foreach ($events as $event_name) {
$this->event->attach($event_name, $listener);
@ -104,7 +108,10 @@ class Webhook extends Base
*/
public function attachCreateEvents()
{
$this->event->attach(Task::EVENT_CREATE, new WebhookListener($this->url_task_creation, $this));
$listener = new WebhookListener($this->registry);
$listener->setUrl($this->url_task_creation);
$this->event->attach(Task::EVENT_CREATE, $listener);
}
/**

View file

@ -2,9 +2,98 @@
namespace Schema;
use PDO;
use Core\Security;
const VERSION = 27;
const VERSION = 34;
function version_34($pdo)
{
$pdo->exec("ALTER TABLE projects ADD COLUMN is_everybody_allowed TINYINT(1) DEFAULT '0'");
}
function version_33($pdo)
{
$pdo->exec("
CREATE TABLE project_activities (
id INT NOT NULL AUTO_INCREMENT,
date_creation INT NOT NULL,
event_name VARCHAR(50) NOT NULL,
creator_id INT,
project_id INT,
task_id INT,
data TEXT,
PRIMARY KEY(id),
FOREIGN KEY(creator_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE,
FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE
) ENGINE=InnoDB CHARSET=utf8
");
$pdo->exec('DROP TABLE task_has_events');
$pdo->exec('DROP TABLE comment_has_events');
$pdo->exec('DROP TABLE subtask_has_events');
}
function version_32($pdo)
{
$pdo->exec("ALTER TABLE tasks ADD COLUMN date_started INTEGER");
$pdo->exec("ALTER TABLE tasks ADD COLUMN time_spent FLOAT DEFAULT 0");
$pdo->exec("ALTER TABLE tasks ADD COLUMN time_estimated FLOAT DEFAULT 0");
$pdo->exec("ALTER TABLE task_has_subtasks MODIFY time_estimated FLOAT");
$pdo->exec("ALTER TABLE task_has_subtasks MODIFY time_spent FLOAT");
}
function version_31($pdo)
{
$pdo->exec("ALTER TABLE projects ADD COLUMN is_private TINYINT(1) DEFAULT '0'");
}
function version_30($pdo)
{
$rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)');
$rq->execute(array('application_date_format', 'm/d/Y'));
}
function version_29($pdo)
{
$pdo->exec("
CREATE TABLE settings (
`option` VARCHAR(100) PRIMARY KEY,
`value` VARCHAR(255) DEFAULT ''
)
");
// Migrate old config parameters
$rq = $pdo->prepare('SELECT * FROM config');
$rq->execute();
$parameters = $rq->fetch(PDO::FETCH_ASSOC);
$rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)');
$rq->execute(array('board_highlight_period', defined('RECENT_TASK_PERIOD') ? RECENT_TASK_PERIOD : 48*60*60));
$rq->execute(array('board_public_refresh_interval', defined('BOARD_PUBLIC_CHECK_INTERVAL') ? BOARD_PUBLIC_CHECK_INTERVAL : 60));
$rq->execute(array('board_private_refresh_interval', defined('BOARD_CHECK_INTERVAL') ? BOARD_CHECK_INTERVAL : 10));
$rq->execute(array('board_columns', $parameters['default_columns']));
$rq->execute(array('webhook_url_task_creation', $parameters['webhooks_url_task_creation']));
$rq->execute(array('webhook_url_task_modification', $parameters['webhooks_url_task_modification']));
$rq->execute(array('webhook_token', $parameters['webhooks_token']));
$rq->execute(array('api_token', $parameters['api_token']));
$rq->execute(array('application_language', $parameters['language']));
$rq->execute(array('application_timezone', $parameters['timezone']));
$rq->execute(array('application_url', defined('KANBOARD_URL') ? KANBOARD_URL : ''));
$pdo->exec('DROP TABLE config');
}
function version_28($pdo)
{
$pdo->exec("ALTER TABLE tasks ADD COLUMN reference VARCHAR(50) DEFAULT ''");
$pdo->exec("ALTER TABLE comments ADD COLUMN reference VARCHAR(50) DEFAULT ''");
$pdo->exec('CREATE INDEX tasks_reference_idx ON tasks(reference)');
$pdo->exec('CREATE INDEX comments_reference_idx ON comments(reference)');
}
function version_27($pdo)
{
@ -294,7 +383,7 @@ function version_1($pdo)
id INT NOT NULL AUTO_INCREMENT,
task_id INT,
user_id INT,
date INT,
`date` INT,
comment TEXT,
PRIMARY KEY (id),
FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE,

View file

@ -2,9 +2,97 @@
namespace Schema;
use PDO;
use Core\Security;
const VERSION = 8;
const VERSION = 15;
function version_15($pdo)
{
$pdo->exec("ALTER TABLE projects ADD COLUMN is_everybody_allowed BOOLEAN DEFAULT '0'");
}
function version_14($pdo)
{
$pdo->exec("
CREATE TABLE project_activities (
id SERIAL PRIMARY KEY,
date_creation INTEGER NOT NULL,
event_name VARCHAR(50) NOT NULL,
creator_id INTEGER,
project_id INTEGER,
task_id INTEGER,
data TEXT,
FOREIGN KEY(creator_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE,
FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE
)
");
$pdo->exec('DROP TABLE task_has_events');
$pdo->exec('DROP TABLE comment_has_events');
$pdo->exec('DROP TABLE subtask_has_events');
}
function version_13($pdo)
{
$pdo->exec("ALTER TABLE tasks ADD COLUMN date_started INTEGER");
$pdo->exec("ALTER TABLE tasks ADD COLUMN time_spent FLOAT DEFAULT 0");
$pdo->exec("ALTER TABLE tasks ADD COLUMN time_estimated FLOAT DEFAULT 0");
$pdo->exec("ALTER TABLE task_has_subtasks ALTER COLUMN time_estimated TYPE FLOAT");
$pdo->exec("ALTER TABLE task_has_subtasks ALTER COLUMN time_spent TYPE FLOAT");
}
function version_12($pdo)
{
$pdo->exec("ALTER TABLE projects ADD COLUMN is_private BOOLEAN DEFAULT '0'");
}
function version_11($pdo)
{
$rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)');
$rq->execute(array('application_date_format', 'm/d/Y'));
}
function version_10($pdo)
{
$pdo->exec("
CREATE TABLE settings (
option VARCHAR(100) PRIMARY KEY,
value VARCHAR(255) DEFAULT ''
)
");
// Migrate old config parameters
$rq = $pdo->prepare('SELECT * FROM config');
$rq->execute();
$parameters = $rq->fetch(PDO::FETCH_ASSOC);
$rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)');
$rq->execute(array('board_highlight_period', defined('RECENT_TASK_PERIOD') ? RECENT_TASK_PERIOD : 48*60*60));
$rq->execute(array('board_public_refresh_interval', defined('BOARD_PUBLIC_CHECK_INTERVAL') ? BOARD_PUBLIC_CHECK_INTERVAL : 60));
$rq->execute(array('board_private_refresh_interval', defined('BOARD_CHECK_INTERVAL') ? BOARD_CHECK_INTERVAL : 10));
$rq->execute(array('board_columns', $parameters['default_columns']));
$rq->execute(array('webhook_url_task_creation', $parameters['webhooks_url_task_creation']));
$rq->execute(array('webhook_url_task_modification', $parameters['webhooks_url_task_modification']));
$rq->execute(array('webhook_token', $parameters['webhooks_token']));
$rq->execute(array('api_token', $parameters['api_token']));
$rq->execute(array('application_language', $parameters['language']));
$rq->execute(array('application_timezone', $parameters['timezone']));
$rq->execute(array('application_url', defined('KANBOARD_URL') ? KANBOARD_URL : ''));
$pdo->exec('DROP TABLE config');
}
function version_9($pdo)
{
$pdo->exec("ALTER TABLE tasks ADD COLUMN reference VARCHAR(50) DEFAULT ''");
$pdo->exec("ALTER TABLE comments ADD COLUMN reference VARCHAR(50) DEFAULT ''");
$pdo->exec('CREATE INDEX tasks_reference_idx ON tasks(reference)');
$pdo->exec('CREATE INDEX comments_reference_idx ON comments(reference)');
}
function version_8($pdo)
{
@ -22,7 +110,7 @@ function version_6($pdo)
CREATE TABLE task_has_events (
id SERIAL PRIMARY KEY,
date_creation INTEGER NOT NULL,
event_name TEXT NOT NULL,
event_name VARCHAR(50) NOT NULL,
creator_id INTEGER,
project_id INTEGER,
task_id INTEGER,
@ -37,7 +125,7 @@ function version_6($pdo)
CREATE TABLE subtask_has_events (
id SERIAL PRIMARY KEY,
date_creation INTEGER NOT NULL,
event_name TEXT NOT NULL,
event_name VARCHAR(50) NOT NULL,
creator_id INTEGER,
project_id INTEGER,
subtask_id INTEGER,
@ -54,7 +142,7 @@ function version_6($pdo)
CREATE TABLE comment_has_events (
id SERIAL PRIMARY KEY,
date_creation INTEGER NOT NULL,
event_name TEXT NOT NULL,
event_name VARCHAR(50) NOT NULL,
creator_id INTEGER,
project_id INTEGER,
comment_id INTEGER,

View file

@ -3,8 +3,93 @@
namespace Schema;
use Core\Security;
use PDO;
const VERSION = 27;
const VERSION = 34;
function version_34($pdo)
{
$pdo->exec('ALTER TABLE projects ADD COLUMN is_everybody_allowed INTEGER DEFAULT "0"');
}
function version_33($pdo)
{
$pdo->exec("
CREATE TABLE project_activities (
id INTEGER PRIMARY KEY,
date_creation INTEGER NOT NULL,
event_name TEXT NOT NULL,
creator_id INTEGER,
project_id INTEGER,
task_id INTEGER,
data TEXT,
FOREIGN KEY(creator_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE,
FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE
)
");
$pdo->exec('DROP TABLE task_has_events');
$pdo->exec('DROP TABLE comment_has_events');
$pdo->exec('DROP TABLE subtask_has_events');
}
function version_32($pdo)
{
$pdo->exec("ALTER TABLE tasks ADD COLUMN date_started INTEGER");
$pdo->exec("ALTER TABLE tasks ADD COLUMN time_spent NUMERIC DEFAULT 0");
$pdo->exec("ALTER TABLE tasks ADD COLUMN time_estimated NUMERIC DEFAULT 0");
}
function version_31($pdo)
{
$pdo->exec('ALTER TABLE projects ADD COLUMN is_private INTEGER DEFAULT "0"');
}
function version_30($pdo)
{
$rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)');
$rq->execute(array('application_date_format', 'm/d/Y'));
}
function version_29($pdo)
{
$pdo->exec("
CREATE TABLE settings (
option TEXT PRIMARY KEY,
value TEXT DEFAULT ''
)
");
// Migrate old config parameters
$rq = $pdo->prepare('SELECT * FROM config');
$rq->execute();
$parameters = $rq->fetch(PDO::FETCH_ASSOC);
$rq = $pdo->prepare('INSERT INTO settings VALUES (?, ?)');
$rq->execute(array('board_highlight_period', defined('RECENT_TASK_PERIOD') ? RECENT_TASK_PERIOD : 48*60*60));
$rq->execute(array('board_public_refresh_interval', defined('BOARD_PUBLIC_CHECK_INTERVAL') ? BOARD_PUBLIC_CHECK_INTERVAL : 60));
$rq->execute(array('board_private_refresh_interval', defined('BOARD_CHECK_INTERVAL') ? BOARD_CHECK_INTERVAL : 10));
$rq->execute(array('board_columns', $parameters['default_columns']));
$rq->execute(array('webhook_url_task_creation', $parameters['webhooks_url_task_creation']));
$rq->execute(array('webhook_url_task_modification', $parameters['webhooks_url_task_modification']));
$rq->execute(array('webhook_token', $parameters['webhooks_token']));
$rq->execute(array('api_token', $parameters['api_token']));
$rq->execute(array('application_language', $parameters['language']));
$rq->execute(array('application_timezone', $parameters['timezone']));
$rq->execute(array('application_url', defined('KANBOARD_URL') ? KANBOARD_URL : ''));
$pdo->exec('DROP TABLE config');
}
function version_28($pdo)
{
$pdo->exec("ALTER TABLE tasks ADD COLUMN reference TEXT DEFAULT ''");
$pdo->exec("ALTER TABLE comments ADD COLUMN reference TEXT DEFAULT ''");
$pdo->exec('CREATE INDEX tasks_reference_idx ON tasks(reference)');
$pdo->exec('CREATE INDEX comments_reference_idx ON comments(reference)');
}
function version_27($pdo)
{
@ -118,8 +203,8 @@ function version_18($pdo)
id INTEGER PRIMARY KEY,
title TEXT COLLATE NOCASE,
status INTEGER DEFAULT 0,
time_estimated INTEGER DEFAULT 0,
time_spent INTEGER DEFAULT 0,
time_estimated NUMERIC DEFAULT 0,
time_spent NUMERIC DEFAULT 0,
task_id INTEGER NOT NULL,
user_id INTEGER,
FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE

View file

@ -0,0 +1,22 @@
<div class="page-header">
<h2><?= t('Automatic actions for the project "%s"', $project['name']) ?></h2>
</div>
<h3><?= t('Choose an event') ?></h3>
<form method="post" action="?controller=action&amp;action=params&amp;project_id=<?= $project['id'] ?>" autocomplete="off">
<?= Helper\form_csrf() ?>
<?= Helper\form_hidden('project_id', $values) ?>
<?= Helper\form_hidden('action_name', $values) ?>
<?= Helper\form_label(t('Event'), 'event_name') ?>
<?= Helper\form_select('event_name', $events, $values) ?><br/>
<div class="form-help">
<?= t('When the selected event occurs execute the corresponding action.') ?>
</div>
<div class="form-actions">
<input type="submit" value="<?= t('Next step') ?>" class="btn btn-blue"/>
<?= t('or') ?> <a href="?controller=action&amp;action=index&amp;project_id=<?= $project['id'] ?>"><?= t('cancel') ?></a>
</div>
</form>

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