summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/Api/Task.php24
-rw-r--r--app/Controller/Analytic.php29
-rw-r--r--app/Controller/Group.php2
-rw-r--r--app/Core/Csv.php2
-rw-r--r--app/Helper/Subtask.php2
-rw-r--r--app/Locale/pl_PL/translations.php450
-rw-r--r--app/Model/ProjectAnalytic.php45
-rw-r--r--app/Model/ProjectPermission.php6
-rw-r--r--app/Model/TaskFinder.php1
-rw-r--r--app/ServiceProvider/RouteProvider.php7
-rw-r--r--app/Subscriber/AuthSubscriber.php2
-rw-r--r--app/Subscriber/BootstrapSubscriber.php1
-rw-r--r--app/Template/analytic/compare_hours.php57
-rw-r--r--app/Template/analytic/sidebar.php5
14 files changed, 395 insertions, 238 deletions
diff --git a/app/Api/Task.php b/app/Api/Task.php
index 0dceb209..4a7ee932 100644
--- a/app/Api/Task.php
+++ b/app/Api/Task.php
@@ -71,6 +71,14 @@ class Task extends Base
{
$this->checkProjectPermission($project_id);
+ if ($owner_id !== 0 && ! $this->projectPermission->isMember($project_id, $owner_id)) {
+ return false;
+ }
+
+ if ($this->userSession->isLogged()) {
+ $creator_id = $this->userSession->getId();
+ }
+
$values = array(
'title' => $title,
'project_id' => $project_id,
@@ -96,20 +104,28 @@ class Task extends Base
return $valid ? $this->taskCreation->create($values) : false;
}
- public function updateTask($id, $title = null, $project_id = null, $color_id = null, $owner_id = null,
- $creator_id = null, $date_due = null, $description = null, $category_id = null, $score = null,
+ public function updateTask($id, $title = null, $color_id = null, $owner_id = null,
+ $date_due = null, $description = null, $category_id = null, $score = null,
$recurrence_status = null, $recurrence_trigger = null, $recurrence_factor = null,
$recurrence_timeframe = null, $recurrence_basedate = null, $reference = null)
{
$this->checkTaskPermission($id);
+ $project_id = $this->taskFinder->getProjectId($id);
+
+ if ($project_id === 0) {
+ return false;
+ }
+
+ if ($owner_id !== null && ! $this->projectPermission->isMember($project_id, $owner_id)) {
+ return false;
+ }
+
$values = array(
'id' => $id,
'title' => $title,
- 'project_id' => $project_id,
'color_id' => $color_id,
'owner_id' => $owner_id,
- 'creator_id' => $creator_id,
'date_due' => $date_due,
'description' => $description,
'category_id' => $category_id,
diff --git a/app/Controller/Analytic.php b/app/Controller/Analytic.php
index e03d8cab..bebb13fa 100644
--- a/app/Controller/Analytic.php
+++ b/app/Controller/Analytic.php
@@ -1,6 +1,7 @@
<?php
namespace Kanboard\Controller;
+use Kanboard\Model\Task as TaskModel;
/**
* Project Analytic controller
@@ -166,4 +167,32 @@ class Analytic extends Base
'title' => t($title, $project['name']),
)));
}
+
+ /**
+ * Show comparison between actual and estimated hours chart
+ *
+ * @access public
+ */
+ public function compareHours()
+ {
+ $project = $this->getProject();
+ $params = $this->getProjectFilters('analytic', 'compareHours');
+ $query = $this->taskFilter->search('status:all')->filterByProject($params['project']['id'])->getQuery();
+
+ $paginator = $this->paginator
+ ->setUrl('analytic', 'compareHours', array('project_id' => $project['id']))
+ ->setMax(30)
+ ->setOrder(TaskModel::TABLE.'.id')
+ ->setQuery($query)
+ ->calculate();
+
+ $stats = $this->projectAnalytic->getHoursByStatus($project['id']);
+
+ $this->response->html($this->layout('analytic/compare_hours', array(
+ 'project' => $project,
+ 'paginator' => $paginator,
+ 'metrics' => $stats,
+ 'title' => t('Compare hours for "%s"', $project['name']),
+ )));
+ }
}
diff --git a/app/Controller/Group.php b/app/Controller/Group.php
index 395a954d..3e6505e9 100644
--- a/app/Controller/Group.php
+++ b/app/Controller/Group.php
@@ -42,7 +42,7 @@ class Group extends Base
$group = $this->group->getById($group_id);
$paginator = $this->paginator
- ->setUrl('group', 'users')
+ ->setUrl('group', 'users', array('group_id' => $group_id))
->setMax(30)
->setOrder('username')
->setQuery($this->groupMember->getQuery($group_id))
diff --git a/app/Core/Csv.php b/app/Core/Csv.php
index 28c1997b..e45af24c 100644
--- a/app/Core/Csv.php
+++ b/app/Core/Csv.php
@@ -93,7 +93,7 @@ class Csv
{
if (! empty($value)) {
$value = trim(strtolower($value));
- return $value === '1' || $value{0} === 't' ? 1 : 0;
+ return $value === '1' || $value{0} === 't' || $value{0} === 'y' ? 1 : 0;
}
return 0;
diff --git a/app/Helper/Subtask.php b/app/Helper/Subtask.php
index 7d474de0..90bd733e 100644
--- a/app/Helper/Subtask.php
+++ b/app/Helper/Subtask.php
@@ -25,7 +25,7 @@ class Subtask extends \Kanboard\Core\Base
return trim($this->template->render('subtask/icons', array('subtask' => $subtask))) . $this->helper->e($subtask['title']);
}
- if ($subtask['status'] == 0 && isset($this->sessionStorage->hasSubtaskInProgress) && $this->sessionStorage->hasSubtaskInProgress === true) {
+ if ($subtask['status'] == 0 && isset($this->sessionStorage->hasSubtaskInProgress) && $this->sessionStorage->hasSubtaskInProgress) {
return $this->helper->url->link(
trim($this->template->render('subtask/icons', array('subtask' => $subtask))) . $this->helper->e($subtask['title']),
'subtask',
diff --git a/app/Locale/pl_PL/translations.php b/app/Locale/pl_PL/translations.php
index 2ac64145..0a3fed4c 100644
--- a/app/Locale/pl_PL/translations.php
+++ b/app/Locale/pl_PL/translations.php
@@ -20,15 +20,15 @@ return array(
'Red' => 'Czerwony',
'Orange' => 'Pomarańczowy',
'Grey' => 'Szary',
- // 'Brown' => '',
- // 'Deep Orange' => '',
- // 'Dark Grey' => '',
- // 'Pink' => '',
- // 'Teal' => '',
- // 'Cyan' => '',
- // 'Lime' => '',
- // 'Light Green' => '',
- // 'Amber' => '',
+ 'Brown' => 'Brąz',
+ 'Deep Orange' => 'Ciemnopomarańczowy',
+ 'Dark Grey' => 'Ciemnoszary',
+ 'Pink' => 'Różowy',
+ 'Teal' => 'Turkusowy',
+ 'Cyan' => 'Cyjan',
+ 'Lime' => 'Limonkowy',
+ 'Light Green' => 'Jasnozielony',
+ 'Amber' => 'Amber',
'Save' => 'Zapisz',
'Login' => 'Login',
'Official website:' => 'Oficjalna strona:',
@@ -148,7 +148,7 @@ return array(
'Task created successfully.' => 'Zadanie zostało utworzone.',
'User created successfully.' => 'Użytkownik dodany',
'Unable to create your user.' => 'Nie udało się dodać użytkownika.',
- 'User updated successfully.' => 'Użytkownik zaktualizowany.',
+ 'User updated successfully.' => 'Profil użytkownika został zaaktualizowany.',
'Unable to update your user.' => 'Nie udało się zaktualizować użytkownika.',
'User removed successfully.' => 'Użytkownik usunięty.',
'Unable to remove this user.' => 'Nie udało się usunąć użytkownika.',
@@ -364,7 +364,7 @@ return array(
'Task updated' => 'Zaktualizowane zadanie',
'Task closed' => 'Zadanie zamknięte',
'Task opened' => 'Zadanie otwarte',
- 'I want to receive notifications only for those projects:' => 'Chcę otrzymywać powiadiomienia tylko dla tych projektów:',
+ 'I want to receive notifications only for those projects:' => 'Chcę otrzymywać powiadomienia tylko dla poniższych projektów:',
'view the task on Kanboard' => 'Zobacz zadanie',
'Public access' => 'Dostęp publiczny',
'User management' => 'Zarządzanie użytkownikami',
@@ -494,7 +494,7 @@ return array(
'Project management' => 'Menadżer projektu',
'My projects' => 'Moje projekty',
'Columns' => 'Kolumny',
- 'Task' => 'zadania',
+ 'Task' => 'Zadanie',
'Your are not member of any project.' => 'Nie bierzesz udziału w żadnym projekcie',
'Percentage' => 'Procent',
'Number of tasks' => 'Liczba zadań',
@@ -688,7 +688,7 @@ return array(
'The two factor authentication code is valid.' => 'Kod weryfikujący poprawny',
'Code' => 'Kod',
'Two factor authentication' => 'Uwierzytelnianie dwustopniowe',
- 'Enable/disable two factor authentication' => 'Włącz/Wyłącz uwierzytelnianie dwustopniowe',
+ 'Enable/disable two factor authentication' => 'Włącz/wyłącz uwierzytelnianie dwustopniowe',
'This QR code contains the key URI: ' => 'Ten kod QR zawiera URI klucza: ',
'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => 'Zapisz sekretny klucz w swoim oprogramowaniu TOTP (na przykład FreeOTP lub Google Authenticator)',
'Check my code' => 'Sprawdź kod',
@@ -699,8 +699,8 @@ return array(
'uploaded by: %s' => 'Dodane przez: %s',
'uploaded on: %s' => 'Data dodania: %s',
'size: %s' => 'Rozmiar: %s',
- // 'Burndown chart for "%s"' => '',
- // 'Burndown chart' => '',
+ 'Burndown chart for "%s"' => 'Wykres Burndown dla "%s"',
+ 'Burndown chart' => 'Wykres Burndown',
// 'This chart show the task complexity over the time (Work Remaining).' => '',
'Screenshot taken %s' => 'Zrzut ekranu zapisany %s',
'Add a screenshot' => 'Dodaj zrzut ekranu',
@@ -770,19 +770,19 @@ return array(
// 'Commit made by @%s on Gitlab' => '',
// 'Add a comment log when moving the task between columns' => '',
// 'Move the task to another column when the category is changed' => '',
- // 'Send a task by email to someone' => '',
- // 'Reopen a task' => '',
+ 'Send a task by email to someone' => 'Wyślij zadanie mailem do kogokolwiek',
+ 'Reopen a task' => 'Otwórz ponownie zadanie',
// 'Bitbucket issue opened' => '',
// 'Bitbucket issue closed' => '',
// 'Bitbucket issue reopened' => '',
// 'Bitbucket issue assignee change' => '',
// 'Bitbucket issue comment created' => '',
- // 'Column change' => '',
- // 'Position change' => '',
- // 'Swimlane change' => '',
- // 'Assignee change' => '',
+ 'Column change' => 'Zmiana kolumny',
+ 'Position change' => 'Zmiana pozycji',
+ 'Swimlane change' => 'Zmiana Swimlane',
+ 'Assignee change' => 'Zmiana przypisanego użytkownika',
// '[%s] Overdue tasks' => '',
- // 'Notification' => '',
+ 'Notification' => 'Powiadomienie',
// '%s moved the task #%d to the first swimlane' => '',
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
@@ -796,12 +796,12 @@ return array(
// 'The task have been moved to the first swimlane' => '',
// 'The task have been moved to another swimlane:' => '',
// 'Overdue tasks for the project "%s"' => '',
- // 'New title: %s' => '',
- // 'The task is not assigned anymore' => '',
- // 'New assignee: %s' => '',
- // 'There is no category now' => '',
- // 'New category: %s' => '',
- // 'New color: %s' => '',
+ 'New title: %s' => 'Nowy tytuł: %s',
+ 'The task is not assigned anymore' => 'Brak osoby odpowiedzialnej za zadanie',
+ 'New assignee: %s' => 'Nowy odpowiedzialny: %s',
+ 'There is no category now' => 'Aktualnie zadanie nie posiada kategorii',
+ 'New category: %s' => 'Nowa kategoria: %s',
+ 'New color: %s' => 'Nowy kolor: %s',
// 'New complexity: %d' => '',
// 'The due date have been removed' => '',
// 'There is no description anymore' => '',
@@ -811,61 +811,61 @@ return array(
// 'The field "%s" have been updated' => '',
// 'The description have been modified' => '',
// 'Do you really want to close the task "%s" as well as all subtasks?' => '',
- // 'Swimlane: %s' => '',
- // 'I want to receive notifications for:' => '',
- // 'All tasks' => '',
- // 'Only for tasks assigned to me' => '',
- // 'Only for tasks created by me' => '',
- // 'Only for tasks created by me and assigned to me' => '',
+ //'Swimlane: %s' => '',
+ 'I want to receive notifications for:' => 'Wysyłaj powiadomienia dla:',
+ 'All tasks' => 'Wszystkich zadań',
+ 'Only for tasks assigned to me' => 'Tylko zadań przypisanych do mnie',
+ 'Only for tasks created by me' => 'Tylko zadań utworzonych przeze mnie',
+ 'Only for tasks created by me and assigned to me' => 'Tylko zadań przypisanych lub utworzonych przeze mnie',
// '%A' => '',
// '%b %e, %Y, %k:%M %p' => '',
- // 'New due date: %B %e, %Y' => '',
- // 'Start date changed: %B %e, %Y' => '',
+ 'New due date: %B %e, %Y' => 'Nowy termin: %B %e, %Y',
+ 'Start date changed: %B %e, %Y' => 'Zmiana daty rozpoczęcia: %B %e, %Y',
// '%k:%M %p' => '',
// '%%Y-%%m-%%d' => '',
// 'Total for all columns' => '',
// 'You need at least 2 days of data to show the chart.' => '',
// '<15m' => '',
// '<30m' => '',
- // 'Stop timer' => '',
- // 'Start timer' => '',
- // 'Add project member' => '',
- // 'Enable notifications' => '',
- // 'My activity stream' => '',
- // 'My calendar' => '',
- // 'Search tasks' => '',
- // 'Back to the calendar' => '',
- // 'Filters' => '',
- // 'Reset filters' => '',
- // 'My tasks due tomorrow' => '',
- // 'Tasks due today' => '',
- // 'Tasks due tomorrow' => '',
- // 'Tasks due yesterday' => '',
- // 'Closed tasks' => '',
- // 'Open tasks' => '',
- // 'Not assigned' => '',
- // 'View advanced search syntax' => '',
- // 'Overview' => '',
+ 'Stop timer' => 'Zatrzymaj pomiar czasu',
+ 'Start timer' => 'Uruchom pomiar czasu',
+ 'Add project member' => 'Dodaj członka projektu',
+ 'Enable notifications' => 'Włącz powiadomienia',
+ 'My activity stream' => 'Moja aktywność',
+ 'My calendar' => 'Mój kalendarz',
+ 'Search tasks' => 'Szukaj zadań',
+ 'Back to the calendar' => 'Wróć do kalendarza',
+ 'Filters' => 'Filtry',
+ 'Reset filters' => 'Resetuj zastosowane filtry',
+ 'My tasks due tomorrow' => 'Moje zadania do jutra',
+ 'Tasks due today' => 'Zadania do dzisiaj',
+ 'Tasks due tomorrow' => 'Zadania do jutra',
+ 'Tasks due yesterday' => 'Zadania na wczoraj',
+ 'Closed tasks' => 'Zamknięte zadania',
+ 'Open tasks' => 'Otwarte zadania',
+ 'Not assigned' => 'Nieprzypisane zadania',
+ 'View advanced search syntax' => 'Pomoc dotycząca budowania filtrów',
+ 'Overview' => 'Przegląd',
// '%b %e %Y' => '',
- // 'Board/Calendar/List view' => '',
- // 'Switch to the board view' => '',
- // 'Switch to the calendar view' => '',
- // 'Switch to the list view' => '',
- // 'Go to the search/filter box' => '',
- // 'There is no activity yet.' => '',
- // 'No tasks found.' => '',
- // 'Keyboard shortcut: "%s"' => '',
- // 'List' => '',
- // 'Filter' => '',
- // 'Advanced search' => '',
- // 'Example of query: ' => '',
- // 'Search by project: ' => '',
- // 'Search by column: ' => '',
- // 'Search by assignee: ' => '',
- // 'Search by color: ' => '',
- // 'Search by category: ' => '',
- // 'Search by description: ' => '',
- // 'Search by due date: ' => '',
+ 'Board/Calendar/List view' => 'Widok: Tablica/Kalendarz/Lista',
+ 'Switch to the board view' => 'Przełącz na tablicę',
+ 'Switch to the calendar view' => 'Przełącz na kalendarz',
+ 'Switch to the list view' => 'Przełącz na listę',
+ 'Go to the search/filter box' => 'Użyj pola wyszukiwania/filtrów',
+ 'There is no activity yet.' => 'Brak powiadomień',
+ 'No tasks found.' => 'Nie znaleziono zadań',
+ 'Keyboard shortcut: "%s"' => 'Skrót klawiaturowy: "%s"',
+ 'List' => 'Lista',
+ 'Filter' => 'Filtr',
+ 'Advanced search' => 'Zaawansowane wyszukiwanie',
+ 'Example of query: ' => 'Przykładowe zapytanie:',
+ 'Search by project: ' => 'Szukaj wg projektów:',
+ 'Search by column: ' => 'Szukaj wg kolumn:',
+ 'Search by assignee: ' => 'Szukaj wg użytkownika:',
+ 'Search by color: ' => 'Szukaj wg koloru:',
+ 'Search by category: ' => 'Szukaj wg kategorii:',
+ 'Search by description: ' => 'Szukaj wg opisu:',
+ 'Search by due date: ' => 'Szukaj wg terminu:',
// 'Lead and Cycle time for "%s"' => '',
// 'Average time spent into each column for "%s"' => '',
// 'Average time spent into each column' => '',
@@ -890,7 +890,7 @@ return array(
// 'The cycle time is the duration between the start date and the completion.' => '',
// 'If the task is not closed the current time is used instead of the completion date.' => '',
// 'Set automatically the start date' => '',
- // 'Edit Authentication' => '',
+ 'Edit Authentication' => 'Edycja autoryzacji',
// 'Google Id' => '',
// 'Github Id' => '',
// 'Remote user' => '',
@@ -898,62 +898,62 @@ return array(
// 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => '',
// 'By @%s on Gitlab' => '',
// 'Gitlab issue comment created' => '',
- // 'New remote user' => '',
- // 'New local user' => '',
- // 'Default task color' => '',
- // 'Hide sidebar' => '',
- // 'Expand sidebar' => '',
- // 'This feature does not work with all browsers.' => '',
+ 'New remote user' => 'Nowy użytkownik zdalny',
+ 'New local user' => 'Nowy użytkownik lokalny',
+ 'Default task color' => 'Domyślny kolor zadań',
+ 'Hide sidebar' => 'Ukryj menu boczne',
+ 'Expand sidebar' => 'Pokaż menu boczne',
+ 'This feature does not work with all browsers.' => 'Ta funkcja może nie działać z każdą przeglądarką',
// 'There is no destination project available.' => '',
// 'Trigger automatically subtask time tracking' => '',
// 'Include closed tasks in the cumulative flow diagram' => '',
- // 'Current swimlane: %s' => '',
- // 'Current column: %s' => '',
- // 'Current category: %s' => '',
- // 'no category' => '',
- // 'Current assignee: %s' => '',
- // 'not assigned' => '',
- // 'Author:' => '',
- // 'contributors' => '',
- // 'License:' => '',
- // 'License' => '',
- // 'Enter the text below' => '',
- // 'Gantt chart for %s' => '',
- // 'Sort by position' => '',
- // 'Sort by date' => '',
- // 'Add task' => '',
- // 'Start date:' => '',
- // 'Due date:' => '',
- // 'There is no start date or due date for this task.' => '',
+ 'Current swimlane: %s' => 'Bieżący swimlane: %s',
+ 'Current column: %s' => 'Bieżąca kolumna: %s',
+ 'Current category: %s' => 'Bieżąca kategoria: %s',
+ 'no category' => 'brak kategorii',
+ //'Current assignee: %s' => '',
+ 'not assigned' => 'Brak osoby odpowiedzialnej',
+ 'Author:' => 'Autor',
+ 'contributors' => 'współautorzy',
+ 'License:' => 'Licencja:',
+ 'License' => 'Licencja',
+ 'Enter the text below' => 'Wpisz tekst poniżej',
+ 'Gantt chart for %s' => 'Wykres Gantt dla %s',
+ 'Sort by position' => 'Sortuj wg pozycji',
+ 'Sort by date' => 'Sortuj wg daty',
+ 'Add task' => 'Dodaj zadanie',
+ 'Start date:' => 'Data rozpoczęcia:',
+ 'Due date:' => 'Termin',
+ 'There is no start date or due date for this task.' => 'Brak daty rozpoczęcia lub terminu zadania',
// 'Moving or resizing a task will change the start and due date of the task.' => '',
// 'There is no task in your project.' => '',
- // 'Gantt chart' => '',
- // 'People who are project managers' => '',
- // 'People who are project members' => '',
+ 'Gantt chart' => 'Wykres Gantta',
+ 'People who are project managers' => 'Użytkownicy będący menedżerami projektu',
+ 'People who are project members' => 'Użytkownicy będący uczestnikami projektu',
// 'NOK - Norwegian Krone' => '',
- // 'Show this column' => '',
- // 'Hide this column' => '',
- // 'open file' => '',
- // 'End date' => '',
- // 'Users overview' => '',
- // 'Managers' => '',
- // 'Members' => '',
- // 'Shared project' => '',
- // 'Project managers' => '',
- // 'Gantt chart for all projects' => '',
- // 'Projects list' => '',
- // 'Gantt chart for this project' => '',
- // 'Project board' => '',
- // 'End date:' => '',
- // 'There is no start date or end date for this project.' => '',
- // 'Projects Gantt chart' => '',
- // 'Start date: %s' => '',
- // 'End date: %s' => '',
- // 'Link type' => '',
- // 'Change task color when using a specific task link' => '',
- // 'Task link creation or modification' => '',
+ 'Show this column' => 'Pokaż tą kolumnę',
+ 'Hide this column' => 'Ukryj tą kolumnę',
+ 'open file' => 'otwórz plik',
+ 'End date' => 'Data zakończenia',
+ 'Users overview' => 'Przegląd użytkowników',
+ 'Managers' => 'Menedżerowie',
+ 'Members' => 'Uczestnicy',
+ 'Shared project' => 'Projekt udostępniony',
+ 'Project managers' => 'Menedżerowie projektu',
+ 'Gantt chart for all projects' => 'Wykres Gantta dla wszystkich projektów',
+ 'Projects list' => 'Lista projektów',
+ 'Gantt chart for this project' => 'Wykres Gantta dla bieżacego projektu',
+ 'Project board' => 'Talica projektu',
+ 'End date:' => 'Data zakończenia:',
+ 'There is no start date or end date for this project.' => 'Nie zdefiniowano czasu trwania projektu',
+ 'Projects Gantt chart' => 'Wykres Gantta dla projektów',
+ 'Start date: %s' => 'Data rozpoczęcia: %s',
+ 'End date: %s' => 'Data zakończenia: %s',
+ 'Link type' => 'Typ adresu URL',
+ 'Change task color when using a specific task link' => 'Zmień kolor zadania używając specjalnego adresu URL',
+ 'Task link creation or modification' => 'Adres URL do utworzenia zadania lub modyfikacji',
// 'Login with my Gitlab Account' => '',
- // 'Milestone' => '',
+ 'Milestone' => 'Kamień milowy',
// 'Gitlab Authentication' => '',
// 'Help on Gitlab authentication' => '',
// 'Gitlab Id' => '',
@@ -963,17 +963,17 @@ return array(
// 'Documentation: %s' => '',
// 'Switch to the Gantt chart view' => '',
// 'Reset the search/filter box' => '',
- // 'Documentation' => '',
+ 'Documentation' => 'Dokumentacja',
// 'Table of contents' => '',
// 'Gantt' => '',
- // 'Author' => '',
- // 'Version' => '',
- // 'Plugins' => '',
- // 'There is no plugin loaded.' => '',
- // 'Set maximum column height' => '',
- // 'Remove maximum column height' => '',
- // 'My notifications' => '',
- // 'Custom filters' => '',
+ 'Author' => 'Autor',
+ 'Version' => 'Wersja',
+ 'Plugins' => 'Wtyczki',
+ 'There is no plugin loaded.' => 'Nie wykryto żadnych wtyczek.',
+ 'Set maximum column height' => 'Ustaw maksymalną wysokość kolumn',
+ 'Remove maximum column height' => 'Usuń maksymalną wysokość kolumn',
+ 'My notifications' => 'Moje powiadomienia',
+ 'Custom filters' => 'Dostosuj filtry',
// 'Your custom filter have been created successfully.' => '',
// 'Unable to create your custom filter.' => '',
// 'Custom filter removed successfully.' => '',
@@ -982,68 +982,68 @@ return array(
// 'Your custom filter have been updated successfully.' => '',
// 'Unable to update custom filter.' => '',
// 'Web' => '',
- // 'New attachment on task #%d: %s' => '',
- // 'New comment on task #%d' => '',
- // 'Comment updated on task #%d' => '',
- // 'New subtask on task #%d' => '',
- // 'Subtask updated on task #%d' => '',
- // 'New task #%d: %s' => '',
- // 'Task updated #%d' => '',
- // 'Task #%d closed' => '',
- // 'Task #%d opened' => '',
- // 'Column changed for task #%d' => '',
- // 'New position for task #%d' => '',
- // 'Swimlane changed for task #%d' => '',
- // 'Assignee changed on task #%d' => '',
+ 'New attachment on task #%d: %s' => 'Nowy załącznik do zadania #%d: %s',
+ 'New comment on task #%d' => 'Nowy załącznik #%d',
+ 'Comment updated on task #%d' => 'Comment updated on task #%d',
+ 'New subtask on task #%d' => 'Nowe pod-zadanie dla zadania #%d',
+ 'Subtask updated on task #%d' => 'Aktualizacja pod-zadania w zadaniu #%d',
+ 'New task #%d: %s' => 'Nowe zadanie #%d: %s',
+ 'Task updated #%d' => 'Aktualizacja zadania #%d',
+ 'Task #%d closed' => 'Zamknięto zadanie #%d',
+ 'Task #%d opened' => 'Otwarto zadanie #%d',
+ 'Column changed for task #%d' => 'Zmieniono kolumnę zadania #%d',
+ 'New position for task #%d' => 'Ustalono nową pozycję zadania #%d',
+ 'Swimlane changed for task #%d' => 'Zmieniono swimlane dla zadania #%d',
+ 'Assignee changed on task #%d' => 'Zmieniono osobę odpowiedzialną dla zadania #%d',
// '%d overdue tasks' => '',
// 'Task #%d is overdue' => '',
- // 'No new notifications.' => '',
- // 'Mark all as read' => '',
- // 'Mark as read' => '',
+ 'No new notifications.' => 'Brak nowych powiadomień.',
+ 'Mark all as read' => 'Oznacz wszystkie jako przeczytane',
+ 'Mark as read' => 'Oznacz jako przeczytane',
// 'Total number of tasks in this column across all swimlanes' => '',
- // 'Collapse swimlane' => '',
- // 'Expand swimlane' => '',
- // 'Add a new filter' => '',
- // 'Share with all project members' => '',
- // 'Shared' => '',
- // 'Owner' => '',
- // 'Unread notifications' => '',
- // 'My filters' => '',
- // 'Notification methods:' => '',
+ 'Collapse swimlane' => 'Zwiń swimlane',
+ 'Expand swimlane' => 'Rozwiń swimlane',
+ 'Add a new filter' => 'Dodaj nowy filtr',
+ 'Share with all project members' => 'Udostępnij wszystkim uczestnikom projektu',
+ //'Shared' => '',
+ 'Owner' => 'Właściciel',
+ 'Unread notifications' => 'Nieprzeczytane powiadomienia',
+ 'My filters' => 'Moje filtry',
+ 'Notification methods:' => 'Metody powiadomień:',
// 'Import tasks from CSV file' => '',
// 'Unable to read your file' => '',
// '%d task(s) have been imported successfully.' => '',
// 'Nothing have been imported!' => '',
// 'Import users from CSV file' => '',
// '%d user(s) have been imported successfully.' => '',
- // 'Comma' => '',
- // 'Semi-colon' => '',
- // 'Tab' => '',
- // 'Vertical bar' => '',
- // 'Double Quote' => '',
- // 'Single Quote' => '',
+ 'Comma' => 'Przecinek',
+ 'Semi-colon' => 'Średnik',
+ 'Tab' => 'Tabulacja',
+ 'Vertical bar' => 'Kreska pionowa',
+ 'Double Quote' => 'Cudzysłów',
+ 'Single Quote' => 'Apostrof',
// '%s attached a file to the task #%d' => '',
// 'There is no column or swimlane activated in your project!' => '',
- // 'Append filter (instead of replacement)' => '',
+ 'Append filter (instead of replacement)' => 'Dołączaj filtr do zastosowanego filtru(zamiast przełączać)',
// 'Append/Replace' => '',
// 'Append' => '',
// 'Replace' => '',
- // 'There is no notification method registered.' => '',
+ 'There is no notification method registered.' => 'Tablica nie posiada aktywnych metod powiadomień.',
// 'Import' => '',
// 'change sorting' => '',
- // 'Tasks Importation' => '',
- // 'Delimiter' => '',
- // 'Enclosure' => '',
- // 'CSV File' => '',
- // 'Instructions' => '',
- // 'Your file must use the predefined CSV format' => '',
- // 'Your file must be encoded in UTF-8' => '',
- // 'The first row must be the header' => '',
- // 'Duplicates are not verified for you' => '',
- // 'The due date must use the ISO format: YYYY-MM-DD' => '',
- // 'Download CSV template' => '',
+ 'Tasks Importation' => 'Import zadań',
+ 'Delimiter' => 'Separator pola',
+ 'Enclosure' => 'Separator tekstu',
+ 'CSV File' => 'Plik CSV',
+ 'Instructions' => 'Instrukcje',
+ 'Your file must use the predefined CSV format' => 'Twój plik musi być zgodny z predefiniowanym formatem CSV (pobierz szablon)',
+ 'Your file must be encoded in UTF-8' => 'Twój plik musi być kodowany w UTF-8',
+ 'The first row must be the header' => 'Pierwszy wiersz pliku musi definiować nagłówki',
+ 'Duplicates are not verified for you' => 'Duplikaty nie będą weryfikowane',
+ 'The due date must use the ISO format: YYYY-MM-DD' => 'Data musi być w formacie ISO: YYYY-MM-DD',
+ 'Download CSV template' => 'Pobierz szablon pliku CSV',
// 'No external integration registered.' => '',
- // 'Duplicates are not imported' => '',
+ 'Duplicates are not imported' => 'Duplikaty nie zostaną zaimportowane',
// 'Usernames must be lowercase and unique' => '',
// 'Passwords will be encrypted if present' => '',
// '%s attached a new file to the task %s' => '',
@@ -1051,52 +1051,52 @@ return array(
// 'BAM - Konvertible Mark' => '',
// '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' => '',
+ 'Groups' => 'Grupy',
+ 'Members of %s' => 'Członkowie %s',
+ 'New group' => 'Nowa grupa',
+ 'Group created successfully.' => 'Grupa została utworzona.',
+ 'Unable to create your group.' => 'Nie można utworzyć grupy.',
+ 'Edit group' => 'Edytuj grupę',
+ 'Group updated successfully.' => 'Grupa została zaaktualizowana.',
+ 'Unable to update your group.' => 'Nie można zaaktualizować grupy.',
+ 'Add group member to "%s"' => 'Dodaj członka do grupy "%s"',
+ 'Group member added successfully.' => 'Użytkownik został dodany do grupy.',
+ 'Unable to add group member.' => 'Nie można dodać użytkownika do grupy.',
+ 'Remove user from group "%s"' => 'Usuń użytkownika z grupy "%s"',
+ 'User removed successfully from this group.' => 'Użytkownik został usunięty z grupy.',
+ 'Unable to remove this user from the group.' => 'Nie można usunąć użytkownika z grupy.',
+ 'Remove group' => 'Usuń grupę',
+ 'Group removed successfully.' => 'Grupa została usunięta.',
+ 'Unable to remove this group.' => 'Nie można usunąć grupy.',
+ 'Project Permissions' => 'Prawa dostępowe projektu',
+ 'Manager' => 'Menedżer',
+ 'Project Manager' => 'Menedżer projektu',
+ 'Project Member' => 'Uczestnik projektu',
+ 'Project Viewer' => 'Obserwator projektu',
// 'Gitlab issue reopened' => '',
- // '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:' => '',
- // 'Project members' => '',
+ 'Your account is locked for %d minutes' => 'Twoje konto zostało zablokowane na %d minut',
+ 'Invalid captcha' => 'Błędny kod z obrazka (captcha)',
+ 'The name must be unique' => 'Nazwa musi być unikatowa',
+ 'View all groups' => 'Wyświetl wszystkie grupy',
+ 'View group members' => 'Wyświetl wszystkich członków grupy',
+ 'There is no user available.' => 'Żaden użytkownik nie jest dostępny.',
+ 'Do you really want to remove the user "%s" from the group "%s"?' => 'Czy napewno chcesz usunąć użytkownika "%s" z grupy "%s"?',
+ 'There is no group.' => 'Brak grup.',
+ 'External Id' => 'Zewnętrzny Id',
+ 'Add group member' => 'Dodaj członka grupy',
+ 'Do you really want to remove this group: "%s"?' => 'Czy napewno chcesz usunąć grupę "%s"?',
+ 'There is no user in this group.' => 'Wybrana grupa nie posiada członków.',
+ 'Remove this user' => 'Usuń użytkownika',
+ 'Permissions' => 'Prawa dostępu',
+ 'Allowed Users' => 'Użytkownicy z dostępem',
+ 'No user have been allowed specifically.' => 'Żaden użytkownik nie ma przyznanego dostępu.',
+ 'Role' => 'Rola',
+ 'Enter user name...' => 'Wprowadź nazwę użytkownika...',
+ 'Allowed Groups' => 'Dostępne grupy',
+ 'No group have been allowed specifically.' => 'Żadna grupa nie ma przyznanego dostępu.',
+ 'Group' => 'Grupa',
+ 'Group Name' => 'Nazwa grupy',
+ 'Enter group name...' => 'Wprowadź nazwę grupy...',
+ 'Role:' => 'Rola:',
+ 'Project members' => 'Uczestnicy projektu',
);
diff --git a/app/Model/ProjectAnalytic.php b/app/Model/ProjectAnalytic.php
index e77a0368..d23695dc 100644
--- a/app/Model/ProjectAnalytic.php
+++ b/app/Model/ProjectAnalytic.php
@@ -179,4 +179,49 @@ class ProjectAnalytic extends Base
return $stats;
}
+
+ /**
+ * Get the time spent and estimated into each status
+ *
+ * @access public
+ * @param integer $project_id
+ * @return array
+ */
+ public function getHoursByStatus($project_id)
+ {
+ $stats = array();
+
+ // Get the times related to each task
+ $tasks = $this->db
+ ->table(Task::TABLE)
+ ->columns('id', 'time_estimated', 'time_spent', 'is_active')
+ ->eq('project_id', $project_id)
+ ->desc('id')
+ ->limit(1000)
+ ->findAll();
+
+ // Init values
+ $stats['closed'] = array(
+ 'time_spent' => 0,
+ 'time_estimated' => 0,
+ );
+
+ $stats['open'] = array(
+ 'time_spent' => 0,
+ 'time_estimated' => 0,
+ );
+
+ // Add times spent and estimated to each status
+ foreach ($tasks as &$task) {
+ if ($task['is_active']) {
+ $stats['open']['time_estimated'] += $task['time_estimated'];
+ $stats['open']['time_spent'] += $task['time_spent'];
+ } else {
+ $stats['closed']['time_estimated'] += $task['time_estimated'];
+ $stats['closed']['time_spent'] += $task['time_spent'];
+ }
+ }
+
+ return $stats;
+ }
}
diff --git a/app/Model/ProjectPermission.php b/app/Model/ProjectPermission.php
index b311c10b..f74b8587 100644
--- a/app/Model/ProjectPermission.php
+++ b/app/Model/ProjectPermission.php
@@ -28,10 +28,10 @@ class ProjectPermission extends Base
return $this
->db
- ->table(self::TABLE)
+ ->table(ProjectUserRole::TABLE)
->join(User::TABLE, 'id', 'user_id')
->join(Project::TABLE, 'id', 'project_id')
- ->eq(self::TABLE.'.role', $role)
+ ->eq(ProjectUserRole::TABLE.'.role', $role)
->eq(Project::TABLE.'.is_private', 0)
->in(Project::TABLE.'.id', $project_ids)
->columns(
@@ -88,7 +88,7 @@ class ProjectPermission extends Base
*/
public function isMember($project_id, $user_id)
{
- return in_array($this->projectUserRole->getUSerRole($project_id, $user_id), array(Role::PROJECT_MEMBER, Role::PROJECT_MANAGER));
+ return in_array($this->projectUserRole->getUserRole($project_id, $user_id), array(Role::PROJECT_MEMBER, Role::PROJECT_MANAGER));
}
/**
diff --git a/app/Model/TaskFinder.php b/app/Model/TaskFinder.php
index 9514fe4a..836fbe46 100644
--- a/app/Model/TaskFinder.php
+++ b/app/Model/TaskFinder.php
@@ -122,6 +122,7 @@ class TaskFinder extends Base
'tasks.recurrence_parent',
'tasks.recurrence_child',
'tasks.time_estimated',
+ 'tasks.time_spent',
User::TABLE.'.username AS assignee_username',
User::TABLE.'.name AS assignee_name',
Category::TABLE.'.name AS category_name',
diff --git a/app/ServiceProvider/RouteProvider.php b/app/ServiceProvider/RouteProvider.php
index 6b1d0d88..26ab488a 100644
--- a/app/ServiceProvider/RouteProvider.php
+++ b/app/ServiceProvider/RouteProvider.php
@@ -57,6 +57,13 @@ class RouteProvider implements ServiceProviderInterface
$container['router']->addRoute('project/:project_id/permissions', 'ProjectPermission', 'index', array('project_id'));
$container['router']->addRoute('project/:project_id/import', 'taskImport', 'step1', array('project_id'));
+ // ProjectUser routes
+ $container['router']->addRoute('projects/managers/:user_id', 'projectuser', 'managers', array('user_id'));
+ $container['router']->addRoute('projects/members/:user_id', 'projectuser', 'members', array('user_id'));
+ $container['router']->addRoute('projects/tasks/:user_id/opens', 'projectuser', 'opens', array('user_id'));
+ $container['router']->addRoute('projects/tasks/:user_id/closed', 'projectuser', 'closed', array('user_id'));
+ $container['router']->addRoute('projects/managers', 'projectuser', 'managers');
+
// Action routes
$container['router']->addRoute('project/:project_id/actions', 'action', 'index', array('project_id'));
$container['router']->addRoute('project/:project_id/action/:action_id/confirm', 'action', 'confirm', array('project_id', 'action_id'));
diff --git a/app/Subscriber/AuthSubscriber.php b/app/Subscriber/AuthSubscriber.php
index efda9663..f834afec 100644
--- a/app/Subscriber/AuthSubscriber.php
+++ b/app/Subscriber/AuthSubscriber.php
@@ -57,8 +57,6 @@ class AuthSubscriber extends Base implements EventSubscriberInterface
$this->userSession->validatePostAuthentication();
}
- $this->sessionStorage->hasSubtaskInProgress = $this->subtask->hasSubtaskInProgress($this->userSession->getId());
-
if (isset($this->sessionStorage->hasRememberMe) && $this->sessionStorage->hasRememberMe) {
$session = $this->rememberMeSession->create($this->userSession->getId(), $ipAddress, $userAgent);
$this->rememberMeCookie->write($session['token'], $session['sequence'], $session['expiration']);
diff --git a/app/Subscriber/BootstrapSubscriber.php b/app/Subscriber/BootstrapSubscriber.php
index ed51a3e1..e399f688 100644
--- a/app/Subscriber/BootstrapSubscriber.php
+++ b/app/Subscriber/BootstrapSubscriber.php
@@ -17,6 +17,7 @@ class BootstrapSubscriber extends \Kanboard\Core\Base implements EventSubscriber
{
$this->config->setupTranslations();
$this->config->setupTimezone();
+ $this->sessionStorage->hasSubtaskInProgress = $this->subtask->hasSubtaskInProgress($this->userSession->getId());
}
public function __destruct()
diff --git a/app/Template/analytic/compare_hours.php b/app/Template/analytic/compare_hours.php
new file mode 100644
index 00000000..c52023c8
--- /dev/null
+++ b/app/Template/analytic/compare_hours.php
@@ -0,0 +1,57 @@
+<div class="page-header">
+ <h2><?= t('Compare Estimated Time vs Actual Time') ?></h2>
+</div>
+
+<div class="listing">
+ <ul>
+ <li><?= t('Estimated hours: ').'<strong>'.$this->e($metrics['open']['time_estimated']+$metrics['open']['time_estimated']) ?></strong></li>
+ <li><?= t('Actual hours: ').'<strong>'.$this->e($metrics['open']['time_spent']+$metrics['closed']['time_spent']) ?></strong></li>
+ </ul>
+</div>
+
+<?php if (empty($metrics)): ?>
+ <p class="alert"><?= t('Not enough data to show the graph.') ?></p>
+<?php else: ?>
+<section id="analytic-compare-hours">
+ <div id="chart" data-metrics='<?= json_encode($metrics, JSON_HEX_APOS)?>' data-label-spent="<?= t('Hours Spent') ?>" data-label-estimated="<?= t('Hours Estimated') ?>"></div>
+
+ <?php if ($paginator->isEmpty()): ?>
+ <p class="alert"><?= t('No tasks found.') ?></p>
+ <?php elseif (! $paginator->isEmpty()): ?>
+ <table class="table-fixed table-small">
+ <tr>
+ <th class="column-5"><?= $paginator->order(t('Id'), 'tasks.id') ?></th>
+ <th><?= $paginator->order(t('Title'), 'tasks.title') ?></th>
+ <th class="column-5"><?= $paginator->order(t('Status'), 'tasks.is_active') ?></th>
+ <th class="column-10"><?= $paginator->order(t('Estimated Time'), 'tasks.time_estimated') ?></th>
+ <th class="column-10"><?= $paginator->order(t('Actual Time'), 'tasks.time_spent') ?></th>
+ </tr>
+ <?php foreach ($paginator->getCollection() as $task): ?>
+ <tr>
+ <td class="task-table color-<?= $task['color_id'] ?>">
+ <?= $this->url->link('#'.$this->e($task['id']), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, '', t('View this task')) ?>
+ </td>
+ <td>
+ <?= $this->url->link($this->e($task['title']), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, '', t('View this task')) ?>
+ </td>
+ <td>
+ <?php if ($task['is_active'] == \Kanboard\Model\Task::STATUS_OPEN): ?>
+ <?= t('Open') ?>
+ <?php else: ?>
+ <?= t('Closed') ?>
+ <?php endif ?>
+ </td>
+ <td>
+ <?= $this->e($task['time_estimated']) ?>
+ </td>
+ <td>
+ <?= $this->e($task['time_spent']) ?>
+ </td>
+ </tr>
+ <?php endforeach ?>
+ </table>
+
+ <?= $paginator ?>
+ <?php endif ?>
+</section>
+<?php endif ?>
diff --git a/app/Template/analytic/sidebar.php b/app/Template/analytic/sidebar.php
index c942f7ed..746fcebb 100644
--- a/app/Template/analytic/sidebar.php
+++ b/app/Template/analytic/sidebar.php
@@ -19,7 +19,10 @@
<li <?= $this->app->getRouterAction() === 'leadandcycletime' ? 'class="active"' : '' ?>>
<?= $this->url->link(t('Lead and cycle time'), 'analytic', 'leadAndCycleTime', array('project_id' => $project['id'])) ?>
</li>
+ <li <?= $this->app->getRouterAction() === 'comparehours' ? 'class="active"' : '' ?>>
+ <?= $this->url->link(t('Compare hours'), 'analytic', 'compareHours', array('project_id' => $project['id'])) ?>
+ </li>
</ul>
<div class="sidebar-collapse"><a href="#" title="<?= t('Hide sidebar') ?>"><i class="fa fa-chevron-left"></i></a></div>
<div class="sidebar-expand" style="display: none"><a href="#" title="<?= t('Expand sidebar') ?>"><i class="fa fa-chevron-right"></i></a></div>
-</div> \ No newline at end of file
+</div>