summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml1
-rw-r--r--CONTRIBUTORS.md3
-rw-r--r--ChangeLog32
-rw-r--r--Makefile15
-rw-r--r--README.md2
-rw-r--r--app/Api/Swimlane.php12
-rw-r--r--app/Auth/Ldap.php422
-rw-r--r--app/Auth/ReverseProxy.php1
-rw-r--r--app/Controller/Board.php12
-rw-r--r--app/Controller/Budget.php135
-rw-r--r--app/Controller/Doc.php22
-rw-r--r--app/Controller/File.php38
-rw-r--r--app/Controller/Hourlyrate.php89
-rw-r--r--app/Controller/Swimlane.php6
-rw-r--r--app/Controller/Taskcreation.php36
-rw-r--r--app/Controller/Taskmodification.php28
-rw-r--r--app/Core/Base.php2
-rw-r--r--app/Core/ObjectStorage/FileStorage.php150
-rw-r--r--app/Core/ObjectStorage/ObjectStorageException.php9
-rw-r--r--app/Core/ObjectStorage/ObjectStorageInterface.php68
-rw-r--r--app/Core/PluginBase.php47
-rw-r--r--app/Core/PluginLoader.php137
-rw-r--r--app/Core/Response.php8
-rw-r--r--app/Core/Router.php46
-rw-r--r--app/Core/Template.php52
-rw-r--r--app/Core/Tool.php101
-rw-r--r--app/Core/Translator.php23
-rw-r--r--app/Helper/App.php2
-rw-r--r--app/Helper/Hook.php49
-rw-r--r--app/Integration/Mailgun.php4
-rw-r--r--app/Integration/Postmark.php5
-rw-r--r--app/Integration/Sendgrid.php4
-rw-r--r--app/Locale/cs_CZ/translations.php32
-rw-r--r--app/Locale/da_DK/translations.php32
-rw-r--r--app/Locale/de_DE/translations.php32
-rw-r--r--app/Locale/es_ES/translations.php86
-rw-r--r--app/Locale/fi_FI/translations.php32
-rw-r--r--app/Locale/fr_FR/translations.php174
-rw-r--r--app/Locale/hu_HU/translations.php32
-rw-r--r--app/Locale/id_ID/translations.php1072
-rw-r--r--app/Locale/it_IT/translations.php32
-rw-r--r--app/Locale/ja_JP/translations.php32
-rwxr-xr-x[-rw-r--r--]app/Locale/nb_NO/translations.php542
-rw-r--r--app/Locale/nl_NL/translations.php32
-rw-r--r--app/Locale/pl_PL/translations.php32
-rw-r--r--app/Locale/pt_BR/translations.php46
-rw-r--r--app/Locale/pt_PT/translations.php32
-rw-r--r--app/Locale/ru_RU/translations.php33
-rw-r--r--app/Locale/sr_Latn_RS/translations.php32
-rw-r--r--app/Locale/sv_SE/translations.php32
-rw-r--r--app/Locale/th_TH/translations.php32
-rw-r--r--app/Locale/tr_TR/translations.php32
-rw-r--r--app/Locale/zh_CN/translations.php32
-rw-r--r--app/Model/Acl.php13
-rw-r--r--app/Model/Budget.php214
-rw-r--r--app/Model/Config.php2
-rw-r--r--app/Model/File.php244
-rw-r--r--app/Model/HourlyRate.php121
-rw-r--r--app/Model/Subtask.php41
-rw-r--r--app/Model/SubtaskTimeTracking.php19
-rw-r--r--app/Model/Swimlane.php28
-rw-r--r--app/Schema/Mysql.php44
-rw-r--r--app/Schema/Postgres.php41
-rw-r--r--app/Schema/Sqlite.php41
-rw-r--r--app/ServiceProvider/ClassProvider.php25
-rw-r--r--app/Subscriber/SubtaskTimeTrackingSubscriber.php1
-rw-r--r--app/Template/app/sidebar.php1
-rw-r--r--app/Template/board/table_swimlane.php18
-rw-r--r--app/Template/budget/breakdown.php30
-rw-r--r--app/Template/budget/create.php47
-rw-r--r--app/Template/budget/index.php34
-rw-r--r--app/Template/budget/remove.php13
-rw-r--r--app/Template/budget/sidebar.php16
-rw-r--r--app/Template/config/sidebar.php1
-rw-r--r--app/Template/currency/index.php2
-rw-r--r--app/Template/export/sidebar.php1
-rw-r--r--app/Template/file/show.php2
-rw-r--r--app/Template/gantt/task_creation.php12
-rw-r--r--app/Template/header.php33
-rw-r--r--app/Template/hourlyrate/index.php46
-rw-r--r--app/Template/hourlyrate/remove.php13
-rw-r--r--app/Template/layout.php43
-rw-r--r--app/Template/project/dropdown.php30
-rw-r--r--app/Template/project/sidebar.php2
-rw-r--r--app/Template/project_user/sidebar.php2
-rw-r--r--app/Template/swimlane/edit.php21
-rw-r--r--app/Template/swimlane/index.php22
-rw-r--r--app/Template/swimlane/table.php2
-rw-r--r--app/Template/task/color_picker.php11
-rw-r--r--app/Template/task/sidebar.php4
-rw-r--r--app/Template/task_creation/form.php11
-rw-r--r--app/Template/task_modification/edit_task.php11
-rw-r--r--app/Template/user/sidebar.php7
-rw-r--r--app/common.php120
-rw-r--r--app/constants.php5
-rw-r--r--app/routes.php117
-rw-r--r--assets/css/app.css2
-rw-r--r--assets/css/print.css2
-rw-r--r--assets/css/src/board.css2
-rw-r--r--assets/css/src/dropdown.css34
-rw-r--r--assets/css/src/form.css10
-rw-r--r--assets/css/src/gantt.css2
-rw-r--r--assets/css/src/popover.css4
-rw-r--r--assets/css/src/table.css4
-rw-r--r--assets/css/src/task.css26
-rw-r--r--assets/js/app.js2
-rw-r--r--assets/js/src/App.js2
-rw-r--r--assets/js/src/BudgetChart.js55
-rw-r--r--assets/js/src/Dropdown.js25
-rw-r--r--assets/js/src/Gantt.js15
-rw-r--r--assets/js/src/Markdown.js14
-rw-r--r--assets/js/src/Popover.js29
-rw-r--r--assets/js/src/Router.js1
-rw-r--r--assets/js/src/Swimlane.js136
-rw-r--r--assets/js/src/Task.js10
-rw-r--r--composer.json16
-rw-r--r--composer.lock113
-rw-r--r--config.default.php15
-rw-r--r--doc/api-json-rpc.markdown2
-rw-r--r--doc/closing-tasks.markdown6
-rw-r--r--doc/config.markdown11
-rw-r--r--doc/fr/analytics.markdown70
-rw-r--r--doc/fr/automatic-actions.markdown137
-rw-r--r--doc/fr/board-collapsed-expanded.markdown19
-rw-r--r--doc/fr/board-horizontal-scrolling-and-compact-view.markdown13
-rw-r--r--doc/fr/board-show-hide-columns.markdown12
-rw-r--r--doc/fr/budget.markdown34
-rw-r--r--doc/fr/calendar.markdown20
-rw-r--r--doc/fr/creating-projects.markdown32
-rw-r--r--doc/fr/editing-projects.markdown16
-rw-r--r--doc/fr/gantt-chart-projects.markdown17
-rw-r--r--doc/fr/gantt-chart-tasks.markdown20
-rw-r--r--doc/fr/kanban-vs-todo-and-scrum.markdown36
-rw-r--r--doc/fr/project-permissions.markdown49
-rw-r--r--doc/fr/project-views.markdown44
-rw-r--r--doc/fr/sharing-projects.markdown37
-rw-r--r--doc/fr/swimlanes.markdown28
-rw-r--r--doc/fr/usage-examples.markdown69
-rw-r--r--doc/fr/what-is-kanban.markdown34
-rw-r--r--doc/index.markdown4
-rw-r--r--doc/ldap-authentication.markdown11
-rw-r--r--doc/ldap-group-sync.markdown36
-rw-r--r--doc/plugins.markdown263
-rw-r--r--doc/screenshots.markdown2
-rw-r--r--doc/subtasks.markdown2
-rw-r--r--plugins/.gitignore2
-rw-r--r--tests/functionals/UserApiTest.php6
-rw-r--r--tests/php.ini5
-rw-r--r--tests/units/Action/CommentCreationTest.php (renamed from tests/units/ActionCommentCreationTest.php)4
-rw-r--r--tests/units/Action/TaskAssignColorCategoryTest.php (renamed from tests/units/ActionTaskAssignColorCategoryTest.php)4
-rw-r--r--tests/units/Action/TaskAssignColorColumnTest.php (renamed from tests/units/ActionTaskAssignColorColumnTest.php)4
-rw-r--r--tests/units/Action/TaskAssignColorLinkTest.php (renamed from tests/units/ActionTaskAssignColorLinkTest.php)4
-rw-r--r--tests/units/Action/TaskAssignColorUserTest.php (renamed from tests/units/ActionTaskAssignColorUserTest.php)4
-rw-r--r--tests/units/Action/TaskAssignCurrentUserTest.php (renamed from tests/units/ActionTaskAssignCurrentUserTest.php)4
-rw-r--r--tests/units/Action/TaskAssignSpecificUserTest.php (renamed from tests/units/ActionTaskAssignSpecificUserTest.php)4
-rw-r--r--tests/units/Action/TaskCloseTest.php (renamed from tests/units/ActionTaskCloseTest.php)4
-rw-r--r--tests/units/Action/TaskDuplicateAnotherProjectTest.php (renamed from tests/units/ActionTaskDuplicateAnotherProjectTest.php)4
-rw-r--r--tests/units/Action/TaskEmailTest.php (renamed from tests/units/ActionTaskEmailTest.php)4
-rw-r--r--tests/units/Action/TaskMoveAnotherProjectTest.php (renamed from tests/units/ActionTaskMoveAnotherProjectTest.php)4
-rw-r--r--tests/units/Action/TaskMoveColumnCategoryChangeTest.php (renamed from tests/units/ActionTaskMoveColumnCategoryChangeTest.php)4
-rw-r--r--tests/units/Action/TaskUpdateStartDateTest.php (renamed from tests/units/ActionTaskUpdateStartDateTest.php)4
-rw-r--r--tests/units/Auth/LdapTest.php678
-rw-r--r--tests/units/Auth/ReverseProxyTest.php37
-rw-r--r--tests/units/Core/LexerTest.php (renamed from tests/units/LexerTest.php)2
-rw-r--r--tests/units/Core/OAuth2Test.php (renamed from tests/units/OAuth2Test.php)2
-rw-r--r--tests/units/Core/PluginLoaderTest.php23
-rw-r--r--tests/units/Core/RouterTest.php (renamed from tests/units/RouterTest.php)2
-rw-r--r--tests/units/Core/TemplateTest.php28
-rw-r--r--tests/units/Core/ToolTest.php (renamed from tests/units/ToolTest.php)2
-rw-r--r--tests/units/Helper/AppHelperTest.php (renamed from tests/units/AppHelperTest.php)2
-rw-r--r--tests/units/Helper/AssetHelperTest.php (renamed from tests/units/AssetHelperTest.php)2
-rw-r--r--tests/units/Helper/DatetimeHelperTest.php (renamed from tests/units/DatetimeHelperTest.php)2
-rw-r--r--tests/units/Helper/FileHelperText.php (renamed from tests/units/FileHelperText.php)2
-rw-r--r--tests/units/Helper/TextHelperTest.php (renamed from tests/units/TextHelperTest.php)2
-rw-r--r--tests/units/Helper/UrlHelperTest.php (renamed from tests/units/UrlHelperTest.php)2
-rw-r--r--tests/units/Helper/UserHelperTest.php (renamed from tests/units/UserHelperTest.php)2
-rw-r--r--tests/units/HourlyRate.php43
-rw-r--r--tests/units/Integration/BitbucketWebhookTest.php (renamed from tests/units/BitbucketWebhookTest.php)30
-rw-r--r--tests/units/Integration/GithubWebhookTest.php (renamed from tests/units/GithubWebhookTest.php)40
-rw-r--r--tests/units/Integration/GitlabWebhookTest.php (renamed from tests/units/GitlabWebhookTest.php)20
-rw-r--r--tests/units/Integration/MailgunTest.php (renamed from tests/units/MailgunTest.php)2
-rw-r--r--tests/units/Integration/PostmarkTest.php (renamed from tests/units/PostmarkTest.php)2
-rw-r--r--tests/units/Integration/SendgridTest.php (renamed from tests/units/SendgridTest.php)2
-rw-r--r--tests/units/Locale/LocaleTest.php (renamed from tests/units/LocaleTest.php)2
-rw-r--r--tests/units/Model/AclTest.php (renamed from tests/units/AclTest.php)14
-rw-r--r--tests/units/Model/ActionTest.php (renamed from tests/units/ActionTest.php)2
-rw-r--r--tests/units/Model/AuthenticationTest.php (renamed from tests/units/AuthenticationTest.php)2
-rw-r--r--tests/units/Model/BoardTest.php (renamed from tests/units/BoardTest.php)4
-rw-r--r--tests/units/Model/CategoryTest.php (renamed from tests/units/CategoryTest.php)2
-rw-r--r--tests/units/Model/CommentTest.php (renamed from tests/units/CommentTest.php)2
-rw-r--r--tests/units/Model/ConfigTest.php (renamed from tests/units/ConfigTest.php)2
-rw-r--r--tests/units/Model/DateParserTest.php (renamed from tests/units/DateParserTest.php)2
-rw-r--r--tests/units/Model/FileTest.php (renamed from tests/units/FileTest.php)79
-rw-r--r--tests/units/Model/LinkTest.php (renamed from tests/units/LinkTest.php)2
-rw-r--r--tests/units/Model/NotificationTest.php (renamed from tests/units/NotificationTest.php)2
-rw-r--r--tests/units/Model/ProjectActivityTest.php (renamed from tests/units/ProjectActivityTest.php)2
-rw-r--r--tests/units/Model/ProjectDailyColumnStatsTest.php (renamed from tests/units/ProjectDailyColumnStatsTest.php)2
-rw-r--r--tests/units/Model/ProjectDuplicationTest.php (renamed from tests/units/ProjectDuplicationTest.php)26
-rw-r--r--tests/units/Model/ProjectPermissionTest.php (renamed from tests/units/ProjectPermissionTest.php)2
-rw-r--r--tests/units/Model/ProjectTest.php (renamed from tests/units/ProjectTest.php)4
-rw-r--r--tests/units/Model/SubtaskTest.php (renamed from tests/units/SubtaskTest.php)132
-rw-r--r--tests/units/Model/SubtaskTimeTrackingTest.php (renamed from tests/units/SubtaskTimeTrackingTest.php)28
-rw-r--r--tests/units/Model/SwimlaneTest.php (renamed from tests/units/SwimlaneTest.php)46
-rw-r--r--tests/units/Model/TaskCreationTest.php (renamed from tests/units/TaskCreationTest.php)2
-rw-r--r--tests/units/Model/TaskDuplicationTest.php (renamed from tests/units/TaskDuplicationTest.php)24
-rw-r--r--tests/units/Model/TaskExportTest.php (renamed from tests/units/TaskExportTest.php)6
-rw-r--r--tests/units/Model/TaskFilterTest.php (renamed from tests/units/TaskFilterTest.php)6
-rw-r--r--tests/units/Model/TaskFinderTest.php (renamed from tests/units/TaskFinderTest.php)2
-rw-r--r--tests/units/Model/TaskLinkTest.php (renamed from tests/units/TaskLinkTest.php)2
-rw-r--r--tests/units/Model/TaskModificationTest.php (renamed from tests/units/TaskModificationTest.php)2
-rw-r--r--tests/units/Model/TaskMovedDateSubscriberTest.php (renamed from tests/units/TaskMovedDateSubscriberTest.php)6
-rw-r--r--tests/units/Model/TaskPermissionTest.php (renamed from tests/units/TaskPermissionTest.php)2
-rw-r--r--tests/units/Model/TaskPositionTest.php (renamed from tests/units/TaskPositionTest.php)6
-rw-r--r--tests/units/Model/TaskStatusTest.php (renamed from tests/units/TaskStatusTest.php)2
-rw-r--r--tests/units/Model/TaskTest.php (renamed from tests/units/TaskTest.php)2
-rw-r--r--tests/units/Model/TimetableTest.php (renamed from tests/units/TimetableTest.php)2
-rw-r--r--tests/units/Model/UserSessionTest.php (renamed from tests/units/UserSessionTest.php)2
-rw-r--r--tests/units/Model/UserTest.php (renamed from tests/units/UserTest.php)2
-rw-r--r--tests/units/Model/WebhookTest.php (renamed from tests/units/WebhookTest.php)2
219 files changed, 5572 insertions, 2895 deletions
diff --git a/.travis.yml b/.travis.yml
index 49f3465e..dd9d05bb 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -19,6 +19,7 @@ matrix:
before_script:
- phpenv config-add tests/php.ini
- composer install
+ - php -i
script:
- phpunit -c tests/units.$DB.xml \ No newline at end of file
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 4c66a430..37973887 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -7,6 +7,7 @@ Contributors:
- Alex Butum
- [Aleix Pol](https://github.com/aleixpol)
+- [Anjar Febrianto](https://github.com/Lasut)
- [Ashbike](https://github.com/ashbike)
- [Ashish Kulkarni](https://github.com/ashkulz)
- [Christian González](https://github.com/nerdoc)
@@ -24,10 +25,12 @@ Contributors:
- [Eskiso](https://github.com/eSkiSo)
- [Esteban Monge](https://github.com/EstebanMonge)
- [Fábio Hideki](https://github.com/fabiohxcx)
+- [Fabiano Castro Pereira](https://github.com/fabiano-pereira)
- [Federico Lazcano](https://github.com/lazki)
- [Fengchao](https://github.com/fengchao)
- [Floaltvater](https://github.com/floaltvater)
- [Gavlepeter](https://github.com/gavlepeter)
+- [Goofy](https://github.com/goofy-bz)
- [Hendrik Stocker](https://github.com/hendrik-stoker)
- [Iterate From 0](https://github.com/freebsd-kanboard)
- [Jan Dittrich](https://github.com/jdittrich)
diff --git a/ChangeLog b/ChangeLog
index ddfb2f79..98c0df7e 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,35 @@
+Version 1.0.19 (unreleased)
+---------------------------
+
+New features:
+
+* Added LDAP group sync
+* Add swimlane description
+* New plugin system (alpha)
+* Added Bahasa Indonesia translation
+
+Breaking changes:
+
+* Budget planning is now a plugin and it's not part of core
+
+Improvements:
+
+* Highlight selected item in dropdown menus
+* Gantt chart: change bar color according to task progress
+* Replace color dropdown by color picker in task forms
+* Creating another task stay in the popover (no full page refresh anymore)
+* Avoid scrollbar in Gantt chart for row title on Windows platform
+* Remove unnecessary margin for calendar header
+* Show localized documentation if available
+* Add event subtask.delete
+
+Bug fixes:
+
+* Fix typo in template that prevent the Gitlab oauth link to be displayed
+* Fix Markdown preview links focus
+* Avoid dropdown menu to be truncated inside a column with scrolling
+* Deleting subtask doesn't update task time tracking
+
Version 1.0.18
--------------
diff --git a/Makefile b/Makefile
index abf859f7..69edbdae 100644
--- a/Makefile
+++ b/Makefile
@@ -4,9 +4,9 @@ CSS_APP = $(addprefix assets/css/src/, $(addsuffix .css, base links title table
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 Sidebar Search App Screenshot Calendar Board Swimlane Gantt TaskRepartitionChart UserRepartitionChart CumulativeFlowDiagram BurndownChart BudgetChart AvgTimeColumnChart TaskTimeColumnChart LeadCycleTimeChart Router))
+JS_APP = $(addprefix assets/js/src/, $(addsuffix .js, Popover Dropdown Tooltip Markdown Sidebar Search App Screenshot Calendar Board Swimlane Gantt Task TaskRepartitionChart UserRepartitionChart CumulativeFlowDiagram BurndownChart AvgTimeColumnChart TaskTimeColumnChart LeadCycleTimeChart Router))
JS_VENDOR = $(addprefix assets/js/vendor/, $(addsuffix .js, jquery-1.11.1.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))
-JS_LANG = $(addprefix assets/js/vendor/lang/, $(addsuffix .js, da de es fi fr hu it ja nl nb pl pt pt-br ru sv sr th tr zh-cn))
+JS_LANG = $(addprefix assets/js/vendor/lang/, $(addsuffix .js, da de es fi fr hu id it ja nl nb pl pt pt-br ru sv sr th tr zh-cn))
all: css js
@@ -77,4 +77,15 @@ archive:
@ cd ${dst} && if [ -L kanboard-latest.zip ]; then unlink kanboard-latest.zip; ln -s kanboard-${version}.zip kanboard-latest.zip; fi
@ rm -rf ${BUILD_DIR}/kanboard
+test-sqlite:
+ @ phpunit -c tests/units.sqlite.xml
+
+test-mysql:
+ @ phpunit -c tests/units.mysql.xml
+
+test-postgres:
+ @ phpunit -c tests/units.postgres.xml
+
+unittest: test-sqlite test-mysql test-postgres
+
.PHONY: all
diff --git a/README.md b/README.md
index 38265d87..c56c3ba5 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@ Official website: <http://kanboard.net>
- Multiple boards with the ability to drag and drop tasks
- Open source and self-hosted
- Super simple installation
-- Translated in 21 languages
+- Translated in 22 languages
- Distributed under [MIT License](LICENSE)
- [List of features are available on the website](http://kanboard.net/features)
- [Change Log](ChangeLog)
diff --git a/app/Api/Swimlane.php b/app/Api/Swimlane.php
index fb40841f..13838d77 100644
--- a/app/Api/Swimlane.php
+++ b/app/Api/Swimlane.php
@@ -40,14 +40,18 @@ class Swimlane extends \Core\Base
return $this->swimlane->getDefault($project_id);
}
- public function addSwimlane($project_id, $name)
+ public function addSwimlane($project_id, $name, $description = '')
{
- return $this->swimlane->create($project_id, $name);
+ return $this->swimlane->create(array('project_id' => $project_id, 'name' => $name, 'description' => $description));
}
- public function updateSwimlane($swimlane_id, $name)
+ public function updateSwimlane($swimlane_id, $name, $description = null)
{
- return $this->swimlane->rename($swimlane_id, $name);
+ $values = array('id' => $swimlane_id, 'name' => $name);
+ if (!is_null($description)) {
+ $values['description'] = $description;
+ }
+ return $this->swimlane->update($values);
}
public function removeSwimlane($project_id, $swimlane_id)
diff --git a/app/Auth/Ldap.php b/app/Auth/Ldap.php
index c1459b4e..0ccd09a4 100644
--- a/app/Auth/Ldap.php
+++ b/app/Auth/Ldap.php
@@ -20,6 +20,178 @@ class Ldap extends Base
const AUTH_NAME = 'LDAP';
/**
+ * Get LDAP server name
+ *
+ * @access public
+ * @return string
+ */
+ public function getLdapServer()
+ {
+ return LDAP_SERVER;
+ }
+
+ /**
+ * Get LDAP server port
+ *
+ * @access public
+ * @return integer
+ */
+ public function getLdapPort()
+ {
+ return LDAP_PORT;
+ }
+
+ /**
+ * Get LDAP username (proxy auth)
+ *
+ * @access public
+ * @return string
+ */
+ public function getLdapUsername()
+ {
+ return LDAP_USERNAME;
+ }
+
+ /**
+ * Get LDAP password (proxy auth)
+ *
+ * @access public
+ * @return string
+ */
+ public function getLdapPassword()
+ {
+ return LDAP_PASSWORD;
+ }
+
+ /**
+ * Get LDAP Base DN
+ *
+ * @access public
+ * @return string
+ */
+ public function getLdapBaseDn()
+ {
+ return LDAP_ACCOUNT_BASE;
+ }
+
+ /**
+ * Get LDAP account id attribute
+ *
+ * @access public
+ * @return string
+ */
+ public function getLdapAccountId()
+ {
+ return LDAP_ACCOUNT_ID;
+ }
+
+ /**
+ * Get LDAP account email attribute
+ *
+ * @access public
+ * @return string
+ */
+ public function getLdapAccountEmail()
+ {
+ return LDAP_ACCOUNT_EMAIL;
+ }
+
+ /**
+ * Get LDAP account name attribute
+ *
+ * @access public
+ * @return string
+ */
+ public function getLdapAccountName()
+ {
+ return LDAP_ACCOUNT_FULLNAME;
+ }
+
+ /**
+ * Get LDAP account memberof attribute
+ *
+ * @access public
+ * @return string
+ */
+ public function getLdapAccountMemberOf()
+ {
+ return LDAP_ACCOUNT_MEMBEROF;
+ }
+
+ /**
+ * Get LDAP admin group DN
+ *
+ * @access public
+ * @return string
+ */
+ public function getLdapGroupAdmin()
+ {
+ return LDAP_GROUP_ADMIN_DN;
+ }
+
+ /**
+ * Get LDAP project admin group DN
+ *
+ * @access public
+ * @return string
+ */
+ public function getLdapGroupProjectAdmin()
+ {
+ return LDAP_GROUP_PROJECT_ADMIN_DN;
+ }
+
+ /**
+ * Get LDAP username pattern
+ *
+ * @access public
+ * @return string
+ */
+ public function getLdapUserPattern($username)
+ {
+ return sprintf(LDAP_USER_PATTERN, $username);
+ }
+
+ /**
+ * Return true if the LDAP username is case sensitive
+ *
+ * @access public
+ * @return boolean
+ */
+ public function isLdapAccountCaseSensitive()
+ {
+ return LDAP_USERNAME_CASE_SENSITIVE;
+ }
+
+ /**
+ * Return true if the automatic account creation is enabled
+ *
+ * @access public
+ * @return boolean
+ */
+ public function isLdapAccountCreationEnabled()
+ {
+ return LDAP_ACCOUNT_CREATION;
+ }
+
+ /**
+ * Ge the list of attributes to fetch when reading the LDAP user entry
+ *
+ * Must returns array with index that start at 0 otherwise ldap_search returns a warning "Array initialization wrong"
+ *
+ * @access public
+ * @return array
+ */
+ public function getProfileAttributes()
+ {
+ return array_values(array_filter(array(
+ $this->getLdapAccountId(),
+ $this->getLdapAccountName(),
+ $this->getLdapAccountEmail(),
+ $this->getLdapAccountMemberOf()
+ )));
+ }
+
+ /**
* Authenticate the user
*
* @access public
@@ -29,7 +201,7 @@ class Ldap extends Base
*/
public function authenticate($username, $password)
{
- $username = LDAP_USERNAME_CASE_SENSITIVE ? $username : strtolower($username);
+ $username = $this->isLdapAccountCaseSensitive() ? $username : strtolower($username);
$result = $this->findUser($username, $password);
if (is_array($result)) {
@@ -46,7 +218,7 @@ class Ldap extends Base
else {
// We create automatically a new user
- if (LDAP_ACCOUNT_CREATION && $this->createUser($username, $result['name'], $result['email'])) {
+ if ($this->isLdapAccountCreationEnabled() && $this->user->create($result) !== false) {
$user = $this->user->getByUsername($username);
}
else {
@@ -65,28 +237,6 @@ class Ldap extends Base
}
/**
- * Create a new local user after the LDAP authentication
- *
- * @access public
- * @param string $username Username
- * @param string $name Name of the user
- * @param string $email Email address
- * @return bool
- */
- public function createUser($username, $name, $email)
- {
- $values = array(
- 'username' => $username,
- 'name' => $name,
- 'email' => $email,
- 'is_admin' => 0,
- 'is_ldap_user' => 1,
- );
-
- return $this->user->create($values);
- }
-
- /**
* Find the user from the LDAP server
*
* @access public
@@ -98,8 +248,8 @@ class Ldap extends Base
{
$ldap = $this->connect();
- if (is_resource($ldap) && $this->bind($ldap, $username, $password)) {
- return $this->search($ldap, $username, $password);
+ if ($ldap !== false && $this->bind($ldap, $username, $password)) {
+ return $this->getProfile($ldap, $username, $password);
}
return false;
@@ -108,13 +258,14 @@ class Ldap extends Base
/**
* LDAP connection
*
- * @access private
- * @return resource $ldap LDAP connection
+ * @access public
+ * @return resource|boolean
*/
- private function connect()
+ public function connect()
{
if (! function_exists('ldap_connect')) {
- die('The PHP LDAP extension is required');
+ $this->logger->error('The PHP LDAP extension is required');
+ return false;
}
// Skip SSL certificate verification
@@ -122,10 +273,11 @@ class Ldap extends Base
putenv('LDAPTLS_REQCERT=never');
}
- $ldap = ldap_connect(LDAP_SERVER, LDAP_PORT);
+ $ldap = ldap_connect($this->getLdapServer(), $this->getLdapPort());
- if (! is_resource($ldap)) {
- die('Unable to connect to the LDAP server: "'.LDAP_SERVER.'"');
+ if ($ldap === false) {
+ $this->logger->error('Unable to connect to the LDAP server');
+ return false;
}
ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
@@ -134,30 +286,32 @@ class Ldap extends Base
ldap_set_option($ldap, LDAP_OPT_TIMELIMIT, 1);
if (LDAP_START_TLS && ! @ldap_start_tls($ldap)) {
- die('Unable to use ldap_start_tls()');
+ $this->logger->error('Unable to use ldap_start_tls()');
+ return false;
}
return $ldap;
}
/**
- * LDAP bind
+ * LDAP authentication
*
- * @access private
- * @param resource $ldap LDAP connection
- * @param string $username Username
- * @param string $password Password
+ * @access public
+ * @param resource $ldap
+ * @param string $username
+ * @param string $password
+ * @param string $ldap_type
* @return boolean
*/
- private function bind($ldap, $username, $password)
+ public function bind($ldap, $username, $password, $ldap_type = LDAP_BIND_TYPE)
{
- if (LDAP_BIND_TYPE === 'user') {
- $ldap_username = sprintf(LDAP_USERNAME, $username);
+ if ($ldap_type === 'user') {
+ $ldap_username = $this->getLdapUserPattern($username);
$ldap_password = $password;
}
- else if (LDAP_BIND_TYPE === 'proxy') {
- $ldap_username = LDAP_USERNAME;
- $ldap_password = LDAP_PASSWORD;
+ else if ($ldap_type === 'proxy') {
+ $ldap_username = $this->getLdapUsername();
+ $ldap_password = $this->getLdapPassword();
}
else {
$ldap_username = null;
@@ -172,118 +326,182 @@ class Ldap extends Base
}
/**
- * LDAP user lookup
+ * Get LDAP user profile
*
- * @access private
- * @param resource $ldap LDAP connection
- * @param string $username Username
- * @param string $password Password
+ * @access public
+ * @param resource $ldap
+ * @param string $username
+ * @param string $password
* @return boolean|array
*/
- private function search($ldap, $username, $password)
+ public function getProfile($ldap, $username, $password)
{
- $sr = @ldap_search($ldap, LDAP_ACCOUNT_BASE, sprintf(LDAP_USER_PATTERN, $username), array(LDAP_ACCOUNT_FULLNAME, LDAP_ACCOUNT_EMAIL));
-
- if ($sr === false) {
+ $entries = $this->executeQuery($ldap, $this->getLdapUserPattern($username));
+ if ($entries === false) {
return false;
}
- $info = ldap_get_entries($ldap, $sr);
+ if (@ldap_bind($ldap, $entries[0]['dn'], $password)) {
+ return $this->prepareProfile($ldap, $entries, $username);
+ }
- // User not found
- if (count($info) == 0 || $info['count'] == 0) {
- return false;
+ return false;
+ }
+
+ /**
+ * Build user profile from LDAP information
+ *
+ * @access public
+ * @param resource $ldap
+ * @param array $entries
+ * @param string $username
+ * @return boolean|array
+ */
+ public function prepareProfile($ldap, array $entries, $username)
+ {
+ if ($this->getLdapAccountId() !== '') {
+ $username = $this->getEntry($entries, $this->getLdapAccountId(), $username);
}
- // We got our user
- if (@ldap_bind($ldap, $info[0]['dn'], $password)) {
+ return array(
+ 'username' => $username,
+ 'name' => $this->getEntry($entries, $this->getLdapAccountName()),
+ 'email' => $this->getEntry($entries, $this->getLdapAccountEmail()),
+ 'is_admin' => (int) $this->isMemberOf($this->getEntries($entries, $this->getLdapAccountMemberOf()), $this->getLdapGroupAdmin()),
+ 'is_project_admin' => (int) $this->isMemberOf($this->getEntries($entries, $this->getLdapAccountMemberOf()), $this->getLdapGroupProjectAdmin()),
+ 'is_ldap_user' => 1,
+ );
+ }
- return array(
- 'username' => $username,
- 'name' => $this->getFromInfo($info, LDAP_ACCOUNT_FULLNAME),
- 'email' => $this->getFromInfo($info, LDAP_ACCOUNT_EMAIL),
- );
+ /**
+ * Check group membership
+ *
+ * @access public
+ * @param array $group_entries
+ * @param string $group_dn
+ * @return boolean
+ */
+ public function isMemberOf(array $group_entries, $group_dn)
+ {
+ if (! isset($group_entries['count']) || empty($group_dn)) {
+ return false;
+ }
+
+ for ($i = 0; $i < $group_entries['count']; $i++) {
+ if ($group_entries[$i] === $group_dn) {
+ return true;
+ }
}
return false;
}
/**
- * Retrieve info on LDAP user
+ * Retrieve info on LDAP user by username or email
*
- * @param string $username Username
- * @param string $email Email address
+ * @access public
+ * @param string $username
+ * @param string $email
+ * @return boolean|array
*/
public function lookup($username = null, $email = null)
{
- $query = $this->getQuery($username, $email);
- if ($query === false) {
+ $query = $this->getLookupQuery($username, $email);
+ if ($query === '') {
return false;
}
- // Connect and attempt anonymous bind
+ // Connect and attempt anonymous or proxy binding
$ldap = $this->connect();
- if (! is_resource($ldap) || ! $this->bind($ldap, null, null)) {
+ if ($ldap === false || ! $this->bind($ldap, null, null)) {
return false;
}
// Try to find user
- $sr = @ldap_search($ldap, LDAP_ACCOUNT_BASE, $query, array(LDAP_ACCOUNT_FULLNAME, LDAP_ACCOUNT_EMAIL, LDAP_ACCOUNT_ID));
- if ($sr === false) {
+ $entries = $this->executeQuery($ldap, $query);
+ if ($entries === false) {
+ return false;
+ }
+
+ // User id not retrieved: LDAP_ACCOUNT_ID not properly configured
+ if (empty($username) && ! isset($entries[0][$this->getLdapAccountId()][0])) {
return false;
}
- $info = ldap_get_entries($ldap, $sr);
+ return $this->prepareProfile($ldap, $entries, $username);
+ }
- // User not found
- if (count($info) == 0 || $info['count'] == 0) {
+ /**
+ * Execute LDAP query
+ *
+ * @access private
+ * @param resource $ldap
+ * @param string $query
+ * @return boolean|array
+ */
+ private function executeQuery($ldap, $query)
+ {
+ $sr = ldap_search($ldap, $this->getLdapBaseDn(), $query, $this->getProfileAttributes());
+ if ($sr === false) {
return false;
}
- // User id not retrieved: LDAP_ACCOUNT_ID not properly configured
- if (empty($username) && ! isset($info[0][LDAP_ACCOUNT_ID][0])) {
+ $entries = ldap_get_entries($ldap, $sr);
+ if ($entries === false || count($entries) === 0 || $entries['count'] == 0) {
return false;
}
- return array(
- 'username' => $this->getFromInfo($info, LDAP_ACCOUNT_ID, $username),
- 'name' => $this->getFromInfo($info, LDAP_ACCOUNT_FULLNAME),
- 'email' => $this->getFromInfo($info, LDAP_ACCOUNT_EMAIL, $email),
- );
+ return $entries;
}
/**
* Get the LDAP query to find a user
*
- * @param string $username Username
- * @param string $email Email address
+ * @access private
+ * @param string $username
+ * @param string $email
+ * @return string
*/
- private function getQuery($username, $email)
+ private function getLookupQuery($username, $email)
{
- if ($username && $email) {
- return '(&('.sprintf(LDAP_USER_PATTERN, $username).')('.LDAP_ACCOUNT_EMAIL.'='.$email.'))';
+ if (! empty($username) && ! empty($email)) {
+ return '(&('.$this->getLdapUserPattern($username).')('.$this->getLdapAccountEmail().'='.$email.'))';
}
- else if ($username) {
- return sprintf(LDAP_USER_PATTERN, $username);
+ else if (! empty($username)) {
+ return $this->getLdapUserPattern($username);
}
- else if ($email) {
- return '('.LDAP_ACCOUNT_EMAIL.'='.$email.')';
- }
- else {
- return false;
+ else if (! empty($email)) {
+ return '('.$this->getLdapAccountEmail().'='.$email.')';
}
+
+ return '';
}
/**
- * Return a value from the LDAP info
+ * Return one entry from a list of entries
*
- * @param array $info LDAP info
- * @param string $key Key
- * @param string $default Default value if key not set in entry
+ * @access private
+ * @param array $entries LDAP entries
+ * @param string $key Key
+ * @param string $default Default value if key not set in entry
* @return string
*/
- private function getFromInfo($info, $key, $default = '')
+ private function getEntry(array $entries, $key, $default = '')
+ {
+ return isset($entries[0][$key][0]) ? $entries[0][$key][0] : $default;
+ }
+
+ /**
+ * Return subset of entries
+ *
+ * @access private
+ * @param array $entries
+ * @param string $key
+ * @param array $default
+ * @return array
+ */
+ private function getEntries(array $entries, $key, $default = array())
{
- return isset($info[0][$key][0]) ? $info[0][$key][0] : $default;
+ return isset($entries[0][$key]) ? $entries[0][$key] : $default;
}
}
diff --git a/app/Auth/ReverseProxy.php b/app/Auth/ReverseProxy.php
index c8fd5eec..7818254c 100644
--- a/app/Auth/ReverseProxy.php
+++ b/app/Auth/ReverseProxy.php
@@ -28,7 +28,6 @@ class ReverseProxy extends Base
public function authenticate()
{
if (isset($_SERVER[REVERSE_PROXY_USER_HEADER])) {
-
$login = $_SERVER[REVERSE_PROXY_USER_HEADER];
$user = $this->user->getByUsername($login);
diff --git a/app/Controller/Board.php b/app/Controller/Board.php
index 179c6b3c..a552b9cf 100644
--- a/app/Controller/Board.php
+++ b/app/Controller/Board.php
@@ -321,6 +321,18 @@ class Board extends Base
}
/**
+ * Display swimlane description in tooltip
+ *
+ * @access public
+ */
+ public function swimlane()
+ {
+ $this->getProject();
+ $swimlane = $this->swimlane->getById($this->request->getIntegerParam('swimlane_id'));
+ $this->response->html($this->template->render('board/tooltip_description', array('task' => $swimlane)));
+ }
+
+ /**
* Enable collapsed mode
*
* @access public
diff --git a/app/Controller/Budget.php b/app/Controller/Budget.php
deleted file mode 100644
index a2f7e0db..00000000
--- a/app/Controller/Budget.php
+++ /dev/null
@@ -1,135 +0,0 @@
-<?php
-
-namespace Controller;
-
-/**
- * Budget
- *
- * @package controller
- * @author Frederic Guillot
- */
-class Budget extends Base
-{
- /**
- * Budget index page
- *
- * @access public
- */
- public function index()
- {
- $project = $this->getProject();
-
- $this->response->html($this->projectLayout('budget/index', array(
- 'daily_budget' => $this->budget->getDailyBudgetBreakdown($project['id']),
- 'project' => $project,
- 'title' => t('Budget')
- ), 'budget/sidebar'));
- }
-
- /**
- * Cost breakdown by users/subtasks/tasks
- *
- * @access public
- */
- public function breakdown()
- {
- $project = $this->getProject();
-
- $paginator = $this->paginator
- ->setUrl('budget', 'breakdown', array('project_id' => $project['id']))
- ->setMax(30)
- ->setOrder('start')
- ->setDirection('DESC')
- ->setQuery($this->budget->getSubtaskBreakdown($project['id']))
- ->calculate();
-
- $this->response->html($this->projectLayout('budget/breakdown', array(
- 'paginator' => $paginator,
- 'project' => $project,
- 'title' => t('Budget')
- ), 'budget/sidebar'));
- }
-
- /**
- * Create budget lines
- *
- * @access public
- */
- public function create(array $values = array(), array $errors = array())
- {
- $project = $this->getProject();
-
- if (empty($values)) {
- $values['date'] = date('Y-m-d');
- }
-
- $this->response->html($this->projectLayout('budget/create', array(
- 'lines' => $this->budget->getAll($project['id']),
- 'values' => $values + array('project_id' => $project['id']),
- 'errors' => $errors,
- 'project' => $project,
- 'title' => t('Budget lines')
- ), 'budget/sidebar'));
- }
-
- /**
- * Validate and save a new budget
- *
- * @access public
- */
- public function save()
- {
- $project = $this->getProject();
-
- $values = $this->request->getValues();
- list($valid, $errors) = $this->budget->validateCreation($values);
-
- if ($valid) {
-
- if ($this->budget->create($values['project_id'], $values['amount'], $values['comment'], $values['date'])) {
- $this->session->flash(t('The budget line have been created successfully.'));
- $this->response->redirect($this->helper->url->to('budget', 'create', array('project_id' => $project['id'])));
- }
- else {
- $this->session->flashError(t('Unable to create the budget line.'));
- }
- }
-
- $this->create($values, $errors);
- }
-
- /**
- * Confirmation dialog before removing a budget
- *
- * @access public
- */
- public function confirm()
- {
- $project = $this->getProject();
-
- $this->response->html($this->projectLayout('budget/remove', array(
- 'project' => $project,
- 'budget_id' => $this->request->getIntegerParam('budget_id'),
- 'title' => t('Remove a budget line'),
- ), 'budget/sidebar'));
- }
-
- /**
- * Remove a budget
- *
- * @access public
- */
- public function remove()
- {
- $this->checkCSRFParam();
- $project = $this->getProject();
-
- if ($this->budget->remove($this->request->getIntegerParam('budget_id'))) {
- $this->session->flash(t('Budget line removed successfully.'));
- } else {
- $this->session->flashError(t('Unable to remove this budget line.'));
- }
-
- $this->response->redirect($this->helper->url->to('budget', 'create', array('project_id' => $project['id'])));
- }
-}
diff --git a/app/Controller/Doc.php b/app/Controller/Doc.php
index 19644b84..f9f0a675 100644
--- a/app/Controller/Doc.php
+++ b/app/Controller/Doc.php
@@ -16,7 +16,7 @@ class Doc extends Base
{
$url = $this->helper->url;
$data = file_get_contents($filename);
- list($title,, $content) = explode("\n", $data, 3);
+ list($title,) = explode("\n", $data, 2);
$replaceUrl = function (array $matches) use ($url) {
return '('.$url->to('doc', 'show', array('file' => str_replace('.markdown', '', $matches[1]))).')';
@@ -32,16 +32,24 @@ class Doc extends Base
public function show()
{
- $filename = $this->request->getStringParam('file', 'index');
+ $page = $this->request->getStringParam('file', 'index');
- if (! preg_match('/^[a-z0-9\-]+/', $filename)) {
- $filename = 'index';
+ if (! preg_match('/^[a-z0-9\-]+/', $page)) {
+ $page = 'index';
}
- $filename = __DIR__.'/../../doc/'.$filename.'.markdown';
+ $filenames = array(__DIR__.'/../../doc/'.$page.'.markdown');
+ $filename = __DIR__.'/../../doc/index.markdown';
- if (! file_exists($filename)) {
- $filename = __DIR__.'/../../doc/index.markdown';
+ if ($this->config->getCurrentLanguage() === 'fr_FR') {
+ array_unshift($filenames, __DIR__.'/../../doc/fr/'.$page.'.markdown');
+ }
+
+ foreach ($filenames as $file) {
+ if (file_exists($file)) {
+ $filename = $file;
+ break;
+ }
}
$this->response->html($this->template->layout('doc/show', $this->readFile($filename) + array(
diff --git a/app/Controller/File.php b/app/Controller/File.php
index f73a9de9..7b7c75ee 100644
--- a/app/Controller/File.php
+++ b/app/Controller/File.php
@@ -60,7 +60,7 @@ class File extends Base
{
$task = $this->getTask();
- if (! $this->file->upload($task['project_id'], $task['id'], 'files')) {
+ if (! $this->file->uploadFiles($task['project_id'], $task['id'], 'files')) {
$this->session->flashError(t('Unable to upload the file.'));
}
@@ -76,14 +76,13 @@ class File extends Base
{
$task = $this->getTask();
$file = $this->file->getById($this->request->getIntegerParam('file_id'));
- $filename = FILES_DIR.$file['path'];
- if ($file['task_id'] == $task['id'] && file_exists($filename)) {
- $this->response->forceDownload($file['name']);
- $this->response->binary(file_get_contents($filename));
+ 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'])));
}
- $this->response->redirect($this->helper->url->to('task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])));
+ $this->response->forceDownload($file['name']);
+ $this->objectStorage->passthru($file['path']);
}
/**
@@ -113,16 +112,13 @@ class File extends Base
{
$task = $this->getTask();
$file = $this->file->getById($this->request->getIntegerParam('file_id'));
- $filename = FILES_DIR.$file['path'];
-
- if ($file['task_id'] == $task['id'] && file_exists($filename)) {
- $metadata = getimagesize($filename);
- if (isset($metadata['mime'])) {
- $this->response->contentType($metadata['mime']);
- readfile($filename);
- }
+ 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'])));
}
+
+ $this->response->contentType($this->file->getImageMimeType($file['name']));
+ $this->objectStorage->passthru($file['path']);
}
/**
@@ -134,17 +130,13 @@ class File extends Base
{
$task = $this->getTask();
$file = $this->file->getById($this->request->getIntegerParam('file_id'));
- $filename = FILES_DIR.$file['path'];
-
- if ($file['task_id'] == $task['id'] && file_exists($filename)) {
- $this->response->contentType('image/jpeg');
- $this->file->generateThumbnail(
- $filename,
- $this->request->getIntegerParam('width'),
- $this->request->getIntegerParam('height')
- );
+ 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'])));
}
+
+ $this->response->contentType('image/jpeg');
+ $this->objectStorage->passthru($this->file->getThumbnailPath($file['path']));
}
/**
diff --git a/app/Controller/Hourlyrate.php b/app/Controller/Hourlyrate.php
deleted file mode 100644
index 19650ede..00000000
--- a/app/Controller/Hourlyrate.php
+++ /dev/null
@@ -1,89 +0,0 @@
-<?php
-
-namespace Controller;
-
-/**
- * Hourly Rate controller
- *
- * @package controller
- * @author Frederic Guillot
- */
-class Hourlyrate extends User
-{
- /**
- * Display rate and form
- *
- * @access public
- */
- public function index(array $values = array(), array $errors = array())
- {
- $user = $this->getUser();
-
- $this->response->html($this->layout('hourlyrate/index', array(
- 'rates' => $this->hourlyRate->getAllByUser($user['id']),
- 'currencies_list' => $this->config->getCurrencies(),
- 'values' => $values + array('user_id' => $user['id']),
- 'errors' => $errors,
- 'user' => $user,
- )));
- }
-
- /**
- * Validate and save a new rate
- *
- * @access public
- */
- public function save()
- {
- $values = $this->request->getValues();
- list($valid, $errors) = $this->hourlyRate->validateCreation($values);
-
- if ($valid) {
-
- if ($this->hourlyRate->create($values['user_id'], $values['rate'], $values['currency'], $values['date_effective'])) {
- $this->session->flash(t('Hourly rate created successfully.'));
- $this->response->redirect($this->helper->url->to('hourlyrate', 'index', array('user_id' => $values['user_id'])));
- }
- else {
- $this->session->flashError(t('Unable to save the hourly rate.'));
- }
- }
-
- $this->index($values, $errors);
- }
-
- /**
- * Confirmation dialag box to remove a row
- *
- * @access public
- */
- public function confirm()
- {
- $user = $this->getUser();
-
- $this->response->html($this->layout('hourlyrate/remove', array(
- 'rate_id' => $this->request->getIntegerParam('rate_id'),
- 'user' => $user,
- )));
- }
-
- /**
- * Remove a row
- *
- * @access public
- */
- public function remove()
- {
- $this->checkCSRFParam();
- $user = $this->getUser();
-
- if ($this->hourlyRate->remove($this->request->getIntegerParam('rate_id'))) {
- $this->session->flash(t('Rate removed successfully.'));
- }
- else {
- $this->session->flash(t('Unable to remove this rate.'));
- }
-
- $this->response->redirect($this->helper->url->to('hourlyrate', 'index', array('user_id' => $user['id'])));
- }
-}
diff --git a/app/Controller/Swimlane.php b/app/Controller/Swimlane.php
index 054fa4ba..f92e9fe3 100644
--- a/app/Controller/Swimlane.php
+++ b/app/Controller/Swimlane.php
@@ -59,13 +59,12 @@ class Swimlane extends Base
public function save()
{
$project = $this->getProject();
-
$values = $this->request->getValues();
list($valid, $errors) = $this->swimlane->validateCreation($values);
if ($valid) {
- if ($this->swimlane->create($project['id'], $values['name'])) {
+ if ($this->swimlane->create($values)) {
$this->session->flash(t('Your swimlane have been created successfully.'));
$this->response->redirect($this->helper->url->to('swimlane', 'index', array('project_id' => $project['id'])));
}
@@ -134,8 +133,7 @@ class Swimlane extends Base
list($valid, $errors) = $this->swimlane->validateModification($values);
if ($valid) {
-
- if ($this->swimlane->rename($values['id'], $values['name'])) {
+ if ($this->swimlane->update($values)) {
$this->session->flash(t('Swimlane updated successfully.'));
$this->response->redirect($this->helper->url->to('swimlane', 'index', array('project_id' => $project['id'])));
}
diff --git a/app/Controller/Taskcreation.php b/app/Controller/Taskcreation.php
index ff25c5da..b9e9a33c 100644
--- a/app/Controller/Taskcreation.php
+++ b/app/Controller/Taskcreation.php
@@ -59,25 +59,29 @@ class Taskcreation extends Base
list($valid, $errors) = $this->taskValidator->validateCreation($values);
- if ($valid) {
+ if ($valid && $this->taskCreation->create($values)) {
+ $this->session->flash(t('Task created successfully.'));
+ $this->afterSave($project, $values);
+ }
+ else {
+ $this->session->flashError(t('Unable to create your task.'));
+ }
- if ($this->taskCreation->create($values)) {
- $this->session->flash(t('Task created successfully.'));
+ $this->create($values, $errors);
+ }
- if (isset($values['another_task']) && $values['another_task'] == 1) {
- unset($values['title']);
- unset($values['description']);
- $this->response->redirect($this->helper->url->to('taskcreation', 'create', $values));
- }
- else {
- $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $project['id'])));
- }
- }
- else {
- $this->session->flashError(t('Unable to create your task.'));
+ private function afterSave(array $project, array &$values)
+ {
+ if (isset($values['another_task']) && $values['another_task'] == 1) {
+ unset($values['title']);
+ unset($values['description']);
+
+ if (! $this->request->isAjax()) {
+ $this->response->redirect($this->helper->url->to('taskcreation', 'create', $values));
}
}
-
- $this->create($values, $errors);
+ else {
+ $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $project['id'])));
+ }
}
}
diff --git a/app/Controller/Taskmodification.php b/app/Controller/Taskmodification.php
index 56d2b9f9..638af594 100644
--- a/app/Controller/Taskmodification.php
+++ b/app/Controller/Taskmodification.php
@@ -126,11 +126,13 @@ class Taskmodification extends Base
);
if ($ajax) {
- $this->response->html($this->template->render('task_modification/edit_task', $params));
+ $html = $this->template->render('task_modification/edit_task', $params);
}
else {
- $this->response->html($this->taskLayout('task_modification/edit_task', $params));
+ $html = $this->taskLayout('task_modification/edit_task', $params);
}
+
+ $this->response->html($html);
}
/**
@@ -145,24 +147,20 @@ class Taskmodification extends Base
list($valid, $errors) = $this->taskValidator->validateModification($values);
- if ($valid) {
-
- if ($this->taskModification->update($values)) {
- $this->session->flash(t('Task updated successfully.'));
+ if ($valid && $this->taskModification->update($values)) {
+ $this->session->flash(t('Task updated successfully.'));
- if ($this->request->getIntegerParam('ajax')) {
- $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $task['project_id'])));
- }
- else {
- $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id'])));
- }
+ if ($this->request->isAjax()) {
+ $this->response->redirect($this->helper->url->to('board', 'show', array('project_id' => $task['project_id'])));
}
else {
- $this->session->flashError(t('Unable to update your task.'));
+ $this->response->redirect($this->helper->url->to('task', 'show', array('project_id' => $task['project_id'], 'task_id' => $task['id'])));
}
}
-
- $this->edit($values, $errors);
+ else {
+ $this->session->flashError(t('Unable to update your task.'));
+ $this->edit($values, $errors);
+ }
}
/**
diff --git a/app/Core/Base.php b/app/Core/Base.php
index 3db0cf74..5ed8f40a 100644
--- a/app/Core/Base.php
+++ b/app/Core/Base.php
@@ -35,7 +35,6 @@ use Pimple\Container;
* @property \Model\Action $action
* @property \Model\Authentication $authentication
* @property \Model\Board $board
- * @property \Model\Budget $budget
* @property \Model\Category $category
* @property \Model\Color $color
* @property \Model\Comment $comment
@@ -43,7 +42,6 @@ use Pimple\Container;
* @property \Model\Currency $currency
* @property \Model\DateParser $dateParser
* @property \Model\File $file
- * @property \Model\HourlyRate $hourlyRate
* @property \Model\LastLogin $lastLogin
* @property \Model\Link $link
* @property \Model\Notification $notification
diff --git a/app/Core/ObjectStorage/FileStorage.php b/app/Core/ObjectStorage/FileStorage.php
new file mode 100644
index 00000000..66c62334
--- /dev/null
+++ b/app/Core/ObjectStorage/FileStorage.php
@@ -0,0 +1,150 @@
+<?php
+
+namespace Core\ObjectStorage;
+
+/**
+ * Local File Storage
+ *
+ * @package ObjectStorage
+ * @author Frederic Guillot
+ */
+class FileStorage implements ObjectStorageInterface
+{
+ /**
+ * Base path
+ *
+ * @access private
+ * @var string
+ */
+ private $path = '';
+
+ /**
+ * Constructor
+ *
+ * @access public
+ * @param string $path
+ */
+ public function __construct($path)
+ {
+ $this->path = $path;
+ }
+
+ /**
+ * Fetch object contents
+ *
+ * @access public
+ * @param string $key
+ * @return string
+ */
+ public function get($key)
+ {
+ $filename = $this->path.DIRECTORY_SEPARATOR.$key;
+
+ if (! file_exists($filename)) {
+ throw new ObjectStorageException('File not found: '.$filename);
+ }
+
+ return file_get_contents($filename);
+ }
+
+ /**
+ * Save object
+ *
+ * @access public
+ * @param string $key
+ * @param string $blob
+ * @return string
+ */
+ public function put($key, &$blob)
+ {
+ $this->createFolder($key);
+
+ if (file_put_contents($this->path.DIRECTORY_SEPARATOR.$key, $blob) === false) {
+ throw new ObjectStorageException('Unable to write the file: '.$this->path.DIRECTORY_SEPARATOR.$key);
+ }
+ }
+
+ /**
+ * Output directly object content
+ *
+ * @access public
+ * @param string $key
+ */
+ public function passthru($key)
+ {
+ $filename = $this->path.DIRECTORY_SEPARATOR.$key;
+
+ if (! file_exists($filename)) {
+ throw new ObjectStorageException('File not found: '.$filename);
+ }
+
+ return readfile($filename);
+ }
+
+ /**
+ * Move local file to object storage
+ *
+ * @access public
+ * @param string $filename
+ * @param string $key
+ * @return boolean
+ */
+ public function moveFile($src_filename, $key)
+ {
+ $this->createFolder($key);
+ $dst_filename = $this->path.DIRECTORY_SEPARATOR.$key;
+
+ if (! rename($src_filename, $dst_filename)) {
+ throw new ObjectStorageException('Unable to move the file: '.$src_filename.' to '.$dst_filename);
+ }
+
+ return true;
+ }
+
+ /**
+ * Move uploaded file to object storage
+ *
+ * @access public
+ * @param string $filename
+ * @param string $key
+ * @return boolean
+ */
+ public function moveUploadedFile($filename, $key)
+ {
+ $this->createFolder($key);
+ return move_uploaded_file($filename, $this->path.DIRECTORY_SEPARATOR.$key);
+ }
+
+ /**
+ * Remove object
+ *
+ * @access public
+ * @param string $key
+ * @return boolean
+ */
+ public function remove($key)
+ {
+ $filename = $this->path.DIRECTORY_SEPARATOR.$key;
+
+ if (file_exists($filename)) {
+ return unlink($filename);
+ }
+
+ return false;
+ }
+
+ /**
+ * Create object folder
+ *
+ * @access private
+ * @param string $key
+ */
+ private function createFolder($key)
+ {
+ $folder = $this->path.DIRECTORY_SEPARATOR.dirname($key);
+
+ if (! is_dir($folder) && ! mkdir($folder, 0755, true)) {
+ throw new ObjectStorageException('Unable to create folder: '.$folder);
+ }
+ }
+}
diff --git a/app/Core/ObjectStorage/ObjectStorageException.php b/app/Core/ObjectStorage/ObjectStorageException.php
new file mode 100644
index 00000000..e89aeb25
--- /dev/null
+++ b/app/Core/ObjectStorage/ObjectStorageException.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace Core\ObjectStorage;
+
+use Exception;
+
+class ObjectStorageException extends Exception
+{
+}
diff --git a/app/Core/ObjectStorage/ObjectStorageInterface.php b/app/Core/ObjectStorage/ObjectStorageInterface.php
new file mode 100644
index 00000000..5440cf2b
--- /dev/null
+++ b/app/Core/ObjectStorage/ObjectStorageInterface.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace Core\ObjectStorage;
+
+/**
+ * Object Storage Interface
+ *
+ * @package ObjectStorage
+ * @author Frederic Guillot
+ */
+interface ObjectStorageInterface
+{
+ /**
+ * Fetch object contents
+ *
+ * @access public
+ * @param string $key
+ * @return string
+ */
+ public function get($key);
+
+ /**
+ * Save object
+ *
+ * @access public
+ * @param string $key
+ * @param string $blob
+ * @return string
+ */
+ public function put($key, &$blob);
+
+ /**
+ * Output directly object content
+ *
+ * @access public
+ * @param string $key
+ */
+ public function passthru($key);
+
+ /**
+ * Move local file to object storage
+ *
+ * @access public
+ * @param string $filename
+ * @param string $key
+ * @return boolean
+ */
+ public function moveFile($filename, $key);
+
+ /**
+ * Move uploaded file to object storage
+ *
+ * @access public
+ * @param string $filename
+ * @param string $key
+ * @return boolean
+ */
+ public function moveUploadedFile($filename, $key);
+
+ /**
+ * Remove object
+ *
+ * @access public
+ * @param string $key
+ * @return boolean
+ */
+ public function remove($key);
+}
diff --git a/app/Core/PluginBase.php b/app/Core/PluginBase.php
new file mode 100644
index 00000000..457afa03
--- /dev/null
+++ b/app/Core/PluginBase.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace Core;
+
+/**
+ * Plugin Base class
+ *
+ * @package core
+ * @author Frederic Guillot
+ */
+abstract class PluginBase extends Base
+{
+ /**
+ * Method called for each request
+ *
+ * @abstract
+ * @access public
+ */
+ abstract public function initialize();
+
+ /**
+ * Returns all classes that needs to be stored in the DI container
+ *
+ * @access public
+ * @return array
+ */
+ public function getClasses()
+ {
+ return array();
+ }
+
+ /**
+ * Listen on internal events
+ *
+ * @access public
+ * @param string $event
+ * @param callable $callback
+ */
+ public function on($event, $callback)
+ {
+ $container = $this->container;
+
+ $this->container['dispatcher']->addListener($event, function() use ($container, $callback) {
+ call_user_func($callback, $container);
+ });
+ }
+}
diff --git a/app/Core/PluginLoader.php b/app/Core/PluginLoader.php
new file mode 100644
index 00000000..c7c254f7
--- /dev/null
+++ b/app/Core/PluginLoader.php
@@ -0,0 +1,137 @@
+<?php
+
+namespace Core;
+
+use DirectoryIterator;
+use PDOException;
+
+/**
+ * Plugin Loader
+ *
+ * @package core
+ * @author Frederic Guillot
+ */
+class PluginLoader extends Base
+{
+ /**
+ * Schema version table for plugins
+ *
+ * @var string
+ */
+ const TABLE_SCHEMA = 'plugin_schema_versions';
+
+ /**
+ * Scan plugin folder and load plugins
+ *
+ * @access public
+ */
+ public function scan()
+ {
+ if (file_exists(__DIR__.'/../../plugins')) {
+ $dir = new DirectoryIterator(__DIR__.'/../../plugins');
+
+ foreach ($dir as $fileinfo) {
+ if (! $fileinfo->isDot() && $fileinfo->isDir()) {
+ $plugin = $fileinfo->getFilename();
+ $this->loadSchema($plugin);
+ $this->load($plugin);
+ }
+ }
+ }
+ }
+
+ /**
+ * Load plugin
+ *
+ * @access public
+ */
+ public function load($plugin)
+ {
+ $class = '\Plugin\\'.$plugin.'\\Plugin';
+ $instance = new $class($this->container);
+
+ Tool::buildDic($this->container, $instance->getClasses());
+
+ $instance->initialize();
+ }
+
+ /**
+ * Load plugin schema
+ *
+ * @access public
+ * @param string $plugin
+ */
+ public function loadSchema($plugin)
+ {
+ $filename = __DIR__.'/../../plugins/'.$plugin.'/Schema/'.ucfirst(DB_DRIVER).'.php';
+
+ if (file_exists($filename)) {
+ require($filename);
+ $this->migrateSchema($plugin);
+ }
+ }
+
+ /**
+ * Execute plugin schema migrations
+ *
+ * @access public
+ * @param string $plugin
+ */
+ public function migrateSchema($plugin)
+ {
+ $last_version = constant('\Plugin\\'.$plugin.'\Schema\VERSION');
+ $current_version = $this->getSchemaVersion($plugin);
+
+ try {
+
+ $this->db->startTransaction();
+ $this->db->getDriver()->disableForeignKeys();
+
+ for ($i = $current_version + 1; $i <= $last_version; $i++) {
+ $function_name = '\Plugin\\'.$plugin.'\Schema\version_'.$i;
+
+ if (function_exists($function_name)) {
+ call_user_func($function_name, $this->db->getConnection());
+ }
+ }
+
+ $this->db->getDriver()->enableForeignKeys();
+ $this->db->closeTransaction();
+ $this->setSchemaVersion($plugin, $i - 1);
+ }
+ catch (PDOException $e) {
+ $this->db->cancelTransaction();
+ $this->db->getDriver()->enableForeignKeys();
+ die('Unable to migrate schema for the plugin: '.$plugin.' => '.$e->getMessage());
+ }
+ }
+
+ /**
+ * Get current plugin schema version
+ *
+ * @access public
+ * @param string $plugin
+ * @return integer
+ */
+ public function getSchemaVersion($plugin)
+ {
+ return (int) $this->db->table(self::TABLE_SCHEMA)->eq('plugin', strtolower($plugin))->findOneColumn('version');
+ }
+
+ /**
+ * Save last plugin schema version
+ *
+ * @access public
+ * @param string $plugin
+ * @param integer $version
+ * @return boolean
+ */
+ public function setSchemaVersion($plugin, $version)
+ {
+ $dictionary = array(
+ strtolower($plugin) => $version
+ );
+
+ return $this->db->getDriver()->upsert(self::TABLE_SCHEMA, 'plugin', 'version', $dictionary);
+ }
+}
diff --git a/app/Core/Response.php b/app/Core/Response.php
index d42a8f1e..f8ca015c 100644
--- a/app/Core/Response.php
+++ b/app/Core/Response.php
@@ -66,7 +66,13 @@ class Response
*/
public function redirect($url)
{
- header('Location: '.$url);
+ if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') {
+ header('X-Ajax-Redirect: '.$url);
+ }
+ else {
+ header('Location: '.$url);
+ }
+
exit;
}
diff --git a/app/Core/Router.php b/app/Core/Router.php
index 6e7576d6..36bbfd55 100644
--- a/app/Core/Router.php
+++ b/app/Core/Router.php
@@ -213,49 +213,17 @@ class Router extends Base
if (! empty($_GET['controller']) && ! empty($_GET['action'])) {
$controller = $this->sanitize($_GET['controller'], 'app');
$action = $this->sanitize($_GET['action'], 'index');
+ $plugin = ! empty($_GET['plugin']) ? $this->sanitize($_GET['plugin'], '') : '';
}
else {
- list($controller, $action) = $this->findRoute($this->getPath($uri, $query_string));
+ list($controller, $action) = $this->findRoute($this->getPath($uri, $query_string)); // TODO: add plugin for routes
+ $plugin = '';
}
- return $this->load(
- __DIR__.'/../Controller/'.ucfirst($controller).'.php',
- $controller,
- '\Controller\\'.ucfirst($controller),
- $action
- );
- }
-
- /**
- * Load a controller and execute the action
- *
- * @access private
- * @param string $filename
- * @param string $controller
- * @param string $class
- * @param string $method
- * @return bool
- */
- private function load($filename, $controller, $class, $method)
- {
- if (file_exists($filename)) {
-
- require $filename;
-
- if (! method_exists($class, $method)) {
- return false;
- }
-
- $this->action = $method;
- $this->controller = $controller;
-
- $instance = new $class($this->container);
- $instance->beforeAction($controller, $method);
- $instance->$method();
-
- return true;
- }
+ $class = empty($plugin) ? '\Controller\\'.ucfirst($controller) : '\Plugin\\'.ucfirst($plugin).'\Controller\\'.ucfirst($controller);
- return false;
+ $instance = new $class($this->container);
+ $instance->beforeAction($controller, $action);
+ $instance->$action();
}
}
diff --git a/app/Core/Template.php b/app/Core/Template.php
index ba869ee6..b75f7da1 100644
--- a/app/Core/Template.php
+++ b/app/Core/Template.php
@@ -13,11 +13,12 @@ use LogicException;
class Template extends Helper
{
/**
- * Template path
+ * List of template overrides
*
- * @var string
+ * @access private
+ * @var array
*/
- const PATH = 'app/Template/';
+ private $overrides = array();
/**
* Render a template
@@ -33,16 +34,10 @@ class Template extends Helper
*/
public function render($__template_name, array $__template_args = array())
{
- $__template_file = self::PATH.$__template_name.'.php';
-
- if (! file_exists($__template_file)) {
- throw new LogicException('Unable to load the template: "'.$__template_name.'"');
- }
-
extract($__template_args);
ob_start();
- include $__template_file;
+ include $this->getTemplateFile($__template_name);
return ob_get_clean();
}
@@ -62,4 +57,41 @@ class Template extends Helper
$template_args + array('content_for_layout' => $this->render($template_name, $template_args))
);
}
+
+ /**
+ * Define a new template override
+ *
+ * @access public
+ * @param string $original_template
+ * @param string $new_template
+ */
+ public function setTemplateOverride($original_template, $new_template)
+ {
+ $this->overrides[$original_template] = $new_template;
+ }
+
+ /**
+ * Find template filename
+ *
+ * Core template name: 'task/show'
+ * Plugin template name: 'myplugin:task/show'
+ *
+ * @access public
+ * @param string $template_name
+ * @return string
+ */
+ public function getTemplateFile($template_name)
+ {
+ $template_name = isset($this->overrides[$template_name]) ? $this->overrides[$template_name] : $template_name;
+
+ if (strpos($template_name, ':') !== false) {
+ list($plugin, $template) = explode(':', $template_name);
+ $path = __DIR__.'/../../plugins/'.ucfirst($plugin).'/Template/'.$template.'.php';
+ }
+ else {
+ $path = __DIR__.'/../Template/'.$template_name.'.php';
+ }
+
+ return $path;
+ }
}
diff --git a/app/Core/Tool.php b/app/Core/Tool.php
index 84e42ba8..887c8fb3 100644
--- a/app/Core/Tool.php
+++ b/app/Core/Tool.php
@@ -2,6 +2,8 @@
namespace Core;
+use Pimple\Container;
+
/**
* Tool class
*
@@ -23,7 +25,6 @@ class Tool
$fp = fopen($filename, 'w');
if (is_resource($fp)) {
-
foreach ($rows as $fields) {
fputcsv($fp, $fields);
}
@@ -51,4 +52,102 @@ class Tool
return $identifier;
}
+
+ /**
+ * Build dependency injection container from an array
+ *
+ * @static
+ * @access public
+ * @param Container $container
+ * @param array $namespaces
+ */
+ public static function buildDIC(Container $container, array $namespaces)
+ {
+ foreach ($namespaces as $namespace => $classes) {
+ foreach ($classes as $name) {
+ $class = '\\'.$namespace.'\\'.$name;
+ $container[lcfirst($name)] = function ($c) use ($class) {
+ return new $class($c);
+ };
+ }
+ }
+ }
+
+ /**
+ * Generate a jpeg thumbnail from an image
+ *
+ * @static
+ * @access public
+ * @param string $src_file Source file image
+ * @param string $dst_file Destination file image
+ * @param integer $resize_width Desired image width
+ * @param integer $resize_height Desired image height
+ */
+ public static function generateThumbnail($src_file, $dst_file, $resize_width = 250, $resize_height = 100)
+ {
+ $metadata = getimagesize($src_file);
+ $src_width = $metadata[0];
+ $src_height = $metadata[1];
+ $dst_y = 0;
+ $dst_x = 0;
+
+ if (empty($metadata['mime'])) {
+ return;
+ }
+
+ if ($resize_width == 0 && $resize_height == 0) {
+ $resize_width = 100;
+ $resize_height = 100;
+ }
+
+ if ($resize_width > 0 && $resize_height == 0) {
+ $dst_width = $resize_width;
+ $dst_height = floor($src_height * ($resize_width / $src_width));
+ $dst_image = imagecreatetruecolor($dst_width, $dst_height);
+ }
+ elseif ($resize_width == 0 && $resize_height > 0) {
+ $dst_width = floor($src_width * ($resize_height / $src_height));
+ $dst_height = $resize_height;
+ $dst_image = imagecreatetruecolor($dst_width, $dst_height);
+ }
+ else {
+
+ $src_ratio = $src_width / $src_height;
+ $resize_ratio = $resize_width / $resize_height;
+
+ if ($src_ratio <= $resize_ratio) {
+ $dst_width = $resize_width;
+ $dst_height = floor($src_height * ($resize_width / $src_width));
+
+ $dst_y = ($dst_height - $resize_height) / 2 * (-1);
+ }
+ else {
+ $dst_width = floor($src_width * ($resize_height / $src_height));
+ $dst_height = $resize_height;
+
+ $dst_x = ($dst_width - $resize_width) / 2 * (-1);
+ }
+
+ $dst_image = imagecreatetruecolor($resize_width, $resize_height);
+ }
+
+ switch ($metadata['mime']) {
+ case 'image/jpeg':
+ case 'image/jpg':
+ $src_image = imagecreatefromjpeg($src_file);
+ break;
+ case 'image/png':
+ $src_image = imagecreatefrompng($src_file);
+ break;
+ case 'image/gif':
+ $src_image = imagecreatefromgif($src_file);
+ break;
+ default:
+ return;
+ }
+
+ imagecopyresampled($dst_image, $src_image, $dst_x, $dst_y, 0, 0, $dst_width, $dst_height, $src_width, $src_height);
+ imagejpeg($dst_image, $dst_file);
+ imagedestroy($dst_image);
+ }
}
diff --git a/app/Core/Translator.php b/app/Core/Translator.php
index e3d19692..e9aa1f3f 100644
--- a/app/Core/Translator.php
+++ b/app/Core/Translator.php
@@ -15,7 +15,7 @@ class Translator
*
* @var string
*/
- const PATH = 'app/Locale/';
+ const PATH = 'app/Locale';
/**
* Locale
@@ -196,18 +196,27 @@ class Translator
* @static
* @access public
* @param string $language Locale code: fr_FR
+ * @param string $path Locale folder
*/
- public static function load($language)
+ public static function load($language, $path = self::PATH)
{
setlocale(LC_TIME, $language.'.UTF-8', $language);
- $filename = self::PATH.$language.DIRECTORY_SEPARATOR.'translations.php';
+ $filename = $path.DIRECTORY_SEPARATOR.$language.DIRECTORY_SEPARATOR.'translations.php';
if (file_exists($filename)) {
- self::$locales = require $filename;
- }
- else {
- self::$locales = array();
+ self::$locales = array_merge(self::$locales, require($filename));
}
}
+
+ /**
+ * Clear locales stored in memory
+ *
+ * @static
+ * @access public
+ */
+ public static function unload()
+ {
+ self::$locales = array();
+ }
}
diff --git a/app/Helper/App.php b/app/Helper/App.php
index e5ebefcb..5fb89afe 100644
--- a/app/Helper/App.php
+++ b/app/Helper/App.php
@@ -67,9 +67,11 @@ class App extends \Core\Base
if (isset($this->session['flash_message'])) {
$html = '<div class="alert alert-success alert-fade-out">'.$this->helper->e($this->session['flash_message']).'</div>';
unset($this->session['flash_message']);
+ unset($this->session['flash_error_message']);
}
else if (isset($this->session['flash_error_message'])) {
$html = '<div class="alert alert-error">'.$this->helper->e($this->session['flash_error_message']).'</div>';
+ unset($this->session['flash_message']);
unset($this->session['flash_error_message']);
}
diff --git a/app/Helper/Hook.php b/app/Helper/Hook.php
new file mode 100644
index 00000000..77756757
--- /dev/null
+++ b/app/Helper/Hook.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace Helper;
+
+/**
+ * Template Hook helpers
+ *
+ * @package helper
+ * @author Frederic Guillot
+ */
+class Hook extends \Core\Base
+{
+ private $hooks = array();
+
+ /**
+ * Render all attached hooks
+ *
+ * @access public
+ * @param string $hook
+ * @param array $variables
+ * @return string
+ */
+ public function render($hook, array $variables = array())
+ {
+ $buffer = '';
+
+ foreach ($this->hooks as $name => $template) {
+ if ($hook === $name) {
+ $buffer .= $this->template->render($template, $variables);
+ }
+ }
+
+ return $buffer;
+ }
+
+ /**
+ * Attach a template to a hook
+ *
+ * @access public
+ * @param string $hook
+ * @param string $template
+ * @return \Helper\Hook
+ */
+ public function attach($hook, $template)
+ {
+ $this->hooks[$hook] = $template;
+ return $this;
+ }
+}
diff --git a/app/Integration/Mailgun.php b/app/Integration/Mailgun.php
index 1451b211..076c311a 100644
--- a/app/Integration/Mailgun.php
+++ b/app/Integration/Mailgun.php
@@ -2,7 +2,6 @@
namespace Integration;
-use HTML_To_Markdown;
use Core\Tool;
/**
@@ -76,8 +75,7 @@ class Mailgun extends \Core\Base
// Get the Markdown contents
if (! empty($payload['stripped-html'])) {
- $markdown = new HTML_To_Markdown($payload['stripped-html'], array('strip_tags' => true));
- $description = $markdown->output();
+ $description = $this->htmlConverter->convert($payload['stripped-html']);
}
else if (! empty($payload['stripped-text'])) {
$description = $payload['stripped-text'];
diff --git a/app/Integration/Postmark.php b/app/Integration/Postmark.php
index dbb70aee..05bdf58b 100644
--- a/app/Integration/Postmark.php
+++ b/app/Integration/Postmark.php
@@ -2,8 +2,6 @@
namespace Integration;
-use HTML_To_Markdown;
-
/**
* Postmark integration
*
@@ -76,8 +74,7 @@ class Postmark extends \Core\Base
// Get the Markdown contents
if (! empty($payload['HtmlBody'])) {
- $markdown = new HTML_To_Markdown($payload['HtmlBody'], array('strip_tags' => true));
- $description = $markdown->output();
+ $description = $this->htmlConverter->convert($payload['HtmlBody']);
}
else if (! empty($payload['TextBody'])) {
$description = $payload['TextBody'];
diff --git a/app/Integration/Sendgrid.php b/app/Integration/Sendgrid.php
index 902749f6..fd58342a 100644
--- a/app/Integration/Sendgrid.php
+++ b/app/Integration/Sendgrid.php
@@ -2,7 +2,6 @@
namespace Integration;
-use HTML_To_Markdown;
use Core\Tool;
/**
@@ -79,8 +78,7 @@ class Sendgrid extends \Core\Base
// Get the Markdown contents
if (! empty($payload['html'])) {
- $markdown = new HTML_To_Markdown($payload['html'], array('strip_tags' => true));
- $description = $markdown->output();
+ $description = $this->htmlConverter->convert($payload['html']);
}
else if (! empty($payload['text'])) {
$description = $payload['text'];
diff --git a/app/Locale/cs_CZ/translations.php b/app/Locale/cs_CZ/translations.php
index 557a62cc..8cea7367 100644
--- a/app/Locale/cs_CZ/translations.php
+++ b/app/Locale/cs_CZ/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Vzdálený',
'Enabled' => 'Povoleno',
'Disabled' => 'Zakázáno',
- 'Google account linked' => 'Google úÄet byl propojen',
- 'Github account linked' => 'Mit Githubaccount verbunden',
'Username:' => 'Uživatelské jméno:',
'Name:' => 'Jméno:',
'Email:' => 'e-mail',
@@ -667,17 +665,7 @@ return array(
'Horizontal scrolling' => 'Horizontální rolování',
'Compact/wide view' => 'Kompaktní/plné zobrazení',
'No results match:' => 'Žádná shoda:',
- 'Remove hourly rate' => 'Stundensatz entfernen',
- 'Do you really want to remove this hourly rate?' => 'Opravdu chcete odstranit tuto hodinovou sazbu?',
- 'Hourly rates' => 'Hodinové sazby',
- 'Hourly rate' => 'Hodinová sazba',
'Currency' => 'Měna',
- 'Effective date' => 'Datum úÄinnosti',
- 'Add new rate' => 'Přidat novou hodinovou sazbu',
- 'Rate removed successfully.' => 'Sazba byla úspěšně odstraněna',
- 'Unable to remove this rate.' => 'Sazbu nelze odstranit.',
- 'Unable to save the hourly rate.' => 'Hodinovou sazbu nelze uložit',
- 'Hourly rate created successfully.' => 'Hodinová sazba byla úspěšně vytvořena.',
'Start time' => 'PoÄáteÄní datum',
'End time' => 'KoneÄné datum',
'Comment' => 'Komentář',
@@ -703,34 +691,18 @@ return array(
'Files' => 'Soubory',
'Images' => 'Obrázky',
'Private project' => 'Soukromý projekt',
- 'Amount' => 'Částka',
// 'AUD - Australian Dollar' => '',
- 'Budget' => 'RozpoÄet',
- 'Budget line' => 'Položka rozpoÄtu',
- 'Budget line removed successfully.' => 'Položka rozpoÄtu byla odstranÄ›na',
- 'Budget lines' => 'Položky rozpoÄtu',
// 'CAD - Canadian Dollar' => '',
// 'CHF - Swiss Francs' => '',
- 'Cost' => 'Cena',
- 'Cost breakdown' => 'Rozpis nákladů',
'Custom Stylesheet' => 'Vlastní šablony stylů',
'download' => 'Stáhnout',
- 'Do you really want to remove this budget line?' => 'Opravdu chcete odstranit tuto rozpoÄtovou řádku?',
'EUR - Euro' => 'EUR - Euro',
- 'Expenses' => 'Náklady',
'GBP - British Pound' => 'GBP - Britská Libra',
'INR - Indian Rupee' => 'INR - Indische Rupien',
'JPY - Japanese Yen' => 'JPY - Japanischer Yen',
- 'New budget line' => 'Nová položka rozpoÄtu',
'NZD - New Zealand Dollar' => 'NZD - Neuseeland-Dollar',
- 'Remove a budget line' => 'Budgetlinie entfernen',
- 'Remove budget line' => 'Budgetlinie entfernen',
'RSD - Serbian dinar' => 'RSD - Serbische Dinar',
- 'The budget line have been created successfully.' => 'Položka rozpoÄtu byla úspěšnÄ› vytvoÅ™ena.',
- 'Unable to create the budget line.' => 'Nelze vytvoÅ™it rozpoÄtovou řádku.',
- 'Unable to remove this budget line.' => 'Nelze vyjmout rozpoÄtovou řádku.',
'USD - US Dollar' => 'USD - US Dollar',
- 'Remaining' => 'Zbývající',
'Destination column' => 'Cílový sloupec',
'Move the task to another column when assigned to a user' => 'Přesunout úkol do jiného sloupce, když je úkol přiřazen uživateli.',
'Move the task to another column when assignee is cleared' => 'Přesunout úkol do jiného sloupce, když je pověření uživatele vymazáno.',
@@ -746,7 +718,6 @@ return array(
'Rate' => 'Kurz',
'Change reference currency' => 'ZmÄ›nit referenÄní mÄ›nu',
'Add a new currency rate' => 'Přidat nový směnný kurz',
- 'Currency rates are used to calculate project budget.' => 'MÄ›nové sazby se používají k výpoÄtu rozpoÄtu projektu.',
'Reference currency' => 'ReferenÄní mÄ›na',
'The currency rate have been added successfully.' => 'Směnný kurz byl úspěšně přidán.',
'Unable to add this currency rate.' => 'Nelze přidat tento směnný kurz',
@@ -878,9 +849,6 @@ return array(
'%s moved the task #%d to the first swimlane' => '%s hat die Aufgabe #%d in die erste Swimlane verschoben',
'%s moved the task #%d to the swimlane "%s"' => '%s hat die Aufgabe #%d in die Swimlane "%s" verschoben',
// 'Swimlane' => '',
- 'Budget overview' => 'Budget Ãœbersicht',
- 'Type' => 'Typ',
- 'There is not enough data to show something.' => 'Es gibt nicht genug Daten für die Anzeige',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Locale/da_DK/translations.php b/app/Locale/da_DK/translations.php
index 6a41f065..027b22c5 100644
--- a/app/Locale/da_DK/translations.php
+++ b/app/Locale/da_DK/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Remote',
'Enabled' => 'Aktiv',
'Disabled' => 'Deaktiveret',
- 'Google account linked' => 'Google-konto forbundet',
- 'Github account linked' => 'Github-konto forbundet',
'Username:' => 'Brugernavn',
'Name:' => 'Navn:',
'Email:' => 'Email:',
@@ -667,17 +665,7 @@ return array(
// 'Horizontal scrolling' => '',
// 'Compact/wide view' => '',
// 'No results match:' => '',
- // 'Remove hourly rate' => '',
- // 'Do you really want to remove this hourly rate?' => '',
- // 'Hourly rates' => '',
- // 'Hourly rate' => '',
// 'Currency' => '',
- // 'Effective date' => '',
- // 'Add new rate' => '',
- // 'Rate removed successfully.' => '',
- // 'Unable to remove this rate.' => '',
- // 'Unable to save the hourly rate.' => '',
- // 'Hourly rate created successfully.' => '',
// 'Start time' => '',
// 'End time' => '',
// 'Comment' => '',
@@ -703,34 +691,18 @@ return array(
// 'Files' => '',
// 'Images' => '',
// 'Private project' => '',
- // 'Amount' => '',
// 'AUD - Australian Dollar' => '',
- // 'Budget' => '',
- // 'Budget line' => '',
- // 'Budget line removed successfully.' => '',
- // 'Budget lines' => '',
// 'CAD - Canadian Dollar' => '',
// 'CHF - Swiss Francs' => '',
- // 'Cost' => '',
- // 'Cost breakdown' => '',
// 'Custom Stylesheet' => '',
// 'download' => '',
- // 'Do you really want to remove this budget line?' => '',
// 'EUR - Euro' => '',
- // 'Expenses' => '',
// 'GBP - British Pound' => '',
// 'INR - Indian Rupee' => '',
// 'JPY - Japanese Yen' => '',
- // 'New budget line' => '',
// 'NZD - New Zealand Dollar' => '',
- // 'Remove a budget line' => '',
- // 'Remove budget line' => '',
// 'RSD - Serbian dinar' => '',
- // 'The budget line have been created successfully.' => '',
- // 'Unable to create the budget line.' => '',
- // 'Unable to remove this budget line.' => '',
// 'USD - US Dollar' => '',
- // 'Remaining' => '',
// 'Destination column' => '',
// 'Move the task to another column when assigned to a user' => '',
// 'Move the task to another column when assignee is cleared' => '',
@@ -746,7 +718,6 @@ return array(
// 'Rate' => '',
// 'Change reference currency' => '',
// 'Add a new currency rate' => '',
- // 'Currency rates are used to calculate project budget.' => '',
// 'Reference currency' => '',
// 'The currency rate have been added successfully.' => '',
// 'Unable to add this currency rate.' => '',
@@ -878,9 +849,6 @@ return array(
// '%s moved the task #%d to the first swimlane' => '',
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
- // 'Budget overview' => '',
- // 'Type' => '',
- // 'There is not enough data to show something.' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Locale/de_DE/translations.php b/app/Locale/de_DE/translations.php
index 7b38e9fc..0b1df2e7 100644
--- a/app/Locale/de_DE/translations.php
+++ b/app/Locale/de_DE/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Remote',
'Enabled' => 'angeschaltet',
'Disabled' => 'abgeschaltet',
- 'Google account linked' => 'Mit Google-Account verbunden',
- 'Github account linked' => 'Mit Github-Account verbunden',
'Username:' => 'Benutzername',
'Name:' => 'Name',
'Email:' => 'E-Mail',
@@ -667,17 +665,7 @@ return array(
'Horizontal scrolling' => 'Horizontales Scrollen',
'Compact/wide view' => 'Kompakt/Breite-Ansicht',
'No results match:' => 'Keine Ergebnisse:',
- 'Remove hourly rate' => 'Stundensatz entfernen',
- 'Do you really want to remove this hourly rate?' => 'Diesen Stundensatz wirklich entfernen?',
- 'Hourly rates' => 'Stundensätze',
- 'Hourly rate' => 'Stundensatz',
'Currency' => 'Währung',
- 'Effective date' => 'Inkraftsetzung',
- 'Add new rate' => 'Neue Rate hinzufügen',
- 'Rate removed successfully.' => 'Rate erfolgreich entfernt',
- 'Unable to remove this rate.' => 'Nicht in der Lage, diese Rate zu entfernen.',
- 'Unable to save the hourly rate.' => 'Nicht in der Lage, diese Rate zu speichern',
- 'Hourly rate created successfully.' => 'Stundensatz erfolgreich angelegt.',
'Start time' => 'Startzeit',
'End time' => 'Endzeit',
'Comment' => 'Kommentar',
@@ -703,34 +691,18 @@ return array(
'Files' => 'Dateien',
'Images' => 'Bilder',
'Private project' => 'privates Projekt',
- 'Amount' => 'Betrag',
'AUD - Australian Dollar' => 'AUD - Australische Dollar',
- 'Budget' => 'Budget',
- 'Budget line' => 'Budgetlinie',
- 'Budget line removed successfully.' => 'Budgetlinie erfolgreich entfernt',
- 'Budget lines' => 'Budgetlinien',
'CAD - Canadian Dollar' => 'CAD - Kanadische Dollar',
'CHF - Swiss Francs' => 'CHF - Schweizer Franken',
- 'Cost' => 'Kosten',
- 'Cost breakdown' => 'Kostenaufschlüsselung',
'Custom Stylesheet' => 'benutzerdefiniertes Stylesheet',
'download' => 'Download',
- 'Do you really want to remove this budget line?' => 'Soll diese Budgetlinie wirklich entfernt werden?',
'EUR - Euro' => 'EUR - Euro',
- 'Expenses' => 'Kosten',
'GBP - British Pound' => 'GBP - Britische Pfund',
'INR - Indian Rupee' => 'INR - Indische Rupien',
'JPY - Japanese Yen' => 'JPY - Japanische Yen',
- 'New budget line' => 'Neue Budgetlinie',
'NZD - New Zealand Dollar' => 'NZD - Neuseeland-Dollar',
- 'Remove a budget line' => 'Budgetlinie entfernen',
- 'Remove budget line' => 'Budgetlinie entfernen',
'RSD - Serbian dinar' => 'RSD - Serbische Dinar',
- 'The budget line have been created successfully.' => 'Die Budgetlinie wurde erfolgreich angelegt.',
- 'Unable to create the budget line.' => 'Budgetlinie konnte nicht erstellt werden.',
- 'Unable to remove this budget line.' => 'Budgetlinie konnte nicht gelöscht werden.',
'USD - US Dollar' => 'USD - US-Dollar',
- 'Remaining' => 'Verbleibend',
'Destination column' => 'Zielspalte',
'Move the task to another column when assigned to a user' => 'Aufgabe in eine andere Spalte verschieben, wenn ein User zugeordnet wurde.',
'Move the task to another column when assignee is cleared' => 'Aufgabe in eine andere Spalte verschieben, wenn die Zuordnung gelöscht wurde.',
@@ -746,7 +718,6 @@ return array(
'Rate' => 'Kurse',
'Change reference currency' => 'Referenzwährung ändern',
'Add a new currency rate' => 'Neuen Währungskurs hinzufügen',
- 'Currency rates are used to calculate project budget.' => 'Währungskurse werden verwendet, um das Projektbudget zu berechnen.',
'Reference currency' => 'Referenzwährung',
'The currency rate have been added successfully.' => 'Der Währungskurs wurde erfolgreich hinzugefügt.',
'Unable to add this currency rate.' => 'Währungskurs konnte nicht hinzugefügt werden',
@@ -878,9 +849,6 @@ return array(
'%s moved the task #%d to the first swimlane' => '%s hat die Aufgabe #%d in die erste Swimlane verschoben',
'%s moved the task #%d to the swimlane "%s"' => '%s hat die Aufgabe #%d in die Swimlane "%s" verschoben',
// 'Swimlane' => '',
- 'Budget overview' => 'Budget-Ãœbersicht',
- 'Type' => 'Typ',
- 'There is not enough data to show something.' => 'Es gibt nicht genügend Daten für diese Anzeige',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Locale/es_ES/translations.php b/app/Locale/es_ES/translations.php
index ac7c0e4b..1e15d8c0 100644
--- a/app/Locale/es_ES/translations.php
+++ b/app/Locale/es_ES/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Remota',
'Enabled' => 'Activada',
'Disabled' => 'Desactivada',
- 'Google account linked' => 'Vinculada con Cuenta de Google',
- 'Github account linked' => 'Vinculada con Cuenta de Gitgub',
'Username:' => 'Nombre de Usuario:',
'Name:' => 'Nombre:',
'Email:' => 'Correo electrónico:',
@@ -667,17 +665,7 @@ return array(
'Horizontal scrolling' => 'Desplazamiento horizontal',
'Compact/wide view' => 'Vista compacta/amplia',
'No results match:' => 'No hay resultados coincidentes:',
- 'Remove hourly rate' => 'Quitar cobro horario',
- 'Do you really want to remove this hourly rate?' => '¿Realmente quire quitar el cobro horario?',
- 'Hourly rates' => 'Cobros horarios',
- 'Hourly rate' => 'Cobro horario',
'Currency' => 'Moneda',
- 'Effective date' => 'Fecha efectiva',
- 'Add new rate' => 'Añadir nuevo cobro',
- 'Rate removed successfully.' => 'Cobro quitado con éxito.',
- 'Unable to remove this rate.' => 'No pude quitar este cobro.',
- 'Unable to save the hourly rate.' => 'No pude grabar el cobro horario.',
- 'Hourly rate created successfully.' => 'Cobro horario creado con éxito',
'Start time' => 'Tiempo de inicio',
'End time' => 'Tiempo de fin',
'Comment' => 'Comentario',
@@ -703,34 +691,18 @@ return array(
'Files' => 'Ficheros',
'Images' => 'Imágenes',
'Private project' => 'Proyecto privado',
- 'Amount' => 'Cantidad',
'AUD - Australian Dollar' => 'AUD - Dólar australiano',
- 'Budget' => 'Presupuesto',
- 'Budget line' => 'Línea de presupuesto',
- 'Budget line removed successfully.' => 'Línea de presupuesto quitada con éxito',
- 'Budget lines' => 'Líneas de presupuesto',
'CAD - Canadian Dollar' => 'CAD - Dólar canadiense',
'CHF - Swiss Francs' => 'CHF - Francos suizos',
- 'Cost' => 'Costo',
- 'Cost breakdown' => 'Desglose de costes',
'Custom Stylesheet' => 'Hoja de estilo Personalizada',
'download' => 'descargar',
- 'Do you really want to remove this budget line?' => '¿Realmente quiere quitar esta línea de presupuesto?',
'EUR - Euro' => 'EUR - Euro',
- 'Expenses' => 'Gastos',
'GBP - British Pound' => 'GBP - Libra británica',
'INR - Indian Rupee' => 'INR - Rupias indúes',
'JPY - Japanese Yen' => 'JPY - Yen japonés',
- 'New budget line' => 'Nueva línea de presupuesto',
'NZD - New Zealand Dollar' => 'NZD - Dóloar neocelandés',
- 'Remove a budget line' => 'Quitar una línea de presupuesto',
- 'Remove budget line' => 'Quitar línea de presupuesto',
'RSD - Serbian dinar' => 'RSD - Dinar serbio',
- 'The budget line have been created successfully.' => 'Se ha creado la línea de presupuesto con éxito.',
- 'Unable to create the budget line.' => 'No pude crear la línea de presupuesto.',
- 'Unable to remove this budget line.' => 'No pude quitar esta línea de presupuesto.',
'USD - US Dollar' => 'USD - Dólar Estadounidense',
- 'Remaining' => 'Restante',
'Destination column' => 'Columna destino',
'Move the task to another column when assigned to a user' => 'Mover la tarea a otra columna al asignarse al usuario',
'Move the task to another column when assignee is cleared' => 'Mover la tarea a otra columna al quitar el concesionario',
@@ -746,7 +718,6 @@ return array(
'Rate' => 'Cambio',
'Change reference currency' => 'Cambiar moneda de referencia',
'Add a new currency rate' => 'Añadir nuevo cambio de moneda',
- 'Currency rates are used to calculate project budget.' => 'Se usan los cambios de moneda para calcular el presupuesto del proyecto.',
'Reference currency' => 'Moneda de referencia',
'The currency rate have been added successfully.' => 'Se ha añadido el cambio de moneda con éxito',
'Unable to add this currency rate.' => 'No pude añadir este cambio de moneda.',
@@ -878,9 +849,6 @@ return array(
'%s moved the task #%d to the first swimlane' => '%s movió la tarea #%d a la primera calle',
'%s moved the task #%d to the swimlane "%s"' => '%s movió la tarea #%d a la calle "%s"',
'Swimlane' => 'Calle',
- 'Budget overview' => 'Resumen del Presupuesto',
- 'Type' => 'Tipo',
- 'There is not enough data to show something.' => 'No hay datos suficientes como para mostrar algo.',
'Gravatar' => 'Gravatar',
'Hipchat' => 'Hipchat',
'Slack' => 'Desatendida',
@@ -1040,31 +1008,31 @@ return array(
'Shared project' => 'Proyecto compartido',
'Project managers' => 'Administradores de proyecto',
'Project members' => 'Miembros de proyecto',
- // 'Gantt chart for all projects' => '',
- // 'Projects list' => '',
- // 'Gantt chart for this project' => '',
- // 'Project board' => '',
- // 'End date:' => '',
- // 'There is no start date or end date for this project.' => '',
- // 'Projects Gantt chart' => '',
- // 'Start date: %s' => '',
- // 'End date: %s' => '',
- // 'Link type' => '',
- // 'Change task color when using a specific task link' => '',
- // 'Task link creation or modification' => '',
- // 'Login with my Gitlab Account' => '',
- // 'Milestone' => '',
- // 'Gitlab Authentication' => '',
- // 'Help on Gitlab authentication' => '',
- // 'Gitlab Id' => '',
- // 'Gitlab Account' => '',
- // 'Link my Gitlab Account' => '',
- // 'Unlink my Gitlab Account' => '',
- // 'Documentation: %s' => '',
- // 'Switch to the Gantt chart view' => '',
- // 'Reset the search/filter box' => '',
- // 'Documentation' => '',
- // 'Table of contents' => '',
- // 'Gantt' => '',
- // 'Help with project permissions' => '',
+ 'Gantt chart for all projects' => 'Diagrama de Gantt para todos los proyectos',
+ 'Projects list' => 'Lista de proyectos',
+ 'Gantt chart for this project' => 'Diagrama de Gantt para este proyecto',
+ 'Project board' => 'Tablero del proyecto',
+ 'End date:' => 'Fecha final',
+ 'There is no start date or end date for this project.' => 'No existe fecha de inicio o de fin para este proyecto.',
+ 'Projects Gantt chart' => 'Diagramas de Gantt de los proyectos',
+ 'Start date: %s' => 'Fecha inicial: %s',
+ 'End date: %s' => 'Fecha final: %s',
+ 'Link type' => 'Tipo de enlace',
+ 'Change task color when using a specific task link' => 'Cambiar colo de la tarea al usar un enlace específico a tarea',
+ 'Task link creation or modification' => 'Creación o modificación de enlace a tarea',
+ 'Login with my Gitlab Account' => 'Ingresar usando mi Cuenta en Gitlab',
+ 'Milestone' => 'Hito',
+ 'Gitlab Authentication' => 'Autenticación Gitlab',
+ 'Help on Gitlab authentication' => 'Ayuda con autenticación Gitlab',
+ 'Gitlab Id' => 'Id de Gitlab',
+ 'Gitlab Account' => 'Cuenta de Gitlab',
+ 'Link my Gitlab Account' => 'Enlazar con mi Cuenta en Gitlab',
+ 'Unlink my Gitlab Account' => 'Desenlazar con mi Cuenta en Gitlab',
+ 'Documentation: %s' => 'Documentación: %s',
+ 'Switch to the Gantt chart view' => 'Conmutar a vista de diagrama de Gantt',
+ 'Reset the search/filter box' => 'Limpiar la caja del filtro de búsqueda',
+ 'Documentation' => 'Documentación',
+ 'Table of contents' => 'Tabla de contenido',
+ 'Gantt' => 'Gantt',
+ 'Help with project permissions' => 'Ayuda con permisos del proyecto',
);
diff --git a/app/Locale/fi_FI/translations.php b/app/Locale/fi_FI/translations.php
index d8c749a3..da462831 100644
--- a/app/Locale/fi_FI/translations.php
+++ b/app/Locale/fi_FI/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Etä',
'Enabled' => 'Käytössä',
'Disabled' => 'Pois käytöstä',
- 'Google account linked' => 'Google-tili liitetty',
- 'Github account linked' => 'Github-tili liitetty',
'Username:' => 'Käyttäjänimi:',
'Name:' => 'Nimi:',
'Email:' => 'Sähköpostiosoite:',
@@ -667,17 +665,7 @@ return array(
// 'Horizontal scrolling' => '',
// 'Compact/wide view' => '',
// 'No results match:' => '',
- // 'Remove hourly rate' => '',
- // 'Do you really want to remove this hourly rate?' => '',
- // 'Hourly rates' => '',
- // 'Hourly rate' => '',
// 'Currency' => '',
- // 'Effective date' => '',
- // 'Add new rate' => '',
- // 'Rate removed successfully.' => '',
- // 'Unable to remove this rate.' => '',
- // 'Unable to save the hourly rate.' => '',
- // 'Hourly rate created successfully.' => '',
// 'Start time' => '',
// 'End time' => '',
// 'Comment' => '',
@@ -703,34 +691,18 @@ return array(
// 'Files' => '',
// 'Images' => '',
// 'Private project' => '',
- // 'Amount' => '',
// 'AUD - Australian Dollar' => '',
- // 'Budget' => '',
- // 'Budget line' => '',
- // 'Budget line removed successfully.' => '',
- // 'Budget lines' => '',
// 'CAD - Canadian Dollar' => '',
// 'CHF - Swiss Francs' => '',
- // 'Cost' => '',
- // 'Cost breakdown' => '',
// 'Custom Stylesheet' => '',
// 'download' => '',
- // 'Do you really want to remove this budget line?' => '',
// 'EUR - Euro' => '',
- // 'Expenses' => '',
// 'GBP - British Pound' => '',
// 'INR - Indian Rupee' => '',
// 'JPY - Japanese Yen' => '',
- // 'New budget line' => '',
// 'NZD - New Zealand Dollar' => '',
- // 'Remove a budget line' => '',
- // 'Remove budget line' => '',
// 'RSD - Serbian dinar' => '',
- // 'The budget line have been created successfully.' => '',
- // 'Unable to create the budget line.' => '',
- // 'Unable to remove this budget line.' => '',
// 'USD - US Dollar' => '',
- // 'Remaining' => '',
// 'Destination column' => '',
// 'Move the task to another column when assigned to a user' => '',
// 'Move the task to another column when assignee is cleared' => '',
@@ -746,7 +718,6 @@ return array(
// 'Rate' => '',
// 'Change reference currency' => '',
// 'Add a new currency rate' => '',
- // 'Currency rates are used to calculate project budget.' => '',
// 'Reference currency' => '',
// 'The currency rate have been added successfully.' => '',
// 'Unable to add this currency rate.' => '',
@@ -878,9 +849,6 @@ return array(
// '%s moved the task #%d to the first swimlane' => '',
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
- // 'Budget overview' => '',
- // 'Type' => '',
- // 'There is not enough data to show something.' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Locale/fr_FR/translations.php b/app/Locale/fr_FR/translations.php
index 0c95c7eb..848b7624 100644
--- a/app/Locale/fr_FR/translations.php
+++ b/app/Locale/fr_FR/translations.php
@@ -75,19 +75,19 @@ return array(
'Change columns' => 'Changer les colonnes',
'Add a new column' => 'Ajouter une nouvelle colonne',
'Title' => 'Titre',
- 'Nobody assigned' => 'Personne assigné',
+ 'Nobody assigned' => 'Personne assignée',
'Assigned to %s' => 'Assigné à %s',
'Remove a column' => 'Supprimer une colonne',
'Remove a column from a board' => 'Supprimer une colonne d\'un tableau',
'Unable to remove this column.' => 'Impossible de supprimer cette colonne.',
'Do you really want to remove this column: "%s"?' => 'Voulez vraiment supprimer cette colonne : « %s » ?',
- 'This action will REMOVE ALL TASKS associated to this column!' => 'Cette action va supprimer toutes les tâches associées à cette colonne !',
+ 'This action will REMOVE ALL TASKS associated to this column!' => 'Cette action va supprimer toutes les tâches associées à cette colonne !',
'Settings' => 'Préférences',
'Application settings' => 'Paramètres de l\'application',
'Language' => 'Langue',
'Webhook token:' => 'Jeton de securité pour les webhooks :',
'API token:' => 'Jeton de securité pour l\'API :',
- 'Database size:' => 'Taille de la base de données :',
+ 'Database size:' => 'Taille de la base de données :',
'Download the database' => 'Télécharger la base de données',
'Optimize the database' => 'Optimiser la base de données',
'(VACUUM command)' => '(Commande VACUUM)',
@@ -96,15 +96,15 @@ return array(
'Edit a task' => 'Modifier une tâche',
'Column' => 'Colonne',
'Color' => 'Couleur',
- 'Assignee' => 'Personne assignée',
+ 'Assignee' => 'Personne assigné',
'Create another task' => 'Créer une autre tâche',
'New task' => 'Nouvelle tâche',
'Open a task' => 'Ouvrir une tâche',
- 'Do you really want to open this task: "%s"?' => 'Voulez-vous vraiment ouvrir cette tâche : « %s » ?',
+ 'Do you really want to open this task: "%s"?' => 'Voulez-vous vraiment ouvrir cette tâche : « %s » ?',
'Back to the board' => 'Retour au tableau',
'Created on %B %e, %Y at %k:%M %p' => 'Créé le %d/%m/%Y à %H:%M',
'There is nobody assigned' => 'Il n\'y a personne d\'assigné à cette tâche',
- 'Column on the board:' => 'Colonne sur le tableau : ',
+ 'Column on the board:' => 'Colonne sur le tableau : ',
'Status is open' => 'État ouvert',
'Status is closed' => 'État fermé',
'Close this task' => 'Fermer cette tâche',
@@ -142,7 +142,7 @@ return array(
'Unable to open this task.' => 'Impossible d\'ouvrir cette tâche.',
'Task opened successfully.' => 'Tâche ouverte avec succès.',
'Unable to close this task.' => 'Impossible de fermer cette tâche.',
- 'Task closed successfully.' => 'Tâche fermé avec succès.',
+ 'Task closed successfully.' => 'Tâche fermée avec succès.',
'Unable to update your task.' => 'Impossible de modifier cette tâche.',
'Task updated successfully.' => 'Tâche mise à jour avec succès.',
'Unable to create your task.' => 'Impossible de créer cette tâche.',
@@ -167,11 +167,11 @@ return array(
'%d closed tasks' => '%d tâches terminées',
'No task for this project' => 'Aucune tâche pour ce projet',
'Public link' => 'Lien public',
- 'There is no column in your project!' => 'Il n\'y a aucune colonne dans votre projet !',
+ 'There is no column in your project!' => 'Il n\'y a aucune colonne dans votre projet !',
'Change assignee' => 'Changer la personne assignée',
'Change assignee for the task "%s"' => 'Changer la personne assignée pour la tâche « %s »',
'Timezone' => 'Fuseau horaire',
- 'Sorry, I didn\'t find this information in my database!' => 'Désolé, je n\'ai pas trouvé cette information dans ma base de données !',
+ 'Sorry, I didn\'t find this information in my database!' => 'Désolé, je n\'ai pas trouvé cette information dans ma base de données !',
'Page not found' => 'Page introuvable',
'Complexity' => 'Complexité',
'Task limit' => 'Tâches Max.',
@@ -197,7 +197,7 @@ return array(
'%B %e, %Y' => '%d %B %Y',
'%b %e, %Y' => '%d/%m/%Y',
'Automatic actions' => 'Actions automatisées',
- 'Your automatic action have been created successfully.' => 'Votre action automatisée a été ajouté avec succès.',
+ 'Your automatic action have been created successfully.' => 'Votre action automatisée a été ajoutée avec succès.',
'Unable to create your automatic action.' => 'Impossible de créer votre action automatisée.',
'Remove an action' => 'Supprimer une action',
'Unable to remove this action.' => 'Impossible de supprimer cette action',
@@ -210,7 +210,7 @@ return array(
'Action parameters' => 'Paramètres de l\'action',
'Action' => 'Action',
'Event' => 'Événement',
- 'When the selected event occurs execute the corresponding action.' => 'Lorsque l\'événement sélectionné se déclenche, executer l\'action correspondante.',
+ 'When the selected event occurs execute the corresponding action.' => 'Lorsque l\'événement sélectionné se déclenche, exécuter l\'action correspondante.',
'Next step' => 'Étape suivante',
'Define action parameters' => 'Définition des paramètres de l\'action',
'Save this action' => 'Sauvegarder cette action',
@@ -237,10 +237,10 @@ return array(
'Comment removed successfully.' => 'Commentaire supprimé avec succès.',
'Unable to remove this comment.' => 'Impossible de supprimer ce commentaire.',
'Do you really want to remove this comment?' => 'Voulez-vous vraiment supprimer ce commentaire ?',
- 'Only administrators or the creator of the comment can access to this page.' => 'Uniquement les administrateurs ou le créateur du commentaire peuvent accéder à cette page.',
+ 'Only administrators or the creator of the comment can access to this page.' => 'Seuls les administrateurs ou le créateur du commentaire peuvent accéder à cette page.',
'Current password for the user "%s"' => 'Mot de passe actuel pour l\'utilisateur « %s »',
'The current password is required' => 'Le mot de passe actuel est obligatoire',
- 'Wrong password' => 'Mauvais mot de passe',
+ 'Wrong password' => 'Mot de passe invalide',
'Unknown' => 'Inconnu',
'Last logins' => 'Dernières connexions',
'Login date' => 'Date de connexion',
@@ -263,10 +263,10 @@ return array(
'%d comments' => '%d commentaires',
'%d comment' => '%d commentaire',
'Email address invalid' => 'Adresse email invalide',
- 'Your external account is not linked anymore to your profile.' => 'Votre compte externe n\'est plus relié à votre profile.',
+ 'Your external account is not linked anymore to your profile.' => 'Votre compte externe n\'est plus relié à votre profil.',
'Unable to unlink your external account.' => 'Impossible de supprimer votre compte externe.',
- 'External authentication failed' => 'Authentification externe échouée',
- 'Your external account is linked to your profile successfully.' => 'Votre compte externe est désormais lié à votre profile.',
+ 'External authentication failed' => 'L’authentification externe a échoué',
+ 'Your external account is linked to your profile successfully.' => 'Votre compte externe est désormais lié à votre profil.',
'Email' => 'Email',
'Link my Google Account' => 'Lier mon compte Google',
'Unlink my Google Account' => 'Ne plus utiliser mon compte Google',
@@ -283,7 +283,7 @@ return array(
'Category:' => 'Catégorie :',
'Categories' => 'Catégories',
'Category not found.' => 'Catégorie introuvable',
- 'Your category have been created successfully.' => 'Votre catégorie a été créé avec succès.',
+ 'Your category have been created successfully.' => 'Votre catégorie a été créée avec succès.',
'Unable to create your category.' => 'Impossible de créer votre catégorie.',
'Your category have been updated successfully.' => 'Votre catégorie a été mise à jour avec succès.',
'Unable to update your category.' => 'Impossible de mettre à jour votre catégorie.',
@@ -311,13 +311,13 @@ return array(
'Summary' => 'Résumé',
'Time tracking' => 'Suivi du temps',
'Estimate:' => 'Estimation :',
- 'Spent:' => 'Passé :',
+ 'Spent:' => 'Passé :',
'Do you really want to remove this sub-task?' => 'Voulez-vous vraiment supprimer cette sous-tâche ?',
- 'Remaining:' => 'Restant :',
+ 'Remaining:' => 'Restant :',
'hours' => 'heures',
'spent' => 'passé',
'estimated' => 'estimé',
- 'Sub-Tasks' => 'Sous-Tâches',
+ 'Sub-Tasks' => 'Sous-tâches',
'Add a sub-task' => 'Ajouter une sous-tâche',
'Original estimate' => 'Estimation originale',
'Create another sub-task' => 'Créer une autre sous-tâche',
@@ -332,8 +332,8 @@ return array(
'Sub-task updated successfully.' => 'Sous-tâche mise à jour avec succès.',
'Unable to update your sub-task.' => 'Impossible de mettre à jour votre sous-tâche.',
'Unable to create your sub-task.' => 'Impossible de créer votre sous-tâche.',
- 'Sub-task added successfully.' => 'Sous-tâche ajouté avec succès.',
- 'Maximum size: ' => 'Taille maximum : ',
+ 'Sub-task added successfully.' => 'Sous-tâche ajoutée avec succès.',
+ 'Maximum size: ' => 'Taille maximum : ',
'Unable to upload the file.' => 'Impossible de transférer le fichier.',
'Display another project' => 'Afficher un autre projet',
'Login with my Github Account' => 'Se connecter avec mon compte Github',
@@ -397,8 +397,6 @@ return array(
'Remote' => 'Distant',
'Enabled' => 'Activé',
'Disabled' => 'Désactivé',
- 'Google account linked' => 'Compte Google attaché',
- 'Github account linked' => 'Compte Github attaché',
'Username:' => 'Nom d\'utilisateur :',
'Name:' => 'Nom :',
'Email:' => 'Email :',
@@ -447,10 +445,10 @@ return array(
'%s moved the task #%d to the position %d in the column "%s"' => '%s a déplacé la tâche n°%d à la position n°%d dans la colonne « %s »',
'Activity' => 'Activité',
'Default values are "%s"' => 'Les valeurs par défaut sont « %s »',
- 'Default columns for new projects (Comma-separated)' => 'Colonnes par défaut pour les nouveaux projets (séparé par des virgules)',
- 'Task assignee change' => 'Modification de la personne assignée sur une tâche',
- '%s change the assignee of the task #%d to %s' => '%s a changé la personne assignée sur la tâche n˚%d pour %s',
- '%s changed the assignee of the task %s to %s' => '%s a changé la personne assignée sur la tâche %s pour %s',
+ 'Default columns for new projects (Comma-separated)' => 'Colonnes par défaut pour les nouveaux projets (séparation par des virgules)',
+ 'Task assignee change' => 'Modification de la personne assignée à une tâche',
+ '%s change the assignee of the task #%d to %s' => '%s a changé la personne assignée à la tâche n˚%d pour %s',
+ '%s changed the assignee of the task %s to %s' => '%s a changé la personne assignée à la tâche %s pour %s',
'New password for the user "%s"' => 'Nouveau mot de passe pour l\'utilisateur « %s »',
'Choose an event' => 'Choisir un événement',
'Github commit received' => 'Commit reçu via Github',
@@ -466,7 +464,7 @@ return array(
'Reference: %s' => 'Référence : %s',
'Label' => 'Libellé',
'Database' => 'Base de données',
- 'About' => 'A propos',
+ 'About' => 'À propos',
'Database driver:' => 'Type de base de données :',
'Board settings' => 'Paramètres du tableau',
'URL and token' => 'URL et jeton de sécurité',
@@ -529,12 +527,12 @@ return array(
'Previous' => 'Précédent',
'The id must be an integer' => 'L\'id doit être un entier',
'The project id must be an integer' => 'L\'id du projet doit être un entier',
- 'The status must be an integer' => 'Le status doit être un entier',
+ 'The status must be an integer' => 'Le statut doit être un entier',
'The subtask id is required' => 'L\'id de la sous-tâche est obligatoire',
- 'The subtask id must be an integer' => 'L\'id de la sous-tâche doit être en entier',
+ 'The subtask id must be an integer' => 'L\'id de la sous-tâche doit être un entier',
'The task id is required' => 'L\'id de la tâche est obligatoire',
- 'The task id must be an integer' => 'L\'id de la tâche doit être en entier',
- 'The user id must be an integer' => 'L\'id de l\'utilisateur doit être en entier',
+ 'The task id must be an integer' => 'L\'id de la tâche doit être un entier',
+ 'The user id must be an integer' => 'L\'id de l\'utilisateur doit être un entier',
'This value is required' => 'Cette valeur est obligatoire',
'This value must be numeric' => 'Cette valeur doit être numérique',
'Unable to create this task.' => 'Impossible de créer cette tâche',
@@ -552,7 +550,7 @@ return array(
'Add a new swimlane' => 'Ajouter une nouvelle swimlane',
'Change default swimlane' => 'Modifier la swimlane par défaut',
'Default swimlane' => 'Swimlane par défaut',
- 'Do you really want to remove this swimlane: "%s"?' => 'Voulez-vous vraiment supprimer cette swimlane : « %s » ?',
+ 'Do you really want to remove this swimlane: "%s"?' => 'Voulez-vous vraiment supprimer cette swimlane : « %s » ?',
'Inactive swimlanes' => 'Swimlanes inactives',
'Set project manager' => 'Mettre chef de projet',
'Set project member' => 'Mettre membre du projet',
@@ -570,7 +568,7 @@ return array(
'Unable to update this swimlane.' => 'Impossible de mettre à jour cette swimlane.',
'Your swimlane have been created successfully.' => 'Votre swimlane a été créée avec succès.',
'Example: "Bug, Feature Request, Improvement"' => 'Exemple: « Incident, Demande de fonctionnalité, Amélioration »',
- 'Default categories for new projects (Comma-separated)' => 'Catégories par défaut pour les nouveaux projets (séparé par des virgules)',
+ 'Default categories for new projects (Comma-separated)' => 'Catégories par défaut pour les nouveaux projets (séparation par des virgules)',
'Gitlab commit received' => 'Commit reçu via Gitlab',
'Gitlab issue opened' => 'Ouverture d\'un ticket sur Gitlab',
'Gitlab issue closed' => 'Fermeture d\'un ticket sur Gitlab',
@@ -604,25 +602,25 @@ return array(
'User dashboard' => 'Tableau de bord de l\'utilisateur',
'Allow only one subtask in progress at the same time for a user' => 'Autoriser une seule sous-tâche en progrès en même temps pour un utilisateur',
'Edit column "%s"' => 'Modifier la colonne « %s »',
- 'Select the new status of the subtask: "%s"' => 'Selectionnez le nouveau statut de la sous-tâche : « %s »',
+ 'Select the new status of the subtask: "%s"' => 'Selectionnez le nouveau statut de la sous-tâche : « %s »',
'Subtask timesheet' => 'Feuille de temps des sous-tâches',
'There is nothing to show.' => 'Il n\'y a rien à montrer.',
'Time Tracking' => 'Feuille de temps',
'You already have one subtask in progress' => 'Vous avez déjà une sous-tâche en progrès',
- 'Which parts of the project do you want to duplicate?' => 'Quelles parties du projet voulez-vous dupliquer ?',
+ 'Which parts of the project do you want to duplicate?' => 'Quelles parties du projet voulez-vous dupliquer ?',
'Disallow login form' => 'Interdir le formulaire d\'authentification',
'Bitbucket commit received' => 'Commit reçu via Bitbucket',
'Bitbucket webhooks' => 'Webhook Bitbucket',
'Help on Bitbucket webhooks' => 'Aide sur les webhooks Bitbucket',
'Start' => 'Début',
'End' => 'Fin',
- 'Task age in days' => 'Age de la tâche en jours',
+ 'Task age in days' => 'Âge de la tâche en jours',
'Days in this column' => 'Jours dans cette colonne',
'%dd' => '%dj',
'Add a link' => 'Ajouter un lien',
'Add a new link' => 'Ajouter un nouveau lien',
- 'Do you really want to remove this link: "%s"?' => 'Voulez-vous vraiment supprimer ce lien : « %s » ?',
- 'Do you really want to remove this link with task #%d?' => 'Voulez-vous vraiment supprimer ce lien avec la tâche n°%d ?',
+ 'Do you really want to remove this link: "%s"?' => 'Voulez-vous vraiment supprimer ce lien : « %s » ?',
+ 'Do you really want to remove this link with task #%d?' => 'Voulez-vous vraiment supprimer ce lien avec la tâche n°%d ?',
'Field required' => 'Champ obligatoire',
'Link added successfully.' => 'Lien créé avec succès.',
'Link updated successfully.' => 'Lien mis à jour avec succès.',
@@ -658,7 +656,7 @@ return array(
'Expand tasks' => 'Déplier les tâches',
'Collapse tasks' => 'Replier les tâches',
'Expand/collapse tasks' => 'Plier/déplier les tâches',
- 'Close dialog box' => 'Fermer une boite de dialogue',
+ 'Close dialog box' => 'Fermer une boîte de dialogue',
'Submit a form' => 'Enregistrer un formulaire',
'Board view' => 'Page du tableau',
'Keyboard shortcuts' => 'Raccourcis clavier',
@@ -669,17 +667,7 @@ return array(
'Horizontal scrolling' => 'Défilement horizontal',
'Compact/wide view' => 'Basculer entre la vue compacte et étendue',
'No results match:' => 'Aucun résultat :',
- 'Remove hourly rate' => 'Supprimer un taux horaire',
- 'Do you really want to remove this hourly rate?' => 'Voulez-vous vraiment supprimer ce taux horaire ?',
- 'Hourly rates' => 'Taux horaires',
- 'Hourly rate' => 'Taux horaire',
'Currency' => 'Devise',
- 'Effective date' => 'Date d\'effet',
- 'Add new rate' => 'Ajouter un nouveau taux horaire',
- 'Rate removed successfully.' => 'Taux horaire supprimé avec succès.',
- 'Unable to remove this rate.' => 'Impossible de supprimer ce taux horaire.',
- 'Unable to save the hourly rate.' => 'Impossible de sauvegarder ce taux horaire.',
- 'Hourly rate created successfully.' => 'Taux horaire créé avec succès.',
'Start time' => 'Date de début',
'End time' => 'Date de fin',
'Comment' => 'Commentaire',
@@ -701,38 +689,22 @@ return array(
'Do you really want to remove this time slot?' => 'Voulez-vous vraiment supprimer ce créneau horaire ?',
'Remove time slot' => 'Supprimer un créneau horaire',
'Add new time slot' => 'Ajouter un créneau horaire',
- 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => 'Ces horaires sont utilisés lorsque la case « Toute la journée » est cochée pour les heures d\'absences ou supplémentaires programmées.',
+ 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => 'Ces horaires sont utilisés lorsque la case « Toute la journée » est cochée pour les heures d\'absences ou supplémentaires programmées.',
'Files' => 'Fichiers',
'Images' => 'Images',
'Private project' => 'Projet privé',
- 'Amount' => 'Montant',
'AUD - Australian Dollar' => 'AUD - Dollar australien',
- 'Budget' => 'Budget',
- 'Budget line' => 'Ligne budgétaire',
- 'Budget line removed successfully.' => 'Ligne budgétaire supprimée avec succès.',
- 'Budget lines' => 'Lignes budgétaire',
'CAD - Canadian Dollar' => 'CAD - Dollar canadien',
'CHF - Swiss Francs' => 'CHF - Franc suisse',
- 'Cost' => 'Coût',
- 'Cost breakdown' => 'Détail des coûts',
'Custom Stylesheet' => 'Feuille de style personalisée',
'download' => 'télécharger',
- 'Do you really want to remove this budget line?' => 'Voulez-vous vraiment supprimer cette ligne budgétaire ?',
'EUR - Euro' => 'EUR - Euro',
- 'Expenses' => 'Dépenses',
'GBP - British Pound' => 'GBP - Livre sterling',
'INR - Indian Rupee' => 'INR - Roupie indienne',
'JPY - Japanese Yen' => 'JPY - Yen',
- 'New budget line' => 'Nouvelle ligne budgétaire',
'NZD - New Zealand Dollar' => 'NZD - Dollar néo-zélandais',
- 'Remove a budget line' => 'Supprimer une ligne budgétaire',
- 'Remove budget line' => 'Supprimer une ligne budgétaire',
'RSD - Serbian dinar' => 'RSD - Dinar serbe',
- 'The budget line have been created successfully.' => 'La ligne de budgétaire a été créée avec succès.',
- 'Unable to create the budget line.' => 'Impossible de créer cette ligne budgétaire.',
- 'Unable to remove this budget line.' => 'Impossible de supprimer cette ligne budgétaire.',
'USD - US Dollar' => 'USD - Dollar américain',
- 'Remaining' => 'Restant',
'Destination column' => 'Colonne de destination',
'Move the task to another column when assigned to a user' => 'Déplacer la tâche dans une autre colonne lorsque celle-ci est assignée à quelqu\'un',
'Move the task to another column when assignee is cleared' => 'Déplacer la tâche dans une autre colonne lorsque celle-ci n\'est plus assignée',
@@ -748,7 +720,6 @@ return array(
'Rate' => 'Taux',
'Change reference currency' => 'Changer la monnaie de référence',
'Add a new currency rate' => 'Ajouter un nouveau taux pour une devise',
- 'Currency rates are used to calculate project budget.' => 'Le cours des devises est utilisé pour calculer le budget des projets.',
'Reference currency' => 'Devise de référence',
'The currency rate have been added successfully.' => 'Le taux de change a été ajouté avec succès.',
'Unable to add this currency rate.' => 'Impossible d\'ajouter ce taux de change',
@@ -769,8 +740,8 @@ return array(
'Code' => 'Code',
'Two factor authentication' => 'Authentification à deux-facteurs',
'Enable/disable two factor authentication' => 'Activer/désactiver l\'authentification à deux-facteurs',
- 'This QR code contains the key URI: ' => 'Ce code QR contient l\'url de la clé : ',
- 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => 'Sauvegardez cette clé secrete dans votre logiciel TOTP (par exemple Google Authenticator ou FreeOTP).',
+ 'This QR code contains the key URI: ' => 'Ce code QR contient l\'url de la clé : ',
+ 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => 'Sauvegardez cette clé secrète dans votre logiciel TOTP (par exemple Google Authenticator ou FreeOTP).',
'Check my code' => 'Vérifier mon code',
'Secret key: ' => 'Clé secrète : ',
'Test your device' => 'Testez votre appareil',
@@ -796,9 +767,9 @@ return array(
'Sendgrid (incoming emails)' => 'Sendgrid (emails entrants)',
'Help on Sendgrid integration' => 'Aide sur l\'intégration avec Sendgrid',
'Disable two factor authentication' => 'Désactiver l\'authentification à deux facteurs',
- 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Voulez-vous vraiment désactiver l\'authentification à deux facteurs pour cet utilisateur : « %s » ?',
+ 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Voulez-vous vraiment désactiver l\'authentification à deux facteurs pour cet utilisateur : « %s » ?',
'Edit link' => 'Modifier un lien',
- 'Start to type task title...' => 'Entrez le titre de la tâche...',
+ 'Start to type task title...' => 'Entrez le titre de la tâche…',
'A task cannot be linked to itself' => 'Une tâche ne peut être liée à elle-même',
'The exact same link already exists' => 'Un lien identique existe déjà',
'Recurrent task is scheduled to be generated' => 'La tâche récurrente est programmée pour être créée',
@@ -814,17 +785,17 @@ return array(
'Timeframe to calculate new due date' => 'Échelle de temps pour calculer la nouvelle date d\'échéance',
'Base date to calculate new due date' => 'Date à utiliser pour calculer la nouvelle date d\'échéance',
'Action date' => 'Date de l\'action',
- 'Base date to calculate new due date: ' => 'Date utilisée pour calculer la nouvelle date d\'échéance : ',
- 'This task has created this child task: ' => 'Cette tâche a créée la tâche enfant : ',
+ 'Base date to calculate new due date: ' => 'Date utilisée pour calculer la nouvelle date d\'échéance : ',
+ 'This task has created this child task: ' => 'Cette tâche a créée la tâche enfant : ',
'Day(s)' => 'Jour(s)',
'Existing due date' => 'Date d\'échéance existante',
- 'Factor to calculate new due date: ' => 'Facteur pour calculer la nouvelle date d\'échéance : ',
+ 'Factor to calculate new due date: ' => 'Facteur pour calculer la nouvelle date d\'échéance : ',
'Month(s)' => 'Mois',
'Recurrence' => 'Récurrence',
- 'This task has been created by: ' => 'Cette tâche a été créée par :',
- 'Recurrent task has been generated:' => 'Une tâche récurrente a été générée :',
- 'Timeframe to calculate new due date: ' => 'Échelle de temps pour calculer la nouvelle date d\'échéance : ',
- 'Trigger to generate recurrent task: ' => 'Déclencheur pour générer la tâche récurrente : ',
+ 'This task has been created by: ' => 'Cette tâche a été créée par :',
+ 'Recurrent task has been generated:' => 'Une tâche récurrente a été générée :',
+ 'Timeframe to calculate new due date: ' => 'Échelle de temps pour calculer la nouvelle date d\'échéance : ',
+ 'Trigger to generate recurrent task: ' => 'Déclencheur pour générer la tâche récurrente : ',
'When task is closed' => 'Lorsque la tâche est fermée',
'When task is moved from first column' => 'Lorsque la tâche est déplacée en dehors de la première colonne',
'When task is moved to last column' => 'Lorsque la tâche est déplacée dans la dernière colonne',
@@ -836,7 +807,7 @@ return array(
'Jabber nickname' => 'Pseudonyme Jabber',
'Multi-user chat room' => 'Salon de discussion multi-utilisateurs',
'Help on Jabber integration' => 'Aide sur l\'intégration avec Jabber',
- 'The server address must use this format: "tcp://hostname:5222"' => 'L\'adresse du serveur doit utiliser le format suivant : « tcp://hostname:5222 »',
+ 'The server address must use this format: "tcp://hostname:5222"' => 'L\'adresse du serveur doit utiliser le format suivant : « tcp://hostname:5222 »',
'Calendar settings' => 'Paramètres du calendrier',
'Project calendar view' => 'Vue en mode projet du calendrier',
'Project settings' => 'Paramètres du projet',
@@ -849,7 +820,7 @@ return array(
'iCal feed' => 'Abonnement iCal',
'Preferences' => 'Préférences',
'Security' => 'Sécurité',
- 'Two factor authentication disabled' => 'Authentification à deux facteurs désactivé',
+ 'Two factor authentication disabled' => 'Authentification à deux facteurs désactivée',
'Two factor authentication enabled' => 'Authentification à deux facteurs activée',
'Unable to update this user.' => 'Impossible de mettre à jour cet utilisateur.',
'There is no user management for private projects.' => 'Il n\'y a pas de gestion d\'utilisateurs pour les projets privés.',
@@ -862,8 +833,8 @@ return array(
'Commit made by @%s on Github' => 'Commit fait par @%s sur Github',
'By @%s on Github' => 'Par @%s sur Github',
'Commit made by @%s on Gitlab' => 'Commit fait par @%s sur Gitlab',
- 'Add a comment log when moving the task between columns' => 'Ajouter un commentaire d\'information lorsque une tâche est déplacée dans une autre colonnes',
- 'Move the task to another column when the category is changed' => 'Déplacer une tâche vers une autre colonne lorsque la catégorie a changée',
+ 'Add a comment log when moving the task between columns' => 'Ajouter un commentaire d\'information lorsque une tâche est déplacée dans une autre colonne',
+ 'Move the task to another column when the category is changed' => 'Déplacer une tâche vers une autre colonne lorsque la catégorie a changé',
'Send a task by email to someone' => 'Envoyer une tâche par email à quelqu\'un',
'Reopen a task' => 'Rouvrir une tâche',
'Bitbucket issue opened' => 'Ticket Bitbucket ouvert',
@@ -880,16 +851,13 @@ return array(
'%s moved the task #%d to the first swimlane' => '%s a déplacé la tâche n°%d dans la première swimlane',
'%s moved the task #%d to the swimlane "%s"' => '%s a déplacé la tâche n°%d dans la swimlane « %s »',
'Swimlane' => 'Swimlane',
- 'Budget overview' => 'Vue d\'ensemble du budget',
- 'Type' => 'Type',
- 'There is not enough data to show something.' => 'Il n\'y a pas assez de données pour montrer quelque chose.',
'Gravatar' => 'Gravatar',
'Hipchat' => 'Hipchat',
'Slack' => 'Slack',
'%s moved the task %s to the first swimlane' => '%s a déplacé la tâche %s dans la première swimlane',
- '%s moved the task %s to the swimlane "%s"' => '%s a déplacé la tâche %s dans la swimlane « %s »',
- 'This report contains all subtasks information for the given date range.' => 'Ce rapport contient les informations de toutes les sous-tâches pour la période selectionnée.',
- 'This report contains all tasks information for the given date range.' => 'Ce rapport contient les informations de toutes les tâches pour la période selectionnée.',
+ '%s moved the task %s to the swimlane "%s"' => '%s a déplacé la tâche %s dans la swimlane « %s »',
+ 'This report contains all subtasks information for the given date range.' => 'Ce rapport contient les informations de toutes les sous-tâches pour la période sélectionnée.',
+ 'This report contains all tasks information for the given date range.' => 'Ce rapport contient les informations de toutes les tâches pour la période sélectionnée.',
'Project activities for %s' => 'Activité des projets pour « %s »',
'view the board on Kanboard' => 'voir le tableau sur Kanboard',
'The task have been moved to the first swimlane' => 'La tâche a été déplacée dans la première swimlane',
@@ -907,15 +875,15 @@ return array(
'Recurrence settings have been modified' => 'Les réglages de la récurrence ont été modifiés',
'Time spent changed: %sh' => 'Le temps passé a été changé : %sh',
'Time estimated changed: %sh' => 'Le temps estimé a été changé : %sh',
- 'The field "%s" have been updated' => 'Le champ « %s » a été mis à jour',
+ 'The field "%s" have been updated' => 'Le champ « %s » a été mis à jour',
'The description have been modified' => 'La description a été modifiée',
- 'Do you really want to close the task "%s" as well as all subtasks?' => 'Voulez-vous vraiment fermer la tâche « %s » ainsi que toutes ses sous-tâches ?',
+ 'Do you really want to close the task "%s" as well as all subtasks?' => 'Voulez-vous vraiment fermer la tâche « %s » ainsi que toutes ses sous-tâches ?',
'Swimlane: %s' => 'Swimlane : %s',
'I want to receive notifications for:' => 'Je veux reçevoir les notifications pour :',
'All tasks' => 'Toutes les Tâches',
'Only for tasks assigned to me' => 'Seulement les tâches qui me sont assignées',
'Only for tasks created by me' => 'Seulement les tâches que j\'ai créées',
- 'Only for tasks created by me and assigned to me' => 'Seulement les tâches créées par moi-même et celles qui me sont asignées',
+ 'Only for tasks created by me and assigned to me' => 'Seulement les tâches créées par moi-même et celles qui me sont assignées',
'%A' => '%A',
'%b %e, %Y, %k:%M %p' => '%d/%m/%Y %H:%M',
'New due date: %B %e, %Y' => 'Nouvelle date d\'échéance : %d/%m/%Y',
@@ -995,7 +963,7 @@ return array(
'Github Id' => 'Identifiant Github',
'Remote user' => 'Utilisateur distant',
'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'Les utilisateurs distants ne stockent pas leur mot de passe dans la base de données de Kanboard, exemples : comptes LDAP, Github ou Google.',
- 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Si vous cochez la case « Interdir le formulaire d\'authentification », les identifiants entrés dans le formulaire d\'authentification seront ignorés.',
+ 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Si vous cochez la case « Interdire le formulaire d\'authentification », les identifiants entrés dans le formulaire d\'authentification seront ignorés.',
'By @%s on Gitlab' => 'Par @%s sur Gitlab',
'Gitlab issue comment created' => 'Commentaire créé sur un ticket Gitlab',
'New remote user' => 'Créer un utilisateur distant',
@@ -1023,14 +991,14 @@ return array(
'Sort by position' => 'Trier par position',
'Sort by date' => 'Trier par date',
'Add task' => 'Ajouter une tâche',
- 'Start date:' => 'Date de début :',
- 'Due date:' => 'Date d\'échéance :',
+ 'Start date:' => 'Date de début :',
+ 'Due date:' => 'Date d\'échéance :',
'There is no start date or due date for this task.' => 'Il n\'y a pas de date de début ou de date de fin pour cette tâche.',
'Moving or resizing a task will change the start and due date of the task.' => 'Déplacer ou redimensionner une tâche va changer la date de début et la date de fin de la tâche.',
- 'There is no task in your project.' => 'Il n\'y a aucun tâche dans votre projet.',
+ 'There is no task in your project.' => 'Il n\'y a aucune tâche dans votre projet.',
'Gantt chart' => 'Diagramme de Gantt',
- 'People who are project managers' => 'Personnes qui sont gestionnaire de projet',
- 'People who are project members' => 'Personnes qui sont membre de projet',
+ 'People who are project managers' => 'Personnes qui sont gestionnaires de projet',
+ 'People who are project members' => 'Personnes qui sont membres de projet',
'NOK - Norwegian Krone' => 'NOK - Couronne norvégienne',
'Show this column' => 'Montrer cette colonne',
'Hide this column' => 'Cacher cette colonne',
@@ -1043,14 +1011,14 @@ return array(
'Project managers' => 'Gestionnaires de projet',
'Project members' => 'Membres de projet',
'Gantt chart for all projects' => 'Diagramme de Gantt pour tous les projets',
- 'Projects list' => 'List des projets',
+ 'Projects list' => 'Liste des projets',
'Gantt chart for this project' => 'Diagramme de Gantt pour ce projet',
'Project board' => 'Tableau du projet',
- 'End date:' => 'Date de fin :',
+ 'End date:' => 'Date de fin :',
'There is no start date or end date for this project.' => 'Il n\'y a pas de date de début ou de date de fin pour ce projet.',
'Projects Gantt chart' => 'Diagramme de Gantt des projets',
- 'Start date: %s' => 'Date de début : %s',
- 'End date: %s' => 'Date de fin : %s',
+ 'Start date: %s' => 'Date de début : %s',
+ 'End date: %s' => 'Date de fin : %s',
'Link type' => 'Type de lien',
'Change task color when using a specific task link' => 'Changer la couleur de la tâche lorsqu\'un lien spécifique est utilisé',
'Task link creation or modification' => 'Création ou modification d\'un lien sur une tâche',
diff --git a/app/Locale/hu_HU/translations.php b/app/Locale/hu_HU/translations.php
index b346f1e3..a3bbd8f5 100644
--- a/app/Locale/hu_HU/translations.php
+++ b/app/Locale/hu_HU/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Távoli',
'Enabled' => 'Engedélyezve',
'Disabled' => 'Letiltva',
- 'Google account linked' => 'Google fiók összekapcsolva',
- 'Github account linked' => 'Github fiók összekapcsolva',
'Username:' => 'Felhasználónév:',
'Name:' => 'Név:',
'Email:' => 'E-mail:',
@@ -667,17 +665,7 @@ return array(
'Horizontal scrolling' => 'Vízszintes görgetés',
'Compact/wide view' => 'Kompakt/széles nézet',
'No results match:' => 'Nincs találat:',
- 'Remove hourly rate' => 'Órabér törlése',
- 'Do you really want to remove this hourly rate?' => 'Valóban törölni kívánja az órabért?',
- 'Hourly rates' => 'Órabérek',
- 'Hourly rate' => 'Órabér',
'Currency' => 'Pénznem',
- 'Effective date' => 'Hatálybalépés ideje',
- 'Add new rate' => 'Új bér',
- 'Rate removed successfully.' => 'Bér sikeresen törölve.',
- 'Unable to remove this rate.' => 'Bér törlése sikertelen.',
- 'Unable to save the hourly rate.' => 'Órabér mentése sikertelen.',
- 'Hourly rate created successfully.' => 'Órabér sikeresen mentve.',
'Start time' => 'Kezdés ideje',
'End time' => 'Végzés ideje',
'Comment' => 'Megjegyzés',
@@ -703,34 +691,18 @@ return array(
'Files' => 'Fájlok',
'Images' => 'Képek',
'Private project' => 'Privát projekt',
- 'Amount' => 'Összeg',
'AUD - Australian Dollar' => 'AUD - Ausztrál dollár',
- 'Budget' => 'Költségvetés',
- 'Budget line' => 'Költségvetési tétel',
- 'Budget line removed successfully.' => 'Költségvetési tétel sikeresen törölve.',
- 'Budget lines' => 'Költségvetési tételek',
'CAD - Canadian Dollar' => 'CAD - Kanadai dollár',
'CHF - Swiss Francs' => 'CHF - Svájci frank',
- 'Cost' => 'Költség',
- 'Cost breakdown' => 'Költség visszaszámlálás',
'Custom Stylesheet' => 'Egyéni sítluslap',
'download' => 'letöltés',
- 'Do you really want to remove this budget line?' => 'Biztos törölni akarja ezt a költségvetési tételt?',
'EUR - Euro' => 'EUR - Euro',
- 'Expenses' => 'Kiadások',
'GBP - British Pound' => 'GBP - Angol font',
'INR - Indian Rupee' => 'INR - Indiai rúpia',
'JPY - Japanese Yen' => 'JPY - Japán Yen',
- 'New budget line' => 'Új költségvetési tétel',
'NZD - New Zealand Dollar' => 'NZD - Új-Zélandi dollár',
- 'Remove a budget line' => 'Költségvetési tétel törlése',
- 'Remove budget line' => 'Költségvetési tétel törlése',
'RSD - Serbian dinar' => 'RSD - Szerb dínár',
- 'The budget line have been created successfully.' => 'Költségvetési tétel sikeresen létrehozva.',
- 'Unable to create the budget line.' => 'Költségvetési tétel létrehozása sikertelen.',
- 'Unable to remove this budget line.' => 'Költségvetési tétel törlése sikertelen.',
'USD - US Dollar' => 'USD - Amerikai ollár',
- 'Remaining' => 'Maradék',
'Destination column' => 'Cél oszlop',
'Move the task to another column when assigned to a user' => 'Feladat másik oszlopba helyezése felhasználóhoz rendélés után',
'Move the task to another column when assignee is cleared' => 'Feladat másik oszlopba helyezése felhasználóhoz rendélés törlésekor',
@@ -746,7 +718,6 @@ return array(
// 'Rate' => '',
// 'Change reference currency' => '',
// 'Add a new currency rate' => '',
- // 'Currency rates are used to calculate project budget.' => '',
// 'Reference currency' => '',
// 'The currency rate have been added successfully.' => '',
// 'Unable to add this currency rate.' => '',
@@ -878,9 +849,6 @@ return array(
// '%s moved the task #%d to the first swimlane' => '',
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
- // 'Budget overview' => '',
- // 'Type' => '',
- // 'There is not enough data to show something.' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Locale/id_ID/translations.php b/app/Locale/id_ID/translations.php
new file mode 100644
index 00000000..a7b52c6f
--- /dev/null
+++ b/app/Locale/id_ID/translations.php
@@ -0,0 +1,1072 @@
+<?php
+
+return array(
+ 'number.decimals_separator' => ',',
+ 'number.thousands_separator' => ' ',
+ 'None' => 'Tidak satupun',
+ 'edit' => 'modifikasi',
+ 'Edit' => 'Modifikasi',
+ 'remove' => 'hapus',
+ 'Remove' => 'Hapus',
+ 'Update' => 'Perbaharui',
+ 'Yes' => 'Ya',
+ 'No' => 'Tidak',
+ 'cancel' => 'batal',
+ 'or' => 'atau',
+ 'Yellow' => 'Kuning',
+ 'Blue' => 'Biru',
+ 'Green' => 'Hijau',
+ 'Purple' => 'Ungu',
+ 'Red' => 'Merah',
+ 'Orange' => 'Jingga',
+ 'Grey' => 'Abu-abu',
+ 'Brown' => 'Coklat',
+ 'Deep Orange' => 'Oranye',
+ 'Dark Grey' => 'Abu-abu Gelap',
+ 'Pink' => 'Merah Muda',
+ 'Teal' => 'Teal',
+ 'Cyan'=> 'Sian',
+ 'Lime' => 'Lime',
+ 'Light Green' => 'Hijau Muda',
+ 'Amber' => 'Amber',
+ 'Save' => 'Simpan',
+ 'Login' => 'Masuk',
+ 'Official website:' => 'Situs resmi :',
+ 'Unassigned' => 'Belum ditugaskan',
+ 'View this task' => 'Lihat tugas ini',
+ 'Remove user' => 'Hapus pengguna',
+ 'Do you really want to remove this user: "%s"?' => 'Anda yakin akan menghapus pengguna ini : « %s » ?',
+ 'New user' => 'Pengguna baru',
+ 'All users' => 'Semua pengguna',
+ 'Username' => 'Nama pengguna',
+ 'Password' => 'Kata sandi',
+ 'Administrator' => 'Administrator',
+ 'Sign in' => 'Masuk',
+ 'Users' => 'Pengguna',
+ 'No user' => 'Tidak ada pengguna',
+ 'Forbidden' => 'Terlarang',
+ 'Access Forbidden' => 'Akses Dilarang',
+ 'Edit user' => 'Rubah Pengguna',
+ 'Logout' => 'Keluar',
+ 'Bad username or password' => 'Nama pengguna atau kata sandri buruk',
+ 'Edit project' => 'Rubah proyek',
+ 'Name' => 'Nama',
+ 'Projects' => 'Proyek',
+ 'No project' => 'Tidak ada proyek',
+ 'Project' => 'Proyek',
+ 'Status' => 'Status',
+ 'Tasks' => 'Tugas',
+ 'Board' => 'Papan',
+ 'Actions' => 'Tindakan',
+ 'Inactive' => 'Non Aktif',
+ 'Active' => 'Aktif',
+ 'Add this column' => 'Tambahkan kolom ini',
+ '%d tasks on the board' => '%d tugas di papan',
+ '%d tasks in total' => '%d tugas di total',
+ 'Unable to update this board.' => 'Tidak dapat memperbaharui papan ini',
+ 'Edit board' => 'Rubah papan',
+ 'Disable' => 'Nonaktifkan',
+ 'Enable' => 'Aktifkan',
+ 'New project' => 'Proyek Baru',
+ 'Do you really want to remove this project: "%s"?' => 'Apakah anda yakin akan menghapus proyek ini : « %s » ?',
+ 'Remove project' => 'Hapus proyek',
+ 'Edit the board for "%s"' => 'Rubah papan untuk « %s »',
+ 'All projects' => 'Semua proyek',
+ 'Change columns' => 'Rubah kolom',
+ 'Add a new column' => 'Tambah kolom baru',
+ 'Title' => 'Judul',
+ 'Nobody assigned' => 'Tidak ada yang ditugaskan',
+ 'Assigned to %s' => 'Ditugaskan ke %s',
+ 'Remove a column' => 'Hapus kolom',
+ 'Remove a column from a board' => 'Hapus kolom dari papan',
+ 'Unable to remove this column.' => 'Tidak dapat menghapus kolom ini.',
+ 'Do you really want to remove this column: "%s"?' => 'Apakah anda yakin akan menghapus kolom ini : « %s » ?',
+ 'This action will REMOVE ALL TASKS associated to this column!' => 'tindakan ini akan MENGHAPUS SEMUA TUGAS yang terkait dengan kolom ini!',
+ 'Settings' => 'Pengaturan',
+ 'Application settings' => 'Pengaturan aplikasi',
+ 'Language' => 'Bahasa',
+ 'Webhook token:' => 'Token webhook :',
+ 'API token:' => 'Token API :',
+ 'Database size:' => 'Ukuran basis data :',
+ 'Download the database' => 'Unduh basis data',
+ 'Optimize the database' => 'Optimasi basis data',
+ '(VACUUM command)' => '(perintah VACUUM)',
+ '(Gzip compressed Sqlite file)' => '(File Sqlite yang terkompress Gzip)',
+ 'Close a task' => 'Tutup tugas',
+ 'Edit a task' => 'Edit tugas',
+ 'Column' => 'Kolom',
+ 'Color' => 'Warna',
+ 'Assignee' => 'Orang yang ditugaskan',
+ 'Create another task' => 'Buat tugas lain',
+ 'New task' => 'Tugas baru',
+ 'Open a task' => 'Buka tugas',
+ 'Do you really want to open this task: "%s"?' => 'Apakah anda yakin akan membuka tugas ini : « %s » ?',
+ 'Back to the board' => 'Kembali ke papan',
+ 'Created on %B %e, %Y at %k:%M %p' => 'Dibuat pada tanggal %d/%m/%Y à %H:%M',
+ 'There is nobody assigned' => 'Tidak ada orang yand ditugaskan',
+ 'Column on the board:' => 'Kolom di dalam papan : ',
+ 'Status is open' => 'Status terbuka',
+ 'Status is closed' => 'Status ditutup',
+ 'Close this task' => 'Tutup tugas ini',
+ 'Open this task' => 'Buka tugas ini',
+ 'There is no description.' => 'Tidak ada deskripsi.',
+ 'Add a new task' => 'Tambah tugas baru',
+ 'The username is required' => 'nama pengguna diperlukan',
+ 'The maximum length is %d characters' => 'Panjang maksimum adalah %d karakter',
+ 'The minimum length is %d characters' => 'Panjang minimum adalah %d karakter',
+ 'The password is required' => 'Kata sandi diperlukan',
+ 'This value must be an integer' => 'Nilai ini harus integer',
+ 'The username must be unique' => 'Nama pengguna harus unik',
+ 'The user id is required' => 'Id Pengguna diperlukan',
+ 'Passwords don\'t match' => 'Kata sandi tidak cocok',
+ 'The confirmation is required' => 'Konfirmasi diperlukan',
+ 'The project is required' => 'Proyek diperlukan',
+ 'The id is required' => 'Id diperlukan',
+ 'The project id is required' => 'Id proyek diperlukan',
+ 'The project name is required' => 'Nama proyek diperlukan',
+ 'This project must be unique' => 'Proyek ini harus unik',
+ 'The title is required' => 'Judul diperlukan',
+ 'Settings saved successfully.' => 'Pengaturan berhasil disimpan.',
+ 'Unable to save your settings.' => 'Tidak dapat menyimpan pengaturan anda.',
+ 'Database optimization done.' => 'Optimasi basis data selesai.',
+ 'Your project have been created successfully.' => 'Proyek anda berhasil dibuat.',
+ 'Unable to create your project.' => 'Tidak dapat membuat proyek anda.',
+ 'Project updated successfully.' => 'Proyek berhasil diperbaharui.',
+ 'Unable to update this project.' => 'Tidak dapat memperbaharui proyek ini.',
+ 'Unable to remove this project.' => 'Tidak dapat menghapus proyek ini.',
+ 'Project removed successfully.' => 'Proyek berhasil dihapus.',
+ 'Project activated successfully.' => 'Proyek berhasil diaktivasi.',
+ 'Unable to activate this project.' => 'Tidak dapat mengaktifkan proyek ini.',
+ 'Project disabled successfully.' => 'Proyek berhasil dinonaktifkan.',
+ 'Unable to disable this project.' => 'Tidak dapat menonaktifkan proyek ini.',
+ 'Unable to open this task.' => 'Tidak dapat membuka tugas ini.',
+ 'Task opened successfully.' => 'Tugas berhasil dibuka.',
+ 'Unable to close this task.' => 'Tidak dapat menutup tugas ini.',
+ 'Task closed successfully.' => 'Tugas berhasil ditutup.',
+ 'Unable to update your task.' => 'Tidak dapat memperbaharui tugas ini.',
+ 'Task updated successfully.' => 'Tugas berhasil diperbaharui.',
+ 'Unable to create your task.' => 'Tidak dapat membuat tugas anda.',
+ 'Task created successfully.' => 'Tugas berhasil dibuat.',
+ 'User created successfully.' => 'Pengguna berhasil dibuat.',
+ 'Unable to create your user.' => 'Tidak dapat membuat pengguna anda.',
+ 'User updated successfully.' => 'Pengguna berhasil diperbaharui.',
+ 'Unable to update your user.' => 'Tidak dapat memperbaharui pengguna anda.',
+ 'User removed successfully.' => 'pengguna berhasil dihapus.',
+ 'Unable to remove this user.' => 'Tidak dapat menghapus pengguna ini.',
+ 'Board updated successfully.' => 'Papan berhasil diperbaharui.',
+ 'Ready' => 'Siap',
+ 'Backlog' => 'Tertunda',
+ 'Work in progress' => 'Sedang dalam pengerjaan',
+ 'Done' => 'Selesai',
+ 'Application version:' => 'Versi aplikasi :',
+ 'Completed on %B %e, %Y at %k:%M %p' => 'Diselesaikan pada tanggal %d/%m/%Y à %H:%M',
+ '%B %e, %Y at %k:%M %p' => '%d/%m/%Y à %H:%M',
+ 'Date created' => 'Tanggal dibuat',
+ 'Date completed' => 'Tanggal diselesaikan',
+ 'Id' => 'Id.',
+ '%d closed tasks' => '%d tugas yang ditutup',
+ 'No task for this project' => 'Tidak ada tugas dalam proyek ini',
+ 'Public link' => 'Tautan publik',
+ 'There is no column in your project!' => 'Tidak ada kolom didalam proyek anda!',
+ 'Change assignee' => 'Mengubah orang yand ditugaskan',
+ 'Change assignee for the task "%s"' => 'Mengubah orang yang ditugaskan untuk tugas « %s »',
+ 'Timezone' => 'Zona waktu',
+ 'Sorry, I didn\'t find this information in my database!' => 'Maaf, saya tidak menemukan informasi ini dalam basis data saya !',
+ 'Page not found' => 'Halaman tidak ditemukan',
+ 'Complexity' => 'Kompleksitas',
+ 'Task limit' => 'Batas tugas.',
+ 'Task count' => 'Jumlah tugas',
+ 'Edit project access list' => 'Modifikasi hak akses proyek',
+ 'Allow this user' => 'Memperbolehkan pengguna ini',
+ 'Don\'t forget that administrators have access to everything.' => 'Ingat bahwa administrator memiliki akses ke semua.',
+ 'Revoke' => 'Mencabut',
+ 'List of authorized users' => 'Daftar pengguna yang berwenang',
+ 'User' => 'Pengguna',
+ 'Nobody have access to this project.' => 'Tidak ada yang berwenang untuk mengakses proyek.',
+ 'Comments' => 'Komentar',
+ 'Write your text in Markdown' => 'Menulis teks anda didalam Markdown',
+ 'Leave a comment' => 'Tinggalkan komentar',
+ 'Comment is required' => 'Komentar diperlukan',
+ 'Leave a description' => 'Tinggalkan deskripsi',
+ 'Comment added successfully.' => 'Komentar berhasil ditambahkan.',
+ 'Unable to create your comment.' => 'Tidak dapat menambahkan komentar anda.',
+ 'Edit this task' => 'Modifikasi tugas ini',
+ 'Due Date' => 'Batas Tanggal Terakhir',
+ 'Invalid date' => 'Tanggal tidak valid',
+ 'Must be done before %B %e, %Y' => 'Harus diselesaikan sebelum tanggal %d/%m/%Y',
+ '%B %e, %Y' => '%d %B %Y',
+ '%b %e, %Y' => '%d/%m/%Y',
+ 'Automatic actions' => 'Tindakan otomatis',
+ 'Your automatic action have been created successfully.' => 'Tindakan otomatis anda berhasil dibuat.',
+ 'Unable to create your automatic action.' => 'Tidak dapat membuat tindakan otomatis anda.',
+ 'Remove an action' => 'Hapus tindakan',
+ 'Unable to remove this action.' => 'Tidak dapat menghapus tindakan ini',
+ 'Action removed successfully.' => 'Tindakan berhasil dihapus.',
+ 'Automatic actions for the project "%s"' => 'Tindakan otomatis untuk proyek ini « %s »',
+ 'Defined actions' => 'Tindakan didefinisikan',
+ 'Add an action' => 'Tambah tindakan',
+ 'Event name' => 'Nama acara',
+ 'Action name' => 'Nama tindakan',
+ 'Action parameters' => 'Parameter tindakan',
+ 'Action' => 'Tindakan',
+ 'Event' => 'Acara',
+ 'When the selected event occurs execute the corresponding action.' => 'Ketika acara yang dipilih terjadi, melakukan tindakan yang sesuai.',
+ 'Next step' => 'Langkah selanjutnya',
+ 'Define action parameters' => 'Definisi parameter tindakan',
+ 'Save this action' => 'Simpan tindakan ini',
+ 'Do you really want to remove this action: "%s"?' => 'Apakah anda yakin akan menghapus tindakan ini « %s » ?',
+ 'Remove an automatic action' => 'Hapus tindakan otomatis',
+ 'Assign the task to a specific user' => 'Menetapkan tugas untuk pengguna tertentu',
+ 'Assign the task to the person who does the action' => 'Memberikan tugas untuk orang yang melakukan tindakan',
+ 'Duplicate the task to another project' => 'Duplikasi tugas ke proyek lain',
+ 'Move a task to another column' => 'Pindahkan tugas ke kolom lain',
+ 'Task modification' => 'Modifikasi tugas',
+ 'Task creation' => 'Membuat tugas',
+ 'Closing a task' => 'Menutup tugas',
+ 'Assign a color to a specific user' => 'Menetapkan warna untuk pengguna tertentu',
+ 'Column title' => 'Judul kolom',
+ 'Position' => 'Posisi',
+ 'Move Up' => 'Pindah ke atas',
+ 'Move Down' => 'Pindah ke bawah',
+ 'Duplicate to another project' => 'Duplikasi ke proyek lain',
+ 'Duplicate' => 'Duplikasi',
+ 'link' => 'tautan',
+ 'Comment updated successfully.' => 'Komentar berhasil diperbaharui.',
+ 'Unable to update your comment.' => 'Tidak dapat memperbaharui komentar anda.',
+ 'Remove a comment' => 'Hapus komentar',
+ 'Comment removed successfully.' => 'Komentar berhasil dihapus.',
+ 'Unable to remove this comment.' => 'Tidak dapat menghapus komentar ini.',
+ 'Do you really want to remove this comment?' => 'Apakah anda yakin akan menghapus komentar ini ?',
+ 'Only administrators or the creator of the comment can access to this page.' => 'Hanya administrator atau pembuat komentar yang dapat mengakses halaman ini.',
+ 'Current password for the user "%s"' => 'Kata sandi saat ini untuk pengguna « %s »',
+ 'The current password is required' => 'Kata sandi saat ini diperlukan',
+ 'Wrong password' => 'Kata sandi salah',
+ 'Unknown' => 'Tidak diketahui',
+ 'Last logins' => 'Masuk terakhir',
+ 'Login date' => 'Tanggal masuk',
+ 'Authentication method' => 'Metode otentifikasi',
+ 'IP address' => 'Alamat IP',
+ 'User agent' => 'Agen Pengguna',
+ 'Persistent connections' => 'Koneksi persisten',
+ 'No session.' => 'Tidak ada sesi.',
+ 'Expiration date' => 'Tanggal kadaluarsa',
+ 'Remember Me' => 'Ingat Saya',
+ 'Creation date' => 'Tanggal dibuat',
+ 'Everybody' => 'Semua orang',
+ 'Open' => 'Terbuka',
+ 'Closed' => 'Ditutup',
+ 'Search' => 'Cari',
+ 'Nothing found.' => 'Tidak ditemukan.',
+ 'Due date' => 'Batas tanggal terakhir',
+ 'Others formats accepted: %s and %s' => 'Format lain yang didukung : %s et %s',
+ 'Description' => 'Deskripsi',
+ '%d comments' => '%d komentar',
+ '%d comment' => '%d komentar',
+ 'Email address invalid' => 'Alamat email tidak valid',
+ 'Your external account is not linked anymore to your profile.' => 'Akun eksternal anda tidak lagi terhubung ke profil anda.',
+ 'Unable to unlink your external account.' => 'Tidak dapat memutuskan akun eksternal anda.',
+ 'External authentication failed' => 'Otentifikasi eksternal gagal',
+ 'Your external account is linked to your profile successfully.' => 'Akun eksternal anda berhasil dihubungkan ke profil anda.',
+ 'Email' => 'Email',
+ 'Link my Google Account' => 'Hubungkan akun Google saya',
+ 'Unlink my Google Account' => 'Putuskan akun Google saya',
+ 'Login with my Google Account' => 'Masuk menggunakan akun Google saya',
+ 'Project not found.' => 'Proyek tidak ditemukan.',
+ 'Task removed successfully.' => 'Tugas berhasil dihapus.',
+ 'Unable to remove this task.' => 'Tidak dapat menghapus tugas ini.',
+ 'Remove a task' => 'Hapus tugas',
+ 'Do you really want to remove this task: "%s"?' => 'Apakah anda yakin akan menghapus tugas ini « %s » ?',
+ 'Assign automatically a color based on a category' => 'Otomatis menetapkan warna berdasarkan kategori',
+ 'Assign automatically a category based on a color' => 'Otomatis menetapkan kategori berdasarkan warna',
+ 'Task creation or modification' => 'Tugas dibuat atau di mofifikasi',
+ 'Category' => 'Kategori',
+ 'Category:' => 'Kategori :',
+ 'Categories' => 'Kategori',
+ 'Category not found.' => 'Kategori tidak ditemukan',
+ 'Your category have been created successfully.' => 'Kategori anda berhasil dibuat.',
+ 'Unable to create your category.' => 'Tidak dapat membuat kategori anda.',
+ 'Your category have been updated successfully.' => 'Kategori anda berhasil diperbaharui.',
+ 'Unable to update your category.' => 'Tidak dapat memperbaharui kategori anda.',
+ 'Remove a category' => 'Hapus kategori',
+ 'Category removed successfully.' => 'Kategori berhasil dihapus.',
+ 'Unable to remove this category.' => 'Tidak dapat menghapus kategori ini.',
+ 'Category modification for the project "%s"' => 'Modifikasi kategori untuk proyek « %s »',
+ 'Category Name' => 'Nama Kategori',
+ 'Add a new category' => 'Tambah kategori baru',
+ 'Do you really want to remove this category: "%s"?' => 'Apakah anda yakin akan menghapus kategori ini « %s » ?',
+ 'All categories' => 'Semua kategori',
+ 'No category' => 'Tidak ada kategori',
+ 'The name is required' => 'Nama diperlukan',
+ 'Remove a file' => 'Hapus berkas',
+ 'Unable to remove this file.' => 'Tidak dapat menghapus berkas ini.',
+ 'File removed successfully.' => 'Berkas berhasil dihapus.',
+ 'Attach a document' => 'Lampirkan dokumen',
+ 'Do you really want to remove this file: "%s"?' => 'Apakah anda yakin akan menghapus berkas ini « %s » ?',
+ 'open' => 'buka',
+ 'Attachments' => 'Lampiran',
+ 'Edit the task' => 'Modifikasi tugas',
+ 'Edit the description' => 'Modifikasi deskripsi',
+ 'Add a comment' => 'Tambahkan komentar',
+ 'Edit a comment' => 'Modifikasi komentar',
+ 'Summary' => 'Ringkasan',
+ 'Time tracking' => 'Pelacakan waktu',
+ 'Estimate:' => 'Estimasi :',
+ 'Spent:' => 'Menghabiskan:',
+ 'Do you really want to remove this sub-task?' => 'Apakah anda yakin akan menghapus sub-tugas ini ?',
+ 'Remaining:' => 'Tersisa:',
+ 'hours' => 'jam',
+ 'spent' => 'menghabiskan',
+ 'estimated' => 'perkiraan',
+ 'Sub-Tasks' => 'Sub-tugas',
+ 'Add a sub-task' => 'Tambahkan sub-tugas',
+ 'Original estimate' => 'Perkiraan semula',
+ 'Create another sub-task' => 'Tambahkan sub-tugas lainnya',
+ 'Time spent' => 'Waktu yang dihabiskan',
+ 'Edit a sub-task' => 'Modifikasi sub-tugas',
+ 'Remove a sub-task' => 'Hapus sub-tugas',
+ 'The time must be a numeric value' => 'Waktu harus berisikan numerik',
+ 'Todo' => 'Yang harus dilakukan',
+ 'In progress' => 'Sedang proses',
+ 'Sub-task removed successfully.' => 'Sub-tugas berhasil dihapus.',
+ 'Unable to remove this sub-task.' => 'Tidak dapat menghapus sub-tugas.',
+ 'Sub-task updated successfully.' => 'Sub-tugas berhasil diperbaharui.',
+ 'Unable to update your sub-task.' => 'Tidak dapat memperbaharui sub-tugas anda.',
+ 'Unable to create your sub-task.' => 'Tidak dapat membuat sub-tugas anda.',
+ 'Sub-task added successfully.' => 'Sub-tugas berhasil dibuat.',
+ 'Maximum size: ' => 'Ukuran maksimum: ',
+ 'Unable to upload the file.' => 'Tidak dapat mengunggah berkas.',
+ 'Display another project' => 'Lihat proyek lain',
+ 'Login with my Github Account' => 'Masuk menggunakan akun Github saya',
+ 'Link my Github Account' => 'Hubungkan akun Github saya ',
+ 'Unlink my Github Account' => 'Putuskan akun Github saya',
+ 'Created by %s' => 'Dibuat oleh %s',
+ 'Last modified on %B %e, %Y at %k:%M %p' => 'Modifikasi terakhir pada tanggal %d/%m/%Y à %H:%M',
+ 'Tasks Export' => 'Ekspor Tugas',
+ 'Tasks exportation for "%s"' => 'Tugas di ekspor untuk « %s »',
+ 'Start Date' => 'Tanggal Mulai',
+ 'End Date' => 'Tanggal Berakhir',
+ 'Execute' => 'Eksekusi',
+ 'Task Id' => 'Id Tugas',
+ 'Creator' => 'Pembuat',
+ 'Modification date' => 'Tanggal modifikasi',
+ 'Completion date' => 'Tanggal penyelesaian',
+ 'Clone' => 'Klon',
+ 'Project cloned successfully.' => 'Kloning proyek berhasil.',
+ 'Unable to clone this project.' => 'Tidak dapat mengkloning proyek.',
+ 'Email notifications' => 'Pemberitahuan email',
+ 'Enable email notifications' => 'Aktifkan pemberitahuan dari email',
+ 'Task position:' => 'Posisi tugas :',
+ 'The task #%d have been opened.' => 'Tugas #%d telah dibuka.',
+ 'The task #%d have been closed.' => 'Tugas #%d telah ditutup.',
+ 'Sub-task updated' => 'Sub-tugas diperbaharui',
+ 'Title:' => 'Judul :',
+ 'Status:' => 'Status :',
+ 'Assignee:' => 'Ditugaskan ke :',
+ 'Time tracking:' => 'Pelacakan waktu :',
+ 'New sub-task' => 'Sub-tugas baru',
+ 'New attachment added "%s"' => 'Lampiran baru ditambahkan « %s »',
+ 'Comment updated' => 'Komentar ditambahkan',
+ 'New comment posted by %s' => 'Komentar baru ditambahkan oleh « %s »',
+ 'New attachment' => 'Lampirkan baru',
+ 'New comment' => 'Komentar baru',
+ 'Comment updated' => 'Komentar diperbaharui',
+ 'New subtask' => 'Sub-tugas baru',
+ 'Subtask updated' => 'Sub-tugas diperbaharui',
+ 'New task' => 'Tugas baru',
+ 'Task updated' => 'Tugas diperbaharui',
+ 'Task closed' => 'Tugas ditutup',
+ 'Task opened' => 'Tugas dibuka',
+ 'I want to receive notifications only for those projects:' => 'Saya ingin menerima pemberitahuan hanya untuk proyek-proyek yang dipilih :',
+ 'view the task on Kanboard' => 'lihat tugas di Kanboard',
+ 'Public access' => 'Akses publik',
+ 'User management' => 'Manajemen pengguna',
+ 'Active tasks' => 'Tugas aktif',
+ 'Disable public access' => 'Nonaktifkan akses publik',
+ 'Enable public access' => 'Aktifkan akses publik',
+ 'Public access disabled' => 'Akses publik dinonaktifkan',
+ 'Do you really want to disable this project: "%s"?' => 'Apakah anda yakin akan menonaktifkan proyek ini : « %s » ?',
+ 'Do you really want to enable this project: "%s"?' => 'Apakah anda yakin akan mengaktifkan proyek ini : « %s » ?',
+ 'Project activation' => 'Aktivasi proyek',
+ 'Move the task to another project' => 'Pindahkan tugas ke proyek lain',
+ 'Move to another project' => 'Pindahkan ke proyek lain',
+ 'Do you really want to duplicate this task?' => 'Apakah anda yakin akan menduplikasi tugas ini ?',
+ 'Duplicate a task' => 'Duplikasi tugas',
+ 'External accounts' => 'Akun eksternal',
+ 'Account type' => 'Tipe akun',
+ 'Local' => 'Lokal',
+ 'Remote' => 'Jauh',
+ 'Enabled' => 'Aktif',
+ 'Disabled' => 'Nonaktif',
+ 'Google account linked' => 'Akun Google yang terhubung',
+ 'Github account linked' => 'Akun Github yang terhubung',
+ 'Username:' => 'Nama pengguna :',
+ 'Name:' => 'Nama :',
+ 'Email:' => 'Email :',
+ 'Notifications:' => 'Pemberitahuan :',
+ 'Notifications' => 'Pemberitahuan',
+ 'Group:' => 'Grup :',
+ 'Regular user' => 'Pengguna normal',
+ 'Account type:' => 'Tipe akun :',
+ 'Edit profile' => 'Modifikasi profil',
+ 'Change password' => 'Rubah kata sandri',
+ 'Password modification' => 'Modifikasi kata sandi',
+ 'External authentications' => 'Otentifikasi eksternal',
+ 'Google Account' => 'Akun Google',
+ 'Github Account' => 'Akun Github',
+ 'Never connected.' => 'Tidak pernah terhubung.',
+ 'No account linked.' => 'Tidak ada akun terhubung.',
+ 'Account linked.' => 'Akun terhubung.',
+ 'No external authentication enabled.' => 'Tidak ada otentifikasi eksternal yang aktif.',
+ 'Password modified successfully.' => 'Kata sandi berhasil dimodifikasi.',
+ 'Unable to change the password.' => 'Tidak dapat merubah kata sandir.',
+ 'Change category for the task "%s"' => 'Rubah kategori untuk tugas « %s »',
+ 'Change category' => 'Rubah kategori',
+ '%s updated the task %s' => '%s memperbaharui tugas %s',
+ '%s opened the task %s' => '%s membuka tugas %s',
+ '%s moved the task %s to the position #%d in the column "%s"' => '%s memindahkan tugas %s ke posisi n°%d dalam kolom « %s »',
+ '%s moved the task %s to the column "%s"' => '%s memindahkan tugas %s ke kolom « %s »',
+ '%s created the task %s' => '%s membuat tugas %s',
+ '%s closed the task %s' => '%s menutup tugas %s',
+ '%s created a subtask for the task %s' => '%s membuat subtugas untuk tugas %s',
+ '%s updated a subtask for the task %s' => '%s memperbaharui subtugas untuk tugas %s',
+ 'Assigned to %s with an estimate of %s/%sh' => 'Ditugaskan untuk %s dengan perkiraan %s/%sh',
+ 'Not assigned, estimate of %sh' => 'Tidak ada yang ditugaskan, perkiraan %sh',
+ '%s updated a comment on the task %s' => '%s memperbaharui komentar pada tugas %s',
+ '%s commented the task %s' => '%s memberikan komentar pada tugas %s',
+ '%s\'s activity' => 'Aktifitas dari %s',
+ 'RSS feed' => 'RSS feed',
+ '%s updated a comment on the task #%d' => '%s memperbaharui komentar pada tugas n°%d',
+ '%s commented on the task #%d' => '%s memberikan komentar pada tugas n°%d',
+ '%s updated a subtask for the task #%d' => '%s memperbaharui subtugas untuk tugas n°%d',
+ '%s created a subtask for the task #%d' => '%s membuat subtugas untuk tugas n°%d',
+ '%s updated the task #%d' => '%s memperbaharui tugas n°%d',
+ '%s created the task #%d' => '%s membuat tugas n°%d',
+ '%s closed the task #%d' => '%s menutup tugas n°%d',
+ '%s open the task #%d' => '%s membuka tugas n°%d',
+ '%s moved the task #%d to the column "%s"' => '%s memindahkan tugas n°%d ke kolom « %s »',
+ '%s moved the task #%d to the position %d in the column "%s"' => '%s memindahkan tugas n°%d ke posisi n°%d dalam kolom « %s »',
+ 'Activity' => 'Aktifitas',
+ 'Default values are "%s"' => 'Standar nilai adalah« %s »',
+ 'Default columns for new projects (Comma-separated)' => 'Kolom default untuk proyek baru (dipisahkan dengan koma)',
+ 'Task assignee change' => 'Mengubah orang ditugaskan untuk tugas',
+ '%s change the assignee of the task #%d to %s' => '%s rubah orang yang ditugaskan dari tugas n%d ke %s',
+ '%s changed the assignee of the task %s to %s' => '%s mengubah orang yang ditugaskan dari tugas %s ke %s',
+ 'New password for the user "%s"' => 'Kata sandi baru untuk pengguna « %s »',
+ 'Choose an event' => 'Pilih acara',
+ 'Github commit received' => 'Menerima komit dari Github',
+ 'Github issue opened' => 'Tiket Github dibuka',
+ 'Github issue closed' => 'Tiket Github ditutup',
+ 'Github issue reopened' => 'Tiket Github dibuka kembali',
+ 'Github issue assignee change' => 'Rubah penugasan tiket Github',
+ 'Github issue label change' => 'Perubahan label pada tiket Github',
+ 'Create a task from an external provider' => 'Buat tugas dari pemasok eksternal',
+ 'Change the assignee based on an external username' => 'Rubah penugasan berdasarkan nama pengguna eksternal',
+ 'Change the category based on an external label' => 'Rubah kategori berdasarkan label eksternal',
+ 'Reference' => 'Referensi',
+ 'Reference: %s' => 'Referensi : %s',
+ 'Label' => 'Label',
+ 'Database' => 'Basis data',
+ 'About' => 'Tentang',
+ 'Database driver:' => 'Driver basis data :',
+ 'Board settings' => 'Pengaturan papan',
+ 'URL and token' => 'URL dan token',
+ 'Webhook settings' => 'Pengaturan webhook',
+ 'URL for task creation:' => 'URL untuk pembuatan tugas :',
+ 'Reset token' => 'Mereset token',
+ 'API endpoint:' => 'API endpoint :',
+ 'Refresh interval for private board' => 'Interval pembaruan untuk papan pribadi',
+ 'Refresh interval for public board' => 'Interval pembaruan untuk papan publik',
+ 'Task highlight period' => 'Periode puncak tugas',
+ 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Periode (dalam detik) untuk mempertimbangkan tugas yang baru dimodifikasi (0 untuk menonaktifkan, standar 2 hari)',
+ 'Frequency in second (60 seconds by default)' => 'Frequensi dalam detik (standar 60 detik)',
+ 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Frequensi dalam detik (0 untuk menonaktifkan fitur ini, standar 10 detik)',
+ 'Application URL' => 'URL Aplikasi',
+ 'Example: http://example.kanboard.net/ (used by email notifications)' => 'Contoh : http://exemple.kanboard.net/ (digunakan untuk pemberitahuan email)',
+ 'Token regenerated.' => 'Token diregenerasi.',
+ 'Date format' => 'Format tanggal',
+ 'ISO format is always accepted, example: "%s" and "%s"' => 'Format ISO selalu diterima, contoh : « %s » et « %s »',
+ 'New private project' => 'Proyek pribadi baru',
+ 'This project is private' => 'Proyek ini adalah pribadi',
+ 'Type here to create a new sub-task' => 'Ketik disini untuk membuat sub-tugas baru',
+ 'Add' => 'Tambah',
+ 'Estimated time: %s hours' => 'Perkiraan waktu: %s jam',
+ 'Time spent: %s hours' => 'Waktu dihabiskan : %s jam',
+ 'Started on %B %e, %Y' => 'Dimulai pada %d/%m/%Y',
+ 'Start date' => 'Tanggal mulai',
+ 'Time estimated' => 'Perkiraan waktu',
+ 'There is nothing assigned to you.' => 'Tidak ada yang diberikan kepada anda.',
+ 'My tasks' => 'Tugas saya',
+ 'Activity stream' => 'Arus aktifitas',
+ 'Dashboard' => 'Dasbor',
+ 'Confirmation' => 'Konfirmasi',
+ 'Allow everybody to access to this project' => 'Memungkinkan semua orang untuk mengakses proyek ini',
+ 'Everybody have access to this project.' => 'Semua orang mendapat akses untuk proyek ini.',
+ 'Webhooks' => 'Webhooks',
+ 'API' => 'API',
+ 'Github webhooks' => 'Webhook Github',
+ 'Help on Github webhooks' => 'Bantuan pada webhook Github',
+ 'Create a comment from an external provider' => 'Buat komentar dari pemasok eksternal',
+ 'Github issue comment created' => 'Komentar dibuat pada tiket Github',
+ 'Project management' => 'Manajemen proyek',
+ 'My projects' => 'Proyek saya',
+ 'Columns' => 'Kolom',
+ 'Task' => 'Tugas',
+ 'Your are not member of any project.' => 'Anda bukan anggota dari setiap proyek.',
+ 'Percentage' => 'Persentasi',
+ 'Number of tasks' => 'Jumlah dari tugas',
+ 'Task distribution' => 'Pembagian tugas',
+ 'Reportings' => 'Pelaporan',
+ 'Task repartition for "%s"' => 'Pembagian tugas untuk « %s »',
+ 'Analytics' => 'Analitis',
+ 'Subtask' => 'Subtugas',
+ 'My subtasks' => 'Subtugas saya',
+ 'User repartition' => 'Partisi ulang pengguna',
+ 'User repartition for "%s"' => 'Partisi ulang pengguna untuk « %s »',
+ 'Clone this project' => 'Gandakan proyek ini',
+ 'Column removed successfully.' => 'Kolom berhasil dihapus.',
+ 'Github Issue' => 'Tiket Github',
+ 'Not enough data to show the graph.' => 'Tidak cukup data untuk menampilkan grafik.',
+ 'Previous' => 'Sebelumnya',
+ 'The id must be an integer' => 'Id harus integer',
+ 'The project id must be an integer' => 'Id proyek harus integer',
+ 'The status must be an integer' => 'Status harus integer',
+ 'The subtask id is required' => 'Id subtugas diperlukan',
+ 'The subtask id must be an integer' => 'Id subtugas harus integer',
+ 'The task id is required' => 'Id tugas diperlukan',
+ 'The task id must be an integer' => 'Id tugas harus integer',
+ 'The user id must be an integer' => 'Id user harus integer',
+ 'This value is required' => 'Nilai ini diperlukan',
+ 'This value must be numeric' => 'Nilai ini harus angka',
+ 'Unable to create this task.' => 'Tidak dapat membuat tugas ini',
+ 'Cumulative flow diagram' => 'Diagram alir kumulatif',
+ 'Cumulative flow diagram for "%s"' => 'Diagram alir kumulatif untuk « %s »',
+ 'Daily project summary' => 'Ringkasan proyek harian',
+ 'Daily project summary export' => 'Ekspor ringkasan proyek harian',
+ 'Daily project summary export for "%s"' => 'Ekspor ringkasan proyek harian untuk « %s »',
+ 'Exports' => 'Ekspor',
+ 'This export contains the number of tasks per column grouped per day.' => 'Ekspor ini berisi jumlah dari tugas per kolom dikelompokan perhari.',
+ 'Nothing to preview...' => 'Tidak ada yang dapat dilihat...',
+ 'Preview' => 'Preview',
+ 'Write' => 'Tulis',
+ 'Active swimlanes' => 'Swimlanes aktif',
+ 'Add a new swimlane' => 'Tambah swimlane baru',
+ 'Change default swimlane' => 'Modifikasi standar swimlane',
+ 'Default swimlane' => 'Standar swimlane',
+ 'Do you really want to remove this swimlane: "%s"?' => 'Apakah anda yakin akan menghapus swimlane ini : « %s » ?',
+ 'Inactive swimlanes' => 'Swimlanes tidak aktif',
+ 'Set project manager' => 'Masukan manajer proyek',
+ 'Set project member' => 'Masukan anggota proyek ',
+ 'Remove a swimlane' => 'Supprimer une swimlane',
+ 'Rename' => 'Ganti nama',
+ 'Show default swimlane' => 'Perlihatkan standar swimlane',
+ 'Swimlane modification for the project "%s"' => 'Modifikasi swimlane untuk proyek « %s »',
+ 'Swimlane not found.' => 'Swimlane tidak ditemukan.',
+ 'Swimlane removed successfully.' => 'Swimlane berhasil dihapus.',
+ 'Swimlanes' => 'Swimlanes',
+ 'Swimlane updated successfully.' => 'Swimlane berhasil diperbaharui.',
+ 'The default swimlane have been updated successfully.' => 'Standar swimlane berhasil diperbaharui.',
+ 'Unable to create your swimlane.' => 'Tidak dapat membuat swimlane anda.',
+ 'Unable to remove this swimlane.' => 'Tidak dapat menghapus swimlane ini.',
+ 'Unable to update this swimlane.' => 'Tidak dapat memperbaharui swimlane ini.',
+ 'Your swimlane have been created successfully.' => 'Swimlane anda berhasil dibuat.',
+ 'Example: "Bug, Feature Request, Improvement"' => 'Contoh: « Insiden, Permintaan Fitur, Perbaikan »',
+ 'Default categories for new projects (Comma-separated)' => 'Standar kategori untuk proyek baru (dipisahkan dengan koma)',
+ 'Gitlab commit received' => 'Menerima komit Gitlab',
+ 'Gitlab issue opened' => 'Tiket Gitlab dibuka',
+ 'Gitlab issue closed' => 'Tiket Gitlab ditutup',
+ 'Gitlab webhooks' => 'Webhook Gitlab',
+ 'Help on Gitlab webhooks' => 'Bantuan pada webhook Gitlab',
+ 'Integrations' => 'Integrasi',
+ 'Integration with third-party services' => 'Integrasi dengan layanan pihak ketiga',
+ 'Role for this project' => 'Peran untuk proyek ini',
+ 'Project manager' => 'Manajer proyek',
+ 'Project member' => 'Anggota proyek',
+ 'A project manager can change the settings of the project and have more privileges than a standard user.' => 'Seorang manajer proyek dapat mengubah pengaturan proyek dan memiliki lebih banyak keistimewaan dibandingkan dengan pengguna biasa.',
+ 'Gitlab Issue' => 'Tiket Gitlab',
+ 'Subtask Id' => 'Id Subtugas',
+ 'Subtasks' => 'Subtugas',
+ 'Subtasks Export' => 'Ekspor Subtugas',
+ 'Subtasks exportation for "%s"' => 'Ekspor subtugas untuk « %s »',
+ 'Task Title' => 'Judul Tugas',
+ 'Untitled' => 'Tanpa nama',
+ 'Application default' => 'Aplikasi standar',
+ 'Language:' => 'Bahasa :',
+ 'Timezone:' => 'Zona waktu :',
+ 'All columns' => 'Semua kolom',
+ 'Calendar' => 'Kalender',
+ 'Next' => 'Selanjutnya',
+ '#%d' => 'nËš%d',
+ 'All swimlanes' => 'Semua swimlane',
+ 'All colors' => 'Semua warna',
+ 'All status' => 'Semua status',
+ 'Moved to column %s' => 'Pindah ke kolom %s',
+ 'Change description' => 'Rubah deskripsi',
+ 'User dashboard' => 'Dasbor pengguna',
+ 'Allow only one subtask in progress at the same time for a user' => 'Izinkan hanya satu subtugas dalam proses secara bersamaan untuk satu pengguna',
+ 'Edit column "%s"' => 'Modifikasi kolom « %s »',
+ 'Select the new status of the subtask: "%s"' => 'Pilih status baru untuk subtugas : « %s »',
+ 'Subtask timesheet' => 'Subtugas absen',
+ 'There is nothing to show.' => 'Tidak ada yang dapat diperlihatkan.',
+ 'Time Tracking' => 'Pelacakan waktu',
+ 'You already have one subtask in progress' => 'Anda sudah ada satu subtugas dalam proses',
+ 'Which parts of the project do you want to duplicate?' => 'Bagian dalam proyek mana yang ingin anda duplikasi?',
+ 'Disallow login form' => 'Larang formulir masuk',
+ 'Bitbucket commit received' => 'Menerima komit Bitbucket',
+ 'Bitbucket webhooks' => 'Webhook Bitbucket',
+ 'Help on Bitbucket webhooks' => 'Bantuan pada webhook Bitbucket',
+ 'Start' => 'Mulai',
+ 'End' => 'Selesai',
+ 'Task age in days' => 'Usia tugas dalam hari',
+ 'Days in this column' => 'Hari dalam kolom ini',
+ '%dd' => '%dj',
+ 'Add a link' => 'Menambahkan tautan',
+ 'Add a new link' => 'Tambah tautan baru',
+ 'Do you really want to remove this link: "%s"?' => 'Apakah anda yakin akan menghapus tautan ini : « %s » ?',
+ 'Do you really want to remove this link with task #%d?' => 'Apakah anda yakin akan menghapus tautan ini dengan tugas n°%d ?',
+ 'Field required' => 'Field diperlukan',
+ 'Link added successfully.' => 'Tautan berhasil ditambahkan.',
+ 'Link updated successfully.' => 'Tautan berhasil diperbaharui.',
+ 'Link removed successfully.' => 'Tautan berhasil dihapus.',
+ 'Link labels' => 'Label tautan',
+ 'Link modification' => 'Modifikasi tautan',
+ 'Links' => 'Tautan',
+ 'Link settings' => 'Pengaturan tautan',
+ 'Opposite label' => 'Label berlawanan',
+ 'Remove a link' => 'Hapus tautan',
+ 'Task\'s links' => 'Tautan tugas',
+ 'The labels must be different' => 'Label harus berbeda',
+ 'There is no link.' => 'Tidak ada tautan.',
+ 'This label must be unique' => 'Label ini harus unik',
+ 'Unable to create your link.' => 'Tidak dapat membuat tautan anda.',
+ 'Unable to update your link.' => 'Tidak dapat memperbaharui tautan anda.',
+ 'Unable to remove this link.' => 'Tidak dapat menghapus tautan ini.',
+ 'relates to' => 'berhubungan dengan',
+ 'blocks' => 'blok',
+ 'is blocked by' => 'diblokir oleh',
+ 'duplicates' => 'duplikat',
+ 'is duplicated by' => 'diduplikasi oleh',
+ 'is a child of' => 'anak dari',
+ 'is a parent of' => 'orant tua dari',
+ 'targets milestone' => 'milestone target',
+ 'is a milestone of' => 'adalah milestone dari',
+ 'fixes' => 'perbaikan',
+ 'is fixed by' => 'diperbaiki oleh',
+ 'This task' => 'Tugas ini',
+ '<1h' => '<1h',
+ '%dh' => '%dh',
+ '%b %e' => '%e %b',
+ 'Expand tasks' => 'Perluas tugas',
+ 'Collapse tasks' => 'Lipat tugas',
+ 'Expand/collapse tasks' => 'Perluas/lipat tugas',
+ 'Close dialog box' => 'Tutup kotak dialog',
+ 'Submit a form' => 'Submit formulir',
+ 'Board view' => 'Table halaman',
+ 'Keyboard shortcuts' => 'pintas keyboard',
+ 'Open board switcher' => 'Buka table switcher',
+ 'Application' => 'Aplikasi',
+ 'since %B %e, %Y at %k:%M %p' => 'sejak %d/%m/%Y à %H:%M',
+ 'Compact view' => 'Tampilan kompak',
+ 'Horizontal scrolling' => 'Horisontal bergulir',
+ 'Compact/wide view' => 'Beralih antara tampilan kompak dan diperluas',
+ 'No results match:' => 'Tidak ada hasil :',
+ 'Remove hourly rate' => 'Hapus tarif per jam',
+ 'Do you really want to remove this hourly rate?' => 'Apakah anda yakin akan menghapus tarif per jam ini?',
+ 'Hourly rates' => 'Tarif per jam',
+ 'Hourly rate' => 'Tarif per jam',
+ 'Currency' => 'Mata uang',
+ 'Effective date' => 'Tanggal berlaku',
+ 'Add new rate' => 'Tambah tarif per jam baru',
+ 'Rate removed successfully.' => 'Tarif per jam berhasil dihapus.',
+ 'Unable to remove this rate.' => 'Tidak dapat menghapus tarif per jam ini.',
+ 'Unable to save the hourly rate.' => 'Tidak dapat menyimpan tarif per jam.',
+ 'Hourly rate created successfully.' => 'Tarif per jam berhasil dibuat.',
+ 'Start time' => 'Waktu mulai',
+ 'End time' => 'Waktu selesai',
+ 'Comment' => 'Komentar',
+ 'All day' => 'Semua hari',
+ 'Day' => 'Hari',
+ 'Manage timetable' => 'Mengatur jadwal',
+ 'Overtime timetable' => 'Jadwal lembur',
+ 'Time off timetable' => 'Jam absensi',
+ 'Timetable' => 'Jadwal',
+ 'Work timetable' => 'Jadwal kerja',
+ 'Week timetable' => 'Jadwal mingguan',
+ 'Day timetable' => 'Jadwal harian',
+ 'From' => 'Dari',
+ 'To' => 'Untuk',
+ 'Time slot created successfully.' => 'Slot waktu berhasil dibuat.',
+ 'Unable to save this time slot.' => 'Tidak dapat menyimpan slot waktu ini.',
+ 'Time slot removed successfully.' => 'Slot waktu berhasil dihapus.',
+ 'Unable to remove this time slot.' => 'Tidak dapat menghapus slot waktu ini.',
+ 'Do you really want to remove this time slot?' => 'Apakah anda yakin akan menghapus slot waktu ini?',
+ 'Remove time slot' => 'Hapus slot waktu',
+ 'Add new time slot' => 'Tambah slot waktu baru',
+ 'This timetable is used when the checkbox "all day" is checked for scheduled time off and overtime.' => 'Jadwal ini digunakan ketika kotak centang "sepanjang hari" dicentang untuk dijadwalkan cuti dan lembur.',
+ 'Files' => 'Arsip',
+ 'Images' => 'Gambar',
+ 'Private project' => 'Proyek pribadi',
+ 'Amount' => 'Jumlah',
+ 'AUD - Australian Dollar' => 'AUD - Dollar Australia',
+ 'Budget' => 'Anggaran',
+ 'Budget line' => 'Garis anggaran',
+ 'Budget line removed successfully.' => 'Garis anggaran berhasil dihapus.',
+ 'Budget lines' => 'Garis anggaran',
+ 'CAD - Canadian Dollar' => 'CAD - Dollar Kanada',
+ 'CHF - Swiss Francs' => 'CHF - Swiss Prancis',
+ 'Cost' => 'Biaya',
+ 'Cost breakdown' => 'Rincian biaya',
+ 'Custom Stylesheet' => 'Kustomisasi Stylesheet',
+ 'download' => 'unduh',
+ 'Do you really want to remove this budget line?' => 'Apakah anda yakin akan menghapus garis anggaran ini?',
+ 'EUR - Euro' => 'EUR - Euro',
+ 'Expenses' => 'Beban',
+ 'GBP - British Pound' => 'GBP - Poundsterling inggris',
+ 'INR - Indian Rupee' => 'INR - Rupe India',
+ 'JPY - Japanese Yen' => 'JPY - Yen Jepang',
+ 'New budget line' => 'Garis anggaran baru',
+ 'NZD - New Zealand Dollar' => 'NZD - Dollar Selandia baru',
+ 'Remove a budget line' => 'Hapus garis anggaran',
+ 'Remove budget line' => 'Hapus garis anggaran',
+ 'RSD - Serbian dinar' => 'RSD - Dinar Serbia',
+ 'The budget line have been created successfully.' => 'Garis anggaran berhasil dibuat.',
+ 'Unable to create the budget line.' => 'Tidak dapat membuat garis anggaran.',
+ 'Unable to remove this budget line.' => 'Tidak dapat menghapus garis anggaran.',
+ 'USD - US Dollar' => 'USD - Dollar Amerika',
+ 'Remaining' => 'Sisa',
+ 'Destination column' => 'Kolom tujuan',
+ 'Move the task to another column when assigned to a user' => 'Pindahkan tugas ke kolom lain ketika ditugaskan ke pengguna',
+ 'Move the task to another column when assignee is cleared' => 'Pindahkan tugas ke kolom lain ketika orang yang ditugaskan dibersihkan',
+ 'Source column' => 'Sumber kolom',
+ 'Show subtask estimates (forecast of future work)' => 'Lihat perkiraan subtugas(perkiraan di masa depan)',
+ 'Transitions' => 'Transisi',
+ 'Executer' => 'Eksekusi',
+ 'Time spent in the column' => 'Waktu yang dihabiskan dalam kolom',
+ 'Task transitions' => 'Transisi tugas',
+ 'Task transitions export' => 'Ekspor transisi tugas',
+ 'This report contains all column moves for each task with the date, the user and the time spent for each transition.' => 'Laporan ini berisi semua kolom yang pindah untuk setiap tugas dengan tanggal, pengguna dan waktu yang dihabiskan untuk setiap transisi.',
+ 'Currency rates' => 'Nilai tukar mata uang',
+ 'Rate' => 'Tarif',
+ 'Change reference currency' => 'Mengubah referensi mata uang',
+ 'Add a new currency rate' => 'Tambahkan nilai tukar mata uang baru',
+ 'Currency rates are used to calculate project budget.' => 'Nilai tukar mata uang digunakan untuk menghitung anggaran proyek.',
+ 'Reference currency' => 'Referensi mata uang',
+ 'The currency rate have been added successfully.' => 'Nilai tukar mata uang berhasil ditambahkan.',
+ 'Unable to add this currency rate.' => 'Tidak dapat menambahkan nilai tukar mata uang',
+ 'Send notifications to a Slack channel' => 'Kirim pemberitahuan ke saluran Slack',
+ 'Webhook URL' => 'URL webhook',
+ 'Help on Slack integration' => 'Bantuan pada integrasi Slack',
+ '%s remove the assignee of the task %s' => '%s menghapus penugasan dari tugas %s',
+ 'Send notifications to Hipchat' => 'Kirim pemberitahuan ke Hipchat',
+ 'API URL' => 'API URL',
+ 'Room API ID or name' => 'Id kamar API atau nama ',
+ 'Room notification token' => 'Token notifikasi kamar',
+ 'Help on Hipchat integration' => 'Bantuan pada integrasi Hipchat',
+ 'Enable Gravatar images' => 'Mengaktifkan gambar Gravatar',
+ 'Information' => 'Informasi',
+ 'Check two factor authentication code' => 'Cek dua faktor kode otentifikasi',
+ 'The two factor authentication code is not valid.' => 'Kode dua faktor kode otentifikasi tidak valid.',
+ 'The two factor authentication code is valid.' => 'Kode dua faktor kode otentifikasi valid.',
+ 'Code' => 'Kode',
+ 'Two factor authentication' => 'Dua faktor otentifikasi',
+ 'Enable/disable two factor authentication' => 'Matikan/hidupkan dua faktor otentifikasi',
+ 'This QR code contains the key URI: ' => 'kode QR ini mengandung kunci URI : ',
+ 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => 'Menyimpan kunci rahasia ini dalam perangkat lunak TOTP anda(misalnya Googel Authenticator atau FreeOTP).',
+ 'Check my code' => 'Memeriksa kode saya',
+ 'Secret key: ' => 'Kunci rahasia : ',
+ '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).',
+ 'Screenshot taken %s' => 'Screenshot diambil %s',
+ 'Add a screenshot' => 'Tambah screenshot',
+ 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Mengambil screenshot dan tekan CTRL + V atau ⌘ + V untuk paste di sini.',
+ 'Screenshot uploaded successfully.' => 'Screenshot berhasil diunggah.',
+ 'SEK - Swedish Krona' => 'SEK - Krona Swedia',
+ 'The project identifier is an optional alphanumeric code used to identify your project.' => 'Identifier proyek adalah kode alfanumerik opsional digunakan untuk mengidentifikasi proyek Anda.',
+ 'Identifier' => 'Identifier',
+ 'Postmark (incoming emails)' => 'Postmark (email masuk)',
+ 'Help on Postmark integration' => 'Bantuan pada integrasi Postmark',
+ 'Mailgun (incoming emails)' => 'Mailgun (email masuk)',
+ 'Help on Mailgun integration' => 'Bantuan pada integrasi Mailgun',
+ 'Sendgrid (incoming emails)' => 'Sendgrid (email masuk)',
+ 'Help on Sendgrid integration' => 'Bantuan pada integrasi Sendgrid',
+ 'Disable two factor authentication' => 'Matikan dua faktor otentifikasi',
+ 'Do you really want to disable the two factor authentication for this user: "%s"?' => 'Apakah anda yakin akan mematikan dua faktor otentifikasi untuk pengguna ini : « %s » ?',
+ 'Edit link' => 'Modifikasi tautan',
+ 'Start to type task title...' => 'Mulai mengetik judul tugas...',
+ 'A task cannot be linked to itself' => 'Sebuah tugas tidak dapat dikaitkan dengan dirinya sendiri',
+ 'The exact same link already exists' => 'Tautan yang sama persis sudah ada',
+ 'Recurrent task is scheduled to be generated' => 'Tugas berulang dijadwalkan akan dihasilkan',
+ 'Recurring information' => 'Informasi berulang',
+ 'Score' => 'Skor',
+ 'The identifier must be unique' => 'Identifier harus unik',
+ 'This linked task id doesn\'t exists' => 'Id tugas terkait tidak ada',
+ 'This value must be alphanumeric' => 'Nilai harus alfanumerik',
+ 'Edit recurrence' => 'Modifikasi pengulangan',
+ 'Generate recurrent task' => 'Menghasilkan tugas berulang',
+ 'Trigger to generate recurrent task' => 'Memicu untuk menghasilkan tugas berulang',
+ 'Factor to calculate new due date' => 'Faktor untuk menghitung tanggal jatuh tempo baru',
+ 'Timeframe to calculate new due date' => 'Jangka waktu untuk menghitung tanggal jatuh tempo baru',
+ 'Base date to calculate new due date' => 'Tanggal dasar untuk menghitung tanggal jatuh tempo baru',
+ 'Action date' => 'Tanggal aksi',
+ 'Base date to calculate new due date: ' => 'Tanggal dasar untuk menghitung tanggal jatuh tempo baru: ',
+ 'This task has created this child task: ' => 'Tugas ini telah menciptakan tugas anak ini: ',
+ 'Day(s)' => 'Hari',
+ 'Existing due date' => 'Batas waktu yang ada',
+ 'Factor to calculate new due date: ' => 'Faktor untuk menghitung tanggal jatuh tempo baru: ',
+ 'Month(s)' => 'Bulan',
+ 'Recurrence' => 'Pengulangan',
+ 'This task has been created by: ' => 'Tugas ini telah dibuat oleh:',
+ 'Recurrent task has been generated:' => 'Tugas berulang telah dihasilkan:',
+ 'Timeframe to calculate new due date: ' => 'Jangka waktu untuk menghitung tanggal jatuh tempo baru: ',
+ 'Trigger to generate recurrent task: ' => 'Pemicu untuk menghasilkan tugas berulang: ',
+ 'When task is closed' => 'Ketika tugas ditutup',
+ 'When task is moved from first column' => 'Ketika tugas dipindahkan dari kolom pertama',
+ 'When task is moved to last column' => 'Ketika tugas dipindahkan ke kolom terakhir',
+ 'Year(s)' => 'Tahun',
+ 'Jabber (XMPP)' => 'Jabber (XMPP)',
+ 'Send notifications to Jabber' => 'Kirim pemberitahuan ke Jabber',
+ 'XMPP server address' => 'alamat server XMPP',
+ 'Jabber domain' => 'Domain Jabber',
+ 'Jabber nickname' => 'Nickname Jabber',
+ 'Multi-user chat room' => 'Multi-pengguna kamar obrolan',
+ 'Help on Jabber integration' => 'Bantuan pada integrasi Jabber',
+ 'The server address must use this format: "tcp://hostname:5222"' => 'Alamat server harus menggunakan format ini : « tcp://hostname:5222 »',
+ 'Calendar settings' => 'Pengaturan kalender',
+ 'Project calendar view' => 'Tampilan kalender proyek',
+ 'Project settings' => 'Pengaturan proyek',
+ 'Show subtasks based on the time tracking' => 'Tampilkan subtugas berdasarkan pelacakan waktu',
+ 'Show tasks based on the creation date' => 'Tampilkan tugas berdasarkan tanggal pembuatan',
+ 'Show tasks based on the start date' => 'Tampilkan tugas berdasarkan tanggal mulai',
+ 'Subtasks time tracking' => 'Pelacakan waktu subtgas',
+ 'User calendar view' => 'Pengguna tampilan kalender',
+ 'Automatically update the start date' => 'Memperbarui tanggal mulai otomatis',
+ 'iCal feed' => 'iCal feed',
+ 'Preferences' => 'Preferensi',
+ 'Security' => 'Keamanan',
+ 'Two factor authentication disabled' => 'Otentifikasi dua faktor dimatikan',
+ 'Two factor authentication enabled' => 'Otentifikasi dua faktor dihidupkan',
+ 'Unable to update this user.' => 'Tidak dapat memperbarui pengguna ini.',
+ 'There is no user management for private projects.' => 'Tidak ada manajemen pengguna untuk proyek-proyek pribadi.',
+ 'User that will receive the email' => 'Pengguna yang akan menerima email',
+ 'Email subject' => 'Subjek Email',
+ 'Date' => 'Tanggal',
+ 'By @%s on Bitbucket' => 'Oleh @%s pada Bitbucket',
+ 'Bitbucket Issue' => 'Tiket Bitbucket',
+ 'Commit made by @%s on Bitbucket' => 'Komit dibuat oleh @%s pada Bitbucket',
+ 'Commit made by @%s on Github' => 'Komit dibuat oleh @%s pada Github',
+ 'By @%s on Github' => 'Oleh @%s pada Github',
+ 'Commit made by @%s on Gitlab' => 'Komit dibuat oleh @%s pada Gitlab',
+ 'Add a comment log when moving the task between columns' => 'Menambahkan log komentar ketika memindahkan tugas antara kolom',
+ 'Move the task to another column when the category is changed' => 'Pindahkan tugas ke kolom lain ketika kategori berubah',
+ 'Send a task by email to someone' => 'Kirim tugas melalui email ke seseorang',
+ 'Reopen a task' => 'Membuka kembali tugas',
+ 'Bitbucket issue opened' => 'Tiket Bitbucket dibuka',
+ 'Bitbucket issue closed' => 'Tiket Bitbucket ditutup',
+ 'Bitbucket issue reopened' => 'Tiket Bitbucket dibuka kembali',
+ 'Bitbucket issue assignee change' => 'Perubahan penugasan tiket Bitbucket',
+ 'Bitbucket issue comment created' => 'Komentar dibuat tiket Bitbucket',
+ 'Column change' => 'Kolom berubah',
+ 'Position change' => 'Posisi berubah',
+ 'Swimlane change' => 'Swimlane berubah',
+ 'Assignee change' => 'Penerima berubah',
+ '[%s] Overdue tasks' => '[%s] Tugas terlambat',
+ 'Notification' => 'Pemberitahuan',
+ '%s moved the task #%d to the first swimlane' => '%s memindahkan tugas n°%d ke swimlane pertama',
+ '%s moved the task #%d to the swimlane "%s"' => '%s memindahkan tugas n°%d ke swimlane « %s »',
+ 'Swimlane' => 'Swimlane',
+ 'Budget overview' => 'Gambaran anggaran',
+ 'Type' => 'Tipe',
+ 'There is not enough data to show something.' => 'Tidak ada data yang cukup untuk menunjukkan sesuatu.',
+ 'Gravatar' => 'Gravatar',
+ 'Hipchat' => 'Hipchat',
+ 'Slack' => 'Slack',
+ '%s moved the task %s to the first swimlane' => '%s memindahkan tugas %s ke swimlane pertama',
+ '%s moved the task %s to the swimlane "%s"' => '%s memindahkan tugas %s ke swimlane « %s »',
+ 'This report contains all subtasks information for the given date range.' => 'Laporan ini berisi semua informasi subtugas untuk rentang tanggal tertentu.',
+ 'This report contains all tasks information for the given date range.' => 'Laporan ini berisi semua informasi tugas untuk rentang tanggal tertentu.',
+ 'Project activities for %s' => 'Aktifitas proyek untuk « %s »',
+ 'view the board on Kanboard' => 'lihat papan di Kanboard',
+ 'The task have been moved to the first swimlane' => 'Tugas telah dipindahkan ke swimlane pertama',
+ 'The task have been moved to another swimlane:' => 'Tugas telah dipindahkan ke swimlane lain:',
+ 'Overdue tasks for the project "%s"' => 'Tugas terlambat untuk proyek « %s »',
+ 'New title: %s' => 'Judul baru : %s',
+ 'The task is not assigned anymore' => 'Tugas tidak ditugaskan lagi',
+ 'New assignee: %s' => 'Penerima baru : %s',
+ 'There is no category now' => 'Tidak ada kategori untuk sekarang',
+ 'New category: %s' => 'Kategori baru : %s',
+ 'New color: %s' => 'Warna baru : %s',
+ 'New complexity: %d' => 'Kompleksitas baru : %d',
+ 'The due date have been removed' => 'Tanggal jatuh tempo telah dihapus',
+ 'There is no description anymore' => 'Tidak ada deskripsi lagi',
+ 'Recurrence settings have been modified' => 'Pengaturan pengulangan telah dimodifikasi',
+ 'Time spent changed: %sh' => 'Waktu yang dihabiskan berubah : %sh',
+ 'Time estimated changed: %sh' => 'Perkiraan waktu berubah : %sh',
+ 'The field "%s" have been updated' => 'Field « %s » telah diperbaharui',
+ 'The description have been modified' => 'Deskripsi telah dimodifikasi',
+ 'Do you really want to close the task "%s" as well as all subtasks?' => 'Apakah anda yakin akan menutup tugas « %s » beserta semua sub-tugasnya ?',
+ 'Swimlane: %s' => 'Swimlane : %s',
+ 'I want to receive notifications for:' => 'Saya ingin menerima pemberitahuan untuk :',
+ 'All tasks' => 'Semua tugas',
+ 'Only for tasks assigned to me' => 'Hanya untuk tugas yang ditugaskan ke saya',
+ 'Only for tasks created by me' => 'Hanya untuk tugas yang dibuat oleh saya',
+ 'Only for tasks created by me and assigned to me' => 'Hanya untuk tugas yang dibuat oleh saya dan ditugaskan ke saya',
+ '%A' => '%A',
+ '%b %e, %Y, %k:%M %p' => '%d/%m/%Y %H:%M',
+ 'New due date: %B %e, %Y' => 'Tanggal jatuh tempo baru : %d/%m/%Y',
+ 'Start date changed: %B %e, %Y' => 'Tanggal mulai berubah : %d/%m/%Y',
+ '%k:%M %p' => '%H:%M',
+ '%%Y-%%m-%%d' => '%%d/%%m/%%Y',
+ 'Total for all columns' => 'Total untuk semua kolom',
+ 'You need at least 2 days of data to show the chart.' => 'Anda memerlukan setidaknya 2 hari dari data yang menunjukkan grafik.',
+ '<15m' => '<15m',
+ '<30m' => '<30m',
+ 'Stop timer' => 'Hentikan timer',
+ 'Start timer' => 'Mulai timer',
+ 'Add project member' => 'Tambahkan anggota proyek',
+ 'Enable notifications' => 'Aktifkan pemberitahuan',
+ 'My activity stream' => 'Aliran kegiatan saya',
+ 'My calendar' => 'Kalender saya',
+ 'Search tasks' => 'Cari tugas',
+ 'Back to the calendar' => 'Kembali ke kalender',
+ 'Filters' => 'Filter',
+ 'Reset filters' => 'Reset ulang filter',
+ 'My tasks due tomorrow' => 'Tugas saya yang berakhir besok',
+ 'Tasks due today' => 'Tugas yang berakhir hari ini',
+ 'Tasks due tomorrow' => 'Tugas yang berakhir besok',
+ 'Tasks due yesterday' => 'Tugas yang berakhir kemarin',
+ 'Closed tasks' => 'Tugas yang ditutup',
+ 'Open tasks' => 'Buka Tugas',
+ 'Not assigned' => 'Tidak ditugaskan',
+ 'View advanced search syntax' => 'Lihat sintaks pencarian lanjutan',
+ 'Overview' => 'Ikhtisar',
+ '%b %e %Y' => '%b %e %Y',
+ 'Board/Calendar/List view' => 'Tampilan Papan/Kalender/Daftar',
+ 'Switch to the board view' => 'Beralih ke tampilan papan',
+ 'Switch to the calendar view' => 'Beralih ke tampilan kalender',
+ 'Switch to the list view' => 'Beralih ke tampilan daftar',
+ 'Go to the search/filter box' => 'Pergi ke kotak pencarian/filter',
+ 'There is no activity yet.' => 'Tidak ada aktifitas saat ini.',
+ 'No tasks found.' => 'Tidak ada tugas yang ditemukan.',
+ 'Keyboard shortcut: "%s"' => 'Keyboard shortcut : « %s »',
+ 'List' => 'Daftar',
+ 'Filter' => 'Filter',
+ 'Advanced search' => 'Pencarian lanjutan',
+ 'Example of query: ' => 'Contoh dari query : ',
+ 'Search by project: ' => 'Pencarian berdasarkan proyek : ',
+ 'Search by column: ' => 'Pencarian berdasarkan kolom : ',
+ 'Search by assignee: ' => 'Pencarian berdasarkan penerima : ',
+ 'Search by color: ' => 'Pencarian berdasarkan warna : ',
+ 'Search by category: ' => 'Pencarian berdasarkan kategori : ',
+ 'Search by description: ' => 'Pencarian berdasarkan deskripsi : ',
+ 'Search by due date: ' => 'Pencarian berdasarkan tanggal jatuh tempo : ',
+ 'Lead and Cycle time for "%s"' => 'Memimpin dan Siklus waktu untuk « %s »',
+ 'Average time spent into each column for "%s"' => 'Rata-rata waktu yang dihabiskan dalam setiap kolom untuk « %s »',
+ 'Average time spent into each column' => 'Rata-rata waktu yang dihabiskan dalam setiap kolom',
+ 'Average time spent' => 'Rata-rata waktu yang dihabiskan',
+ 'This chart show the average time spent into each column for the last %d tasks.' => 'Grafik ini menunjukkan rata-rata waktu yang dihabiskan dalam setiap kolom untuk %d tugas.',
+ 'Average Lead and Cycle time' => 'Rata-rata Memimpin dan Siklus waktu',
+ 'Average lead time: ' => 'Rata-rata waktu pimpinan : ',
+ 'Average cycle time: ' => 'Rata-rata siklus waktu : ',
+ 'Cycle Time' => 'Siklus Waktu',
+ 'Lead Time' => 'Lead Time',
+ 'This chart show the average lead and cycle time for the last %d tasks over the time.' => 'Grafik ini menunjukkan memimpin rata-rata dan waktu siklus untuk %d tugas terakhir dari waktu ke waktu.',
+ 'Average time into each column' => 'Rata-rata waktu ke setiap kolom',
+ 'Lead and cycle time' => 'Lead dan siklus waktu',
+ 'Google Authentication' => 'Google Otentifikasi',
+ 'Help on Google authentication' => 'Bantuan pada otentifikasi Google',
+ 'Github Authentication' => 'Otentifikasi Github',
+ 'Help on Github authentication' => 'Bantuan pada otentifikasi Github',
+ 'Channel/Group/User (Optional)' => 'Kanal/Grup/Pengguna (pilihan)',
+ 'Lead time: ' => 'Lead time : ',
+ 'Cycle time: ' => 'Siklus waktu : ',
+ 'Time spent into each column' => 'Waktu yang dihabiskan di setiap kolom',
+ 'The lead time is the duration between the task creation and the completion.' => 'Lead time adalah durasi antara pembuatan tugas dan penyelesaian.',
+ 'The cycle time is the duration between the start date and the completion.' => 'Siklus waktu adalah durasi antara tanggal mulai dan tanggal penyelesaian.',
+ 'If the task is not closed the current time is used instead of the completion date.' => 'Jika tugas tidak ditutup waktu saat ini yang digunakan sebagai pengganti tanggal penyelesaian.',
+ 'Set automatically the start date' => 'Secara otomatis mengatur tanggal mulai',
+ 'Edit Authentication' => 'Modifikasi Otentifikasi',
+ 'Google Id' => 'Id Google',
+ 'Github Id' => 'Id Github',
+ 'Remote user' => 'Pengguna jauh',
+ 'Remote users do not store their password in Kanboard database, examples: LDAP, Google and Github accounts.' => 'Pengguna jauh tidak menyimpan kata sandi mereka dalam basis data Kanboard, contoh: akun LDAP, Google dan Github.',
+ 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => 'Jika anda mencentang kotak "Larang formulir login", kredensial masuk ke formulis login akan diabaikan.',
+ 'By @%s on Gitlab' => 'Dengan @%s pada Gitlab',
+ 'Gitlab issue comment created' => 'Komentar dibuat pada tiket Gitlab',
+ 'New remote user' => 'Pengguna baru jauh',
+ 'New local user' => 'Pengguna baru lokal',
+ 'Default task color' => 'Standar warna tugas',
+ 'Hide sidebar' => 'Sembunyikan sidebar',
+ 'Expand sidebar' => 'Perluas sidebar',
+ 'This feature does not work with all browsers.' => 'Fitur ini tidak dapat digunakan di semua browsers',
+ 'There is no destination project available.' => 'Tidak ada destinasi proyek yang tersedia.',
+ 'Trigger automatically subtask time tracking' => 'Otomatis memicu pelacakan untuk subtugas',
+ 'Include closed tasks in the cumulative flow diagram' => 'Termasuk tugas yang ditutup pada diagram aliran kumulatif',
+ 'Current swimlane: %s' => 'Swimlane saat ini : %s',
+ 'Current column: %s' => 'Kolom saat ini : %s',
+ 'Current category: %s' => 'Kategori saat ini : %s',
+ 'no category' => 'tidak ada kategori',
+ 'Current assignee: %s' => 'Saat ini ditugaskan : %s',
+ 'not assigned' => 'Belum ditugaskan',
+ 'Author:' => 'Penulis :',
+ 'contributors' => 'kontributor',
+ 'License:' => 'Lisensi :',
+ 'License' => 'Lisensi',
+ 'Project Administrator' => 'Administrator proyek',
+ 'Enter the text below' => 'Masukkan teks di bawah',
+ 'Gantt chart for %s' => 'Grafik Gantt untuk %s',
+ 'Sort by position' => 'Urutkan berdasarkan posisi',
+ 'Sort by date' => 'Urutkan berdasarkan tanggal',
+ 'Add task' => 'Tambah tugas',
+ 'Start date:' => 'Tanggal mulai :',
+ 'Due date:' => 'Batas waktu :',
+ 'There is no start date or due date for this task.' => 'Tidak ada tanggal mulai dan batas waktu untuk tugas ini.',
+ 'Moving or resizing a task will change the start and due date of the task.' => 'Memindahkan atau mengubah ukuran tugas anda akan mengubah tanggal mulai dan batas waktu dari tugas ini.',
+ 'There is no task in your project.' => 'Tidak ada tugas didalam proyek anda.',
+ 'Gantt chart' => 'Grafik Gantt',
+ 'People who are project managers' => 'Orang-orang yang menjadi manajer proyek',
+ 'People who are project members' => 'Orang-orang yang menjadi anggota proyek',
+ 'NOK - Norwegian Krone' => 'NOK - Krone Norwegia',
+ 'Show this column' => 'Perlihatkan kolom ini',
+ 'Hide this column' => 'Sembunyikan kolom ini',
+ 'open file' => 'buka berkas',
+ 'End date' => 'Waktu berakhir',
+ 'Users overview' => 'Ikhtisar pengguna',
+ 'Managers' => 'Manajer',
+ 'Members' => 'Anggota',
+ 'Shared project' => 'Proyek bersama',
+ 'Project managers' => 'Manajer proyek',
+ 'Project members' => 'Anggota proyek',
+ 'Gantt chart for all projects' => 'Grafik Gantt untuk semua proyek',
+ 'Projects list' => 'Daftar proyek',
+ 'Gantt chart for this project' => 'Grafik Gantt untuk proyek ini',
+ 'Project board' => 'Papan proyek',
+ 'End date:' => 'Waktu berakhir :',
+ 'There is no start date or end date for this project.' => 'Tidak ada waktu mulai atau waktu berakhir untuk proyek ini',
+ 'Projects Gantt chart' => 'Proyek grafik Gantt',
+ 'Start date: %s' => 'Waktu mulai : %s',
+ 'End date: %s' => 'Waktu berakhir : %s',
+ 'Link type' => 'Tipe tautan',
+ 'Change task color when using a specific task link' => 'Rubah warna tugas ketika menggunakan tautan tugas yang spesifik',
+ 'Task link creation or modification' => 'Tautan pembuatan atau modifikasi tugas ',
+ 'Login with my Gitlab Account' => 'Masuk menggunakan akun Gitlab saya',
+ 'Milestone' => 'Milestone',
+ 'Gitlab Authentication' => 'Authentification Gitlab',
+ 'Help on Gitlab authentication' => 'Bantuan pada otentifikasi Gitlab',
+ 'Gitlab Id' => 'Id Gitlab',
+ 'Gitlab Account' => 'Akun Gitlab',
+ 'Link my Gitlab Account' => 'Hubungkan akun Gitlab saya',
+ 'Unlink my Gitlab Account' => 'Putuskan akun Gitlab saya',
+ 'Documentation: %s' => 'Dokumentasi : %s',
+ 'Switch to the Gantt chart view' => 'Beralih ke tampilan grafik Gantt',
+ 'Reset the search/filter box' => 'Atur ulang pencarian/kotak filter',
+ 'Documentation' => 'Dokumentasi',
+ 'Table of contents' => 'Daftar isi',
+ 'Gantt' => 'Gantt',
+ 'Help with project permissions' => 'Bantuan dengan izin proyek',
+);
diff --git a/app/Locale/it_IT/translations.php b/app/Locale/it_IT/translations.php
index 06e2c5ca..e27245f9 100644
--- a/app/Locale/it_IT/translations.php
+++ b/app/Locale/it_IT/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Remoto',
'Enabled' => 'Abilitato',
'Disabled' => 'Disabilitato',
- 'Google account linked' => 'Account Google collegato',
- 'Github account linked' => 'Account Github collegato',
// 'Username:' => '',
'Name:' => 'Nome:',
// 'Email:' => '',
@@ -667,17 +665,7 @@ return array(
'Horizontal scrolling' => 'Scrolling orizzontale',
'Compact/wide view' => 'Vista compatta/estesa',
'No results match:' => 'Nessun risultato trovato:',
- 'Remove hourly rate' => 'Rimuovi tariffa oraria',
- 'Do you really want to remove this hourly rate?' => 'Vuoi davvero rimuovere questa tariffa oraria?',
- 'Hourly rates' => 'Tariffe orarie',
- 'Hourly rate' => 'Tariffa oraria',
'Currency' => 'Valuta',
- 'Effective date' => 'Data effettiva',
- 'Add new rate' => 'Aggiungi una nuova tariffa',
- 'Rate removed successfully.' => 'Tariffa rimossa con successo.',
- 'Unable to remove this rate.' => 'Impossibile rimuovere questa tariffa.',
- 'Unable to save the hourly rate.' => 'Impossibile salvare la tariffa oraria.',
- 'Hourly rate created successfully.' => 'Tariffa oraria creata con successo.',
'Start time' => 'Data di inizio',
'End time' => 'Data di completamento',
'Comment' => 'Commento',
@@ -703,34 +691,18 @@ return array(
// 'Files' => '',
'Images' => 'Immagini',
'Private project' => 'Progetto privato',
- 'Amount' => 'Totale',
'AUD - Australian Dollar' => 'AUD - Dollari Australiani',
- 'Budget' => 'Bilancio',
- 'Budget line' => 'Limite di bilancio',
- 'Budget line removed successfully.' => 'Limite al bilancio rimosso con successo.',
- 'Budget lines' => 'Limiti al bilancio',
'CAD - Canadian Dollar' => 'CAD - Dollari Canadesi',
'CHF - Swiss Francs' => 'CHF - Franchi Svizzeri',
- 'Cost' => 'Costi',
- 'Cost breakdown' => 'Abbattimento dei costi',
'Custom Stylesheet' => 'CSS personalizzato',
// 'download' => '',
- 'Do you really want to remove this budget line?' => 'Vuoi davvero rimuovere questo limite al bilancio?',
// 'EUR - Euro' => '',
- 'Expenses' => 'Spese',
'GBP - British Pound' => 'GBP - Pound Inglesi',
'INR - Indian Rupee' => 'INR - Rupie Indiani',
'JPY - Japanese Yen' => 'JPY - Yen Giapponesi',
- 'New budget line' => 'Nuovo limite al bilancio',
'NZD - New Zealand Dollar' => 'NZD - Dollari della Nuova Zelanda',
- 'Remove a budget line' => 'Rimuovi un limite al bilancio',
- 'Remove budget line' => 'Rimuovi limite di bilancio',
'RSD - Serbian dinar' => 'RSD - Dinar Serbi',
- 'The budget line have been created successfully.' => 'Il limite al bilancio è stato creato correttamente',
- 'Unable to create the budget line.' => 'Impossibile creare il limite al bilancio',
- 'Unable to remove this budget line.' => 'Impossibile rimuovere questo limite al bilancio.',
'USD - US Dollar' => 'USD - Dollari Americani',
- 'Remaining' => 'Restanti',
'Destination column' => 'Colonna destinazione',
'Move the task to another column when assigned to a user' => 'Sposta il compito in un\'altra colonna quando viene assegnato ad un utente',
'Move the task to another column when assignee is cleared' => 'Sposta il compito in un\'altra colonna quando l\'assegnatario cancellato',
@@ -746,7 +718,6 @@ return array(
'Rate' => 'Cambio',
'Change reference currency' => 'Cambia la valuta di riferimento',
'Add a new currency rate' => 'Aggiungi un nuovo tasso di cambio',
- 'Currency rates are used to calculate project budget.' => 'I tassi di cambio sono utilizzati per calcolare i bilanci dei progetti',
'Reference currency' => 'Valuta di riferimento',
'The currency rate have been added successfully.' => 'Il tasso di cambio è stato aggiunto con successo.',
'Unable to add this currency rate.' => 'Impossibile aggiungere questo tasso di cambio.',
@@ -878,9 +849,6 @@ return array(
// '%s moved the task #%d to the first swimlane' => '',
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
- // 'Budget overview' => '',
- // 'Type' => '',
- // 'There is not enough data to show something.' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Locale/ja_JP/translations.php b/app/Locale/ja_JP/translations.php
index cb8c550b..49f92f27 100644
--- a/app/Locale/ja_JP/translations.php
+++ b/app/Locale/ja_JP/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'リモート',
'Enabled' => '有効',
'Disabled' => '無効',
- 'Google account linked' => 'Google アカウントãŒãƒªãƒ³ã‚¯',
- 'Github account linked' => 'Github ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆãŒãƒªãƒ³ã‚¯',
'Username:' => 'ユーザå:',
'Name:' => 'åå‰:',
'Email:' => 'Email:',
@@ -667,17 +665,7 @@ return array(
'Horizontal scrolling' => '縦スクロール',
'Compact/wide view' => 'コンパクトï¼ãƒ¯ã‚¤ãƒ‰ãƒ“ュー',
'No results match:' => 'çµæžœãŒä¸€è‡´ã—ã¾ã›ã‚“ã§ã—ãŸ',
- 'Remove hourly rate' => '毎時レートを削除',
- 'Do you really want to remove this hourly rate?' => '毎時レートを削除ã—ã¾ã™ã‹ï¼Ÿ',
- 'Hourly rates' => '毎時レート',
- 'Hourly rate' => '毎時レート',
'Currency' => '通貨',
- 'Effective date' => '有効期é™',
- 'Add new rate' => 'æ–°ã—ã„レート',
- 'Rate removed successfully.' => 'レートã®å‰Šé™¤ã«æˆåŠŸã—ã¾ã—ãŸã€‚',
- 'Unable to remove this rate.' => 'レートを削除ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚',
- 'Unable to save the hourly rate.' => '時間毎ã®ãƒ¬ãƒ¼ãƒˆã‚’ä¿å­˜ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚',
- 'Hourly rate created successfully.' => '時間毎ã®ãƒ¬ãƒ¼ãƒˆã‚’作æˆã—ã¾ã—ãŸã€‚',
'Start time' => '開始時間',
'End time' => '終了時間',
'Comment' => 'コメント',
@@ -703,34 +691,18 @@ return array(
'Files' => 'ファイル',
'Images' => 'ç”»åƒ',
'Private project' => 'プライベートプロジェクト',
- 'Amount' => 'æ•°é‡',
'AUD - Australian Dollar' => 'AUD - 豪ドル',
- 'Budget' => '予算',
- 'Budget line' => '予算ライン',
- 'Budget line removed successfully.' => '予算ラインを削除ã—ã¾ã—ãŸ.',
- 'Budget lines' => '予算ライン',
'CAD - Canadian Dollar' => 'CAD - 加ドル',
'CHF - Swiss Francs' => 'CHF - スイスフラン',
- 'Cost' => 'コスト',
- 'Cost breakdown' => 'コストブレークダウン',
'Custom Stylesheet' => 'カスタムスタイルシート',
'download' => 'ダウンロード',
- 'Do you really want to remove this budget line?' => 'ã“ã®äºˆç®—ラインを本当ã«å‰Šé™¤ã—ã¾ã™ã‹ï¼Ÿ',
'EUR - Euro' => 'EUR - ユーロ',
- 'Expenses' => '支出',
'GBP - British Pound' => 'GBP - 独ãƒãƒ³ãƒ‰',
'INR - Indian Rupee' => 'INR - 伊ルピー',
'JPY - Japanese Yen' => 'JPY - 日本円',
- 'New budget line' => 'æ–°ã—ã„予算ライン',
'NZD - New Zealand Dollar' => 'NZD - NZ ドル',
- 'Remove a budget line' => '予算ラインã®å‰Šé™¤',
- 'Remove budget line' => '予算ラインã®å‰Šé™¤',
'RSD - Serbian dinar' => 'RSD - セルビアデナール',
- 'The budget line have been created successfully.' => '予算ラインを作æˆã—ã¾ã—ãŸ',
- 'Unable to create the budget line.' => '予算ラインを作æˆã§ãã¾ã›ã‚“ã§ã—ãŸã€‚',
- 'Unable to remove this budget line.' => '予算ラインを削除ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚',
'USD - US Dollar' => 'USD - 米ドル',
- 'Remaining' => '残り',
'Destination column' => '移動先ã®ã‚«ãƒ©ãƒ ',
'Move the task to another column when assigned to a user' => 'ユーザã®å‰²ã‚Šå½“ã¦ã‚’ã—ãŸã‚‰ã‚¿ã‚¹ã‚¯ã‚’ä»–ã®ã‚«ãƒ©ãƒ ã«ç§»å‹•',
'Move the task to another column when assignee is cleared' => 'ユーザã®å‰²ã‚Šå½“ã¦ãŒãªããªã£ãŸã‚‰ã‚¿ã‚¹ã‚¯ã‚’ä»–ã®ã‚«ãƒ©ãƒ ã«ç§»å‹•',
@@ -746,7 +718,6 @@ return array(
'Rate' => 'レート',
'Change reference currency' => 'ç¾åœ¨ã®åŸºè»¸é€šè²¨',
'Add a new currency rate' => 'æ–°ã—ã„通貨レートを追加',
- 'Currency rates are used to calculate project budget.' => '通貨レートã¯ãƒ—ロジェクト予算ã®ç®—出ã«åˆ©ç”¨ã•ã‚Œã¾ã™ã€‚',
'Reference currency' => '基軸通貨',
// 'The currency rate have been added successfully.' => '',
'Unable to add this currency rate.' => 'ã“ã®é€šè²¨ãƒ¬ãƒ¼ãƒˆã‚’追加ã§ãã¾ã›ã‚“。',
@@ -878,9 +849,6 @@ return array(
// '%s moved the task #%d to the first swimlane' => '',
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
- // 'Budget overview' => '',
- // 'Type' => '',
- // 'There is not enough data to show something.' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Locale/nb_NO/translations.php b/app/Locale/nb_NO/translations.php
index 155d49b4..9880b921 100644..100755
--- a/app/Locale/nb_NO/translations.php
+++ b/app/Locale/nb_NO/translations.php
@@ -1,8 +1,8 @@
<?php
return array(
- // 'number.decimals_separator' => '',
- // 'number.thousands_separator' => '',
+ 'number.decimals_separator' => ',',
+ 'number.thousands_separator' => '.',
'None' => 'Ingen',
'edit' => 'rediger',
'Edit' => 'Rediger',
@@ -14,12 +14,12 @@ return array(
'cancel' => 'avbryt',
'or' => 'eller',
'Yellow' => 'Gul',
- 'Blue' => 'Blå',
- 'Green' => 'Grønn',
+ 'Blue' => 'Blå',
+ 'Green' => 'Grønn',
'Purple' => 'Lilla',
- 'Red' => 'Rød',
+ 'Red' => 'Rød',
'Orange' => 'Orange',
- 'Grey' => 'Grå',
+ 'Grey' => 'Grå',
// 'Brown' => '',
// 'Deep Orange' => '',
// 'Dark Grey' => '',
@@ -61,7 +61,7 @@ return array(
'Inactive' => 'Inaktiv',
'Active' => 'Aktiv',
'Add this column' => 'Legg til denne kolonnen',
- '%d tasks on the board' => '%d Oppgaver på hovedsiden',
+ '%d tasks on the board' => '%d Oppgaver på hovedsiden',
'%d tasks in total' => '%d Oppgaver i alt',
'Unable to update this board.' => 'Ikke mulig at oppdatere hovedsiden',
'Edit board' => 'Endre prosjektsiden',
@@ -79,15 +79,15 @@ return array(
'Assigned to %s' => 'Tildelt: %s',
'Remove a column' => 'Fjern en kolonne',
'Remove a column from a board' => 'Fjern en kolonne fra et board',
- 'Unable to remove this column.' => 'Ikke mulig fjerne denne kolonnen',
+ 'Unable to remove this column.' => 'Ikke mulig ø fjerne denne kolonnen',
'Do you really want to remove this column: "%s"?' => 'Vil du fjerne denne kolonnen: "%s"?',
'This action will REMOVE ALL TASKS associated to this column!' => 'Denne handlingen vil SLETTE ALLE OPPGAVER tilknyttet denne kolonnen',
'Settings' => 'Innstillinger',
'Application settings' => 'Applikasjonsinnstillinger',
- 'Language' => 'Språk',
+ 'Language' => 'Språk',
'Webhook token:' => 'Webhook token:',
'API token:' => 'API Token:',
- 'Database size:' => 'Databasestørrelse:',
+ 'Database size:' => 'Databasestørrelse:',
'Download the database' => 'Last ned databasen',
'Optimize the database' => 'Optimaliser databasen',
'(VACUUM command)' => '(VACUUM kommando)',
@@ -99,36 +99,37 @@ return array(
'Assignee' => 'Tildelt',
'Create another task' => 'Opprett en annen oppgave',
'New task' => 'Ny oppgave',
- 'Open a task' => 'Ã…pne en oppgave',
- 'Do you really want to open this task: "%s"?' => 'Vil du åpe denne oppgaven: "%s"?',
+ 'Open a task' => 'Åpne en oppgave',
+ 'Do you really want to open this task: "%s"?' => 'Vil du åpne denne oppgaven: "%s"?',
'Back to the board' => 'Tilbake til prosjektsiden',
'Created on %B %e, %Y at %k:%M %p' => 'Opprettet %d.%m.%Y - %H:%M',
'There is nobody assigned' => 'Mangler tildeling',
'Column on the board:' => 'Kolonne:',
- 'Status is open' => 'Status: åpen',
+ 'Status is open' => 'Status: åpen',
'Status is closed' => 'Status: lukket',
'Close this task' => 'Lukk oppgaven',
- 'Open this task' => 'Ã…pne denne oppgaven',
+ 'Open this task' => 'Åpne denne oppgaven',
'There is no description.' => 'Det er ingen beskrivelse.',
'Add a new task' => 'Opprett ny oppgave',
- 'The username is required' => 'Brukernavn er påkrevd',
+ 'The username is required' => 'Brukernavn er påkrevd',
'The maximum length is %d characters' => 'Den maksimale lengden er %d tegn',
'The minimum length is %d characters' => 'Den minimale lengden er %d tegn',
- 'The password is required' => 'Passord er påkrevet',
- 'This value must be an integer' => 'Denne verdien skal være et tall',
- 'The username must be unique' => 'Brukernavnet skal være unikt',
- 'The user id is required' => 'Bruker-id er påkrevet',
+ 'The password is required' => 'Passord er påkrevet',
+ 'This value must be an integer' => 'Denne verdien skal være et tall',
+ 'The username must be unique' => 'Brukernavnet skal være unikt',
+ 'The user id is required' => 'Bruker-id er påkrevet',
'Passwords don\'t match' => 'Passordene stemmer ikke overens',
- 'The confirmation is required' => 'Bekreftelse er nødvendig',
- 'The project is required' => 'Prosjektet er påkrevet',
- 'The id is required' => 'Id\'en er påkrevd',
- 'The project id is required' => 'Prosjektet-id er påkrevet',
- 'The project name is required' => 'Prosjektnavn er påkrevet',
- 'This project must be unique' => 'Prosjektnavnet skal være unikt',
- 'The title is required' => 'Tittel er pårevet',
+ 'The confirmation is required' => 'Bekreftelse er nødvendig',
+ 'The project is required' => 'Prosjektet er påkrevet',
+ 'The id is required' => 'Id\'en er pøøkrevet',
+ 'The project id is required' => 'Prosjektet-id er påkrevet',
+ 'The project name is required' => 'Prosjektnavn er påkrevet',
+ 'This project must be unique' => 'Prosjektnavnet skal være unikt',
+ 'The title is required' => 'Tittel er pårevet',
+ 'There is no active project, the first step is to create a new project.' => 'Det er ingen aktive prosjekter. Førstesteg er åopprette et nytt prosjekt.',
'Settings saved successfully.' => 'Innstillinger lagret.',
'Unable to save your settings.' => 'Innstillinger kunne ikke lagres.',
- 'Database optimization done.' => 'Databaseoptimering er fullført.',
+ 'Database optimization done.' => 'Databaseoptimering er fullført.',
'Your project have been created successfully.' => 'Ditt prosjekt er opprettet.',
'Unable to create your project.' => 'Prosjektet kunne ikke opprettes',
'Project updated successfully.' => 'Prosjektet er oppdatert.',
@@ -139,9 +140,9 @@ return array(
'Unable to activate this project.' => 'Prosjektet kunne ikke aktiveres.',
'Project disabled successfully.' => 'Prosjektet er deaktiveret.',
'Unable to disable this project.' => 'Prosjektet kunne ikke deaktiveres.',
- 'Unable to open this task.' => 'Oppgaven kunne ikke åpnes.',
- 'Task opened successfully.' => 'Oppgaven er åpnet.',
- 'Unable to close this task.' => 'Oppgaven kunne ikke åpnes.',
+ 'Unable to open this task.' => 'Oppgaven kunne ikke åpnes.',
+ 'Task opened successfully.' => 'Oppgaven er åpnet.',
+ 'Unable to close this task.' => 'Oppgaven kunne ikke åpnes.',
'Task closed successfully.' => 'Oppgaven er lukket.',
'Unable to update your task.' => 'Oppgaven kunne ikke oppdateres.',
'Task updated successfully.' => 'Oppgaven er oppdatert.',
@@ -149,7 +150,7 @@ return array(
'Task created successfully.' => 'Oppgaven er opprettet.',
'User created successfully.' => 'Brukeren er opprettet.',
'Unable to create your user.' => 'Brukeren kunne ikke opprettes.',
- 'User updated successfully.' => 'Brukeren er opdateret',
+ 'User updated successfully.' => 'Brukeren er oppdatert',
'Unable to update your user.' => 'Din bruker kunne ikke oppdateres.',
'User removed successfully.' => 'Brukeren er fjernet.',
'Unable to remove this user.' => 'Brukeren kunne ikke slettes.',
@@ -157,13 +158,15 @@ return array(
'Ready' => 'Klar',
'Backlog' => 'Backlog',
'Work in progress' => 'Under arbeid',
- 'Done' => 'Utført',
+ 'Done' => 'Utført',
'Application version:' => 'Versjon:',
- 'Completed on %B %e, %Y at %k:%M %p' => 'Fullført %d.%m.%Y - %H:%M',
+ 'Completed on %B %e, %Y at %k:%M %p' => 'Fullført %d.%m.%Y - %H:%M',
'%B %e, %Y at %k:%M %p' => '%d.%m.%Y - %H:%M',
'Date created' => 'Dato for opprettelse',
- 'Date completed' => 'Dato for fullført',
+ 'Date completed' => 'Dato for fullført',
'Id' => 'ID',
+ 'Completed tasks' => 'Fullførte oppgaver',
+ 'Completed tasks for "%s"' => 'Fullførte oppgaver for "%s"',
'%d closed tasks' => '%d lukkede oppgaver',
'No task for this project' => 'Ingen oppgaver i dette prosjektet',
'Public link' => 'Offentligt lenke',
@@ -175,10 +178,10 @@ return array(
'Page not found' => 'Siden er ikke funnet',
'Complexity' => 'Kompleksitet',
'Task limit' => 'Oppgave begrensning',
- // 'Task count' => '',
+ 'Task count' => 'Antall oppgaver',
'Edit project access list' => 'Endre tillatelser for prosjektet',
'Allow this user' => 'Tillat denne brukeren',
- 'Don\'t forget that administrators have access to everything.' => 'Hust at administratorer har tilgang til alt.',
+ 'Don\'t forget that administrators have access to everything.' => 'Husk at administratorer har tilgang til alt.',
'Revoke' => 'Fjern',
'List of authorized users' => 'Liste over autoriserte brukere',
'User' => 'Bruker',
@@ -186,14 +189,14 @@ return array(
'Comments' => 'Kommentarer',
'Write your text in Markdown' => 'Skriv din tekst i markdown',
'Leave a comment' => 'Legg inn en kommentar',
- 'Comment is required' => 'Kommentar må legges inn',
+ 'Comment is required' => 'Kommentar må legges inn',
'Leave a description' => 'Legg inn en beskrivelse...',
'Comment added successfully.' => 'Kommentaren er lagt til.',
'Unable to create your comment.' => 'Din kommentar kunne ikke opprettes.',
'Edit this task' => 'Rediger oppgaven',
'Due Date' => 'Forfallsdato',
'Invalid date' => 'Ugyldig dato',
- 'Must be done before %B %e, %Y' => 'Skal være utført innen %d.%m.%Y',
+ 'Must be done before %B %e, %Y' => 'Skal være utført innen %d.%m.%Y',
'%B %e, %Y' => '%d.%m.%Y',
// '%b %e, %Y' => '',
'Automatic actions' => 'Automatiske handlinger',
@@ -205,19 +208,19 @@ return array(
'Automatic actions for the project "%s"' => 'Automatiske handlinger for prosjektet "%s"',
'Defined actions' => 'Definerte handlinger',
'Add an action' => 'Legg til en handling',
- 'Event name' => 'Begivenhet',
+ 'Event name' => 'Hendelsehet',
'Action name' => 'Handling',
'Action parameters' => 'Handlingsparametre',
'Action' => 'Handling',
- 'Event' => 'Begivenhet',
- 'When the selected event occurs execute the corresponding action.' => 'Når den valgtebegivenheten oppstår, utføre tilsvarende handlin.',
+ 'Event' => 'Hendelse',
+ 'When the selected event occurs execute the corresponding action.' => 'Når den valgte hendelsen oppstår, utfør tilsvarende handling.',
'Next step' => 'Neste',
'Define action parameters' => 'Definer handlingsparametre',
'Save this action' => 'Lagre handlingen',
- 'Do you really want to remove this action: "%s"?' => 'Vil du virkelig slette denne handlingen: "%s"?',
+ 'Do you really want to remove this action: "%s"?' => 'Vil du slette denne handlingen: "%s"?',
'Remove an automatic action' => 'Fjern en automatisk handling',
'Assign the task to a specific user' => 'Tildel oppgaven til en bestemt bruker',
- 'Assign the task to the person who does the action' => 'Tildel oppgaven til den person, som utfører handlingen',
+ 'Assign the task to the person who does the action' => 'Tildel oppgaven til den person, som utfører handlingen',
'Duplicate the task to another project' => 'Kopier oppgaven til et annet prosjekt',
'Move a task to another column' => 'Flytt oppgaven til en annen kolonne',
'Task modification' => 'Oppgaveendring',
@@ -236,37 +239,41 @@ return array(
'Remove a comment' => 'Fjern en kommentar',
'Comment removed successfully.' => 'Kommentaren ble fjernet.',
'Unable to remove this comment.' => 'Kommentaren kunne ikke fjernes.',
- 'Do you really want to remove this comment?' => 'Vil du virkelig fjerne denne kommentaren?',
+ 'Do you really want to remove this comment?' => 'Vil du fjerne denne kommentaren?',
'Only administrators or the creator of the comment can access to this page.' => 'Kun administrator eller brukeren, som har oprettet kommentaren har adgang til denne siden.',
'Current password for the user "%s"' => 'Aktivt passord for brukeren "%s"',
- 'The current password is required' => 'Passord er påkrevet',
+ 'The current password is required' => 'Passord er påkrevet',
'Wrong password' => 'Feil passord',
'Unknown' => 'Ukjent',
- 'Last logins' => 'Siste login',
+ 'Last logins' => 'Siste innlogging',
'Login date' => 'Login dato',
'Authentication method' => 'Godkjenningsmetode',
'IP address' => 'IP Adresse',
'User agent' => 'User Agent',
'Persistent connections' => 'Varige forbindelser',
'No session.' => 'Ingen session.',
- 'Expiration date' => 'Utløpsdato',
+ 'Expiration date' => 'Utløpsdato',
'Remember Me' => 'Husk meg',
'Creation date' => 'Opprettelsesdato',
+ 'Filter by user' => 'Filtrer efter bruker',
+ 'Filter by due date' => 'Filtrer etter forfallsdato',
'Everybody' => 'Alle',
- 'Open' => 'Ã…pen',
+ 'Open' => 'Åpen',
'Closed' => 'Lukket',
- 'Search' => 'Søk',
+ 'Search' => 'Søk',
'Nothing found.' => 'Intet funnet.',
+ 'Search in the project "%s"' => 'Søk i prosjektet "%s"',
'Due date' => 'Forfallsdato',
'Others formats accepted: %s and %s' => 'Andre formater: %s og %s',
'Description' => 'Beskrivelse',
'%d comments' => '%d kommentarer',
'%d comment' => '%d kommentar',
'Email address invalid' => 'Ugyldig epost',
- // 'Your external account is not linked anymore to your profile.' => '',
- // 'Unable to unlink your external account.' => '',
- // 'External authentication failed' => '',
- // 'Your external account is linked to your profile successfully.' => '',
+ 'Your Google Account is not linked anymore to your profile.' => 'Din Google-konto er ikke lengre knyttet til din profil.',
+ 'Unable to unlink your Google Account.' => 'Det var ikke mulig ø fjerne din Google-konto.',
+ 'Google authentication failed' => 'Google godjenning mislyktes',
+ 'Unable to link your Google Account.' => 'Det var ikke mulig åknytte opp til din Google-konto.',
+ 'Your Google Account is linked to your profile successfully.' => 'Din Google-konto er knyttet til din profil.',
'Email' => 'Epost',
'Link my Google Account' => 'Knytt til min Google-konto',
'Unlink my Google Account' => 'Fjern knytningen til min Google-konto',
@@ -275,9 +282,9 @@ return array(
'Task removed successfully.' => 'Oppgaven er fjernet.',
'Unable to remove this task.' => 'Oppgaven kunne ikke fjernes.',
'Remove a task' => 'Fjern en oppgave',
- 'Do you really want to remove this task: "%s"?' => 'Vil du virkelig fjerne denne opgave: "%s"?',
+ 'Do you really want to remove this task: "%s"?' => 'Vil du fjerne denne oppgaven: "%s"?',
'Assign automatically a color based on a category' => 'Tildel automatisk en farge baseret for en kategori',
- 'Assign automatically a category based on a color' => 'Tildel automatisk en kategori basert på en farve',
+ 'Assign automatically a category based on a color' => 'Tildel automatisk en kategori basert på en farve',
'Task creation or modification' => 'Oppgaveopprettelse eller endring',
'Category' => 'Kategori',
'Category:' => 'Kategori:',
@@ -293,17 +300,18 @@ return array(
'Category modification for the project "%s"' => 'Endring av kategori for prosjektet "%s"',
'Category Name' => 'Kategorinavn',
'Add a new category' => 'Legg til ny kategori',
- 'Do you really want to remove this category: "%s"?' => 'Vil du virkelig fjerne kategorien: "%s"?',
+ 'Do you really want to remove this category: "%s"?' => 'Vil du fjerne kategorien: "%s"?',
+ 'Filter by category' => 'Filter etter kategori',
'All categories' => 'Alle kategorier',
'No category' => 'Ingen kategori',
- 'The name is required' => 'Navnet er påkrevet',
+ 'The name is required' => 'Navnet er påkrevet',
'Remove a file' => 'Fjern en fil',
'Unable to remove this file.' => 'Filen kunne ikke fjernes.',
'File removed successfully.' => 'Filen er fjernet.',
'Attach a document' => 'Legg til et dokument',
- 'Do you really want to remove this file: "%s"?' => 'Vil du virkelig fjerne filen: "%s"?',
- 'open' => 'Ã¥pen',
- 'Attachments' => 'Vedleggr',
+ 'Do you really want to remove this file: "%s"?' => 'Vil du fjerne filen: "%s"?',
+ 'open' => 'øpen',
+ 'Attachments' => 'Vedlegg',
'Edit the task' => 'Rediger oppgaven',
'Edit the description' => 'Rediger beskrivelsen',
'Add a comment' => 'Legg til en kommentar',
@@ -312,8 +320,8 @@ return array(
'Time tracking' => 'Tidsregistrering',
'Estimate:' => 'Estimat:',
'Spent:' => 'Brukt:',
- 'Do you really want to remove this sub-task?' => 'Vil du virkelig fjerne denne deloppgaven?',
- 'Remaining:' => 'Gjenværende:',
+ 'Do you really want to remove this sub-task?' => 'Vil du fjerne denne deloppgaven?',
+ 'Remaining:' => 'Gjenværende:',
'hours' => 'timer',
'spent' => 'brukt',
'estimated' => 'estimat',
@@ -324,8 +332,8 @@ return array(
'Time spent' => 'Tidsforbruk',
'Edit a sub-task' => 'Rediger en deloppgave',
'Remove a sub-task' => 'Fjern en deloppgave',
- 'The time must be a numeric value' => 'Tiden skal være en nummerisk erdi',
- 'Todo' => 'Gjøremål',
+ 'The time must be a numeric value' => 'Tiden skal være en nummerisk erdi',
+ 'Todo' => 'Gjøremål',
'In progress' => 'Under arbeid',
'Sub-task removed successfully.' => 'Deloppgaven er fjernet.',
'Unable to remove this sub-task.' => 'Deloppgaven kunne ikke fjernes.',
@@ -333,19 +341,24 @@ return array(
'Unable to update your sub-task.' => 'Deloppgaven kunne ikke opdateres.',
'Unable to create your sub-task.' => 'Deloppgaven kunne ikke oprettes.',
'Sub-task added successfully.' => 'Deloppgaven er lagt til.',
- 'Maximum size: ' => 'Maksimum størrelse: ',
+ 'Maximum size: ' => 'Maksimum størrelse: ',
'Unable to upload the file.' => 'Filen kunne ikke lastes opp.',
'Display another project' => 'Vis annet prosjekt...',
- // 'Login with my Github Account' => '',
- // 'Link my Github Account' => '',
- // 'Unlink my Github Account' => '',
+ 'Your GitHub account was successfully linked to your profile.' => 'Din GitHub-konto er knyttet til din profil.',
+ 'Unable to link your GitHub Account.' => 'Det var ikke mulig å knytte din GitHub-konto.',
+ 'GitHub authentication failed' => 'GitHub godkjenning mislyktes',
+ 'Your GitHub account is no longer linked to your profile.' => 'Din GitHub-konto er ikke lengere knyttet til din profil.',
+ 'Unable to unlink your GitHub Account.' => 'Det var ikke muligt at fjerne forbindelsen til din GitHub-konto.',
+ 'Login with my GitHub Account' => 'Login med min GitHub-konto',
+ 'Link my GitHub Account' => 'Knytt min GitHub-konto',
+ 'Unlink my GitHub Account' => 'Fjern knytningen til min GitHub-konto',
'Created by %s' => 'Opprettet av %s',
'Last modified on %B %e, %Y at %k:%M %p' => 'Sist endret %d.%m.%Y - %H:%M',
'Tasks Export' => 'Oppgave eksport',
'Tasks exportation for "%s"' => 'Oppgaveeksportering for "%s"',
'Start Date' => 'Start-dato',
'End Date' => 'Slutt-dato',
- 'Execute' => 'KKjør',
+ 'Execute' => 'KKjør',
'Task Id' => 'Oppgave ID',
'Creator' => 'Laget av',
'Modification date' => 'Endringsdato',
@@ -354,15 +367,15 @@ return array(
'Project cloned successfully.' => 'Prosjektet er kopiert.',
'Unable to clone this project.' => 'Prosjektet kunne ikke kopieres',
'Email notifications' => 'Epostvarslinger',
- 'Enable email notifications' => 'Aktiver eposvarslinger',
+ 'Enable email notifications' => 'Aktiver epostvarslinger',
'Task position:' => 'Oppgaveposisjon:',
- 'The task #%d have been opened.' => 'Oppgaven #%d er åpnet.',
+ 'The task #%d have been opened.' => 'Oppgaven #%d er åpnet.',
'The task #%d have been closed.' => 'Oppgaven #%d er lukket.',
'Sub-task updated' => 'Deloppgaven er oppdatert',
'Title:' => 'Tittel:',
'Status:' => 'Status:',
'Assignee:' => 'Ansvarlig:',
- 'Time tracking:' => 'Tidsmåling:',
+ 'Time tracking:' => 'Tidsmåling:',
'New sub-task' => 'Ny deloppgave',
'New attachment added "%s"' => 'Nytt vedlegg er lagt tilet "%s"',
'Comment updated' => 'Kommentar oppdatert',
@@ -373,21 +386,21 @@ return array(
'Subtask updated' => 'Deloppgave oppdatert',
'Task updated' => 'Oppgave oppdatert',
'Task closed' => 'Oppgave lukket',
- 'Task opened' => 'Oppgave åpnet',
+ 'Task opened' => 'Oppgave åpnet',
'I want to receive notifications only for those projects:' => 'Jeg vil kun ha varslinger for disse prosjekter:',
- 'view the task on Kanboard' => 'se oppgaven påhovedsiden',
+ 'view the task on Kanboard' => 'se oppgaven påhovedsiden',
'Public access' => 'Offentlig tilgang',
'User management' => 'Brukere',
'Active tasks' => 'Aktive oppgaver',
'Disable public access' => 'Deaktiver offentlig tilgang',
'Enable public access' => 'Aktiver offentlig tilgang',
'Public access disabled' => 'Offentlig tilgang er deaktivert',
- 'Do you really want to disable this project: "%s"?' => 'Vil du virkelig deaktivere prosjektet: "%s"?',
- 'Do you really want to enable this project: "%s"?' => 'Vil du virkelig aktivere prosjektet: "%s"?',
+ 'Do you really want to disable this project: "%s"?' => 'Vil du deaktivere prosjektet: "%s"?',
+ 'Do you really want to enable this project: "%s"?' => 'Vil du aktivere prosjektet: "%s"?',
'Project activation' => 'Prosjekt aktivering',
'Move the task to another project' => 'Flytt oppgaven til et annet prosjekt',
'Move to another project' => 'Flytt til et annet prosjekt',
- 'Do you really want to duplicate this task?' => 'Vil du virkelig kopiere denne oppgaven?',
+ 'Do you really want to duplicate this task?' => 'Vil du kopiere denne oppgaven?',
'Duplicate a task' => 'Kopier en oppgave',
'External accounts' => 'Eksterne kontoer',
'Account type' => 'Kontotype',
@@ -411,7 +424,7 @@ return array(
'External authentications' => 'Ekstern godkjenning',
'Google Account' => 'Google-konto',
'Github Account' => 'GitHub-konto',
- 'Never connected.' => 'Aldri knyttet.',
+ 'Never connected.' => 'Aldri innlogget.',
'No account linked.' => 'Ingen kontoer knyttet.',
'Account linked.' => 'Konto knyttet.',
'No external authentication enabled.' => 'Ingen eksterne godkjenninger aktiveret.',
@@ -420,18 +433,19 @@ return array(
'Change category for the task "%s"' => 'Endre kategori for oppgaven "%s"',
'Change category' => 'Endre kategori',
'%s updated the task %s' => '%s oppdaterte oppgaven %s',
- '%s opened the task %s' => '%s åpnet oppgaven %s',
+ '%s opened the task %s' => '%s åpnet oppgaven %s',
'%s moved the task %s to the position #%d in the column "%s"' => '%s flyttet oppgaven %s til posisjonen #%d i kolonnen "%s"',
'%s moved the task %s to the column "%s"' => '%s flyttet oppgaven %s til kolonnen "%s"',
'%s created the task %s' => '%s opprettet oppgaven %s',
'%s closed the task %s' => '%s lukket oppgaven %s',
'%s created a subtask for the task %s' => '%s opprettet en deloppgave for oppgaven %s',
'%s updated a subtask for the task %s' => '%s oppdaterte en deloppgave for oppgaven %s',
- 'Assigned to %s with an estimate of %s/%sh' => 'Tildelt til %s med et estimat på %s/%sh',
+ 'Assigned to %s with an estimate of %s/%sh' => 'Tildelt til %s med et estimat på %s/%sh',
'Not assigned, estimate of %sh' => 'Ikke tildelt, estimert til %sh',
'%s updated a comment on the task %s' => '%s oppdaterte en kommentar til oppgaven %s',
'%s commented the task %s' => '%s har kommentert oppgaven %s',
'%s\'s activity' => '%s\'s aktvitet',
+ 'No activity.' => 'Ingen aktivitet',
'RSS feed' => 'RSS feed',
'%s updated a comment on the task #%d' => '%s oppdaterte en kommentar til oppgaven #%d',
'%s commented on the task #%d' => '%s kommenterte oppgaven #%d',
@@ -440,7 +454,7 @@ return array(
'%s updated the task #%d' => '%s oppdaterte oppgaven #%d',
'%s created the task #%d' => '%s opprettet oppgaven #%d',
'%s closed the task #%d' => '%s lukket oppgaven #%d',
- '%s open the task #%d' => '%s åpnet oppgaven #%d',
+ '%s open the task #%d' => '%s åpnet oppgaven #%d',
'%s moved the task #%d to the column "%s"' => '%s flyttet oppgaven #%d til kolonnen "%s"',
'%s moved the task #%d to the position %d in the column "%s"' => '%s flyttet oppgaven #%d til posisjonen %d i kolonnen "%s"',
'Activity' => 'Aktivitetslogg',
@@ -452,21 +466,21 @@ return array(
'New password for the user "%s"' => 'Nytt passord for brukeren "%s"',
'Choose an event' => 'Velg en hendelse',
'Github commit received' => 'Github forpliktelse mottatt',
- 'Github issue opened' => 'Github problem åpnet',
+ 'Github issue opened' => 'Github problem åpnet',
'Github issue closed' => 'Github problem lukket',
- 'Github issue reopened' => 'Github problem gjenåpnet',
+ 'Github issue reopened' => 'Github problem gjenåpnet',
'Github issue assignee change' => 'Endre ansvarlig for Github problem',
'Github issue label change' => 'Endre etikett for Github problem',
'Create a task from an external provider' => 'Oppret en oppgave fra en ekstern tilbyder',
- 'Change the assignee based on an external username' => 'Endre ansvarlige baseret på et eksternt brukernavn',
- 'Change the category based on an external label' => 'Endre kategorien basert på en ekstern etikett',
+ 'Change the assignee based on an external username' => 'Endre ansvarlige baseret på et eksternt brukernavn',
+ 'Change the category based on an external label' => 'Endre kategorien basert på en ekstern etikett',
'Reference' => 'Referanse',
'Reference: %s' => 'Referanse: %s',
'Label' => 'Etikett',
'Database' => 'Database',
'About' => 'Om',
'Database driver:' => 'Database driver:',
- 'Board settings' => 'Innstillinger for ptosjektside',
+ 'Board settings' => 'Innstillinger for prosjektside',
'URL and token' => 'URL og token',
'Webhook settings' => 'Webhook innstillinger',
'URL for task creation:' => 'URL for oppgaveopprettelse:',
@@ -475,9 +489,9 @@ return array(
'Refresh interval for private board' => 'Oppdateringsintervall for privat hovedside',
'Refresh interval for public board' => 'Oppdateringsintervall for offentlig hovedside',
'Task highlight period' => 'Fremhevingsperiode for oppgave',
- 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Periode for å anta at en oppgave nylig ble endretg (0 for å deaktivere, 2 dager som standard)',
+ 'Period (in second) to consider a task was modified recently (0 to disable, 2 days by default)' => 'Periode for ø anta at en oppgave nylig ble endretg (0 for å deaktivere, 2 dager som standard)',
'Frequency in second (60 seconds by default)' => 'Frekevens i sekunder (60 sekunder som standard)',
- 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Frekvens i sekunder (0 for å deaktivere denne funksjonen, 10 sekunder som standard)',
+ 'Frequency in second (0 to disable this feature, 10 seconds by default)' => 'Frekvens i sekunder (0 for øt deaktivere denne funksjonen, 10 sekunder som standard)',
'Application URL' => 'Applikasjons URL',
'Example: http://example.kanboard.net/ (used by email notifications)' => 'Eksempel: http://example.kanboard.net/ (bruges til email notifikationer)',
'Token regenerated.' => 'Token regenerert.',
@@ -485,7 +499,7 @@ return array(
'ISO format is always accepted, example: "%s" and "%s"' => 'ISO format er alltid akseptert, eksempelvis: "%s" og "%s"',
'New private project' => 'Nytt privat prosjekt',
'This project is private' => 'Dette projektet er privat',
- 'Type here to create a new sub-task' => 'Skriv her for å opprette en ny deloppgave',
+ 'Type here to create a new sub-task' => 'Skriv her for ø opprette en ny deloppgave',
'Add' => 'Legg til',
'Estimated time: %s hours' => 'Estimert tid: %s timer',
'Time spent: %s hours' => 'Tid brukt: %s timer',
@@ -510,21 +524,21 @@ return array(
'Columns' => 'Kolonner',
'Task' => 'Oppgave',
// 'Your are not member of any project.' => '',
- // 'Percentage' => '',
- // 'Number of tasks' => '',
- // 'Task distribution' => '',
- // 'Reportings' => '',
+ 'Percentage' => 'Prosent',
+ 'Number of tasks' => 'Antall oppgaver',
+ 'Task distribution' => 'Kolonnefordeling',
+ 'Reportings' => 'Rapportering',
// 'Task repartition for "%s"' => '',
'Analytics' => 'Analyser',
- // 'Subtask' => '',
+ 'Subtask' => 'Deloppgave',
'My subtasks' => 'Mine deloppgaver',
- // 'User repartition' => '',
+ 'User repartition' => 'Brukerfordeling',
// 'User repartition for "%s"' => '',
'Clone this project' => 'Kopier dette prosjektet',
- // 'Column removed successfully.' => '',
+ 'Column removed successfully.' => 'Kolonne flyttet',
// 'Github Issue' => '',
// 'Not enough data to show the graph.' => '',
- // 'Previous' => '',
+ 'Previous' => 'Forrige',
// 'The id must be an integer' => '',
// 'The project id must be an integer' => '',
// 'The status must be an integer' => '',
@@ -536,32 +550,32 @@ return array(
// 'This value is required' => '',
// 'This value must be numeric' => '',
// 'Unable to create this task.' => '',
- // 'Cumulative flow diagram' => '',
+ 'Cumulative flow diagram' => 'Kumulativt flytdiagram',
// 'Cumulative flow diagram for "%s"' => '',
- // 'Daily project summary' => '',
+ 'Daily project summary' => 'Daglig prosjektsammendrag',
// 'Daily project summary export' => '',
// 'Daily project summary export for "%s"' => '',
'Exports' => 'Eksporter',
// 'This export contains the number of tasks per column grouped per day.' => '',
- 'Nothing to preview...' => 'Ingenting å forhåndsvise',
- 'Preview' => 'Forhåndsvisning',
+ 'Nothing to preview...' => 'Ingenting å forhåndsvise',
+ 'Preview' => 'Forhåndsvisning',
'Write' => 'Skriv',
- 'Active swimlanes' => 'Aktive svæmmebaner',
- 'Add a new swimlane' => 'Legg til en ny svømmebane',
- 'Change default swimlane' => 'Endre standard svømmebane',
- 'Default swimlane' => 'Standard svømmebane',
+ 'Active swimlanes' => 'Aktive svømmebaner',
+ 'Add a new swimlane' => 'Legg til en ny svømmebane',
+ 'Change default swimlane' => 'Endre standard svømmebane',
+ 'Default swimlane' => 'Standard svømmebane',
// 'Do you really want to remove this swimlane: "%s"?' => '',
// 'Inactive swimlanes' => '',
'Set project manager' => 'Velg prosjektleder',
'Set project member' => 'Velg prosjektmedlem',
- 'Remove a swimlane' => 'Fjern en svømmebane',
+ 'Remove a swimlane' => 'Fjern en svømmebane',
'Rename' => 'Endre navn',
- 'Show default swimlane' => 'Vis standard svømmebane',
+ 'Show default swimlane' => 'Vis standard svømmebane',
// 'Swimlane modification for the project "%s"' => '',
- // 'Swimlane not found.' => '',
- // 'Swimlane removed successfully.' => '',
- 'Swimlanes' => 'Svømmebaner',
- 'Swimlane updated successfully.' => 'Svæmmebane oppdatert',
+ 'Swimlane not found.' => 'Svømmebane ikke funnet',
+ 'Swimlane removed successfully.' => 'Svømmebane fjernet',
+ 'Swimlanes' => 'Svømmebaner',
+ 'Swimlane updated successfully.' => 'Svømmebane oppdatert',
// 'The default swimlane have been updated successfully.' => '',
// 'Unable to create your swimlane.' => '',
// 'Unable to remove this swimlane.' => '',
@@ -576,27 +590,32 @@ return array(
// 'Help on Gitlab webhooks' => '',
'Integrations' => 'Integrasjoner',
'Integration with third-party services' => 'Integrasjoner med tredje-parts tjenester',
- // 'Role for this project' => '',
+ 'Role for this project' => 'Rolle for dette prosjektet',
'Project manager' => 'Prosjektleder',
'Project member' => 'Prosjektmedlem',
'A project manager can change the settings of the project and have more privileges than a standard user.' => 'Prosjektlederen kan endre flere innstillinger for prosjektet enn den en vanlig bruker kan.',
// 'Gitlab Issue' => '',
- // 'Subtask Id' => '',
- // 'Subtasks' => '',
- // 'Subtasks Export' => '',
+ 'Subtask Id' => 'Deloppgave ID',
+ 'Subtasks' => 'Deloppgaver',
+ 'Subtasks Export' => 'Eksporter deloppgaver',
// 'Subtasks exportation for "%s"' => '',
- // 'Task Title' => '',
+ 'Task Title' => 'Oppgavetittel',
// 'Untitled' => '',
'Application default' => 'Standardinstilling',
- 'Language:' => 'Språk',
+ 'Language:' => 'Språk',
'Timezone:' => 'Tidssone',
- // 'All columns' => '',
+ 'All columns' => 'Alle kolonner',
+ 'Calendar for "%s"' => 'Kalender for "%s"',
+ 'Filter by column' => 'Filtrer etter kolonne',
+ 'Filter by status' => 'Filtrer etter status',
'Calendar' => 'Kalender',
- // 'Next' => '',
+ 'Next' => 'Neste',
// '#%d' => '',
- // 'All swimlanes' => '',
- // 'All colors' => '',
- // 'All status' => '',
+ 'Filter by color' => 'Filtrer etter farge',
+ 'Filter by swimlane' => 'Filtrer etter svømmebane',
+ 'All swimlanes' => 'Alle svømmebaner',
+ 'All colors' => 'Alle farger',
+ 'All status' => 'Alle statuser',
// 'Moved to column %s' => '',
'Change description' => 'Endre beskrivelse',
'User dashboard' => 'Brukerens hovedside',
@@ -604,16 +623,23 @@ return array(
// 'Edit column "%s"' => '',
// 'Select the new status of the subtask: "%s"' => '',
'Subtask timesheet' => 'Tidsskjema for deloppgaver',
- 'There is nothing to show.' => 'Ingen data å vise',
+ 'There is nothing to show.' => 'Ingen data å vise',
// 'Time Tracking' => '',
// 'You already have one subtask in progress' => '',
- 'Which parts of the project do you want to duplicate?' => 'Hvilke deler av dette prosjektet ønsker du å kopiere?',
- // 'Disallow login form' => '',
+ 'Which parts of the project do you want to duplicate?' => 'Hvilke deler av dette prosjektet ønsker du å kopiere?',
+ 'Change dashboard view' => 'Endre visning',
+ 'Show/hide activities' => 'Vis/skjul aktiviteter',
+ 'Show/hide projects' => 'Vis/skjul prosjekter',
+ 'Show/hide subtasks' => 'Vis/skjul deloppgaver',
+ 'Show/hide tasks' => 'Vis/skjul oppgaver',
+ 'Disable login form' => 'Deaktiver innlogging',
+ 'Show/hide calendar' => 'Vis/skjul kalender',
+ 'User calendar' => 'Brukerens kalender',
// 'Bitbucket commit received' => '',
// 'Bitbucket webhooks' => '',
// 'Help on Bitbucket webhooks' => '',
- // 'Start' => '',
- // 'End' => '',
+ 'Start' => 'Start',
+ 'End' => 'Slutt',
'Task age in days' => 'Dager siden oppgaven ble opprettet',
'Days in this column' => 'Dager siden oppgaven ble lagt i denne kolonnen',
// '%dd' => '',
@@ -621,7 +647,7 @@ return array(
'Add a new link' => 'Legg til en ny relasjon',
// 'Do you really want to remove this link: "%s"?' => '',
// 'Do you really want to remove this link with task #%d?' => '',
- // 'Field required' => '',
+ 'Field required' => 'Feltet må fylles ut',
'Link added successfully.' => 'Ny relasjon er lagt til',
'Link updated successfully.' => 'Relasjon er oppdatert',
'Link removed successfully.' => 'Relasjon er fjernet',
@@ -647,8 +673,8 @@ return array(
'is a parent of' => 'er en overordnet oppgave av',
'targets milestone' => 'milepel',
'is a milestone of' => 'er en milepel av',
- 'fixes' => 'løser',
- 'is fixed by' => 'løses av',
+ 'fixes' => 'løser',
+ 'is fixed by' => 'løses av',
'This task' => 'Denne oppgaven',
// '<1h' => '',
// '%dh' => '',
@@ -662,7 +688,9 @@ return array(
'Keyboard shortcuts' => 'Hurtigtaster',
// 'Open board switcher' => '',
// 'Application' => '',
+ 'Filter recently updated' => 'Filter nylig oppdatert',
'since %B %e, %Y at %k:%M %p' => 'siden %B %e, %Y at %k:%M %p',
+ 'More filters' => 'Flere filtre',
'Compact view' => 'Kompakt visning',
'Horizontal scrolling' => 'Bla horisontalt',
'Compact/wide view' => 'Kompakt/bred visning',
@@ -680,9 +708,9 @@ return array(
// 'Hourly rate created successfully.' => '',
// 'Start time' => '',
// 'End time' => '',
- // 'Comment' => '',
- // 'All day' => '',
- // 'Day' => '',
+ 'Comment' => 'Kommentar',
+ 'All day' => 'Alle dager',
+ 'Day' => 'Dag',
'Manage timetable' => 'Tidstabell',
'Overtime timetable' => 'Overtidstabell',
'Time off timetable' => 'Fritidstabell',
@@ -703,18 +731,18 @@ return array(
'Files' => 'Filer',
'Images' => 'Bilder',
'Private project' => 'Privat prosjekt',
- // 'Amount' => '',
+ 'Amount' => 'Beløp',
// 'AUD - Australian Dollar' => '',
'Budget' => 'Budsjett',
// 'Budget line' => '',
// 'Budget line removed successfully.' => '',
- // 'Budget lines' => '',
+ 'Budget lines' => 'Budsjettlinjer',
// 'CAD - Canadian Dollar' => '',
// 'CHF - Swiss Francs' => '',
- // 'Cost' => '',
- // 'Cost breakdown' => '',
+ 'Cost' => 'Kostnad',
+ 'Cost breakdown' => 'Kostnadsnedbryting',
// 'Custom Stylesheet' => '',
- // 'download' => '',
+ 'download' => 'last ned',
// 'Do you really want to remove this budget line?' => '',
// 'EUR - Euro' => '',
// 'Expenses' => '',
@@ -731,10 +759,10 @@ return array(
// 'Unable to remove this budget line.' => '',
// 'USD - US Dollar' => '',
// 'Remaining' => '',
- // 'Destination column' => '',
- 'Move the task to another column when assigned to a user' => 'Flytt oppgaven til en annen kolonne når den er tildelt en bruker',
- 'Move the task to another column when assignee is cleared' => 'Flytt oppgaven til en annen kolonne når ppgavetildeling fjernes ',
- // 'Source column' => '',
+ 'Destination column' => 'Ny kolonne',
+ 'Move the task to another column when assigned to a user' => 'Flytt oppgaven til en annen kolonne når den er tildelt en bruker',
+ 'Move the task to another column when assignee is cleared' => 'Flytt oppgaven til en annen kolonne når ppgavetildeling fjernes ',
+ 'Source column' => 'Opprinnelig kolonne',
// 'Show subtask estimates (forecast of future work)' => '',
'Transitions' => 'Statusendringer',
// 'Executer' => '',
@@ -782,10 +810,10 @@ return array(
// 'This chart show the task complexity over the time (Work Remaining).' => '',
// 'Screenshot taken %s' => '',
'Add a screenshot' => 'Legg til et skjermbilde',
- 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Ta et skjermbilde og trykk CTRL+V for å lime det inn her.',
+ 'Take a screenshot and press CTRL+V or ⌘+V to paste here.' => 'Ta et skjermbilde og trykk CTRL+V for å lime det inn her.',
'Screenshot uploaded successfully.' => 'Skjermbilde opplastet',
// 'SEK - Swedish Krona' => '',
- 'The project identifier is an optional alphanumeric code used to identify your project.' => 'Prosjektkoden er en alfanumerisk kode som kan brukes for å identifisere prosjektet',
+ 'The project identifier is an optional alphanumeric code used to identify your project.' => 'Prosjektkoden er en alfanumerisk kode som kan brukes for å identifisere prosjektet',
'Identifier' => 'Prosjektkode',
// 'Postmark (incoming emails)' => '',
// 'Help on Postmark integration' => '',
@@ -807,26 +835,26 @@ return array(
// 'This value must be alphanumeric' => '',
'Edit recurrence' => 'Endre gjentakelser',
'Generate recurrent task' => 'Opprett gjentagende oppgave',
- 'Trigger to generate recurrent task' => 'Betingelse for å generere gjentakende oppgave',
- 'Factor to calculate new due date' => 'Faktor for å beregne ny tidsfrist',
- 'Timeframe to calculate new due date' => 'Tidsramme for å beregne ny tidsfrist',
- 'Base date to calculate new due date' => 'Grunnlagsdato for å beregne ny tidsfrist',
+ 'Trigger to generate recurrent task' => 'Betingelse for å generere gjentakende oppgave',
+ 'Factor to calculate new due date' => 'Faktor for å beregne ny tidsfrist',
+ 'Timeframe to calculate new due date' => 'Tidsramme for å beregne ny tidsfrist',
+ 'Base date to calculate new due date' => 'Grunnlagsdato for å beregne ny tidsfrist',
'Action date' => 'Hendelsesdato',
- 'Base date to calculate new due date: ' => 'Grunnlagsdato for å beregne ny tidsfrist',
+ 'Base date to calculate new due date: ' => 'Grunnlagsdato for å beregne ny tidsfrist',
'This task has created this child task: ' => 'Denne oppgaven har opprettet denne relaterte oppgaven',
'Day(s)' => 'Dager',
'Existing due date' => 'Eksisterende forfallsdato',
- 'Factor to calculate new due date: ' => 'Faktor for å beregne ny tidsfrist',
- 'Month(s)' => 'MÃ¥neder',
+ 'Factor to calculate new due date: ' => 'Faktor for å beregne ny tidsfrist',
+ 'Month(s)' => 'Måneder',
'Recurrence' => 'Gjentakelse',
'This task has been created by: ' => 'Denne oppgaven er opprettet av:',
// 'Recurrent task has been generated:' => '',
// 'Timeframe to calculate new due date: ' => '',
// 'Trigger to generate recurrent task: ' => '',
- 'When task is closed' => 'NÃ¥r oppgaven er lukket',
- 'When task is moved from first column' => 'Når oppgaven er flyttet fra første kolon',
- 'When task is moved to last column' => 'NÃ¥r oppgaven er flyttet til siste kolonne',
- 'Year(s)' => 'Ã¥r',
+ 'When task is closed' => 'Når oppgaven er lukket',
+ 'When task is moved from first column' => 'Når oppgaven er flyttet fra første kolon',
+ 'When task is moved to last column' => 'Når oppgaven er flyttet til siste kolonne',
+ 'Year(s)' => 'år',
// 'Jabber (XMPP)' => '',
// 'Send notifications to Jabber' => '',
// 'XMPP server address' => '',
@@ -853,16 +881,16 @@ return array(
// 'There is no user management for private projects.' => '',
// 'User that will receive the email' => '',
// 'Email subject' => '',
- // 'Date' => '',
+ 'Date' => 'Dato',
// 'By @%s on Bitbucket' => '',
// 'Bitbucket Issue' => '',
// 'Commit made by @%s on Bitbucket' => '',
// 'Commit made by @%s on Github' => '',
// 'By @%s on Github' => '',
// 'Commit made by @%s on Gitlab' => '',
- 'Add a comment log when moving the task between columns' => 'Legg til en kommentar i loggen når en oppgave flyttes mellom kolonnene',
- 'Move the task to another column when the category is changed' => 'Flytt oppgaven til en annen kolonne når kategorien endres',
- 'Send a task by email to someone' => 'Send en oppgave på epost til noen',
+ 'Add a comment log when moving the task between columns' => 'Legg til en kommentar i loggen når en oppgave flyttes mellom kolonnene',
+ 'Move the task to another column when the category is changed' => 'Flytt oppgaven til en annen kolonne når kategorien endres',
+ 'Send a task by email to someone' => 'Send en oppgave på epost til noen',
// 'Reopen a task' => '',
// 'Bitbucket issue opened' => '',
// 'Bitbucket issue closed' => '',
@@ -871,14 +899,14 @@ return array(
// 'Bitbucket issue comment created' => '',
'Column change' => 'Endret kolonne',
'Position change' => 'Posisjonsendring',
- 'Swimlane change' => 'Endret svømmebane',
+ 'Swimlane change' => 'Endret svømmebane',
'Assignee change' => 'Endret eier',
// '[%s] Overdue tasks' => '',
'Notification' => 'Varsel',
// '%s moved the task #%d to the first swimlane' => '',
// '%s moved the task #%d to the swimlane "%s"' => '',
- // 'Swimlane' => '',
- // 'Budget overview' => '',
+ 'Swimlane' => 'Svømmebane',
+ 'Budget overview' => 'Budsjettoversikt',
// 'Type' => '',
// 'There is not enough data to show something.' => '',
// 'Gravatar' => '',
@@ -893,6 +921,7 @@ return array(
// 'The task have been moved to the first swimlane' => '',
// 'The task have been moved to another swimlane:' => '',
// 'Overdue tasks for the project "%s"' => '',
+ 'There is no completed tasks at the moment.' => 'Ingen fullførte oppgaver funnet.',
// 'New title: %s' => '',
// 'The task is not assigned anymore' => '',
// 'New assignee: %s' => '',
@@ -908,61 +937,62 @@ return array(
// 'The field "%s" have been updated' => '',
// 'The description have been modified' => '',
// 'Do you really want to close the task "%s" as well as all subtasks?' => '',
- // 'Swimlane: %s' => '',
- // 'I want to receive notifications for:' => '',
+ 'Swimlane: %s' => 'Svømmebane: %s',
+ 'Project calendar' => 'Prosjektkalender',
+ 'I want to receive notifications for:' => 'Jeg vil motta varslinger om:',
'All tasks' => 'Alle oppgaver',
- // 'Only for tasks assigned to me' => '',
- // 'Only for tasks created by me' => '',
- // 'Only for tasks created by me and assigned to me' => '',
+ 'Only for tasks assigned to me' => 'Kun oppgaver som er tildelt meg',
+ 'Only for tasks created by me' => 'Kun oppgaver som er opprettet av meg',
+ 'Only for tasks created by me and assigned to me' => 'Kun oppgaver som er opprettet av meg og tildelt meg',
// '%A' => '',
// '%b %e, %Y, %k:%M %p' => '',
// 'New due date: %B %e, %Y' => '',
// 'Start date changed: %B %e, %Y' => '',
// '%k:%M %p' => '',
// '%%Y-%%m-%%d' => '',
- // 'Total for all columns' => '',
- // 'You need at least 2 days of data to show the chart.' => '',
+ 'Total for all columns' => 'Totalt for alle kolonner',
+ //'You need at least 2 days of data to show the chart.' => '',
// '<15m' => '',
// '<30m' => '',
'Stop timer' => 'Stopp timer',
'Start timer' => 'Start timer',
'Add project member' => 'Legg til prosjektmedlem',
'Enable notifications' => 'Aktiver varslinger',
- // 'My activity stream' => '',
- // 'My calendar' => '',
- // 'Search tasks' => '',
- // 'Back to the calendar' => '',
- // 'Filters' => '',
- // 'Reset filters' => '',
- // 'My tasks due tomorrow' => '',
- // 'Tasks due today' => '',
- // 'Tasks due tomorrow' => '',
- // 'Tasks due yesterday' => '',
- // 'Closed tasks' => '',
- // 'Open tasks' => '',
- // 'Not assigned' => '',
- // 'View advanced search syntax' => '',
- // 'Overview' => '',
+ 'My activity stream' => 'Aktivitetslogg',
+ 'My calendar' => 'Min kalender',
+ 'Search tasks' => 'Søk oppgave',
+ 'Back to the calendar' => 'Tilbake til kalender',
+ 'Filters' => 'Filtere',
+ 'Reset filters' => 'Nullstill filter',
+ 'My tasks due tomorrow' => 'Mine oppgaver med frist i morgen',
+ 'Tasks due today' => 'Oppgaver med frist i dag',
+ 'Tasks due tomorrow' => 'Oppgaver med frist i morgen',
+ 'Tasks due yesterday' => 'Oppgaver med frist i går',
+ 'Closed tasks' => 'Fullførte oppgaver',
+ 'Open tasks' => 'Åpne oppgaver',
+ 'Not assigned' => 'Ikke tildelt',
+ 'View advanced search syntax' => 'Vis hjelp for avansert søk ',
+ 'Overview' => 'Oversikt',
// '%b %e %Y' => '',
- // 'Board/Calendar/List view' => '',
- // 'Switch to the board view' => '',
- // 'Switch to the calendar view' => '',
- // 'Switch to the list view' => '',
- // 'Go to the search/filter box' => '',
- // 'There is no activity yet.' => '',
- // 'No tasks found.' => '',
- // 'Keyboard shortcut: "%s"' => '',
- // 'List' => '',
- // 'Filter' => '',
- // 'Advanced search' => '',
- // 'Example of query: ' => '',
- // 'Search by project: ' => '',
- // 'Search by column: ' => '',
- // 'Search by assignee: ' => '',
- // 'Search by color: ' => '',
- // 'Search by category: ' => '',
- // 'Search by description: ' => '',
- // 'Search by due date: ' => '',
+ 'Board/Calendar/List view' => 'Oversikt/kalender/listevisning',
+ 'Switch to the board view' => 'Oversiktsvisning',
+ 'Switch to the calendar view' => 'Kalendevisning',
+ 'Switch to the list view' => 'Listevisning',
+ 'Go to the search/filter box' => 'Gå til søk/filter',
+ 'There is no activity yet.' => 'Ingen aktiviteter ennå.',
+ 'No tasks found.' => 'Ingen oppgaver funnet',
+ 'Keyboard shortcut: "%s"' => 'Hurtigtaster: "%s"',
+ 'List' => 'Liste',
+ 'Filter' => 'Filter',
+ 'Advanced search' => 'Avansert søk',
+ 'Example of query: ' => 'Eksempel på spørring',
+ 'Search by project: ' => 'Søk etter prosjekt',
+ 'Search by column: ' => 'Søk etter kolonne',
+ 'Search by assignee: ' => 'Søk etter tildelt',
+ 'Search by color: ' => 'Søk etter farge',
+ 'Search by category: ' => 'Søk etter kategori',
+ 'Search by description: ' => 'Søk etter beskrivelse',
+ 'Search by due date: ' => 'Søk etter frist',
// 'Lead and Cycle time for "%s"' => '',
// 'Average time spent into each column for "%s"' => '',
// 'Average time spent into each column' => '',
@@ -996,75 +1026,75 @@ return array(
// 'If you check the box "Disallow login form", credentials entered in the login form will be ignored.' => '',
// 'By @%s on Gitlab' => '',
// 'Gitlab issue comment created' => '',
- // 'New remote user' => '',
- // 'New local user' => '',
- // 'Default task color' => '',
- // 'Hide sidebar' => '',
- // 'Expand sidebar' => '',
+ 'New remote user' => 'Ny eksternbruker',
+ 'New local user' => 'Ny internbruker',
+ 'Default task color' => 'Standard oppgavefarge',
+ 'Hide sidebar' => 'Skjul sidemeny',
+ 'Expand sidebar' => 'Vis sidemeny',
// 'This feature does not work with all browsers.' => '',
// 'There is no destination project available.' => '',
// 'Trigger automatically subtask time tracking' => '',
// 'Include closed tasks in the cumulative flow diagram' => '',
- // 'Current swimlane: %s' => '',
- // 'Current column: %s' => '',
- // 'Current category: %s' => '',
- // 'no category' => '',
- // 'Current assignee: %s' => '',
- // 'not assigned' => '',
- // 'Author:' => '',
- // 'contributors' => '',
- // 'License:' => '',
- // 'License' => '',
- // 'Project Administrator' => '',
- // 'Enter the text below' => '',
- // 'Gantt chart for %s' => '',
+ 'Current swimlane: %s' => 'Nåværende svømmebane: %s',
+ 'Current column: %s' => 'Nåværende kolonne: %s',
+ 'Current category: %s' => ': %s',
+ 'no category' => 'ingen kategori',
+ 'Current assignee: %s' => 'Tildelt til %s',
+ 'not assigned' => 'ikke tildelt',
+ 'Author:' => 'Opprettet av',
+ 'contributors' => 'bidragsytere',
+ 'License:' => 'Lisens:',
+ 'License' => 'Lisens',
+ 'Project Administrator' => 'Prosjektadministrator',
+ 'Enter the text below' => 'Legg inn teksten nedenfor',
+ 'Gantt chart for %s' => 'Gantt skjema for %s',
// 'Sort by position' => '',
- // 'Sort by date' => '',
- // 'Add task' => '',
- // 'Start date:' => '',
- // 'Due date:' => '',
- // 'There is no start date or due date for this task.' => '',
+ 'Sort by date' => 'Sorter etter dato',
+ 'Add task' => 'Legg til oppgave',
+ 'Start date:' => 'Startdato:',
+ 'Due date:' => 'Frist:',
+ 'There is no start date or due date for this task.' => 'Det er ingen startdato eller frist for denne oppgaven',
// 'Moving or resizing a task will change the start and due date of the task.' => '',
- // 'There is no task in your project.' => '',
- // 'Gantt chart' => '',
- // 'People who are project managers' => '',
- // 'People who are project members' => '',
+ 'There is no task in your project.' => 'Det er ingen oppgaver i dette prosjektet',
+ 'Gantt chart' => 'Gantt skjema',
+ 'People who are project managers' => 'Prosjektledere',
+ 'People who are project members' => 'Prosjektmedlemmer',
// 'NOK - Norwegian Krone' => '',
- // 'Show this column' => '',
- // 'Hide this column' => '',
- // 'open file' => '',
- // 'End date' => '',
- // 'Users overview' => '',
- // 'Managers' => '',
- // 'Members' => '',
- // 'Shared project' => '',
- // 'Project managers' => '',
- // 'Project members' => '',
+ 'Show this column' => 'Vis denne kolonnen',
+ 'Hide this column' => 'Skjul denne kolonnen',
+ 'open file' => 'Åpne fil',
+ 'End date' => 'Sluttdato',
+ 'Users overview' => 'Brukeroversikt',
+ 'Managers' => 'Ledere',
+ 'Members' => 'Medlemmer',
+ 'Shared project' => 'Delt prosjekt',
+ 'Project managers' => 'Prosjektledere',
+ 'Project members' => 'Prosjektmedlemmer',
// 'Gantt chart for all projects' => '',
- // 'Projects list' => '',
- // 'Gantt chart for this project' => '',
- // 'Project board' => '',
- // 'End date:' => '',
+ 'Projects list' => 'Prosjektliste',
+ 'Gantt chart for this project' => 'Gantt skjema for dette prosjektet',
+ 'Project board' => 'Prosjektsiden',
+ 'End date:' => 'Sluttdato:',
// 'There is no start date or end date for this project.' => '',
- // 'Projects Gantt chart' => '',
- // 'Start date: %s' => '',
- // 'End date: %s' => '',
- // 'Link type' => '',
+ 'Projects Gantt chart' => 'Gantt skjema for prosjekter',
+ 'Start date: %s' => 'Startdato: %s',
+ 'End date: %s' => 'Sluttdato: %s',
+ 'Link type' => 'Relasjonstype',
// 'Change task color when using a specific task link' => '',
// 'Task link creation or modification' => '',
// 'Login with my Gitlab Account' => '',
- // 'Milestone' => '',
+ 'Milestone' => 'Milepæl',
// 'Gitlab Authentication' => '',
// 'Help on Gitlab authentication' => '',
// 'Gitlab Id' => '',
// 'Gitlab Account' => '',
// 'Link my Gitlab Account' => '',
// 'Unlink my Gitlab Account' => '',
- // 'Documentation: %s' => '',
- // 'Switch to the Gantt chart view' => '',
- // 'Reset the search/filter box' => '',
- // 'Documentation' => '',
- // 'Table of contents' => '',
- // 'Gantt' => '',
- // 'Help with project permissions' => '',
+ 'Documentation: %s' => 'Dokumentasjon: %s',
+ 'Switch to the Gantt chart view' => 'Gantt skjema visning',
+ 'Reset the search/filter box' => 'Nullstill søk/filter',
+ 'Documentation' => 'Dokumentasjon',
+ 'Table of contents' => 'Innholdsfortegnelse',
+ 'Gantt' => 'Gantt',
+ 'Help with project permissions' => 'Hjelp med prosjekttilganger',
);
diff --git a/app/Locale/nl_NL/translations.php b/app/Locale/nl_NL/translations.php
index 23d64163..8be0c61d 100644
--- a/app/Locale/nl_NL/translations.php
+++ b/app/Locale/nl_NL/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Remote',
'Enabled' => 'Actief',
'Disabled' => 'Inactief',
- 'Google account linked' => 'Gelinkt Google Account',
- 'Github account linked' => 'Gelinkt Github Account',
'Username:' => 'Gebruikersnaam :',
'Name:' => 'Naam :',
'Email:' => 'Email :',
@@ -667,17 +665,7 @@ return array(
// 'Horizontal scrolling' => '',
// 'Compact/wide view' => '',
// 'No results match:' => '',
- // 'Remove hourly rate' => '',
- // 'Do you really want to remove this hourly rate?' => '',
- // 'Hourly rates' => '',
- // 'Hourly rate' => '',
// 'Currency' => '',
- // 'Effective date' => '',
- // 'Add new rate' => '',
- // 'Rate removed successfully.' => '',
- // 'Unable to remove this rate.' => '',
- // 'Unable to save the hourly rate.' => '',
- // 'Hourly rate created successfully.' => '',
// 'Start time' => '',
// 'End time' => '',
// 'Comment' => '',
@@ -703,34 +691,18 @@ return array(
// 'Files' => '',
// 'Images' => '',
// 'Private project' => '',
- // 'Amount' => '',
// 'AUD - Australian Dollar' => '',
- // 'Budget' => '',
- // 'Budget line' => '',
- // 'Budget line removed successfully.' => '',
- // 'Budget lines' => '',
// 'CAD - Canadian Dollar' => '',
// 'CHF - Swiss Francs' => '',
- // 'Cost' => '',
- // 'Cost breakdown' => '',
// 'Custom Stylesheet' => '',
// 'download' => '',
- // 'Do you really want to remove this budget line?' => '',
// 'EUR - Euro' => '',
- // 'Expenses' => '',
// 'GBP - British Pound' => '',
// 'INR - Indian Rupee' => '',
// 'JPY - Japanese Yen' => '',
- // 'New budget line' => '',
// 'NZD - New Zealand Dollar' => '',
- // 'Remove a budget line' => '',
- // 'Remove budget line' => '',
// 'RSD - Serbian dinar' => '',
- // 'The budget line have been created successfully.' => '',
- // 'Unable to create the budget line.' => '',
- // 'Unable to remove this budget line.' => '',
// 'USD - US Dollar' => '',
- // 'Remaining' => '',
// 'Destination column' => '',
// 'Move the task to another column when assigned to a user' => '',
// 'Move the task to another column when assignee is cleared' => '',
@@ -746,7 +718,6 @@ return array(
// 'Rate' => '',
// 'Change reference currency' => '',
// 'Add a new currency rate' => '',
- // 'Currency rates are used to calculate project budget.' => '',
// 'Reference currency' => '',
// 'The currency rate have been added successfully.' => '',
// 'Unable to add this currency rate.' => '',
@@ -878,9 +849,6 @@ return array(
// '%s moved the task #%d to the first swimlane' => '',
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
- // 'Budget overview' => '',
- // 'Type' => '',
- // 'There is not enough data to show something.' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Locale/pl_PL/translations.php b/app/Locale/pl_PL/translations.php
index 9947cf31..d9cfcdbc 100644
--- a/app/Locale/pl_PL/translations.php
+++ b/app/Locale/pl_PL/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Zdalne',
'Enabled' => 'Odblokowane',
'Disabled' => 'Zablokowane',
- 'Google account linked' => 'Połączone konto Google',
- 'Github account linked' => 'Połączone konto Github',
'Username:' => 'Nazwa Użytkownika:',
'Name:' => 'ImiÄ™ i Nazwisko',
'Email:' => 'Email: ',
@@ -667,17 +665,7 @@ return array(
'Horizontal scrolling' => 'Przewijanie poziome',
'Compact/wide view' => 'Pełny/Kompaktowy widok',
'No results match:' => 'Brak wyników:',
- 'Remove hourly rate' => 'Usuń stawkę godzinową',
- 'Do you really want to remove this hourly rate?' => 'Czy na pewno chcesz usunąć stawkę godzinową?',
- 'Hourly rates' => 'Stawki godzinowe',
- 'Hourly rate' => 'Stawka godzinowa',
'Currency' => 'Waluta',
- 'Effective date' => 'Data efektywna',
- 'Add new rate' => 'Dodaj nowÄ… stawkÄ™',
- 'Rate removed successfully.' => 'Stawka usunięta.',
- 'Unable to remove this rate.' => 'Nie można usunąć tej stawki.',
- 'Unable to save the hourly rate.' => 'Nie można zapisać tej stawki godzinowej.',
- 'Hourly rate created successfully.' => 'Stawka godzinowa utworzona pomyślnie.',
'Start time' => 'Rozpoczęto',
'End time' => 'Zakończono',
'Comment' => 'Komentarz',
@@ -703,34 +691,18 @@ return array(
'Files' => 'Pliki',
'Images' => 'Obrazy',
'Private project' => 'Projekt prywatny',
- 'Amount' => 'Ilość',
'AUD - Australian Dollar' => 'AUD - Dolar australijski',
- 'Budget' => 'Budżet',
- 'Budget line' => 'Linia budżetowa',
- 'Budget line removed successfully.' => 'Linia budżetowa usunięta.',
- 'Budget lines' => 'Linie budżetowe',
'CAD - Canadian Dollar' => 'CAD - Dolar kanadyjski',
'CHF - Swiss Francs' => 'CHF - Frank szwajcarski',
- 'Cost' => 'Koszt',
- 'Cost breakdown' => 'Analiza kosztów',
'Custom Stylesheet' => 'Niestandardowy arkusz stylów',
'download' => 'pobierz',
- 'Do you really want to remove this budget line?' => 'Czy chcesz usunąć tą linię budżetową?',
// 'EUR - Euro' => '',
- 'Expenses' => 'Wydatki',
'GBP - British Pound' => 'GBP - Funt brytyjski',
'INR - Indian Rupee' => 'INR - Rupia indyjska',
'JPY - Japanese Yen' => 'JPY - Jen japoński',
- 'New budget line' => 'Nowa linia budżetowa',
'NZD - New Zealand Dollar' => 'NZD - Dolar nowozelandzki',
- 'Remove a budget line' => 'Usuń linię budżetową',
- 'Remove budget line' => 'Usuń linię budżetową',
'RSD - Serbian dinar' => 'RSD - Dinar serbski',
- // 'The budget line have been created successfully.' => '',
- 'Unable to create the budget line.' => 'Nie można utworzyć linii budżetowej',
- 'Unable to remove this budget line.' => 'Nie można usunąć tej linii budżetowej',
'USD - US Dollar' => 'USD - Dolar amerykański',
- 'Remaining' => 'Pozostało',
'Destination column' => 'Kolumna docelowa',
'Move the task to another column when assigned to a user' => 'PrzenieÅ› zadanie do innej kolumny gdy zostanie przypisane do osoby',
'Move the task to another column when assignee is cleared' => 'Przenieś zadanie do innej kolumny gdy osoba odpowiedzialna zostanie usunięta',
@@ -746,7 +718,6 @@ return array(
'Rate' => 'Kurs',
'Change reference currency' => 'Zmień walutę referencyjną',
'Add a new currency rate' => 'Dodaj nowy kurs waluty',
- 'Currency rates are used to calculate project budget.' => 'Kursy walut są używane do obliczeń budżetu projektu.',
'Reference currency' => 'Waluta referencyjna',
'The currency rate have been added successfully.' => 'Dodano kurs waluty',
'Unable to add this currency rate.' => 'Nie można dodać kursu waluty',
@@ -878,9 +849,6 @@ return array(
// '%s moved the task #%d to the first swimlane' => '',
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
- // 'Budget overview' => '',
- // 'Type' => '',
- // 'There is not enough data to show something.' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Locale/pt_BR/translations.php b/app/Locale/pt_BR/translations.php
index 6aa2f4dd..5f849457 100644
--- a/app/Locale/pt_BR/translations.php
+++ b/app/Locale/pt_BR/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Remoto',
'Enabled' => 'Habilitado',
'Disabled' => 'Desabilitado',
- 'Google account linked' => 'Conta do Google associada',
- 'Github account linked' => 'Conta do Github associada',
'Username:' => 'Usuário:',
'Name:' => 'Nome:',
'Email:' => 'E-mail:',
@@ -667,17 +665,7 @@ return array(
'Horizontal scrolling' => 'Rolagem horizontal',
'Compact/wide view' => 'Alternar entre a vista compacta e ampliada',
'No results match:' => 'Nenhum resultado:',
- 'Remove hourly rate' => 'Retirar taxa horária',
- 'Do you really want to remove this hourly rate?' => 'Você deseja realmente remover esta taxa horária?',
- 'Hourly rates' => 'Taxas horárias',
- 'Hourly rate' => 'Taxa horária',
'Currency' => 'Moeda',
- 'Effective date' => 'Data efetiva',
- 'Add new rate' => 'Adicionar nova taxa',
- 'Rate removed successfully.' => 'Taxa removido com sucesso.',
- 'Unable to remove this rate.' => 'Impossível de remover esta taxa.',
- 'Unable to save the hourly rate.' => 'Impossível salvar a taxa horária.',
- 'Hourly rate created successfully.' => 'Taxa horária criada com sucesso.',
'Start time' => 'Horário de início',
'End time' => 'Horário de término',
'Comment' => 'comentário',
@@ -703,34 +691,18 @@ return array(
'Files' => 'Arquivos',
'Images' => 'Imagens',
'Private project' => 'Projeto privado',
- 'Amount' => 'Quantia',
'AUD - Australian Dollar' => 'AUD - Dólar australiano',
- 'Budget' => 'Orçamento',
- 'Budget line' => 'Rubrica orçamental',
- 'Budget line removed successfully.' => 'Rubrica orçamental removida com sucesso',
- 'Budget lines' => 'Rubricas orçamentais',
'CAD - Canadian Dollar' => 'CAD - Dólar canadense',
'CHF - Swiss Francs' => 'CHF - Francos Suíços',
- 'Cost' => 'Custo',
- 'Cost breakdown' => 'Repartição dos custos',
'Custom Stylesheet' => 'Folha de estilo personalizado',
'download' => 'baixar',
- 'Do you really want to remove this budget line?' => 'Você deseja realmente remover esta rubrica orçamental?',
'EUR - Euro' => 'EUR - Euro',
- 'Expenses' => 'Despesas',
'GBP - British Pound' => 'GBP - Libra Esterlina',
'INR - Indian Rupee' => 'INR - Rúpia indiana',
'JPY - Japanese Yen' => 'JPY - Iene japonês',
- 'New budget line' => 'Nova rubrica orçamental',
'NZD - New Zealand Dollar' => 'NZD - Dólar Neozelandês',
- 'Remove a budget line' => 'Remover uma rubrica orçamental',
- 'Remove budget line' => 'Remover uma rubrica orçamental',
'RSD - Serbian dinar' => 'RSD - Dinar sérvio',
- 'The budget line have been created successfully.' => 'A rubrica orçamental foi criada com sucesso.',
- 'Unable to create the budget line.' => 'Impossível de adicionar esta rubrica orçamental.',
- 'Unable to remove this budget line.' => 'Impossível de remover esta rubrica orçamental.',
'USD - US Dollar' => 'USD - Dólar norte-americano',
- 'Remaining' => 'Restante',
'Destination column' => 'Coluna de destino',
'Move the task to another column when assigned to a user' => 'Mover a tarefa para uma outra coluna quando esta está atribuída a um usuário',
'Move the task to another column when assignee is cleared' => 'Mover a tarefa para uma outra coluna quando esta não está atribuída',
@@ -746,7 +718,6 @@ return array(
'Rate' => 'Taxa',
'Change reference currency' => 'Mudar a moeda de referência',
'Add a new currency rate' => 'Adicionar uma nova taxa para uma moeda',
- 'Currency rates are used to calculate project budget.' => 'As taxas de câmbio são utilizadas para calcular o orçamento do projeto.',
'Reference currency' => 'Moeda de Referência',
'The currency rate have been added successfully.' => 'A taxa de câmbio foi adicionada com sucesso.',
'Unable to add this currency rate.' => 'Impossível de adicionar essa taxa de câmbio.',
@@ -878,9 +849,6 @@ return array(
'%s moved the task #%d to the first swimlane' => '%s moveu a tarefa n° %d no primeiro swimlane',
'%s moved the task #%d to the swimlane "%s"' => '%s moveu a tarefa n° %d no swimlane "%s"',
'Swimlane' => 'Swimlane',
- 'Budget overview' => 'Visão geral do orçamento',
- 'Type' => 'Tipo',
- 'There is not enough data to show something.' => 'Não há dados suficientes para mostrar alguma coisa.',
'Gravatar' => 'Gravatar',
'Hipchat' => 'Hipchat',
'Slack' => 'Slack',
@@ -1060,11 +1028,11 @@ return array(
'Gitlab Account' => 'Conta Gitlab',
'Link my Gitlab Account' => 'Vincular minha conta Gitlab',
'Unlink my Gitlab Account' => 'Desvincular minha conta Gitlab',
- // 'Documentation: %s' => '',
- // 'Switch to the Gantt chart view' => '',
- // 'Reset the search/filter box' => '',
- // 'Documentation' => '',
- // 'Table of contents' => '',
- // 'Gantt' => '',
- // 'Help with project permissions' => '',
+ 'Documentation: %s' => 'Documentação: %s',
+ 'Switch to the Gantt chart view' => 'Mudar para a vista gráfico de Gantt',
+ 'Reset the search/filter box' => 'Reiniciar o campo de pesquisa',
+ 'Documentation' => 'Documentação',
+ 'Table of contents' => 'Ãndice',
+ 'Gantt' => 'Gantt',
+ 'Help with project permissions' => 'Ajuda com as permissões dos projetos',
);
diff --git a/app/Locale/pt_PT/translations.php b/app/Locale/pt_PT/translations.php
index ae73af1e..9e20b112 100644
--- a/app/Locale/pt_PT/translations.php
+++ b/app/Locale/pt_PT/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Remoto',
'Enabled' => 'Activado',
'Disabled' => 'Desactivado',
- 'Google account linked' => 'Conta do Google associada',
- 'Github account linked' => 'Conta do Github associada',
'Username:' => 'Utilizador:',
'Name:' => 'Nome:',
'Email:' => 'E-mail:',
@@ -667,17 +665,7 @@ return array(
'Horizontal scrolling' => 'Deslocamento horizontal',
'Compact/wide view' => 'Alternar entre a vista compacta e ampliada',
'No results match:' => 'Nenhum resultado:',
- 'Remove hourly rate' => 'Retirar taxa horária',
- 'Do you really want to remove this hourly rate?' => 'Tem a certeza que quer remover esta taxa horária?',
- 'Hourly rates' => 'Taxas horárias',
- 'Hourly rate' => 'Taxa horária',
'Currency' => 'Moeda',
- 'Effective date' => 'Data efectiva',
- 'Add new rate' => 'Adicionar nova taxa',
- 'Rate removed successfully.' => 'Taxa removido com sucesso.',
- 'Unable to remove this rate.' => 'Impossível de remover esta taxa.',
- 'Unable to save the hourly rate.' => 'Impossível salvar a taxa horária.',
- 'Hourly rate created successfully.' => 'Taxa horária criada com sucesso.',
'Start time' => 'Horário de início',
'End time' => 'Horário de término',
'Comment' => 'comentário',
@@ -703,34 +691,18 @@ return array(
'Files' => 'Arquivos',
'Images' => 'Imagens',
'Private project' => 'Projecto privado',
- 'Amount' => 'Quantia',
'AUD - Australian Dollar' => 'AUD - Dólar australiano',
- 'Budget' => 'Orçamento',
- 'Budget line' => 'Rubrica orçamental',
- 'Budget line removed successfully.' => 'Rubrica orçamental removida com sucesso',
- 'Budget lines' => 'Rubricas orçamentais',
'CAD - Canadian Dollar' => 'CAD - Dólar canadense',
'CHF - Swiss Francs' => 'CHF - Francos Suíços',
- 'Cost' => 'Custo',
- 'Cost breakdown' => 'Repartição dos custos',
'Custom Stylesheet' => 'Folha de estilos personalizada',
'download' => 'transferir',
- 'Do you really want to remove this budget line?' => 'Tem a certeza que quer remover esta rubrica orçamental?',
'EUR - Euro' => 'EUR - Euro',
- 'Expenses' => 'Despesas',
'GBP - British Pound' => 'GBP - Libra Esterlina',
'INR - Indian Rupee' => 'INR - Rúpia indiana',
'JPY - Japanese Yen' => 'JPY - Iene japonês',
- 'New budget line' => 'Nova rubrica orçamental',
'NZD - New Zealand Dollar' => 'NZD - Dólar Neozelandês',
- 'Remove a budget line' => 'Remover uma rubrica orçamental',
- 'Remove budget line' => 'Remover uma rubrica orçamental',
'RSD - Serbian dinar' => 'RSD - Dinar sérvio',
- 'The budget line have been created successfully.' => 'A rubrica orçamental foi criada com sucesso.',
- 'Unable to create the budget line.' => 'Impossível adicionar esta rubrica orçamental.',
- 'Unable to remove this budget line.' => 'Impossível remover esta rubrica orçamental.',
'USD - US Dollar' => 'USD - Dólar norte-americano',
- 'Remaining' => 'Restante',
'Destination column' => 'Coluna de destino',
'Move the task to another column when assigned to a user' => 'Mover a tarefa para uma outra coluna quando esta está atribuída a um utilizador',
'Move the task to another column when assignee is cleared' => 'Mover a tarefa para uma outra coluna quando esta não está atribuída',
@@ -746,7 +718,6 @@ return array(
'Rate' => 'Taxa',
'Change reference currency' => 'Mudar a moeda de referência',
'Add a new currency rate' => 'Adicionar uma nova taxa para uma moeda',
- 'Currency rates are used to calculate project budget.' => 'As taxas de câmbio são utilizadas para calcular o orçamento do projecto.',
'Reference currency' => 'Moeda de Referência',
'The currency rate have been added successfully.' => 'A taxa de câmbio foi adicionada com sucesso.',
'Unable to add this currency rate.' => 'Impossível adicionar essa taxa de câmbio.',
@@ -878,9 +849,6 @@ return array(
'%s moved the task #%d to the first swimlane' => '%s moveu a tarefa n° %d no primeiro swimlane',
'%s moved the task #%d to the swimlane "%s"' => '%s moveu a tarefa n° %d no swimlane "%s"',
'Swimlane' => 'Swimlane',
- 'Budget overview' => 'Visão geral do orçamento',
- 'Type' => 'Tipo',
- 'There is not enough data to show something.' => 'Não há dados suficientes para mostrar alguma coisa.',
'Gravatar' => 'Gravatar',
'Hipchat' => 'Hipchat',
'Slack' => 'Slack',
diff --git a/app/Locale/ru_RU/translations.php b/app/Locale/ru_RU/translations.php
index 0ad983ff..054e2ac8 100644
--- a/app/Locale/ru_RU/translations.php
+++ b/app/Locale/ru_RU/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Удаленный',
'Enabled' => 'Включен',
'Disabled' => 'Выключены',
- 'Google account linked' => 'Профиль Google ÑвÑзан',
- 'Github account linked' => 'Профиль Github ÑвÑзан',
'Username:' => 'Ð˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ:',
'Name:' => 'ИмÑ:',
'Email:' => 'E-mail:',
@@ -667,17 +665,7 @@ return array(
'Horizontal scrolling' => 'Широкий вид',
'Compact/wide view' => 'Компактный/широкий вид',
'No results match:' => 'ОтÑутÑтвуют результаты:',
- 'Remove hourly rate' => 'Удалить почаÑовую Ñтавку',
- 'Do you really want to remove this hourly rate?' => 'Ð’Ñ‹ дейÑтвительно хотите удалить Ñту почаÑовую Ñтавку?',
- 'Hourly rates' => 'ПочаÑовые Ñтавки',
- 'Hourly rate' => 'ПочаÑÐ¾Ð²Ð°Ñ Ñтавка',
'Currency' => 'Валюта',
- 'Effective date' => 'Дата вÑÑ‚ÑƒÐ¿Ð»ÐµÐ½Ð¸Ñ Ð² Ñилу',
- 'Add new rate' => 'Добавить новый показатель',
- 'Rate removed successfully.' => 'Показатель уÑпешно удален.',
- 'Unable to remove this rate.' => 'Ðе удаетÑÑ ÑƒÐ´Ð°Ð»Ð¸Ñ‚ÑŒ Ñтот показатель.',
- 'Unable to save the hourly rate.' => 'Ðе удаетÑÑ Ñохранить почаÑовую Ñтавку.',
- 'Hourly rate created successfully.' => 'ПочаÑÐ¾Ð²Ð°Ñ Ñтавка уÑпешно Ñоздана.',
'Start time' => 'Ð’Ñ€ÐµÐ¼Ñ Ð½Ð°Ñ‡Ð°Ð»Ð°',
'End time' => 'Ð’Ñ€ÐµÐ¼Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ñ',
'Comment' => 'Комментарий',
@@ -703,34 +691,18 @@ return array(
'Files' => 'Файлы',
'Images' => 'ИзображениÑ',
'Private project' => 'Приватный проект',
- 'Amount' => 'КоличеÑтво',
'AUD - Australian Dollar' => 'AUD - ÐвÑтралийÑкий доллар',
- 'Budget' => 'Бюджет',
- 'Budget line' => 'Ð¡Ñ‚Ð°Ñ‚ÑŒÑ Ð±ÑŽÐ´Ð¶ÐµÑ‚Ð°',
- 'Budget line removed successfully.' => 'Ð‘ÑŽÐ´Ð¶ÐµÑ‚Ð½Ð°Ñ ÑÑ‚Ð°Ñ‚ÑŒÑ ÑƒÑпешно удалена.',
- 'Budget lines' => 'Статьи бюджета',
'CAD - Canadian Dollar' => 'CAD - КанадÑкий доллар',
'CHF - Swiss Francs' => 'CHF - ШвейцарÑкий Франк',
- 'Cost' => 'СтоимоÑÑ‚ÑŒ',
- 'Cost breakdown' => 'Ð”ÐµÑ‚Ð°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð·Ð°Ñ‚Ñ€Ð°Ñ‚',
'Custom Stylesheet' => 'ПользовательÑкий Ñтиль',
'download' => 'загрузить',
- 'Do you really want to remove this budget line?' => 'Ð’Ñ‹ дейÑтвительно хотите удалить Ñту Ñтатью бюджета?',
'EUR - Euro' => 'EUR - Евро',
- 'Expenses' => 'РаÑходы',
'GBP - British Pound' => 'GBP - БританÑкий фунт',
'INR - Indian Rupee' => 'INR - ИндийÑкий рупий',
'JPY - Japanese Yen' => 'JPY - ЯпонÑкай йена',
- 'New budget line' => 'ÐÐ¾Ð²Ð°Ñ ÑÑ‚Ð°Ñ‚ÑŒÑ Ð±ÑŽÐ´Ð¶ÐµÑ‚Ð°',
'NZD - New Zealand Dollar' => 'NZD - ÐовозеландÑкий доллар',
- 'Remove a budget line' => 'Удалить Ñтроку в бюджете',
- 'Remove budget line' => 'Удалить Ñтатью бюджета',
'RSD - Serbian dinar' => 'RSD - СербÑкий динар',
- 'The budget line have been created successfully.' => 'Ð¡Ñ‚Ð°Ñ‚ÑŒÑ Ð±ÑŽÐ´Ð¶ÐµÑ‚Ð° уÑпешно Ñоздана.',
- 'Unable to create the budget line.' => 'Ðе удаетÑÑ Ñоздать Ñту Ñтатью бюджета.',
- 'Unable to remove this budget line.' => 'Ðе удаетÑÑ ÑƒÐ´Ð°Ð»Ð¸Ñ‚ÑŒ Ñту Ñтатью бюджета.',
'USD - US Dollar' => 'USD - доллар СШÐ',
- 'Remaining' => 'Прочее',
'Destination column' => 'Колонка назначениÑ',
'Move the task to another column when assigned to a user' => 'ПеремеÑтить задачу в другую колонку, когда она назначена пользователю',
'Move the task to another column when assignee is cleared' => 'ПеремеÑтить задачу в другую колонку, когда назначение ÑнÑто ',
@@ -746,7 +718,6 @@ return array(
'Rate' => 'КурÑ',
'Change reference currency' => 'Изменить Ñправочник валют',
'Add a new currency rate' => 'Добавить новый валютный курÑ',
- 'Currency rates are used to calculate project budget.' => 'КурÑÑ‹ валют иÑпользуютÑÑ Ð´Ð»Ñ Ñ€Ð°Ñчета бюджета проекта.',
'Reference currency' => 'Справочник валют',
'The currency rate have been added successfully.' => 'ÐšÑƒÑ€Ñ Ð²Ð°Ð»ÑŽÑ‚Ñ‹ был уÑпешно добавлен.',
'Unable to add this currency rate.' => 'Ðевозможно добавить Ñтот ÐºÑƒÑ€Ñ Ð²Ð°Ð»ÑŽÑ‚Ñ‹.',
@@ -895,7 +866,7 @@ return array(
'Overdue tasks for the project "%s"' => 'ПроÑроченные задачи Ð´Ð»Ñ Ð¿Ñ€Ð¾ÐµÐºÑ‚Ð° "%s"',
'New title: %s' => 'Ðовый заголовок: %s',
'The task is not assigned anymore' => 'Задача больше не назначена',
- 'New assignee: %s' => '',
+ // 'New assignee: %s' => '',
'There is no category now' => 'Ð’ наÑтоÑщее Ð²Ñ€ÐµÐ¼Ñ Ð·Ð´ÐµÑÑŒ нет категорий',
'New category: %s' => 'ÐÐ¾Ð²Ð°Ñ ÐºÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ñ: %s',
'New color: %s' => 'Ðовый цвет: %s',
@@ -919,7 +890,7 @@ return array(
'New due date: %B %e, %Y' => 'ÐÐ¾Ð²Ð°Ñ Ð´Ð°Ñ‚Ð° завершениÑ: %B %e, %Y',
'Start date changed: %B %e, %Y' => 'Изменить дату начала: %B %e, %Y',
'%k:%M %p' => '%k:%M %p',
- '%%Y-%%m-%%d' => '%%Г-%%м-%%д',
+ '%%Y-%%m-%%d' => '%%Y-%%m-%%d',
'Total for all columns' => 'Суммарно Ð´Ð»Ñ Ð²Ñех колонок',
'You need at least 2 days of data to show the chart.' => 'Ð”Ð»Ñ Ð¾Ñ‚Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð´Ð¸Ð°Ð³Ñ€Ð°Ð¼Ð¼Ñ‹ нужно по крайней мере 2 днÑ.',
'<15m' => '<15м',
diff --git a/app/Locale/sr_Latn_RS/translations.php b/app/Locale/sr_Latn_RS/translations.php
index 0bc5c248..a05d67d3 100644
--- a/app/Locale/sr_Latn_RS/translations.php
+++ b/app/Locale/sr_Latn_RS/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Udaljno',
'Enabled' => 'Omogući',
'Disabled' => 'Onemogući',
- 'Google account linked' => 'Połączone konto Google',
- 'Github account linked' => 'Połączone konto Github',
'Username:' => 'KorisniÄko ime:',
'Name:' => 'Ime i Prezime',
'Email:' => 'Email: ',
@@ -667,17 +665,7 @@ return array(
// 'Horizontal scrolling' => '',
// 'Compact/wide view' => '',
// 'No results match:' => '',
- // 'Remove hourly rate' => '',
- // 'Do you really want to remove this hourly rate?' => '',
- // 'Hourly rates' => '',
- // 'Hourly rate' => '',
// 'Currency' => '',
- // 'Effective date' => '',
- // 'Add new rate' => '',
- // 'Rate removed successfully.' => '',
- // 'Unable to remove this rate.' => '',
- // 'Unable to save the hourly rate.' => '',
- // 'Hourly rate created successfully.' => '',
// 'Start time' => '',
// 'End time' => '',
// 'Comment' => '',
@@ -703,34 +691,18 @@ return array(
// 'Files' => '',
// 'Images' => '',
// 'Private project' => '',
- // 'Amount' => '',
// 'AUD - Australian Dollar' => '',
- // 'Budget' => '',
- // 'Budget line' => '',
- // 'Budget line removed successfully.' => '',
- // 'Budget lines' => '',
// 'CAD - Canadian Dollar' => '',
// 'CHF - Swiss Francs' => '',
- // 'Cost' => '',
- // 'Cost breakdown' => '',
// 'Custom Stylesheet' => '',
// 'download' => '',
- // 'Do you really want to remove this budget line?' => '',
// 'EUR - Euro' => '',
- // 'Expenses' => '',
// 'GBP - British Pound' => '',
// 'INR - Indian Rupee' => '',
// 'JPY - Japanese Yen' => '',
- // 'New budget line' => '',
// 'NZD - New Zealand Dollar' => '',
- // 'Remove a budget line' => '',
- // 'Remove budget line' => '',
// 'RSD - Serbian dinar' => '',
- // 'The budget line have been created successfully.' => '',
- // 'Unable to create the budget line.' => '',
- // 'Unable to remove this budget line.' => '',
// 'USD - US Dollar' => '',
- // 'Remaining' => '',
// 'Destination column' => '',
// 'Move the task to another column when assigned to a user' => '',
// 'Move the task to another column when assignee is cleared' => '',
@@ -746,7 +718,6 @@ return array(
// 'Rate' => '',
// 'Change reference currency' => '',
// 'Add a new currency rate' => '',
- // 'Currency rates are used to calculate project budget.' => '',
// 'Reference currency' => '',
// 'The currency rate have been added successfully.' => '',
// 'Unable to add this currency rate.' => '',
@@ -878,9 +849,6 @@ return array(
// '%s moved the task #%d to the first swimlane' => '',
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
- // 'Budget overview' => '',
- // 'Type' => '',
- // 'There is not enough data to show something.' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Locale/sv_SE/translations.php b/app/Locale/sv_SE/translations.php
index 9c769724..e8bdb9ce 100644
--- a/app/Locale/sv_SE/translations.php
+++ b/app/Locale/sv_SE/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Fjärr',
'Enabled' => 'Aktiverad',
'Disabled' => 'Inaktiverad',
- 'Google account linked' => 'Googlekonto länkat',
- 'Github account linked' => 'Githubkonto länkat',
'Username:' => 'Användarnam:',
'Name:' => 'Namn:',
'Email:' => 'E-post:',
@@ -667,17 +665,7 @@ return array(
'Horizontal scrolling' => 'Horisontell scroll',
'Compact/wide view' => 'Kompakt/bred vy',
'No results match:' => 'Inga matchande resultat',
- 'Remove hourly rate' => 'Ta bort timtaxa',
- 'Do you really want to remove this hourly rate?' => 'Vill du verkligen ta bort denna timtaxa?',
- 'Hourly rates' => 'Timtaxor',
- 'Hourly rate' => 'Timtaxa',
'Currency' => 'Valuta',
- 'Effective date' => 'Giltighetsdatum',
- 'Add new rate' => 'Lägg till ny taxa',
- 'Rate removed successfully.' => 'Taxan togs bort.',
- 'Unable to remove this rate.' => 'Kunde inte ta bort taxan.',
- 'Unable to save the hourly rate.' => 'Kunde inte spara timtaxan.',
- 'Hourly rate created successfully.' => 'Timtaxan skapades.',
'Start time' => 'Starttid',
'End time' => 'Sluttid',
'Comment' => 'Kommentar',
@@ -703,34 +691,18 @@ return array(
'Files' => 'Filer',
'Images' => 'Bilder',
'Private project' => 'Privat projekt',
- 'Amount' => 'Belopp',
'AUD - Australian Dollar' => 'AUD - Australiska dollar',
- 'Budget' => 'Budget',
- 'Budget line' => 'Budgetlinje',
- 'Budget line removed successfully.' => 'Budgetlinjen togs bort.',
- 'Budget lines' => 'Budgetlinjer',
'CAD - Canadian Dollar' => 'CAD - Kanadensiska dollar',
'CHF - Swiss Francs' => 'CHF - Schweiziska Franc',
- 'Cost' => 'Kostnad',
- 'Cost breakdown' => 'Kostnadssammanställning',
'Custom Stylesheet' => 'Anpassad stilmall',
'download' => 'ladda ned',
- 'Do you really want to remove this budget line?' => 'Vill du verkligen ta bort budgetlinjen?',
'EUR - Euro' => 'EUR - Euro',
- 'Expenses' => 'Utgifter',
'GBP - British Pound' => 'GBP - Brittiska Pund',
'INR - Indian Rupee' => 'INR - Indiska Rupier',
'JPY - Japanese Yen' => 'JPY - Japanska Yen',
- 'New budget line' => 'Ny budgetlinje',
'NZD - New Zealand Dollar' => 'NZD - Nya Zeeländska Dollar',
- 'Remove a budget line' => 'Ta bort en budgetlinje',
- 'Remove budget line' => 'Ta bort budgetlinje',
'RSD - Serbian dinar' => 'RSD - Serbiska Dinarer',
- 'The budget line have been created successfully.' => 'Budgetlinjen har skapats.',
- 'Unable to create the budget line.' => 'Kunde inte skapa budgetlinjen.',
- 'Unable to remove this budget line.' => 'Kunde inte ta bort budgetlinjen.',
'USD - US Dollar' => 'USD - Amerikanska Dollar',
- 'Remaining' => 'Återstående',
'Destination column' => 'MÃ¥lkolumn',
'Move the task to another column when assigned to a user' => 'Flytta uppgiften till en annan kolumn när den tilldelats en användare',
'Move the task to another column when assignee is cleared' => 'Flytta uppgiften till en annan kolumn när tilldelningen tas bort.',
@@ -746,7 +718,6 @@ return array(
'Rate' => 'Kurs',
'Change reference currency' => 'Ändra referenskurs',
'Add a new currency rate' => 'Lägg till ny valutakurs',
- 'Currency rates are used to calculate project budget.' => 'Valutakurser används för att beräkna projektbudget.',
'Reference currency' => 'Referensvaluta',
'The currency rate have been added successfully.' => 'Valutakursen har lagts till.',
'Unable to add this currency rate.' => 'Kunde inte lägga till valutakursen.',
@@ -878,9 +849,6 @@ return array(
'%s moved the task #%d to the first swimlane' => '%s flyttade uppgiften #%d till första swimlane',
'%s moved the task #%d to the swimlane "%s"' => '%s flyttade uppgiften #%d till swimlane "%s"',
'Swimlane' => 'Swimlane',
- 'Budget overview' => 'Budgetöversikt',
- 'Type' => 'Typ',
- 'There is not enough data to show something.' => 'Det finns inte tillräckligt mycket data för att visa något.',
'Gravatar' => 'Gravatar',
'Hipchat' => 'Hipchat',
'Slack' => 'Slack',
diff --git a/app/Locale/th_TH/translations.php b/app/Locale/th_TH/translations.php
index a5ed2474..d94107ad 100644
--- a/app/Locale/th_TH/translations.php
+++ b/app/Locale/th_TH/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'รีโมท',
'Enabled' => 'เปิดà¸à¸²à¸£à¹ƒà¸Šà¹‰',
'Disabled' => 'ปิดà¸à¸²à¸£à¹ƒà¸Šà¹‰',
- 'Google account linked' => 'เชื่อมà¸à¸±à¸šà¸à¸¹à¹€à¸à¸´à¸¥à¹à¸­à¸„เคาท์',
- 'Github account linked' => 'เชื่อมà¸à¸±à¸šà¸à¸´à¸—ฮับà¹à¸­à¸„เคาท์',
'Username:' => 'ชื่อผู้ใช้:',
'Name:' => 'ชื่อ:',
'Email:' => 'อีเมล:',
@@ -667,17 +665,7 @@ return array(
'Horizontal scrolling' => 'เลื่อนตามà¹à¸™à¸§à¸™à¸­à¸™',
'Compact/wide view' => 'พอดี/à¸à¸§à¹‰à¸²à¸‡ มุมมอง',
'No results match:' => 'ไม่มีผลลัพท์ที่ตรง',
- 'Remove hourly rate' => 'ลบอัตรารายชั่วโมง',
- 'Do you really want to remove this hourly rate?' => 'คุณต้องà¸à¸²à¸£à¸¥à¸šà¸­à¸±à¸•à¸£à¸²à¸£à¸²à¸¢à¸Šà¸±à¹ˆà¸§à¹‚มง?',
- 'Hourly rates' => 'อัตรารายชั่วโมง',
- 'Hourly rate' => 'อัตรารายชั่วโมง',
'Currency' => 'สà¸à¸¸à¸¥à¹€à¸‡à¸´à¸™',
- 'Effective date' => 'วันที่จ่าย',
- 'Add new rate' => 'เพิ่มอัตราใหม่',
- 'Rate removed successfully.' => 'ลบอัตราเรียบร้อยà¹à¸¥à¹‰à¸§',
- 'Unable to remove this rate.' => 'ไม่สามารถลบอัตรานี้ได้',
- 'Unable to save the hourly rate.' => 'ไม่สามารถบันทึà¸à¸­à¸±à¸•à¸£à¸²à¸£à¸²à¸¢à¸Šà¸±à¹ˆà¸§à¹‚มง',
- 'Hourly rate created successfully.' => 'อัตรารายชั่วโมงสร้างเรียบร้อยà¹à¸¥à¹‰à¸§',
'Start time' => 'เวลาเริ่มต้น',
'End time' => 'เวลาจบ',
'Comment' => 'ความคิดเห็น',
@@ -703,34 +691,18 @@ return array(
'Files' => 'ไฟล์',
'Images' => 'รูปภาพ',
'Private project' => 'โปรเจคส่วนตัว',
- 'Amount' => 'จำนวนเงิน',
// 'AUD - Australian Dollar' => '',
- 'Budget' => 'งบประมาณ',
- 'Budget line' => 'วงเงินงบประมาณ',
- 'Budget line removed successfully.' => 'ลบวงเงินประมาณเรียบร้อยà¹à¸¥à¹‰à¸§',
- 'Budget lines' => 'วงเงินงบประมาณ',
// 'CAD - Canadian Dollar' => '',
// 'CHF - Swiss Francs' => '',
- 'Cost' => 'มูลค่า',
- 'Cost breakdown' => 'รายละเอียดค่าใช้จ่าย',
// 'Custom Stylesheet' => '',
'download' => 'ดาวน์โหลด',
- 'Do you really want to remove this budget line?' => 'คุณต้องà¸à¸²à¸£à¸¥à¸šà¸§à¸‡à¹€à¸‡à¸´à¸™à¸‡à¸šà¸›à¸£à¸°à¸¡à¸²à¸“นี้?',
// 'EUR - Euro' => '',
- 'Expenses' => 'รายจ่าย',
// 'GBP - British Pound' => '',
// 'INR - Indian Rupee' => '',
// 'JPY - Japanese Yen' => '',
- 'New budget line' => 'วงเงินงบประมาณใหม่',
// 'NZD - New Zealand Dollar' => '',
- 'Remove a budget line' => 'ลบวงเงินประมาณ',
- 'Remove budget line' => 'ลบวงเงินประมาณ',
// 'RSD - Serbian dinar' => '',
- 'The budget line have been created successfully.' => 'สร้างวงเงินงบประมาณเรียบร้อยà¹à¸¥à¹‰à¸§',
- 'Unable to create the budget line.' => 'ไม่สามารถสร้างวงเงินงบประมาณได้',
- 'Unable to remove this budget line.' => 'ไม่สามารถลบวงเงินงบประมาณนี้',
// 'USD - US Dollar' => '',
- 'Remaining' => 'เหลืออยู่',
'Destination column' => 'คอลัมน์เป้าหมาย',
'Move the task to another column when assigned to a user' => 'ย้ายงานไปคอลัมน์อื่นเมื่อà¸à¸³à¸«à¸™à¸”บุคคลรับผิดชอบ',
'Move the task to another column when assignee is cleared' => 'ย้ายงานไปคอลัมน์อื่นเมื่อไม่à¸à¸³à¸«à¸™à¸”บุคคลรับผิดชอบ',
@@ -746,7 +718,6 @@ return array(
'Rate' => 'อัตรา',
// 'Change reference currency' => '',
'Add a new currency rate' => 'เพิ่มอัตราà¹à¸¥à¸à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹€à¸‡à¸´à¸™à¸•à¸£à¸²à¹ƒà¸«à¸¡à¹ˆ',
- 'Currency rates are used to calculate project budget.' => 'อัตราà¹à¸¥à¸à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹€à¸‡à¸´à¸™à¸•à¸£à¸²à¸–ูà¸à¹ƒà¸Šà¹‰à¹ƒà¸™à¸à¸²à¸£à¸„ำนวณงบประมาณของโปรเจค',
// 'Reference currency' => '',
// 'The currency rate have been added successfully.' => '',
// 'Unable to add this currency rate.' => '',
@@ -878,9 +849,6 @@ return array(
// '%s moved the task #%d to the first swimlane' => '',
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
- // 'Budget overview' => '',
- // 'Type' => '',
- // 'There is not enough data to show something.' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Locale/tr_TR/translations.php b/app/Locale/tr_TR/translations.php
index 9eb5c41e..87cccac7 100644
--- a/app/Locale/tr_TR/translations.php
+++ b/app/Locale/tr_TR/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => 'Uzak',
'Enabled' => 'EtkinleÅŸtirildi',
'Disabled' => 'Devre dışı bırakıldı',
- 'Google account linked' => 'Google hesabıyla bağlı',
- 'Github account linked' => 'Github hesabıyla bağlı',
'Username:' => 'Kullanıcı adı',
'Name:' => 'Ad',
'Email:' => 'E-Posta',
@@ -667,17 +665,7 @@ return array(
'Horizontal scrolling' => 'Geniş görünüm',
'Compact/wide view' => 'Ekrana sığdır / Geniş görünüm',
// 'No results match:' => '',
- // 'Remove hourly rate' => '',
- // 'Do you really want to remove this hourly rate?' => '',
- // 'Hourly rates' => '',
- // 'Hourly rate' => '',
// 'Currency' => '',
- // 'Effective date' => '',
- // 'Add new rate' => '',
- // 'Rate removed successfully.' => '',
- // 'Unable to remove this rate.' => '',
- // 'Unable to save the hourly rate.' => '',
- // 'Hourly rate created successfully.' => '',
// 'Start time' => '',
// 'End time' => '',
// 'Comment' => '',
@@ -703,34 +691,18 @@ return array(
// 'Files' => '',
// 'Images' => '',
// 'Private project' => '',
- // 'Amount' => '',
// 'AUD - Australian Dollar' => '',
- // 'Budget' => '',
- // 'Budget line' => '',
- // 'Budget line removed successfully.' => '',
- // 'Budget lines' => '',
// 'CAD - Canadian Dollar' => '',
// 'CHF - Swiss Francs' => '',
- // 'Cost' => '',
- // 'Cost breakdown' => '',
// 'Custom Stylesheet' => '',
// 'download' => '',
- // 'Do you really want to remove this budget line?' => '',
// 'EUR - Euro' => '',
- // 'Expenses' => '',
// 'GBP - British Pound' => '',
// 'INR - Indian Rupee' => '',
// 'JPY - Japanese Yen' => '',
- // 'New budget line' => '',
// 'NZD - New Zealand Dollar' => '',
- // 'Remove a budget line' => '',
- // 'Remove budget line' => '',
// 'RSD - Serbian dinar' => '',
- // 'The budget line have been created successfully.' => '',
- // 'Unable to create the budget line.' => '',
- // 'Unable to remove this budget line.' => '',
// 'USD - US Dollar' => '',
- // 'Remaining' => '',
// 'Destination column' => '',
// 'Move the task to another column when assigned to a user' => '',
// 'Move the task to another column when assignee is cleared' => '',
@@ -746,7 +718,6 @@ return array(
// 'Rate' => '',
// 'Change reference currency' => '',
// 'Add a new currency rate' => '',
- // 'Currency rates are used to calculate project budget.' => '',
// 'Reference currency' => '',
// 'The currency rate have been added successfully.' => '',
// 'Unable to add this currency rate.' => '',
@@ -878,9 +849,6 @@ return array(
// '%s moved the task #%d to the first swimlane' => '',
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
- // 'Budget overview' => '',
- // 'Type' => '',
- // 'There is not enough data to show something.' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Locale/zh_CN/translations.php b/app/Locale/zh_CN/translations.php
index 910bc0b4..102252e5 100644
--- a/app/Locale/zh_CN/translations.php
+++ b/app/Locale/zh_CN/translations.php
@@ -395,8 +395,6 @@ return array(
'Remote' => '远程',
'Enabled' => 'å¯ç”¨',
'Disabled' => 'åœç”¨',
- 'Google account linked' => 'å·²ç»é“¾æŽ¥è°·æ­Œè´¦å·',
- 'Github account linked' => 'å·²ç»é“¾æŽ¥Githubè´¦å·',
'Username:' => '用户å:',
'Name:' => '姓å:',
'Email:' => '电å­é‚®ä»¶ï¼š',
@@ -667,17 +665,7 @@ return array(
'Horizontal scrolling' => '水平滚动',
'Compact/wide view' => '紧凑/宽视图',
'No results match:' => '无匹é…结果:',
- 'Remove hourly rate' => '删除å°æ—¶å·¥èµ„',
- 'Do you really want to remove this hourly rate?' => '确定è¦åˆ é™¤æ­¤è®¡æ—¶å·¥èµ„å—?',
- 'Hourly rates' => 'å°æ—¶å·¥èµ„',
- 'Hourly rate' => 'å°æ—¶å·¥èµ„',
'Currency' => 'è´§å¸',
- 'Effective date' => '开始时间',
- 'Add new rate' => '添加å°æ—¶å·¥èµ„',
- 'Rate removed successfully.' => 'æˆåŠŸåˆ é™¤å·¥èµ„。',
- 'Unable to remove this rate.' => '无法删除此å°æ—¶å·¥èµ„。',
- 'Unable to save the hourly rate.' => '无法删除å°æ—¶å·¥èµ„。',
- 'Hourly rate created successfully.' => 'æˆåŠŸåˆ›å»ºå°æ—¶å·¥èµ„。',
'Start time' => '开始时间',
'End time' => '结æŸæ—¶1é—´',
'Comment' => '注释',
@@ -703,34 +691,18 @@ return array(
'Files' => '文件',
'Images' => '图片',
'Private project' => 'ç§äººé¡¹ç›®',
- 'Amount' => 'æ•°é‡',
// 'AUD - Australian Dollar' => '',
- 'Budget' => '预算',
- 'Budget line' => '预算线',
- 'Budget line removed successfully.' => 'æˆåŠŸåˆ é™¤é¢„算线',
- 'Budget lines' => '预算线',
// 'CAD - Canadian Dollar' => '',
// 'CHF - Swiss Francs' => '',
- 'Cost' => 'æˆæœ¬',
- 'Cost breakdown' => 'æˆæœ¬åˆ†è§£',
'Custom Stylesheet' => '自定义样å¼è¡¨',
'download' => '下载',
- 'Do you really want to remove this budget line?' => '确定è¦åˆ é™¤æ­¤é¢„算线å—?',
// 'EUR - Euro' => '',
- 'Expenses' => '花费',
// 'GBP - British Pound' => '',
// 'INR - Indian Rupee' => '',
// 'JPY - Japanese Yen' => '',
- 'New budget line' => '新预算线',
// 'NZD - New Zealand Dollar' => '',
- 'Remove a budget line' => '删除预算线',
- 'Remove budget line' => '删除预算线',
// 'RSD - Serbian dinar' => '',
- 'The budget line have been created successfully.' => 'æˆåŠŸåˆ›å»ºé¢„算线。',
- 'Unable to create the budget line.' => '无法创建预算线。',
- 'Unable to remove this budget line.' => '无法删除此预算线。',
// 'USD - US Dollar' => '',
- 'Remaining' => '剩余',
'Destination column' => '目标æ ç›®',
'Move the task to another column when assigned to a user' => '指定负责人时移动到其它æ ç›®',
'Move the task to another column when assignee is cleared' => '移除负责人时移动到其它æ ç›®',
@@ -746,7 +718,6 @@ return array(
'Rate' => '汇率',
'Change reference currency' => '修改å‚考货å¸',
'Add a new currency rate' => '添加新汇率',
- 'Currency rates are used to calculate project budget.' => '汇率会用æ¥è®¡ç®—项目预算。',
'Reference currency' => 'å‚考货å¸',
'The currency rate have been added successfully.' => 'æˆåŠŸæ·»åŠ æ±‡çŽ‡ã€‚',
'Unable to add this currency rate.' => '无法添加此汇率',
@@ -878,9 +849,6 @@ return array(
// '%s moved the task #%d to the first swimlane' => '',
// '%s moved the task #%d to the swimlane "%s"' => '',
// 'Swimlane' => '',
- 'Budget overview' => '预算概览',
- 'Type' => '类型',
- // 'There is not enough data to show something.' => '',
// 'Gravatar' => '',
// 'Hipchat' => '',
// 'Slack' => '',
diff --git a/app/Model/Acl.php b/app/Model/Acl.php
index 8c28cb1a..9a227cf5 100644
--- a/app/Model/Acl.php
+++ b/app/Model/Acl.php
@@ -64,7 +64,6 @@ class Acl extends Base
'export' => '*',
'project' => array('edit', 'update', 'share', 'integration', 'users', 'alloweverybody', 'allow', 'setowner', 'revoke', 'duplicate', 'disable', 'enable'),
'swimlane' => '*',
- 'budget' => '*',
'gantt' => array('project', 'savetaskdate', 'task', 'savetask'),
);
@@ -95,6 +94,18 @@ class Acl extends Base
);
/**
+ * Extend ACL rules
+ *
+ * @access public
+ * @param string $acl_name
+ * @param aray $rules
+ */
+ public function extend($acl_name, array $rules)
+ {
+ $this->$acl_name = array_merge($this->$acl_name, $rules);
+ }
+
+ /**
* Return true if the specified controller/action match the given acl
*
* @access public
diff --git a/app/Model/Budget.php b/app/Model/Budget.php
deleted file mode 100644
index 76c42ca9..00000000
--- a/app/Model/Budget.php
+++ /dev/null
@@ -1,214 +0,0 @@
-<?php
-
-namespace Model;
-
-use DateInterval;
-use DateTime;
-use SimpleValidator\Validator;
-use SimpleValidator\Validators;
-
-/**
- * Budget
- *
- * @package model
- * @author Frederic Guillot
- */
-class Budget extends Base
-{
- /**
- * SQL table name
- *
- * @var string
- */
- const TABLE = 'budget_lines';
-
- /**
- * Get all budget lines for a project
- *
- * @access public
- * @param integer $project_id
- * @return array
- */
- public function getAll($project_id)
- {
- return $this->db->table(self::TABLE)->eq('project_id', $project_id)->desc('date')->findAll();
- }
-
- /**
- * Get the current total of the budget
- *
- * @access public
- * @param integer $project_id
- * @return float
- */
- public function getTotal($project_id)
- {
- $result = $this->db->table(self::TABLE)->columns('SUM(amount) as total')->eq('project_id', $project_id)->findOne();
- return isset($result['total']) ? (float) $result['total'] : 0;
- }
-
- /**
- * Get breakdown by tasks/subtasks/users
- *
- * @access public
- * @param integer $project_id
- * @return \PicoDb\Table
- */
- public function getSubtaskBreakdown($project_id)
- {
- return $this->db
- ->table(SubtaskTimeTracking::TABLE)
- ->columns(
- SubtaskTimeTracking::TABLE.'.id',
- SubtaskTimeTracking::TABLE.'.user_id',
- SubtaskTimeTracking::TABLE.'.subtask_id',
- SubtaskTimeTracking::TABLE.'.start',
- SubtaskTimeTracking::TABLE.'.time_spent',
- Subtask::TABLE.'.task_id',
- Subtask::TABLE.'.title AS subtask_title',
- Task::TABLE.'.title AS task_title',
- Task::TABLE.'.project_id',
- User::TABLE.'.username',
- User::TABLE.'.name'
- )
- ->join(Subtask::TABLE, 'id', 'subtask_id')
- ->join(Task::TABLE, 'id', 'task_id', Subtask::TABLE)
- ->join(User::TABLE, 'id', 'user_id')
- ->eq(Task::TABLE.'.project_id', $project_id)
- ->callback(array($this, 'applyUserRate'));
- }
-
- /**
- * Gather necessary information to display the budget graph
- *
- * @access public
- * @param integer $project_id
- * @return array
- */
- public function getDailyBudgetBreakdown($project_id)
- {
- $out = array();
- $in = $this->db->hashtable(self::TABLE)->eq('project_id', $project_id)->gt('amount', 0)->asc('date')->getAll('date', 'amount');
- $time_slots = $this->getSubtaskBreakdown($project_id)->findAll();
-
- foreach ($time_slots as $slot) {
- $date = date('Y-m-d', $slot['start']);
-
- if (! isset($out[$date])) {
- $out[$date] = 0;
- }
-
- $out[$date] += $slot['cost'];
- }
-
- $start = key($in) ?: key($out);
- $end = new DateTime;
- $left = 0;
- $serie = array();
-
- for ($today = new DateTime($start); $today <= $end; $today->add(new DateInterval('P1D'))) {
-
- $date = $today->format('Y-m-d');
- $today_in = isset($in[$date]) ? (int) $in[$date] : 0;
- $today_out = isset($out[$date]) ? (int) $out[$date] : 0;
-
- if ($today_in > 0 || $today_out > 0) {
-
- $left += $today_in;
- $left -= $today_out;
-
- $serie[] = array(
- 'date' => $date,
- 'in' => $today_in,
- 'out' => -$today_out,
- 'left' => $left,
- );
- }
- }
-
- return $serie;
- }
-
- /**
- * Filter callback to apply the rate according to the effective date
- *
- * @access public
- * @param array $records
- * @return array
- */
- public function applyUserRate(array $records)
- {
- $rates = $this->hourlyRate->getAllByProject($records[0]['project_id']);
-
- foreach ($records as &$record) {
-
- $hourly_price = 0;
-
- foreach ($rates as $rate) {
-
- if ($rate['user_id'] == $record['user_id'] && date('Y-m-d', $rate['date_effective']) <= date('Y-m-d', $record['start'])) {
- $hourly_price = $this->currency->getPrice($rate['currency'], $rate['rate']);
- break;
- }
- }
-
- $record['cost'] = $hourly_price * $record['time_spent'];
- }
-
- return $records;
- }
-
- /**
- * Add a new budget line in the database
- *
- * @access public
- * @param integer $project_id
- * @param float $amount
- * @param string $comment
- * @param string $date
- * @return boolean|integer
- */
- public function create($project_id, $amount, $comment, $date = '')
- {
- $values = array(
- 'project_id' => $project_id,
- 'amount' => $amount,
- 'comment' => $comment,
- 'date' => $date ?: date('Y-m-d'),
- );
-
- return $this->persist(self::TABLE, $values);
- }
-
- /**
- * Remove a specific budget line
- *
- * @access public
- * @param integer $budget_id
- * @return boolean
- */
- public function remove($budget_id)
- {
- return $this->db->table(self::TABLE)->eq('id', $budget_id)->remove();
- }
-
- /**
- * Validate creation
- *
- * @access public
- * @param array $values Form values
- * @return array $valid, $errors [0] = Success or not, [1] = List of errors
- */
- public function validateCreation(array $values)
- {
- $v = new Validator($values, array(
- new Validators\Required('project_id', t('Field required')),
- new Validators\Required('amount', t('Field required')),
- ));
-
- return array(
- $v->execute(),
- $v->getErrors()
- );
- }
-} \ No newline at end of file
diff --git a/app/Model/Config.php b/app/Model/Config.php
index 6fa98f93..8e51da24 100644
--- a/app/Model/Config.php
+++ b/app/Model/Config.php
@@ -75,6 +75,7 @@ class Config extends Base
{
// Sorted by value
$languages = array(
+ 'id_ID' => 'Bahasa Indonesia',
'cs_CZ' => 'Čeština',
'da_DK' => 'Dansk',
'de_DE' => 'Deutsch',
@@ -135,6 +136,7 @@ class Config extends Base
'zh_CN' => 'zh-cn',
'ja_JP' => 'ja',
'th_TH' => 'th',
+ 'id_ID' => 'id'
);
$lang = $this->getCurrentLanguage();
diff --git a/app/Model/File.php b/app/Model/File.php
index f884e460..7adab42b 100644
--- a/app/Model/File.php
+++ b/app/Model/File.php
@@ -3,6 +3,8 @@
namespace Model;
use Event\FileEvent;
+use Core\Tool;
+use Core\ObjectStorage\ObjectStorageException;
/**
* File model
@@ -47,14 +49,17 @@ class File extends Base
*/
public function remove($file_id)
{
- $file = $this->getbyId($file_id);
+ try {
- if (! empty($file)) {
- @unlink(FILES_DIR.$file['path']);
- return $this->db->table(self::TABLE)->eq('id', $file_id)->remove();
- }
+ $file = $this->getbyId($file_id);
+ $this->objectStorage->remove($file['path']);
- return false;
+ return $this->db->table(self::TABLE)->eq('id', $file['id'])->remove();
+ }
+ catch (ObjectStorageException $e) {
+ $this->logger->error($e->getMessage());
+ return false;
+ }
}
/**
@@ -66,11 +71,11 @@ class File extends Base
*/
public function removeAll($task_id)
{
- $files = $this->getAll($task_id);
+ $file_ids = $this->db->table(self::TABLE)->eq('task_id', $task_id)->asc('id')->findAllByColumn('id');
$results = array();
- foreach ($files as $file) {
- $results[] = $this->remove($file['id']);
+ foreach ($file_ids as $file_id) {
+ $results[] = $this->remove($file_id);
}
return ! in_array(false, $results, true);
@@ -196,6 +201,30 @@ class File extends Base
}
/**
+ * Return the image mimetype based on the file extension
+ *
+ * @access public
+ * @param $filename
+ * @return string
+ */
+ public function getImageMimeType($filename)
+ {
+ $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
+
+ switch ($extension) {
+ case 'jpeg':
+ case 'jpg':
+ return 'image/jpeg';
+ case 'png':
+ return 'image/png';
+ case 'gif':
+ return 'image/gif';
+ default:
+ return 'image/jpeg';
+ }
+ }
+
+ /**
* Generate the path for a new filename
*
* @access public
@@ -210,6 +239,18 @@ class File extends Base
}
/**
+ * Generate the path for a thumbnails
+ *
+ * @access public
+ * @param string $key Storage key
+ * @return string
+ */
+ public function getThumbnailPath($key)
+ {
+ return 'thumbnails'.DIRECTORY_SEPARATOR.$key;
+ }
+
+ /**
* Handle file upload
*
* @access public
@@ -218,11 +259,13 @@ class File extends Base
* @param string $form_name File form name
* @return bool
*/
- public function upload($project_id, $task_id, $form_name)
+ public function uploadFiles($project_id, $task_id, $form_name)
{
- $results = array();
+ try {
- if (! empty($_FILES[$form_name])) {
+ if (empty($_FILES[$form_name])) {
+ return false;
+ }
foreach ($_FILES[$form_name]['error'] as $key => $error) {
@@ -232,22 +275,27 @@ class File extends Base
$uploaded_filename = $_FILES[$form_name]['tmp_name'][$key];
$destination_filename = $this->generatePath($project_id, $task_id, $original_filename);
- @mkdir(FILES_DIR.dirname($destination_filename), 0755, true);
+ if ($this->isImage($original_filename)) {
+ $this->generateThumbnailFromFile($uploaded_filename, $destination_filename);
+ }
- if (@move_uploaded_file($uploaded_filename, FILES_DIR.$destination_filename)) {
+ $this->objectStorage->moveUploadedFile($uploaded_filename, $destination_filename);
- $results[] = $this->create(
- $task_id,
- $original_filename,
- $destination_filename,
- $_FILES[$form_name]['size'][$key]
- );
- }
+ $this->create(
+ $task_id,
+ $original_filename,
+ $destination_filename,
+ $_FILES[$form_name]['size'][$key]
+ );
}
}
- }
- return ! in_array(false, $results, true);
+ return true;
+ }
+ catch (ObjectStorageException $e) {
+ $this->logger->error($e->getMessage());
+ return false;
+ }
}
/**
@@ -261,129 +309,77 @@ class File extends Base
*/
public function uploadScreenshot($project_id, $task_id, $blob)
{
- $data = base64_decode($blob);
-
- if (empty($data)) {
- return false;
- }
-
$original_filename = e('Screenshot taken %s', dt('%B %e, %Y at %k:%M %p', time())).'.png';
- $destination_filename = $this->generatePath($project_id, $task_id, $original_filename);
-
- @mkdir(FILES_DIR.dirname($destination_filename), 0755, true);
- @file_put_contents(FILES_DIR.$destination_filename, $data);
-
- return $this->create(
- $task_id,
- $original_filename,
- $destination_filename,
- strlen($data)
- );
+ return $this->uploadContent($project_id, $task_id, $original_filename, $blob);
}
/**
* Handle file upload (base64 encoded content)
*
* @access public
- * @param integer $project_id Project id
- * @param integer $task_id Task id
- * @param string $filename Filename
- * @param string $blob Base64 encoded image
+ * @param integer $project_id Project id
+ * @param integer $task_id Task id
+ * @param string $original_filename Filename
+ * @param string $blob Base64 encoded file
* @return bool|integer
*/
- public function uploadContent($project_id, $task_id, $filename, $blob)
+ public function uploadContent($project_id, $task_id, $original_filename, $blob)
{
- $data = base64_decode($blob);
+ try {
- if (empty($data)) {
- return false;
- }
+ $data = base64_decode($blob);
- $destination_filename = $this->generatePath($project_id, $task_id, $filename);
+ if (empty($data)) {
+ return false;
+ }
+
+ $destination_filename = $this->generatePath($project_id, $task_id, $original_filename);
+ $this->objectStorage->put($destination_filename, $data);
- @mkdir(FILES_DIR.dirname($destination_filename), 0755, true);
- @file_put_contents(FILES_DIR.$destination_filename, $data);
+ if ($this->isImage($original_filename)) {
+ $this->generateThumbnailFromData($destination_filename, $data);
+ }
- return $this->create(
- $task_id,
- $filename,
- $destination_filename,
- strlen($data)
- );
+ return $this->create(
+ $task_id,
+ $original_filename,
+ $destination_filename,
+ strlen($data)
+ );
+ }
+ catch (ObjectStorageException $e) {
+ $this->logger->error($e->getMessage());
+ return false;
+ }
}
/**
- * Generate a jpeg thumbnail from an image (output directly the image)
+ * Generate thumbnail from a blob
*
* @access public
- * @param string $filename Source image
- * @param integer $resize_width Desired image width
- * @param integer $resize_height Desired image height
+ * @param string $destination_filename
+ * @param string $data
*/
- public function generateThumbnail($filename, $resize_width, $resize_height)
+ public function generateThumbnailFromData($destination_filename, &$data)
{
- $metadata = getimagesize($filename);
- $src_width = $metadata[0];
- $src_height = $metadata[1];
- $dst_y = 0;
- $dst_x = 0;
-
- if (empty($metadata['mime'])) {
- return;
- }
-
- if ($resize_width == 0 && $resize_height == 0) {
- $resize_width = 100;
- $resize_height = 100;
- }
+ $temp_filename = tempnam(sys_get_temp_dir(), 'datafile');
- if ($resize_width > 0 && $resize_height == 0) {
- $dst_width = $resize_width;
- $dst_height = floor($src_height * ($resize_width / $src_width));
- $dst_image = imagecreatetruecolor($dst_width, $dst_height);
- }
- elseif ($resize_width == 0 && $resize_height > 0) {
- $dst_width = floor($src_width * ($resize_height / $src_height));
- $dst_height = $resize_height;
- $dst_image = imagecreatetruecolor($dst_width, $dst_height);
- }
- else {
-
- $src_ratio = $src_width / $src_height;
- $resize_ratio = $resize_width / $resize_height;
-
- if ($src_ratio <= $resize_ratio) {
- $dst_width = $resize_width;
- $dst_height = floor($src_height * ($resize_width / $src_width));
-
- $dst_y = ($dst_height - $resize_height) / 2 * (-1);
- }
- else {
- $dst_width = floor($src_width * ($resize_height / $src_height));
- $dst_height = $resize_height;
-
- $dst_x = ($dst_width - $resize_width) / 2 * (-1);
- }
-
- $dst_image = imagecreatetruecolor($resize_width, $resize_height);
- }
-
- switch ($metadata['mime']) {
- case 'image/jpeg':
- case 'image/jpg':
- $src_image = imagecreatefromjpeg($filename);
- break;
- case 'image/png':
- $src_image = imagecreatefrompng($filename);
- break;
- case 'image/gif':
- $src_image = imagecreatefromgif($filename);
- break;
- default:
- return;
- }
+ file_put_contents($temp_filename, $data);
+ $this->generateThumbnailFromFile($temp_filename, $destination_filename);
+ unlink($temp_filename);
+ }
- imagecopyresampled($dst_image, $src_image, $dst_x, $dst_y, 0, 0, $dst_width, $dst_height, $src_width, $src_height);
- imagejpeg($dst_image);
+ /**
+ * Generate thumbnail from a blob
+ *
+ * @access public
+ * @param string $uploaded_filename
+ * @param string $destination_filename
+ */
+ public function generateThumbnailFromFile($uploaded_filename, $destination_filename)
+ {
+ $thumbnail_filename = tempnam(sys_get_temp_dir(), 'thumbnail');
+ Tool::generateThumbnail($uploaded_filename, $thumbnail_filename);
+ $this->objectStorage->moveFile($thumbnail_filename, $this->getThumbnailPath($destination_filename));
}
}
diff --git a/app/Model/HourlyRate.php b/app/Model/HourlyRate.php
deleted file mode 100644
index 1550bdae..00000000
--- a/app/Model/HourlyRate.php
+++ /dev/null
@@ -1,121 +0,0 @@
-<?php
-
-namespace Model;
-
-use SimpleValidator\Validator;
-use SimpleValidator\Validators;
-
-/**
- * Hourly Rate
- *
- * @package model
- * @author Frederic Guillot
- */
-class HourlyRate extends Base
-{
- /**
- * SQL table name
- *
- * @var string
- */
- const TABLE = 'hourly_rates';
-
- /**
- * Get all user rates for a given project
- *
- * @access public
- * @param integer $project_id
- * @return array
- */
- public function getAllByProject($project_id)
- {
- $members = $this->projectPermission->getMembers($project_id);
-
- if (empty($members)) {
- return array();
- }
-
- return $this->db->table(self::TABLE)->in('user_id', array_keys($members))->desc('date_effective')->findAll();
- }
-
- /**
- * Get all rates for a given user
- *
- * @access public
- * @param integer $user_id User id
- * @return array
- */
- public function getAllByUser($user_id)
- {
- return $this->db->table(self::TABLE)->eq('user_id', $user_id)->desc('date_effective')->findAll();
- }
-
- /**
- * Get current rate for a given user
- *
- * @access public
- * @param integer $user_id User id
- * @return float
- */
- public function getCurrentRate($user_id)
- {
- return $this->db->table(self::TABLE)->eq('user_id', $user_id)->desc('date_effective')->findOneColumn('rate') ?: 0;
- }
-
- /**
- * Add a new rate in the database
- *
- * @access public
- * @param integer $user_id User id
- * @param float $rate Hourly rate
- * @param string $currency Currency code
- * @param string $date ISO8601 date format
- * @return boolean|integer
- */
- public function create($user_id, $rate, $currency, $date)
- {
- $values = array(
- 'user_id' => $user_id,
- 'rate' => $rate,
- 'currency' => $currency,
- 'date_effective' => $this->dateParser->removeTimeFromTimestamp($this->dateParser->getTimestamp($date)),
- );
-
- return $this->persist(self::TABLE, $values);
- }
-
- /**
- * Remove a specific rate
- *
- * @access public
- * @param integer $rate_id
- * @return boolean
- */
- public function remove($rate_id)
- {
- return $this->db->table(self::TABLE)->eq('id', $rate_id)->remove();
- }
-
- /**
- * Validate creation
- *
- * @access public
- * @param array $values Form values
- * @return array $valid, $errors [0] = Success or not, [1] = List of errors
- */
- public function validateCreation(array $values)
- {
- $v = new Validator($values, array(
- new Validators\Required('user_id', t('Field required')),
- new Validators\Required('rate', t('Field required')),
- new Validators\Numeric('rate', t('This value must be numeric')),
- new Validators\Required('date_effective', t('Field required')),
- new Validators\Required('currency', t('Field required')),
- ));
-
- return array(
- $v->execute(),
- $v->getErrors()
- );
- }
-}
diff --git a/app/Model/Subtask.php b/app/Model/Subtask.php
index d8a44aff..24508c91 100644
--- a/app/Model/Subtask.php
+++ b/app/Model/Subtask.php
@@ -49,6 +49,7 @@ class Subtask extends Base
*/
const EVENT_UPDATE = 'subtask.update';
const EVENT_CREATE = 'subtask.create';
+ const EVENT_DELETE = 'subtask.delete';
/**
* Get available status
@@ -174,6 +175,23 @@ class Subtask extends Base
}
/**
+ * Prepare data before insert
+ *
+ * @access public
+ * @param array $values Form values
+ */
+ public function prepareCreation(array &$values)
+ {
+ $this->prepare($values);
+
+ $values['position'] = $this->getLastPosition($values['task_id']) + 1;
+ $values['status'] = isset($values['status']) ? $values['status'] : self::STATUS_TODO;
+ $values['time_estimated'] = isset($values['time_estimated']) ? $values['time_estimated'] : 0;
+ $values['time_spent'] = isset($values['time_spent']) ? $values['time_spent'] : 0;
+ $values['user_id'] = isset($values['user_id']) ? $values['user_id'] : 0;
+ }
+
+ /**
* Get the position of the last column for a given project
*
* @access public
@@ -198,9 +216,7 @@ class Subtask extends Base
*/
public function create(array $values)
{
- $this->prepare($values);
- $values['position'] = $this->getLastPosition($values['task_id']) + 1;
-
+ $this->prepareCreation($values);
$subtask_id = $this->persist(self::TABLE, $values);
if ($subtask_id) {
@@ -223,14 +239,13 @@ class Subtask extends Base
public function update(array $values)
{
$this->prepare($values);
+ $subtask = $this->getById($values['id']);
$result = $this->db->table(self::TABLE)->eq('id', $values['id'])->save($values);
if ($result) {
-
- $this->container['dispatcher']->dispatch(
- self::EVENT_UPDATE,
- new SubtaskEvent($values)
- );
+ $event = $subtask;
+ $event['changes'] = array_diff_assoc($values, $subtask);
+ $this->container['dispatcher']->dispatch(self::EVENT_UPDATE, new SubtaskEvent($event));
}
return $result;
@@ -302,7 +317,6 @@ class Subtask extends Base
$positions = array_flip($subtasks);
if (isset($subtasks[$subtask_id]) && $subtasks[$subtask_id] < count($subtasks)) {
-
$position = ++$subtasks[$subtask_id];
$subtasks[$positions[$position]]--;
@@ -402,7 +416,14 @@ class Subtask extends Base
*/
public function remove($subtask_id)
{
- return $this->db->table(self::TABLE)->eq('id', $subtask_id)->remove();
+ $subtask = $this->getById($subtask_id);
+ $result = $this->db->table(self::TABLE)->eq('id', $subtask_id)->remove();
+
+ if ($result) {
+ $this->container['dispatcher']->dispatch(self::EVENT_DELETE, new SubtaskEvent($subtask));
+ }
+
+ return $result;
}
/**
diff --git a/app/Model/SubtaskTimeTracking.php b/app/Model/SubtaskTimeTracking.php
index 997031e8..56998769 100644
--- a/app/Model/SubtaskTimeTracking.php
+++ b/app/Model/SubtaskTimeTracking.php
@@ -339,20 +339,7 @@ class SubtaskTimeTracking extends Base
*/
public function updateTaskTimeTracking($task_id)
{
- $result = $this->calculateSubtaskTime($task_id);
- $values = array();
-
- if ($result['total_spent'] > 0) {
- $values['time_spent'] = $result['total_spent'];
- }
-
- if ($result['total_estimated'] > 0) {
- $values['time_estimated'] = $result['total_estimated'];
- }
-
- if (empty($values)) {
- return true;
- }
+ $values = $this->calculateSubtaskTime($task_id);
return $this->db
->table(Task::TABLE)
@@ -373,8 +360,8 @@ class SubtaskTimeTracking extends Base
->table(Subtask::TABLE)
->eq('task_id', $task_id)
->columns(
- 'SUM(time_spent) AS total_spent',
- 'SUM(time_estimated) AS total_estimated'
+ 'SUM(time_spent) AS time_spent',
+ 'SUM(time_estimated) AS time_estimated'
)
->findOne();
}
diff --git a/app/Model/Swimlane.php b/app/Model/Swimlane.php
index 3b78a406..06e879a4 100644
--- a/app/Model/Swimlane.php
+++ b/app/Model/Swimlane.php
@@ -160,7 +160,7 @@ class Swimlane extends Base
public function getSwimlanes($project_id)
{
$swimlanes = $this->db->table(self::TABLE)
- ->columns('id', 'name')
+ ->columns('id', 'name', 'description')
->eq('project_id', $project_id)
->eq('is_active', self::ACTIVE)
->orderBy('position', 'asc')
@@ -216,32 +216,30 @@ class Swimlane extends Base
* Add a new swimlane
*
* @access public
- * @param integer $project_id
- * @param string $name
+ * @param array $values Form values
* @return integer|boolean
*/
- public function create($project_id, $name)
+ public function create($values)
{
- return $this->persist(self::TABLE, array(
- 'project_id' => $project_id,
- 'name' => $name,
- 'position' => $this->getLastPosition($project_id),
- ));
+ if (! $this->project->exists($values['project_id'])) {
+ return 0;
+ }
+ $values['position'] = $this->getLastPosition($values['project_id']);
+ return $this->persist(self::TABLE, $values);
}
/**
- * Rename a swimlane
+ * Update a swimlane
*
* @access public
- * @param integer $swimlane_id Swimlane id
- * @param string $name Swimlane name
+ * @param array $values Form values
* @return bool
*/
- public function rename($swimlane_id, $name)
+ public function update(array $values)
{
return $this->db->table(self::TABLE)
- ->eq('id', $swimlane_id)
- ->update(array('name' => $name));
+ ->eq('id', $values['id'])
+ ->update($values);
}
/**
diff --git a/app/Schema/Mysql.php b/app/Schema/Mysql.php
index b1ac0ab9..e5dff0d5 100644
--- a/app/Schema/Mysql.php
+++ b/app/Schema/Mysql.php
@@ -6,7 +6,23 @@ use PDO;
use Core\Security;
use Model\Link;
-const VERSION = 85;
+const VERSION = 87;
+
+function version_87($pdo)
+{
+ $pdo->exec("
+ CREATE TABLE plugin_schema_versions (
+ plugin VARCHAR(80) NOT NULL,
+ version INT NOT NULL DEFAULT 0,
+ PRIMARY KEY(plugin)
+ ) ENGINE=InnoDB CHARSET=utf8
+ ");
+}
+
+function version_86($pdo)
+{
+ $pdo->exec("ALTER TABLE swimlanes ADD COLUMN description TEXT");
+}
function version_85($pdo)
{
@@ -306,19 +322,6 @@ function version_53($pdo)
$pdo->exec("ALTER TABLE subtask_time_tracking ADD COLUMN time_spent FLOAT DEFAULT 0");
}
-function version_52($pdo)
-{
- $pdo->exec('CREATE TABLE budget_lines (
- `id` INT NOT NULL AUTO_INCREMENT,
- `project_id` INT NOT NULL,
- `amount` FLOAT NOT NULL,
- `date` VARCHAR(10) NOT NULL,
- `comment` TEXT,
- FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE,
- PRIMARY KEY(id)
- ) ENGINE=InnoDB CHARSET=utf8');
-}
-
function version_51($pdo)
{
$pdo->exec('CREATE TABLE timetable_day (
@@ -365,19 +368,6 @@ function version_51($pdo)
) ENGINE=InnoDB CHARSET=utf8');
}
-function version_50($pdo)
-{
- $pdo->exec("CREATE TABLE hourly_rates (
- id INT NOT NULL AUTO_INCREMENT,
- user_id INT NOT NULL,
- rate FLOAT DEFAULT 0,
- date_effective INTEGER NOT NULL,
- currency CHAR(3) NOT NULL,
- FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
- PRIMARY KEY(id)
- ) ENGINE=InnoDB CHARSET=utf8");
-}
-
function version_49($pdo)
{
$pdo->exec('ALTER TABLE subtasks ADD COLUMN position INTEGER DEFAULT 1');
diff --git a/app/Schema/Postgres.php b/app/Schema/Postgres.php
index 9477b416..e7422e8c 100644
--- a/app/Schema/Postgres.php
+++ b/app/Schema/Postgres.php
@@ -6,7 +6,22 @@ use PDO;
use Core\Security;
use Model\Link;
-const VERSION = 65;
+const VERSION = 67;
+
+function version_67($pdo)
+{
+ $pdo->exec("
+ CREATE TABLE plugin_schema_versions (
+ plugin VARCHAR(80) NOT NULL PRIMARY KEY,
+ version INTEGER NOT NULL DEFAULT 0
+ )
+ ");
+}
+
+function version_66($pdo)
+{
+ $pdo->exec("ALTER TABLE swimlanes ADD COLUMN description TEXT");
+}
function version_65($pdo)
{
@@ -300,18 +315,6 @@ function version_34($pdo)
$pdo->exec("ALTER TABLE subtask_time_tracking ADD COLUMN time_spent REAL DEFAULT 0");
}
-function version_33($pdo)
-{
- $pdo->exec('CREATE TABLE budget_lines (
- "id" SERIAL PRIMARY KEY,
- "project_id" INTEGER NOT NULL,
- "amount" REAL NOT NULL,
- "date" VARCHAR(10) NOT NULL,
- "comment" TEXT,
- FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE
- )');
-}
-
function version_32($pdo)
{
$pdo->exec('CREATE TABLE timetable_day (
@@ -354,18 +357,6 @@ function version_32($pdo)
)');
}
-function version_31($pdo)
-{
- $pdo->exec("CREATE TABLE hourly_rates (
- id SERIAL PRIMARY KEY,
- user_id INTEGER NOT NULL,
- rate REAL DEFAULT 0,
- date_effective INTEGER NOT NULL,
- currency CHAR(3) NOT NULL,
- FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
- )");
-}
-
function version_30($pdo)
{
$pdo->exec('ALTER TABLE subtasks ADD COLUMN position INTEGER DEFAULT 1');
diff --git a/app/Schema/Sqlite.php b/app/Schema/Sqlite.php
index b4e4b948..b76e902a 100644
--- a/app/Schema/Sqlite.php
+++ b/app/Schema/Sqlite.php
@@ -6,7 +6,22 @@ use Core\Security;
use PDO;
use Model\Link;
-const VERSION = 81;
+const VERSION = 83;
+
+function version_83($pdo)
+{
+ $pdo->exec("
+ CREATE TABLE plugin_schema_versions (
+ plugin TEXT NOT NULL PRIMARY KEY,
+ version INTEGER NOT NULL DEFAULT 0
+ )
+ ");
+}
+
+function version_82($pdo)
+{
+ $pdo->exec("ALTER TABLE swimlanes ADD COLUMN description TEXT");
+}
function version_81($pdo)
{
@@ -277,18 +292,6 @@ function version_52($pdo)
$pdo->exec("ALTER TABLE subtask_time_tracking ADD COLUMN time_spent REAL DEFAULT 0");
}
-function version_51($pdo)
-{
- $pdo->exec('CREATE TABLE budget_lines (
- "id" INTEGER PRIMARY KEY,
- "project_id" INTEGER NOT NULL,
- "amount" REAL NOT NULL,
- "date" TEXT NOT NULL,
- "comment" TEXT,
- FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE
- )');
-}
-
function version_50($pdo)
{
$pdo->exec('CREATE TABLE timetable_day (
@@ -331,18 +334,6 @@ function version_50($pdo)
)');
}
-function version_49($pdo)
-{
- $pdo->exec("CREATE TABLE hourly_rates (
- id INTEGER PRIMARY KEY,
- user_id INTEGER NOT NULL,
- rate REAL DEFAULT 0,
- date_effective INTEGER NOT NULL,
- currency TEXT NOT NULL,
- FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
- )");
-}
-
function version_48($pdo)
{
$pdo->exec('ALTER TABLE subtasks ADD COLUMN position INTEGER DEFAULT 1');
diff --git a/app/ServiceProvider/ClassProvider.php b/app/ServiceProvider/ClassProvider.php
index ef7aa575..a5677948 100644
--- a/app/ServiceProvider/ClassProvider.php
+++ b/app/ServiceProvider/ClassProvider.php
@@ -2,13 +2,16 @@
namespace ServiceProvider;
+use Core\ObjectStorage\FileStorage;
use Core\Paginator;
use Core\OAuth2;
+use Core\Tool;
use Model\Config;
use Model\Project;
use Model\Webhook;
use Pimple\Container;
use Pimple\ServiceProviderInterface;
+use League\HTMLToMarkdown\HtmlConverter;
class ClassProvider implements ServiceProviderInterface
{
@@ -18,7 +21,6 @@ class ClassProvider implements ServiceProviderInterface
'Action',
'Authentication',
'Board',
- 'Budget',
'Category',
'Color',
'Comment',
@@ -26,7 +28,6 @@ class ClassProvider implements ServiceProviderInterface
'Currency',
'DateParser',
'File',
- 'HourlyRate',
'LastLogin',
'Link',
'Notification',
@@ -93,17 +94,7 @@ class ClassProvider implements ServiceProviderInterface
public function register(Container $container)
{
- foreach ($this->classes as $namespace => $classes) {
-
- foreach ($classes as $name) {
-
- $class = '\\'.$namespace.'\\'.$name;
-
- $container[lcfirst($name)] = function ($c) use ($class) {
- return new $class($c);
- };
- }
- }
+ Tool::buildDIC($container, $this->classes);
$container['paginator'] = $container->factory(function ($c) {
return new Paginator($c);
@@ -112,5 +103,13 @@ class ClassProvider implements ServiceProviderInterface
$container['oauth'] = $container->factory(function ($c) {
return new OAuth2($c);
});
+
+ $container['htmlConverter'] = function($c) {
+ return new HtmlConverter(array('strip_tags' => true));
+ };
+
+ $container['objectStorage'] = function($c) {
+ return new FileStorage(FILES_DIR);
+ };
}
}
diff --git a/app/Subscriber/SubtaskTimeTrackingSubscriber.php b/app/Subscriber/SubtaskTimeTrackingSubscriber.php
index e45b2c93..2d3b5f99 100644
--- a/app/Subscriber/SubtaskTimeTrackingSubscriber.php
+++ b/app/Subscriber/SubtaskTimeTrackingSubscriber.php
@@ -12,6 +12,7 @@ class SubtaskTimeTrackingSubscriber extends \Core\Base implements EventSubscribe
{
return array(
Subtask::EVENT_CREATE => array('updateTaskTime', 0),
+ Subtask::EVENT_DELETE => array('updateTaskTime', 0),
Subtask::EVENT_UPDATE => array(
array('logStartEnd', 10),
array('updateTaskTime', 0),
diff --git a/app/Template/app/sidebar.php b/app/Template/app/sidebar.php
index 2d966009..f4a455f8 100644
--- a/app/Template/app/sidebar.php
+++ b/app/Template/app/sidebar.php
@@ -19,6 +19,7 @@
<li <?= $this->app->getRouterAction() === 'activity' ? 'class="active"' : '' ?>>
<?= $this->url->link(t('My activity stream'), 'app', 'activity', array('user_id' => $user['id'])) ?>
</li>
+ <?= $this->hook->render('dashboard:sidebar') ?>
</ul>
<div class="sidebar-collapse"><a href="#" title="<?= t('Hide sidebar') ?>"><i class="fa fa-chevron-left"></i></a></div>
<div class="sidebar-expand" style="display: none"><a href="#" title="<?= t('Expand sidebar') ?>"><i class="fa fa-chevron-right"></i></a></div>
diff --git a/app/Template/board/table_swimlane.php b/app/Template/board/table_swimlane.php
index be401633..1caa920d 100644
--- a/app/Template/board/table_swimlane.php
+++ b/app/Template/board/table_swimlane.php
@@ -7,6 +7,20 @@
<i class="fa fa-minus-circle hide-icon-swimlane-<?= $swimlane['id'] ?>"></i>
<i class="fa fa-plus-circle show-icon-swimlane-<?= $swimlane['id'] ?>" style="display: none"></i>
</a>
+
+ <?php if (! empty($swimlane['description'])): ?>
+ <span
+ title="<?= t('Description') ?>"
+ class="tooltip"
+ data-href="<?= $this->url->href('board', 'swimlane', array('swimlane_id' => $swimlane['id'], 'project_id' => $project['id'])) ?>">
+ <i class="fa fa-info-circle"></i>
+ </span>
+ <?php endif ?>
+
+ <span title="<?= t('Task count') ?>" class="board-column-header-task-count swimlane-task-count-<?= $swimlane['id'] ?>">
+ (<?= $swimlane['nb_tasks'] ?>)
+ </span>
+
<span class="board-swimlane-toggle-title show-icon-swimlane-<?= $swimlane['id'] ?>"><?= $this->e($swimlane['name']) ?></span>
<?php endif ?>
</th>
@@ -62,10 +76,6 @@
<?php if (! $hide_swimlane): ?>
<th class="board-swimlane-title">
<?= $this->e($swimlane['name']) ?>
-
- <div title="<?= t('Task count') ?>" class="board-column-header-task-count">
- (<span><?= $swimlane['nb_tasks'] ?></span>)
- </div>
</th>
<?php endif ?>
diff --git a/app/Template/budget/breakdown.php b/app/Template/budget/breakdown.php
deleted file mode 100644
index 92561188..00000000
--- a/app/Template/budget/breakdown.php
+++ /dev/null
@@ -1,30 +0,0 @@
-<div class="page-header">
- <h2><?= t('Cost breakdown') ?></h2>
-</div>
-
-<?php if ($paginator->isEmpty()): ?>
- <p class="alert"><?= t('There is nothing to show.') ?></p>
-<?php else: ?>
- <table class="table-fixed">
- <tr>
- <th class="column-20"><?= $paginator->order(t('Task'), 'task_title') ?></th>
- <th class="column-25"><?= $paginator->order(t('Subtask'), 'subtask_title') ?></th>
- <th class="column-20"><?= $paginator->order(t('User'), 'username') ?></th>
- <th class="column-10"><?= t('Cost') ?></th>
- <th class="column-10"><?= $paginator->order(t('Time spent'), \Model\SubtaskTimeTracking::TABLE.'.time_spent') ?></th>
- <th class="column-15"><?= $paginator->order(t('Date'), 'start') ?></th>
- </tr>
- <?php foreach ($paginator->getCollection() as $record): ?>
- <tr>
- <td><?= $this->url->link($this->e($record['task_title']), 'task', 'show', array('project_id' => $project['id'], 'task_id' => $record['task_id'])) ?></td>
- <td><?= $this->url->link($this->e($record['subtask_title']), 'task', 'show', array('project_id' => $project['id'], 'task_id' => $record['task_id'])) ?></td>
- <td><?= $this->url->link($this->e($record['name'] ?: $record['username']), 'user', 'show', array('user_id' => $record['user_id'])) ?></td>
- <td><?= n($record['cost']) ?></td>
- <td><?= n($record['time_spent']).' '.t('hours') ?></td>
- <td><?= dt('%B %e, %Y', $record['start']) ?></td>
- </tr>
- <?php endforeach ?>
- </table>
-
- <?= $paginator ?>
-<?php endif ?> \ No newline at end of file
diff --git a/app/Template/budget/create.php b/app/Template/budget/create.php
deleted file mode 100644
index a563796d..00000000
--- a/app/Template/budget/create.php
+++ /dev/null
@@ -1,47 +0,0 @@
-<div class="page-header">
- <h2><?= t('Budget lines') ?></h2>
-</div>
-
-<?php if (! empty($lines)): ?>
-<table class="table-fixed table-stripped">
- <tr>
- <th class="column-20"><?= t('Budget line') ?></th>
- <th class="column-20"><?= t('Date') ?></th>
- <th><?= t('Comment') ?></th>
- <th><?= t('Action') ?></th>
- </tr>
- <?php foreach ($lines as $line): ?>
- <tr>
- <td><?= n($line['amount']) ?></td>
- <td><?= dt('%B %e, %Y', strtotime($line['date'])) ?></td>
- <td><?= $this->e($line['comment']) ?></td>
- <td>
- <?= $this->url->link(t('Remove'), 'budget', 'confirm', array('project_id' => $project['id'], 'budget_id' => $line['id'])) ?>
- </td>
- </tr>
- <?php endforeach ?>
-</table>
-
-<h3><?= t('New budget line') ?></h3>
-<?php endif ?>
-
-<form method="post" action="<?= $this->url->href('budget', 'save', array('project_id' => $project['id'])) ?>" autocomplete="off">
-
- <?= $this->form->csrf() ?>
-
- <?= $this->form->hidden('id', $values) ?>
- <?= $this->form->hidden('project_id', $values) ?>
-
- <?= $this->form->label(t('Amount'), 'amount') ?>
- <?= $this->form->text('amount', $values, $errors, array('required'), 'form-numeric') ?>
-
- <?= $this->form->label(t('Date'), 'date') ?>
- <?= $this->form->text('date', $values, $errors, array('required'), 'form-date') ?>
-
- <?= $this->form->label(t('Comment'), 'comment') ?>
- <?= $this->form->text('comment', $values, $errors) ?>
-
- <div class="form-actions">
- <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
- </div>
-</form> \ No newline at end of file
diff --git a/app/Template/budget/index.php b/app/Template/budget/index.php
deleted file mode 100644
index 51ef3d87..00000000
--- a/app/Template/budget/index.php
+++ /dev/null
@@ -1,34 +0,0 @@
-<div class="page-header">
- <h2><?= t('Budget overview') ?></h2>
-</div>
-
-<?php if (! empty($daily_budget)): ?>
-<div id="budget-chart">
- <div id="chart"
- data-date-format="<?= e('%%Y-%%m-%%d') ?>"
- data-metrics='<?= json_encode($daily_budget, JSON_HEX_APOS) ?>'
- data-labels='<?= json_encode(array('in' => t('Budget line'), 'out' => t('Expenses'), 'left' => t('Remaining'), 'value' => t('Amount'), 'date' => t('Date'), 'type' => t('Type')), JSON_HEX_APOS) ?>'></div>
-</div>
-<hr/>
-<table class="table-fixed table-stripped">
- <tr>
- <th><?= t('Date') ?></td>
- <th><?= t('Budget line') ?></td>
- <th><?= t('Expenses') ?></td>
- <th><?= t('Remaining') ?></td>
- </tr>
- <?php foreach ($daily_budget as $line): ?>
- <tr>
- <td><?= dt('%B %e, %Y', strtotime($line['date'])) ?></td>
- <td><?= n($line['in']) ?></td>
- <td><?= n($line['out']) ?></td>
- <td><?= n($line['left']) ?></td>
- </tr>
- <?php endforeach ?>
-</table>
-<?php else: ?>
- <p class="alert"><?= t('There is not enough data to show something.') ?></p>
-<?php endif ?>
-
-<?= $this->asset->js('assets/js/vendor/d3.v3.min.js') ?>
-<?= $this->asset->js('assets/js/vendor/c3.min.js') ?> \ No newline at end of file
diff --git a/app/Template/budget/remove.php b/app/Template/budget/remove.php
deleted file mode 100644
index a5b906a1..00000000
--- a/app/Template/budget/remove.php
+++ /dev/null
@@ -1,13 +0,0 @@
-<div class="page-header">
- <h2><?= t('Remove budget line') ?></h2>
-</div>
-
-<div class="confirm">
- <p class="alert alert-info"><?= t('Do you really want to remove this budget line?') ?></p>
-
- <div class="form-actions">
- <?= $this->url->link(t('Yes'), 'budget', 'remove', array('project_id' => $project['id'], 'budget_id' => $budget_id), true, 'btn btn-red') ?>
- <?= t('or') ?>
- <?= $this->url->link(t('cancel'), 'budget', 'create', array('project_id' => $project['id'])) ?>
- </div>
-</div> \ No newline at end of file
diff --git a/app/Template/budget/sidebar.php b/app/Template/budget/sidebar.php
deleted file mode 100644
index 8477c052..00000000
--- a/app/Template/budget/sidebar.php
+++ /dev/null
@@ -1,16 +0,0 @@
-<div class="sidebar">
- <h2><?= t('Budget') ?></h2>
- <ul>
- <li <?= $this->app->getRouterAction() === 'index' ? 'class="active"' : '' ?>>
- <?= $this->url->link(t('Budget overview'), 'budget', 'index', array('project_id' => $project['id'])) ?>
- </li>
- <li <?= $this->app->getRouterAction() === 'create' ? 'class="active"' : '' ?>>
- <?= $this->url->link(t('Budget lines'), 'budget', 'create', array('project_id' => $project['id'])) ?>
- </li>
- <li <?= $this->app->getRouterAction() === 'breakdown' ? 'class="active"' : '' ?>>
- <?= $this->url->link(t('Cost breakdown'), 'budget', 'breakdown', array('project_id' => $project['id'])) ?>
- </li>
- </ul>
- <div class="sidebar-collapse"><a href="#" title="<?= t('Hide sidebar') ?>"><i class="fa fa-chevron-left"></i></a></div>
- <div class="sidebar-expand" style="display: none"><a href="#" title="<?= t('Expand sidebar') ?>"><i class="fa fa-chevron-right"></i></a></div>
-</div> \ No newline at end of file
diff --git a/app/Template/config/sidebar.php b/app/Template/config/sidebar.php
index 3617979a..083da283 100644
--- a/app/Template/config/sidebar.php
+++ b/app/Template/config/sidebar.php
@@ -34,6 +34,7 @@
<li>
<?= $this->url->link(t('Documentation'), 'doc', 'show') ?>
</li>
+ <?= $this->hook->render('config:sidebar') ?>
</ul>
<div class="sidebar-collapse"><a href="#" title="<?= t('Hide sidebar') ?>"><i class="fa fa-chevron-left"></i></a></div>
<div class="sidebar-expand" style="display: none"><a href="#" title="<?= t('Expand sidebar') ?>"><i class="fa fa-chevron-right"></i></a></div>
diff --git a/app/Template/currency/index.php b/app/Template/currency/index.php
index f72c5700..1c78c47a 100644
--- a/app/Template/currency/index.php
+++ b/app/Template/currency/index.php
@@ -52,5 +52,3 @@
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
</div>
</form>
-
-<p class="alert alert-info"><?= t('Currency rates are used to calculate project budget.') ?></p>
diff --git a/app/Template/export/sidebar.php b/app/Template/export/sidebar.php
index f204d29d..7e39a5af 100644
--- a/app/Template/export/sidebar.php
+++ b/app/Template/export/sidebar.php
@@ -13,6 +13,7 @@
<li <?= $this->app->getRouterAction() === 'summary' ? 'class="active"' : '' ?>>
<?= $this->url->link(t('Daily project summary'), 'export', 'summary', array('project_id' => $project['id'])) ?>
</li>
+ <?= $this->hook->render('export:sidebar') ?>
</ul>
<div class="sidebar-collapse"><a href="#" title="<?= t('Hide sidebar') ?>"><i class="fa fa-chevron-left"></i></a></div>
<div class="sidebar-expand" style="display: none"><a href="#" title="<?= t('Expand sidebar') ?>"><i class="fa fa-chevron-right"></i></a></div>
diff --git a/app/Template/file/show.php b/app/Template/file/show.php
index b1a0a813..a390c9fb 100644
--- a/app/Template/file/show.php
+++ b/app/Template/file/show.php
@@ -11,7 +11,7 @@
<li>
<?php if (function_exists('imagecreatetruecolor')): ?>
<div class="img_container">
- <img src="<?= $this->url->href('file', 'thumbnail', array('width' => 250, 'height' => 100, 'file_id' => $file['id'], 'project_id' => $task['project_id'], 'task_id' => $file['task_id'])) ?>" alt="<?= $this->e($file['name']) ?>"/>
+ <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>
<?php endif ?>
<p>
diff --git a/app/Template/gantt/task_creation.php b/app/Template/gantt/task_creation.php
index d0d14c1e..7997e231 100644
--- a/app/Template/gantt/task_creation.php
+++ b/app/Template/gantt/task_creation.php
@@ -12,7 +12,6 @@
<?= $this->form->text('title', $values, $errors, array('autofocus', 'required', 'maxlength="200"', 'tabindex="1"'), 'form-input-large') ?>
<?= $this->form->label(t('Description'), 'description') ?>
-
<div class="form-tabs">
<div class="write-area">
<?= $this->form->textarea('description', $values, $errors, array('placeholder="'.t('Leave a description').'"', 'tabindex="2"')) ?>
@@ -29,6 +28,8 @@
</li>
</ul>
</div>
+
+ <?= $this->render('task/color_picker', array('colors_list' => $colors_list, 'values' => $values)) ?>
</div>
<div class="form-column">
@@ -43,17 +44,14 @@
<?= $this->form->select('swimlane_id', $swimlanes_list, $values, $errors, array('tabindex="5"')) ?><br/>
<?php endif ?>
- <?= $this->form->label(t('Color'), 'color_id') ?>
- <?= $this->form->select('color_id', $colors_list, $values, $errors, array('tabindex="7"')) ?><br/>
-
<?= $this->form->label(t('Complexity'), 'score') ?>
- <?= $this->form->number('score', $values, $errors, array('tabindex="8"')) ?><br/>
+ <?= $this->form->number('score', $values, $errors, array('tabindex="6"')) ?><br/>
<?= $this->form->label(t('Start Date'), 'date_started') ?>
- <?= $this->form->text('date_started', $values, $errors, array('placeholder="'.$this->text->in($date_format, $date_formats).'"', 'tabindex="9"'), 'form-date') ?>
+ <?= $this->form->text('date_started', $values, $errors, array('placeholder="'.$this->text->in($date_format, $date_formats).'"', 'tabindex="7"'), 'form-date') ?>
<?= $this->form->label(t('Due Date'), 'date_due') ?>
- <?= $this->form->text('date_due', $values, $errors, array('placeholder="'.$this->text->in($date_format, $date_formats).'"', 'tabindex="10"'), 'form-date') ?><br/>
+ <?= $this->form->text('date_due', $values, $errors, array('placeholder="'.$this->text->in($date_format, $date_formats).'"', 'tabindex="8"'), 'form-date') ?><br/>
<div class="form-help"><?= t('Others formats accepted: %s and %s', date('Y-m-d'), date('Y_m_d')) ?></div>
</div>
diff --git a/app/Template/header.php b/app/Template/header.php
new file mode 100644
index 00000000..0bcfdbbc
--- /dev/null
+++ b/app/Template/header.php
@@ -0,0 +1,33 @@
+<header>
+ <nav>
+ <h1><?= $this->url->link('K<span>B</span>', 'app', 'index', array(), false, 'logo', t('Dashboard')).' '.$this->e($title) ?>
+ <?php if (! empty($description)): ?>
+ <span class="tooltip" title='<?= $this->e($this->text->markdown($description)) ?>'>
+ <i class="fa fa-info-circle"></i>
+ </span>
+ <?php endif ?>
+ </h1>
+ <ul>
+ <?php if (isset($board_selector) && ! empty($board_selector)): ?>
+ <li>
+ <select id="board-selector"
+ class="chosen-select select-auto-redirect"
+ tabindex="-1"
+ data-notfound="<?= t('No results match:') ?>"
+ data-placeholder="<?= t('Display another project') ?>"
+ data-redirect-regex="PROJECT_ID"
+ data-redirect-url="<?= $this->url->href('board', 'show', array('project_id' => 'PROJECT_ID')) ?>">
+ <option value=""></option>
+ <?php foreach($board_selector as $board_id => $board_name): ?>
+ <option value="<?= $board_id ?>"><?= $this->e($board_name) ?></option>
+ <?php endforeach ?>
+ </select>
+ </li>
+ <?php endif ?>
+ <li>
+ <?= $this->url->link(t('Logout'), 'auth', 'logout') ?>
+ <span class="username hide-tablet">(<?= $this->user->getProfileLink() ?>)</span>
+ </li>
+ </ul>
+ </nav>
+</header> \ No newline at end of file
diff --git a/app/Template/hourlyrate/index.php b/app/Template/hourlyrate/index.php
deleted file mode 100644
index af305d07..00000000
--- a/app/Template/hourlyrate/index.php
+++ /dev/null
@@ -1,46 +0,0 @@
-<div class="page-header">
- <h2><?= t('Hourly rates') ?></h2>
-</div>
-
-<?php if (! empty($rates)): ?>
-
-<table>
- <tr>
- <th><?= t('Hourly rate') ?></th>
- <th><?= t('Currency') ?></th>
- <th><?= t('Effective date') ?></th>
- <th><?= t('Action') ?></th>
- </tr>
- <?php foreach ($rates as $rate): ?>
- <tr>
- <td><?= n($rate['rate']) ?></td>
- <td><?= $rate['currency'] ?></td>
- <td><?= dt('%b %e, %Y', $rate['date_effective']) ?></td>
- <td>
- <?= $this->url->link(t('Remove'), 'hourlyrate', 'confirm', array('user_id' => $user['id'], 'rate_id' => $rate['id'])) ?>
- </td>
- </tr>
- <?php endforeach ?>
-</table>
-
-<h3><?= t('Add new rate') ?></h3>
-<?php endif ?>
-
-<form method="post" action="<?= $this->url->href('hourlyrate', 'save', array('user_id' => $user['id'])) ?>" autocomplete="off">
-
- <?= $this->form->hidden('user_id', $values) ?>
- <?= $this->form->csrf() ?>
-
- <?= $this->form->label(t('Hourly rate'), 'rate') ?>
- <?= $this->form->text('rate', $values, $errors, array('required'), 'form-numeric') ?>
-
- <?= $this->form->label(t('Currency'), 'currency') ?>
- <?= $this->form->select('currency', $currencies_list, $values, $errors, array('required')) ?>
-
- <?= $this->form->label(t('Effective date'), 'date_effective') ?>
- <?= $this->form->text('date_effective', $values, $errors, array('required'), 'form-date') ?>
-
- <div class="form-actions">
- <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
- </div>
-</form>
diff --git a/app/Template/hourlyrate/remove.php b/app/Template/hourlyrate/remove.php
deleted file mode 100644
index 121436e4..00000000
--- a/app/Template/hourlyrate/remove.php
+++ /dev/null
@@ -1,13 +0,0 @@
-<div class="page-header">
- <h2><?= t('Remove hourly rate') ?></h2>
-</div>
-
-<div class="confirm">
- <p class="alert alert-info"><?= t('Do you really want to remove this hourly rate?') ?></p>
-
- <div class="form-actions">
- <?= $this->url->link(t('Yes'), 'hourlyrate', 'remove', array('user_id' => $user['id'], 'rate_id' => $rate_id), true, 'btn btn-red') ?>
- <?= t('or') ?>
- <?= $this->url->link(t('cancel'), 'hourlyrate', 'index', array('user_id' => $user['id'])) ?>
- </div>
-</div> \ No newline at end of file
diff --git a/app/Template/layout.php b/app/Template/layout.php
index 3e883fbf..934fb62c 100644
--- a/app/Template/layout.php
+++ b/app/Template/layout.php
@@ -28,52 +28,29 @@
<link rel="apple-touch-icon" sizes="144x144" href="<?= $this->url->dir() ?>assets/img/touch-icon-ipad-retina.png">
<title><?= isset($title) ? $this->e($title) : 'Kanboard' ?></title>
+
+ <?= $this->hook->render('layout:head') ?>
</head>
<body data-status-url="<?= $this->url->href('app', 'status') ?>"
data-login-url="<?= $this->url->href('auth', 'login') ?>"
+ data-markdown-preview-url="<?= $this->url->href('app', 'preview') ?>"
data-timezone="<?= $this->app->getTimezone() ?>"
data-js-lang="<?= $this->app->jsLang() ?>">
<?php if (isset($no_layout) && $no_layout): ?>
<?= $content_for_layout ?>
<?php else: ?>
- <header>
- <nav>
- <h1><?= $this->url->link('K<span>B</span>', 'app', 'index', array(), false, 'logo', t('Dashboard')).' '.$this->e($title) ?>
- <?php if (! empty($description)): ?>
- <span class="tooltip" title='<?= $this->e($this->text->markdown($description)) ?>'>
- <i class="fa fa-info-circle"></i>
- </span>
- <?php endif ?>
- </h1>
- <ul>
- <?php if (isset($board_selector) && ! empty($board_selector)): ?>
- <li>
- <select id="board-selector"
- class="chosen-select select-auto-redirect"
- tabindex="-1"
- data-notfound="<?= t('No results match:') ?>"
- data-placeholder="<?= t('Display another project') ?>"
- data-redirect-regex="PROJECT_ID"
- data-redirect-url="<?= $this->url->href('board', 'show', array('project_id' => 'PROJECT_ID')) ?>">
- <option value=""></option>
- <?php foreach($board_selector as $board_id => $board_name): ?>
- <option value="<?= $board_id ?>"><?= $this->e($board_name) ?></option>
- <?php endforeach ?>
- </select>
- </li>
- <?php endif ?>
- <li>
- <?= $this->url->link(t('Logout'), 'auth', 'logout') ?>
- <span class="username hide-tablet">(<?= $this->user->getProfileLink() ?>)</span>
- </li>
- </ul>
- </nav>
- </header>
+ <?= $this->hook->render('layout:top') ?>
+ <?= $this->render('header', array(
+ 'title' => $title,
+ 'description' => isset($description) ? $description : '',
+ 'board_selector' => $board_selector,
+ )) ?>
<section class="page">
<?= $this->app->flashMessage() ?>
<?= $content_for_layout ?>
</section>
+ <?= $this->hook->render('layout:bottom') ?>
<?php endif ?>
</body>
</html>
diff --git a/app/Template/project/dropdown.php b/app/Template/project/dropdown.php
index 0a53cc05..0f1e1f6b 100644
--- a/app/Template/project/dropdown.php
+++ b/app/Template/project/dropdown.php
@@ -9,21 +9,19 @@
</li>
<?php endif ?>
+<?= $this->hook->render('project:dropdown', array('project' => $project)) ?>
+
<?php if ($this->user->isProjectManagementAllowed($project['id'])): ?>
-<li>
- <i class="fa fa-line-chart fa-fw"></i>
- <?= $this->url->link(t('Analytics'), 'analytic', 'tasks', array('project_id' => $project['id'])) ?>
-</li>
-<li>
- <i class="fa fa-pie-chart fa-fw"></i>
- <?= $this->url->link(t('Budget'), 'budget', 'index', array('project_id' => $project['id'])) ?>
-</li>
-<li>
- <i class="fa fa-download fa-fw"></i>
- <?= $this->url->link(t('Exports'), 'export', 'tasks', array('project_id' => $project['id'])) ?>
-</li>
-<li>
- <i class="fa fa-cog fa-fw"></i>
- <?= $this->url->link(t('Settings'), 'project', 'show', array('project_id' => $project['id'])) ?>
-</li>
+ <li>
+ <i class="fa fa-line-chart fa-fw"></i>
+ <?= $this->url->link(t('Analytics'), 'analytic', 'tasks', array('project_id' => $project['id'])) ?>
+ </li>
+ <li>
+ <i class="fa fa-download fa-fw"></i>
+ <?= $this->url->link(t('Exports'), 'export', 'tasks', array('project_id' => $project['id'])) ?>
+ </li>
+ <li>
+ <i class="fa fa-cog fa-fw"></i>
+ <?= $this->url->link(t('Settings'), 'project', 'show', array('project_id' => $project['id'])) ?>
+ </li>
<?php endif ?>
diff --git a/app/Template/project/sidebar.php b/app/Template/project/sidebar.php
index 7b5d976f..84bbb6b1 100644
--- a/app/Template/project/sidebar.php
+++ b/app/Template/project/sidebar.php
@@ -48,6 +48,8 @@
</li>
<?php endif ?>
<?php endif ?>
+
+ <?= $this->hook->render('project:sidebar') ?>
</ul>
<div class="sidebar-collapse"><a href="#" title="<?= t('Hide sidebar') ?>"><i class="fa fa-chevron-left"></i></a></div>
<div class="sidebar-expand" style="display: none"><a href="#" title="<?= t('Expand sidebar') ?>"><i class="fa fa-chevron-right"></i></a></div>
diff --git a/app/Template/project_user/sidebar.php b/app/Template/project_user/sidebar.php
index 8cc3f41b..98219a87 100644
--- a/app/Template/project_user/sidebar.php
+++ b/app/Template/project_user/sidebar.php
@@ -24,5 +24,7 @@
<li <?= $this->app->getRouterAction() === 'closed' ? 'class="active"' : '' ?>>
<?= $this->url->link(t('Closed tasks'), 'projectuser', 'closed', $filter) ?>
</li>
+
+ <?= $this->hook->render('project-user:sidebar') ?>
</ul>
</div> \ No newline at end of file
diff --git a/app/Template/swimlane/edit.php b/app/Template/swimlane/edit.php
index 1788fed2..dfc5cf0b 100644
--- a/app/Template/swimlane/edit.php
+++ b/app/Template/swimlane/edit.php
@@ -12,6 +12,27 @@
<?= $this->form->label(t('Name'), 'name') ?>
<?= $this->form->text('name', $values, $errors, array('autofocus', 'required', 'maxlength="50"')) ?>
+ <?= $this->form->label(t('Description'), 'description') ?>
+
+ <div class="form-tabs">
+
+ <div class="write-area">
+ <?= $this->form->textarea('description', $values, $errors) ?>
+ </div>
+ <div class="preview-area">
+ <div class="markdown"></div>
+ </div>
+ <ul class="form-tabs-nav">
+ <li class="form-tab form-tab-selected">
+ <i class="fa fa-pencil-square-o fa-fw"></i><a id="markdown-write" href="#"><?= t('Write') ?></a>
+ </li>
+ <li class="form-tab">
+ <a id="markdown-preview" href="#"><i class="fa fa-eye fa-fw"></i><?= t('Preview') ?></a>
+ </li>
+ </ul>
+ </div>
+ <div class="form-help"><?= $this->url->doc(t('Write your text in Markdown'), 'syntax-guide') ?></div>
+
<div class="form-actions">
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
<?= t('or') ?>
diff --git a/app/Template/swimlane/index.php b/app/Template/swimlane/index.php
index daee6af5..797d2ca2 100644
--- a/app/Template/swimlane/index.php
+++ b/app/Template/swimlane/index.php
@@ -14,7 +14,27 @@
<?= $this->form->hidden('project_id', $values) ?>
<?= $this->form->label(t('Name'), 'name') ?>
- <?= $this->form->text('name', $values, $errors, array('autofocus', 'required', 'maxlength="50"')) ?>
+ <?= $this->form->text('name', $values, $errors, array('required', 'maxlength="50"')) ?>
+
+ <?= $this->form->label(t('Description'), 'description') ?>
+
+ <div class="form-tabs">
+ <div class="write-area">
+ <?= $this->form->textarea('description', $values, $errors) ?>
+ </div>
+ <div class="preview-area">
+ <div class="markdown"></div>
+ </div>
+ <ul class="form-tabs-nav">
+ <li class="form-tab form-tab-selected">
+ <i class="fa fa-pencil-square-o fa-fw"></i><a id="markdown-write" href="#"><?= t('Write') ?></a>
+ </li>
+ <li class="form-tab">
+ <a id="markdown-preview" href="#"><i class="fa fa-eye fa-fw"></i><?= t('Preview') ?></a>
+ </li>
+ </ul>
+ </div>
+ <div class="form-help"><?= $this->url->doc(t('Write your text in Markdown'), 'syntax-guide') ?></div>
<div class="form-actions">
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
diff --git a/app/Template/swimlane/table.php b/app/Template/swimlane/table.php
index f38572a3..b708e633 100644
--- a/app/Template/swimlane/table.php
+++ b/app/Template/swimlane/table.php
@@ -25,7 +25,7 @@
</li>
<?php endif ?>
<li>
- <?= $this->url->link(t('Rename'), 'swimlane', 'edit', array('project_id' => $project['id'], 'swimlane_id' => $swimlane['id'])) ?>
+ <?= $this->url->link(t('Edit'), 'swimlane', 'edit', array('project_id' => $project['id'], 'swimlane_id' => $swimlane['id'])) ?>
</li>
<li>
<?php if ($swimlane['is_active']): ?>
diff --git a/app/Template/task/color_picker.php b/app/Template/task/color_picker.php
new file mode 100644
index 00000000..a849b9ce
--- /dev/null
+++ b/app/Template/task/color_picker.php
@@ -0,0 +1,11 @@
+<div class="color-picker">
+<?php foreach ($colors_list as $color_id => $color_name): ?>
+ <div
+ data-color-id="<?= $color_id ?>"
+ class="color-square color-<?= $color_id ?> <?= isset($values['color_id']) && $values['color_id'] === $color_id ? 'color-square-selected' : '' ?>"
+ title="<?= $this->e($color_name) ?>">
+ </div>
+<?php endforeach ?>
+</div>
+
+<?= $this->form->hidden('color_id', $values) ?> \ No newline at end of file
diff --git a/app/Template/task/sidebar.php b/app/Template/task/sidebar.php
index 1f06ab8c..cf0e9f76 100644
--- a/app/Template/task/sidebar.php
+++ b/app/Template/task/sidebar.php
@@ -18,6 +18,8 @@
<?= $this->url->link(t('Time tracking'), 'task', 'timetracking', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
</li>
<?php endif ?>
+
+ <?= $this->hook->render('task:sidebar:information') ?>
</ul>
<h2><?= t('Actions') ?></h2>
<ul>
@@ -66,6 +68,8 @@
<?= $this->url->link(t('Remove'), 'task', 'remove', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
</li>
<?php endif ?>
+
+ <?= $this->hook->render('task:sidebar:actions') ?>
</ul>
<div class="sidebar-collapse"><a href="#" title="<?= t('Hide sidebar') ?>"><i class="fa fa-chevron-left"></i></a></div>
<div class="sidebar-expand" style="display: none"><a href="#" title="<?= t('Expand sidebar') ?>"><i class="fa fa-chevron-right"></i></a></div>
diff --git a/app/Template/task_creation/form.php b/app/Template/task_creation/form.php
index 8a29896e..325ca1c8 100644
--- a/app/Template/task_creation/form.php
+++ b/app/Template/task_creation/form.php
@@ -10,8 +10,7 @@
</div>
<?php endif ?>
-<section id="task-section">
-<form method="post" action="<?= $this->url->href('taskcreation', 'save', array('project_id' => $values['project_id'])) ?>" autocomplete="off">
+<form id="task-form" method="post" action="<?= $this->url->href('taskcreation', 'save', array('project_id' => $values['project_id'])) ?>" autocomplete="off">
<?= $this->form->csrf() ?>
@@ -38,7 +37,7 @@
</ul>
</div>
- <div class="form-help"><?= $this->url->doc(t('Write your text in Markdown'), 'syntax-guide') ?></div>
+ <?= $this->render('task/color_picker', array('colors_list' => $colors_list, 'values' => $values)) ?>
<?php if (! isset($duplicate)): ?>
<?= $this->form->checkbox('another_task', t('Create another task'), 1, isset($values['another_task']) && $values['another_task'] == 1) ?>
@@ -62,9 +61,6 @@
<?= $this->form->label(t('Column'), 'column_id') ?>
<?= $this->form->select('column_id', $columns_list, $values, $errors, array('tabindex="6"')) ?><br/>
- <?= $this->form->label(t('Color'), 'color_id') ?>
- <?= $this->form->select('color_id', $colors_list, $values, $errors, array('tabindex="7"')) ?><br/>
-
<?= $this->form->label(t('Complexity'), 'score') ?>
<?= $this->form->number('score', $values, $errors, array('tabindex="8"')) ?><br/>
@@ -80,5 +76,4 @@
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue" tabindex="11"/>
<?= t('or') ?> <?= $this->url->link(t('cancel'), 'board', 'show', array('project_id' => $values['project_id']), false, 'close-popover') ?>
</div>
-</form>
-</section>
+</form> \ No newline at end of file
diff --git a/app/Template/task_modification/edit_task.php b/app/Template/task_modification/edit_task.php
index fe4696d6..f4d7449a 100644
--- a/app/Template/task_modification/edit_task.php
+++ b/app/Template/task_modification/edit_task.php
@@ -1,8 +1,7 @@
<div class="page-header">
<h2><?= t('Edit a task') ?></h2>
</div>
-<section id="task-section">
-<form method="post" action="<?= $this->url->href('taskmodification', 'update', array('task_id' => $task['id'], 'project_id' => $task['project_id'], 'ajax' => $ajax)) ?>" autocomplete="off">
+<form id="task-form" method="post" action="<?= $this->url->href('taskmodification', 'update', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>" autocomplete="off">
<?= $this->form->csrf() ?>
@@ -12,7 +11,6 @@
<?= $this->form->text('title', $values, $errors, array('autofocus', 'required', 'maxlength="200"', 'tabindex="1"')) ?><br/>
<?= $this->form->label(t('Description'), 'description') ?>
-
<div class="form-tabs">
<div class="write-area">
<?= $this->form->textarea('description', $values, $errors, array('placeholder="'.t('Leave a description').'"', 'tabindex="2"')) ?>
@@ -30,6 +28,7 @@
</ul>
</div>
+ <?= $this->render('task/color_picker', array('colors_list' => $colors_list, 'values' => $values)) ?>
</div>
<div class="form-column">
@@ -42,9 +41,6 @@
<?= $this->form->label(t('Category'), 'category_id') ?>
<?= $this->form->select('category_id', $categories_list, $values, $errors, array('tabindex="4"')) ?><br/>
- <?= $this->form->label(t('Color'), 'color_id') ?>
- <?= $this->form->select('color_id', $colors_list, $values, $errors, array('tabindex="5"')) ?><br/>
-
<?= $this->form->label(t('Complexity'), 'score') ?>
<?= $this->form->number('score', $values, $errors, array('tabindex="6"')) ?><br/>
@@ -62,5 +58,4 @@
<?= $this->url->link(t('cancel'), 'task', 'show', array('task_id' => $task['id'], 'project_id' => $task['project_id'])) ?>
<?php endif ?>
</div>
-</form>
-</section>
+</form> \ No newline at end of file
diff --git a/app/Template/user/sidebar.php b/app/Template/user/sidebar.php
index cd1c85c1..80fe8684 100644
--- a/app/Template/user/sidebar.php
+++ b/app/Template/user/sidebar.php
@@ -20,6 +20,8 @@
<?= $this->url->link(t('Persistent connections'), 'user', 'sessions', array('user_id' => $user['id'])) ?>
</li>
<?php endif ?>
+
+ <?= $this->hook->render('user:sidebar:information') ?>
</ul>
<h2><?= t('Actions') ?></h2>
@@ -60,14 +62,13 @@
<li <?= $this->app->getRouterController() === 'user' && $this->app->getRouterAction() === 'authentication' ? 'class="active"' : '' ?>>
<?= $this->url->link(t('Edit Authentication'), 'user', 'authentication', array('user_id' => $user['id'])) ?>
</li>
- <li <?= $this->app->getRouterController() === 'hourlyrate' ? 'class="active"' : '' ?>>
- <?= $this->url->link(t('Hourly rates'), 'hourlyrate', 'index', array('user_id' => $user['id'])) ?>
- </li>
<li <?= $this->app->getRouterController() === 'timetable' ? 'class="active"' : '' ?>>
<?= $this->url->link(t('Manage timetable'), 'timetable', 'index', array('user_id' => $user['id'])) ?>
</li>
<?php endif ?>
+ <?= $this->hook->render('user:sidebar:actions', array('user' => $user)) ?>
+
<?php if ($this->user->isAdmin() && ! $this->user->isCurrentUser($user['id'])): ?>
<li <?= $this->app->getRouterController() === 'user' && $this->app->getRouterAction() === 'remove' ? 'class="active"' : '' ?>>
<?= $this->url->link(t('Remove'), 'user', 'remove', array('user_id' => $user['id'])) ?>
diff --git a/app/common.php b/app/common.php
index 1f1c7273..ea38ab36 100644
--- a/app/common.php
+++ b/app/common.php
@@ -30,120 +30,8 @@ $container->register(new ServiceProvider\ClassProvider);
$container->register(new ServiceProvider\EventDispatcherProvider);
if (ENABLE_URL_REWRITE) {
-
- // Dashboard
- $container['router']->addRoute('dashboard', 'app', 'index');
- $container['router']->addRoute('dashboard/:user_id', 'app', 'index', array('user_id'));
- $container['router']->addRoute('dashboard/:user_id/projects', 'app', 'projects', array('user_id'));
- $container['router']->addRoute('dashboard/:user_id/tasks', 'app', 'tasks', array('user_id'));
- $container['router']->addRoute('dashboard/:user_id/subtasks', 'app', 'subtasks', array('user_id'));
- $container['router']->addRoute('dashboard/:user_id/calendar', 'app', 'calendar', array('user_id'));
- $container['router']->addRoute('dashboard/:user_id/activity', 'app', 'activity', array('user_id'));
-
- // Search routes
- $container['router']->addRoute('search', 'search', 'index');
- $container['router']->addRoute('search/:search', 'search', 'index', array('search'));
-
- // Project routes
- $container['router']->addRoute('projects', 'project', 'index');
- $container['router']->addRoute('project/create', 'project', 'create');
- $container['router']->addRoute('project/create/:private', 'project', 'create', array('private'));
- $container['router']->addRoute('project/:project_id', 'project', 'show', array('project_id'));
- $container['router']->addRoute('p/:project_id', 'project', 'show', array('project_id'));
- $container['router']->addRoute('project/:project_id/share', 'project', 'share', array('project_id'));
- $container['router']->addRoute('project/:project_id/edit', 'project', 'edit', array('project_id'));
- $container['router']->addRoute('project/:project_id/integration', 'project', 'integration', array('project_id'));
- $container['router']->addRoute('project/:project_id/users', 'project', 'users', array('project_id'));
- $container['router']->addRoute('project/:project_id/duplicate', 'project', 'duplicate', array('project_id'));
- $container['router']->addRoute('project/:project_id/remove', 'project', 'remove', array('project_id'));
- $container['router']->addRoute('project/:project_id/disable', 'project', 'disable', array('project_id'));
- $container['router']->addRoute('project/:project_id/enable', 'project', 'enable', array('project_id'));
-
- // Action routes
- $container['router']->addRoute('project/:project_id/actions', 'action', 'index', array('project_id'));
- $container['router']->addRoute('project/:project_id/action/:action_id/confirm', 'action', 'confirm', array('project_id', 'action_id'));
-
- // Column routes
- $container['router']->addRoute('project/:project_id/columns', 'column', 'index', array('project_id'));
- $container['router']->addRoute('project/:project_id/column/:column_id/edit', 'column', 'edit', array('project_id', 'column_id'));
- $container['router']->addRoute('project/:project_id/column/:column_id/confirm', 'column', 'confirm', array('project_id', 'column_id'));
- $container['router']->addRoute('project/:project_id/column/:column_id/move/:direction', 'column', 'move', array('project_id', 'column_id', 'direction'));
-
- // Swimlane routes
- $container['router']->addRoute('project/:project_id/swimlanes', 'swimlane', 'index', array('project_id'));
- $container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/edit', 'swimlane', 'edit', array('project_id', 'swimlane_id'));
- $container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/confirm', 'swimlane', 'confirm', array('project_id', 'swimlane_id'));
- $container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/disable', 'swimlane', 'disable', array('project_id', 'swimlane_id'));
- $container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/enable', 'swimlane', 'enable', array('project_id', 'swimlane_id'));
- $container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/up', 'swimlane', 'moveup', array('project_id', 'swimlane_id'));
- $container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/down', 'swimlane', 'movedown', array('project_id', 'swimlane_id'));
-
- // Category routes
- $container['router']->addRoute('project/:project_id/categories', 'category', 'index', array('project_id'));
- $container['router']->addRoute('project/:project_id/category/:category_id/edit', 'category', 'edit', array('project_id', 'category_id'));
- $container['router']->addRoute('project/:project_id/category/:category_id/confirm', 'category', 'confirm', array('project_id', 'category_id'));
-
- // Task routes
- $container['router']->addRoute('project/:project_id/task/:task_id', 'task', 'show', array('project_id', 'task_id'));
- $container['router']->addRoute('t/:task_id', 'task', 'show', array('task_id'));
- $container['router']->addRoute('public/task/:task_id/:token', 'task', 'readonly', array('task_id', 'token'));
-
- $container['router']->addRoute('project/:project_id/task/:task_id/activity', 'activity', 'task', array('project_id', 'task_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/screenshot', 'file', 'screenshot', array('project_id', 'task_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/upload', 'file', 'create', array('project_id', 'task_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/comment', 'comment', 'create', array('project_id', 'task_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/link', 'tasklink', 'create', array('project_id', 'task_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/transitions', 'task', 'transitions', array('project_id', 'task_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/analytics', 'task', 'analytics', array('project_id', 'task_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/remove', 'task', 'remove', array('project_id', 'task_id'));
-
- $container['router']->addRoute('project/:project_id/task/:task_id/edit', 'taskmodification', 'edit', array('project_id', 'task_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/description', 'taskmodification', 'description', array('project_id', 'task_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/recurrence', 'taskmodification', 'recurrence', array('project_id', 'task_id'));
-
- $container['router']->addRoute('project/:project_id/task/:task_id/close', 'taskstatus', 'close', array('task_id', 'project_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/open', 'taskstatus', 'open', array('task_id', 'project_id'));
-
- $container['router']->addRoute('project/:project_id/task/:task_id/duplicate', 'taskduplication', 'duplicate', array('task_id', 'project_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/copy', 'taskduplication', 'copy', array('task_id', 'project_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/copy/:dst_project_id', 'taskduplication', 'copy', array('task_id', 'project_id', 'dst_project_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/move', 'taskduplication', 'move', array('task_id', 'project_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/move/:dst_project_id', 'taskduplication', 'move', array('task_id', 'project_id', 'dst_project_id'));
-
- // Board routes
- $container['router']->addRoute('board/:project_id', 'board', 'show', array('project_id'));
- $container['router']->addRoute('b/:project_id', 'board', 'show', array('project_id'));
- $container['router']->addRoute('public/board/:token', 'board', 'readonly', array('token'));
-
- // Calendar routes
- $container['router']->addRoute('calendar/:project_id', 'calendar', 'show', array('project_id'));
- $container['router']->addRoute('c/:project_id', 'calendar', 'show', array('project_id'));
-
- // Listing routes
- $container['router']->addRoute('list/:project_id', 'listing', 'show', array('project_id'));
- $container['router']->addRoute('l/:project_id', 'listing', 'show', array('project_id'));
-
- // Gantt routes
- $container['router']->addRoute('gantt/:project_id', 'gantt', 'project', array('project_id'));
- $container['router']->addRoute('gantt/:project_id/sort/:sorting', 'gantt', 'project', array('project_id', 'sorting'));
-
- // Subtask routes
- $container['router']->addRoute('project/:project_id/task/:task_id/subtask/create', 'subtask', 'create', array('project_id', 'task_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/subtask/:subtask_id/remove', 'subtask', 'confirm', array('project_id', 'task_id', 'subtask_id'));
- $container['router']->addRoute('project/:project_id/task/:task_id/subtask/:subtask_id/edit', 'subtask', 'edit', array('project_id', 'task_id', 'subtask_id'));
-
- // Feed routes
- $container['router']->addRoute('feed/project/:token', 'feed', 'project', array('token'));
- $container['router']->addRoute('feed/user/:token', 'feed', 'user', array('token'));
-
- // Ical routes
- $container['router']->addRoute('ical/project/:token', 'ical', 'project', array('token'));
- $container['router']->addRoute('ical/user/:token', 'ical', 'user', array('token'));
-
- // Auth routes
- $container['router']->addRoute('oauth/google', 'oauth', 'google');
- $container['router']->addRoute('oauth/github', 'oauth', 'github');
- $container['router']->addRoute('oauth/gitlab', 'oauth', 'gitlab');
- $container['router']->addRoute('login', 'auth', 'login');
- $container['router']->addRoute('logout', 'auth', 'logout');
+ require __DIR__.'/routes.php';
}
+
+$plugin = new Core\PluginLoader($container);
+$plugin->scan();
diff --git a/app/constants.php b/app/constants.php
index cf515932..f25bd903 100644
--- a/app/constants.php
+++ b/app/constants.php
@@ -34,8 +34,11 @@ defined('LDAP_USER_PATTERN') or define('LDAP_USER_PATTERN', '');
defined('LDAP_ACCOUNT_FULLNAME') or define('LDAP_ACCOUNT_FULLNAME', 'displayname');
defined('LDAP_ACCOUNT_EMAIL') or define('LDAP_ACCOUNT_EMAIL', 'mail');
defined('LDAP_ACCOUNT_ID') or define('LDAP_ACCOUNT_ID', '');
-defined('LDAP_USERNAME_CASE_SENSITIVE') or define('LDAP_USERNAME_CASE_SENSITIVE', false);
+defined('LDAP_ACCOUNT_MEMBEROF') or define('LDAP_ACCOUNT_MEMBEROF', 'memberof');
defined('LDAP_ACCOUNT_CREATION') or define('LDAP_ACCOUNT_CREATION', true);
+defined('LDAP_GROUP_ADMIN_DN') or define('LDAP_GROUP_ADMIN_DN', '');
+defined('LDAP_GROUP_PROJECT_ADMIN_DN') or define('LDAP_GROUP_PROJECT_ADMIN_DN', '');
+defined('LDAP_USERNAME_CASE_SENSITIVE') or define('LDAP_USERNAME_CASE_SENSITIVE', false);
// Google authentication
defined('GOOGLE_AUTH') or define('GOOGLE_AUTH', false);
diff --git a/app/routes.php b/app/routes.php
new file mode 100644
index 00000000..159e8f6e
--- /dev/null
+++ b/app/routes.php
@@ -0,0 +1,117 @@
+<?php
+
+// Dashboard
+$container['router']->addRoute('dashboard', 'app', 'index');
+$container['router']->addRoute('dashboard/:user_id', 'app', 'index', array('user_id'));
+$container['router']->addRoute('dashboard/:user_id/projects', 'app', 'projects', array('user_id'));
+$container['router']->addRoute('dashboard/:user_id/tasks', 'app', 'tasks', array('user_id'));
+$container['router']->addRoute('dashboard/:user_id/subtasks', 'app', 'subtasks', array('user_id'));
+$container['router']->addRoute('dashboard/:user_id/calendar', 'app', 'calendar', array('user_id'));
+$container['router']->addRoute('dashboard/:user_id/activity', 'app', 'activity', array('user_id'));
+
+// Search routes
+$container['router']->addRoute('search', 'search', 'index');
+$container['router']->addRoute('search/:search', 'search', 'index', array('search'));
+
+// Project routes
+$container['router']->addRoute('projects', 'project', 'index');
+$container['router']->addRoute('project/create', 'project', 'create');
+$container['router']->addRoute('project/create/:private', 'project', 'create', array('private'));
+$container['router']->addRoute('project/:project_id', 'project', 'show', array('project_id'));
+$container['router']->addRoute('p/:project_id', 'project', 'show', array('project_id'));
+$container['router']->addRoute('project/:project_id/share', 'project', 'share', array('project_id'));
+$container['router']->addRoute('project/:project_id/edit', 'project', 'edit', array('project_id'));
+$container['router']->addRoute('project/:project_id/integration', 'project', 'integration', array('project_id'));
+$container['router']->addRoute('project/:project_id/users', 'project', 'users', array('project_id'));
+$container['router']->addRoute('project/:project_id/duplicate', 'project', 'duplicate', array('project_id'));
+$container['router']->addRoute('project/:project_id/remove', 'project', 'remove', array('project_id'));
+$container['router']->addRoute('project/:project_id/disable', 'project', 'disable', array('project_id'));
+$container['router']->addRoute('project/:project_id/enable', 'project', 'enable', array('project_id'));
+
+// Action routes
+$container['router']->addRoute('project/:project_id/actions', 'action', 'index', array('project_id'));
+$container['router']->addRoute('project/:project_id/action/:action_id/confirm', 'action', 'confirm', array('project_id', 'action_id'));
+
+// Column routes
+$container['router']->addRoute('project/:project_id/columns', 'column', 'index', array('project_id'));
+$container['router']->addRoute('project/:project_id/column/:column_id/edit', 'column', 'edit', array('project_id', 'column_id'));
+$container['router']->addRoute('project/:project_id/column/:column_id/confirm', 'column', 'confirm', array('project_id', 'column_id'));
+$container['router']->addRoute('project/:project_id/column/:column_id/move/:direction', 'column', 'move', array('project_id', 'column_id', 'direction'));
+
+// Swimlane routes
+$container['router']->addRoute('project/:project_id/swimlanes', 'swimlane', 'index', array('project_id'));
+$container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/edit', 'swimlane', 'edit', array('project_id', 'swimlane_id'));
+$container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/confirm', 'swimlane', 'confirm', array('project_id', 'swimlane_id'));
+$container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/disable', 'swimlane', 'disable', array('project_id', 'swimlane_id'));
+$container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/enable', 'swimlane', 'enable', array('project_id', 'swimlane_id'));
+$container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/up', 'swimlane', 'moveup', array('project_id', 'swimlane_id'));
+$container['router']->addRoute('project/:project_id/swimlane/:swimlane_id/down', 'swimlane', 'movedown', array('project_id', 'swimlane_id'));
+
+// Category routes
+$container['router']->addRoute('project/:project_id/categories', 'category', 'index', array('project_id'));
+$container['router']->addRoute('project/:project_id/category/:category_id/edit', 'category', 'edit', array('project_id', 'category_id'));
+$container['router']->addRoute('project/:project_id/category/:category_id/confirm', 'category', 'confirm', array('project_id', 'category_id'));
+
+// Task routes
+$container['router']->addRoute('project/:project_id/task/:task_id', 'task', 'show', array('project_id', 'task_id'));
+$container['router']->addRoute('t/:task_id', 'task', 'show', array('task_id'));
+$container['router']->addRoute('public/task/:task_id/:token', 'task', 'readonly', array('task_id', 'token'));
+
+$container['router']->addRoute('project/:project_id/task/:task_id/activity', 'activity', 'task', array('project_id', 'task_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/screenshot', 'file', 'screenshot', array('project_id', 'task_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/upload', 'file', 'create', array('project_id', 'task_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/comment', 'comment', 'create', array('project_id', 'task_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/link', 'tasklink', 'create', array('project_id', 'task_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/transitions', 'task', 'transitions', array('project_id', 'task_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/analytics', 'task', 'analytics', array('project_id', 'task_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/remove', 'task', 'remove', array('project_id', 'task_id'));
+
+$container['router']->addRoute('project/:project_id/task/:task_id/edit', 'taskmodification', 'edit', array('project_id', 'task_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/description', 'taskmodification', 'description', array('project_id', 'task_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/recurrence', 'taskmodification', 'recurrence', array('project_id', 'task_id'));
+
+$container['router']->addRoute('project/:project_id/task/:task_id/close', 'taskstatus', 'close', array('task_id', 'project_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/open', 'taskstatus', 'open', array('task_id', 'project_id'));
+
+$container['router']->addRoute('project/:project_id/task/:task_id/duplicate', 'taskduplication', 'duplicate', array('task_id', 'project_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/copy', 'taskduplication', 'copy', array('task_id', 'project_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/copy/:dst_project_id', 'taskduplication', 'copy', array('task_id', 'project_id', 'dst_project_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/move', 'taskduplication', 'move', array('task_id', 'project_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/move/:dst_project_id', 'taskduplication', 'move', array('task_id', 'project_id', 'dst_project_id'));
+
+// Board routes
+$container['router']->addRoute('board/:project_id', 'board', 'show', array('project_id'));
+$container['router']->addRoute('b/:project_id', 'board', 'show', array('project_id'));
+$container['router']->addRoute('public/board/:token', 'board', 'readonly', array('token'));
+
+// Calendar routes
+$container['router']->addRoute('calendar/:project_id', 'calendar', 'show', array('project_id'));
+$container['router']->addRoute('c/:project_id', 'calendar', 'show', array('project_id'));
+
+// Listing routes
+$container['router']->addRoute('list/:project_id', 'listing', 'show', array('project_id'));
+$container['router']->addRoute('l/:project_id', 'listing', 'show', array('project_id'));
+
+// Gantt routes
+$container['router']->addRoute('gantt/:project_id', 'gantt', 'project', array('project_id'));
+$container['router']->addRoute('gantt/:project_id/sort/:sorting', 'gantt', 'project', array('project_id', 'sorting'));
+
+// Subtask routes
+$container['router']->addRoute('project/:project_id/task/:task_id/subtask/create', 'subtask', 'create', array('project_id', 'task_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/subtask/:subtask_id/remove', 'subtask', 'confirm', array('project_id', 'task_id', 'subtask_id'));
+$container['router']->addRoute('project/:project_id/task/:task_id/subtask/:subtask_id/edit', 'subtask', 'edit', array('project_id', 'task_id', 'subtask_id'));
+
+// Feed routes
+$container['router']->addRoute('feed/project/:token', 'feed', 'project', array('token'));
+$container['router']->addRoute('feed/user/:token', 'feed', 'user', array('token'));
+
+// Ical routes
+$container['router']->addRoute('ical/project/:token', 'ical', 'project', array('token'));
+$container['router']->addRoute('ical/user/:token', 'ical', 'user', array('token'));
+
+// Auth routes
+$container['router']->addRoute('oauth/google', 'oauth', 'google');
+$container['router']->addRoute('oauth/github', 'oauth', 'github');
+$container['router']->addRoute('oauth/gitlab', 'oauth', 'gitlab');
+$container['router']->addRoute('login', 'auth', 'login');
+$container['router']->addRoute('logout', 'auth', 'logout');
diff --git a/assets/css/app.css b/assets/css/app.css
index befd889b..516f268a 100644
--- a/assets/css/app.css
+++ b/assets/css/app.css
@@ -17,4 +17,4 @@
*/.fc{direction:ltr;text-align:left}.fc-rtl{text-align:right}body .fc{font-size:1em}.fc-unthemed .fc-divider,.fc-unthemed .fc-popover,.fc-unthemed .fc-row,.fc-unthemed tbody,.fc-unthemed td,.fc-unthemed th,.fc-unthemed thead{border-color:#ddd}.fc-unthemed .fc-popover{background-color:#fff}.fc-unthemed .fc-divider,.fc-unthemed .fc-popover .fc-header{background:#eee}.fc-unthemed .fc-popover .fc-header .fc-close{color:#666}.fc-unthemed .fc-today{background:#fcf8e3}.fc-highlight{background:#bce8f1;opacity:.3;filter:alpha(opacity=30)}.fc-bgevent{background:#8fdf82;opacity:.3;filter:alpha(opacity=30)}.fc-nonbusiness{background:#d7d7d7}.fc-icon{display:inline-block;width:1em;height:1em;line-height:1em;font-size:1em;text-align:center;overflow:hidden;font-family:"Courier New",Courier,monospace;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.fc-icon:after{position:relative;margin:0 -1em}.fc-icon-left-single-arrow:after{content:"\02039";font-weight:700;font-size:200%;top:-7%;left:3%}.fc-icon-right-single-arrow:after{content:"\0203A";font-weight:700;font-size:200%;top:-7%;left:-3%}.fc-icon-left-double-arrow:after{content:"\000AB";font-size:160%;top:-7%}.fc-icon-right-double-arrow:after{content:"\000BB";font-size:160%;top:-7%}.fc-icon-left-triangle:after{content:"\25C4";font-size:125%;top:3%;left:-2%}.fc-icon-right-triangle:after{content:"\25BA";font-size:125%;top:3%;left:2%}.fc-icon-down-triangle:after{content:"\25BC";font-size:125%;top:2%}.fc-icon-x:after{content:"\000D7";font-size:200%;top:6%}.fc button{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;height:2.1em;padding:0 .6em;font-size:1em;white-space:nowrap;cursor:pointer}.fc button::-moz-focus-inner{margin:0;padding:0}.fc-state-default{border:1px solid}.fc-state-default.fc-corner-left{border-top-left-radius:4px;border-bottom-left-radius:4px}.fc-state-default.fc-corner-right{border-top-right-radius:4px;border-bottom-right-radius:4px}.fc button .fc-icon{position:relative;top:-.05em;margin:0 .2em;vertical-align:middle}.fc-state-default{background-color:#f5f5f5;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(to bottom,#fff,#e6e6e6);background-repeat:repeat-x;border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);color:#333;text-shadow:0 1px 1px rgba(255,255,255,.75);box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05)}.fc-state-active,.fc-state-disabled,.fc-state-down,.fc-state-hover{color:#333;background-color:#e6e6e6}.fc-state-hover{color:#333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.fc-state-active,.fc-state-down{background-color:#ccc;background-image:none;box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05)}.fc-state-disabled{cursor:default;background-image:none;opacity:.65;filter:alpha(opacity=65);box-shadow:none}.fc-button-group{display:inline-block}.fc .fc-button-group>*{float:left;margin:0 0 0 -1px}.fc .fc-button-group>:first-child{margin-left:0}.fc-popover{position:absolute;box-shadow:0 2px 6px rgba(0,0,0,.15)}.fc-popover .fc-header{padding:2px 4px}.fc-popover .fc-header .fc-title{margin:0 2px}.fc-popover .fc-header .fc-close{cursor:pointer}.fc-ltr .fc-popover .fc-header .fc-title,.fc-rtl .fc-popover .fc-header .fc-close{float:left}.fc-ltr .fc-popover .fc-header .fc-close,.fc-rtl .fc-popover .fc-header .fc-title{float:right}.fc-unthemed .fc-popover{border-width:1px;border-style:solid}.fc-unthemed .fc-popover .fc-header .fc-close{font-size:.9em;margin-top:2px}.fc-popover>.ui-widget-header+.ui-widget-content{border-top:0}.fc-divider{border-style:solid;border-width:1px}hr.fc-divider{height:0;margin:0;padding:0 0 2px;border-width:1px 0}.fc-clear{clear:both}.fc-bg,.fc-bgevent-skeleton,.fc-helper-skeleton,.fc-highlight-skeleton{position:absolute;top:0;left:0;right:0}.fc-bg{bottom:0}.fc-bg table{height:100%}.fc table{width:100%;table-layout:fixed;border-collapse:collapse;border-spacing:0;font-size:1em}.fc th{text-align:center}.fc td,.fc th{border-style:solid;border-width:1px;padding:0;vertical-align:top}.fc td.fc-today{border-style:double}.fc .fc-row{border-style:solid;border-width:0}.fc-row table{border-left:0 hidden transparent;border-right:0 hidden transparent;border-bottom:0 hidden transparent}.fc-row:first-child table{border-top:0 hidden transparent}.fc-row{position:relative}.fc-row .fc-bg{z-index:1}.fc-row .fc-bgevent-skeleton,.fc-row .fc-highlight-skeleton{bottom:0}.fc-row .fc-bgevent-skeleton table,.fc-row .fc-highlight-skeleton table{height:100%}.fc-row .fc-bgevent-skeleton td,.fc-row .fc-highlight-skeleton td{border-color:transparent}.fc-row .fc-bgevent-skeleton{z-index:2}.fc-row .fc-highlight-skeleton{z-index:3}.fc-row .fc-content-skeleton{position:relative;z-index:4;padding-bottom:2px}.fc-row .fc-helper-skeleton{z-index:5}.fc-row .fc-content-skeleton td,.fc-row .fc-helper-skeleton td{background:0 0;border-color:transparent;border-bottom:0}.fc-row .fc-content-skeleton tbody td,.fc-row .fc-helper-skeleton tbody td{border-top:0}.fc-scroller{overflow-y:scroll;overflow-x:hidden}.fc-scroller>*{position:relative;width:100%;overflow:hidden}.fc-event{position:relative;display:block;font-size:.85em;line-height:1.3;border-radius:3px;border:1px solid #3a87ad;background-color:#3a87ad;font-weight:400}.fc-event,.fc-event:hover,.ui-widget .fc-event{color:#fff;text-decoration:none}.fc-event.fc-draggable,.fc-event[href]{cursor:pointer}.fc-not-allowed,.fc-not-allowed .fc-event{cursor:not-allowed}.fc-event .fc-bg{z-index:1;background:#fff;opacity:.25;filter:alpha(opacity=25)}.fc-event .fc-content{position:relative;z-index:2}.fc-event .fc-resizer{position:absolute;z-index:3}.fc-ltr .fc-h-event.fc-not-start,.fc-rtl .fc-h-event.fc-not-end{margin-left:0;border-left-width:0;padding-left:1px;border-top-left-radius:0;border-bottom-left-radius:0}.fc-ltr .fc-h-event.fc-not-end,.fc-rtl .fc-h-event.fc-not-start{margin-right:0;border-right-width:0;padding-right:1px;border-top-right-radius:0;border-bottom-right-radius:0}.fc-h-event .fc-resizer{top:-1px;bottom:-1px;left:-1px;right:-1px;width:5px}.fc-ltr .fc-h-event .fc-start-resizer,.fc-ltr .fc-h-event .fc-start-resizer:after,.fc-ltr .fc-h-event .fc-start-resizer:before,.fc-rtl .fc-h-event .fc-end-resizer,.fc-rtl .fc-h-event .fc-end-resizer:after,.fc-rtl .fc-h-event .fc-end-resizer:before{right:auto;cursor:w-resize}.fc-ltr .fc-h-event .fc-end-resizer,.fc-ltr .fc-h-event .fc-end-resizer:after,.fc-ltr .fc-h-event .fc-end-resizer:before,.fc-rtl .fc-h-event .fc-start-resizer,.fc-rtl .fc-h-event .fc-start-resizer:after,.fc-rtl .fc-h-event .fc-start-resizer:before{left:auto;cursor:e-resize}.fc-day-grid-event{margin:1px 2px 0;padding:0 1px}.fc-day-grid-event .fc-content{white-space:nowrap;overflow:hidden}.fc-day-grid-event .fc-time{font-weight:700}.fc-day-grid-event .fc-resizer{left:-3px;right:-3px;width:7px}a.fc-more{margin:1px 3px;font-size:.85em;cursor:pointer;text-decoration:none}a.fc-more:hover{text-decoration:underline}.fc-limited{display:none}.fc-day-grid .fc-row{z-index:1}.fc-more-popover{z-index:2;width:220px}.fc-more-popover .fc-event-container{padding:10px}.fc-toolbar{text-align:center;margin-bottom:1em}.fc-toolbar .fc-left{float:left}.fc-toolbar .fc-right{float:right}.fc-toolbar .fc-center{display:inline-block}.fc .fc-toolbar>*>*{float:left;margin-left:.75em}.fc .fc-toolbar>*>:first-child{margin-left:0}.fc-toolbar h2{margin:0}.fc-toolbar button{position:relative}.fc-toolbar .fc-state-hover,.fc-toolbar .ui-state-hover{z-index:2}.fc-toolbar .fc-state-down{z-index:3}.fc-toolbar .fc-state-active,.fc-toolbar .ui-state-active{z-index:4}.fc-toolbar button:focus{z-index:5}.fc-view-container *,.fc-view-container :after,.fc-view-container :before{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.fc-view,.fc-view>table{position:relative;z-index:1}.fc-basicDay-view .fc-content-skeleton,.fc-basicWeek-view .fc-content-skeleton{padding-top:1px;padding-bottom:1em}.fc-basic-view .fc-body .fc-row{min-height:4em}.fc-row.fc-rigid{overflow:hidden}.fc-row.fc-rigid .fc-content-skeleton{position:absolute;top:0;left:0;right:0}.fc-basic-view .fc-day-number,.fc-basic-view .fc-week-number{padding:0 2px}.fc-basic-view td.fc-day-number,.fc-basic-view td.fc-week-number span{padding-top:2px;padding-bottom:2px}.fc-basic-view .fc-week-number{text-align:center}.fc-basic-view .fc-week-number span{display:inline-block;min-width:1.25em}.fc-ltr .fc-basic-view .fc-day-number{text-align:right}.fc-rtl .fc-basic-view .fc-day-number{text-align:left}.fc-day-number.fc-other-month{opacity:.3;filter:alpha(opacity=30)}.fc-agenda-view .fc-day-grid{position:relative;z-index:2}.fc-agenda-view .fc-day-grid .fc-row{min-height:3em}.fc-agenda-view .fc-day-grid .fc-row .fc-content-skeleton{padding-top:1px;padding-bottom:1em}.fc .fc-axis{vertical-align:middle;padding:0 4px;white-space:nowrap}.fc-ltr .fc-axis{text-align:right}.fc-rtl .fc-axis{text-align:left}.ui-widget td.fc-axis{font-weight:400}.fc-time-grid,.fc-time-grid-container{position:relative;z-index:1}.fc-time-grid{min-height:100%}.fc-time-grid table{border:0 hidden transparent}.fc-time-grid>.fc-bg{z-index:1}.fc-time-grid .fc-slats,.fc-time-grid>hr{position:relative;z-index:2}.fc-time-grid .fc-bgevent-skeleton,.fc-time-grid .fc-content-skeleton{position:absolute;top:0;left:0;right:0}.fc-time-grid .fc-bgevent-skeleton{z-index:3}.fc-time-grid .fc-highlight-skeleton{z-index:4}.fc-time-grid .fc-content-skeleton{z-index:5}.fc-time-grid .fc-helper-skeleton{z-index:6}.fc-time-grid .fc-slats td{height:1.5em;border-bottom:0}.fc-time-grid .fc-slats .fc-minor td{border-top-style:dotted}.fc-time-grid .fc-slats .ui-widget-content{background:0 0}.fc-time-grid .fc-highlight-container{position:relative}.fc-time-grid .fc-highlight{position:absolute;left:0;right:0}.fc-time-grid .fc-bgevent-container,.fc-time-grid .fc-event-container{position:relative}.fc-ltr .fc-time-grid .fc-event-container{margin:0 2.5% 0 2px}.fc-rtl .fc-time-grid .fc-event-container{margin:0 2px 0 2.5%}.fc-time-grid .fc-bgevent,.fc-time-grid .fc-event{position:absolute;z-index:1}.fc-time-grid .fc-bgevent{left:0;right:0}.fc-v-event.fc-not-start{border-top-width:0;padding-top:1px;border-top-left-radius:0;border-top-right-radius:0}.fc-v-event.fc-not-end{border-bottom-width:0;padding-bottom:1px;border-bottom-left-radius:0;border-bottom-right-radius:0}.fc-time-grid-event{overflow:hidden}.fc-time-grid-event .fc-time,.fc-time-grid-event .fc-title{padding:0 1px}.fc-time-grid-event .fc-time{font-size:.85em;white-space:nowrap}.fc-time-grid-event.fc-short .fc-content{white-space:nowrap}.fc-time-grid-event.fc-short .fc-time,.fc-time-grid-event.fc-short .fc-title{display:inline-block;vertical-align:top}.fc-time-grid-event.fc-short .fc-time span{display:none}.fc-time-grid-event.fc-short .fc-time:before{content:attr(data-start)}.fc-time-grid-event.fc-short .fc-time:after{content:"\000A0-\000A0"}.fc-time-grid-event.fc-short .fc-title{font-size:.85em;padding:0}.fc-time-grid-event .fc-resizer{left:0;right:0;bottom:0;height:8px;overflow:hidden;line-height:8px;font-size:11px;font-family:monospace;text-align:center;cursor:s-resize}.fc-time-grid-event .fc-resizer:after{content:"="}/*!
* Font Awesome 4.3.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.3.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.3.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.3.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.3.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.3.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.3.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;transform:translate(0, 0)}.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}.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-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-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-genderless:before,.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-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-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"}.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:20px;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}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}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-row{margin-top:10px;margin-bottom:20px}.form-column{float:left;padding-right:50px}.form-column:first-child{padding-left:0}.form-column ul{margin-top:15px}.form-login{width:350px;margin:0 auto;margin-top:8%}.form-column li,.form-login li{margin-left:25px;line-height:25px}.form-checkbox-group label{display:inline}label+.form-tabs{margin-top:10px}.form-tabs{width:100%;max-width:800px}.form-tabs-nav{margin-bottom:8px}.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}.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}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}.username a{color:#000}.username a:hover{color:#df5353;text-decoration:underline}.logo{opacity:.3;color:#d40000}.logo span{color:#333}.logo:hover{opacity:.8}.logo:focus span,.logo:hover span{color:#d40000}.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:#ddd}.page-header h2 a:focus,.page-header h2 a:hover{color:#333}.page-header ul{text-align:left;margin-top:5px;display:inline-block}.menu-inline li,.page-header li{display:inline;padding-right:10px;font-size:.95em}.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:scroll}#board{table-layout:fixed}#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}.board-rotation{min-width:250px;-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-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}th.board-swimlane-header{width:120px}a.board-swimlane-toggle{font-size:.95em}.board-swimlane-toggle-title{font-size:.85em;display:none}.board-swimlane-title{vertical-align:top}.board-task-list{overflow:auto}.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{position:relative;margin-bottom:2px;border:1px solid #000;padding:2px;font-size:.85em;word-wrap:break-word}div.task-board-recent{box-shadow:2px 2px 5px rgba(0,0,0,0.25)}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{float:left;margin-right:5px;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-show-details{position:relative;border-radius:5px;padding-bottom:10px}.task-show-details h2{font-size:1.8em;margin:0;margin-bottom:25px;padding:0;padding-left:10px;padding-right:10px}.task-show-details li{margin-left:25px;list-style-type:circle}.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}.task-show-start-link{color:#000}.task-show-start-link:hover,.task-show-start-link:focus{color:red}.flag-milestone{color:green}.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}#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}.listing{border-radius:4px;padding:8px 35px 8px 14px;margin-bottom:20px;border:1px solid #ddd;color:#333;background-color:#fefefe;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:5%;padding:15px;background:#fff;overflow:scroll;max-height:83%}#main .confirm{max-width:700px;font-size:1.1em}.sidebar-container{margin-top:10px;position:relative;clear:both}.sidebar-content{margin-left:23%;width:76%;position:absolute}.sidebar{width:20%;float:left;padding:10px;padding-top:0;border:1px solid #ddd;background:#fdfdfd;border-radius:5px}.sidebar li{list-style-type:square;margin-left:30px;line-height:1.8em}.sidebar li.active a{color:#000;font-weight:bold;text-decoration:none}.sidebar li.active a:focus,.sidebar li.active a:hover{text-decoration:underline}.sidebar-collapsed .sidebar{width:10px;padding-bottom:0;float:none}.sidebar-collapsed .sidebar-content{margin:0;margin-top:15px;width:100%}.sidebar-collapse{text-align:right}.sidebar-collapse a,.sidebar-expand a{color:#333;text-decoration:none}.sidebar-collapse a:hover,.sidebar-expand a:hover{color:#df5353}@media only screen and (max-width:1024px){.sidebar{width:25%}.sidebar-content{margin-left:30%;width:70%}}@media only screen and (max-width:767px){.sidebar{width:95%;float:none}.sidebar-content{margin:0;margin-top:15px;width:100%}}@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){li.hide-tablet,.hide-tablet{display:none}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;left:0;z-index:1000;min-width:280px;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)}ul.dropdown-submenu-top{bottom:0}.dropdown-submenu-open li{display:block;padding:0;padding-left:10px;padding-right:10px;margin:0;line-height:30px}.dropdown-submenu-open a{font-weight:normal}.page-header .dropdown{padding-right:10px}#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}.views li{border:1px solid #eee;padding-left:12px;padding-right:12px;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-left:10px;padding-right:10px;padding-top:5px;padding-bottom:5px;margin-left:10px}.filters ul{font-size:.8em}.page-header .filters ul{font-size:.9em}form.search{display:inline}div.search{margin-bottom:20px}.filter-dropdowns{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:auto;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} \ No newline at end of file
+ */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.3.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.3.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.3.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.3.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.3.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.3.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;transform:translate(0, 0)}.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}.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-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-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-genderless:before,.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-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-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"}.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:20px;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}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-row{margin-top:10px;margin-bottom:20px}.form-column{float:left;margin-right:3%;max-width:47%}.form-column ul{margin-top:15px}.form-login{width:350px;margin:0 auto;margin-top:8%}.form-column li,.form-login li{margin-left:25px;line-height:25px}.form-checkbox-group label{display:inline}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}.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}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}.username a{color:#000}.username a:hover{color:#df5353;text-decoration:underline}.logo{opacity:.3;color:#d40000}.logo span{color:#333}.logo:hover{opacity:.8}.logo:focus span,.logo:hover span{color:#d40000}.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:#ddd}.page-header h2 a:focus,.page-header h2 a:hover{color:#333}.page-header ul{text-align:left;margin-top:5px;display:inline-block}.menu-inline li,.page-header li{display:inline;padding-right:10px;font-size:.95em}.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}#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}.board-rotation{min-width:250px;-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-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}th.board-swimlane-header{width:120px}a.board-swimlane-toggle{font-size:.95em}.board-swimlane-toggle-title{font-size:.85em;display:none}.board-swimlane-title{vertical-align:top}.board-task-list{overflow:auto}.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{position:relative;margin-bottom:2px;border:1px solid #000;padding:2px;font-size:.85em;word-wrap:break-word}div.task-board-recent{box-shadow:2px 2px 5px rgba(0,0,0,0.25)}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{float:left;margin-right:5px;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-show-details{position:relative;border-radius:5px;padding-bottom:10px}.task-show-details h2{font-size:1.8em;margin:0;margin-bottom:25px;padding:0;padding-left:10px;padding-right:10px}.task-show-details li{margin-left:25px;list-style-type:circle}.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}.task-show-start-link{color:#000}.task-show-start-link:hover,.task-show-start-link:focus{color:red}.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)}.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}#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}.listing{border-radius:4px;padding:8px 35px 8px 14px;margin-bottom:20px;border:1px solid #ddd;color:#333;background-color:#fefefe;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:scroll;max-height:85%}#main .confirm{max-width:700px;font-size:1.1em}.sidebar-container{margin-top:10px;position:relative;clear:both}.sidebar-content{margin-left:23%;width:76%;position:absolute}.sidebar{width:20%;float:left;padding:10px;padding-top:0;border:1px solid #ddd;background:#fdfdfd;border-radius:5px}.sidebar li{list-style-type:square;margin-left:30px;line-height:1.8em}.sidebar li.active a{color:#000;font-weight:bold;text-decoration:none}.sidebar li.active a:focus,.sidebar li.active a:hover{text-decoration:underline}.sidebar-collapsed .sidebar{width:10px;padding-bottom:0;float:none}.sidebar-collapsed .sidebar-content{margin:0;margin-top:15px;width:100%}.sidebar-collapse{text-align:right}.sidebar-collapse a,.sidebar-expand a{color:#333;text-decoration:none}.sidebar-collapse a:hover,.sidebar-expand a:hover{color:#df5353}@media only screen and (max-width:1024px){.sidebar{width:25%}.sidebar-content{margin-left:30%;width:70%}}@media only screen and (max-width:767px){.sidebar{width:95%;float:none}.sidebar-content{margin:0;margin-top:15px;width:100%}}@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){li.hide-tablet,.hide-tablet{display:none}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)}.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}.dropdown-submenu-open li:last-child{border:0}.dropdown-submenu-open li:hover{background:#4078c0;color:#fff}.dropdown-submenu-open li:hover a{color:#fff}.dropdown-submenu-open a{text-decoration:none;color:#333}.dropdown-submenu-open a:focus{text-decoration:underline}.page-header .dropdown{padding-right:10px}#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}.views li{border:1px solid #eee;padding-left:12px;padding-right:12px;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-left:10px;padding-right:10px;padding-top:5px;padding-bottom:5px;margin-left:10px}.filters ul{font-size:.8em}.page-header .filters ul{font-size:.9em}form.search{display:inline}div.search{margin-bottom:20px}.filter-dropdowns{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} \ No newline at end of file
diff --git a/assets/css/print.css b/assets/css/print.css
index db889a64..feccdb43 100644
--- a/assets/css/print.css
+++ b/assets/css/print.css
@@ -17,4 +17,4 @@
*/.fc{direction:ltr;text-align:left}.fc-rtl{text-align:right}body .fc{font-size:1em}.fc-unthemed .fc-divider,.fc-unthemed .fc-popover,.fc-unthemed .fc-row,.fc-unthemed tbody,.fc-unthemed td,.fc-unthemed th,.fc-unthemed thead{border-color:#ddd}.fc-unthemed .fc-popover{background-color:#fff}.fc-unthemed .fc-divider,.fc-unthemed .fc-popover .fc-header{background:#eee}.fc-unthemed .fc-popover .fc-header .fc-close{color:#666}.fc-unthemed .fc-today{background:#fcf8e3}.fc-highlight{background:#bce8f1;opacity:.3;filter:alpha(opacity=30)}.fc-bgevent{background:#8fdf82;opacity:.3;filter:alpha(opacity=30)}.fc-nonbusiness{background:#d7d7d7}.fc-icon{display:inline-block;width:1em;height:1em;line-height:1em;font-size:1em;text-align:center;overflow:hidden;font-family:"Courier New",Courier,monospace;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.fc-icon:after{position:relative;margin:0 -1em}.fc-icon-left-single-arrow:after{content:"\02039";font-weight:700;font-size:200%;top:-7%;left:3%}.fc-icon-right-single-arrow:after{content:"\0203A";font-weight:700;font-size:200%;top:-7%;left:-3%}.fc-icon-left-double-arrow:after{content:"\000AB";font-size:160%;top:-7%}.fc-icon-right-double-arrow:after{content:"\000BB";font-size:160%;top:-7%}.fc-icon-left-triangle:after{content:"\25C4";font-size:125%;top:3%;left:-2%}.fc-icon-right-triangle:after{content:"\25BA";font-size:125%;top:3%;left:2%}.fc-icon-down-triangle:after{content:"\25BC";font-size:125%;top:2%}.fc-icon-x:after{content:"\000D7";font-size:200%;top:6%}.fc button{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;margin:0;height:2.1em;padding:0 .6em;font-size:1em;white-space:nowrap;cursor:pointer}.fc button::-moz-focus-inner{margin:0;padding:0}.fc-state-default{border:1px solid}.fc-state-default.fc-corner-left{border-top-left-radius:4px;border-bottom-left-radius:4px}.fc-state-default.fc-corner-right{border-top-right-radius:4px;border-bottom-right-radius:4px}.fc button .fc-icon{position:relative;top:-.05em;margin:0 .2em;vertical-align:middle}.fc-state-default{background-color:#f5f5f5;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(to bottom,#fff,#e6e6e6);background-repeat:repeat-x;border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);color:#333;text-shadow:0 1px 1px rgba(255,255,255,.75);box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05)}.fc-state-active,.fc-state-disabled,.fc-state-down,.fc-state-hover{color:#333;background-color:#e6e6e6}.fc-state-hover{color:#333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.fc-state-active,.fc-state-down{background-color:#ccc;background-image:none;box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05)}.fc-state-disabled{cursor:default;background-image:none;opacity:.65;filter:alpha(opacity=65);box-shadow:none}.fc-button-group{display:inline-block}.fc .fc-button-group>*{float:left;margin:0 0 0 -1px}.fc .fc-button-group>:first-child{margin-left:0}.fc-popover{position:absolute;box-shadow:0 2px 6px rgba(0,0,0,.15)}.fc-popover .fc-header{padding:2px 4px}.fc-popover .fc-header .fc-title{margin:0 2px}.fc-popover .fc-header .fc-close{cursor:pointer}.fc-ltr .fc-popover .fc-header .fc-title,.fc-rtl .fc-popover .fc-header .fc-close{float:left}.fc-ltr .fc-popover .fc-header .fc-close,.fc-rtl .fc-popover .fc-header .fc-title{float:right}.fc-unthemed .fc-popover{border-width:1px;border-style:solid}.fc-unthemed .fc-popover .fc-header .fc-close{font-size:.9em;margin-top:2px}.fc-popover>.ui-widget-header+.ui-widget-content{border-top:0}.fc-divider{border-style:solid;border-width:1px}hr.fc-divider{height:0;margin:0;padding:0 0 2px;border-width:1px 0}.fc-clear{clear:both}.fc-bg,.fc-bgevent-skeleton,.fc-helper-skeleton,.fc-highlight-skeleton{position:absolute;top:0;left:0;right:0}.fc-bg{bottom:0}.fc-bg table{height:100%}.fc table{width:100%;table-layout:fixed;border-collapse:collapse;border-spacing:0;font-size:1em}.fc th{text-align:center}.fc td,.fc th{border-style:solid;border-width:1px;padding:0;vertical-align:top}.fc td.fc-today{border-style:double}.fc .fc-row{border-style:solid;border-width:0}.fc-row table{border-left:0 hidden transparent;border-right:0 hidden transparent;border-bottom:0 hidden transparent}.fc-row:first-child table{border-top:0 hidden transparent}.fc-row{position:relative}.fc-row .fc-bg{z-index:1}.fc-row .fc-bgevent-skeleton,.fc-row .fc-highlight-skeleton{bottom:0}.fc-row .fc-bgevent-skeleton table,.fc-row .fc-highlight-skeleton table{height:100%}.fc-row .fc-bgevent-skeleton td,.fc-row .fc-highlight-skeleton td{border-color:transparent}.fc-row .fc-bgevent-skeleton{z-index:2}.fc-row .fc-highlight-skeleton{z-index:3}.fc-row .fc-content-skeleton{position:relative;z-index:4;padding-bottom:2px}.fc-row .fc-helper-skeleton{z-index:5}.fc-row .fc-content-skeleton td,.fc-row .fc-helper-skeleton td{background:0 0;border-color:transparent;border-bottom:0}.fc-row .fc-content-skeleton tbody td,.fc-row .fc-helper-skeleton tbody td{border-top:0}.fc-scroller{overflow-y:scroll;overflow-x:hidden}.fc-scroller>*{position:relative;width:100%;overflow:hidden}.fc-event{position:relative;display:block;font-size:.85em;line-height:1.3;border-radius:3px;border:1px solid #3a87ad;background-color:#3a87ad;font-weight:400}.fc-event,.fc-event:hover,.ui-widget .fc-event{color:#fff;text-decoration:none}.fc-event.fc-draggable,.fc-event[href]{cursor:pointer}.fc-not-allowed,.fc-not-allowed .fc-event{cursor:not-allowed}.fc-event .fc-bg{z-index:1;background:#fff;opacity:.25;filter:alpha(opacity=25)}.fc-event .fc-content{position:relative;z-index:2}.fc-event .fc-resizer{position:absolute;z-index:3}.fc-ltr .fc-h-event.fc-not-start,.fc-rtl .fc-h-event.fc-not-end{margin-left:0;border-left-width:0;padding-left:1px;border-top-left-radius:0;border-bottom-left-radius:0}.fc-ltr .fc-h-event.fc-not-end,.fc-rtl .fc-h-event.fc-not-start{margin-right:0;border-right-width:0;padding-right:1px;border-top-right-radius:0;border-bottom-right-radius:0}.fc-h-event .fc-resizer{top:-1px;bottom:-1px;left:-1px;right:-1px;width:5px}.fc-ltr .fc-h-event .fc-start-resizer,.fc-ltr .fc-h-event .fc-start-resizer:after,.fc-ltr .fc-h-event .fc-start-resizer:before,.fc-rtl .fc-h-event .fc-end-resizer,.fc-rtl .fc-h-event .fc-end-resizer:after,.fc-rtl .fc-h-event .fc-end-resizer:before{right:auto;cursor:w-resize}.fc-ltr .fc-h-event .fc-end-resizer,.fc-ltr .fc-h-event .fc-end-resizer:after,.fc-ltr .fc-h-event .fc-end-resizer:before,.fc-rtl .fc-h-event .fc-start-resizer,.fc-rtl .fc-h-event .fc-start-resizer:after,.fc-rtl .fc-h-event .fc-start-resizer:before{left:auto;cursor:e-resize}.fc-day-grid-event{margin:1px 2px 0;padding:0 1px}.fc-day-grid-event .fc-content{white-space:nowrap;overflow:hidden}.fc-day-grid-event .fc-time{font-weight:700}.fc-day-grid-event .fc-resizer{left:-3px;right:-3px;width:7px}a.fc-more{margin:1px 3px;font-size:.85em;cursor:pointer;text-decoration:none}a.fc-more:hover{text-decoration:underline}.fc-limited{display:none}.fc-day-grid .fc-row{z-index:1}.fc-more-popover{z-index:2;width:220px}.fc-more-popover .fc-event-container{padding:10px}.fc-toolbar{text-align:center;margin-bottom:1em}.fc-toolbar .fc-left{float:left}.fc-toolbar .fc-right{float:right}.fc-toolbar .fc-center{display:inline-block}.fc .fc-toolbar>*>*{float:left;margin-left:.75em}.fc .fc-toolbar>*>:first-child{margin-left:0}.fc-toolbar h2{margin:0}.fc-toolbar button{position:relative}.fc-toolbar .fc-state-hover,.fc-toolbar .ui-state-hover{z-index:2}.fc-toolbar .fc-state-down{z-index:3}.fc-toolbar .fc-state-active,.fc-toolbar .ui-state-active{z-index:4}.fc-toolbar button:focus{z-index:5}.fc-view-container *,.fc-view-container :after,.fc-view-container :before{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.fc-view,.fc-view>table{position:relative;z-index:1}.fc-basicDay-view .fc-content-skeleton,.fc-basicWeek-view .fc-content-skeleton{padding-top:1px;padding-bottom:1em}.fc-basic-view .fc-body .fc-row{min-height:4em}.fc-row.fc-rigid{overflow:hidden}.fc-row.fc-rigid .fc-content-skeleton{position:absolute;top:0;left:0;right:0}.fc-basic-view .fc-day-number,.fc-basic-view .fc-week-number{padding:0 2px}.fc-basic-view td.fc-day-number,.fc-basic-view td.fc-week-number span{padding-top:2px;padding-bottom:2px}.fc-basic-view .fc-week-number{text-align:center}.fc-basic-view .fc-week-number span{display:inline-block;min-width:1.25em}.fc-ltr .fc-basic-view .fc-day-number{text-align:right}.fc-rtl .fc-basic-view .fc-day-number{text-align:left}.fc-day-number.fc-other-month{opacity:.3;filter:alpha(opacity=30)}.fc-agenda-view .fc-day-grid{position:relative;z-index:2}.fc-agenda-view .fc-day-grid .fc-row{min-height:3em}.fc-agenda-view .fc-day-grid .fc-row .fc-content-skeleton{padding-top:1px;padding-bottom:1em}.fc .fc-axis{vertical-align:middle;padding:0 4px;white-space:nowrap}.fc-ltr .fc-axis{text-align:right}.fc-rtl .fc-axis{text-align:left}.ui-widget td.fc-axis{font-weight:400}.fc-time-grid,.fc-time-grid-container{position:relative;z-index:1}.fc-time-grid{min-height:100%}.fc-time-grid table{border:0 hidden transparent}.fc-time-grid>.fc-bg{z-index:1}.fc-time-grid .fc-slats,.fc-time-grid>hr{position:relative;z-index:2}.fc-time-grid .fc-bgevent-skeleton,.fc-time-grid .fc-content-skeleton{position:absolute;top:0;left:0;right:0}.fc-time-grid .fc-bgevent-skeleton{z-index:3}.fc-time-grid .fc-highlight-skeleton{z-index:4}.fc-time-grid .fc-content-skeleton{z-index:5}.fc-time-grid .fc-helper-skeleton{z-index:6}.fc-time-grid .fc-slats td{height:1.5em;border-bottom:0}.fc-time-grid .fc-slats .fc-minor td{border-top-style:dotted}.fc-time-grid .fc-slats .ui-widget-content{background:0 0}.fc-time-grid .fc-highlight-container{position:relative}.fc-time-grid .fc-highlight{position:absolute;left:0;right:0}.fc-time-grid .fc-bgevent-container,.fc-time-grid .fc-event-container{position:relative}.fc-ltr .fc-time-grid .fc-event-container{margin:0 2.5% 0 2px}.fc-rtl .fc-time-grid .fc-event-container{margin:0 2px 0 2.5%}.fc-time-grid .fc-bgevent,.fc-time-grid .fc-event{position:absolute;z-index:1}.fc-time-grid .fc-bgevent{left:0;right:0}.fc-v-event.fc-not-start{border-top-width:0;padding-top:1px;border-top-left-radius:0;border-top-right-radius:0}.fc-v-event.fc-not-end{border-bottom-width:0;padding-bottom:1px;border-bottom-left-radius:0;border-bottom-right-radius:0}.fc-time-grid-event{overflow:hidden}.fc-time-grid-event .fc-time,.fc-time-grid-event .fc-title{padding:0 1px}.fc-time-grid-event .fc-time{font-size:.85em;white-space:nowrap}.fc-time-grid-event.fc-short .fc-content{white-space:nowrap}.fc-time-grid-event.fc-short .fc-time,.fc-time-grid-event.fc-short .fc-title{display:inline-block;vertical-align:top}.fc-time-grid-event.fc-short .fc-time span{display:none}.fc-time-grid-event.fc-short .fc-time:before{content:attr(data-start)}.fc-time-grid-event.fc-short .fc-time:after{content:"\000A0-\000A0"}.fc-time-grid-event.fc-short .fc-title{font-size:.85em;padding:0}.fc-time-grid-event .fc-resizer{left:0;right:0;bottom:0;height:8px;overflow:hidden;line-height:8px;font-size:11px;font-family:monospace;text-align:center;cursor:s-resize}.fc-time-grid-event .fc-resizer:after{content:"="}/*!
* Font Awesome 4.3.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.3.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.3.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.3.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.3.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.3.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.3.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;transform:translate(0, 0)}.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}.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-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-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-genderless:before,.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-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-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"}.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}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:scroll}#board{table-layout:fixed}#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}.board-rotation{min-width:250px;-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-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}th.board-swimlane-header{width:120px}a.board-swimlane-toggle{font-size:.95em}.board-swimlane-toggle-title{font-size:.85em;display:none}.board-swimlane-title{vertical-align:top}.board-task-list{overflow:auto}.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{position:relative;margin-bottom:2px;border:1px solid #000;padding:2px;font-size:.85em;word-wrap:break-word}div.task-board-recent{box-shadow:2px 2px 5px rgba(0,0,0,0.25)}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{float:left;margin-right:5px;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-show-details{position:relative;border-radius:5px;padding-bottom:10px}.task-show-details h2{font-size:1.8em;margin:0;margin-bottom:25px;padding:0;padding-left:10px;padding-right:10px}.task-show-details li{margin-left:25px;list-style-type:circle}.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}.task-show-start-link{color:#000}.task-show-start-link:hover,.task-show-start-link:focus{color:red}.flag-milestone{color:green}.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}#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} \ No newline at end of file
+ */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.3.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.3.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.3.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.3.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.3.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.3.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;transform:translate(0, 0)}.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}.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-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-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-genderless:before,.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-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-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"}.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}#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}.board-rotation{min-width:250px;-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-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}th.board-swimlane-header{width:120px}a.board-swimlane-toggle{font-size:.95em}.board-swimlane-toggle-title{font-size:.85em;display:none}.board-swimlane-title{vertical-align:top}.board-task-list{overflow:auto}.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{position:relative;margin-bottom:2px;border:1px solid #000;padding:2px;font-size:.85em;word-wrap:break-word}div.task-board-recent{box-shadow:2px 2px 5px rgba(0,0,0,0.25)}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{float:left;margin-right:5px;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-show-details{position:relative;border-radius:5px;padding-bottom:10px}.task-show-details h2{font-size:1.8em;margin:0;margin-bottom:25px;padding:0;padding-left:10px;padding-right:10px}.task-show-details li{margin-left:25px;list-style-type:circle}.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}.task-show-start-link{color:#000}.task-show-start-link:hover,.task-show-start-link:focus{color:red}.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)}.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}#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} \ No newline at end of file
diff --git a/assets/css/src/board.css b/assets/css/src/board.css
index 2111b36b..c1eb8f4e 100644
--- a/assets/css/src/board.css
+++ b/assets/css/src/board.css
@@ -11,7 +11,7 @@
/* board table */
#board-container {
- overflow-x: scroll;
+ overflow-x: auto;
}
#board {
diff --git a/assets/css/src/dropdown.css b/assets/css/src/dropdown.css
index 462a38aa..30e5c22a 100644
--- a/assets/css/src/dropdown.css
+++ b/assets/css/src/dropdown.css
@@ -10,9 +10,8 @@
ul.dropdown-submenu-open {
display: block;
position: absolute;
- left: 0;
z-index: 1000;
- min-width: 280px;
+ min-width: 285px;
list-style: none;
margin: 3px 0 0 1px;
padding: 6px 0;
@@ -22,21 +21,38 @@ ul.dropdown-submenu-open {
box-shadow: 0px 1px 3px rgba(0,0,0,0.15);
}
-ul.dropdown-submenu-top {
- bottom: 0;
-}
-
.dropdown-submenu-open li {
display: block;
+ margin: 0;
padding: 0;
padding-left: 10px;
padding-right: 10px;
- margin: 0;
- line-height: 30px;
+ padding-top: 8px;
+ padding-bottom: 8px;
+ font-size: 0.85em;
+ border-bottom: 1px solid #f8f8f8;
+}
+
+.dropdown-submenu-open li:last-child {
+ border: none;
+}
+
+.dropdown-submenu-open li:hover {
+ background: #4078C0;
+ color: #fff;
+}
+
+.dropdown-submenu-open li:hover a {
+ color: #fff;
}
.dropdown-submenu-open a {
- font-weight: normal;
+ text-decoration: none;
+ color: #333;
+}
+
+.dropdown-submenu-open a:focus {
+ text-decoration: underline;
}
.page-header .dropdown {
diff --git a/assets/css/src/form.css b/assets/css/src/form.css
index 56506e28..bbd9fa9c 100644
--- a/assets/css/src/form.css
+++ b/assets/css/src/form.css
@@ -153,11 +153,8 @@ input.form-input-large {
.form-column {
float: left;
- padding-right: 50px;
-}
-
-.form-column:first-child {
- padding-left: 0;
+ margin-right: 3%;
+ max-width: 47%;
}
.form-column ul {
@@ -190,8 +187,9 @@ label + .form-tabs {
max-width: 800px;
}
-.form-tabs-nav {
+ul.form-tabs-nav {
margin-bottom: 8px;
+ margin-top: 0;
}
.form-tabs-nav li {
diff --git a/assets/css/src/gantt.css b/assets/css/src/gantt.css
index 06349e81..def5bf36 100644
--- a/assets/css/src/gantt.css
+++ b/assets/css/src/gantt.css
@@ -62,7 +62,7 @@ div.ganttview-vtheader-series-name {
border-top: 1px solid #d0d0d0;
font-size: 0.9em;
text-overflow: ellipsis;
- overflow: auto;
+ overflow: hidden;
white-space: nowrap;
}
diff --git a/assets/css/src/popover.css b/assets/css/src/popover.css
index ab499422..7bfbf810 100644
--- a/assets/css/src/popover.css
+++ b/assets/css/src/popover.css
@@ -15,9 +15,9 @@
width: 70%;
margin: 0 0 0 -35%;
left: 50%;
- top: 5%;
+ top: 1%;
padding: 15px;
background: #fff;
overflow: scroll;
- max-height: 83%;
+ max-height: 85%;
}
diff --git a/assets/css/src/table.css b/assets/css/src/table.css
index 21b8ccc3..51d6ecde 100644
--- a/assets/css/src/table.css
+++ b/assets/css/src/table.css
@@ -7,6 +7,10 @@ table {
font-size: 0.95em;
}
+#calendar table {
+ margin-bottom: 0;
+}
+
th,
td {
border: 1px solid #eee;
diff --git a/assets/css/src/task.css b/assets/css/src/task.css
index 2dfaca8a..9fed04e7 100644
--- a/assets/css/src/task.css
+++ b/assets/css/src/task.css
@@ -305,3 +305,29 @@ span.task-board-date-overdue {
.flag-milestone {
color: green;
}
+
+/* color picker */
+.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);
+}
diff --git a/assets/js/app.js b/assets/js/app.js
index 460fd3d6..1f1694af 100644
--- a/assets/js/app.js
+++ b/assets/js/app.js
@@ -50,4 +50,4 @@ c,a,e),l[d.key][c?"unshift":"push"]({callback:b,modifiers:d.modifiers,action:d.a
unbind:function(a,b){return m.bind(a,function(){},b)},trigger:function(a,b){if(q[a+":"+b])q[a+":"+b]({},a);return this},reset:function(){l={};q={};return this},stopCallback:function(a,b){return-1<(" "+b.className+" ").indexOf(" mousetrap ")?!1:"INPUT"==b.tagName||"SELECT"==b.tagName||"TEXTAREA"==b.tagName||b.isContentEditable},handleKey:function(a,b,d){var c=C(a,b,d),e;b={};var f=0,g=!1;for(e=0;e<c.length;++e)c[e].seq&&(f=Math.max(f,c[e].level));for(e=0;e<c.length;++e)c[e].seq?c[e].level==f&&(g=!0,
b[c[e].seq]=1,x(c[e].callback,d,c[e].combo,c[e].seq)):g||x(c[e].callback,d,c[e].combo);c="keypress"==d.type&&I;d.type!=u||w(a)||c||t(b);I=g&&"keydown"==d.type}};J.Mousetrap=m;"function"===typeof define&&define.amd&&define(m)})(window,document);
Mousetrap=function(a){var d={},e=a.stopCallback;a.stopCallback=function(b,c,a){return d[a]?!1:e(b,c,a)};a.bindGlobal=function(b,c,e){a.bind(b,c,e);if(b instanceof Array)for(c=0;c<b.length;c++)d[b[c]]=!0;else d[b]=!0};return a}(Mousetrap);
-!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:"&#x3C;Forrige",nextText:"Næste&#x3E;",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:"&#x3C;Zurück",nextText:"Vor&#x3E;",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:"&#x3C;Ant",nextText:"Sig&#x3E;",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){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:"&#xAB;Edellinen",nextText:"Seuraava&#xBB;",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,"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:"&#x3C;Prec",nextText:"Succ&#x3E;",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:"&#x3C;å‰",nextText:"次&#x3E;",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:"&#xAB;Forrige",nextText:"Neste&#xBB;",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:"&#x3C;Poprzedni",nextText:"NastÄ™pny&#x3E;",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:"&#x3C;Anterior",nextText:"Próximo&#x3E;",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:"&#x3C;Пред",nextText:"След&#x3E;",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:"&#xAB;Förra",nextText:"Nästa&#xBB;",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:"&#x3C;",nextText:"&#x3E;",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:"&#xAB;&#xA0;ย้อน",nextText:"ถัดไป&#xA0;&#xBB;",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:"&#x3C;geri",nextText:"ileri&#x3e",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:"&#x3C;上月",nextText:"下月&#x3E;",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 r(v){this.app=v;this.router=new t();this.router.addRoute("screenshot-zone",e)}r.prototype.isOpen=function(){return $("#popover-container").size()>0};r.prototype.open=function(w){var v=this;v.app.dropdown.close();$.get(w,function(x){$("body").append('<div id="popover-container"><div id="popover-content">'+x+"</div></div>");v.router.dispatch();v.app.listen()})};r.prototype.close=function(v){if(v){v.preventDefault()}$("#popover-container").remove()};r.prototype.onClick=function(w){w.preventDefault();w.stopPropagation();var v=w.target.getAttribute("href");if(!v){v=w.target.getAttribute("data-href")}if(v){this.open(v)}};r.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(v){v.stopPropagation()})};function p(){}p.prototype.listen=function(){var v=this;$(document).on("click",function(){v.close()});$(document).on("click",".dropdown-menu",function(y){y.preventDefault();y.stopImmediatePropagation();var w=$(this).next("ul");var x=240;if(!w.is(":visible")){v.close();if($(this).offset().top+x-$(window).scrollTop()>$(window).height()){w.addClass("dropdown-submenu-open dropdown-submenu-top")}else{w.addClass("dropdown-submenu-open")}}else{v.close()}})};p.prototype.close=function(){$(".dropdown-submenu-open").removeClass("dropdown-submenu-open")};function o(v){this.app=v}o.prototype.listen=function(){var v=this;$(".tooltip").tooltip({track:false,show:false,hide:false,position:{my:"left-20 top",at:"center bottom+9",using:function(w,x){$(this).css(w);var y=x.target.left+x.target.width/2-x.element.left-20;$("<div>").addClass("tooltip-arrow").addClass(x.vertical).addClass(y<1?"align-left":"align-right").appendTo(this)}},content:function(){var y=this;var w=$(this).attr("data-href");if(!w){return'<div class="markdown">'+$(this).attr("title")+"</div>"}$.get(w,function x(B){var A=$(".ui-tooltip:visible");$(".ui-tooltip-content:visible").html(B);A.css({top:"",left:""});A.children(".tooltip-arrow").remove();var z=$(y).tooltip("option","position");z.of=$(y);A.position(z);$("#tooltip-subtasks a").not(".popover").click(function(C){C.preventDefault();C.stopPropagation();if($(this).hasClass("popover-subtask-restriction")){v.app.popover.open($(this).attr("href"));$(y).tooltip("close")}else{$.get($(this).attr("href"),x)}})});return'<i class="fa fa-spinner fa-spin"></i>'}}).on("mouseenter",function(){var w=this;$(this).tooltip("open");$(".ui-tooltip").on("mouseleave",function(){$(w).tooltip("close")})}).on("mouseleave focusout",function(w){w.stopImmediatePropagation();var x=this;setTimeout(function(){if(!$(".ui-tooltip:hover").length){$(x).tooltip("close")}},100)})};function k(){}k.prototype.showPreview=function(A){A.preventDefault();var y=$(this);var B=$(this).closest("ul");var w=$(".write-area");var z=$(".preview-area");var v=$("textarea");var x=$.ajax({url:"?controller=app&action=preview",contentType:"application/json",type:"POST",processData:false,dataType:"html",data:JSON.stringify({text:v.val()})});x.done(function(C){B.find("li").removeClass("form-tab-selected");y.parent().addClass("form-tab-selected");z.find(".markdown").html(C);z.css("height",v.css("height"));z.css("width",v.css("width"));w.hide();z.show()})};k.prototype.showWriter=function(v){v.preventDefault();$(this).closest("ul").find("li").removeClass("form-tab-selected");$(this).parent().addClass("form-tab-selected");$(".write-area").show();$(".preview-area").hide()};k.prototype.listen=function(){$(document).on("click","#markdown-preview",this.showPreview.bind(this));$(document).on("click","#markdown-write",this.showWriter.bind(this))};function b(){}b.prototype.expand=function(v){v.preventDefault();$(".sidebar-container").removeClass("sidebar-collapsed");$(".sidebar-collapse").show();$(".sidebar h2").show();$(".sidebar ul").show();$(".sidebar-expand").hide()};b.prototype.collapse=function(v){v.preventDefault();$(".sidebar-container").addClass("sidebar-collapsed");$(".sidebar-expand").show();$(".sidebar h2").hide();$(".sidebar ul").hide();$(".sidebar-collapse").hide()};b.prototype.listen=function(){$(document).on("click",".sidebar-collapse",this.collapse);$(document).on("click",".sidebar-expand",this.expand)};function f(v){this.app=v;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 v=this;$(document).on("click",".filter-helper",function(x){x.preventDefault();var w=$(this).data("filter");$("#form-search").val(w);if($("#board").length){v.app.board.reloadFilters(w)}else{$("form.search").submit()}})};f.prototype.keyboardShortcuts=function(){var v=this;Mousetrap.bind("v b",function(x){var w=$(".view-board");if(w.length){window.location=w.attr("href")}});Mousetrap.bind("v c",function(x){var w=$(".view-calendar");if(w.length){window.location=w.attr("href")}});Mousetrap.bind("v l",function(x){var w=$(".view-listing");if(w.length){window.location=w.attr("href")}});Mousetrap.bind("v g",function(x){var w=$(".view-gantt");if(w.length){window.location=w.attr("href")}});Mousetrap.bind("f",function(x){x.preventDefault();var w=document.getElementById("form-search");if(w){w.focus()}});Mousetrap.bind("r",function(w){w.preventDefault();$("#form-search").val("status:open");if($("#board").length){v.app.board.reloadFilters("status:open")}else{$("form.search").submit()}})};function l(){this.board=new j(this);this.markdown=new k();this.sidebar=new b();this.search=new f(this);this.swimlane=new g();this.dropdown=new p();this.tooltip=new o(this);this.popover=new r(this);this.keyboardShortcuts();this.chosen();this.poll();$(".alert-fade-out").delay(4000).fadeOut(800,function(){$(this).remove()});var v=false;$("select.task-reload-project-destination").change(function(){if(!v){$(".loading-icon").show();v=true;window.location=$(this).data("redirect").replace(/PROJECT_ID/g,$(this).val())}})}l.prototype.listen=function(){this.popover.listen();this.markdown.listen();this.sidebar.listen();this.tooltip.listen();this.dropdown.listen();this.search.listen();this.search.focus();this.taskAutoComplete();this.datePicker();this.focus()};l.prototype.refresh=function(){$(document).off();this.listen()};l.prototype.focus=function(){$("[autofocus]").each(function(v,w){$(this).focus()});$(document).on("focus",".auto-select",function(){$(this).select()});$(document).on("mouseup",".auto-select",function(v){v.preventDefault()})};l.prototype.poll=function(){window.setInterval(this.checkSession,60000)};l.prototype.keyboardShortcuts=function(){var v=this;Mousetrap.bindGlobal("mod+enter",function(){$("form").submit()});Mousetrap.bind("b",function(w){w.preventDefault();$("#board-selector").trigger("chosen:open")});Mousetrap.bindGlobal("esc",function(){v.popover.close();v.dropdown.close()})};l.prototype.checkSession=function(){if(!$(".form-login").length){$.ajax({cache:false,url:$("body").data("status-url"),statusCode:{401:function(){window.location=$("body").data("login-url")}}})}};l.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})};l.prototype.taskAutoComplete=function(){if($(".task-autocomplete").length){if($(".opposite_task_id").val()==""){$(".task-autocomplete").parent().find("input[type=submit]").attr("disabled","disabled")}$(".task-autocomplete").autocomplete({source:$(".task-autocomplete").data("search-url"),minLength:1,select:function(v,w){var x=$(".task-autocomplete").data("dst-field");$("input[name="+x+"]").val(w.item.id);$(".task-autocomplete").parent().find("input[type=submit]").removeAttr("disabled")}})}};l.prototype.chosen=function(){$(".chosen-select").chosen({width:"180px",no_results_text:$(".chosen-select").data("notfound"),disable_search_threshold:10});$(".select-auto-redirect").change(function(){var v=new RegExp($(this).data("redirect-regex"),"g");window.location=$(this).data("redirect-url").replace(v,$(this).val())})};l.prototype.showLoadingIcon=function(){$("body").append('<span id="app-loading-icon">&nbsp;<i class="fa fa-spinner fa-spin"></i></span>')};l.prototype.hideLoadingIcon=function(){$("#app-loading-icon").remove()};l.prototype.isVisible=function(){var v="";if(typeof document.hidden!=="undefined"){v="visibilityState"}else{if(typeof document.mozHidden!=="undefined"){v="mozVisibilityState"}else{if(typeof document.msHidden!=="undefined"){v="msVisibilityState"}else{if(typeof document.webkitHidden!=="undefined"){v="webkitVisibilityState"}}}}if(v!=""){return document[v]=="visible"}return true};l.prototype.formatDuration=function(v){if(v>=86400){return Math.round(v/86400)+"d"}else{if(v>=3600){return Math.round(v/3600)+"h"}else{if(v>=60){return Math.round(v/60)+"m"}}}return v+"s"};function e(){this.pasteCatcher=null}e.prototype.execute=function(){this.initialize()};e.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))};e.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};e.prototype.setFocus=function(){if(this.pasteCatcher!==null){this.pasteCatcher.focus()}};e.prototype.pasteHandler=function(A){if(A.clipboardData&&A.clipboardData.items){var y=A.clipboardData.items;if(y){for(var z=0;z<y.length;z++){if(y[z].type.indexOf("image")!==-1){var x=y[z].getAsFile();var v=new FileReader();var w=this;v.onload=function(B){w.createImage(B.target.result)};v.readAsDataURL(x)}}}}else{setTimeout(this.checkInput.bind(this),100)}};e.prototype.checkInput=function(){var v=this.pasteCatcher.childNodes[0];if(v){if(v.tagName==="IMG"){this.createImage(v.src)}}this.pasteCatcher.innerHTML=""};e.prototype.createImage=function(x){var w=new Image();w.src=x;w.onload=function(){var y=x.split("base64,");var z=y[1];$("input[name=screenshot]").val(z)};var v=document.getElementById("screenshot-zone");v.innerHTML="";v.className="screenshot-pasted";v.appendChild(w);this.destroy();this.initialize()};function i(){}i.prototype.execute=function(){var v=$("#calendar");v.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(w){$.ajax({cache:false,url:v.data("save-url"),contentType:"application/json",type:"POST",processData:false,data:JSON.stringify({task_id:w.id,date_due:w.start.format()})})},viewRender:function(){var w=v.data("check-url");var y={start:v.fullCalendar("getView").start.format(),end:v.fullCalendar("getView").end.format()};for(var x in y){w+="&"+x+"="+y[x]}$.getJSON(w,function(z){v.fullCalendar("removeEvents");v.fullCalendar("addEventSource",z);v.fullCalendar("rerenderEvents")})}})};function j(v){this.app=v;this.checkInterval=null}j.prototype.execute=function(){this.app.swimlane.refresh();this.app.swimlane.listen();this.restoreColumnViewMode();this.compactView();this.poll();this.keyboardShortcuts();this.resizeColumnHeight();this.listen();this.dragAndDrop();$(window).resize(this.resizeColumnHeight)};j.prototype.poll=function(){var v=parseInt($("#board").attr("data-check-interval"));if(v>0){this.checkInterval=window.setInterval(this.check.bind(this),v*1000)}};j.prototype.reloadFilters=function(v){this.app.showLoadingIcon();$.ajax({cache:false,url:$("#board").data("reload-url"),contentType:"application/json",type:"POST",processData:false,data:JSON.stringify({search:v}),success:this.refresh.bind(this),error:this.app.hideLoadingIcon.bind(this)})};j.prototype.check=function(){if(this.app.isVisible()){var v=this;this.app.showLoadingIcon();$.ajax({cache:false,url:$("#board").data("check-url"),statusCode:{200:function(w){v.refresh(w)},304:function(){v.app.hideLoadingIcon()}}})}};j.prototype.save=function(x,y,v,w){this.app.showLoadingIcon();$.ajax({cache:false,url:$("#board").data("save-url"),contentType:"application/json",type:"POST",processData:false,data:JSON.stringify({task_id:x,column_id:y,swimlane_id:w,position:v}),success:this.refresh.bind(this),error:this.app.hideLoadingIcon.bind(this)})};j.prototype.refresh=function(v){$("#board-container").replaceWith(v);this.app.refresh();this.app.swimlane.refresh();this.app.swimlane.listen();this.resizeColumnHeight();this.app.hideLoadingIcon();this.listen();this.dragAndDrop();this.compactView();this.restoreColumnViewMode()};j.prototype.resizeColumnHeight=function(){if($(".board-swimlane").length>1){$(".board-task-list").each(function(){if($(this).height()>500){$(this).height(500)}else{$(this).css("min-height",320)}})}else{$(".board-task-list").height($(window).height()-145)}};j.prototype.dragAndDrop=function(){var v=this;var w={forcePlaceholderSize:true,delay:300,distance:5,connectWith:".board-task-list",placeholder:"draggable-placeholder",items:".draggable-item",stop:function(x,y){y.item.removeClass("draggable-item-selected");v.save(y.item.attr("data-task-id"),y.item.parent().attr("data-column-id"),y.item.index()+1,y.item.parent().attr("data-swimlane-id"))},start:function(x,y){y.item.addClass("draggable-item-selected");y.placeholder.height(y.item.height())}};if($.support.touch){$(".task-board-sort-handle").css("display","inline");w.handle=".task-board-sort-handle"}$(".board-task-list").sortable(w)};j.prototype.listen=function(){var v=this;$(document).on("click",".task-board",function(w){if(w.target.tagName!="A"){window.location=$(this).data("task-url")}});$(document).on("click",".filter-toggle-scrolling",function(w){w.preventDefault();v.toggleCompactView()});$(document).on("click",".board-column-title",function(){v.toggleColumnViewMode($(this).data("column-id"))})};j.prototype.toggleCompactView=function(){var v=localStorage.getItem("horizontal_scroll")||1;localStorage.setItem("horizontal_scroll",v==0?1:0);this.compactView()};j.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")}};j.prototype.toggleCollapsedMode=function(){var v=this;this.app.showLoadingIcon();$.ajax({cache:false,url:$('.filter-display-mode:not([style="display: none;"]) a').attr("href"),success:function(w){$(".filter-display-mode").toggle();v.refresh(w)}})};j.prototype.restoreColumnViewMode=function(){var v=this;$("tr:first th").each(function(){var w=$(this).data("column-id");if(localStorage.getItem("hidden_column_"+w)){v.hideColumn(w)}})};j.prototype.toggleColumnViewMode=function(v){if(localStorage.getItem("hidden_column_"+v)){this.showColumn(v)}else{this.hideColumn(v)}};j.prototype.hideColumn=function(v){$(".board-column-"+v+" .board-column-expanded").hide();$(".board-column-"+v+" .board-column-collapsed").show();$(".board-column-header-"+v+" .board-column-expanded").hide();$(".board-column-header-"+v+" .board-column-collapsed").show();$(".board-column-header-"+v).each(function(){$(this).removeClass("board-column-compact");$(this).addClass("board-column-header-collapsed")});$(".board-column-"+v).each(function(){$(this).addClass("board-column-task-collapsed")});$(".board-column-"+v+" .board-rotation").each(function(){var w=$(".board-swimlane").position();$(this).css("width",$(".board-column-"+v+"").height())});localStorage.setItem("hidden_column_"+v,1)};j.prototype.showColumn=function(v){$(".board-column-"+v+" .board-column-expanded").show();$(".board-column-"+v+" .board-column-collapsed").hide();$(".board-column-header-"+v+" .board-column-expanded").show();$(".board-column-header-"+v+" .board-column-collapsed").hide();$(".board-column-header-"+v).removeClass("board-column-header-collapsed");$(".board-column-"+v).removeClass("board-column-task-collapsed");if(localStorage.getItem("horizontal_scroll")==0){$(".board-column-header-"+v).addClass("board-column-compact")}localStorage.removeItem("hidden_column_"+v)};j.prototype.keyboardShortcuts=function(){var v=this;Mousetrap.bind("c",function(){v.toggleCompactView()});Mousetrap.bind("s",function(){v.toggleCollapsedMode()});Mousetrap.bind("n",function(){v.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(w){var x=this.getAllCollapsed();var v=x.indexOf(w);if(v>-1){x.splice(v,1)}localStorage.setItem(this.getStorageKey(),JSON.stringify(x));$(".swimlane-row-"+w).css("display","table-row");$(".show-icon-swimlane-"+w).css("display","none");$(".hide-icon-swimlane-"+w).css("display","inline")};g.prototype.collapse=function(v){var w=this.getAllCollapsed();if(w.indexOf(v)<0){w.push(v);localStorage.setItem(this.getStorageKey(),JSON.stringify(w))}$(".swimlane-row-"+v).css("display","none");$(".show-icon-swimlane-"+v).css("display","inline");$(".hide-icon-swimlane-"+v).css("display","none")};g.prototype.isCollapsed=function(v){return this.getAllCollapsed().indexOf(v)>-1};g.prototype.getAllCollapsed=function(){return JSON.parse(localStorage.getItem(this.getStorageKey()))||[]};g.prototype.refresh=function(){var w=this.getAllCollapsed();for(var v=0;v<w.length;v++){this.collapse(w[v])}};g.prototype.listen=function(){var v=this;$(document).on("click",".board-swimlane-toggle",function(x){x.preventDefault();var w=$(this).data("swimlane-id");if(v.isCollapsed(w)){v.expand(w)}else{v.collapse(w)}})};function c(v){this.app=v;this.data=[];this.options={container:"#gantt-chart",showWeekends:true,allowMoves:true,allowResizes:true,cellWidth:21,cellHeight:31,slideWidth:1000,vHeaderWidth:200}}c.prototype.saveRecord=function(v){this.app.showLoadingIcon();$.ajax({cache:false,url:$(this.options.container).data("save-url"),contentType:"application/json",type:"POST",processData:false,data:JSON.stringify(v),complete:this.app.hideLoadingIcon.bind(this)})};c.prototype.execute=function(){this.data=this.prepareData($(this.options.container).data("records"));var y=Math.floor((this.options.slideWidth/this.options.cellWidth)+5);var x=this.getDateRange(y);var v=x[0];var A=x[1];var w=$(this.options.container);var z=jQuery("<div>",{"class":"ganttview"});z.append(this.renderVerticalHeader());z.append(this.renderSlider(v,A));w.append(z);jQuery("div.ganttview-grid-row div.ganttview-grid-row-cell:last-child",w).addClass("last");jQuery("div.ganttview-hzheader-days div.ganttview-hzheader-day:last-child",w).addClass("last");jQuery("div.ganttview-hzheader-months div.ganttview-hzheader-month:last-child",w).addClass("last");if(!$(this.options.container).data("readonly")){this.listenForBlockResize(v);this.listenForBlockMove(v)}else{this.options.allowResizes=false;this.options.allowMoves=false}};c.prototype.renderVerticalHeader=function(){var z=jQuery("<div>",{"class":"ganttview-vtheader"});var w=jQuery("<div>",{"class":"ganttview-vtheader-item"});var y=jQuery("<div>",{"class":"ganttview-vtheader-series"});for(var v=0;v<this.data.length;v++){var x=jQuery("<span>").append(jQuery("<i>",{"class":"fa fa-info-circle tooltip",title:this.getVerticalHeaderTooltip(this.data[v])})).append("&nbsp;");if(this.data[v].type=="task"){x.append(jQuery("<a>",{href:this.data[v].link,target:"_blank"}).append(this.data[v].title))}else{x.append(jQuery("<a>",{href:this.data[v].board_link,target:"_blank",title:$(this.options.container).data("label-board-link")}).append('<i class="fa fa-th"></i>')).append("&nbsp;").append(jQuery("<a>",{href:this.data[v].gantt_link,target:"_blank",title:$(this.options.container).data("label-gantt-link")}).append('<i class="fa fa-sliders"></i>')).append("&nbsp;").append(jQuery("<a>",{href:this.data[v].link,target:"_blank"}).append(this.data[v].title))}y.append(jQuery("<div>",{"class":"ganttview-vtheader-series-name"}).append(x))}w.append(y);z.append(w);return z};c.prototype.renderSlider=function(w,y){var v=jQuery("<div>",{"class":"ganttview-slide-container"});var x=this.getDates(w,y);v.append(this.renderHorizontalHeader(x));v.append(this.renderGrid(x));v.append(this.addBlockContainers());this.addBlocks(v,w);return v};c.prototype.renderHorizontalHeader=function(v){var D=jQuery("<div>",{"class":"ganttview-hzheader"});var B=jQuery("<div>",{"class":"ganttview-hzheader-months"});var A=jQuery("<div>",{"class":"ganttview-hzheader-days"});var z=0;for(var E in v){for(var x in v[E]){var F=v[E][x].length*this.options.cellWidth;z=z+F;B.append(jQuery("<div>",{"class":"ganttview-hzheader-month",css:{width:(F-1)+"px"}}).append($.datepicker.regional[$("body").data("js-lang")].monthNames[x]+" "+E));for(var C in v[E][x]){A.append(jQuery("<div>",{"class":"ganttview-hzheader-day"}).append(v[E][x][C].getDate()))}}}B.css("width",z+"px");A.css("width",z+"px");D.append(B).append(A);return D};c.prototype.renderGrid=function(v){var F=jQuery("<div>",{"class":"ganttview-grid"});var A=jQuery("<div>",{"class":"ganttview-grid-row"});for(var D in v){for(var x in v[D]){for(var C in v[D][x]){var z=jQuery("<div>",{"class":"ganttview-grid-row-cell"});if(this.options.showWeekends&&this.isWeekend(v[D][x][C])){z.addClass("ganttview-weekend")}A.append(z)}}}var E=jQuery("div.ganttview-grid-row-cell",A).length*this.options.cellWidth;A.css("width",E+"px");F.css("width",E+"px");for(var B=0;B<this.data.length;B++){F.append(A.clone())}return F};c.prototype.addBlockContainers=function(){var w=jQuery("<div>",{"class":"ganttview-blocks"});for(var v=0;v<this.data.length;v++){w.append(jQuery("<div>",{"class":"ganttview-block-container"}))}return w};c.prototype.addBlocks=function(w,v){var D=jQuery("div.ganttview-blocks div.ganttview-block-container",w);var x=0;for(var A=0;A<this.data.length;A++){var B=this.data[A];var E=this.daysBetween(B.start,B.end)+1;var z=this.daysBetween(v,B.start);var C=jQuery("<div>",{"class":"ganttview-block-text"});var y=jQuery("<div>",{"class":"ganttview-block tooltip"+(this.options.allowMoves?" ganttview-block-movable":""),title:this.getBarTooltip(this.data[A]),css:{width:((E*this.options.cellWidth)-9)+"px","margin-left":(z*this.options.cellWidth)+"px"}}).append(C);if(E>=2){C.append(this.data[A].progress)}y.data("record",this.data[A]);this.setBarColor(y,this.data[A]);jQuery(D[x]).append(y);x=x+1}};c.prototype.getVerticalHeaderTooltip=function(w){var B="";if(w.type=="task"){B="<strong>"+w.column_title+"</strong> ("+w.progress+")<br/>"+w.title}else{var y=["managers","members"];for(var x in y){var z=y[x];if(!jQuery.isEmptyObject(w.users[z])){var A=jQuery("<ul>");for(var v in w.users[z]){A.append(jQuery("<li>").append(w.users[z][v]))}B+="<p><strong>"+$(this.options.container).data("label-"+z)+"</strong></p>"+A[0].outerHTML}}}return B};c.prototype.getBarTooltip=function(v){var w="";if(v.not_defined){w=$(this.options.container).data("label-not-defined")}else{if(v.type=="task"){w="<strong>"+v.progress+"</strong><br/>"+$(this.options.container).data("label-assignee")+" "+(v.assignee?v.assignee:"")+"<br/>"}w+=$(this.options.container).data("label-start-date")+" "+$.datepicker.formatDate("yy-mm-dd",v.start)+"<br/>";w+=$(this.options.container).data("label-end-date")+" "+$.datepicker.formatDate("yy-mm-dd",v.end)}return w};c.prototype.setBarColor=function(w,v){if(v.not_defined){w.addClass("ganttview-block-not-defined")}else{w.css("background-color",v.color.background);w.css("border-color",v.color.border)}};c.prototype.listenForBlockResize=function(v){var w=this;jQuery("div.ganttview-block",this.options.container).resizable({grid:this.options.cellWidth,handles:"e,w",delay:300,stop:function(){var x=jQuery(this);w.updateDataAndPosition(x,v);w.saveRecord(x.data("record"))}})};c.prototype.listenForBlockMove=function(v){var w=this;jQuery("div.ganttview-block",this.options.container).draggable({axis:"x",delay:300,grid:[this.options.cellWidth,this.options.cellWidth],stop:function(){var x=jQuery(this);w.updateDataAndPosition(x,v);w.saveRecord(x.data("record"))}})};c.prototype.updateDataAndPosition=function(A,y){var v=jQuery("div.ganttview-slide-container",this.options.container);var E=v.scrollLeft();var B=A.offset().left-v.offset().left-1+E;var D=A.data("record");D.not_defined=false;this.setBarColor(A,D);var x=Math.round(B/this.options.cellWidth);var C=this.addDays(this.cloneDate(y),x);D.start=C;var w=A.outerWidth();var z=Math.round(w/this.options.cellWidth)-1;D.end=this.addDays(this.cloneDate(C),z);if(D.type==="task"&&z>0){jQuery("div.ganttview-block-text",A).text(D.progress)}A.attr("title",this.getBarTooltip(D));A.data("record",D);A.css("top","").css("left","").css("position","relative").css("margin-left",B+"px")};c.prototype.getDates=function(z,v){var y=[];y[z.getFullYear()]=[];y[z.getFullYear()][z.getMonth()]=[z];var x=z;while(this.compareDate(x,v)==-1){var w=this.addDays(this.cloneDate(x),1);if(!y[w.getFullYear()]){y[w.getFullYear()]=[]}if(!y[w.getFullYear()][w.getMonth()]){y[w.getFullYear()][w.getMonth()]=[]}y[w.getFullYear()][w.getMonth()].push(w);x=w}return y};c.prototype.prepareData=function(x){for(var w=0;w<x.length;w++){var y=new Date(x[w].start[0],x[w].start[1]-1,x[w].start[2],0,0,0,0);x[w].start=y;var v=new Date(x[w].end[0],x[w].end[1]-1,x[w].end[2],0,0,0,0);x[w].end=v}return x};c.prototype.getDateRange=function(x){var A=new Date();var w=new Date();for(var y=0;y<this.data.length;y++){var z=new Date();z.setTime(Date.parse(this.data[y].start));var v=new Date();v.setTime(Date.parse(this.data[y].end));if(y==0){A=z;w=v}if(this.compareDate(A,z)==1){A=z}if(this.compareDate(w,v)==-1){w=v}}if(this.daysBetween(A,w)<x){w=this.addDays(this.cloneDate(A),x)}A.setDate(A.getDate()-1);return[A,w]};c.prototype.daysBetween=function(y,v){if(!y||!v){return 0}var x=0,w=this.cloneDate(y);while(this.compareDate(w,v)==-1){x=x+1;this.addDays(w,1)}return x};c.prototype.isWeekend=function(v){return v.getDay()%6==0};c.prototype.cloneDate=function(v){return new Date(v.getTime())};c.prototype.addDays=function(v,w){v.setDate(v.getDate()+w*1);return v};c.prototype.compareDate=function(w,v){if(isNaN(w)||isNaN(v)){throw new Error(w+" - "+v)}else{if(w instanceof Date&&v instanceof Date){return(w<v)?-1:(w>v)?1:0}else{throw new TypeError(w+" - "+v)}}};function q(){}q.prototype.execute=function(){var x=$("#chart").data("metrics");var w=[];for(var v=0;v<x.length;v++){w.push([x[v].column_title,x[v].nb_tasks])}c3.generate({data:{columns:w,type:"donut"}})};function n(){}n.prototype.execute=function(){var x=$("#chart").data("metrics");var w=[];for(var v=0;v<x.length;v++){w.push([x[v].user,x[v].nb_tasks])}c3.generate({data:{columns:w,type:"donut"}})};function d(){}d.prototype.execute=function(){var B=$("#chart").data("metrics");var A=[];var v=[];var w=[];var y=d3.time.format("%Y-%m-%d");var C=d3.time.format($("#chart").data("date-format"));for(var z=0;z<B.length;z++){for(var x=0;x<B[z].length;x++){if(z==0){A.push([B[z][x]]);if(x>0){v.push(B[z][x])}}else{A[x].push(B[z][x]);if(x==0){w.push(C(y.parse(B[z][x])))}}}}c3.generate({data:{columns:A,type:"area-spline",groups:[v]},axis:{x:{type:"category",categories:w}}})};function m(){}m.prototype.execute=function(){var A=$("#chart").data("metrics");var z=[[$("#chart").data("label-total")]];var v=[];var x=d3.time.format("%Y-%m-%d");var B=d3.time.format($("#chart").data("date-format"));for(var y=0;y<A.length;y++){for(var w=0;w<A[y].length;w++){if(y==0){z.push([A[y][w]])}else{z[w+1].push(A[y][w]);if(w>0){if(z[0][y]==undefined){z[0].push(0)}z[0][y]+=A[y][w]}if(w==0){v.push(B(x.parse(A[y][w])))}}}}c3.generate({data:{columns:z},axis:{x:{type:"category",categories:v}}})};function a(){}a.prototype.execute=function(){var w=[];var A=$("#chart").data("metrics");var C=$("#chart").data("labels");var x=d3.time.format("%Y-%m-%d");var B=d3.time.format($("#chart").data("date-format"));var z=[[C["in"]],[C.left],[C.out]];var v={};v[C["in"]]="#5858FA";v[C.left]="#04B404";v[C.out]="#DF3A01";for(var y=0;y<A.length;y++){w.push(B(x.parse(A[y]["date"])));z[0].push(A[y]["in"]);z[1].push(A[y]["left"]);z[2].push(A[y]["out"])}c3.generate({data:{columns:z,colors:v,type:"bar"},bar:{width:{ratio:0.25}},grid:{x:{show:true},y:{show:true}},axis:{x:{type:"category",categories:w}}})};function h(v){this.app=v}h.prototype.execute=function(){var x=$("#chart").data("metrics");var y=[$("#chart").data("label")];var v=[];for(var w in x){y.push(x[w].average);v.push(x[w].title)}c3.generate({data:{columns:[y],type:"bar"},bar:{width:{ratio:0.5}},axis:{x:{type:"category",categories:v},y:{tick:{format:this.app.formatDuration}}},legend:{show:false}})};function u(v){this.app=v}u.prototype.execute=function(){var x=$("#chart").data("metrics");var y=[$("#chart").data("label")];var v=[];for(var w=0;w<x.length;w++){y.push(x[w].time_spent);v.push(x[w].title)}c3.generate({data:{columns:[y],type:"bar"},bar:{width:{ratio:0.5}},axis:{x:{type:"category",categories:v},y:{tick:{format:this.app.formatDuration}}},legend:{show:false}})};function s(v){this.app=v}s.prototype.execute=function(){var B=$("#chart").data("metrics");var A=[$("#chart").data("label-cycle")];var x=[$("#chart").data("label-lead")];var w=[];var z={};z[$("#chart").data("label-cycle")]="area";z[$("#chart").data("label-lead")]="area-spline";var v={};v[$("#chart").data("label-lead")]="#afb42b";v[$("#chart").data("label-cycle")]="#4e342e";for(var y=0;y<B.length;y++){A.push(parseInt(B[y].avg_cycle_time));x.push(parseInt(B[y].avg_lead_time));w.push(B[y].day)}c3.generate({data:{columns:[x,A],types:z,colors:v},axis:{x:{type:"category",categories:w},y:{tick:{format:this.app.formatDuration}}}})};function t(){this.routes={}}t.prototype.addRoute=function(w,v){this.routes[w]=v};t.prototype.dispatch=function(w){for(var x in this.routes){if(document.getElementById(x)){var v=Object.create(this.routes[x].prototype);this.routes[x].apply(v,[w]);v.execute();break}}};jQuery(document).ready(function(){var w=new l();var v=new t();v.addRoute("board",j);v.addRoute("calendar",i);v.addRoute("screenshot-zone",e);v.addRoute("analytic-task-repartition",q);v.addRoute("analytic-user-repartition",n);v.addRoute("analytic-cfd",d);v.addRoute("analytic-burndown",m);v.addRoute("budget-chart",a);v.addRoute("analytic-avg-time-column",h);v.addRoute("analytic-task-time-column",u);v.addRoute("analytic-lead-cycle-time",s);v.addRoute("gantt-chart",c);v.dispatch(w);w.listen()})})(); \ No newline at end of file
+!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:"&#x3C;Forrige",nextText:"Næste&#x3E;",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:"&#x3C;Zurück",nextText:"Vor&#x3E;",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:"&#x3C;Ant",nextText:"Sig&#x3E;",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){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:"&#xAB;Edellinen",nextText:"Seuraava&#xBB;",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:"&#x3C;mundur",nextText:"maju&#x3E;",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:"&#x3C;Prec",nextText:"Succ&#x3E;",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:"&#x3C;å‰",nextText:"次&#x3E;",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:"&#xAB;Forrige",nextText:"Neste&#xBB;",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:"&#x3C;Poprzedni",nextText:"NastÄ™pny&#x3E;",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:"&#x3C;Anterior",nextText:"Próximo&#x3E;",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:"&#x3C;Пред",nextText:"След&#x3E;",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:"&#xAB;Förra",nextText:"Nästa&#xBB;",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:"&#x3C;",nextText:"&#x3E;",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:"&#xAB;&#xA0;ย้อน",nextText:"ถัดไป&#xA0;&#xBB;",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:"&#x3C;geri",nextText:"ileri&#x3e",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:"&#x3C;上月",nextText:"下月&#x3E;",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 r(v){this.app=v;this.router=new t();this.router.addRoute("screenshot-zone",e)}r.prototype.isOpen=function(){return $("#popover-container").size()>0};r.prototype.open=function(w){var v=this;v.app.dropdown.close();$.get(w,function(x){$("body").append('<div id="popover-container"><div id="popover-content">'+x+"</div></div>");v.router.dispatch();v.app.refresh();v.afterOpen()})};r.prototype.close=function(v){if(v){v.preventDefault()}$("#popover-container").remove()};r.prototype.onClick=function(w){w.preventDefault();w.stopPropagation();var v=w.target.getAttribute("href");if(!v){v=w.target.getAttribute("data-href")}if(v){this.open(v)}};r.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(v){v.stopPropagation()})};r.prototype.afterOpen=function(){var v=this;var w=$("#task-form");if(w){w.on("submit",function(x){x.preventDefault();$.ajax({type:"POST",url:w.attr("action"),data:w.serialize(),success:function(z,A,y){if(y.getResponseHeader("X-Ajax-Redirect")){window.location=y.getResponseHeader("X-Ajax-Redirect")}else{$("#popover-content").html(z);v.afterOpen()}}})})}};function p(){}p.prototype.listen=function(){var v=this;$(document).on("click",function(){v.close()});$(document).on("click",".dropdown-menu",function(z){z.preventDefault();z.stopImmediatePropagation();v.close();var x=$(this).next("ul");var y=240;var A=$(this).offset();var w=$(this).height();$("body").append(jQuery("<div>",{id:"dropdown"}));x.clone().appendTo("#dropdown");var B=$("#dropdown ul");B.css("left",A.left);if(A.top+y-$(window).scrollTop()>$(window).height()){B.css("top",A.top-y-w)}else{B.css("top",A.top+w)}B.addClass("dropdown-submenu-open")})};p.prototype.close=function(){$("#dropdown").remove()};function o(v){this.app=v}o.prototype.listen=function(){var v=this;$(".tooltip").tooltip({track:false,show:false,hide:false,position:{my:"left-20 top",at:"center bottom+9",using:function(w,x){$(this).css(w);var y=x.target.left+x.target.width/2-x.element.left-20;$("<div>").addClass("tooltip-arrow").addClass(x.vertical).addClass(y<1?"align-left":"align-right").appendTo(this)}},content:function(){var y=this;var w=$(this).attr("data-href");if(!w){return'<div class="markdown">'+$(this).attr("title")+"</div>"}$.get(w,function x(B){var A=$(".ui-tooltip:visible");$(".ui-tooltip-content:visible").html(B);A.css({top:"",left:""});A.children(".tooltip-arrow").remove();var z=$(y).tooltip("option","position");z.of=$(y);A.position(z);$("#tooltip-subtasks a").not(".popover").click(function(C){C.preventDefault();C.stopPropagation();if($(this).hasClass("popover-subtask-restriction")){v.app.popover.open($(this).attr("href"));$(y).tooltip("close")}else{$.get($(this).attr("href"),x)}})});return'<i class="fa fa-spinner fa-spin"></i>'}}).on("mouseenter",function(){var w=this;$(this).tooltip("open");$(".ui-tooltip").on("mouseleave",function(){$(w).tooltip("close")})}).on("mouseleave focusout",function(w){w.stopImmediatePropagation();var x=this;setTimeout(function(){if(!$(".ui-tooltip:hover").length){$(x).tooltip("close")}},100)})};function k(){}k.prototype.showPreview=function(z){z.preventDefault();var w=$(".write-area");var y=$(".preview-area");var v=$("textarea");$("#markdown-write").parent().removeClass("form-tab-selected");$("#markdown-preview").parent().addClass("form-tab-selected");var x=$.ajax({url:$("body").data("markdown-preview-url"),contentType:"application/json",type:"POST",processData:false,dataType:"html",data:JSON.stringify({text:v.val()})});x.done(function(A){y.find(".markdown").html(A);y.css("height",v.css("height"));y.css("width",v.css("width"));w.hide();y.show()})};k.prototype.showWriter=function(v){v.preventDefault();$("#markdown-write").parent().addClass("form-tab-selected");$("#markdown-preview").parent().removeClass("form-tab-selected");$(".write-area").show();$(".preview-area").hide()};k.prototype.listen=function(){$(document).on("click","#markdown-preview",this.showPreview.bind(this));$(document).on("click","#markdown-write",this.showWriter.bind(this))};function b(){}b.prototype.expand=function(v){v.preventDefault();$(".sidebar-container").removeClass("sidebar-collapsed");$(".sidebar-collapse").show();$(".sidebar h2").show();$(".sidebar ul").show();$(".sidebar-expand").hide()};b.prototype.collapse=function(v){v.preventDefault();$(".sidebar-container").addClass("sidebar-collapsed");$(".sidebar-expand").show();$(".sidebar h2").hide();$(".sidebar ul").hide();$(".sidebar-collapse").hide()};b.prototype.listen=function(){$(document).on("click",".sidebar-collapse",this.collapse);$(document).on("click",".sidebar-expand",this.expand)};function f(v){this.app=v;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 v=this;$(document).on("click",".filter-helper",function(x){x.preventDefault();var w=$(this).data("filter");$("#form-search").val(w);if($("#board").length){v.app.board.reloadFilters(w)}else{$("form.search").submit()}})};f.prototype.keyboardShortcuts=function(){var v=this;Mousetrap.bind("v b",function(x){var w=$(".view-board");if(w.length){window.location=w.attr("href")}});Mousetrap.bind("v c",function(x){var w=$(".view-calendar");if(w.length){window.location=w.attr("href")}});Mousetrap.bind("v l",function(x){var w=$(".view-listing");if(w.length){window.location=w.attr("href")}});Mousetrap.bind("v g",function(x){var w=$(".view-gantt");if(w.length){window.location=w.attr("href")}});Mousetrap.bind("f",function(x){x.preventDefault();var w=document.getElementById("form-search");if(w){w.focus()}});Mousetrap.bind("r",function(w){w.preventDefault();$("#form-search").val("status:open");if($("#board").length){v.app.board.reloadFilters("status:open")}else{$("form.search").submit()}})};function l(){this.board=new j(this);this.markdown=new k();this.sidebar=new b();this.search=new f(this);this.swimlane=new g();this.dropdown=new p();this.tooltip=new o(this);this.popover=new r(this);this.task=new a();this.keyboardShortcuts();this.chosen();this.poll();$(".alert-fade-out").delay(4000).fadeOut(800,function(){$(this).remove()});var v=false;$("select.task-reload-project-destination").change(function(){if(!v){$(".loading-icon").show();v=true;window.location=$(this).data("redirect").replace(/PROJECT_ID/g,$(this).val())}})}l.prototype.listen=function(){this.popover.listen();this.markdown.listen();this.sidebar.listen();this.tooltip.listen();this.dropdown.listen();this.search.listen();this.task.listen();this.search.focus();this.taskAutoComplete();this.datePicker();this.focus()};l.prototype.refresh=function(){$(document).off();this.listen()};l.prototype.focus=function(){$("[autofocus]").each(function(v,w){$(this).focus()});$(document).on("focus",".auto-select",function(){$(this).select()});$(document).on("mouseup",".auto-select",function(v){v.preventDefault()})};l.prototype.poll=function(){window.setInterval(this.checkSession,60000)};l.prototype.keyboardShortcuts=function(){var v=this;Mousetrap.bindGlobal("mod+enter",function(){$("form").submit()});Mousetrap.bind("b",function(w){w.preventDefault();$("#board-selector").trigger("chosen:open")});Mousetrap.bindGlobal("esc",function(){v.popover.close();v.dropdown.close()})};l.prototype.checkSession=function(){if(!$(".form-login").length){$.ajax({cache:false,url:$("body").data("status-url"),statusCode:{401:function(){window.location=$("body").data("login-url")}}})}};l.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})};l.prototype.taskAutoComplete=function(){if($(".task-autocomplete").length){if($(".opposite_task_id").val()==""){$(".task-autocomplete").parent().find("input[type=submit]").attr("disabled","disabled")}$(".task-autocomplete").autocomplete({source:$(".task-autocomplete").data("search-url"),minLength:1,select:function(v,w){var x=$(".task-autocomplete").data("dst-field");$("input[name="+x+"]").val(w.item.id);$(".task-autocomplete").parent().find("input[type=submit]").removeAttr("disabled")}})}};l.prototype.chosen=function(){$(".chosen-select").chosen({width:"180px",no_results_text:$(".chosen-select").data("notfound"),disable_search_threshold:10});$(".select-auto-redirect").change(function(){var v=new RegExp($(this).data("redirect-regex"),"g");window.location=$(this).data("redirect-url").replace(v,$(this).val())})};l.prototype.showLoadingIcon=function(){$("body").append('<span id="app-loading-icon">&nbsp;<i class="fa fa-spinner fa-spin"></i></span>')};l.prototype.hideLoadingIcon=function(){$("#app-loading-icon").remove()};l.prototype.isVisible=function(){var v="";if(typeof document.hidden!=="undefined"){v="visibilityState"}else{if(typeof document.mozHidden!=="undefined"){v="mozVisibilityState"}else{if(typeof document.msHidden!=="undefined"){v="msVisibilityState"}else{if(typeof document.webkitHidden!=="undefined"){v="webkitVisibilityState"}}}}if(v!=""){return document[v]=="visible"}return true};l.prototype.formatDuration=function(v){if(v>=86400){return Math.round(v/86400)+"d"}else{if(v>=3600){return Math.round(v/3600)+"h"}else{if(v>=60){return Math.round(v/60)+"m"}}}return v+"s"};function e(){this.pasteCatcher=null}e.prototype.execute=function(){this.initialize()};e.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))};e.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};e.prototype.setFocus=function(){if(this.pasteCatcher!==null){this.pasteCatcher.focus()}};e.prototype.pasteHandler=function(A){if(A.clipboardData&&A.clipboardData.items){var y=A.clipboardData.items;if(y){for(var z=0;z<y.length;z++){if(y[z].type.indexOf("image")!==-1){var x=y[z].getAsFile();var v=new FileReader();var w=this;v.onload=function(B){w.createImage(B.target.result)};v.readAsDataURL(x)}}}}else{setTimeout(this.checkInput.bind(this),100)}};e.prototype.checkInput=function(){var v=this.pasteCatcher.childNodes[0];if(v){if(v.tagName==="IMG"){this.createImage(v.src)}}this.pasteCatcher.innerHTML=""};e.prototype.createImage=function(x){var w=new Image();w.src=x;w.onload=function(){var y=x.split("base64,");var z=y[1];$("input[name=screenshot]").val(z)};var v=document.getElementById("screenshot-zone");v.innerHTML="";v.className="screenshot-pasted";v.appendChild(w);this.destroy();this.initialize()};function i(){}i.prototype.execute=function(){var v=$("#calendar");v.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(w){$.ajax({cache:false,url:v.data("save-url"),contentType:"application/json",type:"POST",processData:false,data:JSON.stringify({task_id:w.id,date_due:w.start.format()})})},viewRender:function(){var w=v.data("check-url");var y={start:v.fullCalendar("getView").start.format(),end:v.fullCalendar("getView").end.format()};for(var x in y){w+="&"+x+"="+y[x]}$.getJSON(w,function(z){v.fullCalendar("removeEvents");v.fullCalendar("addEventSource",z);v.fullCalendar("rerenderEvents")})}})};function j(v){this.app=v;this.checkInterval=null}j.prototype.execute=function(){this.app.swimlane.refresh();this.app.swimlane.listen();this.restoreColumnViewMode();this.compactView();this.poll();this.keyboardShortcuts();this.resizeColumnHeight();this.listen();this.dragAndDrop();$(window).resize(this.resizeColumnHeight)};j.prototype.poll=function(){var v=parseInt($("#board").attr("data-check-interval"));if(v>0){this.checkInterval=window.setInterval(this.check.bind(this),v*1000)}};j.prototype.reloadFilters=function(v){this.app.showLoadingIcon();$.ajax({cache:false,url:$("#board").data("reload-url"),contentType:"application/json",type:"POST",processData:false,data:JSON.stringify({search:v}),success:this.refresh.bind(this),error:this.app.hideLoadingIcon.bind(this)})};j.prototype.check=function(){if(this.app.isVisible()){var v=this;this.app.showLoadingIcon();$.ajax({cache:false,url:$("#board").data("check-url"),statusCode:{200:function(w){v.refresh(w)},304:function(){v.app.hideLoadingIcon()}}})}};j.prototype.save=function(x,y,v,w){this.app.showLoadingIcon();$.ajax({cache:false,url:$("#board").data("save-url"),contentType:"application/json",type:"POST",processData:false,data:JSON.stringify({task_id:x,column_id:y,swimlane_id:w,position:v}),success:this.refresh.bind(this),error:this.app.hideLoadingIcon.bind(this)})};j.prototype.refresh=function(v){$("#board-container").replaceWith(v);this.app.refresh();this.app.swimlane.refresh();this.app.swimlane.listen();this.resizeColumnHeight();this.app.hideLoadingIcon();this.listen();this.dragAndDrop();this.compactView();this.restoreColumnViewMode()};j.prototype.resizeColumnHeight=function(){if($(".board-swimlane").length>1){$(".board-task-list").each(function(){if($(this).height()>500){$(this).height(500)}else{$(this).css("min-height",320)}})}else{$(".board-task-list").height($(window).height()-145)}};j.prototype.dragAndDrop=function(){var v=this;var w={forcePlaceholderSize:true,delay:300,distance:5,connectWith:".board-task-list",placeholder:"draggable-placeholder",items:".draggable-item",stop:function(x,y){y.item.removeClass("draggable-item-selected");v.save(y.item.attr("data-task-id"),y.item.parent().attr("data-column-id"),y.item.index()+1,y.item.parent().attr("data-swimlane-id"))},start:function(x,y){y.item.addClass("draggable-item-selected");y.placeholder.height(y.item.height())}};if($.support.touch){$(".task-board-sort-handle").css("display","inline");w.handle=".task-board-sort-handle"}$(".board-task-list").sortable(w)};j.prototype.listen=function(){var v=this;$(document).on("click",".task-board",function(w){if(w.target.tagName!="A"){window.location=$(this).data("task-url")}});$(document).on("click",".filter-toggle-scrolling",function(w){w.preventDefault();v.toggleCompactView()});$(document).on("click",".board-column-title",function(){v.toggleColumnViewMode($(this).data("column-id"))})};j.prototype.toggleCompactView=function(){var v=localStorage.getItem("horizontal_scroll")||1;localStorage.setItem("horizontal_scroll",v==0?1:0);this.compactView()};j.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")}};j.prototype.toggleCollapsedMode=function(){var v=this;this.app.showLoadingIcon();$.ajax({cache:false,url:$('.filter-display-mode:not([style="display: none;"]) a').attr("href"),success:function(w){$(".filter-display-mode").toggle();v.refresh(w)}})};j.prototype.restoreColumnViewMode=function(){var v=this;$("tr:first th").each(function(){var w=$(this).data("column-id");if(localStorage.getItem("hidden_column_"+w)){v.hideColumn(w)}})};j.prototype.toggleColumnViewMode=function(v){if(localStorage.getItem("hidden_column_"+v)){this.showColumn(v)}else{this.hideColumn(v)}};j.prototype.hideColumn=function(v){$(".board-column-"+v+" .board-column-expanded").hide();$(".board-column-"+v+" .board-column-collapsed").show();$(".board-column-header-"+v+" .board-column-expanded").hide();$(".board-column-header-"+v+" .board-column-collapsed").show();$(".board-column-header-"+v).each(function(){$(this).removeClass("board-column-compact");$(this).addClass("board-column-header-collapsed")});$(".board-column-"+v).each(function(){$(this).addClass("board-column-task-collapsed")});$(".board-column-"+v+" .board-rotation").each(function(){var w=$(".board-swimlane").position();$(this).css("width",$(".board-column-"+v+"").height())});localStorage.setItem("hidden_column_"+v,1)};j.prototype.showColumn=function(v){$(".board-column-"+v+" .board-column-expanded").show();$(".board-column-"+v+" .board-column-collapsed").hide();$(".board-column-header-"+v+" .board-column-expanded").show();$(".board-column-header-"+v+" .board-column-collapsed").hide();$(".board-column-header-"+v).removeClass("board-column-header-collapsed");$(".board-column-"+v).removeClass("board-column-task-collapsed");if(localStorage.getItem("horizontal_scroll")==0){$(".board-column-header-"+v).addClass("board-column-compact")}localStorage.removeItem("hidden_column_"+v)};j.prototype.keyboardShortcuts=function(){var v=this;Mousetrap.bind("c",function(){v.toggleCompactView()});Mousetrap.bind("s",function(){v.toggleCollapsedMode()});Mousetrap.bind("n",function(){v.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(w){var x=this.getAllCollapsed();var v=x.indexOf(w);if(v>-1){x.splice(v,1)}localStorage.setItem(this.getStorageKey(),JSON.stringify(x));$(".swimlane-row-"+w).css("display","table-row");$(".show-icon-swimlane-"+w).css("display","none");$(".hide-icon-swimlane-"+w).css("display","inline");$(".swimlane-task-count-"+w).css("display","inline")};g.prototype.collapse=function(v){var w=this.getAllCollapsed();if(w.indexOf(v)<0){w.push(v);localStorage.setItem(this.getStorageKey(),JSON.stringify(w))}$(".swimlane-row-"+v).css("display","none");$(".show-icon-swimlane-"+v).css("display","inline");$(".hide-icon-swimlane-"+v).css("display","none");$(".swimlane-task-count-"+v).css("display","none")};g.prototype.isCollapsed=function(v){return this.getAllCollapsed().indexOf(v)>-1};g.prototype.getAllCollapsed=function(){return JSON.parse(localStorage.getItem(this.getStorageKey()))||[]};g.prototype.refresh=function(){var w=this.getAllCollapsed();for(var v=0;v<w.length;v++){this.collapse(w[v])}};g.prototype.listen=function(){var v=this;$(document).on("click",".board-swimlane-toggle",function(x){x.preventDefault();var w=$(this).data("swimlane-id");if(v.isCollapsed(w)){v.expand(w)}else{v.collapse(w)}})};function c(v){this.app=v;this.data=[];this.options={container:"#gantt-chart",showWeekends:true,allowMoves:true,allowResizes:true,cellWidth:21,cellHeight:31,slideWidth:1000,vHeaderWidth:200}}c.prototype.saveRecord=function(v){this.app.showLoadingIcon();$.ajax({cache:false,url:$(this.options.container).data("save-url"),contentType:"application/json",type:"POST",processData:false,data:JSON.stringify(v),complete:this.app.hideLoadingIcon.bind(this)})};c.prototype.execute=function(){this.data=this.prepareData($(this.options.container).data("records"));var y=Math.floor((this.options.slideWidth/this.options.cellWidth)+5);var x=this.getDateRange(y);var v=x[0];var A=x[1];var w=$(this.options.container);var z=jQuery("<div>",{"class":"ganttview"});z.append(this.renderVerticalHeader());z.append(this.renderSlider(v,A));w.append(z);jQuery("div.ganttview-grid-row div.ganttview-grid-row-cell:last-child",w).addClass("last");jQuery("div.ganttview-hzheader-days div.ganttview-hzheader-day:last-child",w).addClass("last");jQuery("div.ganttview-hzheader-months div.ganttview-hzheader-month:last-child",w).addClass("last");if(!$(this.options.container).data("readonly")){this.listenForBlockResize(v);this.listenForBlockMove(v)}else{this.options.allowResizes=false;this.options.allowMoves=false}};c.prototype.renderVerticalHeader=function(){var z=jQuery("<div>",{"class":"ganttview-vtheader"});var w=jQuery("<div>",{"class":"ganttview-vtheader-item"});var y=jQuery("<div>",{"class":"ganttview-vtheader-series"});for(var v=0;v<this.data.length;v++){var x=jQuery("<span>").append(jQuery("<i>",{"class":"fa fa-info-circle tooltip",title:this.getVerticalHeaderTooltip(this.data[v])})).append("&nbsp;");if(this.data[v].type=="task"){x.append(jQuery("<a>",{href:this.data[v].link,target:"_blank",title:this.data[v].title}).append(this.data[v].title))}else{x.append(jQuery("<a>",{href:this.data[v].board_link,target:"_blank",title:$(this.options.container).data("label-board-link")}).append('<i class="fa fa-th"></i>')).append("&nbsp;").append(jQuery("<a>",{href:this.data[v].gantt_link,target:"_blank",title:$(this.options.container).data("label-gantt-link")}).append('<i class="fa fa-sliders"></i>')).append("&nbsp;").append(jQuery("<a>",{href:this.data[v].link,target:"_blank"}).append(this.data[v].title))}y.append(jQuery("<div>",{"class":"ganttview-vtheader-series-name"}).append(x))}w.append(y);z.append(w);return z};c.prototype.renderSlider=function(w,y){var v=jQuery("<div>",{"class":"ganttview-slide-container"});var x=this.getDates(w,y);v.append(this.renderHorizontalHeader(x));v.append(this.renderGrid(x));v.append(this.addBlockContainers());this.addBlocks(v,w);return v};c.prototype.renderHorizontalHeader=function(v){var D=jQuery("<div>",{"class":"ganttview-hzheader"});var B=jQuery("<div>",{"class":"ganttview-hzheader-months"});var A=jQuery("<div>",{"class":"ganttview-hzheader-days"});var z=0;for(var E in v){for(var x in v[E]){var F=v[E][x].length*this.options.cellWidth;z=z+F;B.append(jQuery("<div>",{"class":"ganttview-hzheader-month",css:{width:(F-1)+"px"}}).append($.datepicker.regional[$("body").data("js-lang")].monthNames[x]+" "+E));for(var C in v[E][x]){A.append(jQuery("<div>",{"class":"ganttview-hzheader-day"}).append(v[E][x][C].getDate()))}}}B.css("width",z+"px");A.css("width",z+"px");D.append(B).append(A);return D};c.prototype.renderGrid=function(v){var F=jQuery("<div>",{"class":"ganttview-grid"});var A=jQuery("<div>",{"class":"ganttview-grid-row"});for(var D in v){for(var x in v[D]){for(var C in v[D][x]){var z=jQuery("<div>",{"class":"ganttview-grid-row-cell"});if(this.options.showWeekends&&this.isWeekend(v[D][x][C])){z.addClass("ganttview-weekend")}A.append(z)}}}var E=jQuery("div.ganttview-grid-row-cell",A).length*this.options.cellWidth;A.css("width",E+"px");F.css("width",E+"px");for(var B=0;B<this.data.length;B++){F.append(A.clone())}return F};c.prototype.addBlockContainers=function(){var w=jQuery("<div>",{"class":"ganttview-blocks"});for(var v=0;v<this.data.length;v++){w.append(jQuery("<div>",{"class":"ganttview-block-container"}))}return w};c.prototype.addBlocks=function(w,v){var D=jQuery("div.ganttview-blocks div.ganttview-block-container",w);var x=0;for(var A=0;A<this.data.length;A++){var B=this.data[A];var E=this.daysBetween(B.start,B.end)+1;var z=this.daysBetween(v,B.start);var C=jQuery("<div>",{"class":"ganttview-block-text"});var y=jQuery("<div>",{"class":"ganttview-block tooltip"+(this.options.allowMoves?" ganttview-block-movable":""),title:this.getBarTooltip(this.data[A]),css:{width:((E*this.options.cellWidth)-9)+"px","margin-left":(z*this.options.cellWidth)+"px"}}).append(C);if(E>=2){C.append(this.data[A].progress)}y.data("record",this.data[A]);this.setBarColor(y,this.data[A]);y.append(jQuery("<div>",{css:{"z-index":0,position:"absolute",top:0,bottom:0,"background-color":B.color.border,width:B.progress,opacity:0.4}}));jQuery(D[x]).append(y);x=x+1}};c.prototype.getVerticalHeaderTooltip=function(w){var B="";if(w.type=="task"){B="<strong>"+w.column_title+"</strong> ("+w.progress+")<br/>"+w.title}else{var y=["managers","members"];for(var x in y){var z=y[x];if(!jQuery.isEmptyObject(w.users[z])){var A=jQuery("<ul>");for(var v in w.users[z]){A.append(jQuery("<li>").append(w.users[z][v]))}B+="<p><strong>"+$(this.options.container).data("label-"+z)+"</strong></p>"+A[0].outerHTML}}}return B};c.prototype.getBarTooltip=function(v){var w="";if(v.not_defined){w=$(this.options.container).data("label-not-defined")}else{if(v.type=="task"){w="<strong>"+v.progress+"</strong><br/>"+$(this.options.container).data("label-assignee")+" "+(v.assignee?v.assignee:"")+"<br/>"}w+=$(this.options.container).data("label-start-date")+" "+$.datepicker.formatDate("yy-mm-dd",v.start)+"<br/>";w+=$(this.options.container).data("label-end-date")+" "+$.datepicker.formatDate("yy-mm-dd",v.end)}return w};c.prototype.setBarColor=function(w,v){if(v.not_defined){w.addClass("ganttview-block-not-defined")}else{w.css("background-color",v.color.background);w.css("border-color",v.color.border)}};c.prototype.listenForBlockResize=function(v){var w=this;jQuery("div.ganttview-block",this.options.container).resizable({grid:this.options.cellWidth,handles:"e,w",delay:300,stop:function(){var x=jQuery(this);w.updateDataAndPosition(x,v);w.saveRecord(x.data("record"))}})};c.prototype.listenForBlockMove=function(v){var w=this;jQuery("div.ganttview-block",this.options.container).draggable({axis:"x",delay:300,grid:[this.options.cellWidth,this.options.cellWidth],stop:function(){var x=jQuery(this);w.updateDataAndPosition(x,v);w.saveRecord(x.data("record"))}})};c.prototype.updateDataAndPosition=function(A,y){var v=jQuery("div.ganttview-slide-container",this.options.container);var E=v.scrollLeft();var B=A.offset().left-v.offset().left-1+E;var D=A.data("record");D.not_defined=false;this.setBarColor(A,D);var x=Math.round(B/this.options.cellWidth);var C=this.addDays(this.cloneDate(y),x);D.start=C;var w=A.outerWidth();var z=Math.round(w/this.options.cellWidth)-1;D.end=this.addDays(this.cloneDate(C),z);if(D.type==="task"&&z>0){jQuery("div.ganttview-block-text",A).text(D.progress)}A.attr("title",this.getBarTooltip(D));A.data("record",D);A.css("top","").css("left","").css("position","relative").css("margin-left",B+"px")};c.prototype.getDates=function(z,v){var y=[];y[z.getFullYear()]=[];y[z.getFullYear()][z.getMonth()]=[z];var x=z;while(this.compareDate(x,v)==-1){var w=this.addDays(this.cloneDate(x),1);if(!y[w.getFullYear()]){y[w.getFullYear()]=[]}if(!y[w.getFullYear()][w.getMonth()]){y[w.getFullYear()][w.getMonth()]=[]}y[w.getFullYear()][w.getMonth()].push(w);x=w}return y};c.prototype.prepareData=function(x){for(var w=0;w<x.length;w++){var y=new Date(x[w].start[0],x[w].start[1]-1,x[w].start[2],0,0,0,0);x[w].start=y;var v=new Date(x[w].end[0],x[w].end[1]-1,x[w].end[2],0,0,0,0);x[w].end=v}return x};c.prototype.getDateRange=function(x){var A=new Date();var w=new Date();for(var y=0;y<this.data.length;y++){var z=new Date();z.setTime(Date.parse(this.data[y].start));var v=new Date();v.setTime(Date.parse(this.data[y].end));if(y==0){A=z;w=v}if(this.compareDate(A,z)==1){A=z}if(this.compareDate(w,v)==-1){w=v}}if(this.daysBetween(A,w)<x){w=this.addDays(this.cloneDate(A),x)}A.setDate(A.getDate()-1);return[A,w]};c.prototype.daysBetween=function(y,v){if(!y||!v){return 0}var x=0,w=this.cloneDate(y);while(this.compareDate(w,v)==-1){x=x+1;this.addDays(w,1)}return x};c.prototype.isWeekend=function(v){return v.getDay()%6==0};c.prototype.cloneDate=function(v){return new Date(v.getTime())};c.prototype.addDays=function(v,w){v.setDate(v.getDate()+w*1);return v};c.prototype.compareDate=function(w,v){if(isNaN(w)||isNaN(v)){throw new Error(w+" - "+v)}else{if(w instanceof Date&&v instanceof Date){return(w<v)?-1:(w>v)?1:0}else{throw new TypeError(w+" - "+v)}}};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"))})};function q(){}q.prototype.execute=function(){var x=$("#chart").data("metrics");var w=[];for(var v=0;v<x.length;v++){w.push([x[v].column_title,x[v].nb_tasks])}c3.generate({data:{columns:w,type:"donut"}})};function n(){}n.prototype.execute=function(){var x=$("#chart").data("metrics");var w=[];for(var v=0;v<x.length;v++){w.push([x[v].user,x[v].nb_tasks])}c3.generate({data:{columns:w,type:"donut"}})};function d(){}d.prototype.execute=function(){var B=$("#chart").data("metrics");var A=[];var v=[];var w=[];var y=d3.time.format("%Y-%m-%d");var C=d3.time.format($("#chart").data("date-format"));for(var z=0;z<B.length;z++){for(var x=0;x<B[z].length;x++){if(z==0){A.push([B[z][x]]);if(x>0){v.push(B[z][x])}}else{A[x].push(B[z][x]);if(x==0){w.push(C(y.parse(B[z][x])))}}}}c3.generate({data:{columns:A,type:"area-spline",groups:[v]},axis:{x:{type:"category",categories:w}}})};function m(){}m.prototype.execute=function(){var A=$("#chart").data("metrics");var z=[[$("#chart").data("label-total")]];var v=[];var x=d3.time.format("%Y-%m-%d");var B=d3.time.format($("#chart").data("date-format"));for(var y=0;y<A.length;y++){for(var w=0;w<A[y].length;w++){if(y==0){z.push([A[y][w]])}else{z[w+1].push(A[y][w]);if(w>0){if(z[0][y]==undefined){z[0].push(0)}z[0][y]+=A[y][w]}if(w==0){v.push(B(x.parse(A[y][w])))}}}}c3.generate({data:{columns:z},axis:{x:{type:"category",categories:v}}})};function h(v){this.app=v}h.prototype.execute=function(){var x=$("#chart").data("metrics");var y=[$("#chart").data("label")];var v=[];for(var w in x){y.push(x[w].average);v.push(x[w].title)}c3.generate({data:{columns:[y],type:"bar"},bar:{width:{ratio:0.5}},axis:{x:{type:"category",categories:v},y:{tick:{format:this.app.formatDuration}}},legend:{show:false}})};function u(v){this.app=v}u.prototype.execute=function(){var x=$("#chart").data("metrics");var y=[$("#chart").data("label")];var v=[];for(var w=0;w<x.length;w++){y.push(x[w].time_spent);v.push(x[w].title)}c3.generate({data:{columns:[y],type:"bar"},bar:{width:{ratio:0.5}},axis:{x:{type:"category",categories:v},y:{tick:{format:this.app.formatDuration}}},legend:{show:false}})};function s(v){this.app=v}s.prototype.execute=function(){var B=$("#chart").data("metrics");var A=[$("#chart").data("label-cycle")];var x=[$("#chart").data("label-lead")];var w=[];var z={};z[$("#chart").data("label-cycle")]="area";z[$("#chart").data("label-lead")]="area-spline";var v={};v[$("#chart").data("label-lead")]="#afb42b";v[$("#chart").data("label-cycle")]="#4e342e";for(var y=0;y<B.length;y++){A.push(parseInt(B[y].avg_cycle_time));x.push(parseInt(B[y].avg_lead_time));w.push(B[y].day)}c3.generate({data:{columns:[x,A],types:z,colors:v},axis:{x:{type:"category",categories:w},y:{tick:{format:this.app.formatDuration}}}})};function t(){this.routes={}}t.prototype.addRoute=function(w,v){this.routes[w]=v};t.prototype.dispatch=function(w){for(var x in this.routes){if(document.getElementById(x)){var v=Object.create(this.routes[x].prototype);this.routes[x].apply(v,[w]);v.execute();break}}};jQuery(document).ready(function(){var w=new l();var v=new t();v.addRoute("board",j);v.addRoute("calendar",i);v.addRoute("screenshot-zone",e);v.addRoute("analytic-task-repartition",q);v.addRoute("analytic-user-repartition",n);v.addRoute("analytic-cfd",d);v.addRoute("analytic-burndown",m);v.addRoute("analytic-avg-time-column",h);v.addRoute("analytic-task-time-column",u);v.addRoute("analytic-lead-cycle-time",s);v.addRoute("gantt-chart",c);v.dispatch(w);w.listen()})})(); \ No newline at end of file
diff --git a/assets/js/src/App.js b/assets/js/src/App.js
index 2872baf6..02aa7693 100644
--- a/assets/js/src/App.js
+++ b/assets/js/src/App.js
@@ -7,6 +7,7 @@ function App() {
this.dropdown = new Dropdown();
this.tooltip = new Tooltip(this);
this.popover = new Popover(this);
+ this.task = new Task();
this.keyboardShortcuts();
this.chosen();
this.poll();
@@ -34,6 +35,7 @@ App.prototype.listen = function() {
this.tooltip.listen();
this.dropdown.listen();
this.search.listen();
+ this.task.listen();
this.search.focus();
this.taskAutoComplete();
this.datePicker();
diff --git a/assets/js/src/BudgetChart.js b/assets/js/src/BudgetChart.js
deleted file mode 100644
index 9ab0d5a9..00000000
--- a/assets/js/src/BudgetChart.js
+++ /dev/null
@@ -1,55 +0,0 @@
-function BudgetChart() {
-}
-
-BudgetChart.prototype.execute = function() {
- var categories = [];
- var metrics = $("#chart").data("metrics");
- var labels = $("#chart").data("labels");
- var inputFormat = d3.time.format("%Y-%m-%d");
- var outputFormat = d3.time.format($("#chart").data("date-format"));
-
- var columns = [
- [labels["in"]],
- [labels["left"]],
- [labels["out"]]
- ];
-
- var colors = {};
- colors[labels["in"]] = '#5858FA';
- colors[labels["left"]] = '#04B404';
- colors[labels["out"]] = '#DF3A01';
-
- for (var i = 0; i < metrics.length; i++) {
- categories.push(outputFormat(inputFormat.parse(metrics[i]["date"])));
- columns[0].push(metrics[i]["in"]);
- columns[1].push(metrics[i]["left"]);
- columns[2].push(metrics[i]["out"]);
- }
-
- c3.generate({
- data: {
- columns: columns,
- colors: colors,
- type : 'bar'
- },
- bar: {
- width: {
- ratio: 0.25
- }
- },
- grid: {
- x: {
- show: true
- },
- y: {
- show: true
- }
- },
- axis: {
- x: {
- type: 'category',
- categories: categories
- }
- }
- });
-};
diff --git a/assets/js/src/Dropdown.js b/assets/js/src/Dropdown.js
index 6b40d1fc..e04603d8 100644
--- a/assets/js/src/Dropdown.js
+++ b/assets/js/src/Dropdown.js
@@ -11,26 +11,31 @@ Dropdown.prototype.listen = function() {
$(document).on('click', '.dropdown-menu', function(e) {
e.preventDefault();
e.stopImmediatePropagation();
+ self.close();
var submenu = $(this).next('ul');
var submenuHeight = 240;
+ var offset = $(this).offset();
+ var height = $(this).height();
+
+ // Clone the submenu outside of the column to avoid clipping issue with overflow
+ $("body").append(jQuery("<div>", {"id": "dropdown"}));
+ submenu.clone().appendTo("#dropdown");
- if (! submenu.is(':visible')) {
- self.close();
+ var clone = $("#dropdown ul");
+ clone.css('left', offset.left);
- if ($(this).offset().top + submenuHeight - $(window).scrollTop() > $(window).height()) {
- submenu.addClass('dropdown-submenu-open dropdown-submenu-top');
- }
- else {
- submenu.addClass('dropdown-submenu-open');
- }
+ if (offset.top + submenuHeight - $(window).scrollTop() > $(window).height()) {
+ clone.css('top', offset.top - submenuHeight - height);
}
else {
- self.close();
+ clone.css('top', offset.top + height);
}
+
+ clone.addClass('dropdown-submenu-open');
});
};
Dropdown.prototype.close = function() {
- $('.dropdown-submenu-open').removeClass('dropdown-submenu-open');
+ $("#dropdown").remove();
};
diff --git a/assets/js/src/Gantt.js b/assets/js/src/Gantt.js
index cac5f295..380371d1 100644
--- a/assets/js/src/Gantt.js
+++ b/assets/js/src/Gantt.js
@@ -71,7 +71,7 @@ Gantt.prototype.renderVerticalHeader = function() {
.append("&nbsp;");
if (this.data[i].type == "task") {
- content.append(jQuery("<a>", {"href": this.data[i].link, "target": "_blank"}).append(this.data[i].title));
+ content.append(jQuery("<a>", {"href": this.data[i].link, "target": "_blank", "title": this.data[i].title}).append(this.data[i].title));
}
else {
content
@@ -198,6 +198,19 @@ Gantt.prototype.addBlocks = function(slider, start) {
block.data("record", this.data[i]);
this.setBarColor(block, this.data[i]);
+
+ block.append(jQuery("<div>", {
+ "css": {
+ "z-index": 0,
+ "position": "absolute",
+ "top": 0,
+ "bottom": 0,
+ "background-color": series.color.border,
+ "width": series.progress,
+ "opacity": 0.4
+ }
+ }));
+
jQuery(rows[rowIdx]).append(block);
rowIdx = rowIdx + 1;
}
diff --git a/assets/js/src/Markdown.js b/assets/js/src/Markdown.js
index 3a51ffce..50293729 100644
--- a/assets/js/src/Markdown.js
+++ b/assets/js/src/Markdown.js
@@ -4,14 +4,15 @@ function Markdown() {
Markdown.prototype.showPreview = function(e) {
e.preventDefault();
- var link = $(this);
- var nav = $(this).closest("ul");
var write = $(".write-area");
var preview = $(".preview-area");
var textarea = $("textarea");
+ $("#markdown-write").parent().removeClass("form-tab-selected");
+ $("#markdown-preview").parent().addClass("form-tab-selected");
+
var request = $.ajax({
- url: "?controller=app&action=preview", // TODO: remoe harcoded url
+ url: $("body").data("markdown-preview-url"),
contentType: "application/json",
type: "POST",
processData: false,
@@ -22,9 +23,6 @@ Markdown.prototype.showPreview = function(e) {
});
request.done(function(data) {
- nav.find("li").removeClass("form-tab-selected");
- link.parent().addClass("form-tab-selected");
-
preview.find(".markdown").html(data)
preview.css("height", textarea.css("height"));
preview.css("width", textarea.css("width"));
@@ -37,8 +35,8 @@ Markdown.prototype.showPreview = function(e) {
Markdown.prototype.showWriter = function(e) {
e.preventDefault();
- $(this).closest("ul").find("li").removeClass("form-tab-selected")
- $(this).parent().addClass("form-tab-selected");
+ $("#markdown-write").parent().addClass("form-tab-selected");
+ $("#markdown-preview").parent().removeClass("form-tab-selected");
$(".write-area").show();
$(".preview-area").hide();
diff --git a/assets/js/src/Popover.js b/assets/js/src/Popover.js
index 2fd951b4..b978c087 100644
--- a/assets/js/src/Popover.js
+++ b/assets/js/src/Popover.js
@@ -15,7 +15,8 @@ Popover.prototype.open = function(link) {
$.get(link, function(content) {
$("body").append('<div id="popover-container"><div id="popover-content">' + content + '</div></div>');
self.router.dispatch();
- self.app.listen();
+ self.app.refresh();
+ self.afterOpen();
});
};
@@ -48,3 +49,29 @@ Popover.prototype.listen = function() {
$(document).on("click", "#popover-container", this.close.bind(this));
$(document).on("click", "#popover-content", function(e) { e.stopPropagation(); });
};
+
+Popover.prototype.afterOpen = function() {
+ var self = this;
+ var taskForm = $("#task-form");
+
+ if (taskForm) {
+ taskForm.on("submit", function(e) {
+ e.preventDefault();
+
+ $.ajax({
+ type: "POST",
+ url: taskForm.attr("action"),
+ data: taskForm.serialize(),
+ success: function(data, textStatus, request) {
+ if (request.getResponseHeader("X-Ajax-Redirect")) {
+ window.location = request.getResponseHeader("X-Ajax-Redirect");
+ }
+ else {
+ $("#popover-content").html(data);
+ self.afterOpen();
+ }
+ }
+ });
+ });
+ }
+};
diff --git a/assets/js/src/Router.js b/assets/js/src/Router.js
index 6993e5c3..0c96262c 100644
--- a/assets/js/src/Router.js
+++ b/assets/js/src/Router.js
@@ -27,7 +27,6 @@ jQuery(document).ready(function() {
router.addRoute('analytic-user-repartition', UserRepartitionChart);
router.addRoute('analytic-cfd', CumulativeFlowDiagram);
router.addRoute('analytic-burndown', BurndownChart);
- router.addRoute('budget-chart', BudgetChart);
router.addRoute('analytic-avg-time-column', AvgTimeColumnChart);
router.addRoute('analytic-task-time-column', TaskTimeColumnChart);
router.addRoute('analytic-lead-cycle-time', LeadCycleTimeChart);
diff --git a/assets/js/src/Swimlane.js b/assets/js/src/Swimlane.js
index ce18dbfa..0edb4821 100644
--- a/assets/js/src/Swimlane.js
+++ b/assets/js/src/Swimlane.js
@@ -1,67 +1,69 @@
-function Swimlane() {
-}
-
-Swimlane.prototype.getStorageKey = function() {
- return "hidden_swimlanes_" + $("#board").data("project-id");
-};
-
-Swimlane.prototype.expand = function(swimlaneId) {
- var swimlaneIds = this.getAllCollapsed();
- var index = swimlaneIds.indexOf(swimlaneId);
-
- if (index > -1) {
- swimlaneIds.splice(index, 1);
- }
-
- localStorage.setItem(this.getStorageKey(), JSON.stringify(swimlaneIds));
-
- $('.swimlane-row-' + swimlaneId).css('display', 'table-row');
- $('.show-icon-swimlane-' + swimlaneId).css('display', 'none');
- $('.hide-icon-swimlane-' + swimlaneId).css('display', 'inline');
-};
-
-Swimlane.prototype.collapse = function(swimlaneId) {
- var swimlaneIds = this.getAllCollapsed();
-
- if (swimlaneIds.indexOf(swimlaneId) < 0) {
- swimlaneIds.push(swimlaneId);
- localStorage.setItem(this.getStorageKey(), JSON.stringify(swimlaneIds));
- }
-
- $('.swimlane-row-' + swimlaneId).css('display', 'none');
- $('.show-icon-swimlane-' + swimlaneId).css('display', 'inline');
- $('.hide-icon-swimlane-' + swimlaneId).css('display', 'none');
-};
-
-Swimlane.prototype.isCollapsed = function(swimlaneId) {
- return this.getAllCollapsed().indexOf(swimlaneId) > -1;
-};
-
-Swimlane.prototype.getAllCollapsed = function() {
- return JSON.parse(localStorage.getItem(this.getStorageKey())) || [];
-};
-
-Swimlane.prototype.refresh = function() {
- var swimlaneIds = this.getAllCollapsed();
-
- for (var i = 0; i < swimlaneIds.length; i++) {
- this.collapse(swimlaneIds[i]);
- }
-};
-
-Swimlane.prototype.listen = function() {
- var self = this;
-
- $(document).on('click', ".board-swimlane-toggle", function(e) {
- e.preventDefault();
-
- var swimlaneId = $(this).data('swimlane-id');
-
- if (self.isCollapsed(swimlaneId)) {
- self.expand(swimlaneId);
- }
- else {
- self.collapse(swimlaneId);
- }
- });
-};
+function Swimlane() {
+}
+
+Swimlane.prototype.getStorageKey = function() {
+ return "hidden_swimlanes_" + $("#board").data("project-id");
+};
+
+Swimlane.prototype.expand = function(swimlaneId) {
+ var swimlaneIds = this.getAllCollapsed();
+ var index = swimlaneIds.indexOf(swimlaneId);
+
+ if (index > -1) {
+ swimlaneIds.splice(index, 1);
+ }
+
+ localStorage.setItem(this.getStorageKey(), JSON.stringify(swimlaneIds));
+
+ $('.swimlane-row-' + swimlaneId).css('display', 'table-row');
+ $('.show-icon-swimlane-' + swimlaneId).css('display', 'none');
+ $('.hide-icon-swimlane-' + swimlaneId).css('display', 'inline');
+ $('.swimlane-task-count-' + swimlaneId).css('display', 'inline');
+};
+
+Swimlane.prototype.collapse = function(swimlaneId) {
+ var swimlaneIds = this.getAllCollapsed();
+
+ if (swimlaneIds.indexOf(swimlaneId) < 0) {
+ swimlaneIds.push(swimlaneId);
+ localStorage.setItem(this.getStorageKey(), JSON.stringify(swimlaneIds));
+ }
+
+ $('.swimlane-row-' + swimlaneId).css('display', 'none');
+ $('.show-icon-swimlane-' + swimlaneId).css('display', 'inline');
+ $('.hide-icon-swimlane-' + swimlaneId).css('display', 'none');
+ $('.swimlane-task-count-' + swimlaneId).css('display', 'none');
+};
+
+Swimlane.prototype.isCollapsed = function(swimlaneId) {
+ return this.getAllCollapsed().indexOf(swimlaneId) > -1;
+};
+
+Swimlane.prototype.getAllCollapsed = function() {
+ return JSON.parse(localStorage.getItem(this.getStorageKey())) || [];
+};
+
+Swimlane.prototype.refresh = function() {
+ var swimlaneIds = this.getAllCollapsed();
+
+ for (var i = 0; i < swimlaneIds.length; i++) {
+ this.collapse(swimlaneIds[i]);
+ }
+};
+
+Swimlane.prototype.listen = function() {
+ var self = this;
+
+ $(document).on('click', ".board-swimlane-toggle", function(e) {
+ e.preventDefault();
+
+ var swimlaneId = $(this).data('swimlane-id');
+
+ if (self.isCollapsed(swimlaneId)) {
+ self.expand(swimlaneId);
+ }
+ else {
+ self.collapse(swimlaneId);
+ }
+ });
+};
diff --git a/assets/js/src/Task.js b/assets/js/src/Task.js
new file mode 100644
index 00000000..b3dc1b63
--- /dev/null
+++ b/assets/js/src/Task.js
@@ -0,0 +1,10 @@
+function Task() {
+}
+
+Task.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"));
+ });
+};
diff --git a/composer.json b/composer.json
index 1eefe1f8..223aa1fc 100644
--- a/composer.json
+++ b/composer.json
@@ -7,18 +7,22 @@
"eluceo/ical": "0.8.0",
"erusev/parsedown" : "1.5.4",
"fabiang/xmpp" : "0.6.1",
- "fguillot/json-rpc" : "1.0.1",
- "fguillot/picodb" : "1.0.1",
+ "fguillot/json-rpc" : "1.0.2",
+ "fguillot/picodb" : "1.0.2",
"fguillot/simpleLogger" : "1.0.0",
"fguillot/simple-validator" : "1.0.0",
- "nickcernis/html-to-markdown" : "2.2.1",
+ "league/html-to-markdown" : "~4.0",
"pimple/pimple" : "~3.0",
- "swiftmailer/swiftmailer" : "@stable",
- "symfony/console" : "@stable",
- "symfony/event-dispatcher" : "~2.6",
+ "swiftmailer/swiftmailer" : "~5.4",
+ "symfony/console" : "~2.7",
+ "symfony/event-dispatcher" : "~2.7",
"gregwar/captcha": "1.*"
},
"autoload" : {
+ "classmap" : ["app/"],
+ "psr-4" : {
+ "Plugin\\": "plugins/"
+ },
"psr-0" : {
"" : "app/"
},
diff --git a/composer.lock b/composer.lock
index 2afa4785..415305cf 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
- "hash": "70930e0b92d7ff0208ed435fd3accd30",
+ "hash": "10de302e730ebc3076393a3a38d1964f",
"packages": [
{
"name": "christian-riesen/base32",
@@ -260,16 +260,16 @@
},
{
"name": "fguillot/json-rpc",
- "version": "v1.0.1",
+ "version": "v1.0.2",
"source": {
"type": "git",
"url": "https://github.com/fguillot/JsonRPC.git",
- "reference": "9a117e964c4c6ad026da7ae1ca155f7686e3deaf"
+ "reference": "265cf039c2823f684349de78c0c03a597992bea9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/fguillot/JsonRPC/zipball/9a117e964c4c6ad026da7ae1ca155f7686e3deaf",
- "reference": "9a117e964c4c6ad026da7ae1ca155f7686e3deaf",
+ "url": "https://api.github.com/repos/fguillot/JsonRPC/zipball/265cf039c2823f684349de78c0c03a597992bea9",
+ "reference": "265cf039c2823f684349de78c0c03a597992bea9",
"shasum": ""
},
"require": {
@@ -292,20 +292,20 @@
],
"description": "Simple Json-RPC client/server library that just works",
"homepage": "https://github.com/fguillot/JsonRPC",
- "time": "2015-08-07 22:31:21"
+ "time": "2015-09-12 16:27:13"
},
{
"name": "fguillot/picodb",
- "version": "v1.0.1",
+ "version": "v1.0.2",
"source": {
"type": "git",
"url": "https://github.com/fguillot/picoDb.git",
- "reference": "8a311523d114180e04a1e08ced6766f26d7ebbae"
+ "reference": "61f492c125d9195ce869447e2b2450adeb3b01d6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/fguillot/picoDb/zipball/8a311523d114180e04a1e08ced6766f26d7ebbae",
- "reference": "8a311523d114180e04a1e08ced6766f26d7ebbae",
+ "url": "https://api.github.com/repos/fguillot/picoDb/zipball/61f492c125d9195ce869447e2b2450adeb3b01d6",
+ "reference": "61f492c125d9195ce869447e2b2450adeb3b01d6",
"shasum": ""
},
"require": {
@@ -329,7 +329,7 @@
],
"description": "Minimalist database query builder",
"homepage": "https://github.com/fguillot/picoDb",
- "time": "2015-08-13 01:44:29"
+ "time": "2015-08-27 23:33:16"
},
{
"name": "fguillot/simple-validator",
@@ -453,31 +453,38 @@
"time": "2015-05-13 06:34:33"
},
{
- "name": "nickcernis/html-to-markdown",
- "version": "2.2.1",
+ "name": "league/html-to-markdown",
+ "version": "4.0.1",
"source": {
"type": "git",
- "url": "https://github.com/nickcernis/html-to-markdown.git",
- "reference": "7263d2ce65011b050fa7ecda0cbe09b23e84271d"
+ "url": "https://github.com/thephpleague/html-to-markdown.git",
+ "reference": "c496c27e01b9dce310e03afbcdf783347738f67b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nickcernis/html-to-markdown/zipball/7263d2ce65011b050fa7ecda0cbe09b23e84271d",
- "reference": "7263d2ce65011b050fa7ecda0cbe09b23e84271d",
+ "url": "https://api.github.com/repos/thephpleague/html-to-markdown/zipball/c496c27e01b9dce310e03afbcdf783347738f67b",
+ "reference": "c496c27e01b9dce310e03afbcdf783347738f67b",
"shasum": ""
},
"require": {
- "php": ">=5.3"
+ "ext-dom": "*",
+ "ext-xml": "*",
+ "php": ">=5.3.3"
},
"require-dev": {
- "php": ">=5.3.3",
- "phpunit/phpunit": "4.*"
+ "phpunit/phpunit": "4.*",
+ "scrutinizer/ocular": "~1.1"
},
"type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.1-dev"
+ }
+ },
"autoload": {
- "classmap": [
- "HTML_To_Markdown.php"
- ]
+ "psr-4": {
+ "League\\HTMLToMarkdown\\": "src/"
+ }
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@@ -485,31 +492,38 @@
],
"authors": [
{
+ "name": "Colin O'Dell",
+ "email": "colinodell@gmail.com",
+ "homepage": "http://www.colinodell.com",
+ "role": "Lead Developer"
+ },
+ {
"name": "Nick Cernis",
"email": "nick@cern.is",
- "homepage": "http://modernnerd.net"
+ "homepage": "http://modernnerd.net",
+ "role": "Original Author"
}
],
"description": "An HTML-to-markdown conversion helper for PHP",
- "homepage": "https://github.com/nickcernis/html-to-markdown",
+ "homepage": "https://github.com/thephpleague/html-to-markdown",
"keywords": [
"html",
"markdown"
],
- "time": "2015-02-22 12:59:02"
+ "time": "2015-09-01 22:39:54"
},
{
"name": "pimple/pimple",
- "version": "v3.0.1",
+ "version": "v3.0.2",
"source": {
"type": "git",
"url": "https://github.com/silexphp/Pimple.git",
- "reference": "3313af5935dbc560fab845b76a1ca351b47855af"
+ "reference": "a30f7d6e57565a2e1a316e1baf2a483f788b258a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/silexphp/Pimple/zipball/3313af5935dbc560fab845b76a1ca351b47855af",
- "reference": "3313af5935dbc560fab845b76a1ca351b47855af",
+ "url": "https://api.github.com/repos/silexphp/Pimple/zipball/a30f7d6e57565a2e1a316e1baf2a483f788b258a",
+ "reference": "a30f7d6e57565a2e1a316e1baf2a483f788b258a",
"shasum": ""
},
"require": {
@@ -536,13 +550,13 @@
"email": "fabien@symfony.com"
}
],
- "description": "Pimple is a simple Dependency Injection Container for PHP 5.3",
+ "description": "Pimple, a simple Dependency Injection Container",
"homepage": "http://pimple.sensiolabs.org",
"keywords": [
"container",
"dependency injection"
],
- "time": "2015-07-30 09:57:46"
+ "time": "2015-09-11 15:10:35"
},
{
"name": "psr/log",
@@ -637,16 +651,16 @@
},
{
"name": "symfony/console",
- "version": "v2.7.3",
+ "version": "v2.7.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/Console.git",
- "reference": "d6cf02fe73634c96677e428f840704bfbcaec29e"
+ "reference": "9ff9032151186bd66ecee727d728f1319f52d1d8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/Console/zipball/d6cf02fe73634c96677e428f840704bfbcaec29e",
- "reference": "d6cf02fe73634c96677e428f840704bfbcaec29e",
+ "url": "https://api.github.com/repos/symfony/Console/zipball/9ff9032151186bd66ecee727d728f1319f52d1d8",
+ "reference": "9ff9032151186bd66ecee727d728f1319f52d1d8",
"shasum": ""
},
"require": {
@@ -690,20 +704,20 @@
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
- "time": "2015-07-28 15:18:12"
+ "time": "2015-09-03 11:40:38"
},
{
"name": "symfony/event-dispatcher",
- "version": "v2.7.3",
+ "version": "v2.7.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/EventDispatcher.git",
- "reference": "9310b5f9a87ec2ea75d20fec0b0017c77c66dac3"
+ "reference": "b58c916f1db03a611b72dd702564f30ad8fe83fa"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/9310b5f9a87ec2ea75d20fec0b0017c77c66dac3",
- "reference": "9310b5f9a87ec2ea75d20fec0b0017c77c66dac3",
+ "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/b58c916f1db03a611b72dd702564f30ad8fe83fa",
+ "reference": "b58c916f1db03a611b72dd702564f30ad8fe83fa",
"shasum": ""
},
"require": {
@@ -748,22 +762,22 @@
],
"description": "Symfony EventDispatcher Component",
"homepage": "https://symfony.com",
- "time": "2015-06-18 19:21:56"
+ "time": "2015-08-24 07:13:45"
}
],
"packages-dev": [
{
"name": "symfony/stopwatch",
- "version": "v2.7.3",
+ "version": "v2.7.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/Stopwatch.git",
- "reference": "b07a866719bbac5294c67773340f97b871733310"
+ "reference": "abc61bac76fb10ffa2c6373d7932bc35190dbf3b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/Stopwatch/zipball/b07a866719bbac5294c67773340f97b871733310",
- "reference": "b07a866719bbac5294c67773340f97b871733310",
+ "url": "https://api.github.com/repos/symfony/Stopwatch/zipball/abc61bac76fb10ffa2c6373d7932bc35190dbf3b",
+ "reference": "abc61bac76fb10ffa2c6373d7932bc35190dbf3b",
"shasum": ""
},
"require": {
@@ -799,15 +813,12 @@
],
"description": "Symfony Stopwatch Component",
"homepage": "https://symfony.com",
- "time": "2015-07-01 18:23:16"
+ "time": "2015-08-24 07:13:45"
}
],
"aliases": [],
"minimum-stability": "stable",
- "stability-flags": {
- "swiftmailer/swiftmailer": 0,
- "symfony/console": 0
- },
+ "stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
diff --git a/config.default.php b/config.default.php
index d5d1b30d..d3e18b8a 100644
--- a/config.default.php
+++ b/config.default.php
@@ -100,10 +100,21 @@ define('LDAP_ACCOUNT_FULLNAME', 'displayname');
// Name of an attribute of the user account object which should be used as the email of the user.
define('LDAP_ACCOUNT_EMAIL', 'mail');
-// Name of an attribute of the user account object which should be used as the id of the user.
+// Name of an attribute of the user account object which should be used as the id of the user. (optional)
// Example for ActiveDirectory: 'samaccountname'
// Example for OpenLDAP: 'uid'
-define('LDAP_ACCOUNT_ID', 'samaccountname');
+define('LDAP_ACCOUNT_ID', '');
+
+// LDAP Attribute for group membership
+define('LDAP_ACCOUNT_MEMBEROF', 'memberof');
+
+// DN for administrators
+// Example: CN=Kanboard Admins,CN=Users,DC=kanboard,DC=local
+define('LDAP_GROUP_ADMIN_DN', '');
+
+// DN for project administrators
+// Example: CN=Kanboard Project Admins,CN=Users,DC=kanboard,DC=local
+define('LDAP_GROUP_PROJECT_ADMIN_DN', '');
// By default Kanboard lowercase the ldap username to avoid duplicate users (the database is case sensitive)
// Set to true if you want to preserve the case
diff --git a/doc/api-json-rpc.markdown b/doc/api-json-rpc.markdown
index 61852933..b11f256b 100644
--- a/doc/api-json-rpc.markdown
+++ b/doc/api-json-rpc.markdown
@@ -1708,6 +1708,7 @@ Response example:
- Parameters:
- **swimlane_id** (integer, required)
- **name** (string, required)
+ - **description** (string, optional)
- Result on success: **true**
- Result on failure: **false**
@@ -1741,6 +1742,7 @@ Response example:
- Parameters:
- **project_id** (integer, required)
- **name** (string, required)
+ - **description** (string, optional)
- Result on success: **swimlane_id**
- Result on failure: **false**
diff --git a/doc/closing-tasks.markdown b/doc/closing-tasks.markdown
index 235387a9..018acace 100644
--- a/doc/closing-tasks.markdown
+++ b/doc/closing-tasks.markdown
@@ -1,11 +1,11 @@
Closing tasks
=============
-When a task is closed, they are hidden from the board.
+When a task is closed, it is hidden from the board.
However, you can always access to the list of closed tasks by using the query **status:closed** in any search form or simply choose **Closed tasks** from the filter dropdown.
-There are two different way to close a task, from the task dropdown menu on the board:
+There are two different ways to close a task, from the task dropdown menu on the board:
![Close a task from dropdown menu](http://kanboard.net/screenshots/documentation/menu-close-task.png)
@@ -13,4 +13,4 @@ Or from the task sidebar menu in the task detail view:
![Close a task](http://kanboard.net/screenshots/documentation/closing-tasks.png)
-Note: When you close a task, all subtasks not completed will be changed to the status "Done". \ No newline at end of file
+Note: When you close a task, all subtasks not completed will be changed to the status "Done".
diff --git a/doc/config.markdown b/doc/config.markdown
index b5c3ce0d..5473ef9b 100644
--- a/doc/config.markdown
+++ b/doc/config.markdown
@@ -132,6 +132,17 @@ define('LDAP_ACCOUNT_EMAIL', 'mail');
// Example for OpenLDAP: 'uid'
define('LDAP_ACCOUNT_ID', 'samaccountname');
+// LDAP Attribute for group membership
+define('LDAP_ACCOUNT_MEMBEROF', 'memberof');
+
+// DN for administrators
+// Example: CN=Kanboard Admins,CN=Users,DC=kanboard,DC=local
+define('LDAP_GROUP_ADMIN_DN', '');
+
+// DN for project administrators
+// Example: CN=Kanboard Project Admins,CN=Users,DC=kanboard,DC=local
+define('LDAP_GROUP_PROJECT_ADMIN_DN', '');
+
// By default Kanboard lowercase the ldap username to avoid duplicate users (the database is case sensitive)
// Set to true if you want to preserve the case
define('LDAP_USERNAME_CASE_SENSITIVE', false);
diff --git a/doc/fr/analytics.markdown b/doc/fr/analytics.markdown
new file mode 100644
index 00000000..951ade04
--- /dev/null
+++ b/doc/fr/analytics.markdown
@@ -0,0 +1,70 @@
+Analytique
+=========
+
+Chaque projet dispose d'une section analytique. En fonction de la façon dont vous utilisez Kanboard, vous pourrez voir les rapports suivants :
+
+Répartition des utilisateurs
+----------------
+
+![Répartition des utilisateurs](http://kanboard.net/screenshots/documentation/user-repartition.png)
+
+Ce graphique circulaire affiche le nombre de tâches assignées par utilisateur.
+
+Distribution des tâches
+-----------------
+
+![Distribution des tâches](http://kanboard.net/screenshots/documentation/task-distribution.png)
+
+Ce graphique circulaire donne une vue d'ensemble du nombre de tâches ouvertes par colonne.
+
+Diagramme de flux cumulé
+-----------------------
+
+![Diagramme de flux cumulé](http://kanboard.net/screenshots/documentation/cfd.png)
+
+- Ce graphique affiche le nombre de tâches de façon cumulée pour chaque colonne en fonction du temps passé.
+- Chaque jour, le nombre total de tâches est enregistré pour chaque colonne.
+- Si vous souhaitez exclure les tâches terminées, modifiez les [paramètres du projet global](project-configuration.markdown).
+
+Remarque : il faut au moins deux jours de données pour que le graphique apparaisse.
+
+Graphique d'avancement
+--------------
+
+![Graphique d'avancement](http://kanboard.net/screenshots/documentation/burndown-chart.png)
+
+Un [graphique d'avancement](http://en.wikipedia.org/wiki/Burn_down_chart) est disponible pour chaque projet.
+
+- Il s'agit de la représentation graphique du travail qui reste à faire en fonction du temps restant.
+- Kanboard utilise la complexité des estimations d'achèvement pour créer le graphique.
+- Chaque jour, la somme des estimations pour chaque colonne est calculée.
+
+Temps moyen passé pour chaque colonne
+-----------------------------------
+
+![Temps moyen passé pour chaque colonne](http://kanboard.net/screenshots/documentation/average-time-spent-into-each-column.png)
+
+Ce graphique affiche le temps moyen passé pour chaque colonne pour les 1000 dernière tâches.
+
+- Kanboard utilise les transitions entre tâches pour calculer les données.
+- Le temps passé est calculé jusqu'à la fin de la tâche.
+
+Temps moyen de Lead et Cycle
+---------------------------
+
+![Temps moyen passé pour chaque colonne](http://kanboard.net/screenshots/documentation/average-lead-cycle-time.png)
+
+Ce graphique affiche le temps moyen de lead et cycle pour les 1000 dernières tâches au cours du temps.
+
+- Le *lead time* est le temps passé entre la création de la tâche et sa date d'achèvement.
+- Le *cycle time* est le temps passé entre la date de début spécifiée et la date d'achèvement de la tâche.
+- Si la tâche n'est pas close, la date courante est utilisée à la place de la date d'achèvement.
+
+Ces métriques sont calculées et enregistrées chaque jour pour l'ensemble du projet.
+
+N'oubliez pas de lancer chaque jour le calcul statistique
+-------------------------------------------------------
+
+Pour générer des données analytique précises, vous devriez lancer chaque jour le cronjob **statistiques quotidiennes du projet**.
+
+[Consultez la documentation sur la ligne de commande avec Kanboard](cli.markdown)
diff --git a/doc/fr/automatic-actions.markdown b/doc/fr/automatic-actions.markdown
new file mode 100644
index 00000000..844354a5
--- /dev/null
+++ b/doc/fr/automatic-actions.markdown
@@ -0,0 +1,137 @@
+Actions automatiques
+=================
+
+Pour réduire au minimum l'interaction avec les utilisateurs, Kanboard dispose d'actions automatiques.
+
+Chaque action automatique est définie ainsi :
+
+- Un événement à suivre
+- Une action associée à cet évènement
+- Éventuellement quelques paramètres à définir
+
+Chaque projet a une série d'actions automatisées qui lui sont propres, le panneau de configuration est situé sur la page qui liste les projets, il vous suffit de cliquer sur le lien **Actions automatiques**.
+
+Ajouter une nouvelle action
+----------------
+
+### Choisir une action
+
+![Choisir une action](http://kanboard.net/screenshots/documentation/project-automatic-action-step1.png)
+
+### Choisir un évènement
+
+![Choisir un évènement](http://kanboard.net/screenshots/documentation/project-automatic-action-step2.png)
+
+### Définir les paramètres d'une action
+
+![Definir les paramètres](http://kanboard.net/screenshots/documentation/project-automatic-action-step3.png)
+
+Liste des évènements disponibles
+------------------------
+
+- Déplacement d'une tâche vers une autre colonne
+- Déplacement d'une tâche à un autre emplacement de la même colonne
+- Modification d'une tâche
+- Création d'une tâche
+- Réouverture d'une tâche
+- Fermeture d'une tâche
+- Création ou modification d'une tâche
+- Changement d'assigné à une tâche
+- Création ou mise à jour du lien vers une tâche
+- Réception d'un *commit* de Github
+- Ouverture d'une *issue* de Github
+- Fermeture d'une *issue* de Github
+- Réouverture d'une *issue* de Github
+- Modification de l'assigné à une *issue* de Github
+- Modification de l'étiquette d'une *issue* de Github
+- Création d'un commentaire d'une *issue* de Github
+- Ouverture d'une *issue* de Gitlab
+- Fermeture d'une *issue* de Gitlab
+- Réception d'un *commit* de Gitlab
+- Réception d'un *commit* de Bitbucket
+- Ouverture d'une *issue* de Bitbucket
+- Fermeture d'une *issue* de Bitbucket
+- Réouverture d'une *issue* de Bitbucket
+- Modification de l'assigné à une *issue* de Bitbucket issue assignee change
+- Création d'un commentaire d'une *issue* de Bitbucket
+
+Liste des actions disponibles
+-------------------------
+
+- Fermer une tâche
+- Ouvrir une tâche
+- Assigner la tâche à un utilisateur particulier
+- Assigner la tâche à la personne qui fait l'action
+- Cloner la tâche depuis un autre projet
+- Déplacer la tâche vers un autre projet
+- Déplacer la tâche vers une autre colonne quand elle est assignée à un utilisateur
+- Déplacer la tâche vers une autre colonne quand quand l'assigné est supprimé
+- Assigner une couleur quand la tâche est déplacée vers une colonne particulière
+- Assigner une couleur à un utilisateur particulier
+- Assigner automatiquement une couleur selon la catégorie
+- Assigner automatiquement une catégorie en fonction d'une couleur
+- Créer un commentaire depuis un fournisseur externe
+- Créer une tâche depuis un fournisseur externe
+- Ajouter un journal de commentaires quand on change une tâche de colonne
+- Modifier l'assigné en fonction d'un nom d'utilisateur externe
+- Modifier la catégorie en fonction d'une étiquette externe
+- Mettre à jour automatiquement la date de début
+- Déplacer la tâche vers une autre colonne quand la catégorie a changé
+- Envoyer une tâche par mail à quelqu'un
+- Modifier la couleur de la tâche quand on utilise un lien particulier pour cette tâche
+
+Exemples
+--------
+Voici quelques exemples d'utilisation dans la vraie vie :
+
+### Quand je déplace une tâche vers la colonne "Terminer", fermer automatiquement cette tâche
+
+- Choisir l'action : **Fermer la tâche**
+- Choisir l'évènement : **Déplacement d'une tâche vers une autre colonne**
+- Définir le paramètre de l'action : **Colonne = Terminé** (c'est la colonne de destination)
+
+### Quand je déplace une tâche vers la colonne "À valider", assigner cette tâche à un utilisateur particulier
+
+- Choisir l'action : **Assigner la tâche à un utilisateur particulier**
+- Choisir l'évènement : **Déplacer une tâche vers une nouvelle colonne**
+- Définir les paramètres de l'action :**Colonne = À valider** et **Utilisateur = Adrien** (Adrien est par exemple un testeur)
+
+### Quand je déplace une tâche vers la colonne "Travail en cours", assigner cette tâche à l'utilisateur courant
+
+- Choisir l'action : **Assigner la tâche à la personne qui fait cette action**
+- Choisir l'évènement : **Déplacer une tâche vers une autre colonne**
+- Définir le paramètre de l'action : **Colonne = Travail en cours**
+
+### Quand une tâche est terminée, dupliquer cette tâche vers un autre projet
+
+Supposons que nous ayons deux projets : "Commande du client" et "Production". Une fois validée la commande, la basculer vers le projet "Production".
+
+- Choisir l'action : **Dupliquer la tâche vers un autre projet**
+- Choisir l'évènement : **Fermer une tâche**
+- Définir les paramètres de l'action : **Colonne = Validé** et **Projet = Production**
+
+### Quand une tâche est déplacée vers la toute dernière colonne, déplacer la même tâche exactement vers un autre projet
+
+Supposons que nous ayons deux projets : "Idées" et "Développement". Une fois validée l'idée, la basculer vers le projet "Développement".
+
+- Choisir l'action : **Déplacer la tâche vers un autre projet**
+- Choisir l'évènement : **Déplacer une tâche vers une autre colonne**
+- Définir les paramètres de l'action : **Colonne = Validé** et **Projet = Développement**
+
+### Je veux assigner automatiquement une couleur à l'utilisateur Adrien
+
+- Choisir l'action : **Assigner une couleur à un utilisateur particulier**
+- Choisir l'évènement : **Modification de l'assigné à une tâche**
+- Définir les paramètres de l'action :**Couleur = Vert** et **Assigné = Adrien**
+
+### Je veux assigner automatiquement une couleur à la catégorie "Demande de fonctionnalité"
+
+- Choisir l'action : **Assigner automatiquement une couleur à une catégorie particulière**
+- Choisir l'évènement : **Création ou modification d'une tâche**
+- Définir les paramètres de l'action : **Couleur = Bleu** et **Catégorie = Demande de fonctionnalité**
+
+### Je veux régler automatiquement la date de début quand la tâche est déplacée dans la colonne "Travail en cours"
+
+- Choisir l'action : **Mettre à jour automatiquement la date de début**
+- Choisir l'évènement : **Déplacer une tâche vers une autre colonne**
+- Définir les paramètres de l'action : **Colonne= Travail en cours**
diff --git a/doc/fr/board-collapsed-expanded.markdown b/doc/fr/board-collapsed-expanded.markdown
new file mode 100644
index 00000000..f72d990d
--- /dev/null
+++ b/doc/fr/board-collapsed-expanded.markdown
@@ -0,0 +1,19 @@
+Mode Replié et Déplié
+===========================
+
+
+Les tâches peuvent être affichées sur le Tableau en mode Replié ou Déplié.
+Basculer d'un mode à l'autre peut être fait à l'aide du raccourci clavier **"s"** ou en utilisant le menu déroulant sur la gauche.
+
+Mode Replié
+--------------
+![Tâches repliées](http://kanboard.net/screenshots/documentation/board-collapsed-mode.png)
+
+- Si la tâche est affectée à quelqu'un, les initiales de la personne sont affichées à côté du numéro de la tâche.
+- Si le titre de la tâche est trop long, mettez le curseur de la souris au-dessus de la tâche pour voir une boite flottante avec le titre entier.
+
+Mode Déplié
+-------------
+
+![Tâches dépliées](http://kanboard.net/screenshots/documentation/board-expanded-mode.png)
+
diff --git a/doc/fr/board-horizontal-scrolling-and-compact-view.markdown b/doc/fr/board-horizontal-scrolling-and-compact-view.markdown
new file mode 100644
index 00000000..51d28bbb
--- /dev/null
+++ b/doc/fr/board-horizontal-scrolling-and-compact-view.markdown
@@ -0,0 +1,13 @@
+Défilement horizontal et mode compact
+=====================================
+
+Lorsque le tableau ne tient pas dans votre écran, une barre de défilement horizontal appaîtra au bas de l'écran.
+
+Cependant, il est possible de basculer vers la vue compacte pour afficher toutes les colonnes dans votre écran.
+
+![Tableau en mode compact](http://kanboard.net/screenshots/documentation/board-compact-mode.png)
+
+Basculer entre le défilement horizontal et la vue compacte s'effectue avec le raccourci clavier **"c"** ou en utilisant le menu déroulant sur la gauche.
+
+Note : Il est possible que du texte se superpose dans le mode compact, ceci sera amélioré dans les prochaines versions.
+
diff --git a/doc/fr/board-show-hide-columns.markdown b/doc/fr/board-show-hide-columns.markdown
new file mode 100644
index 00000000..e1e02c6b
--- /dev/null
+++ b/doc/fr/board-show-hide-columns.markdown
@@ -0,0 +1,12 @@
+Afficher ou cacher des colonnes dans le tableau
+==================================
+
+Vous pouvez très facilement cacher ou afficher des colonnes dans le tableau :
+
+![Tableau avec des colonnes cachées](http://kanboard.net/screenshots/documentation/board-hide-show-column.png)
+
+- Pour cacher une colonne, cliquez juste sur le titre de la colonne
+- Pour afficher une colonne cachée, cliquez sur le titre vertical
+
+Lorsqu'une colonne est cachée, le nombre de tâches est affiché en haut de la colonne.
+
diff --git a/doc/fr/budget.markdown b/doc/fr/budget.markdown
new file mode 100644
index 00000000..3eac20d5
--- /dev/null
+++ b/doc/fr/budget.markdown
@@ -0,0 +1,34 @@
+Gestion du budget
+=================
+
+La gestion du budget repose sur le suivi du temps d'une sous-tâche, l'emploi du temps de l'utilisateur et le taux horaire de l'utilisateur.
+
+Cette section est accessible depuis la page de paramètres du projet : **Project > Budget**. Il existe également un raccourci depuis le menu déroulant sur le tableau.
+
+Lignes budgétaires
+------------
+
+![Ligne des coûts](http://kanboard.net/screenshots/documentation/budget-lines.png)
+
+Les lignes budgétaires sont utilisées pour définir le budget du projet.
+Celui-ci peut être ajusté en ajoutant une nouvelle entrée avec une date effective.
+
+Détail des coûts
+--------------
+
+![Détail des coûts](http://kanboard.net/screenshots/documentation/budget-cost-breakdown.png)
+
+Selon le tableau qui donne le suivi temporel de la sous-tâche et les informations sur l'utilisateur vous pouvez voir le coût de chaque sous-tâche.
+
+Le temps passé est arrondi au quart d'heure le plus proche.
+
+Graphique du budget
+------------
+
+![Graphique du budget](http://kanboard.net/screenshots/documentation/budget-graph.png)
+
+Finalement, en combinant toutes les informations nous pouvons générer un graphique :
+
+- Les dépenses représentent le coût utilisateur
+- Les lignes budgétaires sont le budget prévisionnel
+- Le restant est le budget qui reste après un délai donné
diff --git a/doc/fr/calendar.markdown b/doc/fr/calendar.markdown
new file mode 100644
index 00000000..94610efb
--- /dev/null
+++ b/doc/fr/calendar.markdown
@@ -0,0 +1,20 @@
+Calendriers
+========
+
+il existe deux visualisations différentes des calendriers :
+
+- La vue du projet avec des filtres (disponibles depuis le tableau)
+- La vue utilisateur (disponible depuis le tableau de bord de l'utilisateur)
+
+Pour l'instant le calendrier permet d'afficher les informations suivantes :
+
+- Les tâches avec une date d'échéance, affichée en haut. **La date d'échéance peut être modifiée en déplaçant la tâche vers un autre jour**.
+- les tâches basées sur la date de création ou la date de début. **Ces évènements ne peuvent pas être modifiés avec le calendrier**.
+- Le suivi dans le temps de sous-tâches, tous les segments temporels sont affichés dans le calendrier.
+- Les estimations pour les sous-tâches, les prévisions et le travail restant
+
+![Calendrier](http://kanboard.net/screenshots/documentation/calendar.png)
+
+La configuration du calendrier peut être modifiée dans la page des paramètres.
+
+Remarque : la date d'échéance n'inclut pas d'information temporelle.
diff --git a/doc/fr/creating-projects.markdown b/doc/fr/creating-projects.markdown
new file mode 100644
index 00000000..d492ac62
--- /dev/null
+++ b/doc/fr/creating-projects.markdown
@@ -0,0 +1,32 @@
+Créer des projets
+=================
+
+Kanboard peut gérer de multiples projets. Voici deux sortes de projets :
+
+- Les projets multi-utilisateurs (pour le travail collaboratif, en équipe)
+- Les projets privés, réservés à un seul utilisateur
+
+Créer des projets multi-utilisateurs
+-------------------------------------
+
+- Seuls les administrateurs et administrateurs de projets peuvent créer ce type de projets
+- La gestion des utilisateurs est disponible
+
+Depuis le tableau principal, cliquez sur le lien **Nouveau projet** :
+
+![Formulaire de création de projet](http://kanboard.net/screenshots/documentation/project-creation-form.png)
+
+C'est vraiment très simple, il vous suffit de trouver un nom pour votre projet !
+
+Créer un projet privé
+--------------------------
+
+- Tout le monde peut créer un projet privé
+- Il n'y a **pas** de gestion des utilisateurs
+- Seuls le propriétaire et les administrateurs peuvent accéder au projet
+
+Depuis le tableau principal, cliquez sur le lien **Nouveau projet privé**.
+
+![Nouveau projet privé](http://kanboard.net/screenshots/documentation/new-private-project.png)
+
+Remarque : les noms de projets doivent être uniques dans toute l'application.
diff --git a/doc/fr/editing-projects.markdown b/doc/fr/editing-projects.markdown
new file mode 100644
index 00000000..67f8a0db
--- /dev/null
+++ b/doc/fr/editing-projects.markdown
@@ -0,0 +1,16 @@
+Modifier des projets
+================
+
+Les projets peuvent être renommés et désactivés à tout moment.
+
+Pour renommer un projet, il suffit de cliquer sur le lien « Modifier un projet » sur la gauche.
+
+![Modification de projet](http://kanboard.net/screenshots/documentation/project-edition.png)
+
+- Les dates de début et de fin sont utilisées pour créer le diagramme de Gantt du projet
+- La description est visible en infobulle sur le tableau et sur la page qui liste les projets
+- Les administrateurs et administrateurs de projets peuvent convertir un projet privé en projet multi-utilisateur en décochant la case « Projet privé ».
+- Vous pouvez également convertir un projet multi-utilisateur en projet privé.
+
+Remarque : quand vous rendez un projet privé, tous les utilisateurs existants auront accès au projet. Ajustez la liste des utilisateurs selon vos besoins.
+
diff --git a/doc/fr/gantt-chart-projects.markdown b/doc/fr/gantt-chart-projects.markdown
new file mode 100644
index 00000000..f2ac40ac
--- /dev/null
+++ b/doc/fr/gantt-chart-projects.markdown
@@ -0,0 +1,17 @@
+Diagramme de Gantt pour tous les projets
+============================
+
+Le but de ce diagramme de Gantt est d'afficher une vue d'ensemble de tous les projets basée sur les dates de début et de fin.
+
+- Ce diagramme de Gantt est disponible dans la section de gestion du projet
+- Seuls les administrateurs et administrateurs de projet peuvent accéder à cette section
+- Les administrateurs de projet ne verront que les projets dans lesquels il y a des membres
+- Les objets privés ne sont pas affichés dans ce graphique
+
+![Diagramme de Gantt pour tous les projets](http://kanboard.net/screenshots/documentation/gantt-chart-all-projects.png)
+
+- La **date de début** et la **date de fin** des projets est utilisée pour construire le graphique
+- Les barres horizontales peuvent être redimensionnées et déplacées latéralement avec votre souris
+- Il n'y a pas de glisser-déposer vertical
+- Les barres de projet sont affichées en noir quand il n'y a ni date de début ni date de fin définies
+- L'infobulle affiche la liste des gestionnaires de projets et les membres ordinaires
diff --git a/doc/fr/gantt-chart-tasks.markdown b/doc/fr/gantt-chart-tasks.markdown
new file mode 100644
index 00000000..73053ed2
--- /dev/null
+++ b/doc/fr/gantt-chart-tasks.markdown
@@ -0,0 +1,20 @@
+Diagramme de Gantt pour les tâches
+======================
+
+Le but de ce diagramme de Gantt est de montrer une vue d'ensemble du temps utilisé en fonction de l'ensemble des tâches d'un projet donné.
+
+- Le diagramme de Gantt est disponible depuis le « sélecteur de vue »
+- Seuls les gestionnaires de projet peuvent accéder à cette section
+
+![Gantt Chart](http://kanboard.net/screenshots/documentation/gantt-chart-project.png)
+
+- La **date de début** et la **date de fin** des tâches sont utilisées pour créer le graphique
+- Les tâches peuvent être redimensionnées et déplacées horizontalement avec votre souris
+- Il n'y a pas de glisser-déposer vertical
+- La barre est de la même couleur que la tâche
+- Chaque barre affiche un niveau de progression en pourcentage, qui est calculé en utilisant la position de la colonne dans le tableau
+- Pour correspondre au modèle du Kanban, les tâches peuvent être ordonnées suivant leur position dans le tableau ou suivant les dates de début
+- Les nouvelles tâches crées avec cette vue seront affichées sur le tableau en position 1 de la première colonne
+- Les tâches sont affichées en noir quand il n'existe ni date de début ni date d'échéance définies
+
+![Tâche non définie](http://kanboard.net/screenshots/documentation/gantt-chart-not-defined.png)
diff --git a/doc/fr/kanban-vs-todo-and-scrum.markdown b/doc/fr/kanban-vs-todo-and-scrum.markdown
new file mode 100644
index 00000000..b6f5bc1f
--- /dev/null
+++ b/doc/fr/kanban-vs-todo-and-scrum.markdown
@@ -0,0 +1,36 @@
+Comparons Kanban aux Todo listes et à Scrum
+==============================
+
+Kanban et les Todo listes
+--------------------
+
+### Todo listes :
+
+- Une seule phase (une simple liste d'éléments)
+- La possibilité de multitâche (moins efficace)
+
+### Kanban:
+
+- Multiples phases, chaque colonne représente une étape
+- Permet de se concentrer sans se disperser sur de multiples tâches, puisque l'on peut poser une limite au travail en cours par colonne
+
+Kanban et Scrum
+---------------
+### Scrum :
+
+- Limite les Sprints dans le temps, généralement à 2 ou 4 semaines
+- N'accepte pas de modifications pendant l'itération
+- Nécessite une estimation
+- Utilise la vélocité comme métrique par défaut
+- Le tableau Scrum est remis à zéro entre chaque Sprint
+- Scrum a des rôles prédéfinis comme Scrum Master, Product Owner et l'équipe
+- Beaucoup de réunions : planification, consolidation du backlog, quotidienne, rétrospective
+
+### Kanban :
+- Flux continu
+- Des modifications peuvent arriver à n'importe quel moment
+- L'estimation est facultative
+- Utilise le temps *lead* et *cycle* pour mesurer l'efficacité
+- Le tableau Kanban est permanent
+- Kanban n'impose aucune contrainte stricte ni de réunion, le processus est plus flexible
+
diff --git a/doc/fr/project-permissions.markdown b/doc/fr/project-permissions.markdown
new file mode 100644
index 00000000..9e21e1ad
--- /dev/null
+++ b/doc/fr/project-permissions.markdown
@@ -0,0 +1,49 @@
+Permissions des projets
+===================
+
+Deux sortes d'utilisateurs sont en charge d'un projet : les **gestionnaires de projet** et les **membres du projet**.
+
+- Les gestionnaires de projet peuvent gérer la configuration du projet et accéder aux rapports.
+- Les membres du projet ne peuvent effectuer que des opérations de base (créer ou déplacer des tâches).
+
+Quand vous créez un nouveau projet, le statut de gestionnaire de projet vous est automatiquement attribué.
+
+Les administrateurs de Kanboard peuvent accéder à tout mais ils ne sont pas nécessairement gestionnaires de projet ni membres du projet. **Ces permissions sont définies au niveau du projet**.
+
+Permissions selon chaque rôle
+-------------------------
+
+### Membres du projet
+
+- Utiliser le tableau (créer, déplacer et modifier les tâches)
+- Supprimer seulement les tâches créées par eux-mêmes
+
+### Gestionnaires du projet
+
+- Utiliser le tableau
+- Configurer le projet
+- Partager, renommer, dupliquer et désactiver le projet
+- Gérer les swimlanes, les catégories, colonnes et utilisateurs
+- Modifier les actions automatisées
+- Exporter en CSV
+- Supprimer les tâches de n'importe quel membre du projet
+- Accéder à la section analytique
+
+Ils ne **peuvent pas supprimer un projet**.
+
+Gérer les utilisateurs et les permissions
+----------------------------
+
+Pour définir les rôles dans un projet, allez sur la page de **configuration de projet** puis cliquez sur **Gestion des utilisateurs**.
+
+### Gestion des utilisateurs
+
+![Permissions du projet](http://kanboard.net/screenshots/documentation/project-permissions.png)
+
+C'est l'endroit où vous pouvez choisir de nouveaux membres, modifier leur rôle ou interrompre l'accès d'un utilisateur.
+
+### Permission générale
+
+Si vous choisissez d'autoriser tout le monde (tous les utilisateurs de Kanboard), le projet est considéré comme public.
+
+Cela signifie qu'il n'y a plus de rôle de gestionnaire de projet. Les permissions par utilisateur ne peuvent pas s'appliquer.
diff --git a/doc/fr/project-views.markdown b/doc/fr/project-views.markdown
new file mode 100644
index 00000000..efaa1c94
--- /dev/null
+++ b/doc/fr/project-views.markdown
@@ -0,0 +1,44 @@
+Vues Tableau, Agenda et Liste
+==============================
+Pour chaque projet, les tâches peuvent être visualisées dans différentes vues : **Tableau, Agenda, Liste ou Gantt**. Chaque vue affiche le résultat filtré par le champ de recherche en haut de page. Le moteur de recherche utilise la [syntaxe avancée](search.markdown).
+
+Vue Tableau
+----------
+![Vue Tableau](http://kanboard.net/screenshots/documentation/board-view.png)
+
+- Dans cette vue, il est possible de glisser-déposer facilement des tâches d'une colonne à l'autre.
+- Il est également possible d'utiliser le raccourci clavier **"v b"** pour afficher la vue Tableau.
+- Les tâches avec une ombre ont été modifiées récemment.
+
+![Tableau Limite de tâches](http://kanboard.net/screenshots/documentation/board-task-limit.png)
+
+Lorsque la limite de tâches est atteinte pour une colonne, l'arrière-plan devient rouge. Ce qui signifie qu'il y a trop de tâches en cours en même temps.
+
+[En apprendre plus sur la configuration du Tableau](board-configuration.markdown)
+
+Vue Agenda
+--------------
+
+![Vue Agenda](http://kanboard.net/screenshots/documentation/calendar-view.png)
+
+- Dans cette vue, il est possible de voir les tâches avec des dates d'échéance.
+- Selon les paramètres, il est également possible de voir les tâches en cours.
+- Il est également possible d'utiliser le raccourci clavier **"v c"** pour afficher la vue Agenda.
+- [En apprendre plus sur la configuration de l'Agenda](calendar-configuration.markdown)
+
+Vue Liste
+----------
+
+![Vue liste](http://kanboard.net/screenshots/documentation/list-view.png)
+
+- Dans cette vue, tous les résultats de votre recherche sont affichés dans un tableau.
+- Il est également possible d'utiliser le raccourci clavier **"v l"** pour afficher la vue Liste.
+
+Vue Gantt
+----------
+
+![Vue Gantt](http://kanboard.net/screenshots/documentation/gantt-view.png)
+
+- La vue Gantt affiche les tâches dans une fresque horizontale
+- Le diagramme utilise la date de début et la date d'échéance pour afficher les tâches
+- Il est également possible d'utiliser le raccourci clavier **"v g"** pour afficher la vue Gantt.
diff --git a/doc/fr/sharing-projects.markdown b/doc/fr/sharing-projects.markdown
new file mode 100644
index 00000000..0d2df7aa
--- /dev/null
+++ b/doc/fr/sharing-projects.markdown
@@ -0,0 +1,37 @@
+Partager des tableaux et des tâches
+========================
+
+Par défaut, les tableaux sont privés, mais il est possible de rendre un tableau public.
+
+Un tableau public ne **peut pas être modifié, il est en lecture seule**.
+Son accès est protégé par un jeton aléatoire, seules les personnes qui ont la bonne URL peuvent voir le tableau.
+
+Les tableaux publics sont automatiquement réactualisés toutes les minutes.
+Les détails des tâches sont disponibles en lecture seule.
+
+Exemples d'utilisation :
+
+- Partager son tableau avec quelqu'un qui ne fait pas partie de votre organisation / entreprise / groupe
+- Afficher le tableau sur un grand écran dans votre bureau
+
+Activer l'accès public
+-------------------
+
+Choisissez votre projet, puis cliquez sur « Accès public » et enfin sur le bouton « Activer l'accès public ».
+
+![Activer l'accès public](http://kanboard.net/screenshots/documentation/project-enable-sharing.png)
+
+Lorsque l'accès public est activé, plusieurs liens sont créés :
+
+- Affichage du tableau public
+- Lien de souscription au fil RSS
+- Lien d'abonnement à iCalendar
+
+![Désactiver l'accès public](http://kanboard.net/screenshots/documentation/project-disable-sharing.png)
+
+Vous pouvez désactiver l'accès public à tout moment.
+
+À chaque fois que vous activez ou désactivez l'accès public, un nouveau jeton aléatoire est créé.
+**Les liens précédents ne fonctionneront pas**.
+
+
diff --git a/doc/fr/swimlanes.markdown b/doc/fr/swimlanes.markdown
new file mode 100644
index 00000000..48772117
--- /dev/null
+++ b/doc/fr/swimlanes.markdown
@@ -0,0 +1,28 @@
+Swimlanes
+=========
+
+Les *swimlanes* sont des séparations horizontales de votre tableau (pensez à des « couloirs » ou « lignes d'eau » dans une piscine).
+Par exemple, cela peut servir à séparer les sorties des différentes versions d'un logiciel, à diviser vos tâches selon différents produits, équipes ou tout autre critère de votre choix.
+
+Tableau avec des swimlanes
+--------------------
+
+![Configuration des swimlanes](http://kanboard.net/screenshots/documentation/swimlanes.png)
+
+Gestion des swimlanes
+------------------
+
+- Tous les projets ont une swimlane par défaut.
+- S'il existe plus d'une swimlane, le tableau les affichera toutes.
+- Vous pouvez glisser-déposer les tâches d'une swimlane à l'autre.
+
+Pour configurer les swimlanes allez sur la page de **Configuration du projet** et choisissez la section **Swimlanes**.
+
+![Swimlanes Configuration](http://kanboard.net/screenshots/documentation/swimlanes-configuration.png)
+
+À partir de cet endroit, vous pouvez ajouter une nouvelle swimlane ou renommer celle qui existe par défaut.
+Vous pouvez aussi désactiver et modifier la position des diverses swimlanes.
+
+- La swimlane par défaut est toujours en haut de tableau mais vous pouvez la cacher.
+- Les swimlanes inactives ne sont pas affichées dans le tableau.
+- **Supprimer une swimlane ne supprime pas les tâches qui lui sont assignées**, ces tâches seront transférées à la swimlane par défaut.
diff --git a/doc/fr/usage-examples.markdown b/doc/fr/usage-examples.markdown
new file mode 100644
index 00000000..b91fa613
--- /dev/null
+++ b/doc/fr/usage-examples.markdown
@@ -0,0 +1,69 @@
+Exemples d'utilisation
+==============
+Il est possible de personnaliser ses tableaux selon l'activité de votre entreprise :
+
+Développement logiciel
+--------------------
+
+- Prévu
+- Prêt
+- En cours
+- À valider
+- Validé
+- En production
+
+Suivi de bogues
+------------
+
+- Rapporté
+- Confirmé
+- En cours
+- Testé
+- Résolu
+
+Ventes
+-----
+
+- Objectifs
+- Réunions
+- Propositions
+- Achats
+
+Gestion au plus juste
+------------------------
+
+- Idées
+- Expression de la demande
+- Étude de marché
+- Analyses
+- Fait
+
+
+Procédure de recrutement
+------------------
+
+- Offres d'emploi
+- Candidats
+- Appels téléphoniques
+- Entretiens
+- Embauches
+
+Boutiques en ligne
+------------
+
+- Commande
+- Empaquetage
+- Prêt à envoyer
+- Envoyé
+
+Artisanat
+-----------
+
+- Commande
+- Assemblage
+- Tests
+- Empaquetage
+- Prêt à envoyer
+- Envoyé
+
+
diff --git a/doc/fr/what-is-kanban.markdown b/doc/fr/what-is-kanban.markdown
new file mode 100644
index 00000000..f479927c
--- /dev/null
+++ b/doc/fr/what-is-kanban.markdown
@@ -0,0 +1,34 @@
+Qu'est-ce que Kanban?
+===============
+
+
+Kanban est une méthodologie développée à l'origine par l'entreprise Toyota pour gagner en efficacité.
+
+Kanban n'impose que deux contraintes :
+
+- Visualiser votre flux d'activité
+- Limiter votre travail en cours
+
+Visualiser votre flux d'activité
+-----------------------
+
+- Votre activité est affichée sur un tableau, vous disposez ainsi d'une vue très nette sur l'ensemble de votre projet
+- Chaque colonne représente une étape de votre flux d'activité
+
+Se concentrer sur une seule tâche à la fois sans disperser son activité
+----------------------------------
+
+- Chaque phase peut avoir sa date d'échéance
+- Les limites fixées sont très utiles pour identifier les goulots d'étranglement
+- Les limites évitent de travailler à un trop grand nombre de tâches à la fois
+
+Mesure des performances et des progrès
+-----------------------------------
+
+Kanban utilise lead time et cycle times pour mesurer les performances :
+
+- **Lead time** : le *lead time* est la durée entre la création de la tâche et son achèvement.
+- **Cycle time** : le *cycle time* est la durée entre la date de début et l'achèvement.
+
+Par exemple, vous pouvez avoir un *lead time* de 100 jours et n'avoir à travailler qu'une heure pour achever la tâche.
+
diff --git a/doc/index.markdown b/doc/index.markdown
index 0c33bfaa..96069250 100644
--- a/doc/index.markdown
+++ b/doc/index.markdown
@@ -78,6 +78,7 @@ Using Kanboard
- [RSS/Atom subscriptions](rss.markdown)
- [Json-RPC API](api-json-rpc.markdown)
- [Webhooks](webhooks.markdown)
+- [Plugins](plugins.markdown)
### More
@@ -104,6 +105,7 @@ Technical details
- [Installation on Heroku](heroku.markdown)
- [Example with Nginx + HTTPS + SPDY + PHP-FPM](nginx-ssl-php-fpm.markdown)
- [Run Kanboard with Docker](docker.markdown)
+- [Run Kanboard with Vagrant](vagrant.markdown)
### Configuration
@@ -120,6 +122,7 @@ Technical details
### Authentication
- [LDAP authentication](ldap-authentication.markdown)
+- [LDAP group sync](ldap-group-sync.markdown)
- [Google authentication](google-authentication.markdown)
- [Github authentication](github-authentication.markdown)
- [Gitlab authentication](gitlab-authentication.markdown)
@@ -132,7 +135,6 @@ Technical details
- [Coding standards](coding-standards.markdown)
- [Running tests](tests.markdown)
- [Build assets](assets.markdown)
-- [Run Kanboard with Vagrant](vagrant.markdown)
The documentation is written in [Markdown](http://en.wikipedia.org/wiki/Markdown).
If you want to improve the documentation, just send a pull-request.
diff --git a/doc/ldap-authentication.markdown b/doc/ldap-authentication.markdown
index 53b3d012..136aa9ac 100644
--- a/doc/ldap-authentication.markdown
+++ b/doc/ldap-authentication.markdown
@@ -225,6 +225,17 @@ define('LDAP_ACCOUNT_EMAIL', 'mail');
// Example for OpenLDAP: 'uid'
define('LDAP_ACCOUNT_ID', 'samaccountname');
+// LDAP Attribute for group membership
+define('LDAP_ACCOUNT_MEMBEROF', 'memberof');
+
+// DN for administrators
+// Example: CN=Kanboard Admins,CN=Users,DC=kanboard,DC=local
+define('LDAP_GROUP_ADMIN_DN', '');
+
+// DN for project administrators
+// Example: CN=Kanboard Project Admins,CN=Users,DC=kanboard,DC=local
+define('LDAP_GROUP_PROJECT_ADMIN_DN', '');
+
// By default Kanboard lowercase the ldap username to avoid duplicate users (the database is case sensitive)
// Set to true if you want to preserve the case
define('LDAP_USERNAME_CASE_SENSITIVE', false);
diff --git a/doc/ldap-group-sync.markdown b/doc/ldap-group-sync.markdown
new file mode 100644
index 00000000..355a1cde
--- /dev/null
+++ b/doc/ldap-group-sync.markdown
@@ -0,0 +1,36 @@
+LDAP Group Synchronization
+==========================
+
+Requirements
+------------
+
+- Have LDAP authentication properly configured
+- Use a LDAP server that supports `memberOf`
+
+Automatically define Kanboard groups based on LDAP groups
+---------------------------------------------------------
+
+In your config file, define the constants `LDAP_GROUP_ADMIN_DN` and `LDAP_GROUP_PROJECT_ADMIN_DN`. Here an example, replace the values according to your own LDAP configuration:
+
+```php
+define('LDAP_GROUP_ADMIN_DN', 'CN=Kanboard Admins,CN=Users,DC=kanboard,DC=local');
+define('LDAP_GROUP_PROJECT_ADMIN_DN', 'CN=Kanboard Project Admins,CN=Users,DC=kanboard,DC=local');
+```
+
+- People member of "Kanboard Admins" will be "Kanboard Administrators"
+- People member of "Kanboard Project Admins" will be "Kanboard Project Administrators"
+- Everybody else will be Kanboard Standard Users
+
+Note: At the moment, that works only at account creation.
+
+Filter Kanboard access based on the LDAP group
+----------------------------------------------
+
+To allow only some users to use Kanboard, use the existing `LDAP_USER_PATTERN` constant:
+
+```php
+define('LDAP_USER_PATTERN', '(&(objectClass=user)(sAMAccountName=%s)(memberOf=CN=Kanboard Users,CN=Users,DC=kanboard,DC=local))');
+```
+
+This example allow only people member of the group "Kanboard Users" to connect to Kanboard.
+
diff --git a/doc/plugins.markdown b/doc/plugins.markdown
new file mode 100644
index 00000000..7f32cf0a
--- /dev/null
+++ b/doc/plugins.markdown
@@ -0,0 +1,263 @@
+Plugin Development
+==================
+
+Note: The plugin API is considered alpha at the moment.
+
+Plugins are useful to extend the core functionalities of Kanboard, adding features, creating themes or changing the default behavior.
+
+Plugin creators should specify explicitly the compatible versions of Kanboard. Internal code of Kanboard may change over the time and your plugin must be tested with new versions.
+
+Directory structure
+-------------------
+
+Plugins are stored in the `plugins` subdirectory. An example of a plugin directory structure:
+
+```bash
+plugins
+└── Budget <= Plugin name
+ ├── Asset <= Javascript/CSS files
+ ├── Controller
+ ├── LICENSE <= Plugin license
+ ├── Locale
+ │ ├── fr_FR
+ │   ├── it_IT
+ │   ├── ja_JP
+ │   └── zh_CN
+ ├── Model
+ ├── Plugin.php <= Plugin registration file
+ ├── README.md
+ ├── Schema <= Database migrations
+ ├── Template
+ └── Test <= Unit tests
+```
+
+Only the registration file `Plugin.php` is required. Other folders are optionals.
+
+The first letter of the plugin name must be capitalized.
+
+Plugin registration file
+------------------------
+
+Kanboard will scan the directory `plugins` and load automatically everything under this directory. The file `Plugin.php` is used to load and register the plugin.
+
+Example of `Plugin.php` file (`plugins/Foobar/Plugin.php`):
+
+```php
+<?php
+
+namespace Plugin\Foobar;
+
+use Core\PluginBase;
+
+class Plugin extends PluginBase
+{
+ public function initialize()
+ {
+ $this->template->hook->attach('layout:head', 'theme:layout/head');
+ }
+}
+```
+
+This file should contains a class `Plugin` defined under the namespace `Plugin\Yourplugin` and extends `Core\PluginBase`.
+
+The only required method is `initialize()`. This method is called for each request when the plugin is loaded.
+
+Plugin methods
+--------------
+
+Available methods from `PluginBase`:
+
+- `initialize()`: Executed when the plugin is loaded
+- `getClasses()`: Return all classes that should be stored in the dependency injection container
+- `on($event, $callback)`: Listen on internal events
+
+Your plugin registration class also inherit from `Core\Base`, that means you can access to all classes and methods of Kanboard easily.
+
+This example will fetch the user #123:
+
+```php
+$this->user->getById(123);
+```
+
+Template hooks
+--------------
+
+Template hooks allow to add new content in existing templates.
+
+Example to add new content in the dashboard sidebar:
+
+```php
+$this->template->hook->attach('dashboard:sidebar', 'myplugin:dashboard/sidebar');
+```
+
+This call is usually defined in the `initialize()` method.
+The first argument is name of the hook and the second argument is the template name.
+
+Template names prefixed with the plugin name and colon indicate the location of the template.
+
+Example with `myplugin:dashboard/sidebar`:
+
+- `myplugin` is the name of your plugin (lowercase)
+- `dashboard/sidebar` is the template name
+- On the filesystem, the plugin will be located here: `plugins\Myplugin\Template\dashboard\sidebar.php`
+- Templates are written in pure PHP (don't forget to escape data)
+
+Template name without prefix are core templates.
+
+List of template hooks:
+
+- `dashboard:sidebar`
+- `config:sidebar`
+- `export:sidebar`
+- `layout:head`
+- `layout:top`
+- `layout:bottom`
+- `project:dropdown`
+- `project-user:sidebar`
+- `task:sidebar:information`
+- `task:sidebar:actions`
+- `user:sidebar:information`
+- `user:sidebar:actions`
+
+Other template hooks can be added if necessary, just ask on the issue tracker.
+
+Template overrides
+------------------
+
+Any templates defined in the core can be overrided. By example, you can redefine the default layout or change email notifications.
+
+Example of template override:
+
+```php
+$this->template->setTemplateOverride('header', 'theme:layout/header');
+```
+
+The first argument is the original template name and the second argument the template to use as replacement.
+
+Listen on events
+----------------
+
+Kanboard use internal events and your plugin can listen and perform actions on these events.
+
+```php
+$this->on('session.bootstrap', function($container) {
+ // Do something
+});
+```
+
+- The first argument is the event name
+- The second argument is a PHP callable function (closure or class method)
+
+Extend ACL
+----------
+
+Kanboard use a custom access list for privilege separations. Your extension can add new rules:
+
+```php
+$this->acl->extend('project_manager_acl', array('mycontroller' => '*'));
+```
+
+- The first argument is the ACL name
+- The second argument are the new rules
+ + Syntax to include only some actions: `array('controller' => array('action1', 'action2'))`
+ + Syntax to include all actions of a controller: `array('controller' => '*')`
+ + Everything is lowercase
+
+List of ACL:
+
+- `public_acl`: Public access without authentication
+- `project_member_acl`: Project member access
+- `project_manager_acl`: Project manager access
+- `project_admin_acl`: Project Admins
+- `admin_acl`: Administrators
+
+Plugin Translations
+-------------------
+
+Plugin can be translated in the same way the rest of the application. You must load the translations yourself when the session is created:
+
+```php
+$this->on('session.bootstrap', function($container) {
+ Translator::load($container['config']->getCurrentLanguage(), __DIR__.'/Locale');
+});
+```
+
+The translations must be stored in `plugins/Myplugin/Locale/xx_XX/translations.php`.
+
+Dependency Injection Container
+------------------------------
+
+Kanboard use Pimple, a simple PHP Dependency Injection Container. However, Kanboard can register any class in the container easily.
+
+Those classes are available everywhere in the application and only one instance is created.
+
+Here an example to register your own models in the container:
+
+```php
+public function getClasses()
+{
+ return array(
+ 'Plugin\Budget\Model' => array(
+ 'HourlyRate',
+ 'Budget',
+ )
+ );
+}
+```
+
+Now, if you use a class that extends from `Core\Base`, you can access directly to those class instance:
+
+```php
+$this->hourlyRate->remove(123);
+$this->budget->getDailyBudgetBreakdown(456);
+
+// It's the same thing as using the container:
+$this->container['hourlyRate']->getAll();
+```
+
+Keys of the containers are unique across the application. If you override an existing class you will change the default behavior.
+
+Schema migrations
+-----------------
+
+Kanboard execute database migrations automatically for you. Migrations must be stored in a folder **Schema** and the filename must be the same as the database driver:
+
+```bash
+Schema
+├── Mysql.php
+├── Postgres.php
+└── Sqlite.php
+```
+
+Each file contains all migrations, here an example for Sqlite:
+
+```php
+<?php
+
+namespace Plugin\Something\Schema;
+
+const VERSION = 1;
+
+function version_1($pdo)
+{
+ $pdo->exec('CREATE TABLE IF NOT EXISTS something (
+ "id" INTEGER PRIMARY KEY,
+ "project_id" INTEGER NOT NULL,
+ "something" TEXT,
+ FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE CASCADE
+ )');
+}
+```
+
+- The constant `VERSION` is the last version of your schema
+- Each function is a migration `version_1()`, `version_2()`, etc...
+- A `PDO` instance is passed as first argument
+- Everything is executed inside a transaction, if something doesn't work a rollback is performed and the error is displayed to the user
+
+Kanboard will compare the version defined in your schema and the version stored in the database. If the versions are different, Kanboard will execute one by one each migration until to reach the last version.
+
+Examples of plugins
+-------------------
+
+- Budget planning: https://github.com/kanboard/plugin-budget
+- Theme plugin sample: https://github.com/kanboard/plugin-example-theme
diff --git a/doc/screenshots.markdown b/doc/screenshots.markdown
index 419de412..95972405 100644
--- a/doc/screenshots.markdown
+++ b/doc/screenshots.markdown
@@ -22,4 +22,4 @@ On Mac OS X, you can use those shortcuts to take screenshots:
There are also several third-party applications that can be used to take screenshots with annotations and shapes.
-**Note: This feature doesn't works with all browsers.** It doesn't work with Safari due to this bug: https://bugs.webkit.org/show_bug.cgi?id=49141
+**Note: This feature doesn't work with all browsers.** It doesn't work with Safari due to this bug: https://bugs.webkit.org/show_bug.cgi?id=49141
diff --git a/doc/subtasks.markdown b/doc/subtasks.markdown
index 3c9e8350..e1acd98c 100644
--- a/doc/subtasks.markdown
+++ b/doc/subtasks.markdown
@@ -40,5 +40,5 @@ Subtask timer
- Each time a subtask is in progress, the timer is also started. The timer can be started and stopped at any time.
- The timer records the time spent on the subtask automatically. You can also change manually the value of the time spent field when you edit a subtask.
- The time calculated is rounded to the nearest quarter. This information is recorded in a separate table.
-- The task time spent is updated automatically according to the sum of all subtasks time spent.
+- The task time spent and time estimated are updated automatically according to the sum of all subtasks.
diff --git a/plugins/.gitignore b/plugins/.gitignore
new file mode 100644
index 00000000..aa0e8eb1
--- /dev/null
+++ b/plugins/.gitignore
@@ -0,0 +1,2 @@
+*
+!/.gitignore \ No newline at end of file
diff --git a/tests/functionals/UserApiTest.php b/tests/functionals/UserApiTest.php
index e1ce1878..e2c693dd 100644
--- a/tests/functionals/UserApiTest.php
+++ b/tests/functionals/UserApiTest.php
@@ -37,15 +37,15 @@ class UserApi 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');
- $this->admin->debug = true;
+ // $this->admin->debug = true;
$this->user = new JsonRPC\Client(API_URL);
$this->user->authentication('user', 'password');
- $this->user->debug = true;
+ // $this->user->debug = true;
}
public function testCreateProject()
diff --git a/tests/php.ini b/tests/php.ini
index 7079a4e6..9f683297 100644
--- a/tests/php.ini
+++ b/tests/php.ini
@@ -1,2 +1,5 @@
short_open_tag = On
-date.timezone = UTC \ No newline at end of file
+date.timezone = UTC
+opcache.enable = Off
+opcache.enable_cli = Off
+extension=ldap.so \ No newline at end of file
diff --git a/tests/units/ActionCommentCreationTest.php b/tests/units/Action/CommentCreationTest.php
index cf9e1e0a..6b5fe1ea 100644
--- a/tests/units/ActionCommentCreationTest.php
+++ b/tests/units/Action/CommentCreationTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Event\GenericEvent;
use Model\Task;
@@ -9,7 +9,7 @@ use Model\Comment;
use Model\Project;
use Integration\GithubWebhook;
-class ActionCommentCreationTest extends Base
+class CommentCreationTest extends Base
{
public function testWithoutRequiredParams()
{
diff --git a/tests/units/ActionTaskAssignColorCategoryTest.php b/tests/units/Action/TaskAssignColorCategoryTest.php
index 3669ce46..44d23943 100644
--- a/tests/units/ActionTaskAssignColorCategoryTest.php
+++ b/tests/units/Action/TaskAssignColorCategoryTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Model\Task;
use Model\TaskCreation;
@@ -9,7 +9,7 @@ use Model\Project;
use Model\Category;
use Event\GenericEvent;
-class ActionTaskAssignColorCategory extends Base
+class TaskAssignColorCategory extends Base
{
public function testBadProject()
{
diff --git a/tests/units/ActionTaskAssignColorColumnTest.php b/tests/units/Action/TaskAssignColorColumnTest.php
index 363bb05c..55767327 100644
--- a/tests/units/ActionTaskAssignColorColumnTest.php
+++ b/tests/units/Action/TaskAssignColorColumnTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Event\GenericEvent;
use Model\Task;
@@ -8,7 +8,7 @@ use Model\TaskCreation;
use Model\TaskFinder;
use Model\Project;
-class ActionTaskAssignColorColumnTest extends Base
+class TaskAssignColorColumnTest extends Base
{
public function testColorChange()
{
diff --git a/tests/units/ActionTaskAssignColorLinkTest.php b/tests/units/Action/TaskAssignColorLinkTest.php
index b0cd269e..c78af5bb 100644
--- a/tests/units/ActionTaskAssignColorLinkTest.php
+++ b/tests/units/Action/TaskAssignColorLinkTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Event\TaskLinkEvent;
use Model\Task;
@@ -9,7 +9,7 @@ use Model\TaskFinder;
use Model\TaskLink;
use Model\Project;
-class ActionTaskAssignColorLinkTest extends Base
+class TaskAssignColorLinkTest extends Base
{
public function testExecute()
{
diff --git a/tests/units/ActionTaskAssignColorUserTest.php b/tests/units/Action/TaskAssignColorUserTest.php
index c1bf3a34..896b48fc 100644
--- a/tests/units/ActionTaskAssignColorUserTest.php
+++ b/tests/units/Action/TaskAssignColorUserTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Model\Task;
use Model\TaskCreation;
@@ -8,7 +8,7 @@ use Model\TaskFinder;
use Model\Project;
use Event\GenericEvent;
-class ActionTaskAssignColorUser extends Base
+class TaskAssignColorUser extends Base
{
public function testBadProject()
{
diff --git a/tests/units/ActionTaskAssignCurrentUserTest.php b/tests/units/Action/TaskAssignCurrentUserTest.php
index f32fc77c..9ea816d4 100644
--- a/tests/units/ActionTaskAssignCurrentUserTest.php
+++ b/tests/units/Action/TaskAssignCurrentUserTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Event\GenericEvent;
use Model\Task;
@@ -9,7 +9,7 @@ use Model\TaskFinder;
use Model\Project;
use Model\UserSession;
-class ActionTaskAssignCurrentUser extends Base
+class TaskAssignCurrentUser extends Base
{
public function testBadProject()
{
diff --git a/tests/units/ActionTaskAssignSpecificUserTest.php b/tests/units/Action/TaskAssignSpecificUserTest.php
index ac054ba6..ae8de177 100644
--- a/tests/units/ActionTaskAssignSpecificUserTest.php
+++ b/tests/units/Action/TaskAssignSpecificUserTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Event\GenericEvent;
use Model\Task;
@@ -8,7 +8,7 @@ use Model\TaskCreation;
use Model\TaskFinder;
use Model\Project;
-class ActionTaskAssignSpecificUser extends Base
+class TaskAssignSpecificUser extends Base
{
public function testBadProject()
{
diff --git a/tests/units/ActionTaskCloseTest.php b/tests/units/Action/TaskCloseTest.php
index 7f2c42de..8cee6f13 100644
--- a/tests/units/ActionTaskCloseTest.php
+++ b/tests/units/Action/TaskCloseTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Event\GenericEvent;
use Model\Task;
@@ -9,7 +9,7 @@ use Model\TaskFinder;
use Model\Project;
use Integration\GithubWebhook;
-class ActionTaskCloseTest extends Base
+class TaskCloseTest extends Base
{
public function testExecutable()
{
diff --git a/tests/units/ActionTaskDuplicateAnotherProjectTest.php b/tests/units/Action/TaskDuplicateAnotherProjectTest.php
index 10c7c36a..37eb4052 100644
--- a/tests/units/ActionTaskDuplicateAnotherProjectTest.php
+++ b/tests/units/Action/TaskDuplicateAnotherProjectTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Event\GenericEvent;
use Model\Task;
@@ -8,7 +8,7 @@ use Model\TaskCreation;
use Model\TaskFinder;
use Model\Project;
-class ActionTaskDuplicateAnotherProject extends Base
+class TaskDuplicateAnotherProject extends Base
{
public function testBadProject()
{
diff --git a/tests/units/ActionTaskEmailTest.php b/tests/units/Action/TaskEmailTest.php
index adc74512..d74e1af2 100644
--- a/tests/units/ActionTaskEmailTest.php
+++ b/tests/units/Action/TaskEmailTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Event\GenericEvent;
use Model\Task;
@@ -9,7 +9,7 @@ use Model\TaskFinder;
use Model\Project;
use Model\User;
-class ActionTaskEmailTest extends Base
+class TaskEmailTest extends Base
{
public function testNoEmail()
{
diff --git a/tests/units/ActionTaskMoveAnotherProjectTest.php b/tests/units/Action/TaskMoveAnotherProjectTest.php
index 3f0c3de6..78bc6570 100644
--- a/tests/units/ActionTaskMoveAnotherProjectTest.php
+++ b/tests/units/Action/TaskMoveAnotherProjectTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Event\GenericEvent;
use Model\Task;
@@ -8,7 +8,7 @@ use Model\TaskCreation;
use Model\TaskFinder;
use Model\Project;
-class ActionTaskMoveAnotherProject extends Base
+class TaskMoveAnotherProject extends Base
{
public function testBadProject()
{
diff --git a/tests/units/ActionTaskMoveColumnCategoryChangeTest.php b/tests/units/Action/TaskMoveColumnCategoryChangeTest.php
index 0ddee786..4b7dec68 100644
--- a/tests/units/ActionTaskMoveColumnCategoryChangeTest.php
+++ b/tests/units/Action/TaskMoveColumnCategoryChangeTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Event\GenericEvent;
use Model\Task;
@@ -10,7 +10,7 @@ use Model\Project;
use Model\Category;
use Integration\GithubWebhook;
-class ActionTaskMoveColumnCategoryChangeTest extends Base
+class TaskMoveColumnCategoryChangeTest extends Base
{
public function testExecute()
{
diff --git a/tests/units/ActionTaskUpdateStartDateTest.php b/tests/units/Action/TaskUpdateStartDateTest.php
index a7df820f..14f3b5b7 100644
--- a/tests/units/ActionTaskUpdateStartDateTest.php
+++ b/tests/units/Action/TaskUpdateStartDateTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Event\GenericEvent;
use Model\Task;
@@ -8,7 +8,7 @@ use Model\TaskCreation;
use Model\TaskFinder;
use Model\Project;
-class ActionTaskUpdateStartDateTest extends Base
+class TaskUpdateStartDateTest extends Base
{
public function testExecute()
{
diff --git a/tests/units/Auth/LdapTest.php b/tests/units/Auth/LdapTest.php
new file mode 100644
index 00000000..a52b8cf8
--- /dev/null
+++ b/tests/units/Auth/LdapTest.php
@@ -0,0 +1,678 @@
+<?php
+
+namespace Auth;
+
+require_once __DIR__.'/../Base.php';
+
+function ldap_connect($hostname, $port)
+{
+ return LdapTest::$functions->ldap_connect($hostname, $port);
+}
+
+function ldap_set_option()
+{
+}
+
+function ldap_bind($link_identifier, $bind_rdn, $bind_password)
+{
+ return LdapTest::$functions->ldap_bind($link_identifier, $bind_rdn, $bind_password);
+}
+
+function ldap_search($link_identifier, $base_dn, $filter, array $attributes)
+{
+ return LdapTest::$functions->ldap_search($link_identifier, $base_dn, $filter, $attributes);
+}
+
+function ldap_get_entries($link_identifier, $result_identifier)
+{
+ return LdapTest::$functions->ldap_get_entries($link_identifier, $result_identifier);
+}
+
+class LdapTest extends \Base
+{
+ public static $functions;
+ private $ldap;
+
+ public function setUp()
+ {
+ parent::setup();
+
+ self::$functions = $this
+ ->getMockBuilder('stdClass')
+ ->setMethods(array(
+ 'ldap_connect',
+ 'ldap_set_option',
+ 'ldap_bind',
+ 'ldap_search',
+ 'ldap_get_entries',
+ ))
+ ->getMock();
+ }
+
+ public function tearDown()
+ {
+ parent::tearDown();
+ self::$functions = null;
+ }
+
+ public function testGetAttributes()
+ {
+ $ldap = new Ldap($this->container);
+ $this->assertCount(3, $ldap->getProfileAttributes());
+ $this->assertContains(LDAP_ACCOUNT_FULLNAME, $ldap->getProfileAttributes());
+ $this->assertContains(LDAP_ACCOUNT_EMAIL, $ldap->getProfileAttributes());
+ $this->assertContains(LDAP_ACCOUNT_MEMBEROF, $ldap->getProfileAttributes());
+ }
+
+ public function testConnectSuccess()
+ {
+ $ldap = $this
+ ->getMockBuilder('\Auth\Ldap')
+ ->setConstructorArgs(array($this->container))
+ ->setMethods(array('getLdapServer'))
+ ->getMock();
+
+ $ldap
+ ->expects($this->once())
+ ->method('getLdapServer')
+ ->will($this->returnValue('my_ldap_server'));
+
+ self::$functions
+ ->expects($this->once())
+ ->method('ldap_connect')
+ ->with(
+ $this->equalTo('my_ldap_server'),
+ $this->equalTo($ldap->getLdapPort())
+ )
+ ->will($this->returnValue('my_ldap_resource'));
+
+ $this->assertNotFalse($ldap->connect());
+ }
+
+ public function testConnectFailure()
+ {
+ $ldap = $this
+ ->getMockBuilder('\Auth\Ldap')
+ ->setConstructorArgs(array($this->container))
+ ->setMethods(array('getLdapServer'))
+ ->getMock();
+
+ $ldap
+ ->expects($this->once())
+ ->method('getLdapServer')
+ ->will($this->returnValue('my_ldap_server'));
+
+ self::$functions
+ ->expects($this->once())
+ ->method('ldap_connect')
+ ->with(
+ $this->equalTo('my_ldap_server'),
+ $this->equalTo($ldap->getLdapPort())
+ )
+ ->will($this->returnValue(false));
+
+ $this->assertFalse($ldap->connect());
+ }
+
+ public function testBindAnonymous()
+ {
+ $ldap = new Ldap($this->container);
+
+ self::$functions
+ ->expects($this->once())
+ ->method('ldap_bind')
+ ->with(
+ $this->equalTo('my_ldap_connection'),
+ $this->equalTo(null),
+ $this->equalTo(null)
+ )
+ ->will($this->returnValue(true));
+
+ $this->assertTrue($ldap->bind('my_ldap_connection', 'my_user', 'my_password', 'anonymous'));
+ }
+
+ public function testBindUser()
+ {
+ $ldap = $this
+ ->getMockBuilder('\Auth\Ldap')
+ ->setConstructorArgs(array($this->container))
+ ->setMethods(array('getLdapUserPattern'))
+ ->getMock();
+
+ $ldap
+ ->expects($this->once())
+ ->method('getLdapUserPattern')
+ ->will($this->returnValue('uid=my_user'));
+
+ self::$functions
+ ->expects($this->once())
+ ->method('ldap_bind')
+ ->with(
+ $this->equalTo('my_ldap_connection'),
+ $this->equalTo('uid=my_user'),
+ $this->equalTo('my_password')
+ )
+ ->will($this->returnValue(true));
+
+ $this->assertTrue($ldap->bind('my_ldap_connection', 'my_user', 'my_password', 'user'));
+ }
+
+ public function testBindProxy()
+ {
+ $ldap = $this
+ ->getMockBuilder('\Auth\Ldap')
+ ->setConstructorArgs(array($this->container))
+ ->setMethods(array('getLdapUsername', 'getLdapPassword'))
+ ->getMock();
+
+ $ldap
+ ->expects($this->once())
+ ->method('getLdapUsername')
+ ->will($this->returnValue('someone'));
+
+ $ldap
+ ->expects($this->once())
+ ->method('getLdapPassword')
+ ->will($this->returnValue('something'));
+
+ self::$functions
+ ->expects($this->once())
+ ->method('ldap_bind')
+ ->with(
+ $this->equalTo('my_ldap_connection'),
+ $this->equalTo('someone'),
+ $this->equalTo('something')
+ )
+ ->will($this->returnValue(true));
+
+ $this->assertTrue($ldap->bind('my_ldap_connection', 'my_user', 'my_password', 'proxy'));
+ }
+
+ public function testSearchSuccess()
+ {
+ $entries = array(
+ 'count' => 1,
+ 0 => array(
+ 'count' => 2,
+ 'dn' => 'uid=my_user,ou=People,dc=kanboard,dc=local',
+ 'displayname' => array(
+ 'count' => 1,
+ 0 => 'My user',
+ ),
+ 'mail' => array(
+ 'count' => 2,
+ 0 => 'user1@localhost',
+ 1 => 'user2@localhost',
+ ),
+ 0 => 'displayname',
+ 1 => 'mail',
+ )
+ );
+
+ $expected = array(
+ 'username' => 'my_user',
+ 'name' => 'My user',
+ 'email' => 'user1@localhost',
+ 'is_admin' => 0,
+ 'is_project_admin' => 0,
+ 'is_ldap_user' => 1,
+ );
+
+ $ldap = $this
+ ->getMockBuilder('\Auth\Ldap')
+ ->setConstructorArgs(array($this->container))
+ ->setMethods(array('getLdapUserPattern', 'getLdapBaseDn'))
+ ->getMock();
+
+ $ldap
+ ->expects($this->once())
+ ->method('getLdapUserPattern')
+ ->will($this->returnValue('uid=my_user'));
+
+ $ldap
+ ->expects($this->once())
+ ->method('getLdapBaseDn')
+ ->will($this->returnValue('ou=People,dc=kanboard,dc=local'));
+
+ self::$functions
+ ->expects($this->at(0))
+ ->method('ldap_search')
+ ->with(
+ $this->equalTo('my_ldap_connection'),
+ $this->equalTo('ou=People,dc=kanboard,dc=local'),
+ $this->equalTo('uid=my_user'),
+ $this->equalTo($ldap->getProfileAttributes())
+ )
+ ->will($this->returnValue('my_result_identifier'));
+
+ self::$functions
+ ->expects($this->at(1))
+ ->method('ldap_get_entries')
+ ->with(
+ $this->equalTo('my_ldap_connection'),
+ $this->equalTo('my_result_identifier')
+ )
+ ->will($this->returnValue($entries));
+
+ self::$functions
+ ->expects($this->at(2))
+ ->method('ldap_bind')
+ ->with(
+ $this->equalTo('my_ldap_connection'),
+ $this->equalTo('uid=my_user,ou=People,dc=kanboard,dc=local'),
+ $this->equalTo('my_password')
+ )
+ ->will($this->returnValue(true));
+
+ $this->assertEquals($expected, $ldap->getProfile('my_ldap_connection', 'my_user', 'my_password'));
+ }
+
+ public function testSearchWithBadPassword()
+ {
+ $entries = array(
+ 'count' => 1,
+ 0 => array(
+ 'count' => 2,
+ 'dn' => 'uid=my_user,ou=People,dc=kanboard,dc=local',
+ 'displayname' => array(
+ 'count' => 1,
+ 0 => 'My user',
+ ),
+ 'mail' => array(
+ 'count' => 2,
+ 0 => 'user1@localhost',
+ 1 => 'user2@localhost',
+ ),
+ 0 => 'displayname',
+ 1 => 'mail',
+ )
+ );
+
+ $ldap = $this
+ ->getMockBuilder('\Auth\Ldap')
+ ->setConstructorArgs(array($this->container))
+ ->setMethods(array('getLdapUserPattern', 'getLdapBaseDn'))
+ ->getMock();
+
+ $ldap
+ ->expects($this->once())
+ ->method('getLdapUserPattern')
+ ->will($this->returnValue('uid=my_user'));
+
+ $ldap
+ ->expects($this->once())
+ ->method('getLdapBaseDn')
+ ->will($this->returnValue('ou=People,dc=kanboard,dc=local'));
+
+ self::$functions
+ ->expects($this->at(0))
+ ->method('ldap_search')
+ ->with(
+ $this->equalTo('my_ldap_connection'),
+ $this->equalTo('ou=People,dc=kanboard,dc=local'),
+ $this->equalTo('uid=my_user'),
+ $this->equalTo($ldap->getProfileAttributes())
+ )
+ ->will($this->returnValue('my_result_identifier'));
+
+ self::$functions
+ ->expects($this->at(1))
+ ->method('ldap_get_entries')
+ ->with(
+ $this->equalTo('my_ldap_connection'),
+ $this->equalTo('my_result_identifier')
+ )
+ ->will($this->returnValue($entries));
+
+ self::$functions
+ ->expects($this->at(2))
+ ->method('ldap_bind')
+ ->with(
+ $this->equalTo('my_ldap_connection'),
+ $this->equalTo('uid=my_user,ou=People,dc=kanboard,dc=local'),
+ $this->equalTo('my_password')
+ )
+ ->will($this->returnValue(false));
+
+ $this->assertFalse($ldap->getProfile('my_ldap_connection', 'my_user', 'my_password'));
+ }
+
+ public function testSearchWithUserNotFound()
+ {
+ $ldap = $this
+ ->getMockBuilder('\Auth\Ldap')
+ ->setConstructorArgs(array($this->container))
+ ->setMethods(array('getLdapUserPattern', 'getLdapBaseDn'))
+ ->getMock();
+
+ $ldap
+ ->expects($this->once())
+ ->method('getLdapUserPattern')
+ ->will($this->returnValue('uid=my_user'));
+
+ $ldap
+ ->expects($this->once())
+ ->method('getLdapBaseDn')
+ ->will($this->returnValue('ou=People,dc=kanboard,dc=local'));
+
+ self::$functions
+ ->expects($this->at(0))
+ ->method('ldap_search')
+ ->with(
+ $this->equalTo('my_ldap_connection'),
+ $this->equalTo('ou=People,dc=kanboard,dc=local'),
+ $this->equalTo('uid=my_user'),
+ $this->equalTo($ldap->getProfileAttributes())
+ )
+ ->will($this->returnValue('my_result_identifier'));
+
+ self::$functions
+ ->expects($this->at(1))
+ ->method('ldap_get_entries')
+ ->with(
+ $this->equalTo('my_ldap_connection'),
+ $this->equalTo('my_result_identifier')
+ )
+ ->will($this->returnValue(array()));
+
+ $this->assertFalse($ldap->getProfile('my_ldap_connection', 'my_user', 'my_password'));
+ }
+
+ public function testSuccessfulAuthentication()
+ {
+ $this->container['userSession'] = $this
+ ->getMockBuilder('\Model\UserSession')
+ ->setConstructorArgs(array($this->container))
+ ->setMethods(array('refresh'))
+ ->getMock();
+
+ $this->container['user'] = $this
+ ->getMockBuilder('\Model\User')
+ ->setConstructorArgs(array($this->container))
+ ->setMethods(array('getByUsername'))
+ ->getMock();
+
+ $ldap = $this
+ ->getMockBuilder('\Auth\Ldap')
+ ->setConstructorArgs(array($this->container))
+ ->setMethods(array('findUser'))
+ ->getMock();
+
+ $ldap
+ ->expects($this->once())
+ ->method('findUser')
+ ->with(
+ $this->equalTo('user'),
+ $this->equalTo('password')
+ )
+ ->will($this->returnValue(array('username' => 'user', 'name' => 'My user', 'email' => 'user@here')));
+
+ $this->container['user']
+ ->expects($this->once())
+ ->method('getByUsername')
+ ->with(
+ $this->equalTo('user')
+ )
+ ->will($this->returnValue(array('id' => 2, 'username' => 'user', 'is_ldap_user' => 1)));
+
+ $this->container['userSession']
+ ->expects($this->once())
+ ->method('refresh');
+
+ $this->assertTrue($ldap->authenticate('user', 'password'));
+ }
+
+ public function testAuthenticationWithExistingLocalUser()
+ {
+ $this->container['userSession'] = $this
+ ->getMockBuilder('\Model\UserSession')
+ ->setConstructorArgs(array($this->container))
+ ->setMethods(array('refresh'))
+ ->getMock();
+
+ $this->container['user'] = $this
+ ->getMockBuilder('\Model\User')
+ ->setConstructorArgs(array($this->container))
+ ->setMethods(array('getByUsername'))
+ ->getMock();
+
+ $ldap = $this
+ ->getMockBuilder('\Auth\Ldap')
+ ->setConstructorArgs(array($this->container))
+ ->setMethods(array('findUser'))
+ ->getMock();
+
+ $ldap
+ ->expects($this->once())
+ ->method('findUser')
+ ->with(
+ $this->equalTo('user'),
+ $this->equalTo('password')
+ )
+ ->will($this->returnValue(array('username' => 'user', 'name' => 'My user', 'email' => 'user@here')));
+
+ $this->container['user']
+ ->expects($this->once())
+ ->method('getByUsername')
+ ->with(
+ $this->equalTo('user')
+ )
+ ->will($this->returnValue(array('id' => 2, 'username' => 'user', 'is_ldap_user' => 0)));
+
+ $this->container['userSession']
+ ->expects($this->never())
+ ->method('refresh');
+
+ $this->assertFalse($ldap->authenticate('user', 'password'));
+ }
+
+ public function testAuthenticationWithAutomaticAccountCreation()
+ {
+ $ldap_profile = array('username' => 'user', 'name' => 'My user', 'email' => 'user@here');
+
+ $this->container['userSession'] = $this
+ ->getMockBuilder('\Model\UserSession')
+ ->setConstructorArgs(array($this->container))
+ ->setMethods(array('refresh'))
+ ->getMock();
+
+ $this->container['user'] = $this
+ ->getMockBuilder('\Model\User')
+ ->setConstructorArgs(array($this->container))
+ ->setMethods(array('getByUsername', 'create'))
+ ->getMock();
+
+ $ldap = $this
+ ->getMockBuilder('\Auth\Ldap')
+ ->setConstructorArgs(array($this->container))
+ ->setMethods(array('findUser'))
+ ->getMock();
+
+ $ldap
+ ->expects($this->at(0))
+ ->method('findUser')
+ ->with(
+ $this->equalTo('user'),
+ $this->equalTo('password')
+ )
+ ->will($this->returnValue($ldap_profile));
+
+ $this->container['user']
+ ->expects($this->at(0))
+ ->method('getByUsername')
+ ->with(
+ $this->equalTo('user')
+ )
+ ->will($this->returnValue(null));
+
+ $this->container['user']
+ ->expects($this->at(1))
+ ->method('create')
+ ->with(
+ $this->equalTo($ldap_profile)
+ )
+ ->will($this->returnValue(true));
+
+ $this->container['user']
+ ->expects($this->at(2))
+ ->method('getByUsername')
+ ->with(
+ $this->equalTo('user')
+ )
+ ->will($this->returnValue(array('id' => 2, 'username' => 'user', 'is_ldap_user' => 1)));
+
+ $this->container['userSession']
+ ->expects($this->once())
+ ->method('refresh');
+
+ $this->assertTrue($ldap->authenticate('user', 'password'));
+ }
+
+ public function testAuthenticationWithAutomaticAccountCreationFailed()
+ {
+ $ldap_profile = array('username' => 'user', 'name' => 'My user', 'email' => 'user@here');
+
+ $this->container['userSession'] = $this
+ ->getMockBuilder('\Model\UserSession')
+ ->setConstructorArgs(array($this->container))
+ ->setMethods(array('refresh'))
+ ->getMock();
+
+ $this->container['user'] = $this
+ ->getMockBuilder('\Model\User')
+ ->setConstructorArgs(array($this->container))
+ ->setMethods(array('getByUsername', 'create'))
+ ->getMock();
+
+ $ldap = $this
+ ->getMockBuilder('\Auth\Ldap')
+ ->setConstructorArgs(array($this->container))
+ ->setMethods(array('findUser'))
+ ->getMock();
+
+ $ldap
+ ->expects($this->at(0))
+ ->method('findUser')
+ ->with(
+ $this->equalTo('user'),
+ $this->equalTo('password')
+ )
+ ->will($this->returnValue($ldap_profile));
+
+ $this->container['user']
+ ->expects($this->at(0))
+ ->method('getByUsername')
+ ->with(
+ $this->equalTo('user')
+ )
+ ->will($this->returnValue(null));
+
+ $this->container['user']
+ ->expects($this->at(1))
+ ->method('create')
+ ->with(
+ $this->equalTo($ldap_profile)
+ )
+ ->will($this->returnValue(false));
+
+ $this->container['userSession']
+ ->expects($this->never())
+ ->method('refresh');
+
+ $this->assertFalse($ldap->authenticate('user', 'password'));
+ }
+
+ public function testLookup()
+ {
+ $entries = array(
+ 'count' => 1,
+ 0 => array(
+ 'count' => 2,
+ 'dn' => 'uid=my_user,ou=People,dc=kanboard,dc=local',
+ 'displayname' => array(
+ 'count' => 1,
+ 0 => 'My LDAP user',
+ ),
+ 'mail' => array(
+ 'count' => 2,
+ 0 => 'user1@localhost',
+ 1 => 'user2@localhost',
+ ),
+ 'samaccountname' => array(
+ 'count' => 1,
+ 0 => 'my_ldap_user',
+ ),
+ 0 => 'displayname',
+ 1 => 'mail',
+ 2 => 'samaccountname',
+ )
+ );
+
+ $expected = array(
+ 'username' => 'my_ldap_user',
+ 'name' => 'My LDAP user',
+ 'email' => 'user1@localhost',
+ 'is_admin' => 0,
+ 'is_project_admin' => 0,
+ 'is_ldap_user' => 1,
+ );
+
+ $ldap = $this
+ ->getMockBuilder('\Auth\Ldap')
+ ->setConstructorArgs(array($this->container))
+ ->setMethods(array('connect', 'getLdapUserPattern', 'getLdapBaseDn', 'getLdapAccountId'))
+ ->getMock();
+
+ $ldap
+ ->expects($this->once())
+ ->method('connect')
+ ->will($this->returnValue('my_ldap_connection'));
+
+ $ldap
+ ->expects($this->once())
+ ->method('getLdapUserPattern')
+ ->will($this->returnValue('sAMAccountName=my_user'));
+
+ $ldap
+ ->expects($this->any())
+ ->method('getLdapAccountId')
+ ->will($this->returnValue('samaccountname'));
+
+ $ldap
+ ->expects($this->once())
+ ->method('getLdapBaseDn')
+ ->will($this->returnValue('ou=People,dc=kanboard,dc=local'));
+
+ self::$functions
+ ->expects($this->at(0))
+ ->method('ldap_bind')
+ ->with(
+ $this->equalTo('my_ldap_connection'),
+ $this->equalTo(null),
+ $this->equalTo(null)
+ )
+ ->will($this->returnValue(true));
+
+ self::$functions
+ ->expects($this->at(1))
+ ->method('ldap_search')
+ ->with(
+ $this->equalTo('my_ldap_connection'),
+ $this->equalTo('ou=People,dc=kanboard,dc=local'),
+ $this->equalTo('(&(sAMAccountName=my_user)(mail=user@localhost))'),
+ $this->equalTo($ldap->getProfileAttributes())
+ )
+ ->will($this->returnValue('my_result_identifier'));
+
+ self::$functions
+ ->expects($this->at(2))
+ ->method('ldap_get_entries')
+ ->with(
+ $this->equalTo('my_ldap_connection'),
+ $this->equalTo('my_result_identifier')
+ )
+ ->will($this->returnValue($entries));
+
+ $this->assertEquals($expected, $ldap->lookup('my_user', 'user@localhost'));
+ }
+}
diff --git a/tests/units/Auth/ReverseProxyTest.php b/tests/units/Auth/ReverseProxyTest.php
new file mode 100644
index 00000000..bbab7c0d
--- /dev/null
+++ b/tests/units/Auth/ReverseProxyTest.php
@@ -0,0 +1,37 @@
+<?php
+
+require_once __DIR__.'/../Base.php';
+
+use Auth\ReverseProxy;
+use Model\User;
+
+class ReverseProxyTest extends Base
+{
+ public function setUp()
+ {
+ parent::setup();
+ $_SERVER = array();
+ }
+
+ public function testFailedAuthentication()
+ {
+ $auth = new ReverseProxy($this->container);
+ $this->assertFalse($auth->authenticate());
+ }
+
+ public function testSuccessfulAuthentication()
+ {
+ $_SERVER[REVERSE_PROXY_USER_HEADER] = 'my_user';
+
+ $a = new ReverseProxy($this->container);
+ $u = new User($this->container);
+
+ $this->assertTrue($a->authenticate());
+
+ $user = $u->getByUsername('my_user');
+ $this->assertNotEmpty($user);
+ $this->assertEquals(0, $user['is_admin']);
+ $this->assertEquals(1, $user['is_ldap_user']);
+ $this->assertEquals(1, $user['disable_login_form']);
+ }
+}
diff --git a/tests/units/LexerTest.php b/tests/units/Core/LexerTest.php
index 3b15810e..044655fc 100644
--- a/tests/units/LexerTest.php
+++ b/tests/units/Core/LexerTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Core\Lexer;
diff --git a/tests/units/OAuth2Test.php b/tests/units/Core/OAuth2Test.php
index 0275f426..a0e4b43f 100644
--- a/tests/units/OAuth2Test.php
+++ b/tests/units/Core/OAuth2Test.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Core\OAuth2;
diff --git a/tests/units/Core/PluginLoaderTest.php b/tests/units/Core/PluginLoaderTest.php
new file mode 100644
index 00000000..62327f01
--- /dev/null
+++ b/tests/units/Core/PluginLoaderTest.php
@@ -0,0 +1,23 @@
+<?php
+
+require_once __DIR__.'/../Base.php';
+
+use Core\PluginLoader;
+
+class PluginLoaderTest extends Base
+{
+ public function testGetSchemaVersion()
+ {
+ $p = new PluginLoader($this->container);
+ $this->assertEquals(0, $p->getSchemaVersion('not_found'));
+
+ $this->assertTrue($p->setSchemaVersion('plugin1', 1));
+ $this->assertEquals(1, $p->getSchemaVersion('plugin1'));
+
+ $this->assertTrue($p->setSchemaVersion('plugin2', 33));
+ $this->assertEquals(33, $p->getSchemaVersion('plugin2'));
+
+ $this->assertTrue($p->setSchemaVersion('plugin1', 2));
+ $this->assertEquals(2, $p->getSchemaVersion('plugin1'));
+ }
+}
diff --git a/tests/units/RouterTest.php b/tests/units/Core/RouterTest.php
index e4582121..99c49ba8 100644
--- a/tests/units/RouterTest.php
+++ b/tests/units/Core/RouterTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Core\Router;
diff --git a/tests/units/Core/TemplateTest.php b/tests/units/Core/TemplateTest.php
new file mode 100644
index 00000000..9833397b
--- /dev/null
+++ b/tests/units/Core/TemplateTest.php
@@ -0,0 +1,28 @@
+<?php
+
+require_once __DIR__.'/../Base.php';
+
+use Core\Template;
+
+class TemplateTest extends Base
+{
+ public function testGetTemplateFile()
+ {
+ $t = new Template($this->container);
+ $this->assertStringEndsWith('app/Core/../Template/a/b.php', $t->getTemplateFile('a/b'));
+ }
+
+ public function testGetPluginTemplateFile()
+ {
+ $t = new Template($this->container);
+ $this->assertStringEndsWith('app/Core/../../plugins/Myplugin/Template/a/b.php', $t->getTemplateFile('myplugin:a/b'));
+ }
+
+ public function testGetOverridedTemplateFile()
+ {
+ $t = new Template($this->container);
+ $t->setTemplateOverride('a/b', 'myplugin:c');
+ $this->assertStringEndsWith('app/Core/../../plugins/Myplugin/Template/c.php', $t->getTemplateFile('a/b'));
+ $this->assertStringEndsWith('app/Core/../Template/d.php', $t->getTemplateFile('d'));
+ }
+}
diff --git a/tests/units/ToolTest.php b/tests/units/Core/ToolTest.php
index 4a62fe3b..e714f506 100644
--- a/tests/units/ToolTest.php
+++ b/tests/units/Core/ToolTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Core\Tool;
diff --git a/tests/units/AppHelperTest.php b/tests/units/Helper/AppHelperTest.php
index ad4bc151..54ccd697 100644
--- a/tests/units/AppHelperTest.php
+++ b/tests/units/Helper/AppHelperTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Core\Session;
use Helper\App;
diff --git a/tests/units/AssetHelperTest.php b/tests/units/Helper/AssetHelperTest.php
index 1143ce1f..ece81d04 100644
--- a/tests/units/AssetHelperTest.php
+++ b/tests/units/Helper/AssetHelperTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Helper\Asset;
use Model\Config;
diff --git a/tests/units/DatetimeHelperTest.php b/tests/units/Helper/DatetimeHelperTest.php
index 21d452dd..d4fc3c3d 100644
--- a/tests/units/DatetimeHelperTest.php
+++ b/tests/units/Helper/DatetimeHelperTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Helper\Dt;
diff --git a/tests/units/FileHelperText.php b/tests/units/Helper/FileHelperText.php
index ce04fdbd..cd5ebffd 100644
--- a/tests/units/FileHelperText.php
+++ b/tests/units/Helper/FileHelperText.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Helper\File;
diff --git a/tests/units/TextHelperTest.php b/tests/units/Helper/TextHelperTest.php
index 95c83e57..5495cd1c 100644
--- a/tests/units/TextHelperTest.php
+++ b/tests/units/Helper/TextHelperTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Helper\Text;
diff --git a/tests/units/UrlHelperTest.php b/tests/units/Helper/UrlHelperTest.php
index ebfe9c99..5c2477b5 100644
--- a/tests/units/UrlHelperTest.php
+++ b/tests/units/Helper/UrlHelperTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Helper\Url;
use Model\Config;
diff --git a/tests/units/UserHelperTest.php b/tests/units/Helper/UserHelperTest.php
index 9129edd9..947f606a 100644
--- a/tests/units/UserHelperTest.php
+++ b/tests/units/Helper/UserHelperTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Helper\User;
diff --git a/tests/units/HourlyRate.php b/tests/units/HourlyRate.php
deleted file mode 100644
index 5daf0446..00000000
--- a/tests/units/HourlyRate.php
+++ /dev/null
@@ -1,43 +0,0 @@
-<?php
-
-require_once __DIR__.'/Base.php';
-
-use Model\User;
-use Model\HourlyRate;
-
-class HourlyRateTest extends Base
-{
- public function testCreation()
- {
- $hr = new HourlyRate($this->container);
- $this->assertEquals(1, $hr->create(1, 32.4, 'EUR', '2015-01-01'));
- $this->assertEquals(2, $hr->create(1, 42, 'CAD', '2015-02-01'));
-
- $rates = $hr->getAllByUser(0);
- $this->assertEmpty($rates);
-
- $rates = $hr->getAllByUser(1);
- $this->assertNotEmpty($rates);
- $this->assertCount(2, $rates);
-
- $this->assertEquals(42, $rates[0]['rate']);
- $this->assertEquals('CAD', $rates[0]['currency']);
- $this->assertEquals('2015-02-01', date('Y-m-d', $rates[0]['date_effective']));
-
- $this->assertEquals(32.4, $rates[1]['rate']);
- $this->assertEquals('EUR', $rates[1]['currency']);
- $this->assertEquals('2015-01-01', date('Y-m-d', $rates[1]['date_effective']));
-
- $this->assertEquals(0, $hr->getCurrentRate(0));
- $this->assertEquals(42, $hr->getCurrentRate(1));
-
- $this->assertTrue($hr->remove(2));
- $this->assertEquals(32.4, $hr->getCurrentRate(1));
-
- $this->assertTrue($hr->remove(1));
- $this->assertEquals(0, $hr->getCurrentRate(1));
-
- $rates = $hr->getAllByUser(1);
- $this->assertEmpty($rates);
- }
-}
diff --git a/tests/units/BitbucketWebhookTest.php b/tests/units/Integration/BitbucketWebhookTest.php
index afc8be1d..1902f738 100644
--- a/tests/units/BitbucketWebhookTest.php
+++ b/tests/units/Integration/BitbucketWebhookTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Integration\BitbucketWebhook;
use Model\TaskCreation;
@@ -18,7 +18,7 @@ class BitbucketWebhookTest extends Base
$tc = new TaskCreation($this->container);
$p = new Project($this->container);
$bw = new BitbucketWebhook($this->container);
- $payload = json_decode(file_get_contents(__DIR__.'/fixtures/bitbucket_push.json'), true);
+ $payload = json_decode(file_get_contents(__DIR__.'/../fixtures/bitbucket_push.json'), true);
$this->assertEquals(1, $p->create(array('name' => 'test')));
$bw->setProjectId(1);
@@ -50,7 +50,7 @@ class BitbucketWebhookTest extends Base
$this->assertNotFalse($bw->parsePayload(
'issue:created',
- json_decode(file_get_contents(__DIR__.'/fixtures/bitbucket_issue_opened.json'), true)
+ json_decode(file_get_contents(__DIR__.'/../fixtures/bitbucket_issue_opened.json'), true)
));
}
@@ -69,7 +69,7 @@ class BitbucketWebhookTest extends Base
$this->assertNotFalse($g->parsePayload(
'issue:comment_created',
- json_decode(file_get_contents(__DIR__.'/fixtures/bitbucket_comment_created.json'), true)
+ json_decode(file_get_contents(__DIR__.'/../fixtures/bitbucket_comment_created.json'), true)
));
}
@@ -91,7 +91,7 @@ class BitbucketWebhookTest extends Base
$this->assertNotFalse($g->parsePayload(
'issue:comment_created',
- json_decode(file_get_contents(__DIR__.'/fixtures/bitbucket_comment_created.json'), true)
+ json_decode(file_get_contents(__DIR__.'/../fixtures/bitbucket_comment_created.json'), true)
));
}
@@ -116,7 +116,7 @@ class BitbucketWebhookTest extends Base
$this->assertNotFalse($g->parsePayload(
'issue:comment_created',
- json_decode(file_get_contents(__DIR__.'/fixtures/bitbucket_comment_created.json'), true)
+ json_decode(file_get_contents(__DIR__.'/../fixtures/bitbucket_comment_created.json'), true)
));
}
@@ -135,7 +135,7 @@ class BitbucketWebhookTest extends Base
$this->assertNotFalse($g->parsePayload(
'issue:updated',
- json_decode(file_get_contents(__DIR__.'/fixtures/bitbucket_issue_closed.json'), true)
+ json_decode(file_get_contents(__DIR__.'/../fixtures/bitbucket_issue_closed.json'), true)
));
}
@@ -154,7 +154,7 @@ class BitbucketWebhookTest extends Base
$this->assertFalse($g->parsePayload(
'issue:updated',
- json_decode(file_get_contents(__DIR__.'/fixtures/bitbucket_issue_closed.json'), true)
+ json_decode(file_get_contents(__DIR__.'/../fixtures/bitbucket_issue_closed.json'), true)
));
$this->assertEmpty($this->container['dispatcher']->getCalledListeners());
@@ -175,7 +175,7 @@ class BitbucketWebhookTest extends Base
$this->assertNotFalse($g->parsePayload(
'issue:updated',
- json_decode(file_get_contents(__DIR__.'/fixtures/bitbucket_issue_reopened.json'), true)
+ json_decode(file_get_contents(__DIR__.'/../fixtures/bitbucket_issue_reopened.json'), true)
));
}
@@ -194,7 +194,7 @@ class BitbucketWebhookTest extends Base
$this->assertFalse($g->parsePayload(
'issue:updated',
- json_decode(file_get_contents(__DIR__.'/fixtures/bitbucket_issue_reopened.json'), true)
+ json_decode(file_get_contents(__DIR__.'/../fixtures/bitbucket_issue_reopened.json'), true)
));
$this->assertEmpty($this->container['dispatcher']->getCalledListeners());
@@ -215,7 +215,7 @@ class BitbucketWebhookTest extends Base
$this->assertNotFalse($g->parsePayload(
'issue:updated',
- json_decode(file_get_contents(__DIR__.'/fixtures/bitbucket_issue_unassigned.json'), true)
+ json_decode(file_get_contents(__DIR__.'/../fixtures/bitbucket_issue_unassigned.json'), true)
));
}
@@ -240,7 +240,7 @@ class BitbucketWebhookTest extends Base
$this->assertNotFalse($g->parsePayload(
'issue:updated',
- json_decode(file_get_contents(__DIR__.'/fixtures/bitbucket_issue_assigned.json'), true)
+ json_decode(file_get_contents(__DIR__.'/../fixtures/bitbucket_issue_assigned.json'), true)
));
$this->assertNotEmpty($this->container['dispatcher']->getCalledListeners());
@@ -264,7 +264,7 @@ class BitbucketWebhookTest extends Base
$this->assertFalse($g->parsePayload(
'issue:updated',
- json_decode(file_get_contents(__DIR__.'/fixtures/bitbucket_issue_assigned.json'), true)
+ json_decode(file_get_contents(__DIR__.'/../fixtures/bitbucket_issue_assigned.json'), true)
));
$this->assertEmpty($this->container['dispatcher']->getCalledListeners());
@@ -285,7 +285,7 @@ class BitbucketWebhookTest extends Base
$this->assertFalse($g->parsePayload(
'issue:updated',
- json_decode(file_get_contents(__DIR__.'/fixtures/bitbucket_issue_assigned.json'), true)
+ json_decode(file_get_contents(__DIR__.'/../fixtures/bitbucket_issue_assigned.json'), true)
));
$this->assertEmpty($this->container['dispatcher']->getCalledListeners());
@@ -306,7 +306,7 @@ class BitbucketWebhookTest extends Base
$this->assertFalse($g->parsePayload(
'issue:updated',
- json_decode(file_get_contents(__DIR__.'/fixtures/bitbucket_issue_assigned.json'), true)
+ json_decode(file_get_contents(__DIR__.'/../fixtures/bitbucket_issue_assigned.json'), true)
));
$this->assertEmpty($this->container['dispatcher']->getCalledListeners());
diff --git a/tests/units/GithubWebhookTest.php b/tests/units/Integration/GithubWebhookTest.php
index e143cc1d..3b67ad1c 100644
--- a/tests/units/GithubWebhookTest.php
+++ b/tests/units/Integration/GithubWebhookTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Integration\GithubWebhook;
use Model\TaskCreation;
@@ -23,7 +23,7 @@ class GithubWebhookTest extends Base
$this->assertNotFalse($g->parsePayload(
'issues',
- json_decode(file_get_contents(__DIR__.'/fixtures/github_issue_opened.json'), true)
+ json_decode(file_get_contents(__DIR__.'/../fixtures/github_issue_opened.json'), true)
));
}
@@ -48,7 +48,7 @@ class GithubWebhookTest extends Base
$this->assertNotFalse($g->parsePayload(
'issues',
- json_decode(file_get_contents(__DIR__.'/fixtures/github_issue_assigned.json'), true)
+ json_decode(file_get_contents(__DIR__.'/../fixtures/github_issue_assigned.json'), true)
));
}
@@ -60,7 +60,7 @@ class GithubWebhookTest extends Base
$g = new GithubWebhook($this->container);
$g->setProjectId(1);
- $payload = json_decode(file_get_contents(__DIR__.'/fixtures/github_issue_assigned.json'), true);
+ $payload = json_decode(file_get_contents(__DIR__.'/../fixtures/github_issue_assigned.json'), true);
$this->assertFalse($g->handleIssueAssigned($payload['issue']));
}
@@ -76,7 +76,7 @@ class GithubWebhookTest extends Base
$g = new GithubWebhook($this->container);
$g->setProjectId(1);
- $payload = json_decode(file_get_contents(__DIR__.'/fixtures/github_issue_assigned.json'), true);
+ $payload = json_decode(file_get_contents(__DIR__.'/../fixtures/github_issue_assigned.json'), true);
$this->assertFalse($g->handleIssueAssigned($payload['issue']));
}
@@ -95,7 +95,7 @@ class GithubWebhookTest extends Base
$g = new GithubWebhook($this->container);
$g->setProjectId(1);
- $payload = json_decode(file_get_contents(__DIR__.'/fixtures/github_issue_assigned.json'), true);
+ $payload = json_decode(file_get_contents(__DIR__.'/../fixtures/github_issue_assigned.json'), true);
$this->assertFalse($g->handleIssueAssigned($payload['issue']));
}
@@ -117,7 +117,7 @@ class GithubWebhookTest extends Base
$g = new GithubWebhook($this->container);
$g->setProjectId(1);
- $payload = json_decode(file_get_contents(__DIR__.'/fixtures/github_issue_assigned.json'), true);
+ $payload = json_decode(file_get_contents(__DIR__.'/../fixtures/github_issue_assigned.json'), true);
$this->assertTrue($g->handleIssueAssigned($payload['issue']));
}
@@ -137,7 +137,7 @@ class GithubWebhookTest extends Base
$this->assertNotFalse($g->parsePayload(
'issues',
- json_decode(file_get_contents(__DIR__.'/fixtures/github_issue_unassigned.json'), true)
+ json_decode(file_get_contents(__DIR__.'/../fixtures/github_issue_unassigned.json'), true)
));
}
@@ -156,7 +156,7 @@ class GithubWebhookTest extends Base
$this->assertNotFalse($g->parsePayload(
'issues',
- json_decode(file_get_contents(__DIR__.'/fixtures/github_issue_closed.json'), true)
+ json_decode(file_get_contents(__DIR__.'/../fixtures/github_issue_closed.json'), true)
));
}
@@ -168,7 +168,7 @@ class GithubWebhookTest extends Base
$g = new GithubWebhook($this->container);
$g->setProjectId(1);
- $payload = json_decode(file_get_contents(__DIR__.'/fixtures/github_issue_closed.json'), true);
+ $payload = json_decode(file_get_contents(__DIR__.'/../fixtures/github_issue_closed.json'), true);
$this->assertFalse($g->handleIssueClosed($payload['issue']));
}
@@ -188,7 +188,7 @@ class GithubWebhookTest extends Base
$this->assertNotFalse($g->parsePayload(
'issues',
- json_decode(file_get_contents(__DIR__.'/fixtures/github_issue_reopened.json'), true)
+ json_decode(file_get_contents(__DIR__.'/../fixtures/github_issue_reopened.json'), true)
));
}
@@ -200,7 +200,7 @@ class GithubWebhookTest extends Base
$g = new GithubWebhook($this->container);
$g->setProjectId(1);
- $payload = json_decode(file_get_contents(__DIR__.'/fixtures/github_issue_reopened.json'), true);
+ $payload = json_decode(file_get_contents(__DIR__.'/../fixtures/github_issue_reopened.json'), true);
$this->assertFalse($g->handleIssueReopened($payload['issue']));
}
@@ -220,7 +220,7 @@ class GithubWebhookTest extends Base
$this->assertNotFalse($g->parsePayload(
'issues',
- json_decode(file_get_contents(__DIR__.'/fixtures/github_issue_labeled.json'), true)
+ json_decode(file_get_contents(__DIR__.'/../fixtures/github_issue_labeled.json'), true)
));
}
@@ -232,7 +232,7 @@ class GithubWebhookTest extends Base
$g = new GithubWebhook($this->container);
$g->setProjectId(1);
- $payload = json_decode(file_get_contents(__DIR__.'/fixtures/github_issue_labeled.json'), true);
+ $payload = json_decode(file_get_contents(__DIR__.'/../fixtures/github_issue_labeled.json'), true);
$this->assertFalse($g->handleIssueLabeled($payload['issue'], $payload['label']));
}
@@ -252,7 +252,7 @@ class GithubWebhookTest extends Base
$this->assertNotFalse($g->parsePayload(
'issues',
- json_decode(file_get_contents(__DIR__.'/fixtures/github_issue_unlabeled.json'), true)
+ json_decode(file_get_contents(__DIR__.'/../fixtures/github_issue_unlabeled.json'), true)
));
}
@@ -264,7 +264,7 @@ class GithubWebhookTest extends Base
$g = new GithubWebhook($this->container);
$g->setProjectId(1);
- $payload = json_decode(file_get_contents(__DIR__.'/fixtures/github_issue_unlabeled.json'), true);
+ $payload = json_decode(file_get_contents(__DIR__.'/../fixtures/github_issue_unlabeled.json'), true);
$this->assertFalse($g->handleIssueUnlabeled($payload['issue'], $payload['label']));
}
@@ -284,7 +284,7 @@ class GithubWebhookTest extends Base
$this->assertNotFalse($g->parsePayload(
'issue_comment',
- json_decode(file_get_contents(__DIR__.'/fixtures/github_comment_created.json'), true)
+ json_decode(file_get_contents(__DIR__.'/../fixtures/github_comment_created.json'), true)
));
}
@@ -306,7 +306,7 @@ class GithubWebhookTest extends Base
$this->assertNotFalse($g->parsePayload(
'issue_comment',
- json_decode(file_get_contents(__DIR__.'/fixtures/github_comment_created.json'), true)
+ json_decode(file_get_contents(__DIR__.'/../fixtures/github_comment_created.json'), true)
));
}
@@ -331,7 +331,7 @@ class GithubWebhookTest extends Base
$this->assertNotFalse($g->parsePayload(
'issue_comment',
- json_decode(file_get_contents(__DIR__.'/fixtures/github_comment_created.json'), true)
+ json_decode(file_get_contents(__DIR__.'/../fixtures/github_comment_created.json'), true)
));
}
@@ -350,7 +350,7 @@ class GithubWebhookTest extends Base
$this->assertNotFalse($g->parsePayload(
'push',
- json_decode(file_get_contents(__DIR__.'/fixtures/github_push.json'), true)
+ json_decode(file_get_contents(__DIR__.'/../fixtures/github_push.json'), true)
));
}
diff --git a/tests/units/GitlabWebhookTest.php b/tests/units/Integration/GitlabWebhookTest.php
index a2dc0d3a..ec073fee 100644
--- a/tests/units/GitlabWebhookTest.php
+++ b/tests/units/Integration/GitlabWebhookTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Integration\GitlabWebhook;
use Model\TaskCreation;
@@ -15,9 +15,9 @@ class GitlabWebhookTest extends Base
{
$g = new GitlabWebhook($this->container);
- $this->assertEquals(GitlabWebhook::TYPE_PUSH, $g->getType(json_decode(file_get_contents(__DIR__.'/fixtures/gitlab_push.json'), true)));
- $this->assertEquals(GitlabWebhook::TYPE_ISSUE, $g->getType(json_decode(file_get_contents(__DIR__.'/fixtures/gitlab_issue_opened.json'), true)));
- $this->assertEquals(GitlabWebhook::TYPE_COMMENT, $g->getType(json_decode(file_get_contents(__DIR__.'/fixtures/gitlab_comment_created.json'), true)));
+ $this->assertEquals(GitlabWebhook::TYPE_PUSH, $g->getType(json_decode(file_get_contents(__DIR__.'/../fixtures/gitlab_push.json'), true)));
+ $this->assertEquals(GitlabWebhook::TYPE_ISSUE, $g->getType(json_decode(file_get_contents(__DIR__.'/../fixtures/gitlab_issue_opened.json'), true)));
+ $this->assertEquals(GitlabWebhook::TYPE_COMMENT, $g->getType(json_decode(file_get_contents(__DIR__.'/../fixtures/gitlab_comment_created.json'), true)));
$this->assertEquals('', $g->getType(array()));
}
@@ -33,7 +33,7 @@ class GitlabWebhookTest extends Base
$this->container['dispatcher']->addListener(GitlabWebhook::EVENT_COMMIT, array($this, 'onCommit'));
- $event = json_decode(file_get_contents(__DIR__.'/fixtures/gitlab_push.json'), true);
+ $event = json_decode(file_get_contents(__DIR__.'/../fixtures/gitlab_push.json'), true);
// No task
$this->assertFalse($g->handleCommit($event['commits'][0]));
@@ -57,7 +57,7 @@ class GitlabWebhookTest extends Base
$this->container['dispatcher']->addListener(GitlabWebhook::EVENT_ISSUE_OPENED, array($this, 'onOpen'));
- $event = json_decode(file_get_contents(__DIR__.'/fixtures/gitlab_issue_opened.json'), true);
+ $event = json_decode(file_get_contents(__DIR__.'/../fixtures/gitlab_issue_opened.json'), true);
$this->assertTrue($g->handleIssueOpened($event['object_attributes']));
$called = $this->container['dispatcher']->getCalledListeners();
@@ -76,7 +76,7 @@ class GitlabWebhookTest extends Base
$this->container['dispatcher']->addListener(GitlabWebhook::EVENT_ISSUE_CLOSED, array($this, 'onClose'));
- $event = json_decode(file_get_contents(__DIR__.'/fixtures/gitlab_issue_closed.json'), true);
+ $event = json_decode(file_get_contents(__DIR__.'/../fixtures/gitlab_issue_closed.json'), true);
// Issue not there
$this->assertFalse($g->handleIssueClosed($event['object_attributes']));
@@ -112,7 +112,7 @@ class GitlabWebhookTest extends Base
$g->setProjectId(1);
$this->assertNotFalse($g->parsePayload(
- json_decode(file_get_contents(__DIR__.'/fixtures/gitlab_comment_created.json'), true)
+ json_decode(file_get_contents(__DIR__.'/../fixtures/gitlab_comment_created.json'), true)
));
}
@@ -133,7 +133,7 @@ class GitlabWebhookTest extends Base
$g->setProjectId(1);
$this->assertNotFalse($g->parsePayload(
- json_decode(file_get_contents(__DIR__.'/fixtures/gitlab_comment_created.json'), true)
+ json_decode(file_get_contents(__DIR__.'/../fixtures/gitlab_comment_created.json'), true)
));
}
@@ -157,7 +157,7 @@ class GitlabWebhookTest extends Base
$g->setProjectId(1);
$this->assertNotFalse($g->parsePayload(
- json_decode(file_get_contents(__DIR__.'/fixtures/gitlab_comment_created.json'), true)
+ json_decode(file_get_contents(__DIR__.'/../fixtures/gitlab_comment_created.json'), true)
));
}
diff --git a/tests/units/MailgunTest.php b/tests/units/Integration/MailgunTest.php
index ce53228e..67914b0a 100644
--- a/tests/units/MailgunTest.php
+++ b/tests/units/Integration/MailgunTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Integration\Mailgun;
use Model\TaskCreation;
diff --git a/tests/units/PostmarkTest.php b/tests/units/Integration/PostmarkTest.php
index f3d2e55b..9115e24a 100644
--- a/tests/units/PostmarkTest.php
+++ b/tests/units/Integration/PostmarkTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Integration\Postmark;
use Model\TaskCreation;
diff --git a/tests/units/SendgridTest.php b/tests/units/Integration/SendgridTest.php
index 1fc5d0d5..b2352076 100644
--- a/tests/units/SendgridTest.php
+++ b/tests/units/Integration/SendgridTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Integration\Sendgrid;
use Model\TaskCreation;
diff --git a/tests/units/LocaleTest.php b/tests/units/Locale/LocaleTest.php
index e00b8c83..6cace0d5 100644
--- a/tests/units/LocaleTest.php
+++ b/tests/units/Locale/LocaleTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
class LocaleTest extends Base
{
diff --git a/tests/units/AclTest.php b/tests/units/Model/AclTest.php
index 4d735dfb..3cb28a77 100644
--- a/tests/units/AclTest.php
+++ b/tests/units/Model/AclTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Core\Session;
use Model\Acl;
@@ -290,4 +290,16 @@ class AclTest extends Base
$this->assertFalse($acl->isAllowed('task', 'remove', 1));
$this->assertTrue($acl->isAllowed('app', 'index', 1));
}
+
+ public function testExtend()
+ {
+ $acl = new Acl($this->container);
+
+ $this->assertFalse($acl->isProjectManagerAction('plop', 'show'));
+
+ $acl->extend('project_manager_acl', array('plop' => '*'));
+
+ $this->assertTrue($acl->isProjectManagerAction('plop', 'show'));
+ $this->assertTrue($acl->isProjectManagerAction('swimlane', 'index'));
+ }
}
diff --git a/tests/units/ActionTest.php b/tests/units/Model/ActionTest.php
index 7e0287a2..9034679b 100644
--- a/tests/units/ActionTest.php
+++ b/tests/units/Model/ActionTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Model\Action;
use Model\Project;
diff --git a/tests/units/AuthenticationTest.php b/tests/units/Model/AuthenticationTest.php
index 75b55ece..7ce81d8b 100644
--- a/tests/units/AuthenticationTest.php
+++ b/tests/units/Model/AuthenticationTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Model\User;
use Model\Authentication;
diff --git a/tests/units/BoardTest.php b/tests/units/Model/BoardTest.php
index ebd4f655..b527e983 100644
--- a/tests/units/BoardTest.php
+++ b/tests/units/Model/BoardTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Model\Project;
use Model\Board;
@@ -70,7 +70,7 @@ class BoardTest extends Base
$s = new Swimlane($this->container);
$this->assertEquals(1, $p->create(array('name' => 'Project #1')));
- $this->assertEquals(1, $s->create(1, 'test 1'));
+ $this->assertEquals(1, $s->create(array('project_id' => 1, 'name' => 'test 1')));
$this->assertEquals(1, $tc->create(array('title' => 'Task #1', 'project_id' => 1, 'column_id' => 1)));
$this->assertEquals(2, $tc->create(array('title' => 'Task #2', 'project_id' => 1, 'column_id' => 3)));
$this->assertEquals(3, $tc->create(array('title' => 'Task #3', 'project_id' => 1, 'column_id' => 2, 'swimlane_id' => 1)));
diff --git a/tests/units/CategoryTest.php b/tests/units/Model/CategoryTest.php
index 638f3fe4..0467dda4 100644
--- a/tests/units/CategoryTest.php
+++ b/tests/units/Model/CategoryTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Model\Task;
use Model\TaskCreation;
diff --git a/tests/units/CommentTest.php b/tests/units/Model/CommentTest.php
index 295ac60e..5e532b8a 100644
--- a/tests/units/CommentTest.php
+++ b/tests/units/Model/CommentTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Model\Task;
use Model\TaskCreation;
diff --git a/tests/units/ConfigTest.php b/tests/units/Model/ConfigTest.php
index f93619d6..c0ed0313 100644
--- a/tests/units/ConfigTest.php
+++ b/tests/units/Model/ConfigTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Model\Config;
use Core\Session;
diff --git a/tests/units/DateParserTest.php b/tests/units/Model/DateParserTest.php
index 4b3e93c8..23c31f14 100644
--- a/tests/units/DateParserTest.php
+++ b/tests/units/Model/DateParserTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Model\DateParser;
diff --git a/tests/units/FileTest.php b/tests/units/Model/FileTest.php
index 4ea7f386..e7520c89 100644
--- a/tests/units/FileTest.php
+++ b/tests/units/Model/FileTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Model\Task;
use Model\File;
@@ -9,6 +9,17 @@ use Model\Project;
class FileTest extends Base
{
+ public function setUp()
+ {
+ parent::setUp();
+
+ $this->container['objectStorage'] = $this
+ ->getMockBuilder('\Core\ObjectStorage\FileStorage')
+ ->setConstructorArgs(array($this->container))
+ ->setMethods(array('put', 'moveFile', 'remove'))
+ ->getMock();
+ }
+
public function testCreation()
{
$p = new Project($this->container);
@@ -85,13 +96,32 @@ class FileTest extends Base
public function testUploadScreenshot()
{
$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->uploadScreenshot(1, 1, base64_encode('image data')));
+ $data = base64_encode('image data');
+
+ $f = $this
+ ->getMockBuilder('\Model\File')
+ ->setConstructorArgs(array($this->container))
+ ->setMethods(array('generateThumbnailFromData'))
+ ->getMock();
+
+ $this->container['objectStorage']
+ ->expects($this->once())
+ ->method('put')
+ ->with(
+ $this->stringContains('1/1/'),
+ $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);
@@ -113,7 +143,18 @@ class FileTest extends Base
$this->assertEquals(1, $p->create(array('name' => 'test')));
$this->assertEquals(1, $tc->create(array('project_id' => 1, 'title' => 'test')));
- $this->assertEquals(1, $f->uploadContent(1, 1, 'my file.pdf', base64_encode('file data')));
+ $data = base64_encode('file data');
+
+ $this->container['objectStorage']
+ ->expects($this->once())
+ ->method('put')
+ ->with(
+ $this->stringContains('1/1/'),
+ $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);
@@ -170,9 +211,33 @@ class FileTest extends Base
$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(1, $f->create(1, 'B.pdf', '/tmp/foo1', 10));
+ $this->assertEquals(2, $f->create(1, 'A.png', '/tmp/foo2', 10));
+ $this->assertEquals(3, $f->create(1, 'D.doc', '/tmp/foo3', 10));
+
+ $this->container['objectStorage']
+ ->expects($this->at(0))
+ ->method('remove')
+ ->with(
+ $this->equalTo('/tmp/foo2')
+ )
+ ->will($this->returnValue(true));
+
+ $this->container['objectStorage']
+ ->expects($this->at(1))
+ ->method('remove')
+ ->with(
+ $this->equalTo('/tmp/foo1')
+ )
+ ->will($this->returnValue(true));
+
+ $this->container['objectStorage']
+ ->expects($this->at(2))
+ ->method('remove')
+ ->with(
+ $this->equalTo('/tmp/foo3')
+ )
+ ->will($this->returnValue(true));
$this->assertTrue($f->remove(2));
diff --git a/tests/units/LinkTest.php b/tests/units/Model/LinkTest.php
index 45e9796c..0399f13e 100644
--- a/tests/units/LinkTest.php
+++ b/tests/units/Model/LinkTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Model\Link;
diff --git a/tests/units/NotificationTest.php b/tests/units/Model/NotificationTest.php
index 4ae46ee3..b0eafc83 100644
--- a/tests/units/NotificationTest.php
+++ b/tests/units/Model/NotificationTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Model\TaskFinder;
use Model\TaskCreation;
diff --git a/tests/units/ProjectActivityTest.php b/tests/units/Model/ProjectActivityTest.php
index f0b27649..fa45d740 100644
--- a/tests/units/ProjectActivityTest.php
+++ b/tests/units/Model/ProjectActivityTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Model\Task;
use Model\TaskFinder;
diff --git a/tests/units/ProjectDailyColumnStatsTest.php b/tests/units/Model/ProjectDailyColumnStatsTest.php
index d314ac93..e01bdcc4 100644
--- a/tests/units/ProjectDailyColumnStatsTest.php
+++ b/tests/units/Model/ProjectDailyColumnStatsTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Model\Project;
use Model\ProjectDailyColumnStats;
diff --git a/tests/units/ProjectDuplicationTest.php b/tests/units/Model/ProjectDuplicationTest.php
index 311ecc4a..0388d4a2 100644
--- a/tests/units/ProjectDuplicationTest.php
+++ b/tests/units/Model/ProjectDuplicationTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Model\Action;
use Model\Project;
@@ -20,7 +20,7 @@ class ProjectDuplicationTest extends Base
$pd = new ProjectDuplication($this->container);
$this->assertEquals('test (Clone)', $pd->getClonedProjectName('test'));
-
+
$this->assertEquals(50, strlen($pd->getClonedProjectName(str_repeat('a', 50))));
$this->assertEquals(str_repeat('a', 42).' (Clone)', $pd->getClonedProjectName(str_repeat('a', 50)));
@@ -155,7 +155,7 @@ class ProjectDuplicationTest extends Base
$pd = new ProjectDuplication($this->container);
$this->assertEquals(1, $p->create(array('name' => 'P1')));
-
+
$this->assertEquals(1, $a->create(array(
'project_id' => 1,
'event_name' => Task::EVENT_MOVE_COLUMN,
@@ -185,7 +185,7 @@ class ProjectDuplicationTest extends Base
$this->assertEquals(1, $c->create(array('name' => 'C1', 'project_id' => 1)));
$this->assertEquals(2, $c->create(array('name' => 'C2', 'project_id' => 1)));
$this->assertEquals(3, $c->create(array('name' => 'C3', 'project_id' => 1)));
-
+
$this->assertEquals(1, $a->create(array(
'project_id' => 1,
'event_name' => Task::EVENT_CREATE_UPDATE,
@@ -215,9 +215,9 @@ class ProjectDuplicationTest extends Base
$this->assertEquals(1, $p->create(array('name' => 'P1')));
// create initial swimlanes
- $this->assertEquals(1, $s->create(1, 'S1'));
- $this->assertEquals(2, $s->create(1, 'S2'));
- $this->assertEquals(3, $s->create(1, 'S3'));
+ $this->assertEquals(1, $s->create(array('project_id' => 1, 'name' => 'S1')));
+ $this->assertEquals(2, $s->create(array('project_id' => 1, 'name' => 'S2')));
+ $this->assertEquals(3, $s->create(array('project_id' => 1, 'name' => 'S3')));
$default_swimlane1 = $s->getDefault(1);
$default_swimlane1['default_swimlane'] = 'New Default';
@@ -277,9 +277,9 @@ class ProjectDuplicationTest extends Base
$this->assertEquals(1, $p->create(array('name' => 'P1')));
// create initial swimlanes
- $this->assertEquals(1, $s->create(1, 'S1'));
- $this->assertEquals(2, $s->create(1, 'S2'));
- $this->assertEquals(3, $s->create(1, 'S3'));
+ $this->assertEquals(1, $s->create(array('project_id' => 1, 'name' => 'S1')));
+ $this->assertEquals(2, $s->create(array('project_id' => 1, 'name' => 'S2')));
+ $this->assertEquals(3, $s->create(array('project_id' => 1, 'name' => 'S3')));
$default_swimlane1 = $s->getDefault(1);
$default_swimlane1['default_swimlane'] = 'New Default';
@@ -323,9 +323,9 @@ class ProjectDuplicationTest extends Base
$this->assertEquals(1, $p->create(array('name' => 'P1')));
// create initial swimlanes
- $this->assertEquals(1, $s->create(1, 'S1'));
- $this->assertEquals(2, $s->create(1, 'S2'));
- $this->assertEquals(3, $s->create(1, 'S3'));
+ $this->assertEquals(1, $s->create(array('project_id' => 1, 'name' => 'S1')));
+ $this->assertEquals(2, $s->create(array('project_id' => 1, 'name' => 'S2')));
+ $this->assertEquals(3, $s->create(array('project_id' => 1, 'name' => 'S3')));
$default_swimlane1 = $s->getDefault(1);
$default_swimlane1['default_swimlane'] = 'New Default';
diff --git a/tests/units/ProjectPermissionTest.php b/tests/units/Model/ProjectPermissionTest.php
index 9f6d6a52..475dd013 100644
--- a/tests/units/ProjectPermissionTest.php
+++ b/tests/units/Model/ProjectPermissionTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Model\Project;
use Model\ProjectPermission;
diff --git a/tests/units/ProjectTest.php b/tests/units/Model/ProjectTest.php
index b7f168d7..9d7b6c0d 100644
--- a/tests/units/ProjectTest.php
+++ b/tests/units/Model/ProjectTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Core\Translator;
use Subscriber\ProjectModificationDateSubscriber;
@@ -26,7 +26,7 @@ class ProjectTest extends Base
$this->assertNotFalse($p->create(array('name' => 'UnitTest '.$locale)), 'Unable to create project with '.$locale.':'.$language);
}
- Translator::load('en_US');
+ Translator::unload();
}
public function testCreation()
diff --git a/tests/units/SubtaskTest.php b/tests/units/Model/SubtaskTest.php
index 3c8cab49..5773893c 100644
--- a/tests/units/SubtaskTest.php
+++ b/tests/units/Model/SubtaskTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Model\Task;
use Model\TaskCreation;
@@ -13,6 +13,136 @@ use Model\UserSession;
class SubTaskTest extends Base
{
+ public function onSubtaskCreated($event)
+ {
+ $this->assertInstanceOf('Event\SubtaskEvent', $event);
+ $data = $event->getAll();
+
+ $this->assertArrayHasKey('id', $data);
+ $this->assertArrayHasKey('title', $data);
+ $this->assertArrayHasKey('status', $data);
+ $this->assertArrayHasKey('time_estimated', $data);
+ $this->assertArrayHasKey('time_spent', $data);
+ $this->assertArrayHasKey('status', $data);
+ $this->assertArrayHasKey('task_id', $data);
+ $this->assertArrayHasKey('user_id', $data);
+ $this->assertArrayHasKey('position', $data);
+ $this->assertNotEmpty($data['task_id']);
+ $this->assertNotEmpty($data['id']);
+ }
+
+ public function onSubtaskUpdated($event)
+ {
+ $this->assertInstanceOf('Event\SubtaskEvent', $event);
+ $data = $event->getAll();
+
+ $this->assertArrayHasKey('id', $data);
+ $this->assertArrayHasKey('title', $data);
+ $this->assertArrayHasKey('status', $data);
+ $this->assertArrayHasKey('time_estimated', $data);
+ $this->assertArrayHasKey('time_spent', $data);
+ $this->assertArrayHasKey('status', $data);
+ $this->assertArrayHasKey('task_id', $data);
+ $this->assertArrayHasKey('user_id', $data);
+ $this->assertArrayHasKey('position', $data);
+ $this->assertArrayHasKey('changes', $data);
+ $this->assertArrayHasKey('user_id', $data['changes']);
+ $this->assertArrayHasKey('status', $data['changes']);
+
+ $this->assertEquals(Subtask::STATUS_INPROGRESS, $data['changes']['status']);
+ $this->assertEquals(1, $data['changes']['user_id']);
+ }
+
+ public function onSubtaskDeleted($event)
+ {
+ $this->assertInstanceOf('Event\SubtaskEvent', $event);
+ $data = $event->getAll();
+
+ $this->assertArrayHasKey('id', $data);
+ $this->assertArrayHasKey('title', $data);
+ $this->assertArrayHasKey('status', $data);
+ $this->assertArrayHasKey('time_estimated', $data);
+ $this->assertArrayHasKey('time_spent', $data);
+ $this->assertArrayHasKey('status', $data);
+ $this->assertArrayHasKey('task_id', $data);
+ $this->assertArrayHasKey('user_id', $data);
+ $this->assertArrayHasKey('position', $data);
+ $this->assertNotEmpty($data['task_id']);
+ $this->assertNotEmpty($data['id']);
+ }
+
+ public function testCreation()
+ {
+ $tc = new TaskCreation($this->container);
+ $s = new Subtask($this->container);
+ $p = new Project($this->container);
+
+ $this->assertEquals(1, $p->create(array('name' => 'test')));
+ $this->assertEquals(1, $tc->create(array('title' => 'test 1', 'project_id' => 1)));
+
+ $this->container['dispatcher']->addListener(Subtask::EVENT_CREATE, array($this, 'onSubtaskCreated'));
+
+ $this->assertEquals(1, $s->create(array('title' => 'subtask #1', 'task_id' => 1)));
+
+ $subtask = $s->getById(1);
+ $this->assertNotEmpty($subtask);
+ $this->assertEquals(1, $subtask['id']);
+ $this->assertEquals(1, $subtask['task_id']);
+ $this->assertEquals('subtask #1', $subtask['title']);
+ $this->assertEquals(Subtask::STATUS_TODO, $subtask['status']);
+ $this->assertEquals(0, $subtask['time_estimated']);
+ $this->assertEquals(0, $subtask['time_spent']);
+ $this->assertEquals(0, $subtask['user_id']);
+ $this->assertEquals(1, $subtask['position']);
+ }
+
+ public function testModification()
+ {
+ $tc = new TaskCreation($this->container);
+ $s = new Subtask($this->container);
+ $p = new Project($this->container);
+
+ $this->assertEquals(1, $p->create(array('name' => 'test')));
+ $this->assertEquals(1, $tc->create(array('title' => 'test 1', 'project_id' => 1)));
+
+ $this->container['dispatcher']->addListener(Subtask::EVENT_UPDATE, array($this, 'onSubtaskUpdated'));
+
+ $this->assertEquals(1, $s->create(array('title' => 'subtask #1', 'task_id' => 1)));
+ $this->assertTrue($s->update(array('id' => 1, 'user_id' => 1, 'status' => Subtask::STATUS_INPROGRESS)));
+
+ $subtask = $s->getById(1);
+ $this->assertNotEmpty($subtask);
+ $this->assertEquals(1, $subtask['id']);
+ $this->assertEquals(1, $subtask['task_id']);
+ $this->assertEquals('subtask #1', $subtask['title']);
+ $this->assertEquals(Subtask::STATUS_INPROGRESS, $subtask['status']);
+ $this->assertEquals(0, $subtask['time_estimated']);
+ $this->assertEquals(0, $subtask['time_spent']);
+ $this->assertEquals(1, $subtask['user_id']);
+ $this->assertEquals(1, $subtask['position']);
+ }
+
+ public function testRemove()
+ {
+ $tc = new TaskCreation($this->container);
+ $s = new Subtask($this->container);
+ $p = new Project($this->container);
+
+ $this->assertEquals(1, $p->create(array('name' => 'test')));
+ $this->assertEquals(1, $tc->create(array('title' => 'test 1', 'project_id' => 1)));
+ $this->assertEquals(1, $s->create(array('title' => 'subtask #1', 'task_id' => 1)));
+
+ $this->container['dispatcher']->addListener(Subtask::EVENT_DELETE, array($this, 'onSubtaskDeleted'));
+
+ $subtask = $s->getById(1);
+ $this->assertNotEmpty($subtask);
+
+ $this->assertTrue($s->remove(1));
+
+ $subtask = $s->getById(1);
+ $this->assertEmpty($subtask);
+ }
+
public function testToggleStatusWithoutSession()
{
$tc = new TaskCreation($this->container);
diff --git a/tests/units/SubtaskTimeTrackingTest.php b/tests/units/Model/SubtaskTimeTrackingTest.php
index 94f2ce54..f68f283d 100644
--- a/tests/units/SubtaskTimeTrackingTest.php
+++ b/tests/units/Model/SubtaskTimeTrackingTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Model\TaskFinder;
use Model\TaskCreation;
@@ -151,8 +151,8 @@ class SubtaskTimeTrackingTest extends Base
$time = $st->calculateSubtaskTime(1);
$this->assertNotempty($time);
$this->assertCount(2, $time);
- $this->assertEquals(3.3, $time['total_spent'], 'Total spent', 0.01);
- $this->assertEquals(7.7, $time['total_estimated'], 'Total estimated', 0.01);
+ $this->assertEquals(3.3, $time['time_spent'], 'Total spent', 0.01);
+ $this->assertEquals(7.7, $time['time_estimated'], 'Total estimated', 0.01);
}
public function testUpdateSubtaskTimeSpent()
@@ -184,13 +184,13 @@ class SubtaskTimeTrackingTest extends Base
$time = $st->calculateSubtaskTime(1);
$this->assertNotempty($time);
- $this->assertEquals(4.2, $time['total_spent'], 'Total spent', 0.01);
- $this->assertEquals(0, $time['total_estimated'], 'Total estimated', 0.01);
+ $this->assertEquals(4.2, $time['time_spent'], 'Total spent', 0.01);
+ $this->assertEquals(0, $time['time_estimated'], 'Total estimated', 0.01);
$time = $st->calculateSubtaskTime(2);
$this->assertNotempty($time);
- $this->assertEquals(0, $time['total_spent'], 'Total spent', 0.01);
- $this->assertEquals(0, $time['total_estimated'], 'Total estimated', 0.01);
+ $this->assertEquals(0, $time['time_spent'], 'Total spent', 0.01);
+ $this->assertEquals(0, $time['time_estimated'], 'Total estimated', 0.01);
}
public function testUpdateTaskTimeTracking()
@@ -205,7 +205,7 @@ class SubtaskTimeTrackingTest extends Base
$this->assertEquals(1, $tc->create(array('title' => 'test 1', 'project_id' => 1)));
$this->assertEquals(2, $tc->create(array('title' => 'test 2', 'project_id' => 1, 'time_estimated' => 1.5, 'time_spent' => 0.5)));
- $this->assertEquals(3, $tc->create(array('title' => 'test 2', 'project_id' => 1, 'time_estimated' => 4, 'time_spent' => 2)));
+ $this->assertEquals(3, $tc->create(array('title' => 'test 3', 'project_id' => 1, 'time_estimated' => 4, 'time_spent' => 2)));
$this->assertEquals(1, $s->create(array('title' => 'subtask #1', 'task_id' => 1, 'time_spent' => 2.2)));
$this->assertEquals(2, $s->create(array('title' => 'subtask #2', 'task_id' => 1, 'time_estimated' => 1)));
@@ -231,8 +231,18 @@ class SubtaskTimeTrackingTest extends Base
$task = $tf->getById(3);
$this->assertNotEmpty($task);
- $this->assertEquals(4, $task['time_estimated']);
+ $this->assertEquals(0, $task['time_estimated']);
$this->assertEquals(8, $task['time_spent']);
+
+ $this->assertTrue($s->remove(3));
+ $this->assertTrue($s->remove(4));
+
+ $st->updateTaskTimeTracking(2);
+
+ $task = $tf->getById(2);
+ $this->assertNotEmpty($task);
+ $this->assertEquals(0, $task['time_estimated']);
+ $this->assertEquals(0, $task['time_spent']);
}
public function testGetCalendarEvents()
diff --git a/tests/units/SwimlaneTest.php b/tests/units/Model/SwimlaneTest.php
index 37226613..7e1adb8e 100644
--- a/tests/units/SwimlaneTest.php
+++ b/tests/units/Model/SwimlaneTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Model\Project;
use Model\Task;
@@ -16,7 +16,7 @@ class SwimlaneTest extends Base
$s = new Swimlane($this->container);
$this->assertEquals(1, $p->create(array('name' => 'UnitTest')));
- $this->assertEquals(1, $s->create(1, 'Swimlane #1'));
+ $this->assertEquals(1, $s->create(array('project_id' => 1, 'name' => 'Swimlane #1')));
$swimlanes = $s->getSwimlanes(1);
$this->assertNotEmpty($swimlanes);
@@ -37,8 +37,8 @@ class SwimlaneTest extends Base
$s = new Swimlane($this->container);
$this->assertEquals(1, $p->create(array('name' => 'UnitTest')));
- $this->assertEquals(1, $s->create(1, 'Swimlane #1'));
- $this->assertEquals(2, $s->create(1, 'Swimlane #2'));
+ $this->assertEquals(1, $s->create(array('project_id' => 1, 'name' => 'Swimlane #1')));
+ $this->assertEquals(2, $s->create(array('project_id' => 1, 'name' => 'Swimlane #2')));
$swimlanes = $s->getList(1);
$expected = array('Default swimlane', 'Swimlane #1', 'Swimlane #2');
@@ -46,26 +46,26 @@ class SwimlaneTest extends Base
$this->assertEquals($expected, $swimlanes);
}
- public function testRename()
+ public function testUpdate()
{
$p = new Project($this->container);
$s = new Swimlane($this->container);
$this->assertEquals(1, $p->create(array('name' => 'UnitTest')));
- $this->assertEquals(1, $s->create(1, 'Swimlane #1'));
+ $this->assertEquals(1, $s->create(array('project_id' => 1, 'name' => 'Swimlane #1')));
$swimlane = $s->getById(1);
$this->assertNotEmpty($swimlane);
$this->assertEquals('Swimlane #1', $swimlane['name']);
- $this->assertTrue($s->rename(1, 'foobar'));
+ $this->assertTrue($s->update(array('id' => 1, 'name' => 'foobar')));
$swimlane = $s->getById(1);
$this->assertNotEmpty($swimlane);
$this->assertEquals('foobar', $swimlane['name']);
}
- public function testRenameDefaultSwimlane()
+ public function testUpdateDefaultSwimlane()
{
$p = new Project($this->container);
$s = new Swimlane($this->container);
@@ -92,7 +92,7 @@ class SwimlaneTest extends Base
$s = new Swimlane($this->container);
$this->assertEquals(1, $p->create(array('name' => 'UnitTest')));
- $this->assertEquals(1, $s->create(1, 'Swimlane #1'));
+ $this->assertEquals(1, $s->create(array('project_id' => 1, 'name' => 'Swimlane #1')));
$swimlane = $s->getById(1);
$this->assertNotEmpty($swimlane);
@@ -110,7 +110,7 @@ class SwimlaneTest extends Base
$this->assertEquals(1, $s->getLastPosition(1));
// Create a new swimlane
- $this->assertEquals(2, $s->create(1, 'Swimlane #2'));
+ $this->assertEquals(2, $s->create(array('project_id' => 1, 'name' => 'Swimlane #2')));
$swimlane = $s->getById(2);
$this->assertNotEmpty($swimlane);
@@ -134,7 +134,7 @@ class SwimlaneTest extends Base
$tf = new TaskFinder($this->container);
$this->assertEquals(1, $p->create(array('name' => 'UnitTest')));
- $this->assertEquals(1, $s->create(1, 'Swimlane #1'));
+ $this->assertEquals(1, $s->create(array('project_id' => 1, 'name' => 'Swimlane #1')));
$this->assertEquals(1, $tc->create(array('title' => 'test', 'project_id' => 1, 'swimlane_id' => 1)));
$task = $tf->getbyId(1);
@@ -156,9 +156,9 @@ class SwimlaneTest extends Base
$s = new Swimlane($this->container);
$this->assertEquals(1, $p->create(array('name' => 'UnitTest')));
- $this->assertEquals(1, $s->create(1, 'Swimlane #1'));
- $this->assertEquals(2, $s->create(1, 'Swimlane #2'));
- $this->assertEquals(3, $s->create(1, 'Swimlane #3'));
+ $this->assertEquals(1, $s->create(array('project_id' => 1, 'name' => 'Swimlane #1')));
+ $this->assertEquals(2, $s->create(array('project_id' => 1, 'name' => 'Swimlane #2')));
+ $this->assertEquals(3, $s->create(array('project_id' => 1, 'name' => 'Swimlane #3')));
$swimlane = $s->getById(1);
$this->assertNotEmpty($swimlane);
@@ -216,9 +216,9 @@ class SwimlaneTest extends Base
$s = new Swimlane($this->container);
$this->assertEquals(1, $p->create(array('name' => 'UnitTest')));
- $this->assertEquals(1, $s->create(1, 'Swimlane #1'));
- $this->assertEquals(2, $s->create(1, 'Swimlane #2'));
- $this->assertEquals(3, $s->create(1, 'Swimlane #3'));
+ $this->assertEquals(1, $s->create(array('project_id' => 1, 'name' => 'Swimlane #1')));
+ $this->assertEquals(2, $s->create(array('project_id' => 1, 'name' => 'Swimlane #2')));
+ $this->assertEquals(3, $s->create(array('project_id' => 1, 'name' => 'Swimlane #3')));
$swimlane = $s->getById(1);
$this->assertNotEmpty($swimlane);
@@ -299,9 +299,9 @@ class SwimlaneTest extends Base
$s = new Swimlane($this->container);
$this->assertEquals(1, $p->create(array('name' => 'UnitTest')));
- $this->assertEquals(1, $s->create(1, 'Swimlane #1'));
- $this->assertEquals(2, $s->create(1, 'Swimlane #2'));
- $this->assertEquals(3, $s->create(1, 'Swimlane #3'));
+ $this->assertEquals(1, $s->create(array('project_id' => 1, 'name' => 'Swimlane #1')));
+ $this->assertEquals(2, $s->create(array('project_id' => 1, 'name' => 'Swimlane #2')));
+ $this->assertEquals(3, $s->create(array('project_id' => 1, 'name' => 'Swimlane #3')));
$swimlane = $s->getById(1);
$this->assertNotEmpty($swimlane);
@@ -383,9 +383,9 @@ class SwimlaneTest extends Base
$this->assertEquals(1, $p->create(array('name' => 'P1')));
$this->assertEquals(2, $p->create(array('name' => 'P2')));
- $this->assertEquals(1, $s->create(1, 'S1'));
- $this->assertEquals(2, $s->create(1, 'S2'));
- $this->assertEquals(3, $s->create(1, 'S3'));
+ $this->assertEquals(1, $s->create(array('project_id' => 1, 'name' => 'S1')));
+ $this->assertEquals(2, $s->create(array('project_id' => 1, 'name' => 'S2')));
+ $this->assertEquals(3, $s->create(array('project_id' => 1, 'name' => 'S3')));
$default_swimlane1 = $s->getDefault(1);
$default_swimlane1['default_swimlane'] = 'New Default';
diff --git a/tests/units/TaskCreationTest.php b/tests/units/Model/TaskCreationTest.php
index a77778d6..f292c7f1 100644
--- a/tests/units/TaskCreationTest.php
+++ b/tests/units/Model/TaskCreationTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Model\Config;
use Model\Task;
diff --git a/tests/units/TaskDuplicationTest.php b/tests/units/Model/TaskDuplicationTest.php
index e87fe9cc..56718841 100644
--- a/tests/units/TaskDuplicationTest.php
+++ b/tests/units/Model/TaskDuplicationTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Model\Task;
use Model\TaskCreation;
@@ -225,8 +225,8 @@ class TaskDuplicationTest extends Base
$this->assertEquals(1, $p->create(array('name' => 'test1')));
$this->assertEquals(2, $p->create(array('name' => 'test2')));
- $this->assertNotFalse($s->create(1, 'Swimlane #1'));
- $this->assertNotFalse($s->create(2, 'Swimlane #1'));
+ $this->assertNotFalse($s->create(array('project_id' => 1, 'name' => 'Swimlane #1')));
+ $this->assertNotFalse($s->create(array('project_id' => 2, 'name' => 'Swimlane #1')));
// We create a task
$this->assertEquals(1, $tc->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 2, 'swimlane_id' => 1)));
@@ -258,8 +258,8 @@ class TaskDuplicationTest extends Base
$this->assertEquals(1, $p->create(array('name' => 'test1')));
$this->assertEquals(2, $p->create(array('name' => 'test2')));
- $this->assertNotFalse($s->create(1, 'Swimlane #1'));
- $this->assertNotFalse($s->create(2, 'Swimlane #2'));
+ $this->assertNotFalse($s->create(array('project_id' => 1, 'name' => 'Swimlane #1')));
+ $this->assertNotFalse($s->create(array('project_id' => 2, 'name' => 'Swimlane #2')));
// We create a task
$this->assertEquals(1, $tc->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 2, 'swimlane_id' => 1)));
@@ -291,9 +291,9 @@ class TaskDuplicationTest extends Base
$this->assertEquals(1, $p->create(array('name' => 'test1')));
$this->assertEquals(2, $p->create(array('name' => 'test2')));
- $this->assertNotFalse($s->create(1, 'Swimlane #1'));
- $this->assertNotFalse($s->create(2, 'Swimlane #1'));
- $this->assertNotFalse($s->create(2, 'Swimlane #2'));
+ $this->assertNotFalse($s->create(array('project_id' => 1, 'name' => 'Swimlane #1')));
+ $this->assertNotFalse($s->create(array('project_id' => 2, 'name' => 'Swimlane #1')));
+ $this->assertNotFalse($s->create(array('project_id' => 2, 'name' => 'Swimlane #2')));
// We create a task
$this->assertEquals(1, $tc->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 2, 'swimlane_id' => 1)));
@@ -574,8 +574,8 @@ class TaskDuplicationTest extends Base
$this->assertEquals(1, $p->create(array('name' => 'test1')));
$this->assertEquals(2, $p->create(array('name' => 'test2')));
- $this->assertNotFalse($s->create(1, 'Swimlane #1'));
- $this->assertNotFalse($s->create(2, 'Swimlane #1'));
+ $this->assertNotFalse($s->create(array('project_id' => 1, 'name' => 'Swimlane #1')));
+ $this->assertNotFalse($s->create(array('project_id' => 2, 'name' => 'Swimlane #1')));
// We create a task
$this->assertEquals(1, $tc->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 2, 'swimlane_id' => 1)));
@@ -607,8 +607,8 @@ class TaskDuplicationTest extends Base
$this->assertEquals(1, $p->create(array('name' => 'test1')));
$this->assertEquals(2, $p->create(array('name' => 'test2')));
- $this->assertNotFalse($s->create(1, 'Swimlane #1'));
- $this->assertNotFalse($s->create(2, 'Swimlane #2'));
+ $this->assertNotFalse($s->create(array('project_id' => 1, 'name' => 'Swimlane #1')));
+ $this->assertNotFalse($s->create(array('project_id' => 2, 'name' => 'Swimlane #2')));
// We create a task
$this->assertEquals(1, $tc->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 2, 'swimlane_id' => 1)));
diff --git a/tests/units/TaskExportTest.php b/tests/units/Model/TaskExportTest.php
index 3892f2bd..52996369 100644
--- a/tests/units/TaskExportTest.php
+++ b/tests/units/Model/TaskExportTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Model\Task;
use Model\TaskCreation;
@@ -22,8 +22,8 @@ class TaskExportTest extends Base
$this->assertEquals(1, $p->create(array('name' => 'Export Project')));
- $this->assertEquals(1, $s->create(1, 'S1'));
- $this->assertEquals(2, $s->create(1, 'S2'));
+ $this->assertEquals(1, $s->create(array('project_id' => 1, 'name' => 'S1')));
+ $this->assertEquals(2, $s->create(array('project_id' => 1, 'name' => 'S2')));
$this->assertNotFalse($c->create(array('name' => 'Category #1', 'project_id' => 1)));
$this->assertNotFalse($c->create(array('name' => 'Category #2', 'project_id' => 1)));
diff --git a/tests/units/TaskFilterTest.php b/tests/units/Model/TaskFilterTest.php
index 1ae1e5b8..1987265d 100644
--- a/tests/units/TaskFilterTest.php
+++ b/tests/units/Model/TaskFilterTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Model\Project;
use Model\User;
@@ -335,8 +335,8 @@ class TaskFilterTest extends Base
$s = new Swimlane($this->container);
$this->assertEquals(1, $p->create(array('name' => 'My project A')));
- $this->assertEquals(1, $s->create(1, 'Version 1.1'));
- $this->assertEquals(2, $s->create(1, 'Version 1.2'));
+ $this->assertEquals(1, $s->create(array('project_id' => 1, 'name' => 'Version 1.1')));
+ $this->assertEquals(2, $s->create(array('project_id' => 1, 'name' => 'Version 1.2')));
$this->assertNotFalse($tc->create(array('project_id' => 1, 'title' => 'task1', 'swimlane_id' => 1)));
$this->assertNotFalse($tc->create(array('project_id' => 1, 'title' => 'task2', 'swimlane_id' => 2)));
$this->assertNotFalse($tc->create(array('project_id' => 1, 'title' => 'task3', 'swimlane_id' => 0)));
diff --git a/tests/units/TaskFinderTest.php b/tests/units/Model/TaskFinderTest.php
index 96a3809b..da0db7a7 100644
--- a/tests/units/TaskFinderTest.php
+++ b/tests/units/Model/TaskFinderTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Model\Task;
use Model\TaskCreation;
diff --git a/tests/units/TaskLinkTest.php b/tests/units/Model/TaskLinkTest.php
index e213e25a..1e79ea74 100644
--- a/tests/units/TaskLinkTest.php
+++ b/tests/units/Model/TaskLinkTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Model\Link;
use Model\TaskLink;
diff --git a/tests/units/TaskModificationTest.php b/tests/units/Model/TaskModificationTest.php
index 4dd89c5e..f85f1c10 100644
--- a/tests/units/TaskModificationTest.php
+++ b/tests/units/Model/TaskModificationTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Model\Task;
use Model\TaskCreation;
diff --git a/tests/units/TaskMovedDateSubscriberTest.php b/tests/units/Model/TaskMovedDateSubscriberTest.php
index e0364918..6e3fb3bd 100644
--- a/tests/units/TaskMovedDateSubscriberTest.php
+++ b/tests/units/Model/TaskMovedDateSubscriberTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Model\TaskPosition;
use Model\TaskCreation;
@@ -54,8 +54,8 @@ class TaskMovedDateSubscriberTest extends Base
$now = time();
$this->assertEquals(1, $p->create(array('name' => 'Project #1')));
- $this->assertEquals(1, $s->create(1, 'S1'));
- $this->assertEquals(2, $s->create(1, 'S2'));
+ $this->assertEquals(1, $s->create(array('project_id' => 1, 'name' => 'S1')));
+ $this->assertEquals(2, $s->create(array('project_id' => 1, 'name' => 'S2')));
$this->assertEquals(1, $tc->create(array('title' => 'Task #1', 'project_id' => 1)));
$task = $tf->getById(1);
diff --git a/tests/units/TaskPermissionTest.php b/tests/units/Model/TaskPermissionTest.php
index 0bf68ce3..af413a76 100644
--- a/tests/units/TaskPermissionTest.php
+++ b/tests/units/Model/TaskPermissionTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Model\Task;
use Model\TaskCreation;
diff --git a/tests/units/TaskPositionTest.php b/tests/units/Model/TaskPositionTest.php
index 83436683..86814c03 100644
--- a/tests/units/TaskPositionTest.php
+++ b/tests/units/Model/TaskPositionTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Model\Task;
use Model\Board;
@@ -425,7 +425,7 @@ class TaskPositionTest extends Base
$s = new Swimlane($this->container);
$this->assertEquals(1, $p->create(array('name' => 'Project #1')));
- $this->assertEquals(1, $s->create(1, 'test 1'));
+ $this->assertEquals(1, $s->create(array('project_id' => 1, 'name' => 'test 1')));
$this->assertEquals(1, $tc->create(array('title' => 'Task #1', 'project_id' => 1, 'column_id' => 1)));
$this->assertEquals(2, $tc->create(array('title' => 'Task #2', 'project_id' => 1, 'column_id' => 1)));
$this->assertEquals(3, $tc->create(array('title' => 'Task #3', 'project_id' => 1, 'column_id' => 1)));
@@ -532,7 +532,7 @@ class TaskPositionTest extends Base
$s = new Swimlane($this->container);
$this->assertEquals(1, $p->create(array('name' => 'Project #1')));
- $this->assertEquals(1, $s->create(1, 'test 1'));
+ $this->assertEquals(1, $s->create(array('project_id' => 1, 'name' => 'test 1')));
$this->assertEquals(1, $tc->create(array('title' => 'Task #1', 'project_id' => 1, 'column_id' => 1)));
$this->assertEquals(2, $tc->create(array('title' => 'Task #2', 'project_id' => 1, 'column_id' => 2)));
diff --git a/tests/units/TaskStatusTest.php b/tests/units/Model/TaskStatusTest.php
index c1f79142..e5172c4d 100644
--- a/tests/units/TaskStatusTest.php
+++ b/tests/units/Model/TaskStatusTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Model\Subtask;
use Model\Task;
diff --git a/tests/units/TaskTest.php b/tests/units/Model/TaskTest.php
index d9c0fa4f..ebafa612 100644
--- a/tests/units/TaskTest.php
+++ b/tests/units/Model/TaskTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Model\Task;
use Model\TaskCreation;
diff --git a/tests/units/TimetableTest.php b/tests/units/Model/TimetableTest.php
index 9c40dce1..887e1787 100644
--- a/tests/units/TimetableTest.php
+++ b/tests/units/Model/TimetableTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Model\User;
use Model\Timetable;
diff --git a/tests/units/UserSessionTest.php b/tests/units/Model/UserSessionTest.php
index 6a831183..31072e6a 100644
--- a/tests/units/UserSessionTest.php
+++ b/tests/units/Model/UserSessionTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Core\Session;
use Model\UserSession;
diff --git a/tests/units/UserTest.php b/tests/units/Model/UserTest.php
index f63ed3de..5b7edfb9 100644
--- a/tests/units/UserTest.php
+++ b/tests/units/Model/UserTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Model\User;
use Model\Subtask;
diff --git a/tests/units/WebhookTest.php b/tests/units/Model/WebhookTest.php
index 946d744c..8ed630e4 100644
--- a/tests/units/WebhookTest.php
+++ b/tests/units/Model/WebhookTest.php
@@ -1,6 +1,6 @@
<?php
-require_once __DIR__.'/Base.php';
+require_once __DIR__.'/../Base.php';
use Model\Config;
use Model\Task;