diff options
Diffstat (limited to 'app')
-rw-r--r-- | app/Api/Task.php | 24 | ||||
-rw-r--r-- | app/Controller/Analytic.php | 29 | ||||
-rw-r--r-- | app/Controller/Group.php | 2 | ||||
-rw-r--r-- | app/Core/Csv.php | 2 | ||||
-rw-r--r-- | app/Helper/Subtask.php | 2 | ||||
-rw-r--r-- | app/Locale/pl_PL/translations.php | 450 | ||||
-rw-r--r-- | app/Model/ProjectAnalytic.php | 45 | ||||
-rw-r--r-- | app/Model/ProjectPermission.php | 6 | ||||
-rw-r--r-- | app/Model/TaskFinder.php | 1 | ||||
-rw-r--r-- | app/ServiceProvider/RouteProvider.php | 7 | ||||
-rw-r--r-- | app/Subscriber/AuthSubscriber.php | 2 | ||||
-rw-r--r-- | app/Subscriber/BootstrapSubscriber.php | 1 | ||||
-rw-r--r-- | app/Template/analytic/compare_hours.php | 57 | ||||
-rw-r--r-- | app/Template/analytic/sidebar.php | 5 |
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> |