diff options
145 files changed, 3135 insertions, 1223 deletions
diff --git a/.docker/nginx/nginx.conf b/.docker/nginx/nginx.conf index a09e8e12..88c532fe 100644 --- a/.docker/nginx/nginx.conf +++ b/.docker/nginx/nginx.conf @@ -53,6 +53,7 @@ http { etag on; } + client_max_body_size 32M; gzip on; gzip_comp_level 3; gzip_disable "msie6"; diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 118a837d..a1492276 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -28,9 +28,11 @@ Contributors: - [Daniel Raknes](https://github.com/danielraknes) - [David-Norris](https://github.com/David-Norris) - [Dmitry](https://github.com/dmkcv) +- [Djpadz](https://github.com/djpadz) - [Draza (bdpsoft)](https://github.com/bdpsoft) - [Eskiso](https://github.com/eSkiSo) - [Esteban Monge](https://github.com/EstebanMonge) +- [Eugene (JohnBat26)](https://github.com/JohnBat26) - [Fábio Hideki](https://github.com/fabiohxcx) - [Fabiano Castro Pereira](https://github.com/fabiano-pereira) - [Federico Lazcano](https://github.com/lazki) @@ -71,6 +73,7 @@ Contributors: - [Nala Ginrut](https://github.com/NalaGinrut) - [Nekohayo](https://github.com/nekohayo) - [Nicolas Lœuillet](https://github.com/nicosomb) +- [Nick Blackledge](https://github.com/nttict-nick-b) - [Norcnorc](https://github.com/norcnorc) - [Nramel](https://github.com/nramel) - [Null-Kelvin](https://github.com/Null-Kelvin) @@ -1,9 +1,24 @@ Version 1.0.26 (unreleased) -------------- +New features: + +* Add file drag and drop and asynchronous upload +* Enable/Disable users +* Add setting option to disable private projects +* Add new config option to disable logout + +Improvements: + +* Improve filter box design +* Improve image thumbnails and files table +* Add confirmation inline popup to remove custom filter +* Increase client_max_body_size value for Nginx + Bug fixes: * Fix PHP notices during creation of first project and in subtasks table +* Fix filter dropdown not accessible when there are too many items Version 1.0.25 -------------- @@ -1,10 +1,10 @@ BUILD_DIR = /tmp -CSS_APP = $(addprefix assets/css/src/, $(addsuffix .css, base links title table form button alert tooltip header board task comment subtask markdown listing activity dashboard pagination popover confirm sidebar responsive dropdown screenshot filters gantt project)) +CSS_APP = $(addprefix assets/css/src/, $(addsuffix .css, base links title table form button alert tooltip header board task comment subtask markdown listing activity dashboard pagination popover confirm sidebar responsive dropdown upload filters gantt project files views)) CSS_PRINT = $(addprefix assets/css/src/, $(addsuffix .css, print links table board task comment subtask markdown)) CSS_VENDOR = $(addprefix assets/css/vendor/, $(addsuffix .css, jquery-ui.min jquery-ui-timepicker-addon.min chosen.min fullcalendar.min font-awesome.min c3.min)) -JS_APP = $(addprefix assets/js/src/, $(addsuffix .js, Popover Dropdown Tooltip Markdown Search App Screenshot Calendar Board Swimlane Gantt Task Project Subtask TaskRepartitionChart UserRepartitionChart CumulativeFlowDiagram BurndownChart AvgTimeColumnChart TaskTimeColumnChart LeadCycleTimeChart CompareHoursColumnChart Router)) +JS_APP = $(addprefix assets/js/src/, $(addsuffix .js, Popover Dropdown Tooltip Markdown Search App Screenshot FileUpload Calendar Board Swimlane Gantt Task Project Subtask TaskRepartitionChart UserRepartitionChart CumulativeFlowDiagram BurndownChart AvgTimeColumnChart TaskTimeColumnChart LeadCycleTimeChart CompareHoursColumnChart Router)) JS_VENDOR = $(addprefix assets/js/vendor/, $(addsuffix .js, jquery-1.11.3.min jquery-ui.min jquery-ui-timepicker-addon.min jquery.ui.touch-punch.min chosen.jquery.min moment.min fullcalendar.min mousetrap.min mousetrap-global-bind.min jquery.textcomplete)) JS_LANG = $(addprefix assets/js/vendor/lang/, $(addsuffix .js, cs da de es el fi fr hu id it ja nl nb pl pt pt-br ru sv sr th tr zh-cn)) @@ -88,6 +88,11 @@ test-archive: @ rm -rf ${BUILD_DIR}/kanboard/.*.yml @ rm -rf ${BUILD_DIR}/kanboard/*.md @ rm -rf ${BUILD_DIR}/kanboard/*.markdown + @ rm -rf ${BUILD_DIR}/kanboard/Dockerfile + @ rm -rf ${BUILD_DIR}/kanboard/.docker + @ rm -rf ${BUILD_DIR}/kanboard/Vagrantfile + @ rm -rf ${BUILD_DIR}/kanboard/app.json + @ rm -rf ${BUILD_DIR}/kanboard/plugins/.gitignore @ cd ${BUILD_DIR}/kanboard && find ./vendor -name notes -type d -exec rm -rf {} +; @ cd ${BUILD_DIR}/kanboard && find ./vendor -name test -type d -exec rm -rf {} +; @ cd ${BUILD_DIR}/kanboard && find ./vendor -name tests -type d -exec rm -rf {} +; diff --git a/app/Api/File.php b/app/Api/File.php index 269803e1..e4204e6d 100644 --- a/app/Api/File.php +++ b/app/Api/File.php @@ -2,6 +2,7 @@ namespace Kanboard\Api; +use Kanboard\Core\Base; use Kanboard\Core\ObjectStorage\ObjectStorageException; /** @@ -10,22 +11,22 @@ use Kanboard\Core\ObjectStorage\ObjectStorageException; * @package api * @author Frederic Guillot */ -class File extends \Kanboard\Core\Base +class File extends Base { - public function getFile($file_id) + public function getTaskFile($file_id) { - return $this->file->getById($file_id); + return $this->taskFile->getById($file_id); } - public function getAllFiles($task_id) + public function getAllTaskFiles($task_id) { - return $this->file->getAll($task_id); + return $this->taskFile->getAll($task_id); } - public function downloadFile($file_id) + public function downloadTaskFile($file_id) { try { - $file = $this->file->getById($file_id); + $file = $this->taskFile->getById($file_id); if (! empty($file)) { return base64_encode($this->objectStorage->get($file['path'])); @@ -36,23 +37,55 @@ class File extends \Kanboard\Core\Base } } - public function createFile($project_id, $task_id, $filename, $blob) + public function createTaskFile($project_id, $task_id, $filename, $blob) { try { - return $this->file->uploadContent($project_id, $task_id, $filename, $blob); + return $this->taskFile->uploadContent($task_id, $filename, $blob); } catch (ObjectStorageException $e) { $this->logger->error($e->getMessage()); return false; } } + public function removeTaskFile($file_id) + { + return $this->taskFile->remove($file_id); + } + + public function removeAllTaskFiles($task_id) + { + return $this->taskFile->removeAll($task_id); + } + + // Deprecated procedures + + public function getFile($file_id) + { + return $this->getTaskFile($file_id); + } + + public function getAllFiles($task_id) + { + return $this->getAllTaskFiles($task_id); + } + + public function downloadFile($file_id) + { + return $this->downloadTaskFile($file_id); + } + + public function createFile($project_id, $task_id, $filename, $blob) + { + return $this->createTaskFile($project_id, $task_id, $filename, $blob); + } + public function removeFile($file_id) { - return $this->file->remove($file_id); + return $this->removeTaskFile($file_id); } public function removeAllFiles($task_id) { - return $this->file->removeAll($task_id); + return $this->removeAllTaskFiles($task_id); } } diff --git a/app/Api/Me.php b/app/Api/Me.php index df8ec070..ccc809ed 100644 --- a/app/Api/Me.php +++ b/app/Api/Me.php @@ -38,6 +38,10 @@ class Me extends Base public function createMyPrivateProject($name, $description = null) { + if ($this->config->get('disable_private_project', 0) == 1) { + return false; + } + $values = array( 'name' => $name, 'description' => $description, diff --git a/app/Api/User.php b/app/Api/User.php index 9f26615d..48337ac6 100644 --- a/app/Api/User.php +++ b/app/Api/User.php @@ -36,6 +36,21 @@ class User extends \Kanboard\Core\Base return $this->user->remove($user_id); } + public function disableUser($user_id) + { + return $this->user->disable($user_id); + } + + public function enableUser($user_id) + { + return $this->user->enable($user_id); + } + + public function isActiveUser($user_id) + { + return $this->user->isActive($user_id); + } + public function createUser($username, $password, $name = '', $email = '', $role = Role::APP_USER) { $values = array( diff --git a/app/Auth/DatabaseAuth.php b/app/Auth/DatabaseAuth.php index 5a8ee64d..c13af687 100644 --- a/app/Auth/DatabaseAuth.php +++ b/app/Auth/DatabaseAuth.php @@ -65,6 +65,7 @@ class DatabaseAuth extends Base implements PasswordAuthenticationProviderInterfa ->eq('username', $this->username) ->eq('disable_login_form', 0) ->eq('is_ldap_user', 0) + ->eq('is_active', 1) ->findOne(); if (! empty($user) && password_verify($this->password, $user['password'])) { @@ -83,7 +84,7 @@ class DatabaseAuth extends Base implements PasswordAuthenticationProviderInterfa */ public function isValidSession() { - return $this->user->exists($this->userSession->getId()); + return $this->user->isActive($this->userSession->getId()); } /** diff --git a/app/Controller/Auth.php b/app/Controller/Auth.php index fef7f0e3..b98dff5d 100644 --- a/app/Controller/Auth.php +++ b/app/Controller/Auth.php @@ -55,8 +55,13 @@ class Auth extends Base */ public function logout() { - $this->sessionManager->close(); - $this->response->redirect($this->helper->url->to('auth', 'login')); + if (! DISABLE_LOGOUT) { + $this->sessionManager->close(); + $this->response->redirect($this->helper->url->to('auth', 'login')); + } + else { + $this->response->redirect($this->helper->url->to('auth', 'index')); + } } /** diff --git a/app/Controller/BoardPopover.php b/app/Controller/BoardPopover.php index f2b39d8d..965669ff 100644 --- a/app/Controller/BoardPopover.php +++ b/app/Controller/BoardPopover.php @@ -93,7 +93,7 @@ class BoardPopover extends Base { $task = $this->getTask(); - $this->response->html($this->template->render('file/screenshot', array( + $this->response->html($this->template->render('task_file/screenshot', array( 'task' => $task, ))); } diff --git a/app/Controller/BoardTooltip.php b/app/Controller/BoardTooltip.php index da07ec4e..bc07ce09 100644 --- a/app/Controller/BoardTooltip.php +++ b/app/Controller/BoardTooltip.php @@ -62,7 +62,7 @@ class BoardTooltip extends Base $task = $this->getTask(); $this->response->html($this->template->render('board/tooltip_files', array( - 'files' => $this->file->getAll($task['id']), + 'files' => $this->taskFile->getAll($task['id']), 'task' => $task, ))); } diff --git a/app/Controller/Config.php b/app/Controller/Config.php index 80522bbe..e811f870 100644 --- a/app/Controller/Config.php +++ b/app/Controller/Config.php @@ -26,7 +26,12 @@ class Config extends Base $values += array('password_reset' => 0); break; case 'project': - $values += array('subtask_restriction' => 0, 'subtask_time_tracking' => 0, 'cfd_include_closed_tasks' => 0); + $values += array( + 'subtask_restriction' => 0, + 'subtask_time_tracking' => 0, + 'cfd_include_closed_tasks' => 0, + 'disable_private_project' => 0, + ); break; case 'integrations': $values += array('integration_gravatar' => 0); diff --git a/app/Controller/Customfilter.php b/app/Controller/Customfilter.php index da7eb77b..41da0b11 100644 --- a/app/Controller/Customfilter.php +++ b/app/Controller/Customfilter.php @@ -57,6 +57,23 @@ class Customfilter extends Base } /** + * Confirmation dialog before removing a custom filter + * + * @access public + */ + public function confirm() + { + $project = $this->getProject(); + $filter = $this->customFilter->getById($this->request->getIntegerParam('filter_id')); + + $this->response->html($this->helper->layout->project('custom_filter/remove', array( + 'project' => $project, + 'filter' => $filter, + 'title' => t('Remove a custom filter') + ))); + } + + /** * Remove a custom filter * * @access public diff --git a/app/Controller/Gantt.php b/app/Controller/Gantt.php index 2d1edc08..5dbd1243 100644 --- a/app/Controller/Gantt.php +++ b/app/Controller/Gantt.php @@ -108,6 +108,7 @@ class Gantt extends Base ); $values = $this->hook->merge('controller:task:form:default', $values, array('default_values' => $values)); + $values = $this->hook->merge('controller:gantt:task:form:default', $values, array('default_values' => $values)); $this->response->html($this->template->render('gantt/task_creation', array( 'project' => $project, diff --git a/app/Controller/Listing.php b/app/Controller/Listing.php index c7d3d9a8..c784dd50 100644 --- a/app/Controller/Listing.php +++ b/app/Controller/Listing.php @@ -32,6 +32,9 @@ class Listing extends Base $this->response->html($this->helper->layout->app('listing/show', $params + array( 'paginator' => $paginator, + 'categories_list' => $this->category->getList($params['project']['id'], false), + 'users_list' => $this->projectUserRole->getAssignableUsersList($params['project']['id'], false), + 'custom_filters_list' => $this->customFilter->getAll($params['project']['id'], $this->userSession->getId()), ))); } } diff --git a/app/Controller/Projectuser.php b/app/Controller/Projectuser.php index 9cd21021..a6d4fe4e 100644 --- a/app/Controller/Projectuser.php +++ b/app/Controller/Projectuser.php @@ -24,7 +24,7 @@ class Projectuser extends Base $project_ids = $this->projectPermission->getActiveProjectIds($this->userSession->getId()); } - return array($user_id, $project_ids, $this->user->getList(true)); + return array($user_id, $project_ids, $this->user->getActiveUsersList(true)); } private function role($role, $action, $title, $title_user) @@ -33,7 +33,7 @@ class Projectuser extends Base $query = $this->projectPermission->getQueryByRole($project_ids, $role)->callback(array($this->project, 'applyColumnStats')); - if ($user_id !== UserModel::EVERYBODY_ID) { + if ($user_id !== UserModel::EVERYBODY_ID && isset($users[$user_id])) { $query->eq(UserModel::TABLE.'.id', $user_id); $title = t($title_user, $users[$user_id]); } @@ -59,7 +59,7 @@ class Projectuser extends Base $query = $this->taskFinder->getProjectUserOverviewQuery($project_ids, $is_active); - if ($user_id !== UserModel::EVERYBODY_ID) { + if ($user_id !== UserModel::EVERYBODY_ID && isset($users[$user_id])) { $query->eq(TaskModel::TABLE.'.owner_id', $user_id); $title = t($title_user, $users[$user_id]); } diff --git a/app/Controller/Task.php b/app/Controller/Task.php index 4db8f86e..98b7a041 100644 --- a/app/Controller/Task.php +++ b/app/Controller/Task.php @@ -66,8 +66,8 @@ class Task extends Base $this->response->html($this->helper->layout->task('task/show', array( 'project' => $this->project->getById($task['project_id']), - 'files' => $this->file->getAllDocuments($task['id']), - 'images' => $this->file->getAllImages($task['id']), + 'files' => $this->taskFile->getAllDocuments($task['id']), + 'images' => $this->taskFile->getAllImages($task['id']), 'comments' => $this->comment->getAll($task['id'], $this->userSession->getCommentSorting()), 'subtasks' => $subtasks, 'links' => $this->taskLink->getAllGroupedByLabel($task['id']), diff --git a/app/Controller/File.php b/app/Controller/TaskFile.php index 50db3865..102fdc5c 100644 --- a/app/Controller/File.php +++ b/app/Controller/TaskFile.php @@ -5,12 +5,12 @@ namespace Kanboard\Controller; use Kanboard\Core\ObjectStorage\ObjectStorageException; /** - * File controller + * File File Controller * * @package controller * @author Frederic Guillot */ -class File extends Base +class TaskFile extends Base { /** * Screenshot @@ -21,12 +21,12 @@ class File extends Base { $task = $this->getTask(); - if ($this->request->isPost() && $this->file->uploadScreenshot($task['project_id'], $task['id'], $this->request->getValue('screenshot')) !== false) { + if ($this->request->isPost() && $this->taskFile->uploadScreenshot($task['id'], $this->request->getValue('screenshot')) !== false) { $this->flash->success(t('Screenshot uploaded successfully.')); return $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])), true); } - $this->response->html($this->helper->layout->task('file/screenshot', array( + $this->response->html($this->helper->layout->task('task_file/screenshot', array( 'task' => $task, ))); } @@ -40,9 +40,9 @@ class File extends Base { $task = $this->getTask(); - $this->response->html($this->helper->layout->task('file/new', array( + $this->response->html($this->helper->layout->task('task_file/new', array( 'task' => $task, - 'max_size' => ini_get('upload_max_filesize'), + 'max_size' => $this->helper->text->phpToBytes(ini_get('upload_max_filesize')), ))); } @@ -55,7 +55,7 @@ class File extends Base { $task = $this->getTask(); - if (! $this->file->uploadFiles($task['project_id'], $task['id'], 'files')) { + if (! $this->taskFile->uploadFiles($task['id'], $this->request->getFileInfo('files'))) { $this->flash->failure(t('Unable to upload the file.')); } @@ -71,7 +71,7 @@ class File extends Base { try { $task = $this->getTask(); - $file = $this->file->getById($this->request->getIntegerParam('file_id')); + $file = $this->taskFile->getById($this->request->getIntegerParam('file_id')); if ($file['task_id'] != $task['id']) { $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']))); @@ -92,10 +92,10 @@ class File extends Base public function open() { $task = $this->getTask(); - $file = $this->file->getById($this->request->getIntegerParam('file_id')); + $file = $this->taskFile->getById($this->request->getIntegerParam('file_id')); if ($file['task_id'] == $task['id']) { - $this->response->html($this->template->render('file/open', array( + $this->response->html($this->template->render('task_file/open', array( 'file' => $file, 'task' => $task, ))); @@ -111,10 +111,10 @@ class File extends Base { try { $task = $this->getTask(); - $file = $this->file->getById($this->request->getIntegerParam('file_id')); + $file = $this->taskFile->getById($this->request->getIntegerParam('file_id')); if ($file['task_id'] == $task['id']) { - $this->response->contentType($this->file->getImageMimeType($file['name'])); + $this->response->contentType($this->taskFile->getImageMimeType($file['name'])); $this->objectStorage->output($file['path']); } } catch (ObjectStorageException $e) { @@ -133,18 +133,18 @@ class File extends Base try { $task = $this->getTask(); - $file = $this->file->getById($this->request->getIntegerParam('file_id')); + $file = $this->taskFile->getById($this->request->getIntegerParam('file_id')); if ($file['task_id'] == $task['id']) { - $this->objectStorage->output($this->file->getThumbnailPath($file['path'])); + $this->objectStorage->output($this->taskFile->getThumbnailPath($file['path'])); } } catch (ObjectStorageException $e) { $this->logger->error($e->getMessage()); // Try to generate thumbnail on the fly for images uploaded before Kanboard < 1.0.19 $data = $this->objectStorage->get($file['path']); - $this->file->generateThumbnailFromData($file['path'], $data); - $this->objectStorage->output($this->file->getThumbnailPath($file['path'])); + $this->taskFile->generateThumbnailFromData($file['path'], $data); + $this->objectStorage->output($this->taskFile->getThumbnailPath($file['path'])); } } @@ -157,9 +157,9 @@ class File extends Base { $this->checkCSRFParam(); $task = $this->getTask(); - $file = $this->file->getById($this->request->getIntegerParam('file_id')); + $file = $this->taskFile->getById($this->request->getIntegerParam('file_id')); - if ($file['task_id'] == $task['id'] && $this->file->remove($file['id'])) { + if ($file['task_id'] == $task['id'] && $this->taskFile->remove($file['id'])) { $this->flash->success(t('File removed successfully.')); } else { $this->flash->failure(t('Unable to remove this file.')); @@ -176,9 +176,9 @@ class File extends Base public function confirm() { $task = $this->getTask(); - $file = $this->file->getById($this->request->getIntegerParam('file_id')); + $file = $this->taskFile->getById($this->request->getIntegerParam('file_id')); - $this->response->html($this->helper->layout->task('file/remove', array( + $this->response->html($this->helper->layout->task('task_file/remove', array( 'task' => $task, 'file' => $file, ))); diff --git a/app/Controller/Taskcreation.php b/app/Controller/Taskcreation.php index e661587c..f1ac7272 100644 --- a/app/Controller/Taskcreation.php +++ b/app/Controller/Taskcreation.php @@ -29,6 +29,7 @@ class Taskcreation extends Base ); $values = $this->hook->merge('controller:task:form:default', $values, array('default_values' => $values)); + $values = $this->hook->merge('controller:task-creation:form:default', $values, array('default_values' => $values)); } $this->response->html($this->template->render('task_creation/form', array( diff --git a/app/Controller/Taskmodification.php b/app/Controller/Taskmodification.php index a321322d..306d34c0 100644 --- a/app/Controller/Taskmodification.php +++ b/app/Controller/Taskmodification.php @@ -80,6 +80,7 @@ class Taskmodification extends Base if (empty($values)) { $values = $task; $values = $this->hook->merge('controller:task:form:default', $values, array('default_values' => $values)); + $values = $this->hook->merge('controller:task-modification:form:default', $values, array('default_values' => $values)); } $values = $this->dateParser->format($values, array('date_due'), $this->config->get('application_date_format', 'm/d/Y')); diff --git a/app/Controller/User.php b/app/Controller/User.php index 881266d4..f7d7d2e0 100644 --- a/app/Controller/User.php +++ b/app/Controller/User.php @@ -32,7 +32,8 @@ class User extends Base $this->helper->layout->app('user/index', array( 'title' => t('Users').' ('.$paginator->getTotal().')', 'paginator' => $paginator, - ))); + ) + )); } /** @@ -404,30 +405,4 @@ class User extends Base 'user' => $user, ))); } - - /** - * Remove a user - * - * @access public - */ - public function remove() - { - $user = $this->getUser(); - - if ($this->request->getStringParam('confirmation') === 'yes') { - $this->checkCSRFParam(); - - if ($this->user->remove($user['id'])) { - $this->flash->success(t('User removed successfully.')); - } else { - $this->flash->failure(t('Unable to remove this user.')); - } - - $this->response->redirect($this->helper->url->to('user', 'index')); - } - - $this->response->html($this->helper->layout->user('user/remove', array( - 'user' => $user, - ))); - } } diff --git a/app/Controller/UserStatus.php b/app/Controller/UserStatus.php new file mode 100644 index 00000000..b8ee5c91 --- /dev/null +++ b/app/Controller/UserStatus.php @@ -0,0 +1,111 @@ +<?php + +namespace Kanboard\Controller; + +/** + * User Status Controller + * + * @package controller + * @author Frederic Guillot + */ +class UserStatus extends Base +{ + /** + * Confirm remove a user + * + * @access public + */ + public function confirmRemove() + { + $user = $this->getUser(); + + $this->response->html($this->helper->layout->user('user_status/remove', array( + 'user' => $user, + ))); + } + + /** + * Remove a user + * + * @access public + */ + public function remove() + { + $user = $this->getUser(); + $this->checkCSRFParam(); + + if ($this->user->remove($user['id'])) { + $this->flash->success(t('User removed successfully.')); + } else { + $this->flash->failure(t('Unable to remove this user.')); + } + + $this->response->redirect($this->helper->url->to('user', 'index')); + } + + /** + * Confirm enable a user + * + * @access public + */ + public function confirmEnable() + { + $user = $this->getUser(); + + $this->response->html($this->helper->layout->user('user_status/enable', array( + 'user' => $user, + ))); + } + + /** + * Enable a user + * + * @access public + */ + public function enable() + { + $user = $this->getUser(); + $this->checkCSRFParam(); + + if ($this->user->enable($user['id'])) { + $this->flash->success(t('User activated successfully.')); + } else { + $this->flash->failure(t('Unable to enable this user.')); + } + + $this->response->redirect($this->helper->url->to('user', 'index')); + } + + /** + * Confirm disable a user + * + * @access public + */ + public function confirmDisable() + { + $user = $this->getUser(); + + $this->response->html($this->helper->layout->user('user_status/disable', array( + 'user' => $user, + ))); + } + + /** + * Disable a user + * + * @access public + */ + public function disable() + { + $user = $this->getUser(); + $this->checkCSRFParam(); + + if ($this->user->disable($user['id'])) { + $this->flash->success(t('User disabled successfully.')); + } else { + $this->flash->failure(t('Unable to disable this user.')); + } + + $this->response->redirect($this->helper->url->to('user', 'index')); + } +} diff --git a/app/Core/Base.php b/app/Core/Base.php index ab99fcea..31c07355 100644 --- a/app/Core/Base.php +++ b/app/Core/Base.php @@ -67,7 +67,8 @@ use Pimple\Container; * @property \Kanboard\Model\Config $config * @property \Kanboard\Model\Currency $currency * @property \Kanboard\Model\CustomFilter $customFilter - * @property \Kanboard\Model\File $file + * @property \Kanboard\Model\TaskFile $taskFile + * @property \Kanboard\Model\ProjectFile $projectFile * @property \Kanboard\Model\Group $group * @property \Kanboard\Model\GroupMember $groupMember * @property \Kanboard\Model\LastLogin $lastLogin diff --git a/app/Core/User/UserProfile.php b/app/Core/User/UserProfile.php index ccbc7f06..ef325801 100644 --- a/app/Core/User/UserProfile.php +++ b/app/Core/User/UserProfile.php @@ -52,7 +52,7 @@ class UserProfile extends Base $this->groupSync->synchronize($profile['id'], $user->getExternalGroupIds()); } - if (! empty($profile)) { + if (! empty($profile) && $profile['is_active'] == 1) { $this->userSession->initialize($profile); return true; } diff --git a/app/Helper/App.php b/app/Helper/App.php index 0593795f..79afa5b9 100644 --- a/app/Helper/App.php +++ b/app/Helper/App.php @@ -17,11 +17,12 @@ class App extends Base * * @access public * @param string $param + * @param mixed $default_value * @return mixed */ - public function config($param) + public function config($param, $default_value = '') { - return $this->config->get($param); + return $this->config->get($param, $default_value); } /** diff --git a/app/Helper/File.php b/app/Helper/File.php index d2cdfc6a..20eda1e3 100644 --- a/app/Helper/File.php +++ b/app/Helper/File.php @@ -38,15 +38,20 @@ class File extends \Kanboard\Core\Base return 'fa-file-powerpoint-o'; case 'zip': case 'rar': + case 'tar': + case 'bz2': + case 'xz': + case 'gz': return 'fa-file-archive-o'; case 'mp3': - return 'fa-audio-o'; + return 'fa-file-audio-o'; case 'avi': - return 'fa-video-o'; + case 'mov': + return 'fa-file-video-o'; case 'php': case 'html': case 'css': - return 'fa-code-o'; + return 'fa-file-code-o'; case 'pdf': return 'fa-file-pdf-o'; } diff --git a/app/Helper/Text.php b/app/Helper/Text.php index 59bfd997..83f1e3f9 100644 --- a/app/Helper/Text.php +++ b/app/Helper/Text.php @@ -43,6 +43,29 @@ class Text extends Base } /** + * Get the number of bytes from PHP size + * + * @param integer $val PHP size (example: 2M) + * @return integer + */ + public function phpToBytes($val) + { + $val = trim($val); + $last = strtolower($val[strlen($val)-1]); + + switch ($last) { + case 'g': + $val *= 1024; + case 'm': + $val *= 1024; + case 'k': + $val *= 1024; + } + + return $val; + } + + /** * Return true if needle is contained in the haystack * * @param string $haystack Haystack diff --git a/app/Locale/bs_BA/translations.php b/app/Locale/bs_BA/translations.php index e331d4a1..69b04c38 100644 --- a/app/Locale/bs_BA/translations.php +++ b/app/Locale/bs_BA/translations.php @@ -598,8 +598,6 @@ return array( 'Compact/wide view' => 'Skupi/raširi pregled', 'No results match:' => 'Nema rezultata:', 'Currency' => 'Valuta', - 'Files' => 'Fajlovi', - 'Images' => 'Slike', 'Private project' => 'Privatni projekat', 'AUD - Australian Dollar' => 'AUD - Australijski dolar', 'CAD - Canadian Dollar' => 'CAD - Kanadski dolar', @@ -645,9 +643,6 @@ return array( 'Test your device' => 'Testiraj svoj uređaj', 'Assign a color when the task is moved to a specific column' => 'Dodijeli boju kada je zadatak pomjeren u odabranu kolonu', '%s via Kanboard' => '%s uz pomoć Kanboard-a', - 'uploaded by: %s' => 'dodano od strane: %s', - 'uploaded on: %s' => 'dodano na: %s', - 'size: %s' => 'veličina: %s', 'Burndown chart for "%s"' => 'Grafikon izgaranja za "%s"', 'Burndown chart' => 'Grafikon izgaranja', 'This chart show the task complexity over the time (Work Remaining).' => 'Ovaj grafikon pokazuje kompleksnost zadatka u vremenu (Preostalo vremena)', @@ -1118,4 +1113,29 @@ return array( // 'End date: ' => '', // 'New due date: ' => '', // 'Start date changed: ' => '', + // 'Disable private projects' => '', + // 'Do you really want to remove this custom filter: "%s"?' => '', + // 'Remove a custom filter' => '', + // 'User activated successfully.' => '', + // 'Unable to enable this user.' => '', + // 'User disabled successfully.' => '', + // 'Unable to disable this user.' => '', + // 'All files have been uploaded successfully.' => '', + // 'View uploaded files' => '', + // 'The maximum allowed file size is %sB.' => '', + // 'Choose files again' => '', + // 'Drag and drop your files here' => '', + // 'choose files' => '', + // 'View profile' => '', + // 'Two Factor' => '', + // 'Disable user' => '', + // 'Do you really want to disable this user: "%s"?' => '', + // 'Enable user' => '', + // 'Do you really want to enable this user: "%s"?' => '', + // 'Download' => '', + // 'Uploaded: %s' => '', + // 'Size: %s' => '', + // 'Uploaded by %s' => '', + // 'Filename' => '', + // 'Size' => '', ); diff --git a/app/Locale/cs_CZ/translations.php b/app/Locale/cs_CZ/translations.php index a495f01e..c4360d3a 100644 --- a/app/Locale/cs_CZ/translations.php +++ b/app/Locale/cs_CZ/translations.php @@ -598,8 +598,6 @@ return array( 'Compact/wide view' => 'Kompaktní/plné zobrazení', 'No results match:' => 'Žádná shoda:', 'Currency' => 'Měna', - 'Files' => 'Soubory', - 'Images' => 'Obrázky', 'Private project' => 'Soukromý projekt', // 'AUD - Australian Dollar' => '', // 'CAD - Canadian Dollar' => '', @@ -645,9 +643,6 @@ return array( 'Test your device' => 'Test Vašeho zařízení', 'Assign a color when the task is moved to a specific column' => 'Přiřadit barvu, když je úkol přesunut do konkrétního sloupce', '%s via Kanboard' => '%s via Kanboard', - 'uploaded by: %s' => 'Nahráno uživatelem: %s', - 'uploaded on: %s' => 'Nahráno dne: %s', - 'size: %s' => 'Velikost: %s', 'Burndown chart for "%s"' => 'Burndown-Chart für "%s"', 'Burndown chart' => 'Burndown-Chart', 'This chart show the task complexity over the time (Work Remaining).' => 'Graf zobrazuje složitost úkolů v čase (Zbývající práce).', @@ -1118,4 +1113,29 @@ return array( // 'End date: ' => '', // 'New due date: ' => '', // 'Start date changed: ' => '', + // 'Disable private projects' => '', + // 'Do you really want to remove this custom filter: "%s"?' => '', + // 'Remove a custom filter' => '', + // 'User activated successfully.' => '', + // 'Unable to enable this user.' => '', + // 'User disabled successfully.' => '', + // 'Unable to disable this user.' => '', + // 'All files have been uploaded successfully.' => '', + // 'View uploaded files' => '', + // 'The maximum allowed file size is %sB.' => '', + // 'Choose files again' => '', + // 'Drag and drop your files here' => '', + // 'choose files' => '', + // 'View profile' => '', + // 'Two Factor' => '', + // 'Disable user' => '', + // 'Do you really want to disable this user: "%s"?' => '', + // 'Enable user' => '', + // 'Do you really want to enable this user: "%s"?' => '', + // 'Download' => '', + // 'Uploaded: %s' => '', + // 'Size: %s' => '', + // 'Uploaded by %s' => '', + // 'Filename' => '', + // 'Size' => '', ); diff --git a/app/Locale/da_DK/translations.php b/app/Locale/da_DK/translations.php index 537b6349..cc296df0 100644 --- a/app/Locale/da_DK/translations.php +++ b/app/Locale/da_DK/translations.php @@ -598,8 +598,6 @@ return array( // 'Compact/wide view' => '', // 'No results match:' => '', // 'Currency' => '', - // 'Files' => '', - // 'Images' => '', // 'Private project' => '', // 'AUD - Australian Dollar' => '', // 'CAD - Canadian Dollar' => '', @@ -645,9 +643,6 @@ return array( // 'Test your device' => '', // 'Assign a color when the task is moved to a specific column' => '', // '%s via Kanboard' => '', - // 'uploaded by: %s' => '', - // 'uploaded on: %s' => '', - // 'size: %s' => '', // 'Burndown chart for "%s"' => '', // 'Burndown chart' => '', // 'This chart show the task complexity over the time (Work Remaining).' => '', @@ -1118,4 +1113,29 @@ return array( // 'End date: ' => '', // 'New due date: ' => '', // 'Start date changed: ' => '', + // 'Disable private projects' => '', + // 'Do you really want to remove this custom filter: "%s"?' => '', + // 'Remove a custom filter' => '', + // 'User activated successfully.' => '', + // 'Unable to enable this user.' => '', + // 'User disabled successfully.' => '', + // 'Unable to disable this user.' => '', + // 'All files have been uploaded successfully.' => '', + // 'View uploaded files' => '', + // 'The maximum allowed file size is %sB.' => '', + // 'Choose files again' => '', + // 'Drag and drop your files here' => '', + // 'choose files' => '', + // 'View profile' => '', + // 'Two Factor' => '', + // 'Disable user' => '', + // 'Do you really want to disable this user: "%s"?' => '', + // 'Enable user' => '', + // 'Do you really want to enable this user: "%s"?' => '', + // 'Download' => '', + // 'Uploaded: %s' => '', + // 'Size: %s' => '', + // 'Uploaded by %s' => '', + // 'Filename' => '', + // 'Size' => '', ); diff --git a/app/Locale/de_DE/translations.php b/app/Locale/de_DE/translations.php index ba6a1fd4..1431d79a 100644 --- a/app/Locale/de_DE/translations.php +++ b/app/Locale/de_DE/translations.php @@ -598,8 +598,6 @@ return array( 'Compact/wide view' => 'Kompakt/Breite-Ansicht', 'No results match:' => 'Keine Ergebnisse:', 'Currency' => 'Währung', - 'Files' => 'Dateien', - 'Images' => 'Bilder', 'Private project' => 'privates Projekt', 'AUD - Australian Dollar' => 'AUD - Australische Dollar', 'CAD - Canadian Dollar' => 'CAD - Kanadische Dollar', @@ -645,9 +643,6 @@ return array( 'Test your device' => 'Teste dein Gerät', 'Assign a color when the task is moved to a specific column' => 'Weise eine Farbe zu, wenn die Aufgabe zu einer bestimmten Spalte bewegt wird', '%s via Kanboard' => '%s via Kanboard', - 'uploaded by: %s' => 'Hochgeladen von: %s', - 'uploaded on: %s' => 'Hochgeladen am: %s', - 'size: %s' => 'Größe: %s', 'Burndown chart for "%s"' => 'Burndown-Diagramm für "%s"', 'Burndown chart' => 'Burndown-Diagramm', 'This chart show the task complexity over the time (Work Remaining).' => 'Dieses Diagramm zeigt die Aufgabenkomplexität über den Faktor Zeit (Verbleibende Arbeit).', @@ -1118,4 +1113,29 @@ return array( // 'End date: ' => '', // 'New due date: ' => '', // 'Start date changed: ' => '', + // 'Disable private projects' => '', + // 'Do you really want to remove this custom filter: "%s"?' => '', + // 'Remove a custom filter' => '', + // 'User activated successfully.' => '', + // 'Unable to enable this user.' => '', + // 'User disabled successfully.' => '', + // 'Unable to disable this user.' => '', + // 'All files have been uploaded successfully.' => '', + // 'View uploaded files' => '', + // 'The maximum allowed file size is %sB.' => '', + // 'Choose files again' => '', + // 'Drag and drop your files here' => '', + // 'choose files' => '', + // 'View profile' => '', + // 'Two Factor' => '', + // 'Disable user' => '', + // 'Do you really want to disable this user: "%s"?' => '', + // 'Enable user' => '', + // 'Do you really want to enable this user: "%s"?' => '', + // 'Download' => '', + // 'Uploaded: %s' => '', + // 'Size: %s' => '', + // 'Uploaded by %s' => '', + // 'Filename' => '', + // 'Size' => '', ); diff --git a/app/Locale/el_GR/translations.php b/app/Locale/el_GR/translations.php index 01b31819..689d6b20 100644 --- a/app/Locale/el_GR/translations.php +++ b/app/Locale/el_GR/translations.php @@ -598,8 +598,6 @@ return array( 'Compact/wide view' => 'Συμπηκνωμένη/Ευρεία Προβολή', 'No results match:' => 'Δεν ταιριάζει κανένα αποτέλεσμα :', 'Currency' => 'Νόμισμα', - 'Files' => 'Αρχεία', - 'Images' => 'Εικόνες', 'Private project' => 'Ιδιωτικό έργο', 'AUD - Australian Dollar' => 'AUD - Australian Dollar', 'CAD - Canadian Dollar' => 'CAD - Canadian Dollar', @@ -645,9 +643,6 @@ return array( 'Test your device' => 'Ελέγξτε τη συσκευή σας', 'Assign a color when the task is moved to a specific column' => 'Αντιστοίχιση χρώματος όταν η εργασία κινείται σε μια συγκεκριμένη στήλη', '%s via Kanboard' => '%s via Kanboard', - 'uploaded by: %s' => 'ανέβασμα από %s', - 'uploaded on: %s' => 'ανέβασμα την %s', - 'size: %s' => 'μέγεθος : %s', 'Burndown chart for "%s"' => 'Δημιουργία διαγράμματος για « %s »', 'Burndown chart' => 'Δημιουργία διαγράμματος', 'This chart show the task complexity over the time (Work Remaining).' => 'Αυτό το γράφημα δείχνει την πολυπλοκότητα του έργου κατά την πάροδο του χρόνου (Εργασία που παραμένει).', @@ -1118,4 +1113,29 @@ return array( // 'End date: ' => '', // 'New due date: ' => '', // 'Start date changed: ' => '', + // 'Disable private projects' => '', + // 'Do you really want to remove this custom filter: "%s"?' => '', + // 'Remove a custom filter' => '', + // 'User activated successfully.' => '', + // 'Unable to enable this user.' => '', + // 'User disabled successfully.' => '', + // 'Unable to disable this user.' => '', + // 'All files have been uploaded successfully.' => '', + // 'View uploaded files' => '', + // 'The maximum allowed file size is %sB.' => '', + // 'Choose files again' => '', + // 'Drag and drop your files here' => '', + // 'choose files' => '', + // 'View profile' => '', + // 'Two Factor' => '', + // 'Disable user' => '', + // 'Do you really want to disable this user: "%s"?' => '', + // 'Enable user' => '', + // 'Do you really want to enable this user: "%s"?' => '', + // 'Download' => '', + // 'Uploaded: %s' => '', + // 'Size: %s' => '', + // 'Uploaded by %s' => '', + // 'Filename' => '', + // 'Size' => '', ); diff --git a/app/Locale/es_ES/translations.php b/app/Locale/es_ES/translations.php index 9c7c6e41..5597f3a1 100644 --- a/app/Locale/es_ES/translations.php +++ b/app/Locale/es_ES/translations.php @@ -598,8 +598,6 @@ return array( 'Compact/wide view' => 'Vista compacta/amplia', 'No results match:' => 'No hay resultados coincidentes:', 'Currency' => 'Moneda', - 'Files' => 'Ficheros', - 'Images' => 'Imágenes', 'Private project' => 'Proyecto privado', 'AUD - Australian Dollar' => 'AUD - Dólar australiano', 'CAD - Canadian Dollar' => 'CAD - Dólar canadiense', @@ -645,9 +643,6 @@ return array( 'Test your device' => 'Probar su dispositivo', 'Assign a color when the task is moved to a specific column' => 'Asignar un color al mover la tarea a una columna específica', '%s via Kanboard' => '%s vía Kanboard', - 'uploaded by: %s' => 'cargado por: %s', - 'uploaded on: %s' => 'cargado en: %s', - 'size: %s' => 'tamaño: %s', 'Burndown chart for "%s"' => 'Trabajo pendiente para "%s"', 'Burndown chart' => 'Trabajo pendiente', 'This chart show the task complexity over the time (Work Remaining).' => 'Este diagrama mestra la complejidad de las tareas a lo largo del tiempo (Trabajo restante)', @@ -1118,4 +1113,29 @@ return array( // 'End date: ' => '', // 'New due date: ' => '', // 'Start date changed: ' => '', + // 'Disable private projects' => '', + // 'Do you really want to remove this custom filter: "%s"?' => '', + // 'Remove a custom filter' => '', + // 'User activated successfully.' => '', + // 'Unable to enable this user.' => '', + // 'User disabled successfully.' => '', + // 'Unable to disable this user.' => '', + // 'All files have been uploaded successfully.' => '', + // 'View uploaded files' => '', + // 'The maximum allowed file size is %sB.' => '', + // 'Choose files again' => '', + // 'Drag and drop your files here' => '', + // 'choose files' => '', + // 'View profile' => '', + // 'Two Factor' => '', + // 'Disable user' => '', + // 'Do you really want to disable this user: "%s"?' => '', + // 'Enable user' => '', + // 'Do you really want to enable this user: "%s"?' => '', + // 'Download' => '', + // 'Uploaded: %s' => '', + // 'Size: %s' => '', + // 'Uploaded by %s' => '', + // 'Filename' => '', + // 'Size' => '', ); diff --git a/app/Locale/fi_FI/translations.php b/app/Locale/fi_FI/translations.php index d50765b6..d6aed20f 100644 --- a/app/Locale/fi_FI/translations.php +++ b/app/Locale/fi_FI/translations.php @@ -598,8 +598,6 @@ return array( // 'Compact/wide view' => '', // 'No results match:' => '', // 'Currency' => '', - // 'Files' => '', - // 'Images' => '', // 'Private project' => '', // 'AUD - Australian Dollar' => '', // 'CAD - Canadian Dollar' => '', @@ -645,9 +643,6 @@ return array( // 'Test your device' => '', // 'Assign a color when the task is moved to a specific column' => '', // '%s via Kanboard' => '', - // 'uploaded by: %s' => '', - // 'uploaded on: %s' => '', - // 'size: %s' => '', // 'Burndown chart for "%s"' => '', // 'Burndown chart' => '', // 'This chart show the task complexity over the time (Work Remaining).' => '', @@ -1118,4 +1113,29 @@ return array( // 'End date: ' => '', // 'New due date: ' => '', // 'Start date changed: ' => '', + // 'Disable private projects' => '', + // 'Do you really want to remove this custom filter: "%s"?' => '', + // 'Remove a custom filter' => '', + // 'User activated successfully.' => '', + // 'Unable to enable this user.' => '', + // 'User disabled successfully.' => '', + // 'Unable to disable this user.' => '', + // 'All files have been uploaded successfully.' => '', + // 'View uploaded files' => '', + // 'The maximum allowed file size is %sB.' => '', + // 'Choose files again' => '', + // 'Drag and drop your files here' => '', + // 'choose files' => '', + // 'View profile' => '', + // 'Two Factor' => '', + // 'Disable user' => '', + // 'Do you really want to disable this user: "%s"?' => '', + // 'Enable user' => '', + // 'Do you really want to enable this user: "%s"?' => '', + // 'Download' => '', + // 'Uploaded: %s' => '', + // 'Size: %s' => '', + // 'Uploaded by %s' => '', + // 'Filename' => '', + // 'Size' => '', ); diff --git a/app/Locale/fr_FR/translations.php b/app/Locale/fr_FR/translations.php index 90d6dc3d..ac62f397 100644 --- a/app/Locale/fr_FR/translations.php +++ b/app/Locale/fr_FR/translations.php @@ -600,8 +600,6 @@ return array( 'Compact/wide view' => 'Basculer entre la vue compacte et étendue', 'No results match:' => 'Aucun résultat :', 'Currency' => 'Devise', - 'Files' => 'Fichiers', - 'Images' => 'Images', 'Private project' => 'Projet privé', 'AUD - Australian Dollar' => 'AUD - Dollar australien', 'CAD - Canadian Dollar' => 'CAD - Dollar canadien', @@ -647,9 +645,6 @@ return array( 'Test your device' => 'Testez votre appareil', 'Assign a color when the task is moved to a specific column' => 'Assigner une couleur lorsque la tâche est déplacée dans une colonne spécifique', '%s via Kanboard' => '%s via Kanboard', - 'uploaded by: %s' => 'Téléchargé par %s', - 'uploaded on: %s' => 'Téléchargé le %s', - 'size: %s' => 'Taille : %s', 'Burndown chart for "%s"' => 'Graphique d\'avancement pour « %s »', 'Burndown chart' => 'Graphique d\'avancement', 'This chart show the task complexity over the time (Work Remaining).' => 'Ce graphique représente la complexité des tâches en fonction du temps (travail restant).', @@ -1121,4 +1116,29 @@ return array( 'End date: ' => 'Date de fin : ', 'New due date: ' => 'Nouvelle date d\'échéance : ', 'Start date changed: ' => 'Date de début modifiée : ', + 'Disable private projects' => 'Désactiver les projets privés', + 'Do you really want to remove this custom filter: "%s"?' => 'Voulez-vous vraiment supprimer ce filtre personnalisé : « %s » ?', + 'Remove a custom filter' => 'Supprimer un filtre personnalisé', + 'User activated successfully.' => 'Utilisateur activé avec succès.', + 'Unable to enable this user.' => 'Impossible d\'activer cet utilisateur.', + 'User disabled successfully.' => 'Utilisateur désactivé avec succès.', + 'Unable to disable this user.' => 'Impossible de désactiver cet utilisateur.', + 'All files have been uploaded successfully.' => 'Tous les fichiers ont été uploadés avec succès.', + 'View uploaded files' => 'Voir les fichiers uploadés', + 'The maximum allowed file size is %sB.' => 'La taille maximale autorisée pour les fichiers est de %so.', + 'Choose files again' => 'Choisir de nouveau des fichiers', + 'Drag and drop your files here' => 'Glissez-déposez vos fichiers ici', + 'choose files' => 'choisissez des fichiers', + 'View profile' => 'Voir le profile', + 'Two Factor' => 'Deux-Facteurs', + 'Disable user' => 'Désactiver l\'utilisateur', + 'Do you really want to disable this user: "%s"?' => 'Voulez-vous vraiment désactiver cet utilisateur : « %s » ?', + 'Enable user' => 'Activer un utilisateur', + 'Do you really want to enable this user: "%s"?' => 'Voulez-vous vraiment activer cet utilisateur : « %s » ?', + 'Download' => 'Télécharger', + 'Uploaded: %s' => 'Uploadé : %s', + 'Size: %s' => 'Taille : %s', + 'Uploaded by %s' => 'Uploadé par %s', + 'Filename' => 'Nom du fichier', + 'Size' => 'Taille', ); diff --git a/app/Locale/hu_HU/translations.php b/app/Locale/hu_HU/translations.php index 447dafc9..2fd9587d 100644 --- a/app/Locale/hu_HU/translations.php +++ b/app/Locale/hu_HU/translations.php @@ -598,8 +598,6 @@ return array( 'Compact/wide view' => 'Kompakt/széles nézet', 'No results match:' => 'Nincs találat:', 'Currency' => 'Pénznem', - 'Files' => 'Fájlok', - 'Images' => 'Képek', 'Private project' => 'Privát projekt', 'AUD - Australian Dollar' => 'AUD - Ausztrál dollár', 'CAD - Canadian Dollar' => 'CAD - Kanadai dollár', @@ -645,9 +643,6 @@ return array( // 'Test your device' => '', // 'Assign a color when the task is moved to a specific column' => '', // '%s via Kanboard' => '', - // 'uploaded by: %s' => '', - // 'uploaded on: %s' => '', - // 'size: %s' => '', // 'Burndown chart for "%s"' => '', // 'Burndown chart' => '', // 'This chart show the task complexity over the time (Work Remaining).' => '', @@ -1118,4 +1113,29 @@ return array( // 'End date: ' => '', // 'New due date: ' => '', // 'Start date changed: ' => '', + // 'Disable private projects' => '', + // 'Do you really want to remove this custom filter: "%s"?' => '', + // 'Remove a custom filter' => '', + // 'User activated successfully.' => '', + // 'Unable to enable this user.' => '', + // 'User disabled successfully.' => '', + // 'Unable to disable this user.' => '', + // 'All files have been uploaded successfully.' => '', + // 'View uploaded files' => '', + // 'The maximum allowed file size is %sB.' => '', + // 'Choose files again' => '', + // 'Drag and drop your files here' => '', + // 'choose files' => '', + // 'View profile' => '', + // 'Two Factor' => '', + // 'Disable user' => '', + // 'Do you really want to disable this user: "%s"?' => '', + // 'Enable user' => '', + // 'Do you really want to enable this user: "%s"?' => '', + // 'Download' => '', + // 'Uploaded: %s' => '', + // 'Size: %s' => '', + // 'Uploaded by %s' => '', + // 'Filename' => '', + // 'Size' => '', ); diff --git a/app/Locale/id_ID/translations.php b/app/Locale/id_ID/translations.php index 8c6db849..f8e465fb 100644 --- a/app/Locale/id_ID/translations.php +++ b/app/Locale/id_ID/translations.php @@ -598,8 +598,6 @@ return array( 'Compact/wide view' => 'Beralih antara tampilan kompak dan diperluas', 'No results match:' => 'Tidak ada hasil :', 'Currency' => 'Mata uang', - 'Files' => 'Arsip', - 'Images' => 'Gambar', 'Private project' => 'Proyek pribadi', 'AUD - Australian Dollar' => 'AUD - Dollar Australia', 'CAD - Canadian Dollar' => 'CAD - Dollar Kanada', @@ -645,9 +643,6 @@ return array( 'Test your device' => 'Menguji perangkat anda', 'Assign a color when the task is moved to a specific column' => 'Menetapkan warna ketika tugas tersebut dipindahkan ke kolom tertentu', '%s via Kanboard' => '%s via Kanboard', - 'uploaded by: %s' => 'diunggah oleh %s', - 'uploaded on: %s' => 'diunggah pada %s', - 'size: %s' => 'ukuran : %s', 'Burndown chart for "%s"' => 'Grafik Burndown untku « %s »', 'Burndown chart' => 'Grafik Burndown', 'This chart show the task complexity over the time (Work Remaining).' => 'Grafik ini menunjukkan kompleksitas tugas dari waktu ke waktu (Sisa Pekerjaan).', @@ -1118,4 +1113,29 @@ return array( // 'End date: ' => '', // 'New due date: ' => '', // 'Start date changed: ' => '', + // 'Disable private projects' => '', + // 'Do you really want to remove this custom filter: "%s"?' => '', + // 'Remove a custom filter' => '', + // 'User activated successfully.' => '', + // 'Unable to enable this user.' => '', + // 'User disabled successfully.' => '', + // 'Unable to disable this user.' => '', + // 'All files have been uploaded successfully.' => '', + // 'View uploaded files' => '', + // 'The maximum allowed file size is %sB.' => '', + // 'Choose files again' => '', + // 'Drag and drop your files here' => '', + // 'choose files' => '', + // 'View profile' => '', + // 'Two Factor' => '', + // 'Disable user' => '', + // 'Do you really want to disable this user: "%s"?' => '', + // 'Enable user' => '', + // 'Do you really want to enable this user: "%s"?' => '', + // 'Download' => '', + // 'Uploaded: %s' => '', + // 'Size: %s' => '', + // 'Uploaded by %s' => '', + // 'Filename' => '', + // 'Size' => '', ); diff --git a/app/Locale/it_IT/translations.php b/app/Locale/it_IT/translations.php index 661e6c86..35b3334b 100644 --- a/app/Locale/it_IT/translations.php +++ b/app/Locale/it_IT/translations.php @@ -598,8 +598,6 @@ return array( 'Compact/wide view' => 'Vista compatta/estesa', 'No results match:' => 'Nessun risultato trovato:', 'Currency' => 'Valuta', - 'Files' => 'File', - 'Images' => 'Immagini', 'Private project' => 'Progetto privato', 'AUD - Australian Dollar' => 'AUD - Dollari Australiani', 'CAD - Canadian Dollar' => 'CAD - Dollari Canadesi', @@ -645,9 +643,6 @@ return array( 'Test your device' => 'Testa il tuo dispositivo', 'Assign a color when the task is moved to a specific column' => 'Assegna un colore quando il task viene spostato in una colonna specifica', '%s via Kanboard' => '%s tramite Kanboard', - 'uploaded by: %s' => 'caricato da: %s', - 'uploaded on: %s' => 'caricato su: %s', - 'size: %s' => 'Dimensione: %s', 'Burndown chart for "%s"' => 'Grafico Burndown per "%s"', 'Burndown chart' => 'Grafico Burndown', 'This chart show the task complexity over the time (Work Remaining).' => 'Questo grafico mostra la complessità dei task nel tempo (Lavoro residuo).', @@ -1118,4 +1113,29 @@ return array( // 'End date: ' => '', // 'New due date: ' => '', // 'Start date changed: ' => '', + // 'Disable private projects' => '', + // 'Do you really want to remove this custom filter: "%s"?' => '', + // 'Remove a custom filter' => '', + // 'User activated successfully.' => '', + // 'Unable to enable this user.' => '', + // 'User disabled successfully.' => '', + // 'Unable to disable this user.' => '', + // 'All files have been uploaded successfully.' => '', + // 'View uploaded files' => '', + // 'The maximum allowed file size is %sB.' => '', + // 'Choose files again' => '', + // 'Drag and drop your files here' => '', + // 'choose files' => '', + // 'View profile' => '', + // 'Two Factor' => '', + // 'Disable user' => '', + // 'Do you really want to disable this user: "%s"?' => '', + // 'Enable user' => '', + // 'Do you really want to enable this user: "%s"?' => '', + // 'Download' => '', + // 'Uploaded: %s' => '', + // 'Size: %s' => '', + // 'Uploaded by %s' => '', + // 'Filename' => '', + // 'Size' => '', ); diff --git a/app/Locale/ja_JP/translations.php b/app/Locale/ja_JP/translations.php index 3609836b..a08a305e 100644 --- a/app/Locale/ja_JP/translations.php +++ b/app/Locale/ja_JP/translations.php @@ -598,8 +598,6 @@ return array( 'Compact/wide view' => 'コンパクト/ワイドビュー', 'No results match:' => '結果が一致しませんでした', 'Currency' => '通貨', - 'Files' => 'ファイル', - 'Images' => '画像', 'Private project' => 'プライベートプロジェクト', 'AUD - Australian Dollar' => 'AUD - 豪ドル', 'CAD - Canadian Dollar' => 'CAD - 加ドル', @@ -645,9 +643,6 @@ return array( 'Test your device' => 'デバイスをテストする', // 'Assign a color when the task is moved to a specific column' => '', // '%s via Kanboard' => '', - // 'uploaded by: %s' => '', - // 'uploaded on: %s' => '', - // 'size: %s' => '', // 'Burndown chart for "%s"' => '', // 'Burndown chart' => '', // 'This chart show the task complexity over the time (Work Remaining).' => '', @@ -1118,4 +1113,29 @@ return array( // 'End date: ' => '', // 'New due date: ' => '', // 'Start date changed: ' => '', + // 'Disable private projects' => '', + // 'Do you really want to remove this custom filter: "%s"?' => '', + // 'Remove a custom filter' => '', + // 'User activated successfully.' => '', + // 'Unable to enable this user.' => '', + // 'User disabled successfully.' => '', + // 'Unable to disable this user.' => '', + // 'All files have been uploaded successfully.' => '', + // 'View uploaded files' => '', + // 'The maximum allowed file size is %sB.' => '', + // 'Choose files again' => '', + // 'Drag and drop your files here' => '', + // 'choose files' => '', + // 'View profile' => '', + // 'Two Factor' => '', + // 'Disable user' => '', + // 'Do you really want to disable this user: "%s"?' => '', + // 'Enable user' => '', + // 'Do you really want to enable this user: "%s"?' => '', + // 'Download' => '', + // 'Uploaded: %s' => '', + // 'Size: %s' => '', + // 'Uploaded by %s' => '', + // 'Filename' => '', + // 'Size' => '', ); diff --git a/app/Locale/my_MY/translations.php b/app/Locale/my_MY/translations.php index 5b1da7cd..d7d3f3cc 100644 --- a/app/Locale/my_MY/translations.php +++ b/app/Locale/my_MY/translations.php @@ -598,8 +598,6 @@ return array( 'Compact/wide view' => 'Beralih antara tampilan kompak dan diperluas', 'No results match:' => 'Tidak ada hasil :', 'Currency' => 'Mata uang', - 'Files' => 'Arsip', - 'Images' => 'Gambar', 'Private project' => 'projek pribadi', 'AUD - Australian Dollar' => 'AUD - Dollar Australia', 'CAD - Canadian Dollar' => 'CAD - Dollar Kanada', @@ -645,9 +643,6 @@ return array( 'Test your device' => 'Menguji perangkat anda', 'Assign a color when the task is moved to a specific column' => 'Menetapkan warna ketika tugas tersebut dipindahkan ke kolom tertentu', '%s via Kanboard' => '%s via Kanboard', - 'uploaded by: %s' => 'diunggah oleh %s', - 'uploaded on: %s' => 'diunggah pada %s', - 'size: %s' => 'ukuran : %s', 'Burndown chart for "%s"' => 'Grafik Burndown untku « %s »', 'Burndown chart' => 'Grafik Burndown', 'This chart show the task complexity over the time (Work Remaining).' => 'Grafik ini menunjukkan kompleksitas tugas dari waktu ke waktu (Sisa Pekerjaan).', @@ -1118,4 +1113,29 @@ return array( // 'End date: ' => '', // 'New due date: ' => '', // 'Start date changed: ' => '', + // 'Disable private projects' => '', + // 'Do you really want to remove this custom filter: "%s"?' => '', + // 'Remove a custom filter' => '', + // 'User activated successfully.' => '', + // 'Unable to enable this user.' => '', + // 'User disabled successfully.' => '', + // 'Unable to disable this user.' => '', + // 'All files have been uploaded successfully.' => '', + // 'View uploaded files' => '', + // 'The maximum allowed file size is %sB.' => '', + // 'Choose files again' => '', + // 'Drag and drop your files here' => '', + // 'choose files' => '', + // 'View profile' => '', + // 'Two Factor' => '', + // 'Disable user' => '', + // 'Do you really want to disable this user: "%s"?' => '', + // 'Enable user' => '', + // 'Do you really want to enable this user: "%s"?' => '', + // 'Download' => '', + // 'Uploaded: %s' => '', + // 'Size: %s' => '', + // 'Uploaded by %s' => '', + // 'Filename' => '', + // 'Size' => '', ); diff --git a/app/Locale/nb_NO/translations.php b/app/Locale/nb_NO/translations.php index 335c699b..b55b846e 100644 --- a/app/Locale/nb_NO/translations.php +++ b/app/Locale/nb_NO/translations.php @@ -598,8 +598,6 @@ return array( 'Compact/wide view' => 'Kompakt/bred visning', 'No results match:' => 'Ingen resultater', 'Currency' => 'Valuta', - 'Files' => 'Filer', - 'Images' => 'Bilder', 'Private project' => 'Privat prosjekt', // 'AUD - Australian Dollar' => '', // 'CAD - Canadian Dollar' => '', @@ -645,9 +643,6 @@ return array( // 'Test your device' => '', 'Assign a color when the task is moved to a specific column' => 'Endre til en valgt farge hvis en oppgave flyttes til en spesifikk kolonne', // '%s via Kanboard' => '', - // 'uploaded by: %s' => '', - // 'uploaded on: %s' => '', - // 'size: %s' => '', // 'Burndown chart for "%s"' => '', // 'Burndown chart' => '', // 'This chart show the task complexity over the time (Work Remaining).' => '', @@ -1118,4 +1113,29 @@ return array( // 'End date: ' => '', // 'New due date: ' => '', // 'Start date changed: ' => '', + // 'Disable private projects' => '', + // 'Do you really want to remove this custom filter: "%s"?' => '', + // 'Remove a custom filter' => '', + // 'User activated successfully.' => '', + // 'Unable to enable this user.' => '', + // 'User disabled successfully.' => '', + // 'Unable to disable this user.' => '', + // 'All files have been uploaded successfully.' => '', + // 'View uploaded files' => '', + // 'The maximum allowed file size is %sB.' => '', + // 'Choose files again' => '', + // 'Drag and drop your files here' => '', + // 'choose files' => '', + // 'View profile' => '', + // 'Two Factor' => '', + // 'Disable user' => '', + // 'Do you really want to disable this user: "%s"?' => '', + // 'Enable user' => '', + // 'Do you really want to enable this user: "%s"?' => '', + // 'Download' => '', + // 'Uploaded: %s' => '', + // 'Size: %s' => '', + // 'Uploaded by %s' => '', + // 'Filename' => '', + // 'Size' => '', ); diff --git a/app/Locale/nl_NL/translations.php b/app/Locale/nl_NL/translations.php index 0d9d9608..21b95446 100644 --- a/app/Locale/nl_NL/translations.php +++ b/app/Locale/nl_NL/translations.php @@ -598,8 +598,6 @@ return array( // 'Compact/wide view' => '', // 'No results match:' => '', // 'Currency' => '', - // 'Files' => '', - // 'Images' => '', // 'Private project' => '', // 'AUD - Australian Dollar' => '', // 'CAD - Canadian Dollar' => '', @@ -645,9 +643,6 @@ return array( // 'Test your device' => '', // 'Assign a color when the task is moved to a specific column' => '', // '%s via Kanboard' => '', - // 'uploaded by: %s' => '', - // 'uploaded on: %s' => '', - // 'size: %s' => '', // 'Burndown chart for "%s"' => '', // 'Burndown chart' => '', // 'This chart show the task complexity over the time (Work Remaining).' => '', @@ -1118,4 +1113,29 @@ return array( // 'End date: ' => '', // 'New due date: ' => '', // 'Start date changed: ' => '', + // 'Disable private projects' => '', + // 'Do you really want to remove this custom filter: "%s"?' => '', + // 'Remove a custom filter' => '', + // 'User activated successfully.' => '', + // 'Unable to enable this user.' => '', + // 'User disabled successfully.' => '', + // 'Unable to disable this user.' => '', + // 'All files have been uploaded successfully.' => '', + // 'View uploaded files' => '', + // 'The maximum allowed file size is %sB.' => '', + // 'Choose files again' => '', + // 'Drag and drop your files here' => '', + // 'choose files' => '', + // 'View profile' => '', + // 'Two Factor' => '', + // 'Disable user' => '', + // 'Do you really want to disable this user: "%s"?' => '', + // 'Enable user' => '', + // 'Do you really want to enable this user: "%s"?' => '', + // 'Download' => '', + // 'Uploaded: %s' => '', + // 'Size: %s' => '', + // 'Uploaded by %s' => '', + // 'Filename' => '', + // 'Size' => '', ); diff --git a/app/Locale/pl_PL/translations.php b/app/Locale/pl_PL/translations.php index 28ed2b3a..ec5918b4 100644 --- a/app/Locale/pl_PL/translations.php +++ b/app/Locale/pl_PL/translations.php @@ -598,8 +598,6 @@ return array( 'Compact/wide view' => 'Pełny/Kompaktowy widok', 'No results match:' => 'Brak wyników:', 'Currency' => 'Waluta', - 'Files' => 'Pliki', - 'Images' => 'Obrazy', 'Private project' => 'Projekt prywatny', 'AUD - Australian Dollar' => 'AUD - Dolar australijski', 'CAD - Canadian Dollar' => 'CAD - Dolar kanadyjski', @@ -645,9 +643,6 @@ return array( 'Test your device' => 'Przetestuj urządzenie', 'Assign a color when the task is moved to a specific column' => 'Przypisz kolor gdy zadanie jest przeniesione do danej kolumny', '%s via Kanboard' => '%s poprzez Kanboard', - 'uploaded by: %s' => 'Dodane przez: %s', - 'uploaded on: %s' => 'Data dodania: %s', - 'size: %s' => 'Rozmiar: %s', 'Burndown chart for "%s"' => 'Wykres Burndown dla "%s"', 'Burndown chart' => 'Wykres Burndown', // 'This chart show the task complexity over the time (Work Remaining).' => '', @@ -1118,4 +1113,29 @@ return array( // 'End date: ' => '', // 'New due date: ' => '', // 'Start date changed: ' => '', + // 'Disable private projects' => '', + // 'Do you really want to remove this custom filter: "%s"?' => '', + // 'Remove a custom filter' => '', + // 'User activated successfully.' => '', + // 'Unable to enable this user.' => '', + // 'User disabled successfully.' => '', + // 'Unable to disable this user.' => '', + // 'All files have been uploaded successfully.' => '', + // 'View uploaded files' => '', + // 'The maximum allowed file size is %sB.' => '', + // 'Choose files again' => '', + // 'Drag and drop your files here' => '', + // 'choose files' => '', + // 'View profile' => '', + // 'Two Factor' => '', + // 'Disable user' => '', + // 'Do you really want to disable this user: "%s"?' => '', + // 'Enable user' => '', + // 'Do you really want to enable this user: "%s"?' => '', + // 'Download' => '', + // 'Uploaded: %s' => '', + // 'Size: %s' => '', + // 'Uploaded by %s' => '', + // 'Filename' => '', + // 'Size' => '', ); diff --git a/app/Locale/pt_BR/translations.php b/app/Locale/pt_BR/translations.php index d29f9f88..ea4a028c 100644 --- a/app/Locale/pt_BR/translations.php +++ b/app/Locale/pt_BR/translations.php @@ -598,8 +598,6 @@ return array( 'Compact/wide view' => 'Alternar entre a vista compacta e ampliada', 'No results match:' => 'Nenhum resultado:', 'Currency' => 'Moeda', - 'Files' => 'Arquivos', - 'Images' => 'Imagens', 'Private project' => 'Projeto privado', 'AUD - Australian Dollar' => 'AUD - Dólar australiano', 'CAD - Canadian Dollar' => 'CAD - Dólar canadense', @@ -645,9 +643,6 @@ return array( 'Test your device' => 'Teste o seu dispositivo', 'Assign a color when the task is moved to a specific column' => 'Atribuir uma cor quando a tarefa é movida em uma coluna específica', '%s via Kanboard' => '%s via Kanboard', - 'uploaded by: %s' => 'carregado por: %s', - 'uploaded on: %s' => 'carregado em: %s', - 'size: %s' => 'tamanho: %s', 'Burndown chart for "%s"' => 'Gráfico de Burndown para "%s"', 'Burndown chart' => 'Gráfico de Burndown', 'This chart show the task complexity over the time (Work Remaining).' => 'Este gráfico mostra a complexidade da tarefa ao longo do tempo (Trabalho Restante).', @@ -954,57 +949,57 @@ return array( 'Duplicates are not imported' => 'Registros duplicados não são importados', 'Usernames must be lowercase and unique' => 'Nomes de usuário devem ser únicos e em letras minúsculas', 'Passwords will be encrypted if present' => 'Senhas serão encriptadas, se presentes', - // '%s attached a new file to the task %s' => '', - // 'Assign automatically a category based on a link' => '', - // 'BAM - Konvertible Mark' => '', - // 'Assignee Username' => '', - // 'Assignee Name' => '', - // 'Groups' => '', - // 'Members of %s' => '', - // 'New group' => '', - // 'Group created successfully.' => '', - // 'Unable to create your group.' => '', - // 'Edit group' => '', - // 'Group updated successfully.' => '', - // 'Unable to update your group.' => '', - // 'Add group member to "%s"' => '', - // 'Group member added successfully.' => '', - // 'Unable to add group member.' => '', - // 'Remove user from group "%s"' => '', - // 'User removed successfully from this group.' => '', - // 'Unable to remove this user from the group.' => '', - // 'Remove group' => '', - // 'Group removed successfully.' => '', - // 'Unable to remove this group.' => '', - // 'Project Permissions' => '', - // 'Manager' => '', - // 'Project Manager' => '', - // 'Project Member' => '', - // 'Project Viewer' => '', - // 'Your account is locked for %d minutes' => '', - // 'Invalid captcha' => '', - // 'The name must be unique' => '', - // 'View all groups' => '', - // 'View group members' => '', - // 'There is no user available.' => '', - // 'Do you really want to remove the user "%s" from the group "%s"?' => '', - // 'There is no group.' => '', - // 'External Id' => '', - // 'Add group member' => '', - // 'Do you really want to remove this group: "%s"?' => '', - // 'There is no user in this group.' => '', - // 'Remove this user' => '', - // 'Permissions' => '', - // 'Allowed Users' => '', - // 'No user have been allowed specifically.' => '', - // 'Role' => '', - // 'Enter user name...' => '', - // 'Allowed Groups' => '', - // 'No group have been allowed specifically.' => '', - // 'Group' => '', - // 'Group Name' => '', - // 'Enter group name...' => '', - // 'Role:' => '', + '%s attached a new file to the task %s' => '%s anexou um novo arquivo a tarefa %s', + 'Assign automatically a category based on a link' => 'Atribuir automaticamente uma categoria baseada num link', + 'BAM - Konvertible Mark' => 'BAM - Mark conversível', + 'Assignee Username' => 'Usuário designado', + 'Assignee Name' => 'Nome do designado', + 'Groups' => 'Grupos', + 'Members of %s' => 'Membros do %s', + 'New group' => 'Novo grupo', + 'Group created successfully.' => 'Grupo criado com sucesso.', + 'Unable to create your group.' => 'Não foi possível de criar o seu grupo.', + 'Edit group' => 'Editar o grupo', + 'Group updated successfully.' => 'Grupo atualizado com sucesso.', + 'Unable to update your group.' => 'Não foi possível atualizar o seu grupo.', + 'Add group member to "%s"' => 'Adicionar um membro ao grupo "%s"', + 'Group member added successfully.' => 'Membro adicionado com sucesso ao o grupo.', + 'Unable to add group member.' => 'Não foi possivel adicionar esse membro ao grupo.', + 'Remove user from group "%s"' => 'Remover usuário do grupo "%s"', + 'User removed successfully from this group.' => 'Usuário removido com sucesso deste grupo.', + 'Unable to remove this user from the group.' => 'Não foi possivel remover este Usuário do grupo.', + 'Remove group' => 'Remover o grupo', + 'Group removed successfully.' => 'Grupo removido com sucesso.', + 'Unable to remove this group.' => 'Não foi possível remover este grupo.', + 'Project Permissions' => 'Permissões do projeto', + 'Manager' => 'Gerente', + 'Project Manager' => 'Gerente de projeto', + 'Project Member' => 'Membro de projeto', + 'Project Viewer' => '', + 'Your account is locked for %d minutes' => 'A sua conta está bloqueada por %d minutos', + 'Invalid captcha' => 'Captcha inválido', + 'The name must be unique' => 'O nome deve ser único', + 'View all groups' => 'Ver todos os grupos', + 'View group members' => 'Ver os membros do grupo', + 'There is no user available.' => 'Não há nenhum usuário disponível', + 'Do you really want to remove the user "%s" from the group "%s"?' => 'Você realmente deseja remover o usuário "%s" do grupo "%s"?', + 'There is no group.' => 'Não há nenhum grupo.', + 'External Id' => 'Id externo', + 'Add group member' => 'Adicionar um membro ao grupo', + 'Do you really want to remove this group: "%s"?' => 'Você realmente deseja excluir este grupo: "%s"?', + 'There is no user in this group.' => 'Não há usuários neste grupo.', + 'Remove this user' => 'Remover este usuário', + 'Permissions' => 'Permissões', + 'Allowed Users' => 'Usuários autorizados', + 'No user have been allowed specifically.' => 'Nenhum usuário foi especificamente autorizado.', + 'Role' => 'Função', + 'Enter user name...' => 'Digite o nome do usuário...', + 'Allowed Groups' => 'Grupos autorizados', + 'No group have been allowed specifically.' => 'Nenhum grupo foi especificamente autorizado.', + 'Group' => 'Grupo', + 'Group Name' => 'Nome do grupo', + 'Enter group name...' => 'Digite o nome do grupo', + 'Role:' => 'Função:', 'Project members' => 'Membros de projeto', // 'Compare hours for "%s"' => '', // '%s mentioned you in the task #%d' => '', @@ -1118,4 +1113,29 @@ return array( // 'End date: ' => '', // 'New due date: ' => '', // 'Start date changed: ' => '', + // 'Disable private projects' => '', + // 'Do you really want to remove this custom filter: "%s"?' => '', + // 'Remove a custom filter' => '', + // 'User activated successfully.' => '', + // 'Unable to enable this user.' => '', + // 'User disabled successfully.' => '', + // 'Unable to disable this user.' => '', + // 'All files have been uploaded successfully.' => '', + // 'View uploaded files' => '', + // 'The maximum allowed file size is %sB.' => '', + // 'Choose files again' => '', + // 'Drag and drop your files here' => '', + // 'choose files' => '', + // 'View profile' => '', + // 'Two Factor' => '', + // 'Disable user' => '', + // 'Do you really want to disable this user: "%s"?' => '', + // 'Enable user' => '', + // 'Do you really want to enable this user: "%s"?' => '', + // 'Download' => '', + // 'Uploaded: %s' => '', + // 'Size: %s' => '', + // 'Uploaded by %s' => '', + // 'Filename' => '', + // 'Size' => '', ); diff --git a/app/Locale/pt_PT/translations.php b/app/Locale/pt_PT/translations.php index b197a1dc..cecae5fe 100644 --- a/app/Locale/pt_PT/translations.php +++ b/app/Locale/pt_PT/translations.php @@ -598,8 +598,6 @@ return array( 'Compact/wide view' => 'Alternar entre a vista compacta e ampliada', 'No results match:' => 'Nenhum resultado:', 'Currency' => 'Moeda', - 'Files' => 'Arquivos', - 'Images' => 'Imagens', 'Private project' => 'Projecto privado', 'AUD - Australian Dollar' => 'AUD - Dólar australiano', 'CAD - Canadian Dollar' => 'CAD - Dólar canadense', @@ -645,9 +643,6 @@ return array( 'Test your device' => 'Teste o seu dispositivo', 'Assign a color when the task is moved to a specific column' => 'Atribuir uma cor quando a tarefa é movida em uma coluna específica', '%s via Kanboard' => '%s via Kanboard', - 'uploaded by: %s' => 'carregado por: %s', - 'uploaded on: %s' => 'carregado em: %s', - 'size: %s' => 'tamanho: %s', 'Burndown chart for "%s"' => 'Gráfico de Burndown para "%s"', 'Burndown chart' => 'Gráfico de Burndown', 'This chart show the task complexity over the time (Work Remaining).' => 'Este gráfico mostra a complexidade da tarefa ao longo do tempo (Trabalho Restante).', @@ -1118,4 +1113,29 @@ return array( 'End date: ' => 'Data final: ', 'New due date: ' => 'Nova data estimada: ', 'Start date changed: ' => 'Data inicio alterada: ', + 'Disable private projects' => 'Desactivar projectos privados', + 'Do you really want to remove this custom filter: "%s"?' => 'Tem a certeza que quer remover este filtro personalizado: "%s"?', + 'Remove a custom filter' => 'Remover o filtro personalizado', + 'User activated successfully.' => 'Utilizador activado com sucesso.', + 'Unable to enable this user.' => 'Não foi possivel activar este utilizador.', + 'User disabled successfully.' => 'Utilizador desactivado com sucesso.', + 'Unable to disable this user.' => 'Não foi possivel desactivar este utilizador.', + 'All files have been uploaded successfully.' => 'Todos os ficheiros foram enviados com sucesso.', + 'View uploaded files' => 'Ver ficheiros enviados', + 'The maximum allowed file size is %sB.' => 'O tamanho máximo permitido é %sB.', + 'Choose files again' => 'Escolher ficheiros novamente', + 'Drag and drop your files here' => 'Arraste e deixe os ficheiros para aqui', + 'choose files' => 'escolher ficheiros', + 'View profile' => 'Ver perfil', + 'Two Factor' => 'Dois Factores', + 'Disable user' => 'Desactivar utilizador', + 'Do you really want to disable this user: "%s"?' => 'Tem a certeza que quer desactivar este utilizador: "%s"?', + 'Enable user' => 'Activar utilizador', + 'Do you really want to enable this user: "%s"?' => 'Tem a certeza que quer activar este utilizador: "%s"?', + 'Download' => 'Transferir', + 'Uploaded: %s' => 'Enviado: %s', + 'Size: %s' => 'Tamanho: %s', + 'Uploaded by %s' => 'Enviado por %s', + 'Filename' => 'Nome do ficheiro', + 'Size' => 'Tamanho', ); diff --git a/app/Locale/ru_RU/translations.php b/app/Locale/ru_RU/translations.php index f971acc1..4f231397 100644 --- a/app/Locale/ru_RU/translations.php +++ b/app/Locale/ru_RU/translations.php @@ -598,8 +598,6 @@ return array( 'Compact/wide view' => 'Компактный/широкий вид', 'No results match:' => 'Отсутствуют результаты:', 'Currency' => 'Валюта', - 'Files' => 'Файлы', - 'Images' => 'Изображения', 'Private project' => 'Приватный проект', 'AUD - Australian Dollar' => 'AUD - Австралийский доллар', 'CAD - Canadian Dollar' => 'CAD - Канадский доллар', @@ -645,9 +643,6 @@ return array( 'Test your device' => 'Проверьте свое устройство', 'Assign a color when the task is moved to a specific column' => 'Назначить цвет, когда задача перемещается в определенную колонку', '%s via Kanboard' => '%s через Канборд', - 'uploaded by: %s' => 'загружено: %s', - 'uploaded on: %s' => 'загружено в: %s', - 'size: %s' => 'размер: %s', 'Burndown chart for "%s"' => 'Диаграмма сгорания для « %s »', 'Burndown chart' => 'Диаграмма сгорания', 'This chart show the task complexity over the time (Work Remaining).' => 'Эта диаграмма показывают сложность задачи по времени (оставшейся работы).', @@ -1112,10 +1107,35 @@ return array( 'Moved:' => 'Перемещена:', 'Task #%d' => 'Задача #%d', 'Sub-tasks' => 'Подзадачи', - // 'Date and time format' => '', - // 'Time format' => '', - // 'Start date: ' => '', - // 'End date: ' => '', - // 'New due date: ' => '', - // 'Start date changed: ' => '', + 'Date and time format' => 'Формат даты и времени', + 'Time format' => 'Формат времени', + 'Start date: ' => 'Дата начала:', + 'End date: ' => 'Дата окончания:', + 'New due date: ' => 'Новая дата исполнения:', + 'Start date changed: ' => 'Дата начала изменена:', + 'Disable private projects' => 'Выключить приватные проекты', + 'Do you really want to remove this custom filter: "%s"?' => 'Вы точно ходите удалить этот пользовательский фильтр: "%s"?', + 'Remove a custom filter' => 'Удалить пользовательский фильтр', + 'User activated successfully.' => 'Пользователь успешно активирован.', + 'Unable to enable this user.' => 'Не удалось включить этого пользователя.', + 'User disabled successfully.' => 'Пользователь был успешно выключен.', + 'Unable to disable this user.' => 'Не удалось выключить пользователя.', + 'All files have been uploaded successfully.' => 'Все файлы были успешно загружены.', + 'View uploaded files' => 'Просмотр загруженных файлов', + 'The maximum allowed file size is %sB.' => 'Максимально допустимый размер файла: %sB.', + 'Choose files again' => 'Выбрать файлы повторно', + 'Drag and drop your files here' => 'Переместите ваши файлы сюда', + 'choose files' => 'выбор файлов', + 'View profile' => 'Просмотр профиля', + 'Two Factor' => 'Двухфакторный', + 'Disable user' => 'Выключить пользователя', + 'Do you really want to disable this user: "%s"?' => 'Вы точно хотите выключить этого пользователя: "%s"?', + 'Enable user' => 'Включить пользователя', + 'Do you really want to enable this user: "%s"?' => 'Вы точно хотите включить этого пользователя: "%s"?', + 'Download' => 'Загрузка', + 'Uploaded: %s' => 'Загружено: %s', + 'Size: %s' => 'Размер: %s', + 'Uploaded by %s' => 'Загружено пользователем: %s', + 'Filename' => 'Имя файла', + 'Size' => 'Размер', ); diff --git a/app/Locale/sr_Latn_RS/translations.php b/app/Locale/sr_Latn_RS/translations.php index 3551c281..3442b970 100644 --- a/app/Locale/sr_Latn_RS/translations.php +++ b/app/Locale/sr_Latn_RS/translations.php @@ -598,8 +598,6 @@ return array( // 'Compact/wide view' => '', // 'No results match:' => '', // 'Currency' => '', - // 'Files' => '', - // 'Images' => '', // 'Private project' => '', // 'AUD - Australian Dollar' => '', // 'CAD - Canadian Dollar' => '', @@ -645,9 +643,6 @@ return array( // 'Test your device' => '', // 'Assign a color when the task is moved to a specific column' => '', // '%s via Kanboard' => '', - // 'uploaded by: %s' => '', - // 'uploaded on: %s' => '', - // 'size: %s' => '', // 'Burndown chart for "%s"' => '', // 'Burndown chart' => '', // 'This chart show the task complexity over the time (Work Remaining).' => '', @@ -1118,4 +1113,29 @@ return array( // 'End date: ' => '', // 'New due date: ' => '', // 'Start date changed: ' => '', + // 'Disable private projects' => '', + // 'Do you really want to remove this custom filter: "%s"?' => '', + // 'Remove a custom filter' => '', + // 'User activated successfully.' => '', + // 'Unable to enable this user.' => '', + // 'User disabled successfully.' => '', + // 'Unable to disable this user.' => '', + // 'All files have been uploaded successfully.' => '', + // 'View uploaded files' => '', + // 'The maximum allowed file size is %sB.' => '', + // 'Choose files again' => '', + // 'Drag and drop your files here' => '', + // 'choose files' => '', + // 'View profile' => '', + // 'Two Factor' => '', + // 'Disable user' => '', + // 'Do you really want to disable this user: "%s"?' => '', + // 'Enable user' => '', + // 'Do you really want to enable this user: "%s"?' => '', + // 'Download' => '', + // 'Uploaded: %s' => '', + // 'Size: %s' => '', + // 'Uploaded by %s' => '', + // 'Filename' => '', + // 'Size' => '', ); diff --git a/app/Locale/sv_SE/translations.php b/app/Locale/sv_SE/translations.php index c0aa883c..96bf437a 100644 --- a/app/Locale/sv_SE/translations.php +++ b/app/Locale/sv_SE/translations.php @@ -598,8 +598,6 @@ return array( 'Compact/wide view' => 'Kompakt/bred vy', 'No results match:' => 'Inga matchande resultat', 'Currency' => 'Valuta', - 'Files' => 'Filer', - 'Images' => 'Bilder', 'Private project' => 'Privat projekt', 'AUD - Australian Dollar' => 'AUD - Australiska dollar', 'CAD - Canadian Dollar' => 'CAD - Kanadensiska dollar', @@ -645,9 +643,6 @@ return array( 'Test your device' => 'Testa din enhet', 'Assign a color when the task is moved to a specific column' => 'Tilldela en färg när uppgiften flyttas till en specifik kolumn', '%s via Kanboard' => '%s via Kanboard', - 'uploaded by: %s' => 'uppladdad av: %s', - 'uploaded on: %s' => 'uppladdad på: %s', - 'size: %s' => 'storlek: %s', 'Burndown chart for "%s"' => 'Burndown diagram för "%s"', 'Burndown chart' => 'Burndown diagram', 'This chart show the task complexity over the time (Work Remaining).' => 'Diagrammet visar uppgiftens svårighet över tid (återstående arbete).', @@ -1118,4 +1113,29 @@ return array( // 'End date: ' => '', // 'New due date: ' => '', // 'Start date changed: ' => '', + // 'Disable private projects' => '', + // 'Do you really want to remove this custom filter: "%s"?' => '', + // 'Remove a custom filter' => '', + // 'User activated successfully.' => '', + // 'Unable to enable this user.' => '', + // 'User disabled successfully.' => '', + // 'Unable to disable this user.' => '', + // 'All files have been uploaded successfully.' => '', + // 'View uploaded files' => '', + // 'The maximum allowed file size is %sB.' => '', + // 'Choose files again' => '', + // 'Drag and drop your files here' => '', + // 'choose files' => '', + // 'View profile' => '', + // 'Two Factor' => '', + // 'Disable user' => '', + // 'Do you really want to disable this user: "%s"?' => '', + // 'Enable user' => '', + // 'Do you really want to enable this user: "%s"?' => '', + // 'Download' => '', + // 'Uploaded: %s' => '', + // 'Size: %s' => '', + // 'Uploaded by %s' => '', + // 'Filename' => '', + // 'Size' => '', ); diff --git a/app/Locale/th_TH/translations.php b/app/Locale/th_TH/translations.php index 1ccc2d90..041cd852 100644 --- a/app/Locale/th_TH/translations.php +++ b/app/Locale/th_TH/translations.php @@ -598,8 +598,6 @@ return array( 'Compact/wide view' => 'พอดี/กว้าง มุมมอง', 'No results match:' => 'ไม่มีผลลัพท์ที่ตรง', 'Currency' => 'สกุลเงิน', - 'Files' => 'ไฟล์', - 'Images' => 'รูปภาพ', 'Private project' => 'โปรเจคส่วนตัว', // 'AUD - Australian Dollar' => '', // 'CAD - Canadian Dollar' => '', @@ -645,9 +643,6 @@ return array( 'Test your device' => 'ทดสอบอุปกรณ์ของคุณ', 'Assign a color when the task is moved to a specific column' => 'กำหนดสีเมื่องานถูกย้ายไปคอลัมน์ที่กำหนดไว้', // '%s via Kanboard' => '', - // 'uploaded by: %s' => '', - // 'uploaded on: %s' => '', - 'size: %s' => 'ขนาด: %s', 'Burndown chart for "%s"' => 'แผนภูมิงานกับเวลา "%s"', 'Burndown chart' => 'แผนภูมิงานกับเวลา', // 'This chart show the task complexity over the time (Work Remaining).' => '', @@ -1118,4 +1113,29 @@ return array( // 'End date: ' => '', // 'New due date: ' => '', // 'Start date changed: ' => '', + // 'Disable private projects' => '', + // 'Do you really want to remove this custom filter: "%s"?' => '', + // 'Remove a custom filter' => '', + // 'User activated successfully.' => '', + // 'Unable to enable this user.' => '', + // 'User disabled successfully.' => '', + // 'Unable to disable this user.' => '', + // 'All files have been uploaded successfully.' => '', + // 'View uploaded files' => '', + // 'The maximum allowed file size is %sB.' => '', + // 'Choose files again' => '', + // 'Drag and drop your files here' => '', + // 'choose files' => '', + // 'View profile' => '', + // 'Two Factor' => '', + // 'Disable user' => '', + // 'Do you really want to disable this user: "%s"?' => '', + // 'Enable user' => '', + // 'Do you really want to enable this user: "%s"?' => '', + // 'Download' => '', + // 'Uploaded: %s' => '', + // 'Size: %s' => '', + // 'Uploaded by %s' => '', + // 'Filename' => '', + // 'Size' => '', ); diff --git a/app/Locale/tr_TR/translations.php b/app/Locale/tr_TR/translations.php index 10153331..f2210dd7 100644 --- a/app/Locale/tr_TR/translations.php +++ b/app/Locale/tr_TR/translations.php @@ -598,8 +598,6 @@ return array( 'Compact/wide view' => 'Ekrana sığdır / Geniş görünüm', 'No results match:' => 'Uygun sonuç bulunamadı', 'Currency' => 'Para birimi', - 'Files' => 'Dosyalar', - 'Images' => 'Resimler', 'Private project' => 'Özel proje', // 'AUD - Australian Dollar' => '', // 'CAD - Canadian Dollar' => '', @@ -645,9 +643,6 @@ return array( 'Test your device' => 'Cihazınızı test edin', 'Assign a color when the task is moved to a specific column' => 'Görev belirli bir sütuna taşındığında rengini değiştir', '%s via Kanboard' => '%s Kanboard ile', - 'uploaded by: %s' => '%s tarafından yüklendi', - 'uploaded on: %s' => '%s tarihinda yüklendi', - 'size: %s' => 'Boyut: %s', 'Burndown chart for "%s"' => '%s icin kalan iş grafiği', 'Burndown chart' => 'Kalan iş grafiği', 'This chart show the task complexity over the time (Work Remaining).' => 'Bu grafik zorluk seviyesini zamana göre gösterir (kalan iş)', @@ -1118,4 +1113,29 @@ return array( // 'End date: ' => '', // 'New due date: ' => '', // 'Start date changed: ' => '', + // 'Disable private projects' => '', + // 'Do you really want to remove this custom filter: "%s"?' => '', + // 'Remove a custom filter' => '', + // 'User activated successfully.' => '', + // 'Unable to enable this user.' => '', + // 'User disabled successfully.' => '', + // 'Unable to disable this user.' => '', + // 'All files have been uploaded successfully.' => '', + // 'View uploaded files' => '', + // 'The maximum allowed file size is %sB.' => '', + // 'Choose files again' => '', + // 'Drag and drop your files here' => '', + // 'choose files' => '', + // 'View profile' => '', + // 'Two Factor' => '', + // 'Disable user' => '', + // 'Do you really want to disable this user: "%s"?' => '', + // 'Enable user' => '', + // 'Do you really want to enable this user: "%s"?' => '', + // 'Download' => '', + // 'Uploaded: %s' => '', + // 'Size: %s' => '', + // 'Uploaded by %s' => '', + // 'Filename' => '', + // 'Size' => '', ); diff --git a/app/Locale/zh_CN/translations.php b/app/Locale/zh_CN/translations.php index acdcdf30..5bcf01dc 100644 --- a/app/Locale/zh_CN/translations.php +++ b/app/Locale/zh_CN/translations.php @@ -598,8 +598,6 @@ return array( 'Compact/wide view' => '紧凑/宽视图', 'No results match:' => '无匹配结果:', 'Currency' => '货币', - 'Files' => '文件', - 'Images' => '图片', 'Private project' => '私人项目', 'AUD - Australian Dollar' => '澳元', 'CAD - Canadian Dollar' => '加元', @@ -645,9 +643,6 @@ return array( 'Test your device' => '测试设备', 'Assign a color when the task is moved to a specific column' => '任务移动到指定栏目时设置颜色', '%s via Kanboard' => '%s 通过KanBoard', - 'uploaded by: %s' => '由: %s 上传', - 'uploaded on: %s' => '上传于 %s', - 'size: %s' => '大小:%s', 'Burndown chart for "%s"' => '燃尽图:"%s"', 'Burndown chart' => '燃尽图', 'This chart show the task complexity over the time (Work Remaining).' => '图表显示任务随时间(剩余时间)的复杂度', @@ -1085,37 +1080,62 @@ return array( 'There is no external link for the moment.' => '当前没有外部关联。', 'Internal links' => '内部关联', 'There is no internal link for the moment.' => '当前没有内部关联。', - // 'Assign to me' => '', - // 'Me' => '', - // 'Do not duplicate anything' => '', - // 'Projects management' => '', - // 'Users management' => '', - // 'Groups management' => '', - // 'Create from another project' => '', - // 'There is no subtask at the moment.' => '', - // 'open' => '', - // 'closed' => '', - // 'Priority:' => '', - // 'Reference:' => '', - // 'Complexity:' => '', - // 'Swimlane:' => '', - // 'Column:' => '', - // 'Position:' => '', - // 'Creator:' => '', - // 'Time estimated:' => '', - // '%s hours' => '', - // 'Time spent:' => '', - // 'Created:' => '', - // 'Modified:' => '', - // 'Completed:' => '', - // 'Started:' => '', - // 'Moved:' => '', - // 'Task #%d' => '', - // 'Sub-tasks' => '', - // 'Date and time format' => '', - // 'Time format' => '', - // 'Start date: ' => '', - // 'End date: ' => '', - // 'New due date: ' => '', - // 'Start date changed: ' => '', + 'Assign to me' => '指派给我', + 'Me' => '我', + 'Do not duplicate anything' => '不再重复', + 'Projects management' => '项目管理', + 'Users management' => '用户管理', + 'Groups management' => '用户组管理', + 'Create from another project' => '从另一个项目中创建', + 'There is no subtask at the moment.' => '当前没有子任务。', + 'open' => '打开', + 'closed' => '已关闭', + 'Priority:' => '优先级:', + 'Reference:' => '引用:', + 'Complexity:' => '复杂度:', + 'Swimlane:' => '里程碑:', + 'Column:' => '列:', + 'Position:' => '位置:', + 'Creator:' => '创建者:', + 'Time estimated:' => '时间已过去:', + '%s hours' => '%s 小时', + 'Time spent:' => '时间消耗:', + 'Created:' => '已创建:', + 'Modified:' => '已修改:', + 'Completed:' => '已完成:', + 'Started:' => '已开始:', + 'Moved:' => '已移走', + 'Task #%d' => '任务#%d', + 'Sub-tasks' => '子任务', + 'Date and time format' => '时间和日期格式', + 'Time format' => '时间格式', + 'Start date: ' => '开始时间:', + 'End date: ' => '结束时间:', + 'New due date: ' => '新超期时间:', + 'Start date changed: ' => '开始时间已改变:', + 'Disable private projects' => '禁用私有项目', + 'Do you really want to remove this custom filter: "%s"?' => '你确定要移除这个自定义过滤器:"%s"?', + 'Remove a custom filter' => '移除自定义过滤器', + 'User activated successfully.' => '用户已激活。', + 'Unable to enable this user.' => '无法启用该用户。', + 'User disabled successfully.' => '用户已禁用。', + 'Unable to disable this user.' => '无法禁用该用户。', + 'All files have been uploaded successfully.' => '所有文件已成功上传。', + 'View uploaded files' => '查看已上传文件', + 'The maximum allowed file size is %sB.' => '最大上传尺寸 %sB', + 'Choose files again' => '重新选择文件', + 'Drag and drop your files here' => '拖放文件到这里', + 'choose files' => '选择文件', + 'View profile' => '查看个人信息', + 'Two Factor' => '双重认证', + 'Disable user' => '禁用用户', + 'Do you really want to disable this user: "%s"?' => '你确定要禁用该用户:"%s"?', + 'Enable user' => '启用用户', + 'Do you really want to enable this user: "%s"?' => '你确定要启用该用户:"%s"?', + 'Download' => '下载', + 'Uploaded: %s' => '上传:%s', + 'Size: %s' => '大小:%s', + 'Uploaded by %s' => '由%s上传', + 'Filename' => '文件名', + 'Size' => '大小', ); diff --git a/app/Model/File.php b/app/Model/File.php index 46fc4bb9..e17ecb2b 100644 --- a/app/Model/File.php +++ b/app/Model/File.php @@ -2,31 +2,44 @@ namespace Kanboard\Model; +use Exception; use Kanboard\Event\FileEvent; use Kanboard\Core\Tool; use Kanboard\Core\ObjectStorage\ObjectStorageException; /** - * File model + * Base File Model * * @package model * @author Frederic Guillot */ -class File extends Base +abstract class File extends Base { /** - * SQL table name - * - * @var string - */ - const TABLE = 'files'; - - /** - * Events + * Get PicoDb query to get all files * - * @var string + * @access protected + * @return \PicoDb\Table */ - const EVENT_CREATE = 'file.create'; + protected function getQuery() + { + return $this->db + ->table(static::TABLE) + ->columns( + static::TABLE.'.id', + static::TABLE.'.name', + static::TABLE.'.path', + static::TABLE.'.is_image', + static::TABLE.'.'.static::FOREIGN_KEY, + static::TABLE.'.date', + static::TABLE.'.user_id', + static::TABLE.'.size', + User::TABLE.'.username', + User::TABLE.'.name as user_name' + ) + ->join(User::TABLE, 'id', 'user_id') + ->asc(static::TABLE.'.name'); + } /** * Get a file by the id @@ -37,146 +50,120 @@ class File extends Base */ public function getById($file_id) { - return $this->db->table(self::TABLE)->eq('id', $file_id)->findOne(); + return $this->db->table(static::TABLE)->eq('id', $file_id)->findOne(); } /** - * Remove a file + * Get all files * * @access public - * @param integer $file_id File id - * @return bool + * @param integer $id + * @return array */ - public function remove($file_id) + public function getAll($id) { - try { - $file = $this->getbyId($file_id); - $this->objectStorage->remove($file['path']); - - if ($file['is_image'] == 1) { - $this->objectStorage->remove($this->getThumbnailPath($file['path'])); - } - - return $this->db->table(self::TABLE)->eq('id', $file['id'])->remove(); - } catch (ObjectStorageException $e) { - $this->logger->error($e->getMessage()); - return false; - } + return $this->getQuery()->eq(static::FOREIGN_KEY, $id)->findAll(); } /** - * Remove all files for a given task + * Get all images * * @access public - * @param integer $task_id Task id - * @return bool + * @param integer $id + * @return array */ - public function removeAll($task_id) + public function getAllImages($id) { - $file_ids = $this->db->table(self::TABLE)->eq('task_id', $task_id)->asc('id')->findAllByColumn('id'); - $results = array(); - - foreach ($file_ids as $file_id) { - $results[] = $this->remove($file_id); - } + return $this->getQuery()->eq(static::FOREIGN_KEY, $id)->eq('is_image', 1)->findAll(); + } - return ! in_array(false, $results, true); + /** + * Get all files without images + * + * @access public + * @param integer $id + * @return array + */ + public function getAllDocuments($id) + { + return $this->getQuery()->eq(static::FOREIGN_KEY, $id)->eq('is_image', 0)->findAll(); } /** * Create a file entry in the database * * @access public - * @param integer $task_id Task id + * @param integer $id Foreign key * @param string $name Filename * @param string $path Path on the disk * @param integer $size File size * @return bool|integer */ - public function create($task_id, $name, $path, $size) + public function create($id, $name, $path, $size) { - $result = $this->db->table(self::TABLE)->save(array( - 'task_id' => $task_id, + $values = array( + static::FOREIGN_KEY => $id, 'name' => substr($name, 0, 255), 'path' => $path, 'is_image' => $this->isImage($name) ? 1 : 0, 'size' => $size, 'user_id' => $this->userSession->getId() ?: 0, 'date' => time(), - )); + ); - if ($result) { - $this->container['dispatcher']->dispatch( - self::EVENT_CREATE, - new FileEvent(array('task_id' => $task_id, 'name' => $name)) - ); + $result = $this->db->table(static::TABLE)->insert($values); - return (int) $this->db->getLastId(); + if ($result) { + $file_id = (int) $this->db->getLastId(); + $event = new FileEvent($values + array('file_id' => $file_id)); + $this->dispatcher->dispatch(static::EVENT_CREATE, $event); + return $file_id; } return false; } /** - * Get PicoDb query to get all files + * Remove all files * * @access public - * @return \PicoDb\Table + * @param integer $id + * @return bool */ - public function getQuery() + public function removeAll($id) { - return $this->db - ->table(self::TABLE) - ->columns( - self::TABLE.'.id', - self::TABLE.'.name', - self::TABLE.'.path', - self::TABLE.'.is_image', - self::TABLE.'.task_id', - self::TABLE.'.date', - self::TABLE.'.user_id', - self::TABLE.'.size', - User::TABLE.'.username', - User::TABLE.'.name as user_name' - ) - ->join(User::TABLE, 'id', 'user_id') - ->asc(self::TABLE.'.name'); - } + $file_ids = $this->db->table(static::TABLE)->eq(static::FOREIGN_KEY, $id)->asc('id')->findAllByColumn('id'); + $results = array(); - /** - * Get all files for a given task - * - * @access public - * @param integer $task_id Task id - * @return array - */ - public function getAll($task_id) - { - return $this->getQuery()->eq('task_id', $task_id)->findAll(); - } + foreach ($file_ids as $file_id) { + $results[] = $this->remove($file_id); + } - /** - * Get all images for a given task - * - * @access public - * @param integer $task_id Task id - * @return array - */ - public function getAllImages($task_id) - { - return $this->getQuery()->eq('task_id', $task_id)->eq('is_image', 1)->findAll(); + return ! in_array(false, $results, true); } /** - * Get all files without images for a given task + * Remove a file * * @access public - * @param integer $task_id Task id - * @return array + * @param integer $file_id File id + * @return bool */ - public function getAllDocuments($task_id) + public function remove($file_id) { - return $this->getQuery()->eq('task_id', $task_id)->eq('is_image', 0)->findAll(); + try { + $file = $this->getById($file_id); + $this->objectStorage->remove($file['path']); + + if ($file['is_image'] == 1) { + $this->objectStorage->remove($this->getThumbnailPath($file['path'])); + } + + return $this->db->table(static::TABLE)->eq('id', $file['id'])->remove(); + } catch (ObjectStorageException $e) { + $this->logger->error($e->getMessage()); + return false; + } } /** @@ -226,103 +213,96 @@ class File extends Base } /** - * Generate the path for a new filename + * Generate the path for a thumbnails * * @access public - * @param integer $project_id Project id - * @param integer $task_id Task id - * @param string $filename Filename + * @param string $key Storage key * @return string */ - public function generatePath($project_id, $task_id, $filename) + public function getThumbnailPath($key) { - return $project_id.DIRECTORY_SEPARATOR.$task_id.DIRECTORY_SEPARATOR.hash('sha1', $filename.time()); + return 'thumbnails'.DIRECTORY_SEPARATOR.$key; } /** - * Generate the path for a thumbnails + * Generate the path for a new filename * * @access public - * @param string $key Storage key + * @param integer $id Foreign key + * @param string $filename Filename * @return string */ - public function getThumbnailPath($key) + public function generatePath($id, $filename) { - return 'thumbnails'.DIRECTORY_SEPARATOR.$key; + return static::PATH_PREFIX.DIRECTORY_SEPARATOR.$id.DIRECTORY_SEPARATOR.hash('sha1', $filename.time()); } /** - * Handle file upload + * Upload multiple files * * @access public - * @param integer $project_id Project id - * @param integer $task_id Task id - * @param string $form_name File form name + * @param integer $id + * @param array $files * @return bool */ - public function uploadFiles($project_id, $task_id, $form_name) + public function uploadFiles($id, array $files) { try { - $file = $this->request->getFileInfo($form_name); - - if (empty($file)) { + if (empty($files)) { return false; } - foreach ($file['error'] as $key => $error) { - if ($error == UPLOAD_ERR_OK && $file['size'][$key] > 0) { - $original_filename = $file['name'][$key]; - $uploaded_filename = $file['tmp_name'][$key]; - $destination_filename = $this->generatePath($project_id, $task_id, $original_filename); - - if ($this->isImage($original_filename)) { - $this->generateThumbnailFromFile($uploaded_filename, $destination_filename); - } + foreach (array_keys($files['error']) as $key) { + $file = array( + 'name' => $files['name'][$key], + 'tmp_name' => $files['tmp_name'][$key], + 'size' => $files['size'][$key], + 'error' => $files['error'][$key], + ); - $this->objectStorage->moveUploadedFile($uploaded_filename, $destination_filename); - - $this->create( - $task_id, - $original_filename, - $destination_filename, - $file['size'][$key] - ); - } + $this->uploadFile($id, $file); } return true; - } catch (ObjectStorageException $e) { + } catch (Exception $e) { $this->logger->error($e->getMessage()); return false; } } /** - * Handle screenshot upload + * Upload a file * * @access public - * @param integer $project_id Project id - * @param integer $task_id Task id - * @param string $blob Base64 encoded image - * @return bool|integer + * @param integer $id + * @param array $file */ - public function uploadScreenshot($project_id, $task_id, $blob) + public function uploadFile($id, array $file) { - $original_filename = e('Screenshot taken %s', $this->helper->dt->datetime(time())).'.png'; - return $this->uploadContent($project_id, $task_id, $original_filename, $blob); + if ($file['error'] == UPLOAD_ERR_OK && $file['size'] > 0) { + $destination_filename = $this->generatePath($id, $file['name']); + + if ($this->isImage($file['name'])) { + $this->generateThumbnailFromFile($file['tmp_name'], $destination_filename); + } + + $this->objectStorage->moveUploadedFile($file['tmp_name'], $destination_filename); + $this->create($id, $file['name'], $destination_filename, $file['size']); + } else { + throw new Exception('File not uploaded: '.var_export($file['error'], true)); + } } /** * Handle file upload (base64 encoded content) * * @access public - * @param integer $project_id Project id - * @param integer $task_id Task id - * @param string $original_filename Filename - * @param string $blob Base64 encoded file + * @param integer $id + * @param string $original_filename + * @param string $blob * @return bool|integer */ - public function uploadContent($project_id, $task_id, $original_filename, $blob) + public function uploadContent($id, $original_filename, $blob) { try { $data = base64_decode($blob); @@ -331,7 +311,7 @@ class File extends Base return false; } - $destination_filename = $this->generatePath($project_id, $task_id, $original_filename); + $destination_filename = $this->generatePath($id, $original_filename); $this->objectStorage->put($destination_filename, $data); if ($this->isImage($original_filename)) { @@ -339,7 +319,7 @@ class File extends Base } return $this->create( - $task_id, + $id, $original_filename, $destination_filename, strlen($data) diff --git a/app/Model/Notification.php b/app/Model/Notification.php index 87c1a796..c252aa31 100644 --- a/app/Model/Notification.php +++ b/app/Model/Notification.php @@ -72,7 +72,7 @@ class Notification extends Base return e('%s updated a comment on the task #%d', $event_author, $event_data['task']['id']); case Comment::EVENT_CREATE: return e('%s commented on the task #%d', $event_author, $event_data['task']['id']); - case File::EVENT_CREATE: + case TaskFile::EVENT_CREATE: return e('%s attached a file to the task #%d', $event_author, $event_data['task']['id']); case Task::EVENT_USER_MENTION: return e('%s mentioned you in the task #%d', $event_author, $event_data['task']['id']); @@ -94,7 +94,7 @@ class Notification extends Base public function getTitleWithoutAuthor($event_name, array $event_data) { switch ($event_name) { - case File::EVENT_CREATE: + case TaskFile::EVENT_CREATE: return e('New attachment on task #%d: %s', $event_data['file']['task_id'], $event_data['file']['name']); case Comment::EVENT_CREATE: return e('New comment on task #%d', $event_data['comment']['task_id']); diff --git a/app/Model/ProjectFile.php b/app/Model/ProjectFile.php new file mode 100644 index 00000000..aa9bf15b --- /dev/null +++ b/app/Model/ProjectFile.php @@ -0,0 +1,40 @@ +<?php + +namespace Kanboard\Model; + +/** + * Project File Model + * + * @package model + * @author Frederic Guillot + */ +class ProjectFile extends File +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'project_has_files'; + + /** + * SQL foreign key + * + * @var string + */ + const FOREIGN_KEY = 'project_id'; + + /** + * Path prefix + * + * @var string + */ + const PATH_PREFIX = 'projects'; + + /** + * Events + * + * @var string + */ + const EVENT_CREATE = 'project.file.create'; +} diff --git a/app/Model/ProjectGroupRole.php b/app/Model/ProjectGroupRole.php index 591b28c6..750ba7fb 100644 --- a/app/Model/ProjectGroupRole.php +++ b/app/Model/ProjectGroupRole.php @@ -106,6 +106,7 @@ class ProjectGroupRole extends Base ->join(GroupMember::TABLE, 'user_id', 'id', User::TABLE) ->join(self::TABLE, 'group_id', 'group_id', GroupMember::TABLE) ->eq(self::TABLE.'.project_id', $project_id) + ->eq(User::TABLE.'.is_active', 1) ->in(self::TABLE.'.role', array(Role::PROJECT_MANAGER, Role::PROJECT_MEMBER)) ->asc(User::TABLE.'.username') ->findAll(); diff --git a/app/Model/ProjectPermission.php b/app/Model/ProjectPermission.php index cea62e13..db1573ae 100644 --- a/app/Model/ProjectPermission.php +++ b/app/Model/ProjectPermission.php @@ -107,7 +107,8 @@ class ProjectPermission extends Base */ public function isAssignable($project_id, $user_id) { - return in_array($this->projectUserRole->getUserRole($project_id, $user_id), array(Role::PROJECT_MEMBER, Role::PROJECT_MANAGER)); + return $this->user->isActive($user_id) && + in_array($this->projectUserRole->getUserRole($project_id, $user_id), array(Role::PROJECT_MEMBER, Role::PROJECT_MANAGER)); } /** diff --git a/app/Model/ProjectUserRole.php b/app/Model/ProjectUserRole.php index 8149a253..56da679c 100644 --- a/app/Model/ProjectUserRole.php +++ b/app/Model/ProjectUserRole.php @@ -152,13 +152,14 @@ class ProjectUserRole extends Base public function getAssignableUsers($project_id) { if ($this->projectPermission->isEverybodyAllowed($project_id)) { - return $this->user->getList(); + return $this->user->getActiveUsersList(); } $userMembers = $this->db->table(self::TABLE) ->columns(User::TABLE.'.id', User::TABLE.'.username', User::TABLE.'.name') ->join(User::TABLE, 'id', 'user_id') - ->eq('project_id', $project_id) + ->eq(User::TABLE.'.is_active', 1) + ->eq(self::TABLE.'.project_id', $project_id) ->in(self::TABLE.'.role', array(Role::PROJECT_MANAGER, Role::PROJECT_MEMBER)) ->findAll(); diff --git a/app/Model/Task.php b/app/Model/Task.php index 38fdd0d5..f8b41b9f 100644 --- a/app/Model/Task.php +++ b/app/Model/Task.php @@ -92,7 +92,7 @@ class Task extends Base return false; } - $this->file->removeAll($task_id); + $this->taskFile->removeAll($task_id); return $this->db->table(self::TABLE)->eq('id', $task_id)->remove(); } diff --git a/app/Model/TaskFile.php b/app/Model/TaskFile.php new file mode 100644 index 00000000..45a3b97f --- /dev/null +++ b/app/Model/TaskFile.php @@ -0,0 +1,54 @@ +<?php + +namespace Kanboard\Model; + +/** + * Task File Model + * + * @package model + * @author Frederic Guillot + */ +class TaskFile extends File +{ + /** + * SQL table name + * + * @var string + */ + const TABLE = 'task_has_files'; + + /** + * SQL foreign key + * + * @var string + */ + const FOREIGN_KEY = 'task_id'; + + /** + * Path prefix + * + * @var string + */ + const PATH_PREFIX = 'tasks'; + + /** + * Events + * + * @var string + */ + const EVENT_CREATE = 'task.file.create'; + + /** + * Handle screenshot upload + * + * @access public + * @param integer $task_id Task id + * @param string $blob Base64 encoded image + * @return bool|integer + */ + public function uploadScreenshot($task_id, $blob) + { + $original_filename = e('Screenshot taken %s', $this->helper->dt->datetime(time())).'.png'; + return $this->uploadContent($task_id, $original_filename, $blob); + } +} diff --git a/app/Model/TaskFinder.php b/app/Model/TaskFinder.php index 4d673097..95ddc12f 100644 --- a/app/Model/TaskFinder.php +++ b/app/Model/TaskFinder.php @@ -89,7 +89,7 @@ class TaskFinder extends Base ->table(Task::TABLE) ->columns( '(SELECT COUNT(*) FROM '.Comment::TABLE.' WHERE task_id=tasks.id) AS nb_comments', - '(SELECT COUNT(*) FROM '.File::TABLE.' WHERE task_id=tasks.id) AS nb_files', + '(SELECT COUNT(*) FROM '.TaskFile::TABLE.' WHERE task_id=tasks.id) AS nb_files', '(SELECT COUNT(*) FROM '.Subtask::TABLE.' WHERE '.Subtask::TABLE.'.task_id=tasks.id) AS nb_subtasks', '(SELECT COUNT(*) FROM '.Subtask::TABLE.' WHERE '.Subtask::TABLE.'.task_id=tasks.id AND status=2) AS nb_completed_subtasks', '(SELECT COUNT(*) FROM '.TaskLink::TABLE.' WHERE '.TaskLink::TABLE.'.task_id = tasks.id) AS nb_links', diff --git a/app/Model/User.php b/app/Model/User.php index dd622207..e2494c4c 100644 --- a/app/Model/User.php +++ b/app/Model/User.php @@ -41,6 +41,18 @@ class User extends Base } /** + * Return true if the user is active + * + * @access public + * @param integer $user_id User id + * @return boolean + */ + public function isActive($user_id) + { + return $this->db->table(self::TABLE)->eq('id', $user_id)->eq('is_active', 1)->exists(); + } + + /** * Get query to fetch all users * * @access public @@ -193,9 +205,9 @@ class User extends Base * @param boolean $prepend Prepend "All users" * @return array */ - public function getList($prepend = false) + public function getActiveUsersList($prepend = false) { - $users = $this->db->table(self::TABLE)->columns('id', 'username', 'name')->findAll(); + $users = $this->db->table(self::TABLE)->eq('is_active', 1)->columns('id', 'username', 'name')->findAll(); $listing = $this->prepareList($users); if ($prepend) { @@ -281,6 +293,30 @@ class User extends Base } /** + * Disable a specific user + * + * @access public + * @param integer $user_id + * @return boolean + */ + public function disable($user_id) + { + return $this->db->table(self::TABLE)->eq('id', $user_id)->update(array('is_active' => 0)); + } + + /** + * Enable a specific user + * + * @access public + * @param integer $user_id + * @return boolean + */ + public function enable($user_id) + { + return $this->db->table(self::TABLE)->eq('id', $user_id)->update(array('is_active' => 1)); + } + + /** * Remove a specific user * * @access public diff --git a/app/Notification/Mail.php b/app/Notification/Mail.php index d05dbdf2..c924fb50 100644 --- a/app/Notification/Mail.php +++ b/app/Notification/Mail.php @@ -4,7 +4,7 @@ namespace Kanboard\Notification; use Kanboard\Core\Base; use Kanboard\Model\Task; -use Kanboard\Model\File; +use Kanboard\Model\TaskFile; use Kanboard\Model\Comment; use Kanboard\Model\Subtask; @@ -82,7 +82,7 @@ class Mail extends Base implements NotificationInterface public function getMailSubject($event_name, array $event_data) { switch ($event_name) { - case File::EVENT_CREATE: + case TaskFile::EVENT_CREATE: $subject = $this->getStandardMailSubject(e('New attachment'), $event_data); break; case Comment::EVENT_CREATE: diff --git a/app/Schema/Mysql.php b/app/Schema/Mysql.php index 036958b6..c85dde6f 100644 --- a/app/Schema/Mysql.php +++ b/app/Schema/Mysql.php @@ -6,7 +6,32 @@ use PDO; use Kanboard\Core\Security\Token; use Kanboard\Core\Security\Role; -const VERSION = 104; +const VERSION = 106; + +function version_106(PDO $pdo) +{ + $pdo->exec('RENAME TABLE files TO task_has_files'); + + $pdo->exec(" + CREATE TABLE project_has_files ( + `id` INT NOT NULL AUTO_INCREMENT, + `project_id` INT NOT NULL, + `name` VARCHAR(255) NOT NULL, + `path` VARCHAR(255) NOT NULL, + `is_image` TINYINT(1) DEFAULT 0, + `size` INT DEFAULT 0 NOT NULL, + `user_id` INT DEFAULT 0 NOT NULL, + `date` INT DEFAULT 0 NOT NULL, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE, + PRIMARY KEY(id) + ) ENGINE=InnoDB CHARSET=utf8" + ); +} + +function version_105(PDO $pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN is_active TINYINT(1) DEFAULT 1"); +} function version_104(PDO $pdo) { diff --git a/app/Schema/Postgres.php b/app/Schema/Postgres.php index 363b633b..dc8de510 100644 --- a/app/Schema/Postgres.php +++ b/app/Schema/Postgres.php @@ -6,13 +6,37 @@ use PDO; use Kanboard\Core\Security\Token; use Kanboard\Core\Security\Role; -const VERSION = 84; +const VERSION = 86; + +function version_86(PDO $pdo) +{ + $pdo->exec('ALTER TABLE files RENAME TO task_has_files'); + + $pdo->exec(" + CREATE TABLE project_has_files ( + id SERIAL PRIMARY KEY, + project_id INTEGER NOT NULL, + name VARCHAR(255) NOT NULL, + path VARCHAR(255) NOT NULL, + is_image BOOLEAN DEFAULT '0', + size INTEGER DEFAULT 0 NOT NULL, + user_id INTEGER DEFAULT 0 NOT NULL, + date INTEGER DEFAULT 0 NOT NULL, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE + )" + ); +} + +function version_85(PDO $pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN is_active BOOLEAN DEFAULT '1'"); +} function version_84(PDO $pdo) { $pdo->exec(" CREATE TABLE task_has_external_links ( - id SERIAL, + id SERIAL PRIMARY KEY, link_type VARCHAR(100) NOT NULL, dependency VARCHAR(100) NOT NULL, title VARCHAR(255) NOT NULL, diff --git a/app/Schema/Sqlite.php b/app/Schema/Sqlite.php index bc701341..e88f621f 100644 --- a/app/Schema/Sqlite.php +++ b/app/Schema/Sqlite.php @@ -6,7 +6,31 @@ use Kanboard\Core\Security\Token; use Kanboard\Core\Security\Role; use PDO; -const VERSION = 96; +const VERSION = 98; + +function version_98(PDO $pdo) +{ + $pdo->exec('ALTER TABLE files RENAME TO task_has_files'); + + $pdo->exec(" + CREATE TABLE project_has_files ( + id INTEGER PRIMARY KEY, + project_id INTEGER NOT NULL, + name TEXT COLLATE NOCASE NOT NULL, + path TEXT NOT NULL, + is_image INTEGER DEFAULT 0, + size INTEGER DEFAULT 0 NOT NULL, + user_id INTEGER DEFAULT 0 NOT NULL, + date INTEGER DEFAULT 0 NOT NULL, + FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE + )" + ); +} + +function version_97(PDO $pdo) +{ + $pdo->exec("ALTER TABLE users ADD COLUMN is_active INTEGER DEFAULT 1"); +} function version_96(PDO $pdo) { diff --git a/app/ServiceProvider/AuthenticationProvider.php b/app/ServiceProvider/AuthenticationProvider.php index 9b5cdbe9..cc7b9302 100644 --- a/app/ServiceProvider/AuthenticationProvider.php +++ b/app/ServiceProvider/AuthenticationProvider.php @@ -76,7 +76,7 @@ class AuthenticationProvider implements ServiceProviderInterface $acl->add('Comment', '*', Role::PROJECT_MEMBER); $acl->add('Customfilter', '*', Role::PROJECT_MEMBER); $acl->add('Export', '*', Role::PROJECT_MANAGER); - $acl->add('File', array('screenshot', 'create', 'save', 'remove', 'confirm'), Role::PROJECT_MEMBER); + $acl->add('TaskFile', array('screenshot', 'create', 'save', 'remove', 'confirm'), Role::PROJECT_MEMBER); $acl->add('Gantt', '*', Role::PROJECT_MANAGER); $acl->add('Project', array('share', 'integrations', 'notifications', 'duplicate', 'disable', 'enable', 'remove'), Role::PROJECT_MANAGER); $acl->add('ProjectPermission', '*', Role::PROJECT_MANAGER); @@ -134,7 +134,8 @@ class AuthenticationProvider implements ServiceProviderInterface $acl->add('Projectuser', '*', Role::APP_MANAGER); $acl->add('Twofactor', 'disable', Role::APP_ADMIN); $acl->add('UserImport', '*', Role::APP_ADMIN); - $acl->add('User', array('index', 'create', 'save', 'authentication', 'remove'), Role::APP_ADMIN); + $acl->add('User', array('index', 'create', 'save', 'authentication'), Role::APP_ADMIN); + $acl->add('UserStatus', '*', Role::APP_ADMIN); return $acl; } diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php index 61a4c512..a4fa1ff2 100644 --- a/app/ServiceProvider/ClassProvider.php +++ b/app/ServiceProvider/ClassProvider.php @@ -31,7 +31,6 @@ class ClassProvider implements ServiceProviderInterface 'Config', 'Currency', 'CustomFilter', - 'File', 'Group', 'GroupMember', 'LastLogin', @@ -40,6 +39,7 @@ class ClassProvider implements ServiceProviderInterface 'OverdueNotification', 'PasswordReset', 'Project', + 'ProjectFile', 'ProjectActivity', 'ProjectDuplication', 'ProjectDailyColumnStats', @@ -63,6 +63,7 @@ class ClassProvider implements ServiceProviderInterface 'TaskExport', 'TaskExternalLink', 'TaskFinder', + 'TaskFile', 'TaskFilter', 'TaskLink', 'TaskModification', diff --git a/app/ServiceProvider/GroupProvider.php b/app/ServiceProvider/GroupProvider.php index dff4b23a..b222b218 100644 --- a/app/ServiceProvider/GroupProvider.php +++ b/app/ServiceProvider/GroupProvider.php @@ -26,7 +26,10 @@ class GroupProvider implements ServiceProviderInterface public function register(Container $container) { $container['groupManager'] = new GroupManager; - $container['groupManager']->register(new DatabaseBackendGroupProvider($container)); + + if (DB_GROUP_PROVIDER) { + $container['groupManager']->register(new DatabaseBackendGroupProvider($container)); + } if (LDAP_AUTH && LDAP_GROUP_PROVIDER) { $container['groupManager']->register(new LdapBackendGroupProvider($container)); diff --git a/app/ServiceProvider/RouteProvider.php b/app/ServiceProvider/RouteProvider.php index 683ea1c1..c723140e 100644 --- a/app/ServiceProvider/RouteProvider.php +++ b/app/ServiceProvider/RouteProvider.php @@ -94,8 +94,6 @@ class RouteProvider implements ServiceProviderInterface $container['route']->addRoute('public/task/:task_id/:token', 'task', 'readonly'); $container['route']->addRoute('project/:project_id/task/:task_id/activity', 'activity', 'task'); - $container['route']->addRoute('project/:project_id/task/:task_id/screenshot', 'file', 'screenshot'); - $container['route']->addRoute('project/:project_id/task/:task_id/upload', 'file', 'create'); $container['route']->addRoute('project/:project_id/task/:task_id/transitions', 'task', 'transitions'); $container['route']->addRoute('project/:project_id/task/:task_id/analytics', 'task', 'analytics'); $container['route']->addRoute('project/:project_id/task/:task_id/subtasks', 'subtask', 'show'); @@ -157,7 +155,6 @@ class RouteProvider implements ServiceProviderInterface $container['route']->addRoute('user/:user_id/accounts', 'user', 'external'); $container['route']->addRoute('user/:user_id/integrations', 'user', 'integrations'); $container['route']->addRoute('user/:user_id/authentication', 'user', 'authentication'); - $container['route']->addRoute('user/:user_id/remove', 'user', 'remove'); $container['route']->addRoute('user/:user_id/2fa', 'twofactor', 'index'); // Groups diff --git a/app/Subscriber/NotificationSubscriber.php b/app/Subscriber/NotificationSubscriber.php index 07660050..651b8a96 100644 --- a/app/Subscriber/NotificationSubscriber.php +++ b/app/Subscriber/NotificationSubscriber.php @@ -6,7 +6,7 @@ use Kanboard\Event\GenericEvent; use Kanboard\Model\Task; use Kanboard\Model\Comment; use Kanboard\Model\Subtask; -use Kanboard\Model\File; +use Kanboard\Model\TaskFile; use Symfony\Component\EventDispatcher\EventSubscriberInterface; class NotificationSubscriber extends BaseSubscriber implements EventSubscriberInterface @@ -28,7 +28,7 @@ class NotificationSubscriber extends BaseSubscriber implements EventSubscriberIn Comment::EVENT_CREATE => 'handleEvent', Comment::EVENT_UPDATE => 'handleEvent', Comment::EVENT_USER_MENTION => 'handleEvent', - File::EVENT_CREATE => 'handleEvent', + TaskFile::EVENT_CREATE => 'handleEvent', ); } diff --git a/app/Template/app/filters_helper.php b/app/Template/app/filters_helper.php index e4cbb942..c16c2251 100644 --- a/app/Template/app/filters_helper.php +++ b/app/Template/app/filters_helper.php @@ -1,6 +1,6 @@ <?= $this->hook->render('template:app:filters-helper:before', isset($project) ? array('project' => $project) : array()) ?> -<div class="dropdown filters"> - <i class="fa fa-caret-down"></i> <a href="#" class="dropdown-menu"><?= t('Filters') ?></a> +<div class="dropdown"> + <a href="#" class="dropdown-menu dropdown-menu-link-icon" title="<?= t('Default filters') ?>"><i class="fa fa-filter fa-fw"></i><i class="fa fa-caret-down"></i></a> <ul> <li><a href="#" class="filter-helper filter-reset" data-filter="<?= isset($reset) ? $reset : '' ?>" title="<?= t('Keyboard shortcut: "%s"', 'r') ?>"><?= t('Reset filters') ?></a></li> <li><a href="#" class="filter-helper" data-filter="status:open assignee:me"><?= t('My tasks') ?></a></li> diff --git a/app/Template/app/layout.php b/app/Template/app/layout.php index 0550cef4..200cb0d7 100644 --- a/app/Template/app/layout.php +++ b/app/Template/app/layout.php @@ -7,10 +7,12 @@ <?= $this->url->link(t('New project'), 'ProjectCreation', 'create', array(), false, 'popover') ?> </li> <?php endif ?> + <?php if ($this->app->config('disable_private_project', 0) == 0): ?> <li> <i class="fa fa-lock fa-fw"></i> <?= $this->url->link(t('New private project'), 'ProjectCreation', 'createPrivate', array(), false, 'popover') ?> </li> + <?php endif ?> <li> <i class="fa fa-search fa-fw"></i> <?= $this->url->link(t('Search'), 'search', 'index') ?> diff --git a/app/Template/app/overview.php b/app/Template/app/overview.php index ebb3b412..0b354791 100644 --- a/app/Template/app/overview.php +++ b/app/Template/app/overview.php @@ -1,11 +1,10 @@ -<div class="search"> +<div class="filter-box"> <form method="get" action="<?= $this->url->dir() ?>" class="search"> <?= $this->form->hidden('controller', array('controller' => 'search')) ?> <?= $this->form->hidden('action', array('action' => 'index')) ?> <?= $this->form->text('search', array(), array(), array('placeholder="'.t('Search').'"'), 'form-input-large') ?> + <?= $this->render('app/filters_helper') ?> </form> - - <?= $this->render('app/filters_helper') ?> </div> <?= $this->render('app/projects', array('paginator' => $project_paginator, 'user' => $user)) ?> diff --git a/app/Template/board/tooltip_files.php b/app/Template/board/tooltip_files.php index 407309b3..4fa14b57 100644 --- a/app/Template/board/tooltip_files.php +++ b/app/Template/board/tooltip_files.php @@ -8,9 +8,9 @@ </tr> <tr> <td> - <i class="fa fa-download fa-fw"></i><?= $this->url->link(t('download'), 'file', 'download', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id'])) ?> + <i class="fa fa-download fa-fw"></i><?= $this->url->link(t('download'), 'TaskFile', 'download', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id'])) ?> <?php if ($file['is_image'] == 1): ?> - <i class="fa fa-eye"></i> <?= $this->url->link(t('open file'), 'file', 'open', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id']), false, 'popover') ?> + <i class="fa fa-eye"></i> <?= $this->url->link(t('open file'), 'TaskFile', 'open', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id']), false, 'popover') ?> <?php endif ?> </td> </tr> diff --git a/app/Template/board/view_private.php b/app/Template/board/view_private.php index 63d261f6..b5e38c66 100644 --- a/app/Template/board/view_private.php +++ b/app/Template/board/view_private.php @@ -1,6 +1,6 @@ <section id="main"> - <?= $this->render('project/filters', array( + <?= $this->render('project_header/header', array( 'project' => $project, 'filters' => $filters, 'categories_list' => $categories_list, diff --git a/app/Template/calendar/show.php b/app/Template/calendar/show.php index d74e945e..7085b51e 100644 --- a/app/Template/calendar/show.php +++ b/app/Template/calendar/show.php @@ -1,5 +1,5 @@ <section id="main"> - <?= $this->render('project/filters', array( + <?= $this->render('project_header/header', array( 'project' => $project, 'filters' => $filters, )) ?> diff --git a/app/Template/config/project.php b/app/Template/config/project.php index a212f65f..1d32a14f 100644 --- a/app/Template/config/project.php +++ b/app/Template/config/project.php @@ -16,6 +16,7 @@ <?= $this->form->text('project_categories', $values, $errors) ?> <p class="form-help"><?= t('Example: "Bug, Feature Request, Improvement"') ?></p> + <?= $this->form->checkbox('disable_private_project', t('Disable private projects'), 1, isset($values['disable_private_project']) && $values['disable_private_project'] == 1) ?> <?= $this->form->checkbox('subtask_restriction', t('Allow only one subtask in progress at the same time for a user'), 1, $values['subtask_restriction'] == 1) ?> <?= $this->form->checkbox('subtask_time_tracking', t('Trigger automatically subtask time tracking'), 1, $values['subtask_time_tracking'] == 1) ?> <?= $this->form->checkbox('cfd_include_closed_tasks', t('Include closed tasks in the cumulative flow diagram'), 1, $values['cfd_include_closed_tasks'] == 1) ?> diff --git a/app/Template/custom_filter/index.php b/app/Template/custom_filter/index.php index 2994ae25..28b6a878 100644 --- a/app/Template/custom_filter/index.php +++ b/app/Template/custom_filter/index.php @@ -36,7 +36,7 @@ <div class="dropdown"> <a href="#" class="dropdown-menu dropdown-menu-link-icon"><i class="fa fa-cog fa-fw"></i><i class="fa fa-caret-down"></i></a> <ul> - <li><?= $this->url->link(t('Remove'), 'customfilter', 'remove', array('project_id' => $filter['project_id'], 'filter_id' => $filter['id']), true, 'popover') ?></li> + <li><?= $this->url->link(t('Remove'), 'customfilter', 'confirm', array('project_id' => $filter['project_id'], 'filter_id' => $filter['id']), false, 'popover') ?></li> <li><?= $this->url->link(t('Edit'), 'customfilter', 'edit', array('project_id' => $filter['project_id'], 'filter_id' => $filter['id']), false, 'popover') ?></li> </ul> </div> diff --git a/app/Template/custom_filter/remove.php b/app/Template/custom_filter/remove.php new file mode 100644 index 00000000..d4c67a2b --- /dev/null +++ b/app/Template/custom_filter/remove.php @@ -0,0 +1,17 @@ +<section id="main"> + <div class="page-header"> + <h2><?= t('Remove a custom filter') ?></h2> + </div> + + <div class="confirm"> + <p class="alert alert-info"> + <?= t('Do you really want to remove this custom filter: "%s"?', $filter['name']) ?> + </p> + + <div class="form-actions"> + <?= $this->url->link(t('Yes'), 'customfilter', 'remove', array('project_id' => $project['id'], 'filter_id' => $filter['id']), true, 'btn btn-red') ?> + <?= t('or') ?> + <?= $this->url->link(t('cancel'), 'customfilter', 'index', array('project_id' => $project['id']), false, 'close-popover') ?> + </div> + </div> +</section> diff --git a/app/Template/event/file_create.php b/app/Template/event/task_file_create.php index 1a36bc8f..1a36bc8f 100644 --- a/app/Template/event/file_create.php +++ b/app/Template/event/task_file_create.php diff --git a/app/Template/file/new.php b/app/Template/file/new.php deleted file mode 100644 index 1702638d..00000000 --- a/app/Template/file/new.php +++ /dev/null @@ -1,14 +0,0 @@ -<div class="page-header"> - <h2><?= t('Attach a document') ?></h2> -</div> - -<form action="<?= $this->url->href('file', 'save', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>" method="post" enctype="multipart/form-data"> - <?= $this->form->csrf() ?> - <input type="file" name="files[]" multiple /> - <div class="form-help"><?= t('Maximum size: ') ?><?= is_integer($max_size) ? $this->text->bytes($max_size) : $max_size ?></div> - <div class="form-actions"> - <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"> - <?= t('or') ?> - <?= $this->url->link(t('cancel'), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'close-popover') ?> - </div> -</form>
\ No newline at end of file diff --git a/app/Template/file/open.php b/app/Template/file/open.php deleted file mode 100644 index 3df012b6..00000000 --- a/app/Template/file/open.php +++ /dev/null @@ -1,6 +0,0 @@ -<div class="page-header"> - <h2><?= $this->e($file['name']) ?></h2> - <div class="task-file-viewer"> - <img src="<?= $this->url->href('file', 'image', array('file_id' => $file['id'], 'project_id' => $task['project_id'], 'task_id' => $file['task_id'])) ?>" alt="<?= $this->e($file['name']) ?>"/> - </div> -</div>
\ No newline at end of file diff --git a/app/Template/file/show.php b/app/Template/file/show.php deleted file mode 100644 index 7a4d38b4..00000000 --- a/app/Template/file/show.php +++ /dev/null @@ -1,58 +0,0 @@ -<?php if (! empty($files) || ! empty($images)): ?> -<div id="attachments" class="task-show-section"> - - <div class="page-header"> - <h2><?= t('Attachments') ?></h2> - </div> - <?php if (! empty($images)): ?> - <h3><?= t('Images') ?></h3> - <ul class="task-show-images"> - <?php foreach ($images as $file): ?> - <li> - <div class="img_container"> - <img src="<?= $this->url->href('file', 'thumbnail', array('file_id' => $file['id'], 'project_id' => $task['project_id'], 'task_id' => $file['task_id'])) ?>" alt="<?= $this->e($file['name']) ?>"/> - </div> - <p> - <?= $this->e($file['name']) ?> - <span class="tooltip" title='<?= t('uploaded by: %s', $file['user_name'] ?: $file['username']).'<br>'.t('uploaded on: %s', $this->dt->datetime($file['date'])).'<br>'.t('size: %s', $this->text->bytes($file['size'])) ?>'> - <i class="fa fa-info-circle"></i> - </span> - </p> - <span class="task-show-file-actions task-show-image-actions"> - <i class="fa fa-eye"></i> <?= $this->url->link(t('open file'), 'file', 'open', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id']), false, 'popover') ?> - <?php if ($this->user->hasProjectAccess('file', 'remove', $task['project_id'])): ?> - <i class="fa fa-trash"></i> <?= $this->url->link(t('remove'), 'file', 'confirm', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id'])) ?> - <?php endif ?> - <i class="fa fa-download"></i> <?= $this->url->link(t('download'), 'file', 'download', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id'])) ?> - </span> - </li> - <?php endforeach ?> - </ul> - <?php endif ?> - - <?php if (! empty($files)): ?> - <h3><?= t('Files') ?></h3> - <table class="task-show-file-table"> - <?php foreach ($files as $file): ?> - <tr> - <td><i class="fa <?= $this->file->icon($file['name']) ?> fa-fw"></i></td> - <td> - <?= $this->e($file['name']) ?> - <span class="tooltip" title='<?= t('uploaded by: %s', $file['user_name'] ?: $file['username']).'<br>'.t('uploaded on: %s', $this->dt->datetime($file['date'])).'<br>'.t('size: %s', $this->text->bytes($file['size'])) ?>'> - <i class="fa fa-info-circle"></i> - </span> - </td> - <td> - <span class="task-show-file-actions"> - <?php if ($this->user->hasProjectAccess('file', 'remove', $task['project_id'])): ?> - <i class="fa fa-trash"></i> <?= $this->url->link(t('remove'), 'file', 'confirm', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id'])) ?> - <?php endif ?> - <i class="fa fa-download"></i> <?= $this->url->link(t('download'), 'file', 'download', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id'])) ?> - </span> - </td> - </tr> - <?php endforeach ?> - </table> - <?php endif ?> -</div> -<?php endif ?>
\ No newline at end of file diff --git a/app/Template/gantt/project.php b/app/Template/gantt/project.php index 1face3b8..fe193c2b 100644 --- a/app/Template/gantt/project.php +++ b/app/Template/gantt/project.php @@ -1,5 +1,5 @@ <section id="main"> - <?= $this->render('project/filters', array( + <?= $this->render('project_header/header', array( 'project' => $project, 'filters' => $filters, 'users_list' => $users_list, diff --git a/app/Template/header.php b/app/Template/header.php index fd9ff24d..a945411e 100644 --- a/app/Template/header.php +++ b/app/Template/header.php @@ -44,9 +44,11 @@ <?php if ($this->user->hasAccess('ProjectCreation', 'create')): ?> <li><i class="fa fa-plus fa-fw"></i><?= $this->url->link(t('New project'), 'ProjectCreation', 'create', array(), false, 'popover') ?></li> <?php endif ?> + <?php if ($this->app->config('disable_private_project', 0) == 0): ?> <li> <i class="fa fa-lock fa-fw"></i><?= $this->url->link(t('New private project'), 'ProjectCreation', 'createPrivate', array(), false, 'popover') ?> </li> + <?php endif ?> </ul> </div> @@ -84,13 +86,15 @@ <i class="fa fa-life-ring fa-fw"></i> <?= $this->url->link(t('Documentation'), 'doc', 'show') ?> </li> - <li> - <i class="fa fa-sign-out fa-fw"></i> - <?= $this->url->link(t('Logout'), 'auth', 'logout') ?> - </li> + <?php if (! DISABLE_LOGOUT): ?> + <li> + <i class="fa fa-sign-out fa-fw"></i> + <?= $this->url->link(t('Logout'), 'auth', 'logout') ?> + </li> + <?php endif ?> </ul> </div> </li> </ul> </nav> -</header>
\ No newline at end of file +</header> diff --git a/app/Template/listing/show.php b/app/Template/listing/show.php index e7aa5947..9a5992e3 100644 --- a/app/Template/listing/show.php +++ b/app/Template/listing/show.php @@ -1,7 +1,10 @@ <section id="main"> - <?= $this->render('project/filters', array( + <?= $this->render('project_header/header', array( 'project' => $project, 'filters' => $filters, + 'custom_filters_list' => $custom_filters_list, + 'users_list' => $users_list, + 'categories_list' => $categories_list, )) ?> <?php if ($paginator->isEmpty()): ?> diff --git a/app/Template/notification/file_create.php b/app/Template/notification/task_file_create.php index 63f7d1b8..63f7d1b8 100644 --- a/app/Template/notification/file_create.php +++ b/app/Template/notification/task_file_create.php diff --git a/app/Template/project/filters.php b/app/Template/project/filters.php deleted file mode 100644 index 78794e1c..00000000 --- a/app/Template/project/filters.php +++ /dev/null @@ -1,106 +0,0 @@ -<div class="page-header"> - <?= $this->hook->render('template:project:header:before', array('project' => $project)) ?> - - <div class="dropdown"> - <i class="fa fa-caret-down"></i> <a href="#" class="dropdown-menu"><?= t('Actions') ?></a> - <ul> - <?php if (isset($is_board)): ?> - <li> - <span class="filter-display-mode" <?= $this->board->isCollapsed($project['id']) ? '' : 'style="display: none;"' ?>> - <i class="fa fa-expand fa-fw"></i> - <?= $this->url->link(t('Expand tasks'), 'board', 'expand', array('project_id' => $project['id']), false, 'board-display-mode', t('Keyboard shortcut: "%s"', 's')) ?> - </span> - <span class="filter-display-mode" <?= $this->board->isCollapsed($project['id']) ? 'style="display: none;"' : '' ?>> - <i class="fa fa-compress fa-fw"></i> - <?= $this->url->link(t('Collapse tasks'), 'board', 'collapse', array('project_id' => $project['id']), false, 'board-display-mode', t('Keyboard shortcut: "%s"', 's')) ?> - </span> - </li> - <li> - <span class="filter-compact"> - <i class="fa fa-th fa-fw"></i> <a href="#" class="filter-toggle-scrolling" title="<?= t('Keyboard shortcut: "%s"', 'c') ?>"><?= t('Compact view') ?></a> - </span> - <span class="filter-wide" style="display: none"> - <i class="fa fa-arrows-h fa-fw"></i> <a href="#" class="filter-toggle-scrolling" title="<?= t('Keyboard shortcut: "%s"', 'c') ?>"><?= t('Horizontal scrolling') ?></a> - </span> - </li> - <li> - <span class="filter-max-height" style="display: none"> - <i class="fa fa-arrows-v fa-fw"></i> <a href="#" class="filter-toggle-height"><?= t('Set maximum column height') ?></a> - </span> - <span class="filter-min-height"> - <i class="fa fa-arrows-v fa-fw"></i> <a href="#" class="filter-toggle-height"><?= t('Remove maximum column height') ?></a> - </span> - </li> - <?php endif ?> - <?= $this->render('project/dropdown', array('project' => $project)) ?> - </ul> - </div> - <ul class="views"> - <li <?= $filters['controller'] === 'board' ? 'class="active"' : '' ?>> - <i class="fa fa-th fa-fw"></i> - <?= $this->url->link(t('Board'), 'board', 'show', array('project_id' => $project['id'], 'search' => $filters['search']), false, 'view-board', t('Keyboard shortcut: "%s"', 'v b')) ?> - </li> - <li <?= $filters['controller'] === 'calendar' ? 'class="active"' : '' ?>> - <i class="fa fa-calendar fa-fw"></i> - <?= $this->url->link(t('Calendar'), 'calendar', 'show', array('project_id' => $project['id'], 'search' => $filters['search']), false, 'view-calendar', t('Keyboard shortcut: "%s"', 'v c')) ?> - </li> - <li <?= $filters['controller'] === 'listing' ? 'class="active"' : '' ?>> - <i class="fa fa-list fa-fw"></i> - <?= $this->url->link(t('List'), 'listing', 'show', array('project_id' => $project['id'], 'search' => $filters['search']), false, 'view-listing', t('Keyboard shortcut: "%s"', 'v l')) ?> - </li> - <?php if ($this->user->hasProjectAccess('gantt', 'project', $project['id'])): ?> - <li <?= $filters['controller'] === 'gantt' ? 'class="active"' : '' ?>> - <i class="fa fa-sliders fa-fw"></i> - <?= $this->url->link(t('Gantt'), 'gantt', 'project', array('project_id' => $project['id'], 'search' => $filters['search']), false, 'view-gantt', t('Keyboard shortcut: "%s"', 'v g')) ?> - </li> - <?php endif ?> - </ul> - <form method="get" action="<?= $this->url->dir() ?>" class="search"> - <?= $this->form->hidden('controller', $filters) ?> - <?= $this->form->hidden('action', $filters) ?> - <?= $this->form->hidden('project_id', $filters) ?> - <?= $this->form->text('search', $filters, array(), array('placeholder="'.t('Filter').'"'), 'form-input-large') ?> - </form> - - <div class="filter-dropdowns"> - - <?= $this->render('app/filters_helper', array('reset' => 'status:open', 'project' => $project)) ?> - - <?php if (isset($custom_filters_list) && ! empty($custom_filters_list)): ?> - <div class="dropdown filters"> - <i class="fa fa-caret-down"></i> <a href="#" class="dropdown-menu"><?= t('My filters') ?></a> - <ul> - <?php foreach ($custom_filters_list as $filter): ?> - <li><a href="#" class="filter-helper" data-<?php if ($filter['append']): ?><?= 'append-' ?><?php endif ?>filter='<?= $this->e($filter['filter']) ?>'><?= $this->e($filter['name']) ?></a></li> - <?php endforeach ?> - </ul> - </div> - <?php endif ?> - - <?php if (isset($users_list)): ?> - <div class="dropdown filters"> - <i class="fa fa-caret-down"></i> <a href="#" class="dropdown-menu"><?= t('Users') ?></a> - <ul> - <li><a href="#" class="filter-helper" data-append-filter="assignee:nobody"><?= t('Not assigned') ?></a></li> - <?php foreach ($users_list as $user): ?> - <li><a href="#" class="filter-helper" data-append-filter='assignee:"<?= $this->e($user) ?>"'><?= $this->e($user) ?></a></li> - <?php endforeach ?> - </ul> - </div> - <?php endif ?> - - <?php if (isset($categories_list) && ! empty($categories_list)): ?> - <div class="dropdown filters"> - <i class="fa fa-caret-down"></i> <a href="#" class="dropdown-menu"><?= t('Categories') ?></a> - <ul> - <li><a href="#" class="filter-helper" data-append-filter="category:none"><?= t('No category') ?></a></li> - <?php foreach ($categories_list as $category): ?> - <li><a href="#" class="filter-helper" data-append-filter='category:"<?= $this->e($category) ?>"'><?= $this->e($category) ?></a></li> - <?php endforeach ?> - </ul> - </div> - <?php endif ?> - </div> - - <?= $this->hook->render('template:project:header:after', array('project' => $project)) ?> -</div>
\ No newline at end of file diff --git a/app/Template/project_header/dropdown.php b/app/Template/project_header/dropdown.php new file mode 100644 index 00000000..bbc033bf --- /dev/null +++ b/app/Template/project_header/dropdown.php @@ -0,0 +1,34 @@ +<div class="dropdown"> + <i class="fa fa-caret-down"></i> <a href="#" class="dropdown-menu"><?= t('Actions') ?></a> + <ul> + <?php if ($is_board): ?> + <li> + <span class="filter-display-mode" <?= $this->board->isCollapsed($project['id']) ? '' : 'style="display: none;"' ?>> + <i class="fa fa-expand fa-fw"></i> + <?= $this->url->link(t('Expand tasks'), 'board', 'expand', array('project_id' => $project['id']), false, 'board-display-mode', t('Keyboard shortcut: "%s"', 's')) ?> + </span> + <span class="filter-display-mode" <?= $this->board->isCollapsed($project['id']) ? 'style="display: none;"' : '' ?>> + <i class="fa fa-compress fa-fw"></i> + <?= $this->url->link(t('Collapse tasks'), 'board', 'collapse', array('project_id' => $project['id']), false, 'board-display-mode', t('Keyboard shortcut: "%s"', 's')) ?> + </span> + </li> + <li> + <span class="filter-compact"> + <i class="fa fa-th fa-fw"></i> <a href="#" class="filter-toggle-scrolling" title="<?= t('Keyboard shortcut: "%s"', 'c') ?>"><?= t('Compact view') ?></a> + </span> + <span class="filter-wide" style="display: none"> + <i class="fa fa-arrows-h fa-fw"></i> <a href="#" class="filter-toggle-scrolling" title="<?= t('Keyboard shortcut: "%s"', 'c') ?>"><?= t('Horizontal scrolling') ?></a> + </span> + </li> + <li> + <span class="filter-max-height" style="display: none"> + <i class="fa fa-arrows-v fa-fw"></i> <a href="#" class="filter-toggle-height"><?= t('Set maximum column height') ?></a> + </span> + <span class="filter-min-height"> + <i class="fa fa-arrows-v fa-fw"></i> <a href="#" class="filter-toggle-height"><?= t('Remove maximum column height') ?></a> + </span> + </li> + <?php endif ?> + <?= $this->render('project/dropdown', array('project' => $project)) ?> + </ul> +</div>
\ No newline at end of file diff --git a/app/Template/project_header/header.php b/app/Template/project_header/header.php new file mode 100644 index 00000000..f6e5af9e --- /dev/null +++ b/app/Template/project_header/header.php @@ -0,0 +1,15 @@ +<div class="project-header"> + <?= $this->hook->render('template:project:header:before', array('project' => $project)) ?> + + <?= $this->render('project_header/dropdown', array('project' => $project, 'is_board' => isset($is_board))) ?> + <?= $this->render('project_header/views', array('project' => $project, 'filters' => $filters)) ?> + <?= $this->render('project_header/search', array( + 'project' => $project, + 'filters' => $filters, + 'custom_filters_list' => isset($custom_filters_list) ? $custom_filters_list : array(), + 'users_list' => isset($users_list) ? $users_list : array(), + 'categories_list' => isset($categories_list) ? $categories_list : array(), + )) ?> + + <?= $this->hook->render('template:project:header:after', array('project' => $project)) ?> +</div>
\ No newline at end of file diff --git a/app/Template/project_header/search.php b/app/Template/project_header/search.php new file mode 100644 index 00000000..2b2a2c39 --- /dev/null +++ b/app/Template/project_header/search.php @@ -0,0 +1,45 @@ +<div class="filter-box"> + <form method="get" action="<?= $this->url->dir() ?>" class="search"> + <?= $this->form->hidden('controller', $filters) ?> + <?= $this->form->hidden('action', $filters) ?> + <?= $this->form->hidden('project_id', $filters) ?> + <?= $this->form->text('search', $filters, array(), array('placeholder="'.t('Filter').'"')) ?> + + <?= $this->render('app/filters_helper', array('reset' => 'status:open', 'project' => $project)) ?> + + <?php if (isset($custom_filters_list) && ! empty($custom_filters_list)): ?> + <div class="dropdown"> + <a href="#" class="dropdown-menu dropdown-menu-link-icon" title="<?= t('Custom filters') ?>"><i class="fa fa-bookmark fa-fw"></i><i class="fa fa-caret-down"></i></a> + <ul> + <?php foreach ($custom_filters_list as $filter): ?> + <li><a href="#" class="filter-helper" data-<?php if ($filter['append']): ?><?= 'append-' ?><?php endif ?>filter='<?= $this->e($filter['filter']) ?>'><?= $this->e($filter['name']) ?></a></li> + <?php endforeach ?> + </ul> + </div> + <?php endif ?> + + <?php if (isset($users_list)): ?> + <div class="dropdown"> + <a href="#" class="dropdown-menu dropdown-menu-link-icon" title="<?= t('User filters') ?>"><i class="fa fa-users fa-fw"></i> <i class="fa fa-caret-down"></i></a> + <ul> + <li><a href="#" class="filter-helper" data-append-filter="assignee:nobody"><?= t('Not assigned') ?></a></li> + <?php foreach ($users_list as $user): ?> + <li><a href="#" class="filter-helper" data-append-filter='assignee:"<?= $this->e($user) ?>"'><?= $this->e($user) ?></a></li> + <?php endforeach ?> + </ul> + </div> + <?php endif ?> + + <?php if (isset($categories_list) && ! empty($categories_list)): ?> + <div class="dropdown"> + <a href="#" class="dropdown-menu dropdown-menu-link-icon" title="<?= t('Category filters') ?>"><i class="fa fa-tags fa-fw"></i><i class="fa fa-caret-down"></i></a> + <ul> + <li><a href="#" class="filter-helper" data-append-filter="category:none"><?= t('No category') ?></a></li> + <?php foreach ($categories_list as $category): ?> + <li><a href="#" class="filter-helper" data-append-filter='category:"<?= $this->e($category) ?>"'><?= $this->e($category) ?></a></li> + <?php endforeach ?> + </ul> + </div> + <?php endif ?> + </form> +</div> diff --git a/app/Template/project_header/views.php b/app/Template/project_header/views.php new file mode 100644 index 00000000..1219a213 --- /dev/null +++ b/app/Template/project_header/views.php @@ -0,0 +1,20 @@ +<ul class="views"> + <li <?= $filters['controller'] === 'board' ? 'class="active"' : '' ?>> + <i class="fa fa-th fa-fw"></i> + <?= $this->url->link(t('Board'), 'board', 'show', array('project_id' => $project['id'], 'search' => $filters['search']), false, 'view-board', t('Keyboard shortcut: "%s"', 'v b')) ?> + </li> + <li <?= $filters['controller'] === 'calendar' ? 'class="active"' : '' ?>> + <i class="fa fa-calendar fa-fw"></i> + <?= $this->url->link(t('Calendar'), 'calendar', 'show', array('project_id' => $project['id'], 'search' => $filters['search']), false, 'view-calendar', t('Keyboard shortcut: "%s"', 'v c')) ?> + </li> + <li <?= $filters['controller'] === 'listing' ? 'class="active"' : '' ?>> + <i class="fa fa-list fa-fw"></i> + <?= $this->url->link(t('List'), 'listing', 'show', array('project_id' => $project['id'], 'search' => $filters['search']), false, 'view-listing', t('Keyboard shortcut: "%s"', 'v l')) ?> + </li> + <?php if ($this->user->hasProjectAccess('gantt', 'project', $project['id'])): ?> + <li <?= $filters['controller'] === 'gantt' ? 'class="active"' : '' ?>> + <i class="fa fa-sliders fa-fw"></i> + <?= $this->url->link(t('Gantt'), 'gantt', 'project', array('project_id' => $project['id'], 'search' => $filters['search']), false, 'view-gantt', t('Keyboard shortcut: "%s"', 'v g')) ?> + </li> + <?php endif ?> +</ul>
\ No newline at end of file diff --git a/app/Template/search/index.php b/app/Template/search/index.php index 329c072a..9231a6f3 100644 --- a/app/Template/search/index.php +++ b/app/Template/search/index.php @@ -8,14 +8,13 @@ </ul> </div> - <div class="search"> + <div class="filter-box"> <form method="get" action="<?= $this->url->dir() ?>" class="search"> <?= $this->form->hidden('controller', $values) ?> <?= $this->form->hidden('action', $values) ?> <?= $this->form->text('search', $values, array(), array(empty($values['search']) ? 'autofocus' : '', 'placeholder="'.t('Search').'"'), 'form-input-large') ?> + <?= $this->render('app/filters_helper') ?> </form> - - <?= $this->render('app/filters_helper') ?> </div> <?php if (empty($values['search'])): ?> diff --git a/app/Template/task/menu.php b/app/Template/task/menu.php index e997a6ad..cddd930a 100644 --- a/app/Template/task/menu.php +++ b/app/Template/task/menu.php @@ -38,11 +38,11 @@ </li> <li> <i class="fa fa-file fa-fw"></i> - <?= $this->url->link(t('Attach a document'), 'file', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?> + <?= $this->url->link(t('Attach a document'), 'TaskFile', 'create', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?> </li> <li> <i class="fa fa-camera fa-fw"></i> - <?= $this->url->link(t('Add a screenshot'), 'file', 'screenshot', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?> + <?= $this->url->link(t('Add a screenshot'), 'TaskFile', 'screenshot', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'popover') ?> </li> <li> <i class="fa fa-files-o fa-fw"></i> diff --git a/app/Template/task/show.php b/app/Template/task/show.php index 43906a81..a32232ae 100644 --- a/app/Template/task/show.php +++ b/app/Template/task/show.php @@ -25,7 +25,7 @@ <?= $this->render('task/time_tracking_summary', array('task' => $task)) ?> -<?= $this->render('file/show', array( +<?= $this->render('task_file/show', array( 'task' => $task, 'files' => $files, 'images' => $images diff --git a/app/Template/task_file/new.php b/app/Template/task_file/new.php new file mode 100644 index 00000000..f03ce8dc --- /dev/null +++ b/app/Template/task_file/new.php @@ -0,0 +1,33 @@ +<div class="page-header"> + <h2><?= t('Attach a document') ?></h2> +</div> +<div id="file-done" style="display:none"> + <p class="alert alert-success"> + <?= t('All files have been uploaded successfully.') ?> + <?= $this->url->link(t('View uploaded files'), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> + </p> +</div> + +<div id="file-error-max-size" style="display:none"> + <p class="alert alert-error"> + <?= t('The maximum allowed file size is %sB.', $this->text->bytes($max_size)) ?> + <a href="#" id="file-browser"><?= t('Choose files again') ?></a> + </p> +</div> + +<div + id="file-dropzone" + data-max-size="<?= $max_size ?>" + data-url="<?= $this->url->href('TaskFile', 'save', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>"> + <div id="file-dropzone-inner"> + <?= t('Drag and drop your files here') ?> <?= t('or') ?> <a href="#" id="file-browser"><?= t('choose files') ?></a> + </div> +</div> + +<input type="file" name="files[]" multiple style="display:none" id="file-form-element"> + +<div class="form-actions"> + <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue" id="file-upload-button" disabled> + <?= t('or') ?> + <?= $this->url->link(t('cancel'), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'close-popover') ?> +</div> diff --git a/app/Template/task_file/open.php b/app/Template/task_file/open.php new file mode 100644 index 00000000..e3721b59 --- /dev/null +++ b/app/Template/task_file/open.php @@ -0,0 +1,6 @@ +<div class="page-header"> + <h2><?= $this->e($file['name']) ?></h2> + <div class="task-file-viewer"> + <img src="<?= $this->url->href('TaskFile', 'image', array('file_id' => $file['id'], 'project_id' => $task['project_id'], 'task_id' => $file['task_id'])) ?>" alt="<?= $this->e($file['name']) ?>"/> + </div> +</div>
\ No newline at end of file diff --git a/app/Template/file/remove.php b/app/Template/task_file/remove.php index 37f648eb..5e6c83f2 100644 --- a/app/Template/file/remove.php +++ b/app/Template/task_file/remove.php @@ -8,8 +8,8 @@ </p> <div class="form-actions"> - <?= $this->url->link(t('Yes'), 'file', 'remove', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id']), true, 'btn btn-red') ?> + <?= $this->url->link(t('Yes'), 'TaskFile', 'remove', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id']), true, 'btn btn-red') ?> <?= t('or') ?> - <?= $this->url->link(t('cancel'), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?> + <?= $this->url->link(t('cancel'), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id']), false, 'close-popover') ?> </div> </div>
\ No newline at end of file diff --git a/app/Template/file/screenshot.php b/app/Template/task_file/screenshot.php index 58b93ac3..72214362 100644 --- a/app/Template/file/screenshot.php +++ b/app/Template/task_file/screenshot.php @@ -6,7 +6,7 @@ <p id="screenshot-inner"><?= t('Take a screenshot and press CTRL+V or ⌘+V to paste here.') ?></p> </div> -<form class="popover-form" action="<?= $this->url->href('file', 'screenshot', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>" method="post"> +<form class="popover-form" action="<?= $this->url->href('TaskFile', 'screenshot', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>" method="post"> <input type="hidden" name="screenshot"/> <?= $this->form->csrf() ?> <div class="form-actions"> diff --git a/app/Template/task_file/show.php b/app/Template/task_file/show.php new file mode 100644 index 00000000..98f26c33 --- /dev/null +++ b/app/Template/task_file/show.php @@ -0,0 +1,84 @@ +<?php if (! empty($files) || ! empty($images)): ?> +<div id="attachments" class="task-show-section"> + + <div class="page-header"> + <h2><?= t('Attachments') ?></h2> + </div> + <?php if (! empty($images)): ?> + <div class="file-thumbnails"> + <?php foreach ($images as $file): ?> + <div class="file-thumbnail"> + <a href="<?= $this->url->href('TaskFile', 'open', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id'])) ?>" class="popover"><img src="<?= $this->url->href('TaskFile', 'thumbnail', array('file_id' => $file['id'], 'project_id' => $task['project_id'], 'task_id' => $file['task_id'])) ?>" title="<?= $this->e($file['name']) ?>" alt="<?= $this->e($file['name']) ?>"></a> + <div class="file-thumbnail-content"> + <div class="file-thumbnail-title"> + <div class="dropdown"> + <a href="#" class="dropdown-menu dropdown-menu-link-text"><?= $this->e($file['name']) ?> <i class="fa fa-caret-down"></i></a> + <ul> + <?php if ($this->user->hasProjectAccess('TaskFile', 'remove', $task['project_id'])): ?> + <li> + <i class="fa fa-trash fa-fw"></i> + <?= $this->url->link(t('Remove'), 'TaskFile', 'confirm', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id']), false, 'popover') ?> + </li> + <?php endif ?> + <li> + <i class="fa fa-download fa-fw"></i> + <?= $this->url->link(t('Download'), 'TaskFile', 'download', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id'])) ?> + </li> + </ul> + </div> + </div> + <div class="file-thumbnail-description"> + <span class="tooltip" title='<?= t('Uploaded: %s', $this->dt->datetime($file['date'])).'<br>'.t('Size: %s', $this->text->bytes($file['size'])) ?>'> + <i class="fa fa-info-circle"></i> + </span> + <?= t('Uploaded by %s', $file['user_name'] ?: $file['username']) ?> + </div> + </div> + </div> + <?php endforeach ?> + </div> + <?php endif ?> + + <?php if (! empty($files)): ?> + <table class="table-stripped"> + <tr> + <th><?= t('Filename') ?></th> + <th><?= t('Creator') ?></th> + <th><?= t('Date') ?></th> + <th><?= t('Size') ?></th> + </tr> + <?php foreach ($files as $file): ?> + <tr> + <td> + <i class="fa <?= $this->file->icon($file['name']) ?> fa-fw"></i> + <div class="dropdown"> + <a href="#" class="dropdown-menu dropdown-menu-link-text"><?= $this->e($file['name']) ?> <i class="fa fa-caret-down"></i></a> + <ul> + <?php if ($this->user->hasProjectAccess('TaskFile', 'remove', $task['project_id'])): ?> + <li> + <i class="fa fa-trash fa-fw"></i> + <?= $this->url->link(t('Remove'), 'TaskFile', 'confirm', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id']), false, 'popover') ?> + </li> + <?php endif ?> + <li> + <i class="fa fa-download fa-fw"></i> + <?= $this->url->link(t('Download'), 'TaskFile', 'download', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'file_id' => $file['id'])) ?> + </li> + </ul> + </div> + </td> + <td> + <?= $this->e($file['user_name'] ?: $file['username']) ?> + </td> + <td> + <?= $this->dt->date($file['date']) ?> + </td> + <td> + <?= $this->text->bytes($file['size']) ?> + </td> + </tr> + <?php endforeach ?> + </table> + <?php endif ?> +</div> +<?php endif ?>
\ No newline at end of file diff --git a/app/Template/user/dropdown.php b/app/Template/user/dropdown.php new file mode 100644 index 00000000..b74ed6e0 --- /dev/null +++ b/app/Template/user/dropdown.php @@ -0,0 +1,27 @@ +<div class="dropdown"> + <a href="#" class="dropdown-menu dropdown-menu-link-icon"><i class="fa fa-cog fa-fw"></i><i class="fa fa-caret-down"></i></a> + <ul> + <li> + <i class="fa fa-user fa-fw"></i> + <?= $this->url->link(t('View profile'), 'user', 'show', array('user_id' => $user['id'])) ?> + </li> + <?php if ($user['is_active'] == 1 && $this->user->hasAccess('UserStatus', 'disable') && ! $this->user->isCurrentUser($user['id'])): ?> + <li> + <i class="fa fa-times fa-fw"></i> + <?= $this->url->link(t('Disable'), 'UserStatus', 'confirmDisable', array('user_id' => $user['id']), false, 'popover') ?> + </li> + <?php endif ?> + <?php if ($user['is_active'] == 0 && $this->user->hasAccess('UserStatus', 'enable') && ! $this->user->isCurrentUser($user['id'])): ?> + <li> + <i class="fa fa-check-square-o fa-fw"></i> + <?= $this->url->link(t('Enable'), 'UserStatus', 'confirmEnable', array('user_id' => $user['id']), false, 'popover') ?> + </li> + <?php endif ?> + <?php if ($this->user->hasAccess('UserStatus', 'remove') && ! $this->user->isCurrentUser($user['id'])): ?> + <li> + <i class="fa fa-trash-o fa-fw"></i> + <?= $this->url->link(t('Remove'), 'UserStatus', 'confirmRemove', array('user_id' => $user['id']), false, 'popover') ?> + </li> + <?php endif ?> + </ul> +</div>
\ No newline at end of file diff --git a/app/Template/user/index.php b/app/Template/user/index.php index cb7416d6..494c1465 100644 --- a/app/Template/user/index.php +++ b/app/Template/user/index.php @@ -12,23 +12,21 @@ <?php if ($paginator->isEmpty()): ?> <p class="alert"><?= t('No user') ?></p> <?php else: ?> - <table> + <table class="table-stripped"> <tr> - <th><?= $paginator->order(t('Id'), 'id') ?></th> - <th><?= $paginator->order(t('Username'), 'username') ?></th> - <th><?= $paginator->order(t('Name'), 'name') ?></th> - <th><?= $paginator->order(t('Email'), 'email') ?></th> - <th><?= $paginator->order(t('Role'), 'role') ?></th> - <th><?= $paginator->order(t('Two factor authentication'), 'twofactor_activated') ?></th> - <th><?= $paginator->order(t('Notifications'), 'notifications_enabled') ?></th> - <th><?= $paginator->order(t('Account type'), 'is_ldap_user') ?></th> + <th class="column-18"><?= $paginator->order(t('Username'), 'username') ?></th> + <th class="column-18"><?= $paginator->order(t('Name'), 'name') ?></th> + <th class="column-15"><?= $paginator->order(t('Email'), 'email') ?></th> + <th class="column-15"><?= $paginator->order(t('Role'), 'role') ?></th> + <th class="column-10"><?= $paginator->order(t('Two Factor'), 'twofactor_activated') ?></th> + <th class="column-10"><?= $paginator->order(t('Account type'), 'is_ldap_user') ?></th> + <th class="column-10"><?= $paginator->order(t('Status'), 'is_active') ?></th> + <th class="column-5"><?= t('Actions') ?></th> </tr> <?php foreach ($paginator->getCollection() as $user): ?> <tr> <td> - <?= $this->url->link('#'.$user['id'], 'user', 'show', array('user_id' => $user['id'])) ?> - </td> - <td> + <?= '#'.$user['id'] ?> <?= $this->url->link($this->e($user['username']), 'user', 'show', array('user_id' => $user['id'])) ?> </td> <td> @@ -44,14 +42,17 @@ <?= $user['twofactor_activated'] ? t('Yes') : t('No') ?> </td> <td> - <?php if ($user['notifications_enabled'] == 1): ?> - <?= t('Enabled') ?> + <?= $user['is_ldap_user'] ? t('Remote') : t('Local') ?> + </td> + <td> + <?php if ($user['is_active'] == 1): ?> + <?= t('Active') ?> <?php else: ?> - <?= t('Disabled') ?> + <?= t('Inactive') ?> <?php endif ?> </td> <td> - <?= $user['is_ldap_user'] ? t('Remote') : t('Local') ?> + <?= $this->render('user/dropdown', array('user' => $user)) ?> </td> </tr> <?php endforeach ?> diff --git a/app/Template/user/show.php b/app/Template/user/show.php index 89c6b36b..9da56666 100644 --- a/app/Template/user/show.php +++ b/app/Template/user/show.php @@ -5,6 +5,7 @@ <li><?= t('Username:') ?> <strong><?= $this->e($user['username']) ?></strong></li> <li><?= t('Name:') ?> <strong><?= $this->e($user['name']) ?: t('None') ?></strong></li> <li><?= t('Email:') ?> <strong><?= $this->e($user['email']) ?: t('None') ?></strong></li> + <li><?= t('Status:') ?> <strong><?= $user['is_active'] ? t('Active') : t('Inactive') ?></strong></li> </ul> <div class="page-header"> diff --git a/app/Template/user/sidebar.php b/app/Template/user/sidebar.php index 7e367443..2faf8220 100644 --- a/app/Template/user/sidebar.php +++ b/app/Template/user/sidebar.php @@ -1,9 +1,11 @@ <div class="sidebar"> <h2><?= t('Information') ?></h2> <ul> - <li <?= $this->app->checkMenuSelection('user', 'show') ?>> - <?= $this->url->link(t('Summary'), 'user', 'show', array('user_id' => $user['id'])) ?> - </li> + <?php if ($this->user->hasAccess('user', 'show')): ?> + <li <?= $this->app->checkMenuSelection('user', 'show') ?>> + <?= $this->url->link(t('Summary'), 'user', 'show', array('user_id' => $user['id'])) ?> + </li> + <?php endif ?> <?php if ($this->user->isAdmin()): ?> <li> <?= $this->url->link(t('User dashboard'), 'app', 'index', array('user_id' => $user['id'])) ?> @@ -30,9 +32,12 @@ <h2><?= t('Actions') ?></h2> <ul> <?php if ($this->user->isAdmin() || $this->user->isCurrentUser($user['id'])): ?> - <li <?= $this->app->checkMenuSelection('user', 'edit') ?>> - <?= $this->url->link(t('Edit profile'), 'user', 'edit', array('user_id' => $user['id'])) ?> - </li> + + <?php if ($this->user->hasAccess('user', 'edit')): ?> + <li <?= $this->app->checkMenuSelection('user', 'edit') ?>> + <?= $this->url->link(t('Edit profile'), 'user', 'edit', array('user_id' => $user['id'])) ?> + </li> + <?php endif ?> <?php if ($user['is_ldap_user'] == 0): ?> <li <?= $this->app->checkMenuSelection('user', 'password') ?>> @@ -71,11 +76,5 @@ <?php endif ?> <?= $this->hook->render('template:user:sidebar:actions', array('user' => $user)) ?> - - <?php if ($this->user->hasAccess('user', 'remove') && ! $this->user->isCurrentUser($user['id'])): ?> - <li <?= $this->app->checkMenuSelection('user', 'remove') ?>> - <?= $this->url->link(t('Remove'), 'user', 'remove', array('user_id' => $user['id'])) ?> - </li> - <?php endif ?> </ul> </div>
\ No newline at end of file diff --git a/app/Template/user_status/disable.php b/app/Template/user_status/disable.php new file mode 100644 index 00000000..90d8c757 --- /dev/null +++ b/app/Template/user_status/disable.php @@ -0,0 +1,13 @@ +<div class="page-header"> + <h2><?= t('Disable user') ?></h2> +</div> + +<div class="confirm"> + <p class="alert alert-info"><?= t('Do you really want to disable this user: "%s"?', $user['name'] ?: $user['username']) ?></p> + + <div class="form-actions"> + <?= $this->url->link(t('Yes'), 'UserStatus', 'disable', array('user_id' => $user['id']), true, 'btn btn-red') ?> + <?= t('or') ?> + <?= $this->url->link(t('cancel'), 'user', 'index', array(), false, 'close-popover') ?> + </div> +</div> diff --git a/app/Template/user_status/enable.php b/app/Template/user_status/enable.php new file mode 100644 index 00000000..cd3d4947 --- /dev/null +++ b/app/Template/user_status/enable.php @@ -0,0 +1,13 @@ +<div class="page-header"> + <h2><?= t('Enable user') ?></h2> +</div> + +<div class="confirm"> + <p class="alert alert-info"><?= t('Do you really want to enable this user: "%s"?', $user['name'] ?: $user['username']) ?></p> + + <div class="form-actions"> + <?= $this->url->link(t('Yes'), 'UserStatus', 'enable', array('user_id' => $user['id']), true, 'btn btn-red') ?> + <?= t('or') ?> + <?= $this->url->link(t('cancel'), 'user', 'index', array(), false, 'close-popover') ?> + </div> +</div> diff --git a/app/Template/user/remove.php b/app/Template/user_status/remove.php index 810a3a3f..cd5c09a6 100644 --- a/app/Template/user/remove.php +++ b/app/Template/user_status/remove.php @@ -6,8 +6,8 @@ <p class="alert alert-info"><?= t('Do you really want to remove this user: "%s"?', $user['name'] ?: $user['username']) ?></p> <div class="form-actions"> - <?= $this->url->link(t('Yes'), 'user', 'remove', array('user_id' => $user['id'], 'confirmation' => 'yes'), true, 'btn btn-red') ?> + <?= $this->url->link(t('Yes'), 'UserStatus', 'remove', array('user_id' => $user['id']), true, 'btn btn-red') ?> <?= t('or') ?> - <?= $this->url->link(t('cancel'), 'user', 'show', array('user_id' => $user['id'])) ?> + <?= $this->url->link(t('cancel'), 'user', 'index', array(), false, 'close-popover') ?> </div> -</div>
\ No newline at end of file +</div> diff --git a/app/constants.php b/app/constants.php index be9bb6a7..011fa784 100644 --- a/app/constants.php +++ b/app/constants.php @@ -23,6 +23,9 @@ defined('DB_HOSTNAME') or define('DB_HOSTNAME', 'localhost'); defined('DB_NAME') or define('DB_NAME', 'kanboard'); defined('DB_PORT') or define('DB_PORT', null); +// Database backend group provider +defined('DB_GROUP_PROVIDER') or define('DB_GROUP_PROVIDER', true); + // LDAP configuration defined('LDAP_AUTH') or define('LDAP_AUTH', false); defined('LDAP_SERVER') or define('LDAP_SERVER', ''); @@ -94,6 +97,9 @@ defined('ENABLE_URL_REWRITE') or define('ENABLE_URL_REWRITE', isset($_SERVER['HT // Hide login form defined('HIDE_LOGIN_FORM') or define('HIDE_LOGIN_FORM', false); +// Disabling logout (for external SSO authentication) +defined('DISABLE_LOGOUT') or define('DISABLE_LOGOUT', false); + // Bruteforce protection defined('BRUTEFORCE_CAPTCHA') or define('BRUTEFORCE_CAPTCHA', 3); defined('BRUTEFORCE_LOCKDOWN') or define('BRUTEFORCE_LOCKDOWN', 6); diff --git a/assets/css/app.css b/assets/css/app.css index 29dd4726..63f4540b 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -18,4 +18,4 @@ * Font Awesome 4.5.0 by @davegandy - http://fontawesome.io - @fontawesome * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.5.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.5.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.5.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.5.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.5.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.5.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"} -.c3 svg{font:10px sans-serif}.c3 line,.c3 path{fill:none;stroke:#000}.c3 text{-webkit-user-select:none;-moz-user-select:none;user-select:none}.c3-bars path,.c3-event-rect,.c3-legend-item-tile,.c3-xgrid-focus,.c3-ygrid{shape-rendering:crispEdges}.c3-chart-arc path{stroke:#fff}.c3-chart-arc text{fill:#fff;font-size:13px}.c3-grid line{stroke:#aaa}.c3-grid text{fill:#aaa}.c3-xgrid,.c3-ygrid{stroke-dasharray:3 3}.c3-text.c3-empty{fill:gray;font-size:2em}.c3-line{stroke-width:1px}.c3-circle._expanded_{stroke-width:1px;stroke:#fff}.c3-selected-circle{fill:#fff;stroke-width:2px}.c3-bar{stroke-width:0}.c3-bar._expanded_{fill-opacity:.75}.c3-target.c3-focused{opacity:1}.c3-target.c3-focused path.c3-line,.c3-target.c3-focused path.c3-step{stroke-width:2px}.c3-target.c3-defocused{opacity:.3!important}.c3-region{fill:#4682b4;fill-opacity:.1}.c3-brush .extent{fill-opacity:.1}.c3-legend-item{font-size:12px}.c3-legend-item-hidden{opacity:.15}.c3-legend-background{opacity:.75;fill:#fff;stroke:#d3d3d3;stroke-width:1}.c3-tooltip-container{z-index:10}.c3-tooltip{border-collapse:collapse;border-spacing:0;background-color:#fff;empty-cells:show;-webkit-box-shadow:7px 7px 12px -9px #777;-moz-box-shadow:7px 7px 12px -9px #777;box-shadow:7px 7px 12px -9px #777;opacity:.9}.c3-tooltip tr{border:1px solid #CCC}.c3-tooltip th{background-color:#aaa;font-size:14px;padding:2px 5px;text-align:left;color:#FFF}.c3-tooltip td{font-size:13px;padding:3px 6px;background-color:#fff;border-left:1px dotted #999}.c3-tooltip td>span{display:inline-block;width:10px;height:10px;margin-right:6px}.c3-tooltip td.value{text-align:right}.c3-area{stroke-width:0;opacity:.2}.c3-chart-arcs-title{dominant-baseline:middle;font-size:1.3em}.c3-chart-arcs .c3-chart-arcs-background{fill:#e0e0e0;stroke:none}.c3-chart-arcs .c3-chart-arcs-gauge-unit{fill:#000;font-size:16px}.c3-chart-arcs .c3-chart-arcs-gauge-max,.c3-chart-arcs .c3-chart-arcs-gauge-min{fill:#777}.c3-chart-arc .c3-gauge-value{fill:#000}li,ul,ol,table,tr,td,th,p,blockquote,body{margin:0;padding:0;font-size:100%}body{margin-left:10px;margin-right:10px;padding-bottom:10px;color:#333;font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;text-rendering:optimizeLegibility}.page{clear:both}ul.no-bullet li{list-style-type:none;margin-left:0}.pull-right{text-align:right}hr{border:0;height:0;border-top:1px solid rgba(0,0,0,0.1);border-bottom:1px solid rgba(255,255,255,0.3)}.chosen-select{min-height:27px}.avatar{float:left;margin-right:10px}#ui-datepicker-div{font-size:.8em}#app-loading-icon{position:fixed;right:3px;bottom:3px}.web-notification-icon{color:#36c}.web-notification-icon:focus,.web-notification-icon:hover{color:#000}.smaller{font-size:.85em}a{color:#36c;border:0}a:focus{outline:0;color:#df5353;text-decoration:none;border:1px dotted #aaa}a:hover{color:#333;text-decoration:none}h1,h2,h3{font-weight:normal;color:#333}h2{font-size:1.3em;margin-bottom:10px}h3{margin-top:10px;font-size:1.2em}table{width:100%;border-collapse:collapse;border-spacing:0;margin-bottom:20px;font-size:.95em}#calendar table{margin-bottom:0}th,td{border:1px solid #eee;padding-top:.5em;padding-bottom:.5em;padding-left:3px;padding-right:3px}td{vertical-align:top}th{background:#fbfbfb;text-align:left}td li{margin-left:20px}.table-small{font-size:.8em}th a{text-decoration:none;color:#333}th a:focus,th a:hover{text-decoration:underline}.table-fixed{table-layout:fixed;white-space:nowrap}.table-fixed th{overflow:hidden}.table-fixed td{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.table-stripped tr:nth-child(odd) td{background:#fefefe}.column-3{width:3%}.column-5{width:5%}.column-8{width:7.5%}.column-10{width:10%}.column-12{width:12%}.column-15{width:15%}.column-18{width:18%}.column-20{width:20%}.column-25{width:25%}.column-30{width:30%}.column-35{width:35%}.column-40{width:40%}.column-50{width:50%}.column-60{width:60%}.column-70{width:70%}form{margin-bottom:20px}label{cursor:pointer;display:block;margin-top:10px}input[type="number"],input[type="date"],input[type="email"],input[type="password"],input[type="text"]{color:#888;border:1px solid #ccc;width:300px;max-width:95%;font-size:100%;height:25px;padding-bottom:0;font-family:sans-serif;margin-top:10px;-webkit-appearance:none;appearance:none}input[type="number"]:focus,input[type="date"]:focus,input[type="email"]:focus,input[type="password"]:focus,input[type="text"]:focus,textarea:focus{color:#000;border-color:rgba(82,168,236,0.8);outline:0;box-shadow:0 0 8px rgba(82,168,236,0.6)}input.form-numeric,input[type="number"]{width:70px}textarea{border:1px solid #ccc;width:400px;max-width:99%;height:200px;font-size:100%;font-family:sans-serif}select{max-width:95%}select:focus{outline:0}::-webkit-input-placeholder{color:#ddd;padding-top:2px}::-ms-input-placeholder{color:#ddd;padding-top:2px}::-moz-placeholder{color:#ddd;padding-top:2px}.form-actions{padding-top:20px;clear:both}input.form-error,textarea.form-error{border:2px solid #b94a48}input.form-error:focus,textarea.form-error:focus{box-shadow:none;border:2px solid #b94a48}.form-required{color:red;padding-left:5px;font-weight:bold}.form-errors{color:#b94a48;list-style-type:none}ul.form-errors li{margin-left:0}.form-help{font-size:.8em;color:brown;margin-bottom:15px}.form-inline{padding:0;margin:0;border:0}.form-inline label{display:inline}.form-inline input,.form-inline select{margin:0;margin-right:15px}.form-inline .form-required{display:none}.form-inline-group{display:inline}input.form-datetime,input.form-date{width:150px}input.form-input-large{width:400px}.form-column{float:left;margin-right:3%;max-width:47%}.form-column ul{margin-top:15px}.form-clear{clear:both;padding-top:20px;padding-bottom:10px}.form-login{width:350px;margin:0 auto;margin-top:8%}.form-column li,.form-login li{margin-left:25px;line-height:25px}.form-login h2{margin-bottom:30px;font-size:1.5em;font-weight:bold}label+.form-tabs{margin-top:10px}.form-tabs{width:100%;max-width:800px}ul.form-tabs-nav{margin-bottom:8px;margin-top:0}.form-tabs-nav li{margin-left:0;display:inline}.form-tab{margin-right:20px}.form-tab a{color:#ccc;font-weight:bold;text-decoration:none}.form-tab a:focus,.form-tab a:hover{color:#000}.form-tab-selected a{color:#333}.preview-area{border:1px dashed #000;padding-top:5px;padding-left:5px;padding-right:5px;margin-bottom:5px;display:none;overflow:auto}.reset-password{margin-top:20px}.reset-password a{font-size:.8em;color:#999}.btn{-webkit-appearance:none;appearance:none;display:inline-block;color:#333;border:1px solid #ccc;background:#efefef;padding:5px;padding-left:15px;padding-right:15px;font-size:.9em;cursor:pointer;border-radius:2px}a.btn{text-decoration:none;font-weight:bold}.btn-small{padding:2px;padding-left:5px;padding-right:5px}.btn-red{border-color:#b0281a;background:#d14836;color:#fff}a.btn-red:hover,.btn-red:hover,.btn-red:focus{color:#fff;background:#c53727}a.btn-blue,.btn-blue{border-color:#3079ed;background:#4d90fe;color:#fff}a.btn-blue:hover,.btn-blue:hover,a.btn-blue:focus,.btn-blue:focus{border-color:#2f5bb7;background:#357ae8}.btn-blue:disabled{color:#ccc;border:1px solid #ccc;background:#f7f7f7}#main .alert,.page .alert{margin-top:10px}.alert{padding:8px 35px 8px 14px;margin-bottom:10px;color:#c09853;background-color:#fcf8e3;border:1px solid #fbeed5;border-radius:4px}.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.alert-error{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.alert-normal{color:#333;background-color:#f0f0f0;border-color:#ddd}.alert ul{margin-top:10px;margin-bottom:10px}.alert li{margin-left:25px}.tooltip-arrow:after{background:#fff;border:1px solid #aaa;box-shadow:0 0 5px #aaa}div.ui-tooltip{min-width:200px;max-width:600px;font-size:.85em}.tooltip-arrow{width:20px;height:10px;overflow:hidden;position:absolute}.tooltip-arrow.top{top:-10px}.tooltip-arrow.bottom{bottom:-10px}.tooltip-arrow.align-left{left:10px}.tooltip-arrow.align-right{right:10px}.tooltip-arrow:after{content:"";position:absolute;width:14px;height:14px;-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg)}.tooltip-arrow.bottom:after{top:-10px}.tooltip-arrow.top:after{bottom:-10px}.tooltip-arrow.align-left:after{left:0}.tooltip-arrow.align-right:after{right:0}.tooltip-large{width:550px}.ui-tooltip-content .markdown p{margin-bottom:0}.tooltip .fa-info-circle{color:#999;font-size:.95em}.ui-tooltip ul{margin-left:20px}.ui-tooltip dl{margin:-5px 0 0 0;padding:0}.ui-tooltip dt{margin-top:5px}.ui-tooltip dd{margin-left:0}.ui-tooltip .progress{display:inline-block;min-width:3em;text-align:right}header{margin-top:10px;padding-bottom:10px;border-bottom:1px solid #dedede}header h1{margin:0;padding:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:70%;float:left}header ul{text-align:right;font-size:.9em}header li{display:inline;padding-left:30px}header a{color:#777;text-decoration:none}nav .active a{color:#333;font-weight:bold}.logo a{opacity:.5;color:#d40000}.logo span{color:#333}.logo a:hover{opacity:.8;color:#333}.logo a:focus span,.logo a:hover span{color:#d40000}header .user-links .dropdown{margin-left:15px}header h1 .tooltip{opacity:.3;font-size:.6em}.page-header{margin-bottom:20px}.page-header h2{margin:0;padding:0;font-size:1.4em;font-weight:bold;border-bottom:1px dotted #ccc}.page-header h2 a{color:#333;text-decoration:none}.page-header h2 a:focus,.page-header h2 a:hover{color:#aaa}.page-header ul{text-align:left;margin-top:5px;display:inline-block}.menu-inline li,.page-header li{display:inline;padding-right:15px;font-size:.95em}.page-header li.active a{color:#333;text-decoration:none;font-weight:bold}.page-header li.active a:hover,.page-header li.active a:focus{text-decoration:underline}.menu-inline{margin-bottom:5px}@media only screen and (max-width:640px){.page-header-mobile li{display:block;margin-bottom:5px}}.public-board{margin-top:5px}.public-task{max-width:800px;margin:0 auto;margin-top:5px}#board-container{overflow-x:auto}#board{table-layout:fixed;margin-bottom:0}#board th.board-column-header{width:240px}#board td{vertical-align:top}.board-container-compact{overflow-x:initial}@media all and (-ms-high-contrast:active),(-ms-high-contrast:none){.board-container-compact #board{table-layout:auto}}#board th.board-column-header.board-column-compact{width:initial}.board-column-collapsed{display:none}td.board-column-task-collapsed{font-weight:bold;background-color:#fbfbfb}#board th.board-column-header-collapsed{width:28px;min-width:28px;text-align:center;overflow:hidden}.board-rotation-wrapper{position:relative;padding:8px 4px;min-height:150px;overflow:hidden}.board-rotation{white-space:nowrap;-webkit-backface-visibility:hidden;-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg);-webkit-transform-origin:0 100%;-moz-transform-origin:0 100%;-ms-transform-origin:0 100%;transform-origin:0 100%}.board-column-title .dropdown-menu{text-decoration:none}.board-add-icon{float:left;padding:0 5px}.board-add-icon a{text-decoration:none;color:#36c;font-size:150%;line-height:70%}.board-add-icon a:focus,.board-add-icon a:hover{text-decoration:none;color:red}.board-column-header-task-count{color:#999;font-weight:normal}th.board-column-header-collapsed .board-column-header-task-count{font-size:.85em}a.board-swimlane-toggle{font-size:.95em;text-decoration:none}a.board-swimlane-toggle:hover,a.board-swimlane-toggle:focus{color:#000;text-decoration:none;border:0}.board-task-list{overflow:auto;min-height:60px}.board-task-list-limit{background-color:#df5353}.draggable-item{cursor:pointer;user-select:none}.draggable-placeholder{border:2px dashed #000;background:#fafafa;height:70px;margin-bottom:10px}div.draggable-item-selected{border:1px solid #000}.task-board-sort-handle{float:left;padding-right:5px}.task-board-saving-state{opacity:.3}.task-board-saving-icon{position:absolute;margin:auto;width:100%;text-align:center;color:#000}.task-board{position:relative;margin-bottom:4px;border:1px solid #000;padding:2px;font-size:.85em;word-wrap:break-word}div.task-board-recent{border-width:2px}div.task-board-status-closed{user-select:none;border:1px dotted #555}.task-table a,.task-board a{color:#000;text-decoration:none;font-weight:bold}.task-table a:focus,.task-table a:hover,.task-board a:focus,.task-board a:hover{text-decoration:underline}.task-board-collapsed{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}a.task-board-collapsed-title{font-weight:normal}.task-board .dropdown{font-size:1.1em}.task-board-title{margin-top:5px;margin-bottom:5px;font-size:1.1em}.task-board-title a{font-weight:normal}.task-board-user{font-size:.8em}.task-board-current-user a{text-decoration:underline}.task-board-current-user a:focus,.task-board-current-user a:hover{text-decoration:none}a.task-board-nobody{font-weight:normal;font-style:italic;color:#444}.task-board-category-container{text-align:right}.task-board-category{font-weight:bold;font-size:.9em;color:#000;border:1px solid #555;padding:2px;padding-right:5px;padding-left:5px}.task-board-icons{text-align:right;margin-top:8px}.task-board-icons a{opacity:.5}.task-board-icons span{opacity:.5;margin-left:2px}.task-board-icons a:hover,.task-board-icons span:hover{opacity:1.0}.task-board-date{font-weight:bold;color:#000}span.task-board-date-overdue{color:#d90000;opacity:1.0}.task-score{font-weight:bold}.task-board .task-score{font-size:1.1em}.task-show-details .task-score{position:absolute;bottom:5px;right:5px;font-size:2em}.task-board-closed,.task-board-days{position:absolute;right:5px;top:5px;opacity:.5;font-size:.8em}.task-board-days:hover{opacity:1.0}.task-days-age{border:#666 1px solid;padding:1px 4px 1px 2px;border-top-left-radius:3px;border-bottom-left-radius:3px}.task-days-incolumn{border:#666 1px solid;border-left:0;margin-left:-5px;padding:1px 2px 1px 4px;border-top-right-radius:3px;border-bottom-right-radius:3px}.board-container-compact .task-board-days{display:none}#task-summary{margin-bottom:15px}#task-summary h2{color:#666;font-size:2.5em;margin-top:0;padding-top:0}.task-summary-container{border:2px solid #000;border-radius:8px;padding:15px;display:-webkit-flex;display:flex;-webkit-flex-direction:row;flex-direction:row;-webkit-justify-content:space-between;justify-content:space-between}.task-summary-column{font-size:.9em;color:#666}.task-summary-column span{color:#555}.task-summary-column li{line-height:23px}.task-show-section{margin-top:30px;margin-bottom:20px}.task-show-files a{font-weight:bold;text-decoration:none}.task-show-files li{margin-left:25px;list-style-type:square;line-height:25px}.task-show-file-actions{font-size:.75em}.task-show-file-actions:before{content:" ["}.task-show-file-actions:after{content:"]"}.task-show-file-actions a{color:#333}.task-show-description{border-left:4px solid #333;padding-left:20px}.task-show-description-textarea{width:99%;max-width:99%;height:300px}.task-file-viewer{position:relative}.task-file-viewer img{max-width:95%;max-height:85%;margin-top:10px}.task-time-form{margin-top:10px;margin-bottom:25px;padding:3px}.task-link-closed{text-decoration:line-through}.task-show-images{list-style-type:none}.task-show-images li img{width:100%}.task-show-images li .img_container{width:250px;height:100px;overflow:hidden}.task-show-images li{padding:10px;overflow:auto;width:250px;min-height:120px;display:inline-block;vertical-align:top}.task-show-images li p{padding:5px;font-weight:bold}.task-show-images li:hover{background:#eee}.task-show-image-actions{margin-left:5px}.task-show-file-table{width:auto}.flag-milestone{color:green}.color-picker{min-height:35px}.color-square{display:inline-block;width:30px;height:30px;margin-right:5px;margin-bottom:5px;border:1px solid #000;cursor:pointer}.color-square:hover{border-style:dotted}div.color-square-selected{border-width:2px;width:28px;height:28px;box-shadow:3px 2px 10px 0 rgba(180,180,180,0.9)}.assign-me{font-size:.8em;vertical-align:bottom}.comment{margin-bottom:20px}.comment:hover{background:#f7f8e0}.comment-inner{border-left:4px solid #333;padding-bottom:10px;padding-left:20px;margin-left:20px;margin-right:10px}.comment-preview{border:2px solid #000;border-radius:3px;padding:10px}.comment-preview .comment-inner{border:0;padding:0;margin:0}.comment-title{margin-bottom:8px;padding-bottom:3px;border-bottom:1px dotted #aaa}.ui-tooltip .comment-title{font-size:80%}.ui-tooltip .comment-inner{padding-bottom:0}.comment-actions{font-size:.8em;padding:0;text-align:right}.comment-actions li{display:inline;padding-left:5px;padding-right:5px;border-right:1px dotted #000}.comment-actions li:last-child{padding-right:0;border:0}.comment-username{font-weight:bold}.comment-textarea{height:200px;width:80%;max-width:800px}.comment-sorting{font-size:.5em}span.comment-sorting a{color:#555;font-weight:normal;text-decoration:none}span.comment-sorting a:hover{color:#aaa}#comments .comment-textarea{height:80px;width:500px}.subtasks-table{font-size:.85em}.subtasks-table td{vertical-align:middle}.markdown{line-height:1.4em;font-size:1.0}.markdown h1{margin-top:5px;margin-bottom:10px;font-size:1.5em;font-weight:bold;text-decoration:underline}.markdown h2{font-size:1.2em;font-weight:bold;text-decoration:underline}.markdown h3{font-size:1.1em;text-decoration:underline}.markdown h4{font-size:1.1em;text-decoration:underline}.markdown p{margin-bottom:10px}.markdown ol,.markdown ul{margin-left:25px;margin-top:10px;margin-bottom:10px}.markdown pre{background:#fbfbfb;padding:10px;border-radius:5px;border:1px solid #ddd;overflow:auto;color:#444}.markdown blockquote{font-style:italic;border-left:3px solid #ddd;padding-left:10px;margin-bottom:10px;margin-left:20px}.markdown img{display:block;max-width:80%;margin-top:10px}.documentation{margin:0 auto;padding:20px;max-width:850px;background:#fefefe;border:1px solid #ccc;border-radius:5px;font-size:1.1em;color:#555}.documentation img{border:1px solid #333}.documentation h1{text-decoration:none;font-size:1.8em;margin-bottom:30px}.documentation h2{font-size:1.3em;text-decoration:none;border-bottom:1px solid #ccc;margin-bottom:25px}.documentation li{line-height:30px}.user-mention-link{font-weight:bold;color:#000;text-decoration:none}.user-mention-link:hover{color:#555}.listing{border-radius:4px;padding:8px 35px 8px 14px;margin-bottom:20px;border:1px solid #ddd;color:#333;background-color:#fcfcfc;overflow:auto}.listing li{list-style-type:square;margin-left:20px;margin-bottom:3px}.listing ul{margin-top:15px;margin-bottom:15px}.activity-event{margin-bottom:20px}.activity-datetime{color:#999;font-size:.85em}.activity-content{margin-top:10px;margin-left:20px;padding-left:20px;border-left:2px solid #666}.activity-title{font-weight:bold;color:#000}.activity-description{font-size:.9em;color:#aaa;padding-top:5px}.activity-description ul{margin-top:10px}.activity-description li{margin-left:40px;list-style-type:circle;color:#555}.activity-description .markdown{margin-top:10px;color:#555}.activity-changes{margin-top:10px;font-size:.85em}.activity-changes ul{margin-left:25px}.dashboard-project-stats span{font-size:.75em;margin-right:10px;color:#999}.dashboard-project-stats strong{font-size:1.2em}.dashboard-table-link{font-weight:bold;color:#444;text-decoration:none}.dashboard-table-link:focus,.dashboard-table-link:hover{color:#999}.pagination{text-align:center}.pagination-next{margin-left:5px}.pagination-previous{margin-right:5px}#popover-container{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.8);overflow:auto;z-index:100}#popover-content{position:absolute;width:70%;margin:0 0 0 -35%;left:50%;top:1%;padding:15px;background:#fff;overflow:auto;max-height:85%}#main .confirm{max-width:700px;font-size:1.1em}.sidebar-container{margin-top:10px;height:100%;width:100%;display:-ms-flexbox;display:-webkit-box;display:-moz-box;display:-ms-box;display:box;-ms-flex-direction:row;-webkit-box-orient:horizontal;-moz-box-orient:horizontal;-ms-box-orient:horizontal;box-orient:horizontal}.sidebar-content{padding-left:10px;-ms-flex:1;-webkit-box-flex:1;-moz-box-flex:1;-ms-box-flex:1;box-flex:1}.sidebar{padding-right:10px;border-right:1px dotted #eee;font-size:.95em;max-width:240px;min-width:190px;width:18%;-ms-flex:0 100px;-webkit-box-flex:0;-moz-box-flex:0;-ms-box-flex:0;box-flex:0}.sidebar a{text-decoration:none}.sidebar li{list-style-type:none;line-height:35px;border-bottom:1px dotted #efefef}.sidebar li:hover{border-left:5px solid #555;padding-left:8px}.sidebar li.active{border-left:5px solid #333;padding-left:8px}.sidebar li.active a{color:#333;font-weight:bold}.sidebar li.active a:focus,.sidebar li.active a:hover{color:#555}@media only screen and (max-width:1080px){div.filter-dropdowns .filters{margin-left:0}div.filter-dropdowns{display:block;margin-top:5px}}@media only screen and (max-width:1024px){body{font-size:.85em}.form-tab{max-width:404px}.form-inline-group input[type="submit"],.form-inline-group label{display:block}.form-inline-group input[type="submit"]{margin-top:20px}td>input[type="text"]{max-width:150px}.task-time-form label{display:block}.task-time-form input[type="submit"]{margin-top:10px;display:block}.page-header .form-input-large{width:300px}}@media only screen and (max-width:1024px) and (orientation:landscape){header{padding-bottom:4px}div.chosen-container{font-size:.9em}input[type="number"],input[type="date"],input[type="email"],input[type="password"],input[type="text"]{height:18px}.page-header .form-input-large{width:300px}}@media only screen and (max-width:640px){.hide-mobile{display:none}}.dropdown{display:inline;position:relative}.dropdown ul{display:none}ul.dropdown-submenu-open{display:block;position:absolute;z-index:1000;min-width:285px;list-style:none;margin:3px 0 0 1px;padding:6px 0;background-color:#fff;border:1px solid #b2b2b2;border-radius:3px;box-shadow:0 1px 3px rgba(0,0,0,0.15)}.textarea-dropdown li,.dropdown-submenu-open li{display:block;margin:0;padding:0;padding-left:10px;padding-right:10px;padding-top:8px;padding-bottom:8px;font-size:.85em;border-bottom:1px solid #f8f8f8;cursor:pointer}.dropdown-submenu-open li.no-hover{cursor:default}.textarea-dropdown li:last-child,.dropdown-submenu-open li:last-child{border:0}.textarea-dropdown .active,.textarea-dropdown li:hover,.dropdown-submenu-open li:not(.no-hover):hover{background:#4078c0;color:#fff}.textarea-dropdown .active a,.textarea-dropdown li:hover a,.dropdown-submenu-open li:hover a{color:#fff}.textarea-dropdown a,.dropdown-submenu-open a{text-decoration:none;color:#333}.dropdown-submenu-open a:focus{text-decoration:underline}.page-header .dropdown{padding-right:10px}.dropdown-menu-link-icon{color:#333;text-decoration:none}.textarea-dropdown{list-style:none;margin:3px 0 0 1px;padding:6px 0;background-color:#fff;border:1px solid #b2b2b2;border-radius:3px;box-shadow:0 1px 3px rgba(0,0,0,0.15)}#screenshot-zone{position:relative;border:2px dashed #ccc;width:90%;height:250px;overflow:auto}#screenshot-inner{position:absolute;left:0;bottom:48%;width:100%;text-align:center}#screenshot-zone.screenshot-pasted{border:2px solid #333}.toolbar{font-size:.9em;padding-top:5px}.views{display:inline-block;margin-right:10px;font-size:.9em}.views li{border:1px solid #eee;padding-left:8px;padding-right:8px;padding-top:5px;padding-bottom:5px;display:inline}.menu-inline li.active a,.views li.active a{font-weight:bold;color:#000;text-decoration:none}.views li:first-child{border-right:0;border-top-left-radius:5px;border-bottom-left-radius:5px}.views li:last-child{border-left:0;border-top-right-radius:5px;border-bottom-right-radius:5px}.filters{display:inline-block;border:1px solid #eee;border-radius:5px;padding:5px;padding-right:10px;margin-left:8px}.filters ul{font-size:.8em}.page-header .filters ul{font-size:.9em}form.search{display:inline}div.search{margin-bottom:20px}.filter-dropdowns{font-size:.9em;display:inline-block}div.ganttview-hzheader-month,div.ganttview-hzheader-day,div.ganttview-vtheader,div.ganttview-vtheader-item-name,div.ganttview-vtheader-series,div.ganttview-grid,div.ganttview-grid-row-cell{float:left}div.ganttview-hzheader-month,div.ganttview-hzheader-day{text-align:center}div.ganttview-grid-row-cell.last,div.ganttview-hzheader-day.last,div.ganttview-hzheader-month.last{border-right:0}div.ganttview{border:1px solid #999}div.ganttview-hzheader-month{width:60px;height:20px;border-right:1px solid #d0d0d0;line-height:20px;overflow:hidden}div.ganttview-hzheader-day{width:20px;height:20px;border-right:1px solid #f0f0f0;border-top:1px solid #d0d0d0;line-height:20px;color:#777}div.ganttview-vtheader{margin-top:41px;width:400px;overflow:hidden;background-color:#fff}div.ganttview-vtheader-item{color:#666}div.ganttview-vtheader-series-name{width:400px;height:31px;line-height:31px;padding-left:3px;border-top:1px solid #d0d0d0;font-size:.9em;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}div.ganttview-vtheader-series-name a{color:#666;text-decoration:none}div.ganttview-vtheader-series-name a:hover{color:#333;text-decoration:underline}div.ganttview-vtheader-series-name a i{color:#000}div.ganttview-vtheader-series-name a:hover i{color:#666}div.ganttview-slide-container{overflow:auto;border-left:1px solid #999}div.ganttview-grid-row-cell{width:20px;height:31px;border-right:1px solid #f0f0f0;border-top:1px solid #f0f0f0}div.ganttview-grid-row-cell.ganttview-weekend{background-color:#fafafa}div.ganttview-blocks{margin-top:40px}div.ganttview-block-container{height:28px;padding-top:4px}div.ganttview-block{position:relative;height:25px;background-color:#e5ecf9;border:1px solid silver;border-radius:3px}.ganttview-block-movable{cursor:move}div.ganttview-block-not-defined{border-color:#000;background-color:#000}div.ganttview-block-text{position:absolute;height:12px;font-size:.7em;color:#999;padding:2px 3px}div.ganttview-block div.ui-resizable-handle.ui-resizable-s{bottom:-0}.project-creation-options{max-width:500px;border-left:3px dotted #efefef;margin-top:20px;padding-left:15px;padding-bottom:5px;padding-top:5px}
\ No newline at end of file +.c3 svg{font:10px sans-serif}.c3 line,.c3 path{fill:none;stroke:#000}.c3 text{-webkit-user-select:none;-moz-user-select:none;user-select:none}.c3-bars path,.c3-event-rect,.c3-legend-item-tile,.c3-xgrid-focus,.c3-ygrid{shape-rendering:crispEdges}.c3-chart-arc path{stroke:#fff}.c3-chart-arc text{fill:#fff;font-size:13px}.c3-grid line{stroke:#aaa}.c3-grid text{fill:#aaa}.c3-xgrid,.c3-ygrid{stroke-dasharray:3 3}.c3-text.c3-empty{fill:gray;font-size:2em}.c3-line{stroke-width:1px}.c3-circle._expanded_{stroke-width:1px;stroke:#fff}.c3-selected-circle{fill:#fff;stroke-width:2px}.c3-bar{stroke-width:0}.c3-bar._expanded_{fill-opacity:.75}.c3-target.c3-focused{opacity:1}.c3-target.c3-focused path.c3-line,.c3-target.c3-focused path.c3-step{stroke-width:2px}.c3-target.c3-defocused{opacity:.3!important}.c3-region{fill:#4682b4;fill-opacity:.1}.c3-brush .extent{fill-opacity:.1}.c3-legend-item{font-size:12px}.c3-legend-item-hidden{opacity:.15}.c3-legend-background{opacity:.75;fill:#fff;stroke:#d3d3d3;stroke-width:1}.c3-tooltip-container{z-index:10}.c3-tooltip{border-collapse:collapse;border-spacing:0;background-color:#fff;empty-cells:show;-webkit-box-shadow:7px 7px 12px -9px #777;-moz-box-shadow:7px 7px 12px -9px #777;box-shadow:7px 7px 12px -9px #777;opacity:.9}.c3-tooltip tr{border:1px solid #CCC}.c3-tooltip th{background-color:#aaa;font-size:14px;padding:2px 5px;text-align:left;color:#FFF}.c3-tooltip td{font-size:13px;padding:3px 6px;background-color:#fff;border-left:1px dotted #999}.c3-tooltip td>span{display:inline-block;width:10px;height:10px;margin-right:6px}.c3-tooltip td.value{text-align:right}.c3-area{stroke-width:0;opacity:.2}.c3-chart-arcs-title{dominant-baseline:middle;font-size:1.3em}.c3-chart-arcs .c3-chart-arcs-background{fill:#e0e0e0;stroke:none}.c3-chart-arcs .c3-chart-arcs-gauge-unit{fill:#000;font-size:16px}.c3-chart-arcs .c3-chart-arcs-gauge-max,.c3-chart-arcs .c3-chart-arcs-gauge-min{fill:#777}.c3-chart-arc .c3-gauge-value{fill:#000}li,ul,ol,table,tr,td,th,p,blockquote,body{margin:0;padding:0;font-size:100%}body{margin-left:10px;margin-right:10px;padding-bottom:10px;color:#333;font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;text-rendering:optimizeLegibility}.page{clear:both}ul.no-bullet li{list-style-type:none;margin-left:0}.pull-right{text-align:right}hr{border:0;height:0;border-top:1px solid rgba(0,0,0,0.1);border-bottom:1px solid rgba(255,255,255,0.3)}.chosen-select{min-height:27px}.avatar{float:left;margin-right:10px}#ui-datepicker-div{font-size:.8em}#app-loading-icon{position:fixed;right:3px;bottom:3px}.web-notification-icon{color:#36c}.web-notification-icon:focus,.web-notification-icon:hover{color:#000}.smaller{font-size:.85em}a{color:#36c;border:0}a:focus{outline:0;color:#df5353;text-decoration:none;border:1px dotted #aaa}a:hover{color:#333;text-decoration:none}h1,h2,h3{font-weight:normal;color:#333}h2{font-size:1.3em;margin-bottom:10px}h3{margin-top:10px;font-size:1.2em}table{width:100%;border-collapse:collapse;border-spacing:0;margin-bottom:20px;font-size:.95em}#calendar table{margin-bottom:0}th,td{border:1px solid #eee;padding-top:.5em;padding-bottom:.5em;padding-left:3px;padding-right:3px}td{vertical-align:top}th{background:#fbfbfb;text-align:left}td li{margin-left:20px}.table-small{font-size:.8em}th a{text-decoration:none;color:#333}th a:focus,th a:hover{text-decoration:underline}.table-fixed{table-layout:fixed;white-space:nowrap}.table-fixed th{overflow:hidden}.table-fixed td{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.table-stripped tr:nth-child(odd) td{background:#fefefe}.column-3{width:3%}.column-5{width:5%}.column-8{width:7.5%}.column-10{width:10%}.column-12{width:12%}.column-15{width:15%}.column-18{width:18%}.column-20{width:20%}.column-25{width:25%}.column-30{width:30%}.column-35{width:35%}.column-40{width:40%}.column-50{width:50%}.column-60{width:60%}.column-70{width:70%}form{margin-bottom:20px}label{cursor:pointer;display:block;margin-top:10px}input[type="number"],input[type="date"],input[type="email"],input[type="password"],input[type="text"]{color:#888;border:1px solid #ccc;width:300px;max-width:95%;font-size:100%;height:25px;padding-bottom:0;font-family:sans-serif;margin-top:10px;-webkit-appearance:none;appearance:none}input[type="number"]:focus,input[type="date"]:focus,input[type="email"]:focus,input[type="password"]:focus,input[type="text"]:focus,textarea:focus{color:#000;border-color:rgba(82,168,236,0.8);outline:0;box-shadow:0 0 8px rgba(82,168,236,0.6)}input.form-numeric,input[type="number"]{width:70px}textarea{border:1px solid #ccc;width:400px;max-width:99%;height:200px;font-size:100%;font-family:sans-serif}select{max-width:95%}select:focus{outline:0}::-webkit-input-placeholder{color:#ddd;padding-top:2px}::-ms-input-placeholder{color:#ddd;padding-top:2px}::-moz-placeholder{color:#ddd;padding-top:2px}.form-actions{padding-top:20px;clear:both}input.form-error,textarea.form-error{border:2px solid #b94a48}input.form-error:focus,textarea.form-error:focus{box-shadow:none;border:2px solid #b94a48}.form-required{color:red;padding-left:5px;font-weight:bold}.form-errors{color:#b94a48;list-style-type:none}ul.form-errors li{margin-left:0}.form-help{font-size:.8em;color:brown;margin-bottom:15px}.form-inline{padding:0;margin:0;border:0}.form-inline label{display:inline}.form-inline input,.form-inline select{margin:0;margin-right:15px}.form-inline .form-required{display:none}.form-inline-group{display:inline}input.form-datetime,input.form-date{width:150px}input.form-input-large{width:400px}.form-column{float:left;margin-right:3%;max-width:47%}.form-column ul{margin-top:15px}.form-clear{clear:both;padding-top:20px;padding-bottom:10px}.form-login{width:350px;margin:0 auto;margin-top:8%}.form-column li,.form-login li{margin-left:25px;line-height:25px}.form-login h2{margin-bottom:30px;font-size:1.5em;font-weight:bold}label+.form-tabs{margin-top:10px}.form-tabs{width:100%;max-width:800px}ul.form-tabs-nav{margin-bottom:8px;margin-top:0}.form-tabs-nav li{margin-left:0;display:inline}.form-tab{margin-right:20px}.form-tab a{color:#ccc;font-weight:bold;text-decoration:none}.form-tab a:focus,.form-tab a:hover{color:#000}.form-tab-selected a{color:#333}.preview-area{border:1px dashed #000;padding-top:5px;padding-left:5px;padding-right:5px;margin-bottom:5px;display:none;overflow:auto}.reset-password{margin-top:20px}.reset-password a{font-size:.8em;color:#999}.btn{-webkit-appearance:none;appearance:none;display:inline-block;color:#333;border:1px solid #ccc;background:#efefef;padding:5px;padding-left:15px;padding-right:15px;font-size:.9em;cursor:pointer;border-radius:2px}a.btn{text-decoration:none;font-weight:bold}.btn-small{padding:2px;padding-left:5px;padding-right:5px}.btn-red{border-color:#b0281a;background:#d14836;color:#fff}a.btn-red:hover,.btn-red:hover,.btn-red:focus{color:#fff;background:#c53727}a.btn-blue,.btn-blue{border-color:#3079ed;background:#4d90fe;color:#fff}a.btn-blue:hover,.btn-blue:hover,a.btn-blue:focus,.btn-blue:focus{border-color:#2f5bb7;background:#357ae8}.btn-blue:disabled{color:#ccc;border:1px solid #ccc;background:#f7f7f7}#main .alert,.page .alert{margin-top:10px}.alert{padding:8px 35px 8px 14px;margin-bottom:10px;color:#c09853;background-color:#fcf8e3;border:1px solid #fbeed5;border-radius:4px}.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.alert-error{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.alert-normal{color:#333;background-color:#f0f0f0;border-color:#ddd}.alert ul{margin-top:10px;margin-bottom:10px}.alert li{margin-left:25px}.tooltip-arrow:after{background:#fff;border:1px solid #aaa;box-shadow:0 0 5px #aaa}div.ui-tooltip{min-width:200px;max-width:600px;font-size:.85em}.tooltip-arrow{width:20px;height:10px;overflow:hidden;position:absolute}.tooltip-arrow.top{top:-10px}.tooltip-arrow.bottom{bottom:-10px}.tooltip-arrow.align-left{left:10px}.tooltip-arrow.align-right{right:10px}.tooltip-arrow:after{content:"";position:absolute;width:14px;height:14px;-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg)}.tooltip-arrow.bottom:after{top:-10px}.tooltip-arrow.top:after{bottom:-10px}.tooltip-arrow.align-left:after{left:0}.tooltip-arrow.align-right:after{right:0}.tooltip-large{width:550px}.ui-tooltip-content .markdown p{margin-bottom:0}.tooltip .fa-info-circle{color:#999;font-size:.95em}.ui-tooltip ul{margin-left:20px}.ui-tooltip dl{margin:-5px 0 0 0;padding:0}.ui-tooltip dt{margin-top:5px}.ui-tooltip dd{margin-left:0}.ui-tooltip .progress{display:inline-block;min-width:3em;text-align:right}header{margin-top:10px;padding-bottom:10px;border-bottom:1px solid #dedede}header h1{margin:0;padding:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:70%;float:left}header ul{text-align:right;font-size:.9em}header li{display:inline;padding-left:30px}header a{color:#777;text-decoration:none}nav .active a{color:#333;font-weight:bold}.logo a{opacity:.5;color:#d40000}.logo span{color:#333}.logo a:hover{opacity:.8;color:#333}.logo a:focus span,.logo a:hover span{color:#d40000}header .user-links .dropdown{margin-left:15px}header h1 .tooltip{opacity:.3;font-size:.6em}.page-header{margin-bottom:20px}.page-header h2{margin:0;padding:0;font-size:1.4em;font-weight:bold;border-bottom:1px dotted #ccc}.page-header h2 a{color:#333;text-decoration:none}.page-header h2 a:focus,.page-header h2 a:hover{color:#aaa}.page-header ul{text-align:left;margin-top:5px;display:inline-block}.menu-inline li,.page-header li{display:inline;padding-right:15px;font-size:.95em}.page-header li.active a{color:#333;text-decoration:none;font-weight:bold}.page-header li.active a:hover,.page-header li.active a:focus{text-decoration:underline}.menu-inline{margin-bottom:5px}@media only screen and (max-width:640px){.page-header-mobile li{display:block;margin-bottom:5px}}.public-board{margin-top:5px}.public-task{max-width:800px;margin:0 auto;margin-top:5px}#board-container{overflow-x:auto}#board{table-layout:fixed;margin-bottom:0}#board th.board-column-header{width:240px}#board td{vertical-align:top}.board-container-compact{overflow-x:initial}@media all and (-ms-high-contrast:active),(-ms-high-contrast:none){.board-container-compact #board{table-layout:auto}}#board th.board-column-header.board-column-compact{width:initial}.board-column-collapsed{display:none}td.board-column-task-collapsed{font-weight:bold;background-color:#fbfbfb}#board th.board-column-header-collapsed{width:28px;min-width:28px;text-align:center;overflow:hidden}.board-rotation-wrapper{position:relative;padding:8px 4px;min-height:150px;overflow:hidden}.board-rotation{white-space:nowrap;-webkit-backface-visibility:hidden;-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg);-webkit-transform-origin:0 100%;-moz-transform-origin:0 100%;-ms-transform-origin:0 100%;transform-origin:0 100%}.board-column-title .dropdown-menu{text-decoration:none}.board-add-icon{float:left;padding:0 5px}.board-add-icon a{text-decoration:none;color:#36c;font-size:150%;line-height:70%}.board-add-icon a:focus,.board-add-icon a:hover{text-decoration:none;color:red}.board-column-header-task-count{color:#999;font-weight:normal}th.board-column-header-collapsed .board-column-header-task-count{font-size:.85em}a.board-swimlane-toggle{font-size:.95em;text-decoration:none}a.board-swimlane-toggle:hover,a.board-swimlane-toggle:focus{color:#000;text-decoration:none;border:0}.board-task-list{overflow:auto;min-height:60px}.board-task-list-limit{background-color:#df5353}.draggable-item{cursor:pointer;user-select:none}.draggable-placeholder{border:2px dashed #000;background:#fafafa;height:70px;margin-bottom:10px}div.draggable-item-selected{border:1px solid #000}.task-board-sort-handle{float:left;padding-right:5px}.task-board-saving-state{opacity:.3}.task-board-saving-icon{position:absolute;margin:auto;width:100%;text-align:center;color:#000}.task-board{position:relative;margin-bottom:4px;border:1px solid #000;padding:2px;font-size:.85em;word-wrap:break-word}div.task-board-recent{border-width:2px}div.task-board-status-closed{user-select:none;border:1px dotted #555}.task-table a,.task-board a{color:#000;text-decoration:none;font-weight:bold}.task-table a:focus,.task-table a:hover,.task-board a:focus,.task-board a:hover{text-decoration:underline}.task-board-collapsed{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}a.task-board-collapsed-title{font-weight:normal}.task-board .dropdown{font-size:1.1em}.task-board-title{margin-top:5px;margin-bottom:5px;font-size:1.1em}.task-board-title a{font-weight:normal}.task-board-user{font-size:.8em}.task-board-current-user a{text-decoration:underline}.task-board-current-user a:focus,.task-board-current-user a:hover{text-decoration:none}a.task-board-nobody{font-weight:normal;font-style:italic;color:#444}.task-board-category-container{text-align:right}.task-board-category{font-weight:bold;font-size:.9em;color:#000;border:1px solid #555;padding:2px;padding-right:5px;padding-left:5px}.task-board-icons{text-align:right;margin-top:8px}.task-board-icons a{opacity:.5}.task-board-icons span{opacity:.5;margin-left:2px}.task-board-icons a:hover,.task-board-icons span:hover{opacity:1.0}.task-board-date{font-weight:bold;color:#000}span.task-board-date-overdue{color:#d90000;opacity:1.0}.task-board .task-score{font-weight:bold;font-size:1.1em}.task-board-closed,.task-board-days{position:absolute;right:5px;top:5px;opacity:.5;font-size:.8em}.task-board-days:hover{opacity:1.0}.task-days-age{border:#666 1px solid;padding:1px 4px 1px 2px;border-top-left-radius:3px;border-bottom-left-radius:3px}.task-days-incolumn{border:#666 1px solid;border-left:0;margin-left:-5px;padding:1px 2px 1px 4px;border-top-right-radius:3px;border-bottom-right-radius:3px}.board-container-compact .task-board-days{display:none}#task-summary{margin-bottom:15px}#task-summary h2{color:#666;font-size:2.5em;margin-top:0;padding-top:0}.task-summary-container{border:2px solid #000;border-radius:8px;padding:15px;display:-webkit-flex;display:flex;-webkit-flex-direction:row;flex-direction:row;-webkit-justify-content:space-between;justify-content:space-between}.task-summary-column{font-size:.9em;color:#666}.task-summary-column span{color:#555}.task-summary-column li{line-height:23px}.task-show-description{border-left:4px solid #333;padding-left:20px}.task-show-description-textarea{width:99%;max-width:99%;height:300px}.task-file-viewer{position:relative}.task-file-viewer img{max-width:95%;max-height:85%;margin-top:10px}.task-time-form{margin-top:10px;margin-bottom:25px;padding:3px}.task-link-closed{text-decoration:line-through}.flag-milestone{color:green}.color-picker{min-height:35px}.color-square{display:inline-block;width:30px;height:30px;margin-right:5px;margin-bottom:5px;border:1px solid #000;cursor:pointer}.color-square:hover{border-style:dotted}div.color-square-selected{border-width:2px;width:28px;height:28px;box-shadow:3px 2px 10px 0 rgba(180,180,180,0.9)}.assign-me{font-size:.8em;vertical-align:bottom}.comment{margin-bottom:20px}.comment:hover{background:#f7f8e0}.comment-inner{border-left:4px solid #333;padding-bottom:10px;padding-left:20px;margin-left:20px;margin-right:10px}.comment-preview{border:2px solid #000;border-radius:3px;padding:10px}.comment-preview .comment-inner{border:0;padding:0;margin:0}.comment-title{margin-bottom:8px;padding-bottom:3px;border-bottom:1px dotted #aaa}.ui-tooltip .comment-title{font-size:80%}.ui-tooltip .comment-inner{padding-bottom:0}.comment-actions{font-size:.8em;padding:0;text-align:right}.comment-actions li{display:inline;padding-left:5px;padding-right:5px;border-right:1px dotted #000}.comment-actions li:last-child{padding-right:0;border:0}.comment-username{font-weight:bold}.comment-textarea{height:200px;width:80%;max-width:800px}.comment-sorting{font-size:.5em}span.comment-sorting a{color:#555;font-weight:normal;text-decoration:none}span.comment-sorting a:hover{color:#aaa}#comments .comment-textarea{height:80px;width:500px}.subtasks-table{font-size:.85em}.subtasks-table td{vertical-align:middle}.markdown{line-height:1.4em;font-size:1.0}.markdown h1{margin-top:5px;margin-bottom:10px;font-size:1.5em;font-weight:bold;text-decoration:underline}.markdown h2{font-size:1.2em;font-weight:bold;text-decoration:underline}.markdown h3{font-size:1.1em;text-decoration:underline}.markdown h4{font-size:1.1em;text-decoration:underline}.markdown p{margin-bottom:10px}.markdown ol,.markdown ul{margin-left:25px;margin-top:10px;margin-bottom:10px}.markdown pre{background:#fbfbfb;padding:10px;border-radius:5px;border:1px solid #ddd;overflow:auto;color:#444}.markdown blockquote{font-style:italic;border-left:3px solid #ddd;padding-left:10px;margin-bottom:10px;margin-left:20px}.markdown img{display:block;max-width:80%;margin-top:10px}.documentation{margin:0 auto;padding:20px;max-width:850px;background:#fefefe;border:1px solid #ccc;border-radius:5px;font-size:1.1em;color:#555}.documentation img{border:1px solid #333}.documentation h1{text-decoration:none;font-size:1.8em;margin-bottom:30px}.documentation h2{font-size:1.3em;text-decoration:none;border-bottom:1px solid #ccc;margin-bottom:25px}.documentation li{line-height:30px}.user-mention-link{font-weight:bold;color:#000;text-decoration:none}.user-mention-link:hover{color:#555}.listing{border-radius:4px;padding:8px 35px 8px 14px;margin-bottom:20px;border:1px solid #ddd;color:#333;background-color:#fcfcfc;overflow:auto}.listing li{list-style-type:square;margin-left:20px;margin-bottom:3px}.listing ul{margin-top:15px;margin-bottom:15px}.activity-event{margin-bottom:20px}.activity-datetime{color:#999;font-size:.85em}.activity-content{margin-top:10px;margin-left:20px;padding-left:20px;border-left:2px solid #666}.activity-title{font-weight:bold;color:#000}.activity-description{font-size:.9em;color:#aaa;padding-top:5px}.activity-description ul{margin-top:10px}.activity-description li{margin-left:40px;list-style-type:circle;color:#555}.activity-description .markdown{margin-top:10px;color:#555}.activity-changes{margin-top:10px;font-size:.85em}.activity-changes ul{margin-left:25px}.dashboard-project-stats span{font-size:.75em;margin-right:10px;color:#999}.dashboard-project-stats strong{font-size:1.2em}.dashboard-table-link{font-weight:bold;color:#444;text-decoration:none}.dashboard-table-link:focus,.dashboard-table-link:hover{color:#999}.pagination{text-align:center}.pagination-next{margin-left:5px}.pagination-previous{margin-right:5px}#popover-container{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.8);overflow:auto;z-index:100}#popover-content{position:absolute;width:70%;margin:0 0 0 -35%;left:50%;top:1%;padding:15px;background:#fff;overflow:auto;max-height:85%}#main .confirm{max-width:700px;font-size:1.1em}.sidebar-container{margin-top:10px;height:100%;width:100%;display:-ms-flexbox;display:-webkit-box;display:-moz-box;display:-ms-box;display:box;-ms-flex-direction:row;-webkit-box-orient:horizontal;-moz-box-orient:horizontal;-ms-box-orient:horizontal;box-orient:horizontal}.sidebar-content{padding-left:10px;-ms-flex:1;-webkit-box-flex:1;-moz-box-flex:1;-ms-box-flex:1;box-flex:1}.sidebar{padding-right:10px;border-right:1px dotted #eee;font-size:.95em;max-width:240px;min-width:190px;width:18%;-ms-flex:0 100px;-webkit-box-flex:0;-moz-box-flex:0;-ms-box-flex:0;box-flex:0}.sidebar a{text-decoration:none}.sidebar li{list-style-type:none;line-height:35px;border-bottom:1px dotted #efefef}.sidebar li:hover{border-left:5px solid #555;padding-left:8px}.sidebar li.active{border-left:5px solid #333;padding-left:8px}.sidebar li.active a{color:#333;font-weight:bold}.sidebar li.active a:focus,.sidebar li.active a:hover{color:#555}@media only screen and (max-width:1024px){body{font-size:.85em}.form-tab{max-width:404px}.form-inline-group input[type="submit"],.form-inline-group label{display:block}.form-inline-group input[type="submit"]{margin-top:20px}td>input[type="text"]{max-width:150px}.page-header .form-input-large{width:300px}}@media only screen and (max-width:1024px) and (orientation:landscape){header{padding-bottom:4px}div.chosen-container{font-size:.9em}input[type="number"],input[type="date"],input[type="email"],input[type="password"],input[type="text"]{height:18px}.page-header .form-input-large{width:300px}}@media only screen and (max-width:640px){.hide-mobile{display:none}}.dropdown{display:inline;position:relative}.dropdown ul{display:none}ul.dropdown-submenu-open{display:block;position:absolute;z-index:1000;min-width:285px;list-style:none;margin:3px 0 0 1px;padding:6px 0;background-color:#fff;border:1px solid #b2b2b2;border-radius:3px;box-shadow:0 1px 3px rgba(0,0,0,0.15)}.textarea-dropdown li,.dropdown-submenu-open li{display:block;margin:0;padding:0;padding-left:10px;padding-right:10px;padding-top:8px;padding-bottom:8px;font-size:.85em;border-bottom:1px solid #f8f8f8;cursor:pointer}.dropdown-submenu-open li.no-hover{cursor:default}.textarea-dropdown li:last-child,.dropdown-submenu-open li:last-child{border:0}.textarea-dropdown .active,.textarea-dropdown li:hover,.dropdown-submenu-open li:not(.no-hover):hover{background:#4078c0;color:#fff}.textarea-dropdown .active a,.textarea-dropdown li:hover a,.dropdown-submenu-open li:hover a{color:#fff}.textarea-dropdown a,.dropdown-submenu-open a{text-decoration:none;color:#333}.dropdown-submenu-open a:focus{text-decoration:underline}.page-header .dropdown{padding-right:10px}.dropdown-menu-link-text,.dropdown-menu-link-icon{color:#333;text-decoration:none}.dropdown-menu-link-text:hover{text-decoration:underline}.textarea-dropdown{list-style:none;margin:3px 0 0 1px;padding:6px 0;background-color:#fff;border:1px solid #b2b2b2;border-radius:3px;box-shadow:0 1px 3px rgba(0,0,0,0.15)}#file-dropzone,#screenshot-zone{position:relative;border:2px dashed #ccc;width:99%;height:250px;overflow:auto}#file-dropzone-inner,#screenshot-inner{position:absolute;left:0;bottom:48%;width:100%;text-align:center;color:#aaa}#screenshot-zone.screenshot-pasted{border:2px solid #333}#file-list{margin:20px}#file-list li{list-style-type:none;padding-top:8px;padding-bottom:8px;border-bottom:1px dotted #ddd;width:95%}#file-list li.file-error{font-weight:bold;color:#b94a48}.project-header{margin-top:8px;margin-bottom:20px}.filter-box{display:inline-block;position:relative;font-size:0;margin-bottom:20px}.project-header .filter-box{margin:0}.filter-box form{margin:0}.filter-box input[type="text"]{margin:0;font-size:16px;height:26px;border-color:#dedede;border-top-left-radius:5px;border-bottom-left-radius:5px;vertical-align:top}.filter-box input[type="text"]:focus{color:#000;border-color:rgba(82,168,236,0.8);outline:0;box-shadow:0 0 8px rgba(82,168,236,0.6)}.filter-box div.dropdown{display:inline-block;font-size:16px;border:1px solid #dedede;border-left:0;margin:0;padding:0;padding-left:5px;padding-right:8px;height:27px}.filter-box div.dropdown:last-child{border-top-right-radius:5px;border-bottom-right-radius:5px}.filter-box div.dropdown a{line-height:27px}div.ganttview-hzheader-month,div.ganttview-hzheader-day,div.ganttview-vtheader,div.ganttview-vtheader-item-name,div.ganttview-vtheader-series,div.ganttview-grid,div.ganttview-grid-row-cell{float:left}div.ganttview-hzheader-month,div.ganttview-hzheader-day{text-align:center}div.ganttview-grid-row-cell.last,div.ganttview-hzheader-day.last,div.ganttview-hzheader-month.last{border-right:0}div.ganttview{border:1px solid #999}div.ganttview-hzheader-month{width:60px;height:20px;border-right:1px solid #d0d0d0;line-height:20px;overflow:hidden}div.ganttview-hzheader-day{width:20px;height:20px;border-right:1px solid #f0f0f0;border-top:1px solid #d0d0d0;line-height:20px;color:#777}div.ganttview-vtheader{margin-top:41px;width:400px;overflow:hidden;background-color:#fff}div.ganttview-vtheader-item{color:#666}div.ganttview-vtheader-series-name{width:400px;height:31px;line-height:31px;padding-left:3px;border-top:1px solid #d0d0d0;font-size:.9em;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}div.ganttview-vtheader-series-name a{color:#666;text-decoration:none}div.ganttview-vtheader-series-name a:hover{color:#333;text-decoration:underline}div.ganttview-vtheader-series-name a i{color:#000}div.ganttview-vtheader-series-name a:hover i{color:#666}div.ganttview-slide-container{overflow:auto;border-left:1px solid #999}div.ganttview-grid-row-cell{width:20px;height:31px;border-right:1px solid #f0f0f0;border-top:1px solid #f0f0f0}div.ganttview-grid-row-cell.ganttview-weekend{background-color:#fafafa}div.ganttview-blocks{margin-top:40px}div.ganttview-block-container{height:28px;padding-top:4px}div.ganttview-block{position:relative;height:25px;background-color:#e5ecf9;border:1px solid silver;border-radius:3px}.ganttview-block-movable{cursor:move}div.ganttview-block-not-defined{border-color:#000;background-color:#000}div.ganttview-block-text{position:absolute;height:12px;font-size:.7em;color:#999;padding:2px 3px}div.ganttview-block div.ui-resizable-handle.ui-resizable-s{bottom:-0}.project-creation-options{max-width:500px;border-left:3px dotted #efefef;margin-top:20px;padding-left:15px;padding-bottom:5px;padding-top:5px}.file-thumbnails{display:-webkit-flex;display:flex;-webkit-flex-direction:row;flex-direction:row;-webkit-flex-wrap:wrap;flex-wrap:wrap;-webkit-justify-content:flex-start;justify-content:flex-start}.file-thumbnail{width:250px;border:1px solid #efefef;border-radius:5px;margin-bottom:20px;box-shadow:2px 2px 5px -2px rgba(0,0,0,0.55);margin-right:15px}.file-thumbnail img{border-top-left-radius:5px;border-top-right-radius:5px}.file-thumbnail img:hover{opacity:.5}.file-thumbnail-content{padding-left:8px;padding-right:8px}.file-thumbnail-title{font-weight:700;font-size:.9em;color:#555}.file-thumbnail-description{font-size:.8em;color:#aaa;margin-top:8px;margin-bottom:5px}.views{display:inline-block;margin-left:10px;margin-right:10px;font-size:.9em}.views li{border:1px solid #eee;padding-left:8px;padding-right:8px;padding-top:5px;padding-bottom:5px;display:inline}.menu-inline li.active a,.views li.active a{font-weight:bold;color:#000;text-decoration:none}.views li:first-child{border-right:0;border-top-left-radius:5px;border-bottom-left-radius:5px}.views li:last-child{border-left:0;border-top-right-radius:5px;border-bottom-right-radius:5px}
\ No newline at end of file diff --git a/assets/css/print.css b/assets/css/print.css index 01d8c32f..5681bf8d 100644 --- a/assets/css/print.css +++ b/assets/css/print.css @@ -18,4 +18,4 @@ * Font Awesome 4.5.0 by @davegandy - http://fontawesome.io - @fontawesome * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.5.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.5.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.5.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.5.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.5.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.5.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"} -.c3 svg{font:10px sans-serif}.c3 line,.c3 path{fill:none;stroke:#000}.c3 text{-webkit-user-select:none;-moz-user-select:none;user-select:none}.c3-bars path,.c3-event-rect,.c3-legend-item-tile,.c3-xgrid-focus,.c3-ygrid{shape-rendering:crispEdges}.c3-chart-arc path{stroke:#fff}.c3-chart-arc text{fill:#fff;font-size:13px}.c3-grid line{stroke:#aaa}.c3-grid text{fill:#aaa}.c3-xgrid,.c3-ygrid{stroke-dasharray:3 3}.c3-text.c3-empty{fill:gray;font-size:2em}.c3-line{stroke-width:1px}.c3-circle._expanded_{stroke-width:1px;stroke:#fff}.c3-selected-circle{fill:#fff;stroke-width:2px}.c3-bar{stroke-width:0}.c3-bar._expanded_{fill-opacity:.75}.c3-target.c3-focused{opacity:1}.c3-target.c3-focused path.c3-line,.c3-target.c3-focused path.c3-step{stroke-width:2px}.c3-target.c3-defocused{opacity:.3!important}.c3-region{fill:#4682b4;fill-opacity:.1}.c3-brush .extent{fill-opacity:.1}.c3-legend-item{font-size:12px}.c3-legend-item-hidden{opacity:.15}.c3-legend-background{opacity:.75;fill:#fff;stroke:#d3d3d3;stroke-width:1}.c3-tooltip-container{z-index:10}.c3-tooltip{border-collapse:collapse;border-spacing:0;background-color:#fff;empty-cells:show;-webkit-box-shadow:7px 7px 12px -9px #777;-moz-box-shadow:7px 7px 12px -9px #777;box-shadow:7px 7px 12px -9px #777;opacity:.9}.c3-tooltip tr{border:1px solid #CCC}.c3-tooltip th{background-color:#aaa;font-size:14px;padding:2px 5px;text-align:left;color:#FFF}.c3-tooltip td{font-size:13px;padding:3px 6px;background-color:#fff;border-left:1px dotted #999}.c3-tooltip td>span{display:inline-block;width:10px;height:10px;margin-right:6px}.c3-tooltip td.value{text-align:right}.c3-area{stroke-width:0;opacity:.2}.c3-chart-arcs-title{dominant-baseline:middle;font-size:1.3em}.c3-chart-arcs .c3-chart-arcs-background{fill:#e0e0e0;stroke:none}.c3-chart-arcs .c3-chart-arcs-gauge-unit{fill:#000;font-size:16px}.c3-chart-arcs .c3-chart-arcs-gauge-max,.c3-chart-arcs .c3-chart-arcs-gauge-min{fill:#777}.c3-chart-arc .c3-gauge-value{fill:#000}header,.sidebar,.form-comment,.page-header{display:none}a{color:#36c;border:0}a:focus{outline:0;color:#df5353;text-decoration:none;border:1px dotted #aaa}a:hover{color:#333;text-decoration:none}table{width:100%;border-collapse:collapse;border-spacing:0;margin-bottom:20px;font-size:.95em}#calendar table{margin-bottom:0}th,td{border:1px solid #eee;padding-top:.5em;padding-bottom:.5em;padding-left:3px;padding-right:3px}td{vertical-align:top}th{background:#fbfbfb;text-align:left}td li{margin-left:20px}.table-small{font-size:.8em}th a{text-decoration:none;color:#333}th a:focus,th a:hover{text-decoration:underline}.table-fixed{table-layout:fixed;white-space:nowrap}.table-fixed th{overflow:hidden}.table-fixed td{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.table-stripped tr:nth-child(odd) td{background:#fefefe}.column-3{width:3%}.column-5{width:5%}.column-8{width:7.5%}.column-10{width:10%}.column-12{width:12%}.column-15{width:15%}.column-18{width:18%}.column-20{width:20%}.column-25{width:25%}.column-30{width:30%}.column-35{width:35%}.column-40{width:40%}.column-50{width:50%}.column-60{width:60%}.column-70{width:70%}.public-board{margin-top:5px}.public-task{max-width:800px;margin:0 auto;margin-top:5px}#board-container{overflow-x:auto}#board{table-layout:fixed;margin-bottom:0}#board th.board-column-header{width:240px}#board td{vertical-align:top}.board-container-compact{overflow-x:initial}@media all and (-ms-high-contrast:active),(-ms-high-contrast:none){.board-container-compact #board{table-layout:auto}}#board th.board-column-header.board-column-compact{width:initial}.board-column-collapsed{display:none}td.board-column-task-collapsed{font-weight:bold;background-color:#fbfbfb}#board th.board-column-header-collapsed{width:28px;min-width:28px;text-align:center;overflow:hidden}.board-rotation-wrapper{position:relative;padding:8px 4px;min-height:150px;overflow:hidden}.board-rotation{white-space:nowrap;-webkit-backface-visibility:hidden;-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg);-webkit-transform-origin:0 100%;-moz-transform-origin:0 100%;-ms-transform-origin:0 100%;transform-origin:0 100%}.board-column-title .dropdown-menu{text-decoration:none}.board-add-icon{float:left;padding:0 5px}.board-add-icon a{text-decoration:none;color:#36c;font-size:150%;line-height:70%}.board-add-icon a:focus,.board-add-icon a:hover{text-decoration:none;color:red}.board-column-header-task-count{color:#999;font-weight:normal}th.board-column-header-collapsed .board-column-header-task-count{font-size:.85em}a.board-swimlane-toggle{font-size:.95em;text-decoration:none}a.board-swimlane-toggle:hover,a.board-swimlane-toggle:focus{color:#000;text-decoration:none;border:0}.board-task-list{overflow:auto;min-height:60px}.board-task-list-limit{background-color:#df5353}.draggable-item{cursor:pointer;user-select:none}.draggable-placeholder{border:2px dashed #000;background:#fafafa;height:70px;margin-bottom:10px}div.draggable-item-selected{border:1px solid #000}.task-board-sort-handle{float:left;padding-right:5px}.task-board-saving-state{opacity:.3}.task-board-saving-icon{position:absolute;margin:auto;width:100%;text-align:center;color:#000}.task-board{position:relative;margin-bottom:4px;border:1px solid #000;padding:2px;font-size:.85em;word-wrap:break-word}div.task-board-recent{border-width:2px}div.task-board-status-closed{user-select:none;border:1px dotted #555}.task-table a,.task-board a{color:#000;text-decoration:none;font-weight:bold}.task-table a:focus,.task-table a:hover,.task-board a:focus,.task-board a:hover{text-decoration:underline}.task-board-collapsed{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}a.task-board-collapsed-title{font-weight:normal}.task-board .dropdown{font-size:1.1em}.task-board-title{margin-top:5px;margin-bottom:5px;font-size:1.1em}.task-board-title a{font-weight:normal}.task-board-user{font-size:.8em}.task-board-current-user a{text-decoration:underline}.task-board-current-user a:focus,.task-board-current-user a:hover{text-decoration:none}a.task-board-nobody{font-weight:normal;font-style:italic;color:#444}.task-board-category-container{text-align:right}.task-board-category{font-weight:bold;font-size:.9em;color:#000;border:1px solid #555;padding:2px;padding-right:5px;padding-left:5px}.task-board-icons{text-align:right;margin-top:8px}.task-board-icons a{opacity:.5}.task-board-icons span{opacity:.5;margin-left:2px}.task-board-icons a:hover,.task-board-icons span:hover{opacity:1.0}.task-board-date{font-weight:bold;color:#000}span.task-board-date-overdue{color:#d90000;opacity:1.0}.task-score{font-weight:bold}.task-board .task-score{font-size:1.1em}.task-show-details .task-score{position:absolute;bottom:5px;right:5px;font-size:2em}.task-board-closed,.task-board-days{position:absolute;right:5px;top:5px;opacity:.5;font-size:.8em}.task-board-days:hover{opacity:1.0}.task-days-age{border:#666 1px solid;padding:1px 4px 1px 2px;border-top-left-radius:3px;border-bottom-left-radius:3px}.task-days-incolumn{border:#666 1px solid;border-left:0;margin-left:-5px;padding:1px 2px 1px 4px;border-top-right-radius:3px;border-bottom-right-radius:3px}.board-container-compact .task-board-days{display:none}#task-summary{margin-bottom:15px}#task-summary h2{color:#666;font-size:2.5em;margin-top:0;padding-top:0}.task-summary-container{border:2px solid #000;border-radius:8px;padding:15px;display:-webkit-flex;display:flex;-webkit-flex-direction:row;flex-direction:row;-webkit-justify-content:space-between;justify-content:space-between}.task-summary-column{font-size:.9em;color:#666}.task-summary-column span{color:#555}.task-summary-column li{line-height:23px}.task-show-section{margin-top:30px;margin-bottom:20px}.task-show-files a{font-weight:bold;text-decoration:none}.task-show-files li{margin-left:25px;list-style-type:square;line-height:25px}.task-show-file-actions{font-size:.75em}.task-show-file-actions:before{content:" ["}.task-show-file-actions:after{content:"]"}.task-show-file-actions a{color:#333}.task-show-description{border-left:4px solid #333;padding-left:20px}.task-show-description-textarea{width:99%;max-width:99%;height:300px}.task-file-viewer{position:relative}.task-file-viewer img{max-width:95%;max-height:85%;margin-top:10px}.task-time-form{margin-top:10px;margin-bottom:25px;padding:3px}.task-link-closed{text-decoration:line-through}.task-show-images{list-style-type:none}.task-show-images li img{width:100%}.task-show-images li .img_container{width:250px;height:100px;overflow:hidden}.task-show-images li{padding:10px;overflow:auto;width:250px;min-height:120px;display:inline-block;vertical-align:top}.task-show-images li p{padding:5px;font-weight:bold}.task-show-images li:hover{background:#eee}.task-show-image-actions{margin-left:5px}.task-show-file-table{width:auto}.flag-milestone{color:green}.color-picker{min-height:35px}.color-square{display:inline-block;width:30px;height:30px;margin-right:5px;margin-bottom:5px;border:1px solid #000;cursor:pointer}.color-square:hover{border-style:dotted}div.color-square-selected{border-width:2px;width:28px;height:28px;box-shadow:3px 2px 10px 0 rgba(180,180,180,0.9)}.assign-me{font-size:.8em;vertical-align:bottom}.comment{margin-bottom:20px}.comment:hover{background:#f7f8e0}.comment-inner{border-left:4px solid #333;padding-bottom:10px;padding-left:20px;margin-left:20px;margin-right:10px}.comment-preview{border:2px solid #000;border-radius:3px;padding:10px}.comment-preview .comment-inner{border:0;padding:0;margin:0}.comment-title{margin-bottom:8px;padding-bottom:3px;border-bottom:1px dotted #aaa}.ui-tooltip .comment-title{font-size:80%}.ui-tooltip .comment-inner{padding-bottom:0}.comment-actions{font-size:.8em;padding:0;text-align:right}.comment-actions li{display:inline;padding-left:5px;padding-right:5px;border-right:1px dotted #000}.comment-actions li:last-child{padding-right:0;border:0}.comment-username{font-weight:bold}.comment-textarea{height:200px;width:80%;max-width:800px}.comment-sorting{font-size:.5em}span.comment-sorting a{color:#555;font-weight:normal;text-decoration:none}span.comment-sorting a:hover{color:#aaa}#comments .comment-textarea{height:80px;width:500px}.subtasks-table{font-size:.85em}.subtasks-table td{vertical-align:middle}.markdown{line-height:1.4em;font-size:1.0}.markdown h1{margin-top:5px;margin-bottom:10px;font-size:1.5em;font-weight:bold;text-decoration:underline}.markdown h2{font-size:1.2em;font-weight:bold;text-decoration:underline}.markdown h3{font-size:1.1em;text-decoration:underline}.markdown h4{font-size:1.1em;text-decoration:underline}.markdown p{margin-bottom:10px}.markdown ol,.markdown ul{margin-left:25px;margin-top:10px;margin-bottom:10px}.markdown pre{background:#fbfbfb;padding:10px;border-radius:5px;border:1px solid #ddd;overflow:auto;color:#444}.markdown blockquote{font-style:italic;border-left:3px solid #ddd;padding-left:10px;margin-bottom:10px;margin-left:20px}.markdown img{display:block;max-width:80%;margin-top:10px}.documentation{margin:0 auto;padding:20px;max-width:850px;background:#fefefe;border:1px solid #ccc;border-radius:5px;font-size:1.1em;color:#555}.documentation img{border:1px solid #333}.documentation h1{text-decoration:none;font-size:1.8em;margin-bottom:30px}.documentation h2{font-size:1.3em;text-decoration:none;border-bottom:1px solid #ccc;margin-bottom:25px}.documentation li{line-height:30px}.user-mention-link{font-weight:bold;color:#000;text-decoration:none}.user-mention-link:hover{color:#555}
\ No newline at end of file +.c3 svg{font:10px sans-serif}.c3 line,.c3 path{fill:none;stroke:#000}.c3 text{-webkit-user-select:none;-moz-user-select:none;user-select:none}.c3-bars path,.c3-event-rect,.c3-legend-item-tile,.c3-xgrid-focus,.c3-ygrid{shape-rendering:crispEdges}.c3-chart-arc path{stroke:#fff}.c3-chart-arc text{fill:#fff;font-size:13px}.c3-grid line{stroke:#aaa}.c3-grid text{fill:#aaa}.c3-xgrid,.c3-ygrid{stroke-dasharray:3 3}.c3-text.c3-empty{fill:gray;font-size:2em}.c3-line{stroke-width:1px}.c3-circle._expanded_{stroke-width:1px;stroke:#fff}.c3-selected-circle{fill:#fff;stroke-width:2px}.c3-bar{stroke-width:0}.c3-bar._expanded_{fill-opacity:.75}.c3-target.c3-focused{opacity:1}.c3-target.c3-focused path.c3-line,.c3-target.c3-focused path.c3-step{stroke-width:2px}.c3-target.c3-defocused{opacity:.3!important}.c3-region{fill:#4682b4;fill-opacity:.1}.c3-brush .extent{fill-opacity:.1}.c3-legend-item{font-size:12px}.c3-legend-item-hidden{opacity:.15}.c3-legend-background{opacity:.75;fill:#fff;stroke:#d3d3d3;stroke-width:1}.c3-tooltip-container{z-index:10}.c3-tooltip{border-collapse:collapse;border-spacing:0;background-color:#fff;empty-cells:show;-webkit-box-shadow:7px 7px 12px -9px #777;-moz-box-shadow:7px 7px 12px -9px #777;box-shadow:7px 7px 12px -9px #777;opacity:.9}.c3-tooltip tr{border:1px solid #CCC}.c3-tooltip th{background-color:#aaa;font-size:14px;padding:2px 5px;text-align:left;color:#FFF}.c3-tooltip td{font-size:13px;padding:3px 6px;background-color:#fff;border-left:1px dotted #999}.c3-tooltip td>span{display:inline-block;width:10px;height:10px;margin-right:6px}.c3-tooltip td.value{text-align:right}.c3-area{stroke-width:0;opacity:.2}.c3-chart-arcs-title{dominant-baseline:middle;font-size:1.3em}.c3-chart-arcs .c3-chart-arcs-background{fill:#e0e0e0;stroke:none}.c3-chart-arcs .c3-chart-arcs-gauge-unit{fill:#000;font-size:16px}.c3-chart-arcs .c3-chart-arcs-gauge-max,.c3-chart-arcs .c3-chart-arcs-gauge-min{fill:#777}.c3-chart-arc .c3-gauge-value{fill:#000}header,.sidebar,.form-comment,.page-header{display:none}a{color:#36c;border:0}a:focus{outline:0;color:#df5353;text-decoration:none;border:1px dotted #aaa}a:hover{color:#333;text-decoration:none}table{width:100%;border-collapse:collapse;border-spacing:0;margin-bottom:20px;font-size:.95em}#calendar table{margin-bottom:0}th,td{border:1px solid #eee;padding-top:.5em;padding-bottom:.5em;padding-left:3px;padding-right:3px}td{vertical-align:top}th{background:#fbfbfb;text-align:left}td li{margin-left:20px}.table-small{font-size:.8em}th a{text-decoration:none;color:#333}th a:focus,th a:hover{text-decoration:underline}.table-fixed{table-layout:fixed;white-space:nowrap}.table-fixed th{overflow:hidden}.table-fixed td{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.table-stripped tr:nth-child(odd) td{background:#fefefe}.column-3{width:3%}.column-5{width:5%}.column-8{width:7.5%}.column-10{width:10%}.column-12{width:12%}.column-15{width:15%}.column-18{width:18%}.column-20{width:20%}.column-25{width:25%}.column-30{width:30%}.column-35{width:35%}.column-40{width:40%}.column-50{width:50%}.column-60{width:60%}.column-70{width:70%}.public-board{margin-top:5px}.public-task{max-width:800px;margin:0 auto;margin-top:5px}#board-container{overflow-x:auto}#board{table-layout:fixed;margin-bottom:0}#board th.board-column-header{width:240px}#board td{vertical-align:top}.board-container-compact{overflow-x:initial}@media all and (-ms-high-contrast:active),(-ms-high-contrast:none){.board-container-compact #board{table-layout:auto}}#board th.board-column-header.board-column-compact{width:initial}.board-column-collapsed{display:none}td.board-column-task-collapsed{font-weight:bold;background-color:#fbfbfb}#board th.board-column-header-collapsed{width:28px;min-width:28px;text-align:center;overflow:hidden}.board-rotation-wrapper{position:relative;padding:8px 4px;min-height:150px;overflow:hidden}.board-rotation{white-space:nowrap;-webkit-backface-visibility:hidden;-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg);-webkit-transform-origin:0 100%;-moz-transform-origin:0 100%;-ms-transform-origin:0 100%;transform-origin:0 100%}.board-column-title .dropdown-menu{text-decoration:none}.board-add-icon{float:left;padding:0 5px}.board-add-icon a{text-decoration:none;color:#36c;font-size:150%;line-height:70%}.board-add-icon a:focus,.board-add-icon a:hover{text-decoration:none;color:red}.board-column-header-task-count{color:#999;font-weight:normal}th.board-column-header-collapsed .board-column-header-task-count{font-size:.85em}a.board-swimlane-toggle{font-size:.95em;text-decoration:none}a.board-swimlane-toggle:hover,a.board-swimlane-toggle:focus{color:#000;text-decoration:none;border:0}.board-task-list{overflow:auto;min-height:60px}.board-task-list-limit{background-color:#df5353}.draggable-item{cursor:pointer;user-select:none}.draggable-placeholder{border:2px dashed #000;background:#fafafa;height:70px;margin-bottom:10px}div.draggable-item-selected{border:1px solid #000}.task-board-sort-handle{float:left;padding-right:5px}.task-board-saving-state{opacity:.3}.task-board-saving-icon{position:absolute;margin:auto;width:100%;text-align:center;color:#000}.task-board{position:relative;margin-bottom:4px;border:1px solid #000;padding:2px;font-size:.85em;word-wrap:break-word}div.task-board-recent{border-width:2px}div.task-board-status-closed{user-select:none;border:1px dotted #555}.task-table a,.task-board a{color:#000;text-decoration:none;font-weight:bold}.task-table a:focus,.task-table a:hover,.task-board a:focus,.task-board a:hover{text-decoration:underline}.task-board-collapsed{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}a.task-board-collapsed-title{font-weight:normal}.task-board .dropdown{font-size:1.1em}.task-board-title{margin-top:5px;margin-bottom:5px;font-size:1.1em}.task-board-title a{font-weight:normal}.task-board-user{font-size:.8em}.task-board-current-user a{text-decoration:underline}.task-board-current-user a:focus,.task-board-current-user a:hover{text-decoration:none}a.task-board-nobody{font-weight:normal;font-style:italic;color:#444}.task-board-category-container{text-align:right}.task-board-category{font-weight:bold;font-size:.9em;color:#000;border:1px solid #555;padding:2px;padding-right:5px;padding-left:5px}.task-board-icons{text-align:right;margin-top:8px}.task-board-icons a{opacity:.5}.task-board-icons span{opacity:.5;margin-left:2px}.task-board-icons a:hover,.task-board-icons span:hover{opacity:1.0}.task-board-date{font-weight:bold;color:#000}span.task-board-date-overdue{color:#d90000;opacity:1.0}.task-board .task-score{font-weight:bold;font-size:1.1em}.task-board-closed,.task-board-days{position:absolute;right:5px;top:5px;opacity:.5;font-size:.8em}.task-board-days:hover{opacity:1.0}.task-days-age{border:#666 1px solid;padding:1px 4px 1px 2px;border-top-left-radius:3px;border-bottom-left-radius:3px}.task-days-incolumn{border:#666 1px solid;border-left:0;margin-left:-5px;padding:1px 2px 1px 4px;border-top-right-radius:3px;border-bottom-right-radius:3px}.board-container-compact .task-board-days{display:none}#task-summary{margin-bottom:15px}#task-summary h2{color:#666;font-size:2.5em;margin-top:0;padding-top:0}.task-summary-container{border:2px solid #000;border-radius:8px;padding:15px;display:-webkit-flex;display:flex;-webkit-flex-direction:row;flex-direction:row;-webkit-justify-content:space-between;justify-content:space-between}.task-summary-column{font-size:.9em;color:#666}.task-summary-column span{color:#555}.task-summary-column li{line-height:23px}.task-show-description{border-left:4px solid #333;padding-left:20px}.task-show-description-textarea{width:99%;max-width:99%;height:300px}.task-file-viewer{position:relative}.task-file-viewer img{max-width:95%;max-height:85%;margin-top:10px}.task-time-form{margin-top:10px;margin-bottom:25px;padding:3px}.task-link-closed{text-decoration:line-through}.flag-milestone{color:green}.color-picker{min-height:35px}.color-square{display:inline-block;width:30px;height:30px;margin-right:5px;margin-bottom:5px;border:1px solid #000;cursor:pointer}.color-square:hover{border-style:dotted}div.color-square-selected{border-width:2px;width:28px;height:28px;box-shadow:3px 2px 10px 0 rgba(180,180,180,0.9)}.assign-me{font-size:.8em;vertical-align:bottom}.comment{margin-bottom:20px}.comment:hover{background:#f7f8e0}.comment-inner{border-left:4px solid #333;padding-bottom:10px;padding-left:20px;margin-left:20px;margin-right:10px}.comment-preview{border:2px solid #000;border-radius:3px;padding:10px}.comment-preview .comment-inner{border:0;padding:0;margin:0}.comment-title{margin-bottom:8px;padding-bottom:3px;border-bottom:1px dotted #aaa}.ui-tooltip .comment-title{font-size:80%}.ui-tooltip .comment-inner{padding-bottom:0}.comment-actions{font-size:.8em;padding:0;text-align:right}.comment-actions li{display:inline;padding-left:5px;padding-right:5px;border-right:1px dotted #000}.comment-actions li:last-child{padding-right:0;border:0}.comment-username{font-weight:bold}.comment-textarea{height:200px;width:80%;max-width:800px}.comment-sorting{font-size:.5em}span.comment-sorting a{color:#555;font-weight:normal;text-decoration:none}span.comment-sorting a:hover{color:#aaa}#comments .comment-textarea{height:80px;width:500px}.subtasks-table{font-size:.85em}.subtasks-table td{vertical-align:middle}.markdown{line-height:1.4em;font-size:1.0}.markdown h1{margin-top:5px;margin-bottom:10px;font-size:1.5em;font-weight:bold;text-decoration:underline}.markdown h2{font-size:1.2em;font-weight:bold;text-decoration:underline}.markdown h3{font-size:1.1em;text-decoration:underline}.markdown h4{font-size:1.1em;text-decoration:underline}.markdown p{margin-bottom:10px}.markdown ol,.markdown ul{margin-left:25px;margin-top:10px;margin-bottom:10px}.markdown pre{background:#fbfbfb;padding:10px;border-radius:5px;border:1px solid #ddd;overflow:auto;color:#444}.markdown blockquote{font-style:italic;border-left:3px solid #ddd;padding-left:10px;margin-bottom:10px;margin-left:20px}.markdown img{display:block;max-width:80%;margin-top:10px}.documentation{margin:0 auto;padding:20px;max-width:850px;background:#fefefe;border:1px solid #ccc;border-radius:5px;font-size:1.1em;color:#555}.documentation img{border:1px solid #333}.documentation h1{text-decoration:none;font-size:1.8em;margin-bottom:30px}.documentation h2{font-size:1.3em;text-decoration:none;border-bottom:1px solid #ccc;margin-bottom:25px}.documentation li{line-height:30px}.user-mention-link{font-weight:bold;color:#000;text-decoration:none}.user-mention-link:hover{color:#555}
\ No newline at end of file diff --git a/assets/css/src/dropdown.css b/assets/css/src/dropdown.css index 8f13f5bf..7d967b06 100644 --- a/assets/css/src/dropdown.css +++ b/assets/css/src/dropdown.css @@ -71,11 +71,16 @@ ul.dropdown-submenu-open { padding-right: 10px; } +.dropdown-menu-link-text, .dropdown-menu-link-icon { color: #333; text-decoration: none; } +.dropdown-menu-link-text:hover { + text-decoration: underline; +} + /* textarea dropdown */ .textarea-dropdown { list-style: none; diff --git a/assets/css/src/files.css b/assets/css/src/files.css new file mode 100644 index 00000000..d21a5e08 --- /dev/null +++ b/assets/css/src/files.css @@ -0,0 +1,46 @@ +.file-thumbnails { + display: -webkit-flex; + display: flex; + -webkit-flex-direction: row; + flex-direction: row; + -webkit-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-justify-content: flex-start; + justify-content: flex-start; +} + +.file-thumbnail { + width: 250px; + border: 1px solid #efefef; + border-radius: 5px; + margin-bottom: 20px; + box-shadow: 2px 2px 5px -2px rgba(0, 0, 0, 0.55); + margin-right: 15px; +} + +.file-thumbnail img { + border-top-left-radius: 5px; + border-top-right-radius: 5px; +} + +.file-thumbnail img:hover { + opacity: 0.5; +} + +.file-thumbnail-content { + padding-left: 8px; + padding-right: 8px; +} + +.file-thumbnail-title { + font-weight: 700; + font-size: 0.9em; + color: #555; +} + +.file-thumbnail-description { + font-size: 0.8em; + color: #aaa; + margin-top: 8px; + margin-bottom: 5px; +} diff --git a/assets/css/src/filters.css b/assets/css/src/filters.css index 8f889556..0e0a35e7 100644 --- a/assets/css/src/filters.css +++ b/assets/css/src/filters.css @@ -1,68 +1,57 @@ -.toolbar { - font-size: 0.9em; - padding-top: 5px; +.project-header { + margin-top: 8px; + margin-bottom: 20px; } -.views { +.filter-box { display: inline-block; - margin-right: 10px; - font-size: 0.9em; + position: relative; + font-size: 0; + margin-bottom: 20px; } -.views li { - border: 1px solid #eee; - padding-left: 8px; - padding-right: 8px; - padding-top: 5px; - padding-bottom: 5px; - display: inline; +.project-header .filter-box { + margin: 0; } -.menu-inline li.active a, -.views li.active a { - font-weight: bold; - color: #000; - text-decoration: none; +.filter-box form { + margin: 0; } -.views li:first-child { - border-right: none; +.filter-box input[type="text"] { + margin: 0; + font-size: 16px; + height: 26px; + border-color: #dedede; border-top-left-radius: 5px; border-bottom-left-radius: 5px; + vertical-align: top; } -.views li:last-child { - border-left: none; - border-top-right-radius: 5px; - border-bottom-right-radius: 5px; +.filter-box input[type="text"]:focus { + color: #000; + border-color: rgba(82, 168, 236, 0.8); + outline: 0; + box-shadow: 0 0 8px rgba(82, 168, 236, 0.6); } -.filters { +.filter-box div.dropdown { display: inline-block; - border: 1px solid #eee; - border-radius: 5px; - padding: 5px; - padding-right: 10px; - margin-left: 8px; -} - -.filters ul { - font-size: 0.8em; -} - -.page-header .filters ul { - font-size: 0.9em; -} - -form.search { - display: inline; + font-size: 16px; + border: 1px solid #dedede; + border-left: none; + margin: 0; + padding: 0; + padding-left: 5px; + padding-right: 8px; + height: 27px; } -div.search { - margin-bottom: 20px; +.filter-box div.dropdown:last-child { + border-top-right-radius: 5px; + border-bottom-right-radius: 5px; } -.filter-dropdowns { - font-size: 0.9em; - display: inline-block; +.filter-box div.dropdown a { + line-height: 27px; } diff --git a/assets/css/src/responsive.css b/assets/css/src/responsive.css index 3ec47255..c94be166 100644 --- a/assets/css/src/responsive.css +++ b/assets/css/src/responsive.css @@ -1,14 +1,3 @@ -@media only screen and (max-width: 1080px) { - div.filter-dropdowns .filters { - margin-left: 0; - } - - div.filter-dropdowns { - display: block; - margin-top: 5px; - } -} - @media only screen and (max-width: 1024px) { body { @@ -32,15 +21,6 @@ max-width: 150px; } - .task-time-form label { - display: block; - } - - .task-time-form input[type="submit"] { - margin-top: 10px; - display: block; - } - .page-header .form-input-large { width: 300px; } diff --git a/assets/css/src/screenshot.css b/assets/css/src/screenshot.css deleted file mode 100644 index 4d917200..00000000 --- a/assets/css/src/screenshot.css +++ /dev/null @@ -1,19 +0,0 @@ -#screenshot-zone { - position: relative; - border: 2px dashed #ccc; - width: 90%; - height: 250px; - overflow: auto; -} - -#screenshot-inner { - position: absolute; - left: 0; - bottom: 48%; - width: 100%; - text-align: center; -} - -#screenshot-zone.screenshot-pasted { - border: 2px solid #333; -}
\ No newline at end of file diff --git a/assets/css/src/task.css b/assets/css/src/task.css index fd5b8efc..57acb52a 100644 --- a/assets/css/src/task.css +++ b/assets/css/src/task.css @@ -118,21 +118,11 @@ span.task-board-date-overdue { } /* task score */ -.task-score { - font-weight: bold; -} - .task-board .task-score { + font-weight: bold; font-size: 1.1em; } -.task-show-details .task-score { - position: absolute; - bottom: 5px; - right: 5px; - font-size: 2em; -} - /* task age */ .task-board-closed, .task-board-days { @@ -167,7 +157,7 @@ span.task-board-date-overdue { display: none; } -/* task view */ +/* task summary */ #task-summary { margin-bottom: 15px; } @@ -204,38 +194,6 @@ span.task-board-date-overdue { line-height: 23px; } -.task-show-section { - margin-top: 30px; - margin-bottom: 20px; -} - -.task-show-files a { - font-weight: bold; - text-decoration: none; -} - -.task-show-files li { - margin-left: 25px; - list-style-type: square; - line-height: 25px; -} - -.task-show-file-actions { - font-size: 0.75em; -} - -.task-show-file-actions:before { - content: " ["; -} - -.task-show-file-actions:after { - content: "]"; -} - -.task-show-file-actions a { - color: #333; -} - .task-show-description { border-left: 4px solid #333; padding-left: 20px; @@ -267,46 +225,6 @@ span.task-board-date-overdue { text-decoration: line-through; } -.task-show-images { - list-style-type: none; -} - -.task-show-images li img { - width: 100%; -} - -.task-show-images li .img_container { - width: 250px; - height: 100px; - overflow: hidden; -} - -.task-show-images li { - padding: 10px; - overflow: auto; - width: 250px; - min-height: 120px; - display: inline-block; - vertical-align: top; -} - -.task-show-images li p{ - padding: 5px; - font-weight: bold; -} - -.task-show-images li:hover { - background: #eee; -} - -.task-show-image-actions { - margin-left: 5px; -} - -.task-show-file-table { - width: auto; -} - .flag-milestone { color: green; } diff --git a/assets/css/src/upload.css b/assets/css/src/upload.css new file mode 100644 index 00000000..aa46bc7a --- /dev/null +++ b/assets/css/src/upload.css @@ -0,0 +1,39 @@ +#file-dropzone, +#screenshot-zone { + position: relative; + border: 2px dashed #ccc; + width: 99%; + height: 250px; + overflow: auto; +} + +#file-dropzone-inner, +#screenshot-inner { + position: absolute; + left: 0; + bottom: 48%; + width: 100%; + text-align: center; + color: #aaa; +} + +#screenshot-zone.screenshot-pasted { + border: 2px solid #333; +} + +#file-list { + margin: 20px; +} + +#file-list li { + list-style-type: none; + padding-top: 8px; + padding-bottom: 8px; + border-bottom: 1px dotted #ddd; + width: 95%; +} + +#file-list li.file-error { + font-weight: bold; + color: #b94a48; +} diff --git a/assets/css/src/views.css b/assets/css/src/views.css new file mode 100644 index 00000000..191b30c6 --- /dev/null +++ b/assets/css/src/views.css @@ -0,0 +1,34 @@ +.views { + display: inline-block; + margin-left: 10px; + margin-right: 10px; + font-size: 0.9em; +} + +.views li { + border: 1px solid #eee; + padding-left: 8px; + padding-right: 8px; + padding-top: 5px; + padding-bottom: 5px; + display: inline; +} + +.menu-inline li.active a, +.views li.active a { + font-weight: bold; + color: #000; + text-decoration: none; +} + +.views li:first-child { + border-right: none; + border-top-left-radius: 5px; + border-bottom-left-radius: 5px; +} + +.views li:last-child { + border-left: none; + border-top-right-radius: 5px; + border-bottom-right-radius: 5px; +} diff --git a/assets/js/app.js b/assets/js/app.js index 4362daab..b0780f01 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -1278,4 +1278,4 @@ if (typeof jQuery === 'undefined') { return jQuery; })); -!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){function c(a){return a>1&&5>a&&1!==~~(a/10)}function d(a,b,d,e){var f=a+" ";switch(d){case"s":return b||e?"pár sekund":"pár sekundami";case"m":return b?"minuta":e?"minutu":"minutou";case"mm":return b||e?f+(c(a)?"minuty":"minut"):f+"minutami";case"h":return b?"hodina":e?"hodinu":"hodinou";case"hh":return b||e?f+(c(a)?"hodiny":"hodin"):f+"hodinami";case"d":return b||e?"den":"dnem";case"dd":return b||e?f+(c(a)?"dny":"dní"):f+"dny";case"M":return b||e?"měsíc":"měsícem";case"MM":return b||e?f+(c(a)?"měsíce":"měsíců"):f+"měsíci";case"y":return b||e?"rok":"rokem";case"yy":return b||e?f+(c(a)?"roky":"let"):f+"lety"}}var e="leden_únor_březen_duben_květen_červen_červenec_srpen_září_říjen_listopad_prosinec".split("_"),f="led_úno_bře_dub_kvě_čvn_čvc_srp_zář_říj_lis_pro".split("_");(b.defineLocale||b.lang).call(b,"cs",{months:e,monthsShort:f,monthsParse:function(a,b){var c,d=[];for(c=0;12>c;c++)d[c]=new RegExp("^"+a[c]+"$|^"+b[c]+"$","i");return d}(e,f),weekdays:"neděle_pondělí_úterý_středa_čtvrtek_pátek_sobota".split("_"),weekdaysShort:"ne_po_út_st_čt_pá_so".split("_"),weekdaysMin:"ne_po_út_st_čt_pá_so".split("_"),longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd D. MMMM YYYY LT"},calendar:{sameDay:"[dnes v] LT",nextDay:"[zítra v] LT",nextWeek:function(){switch(this.day()){case 0:return"[v neděli v] LT";case 1:case 2:return"[v] dddd [v] LT";case 3:return"[ve středu v] LT";case 4:return"[ve čtvrtek v] LT";case 5:return"[v pátek v] LT";case 6:return"[v sobotu v] LT"}},lastDay:"[včera v] LT",lastWeek:function(){switch(this.day()){case 0:return"[minulou neděli v] LT";case 1:case 2:return"[minulé] dddd [v] LT";case 3:return"[minulou středu v] LT";case 4:case 5:return"[minulý] dddd [v] LT";case 6:return"[minulou sobotu v] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"před %s",s:d,m:d,mm:d,h:d,hh:d,d:d,dd:d,M:d,MM:d,y:d,yy:d},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("cs","cs",{closeText:"Zavřít",prevText:"<Dříve",nextText:"Později>",currentText:"Nyní",monthNames:["leden","únor","březen","duben","květen","červen","červenec","srpen","září","říjen","listopad","prosinec"],monthNamesShort:["led","úno","bře","dub","kvě","čer","čvc","srp","zář","říj","lis","pro"],dayNames:["neděle","pondělí","úterý","středa","čtvrtek","pátek","sobota"],dayNamesShort:["ne","po","út","st","čt","pá","so"],dayNamesMin:["ne","po","út","st","čt","pá","so"],weekHeader:"Týd",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("cs",{buttonText:{month:"Měsíc",week:"Týden",day:"Den",list:"Agenda"},allDayText:"Celý den",eventLimitText:function(a){return"+další: "+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"da",{months:"januar_februar_marts_april_maj_juni_juli_august_september_oktober_november_december".split("_"),monthsShort:"jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec".split("_"),weekdays:"søndag_mandag_tirsdag_onsdag_torsdag_fredag_lørdag".split("_"),weekdaysShort:"søn_man_tir_ons_tor_fre_lør".split("_"),weekdaysMin:"sø_ma_ti_on_to_fr_lø".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd [d.] D. MMMM YYYY LT"},calendar:{sameDay:"[I dag kl.] LT",nextDay:"[I morgen kl.] LT",nextWeek:"dddd [kl.] LT",lastDay:"[I går kl.] LT",lastWeek:"[sidste] dddd [kl] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"%s siden",s:"få sekunder",m:"et minut",mm:"%d minutter",h:"en time",hh:"%d timer",d:"en dag",dd:"%d dage",M:"en måned",MM:"%d måneder",y:"et år",yy:"%d år"},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("da","da",{closeText:"Luk",prevText:"<Forrige",nextText:"Næste>",currentText:"Idag",monthNames:["Januar","Februar","Marts","April","Maj","Juni","Juli","August","September","Oktober","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],dayNames:["Søndag","Mandag","Tirsdag","Onsdag","Torsdag","Fredag","Lørdag"],dayNamesShort:["Søn","Man","Tir","Ons","Tor","Fre","Lør"],dayNamesMin:["Sø","Ma","Ti","On","To","Fr","Lø"],weekHeader:"Uge",dateFormat:"dd-mm-yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("da",{buttonText:{month:"Måned",week:"Uge",day:"Dag",list:"Agenda"},allDayText:"Hele dagen",eventLimitText:"flere"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){function c(a,b,c,d){var e={m:["eine Minute","einer Minute"],h:["eine Stunde","einer Stunde"],d:["ein Tag","einem Tag"],dd:[a+" Tage",a+" Tagen"],M:["ein Monat","einem Monat"],MM:[a+" Monate",a+" Monaten"],y:["ein Jahr","einem Jahr"],yy:[a+" Jahre",a+" Jahren"]};return b?e[c][0]:e[c][1]}(b.defineLocale||b.lang).call(b,"de",{months:"Januar_Februar_März_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember".split("_"),monthsShort:"Jan._Febr._Mrz._Apr._Mai_Jun._Jul._Aug._Sept._Okt._Nov._Dez.".split("_"),weekdays:"Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag".split("_"),weekdaysShort:"So._Mo._Di._Mi._Do._Fr._Sa.".split("_"),weekdaysMin:"So_Mo_Di_Mi_Do_Fr_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd, D. MMMM YYYY LT"},calendar:{sameDay:"[Heute um] LT [Uhr]",sameElse:"L",nextDay:"[Morgen um] LT [Uhr]",nextWeek:"dddd [um] LT [Uhr]",lastDay:"[Gestern um] LT [Uhr]",lastWeek:"[letzten] dddd [um] LT [Uhr]"},relativeTime:{future:"in %s",past:"vor %s",s:"ein paar Sekunden",m:c,mm:"%d Minuten",h:c,hh:"%d Stunden",d:c,dd:c,M:c,MM:c,y:c,yy:c},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("de","de",{closeText:"Schließen",prevText:"<Zurück",nextText:"Vor>",currentText:"Heute",monthNames:["Januar","Februar","März","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember"],monthNamesShort:["Jan","Feb","Mär","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Dez"],dayNames:["Sonntag","Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag"],dayNamesShort:["So","Mo","Di","Mi","Do","Fr","Sa"],dayNamesMin:["So","Mo","Di","Mi","Do","Fr","Sa"],weekHeader:"KW",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("de",{buttonText:{month:"Monat",week:"Woche",day:"Tag",list:"Terminübersicht"},allDayText:"Ganztägig",eventLimitText:function(a){return"+ weitere "+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){var c="ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.".split("_"),d="ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic".split("_");(b.defineLocale||b.lang).call(b,"es",{months:"enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre".split("_"),monthsShort:function(a,b){return/-MMM-/.test(b)?d[a.month()]:c[a.month()]},weekdays:"domingo_lunes_martes_miércoles_jueves_viernes_sábado".split("_"),weekdaysShort:"dom._lun._mar._mié._jue._vie._sáb.".split("_"),weekdaysMin:"Do_Lu_Ma_Mi_Ju_Vi_Sá".split("_"),longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY LT",LLLL:"dddd, D [de] MMMM [de] YYYY LT"},calendar:{sameDay:function(){return"[hoy a la"+(1!==this.hours()?"s":"")+"] LT"},nextDay:function(){return"[mañana a la"+(1!==this.hours()?"s":"")+"] LT"},nextWeek:function(){return"dddd [a la"+(1!==this.hours()?"s":"")+"] LT"},lastDay:function(){return"[ayer a la"+(1!==this.hours()?"s":"")+"] LT"},lastWeek:function(){return"[el] dddd [pasado a la"+(1!==this.hours()?"s":"")+"] LT"},sameElse:"L"},relativeTime:{future:"en %s",past:"hace %s",s:"unos segundos",m:"un minuto",mm:"%d minutos",h:"una hora",hh:"%d horas",d:"un día",dd:"%d días",M:"un mes",MM:"%d meses",y:"un año",yy:"%d años"},ordinalParse:/\d{1,2}º/,ordinal:"%dº",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("es","es",{closeText:"Cerrar",prevText:"<Ant",nextText:"Sig>",currentText:"Hoy",monthNames:["enero","febrero","marzo","abril","mayo","junio","julio","agosto","septiembre","octubre","noviembre","diciembre"],monthNamesShort:["ene","feb","mar","abr","may","jun","jul","ago","sep","oct","nov","dic"],dayNames:["domingo","lunes","martes","miércoles","jueves","viernes","sábado"],dayNamesShort:["dom","lun","mar","mié","jue","vie","sáb"],dayNamesMin:["D","L","M","X","J","V","S"],weekHeader:"Sm",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("es",{buttonText:{month:"Mes",week:"Semana",day:"Día",list:"Agenda"},allDayHtml:"Todo<br/>el día",eventLimitText:"más"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"el",{monthsNominativeEl:"Ιανουάριος_Φεβρουάριος_Μάρτιος_Απρίλιος_Μάιος_Ιούνιος_Ιούλιος_Αύγουστος_Σεπτέμβριος_Οκτώβριος_Νοέμβριος_Δεκέμβριος".split("_"),monthsGenitiveEl:"Ιανουαρίου_Φεβρουαρίου_Μαρτίου_Απριλίου_Μαΐου_Ιουνίου_Ιουλίου_Αυγούστου_Σεπτεμβρίου_Οκτωβρίου_Νοεμβρίου_Δεκεμβρίου".split("_"),months:function(a,b){return/D/.test(b.substring(0,b.indexOf("MMMM")))?this._monthsGenitiveEl[a.month()]:this._monthsNominativeEl[a.month()]},monthsShort:"Ιαν_Φεβ_Μαρ_Απρ_Μαϊ_Ιουν_Ιουλ_Αυγ_Σεπ_Οκτ_Νοε_Δεκ".split("_"),weekdays:"Κυριακή_Δευτέρα_Τρίτη_Τετάρτη_Πέμπτη_Παρασκευή_Σάββατο".split("_"),weekdaysShort:"Κυρ_Δευ_Τρι_Τετ_Πεμ_Παρ_Σαβ".split("_"),weekdaysMin:"Κυ_Δε_Τρ_Τε_Πε_Πα_Σα".split("_"),meridiem:function(a,b,c){return a>11?c?"μμ":"ΜΜ":c?"πμ":"ΠΜ"},isPM:function(a){return"μ"===(a+"").toLowerCase()[0]},meridiemParse:/[ΠΜ]\.?Μ?\.?/i,longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendarEl:{sameDay:"[Σήμερα {}] LT",nextDay:"[Αύριο {}] LT",nextWeek:"dddd [{}] LT",lastDay:"[Χθες {}] LT",lastWeek:function(){switch(this.day()){case 6:return"[το προηγούμενο] dddd [{}] LT";default:return"[την προηγούμενη] dddd [{}] LT"}},sameElse:"L"},calendar:function(a,b){var c=this._calendarEl[a],d=b&&b.hours();return"function"==typeof c&&(c=c.apply(b)),c.replace("{}",d%12===1?"στη":"στις")},relativeTime:{future:"σε %s",past:"%s πριν",s:"λίγα δευτερόλεπτα",m:"ένα λεπτό",mm:"%d λεπτά",h:"μία ώρα",hh:"%d ώρες",d:"μία μέρα",dd:"%d μέρες",M:"ένας μήνας",MM:"%d μήνες",y:"ένας χρόνος",yy:"%d χρόνια"},ordinalParse:/\d{1,2}η/,ordinal:"%dη",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("el","el",{closeText:"Κλείσιμο",prevText:"Προηγούμενος",nextText:"Επόμενος",currentText:"Σήμερα",monthNames:["Ιανουάριος","Φεβρουάριος","Μάρτιος","Απρίλιος","Μάιος","Ιούνιος","Ιούλιος","Αύγουστος","Σεπτέμβριος","Οκτώβριος","Νοέμβριος","Δεκέμβριος"],monthNamesShort:["Ιαν","Φεβ","Μαρ","Απρ","Μαι","Ιουν","Ιουλ","Αυγ","Σεπ","Οκτ","Νοε","Δεκ"],dayNames:["Κυριακή","Δευτέρα","Τρίτη","Τετάρτη","Πέμπτη","Παρασκευή","Σάββατο"],dayNamesShort:["Κυρ","Δευ","Τρι","Τετ","Πεμ","Παρ","Σαβ"],dayNamesMin:["Κυ","Δε","Τρ","Τε","Πε","Πα","Σα"],weekHeader:"Εβδ",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("el",{buttonText:{month:"Μήνας",week:"Εβδομάδα",day:"Ημέρα",list:"Ατζέντα"},allDayText:"Ολοήμερο",eventLimitText:"περισσότερα"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){function c(a,b,c,e){var f="";switch(c){case"s":return e?"muutaman sekunnin":"muutama sekunti";case"m":return e?"minuutin":"minuutti";case"mm":f=e?"minuutin":"minuuttia";break;case"h":return e?"tunnin":"tunti";case"hh":f=e?"tunnin":"tuntia";break;case"d":return e?"päivän":"päivä";case"dd":f=e?"päivän":"päivää";break;case"M":return e?"kuukauden":"kuukausi";case"MM":f=e?"kuukauden":"kuukautta";break;case"y":return e?"vuoden":"vuosi";case"yy":f=e?"vuoden":"vuotta"}return f=d(a,e)+" "+f}function d(a,b){return 10>a?b?f[a]:e[a]:a}var e="nolla yksi kaksi kolme neljä viisi kuusi seitsemän kahdeksan yhdeksän".split(" "),f=["nolla","yhden","kahden","kolmen","neljän","viiden","kuuden",e[7],e[8],e[9]];(b.defineLocale||b.lang).call(b,"fi",{months:"tammikuu_helmikuu_maaliskuu_huhtikuu_toukokuu_kesäkuu_heinäkuu_elokuu_syyskuu_lokakuu_marraskuu_joulukuu".split("_"),monthsShort:"tammi_helmi_maalis_huhti_touko_kesä_heinä_elo_syys_loka_marras_joulu".split("_"),weekdays:"sunnuntai_maanantai_tiistai_keskiviikko_torstai_perjantai_lauantai".split("_"),weekdaysShort:"su_ma_ti_ke_to_pe_la".split("_"),weekdaysMin:"su_ma_ti_ke_to_pe_la".split("_"),longDateFormat:{LT:"HH.mm",LTS:"HH.mm.ss",L:"DD.MM.YYYY",LL:"Do MMMM[ta] YYYY",LLL:"Do MMMM[ta] YYYY, [klo] LT",LLLL:"dddd, Do MMMM[ta] YYYY, [klo] LT",l:"D.M.YYYY",ll:"Do MMM YYYY",lll:"Do MMM YYYY, [klo] LT",llll:"ddd, Do MMM YYYY, [klo] LT"},calendar:{sameDay:"[tänään] [klo] LT",nextDay:"[huomenna] [klo] LT",nextWeek:"dddd [klo] LT",lastDay:"[eilen] [klo] LT",lastWeek:"[viime] dddd[na] [klo] LT",sameElse:"L"},relativeTime:{future:"%s päästä",past:"%s sitten",s:c,m:c,mm:c,h:c,hh:c,d:c,dd:c,M:c,MM:c,y:c,yy:c},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("fi","fi",{closeText:"Sulje",prevText:"«Edellinen",nextText:"Seuraava»",currentText:"Tänään",monthNames:["Tammikuu","Helmikuu","Maaliskuu","Huhtikuu","Toukokuu","Kesäkuu","Heinäkuu","Elokuu","Syyskuu","Lokakuu","Marraskuu","Joulukuu"],monthNamesShort:["Tammi","Helmi","Maalis","Huhti","Touko","Kesä","Heinä","Elo","Syys","Loka","Marras","Joulu"],dayNamesShort:["Su","Ma","Ti","Ke","To","Pe","La"],dayNames:["Sunnuntai","Maanantai","Tiistai","Keskiviikko","Torstai","Perjantai","Lauantai"],dayNamesMin:["Su","Ma","Ti","Ke","To","Pe","La"],weekHeader:"Vk",dateFormat:"d.m.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("fi",{buttonText:{month:"Kuukausi",week:"Viikko",day:"Päivä",list:"Tapahtumat"},allDayText:"Koko päivä",eventLimitText:"lisää"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"fr",{months:"janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre".split("_"),monthsShort:"janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.".split("_"),weekdays:"dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi".split("_"),weekdaysShort:"dim._lun._mar._mer._jeu._ven._sam.".split("_"),weekdaysMin:"Di_Lu_Ma_Me_Je_Ve_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[Aujourd'hui à] LT",nextDay:"[Demain à] LT",nextWeek:"dddd [à] LT",lastDay:"[Hier à] LT",lastWeek:"dddd [dernier à] LT",sameElse:"L"},relativeTime:{future:"dans %s",past:"il y a %s",s:"quelques secondes",m:"une minute",mm:"%d minutes",h:"une heure",hh:"%d heures",d:"un jour",dd:"%d jours",M:"un mois",MM:"%d mois",y:"un an",yy:"%d ans"},ordinalParse:/\d{1,2}(er|)/,ordinal:function(a){return a+(1===a?"er":"")},week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("fr","fr",{closeText:"Fermer",prevText:"Précédent",nextText:"Suivant",currentText:"Aujourd'hui",monthNames:["janvier","février","mars","avril","mai","juin","juillet","août","septembre","octobre","novembre","décembre"],monthNamesShort:["janv.","févr.","mars","avr.","mai","juin","juil.","août","sept.","oct.","nov.","déc."],dayNames:["dimanche","lundi","mardi","mercredi","jeudi","vendredi","samedi"],dayNamesShort:["dim.","lun.","mar.","mer.","jeu.","ven.","sam."],dayNamesMin:["D","L","M","M","J","V","S"],weekHeader:"Sem.",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("fr",{buttonText:{month:"Mois",week:"Semaine",day:"Jour",list:"Mon planning"},allDayHtml:"Toute la<br/>journée",eventLimitText:"en plus"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){function c(a,b,c,d){var e=a;switch(c){case"s":return d||b?"néhány másodperc":"néhány másodperce";case"m":return"egy"+(d||b?" perc":" perce");case"mm":return e+(d||b?" perc":" perce");case"h":return"egy"+(d||b?" óra":" órája");case"hh":return e+(d||b?" óra":" órája");case"d":return"egy"+(d||b?" nap":" napja");case"dd":return e+(d||b?" nap":" napja");case"M":return"egy"+(d||b?" hónap":" hónapja");case"MM":return e+(d||b?" hónap":" hónapja");case"y":return"egy"+(d||b?" év":" éve");case"yy":return e+(d||b?" év":" éve")}return""}function d(a){return(a?"":"[múlt] ")+"["+e[this.day()]+"] LT[-kor]"}var e="vasárnap hétfőn kedden szerdán csütörtökön pénteken szombaton".split(" ");(b.defineLocale||b.lang).call(b,"hu",{months:"január_február_március_április_május_június_július_augusztus_szeptember_október_november_december".split("_"),monthsShort:"jan_feb_márc_ápr_máj_jún_júl_aug_szept_okt_nov_dec".split("_"),weekdays:"vasárnap_hétfő_kedd_szerda_csütörtök_péntek_szombat".split("_"),weekdaysShort:"vas_hét_kedd_sze_csüt_pén_szo".split("_"),weekdaysMin:"v_h_k_sze_cs_p_szo".split("_"),longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"YYYY.MM.DD.",LL:"YYYY. MMMM D.",LLL:"YYYY. MMMM D., LT",LLLL:"YYYY. MMMM D., dddd LT"},meridiemParse:/de|du/i,isPM:function(a){return"u"===a.charAt(1).toLowerCase()},meridiem:function(a,b,c){return 12>a?c===!0?"de":"DE":c===!0?"du":"DU"},calendar:{sameDay:"[ma] LT[-kor]",nextDay:"[holnap] LT[-kor]",nextWeek:function(){return d.call(this,!0)},lastDay:"[tegnap] LT[-kor]",lastWeek:function(){return d.call(this,!1)},sameElse:"L"},relativeTime:{future:"%s múlva",past:"%s",s:c,m:c,mm:c,h:c,hh:c,d:c,dd:c,M:c,MM:c,y:c,yy:c},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}}),a.fullCalendar.datepickerLang("hu","hu",{closeText:"bezár",prevText:"vissza",nextText:"előre",currentText:"ma",monthNames:["Január","Február","Március","Április","Május","Június","Július","Augusztus","Szeptember","Október","November","December"],monthNamesShort:["Jan","Feb","Már","Ápr","Máj","Jún","Júl","Aug","Szep","Okt","Nov","Dec"],dayNames:["Vasárnap","Hétfő","Kedd","Szerda","Csütörtök","Péntek","Szombat"],dayNamesShort:["Vas","Hét","Ked","Sze","Csü","Pén","Szo"],dayNamesMin:["V","H","K","Sze","Cs","P","Szo"],weekHeader:"Hét",dateFormat:"yy.mm.dd.",firstDay:1,isRTL:!1,showMonthAfterYear:!0,yearSuffix:""}),a.fullCalendar.lang("hu",{buttonText:{month:"Hónap",week:"Hét",day:"Nap",list:"Napló"},allDayText:"Egész nap",eventLimitText:"további"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"id",{months:"Januari_Februari_Maret_April_Mei_Juni_Juli_Agustus_September_Oktober_November_Desember".split("_"),monthsShort:"Jan_Feb_Mar_Apr_Mei_Jun_Jul_Ags_Sep_Okt_Nov_Des".split("_"),weekdays:"Minggu_Senin_Selasa_Rabu_Kamis_Jumat_Sabtu".split("_"),weekdaysShort:"Min_Sen_Sel_Rab_Kam_Jum_Sab".split("_"),weekdaysMin:"Mg_Sn_Sl_Rb_Km_Jm_Sb".split("_"),longDateFormat:{LT:"HH.mm",LTS:"LT.ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY [pukul] LT",LLLL:"dddd, D MMMM YYYY [pukul] LT"},meridiemParse:/pagi|siang|sore|malam/,meridiemHour:function(a,b){return 12===a&&(a=0),"pagi"===b?a:"siang"===b?a>=11?a:a+12:"sore"===b||"malam"===b?a+12:void 0},meridiem:function(a,b,c){return 11>a?"pagi":15>a?"siang":19>a?"sore":"malam"},calendar:{sameDay:"[Hari ini pukul] LT",nextDay:"[Besok pukul] LT",nextWeek:"dddd [pukul] LT",lastDay:"[Kemarin pukul] LT",lastWeek:"dddd [lalu pukul] LT",sameElse:"L"},relativeTime:{future:"dalam %s",past:"%s yang lalu",s:"beberapa detik",m:"semenit",mm:"%d menit",h:"sejam",hh:"%d jam",d:"sehari",dd:"%d hari",M:"sebulan",MM:"%d bulan",y:"setahun",yy:"%d tahun"},week:{dow:1,doy:7}}),a.fullCalendar.datepickerLang("id","id",{closeText:"Tutup",prevText:"<mundur",nextText:"maju>",currentText:"hari ini",monthNames:["Januari","Februari","Maret","April","Mei","Juni","Juli","Agustus","September","Oktober","Nopember","Desember"],monthNamesShort:["Jan","Feb","Mar","Apr","Mei","Jun","Jul","Agus","Sep","Okt","Nop","Des"],dayNames:["Minggu","Senin","Selasa","Rabu","Kamis","Jumat","Sabtu"],dayNamesShort:["Min","Sen","Sel","Rab","kam","Jum","Sab"],dayNamesMin:["Mg","Sn","Sl","Rb","Km","jm","Sb"],weekHeader:"Mg",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("id",{buttonText:{month:"Bulan",week:"Minggu",day:"Hari",list:"Agenda"},allDayHtml:"Sehari<br/>penuh",eventLimitText:"lebih"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"it",{months:"gennaio_febbraio_marzo_aprile_maggio_giugno_luglio_agosto_settembre_ottobre_novembre_dicembre".split("_"),monthsShort:"gen_feb_mar_apr_mag_giu_lug_ago_set_ott_nov_dic".split("_"),weekdays:"Domenica_Lunedì_Martedì_Mercoledì_Giovedì_Venerdì_Sabato".split("_"),weekdaysShort:"Dom_Lun_Mar_Mer_Gio_Ven_Sab".split("_"),weekdaysMin:"D_L_Ma_Me_G_V_S".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[Oggi alle] LT",nextDay:"[Domani alle] LT",nextWeek:"dddd [alle] LT",lastDay:"[Ieri alle] LT",lastWeek:function(){switch(this.day()){case 0:return"[la scorsa] dddd [alle] LT";default:return"[lo scorso] dddd [alle] LT"}},sameElse:"L"},relativeTime:{future:function(a){return(/^[0-9].+$/.test(a)?"tra":"in")+" "+a},past:"%s fa",s:"alcuni secondi",m:"un minuto",mm:"%d minuti",h:"un'ora",hh:"%d ore",d:"un giorno",dd:"%d giorni",M:"un mese",MM:"%d mesi",y:"un anno",yy:"%d anni"},ordinalParse:/\d{1,2}º/,ordinal:"%dº",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("it","it",{closeText:"Chiudi",prevText:"<Prec",nextText:"Succ>",currentText:"Oggi",monthNames:["Gennaio","Febbraio","Marzo","Aprile","Maggio","Giugno","Luglio","Agosto","Settembre","Ottobre","Novembre","Dicembre"],monthNamesShort:["Gen","Feb","Mar","Apr","Mag","Giu","Lug","Ago","Set","Ott","Nov","Dic"],dayNames:["Domenica","Lunedì","Martedì","Mercoledì","Giovedì","Venerdì","Sabato"],dayNamesShort:["Dom","Lun","Mar","Mer","Gio","Ven","Sab"],dayNamesMin:["Do","Lu","Ma","Me","Gi","Ve","Sa"],weekHeader:"Sm",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("it",{buttonText:{month:"Mese",week:"Settimana",day:"Giorno",list:"Agenda"},allDayHtml:"Tutto il<br/>giorno",eventLimitText:function(a){return"+altri "+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"ja",{months:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),monthsShort:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),weekdays:"日曜日_月曜日_火曜日_水曜日_木曜日_金曜日_土曜日".split("_"),weekdaysShort:"日_月_火_水_木_金_土".split("_"),weekdaysMin:"日_月_火_水_木_金_土".split("_"),longDateFormat:{LT:"Ah時m分",LTS:"LTs秒",L:"YYYY/MM/DD",LL:"YYYY年M月D日",LLL:"YYYY年M月D日LT",LLLL:"YYYY年M月D日LT dddd"},meridiemParse:/午前|午後/i,isPM:function(a){return"午後"===a},meridiem:function(a,b,c){return 12>a?"午前":"午後"},calendar:{sameDay:"[今日] LT",nextDay:"[明日] LT",nextWeek:"[来週]dddd LT",lastDay:"[昨日] LT",lastWeek:"[前週]dddd LT",sameElse:"L"},relativeTime:{future:"%s後",past:"%s前",s:"数秒",m:"1分",mm:"%d分",h:"1時間",hh:"%d時間",d:"1日",dd:"%d日",M:"1ヶ月",MM:"%dヶ月",y:"1年",yy:"%d年"}}),a.fullCalendar.datepickerLang("ja","ja",{closeText:"閉じる",prevText:"<前",nextText:"次>",currentText:"今日",monthNames:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],monthNamesShort:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],dayNames:["日曜日","月曜日","火曜日","水曜日","木曜日","金曜日","土曜日"],dayNamesShort:["日","月","火","水","木","金","土"],dayNamesMin:["日","月","火","水","木","金","土"],weekHeader:"週",dateFormat:"yy/mm/dd",firstDay:0,isRTL:!1,showMonthAfterYear:!0,yearSuffix:"年"}),a.fullCalendar.lang("ja",{buttonText:{month:"月",week:"週",day:"日",list:"予定リスト"},allDayText:"終日",eventLimitText:function(a){return"他 "+a+" 件"}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){var c="jan._feb._mrt._apr._mei_jun._jul._aug._sep._okt._nov._dec.".split("_"),d="jan_feb_mrt_apr_mei_jun_jul_aug_sep_okt_nov_dec".split("_");(b.defineLocale||b.lang).call(b,"nl",{months:"januari_februari_maart_april_mei_juni_juli_augustus_september_oktober_november_december".split("_"),monthsShort:function(a,b){return/-MMM-/.test(b)?d[a.month()]:c[a.month()]},weekdays:"zondag_maandag_dinsdag_woensdag_donderdag_vrijdag_zaterdag".split("_"),weekdaysShort:"zo._ma._di._wo._do._vr._za.".split("_"),weekdaysMin:"Zo_Ma_Di_Wo_Do_Vr_Za".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD-MM-YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[vandaag om] LT",nextDay:"[morgen om] LT",nextWeek:"dddd [om] LT",lastDay:"[gisteren om] LT",lastWeek:"[afgelopen] dddd [om] LT",sameElse:"L"},relativeTime:{future:"over %s",past:"%s geleden",s:"een paar seconden",m:"één minuut",mm:"%d minuten",h:"één uur",hh:"%d uur",d:"één dag",dd:"%d dagen",M:"één maand",MM:"%d maanden",y:"één jaar",yy:"%d jaar"},ordinalParse:/\d{1,2}(ste|de)/,ordinal:function(a){return a+(1===a||8===a||a>=20?"ste":"de")},week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("nl","nl",{closeText:"Sluiten",prevText:"←",nextText:"→",currentText:"Vandaag",monthNames:["januari","februari","maart","april","mei","juni","juli","augustus","september","oktober","november","december"],monthNamesShort:["jan","feb","mrt","apr","mei","jun","jul","aug","sep","okt","nov","dec"],dayNames:["zondag","maandag","dinsdag","woensdag","donderdag","vrijdag","zaterdag"],dayNamesShort:["zon","maa","din","woe","don","vri","zat"],dayNamesMin:["zo","ma","di","wo","do","vr","za"],weekHeader:"Wk",dateFormat:"dd-mm-yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("nl",{buttonText:{month:"Maand",week:"Week",day:"Dag",list:"Agenda"},allDayText:"Hele dag",eventLimitText:"extra"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"nb",{months:"januar_februar_mars_april_mai_juni_juli_august_september_oktober_november_desember".split("_"),monthsShort:"jan_feb_mar_apr_mai_jun_jul_aug_sep_okt_nov_des".split("_"),weekdays:"søndag_mandag_tirsdag_onsdag_torsdag_fredag_lørdag".split("_"),weekdaysShort:"søn_man_tirs_ons_tors_fre_lør".split("_"),weekdaysMin:"sø_ma_ti_on_to_fr_lø".split("_"),longDateFormat:{LT:"H.mm",LTS:"LT.ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY [kl.] LT",LLLL:"dddd D. MMMM YYYY [kl.] LT"},calendar:{sameDay:"[i dag kl.] LT",nextDay:"[i morgen kl.] LT",nextWeek:"dddd [kl.] LT",lastDay:"[i går kl.] LT",lastWeek:"[forrige] dddd [kl.] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"for %s siden",s:"noen sekunder",m:"ett minutt",mm:"%d minutter",h:"en time",hh:"%d timer",d:"en dag",dd:"%d dager",M:"en måned",MM:"%d måneder",y:"ett år",yy:"%d år"},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("nb","nb",{closeText:"Lukk",prevText:"«Forrige",nextText:"Neste»",currentText:"I dag",monthNames:["januar","februar","mars","april","mai","juni","juli","august","september","oktober","november","desember"],monthNamesShort:["jan","feb","mar","apr","mai","jun","jul","aug","sep","okt","nov","des"],dayNamesShort:["søn","man","tir","ons","tor","fre","lør"],dayNames:["søndag","mandag","tirsdag","onsdag","torsdag","fredag","lørdag"],dayNamesMin:["sø","ma","ti","on","to","fr","lø"],weekHeader:"Uke",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("nb",{buttonText:{month:"Måned",week:"Uke",day:"Dag",list:"Agenda"},allDayText:"Hele dagen",eventLimitText:"til"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){function c(a){return 5>a%10&&a%10>1&&~~(a/10)%10!==1}function d(a,b,d){var e=a+" ";switch(d){case"m":return b?"minuta":"minutę";case"mm":return e+(c(a)?"minuty":"minut");case"h":return b?"godzina":"godzinę";case"hh":return e+(c(a)?"godziny":"godzin");case"MM":return e+(c(a)?"miesiące":"miesięcy");case"yy":return e+(c(a)?"lata":"lat")}}var e="styczeń_luty_marzec_kwiecień_maj_czerwiec_lipiec_sierpień_wrzesień_październik_listopad_grudzień".split("_"),f="stycznia_lutego_marca_kwietnia_maja_czerwca_lipca_sierpnia_września_października_listopada_grudnia".split("_");(b.defineLocale||b.lang).call(b,"pl",{months:function(a,b){return/D MMMM/.test(b)?f[a.month()]:e[a.month()]},monthsShort:"sty_lut_mar_kwi_maj_cze_lip_sie_wrz_paź_lis_gru".split("_"),weekdays:"niedziela_poniedziałek_wtorek_środa_czwartek_piątek_sobota".split("_"),weekdaysShort:"nie_pon_wt_śr_czw_pt_sb".split("_"),weekdaysMin:"N_Pn_Wt_Śr_Cz_Pt_So".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[Dziś o] LT",nextDay:"[Jutro o] LT",nextWeek:"[W] dddd [o] LT",lastDay:"[Wczoraj o] LT",lastWeek:function(){switch(this.day()){case 0:return"[W zeszłą niedzielę o] LT";case 3:return"[W zeszłą środę o] LT";case 6:return"[W zeszłą sobotę o] LT";default:return"[W zeszły] dddd [o] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"%s temu",s:"kilka sekund",m:d,mm:d,h:d,hh:d,d:"1 dzień",dd:"%d dni",M:"miesiąc",MM:d,y:"rok",yy:d},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("pl","pl",{closeText:"Zamknij",prevText:"<Poprzedni",nextText:"Następny>",currentText:"Dziś",monthNames:["Styczeń","Luty","Marzec","Kwiecień","Maj","Czerwiec","Lipiec","Sierpień","Wrzesień","Październik","Listopad","Grudzień"],monthNamesShort:["Sty","Lu","Mar","Kw","Maj","Cze","Lip","Sie","Wrz","Pa","Lis","Gru"],dayNames:["Niedziela","Poniedziałek","Wtorek","Środa","Czwartek","Piątek","Sobota"],dayNamesShort:["Nie","Pn","Wt","Śr","Czw","Pt","So"],dayNamesMin:["N","Pn","Wt","Śr","Cz","Pt","So"],weekHeader:"Tydz",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("pl",{buttonText:{month:"Miesiąc",week:"Tydzień",day:"Dzień",list:"Plan dnia"},allDayText:"Cały dzień",eventLimitText:"więcej"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"pt",{months:"janeiro_fevereiro_março_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro".split("_"),monthsShort:"jan_fev_mar_abr_mai_jun_jul_ago_set_out_nov_dez".split("_"),weekdays:"domingo_segunda-feira_terça-feira_quarta-feira_quinta-feira_sexta-feira_sábado".split("_"),weekdaysShort:"dom_seg_ter_qua_qui_sex_sáb".split("_"),weekdaysMin:"dom_2ª_3ª_4ª_5ª_6ª_sáb".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY LT",LLLL:"dddd, D [de] MMMM [de] YYYY LT"},calendar:{sameDay:"[Hoje às] LT",nextDay:"[Amanhã às] LT",nextWeek:"dddd [às] LT",lastDay:"[Ontem às] LT",lastWeek:function(){return 0===this.day()||6===this.day()?"[Último] dddd [às] LT":"[Última] dddd [às] LT"},sameElse:"L"},relativeTime:{future:"em %s",past:"há %s",s:"segundos",m:"um minuto",mm:"%d minutos",h:"uma hora",hh:"%d horas",d:"um dia",dd:"%d dias",M:"um mês",MM:"%d meses",y:"um ano",yy:"%d anos"},ordinalParse:/\d{1,2}º/,ordinal:"%dº",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("pt","pt",{closeText:"Fechar",prevText:"Anterior",nextText:"Seguinte",currentText:"Hoje",monthNames:["Janeiro","Fevereiro","Março","Abril","Maio","Junho","Julho","Agosto","Setembro","Outubro","Novembro","Dezembro"],monthNamesShort:["Jan","Fev","Mar","Abr","Mai","Jun","Jul","Ago","Set","Out","Nov","Dez"],dayNames:["Domingo","Segunda-feira","Terça-feira","Quarta-feira","Quinta-feira","Sexta-feira","Sábado"],dayNamesShort:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],dayNamesMin:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],weekHeader:"Sem",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("pt",{buttonText:{month:"Mês",week:"Semana",day:"Dia",list:"Agenda"},allDayText:"Todo o dia",eventLimitText:"mais"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"pt-br",{months:"janeiro_fevereiro_março_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro".split("_"),monthsShort:"jan_fev_mar_abr_mai_jun_jul_ago_set_out_nov_dez".split("_"),weekdays:"domingo_segunda-feira_terça-feira_quarta-feira_quinta-feira_sexta-feira_sábado".split("_"),weekdaysShort:"dom_seg_ter_qua_qui_sex_sáb".split("_"),weekdaysMin:"dom_2ª_3ª_4ª_5ª_6ª_sáb".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY [às] LT",LLLL:"dddd, D [de] MMMM [de] YYYY [às] LT"},calendar:{sameDay:"[Hoje às] LT",nextDay:"[Amanhã às] LT",nextWeek:"dddd [às] LT",lastDay:"[Ontem às] LT",lastWeek:function(){return 0===this.day()||6===this.day()?"[Último] dddd [às] LT":"[Última] dddd [às] LT"},sameElse:"L"},relativeTime:{future:"em %s",past:"%s atrás",s:"segundos",m:"um minuto",mm:"%d minutos",h:"uma hora",hh:"%d horas",d:"um dia",dd:"%d dias",M:"um mês",MM:"%d meses",y:"um ano",yy:"%d anos"},ordinalParse:/\d{1,2}º/,ordinal:"%dº"}),a.fullCalendar.datepickerLang("pt-br","pt-BR",{closeText:"Fechar",prevText:"<Anterior",nextText:"Próximo>",currentText:"Hoje",monthNames:["Janeiro","Fevereiro","Março","Abril","Maio","Junho","Julho","Agosto","Setembro","Outubro","Novembro","Dezembro"],monthNamesShort:["Jan","Fev","Mar","Abr","Mai","Jun","Jul","Ago","Set","Out","Nov","Dez"],dayNames:["Domingo","Segunda-feira","Terça-feira","Quarta-feira","Quinta-feira","Sexta-feira","Sábado"],dayNamesShort:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],dayNamesMin:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],weekHeader:"Sm",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("pt-br",{buttonText:{month:"Mês",week:"Semana",day:"Dia",list:"Compromissos"},allDayText:"dia inteiro",eventLimitText:function(a){return"mais +"+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){function c(a,b){var c=a.split("_");return b%10===1&&b%100!==11?c[0]:b%10>=2&&4>=b%10&&(10>b%100||b%100>=20)?c[1]:c[2]}function d(a,b,d){var e={mm:b?"минута_минуты_минут":"минуту_минуты_минут",hh:"час_часа_часов",dd:"день_дня_дней",MM:"месяц_месяца_месяцев",yy:"год_года_лет"};return"m"===d?b?"минута":"минуту":a+" "+c(e[d],+a)}function e(a,b){var c={nominative:"январь_февраль_март_апрель_май_июнь_июль_август_сентябрь_октябрь_ноябрь_декабрь".split("_"),accusative:"января_февраля_марта_апреля_мая_июня_июля_августа_сентября_октября_ноября_декабря".split("_")},d=/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/.test(b)?"accusative":"nominative";return c[d][a.month()]}function f(a,b){var c={nominative:"янв_фев_март_апр_май_июнь_июль_авг_сен_окт_ноя_дек".split("_"),accusative:"янв_фев_мар_апр_мая_июня_июля_авг_сен_окт_ноя_дек".split("_")},d=/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/.test(b)?"accusative":"nominative";return c[d][a.month()]}function g(a,b){var c={nominative:"воскресенье_понедельник_вторник_среда_четверг_пятница_суббота".split("_"),accusative:"воскресенье_понедельник_вторник_среду_четверг_пятницу_субботу".split("_")},d=/\[ ?[Вв] ?(?:прошлую|следующую|эту)? ?\] ?dddd/.test(b)?"accusative":"nominative";return c[d][a.day()]}(b.defineLocale||b.lang).call(b,"ru",{months:e,monthsShort:f,weekdays:g,weekdaysShort:"вс_пн_вт_ср_чт_пт_сб".split("_"),weekdaysMin:"вс_пн_вт_ср_чт_пт_сб".split("_"),monthsParse:[/^янв/i,/^фев/i,/^мар/i,/^апр/i,/^ма[й|я]/i,/^июн/i,/^июл/i,/^авг/i,/^сен/i,/^окт/i,/^ноя/i,/^дек/i],longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY г.",LLL:"D MMMM YYYY г., LT",LLLL:"dddd, D MMMM YYYY г., LT"},calendar:{sameDay:"[Сегодня в] LT",nextDay:"[Завтра в] LT",lastDay:"[Вчера в] LT",nextWeek:function(){return 2===this.day()?"[Во] dddd [в] LT":"[В] dddd [в] LT"},lastWeek:function(a){if(a.week()===this.week())return 2===this.day()?"[Во] dddd [в] LT":"[В] dddd [в] LT";switch(this.day()){case 0:return"[В прошлое] dddd [в] LT";case 1:case 2:case 4:return"[В прошлый] dddd [в] LT";case 3:case 5:case 6:return"[В прошлую] dddd [в] LT"}},sameElse:"L"},relativeTime:{future:"через %s",past:"%s назад",s:"несколько секунд",m:d,mm:d,h:"час",hh:d,d:"день",dd:d,M:"месяц",MM:d,y:"год",yy:d},meridiemParse:/ночи|утра|дня|вечера/i,isPM:function(a){return/^(дня|вечера)$/.test(a)},meridiem:function(a,b,c){return 4>a?"ночи":12>a?"утра":17>a?"дня":"вечера"},ordinalParse:/\d{1,2}-(й|го|я)/,ordinal:function(a,b){switch(b){case"M":case"d":case"DDD":return a+"-й";case"D":return a+"-го";case"w":case"W":return a+"-я";default:return a}},week:{dow:1,doy:7}}),a.fullCalendar.datepickerLang("ru","ru",{closeText:"Закрыть",prevText:"<Пред",nextText:"След>",currentText:"Сегодня",monthNames:["Январь","Февраль","Март","Апрель","Май","Июнь","Июль","Август","Сентябрь","Октябрь","Ноябрь","Декабрь"],monthNamesShort:["Янв","Фев","Мар","Апр","Май","Июн","Июл","Авг","Сен","Окт","Ноя","Дек"],dayNames:["воскресенье","понедельник","вторник","среда","четверг","пятница","суббота"],dayNamesShort:["вск","пнд","втр","срд","чтв","птн","сбт"],dayNamesMin:["Вс","Пн","Вт","Ср","Чт","Пт","Сб"],weekHeader:"Нед",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("ru",{buttonText:{month:"Месяц",week:"Неделя",day:"День",list:"Повестка дня"},allDayText:"Весь день",eventLimitText:function(a){return"+ ещё "+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"sv",{months:"januari_februari_mars_april_maj_juni_juli_augusti_september_oktober_november_december".split("_"),monthsShort:"jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec".split("_"),weekdays:"söndag_måndag_tisdag_onsdag_torsdag_fredag_lördag".split("_"),weekdaysShort:"sön_mån_tis_ons_tor_fre_lör".split("_"),weekdaysMin:"sö_må_ti_on_to_fr_lö".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"YYYY-MM-DD",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[Idag] LT",nextDay:"[Imorgon] LT",lastDay:"[Igår] LT",nextWeek:"dddd LT",lastWeek:"[Förra] dddd[en] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"för %s sedan",s:"några sekunder",m:"en minut",mm:"%d minuter",h:"en timme",hh:"%d timmar",d:"en dag",dd:"%d dagar",M:"en månad",MM:"%d månader",y:"ett år",yy:"%d år"},ordinalParse:/\d{1,2}(e|a)/,ordinal:function(a){var b=a%10,c=1===~~(a%100/10)?"e":1===b?"a":2===b?"a":"e";return a+c},week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("sv","sv",{closeText:"Stäng",prevText:"«Förra",nextText:"Nästa»",currentText:"Idag",monthNames:["Januari","Februari","Mars","April","Maj","Juni","Juli","Augusti","September","Oktober","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],dayNamesShort:["Sön","Mån","Tis","Ons","Tor","Fre","Lör"],dayNames:["Söndag","Måndag","Tisdag","Onsdag","Torsdag","Fredag","Lördag"],dayNamesMin:["Sö","Må","Ti","On","To","Fr","Lö"],weekHeader:"Ve",dateFormat:"yy-mm-dd",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("sv",{buttonText:{month:"Månad",week:"Vecka",day:"Dag",list:"Program"},allDayText:"Heldag",eventLimitText:"till"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){var c={words:{m:["jedan minut","jedne minute"],mm:["minut","minute","minuta"],h:["jedan sat","jednog sata"],hh:["sat","sata","sati"],dd:["dan","dana","dana"],MM:["mesec","meseca","meseci"],yy:["godina","godine","godina"]},correctGrammaticalCase:function(a,b){return 1===a?b[0]:a>=2&&4>=a?b[1]:b[2]},translate:function(a,b,d){var e=c.words[d];return 1===d.length?b?e[0]:e[1]:a+" "+c.correctGrammaticalCase(a,e)}};(b.defineLocale||b.lang).call(b,"sr",{months:["januar","februar","mart","april","maj","jun","jul","avgust","septembar","oktobar","novembar","decembar"],monthsShort:["jan.","feb.","mar.","apr.","maj","jun","jul","avg.","sep.","okt.","nov.","dec."],weekdays:["nedelja","ponedeljak","utorak","sreda","četvrtak","petak","subota"],weekdaysShort:["ned.","pon.","uto.","sre.","čet.","pet.","sub."],weekdaysMin:["ne","po","ut","sr","če","pe","su"],longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"DD. MM. YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd, D. MMMM YYYY LT"},calendar:{sameDay:"[danas u] LT",nextDay:"[sutra u] LT",nextWeek:function(){switch(this.day()){case 0:return"[u] [nedelju] [u] LT";case 3:return"[u] [sredu] [u] LT";case 6:return"[u] [subotu] [u] LT";case 1:case 2:case 4:case 5:return"[u] dddd [u] LT"}},lastDay:"[juče u] LT",lastWeek:function(){var a=["[prošle] [nedelje] [u] LT","[prošlog] [ponedeljka] [u] LT","[prošlog] [utorka] [u] LT","[prošle] [srede] [u] LT","[prošlog] [četvrtka] [u] LT","[prošlog] [petka] [u] LT","[prošle] [subote] [u] LT"];return a[this.day()]},sameElse:"L"},relativeTime:{future:"za %s",past:"pre %s",s:"nekoliko sekundi",m:c.translate,mm:c.translate,h:c.translate,hh:c.translate,d:"dan",dd:c.translate,M:"mesec",MM:c.translate,y:"godinu",yy:c.translate},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}}),a.fullCalendar.datepickerLang("sr","sr",{closeText:"Затвори",prevText:"<",nextText:">",currentText:"Данас",monthNames:["Јануар","Фебруар","Март","Април","Мај","Јун","Јул","Август","Септембар","Октобар","Новембар","Децембар"],monthNamesShort:["Јан","Феб","Мар","Апр","Мај","Јун","Јул","Авг","Сеп","Окт","Нов","Дец"],dayNames:["Недеља","Понедељак","Уторак","Среда","Четвртак","Петак","Субота"],dayNamesShort:["Нед","Пон","Уто","Сре","Чет","Пет","Суб"],dayNamesMin:["Не","По","Ут","Ср","Че","Пе","Су"],weekHeader:"Сед",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("sr",{buttonText:{month:"Месец",week:"Недеља",day:"Дан",list:"Планер"},allDayText:"Цео дан",eventLimitText:function(a){return"+ још "+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"th",{months:"มกราคม_กุมภาพันธ์_มีนาคม_เมษายน_พฤษภาคม_มิถุนายน_กรกฎาคม_สิงหาคม_กันยายน_ตุลาคม_พฤศจิกายน_ธันวาคม".split("_"),monthsShort:"มกรา_กุมภา_มีนา_เมษา_พฤษภา_มิถุนา_กรกฎา_สิงหา_กันยา_ตุลา_พฤศจิกา_ธันวา".split("_"),weekdays:"อาทิตย์_จันทร์_อังคาร_พุธ_พฤหัสบดี_ศุกร์_เสาร์".split("_"),weekdaysShort:"อาทิตย์_จันทร์_อังคาร_พุธ_พฤหัส_ศุกร์_เสาร์".split("_"),weekdaysMin:"อา._จ._อ._พ._พฤ._ศ._ส.".split("_"),longDateFormat:{LT:"H นาฬิกา m นาที",LTS:"LT s วินาที",L:"YYYY/MM/DD",LL:"D MMMM YYYY",LLL:"D MMMM YYYY เวลา LT",LLLL:"วันddddที่ D MMMM YYYY เวลา LT"},meridiemParse:/ก่อนเที่ยง|หลังเที่ยง/,isPM:function(a){return"หลังเที่ยง"===a},meridiem:function(a,b,c){return 12>a?"ก่อนเที่ยง":"หลังเที่ยง"},calendar:{sameDay:"[วันนี้ เวลา] LT",nextDay:"[พรุ่งนี้ เวลา] LT",nextWeek:"dddd[หน้า เวลา] LT",lastDay:"[เมื่อวานนี้ เวลา] LT",lastWeek:"[วัน]dddd[ที่แล้ว เวลา] LT",sameElse:"L"},relativeTime:{future:"อีก %s",past:"%sที่แล้ว",s:"ไม่กี่วินาที",m:"1 นาที",mm:"%d นาที",h:"1 ชั่วโมง",hh:"%d ชั่วโมง",d:"1 วัน",dd:"%d วัน",M:"1 เดือน",MM:"%d เดือน",y:"1 ปี",yy:"%d ปี"}}),a.fullCalendar.datepickerLang("th","th",{closeText:"ปิด",prevText:"« ย้อน",nextText:"ถัดไป »",currentText:"วันนี้",monthNames:["มกราคม","กุมภาพันธ์","มีนาคม","เมษายน","พฤษภาคม","มิถุนายน","กรกฎาคม","สิงหาคม","กันยายน","ตุลาคม","พฤศจิกายน","ธันวาคม"],monthNamesShort:["ม.ค.","ก.พ.","มี.ค.","เม.ย.","พ.ค.","มิ.ย.","ก.ค.","ส.ค.","ก.ย.","ต.ค.","พ.ย.","ธ.ค."],dayNames:["อาทิตย์","จันทร์","อังคาร","พุธ","พฤหัสบดี","ศุกร์","เสาร์"],dayNamesShort:["อา.","จ.","อ.","พ.","พฤ.","ศ.","ส."],dayNamesMin:["อา.","จ.","อ.","พ.","พฤ.","ศ.","ส."],weekHeader:"Wk",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("th",{buttonText:{month:"เดือน",week:"สัปดาห์",day:"วัน",list:"แผนงาน"},allDayText:"ตลอดวัน",eventLimitText:"เพิ่มเติม"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){var c={1:"'inci",5:"'inci",8:"'inci",70:"'inci",80:"'inci",2:"'nci",7:"'nci",20:"'nci",50:"'nci",3:"'üncü",4:"'üncü",100:"'üncü",6:"'ncı",9:"'uncu",10:"'uncu",30:"'uncu",60:"'ıncı",90:"'ıncı"};(b.defineLocale||b.lang).call(b,"tr",{months:"Ocak_Şubat_Mart_Nisan_Mayıs_Haziran_Temmuz_Ağustos_Eylül_Ekim_Kasım_Aralık".split("_"),monthsShort:"Oca_Şub_Mar_Nis_May_Haz_Tem_Ağu_Eyl_Eki_Kas_Ara".split("_"),weekdays:"Pazar_Pazartesi_Salı_Çarşamba_Perşembe_Cuma_Cumartesi".split("_"),weekdaysShort:"Paz_Pts_Sal_Çar_Per_Cum_Cts".split("_"),weekdaysMin:"Pz_Pt_Sa_Ça_Pe_Cu_Ct".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[bugün saat] LT",nextDay:"[yarın saat] LT",nextWeek:"[haftaya] dddd [saat] LT",lastDay:"[dün] LT",lastWeek:"[geçen hafta] dddd [saat] LT",sameElse:"L"},relativeTime:{future:"%s sonra",past:"%s önce",s:"birkaç saniye",m:"bir dakika",mm:"%d dakika",h:"bir saat",hh:"%d saat",d:"bir gün",dd:"%d gün",M:"bir ay",MM:"%d ay",y:"bir yıl",yy:"%d yıl"},ordinalParse:/\d{1,2}'(inci|nci|üncü|ncı|uncu|ıncı)/,ordinal:function(a){if(0===a)return a+"'ıncı";var b=a%10,d=a%100-b,e=a>=100?100:null;return a+(c[b]||c[d]||c[e])},week:{dow:1,doy:7}}),a.fullCalendar.datepickerLang("tr","tr",{closeText:"kapat",prevText:"<geri",nextText:"ileri>",currentText:"bugün",monthNames:["Ocak","Şubat","Mart","Nisan","Mayıs","Haziran","Temmuz","Ağustos","Eylül","Ekim","Kasım","Aralık"],monthNamesShort:["Oca","Şub","Mar","Nis","May","Haz","Tem","Ağu","Eyl","Eki","Kas","Ara"],dayNames:["Pazar","Pazartesi","Salı","Çarşamba","Perşembe","Cuma","Cumartesi"],dayNamesShort:["Pz","Pt","Sa","Ça","Pe","Cu","Ct"],dayNamesMin:["Pz","Pt","Sa","Ça","Pe","Cu","Ct"],weekHeader:"Hf",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("tr",{buttonText:{next:"ileri",month:"Ay",week:"Hafta",day:"Gün",list:"Ajanda"},allDayText:"Tüm gün",eventLimitText:"daha fazla"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"zh-cn",{months:"一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月".split("_"),monthsShort:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),weekdays:"星期日_星期一_星期二_星期三_星期四_星期五_星期六".split("_"),weekdaysShort:"周日_周一_周二_周三_周四_周五_周六".split("_"),weekdaysMin:"日_一_二_三_四_五_六".split("_"),longDateFormat:{LT:"Ah点mm",LTS:"Ah点m分s秒",L:"YYYY-MM-DD",LL:"YYYY年MMMD日",LLL:"YYYY年MMMD日LT",LLLL:"YYYY年MMMD日ddddLT",l:"YYYY-MM-DD",ll:"YYYY年MMMD日",lll:"YYYY年MMMD日LT",llll:"YYYY年MMMD日ddddLT"},meridiemParse:/凌晨|早上|上午|中午|下午|晚上/,meridiemHour:function(a,b){return 12===a&&(a=0),"凌晨"===b||"早上"===b||"上午"===b?a:"下午"===b||"晚上"===b?a+12:a>=11?a:a+12},meridiem:function(a,b,c){var d=100*a+b;return 600>d?"凌晨":900>d?"早上":1130>d?"上午":1230>d?"中午":1800>d?"下午":"晚上"},calendar:{sameDay:function(){return 0===this.minutes()?"[今天]Ah[点整]":"[今天]LT"},nextDay:function(){return 0===this.minutes()?"[明天]Ah[点整]":"[明天]LT"},lastDay:function(){return 0===this.minutes()?"[昨天]Ah[点整]":"[昨天]LT"},nextWeek:function(){var a,c;return a=b().startOf("week"),c=this.unix()-a.unix()>=604800?"[下]":"[本]",0===this.minutes()?c+"dddAh点整":c+"dddAh点mm"},lastWeek:function(){var a,c;return a=b().startOf("week"),c=this.unix()<a.unix()?"[上]":"[本]",0===this.minutes()?c+"dddAh点整":c+"dddAh点mm"},sameElse:"LL"},ordinalParse:/\d{1,2}(日|月|周)/,ordinal:function(a,b){switch(b){case"d":case"D":case"DDD":return a+"日";case"M":return a+"月";case"w":case"W":return a+"周";default:return a}},relativeTime:{future:"%s内",past:"%s前",s:"几秒",m:"1分钟",mm:"%d分钟",h:"1小时",hh:"%d小时",d:"1天",dd:"%d天",M:"1个月",MM:"%d个月",y:"1年",yy:"%d年"},week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("zh-cn","zh-CN",{closeText:"关闭",prevText:"<上月",nextText:"下月>",currentText:"今天",monthNames:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],monthNamesShort:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],dayNames:["星期日","星期一","星期二","星期三","星期四","星期五","星期六"],dayNamesShort:["周日","周一","周二","周三","周四","周五","周六"],dayNamesMin:["日","一","二","三","四","五","六"],weekHeader:"周",dateFormat:"yy-mm-dd",firstDay:1,isRTL:!1,showMonthAfterYear:!0,yearSuffix:"年"}),a.fullCalendar.lang("zh-cn",{buttonText:{month:"月",week:"周",day:"日",list:"日程"},allDayText:"全天",eventLimitText:function(a){return"另外 "+a+" 个"}})});(function(){function t(x){this.app=x;this.router=new v();this.router.addRoute("screenshot-zone",d)}t.prototype.isOpen=function(){return $("#popover-container").size()>0};t.prototype.open=function(y){var x=this;x.app.dropdown.close();$.get(y,function(z){$("body").prepend('<div id="popover-container"><div id="popover-content">'+z+"</div></div>");x.app.refresh();x.router.dispatch(this.app);x.afterOpen()})};t.prototype.close=function(x){if(this.isOpen()){if(x){x.preventDefault()}$("#popover-container").remove()}};t.prototype.onClick=function(y){y.preventDefault();y.stopPropagation();var x=y.target.getAttribute("href");if(!x){x=y.target.getAttribute("data-href")}if(x){this.open(x)}};t.prototype.listen=function(){$(document).on("click",".popover",this.onClick.bind(this));$(document).on("click",".close-popover",this.close.bind(this));$(document).on("click","#popover-container",this.close.bind(this));$(document).on("click","#popover-content",function(x){x.stopPropagation()})};t.prototype.afterOpen=function(){var y=this;var x=$("#popover-content .popover-form");if(x){x.on("submit",function(z){z.preventDefault();$.ajax({type:"POST",url:x.attr("action"),data:x.serialize(),success:function(B,C,A){y.afterSubmit(B,A,y)}})})}$(document).on("click",".popover-link",function(z){z.preventDefault();$.ajax({type:"GET",url:$(this).attr("href"),success:function(B,C,A){y.afterSubmit(B,A,y)}})})};t.prototype.afterSubmit=function(z,y,x){var A=y.getResponseHeader("X-Ajax-Redirect");if(A){window.location=A==="self"?window.location.href.split("#")[0]:A}else{$("#popover-content").html(z);$("#popover-content input[autofocus]").focus();x.afterOpen()}};function r(){}r.prototype.listen=function(){var x=this;$(document).on("click",function(){x.close()});$(document).on("click",".dropdown-menu",function(B){B.preventDefault();B.stopImmediatePropagation();x.close();var z=$(this).next("ul");var C=$(this).offset();$("body").append(jQuery("<div>",{id:"dropdown"}));z.clone().appendTo("#dropdown");var D=$("#dropdown ul");D.addClass("dropdown-submenu-open");var A=D.outerHeight();var y=D.outerWidth();if(C.top+A-$(window).scrollTop()>$(window).height()){D.css("top",C.top-A-5)}else{D.css("top",C.top+$(this).height())}if(C.left+y>$(window).width()){D.css("left",C.left-y+$(this).outerWidth())}else{D.css("left",C.left)}});$(document).on("click",".dropdown-submenu-open li",function(y){if($(y.target).is("li")){$(this).find("a:visible")[0].click()}});$("textarea[data-mention-search-url]").textcomplete([{match:/(^|\s)@(\w*)$/,search:function(z,A){var y=$("textarea[data-mention-search-url]").data("mention-search-url");$.getJSON(y,{q:z}).done(function(B){A(B)}).fail(function(){A([])})},replace:function(y){return"$1@"+y+" "},cache:true}],{className:"textarea-dropdown"})};r.prototype.close=function(){$("#dropdown").remove()};function q(x){this.app=x}q.prototype.listen=function(){var x=this;$(".tooltip").tooltip({track:false,show:false,hide:false,position:{my:"left-20 top",at:"center bottom+9",using:function(y,z){$(this).css(y);var A=z.target.left+z.target.width/2-z.element.left-20;$("<div>").addClass("tooltip-arrow").addClass(z.vertical).addClass(A<1?"align-left":"align-right").appendTo(this)}},content:function(){var A=this;var y=$(this).attr("data-href");if(!y){return'<div class="markdown">'+$(this).attr("title")+"</div>"}$.get(y,function z(D){var C=$(".ui-tooltip:visible");$(".ui-tooltip-content:visible").html(D);C.css({top:"",left:""});C.children(".tooltip-arrow").remove();var B=$(A).tooltip("option","position");B.of=$(A);C.position(B)});return'<i class="fa fa-spinner fa-spin"></i>'}}).on("mouseenter",function(){var y=this;$(this).tooltip("open");$(".ui-tooltip").on("mouseleave",function(){$(y).tooltip("close")})}).on("mouseleave focusout",function(y){y.stopImmediatePropagation();var z=this;setTimeout(function(){if(!$(".ui-tooltip:hover").length){$(z).tooltip("close")}},100)})};function l(){}l.prototype.showPreview=function(B){B.preventDefault();var y=$(".write-area");var A=$(".preview-area");var x=$("textarea");$("#markdown-write").parent().removeClass("form-tab-selected");$("#markdown-preview").parent().addClass("form-tab-selected");var z=$.ajax({url:$("body").data("markdown-preview-url"),contentType:"application/json",type:"POST",processData:false,dataType:"html",data:JSON.stringify({text:x.val()})});z.done(function(C){A.find(".markdown").html(C);A.css("height",x.css("height"));A.css("width",x.css("width"));y.hide();A.show()})};l.prototype.showWriter=function(x){x.preventDefault();$("#markdown-write").parent().addClass("form-tab-selected");$("#markdown-preview").parent().removeClass("form-tab-selected");$(".write-area").show();$(".preview-area").hide()};l.prototype.listen=function(){$(document).on("click","#markdown-preview",this.showPreview.bind(this));$(document).on("click","#markdown-write",this.showWriter.bind(this))};function f(x){this.app=x;this.keyboardShortcuts()}f.prototype.focus=function(){$(document).on("focus","#form-search",function(){if($("#form-search")[0].setSelectionRange){$("#form-search")[0].setSelectionRange($("#form-search").val().length,$("#form-search").val().length)}})};f.prototype.listen=function(){var x=this;$(document).on("click",".filter-helper",function(A){A.preventDefault();var z=$(this).data("filter");var y=$(this).data("append-filter");if(y){z=$("#form-search").val()+" "+y}$("#form-search").val(z);if($("#board").length){x.app.board.reloadFilters(z)}else{$("form.search").submit()}})};f.prototype.keyboardShortcuts=function(){var x=this;Mousetrap.bind("v b",function(z){var y=$(".view-board");if(y.length){window.location=y.attr("href")}});Mousetrap.bind("v c",function(z){var y=$(".view-calendar");if(y.length){window.location=y.attr("href")}});Mousetrap.bind("v l",function(z){var y=$(".view-listing");if(y.length){window.location=y.attr("href")}});Mousetrap.bind("v g",function(z){var y=$(".view-gantt");if(y.length){window.location=y.attr("href")}});Mousetrap.bind("f",function(z){z.preventDefault();var y=document.getElementById("form-search");if(y){y.focus()}});Mousetrap.bind("r",function(z){z.preventDefault();var y=$(".filter-reset").data("filter");$("#form-search").val(y);if($("#board").length){x.app.board.reloadFilters(y)}else{$("form.search").submit()}})};function m(){this.board=new k(this);this.markdown=new l();this.search=new f(this);this.swimlane=new g();this.dropdown=new r();this.tooltip=new q(this);this.popover=new t(this);this.task=new a();this.project=new n();this.subtask=new e();this.keyboardShortcuts();this.chosen();this.poll();$(".alert-fade-out").delay(4000).fadeOut(800,function(){$(this).remove()});var x=false;$("select.task-reload-project-destination").change(function(){if(!x){$(".loading-icon").show();x=true;window.location=$(this).data("redirect").replace(/PROJECT_ID/g,$(this).val())}})}m.prototype.listen=function(){this.project.listen();this.popover.listen();this.markdown.listen();this.tooltip.listen();this.dropdown.listen();this.search.listen();this.task.listen();this.swimlane.listen();this.subtask.listen();this.search.focus();this.autoComplete();this.datePicker();this.focus()};m.prototype.refresh=function(){$(document).off();this.listen()};m.prototype.focus=function(){$("[autofocus]").each(function(x,y){$(this).focus()});$(document).on("focus",".auto-select",function(){$(this).select()});$(document).on("mouseup",".auto-select",function(x){x.preventDefault()})};m.prototype.poll=function(){window.setInterval(this.checkSession,60000)};m.prototype.keyboardShortcuts=function(){var x=this;Mousetrap.bindGlobal("mod+enter",function(){$("form").submit()});Mousetrap.bind("b",function(y){y.preventDefault();$("#board-selector").trigger("chosen:open")});Mousetrap.bindGlobal("esc",function(){x.popover.close();x.dropdown.close()})};m.prototype.checkSession=function(){if(!$(".form-login").length){$.ajax({cache:false,url:$("body").data("status-url"),statusCode:{401:function(){window.location=$("body").data("login-url")}}})}};m.prototype.datePicker=function(){$.datepicker.setDefaults($.datepicker.regional[$("body").data("js-lang")]);$(".form-date").datepicker({showOtherMonths:true,selectOtherMonths:true,dateFormat:"yy-mm-dd",constrainInput:false});$(".form-datetime").datetimepicker({controlType:"select",oneLine:true,dateFormat:"yy-mm-dd",constrainInput:false})};m.prototype.autoComplete=function(){$(".autocomplete").each(function(){var y=$(this);var z=y.data("dst-field");var x=y.data("dst-extra-field");if($("#form-"+z).val()==""){y.parent().find("input[type=submit]").attr("disabled","disabled")}y.autocomplete({source:y.data("search-url"),minLength:1,select:function(A,B){$("input[name="+z+"]").val(B.item.id);if(x){$("input[name="+x+"]").val(B.item[x])}y.parent().find("input[type=submit]").removeAttr("disabled")}})})};m.prototype.chosen=function(){$(".chosen-select").each(function(){var x=$(this).data("search-threshold");if(x===undefined){x=10}$(this).chosen({width:"180px",no_results_text:$(this).data("notfound"),disable_search_threshold:x})});$(".select-auto-redirect").change(function(){var x=new RegExp($(this).data("redirect-regex"),"g");window.location=$(this).data("redirect-url").replace(x,$(this).val())})};m.prototype.showLoadingIcon=function(){$("body").append('<span id="app-loading-icon"> <i class="fa fa-spinner fa-spin"></i></span>')};m.prototype.hideLoadingIcon=function(){$("#app-loading-icon").remove()};m.prototype.isVisible=function(){var x="";if(typeof document.hidden!=="undefined"){x="visibilityState"}else{if(typeof document.mozHidden!=="undefined"){x="mozVisibilityState"}else{if(typeof document.msHidden!=="undefined"){x="msVisibilityState"}else{if(typeof document.webkitHidden!=="undefined"){x="webkitVisibilityState"}}}}if(x!=""){return document[x]=="visible"}return true};m.prototype.formatDuration=function(x){if(x>=86400){return Math.round(x/86400)+"d"}else{if(x>=3600){return Math.round(x/3600)+"h"}else{if(x>=60){return Math.round(x/60)+"m"}}}return x+"s"};function d(){this.pasteCatcher=null}d.prototype.execute=function(){this.initialize()};d.prototype.initialize=function(){this.destroy();if(!window.Clipboard){this.pasteCatcher=document.createElement("div");this.pasteCatcher.id="screenshot-pastezone";this.pasteCatcher.contentEditable="true";this.pasteCatcher.style.opacity=0;this.pasteCatcher.style.position="fixed";this.pasteCatcher.style.top=0;this.pasteCatcher.style.right=0;this.pasteCatcher.style.width=0;document.body.insertBefore(this.pasteCatcher,document.body.firstChild);this.pasteCatcher.focus();document.addEventListener("click",this.setFocus.bind(this));document.getElementById("screenshot-zone").addEventListener("click",this.setFocus.bind(this))}window.addEventListener("paste",this.pasteHandler.bind(this))};d.prototype.destroy=function(){if(this.pasteCatcher!=null){document.body.removeChild(this.pasteCatcher)}else{if(document.getElementById("screenshot-pastezone")){document.body.removeChild(document.getElementById("screenshot-pastezone"))}}document.removeEventListener("click",this.setFocus.bind(this));this.pasteCatcher=null};d.prototype.setFocus=function(){if(this.pasteCatcher!==null){this.pasteCatcher.focus()}};d.prototype.pasteHandler=function(C){if(C.clipboardData&&C.clipboardData.items){var A=C.clipboardData.items;if(A){for(var B=0;B<A.length;B++){if(A[B].type.indexOf("image")!==-1){var z=A[B].getAsFile();var x=new FileReader();var y=this;x.onload=function(D){y.createImage(D.target.result)};x.readAsDataURL(z)}}}}else{setTimeout(this.checkInput.bind(this),100)}};d.prototype.checkInput=function(){var x=this.pasteCatcher.childNodes[0];if(x){if(x.tagName==="IMG"){this.createImage(x.src)}}this.pasteCatcher.innerHTML=""};d.prototype.createImage=function(z){var y=new Image();y.src=z;y.onload=function(){var A=z.split("base64,");var B=A[1];$("input[name=screenshot]").val(B)};var x=document.getElementById("screenshot-zone");x.innerHTML="";x.className="screenshot-pasted";x.appendChild(y);this.destroy();this.initialize()};function j(){}j.prototype.execute=function(){var x=$("#calendar");x.fullCalendar({lang:$("body").data("js-lang"),editable:true,eventLimit:true,defaultView:"month",header:{left:"prev,next today",center:"title",right:"month,agendaWeek,agendaDay"},eventDrop:function(y){$.ajax({cache:false,url:x.data("save-url"),contentType:"application/json",type:"POST",processData:false,data:JSON.stringify({task_id:y.id,date_due:y.start.format()})})},viewRender:function(){var y=x.data("check-url");var A={start:x.fullCalendar("getView").start.format(),end:x.fullCalendar("getView").end.format()};for(var z in A){y+="&"+z+"="+A[z]}$.getJSON(y,function(B){x.fullCalendar("removeEvents");x.fullCalendar("addEventSource",B);x.fullCalendar("rerenderEvents")})}})};function k(x){this.app=x;this.checkInterval=null;this.savingInProgress=false}k.prototype.execute=function(){this.app.swimlane.refresh();this.restoreColumnViewMode();this.compactView();this.poll();this.keyboardShortcuts();this.listen();this.dragAndDrop();$(window).on("load",this.columnScrolling);$(window).resize(this.columnScrolling)};k.prototype.poll=function(){var x=parseInt($("#board").attr("data-check-interval"));if(x>0){this.checkInterval=window.setInterval(this.check.bind(this),x*1000)}};k.prototype.reloadFilters=function(x){this.app.showLoadingIcon();$.ajax({cache:false,url:$("#board").data("reload-url"),contentType:"application/json",type:"POST",processData:false,data:JSON.stringify({search:x}),success:this.refresh.bind(this),error:this.app.hideLoadingIcon.bind(this)})};k.prototype.check=function(){if(this.app.isVisible()&&!this.savingInProgress){var x=this;this.app.showLoadingIcon();$.ajax({cache:false,url:$("#board").data("check-url"),statusCode:{200:function(y){x.refresh(y)},304:function(){x.app.hideLoadingIcon()}}})}};k.prototype.save=function(A,B,x,z){var y=this;this.app.showLoadingIcon();this.savingInProgress=true;$.ajax({cache:false,url:$("#board").data("save-url"),contentType:"application/json",type:"POST",processData:false,data:JSON.stringify({task_id:A,column_id:B,swimlane_id:z,position:x}),success:function(C){y.refresh(C);this.savingInProgress=false},error:function(){y.app.hideLoadingIcon();this.savingInProgress=false}})};k.prototype.refresh=function(x){$("#board-container").replaceWith(x);this.app.refresh();this.app.swimlane.refresh();this.app.hideLoadingIcon();this.listen();this.dragAndDrop();this.compactView();this.restoreColumnViewMode();this.columnScrolling()};k.prototype.dragAndDrop=function(){var x=this;var y={forcePlaceholderSize:true,tolerance:"pointer",connectWith:".board-task-list",placeholder:"draggable-placeholder",items:".draggable-item",stop:function(A,H){var C=H.item;var G=C.attr("data-task-id");var I=C.attr("data-position");var F=C.attr("data-column-id");var E=C.attr("data-swimlane-id");var B=C.parent().attr("data-column-id");var z=C.parent().attr("data-swimlane-id");var D=C.index()+1;C.removeClass("draggable-item-selected");if(B!=F||z!=E||D!=I){x.changeTaskState(G);x.save(G,B,D,z)}},start:function(z,A){A.item.addClass("draggable-item-selected");A.placeholder.height(A.item.height())}};if($.support.touch){$(".task-board-sort-handle").css("display","inline");y.handle=".task-board-sort-handle"}$(".board-task-list").sortable(y)};k.prototype.changeTaskState=function(y){var x=$("div[data-task-id="+y+"]");x.addClass("task-board-saving-state");x.find(".task-board-saving-icon").show()};k.prototype.listen=function(){var x=this;$(document).on("click",".task-board",function(y){if(y.target.tagName!="A"){window.location=$(this).data("task-url")}});$(document).on("click",".filter-toggle-scrolling",function(y){y.preventDefault();x.toggleCompactView()});$(document).on("click",".filter-toggle-height",function(y){y.preventDefault();x.toggleColumnScrolling()});$(document).on("click",".board-toggle-column-view",function(){x.toggleColumnViewMode($(this).data("column-id"))})};k.prototype.toggleColumnScrolling=function(){var x=localStorage.getItem("column_scroll");if(x==undefined){x=1}localStorage.setItem("column_scroll",x==0?1:0);this.columnScrolling()};k.prototype.columnScrolling=function(){if(localStorage.getItem("column_scroll")==0){var x=80;$(".filter-max-height").show();$(".filter-min-height").hide();$(".board-rotation-wrapper").css("min-height","");$(".board-task-list").each(function(){var y=$(this).height();if(y>x){x=y}});$(".board-task-list").css("min-height",x);$(".board-task-list").css("height","")}else{$(".filter-max-height").hide();$(".filter-min-height").show();if($(".board-swimlane").length>1){$(".board-task-list").each(function(){if($(this).height()>500){$(this).css("height",500)}else{$(this).css("min-height",320);$(".board-rotation-wrapper").css("min-height",320)}})}else{var x=$(window).height()-170;$(".board-task-list").css("height",x);$(".board-rotation-wrapper").css("min-height",x)}}};k.prototype.toggleCompactView=function(){var x=localStorage.getItem("horizontal_scroll")||1;localStorage.setItem("horizontal_scroll",x==0?1:0);this.compactView()};k.prototype.compactView=function(){if(localStorage.getItem("horizontal_scroll")==0){$(".filter-wide").show();$(".filter-compact").hide();$("#board-container").addClass("board-container-compact");$("#board th:not(.board-column-header-collapsed)").addClass("board-column-compact")}else{$(".filter-wide").hide();$(".filter-compact").show();$("#board-container").removeClass("board-container-compact");$("#board th").removeClass("board-column-compact")}};k.prototype.toggleCollapsedMode=function(){var x=this;this.app.showLoadingIcon();$.ajax({cache:false,url:$('.filter-display-mode:not([style="display: none;"]) a').attr("href"),success:function(y){$(".filter-display-mode").toggle();x.refresh(y)}})};k.prototype.restoreColumnViewMode=function(){var x=this;$(".board-column-header").each(function(){var y=$(this).data("column-id");if(localStorage.getItem("hidden_column_"+y)){x.hideColumn(y)}})};k.prototype.toggleColumnViewMode=function(x){if(localStorage.getItem("hidden_column_"+x)){this.showColumn(x)}else{this.hideColumn(x)}};k.prototype.hideColumn=function(x){$(".board-column-"+x+" .board-column-expanded").hide();$(".board-column-"+x+" .board-column-collapsed").show();$(".board-column-header-"+x+" .board-column-expanded").hide();$(".board-column-header-"+x+" .board-column-collapsed").show();$(".board-column-header-"+x).each(function(){$(this).removeClass("board-column-compact");$(this).addClass("board-column-header-collapsed")});$(".board-column-"+x).each(function(){$(this).addClass("board-column-task-collapsed")});$(".board-column-"+x+" .board-rotation").each(function(){$(this).css("width",$(".board-column-"+x+"").height())});localStorage.setItem("hidden_column_"+x,1)};k.prototype.showColumn=function(x){$(".board-column-"+x+" .board-column-expanded").show();$(".board-column-"+x+" .board-column-collapsed").hide();$(".board-column-header-"+x+" .board-column-expanded").show();$(".board-column-header-"+x+" .board-column-collapsed").hide();$(".board-column-header-"+x).removeClass("board-column-header-collapsed");$(".board-column-"+x).removeClass("board-column-task-collapsed");if(localStorage.getItem("horizontal_scroll")==0){$(".board-column-header-"+x).addClass("board-column-compact")}localStorage.removeItem("hidden_column_"+x)};k.prototype.keyboardShortcuts=function(){var x=this;Mousetrap.bind("c",function(){x.toggleCompactView()});Mousetrap.bind("s",function(){x.toggleCollapsedMode()});Mousetrap.bind("n",function(){x.app.popover.open($("#board").data("task-creation-url"))})};function g(){}g.prototype.getStorageKey=function(){return"hidden_swimlanes_"+$("#board").data("project-id")};g.prototype.expand=function(y){var z=this.getAllCollapsed();var x=z.indexOf(y);if(x>-1){z.splice(x,1)}localStorage.setItem(this.getStorageKey(),JSON.stringify(z));$(".board-swimlane-columns-"+y).css("display","table-row");$(".board-swimlane-tasks-"+y).css("display","table-row");$(".hide-icon-swimlane-"+y).css("display","inline");$(".show-icon-swimlane-"+y).css("display","none")};g.prototype.collapse=function(x){var y=this.getAllCollapsed();if(y.indexOf(x)<0){y.push(x);localStorage.setItem(this.getStorageKey(),JSON.stringify(y))}$(".board-swimlane-columns-"+x+":not(:first-child)").css("display","none");$(".board-swimlane-tasks-"+x).css("display","none");$(".hide-icon-swimlane-"+x).css("display","none");$(".show-icon-swimlane-"+x).css("display","inline")};g.prototype.isCollapsed=function(x){return this.getAllCollapsed().indexOf(x)>-1};g.prototype.getAllCollapsed=function(){return JSON.parse(localStorage.getItem(this.getStorageKey()))||[]};g.prototype.refresh=function(){var y=this.getAllCollapsed();for(var x=0;x<y.length;x++){this.collapse(y[x])}};g.prototype.listen=function(){var x=this;$(document).on("click",".board-swimlane-toggle",function(z){z.preventDefault();var y=$(this).data("swimlane-id");if(x.isCollapsed(y)){x.expand(y)}else{x.collapse(y)}})};function b(x){this.app=x;this.data=[];this.options={container:"#gantt-chart",showWeekends:true,allowMoves:true,allowResizes:true,cellWidth:21,cellHeight:31,slideWidth:1000,vHeaderWidth:200}}b.prototype.saveRecord=function(x){this.app.showLoadingIcon();$.ajax({cache:false,url:$(this.options.container).data("save-url"),contentType:"application/json",type:"POST",processData:false,data:JSON.stringify(x),complete:this.app.hideLoadingIcon.bind(this)})};b.prototype.execute=function(){this.data=this.prepareData($(this.options.container).data("records"));var A=Math.floor((this.options.slideWidth/this.options.cellWidth)+5);var z=this.getDateRange(A);var x=z[0];var C=z[1];var y=$(this.options.container);var B=jQuery("<div>",{"class":"ganttview"});B.append(this.renderVerticalHeader());B.append(this.renderSlider(x,C));y.append(B);jQuery("div.ganttview-grid-row div.ganttview-grid-row-cell:last-child",y).addClass("last");jQuery("div.ganttview-hzheader-days div.ganttview-hzheader-day:last-child",y).addClass("last");jQuery("div.ganttview-hzheader-months div.ganttview-hzheader-month:last-child",y).addClass("last");if(!$(this.options.container).data("readonly")){this.listenForBlockResize(x);this.listenForBlockMove(x)}else{this.options.allowResizes=false;this.options.allowMoves=false}};b.prototype.renderVerticalHeader=function(){var B=jQuery("<div>",{"class":"ganttview-vtheader"});var y=jQuery("<div>",{"class":"ganttview-vtheader-item"});var A=jQuery("<div>",{"class":"ganttview-vtheader-series"});for(var x=0;x<this.data.length;x++){var z=jQuery("<span>").append(jQuery("<i>",{"class":"fa fa-info-circle tooltip",title:this.getVerticalHeaderTooltip(this.data[x])})).append(" ");if(this.data[x].type=="task"){z.append(jQuery("<a>",{href:this.data[x].link,target:"_blank",title:this.data[x].title}).append(this.data[x].title))}else{z.append(jQuery("<a>",{href:this.data[x].board_link,target:"_blank",title:$(this.options.container).data("label-board-link")}).append('<i class="fa fa-th"></i>')).append(" ").append(jQuery("<a>",{href:this.data[x].gantt_link,target:"_blank",title:$(this.options.container).data("label-gantt-link")}).append('<i class="fa fa-sliders"></i>')).append(" ").append(jQuery("<a>",{href:this.data[x].link,target:"_blank"}).append(this.data[x].title))}A.append(jQuery("<div>",{"class":"ganttview-vtheader-series-name"}).append(z))}y.append(A);B.append(y);return B};b.prototype.renderSlider=function(y,A){var x=jQuery("<div>",{"class":"ganttview-slide-container"});var z=this.getDates(y,A);x.append(this.renderHorizontalHeader(z));x.append(this.renderGrid(z));x.append(this.addBlockContainers());this.addBlocks(x,y);return x};b.prototype.renderHorizontalHeader=function(x){var E=jQuery("<div>",{"class":"ganttview-hzheader"});var C=jQuery("<div>",{"class":"ganttview-hzheader-months"});var B=jQuery("<div>",{"class":"ganttview-hzheader-days"});var A=0;for(var F in x){for(var z in x[F]){var G=x[F][z].length*this.options.cellWidth;A=A+G;C.append(jQuery("<div>",{"class":"ganttview-hzheader-month",css:{width:(G-1)+"px"}}).append($.datepicker.regional[$("body").data("js-lang")].monthNames[z]+" "+F));for(var D in x[F][z]){B.append(jQuery("<div>",{"class":"ganttview-hzheader-day"}).append(x[F][z][D].getDate()))}}}C.css("width",A+"px");B.css("width",A+"px");E.append(C).append(B);return E};b.prototype.renderGrid=function(x){var G=jQuery("<div>",{"class":"ganttview-grid"});var B=jQuery("<div>",{"class":"ganttview-grid-row"});for(var E in x){for(var z in x[E]){for(var D in x[E][z]){var A=jQuery("<div>",{"class":"ganttview-grid-row-cell"});if(this.options.showWeekends&&this.isWeekend(x[E][z][D])){A.addClass("ganttview-weekend")}B.append(A)}}}var F=jQuery("div.ganttview-grid-row-cell",B).length*this.options.cellWidth;B.css("width",F+"px");G.css("width",F+"px");for(var C=0;C<this.data.length;C++){G.append(B.clone())}return G};b.prototype.addBlockContainers=function(){var y=jQuery("<div>",{"class":"ganttview-blocks"});for(var x=0;x<this.data.length;x++){y.append(jQuery("<div>",{"class":"ganttview-block-container"}))}return y};b.prototype.addBlocks=function(y,x){var F=jQuery("div.ganttview-blocks div.ganttview-block-container",y);var z=0;for(var C=0;C<this.data.length;C++){var D=this.data[C];var G=this.daysBetween(D.start,D.end)+1;var B=this.daysBetween(x,D.start);var E=jQuery("<div>",{"class":"ganttview-block-text"});var A=jQuery("<div>",{"class":"ganttview-block tooltip"+(this.options.allowMoves?" ganttview-block-movable":""),title:this.getBarTooltip(D),css:{width:((G*this.options.cellWidth)-9)+"px","margin-left":(B*this.options.cellWidth)+"px"}}).append(E);if(G>=2){E.append(D.progress)}A.data("record",D);this.setBarColor(A,D);if(D.progress!="0%"){A.append(jQuery("<div>",{css:{"z-index":0,position:"absolute",top:0,bottom:0,"background-color":D.color.border,width:D.progress,opacity:0.4}}))}jQuery(F[z]).append(A);z=z+1}};b.prototype.getVerticalHeaderTooltip=function(y){var D="";if(y.type=="task"){D="<strong>"+y.column_title+"</strong> ("+y.progress+")<br/>"+y.title}else{var A=["managers","members"];for(var z in A){var B=A[z];if(!jQuery.isEmptyObject(y.users[B])){var C=jQuery("<ul>");for(var x in y.users[B]){C.append(jQuery("<li>").append(y.users[B][x]))}D+="<p><strong>"+$(this.options.container).data("label-"+B)+"</strong></p>"+C[0].outerHTML}}}return D};b.prototype.getBarTooltip=function(x){var y="";if(x.not_defined){y=$(this.options.container).data("label-not-defined")}else{if(x.type=="task"){y="<strong>"+x.progress+"</strong><br/>"+$(this.options.container).data("label-assignee")+" "+(x.assignee?x.assignee:"")+"<br/>"}y+=$(this.options.container).data("label-start-date")+" "+$.datepicker.formatDate("yy-mm-dd",x.start)+"<br/>";y+=$(this.options.container).data("label-end-date")+" "+$.datepicker.formatDate("yy-mm-dd",x.end)}return y};b.prototype.setBarColor=function(y,x){if(x.not_defined){y.addClass("ganttview-block-not-defined")}else{y.css("background-color",x.color.background);y.css("border-color",x.color.border)}};b.prototype.listenForBlockResize=function(x){var y=this;jQuery("div.ganttview-block",this.options.container).resizable({grid:this.options.cellWidth,handles:"e,w",delay:300,stop:function(){var z=jQuery(this);y.updateDataAndPosition(z,x);y.saveRecord(z.data("record"))}})};b.prototype.listenForBlockMove=function(x){var y=this;jQuery("div.ganttview-block",this.options.container).draggable({axis:"x",delay:300,grid:[this.options.cellWidth,this.options.cellWidth],stop:function(){var z=jQuery(this);y.updateDataAndPosition(z,x);y.saveRecord(z.data("record"))}})};b.prototype.updateDataAndPosition=function(C,A){var x=jQuery("div.ganttview-slide-container",this.options.container);var G=x.scrollLeft();var D=C.offset().left-x.offset().left-1+G;var F=C.data("record");F.not_defined=false;this.setBarColor(C,F);var z=Math.round(D/this.options.cellWidth);var E=this.addDays(this.cloneDate(A),z);F.start=E;var y=C.outerWidth();var B=Math.round(y/this.options.cellWidth)-1;F.end=this.addDays(this.cloneDate(E),B);if(F.type==="task"&&B>0){jQuery("div.ganttview-block-text",C).text(F.progress)}C.attr("title",this.getBarTooltip(F));C.data("record",F);C.css("top","").css("left","").css("position","relative").css("margin-left",D+"px")};b.prototype.getDates=function(B,x){var A=[];A[B.getFullYear()]=[];A[B.getFullYear()][B.getMonth()]=[B];var z=B;while(this.compareDate(z,x)==-1){var y=this.addDays(this.cloneDate(z),1);if(!A[y.getFullYear()]){A[y.getFullYear()]=[]}if(!A[y.getFullYear()][y.getMonth()]){A[y.getFullYear()][y.getMonth()]=[]}A[y.getFullYear()][y.getMonth()].push(y);z=y}return A};b.prototype.prepareData=function(z){for(var y=0;y<z.length;y++){var A=new Date(z[y].start[0],z[y].start[1]-1,z[y].start[2],0,0,0,0);z[y].start=A;var x=new Date(z[y].end[0],z[y].end[1]-1,z[y].end[2],0,0,0,0);z[y].end=x}return z};b.prototype.getDateRange=function(z){var C=new Date();var y=new Date();for(var A=0;A<this.data.length;A++){var B=new Date();B.setTime(Date.parse(this.data[A].start));var x=new Date();x.setTime(Date.parse(this.data[A].end));if(A==0){C=B;y=x}if(this.compareDate(C,B)==1){C=B}if(this.compareDate(y,x)==-1){y=x}}if(this.daysBetween(C,y)<z){y=this.addDays(this.cloneDate(C),z)}C.setDate(C.getDate()-1);return[C,y]};b.prototype.daysBetween=function(A,x){if(!A||!x){return 0}var z=0,y=this.cloneDate(A);while(this.compareDate(y,x)==-1){z=z+1;this.addDays(y,1)}return z};b.prototype.isWeekend=function(x){return x.getDay()%6==0};b.prototype.cloneDate=function(x){return new Date(x.getTime())};b.prototype.addDays=function(x,y){x.setDate(x.getDate()+y*1);return x};b.prototype.compareDate=function(y,x){if(isNaN(y)||isNaN(x)){throw new Error(y+" - "+x)}else{if(y instanceof Date&&x instanceof Date){return(y<x)?-1:(y>x)?1:0}else{throw new TypeError(y+" - "+x)}}};function a(){}a.prototype.listen=function(){$(document).on("click",".color-square",function(){$(".color-square-selected").removeClass("color-square-selected");$(this).addClass("color-square-selected");$("#form-color_id").val($(this).data("color-id"))});$(document).on("click",".assign-me",function(z){z.preventDefault();var x=$(this).data("current-id");var y="#"+$(this).data("target-id");if($(y+" option[value="+x+"]").length){$(y).val(x)}})};function n(){}n.prototype.listen=function(){$(".project-change-role").on("change",function(){$.ajax({cache:false,url:$(this).data("url"),contentType:"application/json",type:"POST",processData:false,data:JSON.stringify({id:$(this).data("id"),role:$(this).val()})})});$("#project-creation-form #form-src_project_id").on("change",function(){var x=$(this).val();if(x==0){$(".project-creation-options").hide()}else{$(".project-creation-options").show()}})};function e(){}e.prototype.listen=function(){$(document).on("click",".subtask-toggle-status",function(y){y.preventDefault();var x=$(this);$.ajax({cache:false,url:x.attr("href"),success:function(z){if(x.hasClass("subtask-refresh-table")){$(".subtasks-table").replaceWith(z)}else{x.replaceWith(z)}}})});$(document).on("click",".subtask-toggle-timer",function(y){y.preventDefault();var x=$(this);$.ajax({cache:false,url:x.attr("href"),success:function(z){$(".subtasks-table").replaceWith(z)}})})};function s(){}s.prototype.execute=function(){var z=$("#chart").data("metrics");var y=[];for(var x=0;x<z.length;x++){y.push([z[x].column_title,z[x].nb_tasks])}c3.generate({data:{columns:y,type:"donut"}})};function p(){}p.prototype.execute=function(){var z=$("#chart").data("metrics");var y=[];for(var x=0;x<z.length;x++){y.push([z[x].user,z[x].nb_tasks])}c3.generate({data:{columns:y,type:"donut"}})};function c(){}c.prototype.execute=function(){var D=$("#chart").data("metrics");var C=[];var x=[];var y=[];var A=d3.time.format("%Y-%m-%d");var E=d3.time.format($("#chart").data("date-format"));for(var B=0;B<D.length;B++){for(var z=0;z<D[B].length;z++){if(B==0){C.push([D[B][z]]);if(z>0){x.push(D[B][z])}}else{C[z].push(D[B][z]);if(z==0){y.push(E(A.parse(D[B][z])))}}}}c3.generate({data:{columns:C,type:"area-spline",groups:[x]},axis:{x:{type:"category",categories:y}}})};function o(){}o.prototype.execute=function(){var C=$("#chart").data("metrics");var B=[[$("#chart").data("label-total")]];var x=[];var z=d3.time.format("%Y-%m-%d");var D=d3.time.format($("#chart").data("date-format"));for(var A=0;A<C.length;A++){for(var y=0;y<C[A].length;y++){if(A==0){B.push([C[A][y]])}else{B[y+1].push(C[A][y]);if(y>0){if(B[0][A]==undefined){B[0].push(0)}B[0][A]+=C[A][y]}if(y==0){x.push(D(z.parse(C[A][y])))}}}}c3.generate({data:{columns:B},axis:{x:{type:"category",categories:x}}})};function h(x){this.app=x}h.prototype.execute=function(){var z=$("#chart").data("metrics");var A=[$("#chart").data("label")];var x=[];for(var y in z){A.push(z[y].average);x.push(z[y].title)}c3.generate({data:{columns:[A],type:"bar"},bar:{width:{ratio:0.5}},axis:{x:{type:"category",categories:x},y:{tick:{format:this.app.formatDuration}}},legend:{show:false}})};function w(x){this.app=x}w.prototype.execute=function(){var z=$("#chart").data("metrics");var A=[$("#chart").data("label")];var x=[];for(var y=0;y<z.length;y++){A.push(z[y].time_spent);x.push(z[y].title)}c3.generate({data:{columns:[A],type:"bar"},bar:{width:{ratio:0.5}},axis:{x:{type:"category",categories:x},y:{tick:{format:this.app.formatDuration}}},legend:{show:false}})};function u(x){this.app=x}u.prototype.execute=function(){var D=$("#chart").data("metrics");var C=[$("#chart").data("label-cycle")];var z=[$("#chart").data("label-lead")];var y=[];var B={};B[$("#chart").data("label-cycle")]="area";B[$("#chart").data("label-lead")]="area-spline";var x={};x[$("#chart").data("label-lead")]="#afb42b";x[$("#chart").data("label-cycle")]="#4e342e";for(var A=0;A<D.length;A++){C.push(parseInt(D[A].avg_cycle_time));z.push(parseInt(D[A].avg_lead_time));y.push(D[A].day)}c3.generate({data:{columns:[z,C],types:B,colors:x},axis:{x:{type:"category",categories:y},y:{tick:{format:this.app.formatDuration}}}})};function i(x){this.app=x}i.prototype.execute=function(){var C=$("#chart").data("metrics");var y=$("#chart").data("label-open");var x=$("#chart").data("label-closed");var D=[$("#chart").data("label-spent")];var B=[$("#chart").data("label-estimated")];var A=[];for(var z in C){D.push(parseFloat(C[z].time_spent));B.push(parseFloat(C[z].time_estimated));A.push(z=="open"?y:x)}c3.generate({data:{columns:[D,B],type:"bar"},bar:{width:{ratio:0.2}},axis:{x:{type:"category",categories:A}},legend:{show:true}})};function v(){this.routes={}}v.prototype.addRoute=function(y,x){this.routes[y]=x};v.prototype.dispatch=function(y){for(var z in this.routes){if(document.getElementById(z)){var x=Object.create(this.routes[z].prototype);this.routes[z].apply(x,[y]);x.execute();break}}};jQuery(document).ready(function(){var y=new m();var x=new v();x.addRoute("board",k);x.addRoute("calendar",j);x.addRoute("screenshot-zone",d);x.addRoute("analytic-task-repartition",s);x.addRoute("analytic-user-repartition",p);x.addRoute("analytic-cfd",c);x.addRoute("analytic-burndown",o);x.addRoute("analytic-avg-time-column",h);x.addRoute("analytic-task-time-column",w);x.addRoute("analytic-lead-cycle-time",u);x.addRoute("analytic-compare-hours",i);x.addRoute("gantt-chart",b);x.dispatch(y);y.listen()})})();
\ No newline at end of file +!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){function c(a){return a>1&&5>a&&1!==~~(a/10)}function d(a,b,d,e){var f=a+" ";switch(d){case"s":return b||e?"pár sekund":"pár sekundami";case"m":return b?"minuta":e?"minutu":"minutou";case"mm":return b||e?f+(c(a)?"minuty":"minut"):f+"minutami";case"h":return b?"hodina":e?"hodinu":"hodinou";case"hh":return b||e?f+(c(a)?"hodiny":"hodin"):f+"hodinami";case"d":return b||e?"den":"dnem";case"dd":return b||e?f+(c(a)?"dny":"dní"):f+"dny";case"M":return b||e?"měsíc":"měsícem";case"MM":return b||e?f+(c(a)?"měsíce":"měsíců"):f+"měsíci";case"y":return b||e?"rok":"rokem";case"yy":return b||e?f+(c(a)?"roky":"let"):f+"lety"}}var e="leden_únor_březen_duben_květen_červen_červenec_srpen_září_říjen_listopad_prosinec".split("_"),f="led_úno_bře_dub_kvě_čvn_čvc_srp_zář_říj_lis_pro".split("_");(b.defineLocale||b.lang).call(b,"cs",{months:e,monthsShort:f,monthsParse:function(a,b){var c,d=[];for(c=0;12>c;c++)d[c]=new RegExp("^"+a[c]+"$|^"+b[c]+"$","i");return d}(e,f),weekdays:"neděle_pondělí_úterý_středa_čtvrtek_pátek_sobota".split("_"),weekdaysShort:"ne_po_út_st_čt_pá_so".split("_"),weekdaysMin:"ne_po_út_st_čt_pá_so".split("_"),longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd D. MMMM YYYY LT"},calendar:{sameDay:"[dnes v] LT",nextDay:"[zítra v] LT",nextWeek:function(){switch(this.day()){case 0:return"[v neděli v] LT";case 1:case 2:return"[v] dddd [v] LT";case 3:return"[ve středu v] LT";case 4:return"[ve čtvrtek v] LT";case 5:return"[v pátek v] LT";case 6:return"[v sobotu v] LT"}},lastDay:"[včera v] LT",lastWeek:function(){switch(this.day()){case 0:return"[minulou neděli v] LT";case 1:case 2:return"[minulé] dddd [v] LT";case 3:return"[minulou středu v] LT";case 4:case 5:return"[minulý] dddd [v] LT";case 6:return"[minulou sobotu v] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"před %s",s:d,m:d,mm:d,h:d,hh:d,d:d,dd:d,M:d,MM:d,y:d,yy:d},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("cs","cs",{closeText:"Zavřít",prevText:"<Dříve",nextText:"Později>",currentText:"Nyní",monthNames:["leden","únor","březen","duben","květen","červen","červenec","srpen","září","říjen","listopad","prosinec"],monthNamesShort:["led","úno","bře","dub","kvě","čer","čvc","srp","zář","říj","lis","pro"],dayNames:["neděle","pondělí","úterý","středa","čtvrtek","pátek","sobota"],dayNamesShort:["ne","po","út","st","čt","pá","so"],dayNamesMin:["ne","po","út","st","čt","pá","so"],weekHeader:"Týd",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("cs",{buttonText:{month:"Měsíc",week:"Týden",day:"Den",list:"Agenda"},allDayText:"Celý den",eventLimitText:function(a){return"+další: "+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"da",{months:"januar_februar_marts_april_maj_juni_juli_august_september_oktober_november_december".split("_"),monthsShort:"jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec".split("_"),weekdays:"søndag_mandag_tirsdag_onsdag_torsdag_fredag_lørdag".split("_"),weekdaysShort:"søn_man_tir_ons_tor_fre_lør".split("_"),weekdaysMin:"sø_ma_ti_on_to_fr_lø".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd [d.] D. MMMM YYYY LT"},calendar:{sameDay:"[I dag kl.] LT",nextDay:"[I morgen kl.] LT",nextWeek:"dddd [kl.] LT",lastDay:"[I går kl.] LT",lastWeek:"[sidste] dddd [kl] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"%s siden",s:"få sekunder",m:"et minut",mm:"%d minutter",h:"en time",hh:"%d timer",d:"en dag",dd:"%d dage",M:"en måned",MM:"%d måneder",y:"et år",yy:"%d år"},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("da","da",{closeText:"Luk",prevText:"<Forrige",nextText:"Næste>",currentText:"Idag",monthNames:["Januar","Februar","Marts","April","Maj","Juni","Juli","August","September","Oktober","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],dayNames:["Søndag","Mandag","Tirsdag","Onsdag","Torsdag","Fredag","Lørdag"],dayNamesShort:["Søn","Man","Tir","Ons","Tor","Fre","Lør"],dayNamesMin:["Sø","Ma","Ti","On","To","Fr","Lø"],weekHeader:"Uge",dateFormat:"dd-mm-yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("da",{buttonText:{month:"Måned",week:"Uge",day:"Dag",list:"Agenda"},allDayText:"Hele dagen",eventLimitText:"flere"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){function c(a,b,c,d){var e={m:["eine Minute","einer Minute"],h:["eine Stunde","einer Stunde"],d:["ein Tag","einem Tag"],dd:[a+" Tage",a+" Tagen"],M:["ein Monat","einem Monat"],MM:[a+" Monate",a+" Monaten"],y:["ein Jahr","einem Jahr"],yy:[a+" Jahre",a+" Jahren"]};return b?e[c][0]:e[c][1]}(b.defineLocale||b.lang).call(b,"de",{months:"Januar_Februar_März_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember".split("_"),monthsShort:"Jan._Febr._Mrz._Apr._Mai_Jun._Jul._Aug._Sept._Okt._Nov._Dez.".split("_"),weekdays:"Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag".split("_"),weekdaysShort:"So._Mo._Di._Mi._Do._Fr._Sa.".split("_"),weekdaysMin:"So_Mo_Di_Mi_Do_Fr_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd, D. MMMM YYYY LT"},calendar:{sameDay:"[Heute um] LT [Uhr]",sameElse:"L",nextDay:"[Morgen um] LT [Uhr]",nextWeek:"dddd [um] LT [Uhr]",lastDay:"[Gestern um] LT [Uhr]",lastWeek:"[letzten] dddd [um] LT [Uhr]"},relativeTime:{future:"in %s",past:"vor %s",s:"ein paar Sekunden",m:c,mm:"%d Minuten",h:c,hh:"%d Stunden",d:c,dd:c,M:c,MM:c,y:c,yy:c},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("de","de",{closeText:"Schließen",prevText:"<Zurück",nextText:"Vor>",currentText:"Heute",monthNames:["Januar","Februar","März","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember"],monthNamesShort:["Jan","Feb","Mär","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Dez"],dayNames:["Sonntag","Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag"],dayNamesShort:["So","Mo","Di","Mi","Do","Fr","Sa"],dayNamesMin:["So","Mo","Di","Mi","Do","Fr","Sa"],weekHeader:"KW",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("de",{buttonText:{month:"Monat",week:"Woche",day:"Tag",list:"Terminübersicht"},allDayText:"Ganztägig",eventLimitText:function(a){return"+ weitere "+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){var c="ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.".split("_"),d="ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic".split("_");(b.defineLocale||b.lang).call(b,"es",{months:"enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre".split("_"),monthsShort:function(a,b){return/-MMM-/.test(b)?d[a.month()]:c[a.month()]},weekdays:"domingo_lunes_martes_miércoles_jueves_viernes_sábado".split("_"),weekdaysShort:"dom._lun._mar._mié._jue._vie._sáb.".split("_"),weekdaysMin:"Do_Lu_Ma_Mi_Ju_Vi_Sá".split("_"),longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY LT",LLLL:"dddd, D [de] MMMM [de] YYYY LT"},calendar:{sameDay:function(){return"[hoy a la"+(1!==this.hours()?"s":"")+"] LT"},nextDay:function(){return"[mañana a la"+(1!==this.hours()?"s":"")+"] LT"},nextWeek:function(){return"dddd [a la"+(1!==this.hours()?"s":"")+"] LT"},lastDay:function(){return"[ayer a la"+(1!==this.hours()?"s":"")+"] LT"},lastWeek:function(){return"[el] dddd [pasado a la"+(1!==this.hours()?"s":"")+"] LT"},sameElse:"L"},relativeTime:{future:"en %s",past:"hace %s",s:"unos segundos",m:"un minuto",mm:"%d minutos",h:"una hora",hh:"%d horas",d:"un día",dd:"%d días",M:"un mes",MM:"%d meses",y:"un año",yy:"%d años"},ordinalParse:/\d{1,2}º/,ordinal:"%dº",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("es","es",{closeText:"Cerrar",prevText:"<Ant",nextText:"Sig>",currentText:"Hoy",monthNames:["enero","febrero","marzo","abril","mayo","junio","julio","agosto","septiembre","octubre","noviembre","diciembre"],monthNamesShort:["ene","feb","mar","abr","may","jun","jul","ago","sep","oct","nov","dic"],dayNames:["domingo","lunes","martes","miércoles","jueves","viernes","sábado"],dayNamesShort:["dom","lun","mar","mié","jue","vie","sáb"],dayNamesMin:["D","L","M","X","J","V","S"],weekHeader:"Sm",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("es",{buttonText:{month:"Mes",week:"Semana",day:"Día",list:"Agenda"},allDayHtml:"Todo<br/>el día",eventLimitText:"más"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"el",{monthsNominativeEl:"Ιανουάριος_Φεβρουάριος_Μάρτιος_Απρίλιος_Μάιος_Ιούνιος_Ιούλιος_Αύγουστος_Σεπτέμβριος_Οκτώβριος_Νοέμβριος_Δεκέμβριος".split("_"),monthsGenitiveEl:"Ιανουαρίου_Φεβρουαρίου_Μαρτίου_Απριλίου_Μαΐου_Ιουνίου_Ιουλίου_Αυγούστου_Σεπτεμβρίου_Οκτωβρίου_Νοεμβρίου_Δεκεμβρίου".split("_"),months:function(a,b){return/D/.test(b.substring(0,b.indexOf("MMMM")))?this._monthsGenitiveEl[a.month()]:this._monthsNominativeEl[a.month()]},monthsShort:"Ιαν_Φεβ_Μαρ_Απρ_Μαϊ_Ιουν_Ιουλ_Αυγ_Σεπ_Οκτ_Νοε_Δεκ".split("_"),weekdays:"Κυριακή_Δευτέρα_Τρίτη_Τετάρτη_Πέμπτη_Παρασκευή_Σάββατο".split("_"),weekdaysShort:"Κυρ_Δευ_Τρι_Τετ_Πεμ_Παρ_Σαβ".split("_"),weekdaysMin:"Κυ_Δε_Τρ_Τε_Πε_Πα_Σα".split("_"),meridiem:function(a,b,c){return a>11?c?"μμ":"ΜΜ":c?"πμ":"ΠΜ"},isPM:function(a){return"μ"===(a+"").toLowerCase()[0]},meridiemParse:/[ΠΜ]\.?Μ?\.?/i,longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendarEl:{sameDay:"[Σήμερα {}] LT",nextDay:"[Αύριο {}] LT",nextWeek:"dddd [{}] LT",lastDay:"[Χθες {}] LT",lastWeek:function(){switch(this.day()){case 6:return"[το προηγούμενο] dddd [{}] LT";default:return"[την προηγούμενη] dddd [{}] LT"}},sameElse:"L"},calendar:function(a,b){var c=this._calendarEl[a],d=b&&b.hours();return"function"==typeof c&&(c=c.apply(b)),c.replace("{}",d%12===1?"στη":"στις")},relativeTime:{future:"σε %s",past:"%s πριν",s:"λίγα δευτερόλεπτα",m:"ένα λεπτό",mm:"%d λεπτά",h:"μία ώρα",hh:"%d ώρες",d:"μία μέρα",dd:"%d μέρες",M:"ένας μήνας",MM:"%d μήνες",y:"ένας χρόνος",yy:"%d χρόνια"},ordinalParse:/\d{1,2}η/,ordinal:"%dη",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("el","el",{closeText:"Κλείσιμο",prevText:"Προηγούμενος",nextText:"Επόμενος",currentText:"Σήμερα",monthNames:["Ιανουάριος","Φεβρουάριος","Μάρτιος","Απρίλιος","Μάιος","Ιούνιος","Ιούλιος","Αύγουστος","Σεπτέμβριος","Οκτώβριος","Νοέμβριος","Δεκέμβριος"],monthNamesShort:["Ιαν","Φεβ","Μαρ","Απρ","Μαι","Ιουν","Ιουλ","Αυγ","Σεπ","Οκτ","Νοε","Δεκ"],dayNames:["Κυριακή","Δευτέρα","Τρίτη","Τετάρτη","Πέμπτη","Παρασκευή","Σάββατο"],dayNamesShort:["Κυρ","Δευ","Τρι","Τετ","Πεμ","Παρ","Σαβ"],dayNamesMin:["Κυ","Δε","Τρ","Τε","Πε","Πα","Σα"],weekHeader:"Εβδ",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("el",{buttonText:{month:"Μήνας",week:"Εβδομάδα",day:"Ημέρα",list:"Ατζέντα"},allDayText:"Ολοήμερο",eventLimitText:"περισσότερα"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){function c(a,b,c,e){var f="";switch(c){case"s":return e?"muutaman sekunnin":"muutama sekunti";case"m":return e?"minuutin":"minuutti";case"mm":f=e?"minuutin":"minuuttia";break;case"h":return e?"tunnin":"tunti";case"hh":f=e?"tunnin":"tuntia";break;case"d":return e?"päivän":"päivä";case"dd":f=e?"päivän":"päivää";break;case"M":return e?"kuukauden":"kuukausi";case"MM":f=e?"kuukauden":"kuukautta";break;case"y":return e?"vuoden":"vuosi";case"yy":f=e?"vuoden":"vuotta"}return f=d(a,e)+" "+f}function d(a,b){return 10>a?b?f[a]:e[a]:a}var e="nolla yksi kaksi kolme neljä viisi kuusi seitsemän kahdeksan yhdeksän".split(" "),f=["nolla","yhden","kahden","kolmen","neljän","viiden","kuuden",e[7],e[8],e[9]];(b.defineLocale||b.lang).call(b,"fi",{months:"tammikuu_helmikuu_maaliskuu_huhtikuu_toukokuu_kesäkuu_heinäkuu_elokuu_syyskuu_lokakuu_marraskuu_joulukuu".split("_"),monthsShort:"tammi_helmi_maalis_huhti_touko_kesä_heinä_elo_syys_loka_marras_joulu".split("_"),weekdays:"sunnuntai_maanantai_tiistai_keskiviikko_torstai_perjantai_lauantai".split("_"),weekdaysShort:"su_ma_ti_ke_to_pe_la".split("_"),weekdaysMin:"su_ma_ti_ke_to_pe_la".split("_"),longDateFormat:{LT:"HH.mm",LTS:"HH.mm.ss",L:"DD.MM.YYYY",LL:"Do MMMM[ta] YYYY",LLL:"Do MMMM[ta] YYYY, [klo] LT",LLLL:"dddd, Do MMMM[ta] YYYY, [klo] LT",l:"D.M.YYYY",ll:"Do MMM YYYY",lll:"Do MMM YYYY, [klo] LT",llll:"ddd, Do MMM YYYY, [klo] LT"},calendar:{sameDay:"[tänään] [klo] LT",nextDay:"[huomenna] [klo] LT",nextWeek:"dddd [klo] LT",lastDay:"[eilen] [klo] LT",lastWeek:"[viime] dddd[na] [klo] LT",sameElse:"L"},relativeTime:{future:"%s päästä",past:"%s sitten",s:c,m:c,mm:c,h:c,hh:c,d:c,dd:c,M:c,MM:c,y:c,yy:c},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("fi","fi",{closeText:"Sulje",prevText:"«Edellinen",nextText:"Seuraava»",currentText:"Tänään",monthNames:["Tammikuu","Helmikuu","Maaliskuu","Huhtikuu","Toukokuu","Kesäkuu","Heinäkuu","Elokuu","Syyskuu","Lokakuu","Marraskuu","Joulukuu"],monthNamesShort:["Tammi","Helmi","Maalis","Huhti","Touko","Kesä","Heinä","Elo","Syys","Loka","Marras","Joulu"],dayNamesShort:["Su","Ma","Ti","Ke","To","Pe","La"],dayNames:["Sunnuntai","Maanantai","Tiistai","Keskiviikko","Torstai","Perjantai","Lauantai"],dayNamesMin:["Su","Ma","Ti","Ke","To","Pe","La"],weekHeader:"Vk",dateFormat:"d.m.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("fi",{buttonText:{month:"Kuukausi",week:"Viikko",day:"Päivä",list:"Tapahtumat"},allDayText:"Koko päivä",eventLimitText:"lisää"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"fr",{months:"janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre".split("_"),monthsShort:"janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.".split("_"),weekdays:"dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi".split("_"),weekdaysShort:"dim._lun._mar._mer._jeu._ven._sam.".split("_"),weekdaysMin:"Di_Lu_Ma_Me_Je_Ve_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[Aujourd'hui à] LT",nextDay:"[Demain à] LT",nextWeek:"dddd [à] LT",lastDay:"[Hier à] LT",lastWeek:"dddd [dernier à] LT",sameElse:"L"},relativeTime:{future:"dans %s",past:"il y a %s",s:"quelques secondes",m:"une minute",mm:"%d minutes",h:"une heure",hh:"%d heures",d:"un jour",dd:"%d jours",M:"un mois",MM:"%d mois",y:"un an",yy:"%d ans"},ordinalParse:/\d{1,2}(er|)/,ordinal:function(a){return a+(1===a?"er":"")},week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("fr","fr",{closeText:"Fermer",prevText:"Précédent",nextText:"Suivant",currentText:"Aujourd'hui",monthNames:["janvier","février","mars","avril","mai","juin","juillet","août","septembre","octobre","novembre","décembre"],monthNamesShort:["janv.","févr.","mars","avr.","mai","juin","juil.","août","sept.","oct.","nov.","déc."],dayNames:["dimanche","lundi","mardi","mercredi","jeudi","vendredi","samedi"],dayNamesShort:["dim.","lun.","mar.","mer.","jeu.","ven.","sam."],dayNamesMin:["D","L","M","M","J","V","S"],weekHeader:"Sem.",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("fr",{buttonText:{month:"Mois",week:"Semaine",day:"Jour",list:"Mon planning"},allDayHtml:"Toute la<br/>journée",eventLimitText:"en plus"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){function c(a,b,c,d){var e=a;switch(c){case"s":return d||b?"néhány másodperc":"néhány másodperce";case"m":return"egy"+(d||b?" perc":" perce");case"mm":return e+(d||b?" perc":" perce");case"h":return"egy"+(d||b?" óra":" órája");case"hh":return e+(d||b?" óra":" órája");case"d":return"egy"+(d||b?" nap":" napja");case"dd":return e+(d||b?" nap":" napja");case"M":return"egy"+(d||b?" hónap":" hónapja");case"MM":return e+(d||b?" hónap":" hónapja");case"y":return"egy"+(d||b?" év":" éve");case"yy":return e+(d||b?" év":" éve")}return""}function d(a){return(a?"":"[múlt] ")+"["+e[this.day()]+"] LT[-kor]"}var e="vasárnap hétfőn kedden szerdán csütörtökön pénteken szombaton".split(" ");(b.defineLocale||b.lang).call(b,"hu",{months:"január_február_március_április_május_június_július_augusztus_szeptember_október_november_december".split("_"),monthsShort:"jan_feb_márc_ápr_máj_jún_júl_aug_szept_okt_nov_dec".split("_"),weekdays:"vasárnap_hétfő_kedd_szerda_csütörtök_péntek_szombat".split("_"),weekdaysShort:"vas_hét_kedd_sze_csüt_pén_szo".split("_"),weekdaysMin:"v_h_k_sze_cs_p_szo".split("_"),longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"YYYY.MM.DD.",LL:"YYYY. MMMM D.",LLL:"YYYY. MMMM D., LT",LLLL:"YYYY. MMMM D., dddd LT"},meridiemParse:/de|du/i,isPM:function(a){return"u"===a.charAt(1).toLowerCase()},meridiem:function(a,b,c){return 12>a?c===!0?"de":"DE":c===!0?"du":"DU"},calendar:{sameDay:"[ma] LT[-kor]",nextDay:"[holnap] LT[-kor]",nextWeek:function(){return d.call(this,!0)},lastDay:"[tegnap] LT[-kor]",lastWeek:function(){return d.call(this,!1)},sameElse:"L"},relativeTime:{future:"%s múlva",past:"%s",s:c,m:c,mm:c,h:c,hh:c,d:c,dd:c,M:c,MM:c,y:c,yy:c},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}}),a.fullCalendar.datepickerLang("hu","hu",{closeText:"bezár",prevText:"vissza",nextText:"előre",currentText:"ma",monthNames:["Január","Február","Március","Április","Május","Június","Július","Augusztus","Szeptember","Október","November","December"],monthNamesShort:["Jan","Feb","Már","Ápr","Máj","Jún","Júl","Aug","Szep","Okt","Nov","Dec"],dayNames:["Vasárnap","Hétfő","Kedd","Szerda","Csütörtök","Péntek","Szombat"],dayNamesShort:["Vas","Hét","Ked","Sze","Csü","Pén","Szo"],dayNamesMin:["V","H","K","Sze","Cs","P","Szo"],weekHeader:"Hét",dateFormat:"yy.mm.dd.",firstDay:1,isRTL:!1,showMonthAfterYear:!0,yearSuffix:""}),a.fullCalendar.lang("hu",{buttonText:{month:"Hónap",week:"Hét",day:"Nap",list:"Napló"},allDayText:"Egész nap",eventLimitText:"további"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"id",{months:"Januari_Februari_Maret_April_Mei_Juni_Juli_Agustus_September_Oktober_November_Desember".split("_"),monthsShort:"Jan_Feb_Mar_Apr_Mei_Jun_Jul_Ags_Sep_Okt_Nov_Des".split("_"),weekdays:"Minggu_Senin_Selasa_Rabu_Kamis_Jumat_Sabtu".split("_"),weekdaysShort:"Min_Sen_Sel_Rab_Kam_Jum_Sab".split("_"),weekdaysMin:"Mg_Sn_Sl_Rb_Km_Jm_Sb".split("_"),longDateFormat:{LT:"HH.mm",LTS:"LT.ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY [pukul] LT",LLLL:"dddd, D MMMM YYYY [pukul] LT"},meridiemParse:/pagi|siang|sore|malam/,meridiemHour:function(a,b){return 12===a&&(a=0),"pagi"===b?a:"siang"===b?a>=11?a:a+12:"sore"===b||"malam"===b?a+12:void 0},meridiem:function(a,b,c){return 11>a?"pagi":15>a?"siang":19>a?"sore":"malam"},calendar:{sameDay:"[Hari ini pukul] LT",nextDay:"[Besok pukul] LT",nextWeek:"dddd [pukul] LT",lastDay:"[Kemarin pukul] LT",lastWeek:"dddd [lalu pukul] LT",sameElse:"L"},relativeTime:{future:"dalam %s",past:"%s yang lalu",s:"beberapa detik",m:"semenit",mm:"%d menit",h:"sejam",hh:"%d jam",d:"sehari",dd:"%d hari",M:"sebulan",MM:"%d bulan",y:"setahun",yy:"%d tahun"},week:{dow:1,doy:7}}),a.fullCalendar.datepickerLang("id","id",{closeText:"Tutup",prevText:"<mundur",nextText:"maju>",currentText:"hari ini",monthNames:["Januari","Februari","Maret","April","Mei","Juni","Juli","Agustus","September","Oktober","Nopember","Desember"],monthNamesShort:["Jan","Feb","Mar","Apr","Mei","Jun","Jul","Agus","Sep","Okt","Nop","Des"],dayNames:["Minggu","Senin","Selasa","Rabu","Kamis","Jumat","Sabtu"],dayNamesShort:["Min","Sen","Sel","Rab","kam","Jum","Sab"],dayNamesMin:["Mg","Sn","Sl","Rb","Km","jm","Sb"],weekHeader:"Mg",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("id",{buttonText:{month:"Bulan",week:"Minggu",day:"Hari",list:"Agenda"},allDayHtml:"Sehari<br/>penuh",eventLimitText:"lebih"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"it",{months:"gennaio_febbraio_marzo_aprile_maggio_giugno_luglio_agosto_settembre_ottobre_novembre_dicembre".split("_"),monthsShort:"gen_feb_mar_apr_mag_giu_lug_ago_set_ott_nov_dic".split("_"),weekdays:"Domenica_Lunedì_Martedì_Mercoledì_Giovedì_Venerdì_Sabato".split("_"),weekdaysShort:"Dom_Lun_Mar_Mer_Gio_Ven_Sab".split("_"),weekdaysMin:"D_L_Ma_Me_G_V_S".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[Oggi alle] LT",nextDay:"[Domani alle] LT",nextWeek:"dddd [alle] LT",lastDay:"[Ieri alle] LT",lastWeek:function(){switch(this.day()){case 0:return"[la scorsa] dddd [alle] LT";default:return"[lo scorso] dddd [alle] LT"}},sameElse:"L"},relativeTime:{future:function(a){return(/^[0-9].+$/.test(a)?"tra":"in")+" "+a},past:"%s fa",s:"alcuni secondi",m:"un minuto",mm:"%d minuti",h:"un'ora",hh:"%d ore",d:"un giorno",dd:"%d giorni",M:"un mese",MM:"%d mesi",y:"un anno",yy:"%d anni"},ordinalParse:/\d{1,2}º/,ordinal:"%dº",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("it","it",{closeText:"Chiudi",prevText:"<Prec",nextText:"Succ>",currentText:"Oggi",monthNames:["Gennaio","Febbraio","Marzo","Aprile","Maggio","Giugno","Luglio","Agosto","Settembre","Ottobre","Novembre","Dicembre"],monthNamesShort:["Gen","Feb","Mar","Apr","Mag","Giu","Lug","Ago","Set","Ott","Nov","Dic"],dayNames:["Domenica","Lunedì","Martedì","Mercoledì","Giovedì","Venerdì","Sabato"],dayNamesShort:["Dom","Lun","Mar","Mer","Gio","Ven","Sab"],dayNamesMin:["Do","Lu","Ma","Me","Gi","Ve","Sa"],weekHeader:"Sm",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("it",{buttonText:{month:"Mese",week:"Settimana",day:"Giorno",list:"Agenda"},allDayHtml:"Tutto il<br/>giorno",eventLimitText:function(a){return"+altri "+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"ja",{months:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),monthsShort:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),weekdays:"日曜日_月曜日_火曜日_水曜日_木曜日_金曜日_土曜日".split("_"),weekdaysShort:"日_月_火_水_木_金_土".split("_"),weekdaysMin:"日_月_火_水_木_金_土".split("_"),longDateFormat:{LT:"Ah時m分",LTS:"LTs秒",L:"YYYY/MM/DD",LL:"YYYY年M月D日",LLL:"YYYY年M月D日LT",LLLL:"YYYY年M月D日LT dddd"},meridiemParse:/午前|午後/i,isPM:function(a){return"午後"===a},meridiem:function(a,b,c){return 12>a?"午前":"午後"},calendar:{sameDay:"[今日] LT",nextDay:"[明日] LT",nextWeek:"[来週]dddd LT",lastDay:"[昨日] LT",lastWeek:"[前週]dddd LT",sameElse:"L"},relativeTime:{future:"%s後",past:"%s前",s:"数秒",m:"1分",mm:"%d分",h:"1時間",hh:"%d時間",d:"1日",dd:"%d日",M:"1ヶ月",MM:"%dヶ月",y:"1年",yy:"%d年"}}),a.fullCalendar.datepickerLang("ja","ja",{closeText:"閉じる",prevText:"<前",nextText:"次>",currentText:"今日",monthNames:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],monthNamesShort:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],dayNames:["日曜日","月曜日","火曜日","水曜日","木曜日","金曜日","土曜日"],dayNamesShort:["日","月","火","水","木","金","土"],dayNamesMin:["日","月","火","水","木","金","土"],weekHeader:"週",dateFormat:"yy/mm/dd",firstDay:0,isRTL:!1,showMonthAfterYear:!0,yearSuffix:"年"}),a.fullCalendar.lang("ja",{buttonText:{month:"月",week:"週",day:"日",list:"予定リスト"},allDayText:"終日",eventLimitText:function(a){return"他 "+a+" 件"}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){var c="jan._feb._mrt._apr._mei_jun._jul._aug._sep._okt._nov._dec.".split("_"),d="jan_feb_mrt_apr_mei_jun_jul_aug_sep_okt_nov_dec".split("_");(b.defineLocale||b.lang).call(b,"nl",{months:"januari_februari_maart_april_mei_juni_juli_augustus_september_oktober_november_december".split("_"),monthsShort:function(a,b){return/-MMM-/.test(b)?d[a.month()]:c[a.month()]},weekdays:"zondag_maandag_dinsdag_woensdag_donderdag_vrijdag_zaterdag".split("_"),weekdaysShort:"zo._ma._di._wo._do._vr._za.".split("_"),weekdaysMin:"Zo_Ma_Di_Wo_Do_Vr_Za".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD-MM-YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[vandaag om] LT",nextDay:"[morgen om] LT",nextWeek:"dddd [om] LT",lastDay:"[gisteren om] LT",lastWeek:"[afgelopen] dddd [om] LT",sameElse:"L"},relativeTime:{future:"over %s",past:"%s geleden",s:"een paar seconden",m:"één minuut",mm:"%d minuten",h:"één uur",hh:"%d uur",d:"één dag",dd:"%d dagen",M:"één maand",MM:"%d maanden",y:"één jaar",yy:"%d jaar"},ordinalParse:/\d{1,2}(ste|de)/,ordinal:function(a){return a+(1===a||8===a||a>=20?"ste":"de")},week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("nl","nl",{closeText:"Sluiten",prevText:"←",nextText:"→",currentText:"Vandaag",monthNames:["januari","februari","maart","april","mei","juni","juli","augustus","september","oktober","november","december"],monthNamesShort:["jan","feb","mrt","apr","mei","jun","jul","aug","sep","okt","nov","dec"],dayNames:["zondag","maandag","dinsdag","woensdag","donderdag","vrijdag","zaterdag"],dayNamesShort:["zon","maa","din","woe","don","vri","zat"],dayNamesMin:["zo","ma","di","wo","do","vr","za"],weekHeader:"Wk",dateFormat:"dd-mm-yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("nl",{buttonText:{month:"Maand",week:"Week",day:"Dag",list:"Agenda"},allDayText:"Hele dag",eventLimitText:"extra"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"nb",{months:"januar_februar_mars_april_mai_juni_juli_august_september_oktober_november_desember".split("_"),monthsShort:"jan_feb_mar_apr_mai_jun_jul_aug_sep_okt_nov_des".split("_"),weekdays:"søndag_mandag_tirsdag_onsdag_torsdag_fredag_lørdag".split("_"),weekdaysShort:"søn_man_tirs_ons_tors_fre_lør".split("_"),weekdaysMin:"sø_ma_ti_on_to_fr_lø".split("_"),longDateFormat:{LT:"H.mm",LTS:"LT.ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY [kl.] LT",LLLL:"dddd D. MMMM YYYY [kl.] LT"},calendar:{sameDay:"[i dag kl.] LT",nextDay:"[i morgen kl.] LT",nextWeek:"dddd [kl.] LT",lastDay:"[i går kl.] LT",lastWeek:"[forrige] dddd [kl.] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"for %s siden",s:"noen sekunder",m:"ett minutt",mm:"%d minutter",h:"en time",hh:"%d timer",d:"en dag",dd:"%d dager",M:"en måned",MM:"%d måneder",y:"ett år",yy:"%d år"},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("nb","nb",{closeText:"Lukk",prevText:"«Forrige",nextText:"Neste»",currentText:"I dag",monthNames:["januar","februar","mars","april","mai","juni","juli","august","september","oktober","november","desember"],monthNamesShort:["jan","feb","mar","apr","mai","jun","jul","aug","sep","okt","nov","des"],dayNamesShort:["søn","man","tir","ons","tor","fre","lør"],dayNames:["søndag","mandag","tirsdag","onsdag","torsdag","fredag","lørdag"],dayNamesMin:["sø","ma","ti","on","to","fr","lø"],weekHeader:"Uke",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("nb",{buttonText:{month:"Måned",week:"Uke",day:"Dag",list:"Agenda"},allDayText:"Hele dagen",eventLimitText:"til"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){function c(a){return 5>a%10&&a%10>1&&~~(a/10)%10!==1}function d(a,b,d){var e=a+" ";switch(d){case"m":return b?"minuta":"minutę";case"mm":return e+(c(a)?"minuty":"minut");case"h":return b?"godzina":"godzinę";case"hh":return e+(c(a)?"godziny":"godzin");case"MM":return e+(c(a)?"miesiące":"miesięcy");case"yy":return e+(c(a)?"lata":"lat")}}var e="styczeń_luty_marzec_kwiecień_maj_czerwiec_lipiec_sierpień_wrzesień_październik_listopad_grudzień".split("_"),f="stycznia_lutego_marca_kwietnia_maja_czerwca_lipca_sierpnia_września_października_listopada_grudnia".split("_");(b.defineLocale||b.lang).call(b,"pl",{months:function(a,b){return/D MMMM/.test(b)?f[a.month()]:e[a.month()]},monthsShort:"sty_lut_mar_kwi_maj_cze_lip_sie_wrz_paź_lis_gru".split("_"),weekdays:"niedziela_poniedziałek_wtorek_środa_czwartek_piątek_sobota".split("_"),weekdaysShort:"nie_pon_wt_śr_czw_pt_sb".split("_"),weekdaysMin:"N_Pn_Wt_Śr_Cz_Pt_So".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[Dziś o] LT",nextDay:"[Jutro o] LT",nextWeek:"[W] dddd [o] LT",lastDay:"[Wczoraj o] LT",lastWeek:function(){switch(this.day()){case 0:return"[W zeszłą niedzielę o] LT";case 3:return"[W zeszłą środę o] LT";case 6:return"[W zeszłą sobotę o] LT";default:return"[W zeszły] dddd [o] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"%s temu",s:"kilka sekund",m:d,mm:d,h:d,hh:d,d:"1 dzień",dd:"%d dni",M:"miesiąc",MM:d,y:"rok",yy:d},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("pl","pl",{closeText:"Zamknij",prevText:"<Poprzedni",nextText:"Następny>",currentText:"Dziś",monthNames:["Styczeń","Luty","Marzec","Kwiecień","Maj","Czerwiec","Lipiec","Sierpień","Wrzesień","Październik","Listopad","Grudzień"],monthNamesShort:["Sty","Lu","Mar","Kw","Maj","Cze","Lip","Sie","Wrz","Pa","Lis","Gru"],dayNames:["Niedziela","Poniedziałek","Wtorek","Środa","Czwartek","Piątek","Sobota"],dayNamesShort:["Nie","Pn","Wt","Śr","Czw","Pt","So"],dayNamesMin:["N","Pn","Wt","Śr","Cz","Pt","So"],weekHeader:"Tydz",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("pl",{buttonText:{month:"Miesiąc",week:"Tydzień",day:"Dzień",list:"Plan dnia"},allDayText:"Cały dzień",eventLimitText:"więcej"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"pt",{months:"janeiro_fevereiro_março_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro".split("_"),monthsShort:"jan_fev_mar_abr_mai_jun_jul_ago_set_out_nov_dez".split("_"),weekdays:"domingo_segunda-feira_terça-feira_quarta-feira_quinta-feira_sexta-feira_sábado".split("_"),weekdaysShort:"dom_seg_ter_qua_qui_sex_sáb".split("_"),weekdaysMin:"dom_2ª_3ª_4ª_5ª_6ª_sáb".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY LT",LLLL:"dddd, D [de] MMMM [de] YYYY LT"},calendar:{sameDay:"[Hoje às] LT",nextDay:"[Amanhã às] LT",nextWeek:"dddd [às] LT",lastDay:"[Ontem às] LT",lastWeek:function(){return 0===this.day()||6===this.day()?"[Último] dddd [às] LT":"[Última] dddd [às] LT"},sameElse:"L"},relativeTime:{future:"em %s",past:"há %s",s:"segundos",m:"um minuto",mm:"%d minutos",h:"uma hora",hh:"%d horas",d:"um dia",dd:"%d dias",M:"um mês",MM:"%d meses",y:"um ano",yy:"%d anos"},ordinalParse:/\d{1,2}º/,ordinal:"%dº",week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("pt","pt",{closeText:"Fechar",prevText:"Anterior",nextText:"Seguinte",currentText:"Hoje",monthNames:["Janeiro","Fevereiro","Março","Abril","Maio","Junho","Julho","Agosto","Setembro","Outubro","Novembro","Dezembro"],monthNamesShort:["Jan","Fev","Mar","Abr","Mai","Jun","Jul","Ago","Set","Out","Nov","Dez"],dayNames:["Domingo","Segunda-feira","Terça-feira","Quarta-feira","Quinta-feira","Sexta-feira","Sábado"],dayNamesShort:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],dayNamesMin:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],weekHeader:"Sem",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("pt",{buttonText:{month:"Mês",week:"Semana",day:"Dia",list:"Agenda"},allDayText:"Todo o dia",eventLimitText:"mais"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"pt-br",{months:"janeiro_fevereiro_março_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro".split("_"),monthsShort:"jan_fev_mar_abr_mai_jun_jul_ago_set_out_nov_dez".split("_"),weekdays:"domingo_segunda-feira_terça-feira_quarta-feira_quinta-feira_sexta-feira_sábado".split("_"),weekdaysShort:"dom_seg_ter_qua_qui_sex_sáb".split("_"),weekdaysMin:"dom_2ª_3ª_4ª_5ª_6ª_sáb".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD/MM/YYYY",LL:"D [de] MMMM [de] YYYY",LLL:"D [de] MMMM [de] YYYY [às] LT",LLLL:"dddd, D [de] MMMM [de] YYYY [às] LT"},calendar:{sameDay:"[Hoje às] LT",nextDay:"[Amanhã às] LT",nextWeek:"dddd [às] LT",lastDay:"[Ontem às] LT",lastWeek:function(){return 0===this.day()||6===this.day()?"[Último] dddd [às] LT":"[Última] dddd [às] LT"},sameElse:"L"},relativeTime:{future:"em %s",past:"%s atrás",s:"segundos",m:"um minuto",mm:"%d minutos",h:"uma hora",hh:"%d horas",d:"um dia",dd:"%d dias",M:"um mês",MM:"%d meses",y:"um ano",yy:"%d anos"},ordinalParse:/\d{1,2}º/,ordinal:"%dº"}),a.fullCalendar.datepickerLang("pt-br","pt-BR",{closeText:"Fechar",prevText:"<Anterior",nextText:"Próximo>",currentText:"Hoje",monthNames:["Janeiro","Fevereiro","Março","Abril","Maio","Junho","Julho","Agosto","Setembro","Outubro","Novembro","Dezembro"],monthNamesShort:["Jan","Fev","Mar","Abr","Mai","Jun","Jul","Ago","Set","Out","Nov","Dez"],dayNames:["Domingo","Segunda-feira","Terça-feira","Quarta-feira","Quinta-feira","Sexta-feira","Sábado"],dayNamesShort:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],dayNamesMin:["Dom","Seg","Ter","Qua","Qui","Sex","Sáb"],weekHeader:"Sm",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("pt-br",{buttonText:{month:"Mês",week:"Semana",day:"Dia",list:"Compromissos"},allDayText:"dia inteiro",eventLimitText:function(a){return"mais +"+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){function c(a,b){var c=a.split("_");return b%10===1&&b%100!==11?c[0]:b%10>=2&&4>=b%10&&(10>b%100||b%100>=20)?c[1]:c[2]}function d(a,b,d){var e={mm:b?"минута_минуты_минут":"минуту_минуты_минут",hh:"час_часа_часов",dd:"день_дня_дней",MM:"месяц_месяца_месяцев",yy:"год_года_лет"};return"m"===d?b?"минута":"минуту":a+" "+c(e[d],+a)}function e(a,b){var c={nominative:"январь_февраль_март_апрель_май_июнь_июль_август_сентябрь_октябрь_ноябрь_декабрь".split("_"),accusative:"января_февраля_марта_апреля_мая_июня_июля_августа_сентября_октября_ноября_декабря".split("_")},d=/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/.test(b)?"accusative":"nominative";return c[d][a.month()]}function f(a,b){var c={nominative:"янв_фев_март_апр_май_июнь_июль_авг_сен_окт_ноя_дек".split("_"),accusative:"янв_фев_мар_апр_мая_июня_июля_авг_сен_окт_ноя_дек".split("_")},d=/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/.test(b)?"accusative":"nominative";return c[d][a.month()]}function g(a,b){var c={nominative:"воскресенье_понедельник_вторник_среда_четверг_пятница_суббота".split("_"),accusative:"воскресенье_понедельник_вторник_среду_четверг_пятницу_субботу".split("_")},d=/\[ ?[Вв] ?(?:прошлую|следующую|эту)? ?\] ?dddd/.test(b)?"accusative":"nominative";return c[d][a.day()]}(b.defineLocale||b.lang).call(b,"ru",{months:e,monthsShort:f,weekdays:g,weekdaysShort:"вс_пн_вт_ср_чт_пт_сб".split("_"),weekdaysMin:"вс_пн_вт_ср_чт_пт_сб".split("_"),monthsParse:[/^янв/i,/^фев/i,/^мар/i,/^апр/i,/^ма[й|я]/i,/^июн/i,/^июл/i,/^авг/i,/^сен/i,/^окт/i,/^ноя/i,/^дек/i],longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY г.",LLL:"D MMMM YYYY г., LT",LLLL:"dddd, D MMMM YYYY г., LT"},calendar:{sameDay:"[Сегодня в] LT",nextDay:"[Завтра в] LT",lastDay:"[Вчера в] LT",nextWeek:function(){return 2===this.day()?"[Во] dddd [в] LT":"[В] dddd [в] LT"},lastWeek:function(a){if(a.week()===this.week())return 2===this.day()?"[Во] dddd [в] LT":"[В] dddd [в] LT";switch(this.day()){case 0:return"[В прошлое] dddd [в] LT";case 1:case 2:case 4:return"[В прошлый] dddd [в] LT";case 3:case 5:case 6:return"[В прошлую] dddd [в] LT"}},sameElse:"L"},relativeTime:{future:"через %s",past:"%s назад",s:"несколько секунд",m:d,mm:d,h:"час",hh:d,d:"день",dd:d,M:"месяц",MM:d,y:"год",yy:d},meridiemParse:/ночи|утра|дня|вечера/i,isPM:function(a){return/^(дня|вечера)$/.test(a)},meridiem:function(a,b,c){return 4>a?"ночи":12>a?"утра":17>a?"дня":"вечера"},ordinalParse:/\d{1,2}-(й|го|я)/,ordinal:function(a,b){switch(b){case"M":case"d":case"DDD":return a+"-й";case"D":return a+"-го";case"w":case"W":return a+"-я";default:return a}},week:{dow:1,doy:7}}),a.fullCalendar.datepickerLang("ru","ru",{closeText:"Закрыть",prevText:"<Пред",nextText:"След>",currentText:"Сегодня",monthNames:["Январь","Февраль","Март","Апрель","Май","Июнь","Июль","Август","Сентябрь","Октябрь","Ноябрь","Декабрь"],monthNamesShort:["Янв","Фев","Мар","Апр","Май","Июн","Июл","Авг","Сен","Окт","Ноя","Дек"],dayNames:["воскресенье","понедельник","вторник","среда","четверг","пятница","суббота"],dayNamesShort:["вск","пнд","втр","срд","чтв","птн","сбт"],dayNamesMin:["Вс","Пн","Вт","Ср","Чт","Пт","Сб"],weekHeader:"Нед",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("ru",{buttonText:{month:"Месяц",week:"Неделя",day:"День",list:"Повестка дня"},allDayText:"Весь день",eventLimitText:function(a){return"+ ещё "+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"sv",{months:"januari_februari_mars_april_maj_juni_juli_augusti_september_oktober_november_december".split("_"),monthsShort:"jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec".split("_"),weekdays:"söndag_måndag_tisdag_onsdag_torsdag_fredag_lördag".split("_"),weekdaysShort:"sön_mån_tis_ons_tor_fre_lör".split("_"),weekdaysMin:"sö_må_ti_on_to_fr_lö".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"YYYY-MM-DD",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd D MMMM YYYY LT"},calendar:{sameDay:"[Idag] LT",nextDay:"[Imorgon] LT",lastDay:"[Igår] LT",nextWeek:"dddd LT",lastWeek:"[Förra] dddd[en] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"för %s sedan",s:"några sekunder",m:"en minut",mm:"%d minuter",h:"en timme",hh:"%d timmar",d:"en dag",dd:"%d dagar",M:"en månad",MM:"%d månader",y:"ett år",yy:"%d år"},ordinalParse:/\d{1,2}(e|a)/,ordinal:function(a){var b=a%10,c=1===~~(a%100/10)?"e":1===b?"a":2===b?"a":"e";return a+c},week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("sv","sv",{closeText:"Stäng",prevText:"«Förra",nextText:"Nästa»",currentText:"Idag",monthNames:["Januari","Februari","Mars","April","Maj","Juni","Juli","Augusti","September","Oktober","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],dayNamesShort:["Sön","Mån","Tis","Ons","Tor","Fre","Lör"],dayNames:["Söndag","Måndag","Tisdag","Onsdag","Torsdag","Fredag","Lördag"],dayNamesMin:["Sö","Må","Ti","On","To","Fr","Lö"],weekHeader:"Ve",dateFormat:"yy-mm-dd",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("sv",{buttonText:{month:"Månad",week:"Vecka",day:"Dag",list:"Program"},allDayText:"Heldag",eventLimitText:"till"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){var c={words:{m:["jedan minut","jedne minute"],mm:["minut","minute","minuta"],h:["jedan sat","jednog sata"],hh:["sat","sata","sati"],dd:["dan","dana","dana"],MM:["mesec","meseca","meseci"],yy:["godina","godine","godina"]},correctGrammaticalCase:function(a,b){return 1===a?b[0]:a>=2&&4>=a?b[1]:b[2]},translate:function(a,b,d){var e=c.words[d];return 1===d.length?b?e[0]:e[1]:a+" "+c.correctGrammaticalCase(a,e)}};(b.defineLocale||b.lang).call(b,"sr",{months:["januar","februar","mart","april","maj","jun","jul","avgust","septembar","oktobar","novembar","decembar"],monthsShort:["jan.","feb.","mar.","apr.","maj","jun","jul","avg.","sep.","okt.","nov.","dec."],weekdays:["nedelja","ponedeljak","utorak","sreda","četvrtak","petak","subota"],weekdaysShort:["ned.","pon.","uto.","sre.","čet.","pet.","sub."],weekdaysMin:["ne","po","ut","sr","če","pe","su"],longDateFormat:{LT:"H:mm",LTS:"LT:ss",L:"DD. MM. YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY LT",LLLL:"dddd, D. MMMM YYYY LT"},calendar:{sameDay:"[danas u] LT",nextDay:"[sutra u] LT",nextWeek:function(){switch(this.day()){case 0:return"[u] [nedelju] [u] LT";case 3:return"[u] [sredu] [u] LT";case 6:return"[u] [subotu] [u] LT";case 1:case 2:case 4:case 5:return"[u] dddd [u] LT"}},lastDay:"[juče u] LT",lastWeek:function(){var a=["[prošle] [nedelje] [u] LT","[prošlog] [ponedeljka] [u] LT","[prošlog] [utorka] [u] LT","[prošle] [srede] [u] LT","[prošlog] [četvrtka] [u] LT","[prošlog] [petka] [u] LT","[prošle] [subote] [u] LT"];return a[this.day()]},sameElse:"L"},relativeTime:{future:"za %s",past:"pre %s",s:"nekoliko sekundi",m:c.translate,mm:c.translate,h:c.translate,hh:c.translate,d:"dan",dd:c.translate,M:"mesec",MM:c.translate,y:"godinu",yy:c.translate},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}}),a.fullCalendar.datepickerLang("sr","sr",{closeText:"Затвори",prevText:"<",nextText:">",currentText:"Данас",monthNames:["Јануар","Фебруар","Март","Април","Мај","Јун","Јул","Август","Септембар","Октобар","Новембар","Децембар"],monthNamesShort:["Јан","Феб","Мар","Апр","Мај","Јун","Јул","Авг","Сеп","Окт","Нов","Дец"],dayNames:["Недеља","Понедељак","Уторак","Среда","Четвртак","Петак","Субота"],dayNamesShort:["Нед","Пон","Уто","Сре","Чет","Пет","Суб"],dayNamesMin:["Не","По","Ут","Ср","Че","Пе","Су"],weekHeader:"Сед",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("sr",{buttonText:{month:"Месец",week:"Недеља",day:"Дан",list:"Планер"},allDayText:"Цео дан",eventLimitText:function(a){return"+ још "+a}})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"th",{months:"มกราคม_กุมภาพันธ์_มีนาคม_เมษายน_พฤษภาคม_มิถุนายน_กรกฎาคม_สิงหาคม_กันยายน_ตุลาคม_พฤศจิกายน_ธันวาคม".split("_"),monthsShort:"มกรา_กุมภา_มีนา_เมษา_พฤษภา_มิถุนา_กรกฎา_สิงหา_กันยา_ตุลา_พฤศจิกา_ธันวา".split("_"),weekdays:"อาทิตย์_จันทร์_อังคาร_พุธ_พฤหัสบดี_ศุกร์_เสาร์".split("_"),weekdaysShort:"อาทิตย์_จันทร์_อังคาร_พุธ_พฤหัส_ศุกร์_เสาร์".split("_"),weekdaysMin:"อา._จ._อ._พ._พฤ._ศ._ส.".split("_"),longDateFormat:{LT:"H นาฬิกา m นาที",LTS:"LT s วินาที",L:"YYYY/MM/DD",LL:"D MMMM YYYY",LLL:"D MMMM YYYY เวลา LT",LLLL:"วันddddที่ D MMMM YYYY เวลา LT"},meridiemParse:/ก่อนเที่ยง|หลังเที่ยง/,isPM:function(a){return"หลังเที่ยง"===a},meridiem:function(a,b,c){return 12>a?"ก่อนเที่ยง":"หลังเที่ยง"},calendar:{sameDay:"[วันนี้ เวลา] LT",nextDay:"[พรุ่งนี้ เวลา] LT",nextWeek:"dddd[หน้า เวลา] LT",lastDay:"[เมื่อวานนี้ เวลา] LT",lastWeek:"[วัน]dddd[ที่แล้ว เวลา] LT",sameElse:"L"},relativeTime:{future:"อีก %s",past:"%sที่แล้ว",s:"ไม่กี่วินาที",m:"1 นาที",mm:"%d นาที",h:"1 ชั่วโมง",hh:"%d ชั่วโมง",d:"1 วัน",dd:"%d วัน",M:"1 เดือน",MM:"%d เดือน",y:"1 ปี",yy:"%d ปี"}}),a.fullCalendar.datepickerLang("th","th",{closeText:"ปิด",prevText:"« ย้อน",nextText:"ถัดไป »",currentText:"วันนี้",monthNames:["มกราคม","กุมภาพันธ์","มีนาคม","เมษายน","พฤษภาคม","มิถุนายน","กรกฎาคม","สิงหาคม","กันยายน","ตุลาคม","พฤศจิกายน","ธันวาคม"],monthNamesShort:["ม.ค.","ก.พ.","มี.ค.","เม.ย.","พ.ค.","มิ.ย.","ก.ค.","ส.ค.","ก.ย.","ต.ค.","พ.ย.","ธ.ค."],dayNames:["อาทิตย์","จันทร์","อังคาร","พุธ","พฤหัสบดี","ศุกร์","เสาร์"],dayNamesShort:["อา.","จ.","อ.","พ.","พฤ.","ศ.","ส."],dayNamesMin:["อา.","จ.","อ.","พ.","พฤ.","ศ.","ส."],weekHeader:"Wk",dateFormat:"dd/mm/yy",firstDay:0,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("th",{buttonText:{month:"เดือน",week:"สัปดาห์",day:"วัน",list:"แผนงาน"},allDayText:"ตลอดวัน",eventLimitText:"เพิ่มเติม"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){var c={1:"'inci",5:"'inci",8:"'inci",70:"'inci",80:"'inci",2:"'nci",7:"'nci",20:"'nci",50:"'nci",3:"'üncü",4:"'üncü",100:"'üncü",6:"'ncı",9:"'uncu",10:"'uncu",30:"'uncu",60:"'ıncı",90:"'ıncı"};(b.defineLocale||b.lang).call(b,"tr",{months:"Ocak_Şubat_Mart_Nisan_Mayıs_Haziran_Temmuz_Ağustos_Eylül_Ekim_Kasım_Aralık".split("_"),monthsShort:"Oca_Şub_Mar_Nis_May_Haz_Tem_Ağu_Eyl_Eki_Kas_Ara".split("_"),weekdays:"Pazar_Pazartesi_Salı_Çarşamba_Perşembe_Cuma_Cumartesi".split("_"),weekdaysShort:"Paz_Pts_Sal_Çar_Per_Cum_Cts".split("_"),weekdaysMin:"Pz_Pt_Sa_Ça_Pe_Cu_Ct".split("_"),longDateFormat:{LT:"HH:mm",LTS:"LT:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY LT",LLLL:"dddd, D MMMM YYYY LT"},calendar:{sameDay:"[bugün saat] LT",nextDay:"[yarın saat] LT",nextWeek:"[haftaya] dddd [saat] LT",lastDay:"[dün] LT",lastWeek:"[geçen hafta] dddd [saat] LT",sameElse:"L"},relativeTime:{future:"%s sonra",past:"%s önce",s:"birkaç saniye",m:"bir dakika",mm:"%d dakika",h:"bir saat",hh:"%d saat",d:"bir gün",dd:"%d gün",M:"bir ay",MM:"%d ay",y:"bir yıl",yy:"%d yıl"},ordinalParse:/\d{1,2}'(inci|nci|üncü|ncı|uncu|ıncı)/,ordinal:function(a){if(0===a)return a+"'ıncı";var b=a%10,d=a%100-b,e=a>=100?100:null;return a+(c[b]||c[d]||c[e])},week:{dow:1,doy:7}}),a.fullCalendar.datepickerLang("tr","tr",{closeText:"kapat",prevText:"<geri",nextText:"ileri>",currentText:"bugün",monthNames:["Ocak","Şubat","Mart","Nisan","Mayıs","Haziran","Temmuz","Ağustos","Eylül","Ekim","Kasım","Aralık"],monthNamesShort:["Oca","Şub","Mar","Nis","May","Haz","Tem","Ağu","Eyl","Eki","Kas","Ara"],dayNames:["Pazar","Pazartesi","Salı","Çarşamba","Perşembe","Cuma","Cumartesi"],dayNamesShort:["Pz","Pt","Sa","Ça","Pe","Cu","Ct"],dayNamesMin:["Pz","Pt","Sa","Ça","Pe","Cu","Ct"],weekHeader:"Hf",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),a.fullCalendar.lang("tr",{buttonText:{next:"ileri",month:"Ay",week:"Hafta",day:"Gün",list:"Ajanda"},allDayText:"Tüm gün",eventLimitText:"daha fazla"})});!function(a){"function"==typeof define&&define.amd?define(["jquery","moment"],a):a(jQuery,moment)}(function(a,b){(b.defineLocale||b.lang).call(b,"zh-cn",{months:"一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月".split("_"),monthsShort:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),weekdays:"星期日_星期一_星期二_星期三_星期四_星期五_星期六".split("_"),weekdaysShort:"周日_周一_周二_周三_周四_周五_周六".split("_"),weekdaysMin:"日_一_二_三_四_五_六".split("_"),longDateFormat:{LT:"Ah点mm",LTS:"Ah点m分s秒",L:"YYYY-MM-DD",LL:"YYYY年MMMD日",LLL:"YYYY年MMMD日LT",LLLL:"YYYY年MMMD日ddddLT",l:"YYYY-MM-DD",ll:"YYYY年MMMD日",lll:"YYYY年MMMD日LT",llll:"YYYY年MMMD日ddddLT"},meridiemParse:/凌晨|早上|上午|中午|下午|晚上/,meridiemHour:function(a,b){return 12===a&&(a=0),"凌晨"===b||"早上"===b||"上午"===b?a:"下午"===b||"晚上"===b?a+12:a>=11?a:a+12},meridiem:function(a,b,c){var d=100*a+b;return 600>d?"凌晨":900>d?"早上":1130>d?"上午":1230>d?"中午":1800>d?"下午":"晚上"},calendar:{sameDay:function(){return 0===this.minutes()?"[今天]Ah[点整]":"[今天]LT"},nextDay:function(){return 0===this.minutes()?"[明天]Ah[点整]":"[明天]LT"},lastDay:function(){return 0===this.minutes()?"[昨天]Ah[点整]":"[昨天]LT"},nextWeek:function(){var a,c;return a=b().startOf("week"),c=this.unix()-a.unix()>=604800?"[下]":"[本]",0===this.minutes()?c+"dddAh点整":c+"dddAh点mm"},lastWeek:function(){var a,c;return a=b().startOf("week"),c=this.unix()<a.unix()?"[上]":"[本]",0===this.minutes()?c+"dddAh点整":c+"dddAh点mm"},sameElse:"LL"},ordinalParse:/\d{1,2}(日|月|周)/,ordinal:function(a,b){switch(b){case"d":case"D":case"DDD":return a+"日";case"M":return a+"月";case"w":case"W":return a+"周";default:return a}},relativeTime:{future:"%s内",past:"%s前",s:"几秒",m:"1分钟",mm:"%d分钟",h:"1小时",hh:"%d小时",d:"1天",dd:"%d天",M:"1个月",MM:"%d个月",y:"1年",yy:"%d年"},week:{dow:1,doy:4}}),a.fullCalendar.datepickerLang("zh-cn","zh-CN",{closeText:"关闭",prevText:"<上月",nextText:"下月>",currentText:"今天",monthNames:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],monthNamesShort:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],dayNames:["星期日","星期一","星期二","星期三","星期四","星期五","星期六"],dayNamesShort:["周日","周一","周二","周三","周四","周五","周六"],dayNamesMin:["日","一","二","三","四","五","六"],weekHeader:"周",dateFormat:"yy-mm-dd",firstDay:1,isRTL:!1,showMonthAfterYear:!0,yearSuffix:"年"}),a.fullCalendar.lang("zh-cn",{buttonText:{month:"月",week:"周",day:"日",list:"日程"},allDayText:"全天",eventLimitText:function(a){return"另外 "+a+" 个"}})});(function(){function t(y){this.app=y;this.router=new w();this.router.addRoute("screenshot-zone",d)}t.prototype.isOpen=function(){return $("#popover-container").size()>0};t.prototype.open=function(z){var y=this;y.app.dropdown.close();$.get(z,function(A){$("body").prepend('<div id="popover-container"><div id="popover-content">'+A+"</div></div>");y.app.refresh();y.router.dispatch(this.app);y.afterOpen()})};t.prototype.close=function(y){if(this.isOpen()){if(y){y.preventDefault()}$("#popover-container").remove()}};t.prototype.onClick=function(A){A.preventDefault();A.stopPropagation();var z=A.currentTarget||A.target;var y=z.getAttribute("href");if(!y){y=z.getAttribute("data-href")}if(y){this.open(y)}};t.prototype.listen=function(){$(document).on("click",".popover",this.onClick.bind(this));$(document).on("click",".close-popover",this.close.bind(this));$(document).on("click","#popover-container",this.close.bind(this));$(document).on("click","#popover-content",function(y){y.stopPropagation()})};t.prototype.afterOpen=function(){var z=this;var y=$("#popover-content .popover-form");if(y){y.on("submit",function(A){A.preventDefault();$.ajax({type:"POST",url:y.attr("action"),data:y.serialize(),success:function(C,D,B){z.afterSubmit(C,B,z)}})})}$(document).on("click",".popover-link",function(A){A.preventDefault();$.ajax({type:"GET",url:$(this).attr("href"),success:function(C,D,B){z.afterSubmit(C,B,z)}})})};t.prototype.afterSubmit=function(A,z,y){var B=z.getResponseHeader("X-Ajax-Redirect");if(B){window.location=B==="self"?window.location.href.split("#")[0]:B}else{$("#popover-content").html(A);$("#popover-content input[autofocus]").focus();y.afterOpen()}};function r(){}r.prototype.listen=function(){var y=this;$(document).on("click",function(){y.close()});$(document).on("click",".dropdown-menu",function(C){C.preventDefault();C.stopImmediatePropagation();y.close();var A=$(this).next("ul");var D=$(this).offset();$("body").append(jQuery("<div>",{id:"dropdown"}));A.clone().appendTo("#dropdown");var E=$("#dropdown ul");E.addClass("dropdown-submenu-open");var B=E.outerHeight();var z=E.outerWidth();if(D.top+B-$(window).scrollTop()<$(window).height()||$(window).scrollTop()+D.top<B){E.css("top",D.top+$(this).height())}else{E.css("top",D.top-B-5)}if(D.left+z>$(window).width()){E.css("left",D.left-z+$(this).outerWidth())}else{E.css("left",D.left)}});$(document).on("click",".dropdown-submenu-open li",function(z){if($(z.target).is("li")){$(this).find("a:visible")[0].click()}});$("textarea[data-mention-search-url]").textcomplete([{match:/(^|\s)@(\w*)$/,search:function(A,B){var z=$("textarea[data-mention-search-url]").data("mention-search-url");$.getJSON(z,{q:A}).done(function(C){B(C)}).fail(function(){B([])})},replace:function(z){return"$1@"+z+" "},cache:true}],{className:"textarea-dropdown"})};r.prototype.close=function(){$("#dropdown").remove()};function q(y){this.app=y}q.prototype.listen=function(){var y=this;$(".tooltip").tooltip({track:false,show:false,hide:false,position:{my:"left-20 top",at:"center bottom+9",using:function(z,A){$(this).css(z);var B=A.target.left+A.target.width/2-A.element.left-20;$("<div>").addClass("tooltip-arrow").addClass(A.vertical).addClass(B<1?"align-left":"align-right").appendTo(this)}},content:function(){var B=this;var z=$(this).attr("data-href");if(!z){return'<div class="markdown">'+$(this).attr("title")+"</div>"}$.get(z,function A(E){var D=$(".ui-tooltip:visible");$(".ui-tooltip-content:visible").html(E);D.css({top:"",left:""});D.children(".tooltip-arrow").remove();var C=$(B).tooltip("option","position");C.of=$(B);D.position(C)});return'<i class="fa fa-spinner fa-spin"></i>'}}).on("mouseenter",function(){var z=this;$(this).tooltip("open");$(".ui-tooltip").on("mouseleave",function(){$(z).tooltip("close")})}).on("mouseleave focusout",function(z){z.stopImmediatePropagation();var A=this;setTimeout(function(){if(!$(".ui-tooltip:hover").length){$(A).tooltip("close")}},100)})};function l(){}l.prototype.showPreview=function(C){C.preventDefault();var z=$(".write-area");var B=$(".preview-area");var y=$("textarea");$("#markdown-write").parent().removeClass("form-tab-selected");$("#markdown-preview").parent().addClass("form-tab-selected");var A=$.ajax({url:$("body").data("markdown-preview-url"),contentType:"application/json",type:"POST",processData:false,dataType:"html",data:JSON.stringify({text:y.val()})});A.done(function(D){B.find(".markdown").html(D);B.css("height",y.css("height"));B.css("width",y.css("width"));z.hide();B.show()})};l.prototype.showWriter=function(y){y.preventDefault();$("#markdown-write").parent().addClass("form-tab-selected");$("#markdown-preview").parent().removeClass("form-tab-selected");$(".write-area").show();$(".preview-area").hide()};l.prototype.listen=function(){$(document).on("click","#markdown-preview",this.showPreview.bind(this));$(document).on("click","#markdown-write",this.showWriter.bind(this))};function f(y){this.app=y;this.keyboardShortcuts()}f.prototype.focus=function(){$(document).on("focus","#form-search",function(){if($("#form-search")[0].setSelectionRange){$("#form-search")[0].setSelectionRange($("#form-search").val().length,$("#form-search").val().length)}})};f.prototype.listen=function(){var y=this;$(document).on("click",".filter-helper",function(B){B.preventDefault();var A=$(this).data("filter");var z=$(this).data("append-filter");if(z){A=$("#form-search").val()+" "+z}$("#form-search").val(A);if($("#board").length){y.app.board.reloadFilters(A)}else{$("form.search").submit()}})};f.prototype.keyboardShortcuts=function(){var y=this;Mousetrap.bind("v b",function(A){var z=$(".view-board");if(z.length){window.location=z.attr("href")}});Mousetrap.bind("v c",function(A){var z=$(".view-calendar");if(z.length){window.location=z.attr("href")}});Mousetrap.bind("v l",function(A){var z=$(".view-listing");if(z.length){window.location=z.attr("href")}});Mousetrap.bind("v g",function(A){var z=$(".view-gantt");if(z.length){window.location=z.attr("href")}});Mousetrap.bind("f",function(A){A.preventDefault();var z=document.getElementById("form-search");if(z){z.focus()}});Mousetrap.bind("r",function(A){A.preventDefault();var z=$(".filter-reset").data("filter");$("#form-search").val(z);if($("#board").length){y.app.board.reloadFilters(z)}else{$("form.search").submit()}})};function m(){this.board=new k(this);this.markdown=new l();this.search=new f(this);this.swimlane=new g();this.dropdown=new r();this.tooltip=new q(this);this.popover=new t(this);this.task=new a();this.project=new n();this.subtask=new e();this.file=new v(this);this.keyboardShortcuts();this.chosen();this.poll();$(".alert-fade-out").delay(4000).fadeOut(800,function(){$(this).remove()});var y=false;$("select.task-reload-project-destination").change(function(){if(!y){$(".loading-icon").show();y=true;window.location=$(this).data("redirect").replace(/PROJECT_ID/g,$(this).val())}})}m.prototype.listen=function(){this.project.listen();this.popover.listen();this.markdown.listen();this.tooltip.listen();this.dropdown.listen();this.search.listen();this.task.listen();this.swimlane.listen();this.subtask.listen();this.file.listen();this.search.focus();this.autoComplete();this.datePicker();this.focus()};m.prototype.refresh=function(){$(document).off();this.listen()};m.prototype.focus=function(){$("[autofocus]").each(function(y,z){$(this).focus()});$(document).on("focus",".auto-select",function(){$(this).select()});$(document).on("mouseup",".auto-select",function(y){y.preventDefault()})};m.prototype.poll=function(){window.setInterval(this.checkSession,60000)};m.prototype.keyboardShortcuts=function(){var y=this;Mousetrap.bindGlobal("mod+enter",function(){$("form").submit()});Mousetrap.bind("b",function(z){z.preventDefault();$("#board-selector").trigger("chosen:open")});Mousetrap.bindGlobal("esc",function(){y.popover.close();y.dropdown.close()})};m.prototype.checkSession=function(){if(!$(".form-login").length){$.ajax({cache:false,url:$("body").data("status-url"),statusCode:{401:function(){window.location=$("body").data("login-url")}}})}};m.prototype.datePicker=function(){$.datepicker.setDefaults($.datepicker.regional[$("body").data("js-lang")]);$(".form-date").datepicker({showOtherMonths:true,selectOtherMonths:true,dateFormat:"yy-mm-dd",constrainInput:false});$(".form-datetime").datetimepicker({controlType:"select",oneLine:true,dateFormat:"yy-mm-dd",constrainInput:false})};m.prototype.autoComplete=function(){$(".autocomplete").each(function(){var z=$(this);var A=z.data("dst-field");var y=z.data("dst-extra-field");if($("#form-"+A).val()==""){z.parent().find("input[type=submit]").attr("disabled","disabled")}z.autocomplete({source:z.data("search-url"),minLength:1,select:function(B,C){$("input[name="+A+"]").val(C.item.id);if(y){$("input[name="+y+"]").val(C.item[y])}z.parent().find("input[type=submit]").removeAttr("disabled")}})})};m.prototype.chosen=function(){$(".chosen-select").each(function(){var y=$(this).data("search-threshold");if(y===undefined){y=10}$(this).chosen({width:"180px",no_results_text:$(this).data("notfound"),disable_search_threshold:y})});$(".select-auto-redirect").change(function(){var y=new RegExp($(this).data("redirect-regex"),"g");window.location=$(this).data("redirect-url").replace(y,$(this).val())})};m.prototype.showLoadingIcon=function(){$("body").append('<span id="app-loading-icon"> <i class="fa fa-spinner fa-spin"></i></span>')};m.prototype.hideLoadingIcon=function(){$("#app-loading-icon").remove()};m.prototype.isVisible=function(){var y="";if(typeof document.hidden!=="undefined"){y="visibilityState"}else{if(typeof document.mozHidden!=="undefined"){y="mozVisibilityState"}else{if(typeof document.msHidden!=="undefined"){y="msVisibilityState"}else{if(typeof document.webkitHidden!=="undefined"){y="webkitVisibilityState"}}}}if(y!=""){return document[y]=="visible"}return true};m.prototype.formatDuration=function(y){if(y>=86400){return Math.round(y/86400)+"d"}else{if(y>=3600){return Math.round(y/3600)+"h"}else{if(y>=60){return Math.round(y/60)+"m"}}}return y+"s"};function d(){this.pasteCatcher=null}d.prototype.execute=function(){this.initialize()};d.prototype.initialize=function(){this.destroy();if(!window.Clipboard){this.pasteCatcher=document.createElement("div");this.pasteCatcher.id="screenshot-pastezone";this.pasteCatcher.contentEditable="true";this.pasteCatcher.style.opacity=0;this.pasteCatcher.style.position="fixed";this.pasteCatcher.style.top=0;this.pasteCatcher.style.right=0;this.pasteCatcher.style.width=0;document.body.insertBefore(this.pasteCatcher,document.body.firstChild);this.pasteCatcher.focus();document.addEventListener("click",this.setFocus.bind(this));document.getElementById("screenshot-zone").addEventListener("click",this.setFocus.bind(this))}window.addEventListener("paste",this.pasteHandler.bind(this))};d.prototype.destroy=function(){if(this.pasteCatcher!=null){document.body.removeChild(this.pasteCatcher)}else{if(document.getElementById("screenshot-pastezone")){document.body.removeChild(document.getElementById("screenshot-pastezone"))}}document.removeEventListener("click",this.setFocus.bind(this));this.pasteCatcher=null};d.prototype.setFocus=function(){if(this.pasteCatcher!==null){this.pasteCatcher.focus()}};d.prototype.pasteHandler=function(D){if(D.clipboardData&&D.clipboardData.items){var B=D.clipboardData.items;if(B){for(var C=0;C<B.length;C++){if(B[C].type.indexOf("image")!==-1){var A=B[C].getAsFile();var y=new FileReader();var z=this;y.onload=function(E){z.createImage(E.target.result)};y.readAsDataURL(A)}}}}else{setTimeout(this.checkInput.bind(this),100)}};d.prototype.checkInput=function(){var y=this.pasteCatcher.childNodes[0];if(y){if(y.tagName==="IMG"){this.createImage(y.src)}}this.pasteCatcher.innerHTML=""};d.prototype.createImage=function(A){var z=new Image();z.src=A;z.onload=function(){var B=A.split("base64,");var C=B[1];$("input[name=screenshot]").val(C)};var y=document.getElementById("screenshot-zone");y.innerHTML="";y.className="screenshot-pasted";y.appendChild(z);this.destroy();this.initialize()};function v(y){this.app=y;this.files=[];this.currentFile=0}v.prototype.listen=function(){var y=document.getElementById("file-dropzone");var z=this;if(y){y.ondragover=y.ondragenter=function(A){A.stopPropagation();A.preventDefault()};y.ondrop=function(A){A.stopPropagation();A.preventDefault();z.files=A.dataTransfer.files;z.show();$("#file-error-max-size").hide()};$(document).on("click","#file-browser",function(A){A.preventDefault();$("#file-form-element").get(0).click()});$(document).on("click","#file-upload-button",function(A){A.preventDefault();z.currentFile=0;z.checkFiles()});$("#file-form-element").change(function(){z.files=document.getElementById("file-form-element").files;z.show();$("#file-error-max-size").hide()})}};v.prototype.show=function(){$("#file-list").remove();if(this.files.length>0){$("#file-upload-button").prop("disabled",false);$("#file-dropzone-inner").hide();var C=jQuery("<ul>",{id:"file-list"});for(var B=0;B<this.files.length;B++){var z=jQuery("<span>",{id:"file-percentage-"+B}).append("(0%)");var A=jQuery("<progress>",{id:"file-progress-"+B,value:0});var y=jQuery("<li>",{id:"file-label-"+B}).append(A).append(" ").append(this.files[B].name).append(" ").append(z);C.append(y)}$("#file-dropzone").append(C)}else{$("#file-dropzone-inner").show()}};v.prototype.checkFiles=function(){var y=parseInt($("#file-dropzone").data("max-size"));for(var z=0;z<this.files.length;z++){if(this.files[z].size>y){$("#file-error-max-size").show();$("#file-label-"+z).addClass("file-error");$("#file-upload-button").prop("disabled",true);return}}this.uploadFiles()};v.prototype.uploadFiles=function(){if(this.files.length>0){this.uploadFile(this.files[this.currentFile])}};v.prototype.uploadFile=function(B){var y=document.getElementById("file-dropzone");var z=y.dataset.url;var C=new XMLHttpRequest();var A=new FormData();C.upload.addEventListener("progress",this.updateProgress.bind(this));C.upload.addEventListener("load",this.transferComplete.bind(this));C.open("POST",z,true);A.append("files[]",B);C.send(A)};v.prototype.updateProgress=function(y){if(y.lengthComputable){$("#file-progress-"+this.currentFile).val(y.loaded/y.total);$("#file-percentage-"+this.currentFile).text("("+Math.floor((y.loaded/y.total)*100)+"%)")}};v.prototype.transferComplete=function(){this.currentFile++;if(this.currentFile<this.files.length){this.uploadFile(this.files[this.currentFile])}else{$("#file-upload-button").prop("disabled",true);$("#file-upload-button").parent().hide();$("#file-done").show()}};function j(){}j.prototype.execute=function(){var y=$("#calendar");y.fullCalendar({lang:$("body").data("js-lang"),editable:true,eventLimit:true,defaultView:"month",header:{left:"prev,next today",center:"title",right:"month,agendaWeek,agendaDay"},eventDrop:function(z){$.ajax({cache:false,url:y.data("save-url"),contentType:"application/json",type:"POST",processData:false,data:JSON.stringify({task_id:z.id,date_due:z.start.format()})})},viewRender:function(){var z=y.data("check-url");var B={start:y.fullCalendar("getView").start.format(),end:y.fullCalendar("getView").end.format()};for(var A in B){z+="&"+A+"="+B[A]}$.getJSON(z,function(C){y.fullCalendar("removeEvents");y.fullCalendar("addEventSource",C);y.fullCalendar("rerenderEvents")})}})};function k(y){this.app=y;this.checkInterval=null;this.savingInProgress=false}k.prototype.execute=function(){this.app.swimlane.refresh();this.restoreColumnViewMode();this.compactView();this.poll();this.keyboardShortcuts();this.listen();this.dragAndDrop();$(window).on("load",this.columnScrolling);$(window).resize(this.columnScrolling)};k.prototype.poll=function(){var y=parseInt($("#board").attr("data-check-interval"));if(y>0){this.checkInterval=window.setInterval(this.check.bind(this),y*1000)}};k.prototype.reloadFilters=function(y){this.app.showLoadingIcon();$.ajax({cache:false,url:$("#board").data("reload-url"),contentType:"application/json",type:"POST",processData:false,data:JSON.stringify({search:y}),success:this.refresh.bind(this),error:this.app.hideLoadingIcon.bind(this)})};k.prototype.check=function(){if(this.app.isVisible()&&!this.savingInProgress){var y=this;this.app.showLoadingIcon();$.ajax({cache:false,url:$("#board").data("check-url"),statusCode:{200:function(z){y.refresh(z)},304:function(){y.app.hideLoadingIcon()}}})}};k.prototype.save=function(B,C,y,A){var z=this;this.app.showLoadingIcon();this.savingInProgress=true;$.ajax({cache:false,url:$("#board").data("save-url"),contentType:"application/json",type:"POST",processData:false,data:JSON.stringify({task_id:B,column_id:C,swimlane_id:A,position:y}),success:function(D){z.refresh(D);this.savingInProgress=false},error:function(){z.app.hideLoadingIcon();this.savingInProgress=false}})};k.prototype.refresh=function(y){$("#board-container").replaceWith(y);this.app.refresh();this.app.swimlane.refresh();this.app.hideLoadingIcon();this.listen();this.dragAndDrop();this.compactView();this.restoreColumnViewMode();this.columnScrolling()};k.prototype.dragAndDrop=function(){var y=this;var z={forcePlaceholderSize:true,tolerance:"pointer",connectWith:".board-task-list",placeholder:"draggable-placeholder",items:".draggable-item",stop:function(B,I){var D=I.item;var H=D.attr("data-task-id");var J=D.attr("data-position");var G=D.attr("data-column-id");var F=D.attr("data-swimlane-id");var C=D.parent().attr("data-column-id");var A=D.parent().attr("data-swimlane-id");var E=D.index()+1;D.removeClass("draggable-item-selected");if(C!=G||A!=F||E!=J){y.changeTaskState(H);y.save(H,C,E,A)}},start:function(A,B){B.item.addClass("draggable-item-selected");B.placeholder.height(B.item.height())}};if($.support.touch){$(".task-board-sort-handle").css("display","inline");z.handle=".task-board-sort-handle"}$(".board-task-list").sortable(z)};k.prototype.changeTaskState=function(z){var y=$("div[data-task-id="+z+"]");y.addClass("task-board-saving-state");y.find(".task-board-saving-icon").show()};k.prototype.listen=function(){var y=this;$(document).on("click",".task-board",function(z){if(z.target.tagName!="A"){window.location=$(this).data("task-url")}});$(document).on("click",".filter-toggle-scrolling",function(z){z.preventDefault();y.toggleCompactView()});$(document).on("click",".filter-toggle-height",function(z){z.preventDefault();y.toggleColumnScrolling()});$(document).on("click",".board-toggle-column-view",function(){y.toggleColumnViewMode($(this).data("column-id"))})};k.prototype.toggleColumnScrolling=function(){var y=localStorage.getItem("column_scroll");if(y==undefined){y=1}localStorage.setItem("column_scroll",y==0?1:0);this.columnScrolling()};k.prototype.columnScrolling=function(){if(localStorage.getItem("column_scroll")==0){var y=80;$(".filter-max-height").show();$(".filter-min-height").hide();$(".board-rotation-wrapper").css("min-height","");$(".board-task-list").each(function(){var z=$(this).height();if(z>y){y=z}});$(".board-task-list").css("min-height",y);$(".board-task-list").css("height","")}else{$(".filter-max-height").hide();$(".filter-min-height").show();if($(".board-swimlane").length>1){$(".board-task-list").each(function(){if($(this).height()>500){$(this).css("height",500)}else{$(this).css("min-height",320);$(".board-rotation-wrapper").css("min-height",320)}})}else{var y=$(window).height()-170;$(".board-task-list").css("height",y);$(".board-rotation-wrapper").css("min-height",y)}}};k.prototype.toggleCompactView=function(){var y=localStorage.getItem("horizontal_scroll")||1;localStorage.setItem("horizontal_scroll",y==0?1:0);this.compactView()};k.prototype.compactView=function(){if(localStorage.getItem("horizontal_scroll")==0){$(".filter-wide").show();$(".filter-compact").hide();$("#board-container").addClass("board-container-compact");$("#board th:not(.board-column-header-collapsed)").addClass("board-column-compact")}else{$(".filter-wide").hide();$(".filter-compact").show();$("#board-container").removeClass("board-container-compact");$("#board th").removeClass("board-column-compact")}};k.prototype.toggleCollapsedMode=function(){var y=this;this.app.showLoadingIcon();$.ajax({cache:false,url:$('.filter-display-mode:not([style="display: none;"]) a').attr("href"),success:function(z){$(".filter-display-mode").toggle();y.refresh(z)}})};k.prototype.restoreColumnViewMode=function(){var y=this;$(".board-column-header").each(function(){var z=$(this).data("column-id");if(localStorage.getItem("hidden_column_"+z)){y.hideColumn(z)}})};k.prototype.toggleColumnViewMode=function(y){if(localStorage.getItem("hidden_column_"+y)){this.showColumn(y)}else{this.hideColumn(y)}};k.prototype.hideColumn=function(y){$(".board-column-"+y+" .board-column-expanded").hide();$(".board-column-"+y+" .board-column-collapsed").show();$(".board-column-header-"+y+" .board-column-expanded").hide();$(".board-column-header-"+y+" .board-column-collapsed").show();$(".board-column-header-"+y).each(function(){$(this).removeClass("board-column-compact");$(this).addClass("board-column-header-collapsed")});$(".board-column-"+y).each(function(){$(this).addClass("board-column-task-collapsed")});$(".board-column-"+y+" .board-rotation").each(function(){$(this).css("width",$(".board-column-"+y+"").height())});localStorage.setItem("hidden_column_"+y,1)};k.prototype.showColumn=function(y){$(".board-column-"+y+" .board-column-expanded").show();$(".board-column-"+y+" .board-column-collapsed").hide();$(".board-column-header-"+y+" .board-column-expanded").show();$(".board-column-header-"+y+" .board-column-collapsed").hide();$(".board-column-header-"+y).removeClass("board-column-header-collapsed");$(".board-column-"+y).removeClass("board-column-task-collapsed");if(localStorage.getItem("horizontal_scroll")==0){$(".board-column-header-"+y).addClass("board-column-compact")}localStorage.removeItem("hidden_column_"+y)};k.prototype.keyboardShortcuts=function(){var y=this;Mousetrap.bind("c",function(){y.toggleCompactView()});Mousetrap.bind("s",function(){y.toggleCollapsedMode()});Mousetrap.bind("n",function(){y.app.popover.open($("#board").data("task-creation-url"))})};function g(){}g.prototype.getStorageKey=function(){return"hidden_swimlanes_"+$("#board").data("project-id")};g.prototype.expand=function(z){var A=this.getAllCollapsed();var y=A.indexOf(z);if(y>-1){A.splice(y,1)}localStorage.setItem(this.getStorageKey(),JSON.stringify(A));$(".board-swimlane-columns-"+z).css("display","table-row");$(".board-swimlane-tasks-"+z).css("display","table-row");$(".hide-icon-swimlane-"+z).css("display","inline");$(".show-icon-swimlane-"+z).css("display","none")};g.prototype.collapse=function(y){var z=this.getAllCollapsed();if(z.indexOf(y)<0){z.push(y);localStorage.setItem(this.getStorageKey(),JSON.stringify(z))}$(".board-swimlane-columns-"+y+":not(:first-child)").css("display","none");$(".board-swimlane-tasks-"+y).css("display","none");$(".hide-icon-swimlane-"+y).css("display","none");$(".show-icon-swimlane-"+y).css("display","inline")};g.prototype.isCollapsed=function(y){return this.getAllCollapsed().indexOf(y)>-1};g.prototype.getAllCollapsed=function(){return JSON.parse(localStorage.getItem(this.getStorageKey()))||[]};g.prototype.refresh=function(){var z=this.getAllCollapsed();for(var y=0;y<z.length;y++){this.collapse(z[y])}};g.prototype.listen=function(){var y=this;$(document).on("click",".board-swimlane-toggle",function(A){A.preventDefault();var z=$(this).data("swimlane-id");if(y.isCollapsed(z)){y.expand(z)}else{y.collapse(z)}})};function b(y){this.app=y;this.data=[];this.options={container:"#gantt-chart",showWeekends:true,allowMoves:true,allowResizes:true,cellWidth:21,cellHeight:31,slideWidth:1000,vHeaderWidth:200}}b.prototype.saveRecord=function(y){this.app.showLoadingIcon();$.ajax({cache:false,url:$(this.options.container).data("save-url"),contentType:"application/json",type:"POST",processData:false,data:JSON.stringify(y),complete:this.app.hideLoadingIcon.bind(this)})};b.prototype.execute=function(){this.data=this.prepareData($(this.options.container).data("records"));var B=Math.floor((this.options.slideWidth/this.options.cellWidth)+5);var A=this.getDateRange(B);var y=A[0];var D=A[1];var z=$(this.options.container);var C=jQuery("<div>",{"class":"ganttview"});C.append(this.renderVerticalHeader());C.append(this.renderSlider(y,D));z.append(C);jQuery("div.ganttview-grid-row div.ganttview-grid-row-cell:last-child",z).addClass("last");jQuery("div.ganttview-hzheader-days div.ganttview-hzheader-day:last-child",z).addClass("last");jQuery("div.ganttview-hzheader-months div.ganttview-hzheader-month:last-child",z).addClass("last");if(!$(this.options.container).data("readonly")){this.listenForBlockResize(y);this.listenForBlockMove(y)}else{this.options.allowResizes=false;this.options.allowMoves=false}};b.prototype.renderVerticalHeader=function(){var C=jQuery("<div>",{"class":"ganttview-vtheader"});var z=jQuery("<div>",{"class":"ganttview-vtheader-item"});var B=jQuery("<div>",{"class":"ganttview-vtheader-series"});for(var y=0;y<this.data.length;y++){var A=jQuery("<span>").append(jQuery("<i>",{"class":"fa fa-info-circle tooltip",title:this.getVerticalHeaderTooltip(this.data[y])})).append(" ");if(this.data[y].type=="task"){A.append(jQuery("<a>",{href:this.data[y].link,target:"_blank",title:this.data[y].title}).append(this.data[y].title))}else{A.append(jQuery("<a>",{href:this.data[y].board_link,target:"_blank",title:$(this.options.container).data("label-board-link")}).append('<i class="fa fa-th"></i>')).append(" ").append(jQuery("<a>",{href:this.data[y].gantt_link,target:"_blank",title:$(this.options.container).data("label-gantt-link")}).append('<i class="fa fa-sliders"></i>')).append(" ").append(jQuery("<a>",{href:this.data[y].link,target:"_blank"}).append(this.data[y].title))}B.append(jQuery("<div>",{"class":"ganttview-vtheader-series-name"}).append(A))}z.append(B);C.append(z);return C};b.prototype.renderSlider=function(z,B){var y=jQuery("<div>",{"class":"ganttview-slide-container"});var A=this.getDates(z,B);y.append(this.renderHorizontalHeader(A));y.append(this.renderGrid(A));y.append(this.addBlockContainers());this.addBlocks(y,z);return y};b.prototype.renderHorizontalHeader=function(z){var F=jQuery("<div>",{"class":"ganttview-hzheader"});var D=jQuery("<div>",{"class":"ganttview-hzheader-months"});var C=jQuery("<div>",{"class":"ganttview-hzheader-days"});var B=0;for(var G in z){for(var A in z[G]){var H=z[G][A].length*this.options.cellWidth;B=B+H;D.append(jQuery("<div>",{"class":"ganttview-hzheader-month",css:{width:(H-1)+"px"}}).append($.datepicker.regional[$("body").data("js-lang")].monthNames[A]+" "+G));for(var E in z[G][A]){C.append(jQuery("<div>",{"class":"ganttview-hzheader-day"}).append(z[G][A][E].getDate()))}}}D.css("width",B+"px");C.css("width",B+"px");F.append(D).append(C);return F};b.prototype.renderGrid=function(z){var H=jQuery("<div>",{"class":"ganttview-grid"});var C=jQuery("<div>",{"class":"ganttview-grid-row"});for(var F in z){for(var A in z[F]){for(var E in z[F][A]){var B=jQuery("<div>",{"class":"ganttview-grid-row-cell"});if(this.options.showWeekends&&this.isWeekend(z[F][A][E])){B.addClass("ganttview-weekend")}C.append(B)}}}var G=jQuery("div.ganttview-grid-row-cell",C).length*this.options.cellWidth;C.css("width",G+"px");H.css("width",G+"px");for(var D=0;D<this.data.length;D++){H.append(C.clone())}return H};b.prototype.addBlockContainers=function(){var z=jQuery("<div>",{"class":"ganttview-blocks"});for(var y=0;y<this.data.length;y++){z.append(jQuery("<div>",{"class":"ganttview-block-container"}))}return z};b.prototype.addBlocks=function(z,y){var G=jQuery("div.ganttview-blocks div.ganttview-block-container",z);var A=0;for(var D=0;D<this.data.length;D++){var E=this.data[D];var H=this.daysBetween(E.start,E.end)+1;var C=this.daysBetween(y,E.start);var F=jQuery("<div>",{"class":"ganttview-block-text"});var B=jQuery("<div>",{"class":"ganttview-block tooltip"+(this.options.allowMoves?" ganttview-block-movable":""),title:this.getBarTooltip(E),css:{width:((H*this.options.cellWidth)-9)+"px","margin-left":(C*this.options.cellWidth)+"px"}}).append(F);if(H>=2){F.append(E.progress)}B.data("record",E);this.setBarColor(B,E);if(E.progress!="0%"){B.append(jQuery("<div>",{css:{"z-index":0,position:"absolute",top:0,bottom:0,"background-color":E.color.border,width:E.progress,opacity:0.4}}))}jQuery(G[A]).append(B);A=A+1}};b.prototype.getVerticalHeaderTooltip=function(z){var E="";if(z.type=="task"){E="<strong>"+z.column_title+"</strong> ("+z.progress+")<br/>"+z.title}else{var B=["managers","members"];for(var A in B){var C=B[A];if(!jQuery.isEmptyObject(z.users[C])){var D=jQuery("<ul>");for(var y in z.users[C]){D.append(jQuery("<li>").append(z.users[C][y]))}E+="<p><strong>"+$(this.options.container).data("label-"+C)+"</strong></p>"+D[0].outerHTML}}}return E};b.prototype.getBarTooltip=function(y){var z="";if(y.not_defined){z=$(this.options.container).data("label-not-defined")}else{if(y.type=="task"){z="<strong>"+y.progress+"</strong><br/>"+$(this.options.container).data("label-assignee")+" "+(y.assignee?y.assignee:"")+"<br/>"}z+=$(this.options.container).data("label-start-date")+" "+$.datepicker.formatDate("yy-mm-dd",y.start)+"<br/>";z+=$(this.options.container).data("label-end-date")+" "+$.datepicker.formatDate("yy-mm-dd",y.end)}return z};b.prototype.setBarColor=function(z,y){if(y.not_defined){z.addClass("ganttview-block-not-defined")}else{z.css("background-color",y.color.background);z.css("border-color",y.color.border)}};b.prototype.listenForBlockResize=function(y){var z=this;jQuery("div.ganttview-block",this.options.container).resizable({grid:this.options.cellWidth,handles:"e,w",delay:300,stop:function(){var A=jQuery(this);z.updateDataAndPosition(A,y);z.saveRecord(A.data("record"))}})};b.prototype.listenForBlockMove=function(y){var z=this;jQuery("div.ganttview-block",this.options.container).draggable({axis:"x",delay:300,grid:[this.options.cellWidth,this.options.cellWidth],stop:function(){var A=jQuery(this);z.updateDataAndPosition(A,y);z.saveRecord(A.data("record"))}})};b.prototype.updateDataAndPosition=function(D,B){var y=jQuery("div.ganttview-slide-container",this.options.container);var H=y.scrollLeft();var E=D.offset().left-y.offset().left-1+H;var G=D.data("record");G.not_defined=false;this.setBarColor(D,G);var A=Math.round(E/this.options.cellWidth);var F=this.addDays(this.cloneDate(B),A);G.start=F;var z=D.outerWidth();var C=Math.round(z/this.options.cellWidth)-1;G.end=this.addDays(this.cloneDate(F),C);if(G.type==="task"&&C>0){jQuery("div.ganttview-block-text",D).text(G.progress)}D.attr("title",this.getBarTooltip(G));D.data("record",G);D.css("top","").css("left","").css("position","relative").css("margin-left",E+"px")};b.prototype.getDates=function(C,y){var B=[];B[C.getFullYear()]=[];B[C.getFullYear()][C.getMonth()]=[C];var A=C;while(this.compareDate(A,y)==-1){var z=this.addDays(this.cloneDate(A),1);if(!B[z.getFullYear()]){B[z.getFullYear()]=[]}if(!B[z.getFullYear()][z.getMonth()]){B[z.getFullYear()][z.getMonth()]=[]}B[z.getFullYear()][z.getMonth()].push(z);A=z}return B};b.prototype.prepareData=function(A){for(var z=0;z<A.length;z++){var B=new Date(A[z].start[0],A[z].start[1]-1,A[z].start[2],0,0,0,0);A[z].start=B;var y=new Date(A[z].end[0],A[z].end[1]-1,A[z].end[2],0,0,0,0);A[z].end=y}return A};b.prototype.getDateRange=function(A){var D=new Date();var z=new Date();for(var B=0;B<this.data.length;B++){var C=new Date();C.setTime(Date.parse(this.data[B].start));var y=new Date();y.setTime(Date.parse(this.data[B].end));if(B==0){D=C;z=y}if(this.compareDate(D,C)==1){D=C}if(this.compareDate(z,y)==-1){z=y}}if(this.daysBetween(D,z)<A){z=this.addDays(this.cloneDate(D),A)}D.setDate(D.getDate()-1);return[D,z]};b.prototype.daysBetween=function(B,y){if(!B||!y){return 0}var A=0,z=this.cloneDate(B);while(this.compareDate(z,y)==-1){A=A+1;this.addDays(z,1)}return A};b.prototype.isWeekend=function(y){return y.getDay()%6==0};b.prototype.cloneDate=function(y){return new Date(y.getTime())};b.prototype.addDays=function(y,z){y.setDate(y.getDate()+z*1);return y};b.prototype.compareDate=function(z,y){if(isNaN(z)||isNaN(y)){throw new Error(z+" - "+y)}else{if(z instanceof Date&&y instanceof Date){return(z<y)?-1:(z>y)?1:0}else{throw new TypeError(z+" - "+y)}}};function a(){}a.prototype.listen=function(){$(document).on("click",".color-square",function(){$(".color-square-selected").removeClass("color-square-selected");$(this).addClass("color-square-selected");$("#form-color_id").val($(this).data("color-id"))});$(document).on("click",".assign-me",function(A){A.preventDefault();var y=$(this).data("current-id");var z="#"+$(this).data("target-id");if($(z+" option[value="+y+"]").length){$(z).val(y)}})};function n(){}n.prototype.listen=function(){$(".project-change-role").on("change",function(){$.ajax({cache:false,url:$(this).data("url"),contentType:"application/json",type:"POST",processData:false,data:JSON.stringify({id:$(this).data("id"),role:$(this).val()})})});$("#project-creation-form #form-src_project_id").on("change",function(){var y=$(this).val();if(y==0){$(".project-creation-options").hide()}else{$(".project-creation-options").show()}})};function e(){}e.prototype.listen=function(){$(document).on("click",".subtask-toggle-status",function(z){z.preventDefault();var y=$(this);$.ajax({cache:false,url:y.attr("href"),success:function(A){if(y.hasClass("subtask-refresh-table")){$(".subtasks-table").replaceWith(A)}else{y.replaceWith(A)}}})});$(document).on("click",".subtask-toggle-timer",function(z){z.preventDefault();var y=$(this);$.ajax({cache:false,url:y.attr("href"),success:function(A){$(".subtasks-table").replaceWith(A)}})})};function s(){}s.prototype.execute=function(){var A=$("#chart").data("metrics");var z=[];for(var y=0;y<A.length;y++){z.push([A[y].column_title,A[y].nb_tasks])}c3.generate({data:{columns:z,type:"donut"}})};function p(){}p.prototype.execute=function(){var A=$("#chart").data("metrics");var z=[];for(var y=0;y<A.length;y++){z.push([A[y].user,A[y].nb_tasks])}c3.generate({data:{columns:z,type:"donut"}})};function c(){}c.prototype.execute=function(){var E=$("#chart").data("metrics");var D=[];var y=[];var z=[];var B=d3.time.format("%Y-%m-%d");var F=d3.time.format($("#chart").data("date-format"));for(var C=0;C<E.length;C++){for(var A=0;A<E[C].length;A++){if(C==0){D.push([E[C][A]]);if(A>0){y.push(E[C][A])}}else{D[A].push(E[C][A]);if(A==0){z.push(F(B.parse(E[C][A])))}}}}c3.generate({data:{columns:D,type:"area-spline",groups:[y]},axis:{x:{type:"category",categories:z}}})};function o(){}o.prototype.execute=function(){var D=$("#chart").data("metrics");var C=[[$("#chart").data("label-total")]];var y=[];var A=d3.time.format("%Y-%m-%d");var E=d3.time.format($("#chart").data("date-format"));for(var B=0;B<D.length;B++){for(var z=0;z<D[B].length;z++){if(B==0){C.push([D[B][z]])}else{C[z+1].push(D[B][z]);if(z>0){if(C[0][B]==undefined){C[0].push(0)}C[0][B]+=D[B][z]}if(z==0){y.push(E(A.parse(D[B][z])))}}}}c3.generate({data:{columns:C},axis:{x:{type:"category",categories:y}}})};function h(y){this.app=y}h.prototype.execute=function(){var A=$("#chart").data("metrics");var B=[$("#chart").data("label")];var y=[];for(var z in A){B.push(A[z].average);y.push(A[z].title)}c3.generate({data:{columns:[B],type:"bar"},bar:{width:{ratio:0.5}},axis:{x:{type:"category",categories:y},y:{tick:{format:this.app.formatDuration}}},legend:{show:false}})};function x(y){this.app=y}x.prototype.execute=function(){var A=$("#chart").data("metrics");var B=[$("#chart").data("label")];var y=[];for(var z=0;z<A.length;z++){B.push(A[z].time_spent);y.push(A[z].title)}c3.generate({data:{columns:[B],type:"bar"},bar:{width:{ratio:0.5}},axis:{x:{type:"category",categories:y},y:{tick:{format:this.app.formatDuration}}},legend:{show:false}})};function u(y){this.app=y}u.prototype.execute=function(){var E=$("#chart").data("metrics");var D=[$("#chart").data("label-cycle")];var A=[$("#chart").data("label-lead")];var z=[];var C={};C[$("#chart").data("label-cycle")]="area";C[$("#chart").data("label-lead")]="area-spline";var y={};y[$("#chart").data("label-lead")]="#afb42b";y[$("#chart").data("label-cycle")]="#4e342e";for(var B=0;B<E.length;B++){D.push(parseInt(E[B].avg_cycle_time));A.push(parseInt(E[B].avg_lead_time));z.push(E[B].day)}c3.generate({data:{columns:[A,D],types:C,colors:y},axis:{x:{type:"category",categories:z},y:{tick:{format:this.app.formatDuration}}}})};function i(y){this.app=y}i.prototype.execute=function(){var D=$("#chart").data("metrics");var z=$("#chart").data("label-open");var y=$("#chart").data("label-closed");var E=[$("#chart").data("label-spent")];var C=[$("#chart").data("label-estimated")];var B=[];for(var A in D){E.push(parseFloat(D[A].time_spent));C.push(parseFloat(D[A].time_estimated));B.push(A=="open"?z:y)}c3.generate({data:{columns:[E,C],type:"bar"},bar:{width:{ratio:0.2}},axis:{x:{type:"category",categories:B}},legend:{show:true}})};function w(){this.routes={}}w.prototype.addRoute=function(z,y){this.routes[z]=y};w.prototype.dispatch=function(z){for(var A in this.routes){if(document.getElementById(A)){var y=Object.create(this.routes[A].prototype);this.routes[A].apply(y,[z]);y.execute();break}}};jQuery(document).ready(function(){var z=new m();var y=new w();y.addRoute("board",k);y.addRoute("calendar",j);y.addRoute("screenshot-zone",d);y.addRoute("analytic-task-repartition",s);y.addRoute("analytic-user-repartition",p);y.addRoute("analytic-cfd",c);y.addRoute("analytic-burndown",o);y.addRoute("analytic-avg-time-column",h);y.addRoute("analytic-task-time-column",x);y.addRoute("analytic-lead-cycle-time",u);y.addRoute("analytic-compare-hours",i);y.addRoute("gantt-chart",b);y.dispatch(z);z.listen()})})();
\ No newline at end of file diff --git a/assets/js/src/App.js b/assets/js/src/App.js index d44d9f81..33b3c6b1 100644 --- a/assets/js/src/App.js +++ b/assets/js/src/App.js @@ -9,6 +9,7 @@ function App() { this.task = new Task(); this.project = new Project(); this.subtask = new Subtask(); + this.file = new FileUpload(this); this.keyboardShortcuts(); this.chosen(); this.poll(); @@ -39,6 +40,7 @@ App.prototype.listen = function() { this.task.listen(); this.swimlane.listen(); this.subtask.listen(); + this.file.listen(); this.search.focus(); this.autoComplete(); this.datePicker(); diff --git a/assets/js/src/Dropdown.js b/assets/js/src/Dropdown.js index 146a3c17..61738da9 100644 --- a/assets/js/src/Dropdown.js +++ b/assets/js/src/Dropdown.js @@ -26,11 +26,11 @@ Dropdown.prototype.listen = function() { var submenuHeight = clone.outerHeight(); var submenuWidth = clone.outerWidth(); - if (offset.top + submenuHeight - $(window).scrollTop() > $(window).height()) { - clone.css('top', offset.top - submenuHeight - 5); + if (offset.top + submenuHeight - $(window).scrollTop() < $(window).height() || $(window).scrollTop() + offset.top < submenuHeight) { + clone.css('top', offset.top + $(this).height()); } else { - clone.css('top', offset.top + $(this).height()); + clone.css('top', offset.top - submenuHeight - 5); } if (offset.left + submenuWidth > $(window).width()) { diff --git a/assets/js/src/FileUpload.js b/assets/js/src/FileUpload.js new file mode 100644 index 00000000..a8816bcd --- /dev/null +++ b/assets/js/src/FileUpload.js @@ -0,0 +1,124 @@ +function FileUpload(app) { + this.app = app; + this.files = []; + this.currentFile = 0; +} + +FileUpload.prototype.listen = function() { + var dropzone = document.getElementById("file-dropzone"); + var self = this; + + if (dropzone) { + dropzone.ondragover = dropzone.ondragenter = function(e) { + e.stopPropagation(); + e.preventDefault(); + } + + dropzone.ondrop = function(e) { + e.stopPropagation(); + e.preventDefault(); + self.files = e.dataTransfer.files; + self.show(); + $("#file-error-max-size").hide(); + } + + $(document).on("click", "#file-browser", function(e) { + e.preventDefault(); + $("#file-form-element").get(0).click(); + }); + + $(document).on("click", "#file-upload-button", function(e) { + e.preventDefault(); + self.currentFile = 0; + self.checkFiles(); + }); + + $("#file-form-element").change(function() { + self.files = document.getElementById("file-form-element").files; + self.show(); + $("#file-error-max-size").hide(); + }); + } +}; + +FileUpload.prototype.show = function() { + $("#file-list").remove(); + + if (this.files.length > 0) { + $("#file-upload-button").prop("disabled", false); + $("#file-dropzone-inner").hide(); + + var ul = jQuery("<ul>", {"id": "file-list"}); + + for (var i = 0; i < this.files.length; i++) { + var percentage = jQuery("<span>", {"id": "file-percentage-" + i}).append("(0%)"); + var progress = jQuery("<progress>", {"id": "file-progress-" + i, "value": 0}); + var li = jQuery("<li>", {"id": "file-label-" + i}) + .append(progress) + .append(" ") + .append(this.files[i].name) + .append(" ") + .append(percentage); + + ul.append(li); + } + + $("#file-dropzone").append(ul); + } else { + $("#file-dropzone-inner").show(); + } +}; + +FileUpload.prototype.checkFiles = function() { + var max = parseInt($("#file-dropzone").data("max-size")); + + for (var i = 0; i < this.files.length; i++) { + if (this.files[i].size > max) { + $("#file-error-max-size").show(); + $("#file-label-" + i).addClass("file-error"); + $("#file-upload-button").prop("disabled", true); + return; + } + } + + this.uploadFiles(); +}; + +FileUpload.prototype.uploadFiles = function() { + if (this.files.length > 0) { + this.uploadFile(this.files[this.currentFile]); + } +}; + +FileUpload.prototype.uploadFile = function(file) { + var dropzone = document.getElementById("file-dropzone"); + var url = dropzone.dataset.url; + var xhr = new XMLHttpRequest(); + var fd = new FormData(); + + xhr.upload.addEventListener("progress", this.updateProgress.bind(this)); + xhr.upload.addEventListener("load", this.transferComplete.bind(this)); + + xhr.open("POST", url, true); + fd.append('files[]', file); + xhr.send(fd); +}; + +FileUpload.prototype.updateProgress = function(e) { + if (e.lengthComputable) { + $("#file-progress-" + this.currentFile).val(e.loaded / e.total); + $("#file-percentage-" + this.currentFile).text('(' + Math.floor((e.loaded / e.total) * 100) + '%)'); + } +}; + +FileUpload.prototype.transferComplete = function() { + this.currentFile++; + + if (this.currentFile < this.files.length) { + this.uploadFile(this.files[this.currentFile]); + } else { + $("#file-upload-button").prop("disabled", true); + $("#file-upload-button").parent().hide(); + $("#file-done").show(); + } +}; diff --git a/assets/js/src/Popover.js b/assets/js/src/Popover.js index a5ec647c..5614de85 100644 --- a/assets/js/src/Popover.js +++ b/assets/js/src/Popover.js @@ -35,10 +35,11 @@ Popover.prototype.onClick = function(e) { e.preventDefault(); e.stopPropagation(); - var link = e.target.getAttribute("href"); + var target = e.currentTarget || e.target; + var link = target.getAttribute("href"); if (! link) { - link = e.target.getAttribute("data-href"); + link = target.getAttribute("data-href"); } if (link) { diff --git a/config.default.php b/config.default.php index cc92daa3..52c0c143 100644 --- a/config.default.php +++ b/config.default.php @@ -166,6 +166,9 @@ define('ENABLE_URL_REWRITE', false); // Hide login form, useful if all your users use Google/Github/ReverseProxy authentication define('HIDE_LOGIN_FORM', false); +// Disabling logout (for external SSO authentication) +define('DISABLE_LOGOUT', false); + // Enable captcha after 3 authentication failure define('BRUTEFORCE_CAPTCHA', 3); diff --git a/doc/api-file-procedures.markdown b/doc/api-file-procedures.markdown index bd05ce6b..930be733 100644 --- a/doc/api-file-procedures.markdown +++ b/doc/api-file-procedures.markdown @@ -1,7 +1,7 @@ API File Procedures =================== -## createFile +## createTaskFile - Purpose: **Create and upload a new task attachment** - Parameters: @@ -18,7 +18,7 @@ Request example: ```json { "jsonrpc": "2.0", - "method": "createFile", + "method": "createTaskFile", "id": 94500810, "params": [ 1, @@ -39,7 +39,7 @@ Response example: } ``` -## getAllFiles +## getAllTaskFiles - Purpose: **Get all files attached to task** - Parameters: @@ -52,7 +52,7 @@ Request example: ```json { "jsonrpc": "2.0", - "method": "getAllFiles", + "method": "getAllTaskFiles", "id": 1880662820, "params": { "task_id": 1 @@ -83,7 +83,7 @@ Response example: } ``` -## getFile +## getTaskFile - Purpose: **Get file information** - Parameters: @@ -96,7 +96,7 @@ Request example: ```json { "jsonrpc": "2.0", - "method": "getFile", + "method": "getTaskFile", "id": 318676852, "params": [ "1" @@ -123,7 +123,7 @@ Response example: } ``` -## downloadFile +## downloadTaskFile - Purpose: **Download file contents (encoded in base64)** - Parameters: @@ -136,7 +136,7 @@ Request example: ```json { "jsonrpc": "2.0", - "method": "downloadFile", + "method": "downloadTaskFile", "id": 235943344, "params": [ "1" @@ -154,7 +154,7 @@ Response example: } ``` -## removeFile +## removeTaskFile - Purpose: **Remove file** - Parameters: @@ -167,7 +167,7 @@ Request example: ```json { "jsonrpc": "2.0", - "method": "removeFile", + "method": "removeTaskFile", "id": 447036524, "params": [ "1" @@ -185,7 +185,7 @@ Response example: } ``` -## removeAllFiles +## removeAllTaskFiles - Purpose: **Remove all files associated to a task** - Parameters: @@ -198,7 +198,7 @@ Request example: ```json { "jsonrpc": "2.0", - "method": "removeAllFiles", + "method": "removeAllTaskFiles", "id": 593312993, "params": { "task_id": 1 diff --git a/doc/api-user-procedures.markdown b/doc/api-user-procedures.markdown index 9b43e1e1..6c09355d 100644 --- a/doc/api-user-procedures.markdown +++ b/doc/api-user-procedures.markdown @@ -262,3 +262,96 @@ Response example: "result": true } ``` + +## disableUser + +- Purpose: **Disable a user** +- Parameters: + - **user_id** (integer, required) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "disableUser", + "id": 2094191872, + "params": { + "user_id": 1 + } +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 2094191872, + "result": true +} +``` + +## enableUser + +- Purpose: **Enable a user** +- Parameters: + - **user_id** (integer, required) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "enableUser", + "id": 2094191872, + "params": { + "user_id": 1 + } +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 2094191872, + "result": true +} +``` + +## isActiveUser + +- Purpose: **Check if a user is active** +- Parameters: + - **user_id** (integer, required) +- Result on success: **true** +- Result on failure: **false** + +Request example: + +```json +{ + "jsonrpc": "2.0", + "method": "isActiveUser", + "id": 2094191872, + "params": { + "user_id": 1 + } +} +``` + +Response example: + +```json +{ + "jsonrpc": "2.0", + "id": 2094191872, + "result": true +} +``` diff --git a/doc/config.markdown b/doc/config.markdown index 92ff2217..150cb6dc 100644 --- a/doc/config.markdown +++ b/doc/config.markdown @@ -267,6 +267,9 @@ define('API_AUTHENTICATION_HEADER', ''); // Hide login form, useful if all your users use Google/Github/ReverseProxy authentication define('HIDE_LOGIN_FORM', false); +// Disabling logout (for external SSO authentication) +define('DISABLE_LOGOUT', false); + // Override API token stored in the database, useful for automated tests define('API_AUTHENTICATION_TOKEN', 'My unique API Token'); ``` diff --git a/doc/index.markdown b/doc/index.markdown index fbdb9ff6..99ca01f6 100644 --- a/doc/index.markdown +++ b/doc/index.markdown @@ -105,7 +105,6 @@ Technical details - [Daily background job](cronjob.markdown) - [Config file](config.markdown) -- [Background tasks](cronjob.markdown) - [Email configuration](email-configuration.markdown) - [URL rewriting](nice-urls.markdown) diff --git a/tests/integration/Base.php b/tests/integration/Base.php index 6facd9ce..983d0ed9 100644 --- a/tests/integration/Base.php +++ b/tests/integration/Base.php @@ -35,7 +35,7 @@ abstract class Base extends PHPUnit_Framework_TestCase { $this->app = new JsonRPC\Client(API_URL); $this->app->authentication('jsonrpc', API_KEY); - $this->app->debug = true; + // $this->app->debug = true; $this->admin = new JsonRPC\Client(API_URL); $this->admin->authentication('admin', 'admin'); diff --git a/tests/integration/UserTest.php b/tests/integration/UserTest.php new file mode 100644 index 00000000..10da051c --- /dev/null +++ b/tests/integration/UserTest.php @@ -0,0 +1,18 @@ +<?php + +require_once __DIR__.'/Base.php'; + +class UserTest extends Base +{ + public function testDisableUser() + { + $this->assertEquals(2, $this->app->createUser(array('username' => 'someone', 'password' => 'test123'))); + $this->assertTrue($this->app->isActiveUser(2)); + + $this->assertTrue($this->app->disableUser(2)); + $this->assertFalse($this->app->isActiveUser(2)); + + $this->assertTrue($this->app->enableUser(2)); + $this->assertTrue($this->app->isActiveUser(2)); + } +} diff --git a/tests/units/Analytic/AverageTimeSpentColumnAnalyticTest.php b/tests/units/Analytic/AverageTimeSpentColumnAnalyticTest.php index 8ad6d1e7..4e01bfa9 100644 --- a/tests/units/Analytic/AverageTimeSpentColumnAnalyticTest.php +++ b/tests/units/Analytic/AverageTimeSpentColumnAnalyticTest.php @@ -28,34 +28,26 @@ class AverageTimeSpentColumnAnalyticTest extends Base $this->container['db']->table(Task::TABLE)->eq('id', 2)->update(array('date_completed' => $now + 1800)); $stats = $averageLeadCycleTimeAnalytic->build(1); - $expected = array( - 1 => array( - 'count' => 2, - 'time_spent' => 3600+1800, - 'average' => (int) ((3600+1800)/2), - 'title' => 'Backlog', - ), - 2 => array( - 'count' => 0, - 'time_spent' => 0, - 'average' => 0, - 'title' => 'Ready', - ), - 3 => array( - 'count' => 0, - 'time_spent' => 0, - 'average' => 0, - 'title' => 'Work in progress', - ), - 4 => array( - 'count' => 0, - 'time_spent' => 0, - 'average' => 0, - 'title' => 'Done', - ) - ); - $this->assertEquals($expected, $stats); + $this->assertEquals(2, $stats[1]['count']); + $this->assertEquals(3600+1800, $stats[1]['time_spent'], '', 3); + $this->assertEquals((int) ((3600+1800)/2), $stats[1]['average'], '', 3); + $this->assertEquals('Backlog', $stats[1]['title']); + + $this->assertEquals(0, $stats[2]['count']); + $this->assertEquals(0, $stats[2]['time_spent'], '', 3); + $this->assertEquals(0, $stats[2]['average'], '', 3); + $this->assertEquals('Ready', $stats[2]['title']); + + $this->assertEquals(0, $stats[3]['count']); + $this->assertEquals(0, $stats[3]['time_spent'], '', 3); + $this->assertEquals(0, $stats[3]['average'], '', 3); + $this->assertEquals('Work in progress', $stats[3]['title']); + + $this->assertEquals(0, $stats[4]['count']); + $this->assertEquals(0, $stats[4]['time_spent'], '', 3); + $this->assertEquals(0, $stats[4]['average'], '', 3); + $this->assertEquals('Done', $stats[4]['title']); } public function testAverageWithTransitions() @@ -85,33 +77,25 @@ class AverageTimeSpentColumnAnalyticTest extends Base } $stats = $averageLeadCycleTimeAnalytic->build(1); - $expected = array( - 1 => array( - 'count' => 2, - 'time_spent' => 3600+1800, - 'average' => (int) ((3600+1800)/2), - 'title' => 'Backlog', - ), - 2 => array( - 'count' => 0, - 'time_spent' => 0, - 'average' => 0, - 'title' => 'Ready', - ), - 3 => array( - 'count' => 2, - 'time_spent' => 1800, - 'average' => 900, - 'title' => 'Work in progress', - ), - 4 => array( - 'count' => 0, - 'time_spent' => 0, - 'average' => 0, - 'title' => 'Done', - ) - ); - $this->assertEquals($expected, $stats); + $this->assertEquals(2, $stats[1]['count']); + $this->assertEquals(3600+1800, $stats[1]['time_spent'], '', 3); + $this->assertEquals((int) ((3600+1800)/2), $stats[1]['average'], '', 3); + $this->assertEquals('Backlog', $stats[1]['title']); + + $this->assertEquals(0, $stats[2]['count']); + $this->assertEquals(0, $stats[2]['time_spent'], '', 3); + $this->assertEquals(0, $stats[2]['average'], '', 3); + $this->assertEquals('Ready', $stats[2]['title']); + + $this->assertEquals(2, $stats[3]['count']); + $this->assertEquals(1800, $stats[3]['time_spent'], '', 3); + $this->assertEquals(900, $stats[3]['average'], '', 3); + $this->assertEquals('Work in progress', $stats[3]['title']); + + $this->assertEquals(0, $stats[4]['count']); + $this->assertEquals(0, $stats[4]['time_spent'], '', 3); + $this->assertEquals(0, $stats[4]['average'], '', 3); + $this->assertEquals('Done', $stats[4]['title']); } } diff --git a/tests/units/Auth/DatabaseAuthTest.php b/tests/units/Auth/DatabaseAuthTest.php index a13b7fee..ac099a7e 100644 --- a/tests/units/Auth/DatabaseAuthTest.php +++ b/tests/units/Auth/DatabaseAuthTest.php @@ -3,6 +3,7 @@ require_once __DIR__.'/../Base.php'; use Kanboard\Auth\DatabaseAuth; +use Kanboard\Model\User; class DatabaseAuthTest extends Base { @@ -40,12 +41,21 @@ class DatabaseAuthTest extends Base public function testIsvalidSession() { + $userModel = new User($this->container); $provider = new DatabaseAuth($this->container); + $this->assertFalse($provider->isValidSession()); - $this->container['sessionStorage']->user = array('id' => 1); + $this->assertEquals(2, $userModel->create(array('username' => 'foobar'))); + + $this->container['sessionStorage']->user = array('id' => 2); $this->assertTrue($provider->isValidSession()); + $this->container['sessionStorage']->user = array('id' => 3); + $this->assertFalse($provider->isValidSession()); + + $this->assertTrue($userModel->disable(2)); + $this->container['sessionStorage']->user = array('id' => 2); $this->assertFalse($provider->isValidSession()); } diff --git a/tests/units/Base.php b/tests/units/Base.php index 22c6304f..eb9fc68b 100644 --- a/tests/units/Base.php +++ b/tests/units/Base.php @@ -67,6 +67,12 @@ abstract class Base extends PHPUnit_Framework_TestCase ->setMethods(array('getType', 'getSelectedTypes')) ->getMock(); + $this->container['objectStorage'] = $this + ->getMockBuilder('\Kanboard\Core\ObjectStorage\FileStorage') + ->setConstructorArgs(array($this->container)) + ->setMethods(array('put', 'moveFile', 'remove', 'moveUploadedFile')) + ->getMock(); + $this->container['sessionStorage'] = new SessionStorage; $this->container->register(new ActionProvider); diff --git a/tests/units/Model/FileTest.php b/tests/units/Model/FileTest.php deleted file mode 100644 index 29f6ee93..00000000 --- a/tests/units/Model/FileTest.php +++ /dev/null @@ -1,263 +0,0 @@ -<?php - -require_once __DIR__.'/../Base.php'; - -use Kanboard\Model\Task; -use Kanboard\Model\File; -use Kanboard\Model\TaskCreation; -use Kanboard\Model\Project; - -class FileTest extends Base -{ - public function setUp() - { - parent::setUp(); - - $this->container['objectStorage'] = $this - ->getMockBuilder('\Kanboard\Core\ObjectStorage\FileStorage') - ->setConstructorArgs(array($this->container)) - ->setMethods(array('put', 'moveFile', 'remove')) - ->getMock(); - } - - public function testCreation() - { - $p = new Project($this->container); - $f = new File($this->container); - $tc = new TaskCreation($this->container); - - $this->assertEquals(1, $p->create(array('name' => 'test'))); - $this->assertEquals(1, $tc->create(array('project_id' => 1, 'title' => 'test'))); - - $this->assertEquals(1, $f->create(1, 'test', '/tmp/foo', 10)); - - $file = $f->getById(1); - $this->assertNotEmpty($file); - $this->assertEquals('test', $file['name']); - $this->assertEquals('/tmp/foo', $file['path']); - $this->assertEquals(0, $file['is_image']); - $this->assertEquals(1, $file['task_id']); - $this->assertEquals(time(), $file['date'], '', 2); - $this->assertEquals(0, $file['user_id']); - $this->assertEquals(10, $file['size']); - - $this->assertEquals(2, $f->create(1, 'test2.png', '/tmp/foobar', 10)); - - $file = $f->getById(2); - $this->assertNotEmpty($file); - $this->assertEquals('test2.png', $file['name']); - $this->assertEquals('/tmp/foobar', $file['path']); - $this->assertEquals(1, $file['is_image']); - } - - public function testCreationFileNameTooLong() - { - $p = new Project($this->container); - $f = new File($this->container); - $tc = new TaskCreation($this->container); - - $this->assertEquals(1, $p->create(array('name' => 'test'))); - $this->assertEquals(1, $tc->create(array('project_id' => 1, 'title' => 'test'))); - - $this->assertNotFalse($f->create(1, 'test', '/tmp/foo', 10)); - $this->assertNotFalse($f->create(1, str_repeat('a', 1000), '/tmp/foo', 10)); - - $files = $f->getAll(1); - $this->assertNotEmpty($files); - $this->assertCount(2, $files); - - $this->assertEquals(str_repeat('a', 255), $files[0]['name']); - $this->assertEquals('test', $files[1]['name']); - } - - public function testIsImage() - { - $f = new File($this->container); - - $this->assertTrue($f->isImage('test.png')); - $this->assertTrue($f->isImage('test.jpeg')); - $this->assertTrue($f->isImage('test.gif')); - $this->assertTrue($f->isImage('test.jpg')); - $this->assertTrue($f->isImage('test.JPG')); - - $this->assertFalse($f->isImage('test.bmp')); - $this->assertFalse($f->isImage('test')); - $this->assertFalse($f->isImage('test.pdf')); - } - - public function testGeneratePath() - { - $f = new File($this->container); - - $this->assertStringStartsWith('12'.DIRECTORY_SEPARATOR.'34'.DIRECTORY_SEPARATOR, $f->generatePath(12, 34, 'test.png')); - $this->assertNotEquals($f->generatePath(12, 34, 'test1.png'), $f->generatePath(12, 34, 'test2.png')); - } - - public function testUploadScreenshot() - { - $p = new Project($this->container); - $tc = new TaskCreation($this->container); - - $this->assertEquals(1, $p->create(array('name' => 'test'))); - $this->assertEquals(1, $tc->create(array('project_id' => 1, 'title' => 'test'))); - - $data = base64_encode('image data'); - - $f = $this - ->getMockBuilder('\Kanboard\Model\File') - ->setConstructorArgs(array($this->container)) - ->setMethods(array('generateThumbnailFromData')) - ->getMock(); - - $this->container['objectStorage'] - ->expects($this->once()) - ->method('put') - ->with( - $this->stringContains('1'.DIRECTORY_SEPARATOR.'1'.DIRECTORY_SEPARATOR), - $this->equalTo(base64_decode($data)) - ) - ->will($this->returnValue(true)); - - $f->expects($this->once()) - ->method('generateThumbnailFromData'); - - $this->assertEquals(1, $f->uploadScreenshot(1, 1, $data)); - - $file = $f->getById(1); - $this->assertNotEmpty($file); - $this->assertStringStartsWith('Screenshot taken ', $file['name']); - $this->assertStringStartsWith('1'.DIRECTORY_SEPARATOR.'1'.DIRECTORY_SEPARATOR, $file['path']); - $this->assertEquals(1, $file['is_image']); - $this->assertEquals(1, $file['task_id']); - $this->assertEquals(time(), $file['date'], '', 2); - $this->assertEquals(0, $file['user_id']); - $this->assertEquals(10, $file['size']); - } - - public function testUploadFileContent() - { - $p = new Project($this->container); - $f = new File($this->container); - $tc = new TaskCreation($this->container); - - $this->assertEquals(1, $p->create(array('name' => 'test'))); - $this->assertEquals(1, $tc->create(array('project_id' => 1, 'title' => 'test'))); - - $data = base64_encode('file data'); - - $this->container['objectStorage'] - ->expects($this->once()) - ->method('put') - ->with( - $this->stringContains('1'.DIRECTORY_SEPARATOR.'1'.DIRECTORY_SEPARATOR), - $this->equalTo(base64_decode($data)) - ) - ->will($this->returnValue(true)); - - $this->assertEquals(1, $f->uploadContent(1, 1, 'my file.pdf', $data)); - - $file = $f->getById(1); - $this->assertNotEmpty($file); - $this->assertEquals('my file.pdf', $file['name']); - $this->assertStringStartsWith('1'.DIRECTORY_SEPARATOR.'1'.DIRECTORY_SEPARATOR, $file['path']); - $this->assertEquals(0, $file['is_image']); - $this->assertEquals(1, $file['task_id']); - $this->assertEquals(time(), $file['date'], '', 2); - $this->assertEquals(0, $file['user_id']); - $this->assertEquals(9, $file['size']); - } - - public function testGetAll() - { - $p = new Project($this->container); - $f = new File($this->container); - $tc = new TaskCreation($this->container); - - $this->assertEquals(1, $p->create(array('name' => 'test'))); - $this->assertEquals(1, $tc->create(array('project_id' => 1, 'title' => 'test'))); - - $this->assertEquals(1, $f->create(1, 'B.pdf', '/tmp/foo', 10)); - $this->assertEquals(2, $f->create(1, 'A.png', '/tmp/foo', 10)); - $this->assertEquals(3, $f->create(1, 'D.doc', '/tmp/foo', 10)); - $this->assertEquals(4, $f->create(1, 'C.JPG', '/tmp/foo', 10)); - - $files = $f->getAll(1); - $this->assertNotEmpty($files); - $this->assertCount(4, $files); - $this->assertEquals('A.png', $files[0]['name']); - $this->assertEquals('B.pdf', $files[1]['name']); - $this->assertEquals('C.JPG', $files[2]['name']); - $this->assertEquals('D.doc', $files[3]['name']); - - $files = $f->getAllImages(1); - $this->assertNotEmpty($files); - $this->assertCount(2, $files); - $this->assertEquals('A.png', $files[0]['name']); - $this->assertEquals('C.JPG', $files[1]['name']); - - $files = $f->getAllDocuments(1); - $this->assertNotEmpty($files); - $this->assertCount(2, $files); - $this->assertEquals('B.pdf', $files[0]['name']); - $this->assertEquals('D.doc', $files[1]['name']); - } - - public function testRemove() - { - $p = new Project($this->container); - $f = new File($this->container); - $tc = new TaskCreation($this->container); - - $this->assertEquals(1, $p->create(array('name' => 'test'))); - $this->assertEquals(1, $tc->create(array('project_id' => 1, 'title' => 'test'))); - - $this->assertEquals(1, $f->create(1, 'B.pdf', DIRECTORY_SEPARATOR.'tmp'.DIRECTORY_SEPARATOR.'foo1', 10)); - $this->assertEquals(2, $f->create(1, 'A.png', DIRECTORY_SEPARATOR.'tmp'.DIRECTORY_SEPARATOR.'foo2', 10)); - $this->assertEquals(3, $f->create(1, 'D.doc', DIRECTORY_SEPARATOR.'tmp'.DIRECTORY_SEPARATOR.'foo3', 10)); - - $this->container['objectStorage'] - ->expects($this->at(0)) - ->method('remove') - ->with( - $this->equalTo(DIRECTORY_SEPARATOR.'tmp'.DIRECTORY_SEPARATOR.'foo2') - ) - ->will($this->returnValue(true)); - - $this->container['objectStorage'] - ->expects($this->at(1)) - ->method('remove') - ->with( - $this->equalTo('thumbnails'.DIRECTORY_SEPARATOR.DIRECTORY_SEPARATOR.'tmp'.DIRECTORY_SEPARATOR.'foo2') - ) - ->will($this->returnValue(true)); - - $this->container['objectStorage'] - ->expects($this->at(2)) - ->method('remove') - ->with( - $this->equalTo(DIRECTORY_SEPARATOR.'tmp'.DIRECTORY_SEPARATOR.'foo1') - ) - ->will($this->returnValue(true)); - - $this->container['objectStorage'] - ->expects($this->at(3)) - ->method('remove') - ->with( - $this->equalTo(DIRECTORY_SEPARATOR.'tmp'.DIRECTORY_SEPARATOR.'foo3') - ) - ->will($this->returnValue(true)); - - $this->assertTrue($f->remove(2)); - - $files = $f->getAll(1); - $this->assertNotEmpty($files); - $this->assertCount(2, $files); - $this->assertEquals('B.pdf', $files[0]['name']); - $this->assertEquals('D.doc', $files[1]['name']); - - $this->assertTrue($f->removeAll(1)); - - $files = $f->getAll(1); - $this->assertEmpty($files); - } -} diff --git a/tests/units/Model/NotificationTest.php b/tests/units/Model/NotificationTest.php index 7f9977ce..03ee5867 100644 --- a/tests/units/Model/NotificationTest.php +++ b/tests/units/Model/NotificationTest.php @@ -7,7 +7,7 @@ use Kanboard\Model\TaskCreation; use Kanboard\Model\Subtask; use Kanboard\Model\Comment; use Kanboard\Model\User; -use Kanboard\Model\File; +use Kanboard\Model\TaskFile; use Kanboard\Model\Task; use Kanboard\Model\Project; use Kanboard\Model\Notification; @@ -23,7 +23,7 @@ class NotificationTest extends Base $tc = new TaskCreation($this->container); $s = new Subtask($this->container); $c = new Comment($this->container); - $f = new File($this->container); + $f = new TaskFile($this->container); $this->assertEquals(1, $p->create(array('name' => 'test'))); $this->assertEquals(1, $tc->create(array('title' => 'test', 'project_id' => 1))); diff --git a/tests/units/Model/ProjectActivityTest.php b/tests/units/Model/ProjectActivityTest.php index 10201aa8..04d3004d 100644 --- a/tests/units/Model/ProjectActivityTest.php +++ b/tests/units/Model/ProjectActivityTest.php @@ -9,7 +9,6 @@ use Kanboard\Model\ProjectActivity; use Kanboard\Model\Project; use Kanboard\Model\Subtask; use Kanboard\Model\Comment; -use Kanboard\Model\File; class ProjectActivityTest extends Base { diff --git a/tests/units/Model/ProjectFileTest.php b/tests/units/Model/ProjectFileTest.php new file mode 100644 index 00000000..d9b37fbe --- /dev/null +++ b/tests/units/Model/ProjectFileTest.php @@ -0,0 +1,311 @@ +<?php + +require_once __DIR__.'/../Base.php'; + +use Kanboard\Model\ProjectFile; +use Kanboard\Model\Project; + +class ProjectFileTest extends Base +{ + public function testCreation() + { + $projectModel = new Project($this->container); + $fileModel = new ProjectFile($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'test'))); + $this->assertEquals(1, $fileModel->create(1, 'test', '/tmp/foo', 10)); + + $file = $fileModel->getById(1); + $this->assertEquals('test', $file['name']); + $this->assertEquals('/tmp/foo', $file['path']); + $this->assertEquals(0, $file['is_image']); + $this->assertEquals(1, $file['project_id']); + $this->assertEquals(time(), $file['date'], '', 2); + $this->assertEquals(0, $file['user_id']); + $this->assertEquals(10, $file['size']); + + $this->assertEquals(2, $fileModel->create(1, 'test2.png', '/tmp/foobar', 10)); + + $file = $fileModel->getById(2); + $this->assertEquals('test2.png', $file['name']); + $this->assertEquals('/tmp/foobar', $file['path']); + $this->assertEquals(1, $file['is_image']); + } + + public function testCreationWithFileNameTooLong() + { + $projectModel = new Project($this->container); + $fileModel = new ProjectFile($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'test'))); + + $this->assertNotFalse($fileModel->create(1, 'test', '/tmp/foo', 10)); + $this->assertNotFalse($fileModel->create(1, str_repeat('a', 1000), '/tmp/foo', 10)); + + $files = $fileModel->getAll(1); + $this->assertNotEmpty($files); + $this->assertCount(2, $files); + + $this->assertEquals(str_repeat('a', 255), $files[0]['name']); + $this->assertEquals('test', $files[1]['name']); + } + + public function testCreationWithSessionOpen() + { + $this->container['sessionStorage']->user = array('id' => 1); + + $projectModel = new Project($this->container); + $fileModel = new ProjectFile($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'test'))); + $this->assertEquals(1, $fileModel->create(1, 'test', '/tmp/foo', 10)); + + $file = $fileModel->getById(1); + $this->assertEquals('test', $file['name']); + $this->assertEquals(1, $file['user_id']); + } + + public function testGetAll() + { + $projectModel = new Project($this->container); + $fileModel = new ProjectFile($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'test'))); + + $this->assertEquals(1, $fileModel->create(1, 'B.pdf', '/tmp/foo', 10)); + $this->assertEquals(2, $fileModel->create(1, 'A.png', '/tmp/foo', 10)); + $this->assertEquals(3, $fileModel->create(1, 'D.doc', '/tmp/foo', 10)); + $this->assertEquals(4, $fileModel->create(1, 'C.JPG', '/tmp/foo', 10)); + + $fileModeliles = $fileModel->getAll(1); + $this->assertNotEmpty($fileModeliles); + $this->assertCount(4, $fileModeliles); + $this->assertEquals('A.png', $fileModeliles[0]['name']); + $this->assertEquals('B.pdf', $fileModeliles[1]['name']); + $this->assertEquals('C.JPG', $fileModeliles[2]['name']); + $this->assertEquals('D.doc', $fileModeliles[3]['name']); + + $fileModeliles = $fileModel->getAllImages(1); + $this->assertNotEmpty($fileModeliles); + $this->assertCount(2, $fileModeliles); + $this->assertEquals('A.png', $fileModeliles[0]['name']); + $this->assertEquals('C.JPG', $fileModeliles[1]['name']); + + $fileModeliles = $fileModel->getAllDocuments(1); + $this->assertNotEmpty($fileModeliles); + $this->assertCount(2, $fileModeliles); + $this->assertEquals('B.pdf', $fileModeliles[0]['name']); + $this->assertEquals('D.doc', $fileModeliles[1]['name']); + } + + public function testGetThumbnailPath() + { + $fileModel = new ProjectFile($this->container); + $this->assertEquals('thumbnails'.DIRECTORY_SEPARATOR.'test', $fileModel->getThumbnailPath('test')); + } + + public function testGeneratePath() + { + $fileModel = new ProjectFile($this->container); + + $this->assertStringStartsWith('projects'.DIRECTORY_SEPARATOR.'34'.DIRECTORY_SEPARATOR, $fileModel->generatePath(34, 'test.png')); + $this->assertNotEquals($fileModel->generatePath(34, 'test1.png'), $fileModel->generatePath(34, 'test2.png')); + } + + public function testUploadFiles() + { + $fileModel = $this + ->getMockBuilder('\Kanboard\Model\ProjectFile') + ->setConstructorArgs(array($this->container)) + ->setMethods(array('generateThumbnailFromFile')) + ->getMock(); + + $projectModel = new Project($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'test'))); + + $files = array( + 'name' => array( + 'file1.png', + 'file2.doc', + ), + 'tmp_name' => array( + '/tmp/phpYzdqkD', + '/tmp/phpeEwEWG', + ), + 'error' => array( + UPLOAD_ERR_OK, + UPLOAD_ERR_OK, + ), + 'size' => array( + 123, + 456, + ), + ); + + $fileModel + ->expects($this->once()) + ->method('generateThumbnailFromFile'); + + $this->container['objectStorage'] + ->expects($this->at(0)) + ->method('moveUploadedFile') + ->with($this->equalTo('/tmp/phpYzdqkD'), $this->anything()); + + $this->container['objectStorage'] + ->expects($this->at(1)) + ->method('moveUploadedFile') + ->with($this->equalTo('/tmp/phpeEwEWG'), $this->anything()); + + $this->assertTrue($fileModel->uploadFiles(1, $files)); + + $files = $fileModel->getAll(1); + $this->assertCount(2, $files); + + $this->assertEquals(1, $files[0]['id']); + $this->assertEquals('file1.png', $files[0]['name']); + $this->assertEquals(1, $files[0]['is_image']); + $this->assertEquals(1, $files[0]['project_id']); + $this->assertEquals(0, $files[0]['user_id']); + $this->assertEquals(123, $files[0]['size']); + $this->assertEquals(time(), $files[0]['date'], '', 2); + + $this->assertEquals(2, $files[1]['id']); + $this->assertEquals('file2.doc', $files[1]['name']); + $this->assertEquals(0, $files[1]['is_image']); + $this->assertEquals(1, $files[1]['project_id']); + $this->assertEquals(0, $files[1]['user_id']); + $this->assertEquals(456, $files[1]['size']); + $this->assertEquals(time(), $files[1]['date'], '', 2); + } + + public function testUploadFilesWithEmptyFiles() + { + $fileModel = new ProjectFile($this->container); + $this->assertFalse($fileModel->uploadFiles(1, array())); + } + + public function testUploadFilesWithUploadError() + { + $files = array( + 'name' => array( + 'file1.png', + 'file2.doc', + ), + 'tmp_name' => array( + '', + '/tmp/phpeEwEWG', + ), + 'error' => array( + UPLOAD_ERR_CANT_WRITE, + UPLOAD_ERR_OK, + ), + 'size' => array( + 123, + 456, + ), + ); + + $fileModel = new ProjectFile($this->container); + $this->assertFalse($fileModel->uploadFiles(1, $files)); + } + + public function testUploadFilesWithObjectStorageError() + { + $files = array( + 'name' => array( + 'file1.csv', + 'file2.doc', + ), + 'tmp_name' => array( + '/tmp/phpYzdqkD', + '/tmp/phpeEwEWG', + ), + 'error' => array( + UPLOAD_ERR_OK, + UPLOAD_ERR_OK, + ), + 'size' => array( + 123, + 456, + ), + ); + + $this->container['objectStorage'] + ->expects($this->at(0)) + ->method('moveUploadedFile') + ->with($this->equalTo('/tmp/phpYzdqkD'), $this->anything()) + ->will($this->throwException(new \Kanboard\Core\ObjectStorage\ObjectStorageException('test'))); + + $fileModel = new ProjectFile($this->container); + $this->assertFalse($fileModel->uploadFiles(1, $files)); + } + + public function testUploadFileContent() + { + $fileModel = $this + ->getMockBuilder('\Kanboard\Model\ProjectFile') + ->setConstructorArgs(array($this->container)) + ->setMethods(array('generateThumbnailFromFile')) + ->getMock(); + + $projectModel = new Project($this->container); + $data = 'test'; + + $this->assertEquals(1, $projectModel->create(array('name' => 'test'))); + + $this->container['objectStorage'] + ->expects($this->once()) + ->method('put') + ->with($this->anything(), $this->equalTo($data)); + + $this->assertEquals(1, $fileModel->uploadContent(1, 'test.doc', base64_encode($data))); + + $files = $fileModel->getAll(1); + $this->assertCount(1, $files); + + $this->assertEquals(1, $files[0]['id']); + $this->assertEquals('test.doc', $files[0]['name']); + $this->assertEquals(0, $files[0]['is_image']); + $this->assertEquals(1, $files[0]['project_id']); + $this->assertEquals(0, $files[0]['user_id']); + $this->assertEquals(4, $files[0]['size']); + $this->assertEquals(time(), $files[0]['date'], '', 2); + } + + public function testUploadImageContent() + { + $fileModel = $this + ->getMockBuilder('\Kanboard\Model\ProjectFile') + ->setConstructorArgs(array($this->container)) + ->setMethods(array('generateThumbnailFromFile')) + ->getMock(); + + $projectModel = new Project($this->container); + $data = 'test'; + + $this->assertEquals(1, $projectModel->create(array('name' => 'test'))); + + $fileModel + ->expects($this->once()) + ->method('generateThumbnailFromFile'); + + $this->container['objectStorage'] + ->expects($this->once()) + ->method('put') + ->with($this->anything(), $this->equalTo($data)); + + $this->assertEquals(1, $fileModel->uploadContent(1, 'test.png', base64_encode($data))); + + $files = $fileModel->getAll(1); + $this->assertCount(1, $files); + + $this->assertEquals(1, $files[0]['id']); + $this->assertEquals('test.png', $files[0]['name']); + $this->assertEquals(1, $files[0]['is_image']); + $this->assertEquals(1, $files[0]['project_id']); + $this->assertEquals(0, $files[0]['user_id']); + $this->assertEquals(4, $files[0]['size']); + $this->assertEquals(time(), $files[0]['date'], '', 2); + } +} diff --git a/tests/units/Model/ProjectGroupRoleTest.php b/tests/units/Model/ProjectGroupRoleTest.php index 29a9536b..e38e812a 100644 --- a/tests/units/Model/ProjectGroupRoleTest.php +++ b/tests/units/Model/ProjectGroupRoleTest.php @@ -204,6 +204,44 @@ class ProjectGroupRoleTest extends Base $this->assertEquals('', $users[1]['name']); } + public function testGetAssignableUsersWithDisabledUsers() + { + $userModel = new User($this->container); + $projectModel = new Project($this->container); + $groupModel = new Group($this->container); + $groupMemberModel = new GroupMember($this->container); + $groupRoleModel = new ProjectGroupRole($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'Project 1'))); + $this->assertEquals(2, $projectModel->create(array('name' => 'Project 2'))); + + $this->assertEquals(2, $userModel->create(array('username' => 'user 1', 'name' => 'User #1'))); + $this->assertEquals(3, $userModel->create(array('username' => 'user 2', 'is_active' => 0))); + $this->assertEquals(4, $userModel->create(array('username' => 'user 3'))); + + $this->assertEquals(1, $groupModel->create('Group A')); + $this->assertEquals(2, $groupModel->create('Group B')); + $this->assertEquals(3, $groupModel->create('Group C')); + + $this->assertTrue($groupMemberModel->addUser(1, 4)); + $this->assertTrue($groupMemberModel->addUser(2, 3)); + $this->assertTrue($groupMemberModel->addUser(3, 2)); + + $this->assertTrue($groupRoleModel->addGroup(1, 1, Role::PROJECT_VIEWER)); + $this->assertTrue($groupRoleModel->addGroup(1, 2, Role::PROJECT_MEMBER)); + $this->assertTrue($groupRoleModel->addGroup(1, 3, Role::PROJECT_MANAGER)); + + $users = $groupRoleModel->getAssignableUsers(2); + $this->assertCount(0, $users); + + $users = $groupRoleModel->getAssignableUsers(1); + $this->assertCount(1, $users); + + $this->assertEquals(2, $users[0]['id']); + $this->assertEquals('user 1', $users[0]['username']); + $this->assertEquals('User #1', $users[0]['name']); + } + public function testGetProjectsByUser() { $userModel = new User($this->container); diff --git a/tests/units/Model/ProjectPermissionTest.php b/tests/units/Model/ProjectPermissionTest.php index 035a1246..10fcdcc2 100644 --- a/tests/units/Model/ProjectPermissionTest.php +++ b/tests/units/Model/ProjectPermissionTest.php @@ -192,6 +192,28 @@ class ProjectPermissionTest extends Base $this->assertFalse($projectPermission->isAssignable(2, 5)); } + public function testIsAssignableWhenUserIsDisabled() + { + $userModel = new User($this->container); + $projectModel = new Project($this->container); + $groupModel = new Group($this->container); + $groupRoleModel = new ProjectGroupRole($this->container); + $groupMemberModel = new GroupMember($this->container); + $userRoleModel = new ProjectUserRole($this->container); + $projectPermission = new ProjectPermission($this->container); + + $this->assertEquals(2, $userModel->create(array('username' => 'user 1'))); + $this->assertEquals(3, $userModel->create(array('username' => 'user 2', 'is_active' => 0))); + + $this->assertEquals(1, $projectModel->create(array('name' => 'Project 1'))); + + $this->assertTrue($userRoleModel->addUser(1, 2, Role::PROJECT_MEMBER)); + $this->assertTrue($userRoleModel->addUser(1, 3, Role::PROJECT_MEMBER)); + + $this->assertTrue($projectPermission->isAssignable(1, 2)); + $this->assertFalse($projectPermission->isAssignable(1, 3)); + } + public function testIsMember() { $userModel = new User($this->container); diff --git a/tests/units/Model/ProjectUserRoleTest.php b/tests/units/Model/ProjectUserRoleTest.php index c6b4eb7c..06cd1b70 100644 --- a/tests/units/Model/ProjectUserRoleTest.php +++ b/tests/units/Model/ProjectUserRoleTest.php @@ -8,6 +8,7 @@ use Kanboard\Model\Group; use Kanboard\Model\GroupMember; use Kanboard\Model\ProjectGroupRole; use Kanboard\Model\ProjectUserRole; +use Kanboard\Model\ProjectPermission; use Kanboard\Core\Security\Role; class ProjectUserRoleTest extends Base @@ -100,6 +101,36 @@ class ProjectUserRoleTest extends Base $this->assertEquals('', $userRoleModel->getUserRole(1, 2)); } + public function testGetAssignableUsersWithDisabledUsers() + { + $projectModel = new Project($this->container); + $userModel = new User($this->container); + $userRoleModel = new ProjectUserRole($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'Test'))); + $this->assertEquals(2, $userModel->create(array('username' => 'user1', 'name' => 'User1'))); + $this->assertEquals(3, $userModel->create(array('username' => 'user2', 'name' => 'User2'))); + + $this->assertTrue($userRoleModel->addUser(1, 1, Role::PROJECT_MEMBER)); + $this->assertTrue($userRoleModel->addUser(1, 2, Role::PROJECT_MEMBER)); + $this->assertTrue($userRoleModel->addUser(1, 3, Role::PROJECT_MEMBER)); + + $users = $userRoleModel->getAssignableUsers(1); + $this->assertCount(3, $users); + + $this->assertEquals('admin', $users[1]); + $this->assertEquals('User1', $users[2]); + $this->assertEquals('User2', $users[3]); + + $this->assertTrue($userModel->disable(2)); + + $users = $userRoleModel->getAssignableUsers(1); + $this->assertCount(2, $users); + + $this->assertEquals('admin', $users[1]); + $this->assertEquals('User2', $users[3]); + } + public function testGetAssignableUsersWithoutGroups() { $projectModel = new Project($this->container); @@ -219,6 +250,36 @@ class ProjectUserRoleTest extends Base $this->assertEquals('User4', $users[5]); } + public function testGetAssignableUsersWithDisabledUsersAndEverybodyAllowed() + { + $projectModel = new Project($this->container); + $projectPermission = new ProjectPermission($this->container); + $userModel = new User($this->container); + $userRoleModel = new ProjectUserRole($this->container); + + $this->assertEquals(2, $userModel->create(array('username' => 'user1', 'name' => 'User1'))); + $this->assertEquals(3, $userModel->create(array('username' => 'user2', 'name' => 'User2'))); + + $this->assertEquals(1, $projectModel->create(array('name' => 'Project 1', 'is_everybody_allowed' => 1))); + + $this->assertTrue($projectPermission->isEverybodyAllowed(1)); + + $users = $userRoleModel->getAssignableUsers(1); + $this->assertCount(3, $users); + + $this->assertEquals('admin', $users[1]); + $this->assertEquals('User1', $users[2]); + $this->assertEquals('User2', $users[3]); + + $this->assertTrue($userModel->disable(2)); + + $users = $userRoleModel->getAssignableUsers(1); + $this->assertCount(2, $users); + + $this->assertEquals('admin', $users[1]); + $this->assertEquals('User2', $users[3]); + } + public function testGetProjectsByUser() { $userModel = new User($this->container); diff --git a/tests/units/Model/TaskFileTest.php b/tests/units/Model/TaskFileTest.php new file mode 100644 index 00000000..753a1fb6 --- /dev/null +++ b/tests/units/Model/TaskFileTest.php @@ -0,0 +1,458 @@ +<?php + +require_once __DIR__.'/../Base.php'; + +use Kanboard\Model\Task; +use Kanboard\Model\TaskFile; +use Kanboard\Model\TaskCreation; +use Kanboard\Model\Project; + +class TaskFileTest extends Base +{ + public function testCreation() + { + $projectModel = new Project($this->container); + $fileModel = new TaskFile($this->container); + $taskCreationModel = new TaskCreation($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'test'))); + $this->assertEquals(1, $taskCreationModel->create(array('project_id' => 1, 'title' => 'test'))); + + $this->assertEquals(1, $fileModel->create(1, 'test', '/tmp/foo', 10)); + + $file = $fileModel->getById(1); + $this->assertEquals('test', $file['name']); + $this->assertEquals('/tmp/foo', $file['path']); + $this->assertEquals(0, $file['is_image']); + $this->assertEquals(1, $file['task_id']); + $this->assertEquals(time(), $file['date'], '', 2); + $this->assertEquals(0, $file['user_id']); + $this->assertEquals(10, $file['size']); + + $this->assertEquals(2, $fileModel->create(1, 'test2.png', '/tmp/foobar', 10)); + + $file = $fileModel->getById(2); + $this->assertEquals('test2.png', $file['name']); + $this->assertEquals('/tmp/foobar', $file['path']); + $this->assertEquals(1, $file['is_image']); + } + + public function testCreationWithFileNameTooLong() + { + $projectModel = new Project($this->container); + $fileModel = new TaskFile($this->container); + $taskCreationModel = new TaskCreation($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'test'))); + $this->assertEquals(1, $taskCreationModel->create(array('project_id' => 1, 'title' => 'test'))); + + $this->assertNotFalse($fileModel->create(1, 'test', '/tmp/foo', 10)); + $this->assertNotFalse($fileModel->create(1, str_repeat('a', 1000), '/tmp/foo', 10)); + + $files = $fileModel->getAll(1); + $this->assertNotEmpty($files); + $this->assertCount(2, $files); + + $this->assertEquals(str_repeat('a', 255), $files[0]['name']); + $this->assertEquals('test', $files[1]['name']); + } + + public function testCreationWithSessionOpen() + { + $this->container['sessionStorage']->user = array('id' => 1); + + $projectModel = new Project($this->container); + $fileModel = new TaskFile($this->container); + $taskCreationModel = new TaskCreation($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'test'))); + $this->assertEquals(1, $taskCreationModel->create(array('project_id' => 1, 'title' => 'test'))); + $this->assertEquals(1, $fileModel->create(1, 'test', '/tmp/foo', 10)); + + $file = $fileModel->getById(1); + $this->assertEquals('test', $file['name']); + $this->assertEquals(1, $file['user_id']); + } + + public function testGetAll() + { + $projectModel = new Project($this->container); + $fileModel = new TaskFile($this->container); + $taskCreationModel = new TaskCreation($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'test'))); + $this->assertEquals(1, $taskCreationModel->create(array('project_id' => 1, 'title' => 'test'))); + + $this->assertEquals(1, $fileModel->create(1, 'B.pdf', '/tmp/foo', 10)); + $this->assertEquals(2, $fileModel->create(1, 'A.png', '/tmp/foo', 10)); + $this->assertEquals(3, $fileModel->create(1, 'D.doc', '/tmp/foo', 10)); + $this->assertEquals(4, $fileModel->create(1, 'C.JPG', '/tmp/foo', 10)); + + $fileModeliles = $fileModel->getAll(1); + $this->assertNotEmpty($fileModeliles); + $this->assertCount(4, $fileModeliles); + $this->assertEquals('A.png', $fileModeliles[0]['name']); + $this->assertEquals('B.pdf', $fileModeliles[1]['name']); + $this->assertEquals('C.JPG', $fileModeliles[2]['name']); + $this->assertEquals('D.doc', $fileModeliles[3]['name']); + + $fileModeliles = $fileModel->getAllImages(1); + $this->assertNotEmpty($fileModeliles); + $this->assertCount(2, $fileModeliles); + $this->assertEquals('A.png', $fileModeliles[0]['name']); + $this->assertEquals('C.JPG', $fileModeliles[1]['name']); + + $fileModeliles = $fileModel->getAllDocuments(1); + $this->assertNotEmpty($fileModeliles); + $this->assertCount(2, $fileModeliles); + $this->assertEquals('B.pdf', $fileModeliles[0]['name']); + $this->assertEquals('D.doc', $fileModeliles[1]['name']); + } + + public function testIsImage() + { + $fileModel = new TaskFile($this->container); + + $this->assertTrue($fileModel->isImage('test.png')); + $this->assertTrue($fileModel->isImage('test.jpeg')); + $this->assertTrue($fileModel->isImage('test.gif')); + $this->assertTrue($fileModel->isImage('test.jpg')); + $this->assertTrue($fileModel->isImage('test.JPG')); + + $this->assertFalse($fileModel->isImage('test.bmp')); + $this->assertFalse($fileModel->isImage('test')); + $this->assertFalse($fileModel->isImage('test.pdf')); + } + + public function testGetMimeType() + { + $fileModel = new TaskFile($this->container); + + $this->assertEquals('image/jpeg', $fileModel->getImageMimeType('My File.JPG')); + $this->assertEquals('image/jpeg', $fileModel->getImageMimeType('My File.jpeg')); + $this->assertEquals('image/png', $fileModel->getImageMimeType('My File.PNG')); + $this->assertEquals('image/gif', $fileModel->getImageMimeType('My File.gif')); + $this->assertEquals('image/jpeg', $fileModel->getImageMimeType('My File.bmp')); + $this->assertEquals('image/jpeg', $fileModel->getImageMimeType('My File')); + } + + public function testGetThumbnailPath() + { + $fileModel = new TaskFile($this->container); + $this->assertEquals('thumbnails'.DIRECTORY_SEPARATOR.'test', $fileModel->getThumbnailPath('test')); + } + + public function testGeneratePath() + { + $fileModel = new TaskFile($this->container); + + $this->assertStringStartsWith('tasks'.DIRECTORY_SEPARATOR.'34'.DIRECTORY_SEPARATOR, $fileModel->generatePath(34, 'test.png')); + $this->assertNotEquals($fileModel->generatePath(34, 'test1.png'), $fileModel->generatePath(34, 'test2.png')); + } + + public function testUploadFiles() + { + $fileModel = $this + ->getMockBuilder('\Kanboard\Model\TaskFile') + ->setConstructorArgs(array($this->container)) + ->setMethods(array('generateThumbnailFromFile')) + ->getMock(); + + $projectModel = new Project($this->container); + $taskCreationModel = new TaskCreation($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'test'))); + $this->assertEquals(1, $taskCreationModel->create(array('project_id' => 1, 'title' => 'test'))); + + $files = array( + 'name' => array( + 'file1.png', + 'file2.doc', + ), + 'tmp_name' => array( + '/tmp/phpYzdqkD', + '/tmp/phpeEwEWG', + ), + 'error' => array( + UPLOAD_ERR_OK, + UPLOAD_ERR_OK, + ), + 'size' => array( + 123, + 456, + ), + ); + + $fileModel + ->expects($this->once()) + ->method('generateThumbnailFromFile'); + + $this->container['objectStorage'] + ->expects($this->at(0)) + ->method('moveUploadedFile') + ->with($this->equalTo('/tmp/phpYzdqkD'), $this->anything()); + + $this->container['objectStorage'] + ->expects($this->at(1)) + ->method('moveUploadedFile') + ->with($this->equalTo('/tmp/phpeEwEWG'), $this->anything()); + + $this->assertTrue($fileModel->uploadFiles(1, $files)); + + $files = $fileModel->getAll(1); + $this->assertCount(2, $files); + + $this->assertEquals(1, $files[0]['id']); + $this->assertEquals('file1.png', $files[0]['name']); + $this->assertEquals(1, $files[0]['is_image']); + $this->assertEquals(1, $files[0]['task_id']); + $this->assertEquals(0, $files[0]['user_id']); + $this->assertEquals(123, $files[0]['size']); + $this->assertEquals(time(), $files[0]['date'], '', 2); + + $this->assertEquals(2, $files[1]['id']); + $this->assertEquals('file2.doc', $files[1]['name']); + $this->assertEquals(0, $files[1]['is_image']); + $this->assertEquals(1, $files[1]['task_id']); + $this->assertEquals(0, $files[1]['user_id']); + $this->assertEquals(456, $files[1]['size']); + $this->assertEquals(time(), $files[1]['date'], '', 2); + } + + public function testUploadFilesWithEmptyFiles() + { + $fileModel = new TaskFile($this->container); + $this->assertFalse($fileModel->uploadFiles(1, array())); + } + + public function testUploadFilesWithUploadError() + { + $files = array( + 'name' => array( + 'file1.png', + 'file2.doc', + ), + 'tmp_name' => array( + '', + '/tmp/phpeEwEWG', + ), + 'error' => array( + UPLOAD_ERR_CANT_WRITE, + UPLOAD_ERR_OK, + ), + 'size' => array( + 123, + 456, + ), + ); + + $fileModel = new TaskFile($this->container); + $this->assertFalse($fileModel->uploadFiles(1, $files)); + } + + public function testUploadFilesWithObjectStorageError() + { + $files = array( + 'name' => array( + 'file1.csv', + 'file2.doc', + ), + 'tmp_name' => array( + '/tmp/phpYzdqkD', + '/tmp/phpeEwEWG', + ), + 'error' => array( + UPLOAD_ERR_OK, + UPLOAD_ERR_OK, + ), + 'size' => array( + 123, + 456, + ), + ); + + $this->container['objectStorage'] + ->expects($this->at(0)) + ->method('moveUploadedFile') + ->with($this->equalTo('/tmp/phpYzdqkD'), $this->anything()) + ->will($this->throwException(new \Kanboard\Core\ObjectStorage\ObjectStorageException('test'))); + + $fileModel = new TaskFile($this->container); + $this->assertFalse($fileModel->uploadFiles(1, $files)); + } + + public function testUploadFileContent() + { + $fileModel = $this + ->getMockBuilder('\Kanboard\Model\TaskFile') + ->setConstructorArgs(array($this->container)) + ->setMethods(array('generateThumbnailFromFile')) + ->getMock(); + + $projectModel = new Project($this->container); + $taskCreationModel = new TaskCreation($this->container); + $data = 'test'; + + $this->assertEquals(1, $projectModel->create(array('name' => 'test'))); + $this->assertEquals(1, $taskCreationModel->create(array('project_id' => 1, 'title' => 'test'))); + + $this->container['objectStorage'] + ->expects($this->once()) + ->method('put') + ->with($this->anything(), $this->equalTo($data)); + + $this->assertEquals(1, $fileModel->uploadContent(1, 'test.doc', base64_encode($data))); + + $files = $fileModel->getAll(1); + $this->assertCount(1, $files); + + $this->assertEquals(1, $files[0]['id']); + $this->assertEquals('test.doc', $files[0]['name']); + $this->assertEquals(0, $files[0]['is_image']); + $this->assertEquals(1, $files[0]['task_id']); + $this->assertEquals(0, $files[0]['user_id']); + $this->assertEquals(4, $files[0]['size']); + $this->assertEquals(time(), $files[0]['date'], '', 2); + } + + public function testUploadFileContentWithObjectStorageError() + { + $fileModel = $this + ->getMockBuilder('\Kanboard\Model\TaskFile') + ->setConstructorArgs(array($this->container)) + ->setMethods(array('generateThumbnailFromFile')) + ->getMock(); + + $projectModel = new Project($this->container); + $taskCreationModel = new TaskCreation($this->container); + $data = 'test'; + + $this->assertEquals(1, $projectModel->create(array('name' => 'test'))); + $this->assertEquals(1, $taskCreationModel->create(array('project_id' => 1, 'title' => 'test'))); + + $this->container['objectStorage'] + ->expects($this->once()) + ->method('put') + ->with($this->anything(), $this->equalTo($data)) + ->will($this->throwException(new \Kanboard\Core\ObjectStorage\ObjectStorageException('test'))); + + $this->assertFalse($fileModel->uploadContent(1, 'test.doc', base64_encode($data))); + } + + public function testUploadScreenshot() + { + $fileModel = $this + ->getMockBuilder('\Kanboard\Model\TaskFile') + ->setConstructorArgs(array($this->container)) + ->setMethods(array('generateThumbnailFromFile')) + ->getMock(); + + $projectModel = new Project($this->container); + $taskCreationModel = new TaskCreation($this->container); + $data = 'test'; + + $this->assertEquals(1, $projectModel->create(array('name' => 'test'))); + $this->assertEquals(1, $taskCreationModel->create(array('project_id' => 1, 'title' => 'test'))); + + $fileModel + ->expects($this->once()) + ->method('generateThumbnailFromFile'); + + $this->container['objectStorage'] + ->expects($this->once()) + ->method('put') + ->with($this->anything(), $this->equalTo($data)); + + $this->assertEquals(1, $fileModel->uploadScreenshot(1, base64_encode($data))); + + $files = $fileModel->getAll(1); + $this->assertCount(1, $files); + + $this->assertEquals(1, $files[0]['id']); + $this->assertStringStartsWith('Screenshot taken ', $files[0]['name']); + $this->assertEquals(1, $files[0]['is_image']); + $this->assertEquals(1, $files[0]['task_id']); + $this->assertEquals(0, $files[0]['user_id']); + $this->assertEquals(4, $files[0]['size']); + $this->assertEquals(time(), $files[0]['date'], '', 2); + } + + public function testRemove() + { + $fileModel = new TaskFile($this->container); + $projectModel = new Project($this->container); + $taskCreationModel = new TaskCreation($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'test'))); + $this->assertEquals(1, $taskCreationModel->create(array('project_id' => 1, 'title' => 'test'))); + $this->assertEquals(1, $fileModel->create(1, 'test', 'tmp/foo', 10)); + + $this->container['objectStorage'] + ->expects($this->once()) + ->method('remove') + ->with('tmp/foo'); + + $this->assertTrue($fileModel->remove(1)); + } + + public function testRemoveWithObjectStorageError() + { + $fileModel = new TaskFile($this->container); + $projectModel = new Project($this->container); + $taskCreationModel = new TaskCreation($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'test'))); + $this->assertEquals(1, $taskCreationModel->create(array('project_id' => 1, 'title' => 'test'))); + $this->assertEquals(1, $fileModel->create(1, 'test', 'tmp/foo', 10)); + + $this->container['objectStorage'] + ->expects($this->once()) + ->method('remove') + ->with('tmp/foo') + ->will($this->throwException(new \Kanboard\Core\ObjectStorage\ObjectStorageException('test'))); + + $this->assertFalse($fileModel->remove(1)); + } + + public function testRemoveImage() + { + $fileModel = new TaskFile($this->container); + $projectModel = new Project($this->container); + $taskCreationModel = new TaskCreation($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'test'))); + $this->assertEquals(1, $taskCreationModel->create(array('project_id' => 1, 'title' => 'test'))); + $this->assertEquals(1, $fileModel->create(1, 'image.gif', 'tmp/image.gif', 10)); + + $this->container['objectStorage'] + ->expects($this->at(0)) + ->method('remove') + ->with('tmp/image.gif'); + + $this->container['objectStorage'] + ->expects($this->at(1)) + ->method('remove') + ->with('thumbnails'.DIRECTORY_SEPARATOR.'tmp/image.gif'); + + $this->assertTrue($fileModel->remove(1)); + } + + public function testRemoveAll() + { + $fileModel = new TaskFile($this->container); + $projectModel = new Project($this->container); + $taskCreationModel = new TaskCreation($this->container); + + $this->assertEquals(1, $projectModel->create(array('name' => 'test'))); + $this->assertEquals(1, $taskCreationModel->create(array('project_id' => 1, 'title' => 'test'))); + $this->assertEquals(1, $fileModel->create(1, 'test', 'tmp/foo', 10)); + $this->assertEquals(2, $fileModel->create(1, 'test', 'tmp/foo', 10)); + + $this->container['objectStorage'] + ->expects($this->exactly(2)) + ->method('remove') + ->with('tmp/foo'); + + $this->assertTrue($fileModel->removeAll(1)); + } +} diff --git a/tests/units/Model/UserNotificationTest.php b/tests/units/Model/UserNotificationTest.php index 8168a375..e1928661 100644 --- a/tests/units/Model/UserNotificationTest.php +++ b/tests/units/Model/UserNotificationTest.php @@ -9,7 +9,6 @@ use Kanboard\Model\Comment; use Kanboard\Model\User; use Kanboard\Model\Group; use Kanboard\Model\GroupMember; -use Kanboard\Model\File; use Kanboard\Model\Project; use Kanboard\Model\ProjectPermission; use Kanboard\Model\Task; diff --git a/tests/units/Model/UserTest.php b/tests/units/Model/UserTest.php index 0987fa56..e411da0c 100644 --- a/tests/units/Model/UserTest.php +++ b/tests/units/Model/UserTest.php @@ -96,13 +96,14 @@ class UserTest extends Base $this->assertEquals('you', $users[2]['username']); } - public function testGetList() + public function testGetActiveUsersList() { $u = new User($this->container); $this->assertEquals(2, $u->create(array('username' => 'you'))); $this->assertEquals(3, $u->create(array('username' => 'me', 'name' => 'Me too'))); + $this->assertEquals(4, $u->create(array('username' => 'foobar', 'is_active' => 0))); - $users = $u->getList(); + $users = $u->getActiveUsersList(); $expected = array( 1 => 'admin', @@ -112,7 +113,7 @@ class UserTest extends Base $this->assertEquals($expected, $users); - $users = $u->getList(true); + $users = $u->getActiveUsersList(true); $expected = array( User::EVERYBODY_ID => 'Everybody', @@ -391,4 +392,24 @@ class UserTest extends Base $this->assertEquals('toto', $user['username']); $this->assertEmpty($user['token']); } + + public function testEnableDisable() + { + $userModel = new User($this->container); + $this->assertEquals(2, $userModel->create(array('username' => 'toto'))); + + $this->assertTrue($userModel->isActive(2)); + $user = $userModel->getById(2); + $this->assertEquals(1, $user['is_active']); + + $this->assertTrue($userModel->disable(2)); + $user = $userModel->getById(2); + $this->assertEquals(0, $user['is_active']); + $this->assertFalse($userModel->isActive(2)); + + $this->assertTrue($userModel->enable(2)); + $user = $userModel->getById(2); + $this->assertEquals(1, $user['is_active']); + $this->assertTrue($userModel->isActive(2)); + } } diff --git a/tests/units/Model/UserUnreadNotificationTest.php b/tests/units/Model/UserUnreadNotificationTest.php index bf274d95..62889bf0 100644 --- a/tests/units/Model/UserUnreadNotificationTest.php +++ b/tests/units/Model/UserUnreadNotificationTest.php @@ -7,7 +7,6 @@ use Kanboard\Model\TaskCreation; use Kanboard\Model\Subtask; use Kanboard\Model\Comment; use Kanboard\Model\User; -use Kanboard\Model\File; use Kanboard\Model\Task; use Kanboard\Model\Project; use Kanboard\Model\UserUnreadNotification; diff --git a/tests/units/Notification/MailTest.php b/tests/units/Notification/MailTest.php index 8f343ba3..7dc6aaef 100644 --- a/tests/units/Notification/MailTest.php +++ b/tests/units/Notification/MailTest.php @@ -7,7 +7,7 @@ use Kanboard\Model\TaskCreation; use Kanboard\Model\Subtask; use Kanboard\Model\Comment; use Kanboard\Model\User; -use Kanboard\Model\File; +use Kanboard\Model\TaskFile; use Kanboard\Model\Project; use Kanboard\Model\Task; use Kanboard\Notification\Mail; @@ -23,7 +23,7 @@ class MailTest extends Base $tc = new TaskCreation($this->container); $s = new Subtask($this->container); $c = new Comment($this->container); - $f = new File($this->container); + $f = new TaskFile($this->container); $this->assertEquals(1, $p->create(array('name' => 'test'))); $this->assertEquals(1, $tc->create(array('title' => 'test', 'project_id' => 1))); |
