diff options
Diffstat (limited to 'app')
64 files changed, 3174 insertions, 27 deletions
diff --git a/app/Controller/Base.php b/app/Controller/Base.php index d949048d..ec202c06 100644 --- a/app/Controller/Base.php +++ b/app/Controller/Base.php @@ -34,6 +34,7 @@ use Symfony\Component\EventDispatcher\Event; * @property \Model\Config $config * @property \Model\DateParser $dateParser * @property \Model\File $file + * @property \Model\HourlyRate $hourlyRate * @property \Model\LastLogin $lastLogin * @property \Model\Notification $notification * @property \Model\Project $project @@ -56,6 +57,11 @@ use Symfony\Component\EventDispatcher\Event; * @property \Model\TaskPosition $taskPosition * @property \Model\TaskPermission $taskPermission * @property \Model\TaskStatus $taskStatus + * @property \Model\Timetable $timetable + * @property \Model\TimetableDay $timetableDay + * @property \Model\TimetableWeek $timetableWeek + * @property \Model\TimetableExtra $timetableExtra + * @property \Model\TimetableOff $timetableOff * @property \Model\TaskValidator $taskValidator * @property \Model\TaskLink $taskLink * @property \Model\CommentHistory $commentHistory diff --git a/app/Controller/Hourlyrate.php b/app/Controller/Hourlyrate.php new file mode 100644 index 00000000..8d96e5ca --- /dev/null +++ b/app/Controller/Hourlyrate.php @@ -0,0 +1,89 @@ +<?php + +namespace Controller; + +/** + * Hourly Rate controller + * + * @package controller + * @author Frederic Guillot + */ +class Hourlyrate extends User +{ + /** + * Display rate and form + * + * @access public + */ + public function index(array $values = array(), array $errors = array()) + { + $user = $this->getUser(); + + $this->response->html($this->layout('hourlyrate/index', array( + 'rates' => $this->hourlyRate->getAllByUser($user['id']), + 'currencies_list' => $this->config->getCurrencies(), + 'values' => $values + array('user_id' => $user['id']), + 'errors' => $errors, + 'user' => $user, + ))); + } + + /** + * Validate and save a new rate + * + * @access public + */ + public function save() + { + $values = $this->request->getValues(); + list($valid, $errors) = $this->hourlyRate->validateCreation($values); + + if ($valid) { + + if ($this->hourlyRate->create($values['user_id'], $values['rate'], $values['currency'], $values['date_effective'])) { + $this->session->flash(t('Hourly rate created successfully.')); + $this->response->redirect($this->helper->url('hourlyrate', 'index', array('user_id' => $values['user_id']))); + } + else { + $this->session->flashError(t('Unable to save the hourly rate.')); + } + } + + $this->index($values, $errors); + } + + /** + * Confirmation dialag box to remove a row + * + * @access public + */ + public function confirm() + { + $user = $this->getUser(); + + $this->response->html($this->layout('hourlyrate/remove', array( + 'rate_id' => $this->request->getIntegerParam('rate_id'), + 'user' => $user, + ))); + } + + /** + * Remove a row + * + * @access public + */ + public function remove() + { + $this->checkCSRFParam(); + $user = $this->getUser(); + + if ($this->hourlyRate->remove($this->request->getIntegerParam('rate_id'))) { + $this->session->flash(t('Rate removed successfully.')); + } + else { + $this->session->flash(t('Unable to remove this rate.')); + } + + $this->response->redirect($this->helper->url('hourlyrate', 'index', array('user_id' => $user['id']))); + } +} diff --git a/app/Controller/Subtask.php b/app/Controller/Subtask.php index c7ec00d1..385785a1 100644 --- a/app/Controller/Subtask.php +++ b/app/Controller/Subtask.php @@ -185,7 +185,7 @@ class Subtask extends Base if ($redirect === 'board') { $this->session['has_subtask_inprogress'] = $this->subtask->hasSubtaskInProgress($this->userSession->getId()); - + $this->response->html($this->template->render('board/subtasks', array( 'subtasks' => $this->subtask->getAll($task['id']), 'task' => $task, @@ -259,4 +259,22 @@ class Subtask extends Base $this->response->redirect($this->helper->url('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']))); } } + + /** + * Move subtask position + * + * @access public + */ + public function movePosition() + { + $this->checkCSRFParam(); + $project_id = $this->request->getIntegerParam('project_id'); + $task_id = $this->request->getIntegerParam('task_id'); + $subtask_id = $this->request->getIntegerParam('subtask_id'); + $direction = $this->request->getStringParam('direction'); + $method = $direction === 'up' ? 'moveUp' : 'moveDown'; + + $this->subtask->$method($task_id, $subtask_id); + $this->response->redirect($this->helper->url('task', 'show', array('project_id' => $project_id, 'task_id' => $task_id)).'#subtasks'); + } } diff --git a/app/Controller/Timetable.php b/app/Controller/Timetable.php new file mode 100644 index 00000000..65edb44c --- /dev/null +++ b/app/Controller/Timetable.php @@ -0,0 +1,39 @@ +<?php + +namespace Controller; + +use DateTime; + +/** + * Timetable controller + * + * @package controller + * @author Frederic Guillot + */ +class Timetable extends User +{ + /** + * Display timetable for the user + * + * @access public + */ + public function index() + { + $user = $this->getUser(); + $from = $this->request->getStringParam('from', date('Y-m-d')); + $to = $this->request->getStringParam('to', date('Y-m-d', strtotime('next week'))); + $timetable = $this->timetable->calculate($user['id'], new DateTime($from), new DateTime($to)); + + $this->response->html($this->layout('timetable/index', array( + 'user' => $user, + 'timetable' => $timetable, + 'values' => array( + 'from' => $from, + 'to' => $to, + 'controller' => 'timetable', + 'action' => 'index', + 'user_id' => $user['id'], + ), + ))); + } +} diff --git a/app/Controller/Timetableday.php b/app/Controller/Timetableday.php new file mode 100644 index 00000000..eea44ae1 --- /dev/null +++ b/app/Controller/Timetableday.php @@ -0,0 +1,88 @@ +<?php + +namespace Controller; + +/** + * Day Timetable controller + * + * @package controller + * @author Frederic Guillot + */ +class Timetableday extends User +{ + /** + * Display timetable for the user + * + * @access public + */ + public function index(array $values = array(), array $errors = array()) + { + $user = $this->getUser(); + + $this->response->html($this->layout('timetable_day/index', array( + 'timetable' => $this->timetableDay->getByUser($user['id']), + 'values' => $values + array('user_id' => $user['id']), + 'errors' => $errors, + 'user' => $user, + ))); + } + + /** + * Validate and save + * + * @access public + */ + public function save() + { + $values = $this->request->getValues(); + list($valid, $errors) = $this->timetableDay->validateCreation($values); + + if ($valid) { + + if ($this->timetableDay->create($values['user_id'], $values['start'], $values['end'])) { + $this->session->flash(t('Time slot created successfully.')); + $this->response->redirect($this->helper->url('timetableday', 'index', array('user_id' => $values['user_id']))); + } + else { + $this->session->flashError(t('Unable to save this time slot.')); + } + } + + $this->index($values, $errors); + } + + /** + * Confirmation dialag box to remove a row + * + * @access public + */ + public function confirm() + { + $user = $this->getUser(); + + $this->response->html($this->layout('timetable_day/remove', array( + 'slot_id' => $this->request->getIntegerParam('slot_id'), + 'user' => $user, + ))); + } + + /** + * Remove a row + * + * @access public + */ + public function remove() + { + $this->checkCSRFParam(); + $user = $this->getUser(); + + if ($this->timetableDay->remove($this->request->getIntegerParam('slot_id'))) { + $this->session->flash(t('Time slot removed successfully.')); + } + else { + $this->session->flash(t('Unable to remove this time slot.')); + } + + $this->response->redirect($this->helper->url('timetableday', 'index', array('user_id' => $user['id']))); + } +} diff --git a/app/Controller/Timetableextra.php b/app/Controller/Timetableextra.php new file mode 100644 index 00000000..c362bd1a --- /dev/null +++ b/app/Controller/Timetableextra.php @@ -0,0 +1,16 @@ +<?php + +namespace Controller; + +/** + * Over-time Timetable controller + * + * @package controller + * @author Frederic Guillot + */ +class Timetableextra extends TimetableOff +{ + protected $model = 'timetableExtra'; + protected $controller_url = 'timetableextra'; + protected $template_dir = 'timetable_extra'; +} diff --git a/app/Controller/Timetableoff.php b/app/Controller/Timetableoff.php new file mode 100644 index 00000000..19a6fab1 --- /dev/null +++ b/app/Controller/Timetableoff.php @@ -0,0 +1,107 @@ +<?php + +namespace Controller; + +/** + * Time-off Timetable controller + * + * @package controller + * @author Frederic Guillot + */ +class Timetableoff extends User +{ + protected $model = 'timetableOff'; + protected $controller_url = 'timetableoff'; + protected $template_dir = 'timetable_off'; + + /** + * Display timetable for the user + * + * @access public + */ + public function index(array $values = array(), array $errors = array()) + { + $user = $this->getUser(); + + $paginator = $this->paginator + ->setUrl($this->controller_url, 'index', array('user_id' => $user['id'])) + ->setMax(10) + ->setOrder('date') + ->setDirection('desc') + ->setQuery($this->{$this->model}->getUserQuery($user['id'])) + ->calculate(); + + $this->response->html($this->layout($this->template_dir.'/index', array( + 'values' => $values + array('user_id' => $user['id']), + 'errors' => $errors, + 'paginator' => $paginator, + 'user' => $user, + ))); + } + + /** + * Validate and save + * + * @access public + */ + public function save() + { + $values = $this->request->getValues(); + list($valid, $errors) = $this->{$this->model}->validateCreation($values); + + if ($valid) { + + if ($this->{$this->model}->create( + $values['user_id'], + $values['date'], + isset($values['all_day']) && $values['all_day'] == 1, + $values['start'], + $values['end'], + $values['comment'])) { + + $this->session->flash(t('Time slot created successfully.')); + $this->response->redirect($this->helper->url($this->controller_url, 'index', array('user_id' => $values['user_id']))); + } + else { + $this->session->flashError(t('Unable to save this time slot.')); + } + } + + $this->index($values, $errors); + } + + /** + * Confirmation dialag box to remove a row + * + * @access public + */ + public function confirm() + { + $user = $this->getUser(); + + $this->response->html($this->layout($this->template_dir.'/remove', array( + 'slot_id' => $this->request->getIntegerParam('slot_id'), + 'user' => $user, + ))); + } + + /** + * Remove a row + * + * @access public + */ + public function remove() + { + $this->checkCSRFParam(); + $user = $this->getUser(); + + if ($this->{$this->model}->remove($this->request->getIntegerParam('slot_id'))) { + $this->session->flash(t('Time slot removed successfully.')); + } + else { + $this->session->flash(t('Unable to remove this time slot.')); + } + + $this->response->redirect($this->helper->url($this->controller_url, 'index', array('user_id' => $user['id']))); + } +} diff --git a/app/Controller/Timetableweek.php b/app/Controller/Timetableweek.php new file mode 100644 index 00000000..829f4402 --- /dev/null +++ b/app/Controller/Timetableweek.php @@ -0,0 +1,99 @@ +<?php + +namespace Controller; + +/** + * Week Timetable controller + * + * @package controller + * @author Frederic Guillot + */ +class Timetableweek extends User +{ + /** + * Display timetable for the user + * + * @access public + */ + public function index(array $values = array(), array $errors = array()) + { + $user = $this->getUser(); + + if (empty($values)) { + + $day = $this->timetableDay->getByUser($user['id']); + + $values = array( + 'user_id' => $user['id'], + 'start' => isset($day[0]['start']) ? $day[0]['start'] : null, + 'end' => isset($day[0]['end']) ? $day[0]['end'] : null, + ); + } + + $this->response->html($this->layout('timetable_week/index', array( + 'timetable' => $this->timetableWeek->getByUser($user['id']), + 'values' => $values, + 'errors' => $errors, + 'user' => $user, + ))); + } + + /** + * Validate and save + * + * @access public + */ + public function save() + { + $values = $this->request->getValues(); + list($valid, $errors) = $this->timetableWeek->validateCreation($values); + + if ($valid) { + + if ($this->timetableWeek->create($values['user_id'], $values['day'], $values['start'], $values['end'])) { + $this->session->flash(t('Time slot created successfully.')); + $this->response->redirect($this->helper->url('timetableweek', 'index', array('user_id' => $values['user_id']))); + } + else { + $this->session->flashError(t('Unable to save this time slot.')); + } + } + + $this->index($values, $errors); + } + + /** + * Confirmation dialag box to remove a row + * + * @access public + */ + public function confirm() + { + $user = $this->getUser(); + + $this->response->html($this->layout('timetable_week/remove', array( + 'slot_id' => $this->request->getIntegerParam('slot_id'), + 'user' => $user, + ))); + } + + /** + * Remove a row + * + * @access public + */ + public function remove() + { + $this->checkCSRFParam(); + $user = $this->getUser(); + + if ($this->timetableWeek->remove($this->request->getIntegerParam('slot_id'))) { + $this->session->flash(t('Time slot removed successfully.')); + } + else { + $this->session->flash(t('Unable to remove this time slot.')); + } + + $this->response->redirect($this->helper->url('timetableweek', 'index', array('user_id' => $user['id']))); + } +} diff --git a/app/Controller/User.php b/app/Controller/User.php index decdb646..46d0214d 100644 --- a/app/Controller/User.php +++ b/app/Controller/User.php @@ -69,12 +69,12 @@ class User extends Base /** * Common layout for user views * - * @access private + * @access protected * @param string $template Template name * @param array $params Template parameters * @return string */ - private function layout($template, array $params) + protected function layout($template, array $params) { $content = $this->template->render($template, $params); $params['user_content_for_layout'] = $content; @@ -90,10 +90,10 @@ class User extends Base /** * Common method to get the user * - * @access private + * @access protected * @return array */ - private function getUser() + protected function getUser() { $user = $this->user->getById($this->request->getIntegerParam('user_id')); diff --git a/app/Core/Helper.php b/app/Core/Helper.php index 01ebb08f..78267feb 100644 --- a/app/Core/Helper.php +++ b/app/Core/Helper.php @@ -677,4 +677,53 @@ class Helper array('task_id' => $subtask['task_id'], 'subtask_id' => $subtask['id'], 'redirect' => $redirect) ); } + + /** + * Get all hours for day + * + * @access public + * @return array + */ + public function getDayHours() + { + $values = array(); + + foreach (range(0, 23) as $hour) { + foreach (array(0, 30) as $minute) { + $time = sprintf('%02d:%02d', $hour, $minute); + $values[$time] = $time; + } + } + + return $values; + } + + /** + * Get all days of a week + * + * @access public + * @return array + */ + public function getWeekDays() + { + $values = array(); + + foreach (range(1, 7) as $day) { + $values[$day] = $this->getWeekDay($day); + } + + return $values; + } + + /** + * Get the localized day name from the day number + * + * @access public + * @param integer $day Day number + * @return string + */ + public function getWeekDay($day) + { + return dt('%A', strtotime('next Monday +'.($day - 1).' days')); + } } diff --git a/app/Locale/da_DK/translations.php b/app/Locale/da_DK/translations.php index a378bec6..f31bfa08 100644 --- a/app/Locale/da_DK/translations.php +++ b/app/Locale/da_DK/translations.php @@ -1,6 +1,8 @@ <?php return array( + // 'number.decimals_separator' => '', + // 'number.thousands_separator' => '', 'None' => 'Ingen', 'edit' => 'rediger', 'Edit' => 'Rediger', @@ -737,4 +739,38 @@ return array( // 'Compact view' => '', // 'Horizontal scrolling' => '', // 'Compact/wide view' => '', + // 'No results match:' => '', + // 'Remove hourly rate' => '', + // 'Do you really want to remove this hourly rate?' => '', + // 'Hourly rates' => '', + // 'Hourly rate' => '', + // 'Currency' => '', + // 'Effective date' => '', + // 'Add new rate' => '', + // 'Rate removed successfully.' => '', + // 'Unable to remove this rate.' => '', + // 'Unable to save the hourly rate.' => '', + // 'Hourly rate created successfully.' => '', + // 'Start time' => '', + // 'End time' => '', + // 'Comment' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', ); diff --git a/app/Locale/de_DE/translations.php b/app/Locale/de_DE/translations.php index 5652e3ba..ec1d20ca 100644 --- a/app/Locale/de_DE/translations.php +++ b/app/Locale/de_DE/translations.php @@ -1,6 +1,8 @@ <?php return array( + // 'number.decimals_separator' => '', + // 'number.thousands_separator' => '', 'None' => 'Keines', 'edit' => 'Bearbeiten', 'Edit' => 'Bearbeiten', @@ -737,4 +739,38 @@ return array( // 'Compact view' => '', // 'Horizontal scrolling' => '', // 'Compact/wide view' => '', + // 'No results match:' => '', + // 'Remove hourly rate' => '', + // 'Do you really want to remove this hourly rate?' => '', + // 'Hourly rates' => '', + // 'Hourly rate' => '', + // 'Currency' => '', + // 'Effective date' => '', + // 'Add new rate' => '', + // 'Rate removed successfully.' => '', + // 'Unable to remove this rate.' => '', + // 'Unable to save the hourly rate.' => '', + // 'Hourly rate created successfully.' => '', + // 'Start time' => '', + // 'End time' => '', + // 'Comment' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', ); diff --git a/app/Locale/es_ES/translations.php b/app/Locale/es_ES/translations.php index 15071483..aaacd7f8 100644 --- a/app/Locale/es_ES/translations.php +++ b/app/Locale/es_ES/translations.php @@ -1,6 +1,8 @@ <?php return array( + // 'number.decimals_separator' => '', + // 'number.thousands_separator' => '', 'None' => 'Ninguno', 'edit' => 'modificar', 'Edit' => 'Modificar', @@ -737,4 +739,38 @@ return array( // 'Compact view' => '', // 'Horizontal scrolling' => '', // 'Compact/wide view' => '', + // 'No results match:' => '', + // 'Remove hourly rate' => '', + // 'Do you really want to remove this hourly rate?' => '', + // 'Hourly rates' => '', + // 'Hourly rate' => '', + // 'Currency' => '', + // 'Effective date' => '', + // 'Add new rate' => '', + // 'Rate removed successfully.' => '', + // 'Unable to remove this rate.' => '', + // 'Unable to save the hourly rate.' => '', + // 'Hourly rate created successfully.' => '', + // 'Start time' => '', + // 'End time' => '', + // 'Comment' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', ); diff --git a/app/Locale/fi_FI/translations.php b/app/Locale/fi_FI/translations.php index e9342e80..bd3a7d68 100644 --- a/app/Locale/fi_FI/translations.php +++ b/app/Locale/fi_FI/translations.php @@ -1,6 +1,8 @@ <?php return array( + // 'number.decimals_separator' => '', + // 'number.thousands_separator' => '', 'None' => 'Ei mikään', 'edit' => 'muokkaa', 'Edit' => 'Muokkaa', @@ -737,4 +739,38 @@ return array( // 'Compact view' => '', // 'Horizontal scrolling' => '', // 'Compact/wide view' => '', + // 'No results match:' => '', + // 'Remove hourly rate' => '', + // 'Do you really want to remove this hourly rate?' => '', + // 'Hourly rates' => '', + // 'Hourly rate' => '', + // 'Currency' => '', + // 'Effective date' => '', + // 'Add new rate' => '', + // 'Rate removed successfully.' => '', + // 'Unable to remove this rate.' => '', + // 'Unable to save the hourly rate.' => '', + // 'Hourly rate created successfully.' => '', + // 'Start time' => '', + // 'End time' => '', + // 'Comment' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', ); diff --git a/app/Locale/fr_FR/translations.php b/app/Locale/fr_FR/translations.php index 9a1d5f57..df1d8428 100644 --- a/app/Locale/fr_FR/translations.php +++ b/app/Locale/fr_FR/translations.php @@ -1,6 +1,8 @@ <?php return array( + 'number.decimals_separator' => ',', + 'number.thousands_separator' => ' ', 'None' => 'Aucun', 'edit' => 'modifier', 'Edit' => 'Modifier', @@ -739,4 +741,38 @@ return array( 'Compact view' => 'Vue compacte', 'Horizontal scrolling' => 'Défilement horizontal', 'Compact/wide view' => 'Basculer entre la vue compacte et étendue', + 'No results match:' => 'Aucun résultat :', + 'Remove hourly rate' => 'Supprimer un taux horaire', + 'Do you really want to remove this hourly rate?' => 'Voulez-vous vraiment supprimer ce taux horaire ?', + 'Hourly rates' => 'Taux horaires', + 'Hourly rate' => 'Taux horaire', + 'Currency' => 'Devise', + 'Effective date' => 'Date d\'effet', + 'Add new rate' => 'Ajouter un nouveau taux horaire', + 'Rate removed successfully.' => 'Taux horaire supprimé avec succès.', + 'Unable to remove this rate.' => 'Impossible de supprimer ce taux horaire.', + 'Unable to save the hourly rate.' => 'Impossible de sauvegarder ce taux horaire.', + 'Hourly rate created successfully.' => 'Taux horaire créé avec succès.', + 'Start time' => 'Date de début', + 'End time' => 'Date de fin', + 'Comment' => 'Commentaire', + 'All day' => 'Toute la journée', + 'Day' => 'Jour', + 'Manage timetable' => 'Gérer les horaires', + 'Overtime timetable' => 'Heures supplémentaires', + 'Time off timetable' => 'Heures d\'absences', + 'Timetable' => 'Horaires', + 'Work timetable' => 'Horaires travaillés', + 'Week timetable' => 'Horaires de la semaine', + 'Day timetable' => 'Horaire d\'une journée', + 'From' => 'Depuis', + 'To' => 'À', + 'Time slot created successfully.' => 'Créneau horaire créé avec succès.', + 'Unable to save this time slot.' => 'Impossible de sauvegarder ce créneau horaire.', + 'Time slot removed successfully.' => 'Créneau horaire supprimé avec succès.', + 'Unable to remove this time slot.' => 'Impossible de supprimer ce créneau horaire.', + 'Do you really want to remove this time slot?' => 'Voulez-vous vraiment supprimer ce créneau horaire ?', + 'Remove time slot' => 'Supprimer un créneau horaire', + 'Add new time slot' => 'Ajouter un créneau horaire', + 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => 'Ces horaires sont utilisés lorsque la case « Toute la journée » est cochée pour les heures d\'absences ou supplémentaires programmées.', ); diff --git a/app/Locale/hu_HU/translations.php b/app/Locale/hu_HU/translations.php index ef057f70..fed84522 100644 --- a/app/Locale/hu_HU/translations.php +++ b/app/Locale/hu_HU/translations.php @@ -1,6 +1,8 @@ <?php return array( + // 'number.decimals_separator' => '', + // 'number.thousands_separator' => '', 'None' => 'Nincs', 'edit' => 'szerkesztés', 'Edit' => 'Szerkesztés', @@ -737,4 +739,38 @@ return array( // 'Compact view' => '', // 'Horizontal scrolling' => '', // 'Compact/wide view' => '', + // 'No results match:' => '', + // 'Remove hourly rate' => '', + // 'Do you really want to remove this hourly rate?' => '', + // 'Hourly rates' => '', + // 'Hourly rate' => '', + // 'Currency' => '', + // 'Effective date' => '', + // 'Add new rate' => '', + // 'Rate removed successfully.' => '', + // 'Unable to remove this rate.' => '', + // 'Unable to save the hourly rate.' => '', + // 'Hourly rate created successfully.' => '', + // 'Start time' => '', + // 'End time' => '', + // 'Comment' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', ); diff --git a/app/Locale/it_IT/translations.php b/app/Locale/it_IT/translations.php index 3154ad17..d6c57371 100644 --- a/app/Locale/it_IT/translations.php +++ b/app/Locale/it_IT/translations.php @@ -1,6 +1,8 @@ <?php return array( + // 'number.decimals_separator' => '', + // 'number.thousands_separator' => '', 'None' => 'Nessuno', 'edit' => 'modificare', 'Edit' => 'Modificare', @@ -737,4 +739,38 @@ return array( // 'Compact view' => '', // 'Horizontal scrolling' => '', // 'Compact/wide view' => '', + // 'No results match:' => '', + // 'Remove hourly rate' => '', + // 'Do you really want to remove this hourly rate?' => '', + // 'Hourly rates' => '', + // 'Hourly rate' => '', + // 'Currency' => '', + // 'Effective date' => '', + // 'Add new rate' => '', + // 'Rate removed successfully.' => '', + // 'Unable to remove this rate.' => '', + // 'Unable to save the hourly rate.' => '', + // 'Hourly rate created successfully.' => '', + // 'Start time' => '', + // 'End time' => '', + // 'Comment' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', ); diff --git a/app/Locale/ja_JP/translations.php b/app/Locale/ja_JP/translations.php index d55227f2..dd727d0e 100644 --- a/app/Locale/ja_JP/translations.php +++ b/app/Locale/ja_JP/translations.php @@ -1,6 +1,8 @@ <?php return array( + // 'number.decimals_separator' => '', + // 'number.thousands_separator' => '', 'None' => 'なし', 'edit' => '変更', 'Edit' => '変更', @@ -737,4 +739,38 @@ return array( // 'Compact view' => '', // 'Horizontal scrolling' => '', // 'Compact/wide view' => '', + // 'No results match:' => '', + // 'Remove hourly rate' => '', + // 'Do you really want to remove this hourly rate?' => '', + // 'Hourly rates' => '', + // 'Hourly rate' => '', + // 'Currency' => '', + // 'Effective date' => '', + // 'Add new rate' => '', + // 'Rate removed successfully.' => '', + // 'Unable to remove this rate.' => '', + // 'Unable to save the hourly rate.' => '', + // 'Hourly rate created successfully.' => '', + // 'Start time' => '', + // 'End time' => '', + // 'Comment' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', ); diff --git a/app/Locale/pl_PL/translations.php b/app/Locale/pl_PL/translations.php index 6eac4005..deb5a280 100644 --- a/app/Locale/pl_PL/translations.php +++ b/app/Locale/pl_PL/translations.php @@ -1,6 +1,8 @@ <?php return array( + // 'number.decimals_separator' => '', + // 'number.thousands_separator' => '', 'None' => 'Brak', 'edit' => 'edytuj', 'Edit' => 'Edytuj', @@ -737,4 +739,38 @@ return array( // 'Compact view' => '', // 'Horizontal scrolling' => '', // 'Compact/wide view' => '', + // 'No results match:' => '', + // 'Remove hourly rate' => '', + // 'Do you really want to remove this hourly rate?' => '', + // 'Hourly rates' => '', + // 'Hourly rate' => '', + // 'Currency' => '', + // 'Effective date' => '', + // 'Add new rate' => '', + // 'Rate removed successfully.' => '', + // 'Unable to remove this rate.' => '', + // 'Unable to save the hourly rate.' => '', + // 'Hourly rate created successfully.' => '', + // 'Start time' => '', + // 'End time' => '', + // 'Comment' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', ); diff --git a/app/Locale/pt_BR/translations.php b/app/Locale/pt_BR/translations.php index 1791f74f..f81bc675 100644 --- a/app/Locale/pt_BR/translations.php +++ b/app/Locale/pt_BR/translations.php @@ -1,6 +1,8 @@ <?php return array( + // 'number.decimals_separator' => '', + // 'number.thousands_separator' => '', 'None' => 'Nenhum', 'edit' => 'editar', 'Edit' => 'Editar', @@ -737,4 +739,38 @@ return array( // 'Compact view' => '', // 'Horizontal scrolling' => '', // 'Compact/wide view' => '', + // 'No results match:' => '', + // 'Remove hourly rate' => '', + // 'Do you really want to remove this hourly rate?' => '', + // 'Hourly rates' => '', + // 'Hourly rate' => '', + // 'Currency' => '', + // 'Effective date' => '', + // 'Add new rate' => '', + // 'Rate removed successfully.' => '', + // 'Unable to remove this rate.' => '', + // 'Unable to save the hourly rate.' => '', + // 'Hourly rate created successfully.' => '', + // 'Start time' => '', + // 'End time' => '', + // 'Comment' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', ); diff --git a/app/Locale/ru_RU/translations.php b/app/Locale/ru_RU/translations.php index 2fdf6e50..a4caa545 100644 --- a/app/Locale/ru_RU/translations.php +++ b/app/Locale/ru_RU/translations.php @@ -1,6 +1,8 @@ <?php return array( + // 'number.decimals_separator' => '', + // 'number.thousands_separator' => '', 'None' => 'Отсутствует', 'edit' => 'изменить', 'Edit' => 'Изменить', @@ -737,4 +739,38 @@ return array( // 'Compact view' => '', // 'Horizontal scrolling' => '', // 'Compact/wide view' => '', + // 'No results match:' => '', + // 'Remove hourly rate' => '', + // 'Do you really want to remove this hourly rate?' => '', + // 'Hourly rates' => '', + // 'Hourly rate' => '', + // 'Currency' => '', + // 'Effective date' => '', + // 'Add new rate' => '', + // 'Rate removed successfully.' => '', + // 'Unable to remove this rate.' => '', + // 'Unable to save the hourly rate.' => '', + // 'Hourly rate created successfully.' => '', + // 'Start time' => '', + // 'End time' => '', + // 'Comment' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', ); diff --git a/app/Locale/sv_SE/translations.php b/app/Locale/sv_SE/translations.php index bfc8d714..47ac8b26 100644 --- a/app/Locale/sv_SE/translations.php +++ b/app/Locale/sv_SE/translations.php @@ -1,6 +1,8 @@ <?php return array( + // 'number.decimals_separator' => '', + // 'number.thousands_separator' => '', 'None' => 'Ingen', 'edit' => 'redigera', 'Edit' => 'Redigera', @@ -737,4 +739,38 @@ return array( // 'Compact view' => '', // 'Horizontal scrolling' => '', // 'Compact/wide view' => '', + // 'No results match:' => '', + // 'Remove hourly rate' => '', + // 'Do you really want to remove this hourly rate?' => '', + // 'Hourly rates' => '', + // 'Hourly rate' => '', + // 'Currency' => '', + // 'Effective date' => '', + // 'Add new rate' => '', + // 'Rate removed successfully.' => '', + // 'Unable to remove this rate.' => '', + // 'Unable to save the hourly rate.' => '', + // 'Hourly rate created successfully.' => '', + // 'Start time' => '', + // 'End time' => '', + // 'Comment' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', ); diff --git a/app/Locale/th_TH/translations.php b/app/Locale/th_TH/translations.php index 04e8dcf8..43395db5 100644 --- a/app/Locale/th_TH/translations.php +++ b/app/Locale/th_TH/translations.php @@ -1,6 +1,8 @@ <?php return array( + // 'number.decimals_separator' => '', + // 'number.thousands_separator' => '', 'None' => 'ไม่มี', 'edit' => 'แก้ไข', 'Edit' => 'แก้ไข', @@ -737,4 +739,38 @@ return array( // 'Compact view' => '', // 'Horizontal scrolling' => '', // 'Compact/wide view' => '', + // 'No results match:' => '', + // 'Remove hourly rate' => '', + // 'Do you really want to remove this hourly rate?' => '', + // 'Hourly rates' => '', + // 'Hourly rate' => '', + // 'Currency' => '', + // 'Effective date' => '', + // 'Add new rate' => '', + // 'Rate removed successfully.' => '', + // 'Unable to remove this rate.' => '', + // 'Unable to save the hourly rate.' => '', + // 'Hourly rate created successfully.' => '', + // 'Start time' => '', + // 'End time' => '', + // 'Comment' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', ); diff --git a/app/Locale/tr_TR/translations.php b/app/Locale/tr_TR/translations.php new file mode 100644 index 00000000..e2d4e72c --- /dev/null +++ b/app/Locale/tr_TR/translations.php @@ -0,0 +1,776 @@ +<?php + +return array( + // 'number.decimals_separator' => '', + // 'number.thousands_separator' => '', + 'None' => 'Hiçbiri', + 'edit' => 'düzenle', + 'Edit' => 'Düzenle', + 'remove' => 'sil', + 'Remove' => 'Sil', + 'Update' => 'Güncelle', + 'Yes' => 'Evet', + 'No' => 'Hayır', + 'cancel' => 'İptal', + 'or' => 'veya', + 'Yellow' => 'Sarı', + 'Blue' => 'Mavi', + 'Green' => 'Yeşil', + 'Purple' => 'Mor', + 'Red' => 'Kırmızı', + 'Orange' => 'Turuncu', + 'Grey' => 'Gri', + 'Save' => 'Kaydet', + 'Login' => 'Giriş', + 'Official website:' => 'Resmi internet sitesi:', + 'Unassigned' => 'Atanmamış', + 'View this task' => 'Bu görevi görüntüle', + 'Remove user' => 'Kullanıcıyı kaldır', + 'Do you really want to remove this user: "%s"?' => 'Bu kullanıcıyı gerçekten silmek istiyor musunuz: "%s"?', + 'New user' => 'Yeni kullanıcı', + 'All users' => 'Tüm kullanıcılar', + 'Username' => 'Kullanıcı adı', + 'Password' => 'Şifre', + // 'Default project' => '', + 'Administrator' => 'Yönetici', + 'Sign in' => 'Giriş yap', + 'Users' => 'Kullanıcılar', + 'No user' => 'Kullanıcı yok', + 'Forbidden' => 'Yasak', + 'Access Forbidden' => 'Erişim yasak', + 'Only administrators can access to this page.' => 'Bu sayfaya yalnızca yöneticiler erişebilir.', + 'Edit user' => 'Kullanıcıyı düzenle', + 'Logout' => 'Çıkış yap', + 'Bad username or password' => 'Hatalı kullanıcı adı veya şifre', + 'users' => 'kullanıcılar', + 'projects' => 'projeler', + 'Edit project' => 'Projeyi düzenle', + 'Name' => 'İsim', + 'Activated' => 'Aktif', + 'Projects' => 'Projeler', + 'No project' => 'Proje yok', + 'Project' => 'Proje', + 'Status' => 'Durum', + 'Tasks' => 'Görevler', + 'Board' => 'Tablo', + 'Actions' => 'İşlemler', + 'Inactive' => 'Aktif değil', + 'Active' => 'Aktif', + 'Column %d' => 'Sütun %d', + 'Add this column' => 'Bu sütunu ekle', + '%d tasks on the board' => '%d görev bu tabloda', + '%d tasks in total' => '%d görev toplam', + 'Unable to update this board.' => 'Bu tablo güncellenemiyor.', + 'Edit board' => 'Tabloyu düzenle', + 'Disable' => 'Devre dışı bırak', + 'Enable' => 'Etkinleştir', + 'New project' => 'Yeni proje', + 'Do you really want to remove this project: "%s"?' => 'Bu projeyi gerçekten silmek istiyor musunuz: "%s"?', + 'Remove project' => 'Projeyi sil', + 'Boards' => 'Tablolar', + 'Edit the board for "%s"' => 'Tabloyu "%s" için güncelle', + 'All projects' => 'Tüm projeler', + 'Change columns' => 'Sütunları değiştir', + 'Add a new column' => 'Yeni sütun ekle', + 'Title' => 'Başlık', + 'Add Column' => 'Sütun ekle', + 'Project "%s"' => 'Proje "%s"', + 'Nobody assigned' => 'Kullanıcı atanmamış', + 'Assigned to %s' => '%s kullanıcısına atanmış', + 'Remove a column' => 'Bir sütunu sil', + 'Remove a column from a board' => 'Tablodan bir sütunu sil', + 'Unable to remove this column.' => 'Bu sütun silinemiyor.', + 'Do you really want to remove this column: "%s"?' => 'Bu sütunu gerçekten silmek istiyor musunuz: "%s"?', + 'This action will REMOVE ALL TASKS associated to this column!' => 'Bu komut sütun içindeki TÜM GÖREVLERİ silecek!', + 'Settings' => 'Ayarlar', + 'Application settings' => 'Uygulama ayarları', + 'Language' => 'Dil', + // 'Webhook token:' => '', + 'API token:' => 'API Token:', + 'More information' => 'Daha fazla bilgi', + 'Database size:' => 'Veritabanı boyutu :', + 'Download the database' => 'Veritabanını indir', + 'Optimize the database' => 'Veritabanını optimize et', + '(VACUUM command)' => '(VACUUM komutu)', + '(Gzip compressed Sqlite file)' => '(Gzip ile sıkıştırılmış Sqlite dosyası)', + 'User settings' => 'Kullanıcı ayarları', + 'My default project:' => 'Benim varsayılan projem:', + 'Close a task' => 'Bir görevi kapat', + 'Do you really want to close this task: "%s"?' => 'Bu görevi gerçekten kapatmak istiyor musunuz: "%s"?', + 'Edit a task' => 'Bir görevi düzenle', + 'Column' => 'Sütun', + 'Color' => 'Renk', + 'Assignee' => 'Atanan', + 'Create another task' => 'Başka bir görev oluştur', + 'New task' => 'Nouvelle tâche', + 'Open a task' => 'Bir görevi aç', + 'Do you really want to open this task: "%s"?' => 'Bu görevi gerçekten açmak istiyor musunuz: "%s"?', + 'Back to the board' => 'Tabloya dön', + // 'Created on %B %e, %Y at %k:%M %p' => '', + 'There is nobody assigned' => 'Kimse atanmamış', + 'Column on the board:' => 'Tablodaki sütun:', + 'Status is open' => 'Açık durumda', + 'Status is closed' => 'Kapalı durumda', + 'Close this task' => 'Görevi kapat', + 'Open this task' => 'Görevi aç', + 'There is no description.' => 'Açıklama yok.', + 'Add a new task' => 'Yeni görev ekle', + 'The username is required' => 'Kullanıcı adı gerekli', + 'The maximum length is %d characters' => 'Maksimum uzunluk %d karakterdir', + 'The minimum length is %d characters' => 'Minimum uzunluk %d karakterdir', + 'The password is required' => 'Şifre gerekli', + 'This value must be an integer' => 'Bu değer bir rakam olmak zorunda', + 'The username must be unique' => 'Kullanıcı adı daha önceden var', + 'The username must be alphanumeric' => 'Kullanıcı adı alfanumerik olmalı (geçersiz karakter var)', + 'The user id is required' => 'Kullanıcı kodu gerekli', + 'Passwords don\'t match' => 'Şifreler uyuşmuyor', + 'The confirmation is required' => 'Onay gerekli', + 'The column is required' => 'Sütun gerekli', + 'The project is required' => 'Proje gerekli', + 'The color is required' => 'Renk gerekli', + 'The id is required' => 'Kod gerekli', + 'The project id is required' => 'Proje kodu gerekli', + 'The project name is required' => 'Proje adı gerekli', + 'This project must be unique' => 'Bu projenin tekil olması gerekli', + 'The title is required' => 'Başlık gerekli', + 'The language is required' => 'Dil seçimi gerekli', + 'There is no active project, the first step is to create a new project.' => 'Aktif bir proje yok. İlk aşama yeni bir proje oluşturmak olmalı.', + 'Settings saved successfully.' => 'Ayarlar başarıyla kaydedildi.', + 'Unable to save your settings.' => 'Ayarlarınız kaydedilemedi.', + 'Database optimization done.' => 'Veritabanı optimizasyonu tamamlandı.', + 'Your project have been created successfully.' => 'Projeniz başarıyla oluşturuldu.', + 'Unable to create your project.' => 'Proje oluşturulamadı.', + 'Project updated successfully.' => 'Proje başarıyla güncellendi.', + 'Unable to update this project.' => 'Bu proje güncellenemedi.', + 'Unable to remove this project.' => 'Bu proje silinemedi.', + 'Project removed successfully.' => 'Proje başarıyla silindi.', + 'Project activated successfully.' => 'Proje başarıyla aktive edildi.', + 'Unable to activate this project.' => 'Bu proje aktive edilemedi.', + 'Project disabled successfully.' => 'Proje devre dışı bırakıldı.', + 'Unable to disable this project.' => 'Bu proje devre dışı bırakılamadı.', + 'Unable to open this task.' => 'Bu görev açılamıyor.', + 'Task opened successfully.' => 'Görev başarıyla açıldı.', + 'Unable to close this task.' => 'Bu görev kapatılamıyor.', + 'Task closed successfully.' => 'Görev başarıyla kapatıldı.', + 'Unable to update your task.' => 'Görev güncellenemiyor.', + 'Task updated successfully.' => 'Görev başarıyla güncellendi.', + 'Unable to create your task.' => 'Görev oluşturulamadı.', + 'Task created successfully.' => 'Görev başarıyla oluşturuldu.', + 'User created successfully.' => 'Kullanıcı başarıyla oluşturuldu', + 'Unable to create your user.' => 'Kullanıcı oluşturulamıyor.', + 'User updated successfully.' => 'Kullanıcı başarıyla güncellendi.', + 'Unable to update your user.' => 'Kullanıcı güncellenemiyor.', + 'User removed successfully.' => 'Kullanıcı silindi.', + 'Unable to remove this user.' => 'Bu kullanıcı silinemiyor.', + 'Board updated successfully.' => 'Tablo başarıyla güncellendi.', + 'Ready' => 'Hazır', + 'Backlog' => 'Bekleme listesi', + 'Work in progress' => 'İşlemde', + 'Done' => 'Tamamlandı', + 'Application version:' => 'Uygulama versiyonu:', + // 'Completed on %B %e, %Y at %k:%M %p' => '', + // '%B %e, %Y at %k:%M %p' => '', + 'Date created' => 'Oluşturulma tarihi', + 'Date completed' => 'Tamamlanma tarihi', + 'Id' => 'Kod', + 'No task' => 'Görev yok', + 'Completed tasks' => 'Tamamlanan görevler', + 'List of projects' => 'Proje listesi', + 'Completed tasks for "%s"' => '"%s" için tamamlanan görevler', + '%d closed tasks' => '%d kapatılmış görevler', + // 'No task for this project' => '', + 'Public link' => 'Dışa açık link', + 'There is no column in your project!' => 'Projenizde hiç sütun yok', + 'Change assignee' => 'Atanmış Kullanıcıyı değiştir', + 'Change assignee for the task "%s"' => '"%s" görevi için atanmış kullanıcıyı değiştir', + 'Timezone' => 'Saat dilimi', + // 'Sorry, I didn\'t find this information in my database!' => '', + 'Page not found' => 'Sayfa bulunamadı', + 'Complexity' => 'Zorluk seviyesi', + 'limit' => 'limit', + 'Task limit' => 'Görev limiti', + 'Task count' => 'Görev sayısı', + 'This value must be greater than %d' => 'Bu değer %d den büyük olmalı', + 'Edit project access list' => 'Proje erişim listesini düzenle', + 'Edit users access' => 'Kullanıcı erişim haklarını düzenle', + 'Allow this user' => 'Bu kullanıcıya izin ver', + 'Only those users have access to this project:' => 'Bu projeye yalnızca şu kullanıcılar erişebilir:', + 'Don\'t forget that administrators have access to everything.' => 'Dikkat: Yöneticilerin herşeye erişimi olduğunu unutmayın!', + 'Revoke' => 'Iptal et', + 'List of authorized users' => 'Yetkili kullanıcıların listesi', + 'User' => 'Kullanıcı', + 'Nobody have access to this project.' => 'Bu projeye kimsenin erişimi yok.', + 'You are not allowed to access to this project.' => 'Bu projeye giriş yetkiniz yok.', + 'Comments' => 'Yorumlar', + 'Post comment' => 'Yorum ekle', + 'Write your text in Markdown' => 'Yazınızı Markdown ile yazın', + 'Leave a comment' => 'Bir yorum ekle', + 'Comment is required' => 'Yorum gerekli', + 'Leave a description' => 'Açıklama ekleyin', + 'Comment added successfully.' => 'Yorum eklendi', + 'Unable to create your comment.' => 'Yorumunuz oluşturulamadı', + 'The description is required' => 'Açıklama gerekli', + 'Edit this task' => 'Bu görevi değiştir', + 'Due Date' => 'Termin', + 'Invalid date' => 'Geçersiz tarihi', + // 'Must be done before %B %e, %Y' => '', + '%B %e, %Y' => '%d %B %Y', + '%b %e, %Y' => '%d/%m/%Y', + 'Automatic actions' => 'Otomatik işlemler', + 'Your automatic action have been created successfully.' => 'Otomatik işlem başarıyla oluşturuldu', + 'Unable to create your automatic action.' => 'Otomatik işleminiz oluşturulamadı', + 'Remove an action' => 'Bir işlemi sil', + 'Unable to remove this action.' => 'Bu işlem silinemedi', + 'Action removed successfully.' => 'İşlem başarıyla silindi', + 'Automatic actions for the project "%s"' => '"%s" projesi için otomatik işlemler', + 'Defined actions' => 'Tanımlanan işlemler', + 'Add an action' => 'İşlem ekle', + 'Event name' => 'Durum adı', + 'Action name' => 'İşlem adı', + 'Action parameters' => 'İşlem parametreleri', + 'Action' => 'İşlem', + 'Event' => 'Durum', + 'When the selected event occurs execute the corresponding action.' => 'Seçilen durum oluştuğunda ilgili eylemi gerçekleştir.', + 'Next step' => 'Sonraki adım', + 'Define action parameters' => 'İşlem parametrelerini düzenle', + 'Save this action' => 'Bu işlemi kaydet', + 'Do you really want to remove this action: "%s"?' => 'Bu işlemi silmek istediğinize emin misiniz: "%s"?', + 'Remove an automatic action' => 'Bir otomatik işlemi sil', + 'Close the task' => 'Görevi kapat', + 'Assign the task to a specific user' => 'Görevi bir kullanıcıya ata', + 'Assign the task to the person who does the action' => 'Görevi, işlemi gerçekleştiren kullanıcıya ata', + 'Duplicate the task to another project' => 'Görevi bir başka projeye kopyala', + 'Move a task to another column' => 'Bir görevi başka bir sütuna taşı', + 'Move a task to another position in the same column' => 'Bir görevin aynı sütunda yerini değiştir', + 'Task modification' => 'Görev düzenleme', + 'Task creation' => 'Görev oluşturma', + 'Open a closed task' => 'Kapalı bir görevi aç', + 'Closing a task' => 'Bir görev kapatılıyor', + 'Assign a color to a specific user' => 'Bir kullanıcıya renk tanımla', + 'Column title' => 'Sütun başlığı', + 'Position' => 'Pozisyon', + 'Move Up' => 'Yukarı taşı', + 'Move Down' => 'Aşağı taşı', + 'Duplicate to another project' => 'Başka bir projeye kopyala', + 'Duplicate' => 'Kopya oluştur', + 'link' => 'link', + 'Update this comment' => 'Bu yorumu güncelle', + 'Comment updated successfully.' => 'Yorum güncellendi.', + 'Unable to update your comment.' => 'Yorum güncellenemedi.', + 'Remove a comment' => 'Bir yorumu sil', + 'Comment removed successfully.' => 'Yorum silindi.', + 'Unable to remove this comment.' => 'Bu yorum silinemiyor.', + 'Do you really want to remove this comment?' => 'Bu yorumu silmek istediğinize emin misiniz?', + 'Only administrators or the creator of the comment can access to this page.' => 'Bu sayfaya yalnızca yorum sahibi ve yöneticiler erişebilir.', + 'Details' => 'Detaylar', + 'Current password for the user "%s"' => 'Kullanıcı için mevcut şifre "%s"', + 'The current password is required' => 'Mevcut şifre gerekli', + 'Wrong password' => 'Yanlış Şifre', + 'Reset all tokens' => 'Tüm fişleri sıfırla', + 'All tokens have been regenerated.' => 'Tüm fişler yeniden oluşturuldu.', + 'Unknown' => 'Bilinmeyen', + 'Last logins' => 'Son kullanıcı girişleri', + 'Login date' => 'Giriş tarihi', + 'Authentication method' => 'Doğrulama yöntemi', + 'IP address' => 'IP adresi', + 'User agent' => 'Kullanıcı sistemi', + 'Persistent connections' => 'Kalıcı bağlantılar', + // 'No session.' => '', + 'Expiration date' => 'Geçerlilik sonu', + 'Remember Me' => 'Beni hatırla', + 'Creation date' => 'Oluşturulma tarihi', + 'Filter by user' => 'Kullanıcıya göre filtrele', + 'Filter by due date' => 'Termine göre filtrele', + 'Everybody' => 'Herkes', + 'Open' => 'Açık', + 'Closed' => 'Kapalı', + 'Search' => 'Ara', + 'Nothing found.' => 'Hiçbir şey bulunamadı', + 'Search in the project "%s"' => '"%s" Projesinde ara', + 'Due date' => 'Termin', + 'Others formats accepted: %s and %s' => 'Diğer kabul edilen formatlar: %s ve %s', + 'Description' => 'Açıklama', + '%d comments' => '%d yorumlar', + '%d comment' => '%d yorum', + 'Email address invalid' => 'E-Posta adresi geçersiz', + 'Your Google Account is not linked anymore to your profile.' => 'Google hesabınız artık profilinize bağlı değil', + 'Unable to unlink your Google Account.' => 'Google hesabınızla bağ koparılamadı', + 'Google authentication failed' => 'Google hesap doğrulaması başarısız', + 'Unable to link your Google Account.' => 'Google hesabınızla bağ oluşturulamadı', + 'Your Google Account is linked to your profile successfully.' => 'Google hesabınız profilinize başarıyla bağlandı', + 'Email' => 'E-Posta', + 'Link my Google Account' => 'Google hesabımla bağ oluştur', + 'Unlink my Google Account' => 'Google hesabımla bağı kaldır', + 'Login with my Google Account' => 'Google hesabımla giriş yap', + 'Project not found.' => 'Proje bulunamadı', + 'Task #%d' => 'Görev #%d', + 'Task removed successfully.' => 'Görev silindi', + 'Unable to remove this task.' => 'Görev silinemiyor', + 'Remove a task' => 'Bir görevi sil', + 'Do you really want to remove this task: "%s"?' => 'Bu görevi silmek istediğinize emin misiniz: "%s"?', + 'Assign automatically a color based on a category' => 'Kategoriye göre otomatik renk ata', + 'Assign automatically a category based on a color' => 'Rengine göre otomatik kategori ata', + 'Task creation or modification' => 'Görev oluşturma veya değiştirme', + 'Category' => 'Kategori', + 'Category:' => 'Kategori:', + 'Categories' => 'Kategoriler', + 'Category not found.' => 'Kategori bulunamadı', + 'Your category have been created successfully.' => 'Kategori oluşturuldu', + 'Unable to create your category.' => 'Kategori oluşturulamadı', + 'Your category have been updated successfully.' => 'Kategori başarıyla güncellendi', + 'Unable to update your category.' => 'Kategori güncellenemedi', + 'Remove a category' => 'Bir kategoriyi sil', + 'Category removed successfully.' => 'Kategori silindi', + 'Unable to remove this category.' => 'Bu kategori silinemedi', + 'Category modification for the project "%s"' => '"%s" projesi için kategori değiştirme', + 'Category Name' => 'Kategori adı', + 'Categories for the project "%s"' => '"%s" Projesi için kategoriler', + 'Add a new category' => 'Yeni kategori ekle', + 'Do you really want to remove this category: "%s"?' => 'Bu kategoriyi silmek istediğinize emin misiniz: "%s"?', + 'Filter by category' => 'Kategoriye göre filtrele', + 'All categories' => 'Tüm kategoriler', + 'No category' => 'Kategori Yok', + 'The name is required' => 'İsim gerekli', + 'Remove a file' => 'Dosya sil', + 'Unable to remove this file.' => 'Dosya silinemedi', + 'File removed successfully.' => 'Dosya silindi', + 'Attach a document' => 'Dosya ekle', + 'Do you really want to remove this file: "%s"?' => 'Bu dosyayı silmek istediğinize emin misiniz: "%s"?', + 'open' => 'aç', + 'Attachments' => 'Ekler', + 'Edit the task' => 'Görevi değiştir', + 'Edit the description' => 'Açıklamayı değiştir', + 'Add a comment' => 'Yorum ekle', + 'Edit a comment' => 'Yorum değiştir', + 'Summary' => 'Özet', + 'Time tracking' => 'Zaman takibi', + 'Estimate:' => 'Tahmini:', + 'Spent:' => 'Harcanan:', + 'Do you really want to remove this sub-task?' => 'Bu alt görevi silmek istediğinize emin misiniz', + 'Remaining:' => 'Kalan', + 'hours' => 'saat', + 'spent' => 'harcanan', + 'estimated' => 'tahmini', + 'Sub-Tasks' => 'Alt Görev', + 'Add a sub-task' => 'Alt görev ekle', + // 'Original estimate' => '', + 'Create another sub-task' => 'Başka bir alt görev daha oluştur', + // 'Time spent' => '', + 'Edit a sub-task' => 'Alt görev düzenle', + 'Remove a sub-task' => 'Alt görev sil', + 'The time must be a numeric value' => 'Zaman alfanumerik bir değer olmalı', + 'Todo' => 'Yapılacaklar', + 'In progress' => 'İşlemde', + 'Sub-task removed successfully.' => 'Alt görev silindi', + 'Unable to remove this sub-task.' => 'Alt görev silinemedi', + 'Sub-task updated successfully.' => 'Alt görev güncellendi', + 'Unable to update your sub-task.' => 'Alt görev güncellenemiyor', + 'Unable to create your sub-task.' => 'Alt görev oluşturulamadı', + 'Sub-task added successfully.' => 'Alt görev başarıyla eklendii', + 'Maximum size: ' => 'Maksimum boyutu', + 'Unable to upload the file.' => 'Karşıya yükleme başarısız', + 'Display another project' => 'Başka bir proje göster', + 'Your GitHub account was successfully linked to your profile.' => 'GitHub Hesabınız Profilinize bağlandı.', + 'Unable to link your GitHub Account.' => 'GitHub hesabınızla bağ oluşturulamadı.', + // 'GitHub authentication failed' => '', + // 'Your GitHub account is no longer linked to your profile.' => '', + // 'Unable to unlink your GitHub Account.' => '', + // 'Login with my GitHub Account' => '', + // 'Link my GitHub Account' => '', + // 'Unlink my GitHub Account' => '', + 'Created by %s' => '%s tarafından oluşturuldu', + 'Last modified on %B %e, %Y at %k:%M %p' => 'Son değişiklik tarihi %d.%m.%Y, saati %H:%M', + 'Tasks Export' => 'Görevleri dışa aktar', + 'Tasks exportation for "%s"' => '"%s" için görevleri dışa aktar', + 'Start Date' => 'Başlangıç tarihi', + 'End Date' => 'Bitiş tarihi', + 'Execute' => 'Gerçekleştir', + 'Task Id' => 'Görev No', + 'Creator' => 'Oluşturan', + 'Modification date' => 'Değişiklik tarihi', + 'Completion date' => 'Tamamlanma tarihi', + // 'Webhook URL for task creation' => '', + // 'Webhook URL for task modification' => '', + 'Clone' => 'Kopya oluştur', + 'Clone Project' => 'Projenin kopyasını oluştur', + 'Project cloned successfully.' => 'Proje kopyası başarıyla oluşturuldu.', + 'Unable to clone this project.' => 'Proje kopyası oluşturulamadı.', + 'Email notifications' => 'E-Posta bilgilendirmesi', + 'Enable email notifications' => 'E-Posta bilgilendirmesini aç', + 'Task position:' => 'Görev pozisyonu', + 'The task #%d have been opened.' => '#%d numaralı görev açıldı.', + 'The task #%d have been closed.' => '#%d numaralı görev kapatıldı.', + 'Sub-task updated' => 'Alt görev güncellendi', + 'Title:' => 'Başlık', + 'Status:' => 'Durum', + 'Assignee:' => 'Sorumlu:', + 'Time tracking:' => 'Zaman takibi', + 'New sub-task' => 'Yeni alt görev', + 'New attachment added "%s"' => 'Yeni dosya "%s" eklendi.', + 'Comment updated' => 'Yorum güncellendi', + 'New comment posted by %s' => '%s tarafından yeni yorum eklendi', + 'List of due tasks for the project "%s"' => '"%s" projesi için ilgili görevlerin listesi', + 'New attachment' => 'Yeni dosya eki', + 'New comment' => 'Yeni yorum', + 'New subtask' => 'Yeni alt görev', + 'Subtask updated' => 'Alt görev güncellendi', + 'Task updated' => 'Görev güncellendi', + 'Task closed' => 'Görev kapatıldı', + 'Task opened' => 'Görev açıldı', + '[%s][Due tasks]' => '[%s][İlgili görevler]', + '[Kanboard] Notification' => '[Kanboard] Bildirim', + 'I want to receive notifications only for those projects:' => 'Yalnızca bu projelerle ilgili bildirim almak istiyorum:', + 'view the task on Kanboard' => 'bu görevi Kanboard\'da göster', + 'Public access' => 'Dışa açık erişim', + 'Category management' => 'Kategori yönetimi', + 'User management' => 'Kullanıcı yönetimi', + 'Active tasks' => 'Aktif görevler', + 'Disable public access' => 'Dışa açık erişimi kapat', + 'Enable public access' => 'Dışa açık erişimi aç', + 'Active projects' => 'Aktif projeler', + 'Inactive projects' => 'Aktif olmayan projeler', + 'Public access disabled' => 'Dışa açık erişim kapatıldı', + 'Do you really want to disable this project: "%s"?' => 'Bu projeyi devre dışı bırakmak istediğinize emin misiniz?: "%s"', + 'Do you really want to duplicate this project: "%s"?' => 'Bu projenin kopyasını oluşturmak istediğinize emin misiniz?: "%s"', + 'Do you really want to enable this project: "%s"?' => 'Bu projeyi aktive etmek istediğinize emin misiniz?: "%s"', + 'Project activation' => 'Proje aktivasyonu', + 'Move the task to another project' => 'Görevi başka projeye taşı', + 'Move to another project' => 'Başka projeye taşı', + 'Do you really want to duplicate this task?' => 'Bu görevin kopyasını oluşturmak istediğinize emin misiniz?', + 'Duplicate a task' => 'Görevin kopyasını oluştur', + 'External accounts' => 'Dış hesaplar', + 'Account type' => 'Hesap türü', + 'Local' => 'Yerel', + 'Remote' => 'Uzak', + 'Enabled' => 'Etkinleştirildi', + 'Disabled' => 'Devre dışı bırakıldı', + 'Google account linked' => 'Google hesabıyla bağlı', + 'Github account linked' => 'Github hesabıyla bağlı', + 'Username:' => 'Kullanıcı adı', + 'Name:' => 'Ad', + 'Email:' => 'E-Posta', + 'Default project:' => 'Varsayılan Proje:', + 'Notifications:' => 'Bildirimler:', + 'Notifications' => 'Bildirimler', + 'Group:' => 'Grup', + 'Regular user' => 'Varsayılan kullanıcı', + 'Account type:' => 'Hesap türü:', + 'Edit profile' => 'Profili değiştir', + 'Change password' => 'Şifre değiştir', + 'Password modification' => 'Şifre değişimi', + 'External authentications' => 'Dış kimlik doğrulamaları', + 'Google Account' => 'Google hesabı', + 'Github Account' => 'Github hesabı', + 'Never connected.' => 'Hiç bağlanmamış.', + 'No account linked.' => 'Bağlanmış hesap yok.', + 'Account linked.' => 'Hesap bağlandı', + 'No external authentication enabled.' => 'Dış kimlik doğrulama kapalı.', + 'Password modified successfully.' => 'Şifre başarıyla değiştirildi.', + 'Unable to change the password.' => 'Şifre değiştirilemedi.', + 'Change category for the task "%s"' => '"%s" görevi için kategori değiştirme', + 'Change category' => 'Kategori değiştirme', + '%s updated the task %s' => '%s kullanıcısı %s görevini güncelledi', + '%s opened the task %s' => '%s kullanıcısı %s görevini açtı', + '%s moved the task %s to the position #%d in the column "%s"' => '%s kullanıcısı %s görevini #%d pozisyonu "%s" sütununa taşıdı', + '%s moved the task %s to the column "%s"' => '%s kullanıcısı %s görevini "%s" sütununa taşıdı', + '%s created the task %s' => '%s kullanıcısı %s görevini oluşturdu', + '%s closed the task %s' => '%s kullanıcısı %s görevini kapattı', + '%s created a subtask for the task %s' => '%s kullanıcısı %s görevi için bir alt görev oluşturdu', + '%s updated a subtask for the task %s' => '%s kullanıcısı %s görevinin bir alt görevini güncelledi', + 'Assigned to %s with an estimate of %s/%sh' => '%s kullanıcısına tahmini %s/%s saat tamamlanma süresi ile atanmış', + 'Not assigned, estimate of %sh' => 'Kimseye atanmamış, tahmini süre %s saat', + '%s updated a comment on the task %s' => '%s kullanıcısı %s görevinde bir yorumu güncelledi', + '%s commented the task %s' => '%s kullanıcısı %s görevine yorum ekledi', + '%s\'s activity' => '%s\'in aktivitesi', + 'No activity.' => 'Aktivite yok.', + 'RSS feed' => 'RSS kaynağı', + '%s updated a comment on the task #%d' => '%s kullanıcısı #%d nolu görevde bir yorumu güncelledi', + '%s commented on the task #%d' => '%s kullanıcısı #%d nolu göreve yorum ekledi', + '%s updated a subtask for the task #%d' => '%s kullanıcısı #%d nolu görevin bir alt görevini güncelledi', + '%s created a subtask for the task #%d' => '%s kullanıcısı #%d nolu göreve bir alt görev ekledi', + '%s updated the task #%d' => '%s kullanıcısı #%d nolu görevi güncelledi', + '%s created the task #%d' => '%s kullanıcısı #%d nolu görevi oluşturdu', + '%s closed the task #%d' => '%s kullanıcısı #%d nolu görevi kapattı', + '%s open the task #%d' => '%s kullanıcısı #%d nolu görevi açtı', + '%s moved the task #%d to the column "%s"' => '%s kullanıcısı #%d nolu görevi "%s" sütununa taşıdı', + '%s moved the task #%d to the position %d in the column "%s"' => '%s kullanıcısı #%d nolu görevi %d pozisyonu "%s" sütununa taşıdı', + 'Activity' => 'Aktivite', + 'Default values are "%s"' => 'Varsayılan değerler "%s"', + 'Default columns for new projects (Comma-separated)' => 'Yeni projeler için varsayılan sütunlar (virgül ile ayrılmış)', + 'Task assignee change' => 'Göreve atanan kullanıcı değişikliği', + '%s change the assignee of the task #%d to %s' => '%s kullanıcısı #%d nolu görevin sorumlusunu %s olarak değiştirdi', + '%s changed the assignee of the task %s to %s' => '%s kullanıcısı %s görevinin sorumlusunu %s olarak değiştirdi', + 'Column Change' => 'Sütun değişikliği', + 'Position Change' => 'Konum değişikliği', + 'Assignee Change' => 'Sorumlu değişikliği', + 'New password for the user "%s"' => '"%s" kullanıcısı için yeni şifre', + 'Choose an event' => 'Bir durum seçin', + // 'Github commit received' => '', + // 'Github issue opened' => '', + // 'Github issue closed' => '', + // 'Github issue reopened' => '', + // 'Github issue assignee change' => '', + // 'Github issue label change' => '', + 'Create a task from an external provider' => 'Dış sağlayıcı ile bir görev oluştur', + 'Change the assignee based on an external username' => 'Dış kaynaklı kullanıcı adı ile göreve atananı değiştir', + 'Change the category based on an external label' => 'Dış kaynaklı bir etiket ile kategori değiştir', + 'Reference' => 'Referans', + 'Reference: %s' => 'Referans: %s', + 'Label' => 'Etiket', + 'Database' => 'Veri bankası', + 'About' => 'Hakkında', + 'Database driver:' => 'Veri bankası sürücüsü', + 'Board settings' => 'Tablo ayarları', + 'URL and token' => 'URL veya Token', + 'Webhook settings' => 'Webhook ayarları', + 'URL for task creation:' => 'Görev oluşturma için URL', + 'Reset token' => 'Reset Token', + 'API endpoint:' => 'API endpoint', + 'Refresh interval for private board' => 'Özel tablolar için yenileme sıklığı', + 'Refresh interval for public board' => 'Dışa açık tablolar için yenileme sıklığı', + 'Task highlight period' => 'Görevi öne çıkarma süresi', + 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Bir görevin yeni değiştirilmiş sayılması için süre (saniye olarak) (Bu özelliği iptal etmek için 0, varsayılan değer 2 gün)', + 'Frequency in second (60 seconds by default)' => 'Saniye olarak frekans (varsayılan 60 saniye)', + 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Saniye olarak frekans (Bu özelliği iptal etmek için 0, varsayılan değer 10 saniye)', + 'Application URL' => 'Uygulama URL', + 'Example: http://example.kanboard.net/ (used by email notifications)' => 'Örneğin: http://example.kanboard.net/ (E-posta bildirimleri için kullanılıyor)', + 'Token regenerated.' => 'Token yeniden oluşturuldu.', + 'Date format' => 'Tarih formatı', + 'ISO format is always accepted, example: "%s" and "%s"' => 'ISO formatı her zaman kabul edilir, örneğin: "%s" ve "%s"', + 'New private project' => 'Yeni özel proje', + 'This project is private' => 'Bu proje özel', + 'Type here to create a new sub-task' => 'Yeni bir alt görev oluşturmak için buraya yazın', + 'Add' => 'Ekle', + 'Estimated time: %s hours' => 'Tahmini süre: %s Saat', + 'Time spent: %s hours' => 'Kullanılan süre: %s Saat', + 'Started on %B %e, %Y' => '%B %e %Y tarihinde başlatıldı', + 'Start date' => 'Başlangıç tarihi', + 'Time estimated' => 'Tahmini süre', + 'There is nothing assigned to you.' => 'Size atanan hiçbir şey yok.', + 'My tasks' => 'Görevlerim', + 'Activity stream' => 'Güncel olay akışı', + 'Dashboard' => 'Anasayfa', + 'Confirmation' => 'Onay', + 'Allow everybody to access to this project' => 'Bu projeye herkesin erişimine izin ver', + 'Everybody have access to this project.' => 'Bu projeye herkesin erişimi var.', + 'Webhooks' => 'Webhooks', + 'API' => 'API', + 'Integration' => 'Entegrasyon', + 'Github webhooks' => 'Github Webhook', + 'Help on Github webhooks' => 'Github Webhooks hakkında yardım', + 'Create a comment from an external provider' => 'Dış sağlayıcı ile bir yorum oluştur', + 'Github issue comment created' => 'Github hata yorumu oluşturuldu', + 'Configure' => 'Ayarla', + 'Project management' => 'Proje yönetimi', + 'My projects' => 'Projelerim', + 'Columns' => 'Sütunlar', + 'Task' => 'Görev', + 'Your are not member of any project.' => 'Hiç bir projenin üyesi değilsiniz.', + 'Percentage' => 'Yüzde', + 'Number of tasks' => 'Görev sayısı', + 'Task distribution' => 'Görev dağılımı', + 'Reportings' => 'Raporlar', + 'Task repartition for "%s"' => '"%s" için görev dağılımı', + 'Analytics' => 'Analiz', + 'Subtask' => 'Alt görev', + 'My subtasks' => 'Alt görevlerim', + 'User repartition' => 'Kullanıcı dağılımı', + 'User repartition for "%s"' => '"%s" için kullanıcı dağılımı', + 'Clone this project' => 'Projenin kopyasını oluştur', + 'Column removed successfully.' => 'Sütun başarıyla kaldırıldı.', + 'Edit Project' => 'Projeyi düzenle', + 'Github Issue' => 'Github Issue', + 'Not enough data to show the graph.' => 'Grafik gösterimi için yeterli veri yok.', + 'Previous' => 'Önceki', + 'The id must be an integer' => 'ID bir tamsayı olmalı', + 'The project id must be an integer' => 'Proje numarası bir tam sayı olmalı', + 'The status must be an integer' => 'Durum bir tam sayı olmalı', + 'The subtask id is required' => 'Alt görev numarası gerekli', + 'The subtask id must be an integer' => 'Alt görev numarası bir tam sayı olmalı', + 'The task id is required' => 'Görev numarası gerekli', + 'The task id must be an integer' => 'Görev numarası bir tam sayı olmalı', + 'The user id must be an integer' => 'Kullanıcı numarası bir tam sayı olmalı', + 'This value is required' => 'Bu değer gerekli', + 'This value must be numeric' => 'Bu değer sayı olmalı', + 'Unable to create this task.' => 'Bu görev oluşturulamıyor.', + 'Cumulative flow diagram' => 'Kümülatif akış diyagramı', + 'Cumulative flow diagram for "%s"' => '"%s" için kümülatif akış diyagramı', + 'Daily project summary' => 'Günlük proje özeti', + 'Daily project summary export' => 'Günlük proje özetini dışa aktar', + 'Daily project summary export for "%s"' => '"%s" için günlük proje özetinin dışa', + 'Exports' => 'Dışa aktarımlar', + 'This export contains the number of tasks per column grouped per day.' => 'Bu dışa aktarım günlük gruplanmış olarak her sütundaki görev sayısını içerir.', + 'Nothing to preview...' => 'Önizleme yapılacak bir şey yok ...', + 'Preview' => 'Önizleme', + 'Write' => 'Değiştir', + 'Active swimlanes' => 'Aktif Kulvar', + 'Add a new swimlane' => 'Yeni bir Kulvar ekle', + 'Change default swimlane' => 'Varsayılan Kulvarı değiştir', + 'Default swimlane' => 'Varsayılan Kulvar', + 'Do you really want to remove this swimlane: "%s"?' => 'Bu Kulvarı silmek istediğinize emin misiniz?: "%s"?', + 'Inactive swimlanes' => 'Pasif Kulvarlar', + 'Set project manager' => 'Proje yöneticisi olarak ata', + 'Set project member' => 'Proje üyesi olarak ata', + 'Remove a swimlane' => 'Kulvarı sil', + 'Rename' => 'Yeniden adlandır', + 'Show default swimlane' => 'Varsayılan Kulvarı göster', + 'Swimlane modification for the project "%s"' => '"% s" Projesi için Kulvar değişikliği', + 'Swimlane not found.' => 'Kulvar bulunamadı', + 'Swimlane removed successfully.' => 'Kulvar başarıyla kaldırıldı.', + 'Swimlanes' => 'Kulvarlar', + 'Swimlane updated successfully.' => 'Kulvar başarıyla güncellendi.', + 'The default swimlane have been updated successfully.' => 'Varsayılan Kulvarlar başarıyla güncellendi.', + 'Unable to create your swimlane.' => 'Bu Kulvarı oluşturmak mümkün değil.', + 'Unable to remove this swimlane.' => 'Bu Kulvarı silmek mümkün değil.', + 'Unable to update this swimlane.' => 'Bu Kulvarı değiştirmek mümkün değil.', + 'Your swimlane have been created successfully.' => 'Kulvar başarıyla oluşturuldu.', + 'Example: "Bug, Feature Request, Improvement"' => 'Örnek: "Sorun, Özellik talebi, İyileştirme"', + 'Default categories for new projects (Comma-separated)' => 'Yeni projeler için varsayılan kategoriler (Virgül ile ayrılmış)', + // 'Gitlab commit received' => '', + // 'Gitlab issue opened' => '', + // 'Gitlab issue closed' => '', + // 'Gitlab webhooks' => '', + // 'Help on Gitlab webhooks' => '', + 'Integrations' => 'Entegrasyon', + 'Integration with third-party services' => 'Dış servislerle entegrasyon', + 'Role for this project' => 'Bu proje için rol', + 'Project manager' => 'Proje Yöneticisi', + 'Project member' => 'Proje Üyesi', + 'A project manager can change the settings of the project and have more privileges than a standard user.' => 'Bir Proje Yöneticisi proje ayarlarını değiştirebilir ve bir üyeden daha fazla yetkiye sahiptir.', + // 'Gitlab Issue' => '', + 'Subtask Id' => 'Alt görev No:', + 'Subtasks' => 'Alt görevler', + 'Subtasks Export' => 'Alt görevleri dışa aktar', + 'Subtasks exportation for "%s"' => '"%s" için alt görevleri dışa aktarımı', + 'Task Title' => 'Görev Başlığı', + 'Untitled' => 'Başlıksız', + 'Application default' => 'Uygulama varsayılanları', + 'Language:' => 'Dil:', + 'Timezone:' => 'Saat dilimi:', + 'All columns' => 'Tüm sütunlar', + 'Calendar for "%s"' => '"%s" için takvim', + 'Filter by column' => 'Sütuna göre filtrele', + 'Filter by status' => 'Duruma göre filtrele', + 'Calendar' => 'Takvim', + 'Next' => 'Sonraki', + '#%d' => '#%d', + 'Filter by color' => 'Renklere göre filtrele', + 'Filter by swimlane' => 'Kulvara göre filtrele', + 'All swimlanes' => 'Tüm Kulvarlar', + 'All colors' => 'Tüm Renkler', + 'All status' => 'Tüm Durumlar', + 'Add a comment logging moving the task between columns' => 'Sütun değiştiğinde kayıt olarak yorum ekle', + 'Moved to column %s' => '%s Sütununa taşındı', + 'Change description' => 'Açıklamayı değiştir', + 'User dashboard' => 'Kullanıcı Anasayfası', + 'Allow only one subtask in progress at the same time for a user' => 'Bir kullanıcı için aynı anda yalnızca bir alt göreve izin ver', + 'Edit column "%s"' => '"%s" sütununu değiştir', + 'Enable time tracking for subtasks' => 'Alt görevler için zaman takibini etkinleştir', + 'Select the new status of the subtask: "%s"' => '"%s" alt görevi için yeni durum seçin.', + 'Subtask timesheet' => 'Alt görev için zaman takip tablosu', + 'There is nothing to show.' => 'Gösterilecek hiçbir şey yok.', + 'Time Tracking' => 'Zaman takibi', + 'You already have one subtask in progress' => 'Zaten işlemde olan bir alt görev var', + 'Which parts of the project do you want to duplicate?' => 'Projenin hangi kısımlarının kopyasını oluşturmak istiyorsunuz?', + 'Change dashboard view' => 'Anasayfa görünümünü değiştir', + 'Show/hide activities' => 'Aktiviteleri göster/gizle', + 'Show/hide projects' => 'Projeleri göster/gizle', + 'Show/hide subtasks' => 'Alt görevleri göster/gizle', + 'Show/hide tasks' => 'Görevleri göster/gizle', + 'Disable login form' => 'Giriş formunu devre dışı bırak', + 'Show/hide calendar' => 'Takvimi göster/gizle', + 'User calendar' => 'Kullanıcı takvimi', + 'Bitbucket commit received' => 'Bitbucket commit alındı', + 'Bitbucket webhooks' => 'Bitbucket webhooks', + 'Help on Bitbucket webhooks' => 'Bitbucket webhooks için yardım', + 'Start' => 'Başlangıç', + 'End' => 'Son', + 'Task age in days' => 'Görev yaşı gün olarak', + 'Days in this column' => 'Bu sütunda geçirilen gün', + '%dd' => '%dG', + 'Add a link' => 'Link ekle', + 'Add a new link' => 'Yeni link ekle', + 'Do you really want to remove this link: "%s"?' => '"%s" linkini gerçekten silmek istiyor musunuz?', + 'Do you really want to remove this link with task #%d?' => '#%d numaralı görev ile linki gerçekten silmek istiyor musunuz?', + 'Field required' => 'Bu alan gerekli', + 'Link added successfully.' => 'Link başarıyla eklendi.', + 'Link updated successfully.' => 'Link başarıyla güncellendi.', + 'Link removed successfully.' => 'Link başarıyla silindi.', + 'Link labels' => 'Link etiketleri', + 'Link modification' => 'Link değiştirme', + 'Links' => 'Links', + 'Link settings' => 'Link ayarları', + 'Opposite label' => 'Zıt etiket', + 'Remove a link' => 'Bir link silmek', + 'Task\'s links' => 'Görevin linkleri', + 'The labels must be different' => 'Etiketler farklı olmalı', + 'There is no link.' => 'Hiç bir bağ yok', + 'This label must be unique' => 'Bu etiket tek olmalı', + 'Unable to create your link.' => 'Link oluşturulamadı.', + 'Unable to update your link.' => 'Link güncellenemiyor.', + 'Unable to remove this link.' => 'Link kaldırılamıyor', + 'relates to' => 'şununla ilgili', + 'blocks' => 'şunu engelliyor', + 'is blocked by' => 'şunun tarafından engelleniyor', + 'duplicates' => 'şunun kopyasını oluşturuyor', + 'is duplicated by' => 'şunun tarafından kopyası oluşturuluyor', + 'is a child of' => 'şunun astı', + 'is a parent of' => 'şunun üstü', + 'targets milestone' => 'şu kilometre taşını hedefliyor', + 'is a milestone of' => 'şunun için bir kilometre taşı', + 'fixes' => 'düzeltiyor', + 'is fixed by' => 'şunun tarafından düzeltildi', + 'This task' => 'Bu görev', + '<1h' => '<1s', + '%dh' => '%ds', + // '%b %e' => '', + 'Expand tasks' => 'Görevleri genişlet', + 'Collapse tasks' => 'Görevleri daralt', + 'Expand/collapse tasks' => 'Görevleri genişlet/daralt', + 'Close dialog box' => 'İletiyi kapat', + 'Submit a form' => 'Formu gönder', + 'Board view' => 'Tablo görünümü', + 'Keyboard shortcuts' => 'Klavye kısayolları', + 'Open board switcher' => 'Tablo seçim listesini aç', + 'Application' => 'Uygulama', + 'Filter recently updated' => 'Son güncellenenleri göster', + 'since %B %e, %Y at %k:%M %p' => '%B %e, %Y saat %k:%M %p\'den beri', + 'More filters' => 'Daha fazla filtre', + 'Compact view' => 'Ekrana sığdır', + 'Horizontal scrolling' => 'Geniş görünüm', + 'Compact/wide view' => 'Ekrana sığdır / Geniş görünüm', + // 'No results match:' => '', + // 'Remove hourly rate' => '', + // 'Do you really want to remove this hourly rate?' => '', + // 'Hourly rates' => '', + // 'Hourly rate' => '', + // 'Currency' => '', + // 'Effective date' => '', + // 'Add new rate' => '', + // 'Rate removed successfully.' => '', + // 'Unable to remove this rate.' => '', + // 'Unable to save the hourly rate.' => '', + // 'Hourly rate created successfully.' => '', + // 'Start time' => '', + // 'End time' => '', + // 'Comment' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', +); diff --git a/app/Locale/zh_CN/translations.php b/app/Locale/zh_CN/translations.php index cb36250b..f1bbd87c 100644 --- a/app/Locale/zh_CN/translations.php +++ b/app/Locale/zh_CN/translations.php @@ -1,6 +1,8 @@ <?php return array( + // 'number.decimals_separator' => '', + // 'number.thousands_separator' => '', 'None' => '无', 'edit' => '编辑', 'Edit' => '编辑', @@ -737,4 +739,38 @@ return array( // 'Compact view' => '', // 'Horizontal scrolling' => '', // 'Compact/wide view' => '', + // 'No results match:' => '', + // 'Remove hourly rate' => '', + // 'Do you really want to remove this hourly rate?' => '', + // 'Hourly rates' => '', + // 'Hourly rate' => '', + // 'Currency' => '', + // 'Effective date' => '', + // 'Add new rate' => '', + // 'Rate removed successfully.' => '', + // 'Unable to remove this rate.' => '', + // 'Unable to save the hourly rate.' => '', + // 'Hourly rate created successfully.' => '', + // 'Start time' => '', + // 'End time' => '', + // 'Comment' => '', + // 'All day' => '', + // 'Day' => '', + // 'Manage timetable' => '', + // 'Overtime timetable' => '', + // 'Time off timetable' => '', + // 'Timetable' => '', + // 'Work timetable' => '', + // 'Week timetable' => '', + // 'Day timetable' => '', + // 'From' => '', + // 'To' => '', + // 'Time slot created successfully.' => '', + // 'Unable to save this time slot.' => '', + // 'Time slot removed successfully.' => '', + // 'Unable to remove this time slot.' => '', + // 'Do you really want to remove this time slot?' => '', + // 'Remove time slot' => '', + // 'Add new time slot' => '', + // 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => '', ); diff --git a/app/Model/Acl.php b/app/Model/Acl.php index 9fc81747..56938f9d 100644 --- a/app/Model/Acl.php +++ b/app/Model/Acl.php @@ -70,6 +70,7 @@ class Acl extends Base 'config' => '*', 'link' => '*', 'project' => array('remove'), + 'hourlyrate' => '*', ); /** diff --git a/app/Model/Config.php b/app/Model/Config.php index 48640f4e..a2db25e9 100644 --- a/app/Model/Config.php +++ b/app/Model/Config.php @@ -22,6 +22,27 @@ class Config extends Base const TABLE = 'settings'; /** + * Get available currencies + * + * @access public + * @return array + */ + public function getCurrencies() + { + return array( + 'USD' => t('USD - US Dollar'), + 'EUR' => t('EUR - Euro'), + 'GBP' => t('GBP - British Pound'), + 'CHF' => t('CHF - Swiss Francs'), + 'CAD' => t('CAD - Canadian Dollar'), + 'AUD' => t('AUD - Australian Dollar'), + 'NZD' => t('NZD - New Zealand Dollar'), + 'INR' => t('INR - Indian Rupee'), + 'JPY' => t('JPY - Japanese Yen'), + ); + } + + /** * Get available timezones * * @access public @@ -63,6 +84,7 @@ class Config extends Base 'ru_RU' => 'Русский', 'fi_FI' => 'Suomi', 'sv_SE' => 'Svenska', + 'tr_TR' => 'Türkçe', 'zh_CN' => '中文(简体)', 'ja_JP' => '日本語', 'th_TH' => 'ไทย', @@ -99,6 +121,7 @@ class Config extends Base 'zh_CN' => 'zh-cn', 'ja_JP' => 'ja', 'th_TH' => 'th', + 'tr_TR' => 'tr', ); $lang = $this->getCurrentLanguage(); diff --git a/app/Model/HourlyRate.php b/app/Model/HourlyRate.php new file mode 100644 index 00000000..c2ce3eaf --- /dev/null +++ b/app/Model/HourlyRate.php @@ -0,0 +1,103 @@ +<?php + +namespace Model; + +use SimpleValidator\Validator; +use SimpleValidator\Validators; + +/** + * Hourly Rate + * + * @package model + * @author Frederic Guillot + */ +class HourlyRate extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'hourly_rates'; + + /** + * Get all rates for a given user + * + * @access public + * @param integer $user_id User id + * @return array + */ + public function getAllByUser($user_id) + { + return $this->db->table(self::TABLE)->eq('user_id', $user_id)->desc('date_effective')->findAll(); + } + + /** + * Get current rate for a given user + * + * @access public + * @param integer $user_id User id + * @return float + */ + public function getCurrentRate($user_id) + { + return $this->db->table(self::TABLE)->eq('user_id', $user_id)->desc('date_effective')->findOneColumn('rate') ?: 0; + } + + /** + * Add a new rate in the database + * + * @access public + * @param integer $user_id User id + * @param float $rate Hourly rate + * @param string $currency Currency code + * @param string $date ISO8601 date format + * @return boolean|integer + */ + public function create($user_id, $rate, $currency, $date) + { + $values = array( + 'user_id' => $user_id, + 'rate' => $rate, + 'currency' => $currency, + 'date_effective' => $this->dateParser->removeTimeFromTimestamp($this->dateParser->getTimestamp($date)), + ); + + return $this->persist(self::TABLE, $values); + } + + /** + * Remove a specific rate + * + * @access public + * @param integer $rate_id + * @return boolean + */ + public function remove($rate_id) + { + return $this->db->table(self::TABLE)->eq('id', $rate_id)->remove(); + } + + /** + * Validate creation + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateCreation(array $values) + { + $v = new Validator($values, array( + new Validators\Required('user_id', t('Field required')), + new Validators\Required('rate', t('Field required')), + new Validators\Numeric('rate', t('This value must be numeric')), + new Validators\Required('date_effective', t('Field required')), + new Validators\Required('currency', t('Field required')), + )); + + return array( + $v->execute(), + $v->getErrors() + ); + } +} diff --git a/app/Model/Subtask.php b/app/Model/Subtask.php index aa4a6f81..e33373e0 100644 --- a/app/Model/Subtask.php +++ b/app/Model/Subtask.php @@ -122,7 +122,7 @@ class Subtask extends Base ->eq('task_id', $task_id) ->columns(self::TABLE.'.*', User::TABLE.'.username', User::TABLE.'.name') ->join(User::TABLE, 'id', 'user_id') - ->asc(self::TABLE.'.id') + ->asc(self::TABLE.'.position') ->filter(array($this, 'addStatusName')) ->findAll(); } @@ -164,6 +164,22 @@ class Subtask extends Base } /** + * Get the position of the last column for a given project + * + * @access public + * @param integer $task_id Task id + * @return integer + */ + public function getLastPosition($task_id) + { + return (int) $this->db + ->table(self::TABLE) + ->eq('task_id', $task_id) + ->desc('position') + ->findOneColumn('position'); + } + + /** * Create a new subtask * * @access public @@ -173,6 +189,8 @@ class Subtask extends Base public function create(array $values) { $this->prepare($values); + $values['position'] = $this->getLastPosition($values['task_id']) + 1; + $subtask_id = $this->persist(self::TABLE, $values); if ($subtask_id) { @@ -209,6 +227,64 @@ class Subtask extends Base } /** + * Move a subtask down, increment the position value + * + * @access public + * @param integer $task_id + * @param integer $subtask_id + * @return boolean + */ + public function moveDown($task_id, $subtask_id) + { + $subtasks = $this->db->hashtable(self::TABLE)->eq('task_id', $task_id)->asc('position')->getAll('id', 'position'); + $positions = array_flip($subtasks); + + if (isset($subtasks[$subtask_id]) && $subtasks[$subtask_id] < count($subtasks)) { + + $position = ++$subtasks[$subtask_id]; + $subtasks[$positions[$position]]--; + + $this->db->startTransaction(); + $this->db->table(self::TABLE)->eq('id', $subtask_id)->update(array('position' => $position)); + $this->db->table(self::TABLE)->eq('id', $positions[$position])->update(array('position' => $subtasks[$positions[$position]])); + $this->db->closeTransaction(); + + return true; + } + + return false; + } + + /** + * Move a subtask up, decrement the position value + * + * @access public + * @param integer $task_id + * @param integer $subtask_id + * @return boolean + */ + public function moveUp($task_id, $subtask_id) + { + $subtasks = $this->db->hashtable(self::TABLE)->eq('task_id', $task_id)->asc('position')->getAll('id', 'position'); + $positions = array_flip($subtasks); + + if (isset($subtasks[$subtask_id]) && $subtasks[$subtask_id] > 1) { + + $position = --$subtasks[$subtask_id]; + $subtasks[$positions[$position]]++; + + $this->db->startTransaction(); + $this->db->table(self::TABLE)->eq('id', $subtask_id)->update(array('position' => $position)); + $this->db->table(self::TABLE)->eq('id', $positions[$position])->update(array('position' => $subtasks[$positions[$position]])); + $this->db->closeTransaction(); + + return true; + } + + return false; + } + + /** * Change the status of subtask * * Todo -> In progress -> Done -> Todo -> etc... @@ -286,9 +362,9 @@ class Subtask extends Base return $this->db->transaction(function ($db) use ($src_task_id, $dst_task_id) { $subtasks = $db->table(Subtask::TABLE) - ->columns('title', 'time_estimated') + ->columns('title', 'time_estimated', 'position') ->eq('task_id', $src_task_id) - ->asc('id') // Explicit sorting for postgresql + ->asc('position') ->findAll(); foreach ($subtasks as &$subtask) { @@ -380,7 +456,7 @@ class Subtask extends Base return array( new Validators\Integer('id', t('The subtask id must be an integer')), new Validators\Integer('task_id', t('The task id must be an integer')), - new Validators\MaxLength('title', t('The maximum length is %d characters', 100), 100), + new Validators\MaxLength('title', t('The maximum length is %d characters', 255), 255), new Validators\Integer('user_id', t('The user id must be an integer')), new Validators\Integer('status', t('The status must be an integer')), new Validators\Numeric('time_estimated', t('The time must be a numeric value')), diff --git a/app/Model/Swimlane.php b/app/Model/Swimlane.php index c9bc43e1..8f417fca 100644 --- a/app/Model/Swimlane.php +++ b/app/Model/Swimlane.php @@ -75,6 +75,22 @@ class Swimlane extends Base } /** + * Get a swimlane by the project and the name + * + * @access public + * @param integer $project_id Project id + * @param string $name Swimlane name + * @return array + */ + public function getByName($project_id, $name) + { + return $this->db->table(self::TABLE) + ->eq('project_id', $project_id) + ->eq('name', $name) + ->findAll(); + } + + /** * Get default swimlane properties * * @access public diff --git a/app/Model/Timetable.php b/app/Model/Timetable.php new file mode 100644 index 00000000..eb37cefd --- /dev/null +++ b/app/Model/Timetable.php @@ -0,0 +1,194 @@ +<?php + +namespace Model; + +use DateTime; +use DateInterval; + +/** + * Timetable + * + * @package model + * @author Frederic Guillot + */ +class Timetable extends Base +{ + /** + * User time slots + * + * @access private + * @var array + */ + private $day; + private $week; + private $overtime; + private $timeoff; + + /** + * Get the timetable for a user for a given date range + * + * @access public + * @param integer $user_id + * @param \DateTime $start + * @param \DateTime $end + * @return array + */ + public function calculate($user_id, DateTime $start, DateTime $end) + { + $timetable = array(); + + $this->day = $this->timetableDay->getByUser($user_id); + $this->week = $this->timetableWeek->getByUser($user_id); + $this->overtime = $this->timetableExtra->getByUserAndDate($user_id, $start->format('Y-m-d'), $end->format('Y-m-d')); + $this->timeoff = $this->timetableOff->getByUserAndDate($user_id, $start->format('Y-m-d'), $end->format('Y-m-d')); + + for ($today = clone($start); $today <= $end; $today->add(new DateInterval('P1D'))) { + $week_day = $today->format('N'); + $timetable = array_merge($timetable, $this->getWeekSlots($today, $week_day)); + $timetable = array_merge($timetable, $this->getOvertimeSlots($today, $week_day)); + } + + return $timetable; + } + + /** + * Return worked time slots for the given day + * + * @access public + * @param \DateTime $today + * @param string $week_day + * @return array + */ + public function getWeekSlots(DateTime $today, $week_day) + { + $slots = array(); + $dayoff = $this->getDayOff($today); + + if (! empty($dayoff) && $dayoff['all_day'] == 1) { + return array(); + } + + foreach ($this->week as $slot) { + if ($week_day == $slot['day']) { + $slots = array_merge($slots, $this->getDayWorkSlots($slot, $dayoff, $today)); + } + } + + return $slots; + } + + /** + * Get the overtime time slots for the given day + * + * @access public + * @param \DateTime $today + * @param string $week_day + * @return array + */ + public function getOvertimeSlots(DateTime $today, $week_day) + { + $slots = array(); + + foreach ($this->overtime as $slot) { + + $day = new DateTime($slot['date']); + + if ($week_day == $day->format('N')) { + + if ($slot['all_day'] == 1) { + $slots = array_merge($slots, $this->getDaySlots($today)); + } + else { + $slots[] = $this->getTimeSlot($slot, $day); + } + } + } + + return $slots; + } + + /** + * Get worked time slots and remove time off + * + * @access public + * @param array $slot + * @param array $dayoff + * @param \DateTime $today + * @return array + */ + public function getDayWorkSlots(array $slot, array $dayoff, DateTime $today) + { + $slots = array(); + + if (! empty($dayoff) && $dayoff['start'] < $slot['end']) { + + if ($dayoff['start'] > $slot['start']) { + $slots[] = $this->getTimeSlot(array('end' => $dayoff['start']) + $slot, $today); + } + + if ($dayoff['end'] < $slot['end']) { + $slots[] = $this->getTimeSlot(array('start' => $dayoff['end']) + $slot, $today); + } + } + else { + $slots[] = $this->getTimeSlot($slot, $today); + } + + return $slots; + } + + /** + * Get regular day work time slots + * + * @access public + * @param \DateTime $today + * @return array + */ + public function getDaySlots(DateTime $today) + { + $slots = array(); + + foreach ($this->day as $day) { + $slots[] = $this->getTimeSlot($day, $today); + } + + return $slots; + } + + /** + * Get the start and end time slot for a given day + * + * @access public + * @param array $slot + * @param \DateTime $today + * @return array + */ + public function getTimeSlot(array $slot, DateTime $today) + { + $date = $today->format('Y-m-d'); + + return array( + new DateTime($date.' '.$slot['start']), + new DateTime($date.' '.$slot['end']), + ); + } + + /** + * Return day off time slot + * + * @access public + * @param \DateTime $today + * @return array + */ + public function getDayOff(DateTime $today) + { + foreach ($this->timeoff as $day) { + + if ($day['date'] === $today->format('Y-m-d')) { + return $day; + } + } + + return array(); + } +} diff --git a/app/Model/TimetableDay.php b/app/Model/TimetableDay.php new file mode 100644 index 00000000..0c7bf20b --- /dev/null +++ b/app/Model/TimetableDay.php @@ -0,0 +1,87 @@ +<?php + +namespace Model; + +use SimpleValidator\Validator; +use SimpleValidator\Validators; + +/** + * Timetable Workweek + * + * @package model + * @author Frederic Guillot + */ +class TimetableDay extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'timetable_day'; + + /** + * Get the timetable for a given user + * + * @access public + * @param integer $user_id User id + * @return array + */ + public function getByUser($user_id) + { + return $this->db->table(self::TABLE)->eq('user_id', $user_id)->asc('start')->findAll(); + } + + /** + * Add a new time slot in the database + * + * @access public + * @param integer $user_id User id + * @param string $start Start hour (24h format) + * @param string $end End hour (24h format) + * @return boolean|integer + */ + public function create($user_id, $start, $end) + { + $values = array( + 'user_id' => $user_id, + 'start' => $start, + 'end' => $end, + ); + + return $this->persist(self::TABLE, $values); + } + + /** + * Remove a specific time slot + * + * @access public + * @param integer $slot_id + * @return boolean + */ + public function remove($slot_id) + { + return $this->db->table(self::TABLE)->eq('id', $slot_id)->remove(); + } + + /** + * Validate creation + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateCreation(array $values) + { + $v = new Validator($values, array( + new Validators\Required('user_id', t('Field required')), + new Validators\Required('start', t('Field required')), + new Validators\Required('end', t('Field required')), + )); + + return array( + $v->execute(), + $v->getErrors() + ); + } +} diff --git a/app/Model/TimetableExtra.php b/app/Model/TimetableExtra.php new file mode 100644 index 00000000..48db662d --- /dev/null +++ b/app/Model/TimetableExtra.php @@ -0,0 +1,22 @@ +<?php + +namespace Model; + +use SimpleValidator\Validator; +use SimpleValidator\Validators; + +/** + * Timetable over-time + * + * @package model + * @author Frederic Guillot + */ +class TimetableExtra extends TimetableOff +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'timetable_extra'; +} diff --git a/app/Model/TimetableOff.php b/app/Model/TimetableOff.php new file mode 100644 index 00000000..aa064f05 --- /dev/null +++ b/app/Model/TimetableOff.php @@ -0,0 +1,125 @@ +<?php + +namespace Model; + +use SimpleValidator\Validator; +use SimpleValidator\Validators; + +/** + * Timetable time off + * + * @package model + * @author Frederic Guillot + */ +class TimetableOff extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'timetable_off'; + + /** + * Get query to fetch everything (pagination) + * + * @access public + * @param integer $user_id User id + * @return \PicoDb\Table + */ + public function getUserQuery($user_id) + { + return $this->db->table(static::TABLE)->eq('user_id', $user_id); + } + + /** + * Get the timetable for a given user + * + * @access public + * @param integer $user_id User id + * @return array + */ + public function getByUser($user_id) + { + return $this->db->table(static::TABLE)->eq('user_id', $user_id)->desc('date')->asc('start')->findAll(); + } + + /** + * Get the timetable for a given user + * + * @access public + * @param integer $user_id User id + * @param string $start_date + * @param string $end_date + * @return array + */ + public function getByUserAndDate($user_id, $start_date, $end_date) + { + return $this->db->table(static::TABLE) + ->eq('user_id', $user_id) + ->gte('date', $start_date) + ->lte('date', $end_date) + ->desc('date') + ->asc('start') + ->findAll(); + } + + /** + * Add a new time slot in the database + * + * @access public + * @param integer $user_id User id + * @param string $date Day (ISO8601 format) + * @param boolean $all_day All day flag + * @param float $start Start hour (24h format) + * @param float $end End hour (24h format) + * @param string $comment + * @return boolean|integer + */ + public function create($user_id, $date, $all_day, $start = '', $end = '', $comment = '') + { + $values = array( + 'user_id' => $user_id, + 'date' => $date, + 'all_day' => $all_day, + 'start' => $all_day ? '' : $start, + 'end' => $all_day ? '' : $end, + 'comment' => $comment, + ); + + return $this->persist(static::TABLE, $values); + } + + /** + * Remove a specific time slot + * + * @access public + * @param integer $slot_id + * @return boolean + */ + public function remove($slot_id) + { + return $this->db->table(static::TABLE)->eq('id', $slot_id)->remove(); + } + + /** + * Validate creation + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateCreation(array $values) + { + $v = new Validator($values, array( + new Validators\Required('user_id', t('Field required')), + new Validators\Required('date', t('Field required')), + new Validators\Numeric('all_day', t('This value must be numeric')), + )); + + return array( + $v->execute(), + $v->getErrors() + ); + } +} diff --git a/app/Model/TimetableWeek.php b/app/Model/TimetableWeek.php new file mode 100644 index 00000000..b22b3b7e --- /dev/null +++ b/app/Model/TimetableWeek.php @@ -0,0 +1,91 @@ +<?php + +namespace Model; + +use SimpleValidator\Validator; +use SimpleValidator\Validators; + +/** + * Timetable Workweek + * + * @package model + * @author Frederic Guillot + */ +class TimetableWeek extends Base +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'timetable_week'; + + /** + * Get the timetable for a given user + * + * @access public + * @param integer $user_id User id + * @return array + */ + public function getByUser($user_id) + { + return $this->db->table(self::TABLE)->eq('user_id', $user_id)->asc('day')->asc('start')->findAll(); + } + + /** + * Add a new time slot in the database + * + * @access public + * @param integer $user_id User id + * @param string $day Day of the week (ISO-8601) + * @param string $start Start hour (24h format) + * @param string $end End hour (24h format) + * @return boolean|integer + */ + public function create($user_id, $day, $start, $end) + { + $values = array( + 'user_id' => $user_id, + 'day' => $day, + 'start' => $start, + 'end' => $end, + ); + + return $this->persist(self::TABLE, $values); + } + + /** + * Remove a specific time slot + * + * @access public + * @param integer $slot_id + * @return boolean + */ + public function remove($slot_id) + { + return $this->db->table(self::TABLE)->eq('id', $slot_id)->remove(); + } + + /** + * Validate creation + * + * @access public + * @param array $values Form values + * @return array $valid, $errors [0] = Success or not, [1] = List of errors + */ + public function validateCreation(array $values) + { + $v = new Validator($values, array( + new Validators\Required('user_id', t('Field required')), + new Validators\Required('day', t('Field required')), + new Validators\Numeric('day', t('This value must be numeric')), + new Validators\Required('start', t('Field required')), + new Validators\Required('end', t('Field required')), + )); + + return array( + $v->execute(), + $v->getErrors() + ); + } +} diff --git a/app/Schema/Mysql.php b/app/Schema/Mysql.php index 1c5cdc3a..44ca7fd4 100644 --- a/app/Schema/Mysql.php +++ b/app/Schema/Mysql.php @@ -6,7 +6,88 @@ use PDO; use Core\Security; use Model\Link; -const VERSION = 48; +const VERSION = 51; + +function version_51($pdo) +{ + $pdo->exec('CREATE TABLE timetable_day ( + id INT NOT NULL AUTO_INCREMENT, + user_id INT NOT NULL, + start VARCHAR(5) NOT NULL, + end VARCHAR(5) NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + PRIMARY KEY(id) + ) ENGINE=InnoDB CHARSET=utf8'); + + $pdo->exec('CREATE TABLE timetable_week ( + id INT NOT NULL AUTO_INCREMENT, + user_id INTEGER NOT NULL, + day INT NOT NULL, + start VARCHAR(5) NOT NULL, + end VARCHAR(5) NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + PRIMARY KEY(id) + ) ENGINE=InnoDB CHARSET=utf8'); + + $pdo->exec('CREATE TABLE timetable_off ( + id INT NOT NULL AUTO_INCREMENT, + user_id INT NOT NULL, + date VARCHAR(10) NOT NULL, + all_day TINYINT(1) DEFAULT 0, + start VARCHAR(5) DEFAULT 0, + end VARCHAR(5) DEFAULT 0, + comment TEXT, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + PRIMARY KEY(id) + ) ENGINE=InnoDB CHARSET=utf8'); + + $pdo->exec('CREATE TABLE timetable_extra ( + id INT NOT NULL AUTO_INCREMENT, + user_id INT NOT NULL, + date VARCHAR(10) NOT NULL, + all_day TINYINT(1) DEFAULT 0, + start VARCHAR(5) DEFAULT 0, + end VARCHAR(5) DEFAULT 0, + comment TEXT, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + PRIMARY KEY(id) + ) ENGINE=InnoDB CHARSET=utf8'); +} + +function version_50($pdo) +{ + $pdo->exec("CREATE TABLE hourly_rates ( + id INT NOT NULL AUTO_INCREMENT, + user_id INT NOT NULL, + rate FLOAT DEFAULT 0, + date_effective INTEGER NOT NULL, + currency TEXT NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE, + PRIMARY KEY(id) + ) ENGINE=InnoDB CHARSET=utf8"); +} + +function version_49($pdo) +{ + $pdo->exec('ALTER TABLE subtasks ADD COLUMN position INTEGER DEFAULT 1'); + + $task_id = 0; + $urq = $pdo->prepare('UPDATE subtasks SET position=? WHERE id=?'); + + $rq = $pdo->prepare('SELECT * FROM subtasks ORDER BY task_id, id ASC'); + $rq->execute(); + + foreach ($rq->fetchAll(PDO::FETCH_ASSOC) as $subtask) { + + if ($task_id != $subtask['task_id']) { + $position = 1; + $task_id = $subtask['task_id']; + } + + $urq->execute(array($position, $subtask['id'])); + $position++; + } +} function version_48($pdo) { diff --git a/app/Schema/Postgres.php b/app/Schema/Postgres.php index aa3f60f7..fe01b1e9 100644 --- a/app/Schema/Postgres.php +++ b/app/Schema/Postgres.php @@ -6,7 +6,83 @@ use PDO; use Core\Security; use Model\Link; -const VERSION = 29; +const VERSION = 32; + +function version_32($pdo) +{ + $pdo->exec('CREATE TABLE timetable_day ( + "id" SERIAL PRIMARY KEY, + "user_id" INTEGER NOT NULL, + "start" VARCHAR(5) NOT NULL, + "end" VARCHAR(5) NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + )'); + + $pdo->exec('CREATE TABLE timetable_week ( + "id" SERIAL PRIMARY KEY, + "user_id" INTEGER NOT NULL, + "day" INTEGER NOT NULL, + "start" VARCHAR(5) NOT NULL, + "end" VARCHAR(5) NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + )'); + + $pdo->exec('CREATE TABLE timetable_off ( + "id" SERIAL PRIMARY KEY, + "user_id" INTEGER NOT NULL, + "date" VARCHAR(10) NOT NULL, + "all_day" BOOLEAN DEFAULT \'0\', + "start" VARCHAR(5) DEFAULT 0, + "end" VARCHAR(5) DEFAULT 0, + "comment" TEXT, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + )'); + + $pdo->exec('CREATE TABLE timetable_extra ( + "id" SERIAL PRIMARY KEY, + "user_id" INTEGER NOT NULL, + "date" VARCHAR(10) NOT NULL, + "all_day" BOOLEAN DEFAULT \'0\', + "start" VARCHAR(5) DEFAULT 0, + "end" VARCHAR(5) DEFAULT 0, + "comment" TEXT, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + )'); +} + +function version_31($pdo) +{ + $pdo->exec("CREATE TABLE hourly_rates ( + id SERIAL PRIMARY KEY, + user_id INTEGER NOT NULL, + rate REAL DEFAULT 0, + date_effective INTEGER NOT NULL, + currency TEXT NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + )"); +} + +function version_30($pdo) +{ + $pdo->exec('ALTER TABLE subtasks ADD COLUMN position INTEGER DEFAULT 1'); + + $task_id = 0; + $urq = $pdo->prepare('UPDATE subtasks SET position=? WHERE id=?'); + + $rq = $pdo->prepare('SELECT * FROM subtasks ORDER BY task_id, id ASC'); + $rq->execute(); + + foreach ($rq->fetchAll(PDO::FETCH_ASSOC) as $subtask) { + + if ($task_id != $subtask['task_id']) { + $position = 1; + $task_id = $subtask['task_id']; + } + + $urq->execute(array($position, $subtask['id'])); + $position++; + } +} function version_29($pdo) { diff --git a/app/Schema/Sqlite.php b/app/Schema/Sqlite.php index 6fc2d07e..eaa5c734 100644 --- a/app/Schema/Sqlite.php +++ b/app/Schema/Sqlite.php @@ -6,7 +6,85 @@ use Core\Security; use PDO; use Model\Link; -const VERSION = 47; +const VERSION = 50; + +function version_50($pdo) +{ + $pdo->exec('CREATE TABLE timetable_day ( + "id" INTEGER PRIMARY KEY, + "user_id" INTEGER NOT NULL, + "start" TEXT NOT NULL, + "end" TEXT NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + )'); + + $pdo->exec('CREATE TABLE timetable_week ( + "id" INTEGER PRIMARY KEY, + "user_id" INTEGER NOT NULL, + "day" INTEGER NOT NULL, + "start" TEXT NOT NULL, + "end" TEXT NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + )'); + + $pdo->exec('CREATE TABLE timetable_off ( + "id" INTEGER PRIMARY KEY, + "user_id" INTEGER NOT NULL, + "date" TEXT NOT NULL, + "all_day" INTEGER DEFAULT 0, + "start" TEXT DEFAULT 0, + "end" TEXT DEFAULT 0, + "comment" TEXT, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + )'); + + $pdo->exec('CREATE TABLE timetable_extra ( + "id" INTEGER PRIMARY KEY, + "user_id" INTEGER NOT NULL, + "date" TEXT NOT NULL, + "all_day" INTEGER DEFAULT 0, + "start" TEXT DEFAULT 0, + "end" TEXT DEFAULT 0, + "comment" TEXT, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + )'); +} + +function version_49($pdo) +{ + $pdo->exec("CREATE TABLE hourly_rates ( + id INTEGER PRIMARY KEY, + user_id INTEGER NOT NULL, + rate REAL DEFAULT 0, + date_effective INTEGER NOT NULL, + currency TEXT NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE + )"); +} + +function version_48($pdo) +{ + $pdo->exec('ALTER TABLE subtasks ADD COLUMN position INTEGER DEFAULT 1'); + + // Migrate all subtasks position + + $task_id = 0; + $urq = $pdo->prepare('UPDATE subtasks SET position=? WHERE id=?'); + + $rq = $pdo->prepare('SELECT * FROM subtasks ORDER BY task_id, id ASC'); + $rq->execute(); + + foreach ($rq->fetchAll(PDO::FETCH_ASSOC) as $subtask) { + + if ($task_id != $subtask['task_id']) { + $position = 1; + $task_id = $subtask['task_id']; + } + + $urq->execute(array($position, $subtask['id'])); + $position++; + } +} function version_47($pdo) { diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php index 213972ed..a94bb745 100644 --- a/app/ServiceProvider/ClassProvider.php +++ b/app/ServiceProvider/ClassProvider.php @@ -23,6 +23,7 @@ class ClassProvider implements ServiceProviderInterface 'Config', 'DateParser', 'File', + 'HourlyRate', 'LastLogin', 'Link', 'Notification', @@ -48,6 +49,11 @@ class ClassProvider implements ServiceProviderInterface 'TaskPosition', 'TaskStatus', 'TaskValidator', + 'Timetable', + 'TimetableDay', + 'TimetableWeek', + 'TimetableOff', + 'TimetableExtra', 'TimeTracking', 'User', 'UserSession', diff --git a/app/ServiceProvider/DatabaseProvider.php b/app/ServiceProvider/DatabaseProvider.php index 4218f5ff..7ee35d5f 100644 --- a/app/ServiceProvider/DatabaseProvider.php +++ b/app/ServiceProvider/DatabaseProvider.php @@ -79,6 +79,7 @@ class DatabaseProvider implements ServiceProviderInterface 'password' => DB_PASSWORD, 'database' => DB_NAME, 'charset' => 'utf8', + 'port' => DB_PORT, )); } @@ -97,6 +98,7 @@ class DatabaseProvider implements ServiceProviderInterface 'username' => DB_USERNAME, 'password' => DB_PASSWORD, 'database' => DB_NAME, + 'port' => DB_PORT, )); } } diff --git a/app/Template/app/projects.php b/app/Template/app/projects.php index 92675729..b2744644 100644 --- a/app/Template/app/projects.php +++ b/app/Template/app/projects.php @@ -22,7 +22,7 @@ <?= $this->a($this->e($project['name']), 'board', 'show', array('project_id' => $project['id'])) ?> <?php if (! empty($project['description'])): ?> - <span class="column-tooltip" title='<?= $this->markdown($project['description']) ?>'> + <span class="column-tooltip" title='<?= $this->e($this->markdown($project['description'])) ?>'> <i class="fa fa-info-circle"></i> </span> <?php endif ?> diff --git a/app/Template/board/edit.php b/app/Template/board/edit.php index cf0c28e6..a6df1000 100644 --- a/app/Template/board/edit.php +++ b/app/Template/board/edit.php @@ -13,7 +13,7 @@ <tr> <td class="column-60"><?= $this->e($column['title']) ?> <?php if (! empty($column['description'])): ?> - <span class="column-tooltip" title='<?= $this->markdown($column['description']) ?>'> + <span class="column-tooltip" title='<?= $this->e($this->markdown($column['description'])) ?>'> <i class="fa fa-info-circle"></i> </span> <?php endif ?> diff --git a/app/Template/board/filters.php b/app/Template/board/filters.php index fc5bfc34..2dd559b9 100644 --- a/app/Template/board/filters.php +++ b/app/Template/board/filters.php @@ -57,13 +57,13 @@ </span> </li> <li> - <?= $this->formSelect('user_id', $users, array(), array(), array('data-placeholder="'.t('Filter by user').'"'), 'apply-filters chosen-select') ?> + <?= $this->formSelect('user_id', $users, array(), array(), array('data-placeholder="'.t('Filter by user').'"', 'data-notfound="'.t('No results match:').'"'), 'apply-filters chosen-select') ?> </li> <li> - <?= $this->formSelect('category_id', $categories, array(), array(), array('data-placeholder="'.t('Filter by category').'"'), 'apply-filters chosen-select') ?> + <?= $this->formSelect('category_id', $categories, array(), array(), array('data-placeholder="'.t('Filter by category').'"', 'data-notfound="'.t('No results match:').'"'), 'apply-filters chosen-select') ?> </li> <li> - <select id="more-filters" multiple data-placeholder="<?= t('More filters') ?>" class="apply-filters chosen-select hide-mobile"> + <select id="more-filters" multiple data-placeholder="<?= t('More filters') ?>" data-notfound="<?= t('No results match:') ?>" class="apply-filters chosen-select hide-mobile"> <option value=""></option> <option value="filter-due-date"><?= t('Filter by due date') ?></option> <option value="filter-recent"><?= t('Filter recently updated') ?></option> diff --git a/app/Template/board/swimlane.php b/app/Template/board/swimlane.php index 4be92e58..744610ab 100644 --- a/app/Template/board/swimlane.php +++ b/app/Template/board/swimlane.php @@ -28,7 +28,7 @@ <?= $this->e($column['title']) ?> <?php if (! empty($column['description'])): ?> - <span class="column-tooltip pull-right" title='<?= $this->markdown($column['description']) ?>'> + <span class="column-tooltip pull-right" title='<?= $this->e($this->markdown($column['description'])) ?>'> <i class="fa fa-info-circle"></i> </span> <?php endif ?> diff --git a/app/Template/hourlyrate/index.php b/app/Template/hourlyrate/index.php new file mode 100644 index 00000000..9d0b77c8 --- /dev/null +++ b/app/Template/hourlyrate/index.php @@ -0,0 +1,46 @@ +<div class="page-header"> + <h2><?= t('Hourly rates') ?></h2> +</div> + +<?php if (! empty($rates)): ?> + +<table> + <tr> + <th><?= t('Hourly rate') ?></th> + <th><?= t('Currency') ?></th> + <th><?= t('Effective date') ?></th> + <th><?= t('Action') ?></th> + </tr> + <?php foreach ($rates as $rate): ?> + <tr> + <td><?= n($rate['rate']) ?></td> + <td><?= $rate['currency'] ?></td> + <td><?= dt('%b %e, %Y', $rate['date_effective']) ?></td> + <td> + <?= $this->a(t('Remove'), 'hourlyrate', 'confirm', array('user_id' => $user['id'], 'rate_id' => $rate['id'])) ?> + </td> + </tr> + <?php endforeach ?> +</table> + +<h3><?= t('Add new rate') ?></h3> +<?php endif ?> + +<form method="post" action="<?= $this->u('hourlyrate', 'save', array('user_id' => $user['id'])) ?>" autocomplete="off"> + + <?= $this->formHidden('user_id', $values) ?> + <?= $this->formCsrf() ?> + + <?= $this->formLabel(t('Hourly rate'), 'rate') ?> + <?= $this->formText('rate', $values, $errors, array('required'), 'form-numeric') ?> + + <?= $this->formLabel(t('Currency'), 'currency') ?> + <?= $this->formSelect('currency', $currencies_list, $values, $errors, array('required')) ?> + + <?= $this->formLabel(t('Effective date'), 'date_effective') ?> + <?= $this->formText('date_effective', $values, $errors, array('required'), 'form-date') ?> + + <div class="form-actions"> + <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> + </div> +</form> diff --git a/app/Template/hourlyrate/remove.php b/app/Template/hourlyrate/remove.php new file mode 100644 index 00000000..7f22728e --- /dev/null +++ b/app/Template/hourlyrate/remove.php @@ -0,0 +1,13 @@ +<div class="page-header"> + <h2><?= t('Remove hourly rate') ?></h2> +</div> + +<div class="confirm"> + <p class="alert alert-info"><?= t('Do you really want to remove this hourly rate?') ?></p> + + <div class="form-actions"> + <?= $this->a(t('Yes'), 'hourlyrate', 'remove', array('user_id' => $user['id'], 'rate_id' => $rate_id), true, 'btn btn-red') ?> + <?= t('or') ?> + <?= $this->a(t('cancel'), 'hourlyrate', 'index', array('user_id' => $user['id'])) ?> + </div> +</div>
\ No newline at end of file diff --git a/app/Template/layout.php b/app/Template/layout.php index 6c88d33b..9290a21e 100644 --- a/app/Template/layout.php +++ b/app/Template/layout.php @@ -37,7 +37,7 @@ <nav> <h1><?= $this->a('K<span>B</span>', 'app', 'index', array(), false, 'logo', t('Dashboard')).' '.$this->summary($this->e($title)) ?> <?php if (! empty($description)): ?> - <span class="column-tooltip" title='<?= $this->markdown($description) ?>'> + <span class="column-tooltip" title='<?= $this->e($this->markdown($description)) ?>'> <i class="fa fa-info-circle"></i> </span> <?php endif ?> @@ -45,7 +45,7 @@ <ul> <?php if (isset($board_selector) && ! empty($board_selector)): ?> <li> - <select id="board-selector" data-placeholder="<?= t('Display another project') ?>" data-board-url="<?= $this->u('board', 'show', array('project_id' => 'PROJECT_ID')) ?>"> + <select id="board-selector" data-notfound="<?= t('No results match:') ?>" data-placeholder="<?= t('Display another project') ?>" data-board-url="<?= $this->u('board', 'show', array('project_id' => 'PROJECT_ID')) ?>"> <option value=""></option> <?php foreach($board_selector as $board_id => $board_name): ?> <option value="<?= $board_id ?>"><?= $this->e($board_name) ?></option> diff --git a/app/Template/project/index.php b/app/Template/project/index.php index 9a0f641c..05a7d955 100644 --- a/app/Template/project/index.php +++ b/app/Template/project/index.php @@ -42,7 +42,7 @@ <?= $this->a($this->e($project['name']), 'project', 'show', array('project_id' => $project['id'])) ?> <?php if (! empty($project['description'])): ?> - <span class="column-tooltip" title='<?= $this->markdown($project['description']) ?>'> + <span class="column-tooltip" title='<?= $this->e($this->markdown($project['description'])) ?>'> <i class="fa fa-info-circle"></i> </span> <?php endif ?> diff --git a/app/Template/project/show.php b/app/Template/project/show.php index 4ad710a8..9c6cd1a8 100644 --- a/app/Template/project/show.php +++ b/app/Template/project/show.php @@ -50,7 +50,7 @@ <td> <?= $this->e($column['title']) ?> <?php if (! empty($column['description'])): ?> - <span class="column-tooltip" title='<?= $this->markdown($column['description']) ?>'> + <span class="column-tooltip" title='<?= $this->e($this->markdown($column['description'])) ?>'> <i class="fa fa-info-circle"></i> </span> <?php endif ?> diff --git a/app/Template/subtask/create.php b/app/Template/subtask/create.php index be7fc311..8c5aae11 100644 --- a/app/Template/subtask/create.php +++ b/app/Template/subtask/create.php @@ -9,7 +9,7 @@ <?= $this->formHidden('task_id', $values) ?> <?= $this->formLabel(t('Title'), 'title') ?> - <?= $this->formText('title', $values, $errors, array('required', 'autofocus', 'maxlength="50"')) ?><br/> + <?= $this->formText('title', $values, $errors, array('required', 'autofocus', 'maxlength="255"')) ?><br/> <?= $this->formLabel(t('Assignee'), 'user_id') ?> <?= $this->formSelect('user_id', $users_list, $values, $errors) ?><br/> diff --git a/app/Template/subtask/edit.php b/app/Template/subtask/edit.php index f34d9532..3058ff44 100644 --- a/app/Template/subtask/edit.php +++ b/app/Template/subtask/edit.php @@ -10,7 +10,7 @@ <?= $this->formHidden('task_id', $values) ?> <?= $this->formLabel(t('Title'), 'title') ?> - <?= $this->formText('title', $values, $errors, array('required', 'autofocus', 'maxlength="50"')) ?><br/> + <?= $this->formText('title', $values, $errors, array('required', 'autofocus', 'maxlength="255"')) ?><br/> <?= $this->formLabel(t('Assignee'), 'user_id') ?> <?= $this->formSelect('user_id', $users_list, $values, $errors) ?><br/> diff --git a/app/Template/subtask/show.php b/app/Template/subtask/show.php index 1d55d1ee..c7ac652a 100644 --- a/app/Template/subtask/show.php +++ b/app/Template/subtask/show.php @@ -20,7 +20,7 @@ <?php if (! isset($not_editable)): ?> <?= $this->toggleSubtaskStatus($subtask, 'task') ?> <?php else: ?> - <?= $this->render('subtask/icons', array('subtask' => $subtask)) . $this->e($subtask['status_name']) ?> + <?= $this->render('subtask/icons', array('subtask' => $subtask)) . $this->e($subtask['title']) ?> <?php endif ?> </td> <td> @@ -40,6 +40,16 @@ <?php if (! isset($not_editable)): ?> <td> <ul> + <?php if ($subtask['position'] > 1): ?> + <li> + <?= $this->a(t('Move Up'), 'subtask', 'movePosition', array('project_id' => $project['id'], 'task_id' => $subtask['task_id'], 'subtask_id' => $subtask['id'], 'direction' => 'up'), true) ?> + </li> + <?php endif ?> + <?php if ($subtask['position'] != 0 && $subtask['position'] != count($subtasks)): ?> + <li> + <?= $this->a(t('Move Down'), 'subtask', 'movePosition', array('project_id' => $project['id'], 'task_id' => $subtask['task_id'], 'subtask_id' => $subtask['id'], 'direction' => 'down'), true) ?> + </li> + <?php endif ?> <li> <?= $this->a(t('Edit'), 'subtask', 'edit', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'subtask_id' => $subtask['id'])) ?> </li> diff --git a/app/Template/task/show.php b/app/Template/task/show.php index 45485e53..ec930715 100644 --- a/app/Template/task/show.php +++ b/app/Template/task/show.php @@ -2,7 +2,7 @@ <?= $this->render('task/time', array('task' => $task, 'values' => $values, 'date_format' => $date_format, 'date_formats' => $date_formats)) ?> <?= $this->render('task/show_description', array('task' => $task)) ?> <?= $this->render('tasklink/show', array('task' => $task, 'links' => $links)) ?> -<?= $this->render('subtask/show', array('task' => $task, 'subtasks' => $subtasks)) ?> +<?= $this->render('subtask/show', array('task' => $task, 'subtasks' => $subtasks, 'project' => $project)) ?> <?= $this->render('task/timesheet', array('task' => $task)) ?> <?= $this->render('file/show', array('task' => $task, 'files' => $files, 'images' => $images)) ?> <?= $this->render('task/comments', array('task' => $task, 'comments' => $comments, 'project' => $project)) ?> diff --git a/app/Template/timetable/index.php b/app/Template/timetable/index.php new file mode 100644 index 00000000..27cbe39c --- /dev/null +++ b/app/Template/timetable/index.php @@ -0,0 +1,44 @@ +<div class="page-header"> + <h2><?= t('Timetable') ?></h2> + <ul> + <li><?= $this->a(t('Day timetable'), 'timetableday', 'index', array('user_id' => $user['id'])) ?></li> + <li><?= $this->a(t('Week timetable'), 'timetableweek', 'index', array('user_id' => $user['id'])) ?></li> + <li><?= $this->a(t('Time off timetable'), 'timetableoff', 'index', array('user_id' => $user['id'])) ?></li> + <li><?= $this->a(t('Overtime timetable'), 'timetableextra', 'index', array('user_id' => $user['id'])) ?></li> + </ul> +</div> + +<form method="get" action="?" autocomplete="off" class="form-inline"> + + <?= $this->formHidden('controller', $values) ?> + <?= $this->formHidden('action', $values) ?> + <?= $this->formHidden('user_id', $values) ?> + + <?= $this->formLabel(t('From'), 'from') ?> + <?= $this->formText('from', $values, array(), array(), 'form-date') ?> + + <?= $this->formLabel(t('To'), 'to') ?> + <?= $this->formText('to', $values, array(), array(), 'form-date') ?> + + <input type="submit" value="<?= t('Execute') ?>" class="btn btn-blue"/> +</form> + +<?php if (! empty($timetable)): ?> +<hr/> +<h3><?= t('Work timetable') ?></h3> +<table class="table-fixed table-stripped"> + <tr> + <th><?= t('Day') ?></th> + <th><?= t('Start') ?></th> + <th><?= t('End') ?></th> + </tr> + <?php foreach ($timetable as $slot): ?> + <tr> + <td><?= dt('%B %e, %Y', $slot[0]->getTimestamp()) ?></td> + <td><?= dt('%k:%M %p', $slot[0]->getTimestamp()) ?></td> + <td><?= dt('%k:%M %p', $slot[1]->getTimestamp()) ?></td> + </tr> + <?php endforeach ?> +</table> + +<?php endif ?>
\ No newline at end of file diff --git a/app/Template/timetable_day/index.php b/app/Template/timetable_day/index.php new file mode 100644 index 00000000..50aca602 --- /dev/null +++ b/app/Template/timetable_day/index.php @@ -0,0 +1,45 @@ +<div class="page-header"> + <h2><?= t('Day timetable') ?></h2> +</div> + +<?php if (! empty($timetable)): ?> + +<table class="table-fixed table-stripped"> + <tr> + <th><?= t('Start time') ?></th> + <th><?= t('End time') ?></th> + <th><?= t('Action') ?></th> + </tr> + <?php foreach ($timetable as $slot): ?> + <tr> + <td><?= $slot['start'] ?></td> + <td><?= $slot['end'] ?></td> + <td> + <?= $this->a(t('Remove'), 'timetableday', 'confirm', array('user_id' => $user['id'], 'slot_id' => $slot['id'])) ?> + </td> + </tr> + <?php endforeach ?> +</table> + +<h3><?= t('Add new time slot') ?></h3> +<?php endif ?> + +<form method="post" action="<?= $this->u('timetableday', 'save', array('user_id' => $user['id'])) ?>" autocomplete="off"> + + <?= $this->formHidden('user_id', $values) ?> + <?= $this->formCsrf() ?> + + <?= $this->formLabel(t('Start time'), 'start') ?> + <?= $this->formSelect('start', $this->getDayHours(), $values, $errors) ?> + + <?= $this->formLabel(t('End time'), 'end') ?> + <?= $this->formSelect('end', $this->getDayHours(), $values, $errors) ?> + + <div class="form-actions"> + <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> + </div> +</form> + +<p class="alert alert-info"> + <?= t('This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.') ?> +</p>
\ No newline at end of file diff --git a/app/Template/timetable_day/remove.php b/app/Template/timetable_day/remove.php new file mode 100644 index 00000000..b3ee8775 --- /dev/null +++ b/app/Template/timetable_day/remove.php @@ -0,0 +1,13 @@ +<div class="page-header"> + <h2><?= t('Remove time slot') ?></h2> +</div> + +<div class="confirm"> + <p class="alert alert-info"><?= t('Do you really want to remove this time slot?') ?></p> + + <div class="form-actions"> + <?= $this->a(t('Yes'), 'timetableday', 'remove', array('user_id' => $user['id'], 'slot_id' => $slot_id), true, 'btn btn-red') ?> + <?= t('or') ?> + <?= $this->a(t('cancel'), 'timetableday', 'index', array('user_id' => $user['id'])) ?> + </div> +</div>
\ No newline at end of file diff --git a/app/Template/timetable_extra/index.php b/app/Template/timetable_extra/index.php new file mode 100644 index 00000000..a0a55bec --- /dev/null +++ b/app/Template/timetable_extra/index.php @@ -0,0 +1,56 @@ +<div class="page-header"> + <h2><?= t('Overtime timetable') ?></h2> +</div> + +<?php if (! $paginator->isEmpty()): ?> + +<table class="table-fixed table-stripped"> + <tr> + <th><?= $paginator->order(t('Day'), 'Day') ?></th> + <th><?= $paginator->order(t('All day'), 'all_day') ?></th> + <th><?= $paginator->order(t('Start time'), 'start') ?></th> + <th><?= $paginator->order(t('End time'), 'end') ?></th> + <th class="column-40"><?= t('Comment') ?></th> + <th><?= t('Action') ?></th> + </tr> + <?php foreach ($paginator->getCollection() as $slot): ?> + <tr> + <td><?= $slot['date'] ?></td> + <td><?= $slot['all_day'] == 1 ? t('Yes') : t('No') ?></td> + <td><?= $slot['start'] ?></td> + <td><?= $slot['end'] ?></td> + <td><?= $this->e($slot['comment']) ?></td> + <td> + <?= $this->a(t('Remove'), 'timetableextra', 'confirm', array('user_id' => $user['id'], 'slot_id' => $slot['id'])) ?> + </td> + </tr> + <?php endforeach ?> +</table> + +<?= $paginator ?> + +<?php endif ?> + +<form method="post" action="<?= $this->u('timetableextra', 'save', array('user_id' => $user['id'])) ?>" autocomplete="off"> + + <?= $this->formHidden('user_id', $values) ?> + <?= $this->formCsrf() ?> + + <?= $this->formLabel(t('Day'), 'date') ?> + <?= $this->formText('date', $values, $errors, array('required'), 'form-date') ?> + + <?= $this->formCheckbox('all_day', t('All day'), 1) ?> + + <?= $this->formLabel(t('Start time'), 'start') ?> + <?= $this->formSelect('start', $this->getDayHours(), $values, $errors) ?> + + <?= $this->formLabel(t('End time'), 'end') ?> + <?= $this->formSelect('end', $this->getDayHours(), $values, $errors) ?> + + <?= $this->formLabel(t('Comment'), 'comment') ?> + <?= $this->formText('comment', $values, $errors) ?> + + <div class="form-actions"> + <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> + </div> +</form>
\ No newline at end of file diff --git a/app/Template/timetable_extra/remove.php b/app/Template/timetable_extra/remove.php new file mode 100644 index 00000000..d8dc5b3b --- /dev/null +++ b/app/Template/timetable_extra/remove.php @@ -0,0 +1,13 @@ +<div class="page-header"> + <h2><?= t('Remove time slot') ?></h2> +</div> + +<div class="confirm"> + <p class="alert alert-info"><?= t('Do you really want to remove this time slot?') ?></p> + + <div class="form-actions"> + <?= $this->a(t('Yes'), 'timetableextra', 'remove', array('user_id' => $user['id'], 'slot_id' => $slot_id), true, 'btn btn-red') ?> + <?= t('or') ?> + <?= $this->a(t('cancel'), 'timetableextra', 'index', array('user_id' => $user['id'])) ?> + </div> +</div>
\ No newline at end of file diff --git a/app/Template/timetable_off/index.php b/app/Template/timetable_off/index.php new file mode 100644 index 00000000..f35d331e --- /dev/null +++ b/app/Template/timetable_off/index.php @@ -0,0 +1,56 @@ +<div class="page-header"> + <h2><?= t('Time off timetable') ?></h2> +</div> + +<?php if (! $paginator->isEmpty()): ?> + +<table class="table-fixed table-stripped"> + <tr> + <th><?= $paginator->order(t('Day'), 'Day') ?></th> + <th><?= $paginator->order(t('All day'), 'all_day') ?></th> + <th><?= $paginator->order(t('Start time'), 'start') ?></th> + <th><?= $paginator->order(t('End time'), 'end') ?></th> + <th class="column-40"><?= t('Comment') ?></th> + <th><?= t('Action') ?></th> + </tr> + <?php foreach ($paginator->getCollection() as $slot): ?> + <tr> + <td><?= $slot['date'] ?></td> + <td><?= $slot['all_day'] == 1 ? t('Yes') : t('No') ?></td> + <td><?= $slot['start'] ?></td> + <td><?= $slot['end'] ?></td> + <td><?= $this->e($slot['comment']) ?></td> + <td> + <?= $this->a(t('Remove'), 'timetableoff', 'confirm', array('user_id' => $user['id'], 'slot_id' => $slot['id'])) ?> + </td> + </tr> + <?php endforeach ?> +</table> + +<?= $paginator ?> + +<?php endif ?> + +<form method="post" action="<?= $this->u('timetableoff', 'save', array('user_id' => $user['id'])) ?>" autocomplete="off"> + + <?= $this->formHidden('user_id', $values) ?> + <?= $this->formCsrf() ?> + + <?= $this->formLabel(t('Day'), 'date') ?> + <?= $this->formText('date', $values, $errors, array('required'), 'form-date') ?> + + <?= $this->formCheckbox('all_day', t('All day'), 1) ?> + + <?= $this->formLabel(t('Start time'), 'start') ?> + <?= $this->formSelect('start', $this->getDayHours(), $values, $errors) ?> + + <?= $this->formLabel(t('End time'), 'end') ?> + <?= $this->formSelect('end', $this->getDayHours(), $values, $errors) ?> + + <?= $this->formLabel(t('Comment'), 'comment') ?> + <?= $this->formText('comment', $values, $errors) ?> + + <div class="form-actions"> + <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> + </div> +</form>
\ No newline at end of file diff --git a/app/Template/timetable_off/remove.php b/app/Template/timetable_off/remove.php new file mode 100644 index 00000000..64863781 --- /dev/null +++ b/app/Template/timetable_off/remove.php @@ -0,0 +1,13 @@ +<div class="page-header"> + <h2><?= t('Remove time slot') ?></h2> +</div> + +<div class="confirm"> + <p class="alert alert-info"><?= t('Do you really want to remove this time slot?') ?></p> + + <div class="form-actions"> + <?= $this->a(t('Yes'), 'timetableoff', 'remove', array('user_id' => $user['id'], 'slot_id' => $slot_id), true, 'btn btn-red') ?> + <?= t('or') ?> + <?= $this->a(t('cancel'), 'timetableoff', 'index', array('user_id' => $user['id'])) ?> + </div> +</div>
\ No newline at end of file diff --git a/app/Template/timetable_week/index.php b/app/Template/timetable_week/index.php new file mode 100644 index 00000000..8fb51909 --- /dev/null +++ b/app/Template/timetable_week/index.php @@ -0,0 +1,46 @@ +<div class="page-header"> + <h2><?= t('Week timetable') ?></h2> +</div> + +<?php if (! empty($timetable)): ?> + +<table class="table-fixed table-stripped"> + <tr> + <th><?= t('Day') ?></th> + <th><?= t('Start time') ?></th> + <th><?= t('End time') ?></th> + <th><?= t('Action') ?></th> + </tr> + <?php foreach ($timetable as $slot): ?> + <tr> + <td><?= $this->getWeekDay($slot['day']) ?></td> + <td><?= $slot['start'] ?></td> + <td><?= $slot['end'] ?></td> + <td> + <?= $this->a(t('Remove'), 'timetableweek', 'confirm', array('user_id' => $user['id'], 'slot_id' => $slot['id'])) ?> + </td> + </tr> + <?php endforeach ?> +</table> + +<h3><?= t('Add new time slot') ?></h3> +<?php endif ?> + +<form method="post" action="<?= $this->u('timetableweek', 'save', array('user_id' => $user['id'])) ?>" autocomplete="off"> + + <?= $this->formHidden('user_id', $values) ?> + <?= $this->formCsrf() ?> + + <?= $this->formLabel(t('Day'), 'day') ?> + <?= $this->formSelect('day', $this->getWeekDays(), $values, $errors) ?> + + <?= $this->formLabel(t('Start time'), 'start') ?> + <?= $this->formSelect('start', $this->getDayHours(), $values, $errors) ?> + + <?= $this->formLabel(t('End time'), 'end') ?> + <?= $this->formSelect('end', $this->getDayHours(), $values, $errors) ?> + + <div class="form-actions"> + <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/> + </div> +</form>
\ No newline at end of file diff --git a/app/Template/timetable_week/remove.php b/app/Template/timetable_week/remove.php new file mode 100644 index 00000000..f8eb2bbe --- /dev/null +++ b/app/Template/timetable_week/remove.php @@ -0,0 +1,13 @@ +<div class="page-header"> + <h2><?= t('Remove time slot') ?></h2> +</div> + +<div class="confirm"> + <p class="alert alert-info"><?= t('Do you really want to remove this time slot?') ?></p> + + <div class="form-actions"> + <?= $this->a(t('Yes'), 'timetableweek', 'remove', array('user_id' => $user['id'], 'slot_id' => $slot_id), true, 'btn btn-red') ?> + <?= t('or') ?> + <?= $this->a(t('cancel'), 'timetableweek', 'index', array('user_id' => $user['id'])) ?> + </div> +</div>
\ No newline at end of file diff --git a/app/Template/user/sidebar.php b/app/Template/user/sidebar.php index e41851a9..1af10c1d 100644 --- a/app/Template/user/sidebar.php +++ b/app/Template/user/sidebar.php @@ -32,7 +32,7 @@ <?= $this->a(t('Time tracking'), 'user', 'timesheet', array('user_id' => $user['id'])) ?> </li> <?php endif ?> - + <?php if ($this->userSession->isAdmin()): ?> <li> <?= $this->a(t('User dashboard'), 'app', 'dashboard', array('user_id' => $user['id'])) ?> @@ -40,6 +40,12 @@ <li> <?= $this->a(t('User calendar'), 'user', 'calendar', array('user_id' => $user['id'])) ?> </li> + <li> + <?= $this->a(t('Hourly rates'), 'hourlyrate', 'index', array('user_id' => $user['id'])) ?> + </li> + <li> + <?= $this->a(t('Manage timetable'), 'timetable', 'index', array('user_id' => $user['id'])) ?> + </li> <?php endif ?> <?php if ($this->userSession->isAdmin() && ! $this->userSession->isCurrentUser($user['id'])): ?> diff --git a/app/constants.php b/app/constants.php index 39ab5470..63902b4d 100644 --- a/app/constants.php +++ b/app/constants.php @@ -21,6 +21,7 @@ defined('DB_USERNAME') or define('DB_USERNAME', 'root'); defined('DB_PASSWORD') or define('DB_PASSWORD', ''); defined('DB_HOSTNAME') or define('DB_HOSTNAME', 'localhost'); defined('DB_NAME') or define('DB_NAME', 'kanboard'); +defined('DB_PORT') or define('DB_PORT', null); // LDAP configuration defined('LDAP_AUTH') or define('LDAP_AUTH', false); |