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

Update to kanboard v1.0.27

This commit is contained in:
mbugeia 2016-04-27 20:50:42 +02:00
parent 00a82fdf05
commit fff59a2e84
570 changed files with 9329 additions and 8452 deletions

View file

@ -1,11 +1,56 @@
Version 1.0.27
--------------
New features:
* Added Markdown editor
* Added user avatars with pluggable system
- Default is a letter based avatar
- Gravatar
- Avatar Image upload
* Added Korean translation
Improvements:
* Added more logging for LDAP client
* Improve schema migration process
* Improve notification configuration form
* Handle state in OAuth2 client
* Allow to use the original template in overridden templates
* Unification of the project header
* Refactoring of Javascript code
* Improve comments design
* Improve task summary sections
* Put back the action sidebar in task view
* Added support for multiple placeholders for LDAP_USER_FILTER
* Added local file link provider
* Show configuration in settings page
* Added "?" to display list of keyboard shortcuts
* Added new keyboard shortcuts for task view
* Always display project name and task title in task views
* Improve automatic action creation
* Move notifications to the bottom of the screen
* Added the possibility to import automatic actions from another project
* Added Ajax loading icon for submit buttons
* Added support for HTTP header "X-Forwarded-Proto: https"
Bug fixes:
* Fix bad unique constraints in Mysql table user_has_notifications
* Force integer type for aggregated metrics (Burndown chart concat values instead of summing)
* Fixes cycle time calculation when the start date is defined in the future
* Access allowed to any tasks from the shared public board by changing the URL parameters
* Fix invalid user filter for API procedure createLdapUser()
* Ambiguous column name with very old version of Sqlite
Version 1.0.26
--------------
Breaking changes:
* API procedures:
- "moveColumnUp" and "moveColumnDown" are replace by "changeColumnPosition"
- "moveSwimlaneUp" and "moveSwimlaneDown" are replace by "changeSwimlanePosition"
- "moveColumnUp" and "moveColumnDown" are replaced by "changeColumnPosition"
- "moveSwimlaneUp" and "moveSwimlaneDown" are replaced by "changeSwimlanePosition"
New features:

View file

@ -132,6 +132,7 @@ abstract class Base extends \Kanboard\Core\Base
* Set project id
*
* @access public
* @param integer $project_id
* @return Base
*/
public function setProjectId($project_id)
@ -157,7 +158,7 @@ abstract class Base extends \Kanboard\Core\Base
* @access public
* @param string $name Parameter name
* @param mixed $value Value
* @param Base
* @return Base
*/
public function setParam($name, $value)
{
@ -271,6 +272,7 @@ abstract class Base extends \Kanboard\Core\Base
* @access public
* @param string $event
* @param string $description
* @return Base
*/
public function addEvent($event, $description = '')
{

View file

@ -85,20 +85,22 @@ class AverageLeadCycleTimeAnalytic extends Base
*/
private function calculateCycleTime(array &$task)
{
if (empty($task['date_started'])) {
return 0;
$end = (int) $task['date_completed'] ?: time();
$start = (int) $task['date_started'];
// Start date can be in the future when defined with the Gantt chart
if ($start > 0 && $end > $start) {
return $end - $start;
}
$end = $task['date_completed'] ?: time();
$start = $task['date_started'];
return $end - $start;
return 0;
}
/**
* Get the 1000 last created tasks
*
* @access private
* @param integer $project_id
* @return array
*/
private function getTasks($project_id)

View file

@ -126,6 +126,7 @@ class AverageTimeSpentColumnAnalytic extends Base
*
* @access private
* @param array $task
* @return integer
*/
private function getTaskTimeSpentInCurrentColumn(array &$task)
{

View file

@ -31,6 +31,7 @@ class Auth extends Base
} elseif ($this->isAppAuthenticated($username, $password)) {
$this->checkProcedurePermission(false, $method);
} else {
$this->logger->error('API authentication failure for '.$username);
throw new AuthenticationFailure('Wrong credentials');
}
}

View file

@ -66,12 +66,29 @@ class User extends \Kanboard\Core\Base
return $valid ? $this->user->create($values) : false;
}
/**
* Create LDAP user in the database
*
* Only "anonymous" and "proxy" LDAP authentication are supported by this method
*
* User information will be fetched from the LDAP server
*
* @access public
* @param string $username
* @return bool|int
*/
public function createLdapUser($username)
{
if (LDAP_BIND_TYPE === 'user') {
$this->logger->error('LDAP authentication "user" is not supported by this API call');
return false;
}
try {
$ldap = LdapClient::connect();
$user = LdapUser::getUser($ldap, sprintf(LDAP_USER_FILTER, $username));
$ldap->setLogger($this->logger);
$user = LdapUser::getUser($ldap, $username);
if ($user === null) {
$this->logger->info('User not found in LDAP server');

View file

@ -63,10 +63,12 @@ class LdapAuth extends Base implements PasswordAuthenticationProviderInterface
try {
$client = LdapClient::connect($this->getLdapUsername(), $this->getLdapPassword());
$client->setLogger($this->logger);
$user = LdapUser::getUser($client, $this->username);
if ($user === null) {
$this->logger->info('User not found in LDAP server');
$this->logger->info('User ('.$this->username.') not found in LDAP server');
return false;
}
@ -74,6 +76,8 @@ class LdapAuth extends Base implements PasswordAuthenticationProviderInterface
throw new LogicException('Username not found in LDAP profile, check the parameter LDAP_USER_ATTRIBUTE_USERNAME');
}
$this->logger->info('Authenticate user: '.$user->getDn());
if ($client->authenticate($user->getDn(), $this->password)) {
$this->userInfo = $user;
return true;

View file

@ -11,18 +11,18 @@ use Symfony\Component\Console\Command\Command;
* @package console
* @author Frederic Guillot
*
* @property \Kanboard\Export\SubtaskExport $subtaskExport
* @property \Kanboard\Export\TaskExport $taskExport
* @property \Kanboard\Export\TransitionExport $transitionExport
* @property \Kanboard\Model\Notification $notification
* @property \Kanboard\Model\Project $project
* @property \Kanboard\Model\ProjectPermission $projectPermission
* @property \Kanboard\Model\ProjectAnalytic $projectAnalytic
* @property \Kanboard\Model\ProjectDailyColumnStats $projectDailyColumnStats
* @property \Kanboard\Model\ProjectDailyStats $projectDailyStats
* @property \Kanboard\Model\SubtaskExport $subtaskExport
* @property \Kanboard\Model\OverdueNotification $overdueNotification
* @property \Kanboard\Model\Task $task
* @property \Kanboard\Model\TaskExport $taskExport
* @property \Kanboard\Model\TaskFinder $taskFinder
* @property \Kanboard\Model\Transition $transition
* @property \Kanboard\Model\UserNotification $userNotification
* @property \Kanboard\Model\UserNotificationFilter $userNotificationFilter
* @property \Symfony\Component\EventDispatcher\EventDispatcher $dispatcher
*/
abstract class Base extends Command

View file

@ -2,6 +2,7 @@
namespace Kanboard\Console;
use Kanboard\Model\Task;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
@ -19,7 +20,7 @@ class TaskOverdueNotification extends Base
protected function execute(InputInterface $input, OutputInterface $output)
{
$tasks = $this->overdueNotification->sendOverdueTaskNotifications();
$tasks = $this->sendOverdueTaskNotifications();
if ($input->getOption('show')) {
$this->showTable($output, $tasks);
@ -47,4 +48,69 @@ class TaskOverdueNotification extends Base
->setRows($rows)
->render();
}
/**
* Send overdue tasks
*
* @access public
*/
public function sendOverdueTaskNotifications()
{
$tasks = $this->taskFinder->getOverdueTasks();
foreach ($this->groupByColumn($tasks, 'project_id') as $project_id => $project_tasks) {
$users = $this->userNotification->getUsersWithNotificationEnabled($project_id);
foreach ($users as $user) {
$this->sendUserOverdueTaskNotifications($user, $project_tasks);
}
}
return $tasks;
}
/**
* Send overdue tasks for a given user
*
* @access public
* @param array $user
* @param array $tasks
*/
public function sendUserOverdueTaskNotifications(array $user, array $tasks)
{
$user_tasks = array();
foreach ($tasks as $task) {
if ($this->userNotificationFilter->shouldReceiveNotification($user, array('task' => $task))) {
$user_tasks[] = $task;
}
}
if (! empty($user_tasks)) {
$this->userNotification->sendUserNotification(
$user,
Task::EVENT_OVERDUE,
array('tasks' => $user_tasks, 'project_name' => $tasks[0]['project_name'])
);
}
}
/**
* Group a collection of records by a column
*
* @access public
* @param array $collection
* @param string $column
* @return array
*/
public function groupByColumn(array $collection, $column)
{
$result = array();
foreach ($collection as $item) {
$result[$item[$column]][] = $item;
}
return $result;
}
}

View file

@ -21,7 +21,7 @@ class TransitionExport extends Base
protected function execute(InputInterface $input, OutputInterface $output)
{
$data = $this->transition->export(
$data = $this->transitionExport->export(
$input->getArgument('project_id'),
$input->getArgument('start_date'),
$input->getArgument('end_date')

View file

@ -3,7 +3,7 @@
namespace Kanboard\Controller;
/**
* Automatic actions management
* Automatic Actions
*
* @package controller
* @author Frederic Guillot
@ -37,98 +37,6 @@ class Action extends Base
)));
}
/**
* Choose the event according to the action (step 2)
*
* @access public
*/
public function event()
{
$project = $this->getProject();
$values = $this->request->getValues();
if (empty($values['action_name']) || empty($values['project_id'])) {
$this->response->redirect($this->helper->url->to('action', 'index', array('project_id' => $project['id'])));
}
$this->response->html($this->helper->layout->project('action/event', array(
'values' => $values,
'project' => $project,
'events' => $this->actionManager->getCompatibleEvents($values['action_name']),
'title' => t('Automatic actions')
)));
}
/**
* Define action parameters (step 3)
*
* @access public
*/
public function params()
{
$project = $this->getProject();
$values = $this->request->getValues();
if (empty($values['action_name']) || empty($values['project_id']) || empty($values['event_name'])) {
$this->response->redirect($this->helper->url->to('action', 'index', array('project_id' => $project['id'])));
}
$action = $this->actionManager->getAction($values['action_name']);
$action_params = $action->getActionRequiredParameters();
if (empty($action_params)) {
$this->doCreation($project, $values + array('params' => array()));
}
$projects_list = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId());
unset($projects_list[$project['id']]);
$this->response->html($this->helper->layout->project('action/params', array(
'values' => $values,
'action_params' => $action_params,
'columns_list' => $this->column->getList($project['id']),
'users_list' => $this->projectUserRole->getAssignableUsersList($project['id']),
'projects_list' => $projects_list,
'colors_list' => $this->color->getList(),
'categories_list' => $this->category->getList($project['id']),
'links_list' => $this->link->getList(0, false),
'project' => $project,
'title' => t('Automatic actions')
)));
}
/**
* Create a new action (last step)
*
* @access public
*/
public function create()
{
$this->doCreation($this->getProject(), $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->actionValidator->validateCreation($values);
if ($valid) {
if ($this->action->create($values) !== false) {
$this->flash->success(t('Your automatic action have been created successfully.'));
} else {
$this->flash->failure(t('Unable to create your automatic action.'));
}
}
$this->response->redirect($this->helper->url->to('action', 'index', array('project_id' => $project['id'])));
}
/**
* Confirmation dialog before removing an action
*

View file

@ -0,0 +1,121 @@
<?php
namespace Kanboard\Controller;
/**
* Action Creation
*
* @package controller
* @author Frederic Guillot
*/
class ActionCreation extends Base
{
/**
* Show the form (step 1)
*
* @access public
*/
public function create()
{
$project = $this->getProject();
$this->response->html($this->template->render('action_creation/create', array(
'project' => $project,
'values' => array('project_id' => $project['id']),
'available_actions' => $this->actionManager->getAvailableActions(),
)));
}
/**
* Choose the event according to the action (step 2)
*
* @access public
*/
public function event()
{
$project = $this->getProject();
$values = $this->request->getValues();
if (empty($values['action_name']) || empty($values['project_id'])) {
return $this->create();
}
$this->response->html($this->template->render('action_creation/event', array(
'values' => $values,
'project' => $project,
'available_actions' => $this->actionManager->getAvailableActions(),
'events' => $this->actionManager->getCompatibleEvents($values['action_name']),
)));
}
/**
* Define action parameters (step 3)
*
* @access public
*/
public function params()
{
$project = $this->getProject();
$values = $this->request->getValues();
if (empty($values['action_name']) || empty($values['project_id']) || empty($values['event_name'])) {
return $this->create();
}
$action = $this->actionManager->getAction($values['action_name']);
$action_params = $action->getActionRequiredParameters();
if (empty($action_params)) {
$this->doCreation($project, $values + array('params' => array()));
}
$projects_list = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId());
unset($projects_list[$project['id']]);
$this->response->html($this->template->render('action_creation/params', array(
'values' => $values,
'action_params' => $action_params,
'columns_list' => $this->column->getList($project['id']),
'users_list' => $this->projectUserRole->getAssignableUsersList($project['id']),
'projects_list' => $projects_list,
'colors_list' => $this->color->getList(),
'categories_list' => $this->category->getList($project['id']),
'links_list' => $this->link->getList(0, false),
'project' => $project,
'available_actions' => $this->actionManager->getAvailableActions(),
'events' => $this->actionManager->getCompatibleEvents($values['action_name']),
)));
}
/**
* Save the action (last step)
*
* @access public
*/
public function save()
{
$this->doCreation($this->getProject(), $this->request->getValues());
}
/**
* Common method to 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->actionValidator->validateCreation($values);
if ($valid) {
if ($this->action->create($values) !== false) {
$this->flash->success(t('Your automatic action have been created successfully.'));
} else {
$this->flash->failure(t('Unable to create your automatic action.'));
}
}
$this->response->redirect($this->helper->url->to('action', 'index', array('project_id' => $project['id'])));
}
}

View file

@ -0,0 +1,38 @@
<?php
namespace Kanboard\Controller;
/**
* Duplicate automatic action from another project
*
* @package controller
* @author Frederic Guillot
*/
class ActionProject extends Base
{
public function project()
{
$project = $this->getProject();
$projects = $this->projectUserRole->getProjectsByUser($this->userSession->getId());
unset($projects[$project['id']]);
$this->response->html($this->template->render('action_project/project', array(
'project' => $project,
'projects_list' => $projects,
)));
}
public function save()
{
$project = $this->getProject();
$src_project_id = $this->request->getValue('src_project_id');
if ($this->action->duplicate($src_project_id, $project['id'])) {
$this->flash->success(t('Actions duplicated successfully.'));
} else {
$this->flash->failure(t('Unable to duplicate actions.'));
}
$this->response->redirect($this->helper->url->to('action', 'index', array('project_id' => $project['id'])));
}
}

View file

@ -38,6 +38,7 @@ class Activity extends Base
$this->response->html($this->helper->layout->task('activity/task', array(
'title' => $task['title'],
'task' => $task,
'project' => $this->project->getById($task['project_id']),
'events' => $this->projectActivity->getTask($task['id']),
)));
}

View file

@ -44,8 +44,7 @@ class Analytic extends Base
public function compareHours()
{
$project = $this->getProject();
$params = $this->getProjectFilters('analytic', 'compareHours');
$query = $this->taskFilter->create()->filterByProject($params['project']['id'])->getQuery();
$query = $this->taskFilter->create()->filterByProject($project['id'])->getQuery();
$paginator = $this->paginator
->setUrl('analytic', 'compareHours', array('project_id' => $project['id']))

View file

@ -2,6 +2,7 @@
namespace Kanboard\Controller;
use Kanboard\Model\Project as ProjectModel;
use Kanboard\Model\Subtask as SubtaskModel;
/**
@ -19,13 +20,14 @@ class App extends Base
* @param integer $user_id
* @param string $action
* @param integer $max
* @return \Kanboard\Core\Paginator
*/
private function getProjectPaginator($user_id, $action, $max)
{
return $this->paginator
->setUrl('app', $action, array('pagination' => 'projects', 'user_id' => $user_id))
->setMax($max)
->setOrder('name')
->setOrder(ProjectModel::TABLE.'.name')
->setQuery($this->project->getQueryColumnStats($this->projectPermission->getActiveProjectIds($user_id)))
->calculateOnlyIf($this->request->getStringParam('pagination') === 'projects');
}
@ -37,6 +39,7 @@ class App extends Base
* @param integer $user_id
* @param string $action
* @param integer $max
* @return \Kanboard\Core\Paginator
*/
private function getTaskPaginator($user_id, $action, $max)
{
@ -55,6 +58,7 @@ class App extends Base
* @param integer $user_id
* @param string $action
* @param integer $max
* @return \Kanboard\Core\Paginator
*/
private function getSubtaskPaginator($user_id, $action, $max)
{

View file

@ -14,6 +14,8 @@ class Auth extends Base
* Display the form login
*
* @access public
* @param array $values
* @param array $errors
*/
public function login(array $values = array(), array $errors = array())
{

View file

@ -0,0 +1,92 @@
<?php
namespace Kanboard\Controller;
use Kanboard\Core\ObjectStorage\ObjectStorageException;
use Kanboard\Core\Thumbnail;
/**
* Avatar File Controller
*
* @package controller
* @author Frederic Guillot
*/
class AvatarFile extends Base
{
/**
* Display avatar page
*/
public function show()
{
$user = $this->getUser();
$this->response->html($this->helper->layout->user('avatar_file/show', array(
'user' => $user,
)));
}
/**
* Upload Avatar
*/
public function upload()
{
$user = $this->getUser();
if (! $this->avatarFile->uploadFile($user['id'], $this->request->getFileInfo('avatar'))) {
$this->flash->failure(t('Unable to upload the file.'));
}
$this->response->redirect($this->helper->url->to('AvatarFile', 'show', array('user_id' => $user['id'])));
}
/**
* Remove Avatar image
*/
public function remove()
{
$this->checkCSRFParam();
$user = $this->getUser();
$this->avatarFile->remove($user['id']);
$this->response->redirect($this->helper->url->to('AvatarFile', 'show', array('user_id' => $user['id'])));
}
/**
* Show Avatar image (public)
*/
public function image()
{
$user_id = $this->request->getIntegerParam('user_id');
$size = $this->request->getStringParam('size', 48);
$filename = $this->avatarFile->getFilename($user_id);
$etag = md5($filename.$size);
$this->response->cache(365 * 86400, $etag);
$this->response->contentType('image/jpeg');
if ($this->request->getHeader('If-None-Match') !== '"'.$etag.'"') {
$this->render($filename, $size);
} else {
$this->response->status(304);
}
}
/**
* Render thumbnail from object storage
*
* @access private
* @param string $filename
* @param integer $size
*/
private function render($filename, $size)
{
try {
$blob = $this->objectStorage->get($filename);
Thumbnail::createFromString($blob)
->resize($size, $size)
->toOutput();
} catch (ObjectStorageException $e) {
$this->logger->error($e->getMessage());
}
}
}

View file

@ -287,60 +287,4 @@ abstract class Base extends \Kanboard\Core\Base
return $subtask;
}
/**
* Common method to get project filters
*
* @access protected
* @param string $controller
* @param string $action
* @return array
*/
protected function getProjectFilters($controller, $action)
{
$project = $this->getProject();
$search = $this->request->getStringParam('search', $this->userSession->getFilters($project['id']));
$board_selector = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId());
unset($board_selector[$project['id']]);
$filters = array(
'controller' => $controller,
'action' => $action,
'project_id' => $project['id'],
'search' => urldecode($search),
);
$this->userSession->setFilters($project['id'], $filters['search']);
return array(
'project' => $project,
'board_selector' => $board_selector,
'filters' => $filters,
'title' => $project['name'],
'description' => $this->getProjectDescription($project),
);
}
/**
* Get project description
*
* @access protected
* @param array &$project
* @return string
*/
protected function getProjectDescription(array &$project)
{
if ($project['owner_id'] > 0) {
$description = t('Project owner: ').'**'.$this->template->e($project['owner_name'] ?: $project['owner_username']).'**'.PHP_EOL.PHP_EOL;
if (! empty($project['description'])) {
$description .= '***'.PHP_EOL.PHP_EOL;
$description .= $project['description'];
}
} else {
$description = $project['description'];
}
return $description;
}
}

View file

@ -47,16 +47,17 @@ class Board extends Base
*/
public function show()
{
$params = $this->getProjectFilters('board', 'show');
$project = $this->getProject();
$search = $this->helper->projectHeader->getSearchQuery($project);
$this->response->html($this->helper->layout->app('board/view_private', array(
'categories_list' => $this->category->getList($params['project']['id'], false),
'users_list' => $this->projectUserRole->getAssignableUsersList($params['project']['id'], false),
'custom_filters_list' => $this->customFilter->getAll($params['project']['id'], $this->userSession->getId()),
'swimlanes' => $this->taskFilter->search($params['filters']['search'])->getBoard($params['project']['id']),
'swimlanes' => $this->taskFilter->search($search)->getBoard($project['id']),
'project' => $project,
'title' => $project['name'],
'description' => $this->helper->projectHeader->getDescription($project),
'board_private_refresh_interval' => $this->config->get('board_private_refresh_interval'),
'board_highlight_period' => $this->config->get('board_highlight_period'),
) + $params));
)));
}
/**

View file

@ -77,6 +77,7 @@ class BoardTooltip extends Base
$task = $this->getTask();
$this->response->html($this->template->render('board/tooltip_comments', array(
'task' => $task,
'comments' => $this->comment->getAll($task['id'], $this->userSession->getCommentSorting())
)));
}

View file

@ -20,9 +20,14 @@ class Calendar extends Base
*/
public function show()
{
$project = $this->getProject();
$this->response->html($this->helper->layout->app('calendar/show', array(
'project' => $project,
'title' => $project['name'],
'description' => $this->helper->projectHeader->getDescription($project),
'check_interval' => $this->config->get('board_private_refresh_interval'),
) + $this->getProjectFilters('calendar', 'show')));
)));
}
/**

View file

@ -76,6 +76,8 @@ class Column extends Base
* Display a form to edit a column
*
* @access public
* @param array $values
* @param array $errors
*/
public function edit(array $values = array(), array $errors = array())
{

View file

@ -47,11 +47,10 @@ class Comment extends Base
);
}
$this->response->html($this->helper->layout->task('comment/create', array(
$this->response->html($this->template->render('comment/create', array(
'values' => $values,
'errors' => $errors,
'task' => $task,
'title' => t('Add a comment'),
)));
}
@ -90,7 +89,7 @@ class Comment extends Base
$task = $this->getTask();
$comment = $this->getComment();
$this->response->html($this->helper->layout->task('comment/edit', array(
$this->response->html($this->template->render('comment/edit', array(
'values' => empty($values) ? $comment : $values,
'errors' => $errors,
'comment' => $comment,
@ -135,7 +134,7 @@ class Comment extends Base
$task = $this->getTask();
$comment = $this->getComment();
$this->response->html($this->helper->layout->task('comment/remove', array(
$this->response->html($this->template->render('comment/remove', array(
'comment' => $comment,
'task' => $task,
'title' => t('Remove a comment')

View file

@ -61,6 +61,8 @@ class Config extends Base
{
$this->response->html($this->helper->layout->config('config/about', array(
'db_size' => $this->config->getDatabaseSize(),
'db_version' => $this->db->getDriver()->getDatabaseVersion(),
'user_agent' => $this->request->getServerVariable('HTTP_USER_AGENT'),
'title' => t('Settings').' &gt; '.t('About'),
)));
}

View file

@ -5,31 +5,13 @@ namespace Kanboard\Controller;
use Parsedown;
/**
* Documentation controller
* Documentation Viewer
*
* @package controller
* @author Frederic Guillot
*/
class Doc extends Base
{
private function readFile($filename)
{
$url = $this->helper->url;
$data = file_get_contents($filename);
list($title, ) = explode("\n", $data, 2);
$replaceUrl = function (array $matches) use ($url) {
return '('.$url->to('doc', 'show', array('file' => str_replace('.markdown', '', $matches[1]))).')';
};
$content = preg_replace_callback('/\((.*.markdown)\)/', $replaceUrl, $data);
return array(
'content' => Parsedown::instance()->text($content),
'title' => $title !== 'Documentation' ? t('Documentation: %s', $title) : $title,
);
}
public function show()
{
$page = $this->request->getStringParam('file', 'index');
@ -38,20 +20,73 @@ class Doc extends Base
$page = 'index';
}
$filenames = array(__DIR__.'/../../doc/'.$page.'.markdown');
$filename = __DIR__.'/../../doc/index.markdown';
if ($this->config->getCurrentLanguage() === 'fr_FR') {
array_unshift($filenames, __DIR__.'/../../doc/fr/'.$page.'.markdown');
$filename = __DIR__.'/../../doc/fr/' . $page . '.markdown';
} else {
$filename = __DIR__ . '/../../doc/' . $page . '.markdown';
}
foreach ($filenames as $file) {
if (file_exists($file)) {
$filename = $file;
break;
}
if (!file_exists($filename)) {
$filename = __DIR__.'/../../doc/index.markdown';
}
$this->response->html($this->helper->layout->app('doc/show', $this->readFile($filename)));
$this->response->html($this->helper->layout->app('doc/show', $this->render($filename)));
}
/**
* Display keyboard shortcut
*/
public function shortcuts()
{
$this->response->html($this->template->render('config/keyboard_shortcuts'));
}
/**
* Prepare Markdown file
*
* @access private
* @param string $filename
* @return array
*/
private function render($filename)
{
$data = file_get_contents($filename);
$content = preg_replace_callback('/\((.*.markdown)\)/', array($this, 'replaceMarkdownUrl'), $data);
$content = preg_replace_callback('/\((screenshots.*\.png)\)/', array($this, 'replaceImageUrl'), $content);
list($title, ) = explode("\n", $data, 2);
return array(
'content' => Parsedown::instance()->text($content),
'title' => $title !== 'Documentation' ? t('Documentation: %s', $title) : $title,
);
}
/**
* Regex callback to replace Markdown links
*
* @access public
* @param array $matches
* @return string
*/
public function replaceMarkdownUrl(array $matches)
{
return '('.$this->helper->url->to('doc', 'show', array('file' => str_replace('.markdown', '', $matches[1]))).')';
}
/**
* Regex callback to replace image links
*
* @access public
* @param array $matches
* @return string
*/
public function replaceImageUrl(array $matches)
{
if ($this->config->getCurrentLanguage() === 'fr_FR') {
return '('.$this->helper->url->base().'doc/fr/'.$matches[1].')';
}
return '('.$this->helper->url->base().'doc/'.$matches[1].')';
}
}

View file

@ -80,6 +80,6 @@ class Export extends Base
*/
public function transitions()
{
$this->common('transition', 'export', t('Transitions'), 'transitions', t('Task transitions export'));
$this->common('transitionExport', 'export', t('Transitions'), 'transitions', t('Task transitions export'));
}
}

View file

@ -66,9 +66,16 @@ class FileViewer extends Base
*/
public function image()
{
try {
$file = $this->getFile();
$etag = md5($file['path']);
$this->response->contentType($this->helper->file->getImageMimeType($file['name']));
$this->response->cache(5 * 86400, $etag);
if ($this->request->getHeader('If-None-Match') === '"'.$etag.'"') {
return $this->response->status(304);
}
try {
$this->objectStorage->output($file['path']);
} catch (ObjectStorageException $e) {
$this->logger->error($e->getMessage());
@ -82,12 +89,21 @@ class FileViewer extends Base
*/
public function thumbnail()
{
$this->response->contentType('image/jpeg');
try {
$file = $this->getFile();
$model = $file['model'];
$this->objectStorage->output($this->$model->getThumbnailPath($file['path']));
$filename = $this->$model->getThumbnailPath($file['path']);
$etag = md5($filename);
$this->response->cache(5 * 86400, $etag);
$this->response->contentType('image/jpeg');
if ($this->request->getHeader('If-None-Match') === '"'.$etag.'"') {
return $this->response->status(304);
}
try {
$this->objectStorage->output($filename);
} catch (ObjectStorageException $e) {
$this->logger->error($e->getMessage());

View file

@ -54,8 +54,9 @@ class Gantt extends Base
*/
public function project()
{
$params = $this->getProjectFilters('gantt', 'project');
$filter = $this->taskFilterGanttFormatter->search($params['filters']['search'])->filterByProject($params['project']['id']);
$project = $this->getProject();
$search = $this->helper->projectHeader->getSearchQuery($project);
$filter = $this->taskFilterGanttFormatter->search($search)->filterByProject($project['id']);
$sorting = $this->request->getStringParam('sorting', 'board');
if ($sorting === 'date') {
@ -64,8 +65,10 @@ class Gantt extends Base
$filter->getQuery()->asc('column_position')->asc(TaskModel::TABLE.'.position');
}
$this->response->html($this->helper->layout->app('gantt/project', $params + array(
'users_list' => $this->projectUserRole->getAssignableUsersList($params['project']['id'], false),
$this->response->html($this->helper->layout->app('gantt/project', array(
'project' => $project,
'title' => $project['name'],
'description' => $this->helper->projectHeader->getDescription($project),
'sorting' => $sorting,
'tasks' => $filter->format(),
)));

View file

@ -19,22 +19,23 @@ class Listing extends Base
*/
public function show()
{
$params = $this->getProjectFilters('listing', 'show');
$query = $this->taskFilter->search($params['filters']['search'])->filterByProject($params['project']['id'])->getQuery();
$project = $this->getProject();
$search = $this->helper->projectHeader->getSearchQuery($project);
$query = $this->taskFilter->search($search)->filterByProject($project['id'])->getQuery();
$paginator = $this->paginator
->setUrl('listing', 'show', array('project_id' => $params['project']['id']))
->setUrl('listing', 'show', array('project_id' => $project['id']))
->setMax(30)
->setOrder(TaskModel::TABLE.'.id')
->setDirection('DESC')
->setQuery($query)
->calculate();
$this->response->html($this->helper->layout->app('listing/show', $params + array(
$this->response->html($this->helper->layout->app('listing/show', array(
'project' => $project,
'title' => $project['name'],
'description' => $this->helper->projectHeader->getDescription($project),
'paginator' => $paginator,
'categories_list' => $this->category->getList($params['project']['id'], false),
'users_list' => $this->projectUserRole->getAssignableUsersList($params['project']['id'], false),
'custom_filters_list' => $this->customFilter->getAll($params['project']['id'], $this->userSession->getId()),
)));
}
}

View file

@ -2,6 +2,8 @@
namespace Kanboard\Controller;
use Kanboard\Core\Security\OAuthAuthenticationProviderInterface;
/**
* OAuth controller
*
@ -10,6 +12,72 @@ namespace Kanboard\Controller;
*/
class Oauth extends Base
{
/**
* Redirect to the provider if no code received
*
* @access private
* @param string $provider
*/
protected function step1($provider)
{
$code = $this->request->getStringParam('code');
$state = $this->request->getStringParam('state');
if (! empty($code)) {
$this->step2($provider, $code, $state);
} else {
$this->response->redirect($this->authenticationManager->getProvider($provider)->getService()->getAuthorizationUrl());
}
}
/**
* Link or authenticate the user
*
* @access protected
* @param string $providerName
* @param string $code
* @param string $state
*/
protected function step2($providerName, $code, $state)
{
$provider = $this->authenticationManager->getProvider($providerName);
$provider->setCode($code);
$hasValidState = $provider->getService()->isValidateState($state);
if ($this->userSession->isLogged()) {
if ($hasValidState) {
$this->link($provider);
} else {
$this->flash->failure(t('The OAuth2 state parameter is invalid'));
$this->response->redirect($this->helper->url->to('user', 'external', array('user_id' => $this->userSession->getId())));
}
} else {
if ($hasValidState) {
$this->authenticate($providerName);
} else {
$this->authenticationFailure(t('The OAuth2 state parameter is invalid'));
}
}
}
/**
* Link the account
*
* @access protected
* @param OAuthAuthenticationProviderInterface $provider
*/
protected function link(OAuthAuthenticationProviderInterface $provider)
{
if (! $provider->authenticate()) {
$this->flash->failure(t('External authentication failed'));
} else {
$this->userProfile->assign($this->userSession->getId(), $provider->getUser());
$this->flash->success(t('Your external account is linked to your profile successfully.'));
}
$this->response->redirect($this->helper->url->to('user', 'external', array('user_id' => $this->userSession->getId())));
}
/**
* Unlink external account
*
@ -29,78 +97,34 @@ class Oauth extends Base
$this->response->redirect($this->helper->url->to('user', 'external', array('user_id' => $this->userSession->getId())));
}
/**
* Redirect to the provider if no code received
*
* @access private
* @param string $provider
*/
protected function step1($provider)
{
$code = $this->request->getStringParam('code');
if (! empty($code)) {
$this->step2($provider, $code);
} else {
$this->response->redirect($this->authenticationManager->getProvider($provider)->getService()->getAuthorizationUrl());
}
}
/**
* Link or authenticate the user
*
* @access protected
* @param string $provider
* @param string $code
*/
protected function step2($provider, $code)
{
$this->authenticationManager->getProvider($provider)->setCode($code);
if ($this->userSession->isLogged()) {
$this->link($provider);
}
$this->authenticate($provider);
}
/**
* Link the account
*
* @access protected
* @param string $provider
*/
protected function link($provider)
{
$authProvider = $this->authenticationManager->getProvider($provider);
if (! $authProvider->authenticate()) {
$this->flash->failure(t('External authentication failed'));
} else {
$this->userProfile->assign($this->userSession->getId(), $authProvider->getUser());
$this->flash->success(t('Your external account is linked to your profile successfully.'));
}
$this->response->redirect($this->helper->url->to('user', 'external', array('user_id' => $this->userSession->getId())));
}
/**
* Authenticate the account
*
* @access protected
* @param string $provider
* @param string $providerName
*/
protected function authenticate($provider)
protected function authenticate($providerName)
{
if ($this->authenticationManager->oauthAuthentication($provider)) {
if ($this->authenticationManager->oauthAuthentication($providerName)) {
$this->response->redirect($this->helper->url->to('app', 'index'));
} else {
$this->authenticationFailure(t('External authentication failed'));
}
}
/**
* Show login failure page
*
* @access protected
* @param string $message
*/
protected function authenticationFailure($message)
{
$this->response->html($this->helper->layout->app('auth/index', array(
'errors' => array('login' => t('External authentication failed')),
'errors' => array('login' => $message),
'values' => array(),
'no_layout' => true,
'title' => t('Login')
)));
}
}
}

View file

@ -67,7 +67,7 @@ class ProjectEdit extends Base
if ($valid) {
if ($this->project->update($values)) {
$this->flash->success(t('Project updated successfully.'));
$this->response->redirect($this->helper->url->to('ProjectEdit', $redirect, array('project_id' => $project['id'])));
$this->response->redirect($this->helper->url->to('ProjectEdit', $redirect, array('project_id' => $project['id'])), true);
} else {
$this->flash->failure(t('Unable to update this project.'));
}

View file

@ -15,15 +15,18 @@ class ProjectOverview extends Base
*/
public function show()
{
$params = $this->getProjectFilters('ProjectOverview', 'show');
$params['users'] = $this->projectUserRole->getAllUsersGroupedByRole($params['project']['id']);
$params['roles'] = $this->role->getProjectRoles();
$params['events'] = $this->projectActivity->getProject($params['project']['id'], 10);
$params['images'] = $this->projectFile->getAllImages($params['project']['id']);
$params['files'] = $this->projectFile->getAllDocuments($params['project']['id']);
$project = $this->getProject();
$this->project->getColumnStats($project);
$this->project->getColumnStats($params['project']);
$this->response->html($this->helper->layout->app('project_overview/show', $params));
$this->response->html($this->helper->layout->app('project_overview/show', array(
'project' => $project,
'title' => $project['name'],
'description' => $this->helper->projectHeader->getDescription($project),
'users' => $this->projectUserRole->getAllUsersGroupedByRole($project['id']),
'roles' => $this->role->getProjectRoles(),
'events' => $this->projectActivity->getProject($project['id'], 10),
'images' => $this->projectFile->getAllImages($project['id']),
'files' => $this->projectFile->getAllDocuments($project['id']),
)));
}
}

View file

@ -10,22 +10,6 @@ namespace Kanboard\Controller;
*/
class Subtask extends Base
{
/**
* Show list of subtasks
*/
public function show()
{
$task = $this->getTask();
$this->response->html($this->helper->layout->task('subtask/show', array(
'users_list' => $this->projectUserRole->getAssignableUsersList($task['project_id']),
'task' => $task,
'project' => $this->getProject(),
'subtasks' => $this->subtask->getAll($task['id']),
'editable' => true,
)));
}
/**
* Creation form
*
@ -42,7 +26,7 @@ class Subtask extends Base
);
}
$this->response->html($this->helper->layout->task('subtask/create', array(
$this->response->html($this->template->render('subtask/create', array(
'values' => $values,
'errors' => $errors,
'users_list' => $this->projectUserRole->getAssignableUsersList($task['project_id']),
@ -89,7 +73,7 @@ class Subtask extends Base
$task = $this->getTask();
$subtask = $this->getSubTask();
$this->response->html($this->helper->layout->task('subtask/edit', array(
$this->response->html($this->template->render('subtask/edit', array(
'values' => empty($values) ? $subtask : $values,
'errors' => $errors,
'users_list' => $this->projectUserRole->getAssignableUsersList($task['project_id']),
@ -135,7 +119,7 @@ class Subtask extends Base
$task = $this->getTask();
$subtask = $this->getSubtask();
$this->response->html($this->helper->layout->task('subtask/remove', array(
$this->response->html($this->template->render('subtask/remove', array(
'subtask' => $subtask,
'task' => $task,
)));

View file

@ -2,6 +2,8 @@
namespace Kanboard\Controller;
use Kanboard\Core\DateParser;
/**
* Task controller
*
@ -21,13 +23,17 @@ class Task extends Base
// Token verification
if (empty($project)) {
$this->forbidden(true);
return $this->forbidden(true);
}
$task = $this->taskFinder->getDetails($this->request->getIntegerParam('task_id'));
if (empty($task)) {
$this->notfound(true);
return $this->notfound(true);
}
if ($task['project_id'] != $project['id']) {
return $this->forbidden(true);
}
$this->response->html($this->helper->layout->app('task/public', array(
@ -62,25 +68,19 @@ class Task extends Base
'time_spent' => $task['time_spent'] ?: '',
);
$values = $this->dateParser->format($values, array('date_started'), $this->config->get('application_datetime_format', 'm/d/Y H:i'));
$values = $this->dateParser->format($values, array('date_started'), $this->config->get('application_datetime_format', DateParser::DATE_TIME_FORMAT));
$this->response->html($this->helper->layout->task('task/show', array(
'task' => $task,
'project' => $this->project->getById($task['project_id']),
'values' => $values,
'files' => $this->taskFile->getAllDocuments($task['id']),
'images' => $this->taskFile->getAllImages($task['id']),
'comments' => $this->comment->getAll($task['id'], $this->userSession->getCommentSorting()),
'subtasks' => $subtasks,
'links' => $this->taskLink->getAllGroupedByLabel($task['id']),
'task' => $task,
'values' => $values,
'internal_links' => $this->taskLink->getAllGroupedByLabel($task['id']),
'external_links' => $this->taskExternalLink->getAll($task['id']),
'link_label_list' => $this->link->getList(0, false),
'columns_list' => $this->column->getList($task['project_id']),
'colors_list' => $this->color->getList(),
'users_list' => $this->projectUserRole->getAssignableUsersList($task['project_id'], true, false, false),
'title' => $task['project_name'].' &gt; '.$task['title'],
'recurrence_trigger_list' => $this->task->getRecurrenceTriggerList(),
'recurrence_timeframe_list' => $this->task->getRecurrenceTimeframeList(),
'recurrence_basedate_list' => $this->task->getRecurrenceBasedateList(),
)));
}
@ -94,8 +94,8 @@ class Task extends Base
$task = $this->getTask();
$this->response->html($this->helper->layout->task('task/analytics', array(
'title' => $task['title'],
'task' => $task,
'project' => $this->project->getById($task['project_id']),
'lead_time' => $this->taskAnalytic->getLeadTime($task),
'cycle_time' => $this->taskAnalytic->getCycleTime($task),
'time_spent_columns' => $this->taskAnalytic->getTimeSpentByColumn($task),
@ -121,6 +121,7 @@ class Task extends Base
$this->response->html($this->helper->layout->task('task/time_tracking_details', array(
'task' => $task,
'project' => $this->project->getById($task['project_id']),
'subtask_paginator' => $subtask_paginator,
)));
}
@ -136,6 +137,7 @@ class Task extends Base
$this->response->html($this->helper->layout->task('task/transitions', array(
'task' => $task,
'project' => $this->project->getById($task['project_id']),
'transitions' => $this->transition->getAllByTask($task['id']),
)));
}
@ -165,7 +167,7 @@ class Task extends Base
$this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $task['project_id'])));
}
$this->response->html($this->helper->layout->task('task/remove', array(
$this->response->html($this->template->render('task/remove', array(
'task' => $task,
)));
}

View file

@ -12,22 +12,6 @@ use Kanboard\Core\ExternalLink\ExternalLinkProviderNotFound;
*/
class TaskExternalLink extends Base
{
/**
* Creation form
*
* @access public
*/
public function show()
{
$task = $this->getTask();
$this->response->html($this->helper->layout->task('task_external_link/show', array(
'links' => $this->taskExternalLink->getAll($task['id']),
'task' => $task,
'title' => t('List of external links'),
)));
}
/**
* First creation form
*
@ -37,7 +21,7 @@ class TaskExternalLink extends Base
{
$task = $this->getTask();
$this->response->html($this->helper->layout->task('task_external_link/find', array(
$this->response->html($this->template->render('task_external_link/find', array(
'values' => $values,
'errors' => $errors,
'task' => $task,
@ -60,7 +44,7 @@ class TaskExternalLink extends Base
$provider = $this->externalLinkManager->setUserInput($values)->find();
$link = $provider->getLink();
$this->response->html($this->helper->layout->task('task_external_link/create', array(
$this->response->html($this->template->render('task_external_link/create', array(
'values' => array(
'title' => $link->getTitle(),
'url' => $link->getUrl(),
@ -90,7 +74,7 @@ class TaskExternalLink extends Base
if ($valid && $this->taskExternalLink->create($values)) {
$this->flash->success(t('Link added successfully.'));
return $this->response->redirect($this->helper->url->to('TaskExternalLink', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])), true);
return $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])), true);
}
$this->edit($values, $errors);
@ -116,7 +100,7 @@ class TaskExternalLink extends Base
$provider = $this->externalLinkManager->getProvider($values['link_type']);
$this->response->html($this->helper->layout->task('task_external_link/edit', array(
$this->response->html($this->template->render('task_external_link/edit', array(
'values' => $values,
'errors' => $errors,
'task' => $task,
@ -137,7 +121,7 @@ class TaskExternalLink extends Base
if ($valid && $this->taskExternalLink->update($values)) {
$this->flash->success(t('Link updated successfully.'));
return $this->response->redirect($this->helper->url->to('TaskExternalLink', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])), true);
return $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])), true);
}
$this->edit($values, $errors);
@ -158,7 +142,7 @@ class TaskExternalLink extends Base
return $this->notfound();
}
$this->response->html($this->helper->layout->task('task_external_link/remove', array(
$this->response->html($this->template->render('task_external_link/remove', array(
'link' => $link,
'task' => $task,
)));
@ -180,6 +164,6 @@ class TaskExternalLink extends Base
$this->flash->failure(t('Unable to remove this link.'));
}
$this->response->redirect($this->helper->url->to('TaskExternalLink', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])));
$this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])));
}
}

View file

@ -10,22 +10,6 @@ namespace Kanboard\Controller;
*/
class TaskHelper extends Base
{
/**
* Render Markdown text and reply with the HTML Code
*
* @access public
*/
public function preview()
{
$payload = $this->request->getJson();
if (empty($payload['text'])) {
$this->response->html('<p>'.t('Nothing to preview...').'</p>');
}
$this->response->html($this->helper->text->markdown($payload['text']));
}
/**
* Task autocompletion (Ajax)
*

View file

@ -3,13 +3,13 @@
namespace Kanboard\Controller;
/**
* TaskLink controller
* TaskInternalLink Controller
*
* @package controller
* @author Olivier Maridat
* @author Frederic Guillot
*/
class Tasklink extends Base
class TaskInternalLink extends Base
{
/**
* Get the current link
@ -28,25 +28,6 @@ class Tasklink extends Base
return $link;
}
/**
* Show links
*
* @access public
*/
public function show()
{
$task = $this->getTask();
$project = $this->project->getById($task['project_id']);
$this->response->html($this->helper->layout->task('tasklink/show', array(
'links' => $this->taskLink->getAllGroupedByLabel($task['id']),
'task' => $task,
'project' => $project,
'editable' => true,
'is_public' => false,
)));
}
/**
* Creation form
*
@ -56,12 +37,11 @@ class Tasklink extends Base
{
$task = $this->getTask();
$this->response->html($this->helper->layout->task('tasklink/create', array(
$this->response->html($this->template->render('task_internal_link/create', array(
'values' => $values,
'errors' => $errors,
'task' => $task,
'labels' => $this->link->getList(0, false),
'title' => t('Add a new link')
)));
}
@ -80,7 +60,7 @@ class Tasklink extends Base
if ($valid) {
if ($this->taskLink->create($values['task_id'], $values['opposite_task_id'], $values['link_id'])) {
$this->flash->success(t('Link added successfully.'));
return $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])).'#links', true);
return $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])), true);
}
$errors = array('title' => array(t('The exact same link already exists')));
@ -106,13 +86,12 @@ class Tasklink extends Base
$values['title'] = '#'.$opposite_task['id'].' - '.$opposite_task['title'];
}
$this->response->html($this->helper->layout->task('tasklink/edit', array(
$this->response->html($this->template->render('task_internal_link/edit', array(
'values' => $values,
'errors' => $errors,
'task_link' => $task_link,
'task' => $task,
'labels' => $this->link->getList(0, false),
'title' => t('Edit link')
'labels' => $this->link->getList(0, false)
)));
}
@ -150,7 +129,7 @@ class Tasklink extends Base
$task = $this->getTask();
$link = $this->getTaskLink();
$this->response->html($this->helper->layout->task('tasklink/remove', array(
$this->response->html($this->template->render('task_internal_link/remove', array(
'link' => $link,
'task' => $task,
)));
@ -172,6 +151,6 @@ class Tasklink extends Base
$this->flash->failure(t('Unable to remove this link.'));
}
$this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])).'#links');
$this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])));
}
}

View file

@ -23,7 +23,7 @@ class TaskRecurrence extends Base
$values = $task;
}
$this->response->html($this->helper->layout->task('task_recurrence/edit', array(
$this->response->html($this->template->render('task_recurrence/edit', array(
'values' => $values,
'errors' => $errors,
'task' => $task,

View file

@ -32,7 +32,7 @@ class Taskduplication extends Base
}
}
$this->response->html($this->helper->layout->task('task_duplication/duplicate', array(
$this->response->html($this->template->render('task_duplication/duplicate', array(
'task' => $task,
)));
}
@ -128,7 +128,7 @@ class Taskduplication extends Base
$users_list = array();
}
$this->response->html($this->helper->layout->task($template, array(
$this->response->html($this->template->render($template, array(
'values' => $values,
'task' => $task,
'projects_list' => $projects_list,

View file

@ -2,6 +2,8 @@
namespace Kanboard\Controller;
use Kanboard\Core\DateParser;
/**
* Task Modification controller
*
@ -35,7 +37,7 @@ class Taskmodification extends Base
$values = array('id' => $task['id'], 'description' => $task['description']);
}
$this->response->html($this->helper->layout->task('task_modification/edit_description', array(
$this->response->html($this->template->render('task_modification/edit_description', array(
'values' => $values,
'errors' => $errors,
'task' => $task,
@ -83,10 +85,10 @@ class Taskmodification extends Base
$values = $this->hook->merge('controller:task-modification:form:default', $values, array('default_values' => $values));
}
$values = $this->dateParser->format($values, array('date_due'), $this->config->get('application_date_format', 'm/d/Y'));
$values = $this->dateParser->format($values, array('date_started'), $this->config->get('application_datetime_format', 'm/d/Y H:i'));
$values = $this->dateParser->format($values, array('date_due'), $this->config->get('application_date_format', DateParser::DATE_FORMAT));
$values = $this->dateParser->format($values, array('date_started'), $this->config->get('application_datetime_format', DateParser::DATE_TIME_FORMAT));
$this->response->html($this->helper->layout->task('task_modification/edit_task', array(
$this->response->html($this->template->render('task_modification/edit_task', array(
'project' => $project,
'values' => $values,
'errors' => $errors,

View file

@ -55,7 +55,7 @@ class Taskstatus extends Base
return $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])), true);
}
$this->response->html($this->helper->layout->task($template, array(
$this->response->html($this->template->render($template, array(
'task' => $task,
)));
}

View file

@ -31,7 +31,6 @@ use Pimple\Container;
* @property \Kanboard\Core\ObjectStorage\ObjectStorageInterface $objectStorage
* @property \Kanboard\Core\Plugin\Hook $hook
* @property \Kanboard\Core\Plugin\Loader $pluginLoader
* @property \Kanboard\Core\Security\AccessMap $projectAccessMap
* @property \Kanboard\Core\Security\AuthenticationManager $authenticationManager
* @property \Kanboard\Core\Security\AccessMap $applicationAccessMap
* @property \Kanboard\Core\Security\AccessMap $projectAccessMap
@ -42,6 +41,7 @@ use Pimple\Container;
* @property \Kanboard\Core\Session\FlashMessage $flash
* @property \Kanboard\Core\Session\SessionManager $sessionManager
* @property \Kanboard\Core\Session\SessionStorage $sessionStorage
* @property \Kanboard\Core\User\Avatar\AvatarManager $avatarManager
* @property \Kanboard\Core\User\GroupSync $groupSync
* @property \Kanboard\Core\User\UserProfile $userProfile
* @property \Kanboard\Core\User\UserSync $userSync
@ -60,6 +60,7 @@ use Pimple\Container;
* @property \Kanboard\Formatter\GroupAutoCompleteFormatter $groupAutoCompleteFormatter
* @property \Kanboard\Model\Action $action
* @property \Kanboard\Model\ActionParameter $actionParameter
* @property \Kanboard\Model\AvatarFile $avatarFile
* @property \Kanboard\Model\Board $board
* @property \Kanboard\Model\Category $category
* @property \Kanboard\Model\Color $color
@ -75,11 +76,9 @@ use Pimple\Container;
* @property \Kanboard\Model\LastLogin $lastLogin
* @property \Kanboard\Model\Link $link
* @property \Kanboard\Model\Notification $notification
* @property \Kanboard\Model\OverdueNotification $overdueNotification
* @property \Kanboard\Model\PasswordReset $passwordReset
* @property \Kanboard\Model\Project $project
* @property \Kanboard\Model\ProjectActivity $projectActivity
* @property \Kanboard\Model\ProjectAnalytic $projectAnalytic
* @property \Kanboard\Model\ProjectDuplication $projectDuplication
* @property \Kanboard\Model\ProjectDailyColumnStats $projectDailyColumnStats
* @property \Kanboard\Model\ProjectDailyStats $projectDailyStats
@ -92,16 +91,13 @@ use Pimple\Container;
* @property \Kanboard\Model\ProjectNotificationType $projectNotificationType
* @property \Kanboard\Model\RememberMeSession $rememberMeSession
* @property \Kanboard\Model\Subtask $subtask
* @property \Kanboard\Model\SubtaskExport $subtaskExport
* @property \Kanboard\Model\SubtaskTimeTracking $subtaskTimeTracking
* @property \Kanboard\Model\Swimlane $swimlane
* @property \Kanboard\Model\Task $task
* @property \Kanboard\Model\TaskAnalytic $taskAnalytic
* @property \Kanboard\Model\TaskCreation $taskCreation
* @property \Kanboard\Model\TaskDuplication $taskDuplication
* @property \Kanboard\Model\TaskExport $taskExport
* @property \Kanboard\Model\TaskExternalLink $taskExternalLink
* @property \Kanboard\Model\TaskImport $taskImport
* @property \Kanboard\Model\TaskFinder $taskFinder
* @property \Kanboard\Model\TaskFilter $taskFilter
* @property \Kanboard\Model\TaskLink $taskLink
@ -112,7 +108,6 @@ use Pimple\Container;
* @property \Kanboard\Model\TaskMetadata $taskMetadata
* @property \Kanboard\Model\Transition $transition
* @property \Kanboard\Model\User $user
* @property \Kanboard\Model\UserImport $userImport
* @property \Kanboard\Model\UserLocking $userLocking
* @property \Kanboard\Model\UserMention $userMention
* @property \Kanboard\Model\UserNotification $userNotification
@ -120,12 +115,10 @@ use Pimple\Container;
* @property \Kanboard\Model\UserNotificationFilter $userNotificationFilter
* @property \Kanboard\Model\UserUnreadNotification $userUnreadNotification
* @property \Kanboard\Model\UserMetadata $userMetadata
* @property \Kanboard\Model\Webhook $webhook
* @property \Kanboard\Validator\ActionValidator $actionValidator
* @property \Kanboard\Validator\AuthValidator $authValidator
* @property \Kanboard\Validator\ColumnValidator $columnValidator
* @property \Kanboard\Validator\CategoryValidator $categoryValidator
* @property \Kanboard\Validator\ColumnValidator $columnValidator
* @property \Kanboard\Validator\CommentValidator $commentValidator
* @property \Kanboard\Validator\CurrencyValidator $currencyValidator
* @property \Kanboard\Validator\CustomFilterValidator $customFilterValidator
@ -136,9 +129,14 @@ use Pimple\Container;
* @property \Kanboard\Validator\SubtaskValidator $subtaskValidator
* @property \Kanboard\Validator\SwimlaneValidator $swimlaneValidator
* @property \Kanboard\Validator\TaskLinkValidator $taskLinkValidator
* @property \Kanboard\Validator\TaskExternalLinkValidator $taskExternalLinkValidator
* @property \Kanboard\Validator\ExternalLinkValidator $externalLinkValidator
* @property \Kanboard\Validator\TaskValidator $taskValidator
* @property \Kanboard\Validator\UserValidator $userValidator
* @property \Kanboard\Import\TaskImport $taskImport
* @property \Kanboard\Import\UserImport $userImport
* @property \Kanboard\Export\SubtaskExport $subtaskExport
* @property \Kanboard\Export\TaskExport $taskExport
* @property \Kanboard\Export\TransitionExport $transitionExport
* @property \Psr\Log\LoggerInterface $logger
* @property \PicoDb\Database $db
* @property \Symfony\Component\EventDispatcher\EventDispatcher $dispatcher

View file

@ -87,7 +87,8 @@ class Csv
*
* @static
* @access public
* @return integer
* @param mixed $value
* @return int
*/
public static function getBooleanValue($value)
{

View file

@ -12,6 +12,9 @@ use DateTime;
*/
class DateParser extends Base
{
const DATE_FORMAT = 'm/d/Y';
const DATE_TIME_FORMAT = 'm/d/Y H:i';
/**
* List of time formats
*
@ -201,7 +204,7 @@ class DateParser extends Base
}
/**
* Get a timetstamp from an ISO date format
* Get a timestamp from an ISO date format
*
* @access public
* @param string $value

View file

@ -10,17 +10,20 @@ use Pimple\Container;
* @package core
* @author Frederic Guillot
*
* @property \Helper\App $app
* @property \Helper\Asset $asset
* @property \Helper\Dt $dt
* @property \Helper\File $file
* @property \Helper\Form $form
* @property \Helper\Subtask $subtask
* @property \Helper\Task $task
* @property \Helper\Text $text
* @property \Helper\Url $url
* @property \Helper\User $user
* @property \Helper\Layout $layout
* @property \Kanboard\Helper\AppHelper $app
* @property \Kanboard\Helper\AssetHelper $asset
* @property \Kanboard\Helper\DateHelper $dt
* @property \Kanboard\Helper\FileHelper $file
* @property \Kanboard\Helper\FormHelper $form
* @property \Kanboard\Helper\HookHelper $hook
* @property \Kanboard\Helper\ModelHelper $model
* @property \Kanboard\Helper\SubtaskHelper $subtask
* @property \Kanboard\Helper\TaskHelper $task
* @property \Kanboard\Helper\TextHelper $text
* @property \Kanboard\Helper\UrlHelper $url
* @property \Kanboard\Helper\UserHelper $user
* @property \Kanboard\Helper\LayoutHelper $layout
* @property \Kanboard\Helper\ProjectHeaderHelper $projectHeader
*/
class Helper
{
@ -28,17 +31,17 @@ class Helper
* Helper instances
*
* @access private
* @var array
* @var \Pimple\Container
*/
private $helpers = array();
private $helpers;
/**
* Container instance
*
* @access protected
* @access private
* @var \Pimple\Container
*/
protected $container;
private $container;
/**
* Constructor
@ -49,33 +52,49 @@ class Helper
public function __construct(Container $container)
{
$this->container = $container;
$this->helpers = new Container;
}
/**
* Load automatically helpers
* Expose helpers with magic getter
*
* @access public
* @param string $name Helper name
* @param string $helper
* @return mixed
*/
public function __get($name)
public function __get($helper)
{
if (! isset($this->helpers[$name])) {
$class = '\Kanboard\Helper\\'.ucfirst($name);
$this->helpers[$name] = new $class($this->container);
}
return $this->helpers[$name];
return $this->getHelper($helper);
}
/**
* HTML escaping
* Expose helpers with method
*
* @param string $value Value to escape
* @return string
* @access public
* @param string $helper
* @return mixed
*/
public function e($value)
public function getHelper($helper)
{
return htmlspecialchars($value, ENT_QUOTES, 'UTF-8', false);
return $this->helpers[$helper];
}
/**
* Register a new Helper
*
* @access public
* @param string $property
* @param string $className
* @return Helper
*/
public function register($property, $className)
{
$container = $this->container;
$this->helpers[$property] = function() use($className, $container) {
return new $className($container);
};
return $this;
}
}

View file

@ -12,14 +12,14 @@ use Kanboard\Core\Base;
*/
class OAuth2 extends Base
{
private $clientId;
private $secret;
private $callbackUrl;
private $authUrl;
private $tokenUrl;
private $scopes;
private $tokenType;
private $accessToken;
protected $clientId;
protected $secret;
protected $callbackUrl;
protected $authUrl;
protected $tokenUrl;
protected $scopes;
protected $tokenType;
protected $accessToken;
/**
* Create OAuth2 service
@ -45,6 +45,33 @@ class OAuth2 extends Base
return $this;
}
/**
* Generate OAuth2 state and return the token value
*
* @access public
* @return string
*/
public function getState()
{
if (! isset($this->sessionStorage->oauthState) || empty($this->sessionStorage->oauthState)) {
$this->sessionStorage->oauthState = $this->token->getToken();
}
return $this->sessionStorage->oauthState;
}
/**
* Check the validity of the state (CSRF token)
*
* @access public
* @param string $state
* @return bool
*/
public function isValidateState($state)
{
return $state === $this->getState();
}
/**
* Get authorization url
*
@ -58,6 +85,7 @@ class OAuth2 extends Base
'client_id' => $this->clientId,
'redirect_uri' => $this->callbackUrl,
'scope' => implode(' ', $this->scopes),
'state' => $this->getState(),
);
return $this->authUrl.'?'.http_build_query($params);
@ -94,6 +122,7 @@ class OAuth2 extends Base
'client_secret' => $this->secret,
'redirect_uri' => $this->callbackUrl,
'grant_type' => 'authorization_code',
'state' => $this->getState(),
);
$response = json_decode($this->httpClient->postForm($this->tokenUrl, $params, array('Accept: application/json')), true);

View file

@ -30,6 +30,11 @@ class Request extends Base
*
* @access public
* @param \Pimple\Container $container
* @param array $server
* @param array $get
* @param array $post
* @param array $files
* @param array $cookies
*/
public function __construct(Container $container, array $server = array(), array $get = array(), array $post = array(), array $files = array(), array $cookies = array())
{
@ -211,7 +216,11 @@ class Request extends Base
*/
public function isHTTPS()
{
return isset($this->server['HTTPS']) && $this->server['HTTPS'] !== '' && $this->server['HTTPS'] !== 'off';
if ($this->getServerVariable('HTTP_X_FORWARDED_PROTO') === 'https') {
return true;
}
return $this->getServerVariable('HTTPS') !== '' && $this->server['HTTPS'] !== 'off';
}
/**

View file

@ -13,6 +13,24 @@ use Kanboard\Core\Csv;
*/
class Response extends Base
{
/**
* Send headers to cache a resource
*
* @access public
* @param integer $duration
* @param string $etag
*/
public function cache($duration, $etag = '')
{
header('Pragma: cache');
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + $duration) . ' GMT');
header('Cache-Control: public, max-age=' . $duration);
if ($etag) {
header('ETag: "' . $etag . '"');
}
}
/**
* Send no cache headers
*

View file

@ -3,6 +3,7 @@
namespace Kanboard\Core\Ldap;
use LogicException;
use Psr\Log\LoggerInterface;
/**
* LDAP Client
@ -20,6 +21,14 @@ class Client
*/
protected $ldap;
/**
* Logger instance
*
* @access private
* @var LoggerInterface
*/
private $logger;
/**
* Establish LDAP connection
*
@ -31,7 +40,7 @@ class Client
*/
public static function connect($username = null, $password = null)
{
$client = new self;
$client = new static;
$client->open($client->getLdapServer());
$username = $username ?: $client->getLdapUsername();
$password = $password ?: $client->getLdapPassword();
@ -60,6 +69,7 @@ class Client
* Establish server connection
*
* @access public
* @throws ClientException
* @param string $server LDAP server hostname or IP
* @param integer $port LDAP port
* @param boolean $tls Start TLS
@ -98,6 +108,7 @@ class Client
* Anonymous authentication
*
* @access public
* @throws ClientException
* @return boolean
*/
public function useAnonymousAuthentication()
@ -113,6 +124,7 @@ class Client
* Authentication with username/password
*
* @access public
* @throws ClientException
* @param string $bind_rdn
* @param string $bind_password
* @return boolean
@ -162,4 +174,39 @@ class Client
{
return LDAP_PASSWORD;
}
/**
* Set logger
*
* @access public
* @param LoggerInterface $logger
* @return Client
*/
public function setLogger(LoggerInterface $logger)
{
$this->logger = $logger;
return $this;
}
/**
* Get logger
*
* @access public
* @return LoggerInterface
*/
public function getLogger()
{
return $this->logger;
}
/**
* Test if a logger is defined
*
* @access public
* @return boolean
*/
public function hasLogger()
{
return $this->logger !== null;
}
}

View file

@ -48,6 +48,12 @@ class Query
*/
public function execute($baseDn, $filter, array $attributes)
{
if (DEBUG && $this->client->hasLogger()) {
$this->client->getLogger()->debug('BaseDN='.$baseDn);
$this->client->getLogger()->debug('Filter='.$filter);
$this->client->getLogger()->debug('Attributes='.implode(', ', $attributes));
}
$sr = ldap_search($this->client->getConnection(), $baseDn, $filter, $attributes);
if ($sr === false) {
return $this;
@ -78,7 +84,7 @@ class Query
* Get LDAP Entries
*
* @access public
* @return Entities
* @return Entries
*/
public function getEntries()
{

View file

@ -44,8 +44,7 @@ class User
*/
public static function getUser(Client $client, $username)
{
$className = get_called_class();
$self = new $className(new Query($client));
$self = new static(new Query($client));
return $self->find($self->getLdapUserPattern($username));
}
@ -211,14 +210,15 @@ class User
*
* @access public
* @param string $username
* @param string $filter
* @return string
*/
public function getLdapUserPattern($username)
public function getLdapUserPattern($username, $filter = LDAP_USER_FILTER)
{
if (! LDAP_USER_FILTER) {
if (! $filter) {
throw new LogicException('LDAP user filter empty, check the parameter LDAP_USER_FILTER');
}
return sprintf(LDAP_USER_FILTER, $username);
return str_replace('%s', $username, $filter);
}
}

View file

@ -41,7 +41,7 @@ class Client extends Base
* @param string $name
* @param string $subject
* @param string $html
* @return EmailClient
* @return Client
*/
public function send($email, $name, $subject, $html)
{
@ -70,7 +70,7 @@ class Client extends Base
*
* @access public
* @param string $transport
* @return EmailClientInterface
* @return ClientInterface
*/
public function getTransport($transport)
{
@ -83,7 +83,7 @@ class Client extends Base
* @access public
* @param string $transport
* @param string $class
* @return EmailClient
* @return Client
*/
public function setTransport($transport, $class)
{

View file

@ -33,6 +33,7 @@ class FileStorage implements ObjectStorageInterface
* Fetch object contents
*
* @access public
* @throws ObjectStorageException
* @param string $key
* @return string
*/
@ -51,6 +52,7 @@ class FileStorage implements ObjectStorageInterface
* Save object
*
* @access public
* @throws ObjectStorageException
* @param string $key
* @param string $blob
*/
@ -67,6 +69,7 @@ class FileStorage implements ObjectStorageInterface
* Output directly object content
*
* @access public
* @throws ObjectStorageException
* @param string $key
*/
public function output($key)
@ -84,6 +87,7 @@ class FileStorage implements ObjectStorageInterface
* Move local file to object storage
*
* @access public
* @throws ObjectStorageException
* @param string $src_filename
* @param string $key
* @return boolean
@ -136,6 +140,7 @@ class FileStorage implements ObjectStorageInterface
* Create object folder
*
* @access private
* @throws ObjectStorageException
* @param string $key
*/
private function createFolder($key)

View file

@ -40,6 +40,17 @@ abstract class Base extends \Kanboard\Core\Base
return array();
}
/**
* Returns all helper classes that needs to be stored in the DI container
*
* @access public
* @return array
*/
public function getHelpers()
{
return array();
}
/**
* Listen on internal events
*

View file

@ -55,6 +55,7 @@ class Loader extends \Kanboard\Core\Base
* Load plugin
*
* @access public
* @throws LogicException
* @param string $plugin
*/
public function load($plugin)
@ -69,6 +70,8 @@ class Loader extends \Kanboard\Core\Base
Tool::buildDic($this->container, $instance->getClasses());
Tool::buildDICHelpers($this->container, $instance->getHelpers());
$instance->initialize();
$this->plugins[] = $instance;
}

View file

@ -39,7 +39,7 @@ class AccessMap
*
* @access public
* @param string $role
* @return Acl
* @return AccessMap
*/
public function setDefaultRole($role)
{
@ -53,7 +53,7 @@ class AccessMap
* @access public
* @param string $role
* @param array $subroles
* @return Acl
* @return AccessMap
*/
public function setRoleHierarchy($role, array $subroles)
{
@ -113,7 +113,7 @@ class AccessMap
* @param string $controller Controller class name
* @param mixed $methods List of method name or just one method
* @param string $role Lowest role required
* @return Acl
* @return AccessMap
*/
public function add($controller, $methods, $role)
{
@ -135,7 +135,7 @@ class AccessMap
* @param string $controller
* @param string $method
* @param string $role
* @return Acl
* @return AccessMap
*/
private function addRule($controller, $method, $role)
{
@ -157,7 +157,7 @@ class AccessMap
* @access public
* @param string $controller
* @param string $method
* @return boolean
* @return array
*/
public function getRoles($controller, $method)
{

View file

@ -14,7 +14,7 @@ interface OAuthAuthenticationProviderInterface extends AuthenticationProviderInt
* Get user object
*
* @access public
* @return UserProviderInterface
* @return \Kanboard\Core\User\UserProviderInterface
*/
public function getUser();
@ -31,7 +31,7 @@ interface OAuthAuthenticationProviderInterface extends AuthenticationProviderInt
* Get configured OAuth2 service
*
* @access public
* @return Kanboard\Core\Http\OAuth2
* @return \Kanboard\Core\Http\OAuth2
*/
public function getService();

View file

@ -14,7 +14,7 @@ interface PasswordAuthenticationProviderInterface extends AuthenticationProvider
* Get user object
*
* @access public
* @return UserProviderInterface
* @return \Kanboard\Core\User\UserProviderInterface
*/
public function getUser();

View file

@ -14,7 +14,7 @@ interface PreAuthenticationProviderInterface extends AuthenticationProviderInter
* Get user object
*
* @access public
* @return UserProviderInterface
* @return \Kanboard\Core\User\UserProviderInterface
*/
public function getUser();
}

View file

@ -21,6 +21,7 @@ namespace Kanboard\Core\Session;
* @property bool $boardCollapsed
* @property bool $twoFactorBeforeCodeCalled
* @property string $twoFactorSecret
* @property string $oauthState
*/
class SessionStorage
{

View file

@ -3,13 +3,36 @@
namespace Kanboard\Core;
/**
* Template class
* Template
*
* @package core
* @author Frederic Guillot
*
* @property \Kanboard\Helper\AppHelper $app
* @property \Kanboard\Helper\AssetHelper $asset
* @property \Kanboard\Helper\DateHelper $dt
* @property \Kanboard\Helper\FileHelper $file
* @property \Kanboard\Helper\FormHelper $form
* @property \Kanboard\Helper\HookHelper $hook
* @property \Kanboard\Helper\ModelHelper $model
* @property \Kanboard\Helper\SubtaskHelper $subtask
* @property \Kanboard\Helper\TaskHelper $task
* @property \Kanboard\Helper\TextHelper $text
* @property \Kanboard\Helper\UrlHelper $url
* @property \Kanboard\Helper\UserHelper $user
* @property \Kanboard\Helper\LayoutHelper $layout
* @property \Kanboard\Helper\ProjectHeaderHelper $projectHeader
*/
class Template extends Helper
class Template
{
/**
* Helper object
*
* @access private
* @var Helper
*/
private $helper;
/**
* List of template overrides
*
@ -19,47 +42,26 @@ class Template extends Helper
private $overrides = array();
/**
* Rendering start time
* Template constructor
*
* @access private
* @var float
* @access public
* @param Helper $helper
*/
private $startTime = 0;
/**
* Total rendering time
*
* @access private
* @var float
*/
private $renderingTime = 0;
/**
* Method executed before the rendering
*
* @access protected
* @param string $template
*/
protected function beforeRender($template)
public function __construct(Helper $helper)
{
if (DEBUG) {
$this->startTime = microtime(true);
}
$this->helper = $helper;
}
/**
* Method executed after the rendering
* Expose helpers with magic getter
*
* @access protected
* @param string $template
* @access public
* @param string $helper
* @return mixed
*/
protected function afterRender($template)
public function __get($helper)
{
if (DEBUG) {
$duration = microtime(true) - $this->startTime;
$this->renderingTime += $duration;
$this->container['logger']->debug('Rendering '.$template.' in '.$duration.'s, total='.$this->renderingTime);
}
return $this->helper->getHelper($helper);
}
/**
@ -76,33 +78,10 @@ class Template extends Helper
*/
public function render($__template_name, array $__template_args = array())
{
$this->beforeRender($__template_name);
extract($__template_args);
ob_start();
include $this->getTemplateFile($__template_name);
$html = ob_get_clean();
$this->afterRender($__template_name);
return $html;
}
/**
* Render a page layout
*
* @access public
* @param string $template_name Template name
* @param array $template_args Key/value map
* @param string $layout_name Layout name
* @return string
*/
public function layout($template_name, array $template_args = array(), $layout_name = 'layout')
{
return $this->render(
$layout_name,
$template_args + array('content_for_layout' => $this->render($template_name, $template_args))
);
return ob_get_clean();
}
/**
@ -120,25 +99,26 @@ class Template extends Helper
/**
* Find template filename
*
* Core template name: 'task/show'
* Plugin template name: 'myplugin:task/show'
* Core template: 'task/show' or 'kanboard:task/show'
* Plugin template: 'myplugin:task/show'
*
* @access public
* @param string $template_name
* @param string $template
* @return string
*/
public function getTemplateFile($template_name)
public function getTemplateFile($template)
{
$template_name = isset($this->overrides[$template_name]) ? $this->overrides[$template_name] : $template_name;
$plugin = '';
$template = isset($this->overrides[$template]) ? $this->overrides[$template] : $template;
if (strpos($template_name, ':') !== false) {
list($plugin, $template) = explode(':', $template_name);
$path = __DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'plugins';
$path .= DIRECTORY_SEPARATOR.ucfirst($plugin).DIRECTORY_SEPARATOR.'Template'.DIRECTORY_SEPARATOR.$template.'.php';
} else {
$path = __DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'Template'.DIRECTORY_SEPARATOR.$template_name.'.php';
if (strpos($template, ':') !== false) {
list($plugin, $template) = explode(':', $template);
}
return $path;
if ($plugin !== 'kanboard' && $plugin !== '') {
return implode(DIRECTORY_SEPARATOR, array(__DIR__, '..', '..', 'plugins', ucfirst($plugin), 'Template', $template.'.php'));
}
return implode(DIRECTORY_SEPARATOR, array(__DIR__, '..', 'Template', $template.'.php'));
}
}

View file

@ -0,0 +1,172 @@
<?php
namespace Kanboard\Core;
/**
* Thumbnail Generator
*
* @package core
* @author Frederic Guillot
*/
class Thumbnail
{
protected $metadata = array();
protected $srcImage;
protected $dstImage;
/**
* Create a thumbnail from a local file
*
* @static
* @access public
* @param string $filename
* @return Thumbnail
*/
public static function createFromFile($filename)
{
$self = new static();
$self->fromFile($filename);
return $self;
}
/**
* Create a thumbnail from a string
*
* @static
* @access public
* @param string $blob
* @return Thumbnail
*/
public static function createFromString($blob)
{
$self = new static();
$self->fromString($blob);
return $self;
}
/**
* Load the local image file in memory with GD
*
* @access public
* @param string $filename
* @return Thumbnail
*/
public function fromFile($filename)
{
$this->metadata = getimagesize($filename);
$this->srcImage = imagecreatefromstring(file_get_contents($filename));
return $this;
}
/**
* Load the image blob in memory with GD
*
* @access public
* @param string $blob
* @return Thumbnail
*/
public function fromString($blob)
{
if (!function_exists('getimagesizefromstring')) {
$uri = 'data://application/octet-stream;base64,' . base64_encode($blob);
$this->metadata = getimagesize($uri);
} else {
$this->metadata = getimagesizefromstring($blob);
}
$this->srcImage = imagecreatefromstring($blob);
return $this;
}
/**
* Resize the image
*
* @access public
* @param int $width
* @param int $height
* @return Thumbnail
*/
public function resize($width = 250, $height = 100)
{
$srcWidth = $this->metadata[0];
$srcHeight = $this->metadata[1];
$dstX = 0;
$dstY = 0;
if ($width == 0 && $height == 0) {
$width = 100;
$height = 100;
}
if ($width > 0 && $height == 0) {
$dstWidth = $width;
$dstHeight = floor($srcHeight * ($width / $srcWidth));
$this->dstImage = imagecreatetruecolor($dstWidth, $dstHeight);
} elseif ($width == 0 && $height > 0) {
$dstWidth = floor($srcWidth * ($height / $srcHeight));
$dstHeight = $height;
$this->dstImage = imagecreatetruecolor($dstWidth, $dstHeight);
} else {
$srcRatio = $srcWidth / $srcHeight;
$resizeRatio = $width / $height;
if ($srcRatio <= $resizeRatio) {
$dstWidth = $width;
$dstHeight = floor($srcHeight * ($width / $srcWidth));
$dstY = ($dstHeight - $height) / 2 * (-1);
} else {
$dstWidth = floor($srcWidth * ($height / $srcHeight));
$dstHeight = $height;
$dstX = ($dstWidth - $width) / 2 * (-1);
}
$this->dstImage = imagecreatetruecolor($width, $height);
}
imagecopyresampled($this->dstImage, $this->srcImage, $dstX, $dstY, 0, 0, $dstWidth, $dstHeight, $srcWidth, $srcHeight);
return $this;
}
/**
* Save the thumbnail to a local file
*
* @access public
* @param string $filename
* @return Thumbnail
*/
public function toFile($filename)
{
imagejpeg($this->dstImage, $filename);
imagedestroy($this->dstImage);
imagedestroy($this->srcImage);
return $this;
}
/**
* Return the thumbnail as a string
*
* @access public
* @return string
*/
public function toString()
{
ob_start();
imagejpeg($this->dstImage, null);
imagedestroy($this->dstImage);
imagedestroy($this->srcImage);
return ob_get_clean();
}
/**
* Output the thumbnail directly to the browser or stdout
*
* @access public
*/
public function toOutput()
{
imagejpeg($this->dstImage, null);
imagedestroy($this->dstImage);
imagedestroy($this->srcImage);
}
}

View file

@ -56,76 +56,23 @@ class Tool
}
/**
* Generate a jpeg thumbnail from an image
* Build dependency injection container for custom helpers from an array
*
* @static
* @access public
* @param string $src_file Source file image
* @param string $dst_file Destination file image
* @param integer $resize_width Desired image width
* @param integer $resize_height Desired image height
* @param Container $container
* @param array $namespaces
* @return Container
*/
public static function generateThumbnail($src_file, $dst_file, $resize_width = 250, $resize_height = 100)
public static function buildDICHelpers(Container $container, array $namespaces)
{
$metadata = getimagesize($src_file);
$src_width = $metadata[0];
$src_height = $metadata[1];
$dst_y = 0;
$dst_x = 0;
if (empty($metadata['mime'])) {
return;
}
if ($resize_width == 0 && $resize_height == 0) {
$resize_width = 100;
$resize_height = 100;
}
if ($resize_width > 0 && $resize_height == 0) {
$dst_width = $resize_width;
$dst_height = floor($src_height * ($resize_width / $src_width));
$dst_image = imagecreatetruecolor($dst_width, $dst_height);
} elseif ($resize_width == 0 && $resize_height > 0) {
$dst_width = floor($src_width * ($resize_height / $src_height));
$dst_height = $resize_height;
$dst_image = imagecreatetruecolor($dst_width, $dst_height);
} else {
$src_ratio = $src_width / $src_height;
$resize_ratio = $resize_width / $resize_height;
if ($src_ratio <= $resize_ratio) {
$dst_width = $resize_width;
$dst_height = floor($src_height * ($resize_width / $src_width));
$dst_y = ($dst_height - $resize_height) / 2 * (-1);
} else {
$dst_width = floor($src_width * ($resize_height / $src_height));
$dst_height = $resize_height;
$dst_x = ($dst_width - $resize_width) / 2 * (-1);
}
$dst_image = imagecreatetruecolor($resize_width, $resize_height);
}
switch ($metadata['mime']) {
case 'image/jpeg':
case 'image/jpg':
$src_image = imagecreatefromjpeg($src_file);
break;
case 'image/png':
$src_image = imagecreatefrompng($src_file);
break;
case 'image/gif':
$src_image = imagecreatefromgif($src_file);
break;
default:
return;
}
imagecopyresampled($dst_image, $src_image, $dst_x, $dst_y, 0, 0, $dst_width, $dst_height, $src_width, $src_height);
imagejpeg($dst_image, $dst_file);
imagedestroy($dst_image);
foreach ($namespaces as $namespace => $classes) {
foreach ($classes as $name) {
$class = '\\Kanboard\\'.$namespace.'\\'.$name;
$container['helper']->register($name, $class);
}
}
return $container;
}
}

View file

@ -0,0 +1,93 @@
<?php
namespace Kanboard\Core\User\Avatar;
/**
* Avatar Manager
*
* @package avatar
* @author Frederic Guillot
*/
class AvatarManager
{
/**
* Providers
*
* @access private
* @var AvatarProviderInterface[]
*/
private $providers = array();
/**
* Register a new Avatar provider
*
* @access public
* @param AvatarProviderInterface $provider
* @return $this
*/
public function register(AvatarProviderInterface $provider)
{
$this->providers[] = $provider;
return $this;
}
/**
* Render avatar HTML element
*
* @access public
* @param string $user_id
* @param string $username
* @param string $name
* @param string $email
* @param string $avatar_path
* @param int $size
* @return string
*/
public function render($user_id, $username, $name, $email, $avatar_path, $size)
{
$user = array(
'id' => $user_id,
'username' => $username,
'name' => $name,
'email' => $email,
'avatar_path' => $avatar_path,
);
krsort($this->providers);
foreach ($this->providers as $provider) {
if ($provider->isActive($user)) {
return $provider->render($user, $size);
}
}
return '';
}
/**
* Render default provider for unknown users (first provider registered)
*
* @access public
* @param integer $size
* @return string
*/
public function renderDefault($size)
{
if (count($this->providers) > 0) {
ksort($this->providers);
$provider = current($this->providers);
$user = array(
'id' => 0,
'username' => '',
'name' => '?',
'email' => '',
'avatar_path' => '',
);
return $provider->render($user, $size);
}
return '';
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace Kanboard\Core\User\Avatar;
/**
* Avatar Provider Interface
*
* @package user
* @author Frederic Guillot
*/
interface AvatarProviderInterface
{
/**
* Render avatar html
*
* @access public
* @param array $user
* @param int $size
*/
public function render(array $user, $size);
/**
* Determine if the provider is active
*
* @access public
* @param array $user
* @return boolean
*/
public function isActive(array $user);
}

View file

@ -13,6 +13,19 @@ use Kanboard\Core\Security\Role;
*/
class UserSession extends Base
{
/**
* Refresh current session if necessary
*
* @access public
* @param integer $user_id
*/
public function refresh($user_id)
{
if ($this->getId() == $user_id) {
$this->initialize($this->user->getById($user_id));
}
}
/**
* Update user session
*
@ -35,6 +48,17 @@ class UserSession extends Base
$this->sessionStorage->postAuthenticationValidated = false;
}
/**
* Get user properties
*
* @access public
* @return array
*/
public function getAll()
{
return $this->sessionStorage->user;
}
/**
* Get user application role
*

View file

@ -1,11 +1,16 @@
<?php
namespace Kanboard\Model;
namespace Kanboard\Export;
use Kanboard\Core\Base;
use Kanboard\Model\Task;
use Kanboard\Model\Subtask;
use Kanboard\Model\User;
/**
* Subtask Export
*
* @package model
* @package export
* @author Frederic Guillot
*/
class SubtaskExport extends Base

View file

@ -1,13 +1,16 @@
<?php
namespace Kanboard\Model;
namespace Kanboard\Export;
use Kanboard\Core\Base;
use Kanboard\Core\DateParser;
use Kanboard\Model\Task;
use PDO;
/**
* Task Export model
* Task Export
*
* @package model
* @package export
* @author Frederic Guillot
*/
class TaskExport extends Base
@ -106,7 +109,7 @@ class TaskExport extends Base
$task['score'] = $task['score'] ?: 0;
$task['swimlane_id'] = isset($swimlanes[$task['swimlane_id']]) ? $swimlanes[$task['swimlane_id']] : '?';
$task = $this->dateParser->format($task, array('date_due', 'date_modification', 'date_creation', 'date_started', 'date_completed'), 'Y-m-d');
$task = $this->dateParser->format($task, array('date_due', 'date_modification', 'date_creation', 'date_started', 'date_completed'), DateParser::DATE_FORMAT);
return $task;
}

View file

@ -0,0 +1,77 @@
<?php
namespace Kanboard\Export;
use Kanboard\Core\Base;
use Kanboard\Core\DateParser;
/**
* Transition Export
*
* @package export
* @author Frederic Guillot
*/
class TransitionExport extends Base
{
/**
* Get project export
*
* @access public
* @param integer $project_id Project id
* @param mixed $from Start date (timestamp or user formatted date)
* @param mixed $to End date (timestamp or user formatted date)
* @return array
*/
public function export($project_id, $from, $to)
{
$results = array($this->getColumns());
$transitions = $this->transition->getAllByProjectAndDate($project_id, $from, $to);
foreach ($transitions as $transition) {
$results[] = $this->format($transition);
}
return $results;
}
/**
* Get column titles
*
* @access protected
* @return string[]
*/
protected function getColumns()
{
return array(
e('Id'),
e('Task Title'),
e('Source column'),
e('Destination column'),
e('Executer'),
e('Date'),
e('Time spent'),
);
}
/**
* Format the output of a transition array
*
* @access protected
* @param array $transition
* @return array
*/
protected function format(array $transition)
{
$values = array(
(int) $transition['id'],
$transition['title'],
$transition['src_column'],
$transition['dst_column'],
$transition['name'] ?: $transition['username'],
date($this->config->get('application_datetime_format', DateParser::DATE_TIME_FORMAT), $transition['date']),
round($transition['time_spent'] / 3600, 2)
);
return $values;
}
}

View file

@ -85,7 +85,7 @@ class AttachmentLinkProvider extends BaseLinkProvider implements ExternalLinkPro
* Get the link found with the properties
*
* @access public
* @return ExternalLinkInterface
* @return \Kanboard\Core\ExternalLink\ExternalLinkInterface
*/
public function getLink()
{

View file

@ -0,0 +1,26 @@
<?php
namespace Kanboard\ExternalLink;
use Kanboard\Core\ExternalLink\ExternalLinkInterface;
/**
* File Link
*
* @package externalLink
* @author Frederic Guillot
*/
class FileLink extends BaseLink implements ExternalLinkInterface
{
/**
* Get link title
*
* @access public
* @return string
*/
public function getTitle()
{
$path = parse_url($this->url, PHP_URL_PATH);
return basename(str_replace('\\', '/', $path));
}
}

View file

@ -0,0 +1,74 @@
<?php
namespace Kanboard\ExternalLink;
use Kanboard\Core\ExternalLink\ExternalLinkProviderInterface;
/**
* File Link Provider
*
* @package externalLink
* @author Frederic Guillot
*/
class FileLinkProvider extends BaseLinkProvider implements ExternalLinkProviderInterface
{
/**
* Get provider name
*
* @access public
* @return string
*/
public function getName()
{
return t('Local File');
}
/**
* Get link type
*
* @access public
* @return string
*/
public function getType()
{
return 'file';
}
/**
* Get a dictionary of supported dependency types by the provider
*
* @access public
* @return array
*/
public function getDependencies()
{
return array(
'related' => t('Related'),
);
}
/**
* Return true if the provider can parse correctly the user input
*
* @access public
* @return boolean
*/
public function match()
{
return strpos($this->userInput, 'file://') === 0;
}
/**
* Get the link found with the properties
*
* @access public
* @return \Kanboard\Core\ExternalLink\ExternalLinkInterface
*/
public function getLink()
{
$link = new FileLink($this->container);
$link->setUrl($this->userInput);
return $link;
}
}

View file

@ -65,7 +65,7 @@ class WebLinkProvider extends BaseLinkProvider implements ExternalLinkProviderIn
* Get the link found with the properties
*
* @access public
* @return ExternalLinkInterface
* @return \Kanboard\Core\ExternalLink\ExternalLinkInterface
*/
public function getLink()
{

View file

@ -5,12 +5,12 @@ namespace Kanboard\Helper;
use Kanboard\Core\Base;
/**
* Application helpers
* Application Helper
*
* @package helper
* @author Frederic Guillot
*/
class App extends Base
class AppHelper extends Base
{
/**
* Get config variable
@ -116,11 +116,11 @@ class App extends Base
$failure_message = $this->flash->getMessage('failure');
if (! empty($success_message)) {
return '<div class="alert alert-success alert-fade-out">'.$this->helper->e($success_message).'</div>';
return '<div class="alert alert-success alert-fade-out">'.$this->helper->text->e($success_message).'</div>';
}
if (! empty($failure_message)) {
return '<div class="alert alert-error">'.$this->helper->e($failure_message).'</div>';
return '<div class="alert alert-error">'.$this->helper->text->e($failure_message).'</div>';
}
return '';

View file

@ -2,18 +2,21 @@
namespace Kanboard\Helper;
use Kanboard\Core\Base;
/**
* Assets helpers
* Asset Helper
*
* @package helper
* @author Frederic Guillot
*/
class Asset extends \Kanboard\Core\Base
class AssetHelper extends Base
{
/**
* Add a Javascript asset
*
* @param string $filename Filename
* @param bool $async
* @return string
*/
public function js($filename, $async = false)

View file

@ -0,0 +1,68 @@
<?php
namespace Kanboard\Helper;
use Kanboard\Core\Base;
/**
* Avatar Helper
*
* @package helper
* @author Frederic Guillot
*/
class AvatarHelper extends Base
{
/**
* Render user avatar
*
* @access public
* @param string $user_id
* @param string $username
* @param string $name
* @param string $email
* @param string $avatar_path
* @param string $css
* @param int $size
* @return string
*/
public function render($user_id, $username, $name, $email, $avatar_path, $css = 'avatar-left', $size = 48)
{
if (empty($user_id) && empty($username)) {
$html = $this->avatarManager->renderDefault($size);
} else {
$html = $this->avatarManager->render($user_id, $username, $name, $email, $avatar_path, $size);
}
return '<div class="avatar avatar-'.$size.' '.$css.'">'.$html.'</div>';
}
/**
* Render small user avatar
*
* @access public
* @param string $user_id
* @param string $username
* @param string $name
* @param string $email
* @param string $avatar_path
* @param string $css
* @return string
*/
public function small($user_id, $username, $name, $email, $avatar_path, $css = '')
{
return $this->render($user_id, $username, $name, $email, $avatar_path, $css, 20);
}
/**
* Get a small avatar for the current user
*
* @access public
* @param string $css
* @return string
*/
public function currentUserSmall($css = '')
{
$user = $this->userSession->getAll();
return $this->small($user['id'], $user['username'], $user['name'], $user['email'], $user['avatar_path'], $css);
}
}

View file

@ -2,13 +2,15 @@
namespace Kanboard\Helper;
use Kanboard\Core\Base;
/**
* Board Helper
*
* @package helper
* @author Frederic Guillot
*/
class Board extends \Kanboard\Core\Base
class BoardHelper extends Base
{
/**
* Return true if tasks are collapsed

View file

@ -3,6 +3,7 @@
namespace Kanboard\Helper;
use DateTime;
use Kanboard\Core\Base;
/**
* DateTime helpers
@ -10,7 +11,7 @@ use DateTime;
* @package helper
* @author Frederic Guillot
*/
class Dt extends \Kanboard\Core\Base
class DateHelper extends Base
{
/**
* Get formatted time

View file

@ -2,13 +2,15 @@
namespace Kanboard\Helper;
use Kanboard\Core\Base;
/**
* File helpers
*
* @package helper
* @author Frederic Guillot
*/
class File extends \Kanboard\Core\Base
class FileHelper extends Base
{
/**
* Get file icon

View file

@ -10,7 +10,7 @@ use Kanboard\Core\Base;
* @package helper
* @author Frederic Guillot
*/
class Form extends Base
class FormHelper extends Base
{
/**
* Hidden CSRF token field
@ -44,6 +44,7 @@ class Form extends Base
* @param array $options Options
* @param array $values Form values
* @param array $errors Form errors
* @param array $attributes
* @param string $class CSS class
* @return string
*/
@ -52,7 +53,7 @@ class Form extends Base
$html = '<select name="'.$name.'" id="form-'.$name.'" class="'.$class.'" '.implode(' ', $attributes).'>';
foreach ($options as $id => $value) {
$html .= '<option value="'.$this->helper->e($id).'"';
$html .= '<option value="'.$this->helper->text->e($id).'"';
if (isset($values->$name) && $id == $values->$name) {
$html .= ' selected="selected"';
@ -61,7 +62,7 @@ class Form extends Base
$html .= ' selected="selected"';
}
$html .= '>'.$this->helper->e($value).'</option>';
$html .= '>'.$this->helper->text->e($value).'</option>';
}
$html .= '</select>';
@ -103,7 +104,7 @@ class Form extends Base
*/
public function radio($name, $label, $value, $selected = false, $class = '')
{
return '<label><input type="radio" name="'.$name.'" class="'.$class.'" value="'.$this->helper->e($value).'" '.($selected ? 'checked="checked"' : '').'> '.$this->helper->e($label).'</label>';
return '<label><input type="radio" name="'.$name.'" class="'.$class.'" value="'.$this->helper->text->e($value).'" '.($selected ? 'checked="checked"' : '').'> '.$this->helper->text->e($label).'</label>';
}
/**
@ -139,7 +140,7 @@ class Form extends Base
*/
public function checkbox($name, $label, $value, $checked = false, $class = '')
{
return '<label><input type="checkbox" name="'.$name.'" class="'.$class.'" value="'.$this->helper->e($value).'" '.($checked ? 'checked="checked"' : '').'>&nbsp;'.$this->helper->e($label).'</label>';
return '<label><input type="checkbox" name="'.$name.'" class="'.$class.'" value="'.$this->helper->text->e($value).'" '.($checked ? 'checked="checked"' : '').'>&nbsp;'.$this->helper->text->e($label).'</label>';
}
/**
@ -153,7 +154,7 @@ class Form extends Base
*/
public function label($label, $name, array $attributes = array())
{
return '<label for="form-'.$name.'" '.implode(' ', $attributes).'>'.$this->helper->e($label).'</label>';
return '<label for="form-'.$name.'" '.implode(' ', $attributes).'>'.$this->helper->text->e($label).'</label>';
}
/**
@ -173,7 +174,7 @@ class Form extends Base
$html = '<textarea name="'.$name.'" id="form-'.$name.'" class="'.$class.'" ';
$html .= implode(' ', $attributes).'>';
$html .= isset($values->$name) ? $this->helper->e($values->$name) : isset($values[$name]) ? $values[$name] : '';
$html .= isset($values->$name) ? $this->helper->text->e($values->$name) : isset($values[$name]) ? $values[$name] : '';
$html .= '</textarea>';
$html .= $this->errorList($errors, $name);
@ -334,7 +335,7 @@ class Form extends Base
$html .= '<ul class="form-errors">';
foreach ($errors[$name] as $error) {
$html .= '<li>'.$this->helper->e($error).'</li>';
$html .= '<li>'.$this->helper->text->e($error).'</li>';
}
$html .= '</ul>';
@ -354,9 +355,9 @@ class Form extends Base
private function formValue($values, $name)
{
if (isset($values->$name)) {
return 'value="'.$this->helper->e($values->$name).'"';
return 'value="'.$this->helper->text->e($values->$name).'"';
}
return isset($values[$name]) ? 'value="'.$this->helper->e($values[$name]).'"' : '';
return isset($values[$name]) ? 'value="'.$this->helper->text->e($values[$name]).'"' : '';
}
}

View file

@ -2,13 +2,15 @@
namespace Kanboard\Helper;
use Kanboard\Core\Base;
/**
* Template Hook helpers
*
* @package helper
* @author Frederic Guillot
*/
class Hook extends \Kanboard\Core\Base
class HookHelper extends Base
{
/**
* Add assets JS or CSS
@ -54,7 +56,7 @@ class Hook extends \Kanboard\Core\Base
* @access public
* @param string $hook
* @param string $template
* @return \Helper\Hook
* @return \Kanboard\Helper\Hook
*/
public function attach($hook, $template)
{

View file

@ -5,12 +5,12 @@ namespace Kanboard\Helper;
use Kanboard\Core\Base;
/**
* Layout helpers
* Layout Helper
*
* @package helper
* @author Frederic Guillot
*/
class Layout extends Base
class LayoutHelper extends Base
{
/**
* Render a template without the layout if Ajax request
@ -30,7 +30,7 @@ class Layout extends Base
$params['board_selector'] = $this->projectUserRole->getActiveProjectsByUser($this->userSession->getId());
}
return $this->template->layout($template, $params);
return $this->pageLayout($template, $params);
}
/**
@ -60,7 +60,7 @@ class Layout extends Base
*/
public function task($template, array $params)
{
$params['title'] = $params['task']['title'];
$params['title'] = $params['task']['project_name'];
return $this->subLayout('task/layout', 'task/sidebar', $template, $params);
}
@ -146,7 +146,24 @@ class Layout extends Base
}
/**
* Common method to generate a sublayout
* Render page layout
*
* @access public
* @param string $template Template name
* @param array $params Key/value dictionary
* @param string $layout Layout name
* @return string
*/
public function pageLayout($template, array $params = array(), $layout = 'layout')
{
return $this->template->render(
$layout,
$params + array('content_for_layout' => $this->template->render($template, $params))
);
}
/**
* Common method to generate a sub-layout
*
* @access public
* @param string $sublayout

View file

@ -0,0 +1,94 @@
<?php
namespace Kanboard\Helper;
use Kanboard\Core\Base;
/**
* Model Helper
*
* @package helper
* @author Frederic Guillot
*/
class ModelHelper extends Base
{
/**
* Remove keys from an array
*
* @access public
* @param array $values Input array
* @param string[] $keys List of keys to remove
*/
public function removeFields(array &$values, array $keys)
{
foreach ($keys as $key) {
if (array_key_exists($key, $values)) {
unset($values[$key]);
}
}
}
/**
* Remove keys from an array if empty
*
* @access public
* @param array $values Input array
* @param string[] $keys List of keys to remove
*/
public function removeEmptyFields(array &$values, array $keys)
{
foreach ($keys as $key) {
if (array_key_exists($key, $values) && empty($values[$key])) {
unset($values[$key]);
}
}
}
/**
* Force fields to be at 0 if empty
*
* @access public
* @param array $values Input array
* @param string[] $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 string[] $keys List of keys
*/
public function convertIntegerFields(array &$values, array $keys)
{
foreach ($keys as $key) {
if (isset($values[$key])) {
$values[$key] = (int) $values[$key];
}
}
}
/**
* Force some fields to be null if empty
*
* @access public
* @param array $values Input array
* @param string[] $keys List of keys
*/
public function convertNullFields(array &$values, array $keys)
{
foreach ($keys as $key) {
if (array_key_exists($key, $values) && empty($values[$key])) {
$values[$key] = null;
}
}
}
}

View file

@ -0,0 +1,80 @@
<?php
namespace Kanboard\Helper;
use Kanboard\Core\Base;
/**
* Project Header Helper
*
* @package helper
* @author Frederic Guillot
*/
class ProjectHeaderHelper extends Base
{
/**
* Get current search query
*
* @access public
* @param array $project
* @return string
*/
public function getSearchQuery(array $project)
{
$search = $this->request->getStringParam('search', $this->userSession->getFilters($project['id']));
$this->userSession->setFilters($project['id'], $search);
return urldecode($search);
}
/**
* Render project header (views switcher and search box)
*
* @access public
* @param array $project
* @param string $controller
* @param string $action
* @param bool $boardView
* @return string
*/
public function render(array $project, $controller, $action, $boardView = false)
{
$filters = array(
'controller' => $controller,
'action' => $action,
'project_id' => $project['id'],
'search' => $this->getSearchQuery($project),
);
return $this->template->render('project_header/header', array(
'project' => $project,
'filters' => $filters,
'categories_list' => $this->category->getList($project['id'], false),
'users_list' => $this->projectUserRole->getAssignableUsersList($project['id'], false),
'custom_filters_list' => $this->customFilter->getAll($project['id'], $this->userSession->getId()),
'board_view' => $boardView,
));
}
/**
* Get project description
*
* @access public
* @param array &$project
* @return string
*/
public function getDescription(array &$project)
{
if ($project['owner_id'] > 0) {
$description = t('Project owner: ').'**'.$this->helper->text->e($project['owner_name'] ?: $project['owner_username']).'**'.PHP_EOL.PHP_EOL;
if (! empty($project['description'])) {
$description .= '***'.PHP_EOL.PHP_EOL;
$description .= $project['description'];
}
} else {
$description = $project['description'];
}
return $description;
}
}

View file

@ -2,13 +2,15 @@
namespace Kanboard\Helper;
use Kanboard\Core\Base;
/**
* Subtask helpers
*
* @package helper
* @author Frederic Guillot
*/
class Subtask extends \Kanboard\Core\Base
class SubtaskHelper extends Base
{
public function getTitle(array $subtask)
{
@ -20,7 +22,7 @@ class Subtask extends \Kanboard\Core\Base
$html = '<i class="fa fa-check-square-o fa-fw"></i>';
}
return $html.$this->helper->e($subtask['title']);
return $html.$this->helper->text->e($subtask['title']);
}
/**

View file

@ -10,7 +10,7 @@ use Kanboard\Core\Base;
* @package helper
* @author Frederic Guillot
*/
class Task extends Base
class TaskHelper extends Base
{
/**
* Local cache for project columns

View file

@ -11,8 +11,19 @@ use Kanboard\Core\Base;
* @package helper
* @author Frederic Guillot
*/
class Text extends Base
class TextHelper extends Base
{
/**
* HTML escaping
*
* @param string $value Value to escape
* @return string
*/
public function e($value)
{
return htmlspecialchars($value, ENT_QUOTES, 'UTF-8', false);
}
/**
* Markdown transformation
*
@ -88,7 +99,7 @@ class Text extends Base
public function in($id, array $listing, $default_value = '?')
{
if (isset($listing[$id])) {
return $this->helper->e($listing[$id]);
return $this->helper->text->e($listing[$id]);
}
return $default_value;

View file

@ -5,12 +5,12 @@ namespace Kanboard\Helper;
use Kanboard\Core\Base;
/**
* Url helpers
* Url Helper
*
* @package helper
* @author Frederic Guillot
*/
class Url extends Base
class UrlHelper extends Base
{
private $base = '';
private $directory = '';
@ -29,7 +29,26 @@ class Url extends Base
}
/**
* HTML Link tag
* Button Link Element
*
* @access public
* @param string $icon Font-Awesome icon
* @param string $label Link label
* @param string $controller Controller name
* @param string $action Action name
* @param array $params Url parameters
* @param string $class CSS class attribute
* @return string
*/
public function button($icon, $label, $controller, $action, array $params = array(), $class = '')
{
$icon = '<i class="fa '.$icon.' fa-fw"></i> ';
$class = 'btn '.$class;
return $this->link($icon.$label, $controller, $action, $params, false, $class);
}
/**
* Link element
*
* @access public
* @param string $label Link label
@ -38,6 +57,7 @@ class Url extends Base
* @param array $params Url parameters
* @param boolean $csrf Add a CSRF token
* @param string $class CSS class attribute
* @param string $title
* @param boolean $new_tab Open the link in a new tab
* @param string $anchor Link Anchor
* @return string

View file

@ -2,13 +2,15 @@
namespace Kanboard\Helper;
use Kanboard\Core\Base;
/**
* User helpers
*
* @package helper
* @author Frederic Guillot
*/
class User extends \Kanboard\Core\Base
class UserHelper extends Base
{
/**
* Return true if the logged user as unread notifications
@ -32,7 +34,7 @@ class User extends \Kanboard\Core\Base
{
$initials = '';
foreach (explode(' ', $name) as $string) {
foreach (explode(' ', $name, 2) as $string) {
$initials .= mb_substr($string, 0, 1);
}
@ -154,23 +156,6 @@ class User extends \Kanboard\Core\Base
*/
public function getFullname(array $user = array())
{
return $this->user->getFullname(empty($user) ? $this->sessionStorage->user : $user);
}
/**
* Display gravatar image
*
* @access public
* @param string $email
* @param string $alt
* @return string
*/
public function avatar($email, $alt = '')
{
if (! empty($email) && $this->config->get('integration_gravatar') == 1) {
return '<img class="avatar" src="https://www.gravatar.com/avatar/'.md5(strtolower($email)).'?s=25" alt="'.$this->helper->e($alt).'" title="'.$this->helper->e($alt).'">';
}
return '';
return $this->user->getFullname(empty($user) ? $this->userSession->getAll() : $user);
}
}

View file

@ -1,7 +1,8 @@
<?php
namespace Kanboard\Model;
namespace Kanboard\Import;
use Kanboard\Core\Base;
use Kanboard\Core\Csv;
use SimpleValidator\Validator;
use SimpleValidator\Validators;
@ -9,7 +10,7 @@ use SimpleValidator\Validators;
/**
* Task Import
*
* @package model
* @package import
* @author Frederic Guillot
*/
class TaskImport extends Base
@ -126,7 +127,7 @@ class TaskImport extends Base
$values['date_due'] = $this->dateParser->getTimestampFromIsoFormat($row['date_due']);
}
$this->removeEmptyFields(
$this->helper->model->removeEmptyFields(
$values,
array('owner_id', 'creator_id', 'color_id', 'column_id', 'category_id', 'swimlane_id', 'date_due')
);

View file

@ -1,16 +1,18 @@
<?php
namespace Kanboard\Model;
namespace Kanboard\Import;
use Kanboard\Model\User;
use SimpleValidator\Validator;
use SimpleValidator\Validators;
use Kanboard\Core\Security\Role;
use Kanboard\Core\Base;
use Kanboard\Core\Csv;
/**
* User Import
*
* @package model
* @package import
* @author Frederic Guillot
*/
class UserImport extends Base
@ -91,7 +93,7 @@ class UserImport extends Base
unset($row['is_admin']);
unset($row['is_manager']);
$this->removeEmptyFields($row, array('password', 'email', 'name'));
$this->helper->model->removeEmptyFields($row, array('password', 'email', 'name'));
return $row;
}

View file

@ -8,7 +8,6 @@ return array(
'Edit' => 'Uredi',
'remove' => 'ukloni',
'Remove' => 'Ukloni',
'Update' => 'Ažuriraj',
'Yes' => 'Da',
'No' => 'Ne',
'cancel' => 'odustani',
@ -56,25 +55,23 @@ return array(
'Project' => 'Projekat',
'Status' => 'Status',
'Tasks' => 'Zadatak',
'Board' => 'Tabla',
'Board' => 'Ploča',
'Actions' => 'Akcije',
'Inactive' => 'Neaktivan',
'Active' => 'Aktivan',
'Add this column' => 'Dodaj kolonu',
'%d tasks on the board' => '%d zadataka na tabli',
'%d tasks in total' => '%d zadataka ukupno',
'Unable to update this board.' => 'Nemogu da ažuriram ovu tablu.',
'Edit board' => 'Izmijeni tablu',
'Unable to update this board.' => 'Nemogu da ažuriram ovu ploču.',
'Edit board' => 'Izmijeni ploču',
'Disable' => 'Onemogući',
'Enable' => 'Omogući',
'New project' => 'Novi projekat',
'Do you really want to remove this project: "%s"?' => 'Da li želiš da ukloniš projekat: "%s"?',
'Remove project' => 'Ukloni projekat',
'Edit the board for "%s"' => 'Uredi tablu za "%s"',
'Edit the board for "%s"' => 'Uredi ploču za "%s"',
'All projects' => 'Svi projekti',
'Add a new column' => 'Dodaj novu kolonu',
'Title' => 'Naslov',
'Nobody assigned' => 'Niko nije dodijeljen',
'Assigned to %s' => 'Dodijeljen korisniku %s',
'Remove a column' => 'Ukloni kolonu',
'Remove a column from a board' => 'Ukloni kolonu sa table',
@ -100,7 +97,7 @@ return array(
'New task' => 'Novi zadatak',
'Open a task' => 'Otvori zadatak',
'Do you really want to open this task: "%s"?' => 'Da li zaista želiš da otvoriš zadatak: "%s"?',
'Back to the board' => 'Nazad na tablu',
'Back to the board' => 'Nazad na ploču',
'There is nobody assigned' => 'Niko nije dodijeljen!',
'Column on the board:' => 'Kolona na tabli:',
'Close this task' => 'Zatvori ovaj zadatak',
@ -148,7 +145,7 @@ return array(
'Unable to update your user.' => 'Nije moguće ažuriranje korisnika.',
'User removed successfully.' => 'Korisnik uspješno uklonjen.',
'Unable to remove this user.' => 'Nije moguće uklanjanje korisnika.',
'Board updated successfully.' => 'Tabla uspješno ažurirana.',
'Board updated successfully.' => 'Ploča je uspješno ažurirana.',
'Ready' => 'Spreman',
'Backlog' => 'Zaliha',
'Work in progress' => 'U izradi',
@ -168,7 +165,6 @@ return array(
'Task count' => 'Broj zadataka',
'User' => 'Korisnik',
'Comments' => 'Komentari',
'Write your text in Markdown' => 'Pisanje teksta pomoću Markdown',
'Leave a comment' => 'Ostavi komentar',
'Comment is required' => 'Komentar je obavezan',
'Leave a description' => 'Dodaj opis',
@ -184,7 +180,6 @@ return array(
'Unable to remove this action.' => 'Nije moguće obrisati akciju',
'Action removed successfully.' => 'Akcija obrisana',
'Automatic actions for the project "%s"' => 'Akcije za automatizaciju projekta "%s"',
'Defined actions' => 'Definisane akcje',
'Add an action' => 'dodaj akcju',
'Event name' => 'Naziv događaja',
'Action name' => 'Naziv akcije',
@ -194,7 +189,6 @@ return array(
'When the selected event occurs execute the corresponding action.' => 'Na izabrani događaj izvrši odgovarajuću akciju',
'Next step' => 'Slijedeći korak',
'Define action parameters' => 'Definiši parametre akcije',
'Save this action' => 'Snimi akciju',
'Do you really want to remove this action: "%s"?' => 'Da li da obrišem akciju "%s"?',
'Remove an automatic action' => 'Obriši automatsku akciju',
'Assign the task to a specific user' => 'Dodijeli zadatak određenom korisniku',
@ -333,10 +327,10 @@ return array(
'Time tracking:' => 'Praćenje vremena:',
'New sub-task' => 'Novi pod-zadatak',
'New attachment added "%s"' => 'Ubačen novi prilog "%s"',
'Comment updated' => 'Komentar ažuriran',
'New comment posted by %s' => '%s ostavio novi komentar',
'New attachment' => 'Novi prilog',
'New comment' => 'Novi komentar',
'Comment updated' => 'Komentar ažuriran',
'New subtask' => 'Novi pod-zadatak',
'Subtask updated' => 'Pod-zadatak ažuriran',
'Task updated' => 'Zadatak ažuriran',
@ -404,7 +398,7 @@ return array(
'%s moved the task #%d to the position %d in the column "%s"' => '%s premjestio zadatak #%d na poziciju %d u koloni "%s"',
'Activity' => 'Aktivnosti',
'Default values are "%s"' => 'Podrazumijevane vrijednosti su: "%s"',
'Default columns for new projects (Comma-separated)' => 'Podrazumijevane kolone za novi projekat (Odvojeni zarezom)',
'Default columns for new projects (Comma-separated)' => 'Podrazumijevane kolone za novi projekat (Odvojene zarezom)',
'Task assignee change' => 'Promijena izvršioca zadatka',
'%s change the assignee of the task #%d to %s' => '%s zamijeni izvršioca za zadatak #%d u %s',
'%s changed the assignee of the task %s to %s' => '%s promijenio izvršioca za zadatak %s u %s',
@ -416,7 +410,7 @@ return array(
'Reference' => 'Referenca',
'Label' => 'Etiketa',
'Database' => 'Baza',
'About' => 'O',
'About' => 'O Kanboardu',
'Database driver:' => 'Database driver:',
'Board settings' => 'Postavke table',
'URL and token' => 'URL i token',
@ -424,23 +418,22 @@ return array(
'URL for task creation:' => 'URL za kreiranje zadataka',
'Reset token' => 'Resetuj token',
'API endpoint:' => 'API endpoint',
'Refresh interval for private board' => 'Interval osvježavanja privatnih tabli',
'Refresh interval for public board' => 'Interval osvježavanja javnih tabli',
'Refresh interval for private board' => 'Interval osvježavanja privatnih ploča',
'Refresh interval for public board' => 'Interval osvježavanja javnih ploča',
'Task highlight period' => 'Period naznačavanja zadatka',
'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Period (u sekundama) u kom su se događale promjene na zadatku (0 je onemogućeno, 2 dana je uobičajeno)',
'Frequency in second (60 seconds by default)' => 'Frekvencija u sekundama (60 sekundi je uobičajeno)',
'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Frekvencija u sekundama (0 je onemogućeno u budućnosti, 10 sekundi je uobičajeno)',
'Application URL' => 'URL aplikacje',
'Application URL' => 'URL aplikacije',
'Token regenerated.' => 'Token regenerisan.',
'Date format' => 'Format datuma',
'ISO format is always accepted, example: "%s" and "%s"' => 'Format ISO je uvek prihvatljiv, primjer: "%s", "%s"',
'ISO format is always accepted, example: "%s" and "%s"' => 'Format ISO je uvijek prihvatljiv, primjer: "%s", "%s"',
'New private project' => 'Novi privatni projekat',
'This project is private' => 'Ovaj projekat je privatan',
'Type here to create a new sub-task' => 'Piši ovdje za kreiranje novog pod-zadatka',
'Add' => 'Dodaj',
'Start date' => 'Datum početka',
'Time estimated' => 'Procijenjeno vrijeme',
'There is nothing assigned to you.' => 'Ništa vam nije dodijeljeno',
'There is nothing assigned to you.' => 'Ništa ti nije dodijeljeno',
'My tasks' => 'Moji zadaci',
'Activity stream' => 'Spisak aktivnosti',
'Dashboard' => 'Panel',
@ -487,9 +480,6 @@ return array(
'Daily project summary export for "%s"' => 'Izvoz zbirnog pregleda po danima za "%s"',
'Exports' => 'Izvozi',
'This export contains the number of tasks per column grouped per day.' => 'Ovaj izvoz sadržava broj zadataka po koloni grupisanih po danima.',
'Nothing to preview...' => 'Ništa za pokazati...',
'Preview' => 'Pregled',
'Write' => 'Piši',
'Active swimlanes' => 'Aktivne swimline trake',
'Add a new swimlane' => 'Dodaj novu swimline traku',
'Change default swimlane' => 'Preimenuj podrazumijevanu swimline traku',
@ -543,7 +533,6 @@ return array(
'Task age in days' => 'Trajanje zadatka u danima',
'Days in this column' => 'Dani u ovoj koloni',
'%dd' => '%dd',
'Add a link' => 'Dodaj vezu',
'Add a new link' => 'Dodaj novu vezu',
'Do you really want to remove this link: "%s"?' => 'Da li zaista želite ukloniti ovu vezu: "%s"?',
'Do you really want to remove this link with task #%d?' => 'Da li zaista želite ukloniti ovu vezu sa zadatkom #%d?',
@ -598,7 +587,7 @@ return array(
'CHF - Swiss Francs' => 'CHF - Švicarski franak',
'Custom Stylesheet' => 'Prilagođeni stil',
'download' => 'preuzmi',
'EUR - Euro' => 'EUR - Evro',
'EUR - Euro' => 'EUR - Euro',
'GBP - British Pound' => 'GBP - Britanska funta',
'INR - Indian Rupee' => 'INR - Indijski rupi',
'JPY - Japanese Yen' => 'JPY - Japanski jen',
@ -619,7 +608,7 @@ return array(
'Rate' => 'Stopa',
'Change reference currency' => 'Promijeni referencu valute',
'Add a new currency rate' => 'Dodaj novu stopu valute',
'Reference currency' => 'Referenca valute',
'Reference currency' => 'Referentna valuta',
'The currency rate have been added successfully.' => 'Stopa valute je uspješno dodana.',
'Unable to add this currency rate.' => 'Nemoguće dodati stopu valute.',
'Webhook URL' => 'Webhook URL',
@ -734,7 +723,7 @@ return array(
'Time spent changed: %sh' => 'Utrošeno vrijeme je promijenjeno: %sh',
'Time estimated changed: %sh' => 'Očekivano vrijeme je promijenjeno: %sh',
'The field "%s" have been updated' => 'Polje "%s" je ažurirano',
'The description have been modified' => 'Promijenjen opis',
'The description has been modified:' => 'Promijenjen opis:',
'Do you really want to close the task "%s" as well as all subtasks?' => 'Da li zaista želiš zatvoriti zadatak "%s" kao i sve pod-zadatke?',
'I want to receive notifications for:' => 'Želim dobijati obavještenja za:',
'All tasks' => 'Sve zadatke',
@ -753,8 +742,6 @@ return array(
'My activity stream' => 'Tok mojih aktivnosti',
'My calendar' => 'Moj kalendar',
'Search tasks' => 'Pretraga zadataka',
'Back to the calendar' => 'Vrati na kalendar',
'Filters' => 'Filteri',
'Reset filters' => 'Vrati filtere na početno',
'My tasks due tomorrow' => 'Moji zadaci koje treba završiti sutra',
'Tasks due today' => 'Zadaci koje treba završiti danas',
@ -765,8 +752,8 @@ return array(
'Not assigned' => 'Bez izvršioca',
'View advanced search syntax' => 'Vidi naprednu sintaksu pretrage',
'Overview' => 'Opšti pregled',
'Board/Calendar/List view' => 'Pregle Table/Kalendara/Liste',
'Switch to the board view' => 'Promijeni da vidim tablu',
'Board/Calendar/List view' => 'Pregled Ploče/Kalendara/Liste',
'Switch to the board view' => 'Promijeni da vidim ploču',
'Switch to the calendar view' => 'Promijeni da vidim kalendar',
'Switch to the list view' => 'Promijeni da vidim listu',
'Go to the search/filter box' => 'Idi na kutiju s pretragom/filterima',
@ -850,11 +837,10 @@ return array(
'Gantt chart for all projects' => 'Gantogram za sve projekte',
'Projects list' => 'Lista projekata',
'Gantt chart for this project' => 'Gantogram za ovaj projekat',
'Project board' => 'Tabla projekta',
'Project board' => 'Ploča projekta',
'End date:' => 'Datum završetka:',
'There is no start date or end date for this project.' => 'Nema početnog ili krajnjeg datuma za ovaj projekat.',
'Projects Gantt chart' => 'Gantogram projekata',
'Link type' => 'Tip veze',
'Change task color when using a specific task link' => 'Promijeni boju zadatka kada se koristi određena veza na zadatku',
'Task link creation or modification' => 'Veza na zadatku je napravljena ili izmijenjena',
'Milestone' => 'Prekretnica',
@ -895,18 +881,17 @@ return array(
'Assignee changed on task #%d' => 'Promijenjen izvršilac na zadatku #%d',
'%d overdue tasks' => '%d zadataka kasni',
'Task #%d is overdue' => 'Zadatak #%d kasni',
'No new notifications.' => 'Nema novi obavještenja.',
'No new notifications.' => 'Nema novih obavještenja.',
'Mark all as read' => 'Označi sve kao pročitano',
'Mark as read' => 'Označi kao pročitano',
'Total number of tasks in this column across all swimlanes' => 'Ukupan broj zadataka u ovoj koloni u svim swimline trakama',
'Collapse swimlane' => 'Skupi swimline trake',
'Expand swimlane' => 'Proširi swimline trake',
'Add a new filter' => 'Dodaj novi filter',
'Share with all project members' => 'Podijeli s svim članovima projekta',
'Share with all project members' => 'Podijeli sa svim članovima projekta',
'Shared' => 'Podijeljeno',
'Owner' => 'Vlasnik',
'Unread notifications' => 'Nepročitana obavještenja',
'My filters' => 'Moji filteri',
'Notification methods:' => 'Metode obavještenja:',
'Import tasks from CSV file' => 'Uvezi zadatke putem CSV fajla',
'Unable to read your file' => 'Nemoguće pročitati fajl',
@ -936,216 +921,236 @@ return array(
'Your file must use the predefined CSV format' => 'File mora biti predefinisani CSV format',
'Your file must be encoded in UTF-8' => 'File mora biti u UTF-8 kodu',
'The first row must be the header' => 'Prvi red mora biti zaglavlje',
'Duplicates are not verified for you' => 'Dipliciranje nisu potvrđena',
'Duplicates are not verified for you' => 'Dupliciranja neće biti provjeravana (to ćeš morati uraditi ručno)',
'The due date must use the ISO format: YYYY-MM-DD' => 'Datum do kog se treba izvršiti mora biti u ISO formatu: GGGG-MM-DD',
'Download CSV template' => 'Preuzmi CSV šablon',
'No external integration registered.' => 'Nema registrovanih vanjskih integracija.',
'Duplicates are not imported' => 'Duplikati nisu uvezeni',
'Usernames must be lowercase and unique' => 'Korisničko ime mora biti malim slovima i jedinstveno',
'Passwords will be encrypted if present' => 'Šifra će biti kriptovana',
// '%s attached a new file to the task %s' => '',
'%s attached a new file to the task %s' => '%s je dodano novi fajl u zadatak %s',
'Link type' => 'Tip veze',
'Assign automatically a category based on a link' => 'Automatsko pridruživanje kategorije bazirano na vezi',
'BAM - Konvertible Mark' => 'BAM - Konvertibilna marka',
// 'Assignee Username' => '',
// 'Assignee Name' => '',
// 'Groups' => '',
// 'Members of %s' => '',
// 'New group' => '',
// 'Group created successfully.' => '',
// 'Unable to create your group.' => '',
// 'Edit group' => '',
// 'Group updated successfully.' => '',
// 'Unable to update your group.' => '',
// 'Add group member to "%s"' => '',
// 'Group member added successfully.' => '',
// 'Unable to add group member.' => '',
// 'Remove user from group "%s"' => '',
// 'User removed successfully from this group.' => '',
// 'Unable to remove this user from the group.' => '',
// 'Remove group' => '',
// 'Group removed successfully.' => '',
// 'Unable to remove this group.' => '',
// 'Project Permissions' => '',
// 'Manager' => '',
// 'Project Manager' => '',
// 'Project Member' => '',
// 'Project Viewer' => '',
// 'Your account is locked for %d minutes' => '',
// 'Invalid captcha' => '',
// 'The name must be unique' => '',
// 'View all groups' => '',
// 'View group members' => '',
// 'There is no user available.' => '',
// 'Do you really want to remove the user "%s" from the group "%s"?' => '',
// 'There is no group.' => '',
// 'External Id' => '',
// 'Add group member' => '',
// 'Do you really want to remove this group: "%s"?' => '',
// 'There is no user in this group.' => '',
// 'Remove this user' => '',
// 'Permissions' => '',
// 'Allowed Users' => '',
// 'No user have been allowed specifically.' => '',
// 'Role' => '',
// 'Enter user name...' => '',
// 'Allowed Groups' => '',
// 'No group have been allowed specifically.' => '',
// 'Group' => '',
// 'Group Name' => '',
// 'Enter group name...' => '',
// 'Role:' => '',
'Assignee Username' => 'Pridruži korisničko ime',
'Assignee Name' => 'Pridruži ime',
'Groups' => 'Grupe',
'Members of %s' => 'Članovi %s',
'New group' => 'Nova grupa',
'Group created successfully.' => 'Grupa uspješno kreirana.',
'Unable to create your group.' => 'Nemoguće kreirati grupu.',
'Edit group' => 'Uredi grupu',
'Group updated successfully.' => 'Grupa uspješno ažurirana.',
'Unable to update your group.' => 'Nemoguće ažurirati grupu',
'Add group member to "%s"' => 'Dodaj člana grupe u ""%s"',
'Group member added successfully.' => 'Uspješno dodan član grupe.',
'Unable to add group member.' => 'Nemoguće dodati člana grupe.',
'Remove user from group "%s"' => 'Ukloni korisnika iz grupe "%s"',
'User removed successfully from this group.' => 'Korisnik uspješno uklonjen iz grupe.',
'Unable to remove this user from the group.' => 'Nemoguće ukloniti korisnika iz grupe.',
'Remove group' => 'Ukloni grupu',
'Group removed successfully.' => 'Grupa uspješno uklonjena.',
'Unable to remove this group.' => 'Nemoguće ukloniti grupu.',
'Project Permissions' => 'Prava na projektu',
'Manager' => 'Menadžer',
'Project Manager' => 'Menadžer projekta',
'Project Member' => 'Član projekta',
'Project Viewer' => 'Preglednik projekta',
'Your account is locked for %d minutes' => 'Tvoj korisnički račun je zaključan za narednih %d minuta',
'Invalid captcha' => 'Pogrešna captcha',
'The name must be unique' => 'Ime mora biti jedinstveno',
'View all groups' => 'Pregledaj sve grupe',
'View group members' => 'Pregledaj članove grupe',
'There is no user available.' => 'Trenutno nema dostupnih korisnika.',
'Do you really want to remove the user "%s" from the group "%s"?' => 'Da li zaista želiš ukloniti korisnika "%s" iz grupe "%s"?',
'There is no group.' => 'Trenutno nema grupa.',
'External Id' => 'Vanjski Id',
'Add group member' => 'Dodaj člana grupe',
'Do you really want to remove this group: "%s"?' => 'Da li zaista želiš ukloniti ovu grupu: "%s"?',
'There is no user in this group.' => 'Trenutno nema korisnika u grupi.',
'Remove this user' => 'Ukloni ovog korisnika',
'Permissions' => 'Prava',
'Allowed Users' => 'Dozvoljeni korisnici',
'No user have been allowed specifically.' => 'Nema korisnika sa specijalnim dozvolama.',
'Role' => 'Uloge',
'Enter user name...' => 'Unesi korisničko ime...',
'Allowed Groups' => 'Dozvoljene grupe',
'No group have been allowed specifically.' => 'Nema grupa sa specijalnim dozvolama.',
'Group' => 'Grupa',
'Group Name' => 'Ime grupe',
'Enter group name...' => 'Unesi ime grupe...',
'Role:' => 'Uloga:',
'Project members' => 'Članovi projekta',
// 'Compare hours for "%s"' => '',
// '%s mentioned you in the task #%d' => '',
// '%s mentioned you in a comment on the task #%d' => '',
// 'You were mentioned in the task #%d' => '',
// 'You were mentioned in a comment on the task #%d' => '',
// 'Mentioned' => '',
// 'Compare Estimated Time vs Actual Time' => '',
// 'Estimated hours: ' => '',
// 'Actual hours: ' => '',
// 'Hours Spent' => '',
// 'Hours Estimated' => '',
// 'Estimated Time' => '',
// 'Actual Time' => '',
// 'Estimated vs actual time' => '',
// 'RUB - Russian Ruble' => '',
// 'Assign the task to the person who does the action when the column is changed' => '',
// 'Close a task in a specific column' => '',
// 'Time-based One-time Password Algorithm' => '',
// 'Two-Factor Provider: ' => '',
// 'Disable two-factor authentication' => '',
// 'Enable two-factor authentication' => '',
// 'There is no integration registered at the moment.' => '',
// 'Password Reset for Kanboard' => '',
// 'Forgot password?' => '',
// 'Enable "Forget Password"' => '',
// 'Password Reset' => '',
// 'New password' => '',
// 'Change Password' => '',
// 'To reset your password click on this link:' => '',
// 'Last Password Reset' => '',
// 'The password has never been reinitialized.' => '',
// 'Creation' => '',
// 'Expiration' => '',
// 'Password reset history' => '',
// 'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => '',
// 'Do you really want to close all tasks of this column?' => '',
// '%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '',
// 'Close all tasks of this column' => '',
// 'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => '',
// 'My dashboard' => '',
// 'My profile' => '',
// 'Project owner: ' => '',
// 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => '',
// 'Project owner' => '',
// 'Those dates are useful for the project Gantt chart.' => '',
// 'Private projects do not have users and groups management.' => '',
// 'There is no project member.' => '',
// 'Priority' => '',
// 'Task priority' => '',
// 'General' => '',
// 'Dates' => '',
// 'Default priority' => '',
// 'Lowest priority' => '',
// 'Highest priority' => '',
// 'If you put zero to the low and high priority, this feature will be disabled.' => '',
// 'Close a task when there is no activity' => '',
// 'Duration in days' => '',
// 'Send email when there is no activity on a task' => '',
// 'List of external links' => '',
// 'Unable to fetch link information.' => '',
// 'Daily background job for tasks' => '',
// 'Auto' => '',
// 'Related' => '',
// 'Attachment' => '',
// 'Title not found' => '',
// 'Web Link' => '',
// 'External links' => '',
// 'Add external link' => '',
// 'Type' => '',
// 'Dependency' => '',
// 'Add internal link' => '',
// 'Add a new external link' => '',
// 'Edit external link' => '',
// 'External link' => '',
// 'Copy and paste your link here...' => '',
// 'URL' => '',
// 'There is no external link for the moment.' => '',
// 'Internal links' => '',
// 'There is no internal link for the moment.' => '',
// 'Assign to me' => '',
// 'Me' => '',
// 'Do not duplicate anything' => '',
// 'Projects management' => '',
// 'Users management' => '',
// 'Groups management' => '',
// 'Create from another project' => '',
// 'There is no subtask at the moment.' => '',
// 'open' => '',
// 'closed' => '',
// 'Priority:' => '',
// 'Reference:' => '',
// 'Complexity:' => '',
// 'Swimlane:' => '',
// 'Column:' => '',
// 'Position:' => '',
// 'Creator:' => '',
// 'Time estimated:' => '',
// '%s hours' => '',
// 'Time spent:' => '',
// 'Created:' => '',
// 'Modified:' => '',
// 'Completed:' => '',
// 'Started:' => '',
// 'Moved:' => '',
// 'Task #%d' => '',
// 'Sub-tasks' => '',
// 'Date and time format' => '',
// 'Time format' => '',
// 'Start date: ' => '',
// 'End date: ' => '',
// 'New due date: ' => '',
// 'Start date changed: ' => '',
// 'Disable private projects' => '',
// 'Do you really want to remove this custom filter: "%s"?' => '',
// 'Remove a custom filter' => '',
// 'User activated successfully.' => '',
// 'Unable to enable this user.' => '',
// 'User disabled successfully.' => '',
// 'Unable to disable this user.' => '',
// 'All files have been uploaded successfully.' => '',
// 'View uploaded files' => '',
// 'The maximum allowed file size is %sB.' => '',
// 'Choose files again' => '',
// 'Drag and drop your files here' => '',
// 'choose files' => '',
// 'View profile' => '',
// 'Two Factor' => '',
// 'Disable user' => '',
// 'Do you really want to disable this user: "%s"?' => '',
// 'Enable user' => '',
// 'Do you really want to enable this user: "%s"?' => '',
// 'Download' => '',
// 'Uploaded: %s' => '',
// 'Size: %s' => '',
// 'Uploaded by %s' => '',
// 'Filename' => '',
// 'Size' => '',
// 'Column created successfully.' => '',
// 'Another column with the same name exists in the project' => '',
// 'Default filters' => '',
// 'Your board doesn\'t have any column!' => '',
// 'Change column position' => '',
// 'Switch to the project overview' => '',
// 'User filters' => '',
// 'Category filters' => '',
// 'Upload a file' => '',
// 'There is no attachment at the moment.' => '',
// 'View file' => '',
// 'Last activity' => '',
// 'Change subtask position' => '',
// 'This value must be greater than %d' => '',
// 'Another swimlane with the same name exists in the project' => '',
// 'Example: http://example.kanboard.net/ (used to generate absolute URLs)' => '',
'Compare hours for "%s"' => 'Poredi sate za "%s"',
'%s mentioned you in the task #%d' => '%s te spomenuo u zadatku #%d',
'%s mentioned you in a comment on the task #%d' => '%s te spomenuo u komentaru zadatka #%d',
'You were mentioned in the task #%d' => 'Spomenut si u zadatku #%d',
'You were mentioned in a comment on the task #%d' => 'Spomenut si u komentaru zadatka #%d',
'Mentioned' => 'Spominjanja',
'Compare Estimated Time vs Actual Time' => 'Poređenje očekivanog i aktuelnog vremena',
'Estimated hours: ' => 'Očekivani sati:',
'Actual hours: ' => 'Aktuelni sati:',
'Hours Spent' => 'Utrošeni sati:',
'Hours Estimated' => 'Očekivani sati',
'Estimated Time' => 'Očekivano vrijeme',
'Actual Time' => 'Aktuelno vrijeme',
'Estimated vs actual time' => 'Očekivano nasuprot aktuelnog vremena',
'RUB - Russian Ruble' => 'RUB - Ruski rubij',
'Assign the task to the person who does the action when the column is changed' => 'Dodijeli zadatak osobi koja izvrši akciju promjene kolone',
'Close a task in a specific column' => 'Zatvori zadatak u određenoj koloni',
'Time-based One-time Password Algorithm' => 'Vremenski bazirani One-time Algoritam šifri',
'Two-Factor Provider: ' => 'Two-Factor provajder',
'Disable two-factor authentication' => 'Onemogući two-factor autentifikaciju',
'Enable two-factor authentication' => 'Omogući two-factor autentifikaciju',
'There is no integration registered at the moment.' => 'Trenutno nema registrovanih integracija.',
'Password Reset for Kanboard' => 'Promjena šifre za Kanboard',
'Forgot password?' => 'Ne mogu da se sjetim šifre?',
'Enable "Forget Password"' => 'Omogući "Ne mogu da se sjetim šifre"',
'Password Reset' => 'Promijena šifre',
'New password' => 'Nova šifra',
'Change Password' => 'Promijeni šifru',
'To reset your password click on this link:' => 'Da bi promijenio šifru klikni na ovaj link:',
'Last Password Reset' => 'Posljednja promjena šifre',
'The password has never been reinitialized.' => 'Šifra nije nikada promijenjena.',
'Creation' => 'Napravljena',
'Expiration' => 'Ističe',
'Password reset history' => 'Hitorija promjena šifri',
'All tasks of the column "%s" and the swimlane "%s" have been closed successfully.' => 'Svi zadaci kolone "%s" i swimline-a "%s" uspješno su zatvoreni.',
'Do you really want to close all tasks of this column?' => 'Da li zaista želiš zatvoriti sve zadatke ove kolone?',
'%d task(s) in the column "%s" and the swimlane "%s" will be closed.' => '%d zadatak(ci) u koloni "%s" i swimline-u "%s" će biti zatvoreni.',
'Close all tasks of this column' => 'Zatvori sve zadatke ove kolone',
'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'Nema posebnog plugin-a za obavještenja. Još uvijek možeš koristiti individualne postavke obavještenja na svom profilu.',
'My dashboard' => 'Moj panel',
'My profile' => 'Moj profil',
'Project owner: ' => 'Vlasnik projekta:',
'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'Identifikator projekta je opcionalan i mora biti alfanumerički, na primjer: MOJPROJEKAT.',
'Project owner' => 'Vlasnik projekta',
'Those dates are useful for the project Gantt chart.' => 'Ovi datumi su korisni za pravljenje Gantt dijagrama za projekat.',
'Private projects do not have users and groups management.' => 'Privatni projekti ne mogu imati korisnike ili grupe korisnika.',
'There is no project member.' => 'Nema članova projekta.',
'Priority' => 'Prioritet',
'Task priority' => 'Prioritet zadatka',
'General' => 'Opšte',
'Dates' => 'Datumi',
'Default priority' => 'Podrazumijevani prioritet',
'Lowest priority' => 'Najmanji prioritet',
'Highest priority' => 'Najveći prioritet',
'If you put zero to the low and high priority, this feature will be disabled.' => 'Ako upišeš nulu za najmanji i najveći prioritet, ova opcija će biti onemogućena.',
'Close a task when there is no activity' => 'Zatvori zadatak kada nema aktivnosti',
'Duration in days' => 'Dužina trajanja u danima',
'Send email when there is no activity on a task' => 'Pošalji email kada nema aktivnosti na zadatku',
'Unable to fetch link information.' => 'Ne mogu da pribavim informacije o vezi.',
'Daily background job for tasks' => 'Dnevni pozadinski poslovi na zadacima',
'Auto' => 'Automatski',
'Related' => 'Povezani',
'Attachment' => 'Dodijeljeni',
'Title not found' => 'Bez naslova',
'Web Link' => 'Web veza',
'External links' => 'Vanjska veza',
'Add external link' => 'Dodaj vanjsku vezu',
'Type' => 'Vrsta',
'Dependency' => 'Zavisnost',
'Add internal link' => 'Dodaj unutrašnju vezu',
'Add a new external link' => 'Dodaj novu vanjsku vezu',
'Edit external link' => 'Uredi vanjsku vezu',
'External link' => 'Vanjska veza',
'Copy and paste your link here...' => 'Kopiraj i zalijepi svoju vezu ovdje...',
'URL' => 'URL',
'Internal links' => 'Unutrašnje veze',
'Assign to me' => 'Dodijeli meni',
'Me' => 'Za mene',
'Do not duplicate anything' => 'Ništa ne dupliciraj',
'Projects management' => 'Menadžment projekata',
'Users management' => 'Menadžment korisnika',
'Groups management' => 'Menadžment grupa',
'Create from another project' => 'Napravi iz drugog projekta',
'open' => 'otvoreno',
'closed' => 'zatvoreno',
'Priority:' => 'Prioritet:',
'Reference:' => 'Preporuka:',
'Complexity:' => 'Složenost:',
'Swimlane:' => 'Swimline:',
'Column:' => 'Kolona:',
'Position:' => 'Pozicija:',
'Creator:' => 'Kreator:',
'Time estimated:' => 'Očekivano vrijeme:',
'%s hours' => '%s sati',
'Time spent:' => 'Utrošeno vrijeme:',
'Created:' => 'Kreirao:',
'Modified:' => 'Uredio:',
'Completed:' => 'Završio:',
'Started:' => 'Početo:',
'Moved:' => 'Pomjereno:',
'Task #%d' => 'Zadatak #%d',
'Date and time format' => 'Format za datum i vrijeme',
'Time format' => 'Format za vrijeme',
'Start date: ' => 'Početni datum:',
'End date: ' => 'Krajnji datum:',
'New due date: ' => 'Novi datum očekivanja:',
'Start date changed: ' => 'Početni datum promijenjen:',
'Disable private projects' => 'Onemogući privatne projekte',
'Do you really want to remove this custom filter: "%s"?' => 'Da li zaista želiš ukloniti ovaj prilagođeni filter "%s"?',
'Remove a custom filter' => 'Ukloni prilagođeni filter',
'User activated successfully.' => 'Korisnik uspješno aktiviran.',
'Unable to enable this user.' => 'Nemoguće omogućiti ovog korisnika.',
'User disabled successfully.' => 'Korisnik uspješno onemogućen.',
'Unable to disable this user.' => 'Nemoguće onemogućiti ovog korisnika.',
'All files have been uploaded successfully.' => 'Svi fajlovi su uspješno dodani.',
'View uploaded files' => 'Pregled dodanih fajlova',
'The maximum allowed file size is %sB.' => 'Maksimalna dozvoljena veličina fajla je %sB.',
'Choose files again' => 'Izaberi ponovo fajlove',
'Drag and drop your files here' => 'Povuci i spusti svoje fajlove ovdje',
'choose files' => 'izaberi fajlove',
'View profile' => 'Pregledaj profil',
'Two Factor' => 'Dva faktora',
'Disable user' => 'Onemogući korisnika',
'Do you really want to disable this user: "%s"?' => 'Da li zaista želiš onemogućiti ovog korisnika: "%s"?',
'Enable user' => 'Omogući korisnika',
'Do you really want to enable this user: "%s"?' => 'Da li zaista želiš omogućiti ovog korisnika: "%s"?',
'Download' => 'Preuzeto',
'Uploaded: %s' => 'Dodano: %s',
'Size: %s' => 'Veličina: %s',
'Uploaded by %s' => 'Dodao %s',
'Filename' => 'Ime fajla',
'Size' => 'Veličina',
'Column created successfully.' => 'Kolona uspješno napravljena.',
'Another column with the same name exists in the project' => 'Već postoji kolona s istim imenom u ovom projektu.',
'Default filters' => 'Podrazumijevani filteri',
'Your board doesn\'t have any column!' => 'Vaš panel nema ni jednu kolonu!',
'Change column position' => 'Promijeni poziciju kolone',
'Switch to the project overview' => 'Promijeni u pregled projekta',
'User filters' => 'Korisnički filteri',
'Category filters' => 'Kategorija filtera',
'Upload a file' => 'Dodaj fajl',
'View file' => 'Pregled fajla',
'Last activity' => 'Posljednja aktivnost',
'Change subtask position' => 'Promijeni poziciju pod-zadatka',
'This value must be greater than %d' => 'Ova vrijednost mora biti veća od %d',
'Another swimlane with the same name exists in the project' => 'Već postoji swimline sa istim imenom u ovom projektu',
'Example: http://example.kanboard.net/ (used to generate absolute URLs)' => 'Na primjer: http://example.kanboard.net/ (koristi se za pravljenje apsolutnog URL-a)',
'Actions duplicated successfully.' => 'Akcije uspješno duplicirane.',
'Unable to duplicate actions.' => 'Nemoguće duplicirati akcije.',
'Add a new action' => 'Dodaj novu akciju',
'Import from another project' => 'Uvezi iz drugog projekta',
'There is no action at the moment.' => 'Trenutno nema akcija.',
'Import actions from another project' => 'Uvezi akcije iz drugog projekta',
'There is no available project.' => 'Trenutno nema dostupnih projekata.',
// 'Local File' => '',
// 'Configuration' => '',
// 'PHP version:' => '',
// 'PHP SAPI:' => '',
// 'OS version:' => '',
// 'Database version:' => '',
// 'Browser:' => '',
// 'Task view' => '',
// 'Edit task' => '',
// 'Edit description' => '',
// 'New internal link' => '',
// 'Display list of keyboard shortcuts' => '',
// 'Menu' => '',
// 'Set start date' => '',
// 'Avatar' => '',
// 'Upload my avatar image' => '',
// 'Remove my image' => '',
// 'The OAuth2 state parameter is invalid' => '',
);

View file

@ -8,7 +8,6 @@ return array(
'Edit' => 'Editovat',
'remove' => 'odstranit',
'Remove' => 'Odstranit',
'Update' => 'Akualizovat',
'Yes' => 'Ano',
'No' => 'Ne',
'cancel' => 'Zrušit',
@ -60,7 +59,6 @@ return array(
'Actions' => 'Akce',
'Inactive' => 'Neaktivní',
'Active' => 'Aktivní',
'Add this column' => 'Přidat sloupec',
'%d tasks on the board' => '%d úkolů na nástěnce',
'%d tasks in total' => '%d úkolů celkem',
'Unable to update this board.' => 'Nástěnku není možné aktualizovat',
@ -74,7 +72,6 @@ return array(
'All projects' => 'Všechny projekty',
'Add a new column' => 'Přidat nový sloupec',
'Title' => 'Název',
'Nobody assigned' => 'Nepřiřazena žádná osoba',
'Assigned to %s' => 'Přiřazeno uživateli: %s',
'Remove a column' => 'Vyjmout sloupec',
'Remove a column from a board' => 'Vyjmout sloupec z nástěnky',
@ -168,7 +165,6 @@ return array(
'Task count' => 'Počet úkolů',
'User' => 'Uživatel',
'Comments' => 'Komentáře',
'Write your text in Markdown' => 'Můžete použít i Markdown-syntaxi',
'Leave a comment' => 'Zanechte komentář',
'Comment is required' => 'Komentář je vyžadován',
'Leave a description' => 'Vložte popis',
@ -184,7 +180,6 @@ return array(
'Unable to remove this action.' => 'Tuto akci nelze odstranit.',
'Action removed successfully.' => 'Akce byla úspěšně odstraněna.',
'Automatic actions for the project "%s"' => 'Automaticky vykonávané akce pro projekt "%s"',
'Defined actions' => 'Definované akce',
'Add an action' => 'Přidat akci',
'Event name' => 'Název události',
'Action name' => 'Název akce',
@ -194,7 +189,6 @@ return array(
'When the selected event occurs execute the corresponding action.' => 'Kdykoliv se vybraná událost objeví, vykonat odpovídající akci.',
'Next step' => 'Další krok',
'Define action parameters' => 'Definovat parametry akce',
'Save this action' => 'Uložit akci',
'Do you really want to remove this action: "%s"?' => 'Skutečně chcete odebrat tuto akci: "%s"?',
'Remove an automatic action' => 'Odebrat automaticky prováděnou akci',
'Assign the task to a specific user' => 'Přiřadit tento úkol konkrétnímu uživateli',
@ -333,10 +327,10 @@ return array(
'Time tracking:' => 'Sledování času:',
'New sub-task' => 'Nový dílčí úkol',
'New attachment added "%s"' => 'Byla přidána nová příloha "%s".',
'Comment updated' => 'Komentář byl aktualizován.',
'New comment posted by %s' => 'Nový komentář publikovaný uživatelem %s',
'New attachment' => 'Nová příloha',
'New comment' => 'Nový komentář',
'Comment updated' => 'Komentář byl aktualizován.',
'New subtask' => 'Nový dílčí úkol',
'Subtask updated' => 'Dílčí úkol byl aktualizován',
'Task updated' => 'Úkol byl aktualizován',
@ -436,7 +430,6 @@ return array(
'ISO format is always accepted, example: "%s" and "%s"' => 'ISO formát je vždy akceptován, například: "%s" a "%s"',
'New private project' => 'Nový soukromý projekt',
'This project is private' => 'Tento projekt je soukromuý',
'Type here to create a new sub-task' => 'Uveďte zde pro vytvoření nového dílčího úkolu',
'Add' => 'Přidat',
'Start date' => 'Počáteční datum',
'Time estimated' => 'Odhadovaný čas',
@ -487,9 +480,6 @@ return array(
'Daily project summary export for "%s"' => 'Export denních přehledů pro "%s"',
'Exports' => 'Exporty',
'This export contains the number of tasks per column grouped per day.' => 'Tento export obsahuje počet úkolů pro jednotlivé sloupce seskupených podle dní.',
'Nothing to preview...' => 'Žádná položka k zobrazení ...',
'Preview' => 'Náhled',
'Write' => 'Režim psaní',
'Active swimlanes' => 'Aktive Swimlane',
'Add a new swimlane' => 'Přidat nový řádek',
'Change default swimlane' => 'Standard Swimlane ändern',
@ -543,7 +533,6 @@ return array(
'Task age in days' => 'Doba trvání úkolu ve dnech',
'Days in this column' => 'Dní v tomto sloupci',
'%dd' => '%d d',
'Add a link' => 'Přidat odkaz',
'Add a new link' => 'Přidat nový odkaz',
'Do you really want to remove this link: "%s"?' => 'Die Verbindung "%s" wirklich löschen?',
'Do you really want to remove this link with task #%d?' => 'Die Verbindung mit der Aufgabe #%d wirklich löschen?',
@ -734,7 +723,7 @@ return array(
'Time spent changed: %sh' => 'Verbrauchte Zeit geändert: %sh',
'Time estimated changed: %sh' => 'Geschätzte Zeit geändert: %sh',
'The field "%s" have been updated' => 'Das Feld "%s" wurde verändert',
'The description have been modified' => 'Die Beschreibung wurde geändert',
'The description has been modified:' => 'Die Beschreibung wurde geändert:',
'Do you really want to close the task "%s" as well as all subtasks?' => 'Soll die Aufgabe "%s" wirklich geschlossen werden? (einschließlich Teilaufgaben)',
'I want to receive notifications for:' => 'Chci dostávat upozornění na:',
'All tasks' => 'Všechny úkoly',
@ -753,8 +742,6 @@ return array(
'My activity stream' => 'Přehled mých aktivit',
'My calendar' => 'Můj kalendář',
'Search tasks' => 'Hledání úkolů',
'Back to the calendar' => 'Zpět do kalendáře',
'Filters' => 'Filtry',
'Reset filters' => 'Resetovat filtry',
'My tasks due tomorrow' => 'Moje zítřejší úkoly',
'Tasks due today' => 'Dnešní úkoly',
@ -854,7 +841,6 @@ return array(
// 'End date:' => '',
// 'There is no start date or end date for this project.' => '',
// 'Projects Gantt chart' => '',
// 'Link type' => '',
// 'Change task color when using a specific task link' => '',
// 'Task link creation or modification' => '',
// 'Milestone' => '',
@ -906,7 +892,6 @@ return array(
// 'Shared' => '',
// 'Owner' => '',
// 'Unread notifications' => '',
// 'My filters' => '',
// 'Notification methods:' => '',
// 'Import tasks from CSV file' => '',
// 'Unable to read your file' => '',
@ -944,6 +929,7 @@ return array(
// 'Usernames must be lowercase and unique' => '',
// 'Passwords will be encrypted if present' => '',
// '%s attached a new file to the task %s' => '',
// 'Link type' => '',
// 'Assign automatically a category based on a link' => '',
// 'BAM - Konvertible Mark' => '',
// 'Assignee Username' => '',
@ -1053,7 +1039,6 @@ return array(
// 'Close a task when there is no activity' => '',
// 'Duration in days' => '',
// 'Send email when there is no activity on a task' => '',
// 'List of external links' => '',
// 'Unable to fetch link information.' => '',
// 'Daily background job for tasks' => '',
// 'Auto' => '',
@ -1071,9 +1056,7 @@ return array(
// 'External link' => '',
// 'Copy and paste your link here...' => '',
// 'URL' => '',
// 'There is no external link for the moment.' => '',
// 'Internal links' => '',
// 'There is no internal link for the moment.' => '',
// 'Assign to me' => '',
// 'Me' => '',
// 'Do not duplicate anything' => '',
@ -1081,7 +1064,6 @@ return array(
// 'Users management' => '',
// 'Groups management' => '',
// 'Create from another project' => '',
// 'There is no subtask at the moment.' => '',
// 'open' => '',
// 'closed' => '',
// 'Priority:' => '',
@ -1100,7 +1082,6 @@ return array(
// 'Started:' => '',
// 'Moved:' => '',
// 'Task #%d' => '',
// 'Sub-tasks' => '',
// 'Date and time format' => '',
// 'Time format' => '',
// 'Start date: ' => '',
@ -1141,11 +1122,35 @@ return array(
// 'User filters' => '',
// 'Category filters' => '',
// 'Upload a file' => '',
// 'There is no attachment at the moment.' => '',
// 'View file' => '',
// 'Last activity' => '',
// 'Change subtask position' => '',
// 'This value must be greater than %d' => '',
// 'Another swimlane with the same name exists in the project' => '',
// 'Example: http://example.kanboard.net/ (used to generate absolute URLs)' => '',
// 'Actions duplicated successfully.' => '',
// 'Unable to duplicate actions.' => '',
// 'Add a new action' => '',
// 'Import from another project' => '',
// 'There is no action at the moment.' => '',
// 'Import actions from another project' => '',
// 'There is no available project.' => '',
// 'Local File' => '',
// 'Configuration' => '',
// 'PHP version:' => '',
// 'PHP SAPI:' => '',
// 'OS version:' => '',
// 'Database version:' => '',
// 'Browser:' => '',
// 'Task view' => '',
// 'Edit task' => '',
// 'Edit description' => '',
// 'New internal link' => '',
// 'Display list of keyboard shortcuts' => '',
// 'Menu' => '',
// 'Set start date' => '',
// 'Avatar' => '',
// 'Upload my avatar image' => '',
// 'Remove my image' => '',
// 'The OAuth2 state parameter is invalid' => '',
);

View file

@ -8,7 +8,6 @@ return array(
'Edit' => 'Rediger',
'remove' => 'fjern',
'Remove' => 'Fjern',
'Update' => 'Opdater',
'Yes' => 'Ja',
'No' => 'Nej',
'cancel' => 'annuller',
@ -60,7 +59,6 @@ return array(
'Actions' => 'Handlinger',
'Inactive' => 'Inaktiv',
'Active' => 'Aktiv',
'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',
@ -74,7 +72,6 @@ return array(
'All projects' => 'Alle Projekter',
'Add a new column' => 'Tilføj en ny kolonne',
'Title' => 'Titel',
'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',
@ -168,7 +165,6 @@ return array(
// 'Task count' => '',
'User' => 'Bruger',
'Comments' => 'Kommentarer',
'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...',
@ -184,7 +180,6 @@ return array(
'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',
@ -194,7 +189,6 @@ return array(
'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',
'Assign the task to a specific user' => 'Tildel opgaven til en bestem bruger',
@ -333,10 +327,10 @@ return array(
'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',
// 'New attachment' => '',
// 'New comment' => '',
'Comment updated' => 'Kommentar opdateret',
// 'New subtask' => '',
// 'Subtask updated' => '',
// 'Task updated' => '',
@ -436,7 +430,6 @@ return array(
'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',
'Start date' => 'Start dato',
'Time estimated' => 'Tid estimeret',
@ -487,9 +480,6 @@ return array(
// 'Daily project summary export for "%s"' => '',
// 'Exports' => '',
// 'This export contains the number of tasks per column grouped per day.' => '',
// 'Nothing to preview...' => '',
// 'Preview' => '',
// 'Write' => '',
// 'Active swimlanes' => '',
// 'Add a new swimlane' => '',
// 'Change default swimlane' => '',
@ -543,7 +533,6 @@ return array(
// 'Task age in days' => '',
// 'Days in this column' => '',
// '%dd' => '',
// 'Add a link' => '',
// 'Add a new link' => '',
// 'Do you really want to remove this link: "%s"?' => '',
// 'Do you really want to remove this link with task #%d?' => '',
@ -734,7 +723,7 @@ return array(
// 'Time spent changed: %sh' => '',
// 'Time estimated changed: %sh' => '',
// 'The field "%s" have been updated' => '',
// 'The description have been modified' => '',
// 'The description has been modified:' => '',
// 'Do you really want to close the task "%s" as well as all subtasks?' => '',
// 'I want to receive notifications for:' => '',
// 'All tasks' => '',
@ -753,8 +742,6 @@ return array(
// 'My activity stream' => '',
// 'My calendar' => '',
// 'Search tasks' => '',
// 'Back to the calendar' => '',
// 'Filters' => '',
// 'Reset filters' => '',
// 'My tasks due tomorrow' => '',
// 'Tasks due today' => '',
@ -854,7 +841,6 @@ return array(
// 'End date:' => '',
// 'There is no start date or end date for this project.' => '',
// 'Projects Gantt chart' => '',
// 'Link type' => '',
// 'Change task color when using a specific task link' => '',
// 'Task link creation or modification' => '',
// 'Milestone' => '',
@ -906,7 +892,6 @@ return array(
// 'Shared' => '',
// 'Owner' => '',
// 'Unread notifications' => '',
// 'My filters' => '',
// 'Notification methods:' => '',
// 'Import tasks from CSV file' => '',
// 'Unable to read your file' => '',
@ -944,6 +929,7 @@ return array(
// 'Usernames must be lowercase and unique' => '',
// 'Passwords will be encrypted if present' => '',
// '%s attached a new file to the task %s' => '',
// 'Link type' => '',
// 'Assign automatically a category based on a link' => '',
// 'BAM - Konvertible Mark' => '',
// 'Assignee Username' => '',
@ -1053,7 +1039,6 @@ return array(
// 'Close a task when there is no activity' => '',
// 'Duration in days' => '',
// 'Send email when there is no activity on a task' => '',
// 'List of external links' => '',
// 'Unable to fetch link information.' => '',
// 'Daily background job for tasks' => '',
// 'Auto' => '',
@ -1071,9 +1056,7 @@ return array(
// 'External link' => '',
// 'Copy and paste your link here...' => '',
// 'URL' => '',
// 'There is no external link for the moment.' => '',
// 'Internal links' => '',
// 'There is no internal link for the moment.' => '',
// 'Assign to me' => '',
// 'Me' => '',
// 'Do not duplicate anything' => '',
@ -1081,7 +1064,6 @@ return array(
// 'Users management' => '',
// 'Groups management' => '',
// 'Create from another project' => '',
// 'There is no subtask at the moment.' => '',
// 'open' => '',
// 'closed' => '',
// 'Priority:' => '',
@ -1100,7 +1082,6 @@ return array(
// 'Started:' => '',
// 'Moved:' => '',
// 'Task #%d' => '',
// 'Sub-tasks' => '',
// 'Date and time format' => '',
// 'Time format' => '',
// 'Start date: ' => '',
@ -1141,11 +1122,35 @@ return array(
// 'User filters' => '',
// 'Category filters' => '',
// 'Upload a file' => '',
// 'There is no attachment at the moment.' => '',
// 'View file' => '',
// 'Last activity' => '',
// 'Change subtask position' => '',
// 'This value must be greater than %d' => '',
// 'Another swimlane with the same name exists in the project' => '',
// 'Example: http://example.kanboard.net/ (used to generate absolute URLs)' => '',
// 'Actions duplicated successfully.' => '',
// 'Unable to duplicate actions.' => '',
// 'Add a new action' => '',
// 'Import from another project' => '',
// 'There is no action at the moment.' => '',
// 'Import actions from another project' => '',
// 'There is no available project.' => '',
// 'Local File' => '',
// 'Configuration' => '',
// 'PHP version:' => '',
// 'PHP SAPI:' => '',
// 'OS version:' => '',
// 'Database version:' => '',
// 'Browser:' => '',
// 'Task view' => '',
// 'Edit task' => '',
// 'Edit description' => '',
// 'New internal link' => '',
// 'Display list of keyboard shortcuts' => '',
// 'Menu' => '',
// 'Set start date' => '',
// 'Avatar' => '',
// 'Upload my avatar image' => '',
// 'Remove my image' => '',
// 'The OAuth2 state parameter is invalid' => '',
);

View file

@ -8,7 +8,6 @@ return array(
'Edit' => 'Bearbeiten',
'remove' => 'Entfernen',
'Remove' => 'Entfernen',
'Update' => 'Aktualisieren',
'Yes' => 'Ja',
'No' => 'Nein',
'cancel' => 'Abbrechen',
@ -60,7 +59,6 @@ return array(
'Actions' => 'Aktionen',
'Inactive' => 'Inaktiv',
'Active' => 'Aktiv',
'Add this column' => 'Diese Spalte hinzufügen',
'%d tasks on the board' => '%d Aufgaben auf dieser Pinnwand',
'%d tasks in total' => '%d Aufgaben insgesamt',
'Unable to update this board.' => 'Ändern dieser Pinnwand nicht möglich.',
@ -74,7 +72,6 @@ return array(
'All projects' => 'Alle Projekte',
'Add a new column' => 'Neue Spalte hinzufügen',
'Title' => 'Titel',
'Nobody assigned' => 'Nicht zugeordnet',
'Assigned to %s' => 'Zuständig: %s',
'Remove a column' => 'Spalte löschen',
'Remove a column from a board' => 'Spalte einer Pinnwand löschen',
@ -168,7 +165,6 @@ return array(
'Task count' => 'Aufgabenanzahl',
'User' => 'Benutzer',
'Comments' => 'Kommentare',
'Write your text in Markdown' => 'Schreibe deinen Text in Markdown-Syntax',
'Leave a comment' => 'Kommentar eingeben',
'Comment is required' => 'Ein Kommentar wird benötigt',
'Leave a description' => 'Beschreibung eingeben',
@ -184,7 +180,6 @@ return array(
'Unable to remove this action.' => 'Löschen der Aktion nicht möglich.',
'Action removed successfully.' => 'Aktion erfolgreich gelöscht.',
'Automatic actions for the project "%s"' => 'Automatische Aktionen für das Projekt "%s"',
'Defined actions' => 'Definierte Aktionen',
'Add an action' => 'Aktion hinzufügen',
'Event name' => 'Ereignisname',
'Action name' => 'Aktionsname',
@ -194,7 +189,6 @@ return array(
'When the selected event occurs execute the corresponding action.' => 'Wenn das gewählte Ereignis eintritt, führe die zugehörige Aktion aus.',
'Next step' => 'Weiter',
'Define action parameters' => 'Aktionsparameter definieren',
'Save this action' => 'Aktion speichern',
'Do you really want to remove this action: "%s"?' => 'Soll diese Aktion wirklich gelöscht werden: "%s"?',
'Remove an automatic action' => 'Löschen einer automatischen Aktion',
'Assign the task to a specific user' => 'Aufgabe einem Benutzer zuordnen',
@ -333,10 +327,10 @@ return array(
'Time tracking:' => 'Zeittracking',
'New sub-task' => 'Neue Teilaufgabe',
'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',
'New attachment' => 'Neuer Anhang',
'New comment' => 'Neuer Kommentar',
'Comment updated' => 'Kommentar wurde aktualisiert',
'New subtask' => 'Neue Teilaufgabe',
'Subtask updated' => 'Teilaufgabe aktualisiert',
'Task updated' => 'Aufgabe aktualisiert',
@ -436,7 +430,6 @@ return array(
'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 Teilaufgabe zu erstellen',
'Add' => 'Hinzufügen',
'Start date' => 'Startdatum',
'Time estimated' => 'Geschätzte Zeit',
@ -487,9 +480,6 @@ return array(
'Daily project summary export for "%s"' => 'Export der täglichen Projektzusammenfassung für "%s"',
'Exports' => 'Exporte',
'This export contains the number of tasks per column grouped per day.' => 'Dieser Export enthält die Anzahl der Aufgaben pro Spalte nach Tagen gruppiert.',
'Nothing to preview...' => 'Nichts in der Vorschau anzuzeigen ...',
'Preview' => 'Vorschau',
'Write' => 'Ändern',
'Active swimlanes' => 'Aktive Swimlane',
'Add a new swimlane' => 'Eine neue Swimlane hinzufügen',
'Change default swimlane' => 'Standard-Swimlane ändern',
@ -543,7 +533,6 @@ return array(
'Task age in days' => 'Aufgabenalter in Tagen',
'Days in this column' => 'Tage in dieser Spalte',
'%dd' => '%dT',
'Add a link' => 'Verbindung hinzufügen',
'Add a new link' => 'Neue Verbindung hinzufügen',
'Do you really want to remove this link: "%s"?' => 'Die Verbindung "%s" wirklich löschen?',
'Do you really want to remove this link with task #%d?' => 'Die Verbindung mit der Aufgabe #%d wirklich löschen?',
@ -641,7 +630,7 @@ return array(
'Burndown chart' => 'Burndown-Diagramm',
'This chart show the task complexity over the time (Work Remaining).' => 'Dieses Diagramm zeigt die Aufgabenkomplexität über den Faktor Zeit (Verbleibende Arbeit).',
'Screenshot taken %s' => 'Screenshot aufgenommen %s ',
'Add a screenshot' => 'Füge einen Screenshot hinzu',
'Add a screenshot' => 'Screenshot hinzufügen',
'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Nimm einen Screenshot auf und drücke STRG+V oder ⌘+V um ihn hier einzufügen.',
'Screenshot uploaded successfully.' => 'Screenshot erfolgreich hochgeladen.',
'SEK - Swedish Krona' => 'SEK - Schwedische Kronen',
@ -734,7 +723,7 @@ return array(
'Time spent changed: %sh' => 'Verbrauchte Zeit geändert: %sh',
'Time estimated changed: %sh' => 'Geschätzte Zeit geändert: %sh',
'The field "%s" have been updated' => 'Das Feld "%s" wurde verändert',
'The description have been modified' => 'Die Beschreibung wurde geändert',
'The description has been modified:' => 'Die Beschreibung wurde geändert:',
'Do you really want to close the task "%s" as well as all subtasks?' => 'Soll die Aufgabe "%s" wirklich geschlossen werden? (einschließlich Teilaufgaben)',
'I want to receive notifications for:' => 'Ich möchte Benachrichtigungen erhalten für:',
'All tasks' => 'Alle Aufgaben',
@ -753,8 +742,6 @@ return array(
'My activity stream' => 'Aktivitätsstream',
'My calendar' => 'Mein Kalender',
'Search tasks' => 'Suche nach Aufgaben',
'Back to the calendar' => 'Zurück zum Kalender',
'Filters' => 'Filter',
'Reset filters' => 'Filter zurücksetzen',
'My tasks due tomorrow' => 'Meine morgen fälligen Aufgaben',
'Tasks due today' => 'Heute fällige Aufgaben',
@ -854,7 +841,6 @@ return array(
'End date:' => 'Endedatum:',
'There is no start date or end date for this project.' => 'Es gibt kein Startdatum oder Endedatum für dieses Projekt',
'Projects Gantt chart' => 'Projekt Gantt Diagramm',
'Link type' => 'Verbindungstyp',
'Change task color when using a specific task link' => 'Aufgabefarbe ändern bei bestimmter Aufgabenverbindung',
'Task link creation or modification' => 'Aufgabenverbindung erstellen oder bearbeiten',
'Milestone' => 'Meilenstein',
@ -906,7 +892,6 @@ return array(
'Shared' => 'Geteilt',
'Owner' => 'Eigentümer',
'Unread notifications' => 'Ungelesene Benachrichtigungen',
'My filters' => 'Meine Filter',
'Notification methods:' => 'Benachrichtigungs-Methoden:',
'Import tasks from CSV file' => 'Importiere Aufgaben aus CSV Datei',
'Unable to read your file' => 'Die Datei kann nicht gelesen werden',
@ -930,7 +915,7 @@ return array(
'change sorting' => 'Sortierung ändern',
'Tasks Importation' => 'Aufgaben Import',
'Delimiter' => 'Trennzeichen',
'Enclosure' => 'Anlage',
'Enclosure' => 'Textbegrenzer',
'CSV File' => 'CSV Datei',
'Instructions' => 'Anweisungen',
'Your file must use the predefined CSV format' => 'Ihre Datei muss das vorgegebene CSV Format haben',
@ -944,6 +929,7 @@ return array(
'Usernames must be lowercase and unique' => 'Benutzernamen müssen in Kleinbuschstaben und eindeutig sein',
'Passwords will be encrypted if present' => 'Passwörter werden verschlüsselt wenn vorhanden',
'%s attached a new file to the task %s' => '%s hat eine neue Datei zur Aufgabe %s hinzufgefügt',
'Link type' => 'Verbindungstyp',
'Assign automatically a category based on a link' => 'Linkbasiert eine Kategorie automatisch zuordnen',
'BAM - Konvertible Mark' => 'BAM - Konvertible Mark',
'Assignee Username' => 'Benutzername des Zuständigen',
@ -1053,7 +1039,6 @@ return array(
'Close a task when there is no activity' => 'Schliesse eine Aufgabe, wenn keine Aktivitäten vorhanden sind',
'Duration in days' => 'Dauer in Tagen',
'Send email when there is no activity on a task' => 'Versende eine Email, wenn keine Aktivitäten an einer Aufgabe vorhanden sind',
'List of external links' => 'Liste der externen Verbindungen',
'Unable to fetch link information.' => 'Kann keine Informationen über Verbindungen holen',
'Daily background job for tasks' => 'Tägliche Hintergrundarbeit für Aufgaben',
'Auto' => 'Auto',
@ -1065,15 +1050,13 @@ return array(
'Add external link' => 'Externe Verbindung hinzufügen',
'Type' => 'Typ',
'Dependency' => 'Abhängigkeit',
'Add internal link' => 'Füge interne Verbindung hinzu',
'Add internal link' => 'Interne Verbindung hinzufügen',
'Add a new external link' => 'Füge eine neue externe Verbindung hinzu',
'Edit external link' => 'Externe Verbindung bearbeiten',
'External link' => 'Externe Verbindung',
'Copy and paste your link here...' => 'Kopieren Sie Ihren Link hier...',
'URL' => 'URL',
'There is no external link for the moment.' => 'Es gibt im Moment keine externe Verbindung.',
'Internal links' => 'Interne Verbindungen',
'There is no internal link for the moment.' => 'Es gibt im Moment keine interne Verbindung.',
'Assign to me' => 'Mir zuweisen',
'Me' => 'Mich',
'Do not duplicate anything' => 'Nichts duplizieren',
@ -1081,7 +1064,6 @@ return array(
'Users management' => 'Benutzermanagement',
'Groups management' => 'Gruppenmanagement',
'Create from another project' => 'Von einem anderen Projekt erstellen',
'There is no subtask at the moment.' => 'Es gibt im Moment keine Teilaufgabe',
'open' => 'offen',
'closed' => 'geschlossen',
'Priority:' => 'Priorität:',
@ -1100,7 +1082,6 @@ return array(
'Started:' => 'Gestarted:',
'Moved:' => 'Verschoben:',
'Task #%d' => 'Aufgabe #%d',
'Sub-tasks' => 'Teilaufgaben',
'Date and time format' => 'Datums- und Zeitformat',
'Time format' => 'Zeitformat',
'Start date: ' => 'Anfangsdatum:',
@ -1141,11 +1122,35 @@ return array(
'User filters' => 'Benutzer-Filter',
'Category filters' => 'Kategorie-Filter',
'Upload a file' => 'Eine Datei hochladen',
'There is no attachment at the moment.' => 'Es gibt zur Zeit keine Anhänge',
'View file' => 'Datei ansehen',
'Last activity' => 'Letzte Aktivität',
'Change subtask position' => 'Position der Unteraufgabe ändern',
'This value must be greater than %d' => 'Dieser Wert muss größer als %d sein',
'Another swimlane with the same name exists in the project' => 'Es gibt bereits eine Swimlane mit diesem Namen im Projekt',
'Example: http://example.kanboard.net/ (used to generate absolute URLs)' => 'Beispiel: http://example.kanboard.net (wird zum Erstellen absoluter URLs genutzt)',
'Actions duplicated successfully.' => 'Aktionen erfolgreich dupliziert',
'Unable to duplicate actions.' => 'Aktionen können nicht dupliziert werden.',
'Add a new action' => 'Neue Aktion hinzufügen',
'Import from another project' => 'Von einem anderen Projekt importieren',
'There is no action at the moment.' => 'Es gibt zur Zeit keine Aktionen.',
'Import actions from another project' => 'Aktionen von einem anderen Projekt importieren',
'There is no available project.' => 'Es ist kein Projekt verfügbar.',
// 'Local File' => '',
// 'Configuration' => '',
// 'PHP version:' => '',
// 'PHP SAPI:' => '',
// 'OS version:' => '',
// 'Database version:' => '',
// 'Browser:' => '',
// 'Task view' => '',
// 'Edit task' => '',
// 'Edit description' => '',
// 'New internal link' => '',
// 'Display list of keyboard shortcuts' => '',
// 'Menu' => '',
// 'Set start date' => '',
// 'Avatar' => '',
// 'Upload my avatar image' => '',
// 'Remove my image' => '',
// 'The OAuth2 state parameter is invalid' => '',
);

View file

@ -8,7 +8,6 @@ return array(
'Edit' => 'Διόρθωση',
'remove' => 'αφαιρετής',
'Remove' => 'Αφαίρεση',
'Update' => 'Ενημέρωση',
'Yes' => 'Ναι',
'No' => 'Όχι',
'cancel' => 'ακύρωση',
@ -18,13 +17,13 @@ return array(
'Green' => 'Πράσινο',
'Purple' => 'Βιολετί',
'Red' => 'Κόκκινο',
'Orange' => 'Ποστοκαλί',
'Orange' => 'Πορτοκαλί',
'Grey' => 'Γκρίζο',
'Brown' => 'Καφέ',
'Deep Orange' => 'Βαθύ πορτοκαλί',
'Dark Grey' => 'Βαθύ γκρί',
'Pink' => 'Ρόζ',
'Teal' => 'Τουρκουάζ',
'Teal' => 'Τυρκουάζ',
'Cyan' => 'Γαλάζιο',
'Lime' => 'Λεμονί',
'Light Green' => 'Ανοιχτό πράσινο',
@ -60,7 +59,6 @@ return array(
'Actions' => 'Ενέργειες',
'Inactive' => 'Ανενεργός',
'Active' => 'Ενεργός',
'Add this column' => 'Προσθήκη αυτής της στήλης',
'%d tasks on the board' => '%d εργασίες στον κεντρικό πίνακα έργου',
'%d tasks in total' => '%d εργασιών στο σύνολο',
'Unable to update this board.' => 'Αδύνατη η ενημέρωση αυτού του πίνακα',
@ -74,7 +72,6 @@ return array(
'All projects' => 'Όλα τα έργα',
'Add a new column' => 'Πρόσθήκη στήλης',
'Title' => 'Τίτλος',
'Nobody assigned' => 'Δεν έχει ανατεθεί',
'Assigned to %s' => 'Ανατιθεμένο στον %s',
'Remove a column' => 'Αφαίρεση στήλης',
'Remove a column from a board' => 'Αφαίρεση στήλης από τον πίνακα',
@ -95,7 +92,7 @@ return array(
'Edit a task' => 'Διόρθωση εργασίας',
'Column' => 'Στήλη',
'Color' => 'Χρώμα',
'Assignee' => 'Ανατεθιμένα',
'Assignee' => 'Ανατεθιμένο στον χρήστη',
'Create another task' => 'Δημιουργία και άλλης εργασίας',
'New task' => 'Νέα εργασία',
'Open a task' => 'Άνοιγμα εργασίας',
@ -168,7 +165,6 @@ return array(
'Task count' => 'Αρίθμηση εργασιών',
'User' => 'Χρήστης',
'Comments' => 'Σχόλια',
'Write your text in Markdown' => 'Δυνατότητα γραφής και σε Markdown',
'Leave a comment' => 'Αφήστε ένα σχόλιο',
'Comment is required' => 'Το σχόλιο απαιτείται',
'Leave a description' => 'Αφήστε μια περιγραφή',
@ -184,7 +180,6 @@ return array(
'Unable to remove this action.' => 'Δεν είναι δυνατή η αφαίρεση αυτής της ενέργειας',
'Action removed successfully.' => 'Η ενέργεια αφαιρέθηκε με επιτυχία.',
'Automatic actions for the project "%s"' => 'Αυτόματες ενέργειες για το έργο « %s »',
'Defined actions' => 'Ορισμένες ενέργειες',
'Add an action' => 'Προσθήκη ενέργειας',
'Event name' => 'Ονομασία συμβάντος',
'Action name' => 'Ονομασία ενέργειας',
@ -194,7 +189,6 @@ return array(
'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' => 'Αφαίρεση της αυτόματης ενέργειας',
'Assign the task to a specific user' => 'Ανάθεση της εργασίας σε συγκεκριμένο χρήστη',
@ -228,7 +222,7 @@ return array(
'Persistent connections' => 'Μόνιμες συνδέσεις',
'No session.' => 'Καμμία συνεδρία',
'Expiration date' => 'Ημερομηνία λήξης',
'Remember Me' => 'Remember Me',
'Remember Me' => 'Να με θυμάσαι',
'Creation date' => 'Ημερομηνία δημιουργίας',
'Everybody' => 'Όλα',
'Open' => 'Ανοικτά',
@ -292,7 +286,7 @@ return array(
'estimated' => 'κατ\' εκτίμηση',
'Sub-Tasks' => 'Υπο-Εργασίες',
'Add a sub-task' => 'Προσθήκη υπο-εργασίας',
'Original estimate' => 'Original estimate',
'Original estimate' => 'Αρχική πρόβλεψη χρόνου',
'Create another sub-task' => 'Δημιουργία κι άλλης υπο-εργασίας',
'Time spent' => 'Χρόνος που ξοδεύτηκε',
'Edit a sub-task' => 'Διόρθωση υπο-εργασίας',
@ -333,12 +327,12 @@ return array(
'Time tracking:' => 'Παρακολούθηση του χρόνου:',
'New sub-task' => 'Νέα υπο-εργασία',
'New attachment added "%s"' => 'Νέα επικόλληση προστέθηκε « %s »',
'Comment updated' => 'Το σχόλιο ενημερώθηκε',
'New comment posted by %s' => 'Νέο σχόλιο από τον χρήστη « %s »',
'New attachment' => 'New attachment',
'New comment' => 'Νέο σχόλιο',
'Comment updated' => 'Το σχόλιο ενημερώθηκε',
'New subtask' => 'Νέα υπο-εργασία',
'Subtask updated' => 'Υπο-Εργασία ενημερώθηκε',
'Subtask updated' => 'Η Υπο-Εργασία ενημερώθηκε',
'Task updated' => 'Η εργασία ενημερώθηκε',
'Task closed' => 'Η εργασία έκλεισε',
'Task opened' => 'Η εργασία άνοιξε',
@ -394,8 +388,8 @@ return array(
'RSS feed' => 'RSS feed',
'%s updated a comment on the task #%d' => '%s ενημέρωσε ένα σχόλιο στην εργασία n°%d',
'%s commented on the task #%d' => '%s σχολίασε την εργασία n°%d',
'%s updated a subtask for the task #%d' => '%s ενημέρωσε μια υπο-εργασία στην εργασία n °%d',
'%s created a subtask for the task #%d' => '%s δημιούργησε μια υπο-εργασία στην εργασία n°%d',
'%s updated a subtask for the task #%d' => '%s ενημερώθηκε μια υπο-εργασία στην εργασία n °%d',
'%s created a subtask for the task #%d' => '%s δημιουργήθηκε μια υπο-εργασία στην εργασία n°%d',
'%s updated the task #%d' => '%s ενημέρωσε την εργασία n°%d',
'%s created the task #%d' => '%s δημιούργησε την εργασία n°%d',
'%s closed the task #%d' => '%s έκλεισε την εργασία n°%d',
@ -421,7 +415,7 @@ return array(
'Board settings' => 'Board settings',
'URL and token' => 'URL / token',
'Webhook settings' => 'Webhook settings',
'URL for task creation:' => 'URL για δημιουργία εργασίας: :',
'URL for task creation:' => 'URL για δημιουργία εργασίας:',
'Reset token' => 'Reset token',
'API endpoint:' => 'URL API:',
'Refresh interval for private board' => 'Ανανέωση interval στο private board',
@ -436,7 +430,6 @@ return array(
'ISO format is always accepted, example: "%s" and "%s"' => 'ISO format είναι πάντα αποδεκτό, π.χ.: « %s » και « %s »',
'New private project' => 'Νέο ιδιωτικό έργο',
'This project is private' => 'Αυτό το έργο είναι ιδιωτικό',
'Type here to create a new sub-task' => 'Πληκτρολογήστε εδώ για να δημιουργήσετε μια νέα υπο-εργασία',
'Add' => 'Προσθήκη',
'Start date' => 'Ημερομηνία έναρξης',
'Time estimated' => 'Εκτιμώμενος χρόνος',
@ -487,21 +480,18 @@ return array(
'Daily project summary export for "%s"' => 'Εξαγωγή της καθημερινής περίληψης του έργου « %s »',
'Exports' => 'Εξαγωγές',
'This export contains the number of tasks per column grouped per day.' => 'Αυτή η κατάσταση περιέχει τον αριθμό των εργασιών ανά στήλη ομαδοποιημένα ανά ημέρα.',
'Nothing to preview...' => 'Τίποτα για προεπισκόπηση...',
'Preview' => 'Προεπισκόπηση',
'Write' => 'Write',
'Active swimlanes' => 'Ενεργά swimlanes',
'Add a new swimlane' => 'Πρόσθεσε ένα νέο λωρίδα',
'Change default swimlane' => 'Αλλαγή του default λωρίδα',
'Default swimlane' => 'Default λωρίδα',
'Active swimlanes' => 'Ενεργές λωρίδες',
'Add a new swimlane' => 'Προσθήκη λωρίδας',
'Change default swimlane' => 'Αλλαγή της εξ\' ορισμού λωρίδας',
'Default swimlane' => 'Εξ\' ορισμού λωρίδα',
'Do you really want to remove this swimlane: "%s"?' => 'Σίγουρα θέλετε να αφαιρέσετε τη λωρίδα : « %s » ?',
'Inactive swimlanes' => 'Λωρίδες ανενεργές',
'Remove a swimlane' => 'Αφαιρέστε μια λωρίδα',
'Inactive swimlanes' => 'Ανενεργές Λωρίδες',
'Remove a swimlane' => 'Αφαίρεση λωρίδας',
'Show default swimlane' => 'Εμφάνιση προεπιλεγμένων λωρίδων',
'Swimlane modification for the project "%s"' => 'Τροποποίηση λωρίδας για το έργο « %s »',
'Swimlane not found.' => 'Η λωρίδα δεν βρέθηκε.',
'Swimlane removed successfully.' => 'Η λωρίδα αφαιρέθηκε με επιτυχία.',
'Swimlanes' => 'Swimlanes',
'Swimlanes' => 'Λωρίδες',
'Swimlane updated successfully.' => 'Η λωρίδα ενημερώθηκε με επιτυχία.',
'The default swimlane have been updated successfully.' => 'Η προεπιλεγμένη λωρίδα ενημερώθηκε με επιτυχία.',
'Unable to remove this swimlane.' => 'Αδύνατο να αφαιρεθεί η λωρίδα.',
@ -535,7 +525,7 @@ return array(
'Subtask timesheet' => 'Πρόγραμμα υπο-εργασίας',
'There is nothing to show.' => 'Δεν υπάρχει κάτι.',
'Time Tracking' => 'Παρακολούθηση χρονοδιαγράμματος',
'You already have one subtask in progress' => 'Έχτε ήδη μια υπο-εργασία σε εξέλιξη',
'You already have one subtask in progress' => 'Έχετε ήδη μια υπο-εργασία σε εξέλιξη',
'Which parts of the project do you want to duplicate?' => 'Ποιά κομμάτια του έργου θέλετε να αντιγράψετε ?',
'Disallow login form' => 'Απαγόρευση φόρμας σύνδεσης',
'Start' => 'Εκκίνηση',
@ -543,7 +533,6 @@ return array(
'Task age in days' => 'Χρόνος εργασίας σε μέρες',
'Days in this column' => 'Μέρες σε αυτή την στήλη',
'%dd' => '%dημ',
'Add a link' => 'Προσθήκη ενός link',
'Add a new link' => 'Προσθήκη ενός νέου link',
'Do you really want to remove this link: "%s"?' => 'Θέλετε σίγουρα να αφαιρέσετε αυτό το link : « %s » ?',
'Do you really want to remove this link with task #%d?' => 'Θέλετε σίγουρα να αφαιρέσετε αυτό το link του έργου n°%d ?',
@ -587,9 +576,9 @@ return array(
'Keyboard shortcuts' => 'Συντομεύσεις πληκτρολογίου',
'Open board switcher' => 'Άνοιγμα μεταγωγέα κεντρικού πίνακα',
'Application' => 'Εφαρμογή',
'Compact view' => 'Συμπηκνωμένη προβολή',
'Compact view' => 'Συμπυκνωμένη προβολή',
'Horizontal scrolling' => 'Οριζόντια ολίσθηση',
'Compact/wide view' => 'Συμπηκνωμένη/Ευρεία Προβολή',
'Compact/wide view' => 'Συμπυκνωμένη/Ευρεία Προβολή',
'No results match:' => 'Δεν ταιριάζει κανένα αποτέλεσμα:',
'Currency' => 'Νόμισμα',
'Private project' => 'Ιδιωτικό έργο',
@ -734,7 +723,7 @@ return array(
'Time spent changed: %sh' => 'Ο χρόνος που πέρασε έχει αλλάξει: %sh',
'Time estimated changed: %sh' => 'Ο εκτιμώμενος χρόνος άλλαξε: %sh',
'The field "%s" have been updated' => 'Το πεδίο « %s » έχει ενημερωθεί',
'The description have been modified' => 'Η περιγραφή έχει ενημερωθεί',
'The description has been modified:' => 'Η περιγραφή έχει ενημερωθεί',
'Do you really want to close the task "%s" as well as all subtasks?' => 'Σίγουρα θέλετε να κλείσετε την εργασία « %s » και την υπο-εργασία ?',
'I want to receive notifications for:' => 'Επιθυμώ να λαμβάνω ενημερώσεις για :',
'All tasks' => 'Όλες οι εργασίες',
@ -753,8 +742,6 @@ return array(
'My activity stream' => 'Η ροή δραστηριοτήτων μου',
'My calendar' => 'Το ημερολόγιο μου',
'Search tasks' => 'Αναζήτηση εργασιών',
'Back to the calendar' => 'Πίσω στο ημερολόγιο',
'Filters' => 'Φίλτρα',
'Reset filters' => 'Επαναφορά φίλτρων',
'My tasks due tomorrow' => 'Οι εργασίες καθηκόντων μου αύριο',
'Tasks due today' => 'Οι εργασίες καθηκόντων μου αύριο',
@ -830,8 +817,8 @@ return array(
'Sort by position' => 'Ταξινόμηση κατά Θέση',
'Sort by date' => 'Ταξινόμηση κατά ημέρα',
'Add task' => 'Προσθήκη εργασίας',
'Start date:' => 'Ημέρα εκκίνησης :',
'Due date:' => 'Ημέρα καθηκόντων :',
'Start date:' => 'Ημερομηνία εκκίνησης :',
'Due date:' => 'Ημερομηνία λήξης:',
'There is no start date or due date for this task.' => 'Δεν υπάρχει ημερομηνία έναρξης ή ημερομηνία λήξης καθηκόντων για το έργο αυτό.',
'Moving or resizing a task will change the start and due date of the task.' => 'Μετακίνηση ή αλλαγή μεγέθους μιας εργασίας θα αλλάξει την ώρα έναρξης και ημερομηνία λήξης της εργασίας.',
'There is no task in your project.' => 'Δεν υπάρχει καμία εργασία στο έργο σας.',
@ -854,7 +841,6 @@ return array(
'End date:' => 'Ημερομηνία λήξης :',
'There is no start date or end date for this project.' => 'Δεν υπάρχει ημερομηνία έναρξης ή λήξης για το έργο αυτό.',
'Projects Gantt chart' => 'Διάγραμμα Gantt έργων',
'Link type' => 'Τύπος συνδέσμου',
'Change task color when using a specific task link' => 'Αλλαγή χρώματος εργασίας χρησιμοποιώντας συγκεκριμένο σύνδεσμο εργασίας',
'Task link creation or modification' => 'Σύνδεσμος δημιουργίας ή τροποποίησης εργασίας',
'Milestone' => 'Ορόσημο',
@ -906,7 +892,6 @@ return array(
'Shared' => 'Διαμοιρασμένα',
'Owner' => 'Ιδιοκτήτης',
'Unread notifications' => 'Αδιάβαστες ειδοποιήσεις',
'My filters' => 'Τα φίλτρα μου',
'Notification methods:' => 'Μέθοδοι ειδοποίησης:',
'Import tasks from CSV file' => 'Εισαγωγή εργασιών μέσω αρχείου CSV',
'Unable to read your file' => 'Δεν είναι δυνατή η ανάγνωση του αρχείου',
@ -944,6 +929,7 @@ return array(
'Usernames must be lowercase and unique' => 'Οι ονομασίες χρηστών πρέπει να είναι σε μικρά γράμματα (lowercase) και μοναδικά',
'Passwords will be encrypted if present' => 'Οι κωδικοί πρόσβασης κρυπτογραφούνται, αν υπάρχουν',
'%s attached a new file to the task %s' => '%s νέο συνημμένο αρχείο της εργασίας %s',
'Link type' => 'Τύπος συνδέσμου',
'Assign automatically a category based on a link' => 'Ανατίθεται αυτόματα κατηγορία, βασισμένη στον σύνδεσμο',
'BAM - Konvertible Mark' => 'BAM - Konvertible Mark',
'Assignee Username' => 'Δικαιοδόχο όνομα χρήστη',
@ -1014,8 +1000,8 @@ return array(
'Close a task in a specific column' => 'Κλείσιμο εργασίας σε συγκεκριμένη στήλη',
'Time-based One-time Password Algorithm' => 'Time-based One-time Password Algorithm',
'Two-Factor Provider: ' => 'Two-Factor Provider: ',
'Disable two-factor authentication' => 'Disable two-factor authentication',
'Enable two-factor authentication' => 'Enable two-factor authentication',
'Disable two-factor authentication' => 'Απενεργοποίηση two-factor authentication',
'Enable two-factor authentication' => 'Ενεργοποίηση two-factor authentication',
'There is no integration registered at the moment.' => 'There is no integration registered at the moment.',
'Password Reset for Kanboard' => 'Αρχικοποίηση κωδικών πρόσβασης για την εφαρμογή Kanboard',
'Forgot password?' => 'Ξεχάσατε τον κωδικό πρόσβασης ?',
@ -1036,116 +1022,135 @@ return array(
'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'Κανένα plugin δεν έχει καταχωρηθεί με τη μέθοδο της κοινοποίησης του έργου. Μπορείτε ακόμα να διαμορφώσετε τις μεμονωμένες κοινοποιήσεις στο προφίλ χρήστη σας.',
'My dashboard' => 'Το κεντρικό ταμπλό μου',
'My profile' => 'Το προφίλ μου',
// 'Project owner: ' => '',
// 'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => '',
// 'Project owner' => '',
// 'Those dates are useful for the project Gantt chart.' => '',
// 'Private projects do not have users and groups management.' => '',
// 'There is no project member.' => '',
// 'Priority' => '',
// 'Task priority' => '',
// 'General' => '',
// 'Dates' => '',
// 'Default priority' => '',
// 'Lowest priority' => '',
// 'Highest priority' => '',
// 'If you put zero to the low and high priority, this feature will be disabled.' => '',
// 'Close a task when there is no activity' => '',
// 'Duration in days' => '',
// 'Send email when there is no activity on a task' => '',
// 'List of external links' => '',
// 'Unable to fetch link information.' => '',
// 'Daily background job for tasks' => '',
// 'Auto' => '',
// 'Related' => '',
// 'Attachment' => '',
// 'Title not found' => '',
// 'Web Link' => '',
// 'External links' => '',
// 'Add external link' => '',
// 'Type' => '',
// 'Dependency' => '',
// 'Add internal link' => '',
// 'Add a new external link' => '',
// 'Edit external link' => '',
// 'External link' => '',
// 'Copy and paste your link here...' => '',
// 'URL' => '',
// 'There is no external link for the moment.' => '',
// 'Internal links' => '',
// 'There is no internal link for the moment.' => '',
// 'Assign to me' => '',
// 'Me' => '',
// 'Do not duplicate anything' => '',
// 'Projects management' => '',
// 'Users management' => '',
// 'Groups management' => '',
// 'Create from another project' => '',
// 'There is no subtask at the moment.' => '',
// 'open' => '',
// 'closed' => '',
// 'Priority:' => '',
// 'Reference:' => '',
// 'Complexity:' => '',
// 'Swimlane:' => '',
// 'Column:' => '',
// 'Position:' => '',
// 'Creator:' => '',
// 'Time estimated:' => '',
// '%s hours' => '',
// 'Time spent:' => '',
// 'Created:' => '',
// 'Modified:' => '',
// 'Completed:' => '',
// 'Started:' => '',
// 'Moved:' => '',
// 'Task #%d' => '',
// 'Sub-tasks' => '',
// 'Date and time format' => '',
// 'Time format' => '',
// 'Start date: ' => '',
// 'End date: ' => '',
// 'New due date: ' => '',
// 'Start date changed: ' => '',
// 'Disable private projects' => '',
// 'Do you really want to remove this custom filter: "%s"?' => '',
// 'Remove a custom filter' => '',
// 'User activated successfully.' => '',
// 'Unable to enable this user.' => '',
// 'User disabled successfully.' => '',
// 'Unable to disable this user.' => '',
// 'All files have been uploaded successfully.' => '',
// 'View uploaded files' => '',
// 'The maximum allowed file size is %sB.' => '',
// 'Choose files again' => '',
// 'Drag and drop your files here' => '',
// 'choose files' => '',
// 'View profile' => '',
// 'Two Factor' => '',
// 'Disable user' => '',
// 'Do you really want to disable this user: "%s"?' => '',
// 'Enable user' => '',
// 'Do you really want to enable this user: "%s"?' => '',
// 'Download' => '',
// 'Uploaded: %s' => '',
// 'Size: %s' => '',
// 'Uploaded by %s' => '',
// 'Filename' => '',
// 'Size' => '',
// 'Column created successfully.' => '',
// 'Another column with the same name exists in the project' => '',
// 'Default filters' => '',
// 'Your board doesn\'t have any column!' => '',
// 'Change column position' => '',
// 'Switch to the project overview' => '',
// 'User filters' => '',
// 'Category filters' => '',
// 'Upload a file' => '',
// 'There is no attachment at the moment.' => '',
// 'View file' => '',
// 'Last activity' => '',
// 'Change subtask position' => '',
// 'This value must be greater than %d' => '',
// 'Another swimlane with the same name exists in the project' => '',
// 'Example: http://example.kanboard.net/ (used to generate absolute URLs)' => '',
'Project owner: ' => 'Ιδιοκτήτης έργου: ',
'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'Το αναγνωριστικό έργου είναι προαιρετικό και πρέπει να είναι αλφαριθμητικό, για παράδειγμα: MYPROJECT',
'Project owner' => 'Ιδιοκτήτης έργου',
'Those dates are useful for the project Gantt chart.' => 'Οι ημερομηνίες αυτές είανι χρήσιμες για το διάγραμμα Gantt του έργου',
'Private projects do not have users and groups management.' => 'Τα ιδιωτικά έργα δεν έχουν χρήστες και διαχείριση ομάδων',
'There is no project member.' => 'Δεν υπάρχει μέλος στο έργο',
'Priority' => 'Προτεραιότητα',
'Task priority' => 'Προτεραιότητα εργασίας',
'General' => 'Γενικά',
'Dates' => 'Ημερομηνίες',
'Default priority' => 'Εξ ορισμού προτεραιότητα',
'Lowest priority' => 'η χαμηλότερη προτεραιότητα',
'Highest priority' => 'η υψηλότερη προτεραιότητα',
'If you put zero to the low and high priority, this feature will be disabled.' => 'Αν βάλετε μηδέν στη χαμηλή και στην υψηλή προτεραιότητα, το χαρακτηριστικό αυτό απενεργοποιείται.',
'Close a task when there is no activity' => 'Κλείσιμο εργασίας όταν δεν υπάρχει δραστηριότητα',
'Duration in days' => 'Διάρκεια σε ημέρες',
'Send email when there is no activity on a task' => 'Αποστολή email όταν δεν υπάρχει δραστηριότητα σε εργασία',
'Unable to fetch link information.' => 'Δεν είναι δυνατή η ανάλυση της πληροφορίας συνδεσμου',
'Daily background job for tasks' => 'Ημερήσια παρασκηνιακή δουλειά για τις εργασίες',
'Auto' => 'Αυτόματο',
'Related' => 'Σχετίζεται',
'Attachment' => 'Συνημμένο',
'Title not found' => 'Ο τίτλος δεν βρέθηκε',
'Web Link' => 'Σύνδεσμος web',
'External links' => 'Εξωτερικοί σύνδεσμοι',
'Add external link' => 'Προσθήκη εξωτερικού συνδέσμου',
'Type' => 'Τύπος',
'Dependency' => 'Εξάρτηση',
'Add internal link' => 'Προσθήκη εσωτερικού συνδέσμου',
'Add a new external link' => 'Προσθήκη νέου εξωτερικού συνδέσμου',
'Edit external link' => 'Διόρθωση εξωτερικού συνδέσμου',
'External link' => 'Εξωτερικός σύνδεσμος',
'Copy and paste your link here...' => 'Κάντε αντιγραφή και επικόλληση εδώ',
'URL' => 'URL',
'Internal links' => 'Εσωτερικοί σύνδεσμοι',
'Assign to me' => 'Αναττίθεται σε εμένα',
'Me' => 'Σε μένα',
'Do not duplicate anything' => 'Να μην γίνει κλωνοποίηση από άλλο έργο',
'Projects management' => 'Διαχείριση έργων',
'Users management' => 'Διαχείριση χρηστών',
'Groups management' => 'Διαχείριση ομάδων',
'Create from another project' => 'Δημιουργία από άλλο έργο',
'open' => 'Ανοικτό',
'closed' => 'Κλειστό',
'Priority:' => 'Προτεραιότητα:',
'Reference:' => 'Αναφορά:',
'Complexity:' => 'Πολυπλοκότητα:',
'Swimlane:' => 'Λωρίδα:',
'Column:' => 'Στήλη:',
'Position:' => 'Θέση:',
'Creator:' => 'Δημιουργός:',
'Time estimated:' => 'Προβλεπόμενη ώρα:',
'%s hours' => '%s ώρες',
'Time spent:' => 'χρόνος που καταναλώθηκε:',
'Created:' => 'Δημιουργήθηκε:',
'Modified:' => 'Διορθώθηκε:',
'Completed:' => 'Ολοκληρώθηκε:',
'Started:' => 'Ξεκίνησε:',
'Moved:' => 'Μετακινήθηκε:',
'Task #%d' => 'Εργασία #%d',
'Date and time format' => 'Μορφή ημερομηνίας και ώρας',
'Time format' => 'Μορφή ώρας',
'Start date: ' => 'Ημερομηνία έναρξης: ',
'End date: ' => 'Ημερομηνία λήξης: ',
'New due date: ' => 'Νέα ημερομηνία λήξης: ',
'Start date changed: ' => 'Αλλαγμένη ημερομηνία έναρξης: ',
'Disable private projects' => 'Απενεργοποίηση ιδιωτικών έργων',
'Do you really want to remove this custom filter: "%s"?' => 'Αφαίρεση αυτού του οριζόμενου από το χρήστη φίλτρου %s ?',
'Remove a custom filter' => 'Αφαίρεση του οριζόμενου από το χρήστη φίλτρου',
'User activated successfully.' => 'Ο χρήστης ενεργοποιήθηκε με επιτυχία',
'Unable to enable this user.' => 'Δεν είναι δυνατή η ενεργοποίηση του χρήστη',
'User disabled successfully.' => 'Η απενεργοποίηση του χρήστη έγινε με επιτυχία',
'Unable to disable this user.' => 'Δεν είναι δυνατή η απενεργοποίηση του χρήστη',
'All files have been uploaded successfully.' => 'Όλα τα αρχεία ανέβηκαν με επιτυχία',
'View uploaded files' => 'Προβολή ανεβασμένων αρχείων',
'The maximum allowed file size is %sB.' => 'Το μέγιστο μέγεθος αρχείου που επιτρέπεται είναι %sB.',
'Choose files again' => 'Επιλογή κι άλλων αρχείων',
'Drag and drop your files here' => 'Σύρετε τα αρχεία σας εδώ',
'choose files' => 'Επιλογή αρχείων',
'View profile' => 'Προβολή προφίλ',
'Two Factor' => 'Δύο παραγόντων',
'Disable user' => 'Απενεργοποίηση χρήστη',
'Do you really want to disable this user: "%s"?' => 'Απενεργοποίηση του χρήστη: "%s";',
'Enable user' => 'Ενεργοποίηση χρήστη',
'Do you really want to enable this user: "%s"?' => 'Ενεργοποίηση του χρήστη "%s";',
'Download' => 'Κατέβασμα',
'Uploaded: %s' => 'Ανέβηκε το αρχείο: %s',
'Size: %s' => 'Μέγεθος: %s',
'Uploaded by %s' => 'Ανέβασμα από τον χρήστη: %s',
'Filename' => 'Όνομα αρχείου',
'Size' => 'Μέγεθος',
'Column created successfully.' => 'Η στήλη δημιουργήθηκε με επιτυχία.',
'Another column with the same name exists in the project' => 'Μια άλλη στήλη με το ίδιο όνομα υπάρχει στο έργο',
'Default filters' => 'Εξ\' ορισμού φίλτρα',
'Your board doesn\'t have any column!' => 'Το ταμπλό δεν έχει καμία στήλη!!=',
'Change column position' => 'Αλλαγή θέσης στήλης',
'Switch to the project overview' => 'Αλλαγή προβολής σε επισκόπηση έργου',
'User filters' => 'Φίλτρα οριζόμενα από τον χρήστη',
'Category filters' => 'Κατηγορία φίλτρων',
'Upload a file' => 'Ανέβασμα αρχείου',
'View file' => 'Προβολή αρχείου',
'Last activity' => 'Τελευταία δραστηριότητα',
'Change subtask position' => 'Αλλαγή θέσης υπο-εργασίας',
'This value must be greater than %d' => 'Η τιμή πρέπει να είναι μεγαλύτερη από %d',
'Another swimlane with the same name exists in the project' => 'Μια άλλη λωρίδα, με το ίδιο όνομα υπάρχει στο έργο',
'Example: http://example.kanboard.net/ (used to generate absolute URLs)' => 'Παράδειγμα: http://example.kanboard.net/ (χρησιμοποιείται για τη δημιουργία απόλυτων URLs)',
// 'Actions duplicated successfully.' => '',
// 'Unable to duplicate actions.' => '',
// 'Add a new action' => '',
// 'Import from another project' => '',
// 'There is no action at the moment.' => '',
// 'Import actions from another project' => '',
// 'There is no available project.' => '',
// 'Local File' => '',
// 'Configuration' => '',
// 'PHP version:' => '',
// 'PHP SAPI:' => '',
// 'OS version:' => '',
// 'Database version:' => '',
// 'Browser:' => '',
// 'Task view' => '',
// 'Edit task' => '',
// 'Edit description' => '',
// 'New internal link' => '',
// 'Display list of keyboard shortcuts' => '',
// 'Menu' => '',
// 'Set start date' => '',
// 'Avatar' => '',
// 'Upload my avatar image' => '',
// 'Remove my image' => '',
// 'The OAuth2 state parameter is invalid' => '',
);

View file

@ -8,7 +8,6 @@ return array(
'Edit' => 'Modificar',
'remove' => 'suprimir',
'Remove' => 'Suprimir',
'Update' => 'Actualizar',
'Yes' => 'Sí',
'No' => 'No',
'cancel' => 'cancelar',
@ -60,7 +59,6 @@ return array(
'Actions' => 'Acciones',
'Inactive' => 'Inactivo',
'Active' => 'Activo',
'Add this column' => 'Añadir esta columna',
'%d tasks on the board' => '%d tareas en el tablero',
'%d tasks in total' => '%d tareas en total',
'Unable to update this board.' => 'No se puede actualizar este tablero.',
@ -74,7 +72,6 @@ return array(
'All projects' => 'Todos los proyectos',
'Add a new column' => 'Añadir una nueva columna',
'Title' => 'Título',
'Nobody assigned' => 'Nadie asignado',
'Assigned to %s' => 'Asignada a %s',
'Remove a column' => 'Suprimir esta columna',
'Remove a column from a board' => 'Suprimir una columna de un tablero',
@ -168,7 +165,6 @@ return array(
'Task count' => 'Contador de tareas',
'User' => 'Usuario',
'Comments' => 'Comentarios',
'Write your text in Markdown' => 'Redacta el texto en Markdown',
'Leave a comment' => 'Dejar un comentario',
'Comment is required' => 'El comentario es obligatorio',
'Leave a description' => 'Dejar una descripción',
@ -184,7 +180,6 @@ return array(
'Unable to remove this action.' => 'No se puede suprimir esta accción.',
'Action removed successfully.' => 'La acción ha sido borrada correctamente.',
'Automatic actions for the project "%s"' => 'Acciones automatizadas para este proyecto « %s »',
'Defined actions' => 'Acciones definidas',
'Add an action' => 'Agregar una acción',
'Event name' => 'Nombre del evento',
'Action name' => 'Nombre de la acción',
@ -194,7 +189,6 @@ return array(
'When the selected event occurs execute the corresponding action.' => 'Cuando tiene lugar el evento seleccionado, ejecutar la acción correspondiente.',
'Next step' => 'Etapa siguiente',
'Define action parameters' => 'Definición de los parametros de la acción',
'Save this action' => 'Guardar esta acción',
'Do you really want to remove this action: "%s"?' => '¿Realmente desea suprimir esta acción « %s » ?',
'Remove an automatic action' => 'Suprimir una acción automatizada',
'Assign the task to a specific user' => 'Asignar una tarea a un usuario especifico',
@ -333,10 +327,10 @@ return array(
'Time tracking:' => 'Control de tiempo:',
'New sub-task' => 'Nueva subtarea',
'New attachment added "%s"' => 'Nuevo adjunto agregado "%s"',
'Comment updated' => 'Comentario actualizado',
'New comment posted by %s' => 'Nuevo comentario agregado por %s',
'New attachment' => 'Nuevo adjunto',
'New comment' => 'Nuevo comentario',
'Comment updated' => 'Comentario actualizado',
'New subtask' => 'Nueva subtarea',
'Subtask updated' => 'Subtarea actualizada',
'Task updated' => 'Tarea actualizada',
@ -436,7 +430,6 @@ return array(
'ISO format is always accepted, example: "%s" and "%s"' => 'El formato ISO siempre es aceptado, ejemplo: "%s" y "%s"',
'New private project' => 'Nuevo proyecto privado',
'This project is private' => 'Este proyecto es privado',
'Type here to create a new sub-task' => 'Escriba aquí para crear una nueva sub-tarea',
'Add' => 'Añadir',
'Start date' => 'Fecha de inicio',
'Time estimated' => 'Tiempo estimado',
@ -487,9 +480,6 @@ return array(
'Daily project summary export for "%s"' => 'Exportar sumario diario del proyecto para "%s"',
'Exports' => 'Exportaciones',
'This export contains the number of tasks per column grouped per day.' => 'Esta exportación contiene el número de tereas por columna agrupada por día.',
'Nothing to preview...' => 'Nada que previsualizar...',
'Preview' => 'Previsualizar',
'Write' => 'Grabar',
'Active swimlanes' => 'Calles activas',
'Add a new swimlane' => 'Añadir nueva calle',
'Change default swimlane' => 'Cambiar la calle por defecto',
@ -543,7 +533,6 @@ return array(
'Task age in days' => 'Edad de la tarea en días',
'Days in this column' => 'Días en esta columna',
'%dd' => '%dd',
'Add a link' => 'Añadir enlace',
'Add a new link' => 'Añadir nuevo enlace',
'Do you really want to remove this link: "%s"?' => '¿Realmente quiere quitar este enlace: "%s"?',
'Do you really want to remove this link with task #%d?' => '¿Realmente quiere quitar este enlace con esta tarea: #%d?',
@ -734,7 +723,7 @@ return array(
'Time spent changed: %sh' => 'Se ha cambiado el tiempo empleado: %sh',
'Time estimated changed: %sh' => 'Se ha cambiado el tiempo estimado: %sh',
'The field "%s" have been updated' => 'Se ha actualizado el campo "%s"',
'The description have been modified' => 'Se ha modificado la descripción',
'The description has been modified:' => 'Se ha modificado la descripción',
'Do you really want to close the task "%s" as well as all subtasks?' => '¿De verdad que quiere cerra la tarea "%s" así como todas las subtareas?',
'I want to receive notifications for:' => 'Deseo recibir notificaciones para:',
'All tasks' => 'Todas las tareas',
@ -753,8 +742,6 @@ return array(
'My activity stream' => 'Mi flujo de actividad',
'My calendar' => 'Mi calendario',
'Search tasks' => 'Buscar tareas',
'Back to the calendar' => 'Volver al calendario',
'Filters' => 'Filtros',
'Reset filters' => 'Limpiar filtros',
'My tasks due tomorrow' => 'Mis tareas a entregar mañana',
'Tasks due today' => 'Tareas a antregar hoy',
@ -854,7 +841,6 @@ return array(
'End date:' => 'Fecha final',
'There is no start date or end date for this project.' => 'No existe fecha de inicio o de fin para este proyecto.',
'Projects Gantt chart' => 'Diagramas de Gantt de los proyectos',
'Link type' => 'Tipo de enlace',
'Change task color when using a specific task link' => 'Cambiar colo de la tarea al usar un enlace específico a tarea',
'Task link creation or modification' => 'Creación o modificación de enlace a tarea',
'Milestone' => 'Hito',
@ -906,7 +892,6 @@ return array(
'Shared' => 'Compartido',
'Owner' => 'Dueño',
'Unread notifications' => 'Notificaciones sin leer',
'My filters' => 'Mis filtros',
'Notification methods:' => 'Métodos de notificación',
'Import tasks from CSV file' => 'Importar tareas desde archivo CSV',
'Unable to read your file' => 'No es posible leer el archivo',
@ -930,7 +915,7 @@ return array(
'change sorting' => 'Cambiar orden',
'Tasks Importation' => 'Importación de tareas',
'Delimiter' => 'Delimitador',
// 'Enclosure' => '',
'Enclosure' => 'Recinto',
'CSV File' => 'Archivo CSV',
'Instructions' => 'Indicaciones',
'Your file must use the predefined CSV format' => 'Su archivo debe utilizar el formato CSV predeterminado',
@ -944,6 +929,7 @@ return array(
'Usernames must be lowercase and unique' => 'Los nombres de usuario deben ser únicos y contener sólo minúsculas',
'Passwords will be encrypted if present' => 'Las contraseñas serán cifradas si es que existen',
'%s attached a new file to the task %s' => '%s adjuntó un nuevo archivo a la tarea %s',
'Link type' => 'Tipo de enlace',
'Assign automatically a category based on a link' => 'Asignar una categoría automáticamente basado en un enlace',
'BAM - Konvertible Mark' => 'BAM - marco convertible',
'Assignee Username' => 'Nombre de usuario del concesionario',
@ -995,19 +981,19 @@ return array(
'Enter group name...' => 'Ingresa el nombre del grupo...',
'Role:' => 'Rol:',
'Project members' => 'Miembros del proyecto',
// 'Compare hours for "%s"' => '',
'Compare hours for "%s"' => 'Compara horas con "%s"',
'%s mentioned you in the task #%d' => '%s te mencionó en la tarea #%d',
'%s mentioned you in a comment on the task #%d' => '%s te mencionó en un comentario en la tarea #%d',
'You were mentioned in the task #%d' => 'Te mencionaron en la tarea #%d',
'You were mentioned in a comment on the task #%d' => 'Te mencionaron en un comentario en la tarea #%d',
'Mentioned' => 'Mencionado',
// 'Compare Estimated Time vs Actual Time' => '',
// 'Estimated hours: ' => '',
// 'Actual hours: ' => '',
// 'Hours Spent' => '',
// 'Hours Estimated' => '',
// 'Estimated Time' => '',
// 'Actual Time' => '',
'Compare Estimated Time vs Actual Time' => 'Comparar Tiempo Estimado vs Tiempo Actual',
'Estimated hours: ' => 'Horas estimadas: ',
'Actual hours: ' => 'Horas actuales: ',
'Hours Spent' => 'Horas gastadas',
'Hours Estimated' => 'Hora Estimada',
'Estimated Time' => 'Tiempo Estimado',
'Actual Time' => 'Tiempo Actual',
'Estimated vs actual time' => 'Tiempo estimado vs real',
'RUB - Russian Ruble' => 'RUB - rublo ruso',
'Assign the task to the person who does the action when the column is changed' => 'Asignar la tarea a la persona que haga la acción al cambiar de columna',
@ -1025,7 +1011,7 @@ return array(
'Change Password' => 'Cambiar contraseña',
'To reset your password click on this link:' => 'Para cambiar tu contraseña haz clic en el siguiente enlace:',
'Last Password Reset' => 'Último cambio de contraseña',
// 'The password has never been reinitialized.' => '',
'The password has never been reinitialized.' => 'La contraseña nunca se ha reinicializado.',
'Creation' => 'Creación',
'Expiration' => 'Vencimiento',
'Password reset history' => 'Historial de restablecimiento de contraseña',
@ -1053,7 +1039,6 @@ return array(
'Close a task when there is no activity' => 'Cerrar tarea cuando no haya actividad',
'Duration in days' => 'Duración en días',
'Send email when there is no activity on a task' => 'Enviar correo cuando no haya actividad en una tarea',
'List of external links' => 'Lista de enlaces externos',
'Unable to fetch link information.' => 'No es posible obtener información sobre el enlace',
'Daily background job for tasks' => 'Tarea de fondo diaria para las tareas',
'Auto' => 'Automático',
@ -1071,9 +1056,7 @@ return array(
'External link' => 'Enlace externo',
'Copy and paste your link here...' => 'Copia y pega tu enlace aquí...',
'URL' => 'URL',
'There is no external link for the moment.' => 'No existe un enlace externo por el momento',
'Internal links' => 'Enlaces internos',
'There is no internal link for the moment.' => 'No existe un enlace interno por el momento',
'Assign to me' => 'Asignar a mí',
'Me' => 'Yo',
'Do not duplicate anything' => 'No duplicar nada',
@ -1081,26 +1064,24 @@ return array(
'Users management' => 'Administración de usuarios',
'Groups management' => 'Administración de grupos',
'Create from another project' => 'Crear de otro proyecto',
'There is no subtask at the moment.' => 'No existe subtarea por el momento',
'open' => 'abierto',
'closed' => 'cerrado',
'Priority:' => 'Prioridad',
'Reference:' => 'Referencia',
// 'Complexity:' => 'Complejidad',
// 'Swimlane:' => 'Swimlane',
// 'Column:' => 'Columna',
// 'Position:' => 'Posición',
// 'Creator:' => 'Creador',
// 'Time estimated:' => '',
'Complexity:' => 'Complejidad:',
'Swimlane:' => 'Swimlane:',
'Column:' => 'Columna:',
'Position:' => 'Posición:',
'Creator:' => 'Creador:',
'Time estimated:' => 'Tiempo estimado:',
'%s hours' => '%s horas',
// 'Time spent:' => '',
'Time spent:' => 'Tiempo gastado:',
'Created:' => 'Creado',
'Modified:' => 'Modificado',
'Completed:' => 'Terminado',
'Started:' => 'Iniciado',
'Moved:' => 'Movido',
'Task #%d' => 'Tarea #%d',
'Sub-tasks' => 'Subtareas',
'Date and time format' => 'Formato de hora y fecha',
'Time format' => 'Formato de hora',
'Start date: ' => 'Fecha de inicio',
@ -1135,17 +1116,41 @@ return array(
'Column created successfully.' => 'Columna creada exitosamente',
'Another column with the same name exists in the project' => 'Ya existe una columna con el mismo nombre en el proyecto',
'Default filters' => 'Filtros predeterminados',
'Your board doesn\'t have any column!' => '¡Tu tablero no tiene ninguna columna',
'Your board doesn\'t have any column!' => '¡Tu tablero no tiene ninguna columna!',
'Change column position' => 'Cambiar posición de la columna',
'Switch to the project overview' => 'Cambiar a vista general del proyecto',
'User filters' => 'Usar filtros',
'Category filters' => 'Categoría y filtros',
'Upload a file' => 'Subir archivo',
'There is no attachment at the moment.' => 'No existe ningún adjunto por el momento',
'View file' => 'Ver archivo',
'Last activity' => 'Última actividad',
'Change subtask position' => 'Cambiar posición de la subtarea',
'This value must be greater than %d' => 'Este valor debe ser mayor a %d',
'Another swimlane with the same name exists in the project' => 'Ya existe otro swimlane con el mismo nombre en el proyecto',
'Example: http://example.kanboard.net/ (used to generate absolute URLs)' => 'Ejemplo: http://ejemplo.kanboard.net/ (Usado para generar URLs absolutas)',
'Actions duplicated successfully.' => 'Acción duplicada con exito.',
'Unable to duplicate actions.' => 'No se ha podido duplicar la acción.',
'Add a new action' => 'Añadir una nueva acción',
'Import from another project' => 'Importar de otro proyecto',
'There is no action at the moment.' => 'No hay ninguna acción en este momento.',
'Import actions from another project' => 'Importar acciones de otro proyecto',
'There is no available project.' => 'No hay proyectos disponibles.',
// 'Local File' => '',
// 'Configuration' => '',
// 'PHP version:' => '',
// 'PHP SAPI:' => '',
// 'OS version:' => '',
// 'Database version:' => '',
// 'Browser:' => '',
// 'Task view' => '',
// 'Edit task' => '',
// 'Edit description' => '',
// 'New internal link' => '',
// 'Display list of keyboard shortcuts' => '',
// 'Menu' => '',
// 'Set start date' => '',
// 'Avatar' => '',
// 'Upload my avatar image' => '',
// 'Remove my image' => '',
// 'The OAuth2 state parameter is invalid' => '',
);

View file

@ -8,7 +8,6 @@ return array(
'Edit' => 'Muokkaa',
'remove' => 'poista',
'Remove' => 'Poista',
'Update' => 'Päivitä',
'Yes' => 'Kyllä',
'No' => 'Ei',
'cancel' => 'peruuta',
@ -60,7 +59,6 @@ return array(
'Actions' => 'Toiminnot',
'Inactive' => 'Ei aktiivinen',
'Active' => 'Aktiivinen',
'Add this column' => 'Lisää tämä sarake',
'%d tasks on the board' => '%d tehtävää taululla',
'%d tasks in total' => '%d tehtävää yhteensä',
'Unable to update this board.' => 'Taulun muuttaminen ei onnistunut.',
@ -74,7 +72,6 @@ return array(
'All projects' => 'Kaikki projektit',
'Add a new column' => 'Lisää uusi sarake',
'Title' => 'Nimi',
'Nobody assigned' => 'Ei suorittajaa',
'Assigned to %s' => 'Tekijä: %s',
'Remove a column' => 'Poista sarake',
'Remove a column from a board' => 'Poista sarake taulusta',
@ -168,7 +165,6 @@ return array(
'Task count' => 'Tehtävien määrä',
'User' => 'Käyttäjät',
'Comments' => 'Kommentit',
'Write your text in Markdown' => 'Kirjoita kommenttisi Markdownilla',
'Leave a comment' => 'Lisää kommentti',
'Comment is required' => 'Kommentti vaaditaan',
'Leave a description' => 'Lisää kuvaus',
@ -184,7 +180,6 @@ return array(
'Unable to remove this action.' => 'Toiminnon poistaminen epäonnistui.',
'Action removed successfully.' => 'Toiminto poistettiin onnistuneesti.',
'Automatic actions for the project "%s"' => 'Automaattiset toiminnot projektille "%s"',
'Defined actions' => 'Määritellyt toiminnot',
// 'Add an action' => '',
'Event name' => 'Tapahtuman nimi',
'Action name' => 'Toiminnon nimi',
@ -194,7 +189,6 @@ return array(
'When the selected event occurs execute the corresponding action.' => 'Kun valittu tapahtuma tapahtuu, suorita vastaava toiminto.',
'Next step' => 'Seuraava vaihe',
'Define action parameters' => 'Määrittele toiminnon parametrit',
'Save this action' => 'Tallenna toiminto',
'Do you really want to remove this action: "%s"?' => 'Oletko varma että haluat poistaa toiminnon "%s"?',
'Remove an automatic action' => 'Poista automaattintn toiminto',
'Assign the task to a specific user' => 'Osoita tehtävä käyttäjälle',
@ -333,10 +327,10 @@ return array(
'Time tracking:' => 'Ajan seuranta:',
'New sub-task' => 'Uusi alitehtävä',
'New attachment added "%s"' => 'Uusi liite lisätty "%s"',
'Comment updated' => 'Kommentti päivitetty',
'New comment posted by %s' => '%s lisäsi uuden kommentin',
// 'New attachment' => '',
// 'New comment' => '',
'Comment updated' => 'Kommentti päivitetty',
// 'New subtask' => '',
// 'Subtask updated' => '',
// 'Task updated' => '',
@ -436,7 +430,6 @@ return array(
'ISO format is always accepted, example: "%s" and "%s"' => 'ISO-muoto on aina hyväksytty, esimerkiksi %s ja %s',
'New private project' => 'Uusi yksityinen projekti',
'This project is private' => 'Tämä projekti on yksityinen',
'Type here to create a new sub-task' => 'Kirjoita tähän luodaksesi uuden alitehtävän',
'Add' => 'Lisää',
'Start date' => 'Aloituspäivä',
'Time estimated' => 'Arvioitu aika',
@ -487,9 +480,6 @@ return array(
'Daily project summary export for "%s"' => 'Päivittäisen yhteenvedon vienti kohteeseen "%s"',
'Exports' => 'Viennit',
'This export contains the number of tasks per column grouped per day.' => 'Tämä tiedosto sisältää tehtäviä sarakkeisiin päiväkohtaisesti ryhmilteltyinä',
'Nothing to preview...' => 'Ei esikatselua...',
'Preview' => 'Ei esikatselua',
'Write' => 'Kirjoita',
'Active swimlanes' => 'Aktiiviset kaistat',
'Add a new swimlane' => 'Lisää uusi kaista',
'Change default swimlane' => 'Vaihda oletuskaistaa',
@ -543,7 +533,6 @@ return array(
// 'Task age in days' => '',
// 'Days in this column' => '',
// '%dd' => '',
// 'Add a link' => '',
// 'Add a new link' => '',
// 'Do you really want to remove this link: "%s"?' => '',
// 'Do you really want to remove this link with task #%d?' => '',
@ -734,7 +723,7 @@ return array(
// 'Time spent changed: %sh' => '',
// 'Time estimated changed: %sh' => '',
// 'The field "%s" have been updated' => '',
// 'The description have been modified' => '',
// 'The description has been modified:' => '',
// 'Do you really want to close the task "%s" as well as all subtasks?' => '',
// 'I want to receive notifications for:' => '',
// 'All tasks' => '',
@ -753,8 +742,6 @@ return array(
// 'My activity stream' => '',
// 'My calendar' => '',
// 'Search tasks' => '',
// 'Back to the calendar' => '',
// 'Filters' => '',
// 'Reset filters' => '',
// 'My tasks due tomorrow' => '',
// 'Tasks due today' => '',
@ -854,7 +841,6 @@ return array(
// 'End date:' => '',
// 'There is no start date or end date for this project.' => '',
// 'Projects Gantt chart' => '',
// 'Link type' => '',
// 'Change task color when using a specific task link' => '',
// 'Task link creation or modification' => '',
// 'Milestone' => '',
@ -906,7 +892,6 @@ return array(
// 'Shared' => '',
// 'Owner' => '',
// 'Unread notifications' => '',
// 'My filters' => '',
// 'Notification methods:' => '',
// 'Import tasks from CSV file' => '',
// 'Unable to read your file' => '',
@ -944,6 +929,7 @@ return array(
// 'Usernames must be lowercase and unique' => '',
// 'Passwords will be encrypted if present' => '',
// '%s attached a new file to the task %s' => '',
// 'Link type' => '',
// 'Assign automatically a category based on a link' => '',
// 'BAM - Konvertible Mark' => '',
// 'Assignee Username' => '',
@ -1053,7 +1039,6 @@ return array(
// 'Close a task when there is no activity' => '',
// 'Duration in days' => '',
// 'Send email when there is no activity on a task' => '',
// 'List of external links' => '',
// 'Unable to fetch link information.' => '',
// 'Daily background job for tasks' => '',
// 'Auto' => '',
@ -1071,9 +1056,7 @@ return array(
// 'External link' => '',
// 'Copy and paste your link here...' => '',
// 'URL' => '',
// 'There is no external link for the moment.' => '',
// 'Internal links' => '',
// 'There is no internal link for the moment.' => '',
// 'Assign to me' => '',
// 'Me' => '',
// 'Do not duplicate anything' => '',
@ -1081,7 +1064,6 @@ return array(
// 'Users management' => '',
// 'Groups management' => '',
// 'Create from another project' => '',
// 'There is no subtask at the moment.' => '',
// 'open' => '',
// 'closed' => '',
// 'Priority:' => '',
@ -1100,7 +1082,6 @@ return array(
// 'Started:' => '',
// 'Moved:' => '',
// 'Task #%d' => '',
// 'Sub-tasks' => '',
// 'Date and time format' => '',
// 'Time format' => '',
// 'Start date: ' => '',
@ -1141,11 +1122,35 @@ return array(
// 'User filters' => '',
// 'Category filters' => '',
// 'Upload a file' => '',
// 'There is no attachment at the moment.' => '',
// 'View file' => '',
// 'Last activity' => '',
// 'Change subtask position' => '',
// 'This value must be greater than %d' => '',
// 'Another swimlane with the same name exists in the project' => '',
// 'Example: http://example.kanboard.net/ (used to generate absolute URLs)' => '',
// 'Actions duplicated successfully.' => '',
// 'Unable to duplicate actions.' => '',
// 'Add a new action' => '',
// 'Import from another project' => '',
// 'There is no action at the moment.' => '',
// 'Import actions from another project' => '',
// 'There is no available project.' => '',
// 'Local File' => '',
// 'Configuration' => '',
// 'PHP version:' => '',
// 'PHP SAPI:' => '',
// 'OS version:' => '',
// 'Database version:' => '',
// 'Browser:' => '',
// 'Task view' => '',
// 'Edit task' => '',
// 'Edit description' => '',
// 'New internal link' => '',
// 'Display list of keyboard shortcuts' => '',
// 'Menu' => '',
// 'Set start date' => '',
// 'Avatar' => '',
// 'Upload my avatar image' => '',
// 'Remove my image' => '',
// 'The OAuth2 state parameter is invalid' => '',
);

View file

@ -8,7 +8,6 @@ return array(
'Edit' => 'Modifier',
'remove' => 'supprimer',
'Remove' => 'Supprimer',
'Update' => 'Mettre à jour',
'Yes' => 'Oui',
'No' => 'Non',
'cancel' => 'annuler',
@ -60,7 +59,6 @@ return array(
'Actions' => 'Actions',
'Inactive' => 'Inactif',
'Active' => 'Actif',
'Add this column' => 'Ajouter cette colonne',
'%d tasks on the board' => '%d tâches sur le tableau',
'%d tasks in total' => '%d tâches au total',
'Unable to update this board.' => 'Impossible de mettre à jour ce tableau.',
@ -74,7 +72,6 @@ return array(
'All projects' => 'Tous les projets',
'Add a new column' => 'Ajouter une nouvelle colonne',
'Title' => 'Titre',
'Nobody assigned' => 'Personne assignée',
'Assigned to %s' => 'Assigné à %s',
'Remove a column' => 'Supprimer une colonne',
'Remove a column from a board' => 'Supprimer une colonne d\'un tableau',
@ -95,7 +92,7 @@ return array(
'Edit a task' => 'Modifier une tâche',
'Column' => 'Colonne',
'Color' => 'Couleur',
'Assignee' => 'Personne assigné',
'Assignee' => 'Personne assignée',
'Create another task' => 'Créer une autre tâche',
'New task' => 'Nouvelle tâche',
'Open a task' => 'Ouvrir une tâche',
@ -168,7 +165,6 @@ return array(
'Task count' => 'Nombre de tâches',
'User' => 'Utilisateur',
'Comments' => 'Commentaires',
'Write your text in Markdown' => 'Écrivez votre texte en Markdown',
'Leave a comment' => 'Laissez un commentaire',
'Comment is required' => 'Le commentaire est obligatoire',
'Leave a description' => 'Laissez une description',
@ -184,7 +180,6 @@ return array(
'Unable to remove this action.' => 'Impossible de supprimer cette action',
'Action removed successfully.' => 'Action supprimée avec succès.',
'Automatic actions for the project "%s"' => 'Actions automatisées pour le projet « %s »',
'Defined actions' => 'Actions définies',
'Add an action' => 'Ajouter une action',
'Event name' => 'Nom de l\'événement',
'Action name' => 'Nom de l\'action',
@ -194,7 +189,6 @@ return array(
'When the selected event occurs execute the corresponding action.' => 'Lorsque l\'événement sélectionné se déclenche, exécuter l\'action correspondante.',
'Next step' => 'Étape suivante',
'Define action parameters' => 'Définition des paramètres de l\'action',
'Save this action' => 'Sauvegarder cette action',
'Do you really want to remove this action: "%s"?' => 'Voulez-vous vraiment supprimer cette action « %s » ?',
'Remove an automatic action' => 'Supprimer une action automatisée',
'Assign the task to a specific user' => 'Assigner la tâche à un utilisateur spécifique',
@ -333,14 +327,12 @@ return array(
'Time tracking:' => 'Gestion du temps :',
'New sub-task' => 'Nouvelle sous-tâche',
'New attachment added "%s"' => 'Nouvelle pièce-jointe ajoutée « %s »',
'Comment updated' => 'Commentaire ajouté',
'New comment posted by %s' => 'Nouveau commentaire ajouté par « %s »',
'New attachment' => 'Nouveau document',
'New comment' => 'Nouveau commentaire',
'Comment updated' => 'Commentaire mis à jour',
'New subtask' => 'Nouvelle sous-tâche',
'Subtask updated' => 'Sous-tâche mise à jour',
'New task' => 'Nouvelle tâche',
'Task updated' => 'Tâche mise à jour',
'Task closed' => 'Tâche fermée',
'Task opened' => 'Tâche ouverte',
@ -438,7 +430,6 @@ return array(
'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',
'Start date' => 'Date de début',
'Time estimated' => 'Temps estimé',
@ -489,9 +480,6 @@ return array(
'Daily project summary export for "%s"' => 'Export du résumé quotidien du projet pour « %s »',
'Exports' => 'Exports',
'This export contains the number of tasks per column grouped per day.' => 'Cet export contient le nombre de tâches par colonne groupé par jour.',
'Nothing to preview...' => 'Rien à prévisualiser...',
'Preview' => 'Prévisualiser',
'Write' => 'Écrire',
'Active swimlanes' => 'Swimlanes actives',
'Add a new swimlane' => 'Ajouter une nouvelle swimlane',
'Change default swimlane' => 'Modifier la swimlane par défaut',
@ -545,7 +533,6 @@ return array(
'Task age in days' => 'Âge de la tâche en jours',
'Days in this column' => 'Jours dans cette colonne',
'%dd' => '%dj',
'Add a link' => 'Ajouter un lien',
'Add a new link' => 'Ajouter un nouveau lien',
'Do you really want to remove this link: "%s"?' => 'Voulez-vous vraiment supprimer ce lien : « %s » ?',
'Do you really want to remove this link with task #%d?' => 'Voulez-vous vraiment supprimer ce lien avec la tâche n°%d ?',
@ -736,7 +723,7 @@ return array(
'Time spent changed: %sh' => 'Le temps passé a été changé : %sh',
'Time estimated changed: %sh' => 'Le temps estimé a été changé : %sh',
'The field "%s" have been updated' => 'Le champ « %s » a été mis à jour',
'The description have been modified' => 'La description a été modifiée',
'The description has been modified:' => 'La description a été modifiée',
'Do you really want to close the task "%s" as well as all subtasks?' => 'Voulez-vous vraiment fermer la tâche « %s » ainsi que toutes ses sous-tâches ?',
'I want to receive notifications for:' => 'Je veux reçevoir les notifications pour :',
'All tasks' => 'Toutes les Tâches',
@ -755,8 +742,6 @@ return array(
'My activity stream' => 'Mon flux d\'activité',
'My calendar' => 'Mon agenda',
'Search tasks' => 'Rechercher des tâches',
'Back to the calendar' => 'Retour au calendrier',
'Filters' => 'Filtres',
'Reset filters' => 'Réinitialiser les filtres',
'My tasks due tomorrow' => 'Mes tâches qui arrivent à échéance demain',
'Tasks due today' => 'Tâches qui arrivent à échéance aujourd\'hui',
@ -856,7 +841,6 @@ return array(
'End date:' => 'Date de fin :',
'There is no start date or end date for this project.' => 'Il n\'y a pas de date de début ou de date de fin pour ce projet.',
'Projects Gantt chart' => 'Diagramme de Gantt des projets',
'Link type' => 'Type de lien',
'Change task color when using a specific task link' => 'Changer la couleur de la tâche lorsqu\'un lien spécifique est utilisé',
'Task link creation or modification' => 'Création ou modification d\'un lien sur une tâche',
'Milestone' => 'Étape importante',
@ -908,7 +892,6 @@ return array(
'Shared' => 'Partagé',
'Owner' => 'Propriétaire',
'Unread notifications' => 'Notifications non lus',
'My filters' => 'Mes filtres',
'Notification methods:' => 'Méthodes de notifications :',
'Import tasks from CSV file' => 'Importer les tâches depuis un fichier CSV',
'Unable to read your file' => 'Impossible de lire votre fichier',
@ -1038,7 +1021,7 @@ return array(
'Close all tasks of this column' => 'Fermer toutes les tâches de cette colonne',
'No plugin has registered a project notification method. You can still configure individual notifications in your user profile.' => 'Aucun plugin n\'a enregistré une méthode de notification de projet. Vous pouvez toujours configurer les notifications individuelles dans votre profil d\'utilisateur.',
'My dashboard' => 'Mon tableau de bord',
'My profile' => 'Mon profile',
'My profile' => 'Mon profil',
'Project owner: ' => 'Responsable du projet : ',
'The project identifier is optional and must be alphanumeric, example: MYPROJECT.' => 'L\'identifiant du projet est optionnel et doit être alphanumérique, example: MONPROJET.',
'Project owner' => 'Responsable du projet',
@ -1056,7 +1039,6 @@ return array(
'Close a task when there is no activity' => 'Fermer une tâche sans activité',
'Duration in days' => 'Durée en jours',
'Send email when there is no activity on a task' => 'Envoyer un email lorsqu\'il n\'y a pas d\'activité sur une tâche',
'List of external links' => 'Liste des liens externes',
'Unable to fetch link information.' => 'Impossible de récupérer les informations sur le lien.',
'Daily background job for tasks' => 'Tâche planifiée quotidienne pour les tâches',
'Auto' => 'Auto',
@ -1074,9 +1056,7 @@ return array(
'External link' => 'Lien externe',
'Copy and paste your link here...' => 'Copier-coller vôtre lien ici...',
'URL' => 'URL',
'There is no external link for the moment.' => 'Il n\'y a pas de lien externe pour le moment.',
'Internal links' => 'Liens internes',
'There is no internal link for the moment.' => 'Il n\'y a pas de lien interne pour le moment.',
'Assign to me' => 'Assigner à moi',
'Me' => 'Moi',
'Do not duplicate anything' => 'Ne rien dupliquer',
@ -1084,7 +1064,6 @@ return array(
'Users management' => 'Gestion des utilisateurs',
'Groups management' => 'Gestion des groupes',
'Create from another project' => 'Créer depuis un autre projet',
'There is no subtask at the moment.' => 'Il n\'y a aucune sous-tâche pour le moment.',
'open' => 'ouvert',
'closed' => 'fermé',
'Priority:' => 'Priorité :',
@ -1103,7 +1082,6 @@ return array(
'Started:' => 'Commençé le :',
'Moved:' => 'Déplacé le : ',
'Task #%d' => 'Tâche n°%d',
'Sub-tasks' => 'Sous-tâches',
'Date and time format' => 'Format de la date et de l\'heure',
'Time format' => 'Format de l\'heure',
'Start date: ' => 'Date de début : ',
@ -1123,7 +1101,7 @@ return array(
'Choose files again' => 'Choisir de nouveau des fichiers',
'Drag and drop your files here' => 'Glissez-déposez vos fichiers ici',
'choose files' => 'choisissez des fichiers',
'View profile' => 'Voir le profile',
'View profile' => 'Voir le profil',
'Two Factor' => 'Deux-Facteurs',
'Disable user' => 'Désactiver l\'utilisateur',
'Do you really want to disable this user: "%s"?' => 'Voulez-vous vraiment désactiver cet utilisateur : « %s » ?',
@ -1144,11 +1122,35 @@ return array(
'User filters' => 'Filtres des utilisateurs',
'Category filters' => 'Filtres des catégories',
'Upload a file' => 'Uploader un fichier',
'There is no attachment at the moment.' => 'Il n\'y a aucune pièce-jointe pour le moment.',
'View file' => 'Voir le fichier',
'Last activity' => 'Dernières activités',
'Change subtask position' => 'Changer la position de la sous-tâche',
'This value must be greater than %d' => 'Cette valeur doit être plus grande que %d',
'Another swimlane with the same name exists in the project' => 'Une autre swimlane existe avec le même nom dans le projet',
'Example: http://example.kanboard.net/ (used to generate absolute URLs)' => 'Exemple : http://exemple.kanboard.net/ (utilisé pour générer les URLs absolues)',
'Actions duplicated successfully.' => 'Actions dupliquées avec succès.',
'Unable to duplicate actions.' => 'Impossible de dupliquer les actions.',
'Add a new action' => 'Ajouter une nouvelle action',
'Import from another project' => 'Importer depuis un autre projet',
'There is no action at the moment.' => 'Il n\'y a aucune action pour le moment.',
'Import actions from another project' => 'Importer les actions depuis un autre projet',
'There is no available project.' => 'Il n\'y a pas de projet disponible.',
'Local File' => 'Fichier local',
'Configuration' => 'Configuration',
'PHP version:' => 'Version de PHP :',
'PHP SAPI:' => 'PHP SAPI :',
'OS version:' => 'Version du système d\'exploitation :',
'Database version:' => 'Version de la base de donnée :',
'Browser:' => 'Navigateur web :',
'Task view' => 'Vue détaillée d\'une tâche',
'Edit task' => 'Modifier la tâche',
'Edit description' => 'Modifier la description',
'New internal link' => 'Nouveau lien interne',
'Display list of keyboard shortcuts' => 'Afficher la liste des raccourcis claviers',
'Menu' => 'Menu',
'Set start date' => 'Définir la date de début',
'Avatar' => 'Avatar',
'Upload my avatar image' => 'Uploader mon image d\'avatar',
'Remove my image' => 'Supprimer mon image',
'The OAuth2 state parameter is invalid' => 'Le paramètre "state" de OAuth2 est invalide',
);

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